Compare commits

..
Author SHA1 Message Date
mmintel aa0dfdf331 fix: refactor standorte detail page layout to match standard mdx pages and fix bohrtechnik links
Build & Deploy / 🔍 Prepare (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m10s
Build & Deploy / 🏗️ Build (push) Successful in 2m13s
Build & Deploy / 🚀 Deploy (push) Successful in 27s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 45s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-15 11:26:09 +02:00
15 changed files with 191 additions and 133 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

+95 -124
View File
@@ -63,9 +63,9 @@ export default async function StandortDetail(props: { params: Promise<{ locale:
}
return (
<div className="flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24">
{/* Industrial Hero Section */}
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-48 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<div className="flex flex-col min-h-screen bg-white">
{/* Generic Hero Section to match standard pages */}
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<div className="absolute inset-0 opacity-20 pointer-events-none">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
</div>
@@ -75,139 +75,110 @@ export default async function StandortDetail(props: { params: Promise<{ locale:
<MapPin className="w-4 h-4" />
{standort.type === 'hq' ? (safeLocale === 'de' ? 'Hauptsitz' : 'Headquarters') : (safeLocale === 'de' ? 'Niederlassung' : 'Branch')}
</Badge>
<Heading level={1} variant="white" className="mb-6 drop-shadow-sm">
<Heading level={1} variant="white" className="mb-0 drop-shadow-sm">
{standort.name}
</Heading>
<p className="text-xl md:text-2xl text-white/80 max-w-3xl leading-relaxed font-medium">
{standort.description[safeLocale]}
</p>
</div>
</Container>
</section>
<Container className="relative z-20 mt-12 md:mt-16">
{/* Main Content Area */}
<Container className="py-16 md:py-24">
{/* Excerpt/Lead paragraph */}
<div className="mb-16 max-w-4xl mx-auto">
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
{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>
{/* Main content in prose style */}
<div className="max-w-4xl mx-auto">
<div className="prose prose-lg md:prose-xl prose-neutral max-w-none prose-headings:font-heading prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-4xl prose-h2:text-3xl prose-h2:mt-12 prose-h2:border-b prose-h2:border-neutral-200 prose-h2:pb-4 prose-h3:text-2xl prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:font-medium prose-a:underline prose-a:underline-offset-4 prose-a:decoration-primary/30 hover:prose-a:decoration-primary prose-strong:text-text-primary prose-strong:font-semibold prose-li:text-text-secondary prose-ul:list-disc prose-ol:list-decimal">
{/* Gallery (if any) */}
<img className="rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100" src={standort.image} alt={standort.name} />
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">{safeLocale === 'de' ? 'Unsere Leistungen vor Ort' : 'Our Local Services'}</h2>
<ul className="list-none mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl">
{standort.keyFeatures[safeLocale].map((feature, idx) => (
<li key={idx} className="relative pl-6 before:content-[''] before:absolute before:left-0 before:top-[0.6em] before:w-2 before:h-2 before:bg-primary/50 before:rounded-sm">
{feature}
</li>
))}
</ul>
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">{safeLocale === 'de' ? 'Kontakt & Anfahrt' : 'Contact & Directions'}</h2>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<strong>{safeLocale === 'de' ? 'Adresse:' : 'Address:'}</strong><br/>
{standort.address.street}<br/>
{standort.address.city}
</p>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<TrackedLink
href={`https://maps.google.com/?q=${encodeURIComponent(`${standort.address.street}, ${standort.address.city}`)}`}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold flex items-center gap-1 w-fit"
eventProperties={{ location: 'standort_maps_link', slug: standort.id }}
>
{safeLocale === 'de' ? 'In Google Maps öffnen' : 'Open in Google Maps'}
<ArrowUpRight className="w-4 h-4 inline-block" />
</TrackedLink>
</p>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<strong>{safeLocale === 'de' ? 'Telefon:' : 'Phone:'}</strong><br/>
<a href={`tel:${standort.contact.phone.replace(/[^0-9+]/g, '')}`} className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold">
{standort.contact.phone}
</a>
</p>
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
<strong>Email:</strong><br/>
<a href={`mailto:${standort.contact.email}`} className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold">
{standort.contact.email}
</a>
</p>
{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>
<>
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">Galerie</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 not-prose my-12">
{standort.gallery.map((img, idx) => (
<div key={idx} className="relative aspect-[4/3] rounded-2xl overflow-hidden bg-neutral-100 border border-neutral-200 shadow-md hover:shadow-xl transition-shadow duration-300">
<Image src={img} alt={`Gallery ${idx + 1}`} fill className="object-cover transition-transform duration-700 hover:scale-105" />
</div>
))}
</div>
</>
)}
</div>
</div>
</Container>
{/* 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>
{(() => {
const baseUrl = 'https://maps.google.com/';
const mapsUrl = `${baseUrl}?q=${encodeURIComponent(`${standort.address.street}, ${standort.address.city}`)}`;
return (
<TrackedLink
href={mapsUrl}
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>
{/* Support Section */}
<Container>
<div className="mt-12 mb-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: 'standort_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>
</span>
<ButtonOverlay variant="accent" />
</TrackedLink>
</div>
</div>
</Container>
+2 -4
View File
@@ -7,12 +7,10 @@ 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 { Location, defaultLocations, minorLocations, projectLocations } from '@/lib/map-data';
import { standorteLocations } from '@/lib/standorte-data';
import { useLocale, useTranslations } from 'next-intl';
const allLocations = [...standorteLocations, ...defaultLocations, ...minorLocations];
interface Stat {
value: string;
suffix?: string;
@@ -34,7 +32,7 @@ export function InteractiveGermanyMap({
title,
description,
stats,
locations = allLocations,
locations = projectLocations,
isHero = false
}: InteractiveGermanyMapProps) {
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

+1 -1
View File
@@ -86,7 +86,7 @@ layout: "fullBleed"
title: "E-TIB Bohrtechnik GmbH",
description: "Präzise Horizontalspülbohrungen in allen Bodenklassen für eine oberflächenschonende und grabenlose Leitungsverlegung.",
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
url: "/de/bohrtechnik",
url: "/de/standorte/kirchheilingen",
backgroundImage: "/assets/photos/DSC08653.JPG"
}
]}
+1 -1
View File
@@ -86,7 +86,7 @@ layout: "fullBleed"
title: "E-TIB Bohrtechnik GmbH",
description: "Precise horizontal directional drilling in all soil classes for surface-friendly and trenchless pipe laying.",
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
url: "/en/drilling-technology",
url: "/en/standorte/kirchheilingen",
backgroundImage: "/assets/photos/DSC08653.JPG"
}
]}
+1
View File
@@ -896,3 +896,4 @@ export const minorLocations: Location[] = [
description: 'pv',
},
];
export const projectLocations = [...defaultLocations, ...minorLocations];
File diff suppressed because one or more lines are too long
+8 -3
View File
@@ -29,7 +29,12 @@ async function run() {
console.log(` Waiting for potential Gatekeeper redirect...`);
await new Promise((resolve) => setTimeout(resolve, 3000));
const isGatekeeper = await page.$('input[name="password"]');
let isGatekeeper = null;
try {
isGatekeeper = await page.$('input[name="password"]');
} catch (e) {
console.log('Navigation happened while checking gatekeeper:', e.message);
}
if (isGatekeeper) {
await page.type('input[name="password"]', gatekeeperPassword);
await Promise.all([
@@ -65,7 +70,7 @@ async function run() {
urls.map(async (url) => {
if (url.includes('openstreetmap.org') || url.includes('unpkg.com')) return;
try {
const res = await fetch(url, { method: 'HEAD' });
const res = await fetch(url, { method: 'GET' });
if (res.status >= 400) {
// Diagnostic: check if the raw source is available
const rawUrlMatch = url.match(/[?&]url=([^&]+)/);
@@ -73,7 +78,7 @@ async function run() {
const rawPath = decodeURIComponent(rawUrlMatch[1]);
const baseUrl = new URL(url).origin;
const rawUrl = `${baseUrl}${rawPath}`;
const rawRes = await fetch(rawUrl, { method: 'HEAD' });
const rawRes = await fetch(rawUrl, { method: 'GET' });
broken.push(`${url} (Status: ${res.status}, Raw Status: ${rawRes.status})`);
} else {
broken.push(`${url} (Status: ${res.status})`);
+32
View File
@@ -0,0 +1,32 @@
const puppeteer = require('puppeteer');
(async () => {
const targetUrl = 'https://test.e-tib.com';
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
await new Promise((resolve) => setTimeout(resolve, 3000));
const isGatekeeper = await page.$('input[name="password"]');
if (isGatekeeper) {
await page.type('input[name="password"]', gatekeeperPassword);
await Promise.all([
page.click('button[type="submit"]'),
]);
await new Promise((resolve) => setTimeout(resolve, 5000));
}
await page.screenshot({ path: 'after-login.png' });
await page.goto(`${targetUrl}/de/kontakt`, { waitUntil: 'domcontentloaded' });
await new Promise(r => setTimeout(r, 2000));
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('contact-page-auth-debug.html', html);
await page.screenshot({ path: 'contact.png' });
await browser.close();
})();
+12
View File
@@ -0,0 +1,12 @@
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://test.e-tib.com/de/kontakt', { waitUntil: 'networkidle0' });
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('contact-page-debug.html', html);
await browser.close();
})();
+12
View File
@@ -0,0 +1,12 @@
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://test.e-tib.com/de/kontakt', { waitUntil: 'networkidle0' });
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('contact-page-debug.html', html);
await browser.close();
})();
+24
View File
@@ -0,0 +1,24 @@
const puppeteer = require('puppeteer');
(async () => {
const targetUrl = 'https://test.e-tib.com';
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(targetUrl, { waitUntil: 'networkidle0' });
const isGatekeeper = await page.$('input[name="password"]');
if (isGatekeeper) {
await page.type('input[name="password"]', gatekeeperPassword);
await Promise.all([
page.click('button[type="submit"]'),
page.waitForNavigation({ waitUntil: 'networkidle0' }).catch(() => {})
]);
const html = await page.content();
const fs = require('fs');
fs.writeFileSync('login-result.html', html);
} else {
console.log("No gatekeeper found!");
}
await browser.close();
})();