chore: optimize reference gallery, add typography plugin, fix SEO and map animations
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 37s
Build & Deploy / 🧪 QA (push) Failing after 1m31s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s

This commit is contained in:
2026-05-13 22:14:47 +02:00
parent 9c60f6d586
commit 84f035a9fd
34 changed files with 913 additions and 162 deletions

View File

@@ -203,6 +203,7 @@ jobs:
build-args: |
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
NEXT_PUBLIC_APP_VERSION=${{ needs.prepare.outputs.image_tag }}
UMAMI_WEBSITE_ID=${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
UMAMI_API_ENDPOINT=${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
NPM_TOKEN=${{ steps.auth.outputs.working_token }}

View File

@@ -146,44 +146,42 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
{/* Main Content Area */}
<Container className="py-16 md:py-24">
<div className="max-w-4xl mx-auto">
{/* Excerpt/Lead paragraph if available */}
{pageData.frontmatter.excerpt && (
<div className="mb-16">
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
{pageData.frontmatter.excerpt}
</p>
</div>
)}
{/* Main content */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
<MDXRemote source={pageData.content} components={mdxComponents} />
{/* Excerpt/Lead paragraph if available */}
{pageData.frontmatter.excerpt && (
<div className="mb-16">
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
{pageData.frontmatter.excerpt}
</p>
</div>
)}
{/* Support Section */}
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group animate-slight-fade-in-from-bottom">
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
<div className="relative z-10 max-w-2xl">
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('needHelp')}</h3>
<p className="text-lg text-white/70 mb-8">{t('supportTeamAvailable')}</p>
<TrackedLink
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
className={getButtonClasses('accent', 'lg')}
eventProperties={{
location: 'generic_page_support_cta',
page_slug: slug,
}}
>
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
{t('contactUs')}
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
&rarr;
</span>
{/* Main content */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
<MDXRemote source={pageData.content} components={mdxComponents} />
</div>
{/* Support Section */}
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group animate-slight-fade-in-from-bottom">
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
<div className="relative z-10 max-w-2xl">
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('needHelp')}</h3>
<p className="text-lg text-white/70 mb-8">{t('supportTeamAvailable')}</p>
<TrackedLink
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
className={getButtonClasses('accent', 'lg')}
eventProperties={{
location: 'generic_page_support_cta',
page_slug: slug,
}}
>
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
{t('contactUs')}
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
&rarr;
</span>
<ButtonOverlay variant="accent" />
</TrackedLink>
</div>
</span>
<ButtonOverlay variant="accent" />
</TrackedLink>
</div>
</div>
</Container>

View File

@@ -91,6 +91,10 @@ export default async function Layout(props: {
label: safeLocale === 'de' ? 'Über uns' : 'About Us',
url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}`
},
{
label: safeLocale === 'de' ? 'Referenzen' : 'References',
url: `/${safeLocale}/referenzen`
},
{
label: safeLocale === 'de' ? 'Karriere' : 'Careers',
url: `/${safeLocale}/${await mapFileSlugToTranslated('karriere', safeLocale)}`
@@ -103,7 +107,7 @@ export default async function Layout(props: {
const companyInfo = {
contactEmail: 'info@e-tib.com',
contactPhone: '+49 15207230518',
contactPhone: '+49 (0) 3561 / 68577 33',
address: 'Gewerbestraße 22\n03172 Guben'
};

View File

@@ -0,0 +1,166 @@
import { notFound } from 'next/navigation';
import { Container, Badge, Heading } from '@/components/ui';
import { setRequestLocale } from 'next-intl/server';
import { Metadata } from 'next';
import { getReferenceBySlug, getAllReferences } from '@/lib/references';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { SITE_URL } from '@/lib/schema';
import TrackedLink from '@/components/analytics/TrackedLink';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import { MapPin, Calendar, Briefcase, ChevronLeft } from 'lucide-react';
import Image from 'next/image';
interface PageProps {
params: Promise<{
locale: string;
slug: string;
}>;
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { locale, slug } = await params;
if (locale !== 'de' && locale !== 'en') return {};
const reference = await getReferenceBySlug(slug, locale);
if (!reference) return {};
return {
title: `${reference.frontmatter.title} | Referenzen | E-TIB Gruppe`,
description: `Details zum Projekt ${reference.frontmatter.title} in ${reference.frontmatter.location}.`,
alternates: {
canonical: `${SITE_URL}/${locale}/referenzen/${slug}`,
},
openGraph: {
title: `${reference.frontmatter.title} | E-TIB`,
description: `Details zum Projekt ${reference.frontmatter.title} in ${reference.frontmatter.location}.`,
url: `${SITE_URL}/${locale}/referenzen/${slug}`,
},
};
}
export async function generateStaticParams({ params }: { params: { locale: string } }) {
const references = await getAllReferences(params.locale);
return references.map((ref) => ({
slug: ref.slug,
}));
}
export default async function ReferenceDetail(props: { params: Promise<{ locale: string; slug: string }> }) {
const { locale, slug } = await props.params;
if (locale !== 'de' && locale !== 'en') {
notFound();
}
setRequestLocale(locale);
const reference = await getReferenceBySlug(slug, locale);
if (!reference) {
notFound();
}
// MDX components mapping can be expanded if we use custom components in reference MDX
const mdxComponents = {
ul: ({ children }: any) => <ul className="space-y-4 my-8 pl-0 list-none">{children}</ul>,
li: ({ children }: any) => (
<li className="flex items-start gap-4 p-4 bg-neutral-50 rounded-2xl border border-neutral-100 shadow-sm">
<div className="w-6 h-6 rounded-full bg-primary/20 flex items-center justify-center shrink-0 mt-0.5">
<div className="w-2.5 h-2.5 rounded-full bg-primary" />
</div>
<span className="text-lg font-medium text-neutral-800">{children}</span>
</li>
),
};
return (
<div className="flex flex-col min-h-screen bg-white">
{/* Hero Section */}
<section className="bg-[#050B14] text-white py-20 md:py-32 relative overflow-hidden">
{reference.frontmatter.featuredImage && (
<div className="absolute inset-0 z-0">
<Image
src={reference.frontmatter.featuredImage}
alt={reference.frontmatter.title}
fill
className="object-cover opacity-25 mix-blend-luminosity scale-105"
priority
/>
<div className="absolute inset-0 bg-gradient-to-t from-[#050B14] via-[#050B14]/60 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-r from-[#050B14] via-transparent to-transparent" />
</div>
)}
<div className="absolute inset-0 opacity-20 z-0">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/30 via-transparent to-transparent" />
</div>
<Container className="relative z-10">
<div className="mb-12">
<TrackedLink
href={`/${locale}/referenzen`}
className="inline-flex items-center gap-2 text-white/60 hover:text-white transition-colors"
eventProperties={{ location: 'reference_back_btn' }}
>
<ChevronLeft className="w-5 h-5" />
Zurück zur Übersicht
</TrackedLink>
</div>
<div className="max-w-4xl">
<Badge variant="accent" className="mb-4 md:mb-6">
Projektreferenz
</Badge>
<Heading level={1} variant="white" className="mb-8 leading-tight">
{reference.frontmatter.title}
</Heading>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12">
<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" />
<div>
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Ort</p>
<p className="font-semibold text-lg">{reference.frontmatter.location}</p>
</div>
</div>
<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" />
<div>
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Auftraggeber</p>
<p className="font-semibold text-lg">{reference.frontmatter.client}</p>
</div>
</div>
<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" />
<div>
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Zeitraum</p>
<p className="font-semibold text-lg">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p>
</div>
</div>
</div>
</div>
</Container>
</section>
{/* Main Content Area */}
<Container className="py-16 md:py-24">
<div className="max-w-4xl mx-auto">
<h2 className="text-3xl font-bold text-neutral-dark mb-8">Leistungsumfang & Projektbeschreibung</h2>
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary">
<MDXRemote source={reference.content} components={mdxComponents} />
</div>
<div className="mt-20 flex justify-center">
<TrackedLink
href={`/${locale}/referenzen`}
className={getButtonClasses('primary', 'lg')}
eventProperties={{ location: 'reference_bottom_back_btn' }}
>
<span className="relative z-10 flex items-center justify-center gap-2">
Alle Referenzen ansehen
</span>
<ButtonOverlay variant="primary" />
</TrackedLink>
</div>
</div>
</Container>
</div>
);
}

View File

@@ -0,0 +1,172 @@
import { Container, Heading, Badge } from '@/components/ui';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { Metadata } from 'next';
import { getAllReferences } from '@/lib/references';
import TrackedLink from '@/components/analytics/TrackedLink';
import { MapPin, Calendar, Briefcase, ArrowUpRight } from 'lucide-react';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import { SITE_URL } from '@/lib/schema';
import Image from 'next/image';
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
import { defaultLocations } from '@/lib/map-data';
interface PageProps {
params: Promise<{
locale: string;
}>;
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { locale } = await params;
if (locale !== 'de' && locale !== 'en') return {};
return {
title: 'Referenzen | E-TIB Gruppe',
description: 'Erfolgreich abgeschlossene Projekte der E-TIB Gruppe im Bereich Kabeltiefbau, Spülbohrtechnik und Netzinfrastruktur.',
alternates: {
canonical: `${SITE_URL}/${locale}/referenzen`,
languages: {
de: `${SITE_URL}/de/referenzen`,
en: `${SITE_URL}/en/referenzen`,
'x-default': `${SITE_URL}/en/referenzen`,
},
},
};
}
export default async function ReferenzenOverview(props: { params: Promise<{ locale: string }> }) {
const { locale } = await props.params;
setRequestLocale(locale);
const t = await getTranslations('StandardPage');
const references = await getAllReferences(locale);
// Enrich the static map locations with dynamic project data (images, details)
const enrichedLocations = defaultLocations.map(loc => {
if (loc.type === 'project') {
const ref = references.find(r => r.slug === loc.id);
if (ref) {
return {
...loc,
featuredImage: ref.frontmatter.featuredImage,
details: [
ref.frontmatter.client || 'Kunde',
ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear().toString(),
],
};
}
}
return loc;
});
return (
<div className="flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24">
{/* Map Hero Section */}
<InteractiveGermanyMap
isHero={true}
badge="Unsere Referenzen"
title={<>Erfolgreich umgesetzte<br/>Projekte.</>}
description="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}
/>
{/* Main Content Area */}
<Container className="relative z-20 mt-16 md:mt-24">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
{references.map((ref) => (
<TrackedLink
key={ref.slug}
href={`/${locale}/referenzen/${ref.slug}`}
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2"
eventProperties={{
location: 'referenzen_grid',
target_slug: ref.slug,
}}
>
<div className="flex flex-col h-full relative">
{/* Image Section */}
<div className="relative h-64 w-full bg-[#f4f4f5] overflow-hidden shrink-0 border-b border-neutral-100">
{ref.frontmatter.featuredImage ? (
<Image
src={ref.frontmatter.featuredImage}
alt={ref.frontmatter.title}
fill
className="object-cover group-hover:scale-105 transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%] group-hover:grayscale-0"
/>
) : (
<div className="absolute inset-0 bg-[#f4f4f5] flex items-center justify-center">
<Image src="/assets/logo.png" alt="E-TIB Logo" width={80} height={80} className="opacity-20 grayscale" />
</div>
)}
<div className="absolute inset-0 bg-black/5 group-hover:bg-transparent transition-colors duration-500" />
{/* Location Badge */}
<div className="absolute top-4 left-4 z-10">
<span className="bg-white/95 backdrop-blur-md text-neutral-dark px-3 py-1.5 rounded-sm text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 shadow-sm border border-neutral-200">
<MapPin className="w-3 h-3 text-primary" />
{ref.frontmatter.location}
</span>
</div>
</div>
{/* Content Section */}
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 group-hover:text-primary transition-colors line-clamp-3 leading-[1.2]">
{ref.frontmatter.title}
</h3>
{/* Technical Meta Data */}
<div className="grid grid-cols-[1fr_1fr] gap-4 mt-auto border-t border-neutral-100 pt-6 pr-16 md:pr-20 relative z-10">
<div className="min-w-0">
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">Kunde</span>
<span className="flex items-center gap-1.5 text-sm font-semibold text-neutral-700">
<Briefcase className="w-3.5 h-3.5 text-primary shrink-0" />
<span className="truncate">{ref.frontmatter.client}</span>
</span>
</div>
<div className="min-w-0">
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">Zeitraum</span>
<span className="flex items-center gap-1.5 text-sm font-semibold text-neutral-700">
<Calendar className="w-3.5 h-3.5 text-primary shrink-0" />
<span className="truncate">{ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear()}</span>
</span>
</div>
</div>
</div>
{/* Industrial Hover Indicator */}
<div className="absolute right-6 bottom-6 md:right-8 md:bottom-8 w-10 h-10 bg-neutral-50 border border-neutral-200 rounded flex items-center justify-center transform group-hover:bg-primary group-hover:border-primary transition-colors duration-300">
<ArrowUpRight className="w-5 h-5 text-neutral-dark group-hover:text-white transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform duration-300" />
</div>
</div>
</TrackedLink>
))}
</div>
{/* Support Section */}
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
<div className="relative z-10 max-w-2xl">
<h3 className="text-2xl md:text-3xl font-bold mb-4">Ihr nächstes Projekt?</h3>
<p className="text-lg text-white/70 mb-8">Lassen Sie uns gemeinsam herausragende Infrastruktur bauen.</p>
<TrackedLink
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
className={getButtonClasses('accent', 'lg')}
eventProperties={{
location: 'referenzen_support_cta',
}}
>
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
{t('contactUs')}
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
&rarr;
</span>
</span>
<ButtonOverlay variant="accent" />
</TrackedLink>
</div>
</div>
</Container>
</div>
);
}

View File

@@ -1,4 +1,5 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@config "../tailwind.config.cjs";

View File

@@ -36,8 +36,8 @@ export const ContactSection: React.FC<ContactSectionProps> = (props) => {
</div>
<div>
<h4 className="font-bold text-neutral-dark">Telefon</h4>
<p className="text-primary hover:text-primary-dark font-medium transition-colors">
<a href="tel:+4935616892550">+49 3561 6892 550</a>
<p className="text-xl md:text-2xl font-bold font-heading text-neutral-dark hover:text-primary transition-colors">
<a href="tel:+4935616857733">+49 (0) 3561 / 68577 33</a>
</p>
</div>
</div>

View File

@@ -4,77 +4,8 @@ import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { MapPin, Factory, Zap, CheckCircle2 } from 'lucide-react';
import Image from 'next/image';
export interface Location {
id: string;
name: string;
type: 'hq' | 'branch' | 'project';
x: number;
y: number;
description: string;
details?: string[];
}
const defaultLocations: Location[] = [
{
id: 'guben',
name: 'Guben (Hauptsitz)',
type: 'hq',
x: 85,
y: 48,
description: 'E-TIB GmbH Holding & Bohrtechnik GmbH',
details: ['Zentrale Steuerung', 'Kabelleitungstiefbau', 'Maschinenpark'],
},
{
id: 'buelstedt',
name: 'Bülstedt (Ingenieurgesellschaft)',
type: 'branch',
x: 37,
y: 25,
description: 'E-TIB Ingenieurgesellschaft mbH',
details: ['Planung & Projektierung', 'Vermessung', 'Dokumentation'],
},
{
id: 'spreewald',
name: 'Spreewald (Projekt)',
type: 'project',
x: 82,
y: 45,
description: 'Breitbandausbau FTTC',
},
{
id: 'bomlitz',
name: 'Bomlitz (Projekt)',
type: 'project',
x: 45,
y: 30,
description: 'Breitbandausbau FTTH',
},
{
id: 'forst',
name: 'Forst (Projekt)',
type: 'project',
x: 88,
y: 50,
description: 'Neubau Kabeltrasse',
},
{
id: 'eisenhuettenstadt',
name: 'Eisenhüttenstadt (Projekt)',
type: 'project',
x: 85,
y: 40,
description: 'Kabelverbindung U30 - T10',
},
{
id: 'goerne',
name: 'Görne (Projekt)',
type: 'project',
x: 70,
y: 35,
description: 'Einspeisung PV-Anlage',
}
];
import { useRouter } from 'next/navigation';
import { Location } from '@/lib/map-data';
interface Stat {
value: string;
@@ -88,6 +19,7 @@ interface InteractiveGermanyMapProps {
description?: string;
stats?: Stat[];
locations?: Location[];
isHero?: boolean;
}
export function InteractiveGermanyMap({
@@ -98,22 +30,24 @@ export function InteractiveGermanyMap({
{ value: '100', suffix: '%', label: 'Überregionale Reichweite' },
{ value: '2', suffix: '+', label: 'Operative Standorte' },
],
locations = defaultLocations
locations = defaultLocations,
isHero = false
}: InteractiveGermanyMapProps) {
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
const router = useRouter();
const hq = locations.find((l) => l.type === 'hq');
const branch = locations.find((l) => l.type === 'branch');
const projects = locations.filter((l) => l.type === 'project');
return (
<div className="relative w-full max-w-7xl mx-auto py-16 md:py-32 px-4 sm:px-6">
<div className="bg-[#050B14] rounded-[2.5rem] md:rounded-[3.5rem] overflow-hidden relative shadow-2xl border border-white/5">
<div className={isHero ? "relative w-full" : "relative w-full max-w-7xl mx-auto py-16 md:py-24 px-4 sm:px-6 mt-16 md:mt-20"}>
<div className={`bg-[#050B14] ${isHero ? 'pt-32 pb-16 md:pt-40 md:pb-24' : 'rounded-[2.5rem] md:rounded-[3.5rem] shadow-2xl border border-white/5'} overflow-visible relative`}>
{/* Background Effects */}
<div className="absolute inset-0 bg-gradient-to-br from-[#050B14] via-[#0A1322] to-[#050B14]" />
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-primary/5 rounded-full blur-[120px] pointer-events-none translate-x-1/3 -translate-y-1/3" />
<div className={`absolute inset-0 bg-gradient-to-br from-[#050B14] via-[#0A1322] to-[#050B14] ${!isHero && 'rounded-[2.5rem] md:rounded-[3.5rem]'} overflow-hidden`} />
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-primary/5 rounded-full blur-[120px] pointer-events-none translate-x-1/3 -translate-y-1/3 overflow-hidden" />
<div className="relative z-10 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-12 gap-12 items-center min-h-[600px] p-8 md:p-12 lg:p-16">
<div className={`relative z-10 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-12 gap-12 items-center min-h-[600px] ${isHero ? 'max-w-7xl mx-auto px-4 sm:px-6' : 'p-8 md:p-12 lg:p-16'}`}>
{/* Content Left */}
<div className="xl:col-span-5 text-white z-20">
@@ -122,9 +56,15 @@ export function InteractiveGermanyMap({
<span>{badge}</span>
</div>
<h3 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]">
{title}
</h3>
{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]">
{title}
</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]">
{title}
</h3>
)}
<p className="text-white/60 text-lg mb-12 leading-relaxed">
{description}
@@ -199,13 +139,32 @@ export function InteractiveGermanyMap({
const isBranch = loc.type === 'branch';
const isActive = activeLocation?.id === loc.id;
const isNearLeft = loc.x < 50;
const isNearRight = loc.x >= 50;
const isNearTop = loc.y < 55; // 55% threshold to strongly avoid the top header
const tooltipClasses = [
'absolute w-[280px] sm:w-64 bg-white text-neutral-dark p-5 rounded-2xl shadow-2xl z-[9999] pointer-events-none border border-neutral-100',
isNearLeft ? 'left-1/2 sm:left-0 -translate-x-1/2 sm:translate-x-0' : 'left-1/2 sm:left-auto sm:right-0 -translate-x-1/2 sm:translate-x-0',
isNearTop ? 'top-full mt-4' : 'bottom-full mb-4'
].join(' ');
const arrowClasses = [
'absolute border-8 border-transparent drop-shadow-sm hidden sm:block',
isNearLeft ? 'left-6' : 'right-6',
isNearTop ? '-top-4 border-b-white border-t-0' : '-bottom-4 border-t-white border-b-0'
].join(' ');
return (
<div
key={loc.id}
className="absolute transform -translate-x-1/2 -translate-y-1/2 z-20 group cursor-pointer w-16 h-16 flex items-center justify-center"
className={`absolute transform -translate-x-1/2 -translate-y-1/2 group cursor-pointer w-16 h-16 flex items-center justify-center ${isActive ? 'z-[60]' : 'z-20'}`}
style={{ left: `${loc.x}%`, top: `${loc.y}%` }}
onMouseEnter={() => setActiveLocation(loc)}
onMouseLeave={() => setActiveLocation(null)}
onClick={() => {
if (loc.href) router.push(loc.href);
}}
>
{/* Ping Animation for HQ / Branch */}
{(isHQ || isBranch) && (
@@ -222,7 +181,7 @@ export function InteractiveGermanyMap({
} ${
isHQ || isBranch
? 'w-6 h-6 bg-primary text-[#050B14]'
: 'w-3 h-3 bg-white/90 ring-2 ring-primary'
: 'w-4 h-4 bg-white ring-[3px] ring-primary'
}`}
>
{(isHQ || isBranch) && <MapPin className="w-3.5 h-3.5" />}
@@ -232,33 +191,92 @@ export function InteractiveGermanyMap({
<AnimatePresence>
{isActive && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute left-1/2 -translate-x-1/2 bottom-full mb-3 w-56 bg-white text-neutral-dark p-5 rounded-2xl shadow-2xl z-50 pointer-events-none border border-neutral-100"
variants={{
hidden: { opacity: 0, y: isNearTop ? -15 : 15, scale: 0.9 },
visible: {
opacity: 1, y: 0, scale: 1,
transition: {
type: "spring", stiffness: 350, damping: 25,
staggerChildren: 0.05, delayChildren: 0.05
}
},
exit: {
opacity: 0, y: isNearTop ? -10 : 10, scale: 0.95,
transition: { duration: 0.15, ease: "easeIn" }
}
}}
initial="hidden"
animate="visible"
exit="exit"
className={`${tooltipClasses} overflow-hidden p-0`}
>
<div className="flex items-center gap-2 mb-2">
{isHQ ? <Factory className="w-4 h-4 text-primary" /> : <Zap className="w-4 h-4 text-primary" />}
<span className="font-extrabold text-sm leading-tight text-neutral-dark">{loc.name}</span>
</div>
<p className="text-xs text-neutral-500 mt-2 font-medium">{loc.description}</p>
{loc.featuredImage && (
<motion.div
variants={{ hidden: { opacity: 0 }, visible: { opacity: 1 } }}
className="relative w-full h-32 bg-neutral-100 overflow-hidden"
>
<Image
src={loc.featuredImage}
alt={loc.name}
fill
className="object-cover transform transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-80" />
<div className="absolute bottom-3 left-4 flex items-center gap-2">
{isHQ ? <Factory className="w-4 h-4 text-primary drop-shadow" /> : <Zap className="w-4 h-4 text-primary drop-shadow" />}
<span className="font-extrabold text-sm leading-tight text-white drop-shadow-md">{loc.name}</span>
</div>
</motion.div>
)}
<div className={loc.featuredImage ? "p-5 pt-4" : "p-5"}>
{!loc.featuredImage && (
<motion.div
variants={{ hidden: { opacity: 0, x: -5 }, visible: { opacity: 1, x: 0 } }}
className="flex items-center gap-2 mb-2"
>
{isHQ ? <Factory className="w-4 h-4 text-primary" /> : <Zap className="w-4 h-4 text-primary" />}
<span className="font-extrabold text-sm leading-tight text-neutral-dark">{loc.name}</span>
</motion.div>
)}
<motion.p
variants={{ hidden: { opacity: 0, y: 5 }, visible: { opacity: 1, y: 0 } }}
className="text-xs text-neutral-500 font-medium"
>
{loc.description}
</motion.p>
{loc.details && (
<ul className="mt-4 space-y-2">
{loc.details.map((detail, i) => (
<li key={i} className="text-[11px] text-neutral-dark flex items-center gap-2 font-semibold">
<motion.li
key={i}
variants={{ hidden: { opacity: 0, x: -5 }, visible: { opacity: 1, x: 0 } }}
className="text-[11px] text-neutral-dark flex items-center gap-2 font-semibold"
>
<div className="w-4 h-4 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
<CheckCircle2 className="w-3 h-3 text-primary" />
</div>
{detail}
</li>
</motion.li>
))}
</ul>
)}
{/* Arrow Down */}
<div className="absolute left-1/2 -bottom-2 -translate-x-1/2 border-8 border-transparent border-t-white drop-shadow-sm" />
{loc.href && (
<motion.div
variants={{ hidden: { opacity: 0, y: 5 }, visible: { opacity: 1, y: 0 } }}
className="mt-4 pt-3 border-t border-neutral-100 text-xs font-bold text-primary flex items-center gap-1 group/link"
>
Zum Projekt
<span className="text-base leading-none translate-y-[1px] transform transition-transform duration-300 group-hover:translate-x-1.5">&rarr;</span>
</motion.div>
)}
</div>
{/* Arrow Down/Up */}
<div className={arrowClasses} />
</motion.div>
)}
</AnimatePresence>

View File

@@ -122,13 +122,9 @@ export function Footer({ companyInfo }: FooterProps) {
))}
</ul>
{/* Built Info inside Legal box for compact layout */}
<div className="mt-8 pt-4 md:mt-12 md:pt-6 border-t border-white/5 text-white/30 text-xs">
<a href="https://mintel.me" target="_blank" rel="noopener noreferrer" className="hover:text-white/60 transition-colors flex items-center gap-2">
<span>Designed & Engineered by Mintel</span>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17l9.2-9.2M17 17V7H7"/></svg>
</a>
</div>
{/* Built Info */}
{/* Legal Info removed from here */}
</div>
</div>
@@ -136,7 +132,31 @@ export function Footer({ companyInfo }: FooterProps) {
<div className="container relative z-10">
<div className="flex flex-col md:flex-row justify-between items-center gap-4 md:gap-6 py-4 px-5 md:py-6 md:px-8 rounded-2xl bg-white/[0.02] border border-white/5 backdrop-blur-md text-center md:text-left">
<p className="text-white/50 text-sm font-medium">&copy; {new Date().getFullYear()} E-TIB GmbH. {locale === 'de' ? 'Alle Rechte vorbehalten.' : 'All rights reserved.'}</p>
<div className="flex flex-col md:flex-row items-center gap-4 md:gap-6 h-full">
<p className="text-white/50 text-xs font-medium m-0">&copy; {new Date().getFullYear()} E-TIB GmbH. {locale === 'de' ? 'Alle Rechte vorbehalten.' : 'All rights reserved.'}</p>
<div className="hidden md:block w-px h-3 bg-white/10" />
<div className="flex items-center gap-3 text-white/30 text-xs">
<a href="https://mintel.me" target="_blank" rel="noopener noreferrer" className="hover:text-white/60 transition-colors flex items-center gap-2">
<span>Designed & Engineered by Mintel</span>
</a>
{process.env.NEXT_PUBLIC_APP_VERSION && (
<>
<span className="w-1 h-1 rounded-full bg-white/20" />
<div className="flex items-center gap-2 opacity-60">
<span className="font-mono text-[10px]">v{process.env.NEXT_PUBLIC_APP_VERSION.replace(/^v/, '')}</span>
{process.env.NEXT_PUBLIC_TARGET && process.env.NEXT_PUBLIC_TARGET !== 'production' && (
<span className="px-1.5 py-0.5 rounded-sm bg-white/10 text-[9px] uppercase tracking-wider">
{process.env.NEXT_PUBLIC_TARGET}
</span>
)}
</div>
</>
)}
</div>
</div>
<div className="flex items-center gap-6">
<LanguageSwitcher isSolidMode={false} />
</div>

View File

@@ -30,8 +30,9 @@ E-TIB GmbH
Gewerbestraße 22
03172 Guben
Telefon: +49 1520 7230518
E-Mail: d.joseph@e-tib.com
Telefon: +49 (0) 3561 / 68577 33
Telefax: +49 (0) 3561 / 68577 35
E-Mail: info@e-tib.com
## 4. Datenerfassung auf dieser Website

View File

@@ -17,8 +17,9 @@ Danny Joseph
## Kontakt
Telefon: +49 1520 7230518
E-Mail: d.joseph@e-tib.com
Telefon: +49 (0) 3561 / 68577 33
Telefax: +49 (0) 3561 / 68577 35
E-Mail: info@e-tib.com
## Registereintrag

View File

@@ -6,12 +6,12 @@ layout: "fullBleed"
---
<HomeHero
badge="Events"
title="Treffen Sie uns\nvor Ort"
description="Kommen Sie mit unseren Experten ins Gespräch. Wir sind auf den wichtigsten Branchenmessen vertreten und freuen uns auf den persönlichen Austausch mit Ihnen."
videoUrl="/assets/videos/web/hero-messe.mp4"
linkText="Zu den Terminen"
linkHref="#termine"
badge="Messen & Events"
title="Treffen Sie uns persönlich"
description="Lernen Sie die E-TIB Gruppe auf den führenden Branchenmessen kennen."
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
linkText="Kontakt aufnehmen"
linkHref="/de/kontakt"
/>
<div id="termine">

View File

@@ -0,0 +1,12 @@
---
title: "Breitbandausbau FTTH, Landkreis Börde"
client: "EKN Breitband GmbH & Co. KG"
date: "2026-01-01"
dateString: "seit 05/2019"
location: "Landkreis Börde"
featuredImage: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-294.jpg"
---
- 109.000 m Leitungstrasse herstellen (Tiefbau/HDD)
- 63 Stück Aufstellung Netzverteiler
- 747 Stück Herstellung Hausanschlüsse

View File

@@ -0,0 +1,13 @@
---
title: "Breitbandausbau FTTH, Planungsregion Bomlitz"
client: "Telekom Deutschland GmbH"
date: "2018-12-31"
dateString: "04/2018 bis 12/2018"
location: "Bomlitz"
featuredImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-100.jpg"
---
- 88.890 m Leitungstrasse herstellen (Tiefbau/HDD)
- 23 Stück Aufstellung Multifunktionsgehäuse
- 52 Stück Aufstellung Netzverteiler
- 571 Stück Herstellung Hausanschlüsse

View File

@@ -0,0 +1,19 @@
---
title: "Kabelverbindung U30 - T10, 15890 Eisenhüttenstadt"
client: "ArcelorMittal Eisenhüttenstadt GmbH"
date: "2019-12-31"
dateString: "07/2019 bis 12/2019"
location: "Eisenhüttenstadt"
featuredImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(24_von_99)_groß2.jpg"
---
- 2.095 m Kabelgraben ausheben und verfüllen
- 180 m Horizontalspülbohrung d 160 + d 50
- 5.700 m Lieferung und Verlegung MS-Kabel 240 mm², 18/30 kV
- 3.000 m Lieferung und Verlegung PE-Schutzrohr 50 x 4,6 mm
- 3.000 m Lieferung und Einblasen LWL-Kabel, 12 Fasern
- 3.000 m Lieferung und Verlegung Steuerkabel, 0,6/1 kV
- 16 Satz Lieferung und Montage MS-Verbindungsmuffe 240 mm², 18/30 kV
- 1 Stück Lieferung und Montage LWL-Verbindungsmuffe, 12 Fasern
- 4 Satz Lieferung und Montage MS-T-Stecker 240 mm², 18/30 kV
- Hauptprüfung / Mantelprüfung / OTDR-Messung

View File

@@ -0,0 +1,16 @@
---
title: "Neubau Kabeltrasse, 03149 Forst"
client: "Mitteldeutsche Netzgesellschaft Strom mbH"
date: "2020-09-30"
dateString: "03/2020 bis 09/2020"
location: "Forst"
featuredImage: "/assets/photos/DSC02156.jpg"
---
- 2984 m Kabelgraben ausheben und verfüllen
- 945 m Horizontalspülbohrung d 160 + d 50
- 3.929 m Verlegung MS-Kabel 240 mm², 12/20 kV
- 3.929 m Verlegung PE-Schutzrohr 50 x 4,6 mm
- 11 Satz Montage MS-Verbindungsmuffe 240 mm², 12/20 kV
- 1 Satz Montage MS-Endverschluss 240 mm², 12/20 kV
- 2.000 m Rückbau Freileitung (inkl. 16 Stück Maste)

View File

@@ -0,0 +1,19 @@
---
title: "Einspeisung PV-Anlage 14728 Görne"
client: "EVH GmbH"
date: "2020-07-31"
dateString: "05/2020 bis 07/2020"
location: "Görne"
featuredImage: "/assets/photos/DSC08744.JPG"
---
- 4.215 m Kabelgraben ausheben und verfüllen
- 1.285 m Horizontalspülbohrung d 140 + d 50
- 5.500 m Verlegung MS-Kabel 300 mm², 12/20 kV
- 4.500 m Verlegung MS-Kabel 300 mm², 12/20 kV mittels Kabelpflug
- 10.000 m Verlegung PE-Schutzrohr 50 x 4,6 mm
- 10.000 m Einblasen LWL-Kabel, 12 Fasern
- 3 Stück Lieferung und Montage LWL-Verbindungsmuffe, 12 Fasern
- 16 Satz Montage MS-Verbindungsmuffe 300 mm², 12/20 kV
- 4 Satz Montage MS-T-Stecker 300 mm², 12/20 kV
- Mantelprüfung, VLF-Prüfung, OTDR-Messung

View File

@@ -0,0 +1,19 @@
---
title: "Breitbandausbau FTTC, Planungsregion Spreewald"
client: "Telekom Deutschland GmbH"
date: "2018-01-31"
dateString: "09/2017 bis 01/2018"
location: "Spreewald"
featuredImage: "/assets/photos/DSC01129.JPG"
---
- 4.006 m Kabelgraben ausheben und verfüllen
- 4.006 m Verlegung PE-Schutzrohr 50 x 4,6 mm
- 13.008 m Horizontalspülbohrung D 50
- 94.951 m Einblasen von Kabel/Rohren unbelegt
- 42 Stück Stellen von Multifunktionsgehäusen
- Aufbau von Energieanschlusssäulen
- Aufbau elektrischer Anlagen 230V
- Koordination mit Energieversorgungsunternehmen
- Inbetriebnahme der elektrischen Anlagen
- Oberflächen- und Tiefbauarbeiten für Multifunktionsgehäuse

View File

@@ -31,8 +31,9 @@ Gewerbestraße 22
03172 Guben
Germany
Phone: +49 1520 7230518
Email: d.joseph@e-tib.com
Phone: +49 (0) 3561 / 68577 33
Fax: +49 (0) 3561 / 68577 35
Email: info@e-tib.com
## 4. Data Collection on this Website

View File

@@ -18,8 +18,9 @@ Danny Joseph
## Contact
Phone: +49 1520 7230518
Email: d.joseph@e-tib.com
Phone: +49 (0) 3561 / 68577 33
Fax: +49 (0) 3561 / 68577 35
Email: info@e-tib.com
## Register Entry

View File

@@ -0,0 +1,12 @@
---
title: "Breitbandausbau FTTH, Landkreis Börde"
client: "EKN Breitband GmbH & Co. KG"
date: "2026-01-01"
dateString: "seit 05/2019"
location: "Landkreis Börde"
featuredImage: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-294.jpg"
---
- 109.000 m Leitungstrasse herstellen (Tiefbau/HDD)
- 63 Stück Aufstellung Netzverteiler
- 747 Stück Herstellung Hausanschlüsse

View File

@@ -0,0 +1,13 @@
---
title: "Breitbandausbau FTTH, Planungsregion Bomlitz"
client: "Telekom Deutschland GmbH"
date: "2018-12-31"
dateString: "04/2018 bis 12/2018"
location: "Bomlitz"
featuredImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-100.jpg"
---
- 88.890 m Leitungstrasse herstellen (Tiefbau/HDD)
- 23 Stück Aufstellung Multifunktionsgehäuse
- 52 Stück Aufstellung Netzverteiler
- 571 Stück Herstellung Hausanschlüsse

View File

@@ -0,0 +1,19 @@
---
title: "Kabelverbindung U30 - T10, 15890 Eisenhüttenstadt"
client: "ArcelorMittal Eisenhüttenstadt GmbH"
date: "2019-12-31"
dateString: "07/2019 bis 12/2019"
location: "Eisenhüttenstadt"
featuredImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(24_von_99)_groß2.jpg"
---
- 2.095 m Kabelgraben ausheben und verfüllen
- 180 m Horizontalspülbohrung d 160 + d 50
- 5.700 m Lieferung und Verlegung MS-Kabel 240 mm², 18/30 kV
- 3.000 m Lieferung und Verlegung PE-Schutzrohr 50 x 4,6 mm
- 3.000 m Lieferung und Einblasen LWL-Kabel, 12 Fasern
- 3.000 m Lieferung und Verlegung Steuerkabel, 0,6/1 kV
- 16 Satz Lieferung und Montage MS-Verbindungsmuffe 240 mm², 18/30 kV
- 1 Stück Lieferung und Montage LWL-Verbindungsmuffe, 12 Fasern
- 4 Satz Lieferung und Montage MS-T-Stecker 240 mm², 18/30 kV
- Hauptprüfung / Mantelprüfung / OTDR-Messung

View File

@@ -0,0 +1,16 @@
---
title: "Neubau Kabeltrasse, 03149 Forst"
client: "Mitteldeutsche Netzgesellschaft Strom mbH"
date: "2020-09-30"
dateString: "03/2020 bis 09/2020"
location: "Forst"
featuredImage: "/assets/photos/DSC02156.jpg"
---
- 2984 m Kabelgraben ausheben und verfüllen
- 945 m Horizontalspülbohrung d 160 + d 50
- 3.929 m Verlegung MS-Kabel 240 mm², 12/20 kV
- 3.929 m Verlegung PE-Schutzrohr 50 x 4,6 mm
- 11 Satz Montage MS-Verbindungsmuffe 240 mm², 12/20 kV
- 1 Satz Montage MS-Endverschluss 240 mm², 12/20 kV
- 2.000 m Rückbau Freileitung (inkl. 16 Stück Maste)

View File

@@ -0,0 +1,19 @@
---
title: "Einspeisung PV-Anlage 14728 Görne"
client: "EVH GmbH"
date: "2020-07-31"
dateString: "05/2020 bis 07/2020"
location: "Görne"
featuredImage: "/assets/photos/DSC08744.JPG"
---
- 4.215 m Kabelgraben ausheben und verfüllen
- 1.285 m Horizontalspülbohrung d 140 + d 50
- 5.500 m Verlegung MS-Kabel 300 mm², 12/20 kV
- 4.500 m Verlegung MS-Kabel 300 mm², 12/20 kV mittels Kabelpflug
- 10.000 m Verlegung PE-Schutzrohr 50 x 4,6 mm
- 10.000 m Einblasen LWL-Kabel, 12 Fasern
- 3 Stück Lieferung und Montage LWL-Verbindungsmuffe, 12 Fasern
- 16 Satz Montage MS-Verbindungsmuffe 300 mm², 12/20 kV
- 4 Satz Montage MS-T-Stecker 300 mm², 12/20 kV
- Mantelprüfung, VLF-Prüfung, OTDR-Messung

View File

@@ -0,0 +1,19 @@
---
title: "Breitbandausbau FTTC, Planungsregion Spreewald"
client: "Telekom Deutschland GmbH"
date: "2018-01-31"
dateString: "09/2017 bis 01/2018"
location: "Spreewald"
featuredImage: "/assets/photos/DSC01129.JPG"
---
- 4.006 m Kabelgraben ausheben und verfüllen
- 4.006 m Verlegung PE-Schutzrohr 50 x 4,6 mm
- 13.008 m Horizontalspülbohrung D 50
- 94.951 m Einblasen von Kabel/Rohren unbelegt
- 42 Stück Stellen von Multifunktionsgehäusen
- Aufbau von Energieanschlusssäulen
- Aufbau elektrischer Anlagen 230V
- Koordination mit Energieversorgungsunternehmen
- Inbetriebnahme der elektrischen Anlagen
- Oberflächen- und Tiefbauarbeiten für Multifunktionsgehäuse

86
lib/map-data.ts Normal file
View File

@@ -0,0 +1,86 @@
export interface Location {
id: string;
name: string;
type: 'hq' | 'branch' | 'project';
x: number;
y: number;
description: string;
details?: string[];
href?: string;
featuredImage?: string;
}
export const defaultLocations: Location[] = [
{
id: 'guben',
name: 'Guben (Hauptsitz)',
type: 'hq',
x: 85,
y: 48,
description: 'E-TIB GmbH Holding & Bohrtechnik GmbH',
details: ['Zentrale Steuerung', 'Kabelleitungstiefbau', 'Maschinenpark'],
},
{
id: 'buelstedt',
name: 'Bülstedt (Ingenieurgesellschaft)',
type: 'branch',
x: 37,
y: 25,
description: 'E-TIB Ingenieurgesellschaft mbH',
details: ['Planung & Projektierung', 'Vermessung', 'Dokumentation'],
},
{
id: 'spreewald',
name: 'Spreewald (LWL-Trasse)',
type: 'project',
x: 81,
y: 42,
description: '28km LWL-Trasse inkl. Spülbohrung',
href: '/referenzen/spreewald',
},
{
id: 'bomlitz',
name: 'Bomlitz (110kV-Trasse)',
type: 'project',
x: 43,
y: 30,
description: 'Neuverlegung einer 110kV Hochspannungstrasse',
href: '/referenzen/bomlitz',
},
{
id: 'forst',
name: 'Forst (Verteilerknoten)',
type: 'project',
x: 87,
y: 52,
description: 'Ausbau lokaler Glasfasernetze',
href: '/referenzen/forst',
},
{
id: 'eisenhuettenstadt',
name: 'Eisenhüttenstadt (Industriegebiet)',
type: 'project',
x: 86,
y: 44,
description: 'Erschließung Industriegebiet Nord',
href: '/referenzen/eisenhuettenstadt',
},
{
id: 'goerne',
name: 'Görne (Breitbandausbau)',
type: 'project',
x: 65,
y: 35,
description: 'Glasfaser-Rückgrat für 3 Gemeinden',
href: '/referenzen/goerne',
},
{
id: 'boerde',
name: 'Börde (Windpark-Anschluss)',
type: 'project',
x: 55,
y: 38,
description: 'Kabeltrasse für erneuerbare Energien',
href: '/referenzen/boerde',
}
];

52
lib/references.ts Normal file
View File

@@ -0,0 +1,52 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
export interface ReferenceFrontmatter {
title: string;
client: string;
date: string;
dateString?: string;
featuredImage?: string | null;
location: string;
}
export interface ReferenceData {
slug: string;
frontmatter: ReferenceFrontmatter;
content: string;
}
export async function getReferenceBySlug(slug: string, locale: string): Promise<ReferenceData | null> {
const localeDir = path.join(process.cwd(), 'content', locale, 'referenzen');
const filePath = path.join(localeDir, `${slug}.mdx`);
if (!fs.existsSync(filePath)) return null;
const source = fs.readFileSync(filePath, 'utf8');
const { content, data } = matter(source);
return {
slug,
frontmatter: data as ReferenceFrontmatter,
content,
};
}
export async function getAllReferences(locale: string): Promise<ReferenceData[]> {
const localeDir = path.join(process.cwd(), 'content', locale, 'referenzen');
if (!fs.existsSync(localeDir)) return [];
const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.mdx'));
const references = await Promise.all(
files.map(async f => {
const slug = f.replace('.mdx', '');
return await getReferenceBySlug(slug, locale);
})
);
return references
.filter((p): p is ReferenceData => p !== null)
.sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime());
}

View File

@@ -17,9 +17,9 @@ export const getOrganizationSchema = () => ({
sameAs: ['https://www.instagram.com/me.and.eloise/'],
contactPoint: {
'@type': 'ContactPoint' as const,
telephone: '+49-15207230518',
telephone: '+4935616857733',
contactType: 'customer service' as const,
email: 'd.joseph@e-tib.com',
email: 'info@e-tib.com',
availableLanguage: ['German', 'English'],
},
});
@@ -43,8 +43,8 @@ export const getLocalBusinessSchema = () => ({
logo: LOGO_URL,
image: LOGO_URL,
description: 'Ihr Partner für Kabelleitungstiefbau, Horizontalspülbohrungen, Planung und Vermessung in Guben und überregional.',
telephone: '+49-15207230518',
email: 'd.joseph@e-tib.com',
telephone: '+4935616857733',
email: 'info@e-tib.com',
address: {
'@type': 'PostalAddress' as const,
streetAddress: 'Gewerbestraße 22',

View File

@@ -96,7 +96,7 @@
"howToReachUs": "So erreichen Sie uns",
"office": "Hauptsitz Guben",
"address": "Gewerbestraße 22\n03172 Guben\nDeutschland",
"phone": "+49 15207230518",
"phone": "+49 (0) 3561 / 68577 33",
"email": "d.joseph@e-tib.com"
},
"hours": {

View File

@@ -96,7 +96,7 @@
"howToReachUs": "How to reach us",
"office": "Headquarters Guben",
"address": "Gewerbestraße 22\n03172 Guben\nGermany",
"phone": "+49 15207230518",
"phone": "+49 (0) 3561 / 68577 33",
"email": "d.joseph@e-tib.com"
},
"hours": {

View File

@@ -38,6 +38,9 @@ const nextConfig = {
},
},
...(isProd ? { output: 'standalone' } : {}),
env: {
NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION || process.env.npm_package_version || 'dev',
},
// Prevent webpack from restarting when .env files are touched via Docker volume mount
webpack: (config, { dev }) => {
if (dev) {

View File

@@ -61,6 +61,7 @@
"@next/bundle-analyzer": "^16.1.6",
"@tailwindcss/cli": "^4.1.18",
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/typography": "^0.5.19",
"@types/geojson": "^7946.0.16",
"@types/leaflet": "^1.9.21",
"@types/node": "^22.19.3",

29
pnpm-lock.yaml generated
View File

@@ -178,6 +178,9 @@ importers:
'@tailwindcss/postcss':
specifier: ^4.1.18
version: 4.2.2
'@tailwindcss/typography':
specifier: ^0.5.19
version: 0.5.19(tailwindcss@4.2.2)
'@types/geojson':
specifier: ^7946.0.16
version: 7946.0.16
@@ -2928,6 +2931,11 @@ packages:
'@tailwindcss/postcss@4.2.2':
resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==}
'@tailwindcss/typography@0.5.19':
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
'@tootallnate/quickjs-emscripten@0.23.0':
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
@@ -4131,6 +4139,11 @@ packages:
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
engines: {node: '>= 6'}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
cssstyle@5.3.7:
resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==}
engines: {node: '>=20'}
@@ -6634,6 +6647,10 @@ packages:
postcss-media-query-parser@0.2.3:
resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -10800,6 +10817,11 @@ snapshots:
postcss: 8.5.8
tailwindcss: 4.2.2
'@tailwindcss/typography@0.5.19(tailwindcss@4.2.2)':
dependencies:
postcss-selector-parser: 6.0.10
tailwindcss: 4.2.2
'@tootallnate/quickjs-emscripten@0.23.0': {}
'@turbo/darwin-64@2.9.3':
@@ -12133,6 +12155,8 @@ snapshots:
css-what@6.2.2: {}
cssesc@3.0.0: {}
cssstyle@5.3.7:
dependencies:
'@asamuzakjp/css-color': 4.1.2
@@ -15275,6 +15299,11 @@ snapshots:
postcss-media-query-parser@0.2.3: {}
postcss-selector-parser@6.0.10:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss-value-parser@4.2.0: {}
postcss@8.4.31: