feat: integrate standalone standorte pages with real photos and map routing
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 21s
Build & Deploy / 🧪 QA (push) Successful in 1m6s
Build & Deploy / 🏗️ Build (push) Successful in 1m35s
Build & Deploy / 🚀 Deploy (push) Successful in 25s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 41s
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-06-14 10:03:46 +02:00
parent d8634efa9a
commit 17a807c48b
7 changed files with 543 additions and 12 deletions

View File

@@ -92,6 +92,7 @@ export default async function Layout(props: {
url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}`,
children: [
{ label: safeLocale === 'de' ? 'Firma' : 'Company', url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}` },
{ label: safeLocale === 'de' ? 'Standorte' : 'Locations', url: `/${safeLocale}/standorte` },
{ label: safeLocale === 'de' ? 'Unser Team' : 'Our Team', url: `/${safeLocale}/team` },
{ label: safeLocale === 'de' ? 'Zertifikate' : 'Certificates', url: `/${safeLocale}/zertifikate` }
]

View File

@@ -0,0 +1,202 @@
import { Container } from '@/components/ui';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { standorteData, getStandortById } from '@/lib/standorte-data';
import { SITE_URL } from '@/lib/schema';
import Image from 'next/image';
import { MapPin, Phone, Mail, Navigation, CheckCircle2, ArrowUpRight } from 'lucide-react';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import TrackedLink from '@/components/analytics/TrackedLink';
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
interface PageProps {
params: Promise<{
locale: string;
slug: string;
}>;
}
export async function generateStaticParams() {
const locales = ['de', 'en'];
const params: { locale: string; slug: string }[] = [];
for (const locale of locales) {
for (const standort of standorteData) {
params.push({ locale, slug: standort.id });
}
}
return params;
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { locale, slug } = await params;
if (locale !== 'de' && locale !== 'en') return {};
const standort = getStandortById(slug);
if (!standort) return {};
return {
title: `${standort.name} | E-TIB Gruppe`,
description: standort.description[locale],
alternates: {
canonical: `${SITE_URL}/${locale}/standorte/${slug}`,
languages: {
de: `${SITE_URL}/de/standorte/${slug}`,
en: `${SITE_URL}/en/standorte/${slug}`,
'x-default': `${SITE_URL}/en/standorte/${slug}`,
},
},
};
}
export default async function StandortDetail(props: { params: Promise<{ locale: string; slug: string }> }) {
const { locale, slug } = await props.params;
const safeLocale = locale === 'de' ? 'de' : 'en';
setRequestLocale(safeLocale);
const t = await getTranslations('StandardPage');
const standort = getStandortById(slug);
if (!standort) {
notFound();
}
return (
<div className="flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24 pt-32">
<Container>
{/* Header Section */}
<div className="mb-12 md:mb-16">
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-6">
<MapPin className="w-4 h-4" />
<span>{standort.type === 'hq' ? (safeLocale === 'de' ? 'Hauptsitz' : 'Headquarters') : (safeLocale === 'de' ? 'Niederlassung' : 'Branch')}</span>
</div>
<h1 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-neutral-dark text-balance">
{standort.name}
</h1>
<p className="text-xl text-neutral-500 max-w-3xl leading-relaxed">
{standort.description[safeLocale]}
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
{/* Main Content (Images & Description) */}
<div className="lg:col-span-8 space-y-8">
{/* Featured Image */}
<div className="relative aspect-video w-full rounded-3xl overflow-hidden bg-neutral-900 shadow-xl border border-neutral-200">
<Image
src={standort.image}
alt={standort.name}
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent" />
</div>
{/* Key Features */}
<div className="bg-white rounded-3xl p-8 md:p-10 shadow-sm border border-neutral-100">
<h2 className="text-2xl font-bold font-heading mb-6 text-neutral-dark">
{safeLocale === 'de' ? 'Unsere Leistungen vor Ort' : 'Our Local Services'}
</h2>
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{standort.keyFeatures[safeLocale].map((feature, idx) => (
<li key={idx} className="flex items-start gap-3">
<CheckCircle2 className="w-5 h-5 text-primary shrink-0 mt-0.5" />
<span className="text-neutral-700 font-medium">{feature}</span>
</li>
))}
</ul>
</div>
{/* Gallery (if any) */}
{standort.gallery && standort.gallery.length > 0 && (
<div className="grid grid-cols-2 gap-4">
{standort.gallery.map((img, idx) => (
<div key={idx} className="relative aspect-[4/3] rounded-2xl overflow-hidden bg-neutral-100">
<Image src={img} alt={`Gallery ${idx + 1}`} fill className="object-cover" />
</div>
))}
</div>
)}
</div>
{/* Sidebar (Contact & Address) */}
<div className="lg:col-span-4">
<div className="sticky top-32 space-y-6">
{/* Contact Card */}
<div className="bg-[#050B14] text-white rounded-3xl p-8 shadow-2xl relative overflow-hidden group">
<AnimatedGlossyBorder opacity={0.5} className="z-10" />
<div className="absolute top-0 right-0 w-64 h-full bg-primary/5 blur-3xl -translate-y-1/2 translate-x-1/3" />
<div className="relative z-20">
<h3 className="text-xl font-bold font-heading mb-6 flex items-center gap-2">
<Navigation className="w-5 h-5 text-primary" />
{safeLocale === 'de' ? 'Kontakt & Anfahrt' : 'Contact & Directions'}
</h3>
<div className="space-y-6">
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center shrink-0">
<MapPin className="w-5 h-5 text-primary" />
</div>
<div>
<div className="text-xs text-white/50 font-bold uppercase tracking-wider mb-1">
{safeLocale === 'de' ? 'Adresse' : 'Address'}
</div>
<div className="text-white font-medium">
{standort.address.street}<br />
{standort.address.city}
</div>
<TrackedLink
href={`https://maps.google.com/?q=${encodeURIComponent(`${standort.address.street}, ${standort.address.city}`)}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 mt-2 text-primary hover:text-white transition-colors text-sm font-bold"
eventProperties={{ location: 'standort_maps_link', slug: standort.id }}
>
{safeLocale === 'de' ? 'In Google Maps öffnen' : 'Open in Google Maps'}
<ArrowUpRight className="w-3.5 h-3.5" />
</TrackedLink>
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center shrink-0">
<Phone className="w-5 h-5 text-primary" />
</div>
<div>
<div className="text-xs text-white/50 font-bold uppercase tracking-wider mb-1">
{safeLocale === 'de' ? 'Telefon' : 'Phone'}
</div>
<a href={`tel:${standort.contact.phone.replace(/[^0-9+]/g, '')}`} className="text-white font-medium hover:text-primary transition-colors">
{standort.contact.phone}
</a>
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center shrink-0">
<Mail className="w-5 h-5 text-primary" />
</div>
<div>
<div className="text-xs text-white/50 font-bold uppercase tracking-wider mb-1">
Email
</div>
<a href={`mailto:${standort.contact.email}`} className="text-white font-medium hover:text-primary transition-colors">
{standort.contact.email}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Container>
</div>
);
}

View File

@@ -0,0 +1,143 @@
import { Container } from '@/components/ui';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { Metadata } from 'next';
import TrackedLink from '@/components/analytics/TrackedLink';
import { MapPin, Navigation, ArrowUpRight } from 'lucide-react';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import { SITE_URL } from '@/lib/schema';
import Image from 'next/image';
import { standorteData } from '@/lib/standorte-data';
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
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: locale === 'de' ? 'Standorte | E-TIB Gruppe' : 'Locations | E-TIB Group',
description: locale === 'de'
? 'Die operativen Standorte der E-TIB Gruppe: Guben, Kirchheilingen und Bülstedt.'
: 'The operational locations of the E-TIB Group: Guben, Kirchheilingen and Bülstedt.',
alternates: {
canonical: `${SITE_URL}/${locale}/standorte`,
languages: {
de: `${SITE_URL}/de/standorte`,
en: `${SITE_URL}/en/standorte`,
'x-default': `${SITE_URL}/en/standorte`,
},
},
};
}
export default async function StandorteOverview(props: { params: Promise<{ locale: string }> }) {
const { locale } = await props.params;
const safeLocale = locale === 'de' ? 'de' : 'en';
setRequestLocale(safeLocale);
const t = await getTranslations('StandardPage');
return (
<div className="flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24">
{/* Map Hero Section */}
<InteractiveGermanyMap
isHero={true}
badge={safeLocale === 'en' ? 'Our Locations' : 'Unsere Standorte'}
title={safeLocale === 'en' ? 'Nationwide operational for you.' : 'Deutschlandweit für Sie im Einsatz.'}
description={safeLocale === 'en'
? 'From our strategic locations in Guben, Kirchheilingen, and Bülstedt, we control and implement complex infrastructure projects nationwide.'
: 'Von unseren strategischen Standorten in Guben, Kirchheilingen und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.'}
/>
{/* 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">
{standorteData.map((standort) => (
<TrackedLink
key={standort.id}
href={`/${safeLocale}/standorte/${standort.id}`}
eventProperties={{ location: 'standorte_list_card', slug: standort.id }}
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-1 cursor-pointer block"
>
<div className="flex flex-col h-full relative">
{/* Image Section */}
<div className="relative h-64 w-full bg-[#050B14] overflow-hidden shrink-0 border-b border-neutral-100">
<Image
src={standort.image}
alt={standort.name}
fill
className="object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%] group-hover:scale-105 group-hover:grayscale-0"
/>
<div className="absolute inset-0 bg-black/10 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" />
{standort.address.city}
</span>
</div>
</div>
{/* Content Section */}
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
<div className="flex items-start justify-between gap-4 mb-4">
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark transition-colors leading-[1.2] group-hover:text-primary">
{standort.name}
</h3>
<ArrowUpRight className="w-5 h-5 text-neutral-400 group-hover:text-primary transition-colors shrink-0" />
</div>
<p className="text-neutral-500 text-sm leading-relaxed mb-8 line-clamp-3">
{standort.description[safeLocale]}
</p>
{/* Meta Data */}
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10">
<div className="min-w-0">
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">
{safeLocale === 'de' ? 'Adresse' : 'Address'}
</span>
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
<Navigation className="w-3.5 h-3.5 text-primary shrink-0 mt-[2px]" />
<span className="line-clamp-2">{standort.address.street}, {standort.address.city}</span>
</span>
</div>
</div>
</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">{t('nextProjectTitle')}</h3>
<p className="text-lg text-white/70 mb-8">{t('nextProjectDesc')}</p>
<TrackedLink
href={`/${safeLocale}/${safeLocale === 'de' ? 'kontakt' : 'contact'}`}
className={getButtonClasses('accent', 'lg')}
eventProperties={{
location: 'standorte_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

@@ -12,6 +12,8 @@ interface TrackedLinkProps {
className?: string;
children: React.ReactNode;
onClick?: () => void;
target?: string;
rel?: string;
}
/**
@@ -25,6 +27,8 @@ export default function TrackedLink({
className,
children,
onClick,
target,
rel,
}: TrackedLinkProps) {
const { trackEvent } = useAnalytics();
@@ -41,7 +45,7 @@ export default function TrackedLink({
};
return (
<Link href={href} className={className} onClick={handleClick}>
<Link href={href} className={className} onClick={handleClick} target={target} rel={rel}>
{children}
</Link>
);

View File

@@ -5,6 +5,7 @@ import { motion, AnimatePresence } from 'framer-motion';
import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
import { Location, defaultLocations, minorLocations } from '@/lib/map-data';
import { useLocale, useTranslations } from 'next-intl';
@@ -15,6 +16,7 @@ interface Stat {
value: string;
suffix?: string;
label: string;
href?: string;
}
interface InteractiveGermanyMapProps {
@@ -42,7 +44,7 @@ export function InteractiveGermanyMap({
const finalStats = stats || [
{ value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },
{ value: '3', suffix: '', label: locale === 'en' ? 'Operational Locations' : 'Operative Standorte' },
{ value: '3', suffix: '', label: locale === 'en' ? 'Operational Locations' : 'Operative Standorte', href: `/${locale}/standorte` },
];
const finalBadge = badge || tStandard('badge');
@@ -90,17 +92,32 @@ export function InteractiveGermanyMap({
{/* Industrial Stats Grid */}
<div className="grid grid-cols-2 gap-4">
{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 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="text-4xl font-extrabold text-white mb-1 flex items-baseline gap-1">
{stat.value}<span className="text-xl text-primary">{stat.suffix}</span>
{finalStats.map((stat, i) => {
const StatCard = () => (
<div className={`bg-white/5 border border-white/10 rounded-2xl p-6 backdrop-blur-md relative overflow-hidden group h-full ${stat.href ? 'cursor-pointer' : ''}`}>
<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 flex flex-col h-full justify-between">
<div className="text-4xl font-extrabold text-white mb-1 flex items-baseline gap-1">
{stat.value}<span className="text-xl text-primary">{stat.suffix}</span>
</div>
<div className={`text-sm text-white/50 font-medium flex items-center ${stat.href ? 'group-hover:text-primary transition-colors' : ''}`}>
{stat.label}
{stat.href && <ArrowUpRight className="w-4 h-4 ml-1 opacity-0 -translate-y-1 translate-x-1 group-hover:opacity-100 group-hover:translate-y-0 group-hover:translate-x-0 transition-all duration-300" />}
</div>
</div>
<div className="text-sm text-white/50 font-medium">{stat.label}</div>
</div>
</div>
))}
);
return stat.href ? (
<Link key={i} href={stat.href} className="block group/link hover:-translate-y-1 transition-transform duration-300">
<StatCard />
</Link>
) : (
<div key={i}>
<StatCard />
</div>
);
})}
</div>
</div>

View File

@@ -18,6 +18,7 @@ export const defaultLocations: Location[] = [
x: 85,
y: 41.1,
description: 'E-TIB GmbH Holding & Bohrtechnik GmbH',
href: '/standorte/guben',
details: ['Zentrale Steuerung', 'Kabelleitungstiefbau', 'Maschinenpark'],
},
{
@@ -27,7 +28,7 @@ export const defaultLocations: Location[] = [
x: 52,
y: 55,
description: 'Standort in Thüringen',
href: '/bohrtechnik',
href: '/standorte/kirchheilingen',
details: ['Bahnhofstraße 180a', '99947 Kirchheilingen'],
},
{
@@ -37,6 +38,7 @@ export const defaultLocations: Location[] = [
x: 37,
y: 25.33,
description: 'E-TIB Ingenieurgesellschaft mbH',
href: '/standorte/buelstedt',
details: ['Planung & Projektierung', 'Vermessung', 'Dokumentation'],
},
{

162
lib/standorte-data.ts Normal file
View File

@@ -0,0 +1,162 @@
import { LucideIcon, MapPin, Building2, HardHat, Phone, Mail, Navigation } from 'lucide-react';
export interface ContactPerson {
name: string;
role: string;
phone: string;
email: string;
image?: string;
}
export interface StandortData {
id: string;
name: string;
shortName: string;
description: Record<'de' | 'en', string>;
address: {
street: string;
city: string;
};
contact: {
phone: string;
email: string;
};
coordinates: {
lat: number;
lng: number;
};
keyFeatures: Record<'de' | 'en', string[]>;
image: string;
gallery: string[];
type: 'hq' | 'branch';
}
export const standorteData: StandortData[] = [
{
id: 'guben',
name: 'E-TIB GmbH Holding & Bohrtechnik GmbH',
shortName: 'Guben (Hauptsitz)',
type: 'hq',
description: {
de: 'Unser Hauptsitz in Guben bildet das strategische und operative Zentrum der E-TIB Gruppe. Von hier aus steuern wir bundesweit Großprojekte im Kabelleitungstiefbau und koordinieren unseren hochmodernen Maschinenpark.',
en: 'Our headquarters in Guben forms the strategic and operational center of the E-TIB Group. From here, we manage large-scale civil engineering projects nationwide and coordinate our state-of-the-art machinery fleet.'
},
address: {
street: 'Gewerbestraße 22',
city: '03172 Guben'
},
contact: {
phone: '+49 (0) 3561 / 68577 33',
email: 'info@e-tib.com'
},
coordinates: {
lat: 51.942,
lng: 14.717
},
keyFeatures: {
de: [
'Zentrale strategische Steuerung',
'Kabelleitungstiefbau & Infrastruktur',
'Koordination Großmaschinenpark',
'Zentrales Projektmanagement'
],
en: [
'Central strategic management',
'Underground cable engineering & infrastructure',
'Coordination of heavy machinery',
'Central project management'
]
},
image: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-100.jpg',
gallery: [
'/assets/photos/DSC02114.jpg',
'/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-45.jpg'
]
},
{
id: 'kirchheilingen',
name: 'E-TIB Bohrtechnik GmbH',
shortName: 'Standort Thüringen',
type: 'branch',
description: {
de: 'Unser spezialisierter Standort für Horizontalspülbohrtechnik in Thüringen. Mit modernsten Bohranlagen realisieren wir grabenlose Leitungsverlegungen für komplexe Infrastrukturprojekte, umweltschonend und hocheffizient.',
en: 'Our specialized location for horizontal directional drilling in Thuringia. Utilizing state-of-the-art drilling rigs, we implement trenchless cable laying for complex infrastructure projects, environmentally friendly and highly efficient.'
},
address: {
street: 'Bahnhofstraße 180a',
city: '99947 Kirchheilingen'
},
contact: {
phone: '+49 (0) 3561 / 68577 33', // Placeholder, using central
email: 'info@e-tib.com'
},
coordinates: {
lat: 51.1852,
lng: 10.6865
},
keyFeatures: {
de: [
'Horizontalspülbohrtechnik (HDD)',
'Grabenlose Rohrverlegung',
'Felsbohrungen',
'Unterquerung von Gewässern & Straßen'
],
en: [
'Horizontal Directional Drilling (HDD)',
'Trenchless pipe laying',
'Rock drilling',
'Crossing underneath water bodies & roads'
]
},
image: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(247_von_80).jpg',
gallery: [
'/assets/photos/DSC02089.jpg',
'/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-6.jpg'
]
},
{
id: 'buelstedt',
name: 'E-TIB Ingenieurgesellschaft mbH',
shortName: 'Bülstedt (Ingenieurgesellschaft)',
type: 'branch',
description: {
de: 'Unsere Ingenieurgesellschaft in Bülstedt ist spezialisiert auf die hochpräzise Planung, Vermessung und Projektierung von Energie- und Telekommunikationsnetzen. Hier verschmelzen technische Expertise und modernes Datenmanagement.',
en: 'Our engineering firm in Bülstedt specializes in the high-precision planning, surveying, and project management of energy and telecommunication networks. This is where technical expertise and modern data management converge.'
},
address: {
street: 'Dorfstraße 1', // Placeholder
city: '27412 Bülstedt'
},
contact: {
phone: '+49 (0) 3561 / 68577 33', // Placeholder, using central
email: 'info@e-tib.com'
},
coordinates: {
lat: 53.2167,
lng: 9.15
},
keyFeatures: {
de: [
'Trassenplanung & Projektierung',
'Geodätische Vermessung (GPS/Tachymetrie)',
'As-Built Dokumentation (GIS/CAD)',
'Genehmigungsmanagement'
],
en: [
'Route planning & project management',
'Geodetic surveying (GPS/Tacheometry)',
'As-built documentation (GIS/CAD)',
'Permit management'
]
},
image: '/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-256.jpg',
gallery: [
'/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-294.jpg',
'/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg'
]
}
];
export function getStandortById(id: string): StandortData | undefined {
return standorteData.find(s => s.id === id);
}