style: finalize industrial design system and fix typography conflicts
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 22s
Build & Deploy / 🧪 QA (push) Failing after 1m5s
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 2s

- Refactor app/[locale]/[slug]/page.tsx to handle fullBleed layouts cleanly
- Fix heading styling on 'About Us' and other MDX pages
- Implement staggered Framer Motion animations across all core blocks
- Resolve InteractiveGermanyMap reference error
- Standardize typography tokens for prose and custom headings
- Consolidate support CTA placement
This commit is contained in:
2026-05-14 10:51:50 +02:00
parent 7c633c6eed
commit ce513080d2
10 changed files with 425 additions and 234 deletions

View File

@@ -132,52 +132,57 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
}
*/
// Full-bleed pages render blocks edge-to-edge without the generic article wrapper
if (pageData.frontmatter.layout === 'fullBleed') {
return (
<div className="flex flex-col min-h-screen">
<MDXRemote source={pageData.content} components={mdxComponents} />
</div>
);
}
// Determine layout type
const isFullBleed = pageData.frontmatter.layout === 'fullBleed';
// Default article layout with hero, content, and support CTA
return (
<div className="flex flex-col min-h-screen bg-white">
{/* Hero Section */}
<section className="bg-primary-dark text-white pt-40 pb-16 md:pt-48 md:pb-20 min-h-[35vh] flex flex-col justify-end relative overflow-hidden">
<div className="absolute inset-0 opacity-20">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
</div>
<Container className="relative z-10">
<div className="max-w-4xl">
<Badge variant="accent" className="mb-4 md:mb-6">
{t('badge')}
</Badge>
<Heading level={1} variant="white" className="mb-0">
{pageData.frontmatter.title}
</Heading>
<div className={`flex flex-col min-h-screen ${isFullBleed ? '' : 'bg-white'}`}>
{/* Generic Hero Section (only for standard pages) */}
{!isFullBleed && (
<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>
</Container>
</section>
<Container className="relative z-10">
<div className="max-w-4xl">
<Badge variant="accent" className="mb-4 md:mb-6 shadow-sm">
{t('badge')}
</Badge>
<Heading level={1} variant="white" className="mb-0 drop-shadow-sm">
{pageData.frontmatter.title}
</Heading>
</div>
</Container>
</section>
)}
{/* Main Content Area */}
<Container className="py-16 md:py-24">
{/* 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 */}
{isFullBleed ? (
<div className="w-full">
<MDXRemote source={pageData.content} components={mdxComponents} />
</div>
) : (
<Container className="py-16 md:py-24">
{/* Excerpt/Lead paragraph if available */}
{pageData.frontmatter.excerpt && (
<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">
{pageData.frontmatter.excerpt}
</p>
</div>
)}
{/* Support Section */}
{/* Main content */}
<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">
<MDXRemote source={pageData.content} components={mdxComponents} />
</div>
</div>
</Container>
)}
{/* Support Section */}
<Container>
<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">

View File

@@ -1,5 +1,8 @@
'use client';
import React from 'react';
import { Button } from '@/components/ui/Button';
import { motion } from 'framer-motion';
export interface CallToActionProps {
title?: string;
@@ -9,6 +12,26 @@ export interface CallToActionProps {
theme?: 'light' | 'dark';
}
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1] },
},
};
export const CallToAction: React.FC<CallToActionProps> = ({
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.',
@@ -20,21 +43,29 @@ export const CallToAction: React.FC<CallToActionProps> = ({
return (
<div className={`py-24 text-center ${isDark ? 'bg-primary-dark text-white' : 'bg-neutral-50 border-t border-neutral-100'}`}>
<div className="container max-w-3xl mx-auto px-4">
<h2 className={`font-heading text-4xl font-extrabold mb-6 ${isDark ? 'text-white' : 'text-neutral-dark'}`}>
<motion.div
className="container max-w-3xl mx-auto px-4"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
>
<motion.h2 variants={itemVariants} className={`font-heading text-4xl font-extrabold mb-6 ${isDark ? 'text-white' : 'text-neutral-dark'}`}>
{title}
</h2>
<p className={`text-xl mb-10 leading-relaxed ${isDark ? 'text-white/70' : 'text-text-secondary'}`}>
</motion.h2>
<motion.p variants={itemVariants} className={`text-xl mb-10 leading-relaxed ${isDark ? 'text-white/70' : 'text-text-secondary'}`}>
{description}
</p>
<Button
href={ctaHref}
size="xl"
variant={isDark ? 'white' : 'primary'}
>
{ctaLabel}
</Button>
</div>
</motion.p>
<motion.div variants={itemVariants}>
<Button
href={ctaHref}
size="xl"
variant={isDark ? 'white' : 'primary'}
>
{ctaLabel}
</Button>
</motion.div>
</motion.div>
</div>
);
};

View File

@@ -4,6 +4,7 @@ import React, { Suspense } from 'react';
import { Section, Container } from '@/components/ui';
import { ContactForm } from '@/components/forms/ContactForm';
import dynamic from 'next/dynamic';
import { motion } from 'framer-motion';
const ContactMap = dynamic(() => import('@/components/ContactMap'), {
ssr: false,
@@ -15,63 +16,96 @@ export interface ContactSectionProps {
showMap?: boolean;
}
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.2,
},
},
};
const itemVariants = {
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0, transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1] } },
};
const formVariants = {
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1], delay: 0.4 } },
};
export const ContactSection: React.FC<ContactSectionProps> = (props) => {
const { showForm, showMap } = props;
return (
<Section className="bg-neutral-light py-12 md:py-28">
<Section className="bg-neutral-light py-12 md:py-28 relative overflow-hidden">
<Container>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16">
<div className="lg:col-span-5 flex flex-col justify-center">
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">Direktkontakt</h2>
<h3 className="font-heading text-4xl font-extrabold text-neutral-dark mb-6">Wir sind für Sie da.</h3>
<p className="text-lg text-text-secondary mb-8">
<motion.div
className="lg:col-span-5 flex flex-col justify-center"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-50px" }}
>
<motion.h2 variants={itemVariants} className="text-primary font-bold tracking-wider uppercase text-sm mb-3">Direktkontakt</motion.h2>
<motion.h3 variants={itemVariants} className="font-heading text-4xl font-extrabold text-neutral-dark mb-6">Wir sind für Sie da.</motion.h3>
<motion.p variants={itemVariants} className="text-lg text-text-secondary mb-8 leading-relaxed">
Haben Sie Fragen zu unseren Leistungen oder möchten Sie ein konkretes Projekt besprechen? Rufen Sie uns an oder schreiben Sie uns eine E-Mail.
</p>
</motion.p>
<div className="space-y-6">
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center text-primary shrink-0">
<motion.div variants={itemVariants} className="group flex items-start gap-4">
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center text-primary shrink-0 transition-all duration-500 group-hover:bg-primary group-hover:text-white group-hover:scale-110 group-hover:-rotate-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
</div>
<div>
<h4 className="font-bold text-neutral-dark">Telefon</h4>
<h4 className="font-bold text-neutral-dark group-hover:text-primary transition-colors">Telefon</h4>
<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>
</motion.div>
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center text-primary shrink-0">
<motion.div variants={itemVariants} className="group flex items-start gap-4">
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center text-primary shrink-0 transition-all duration-500 group-hover:bg-primary group-hover:text-white group-hover:scale-110 group-hover:-rotate-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
</div>
<div>
<h4 className="font-bold text-neutral-dark">E-Mail</h4>
<h4 className="font-bold text-neutral-dark group-hover:text-primary transition-colors">E-Mail</h4>
<p className="text-primary hover:text-primary-dark font-medium transition-colors">
<a href="mailto:info@e-tib.com">info@e-tib.com</a>
</p>
</div>
</div>
</motion.div>
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center text-primary shrink-0">
<motion.div variants={itemVariants} className="group flex items-start gap-4">
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center text-primary shrink-0 transition-all duration-500 group-hover:bg-primary group-hover:text-white group-hover:scale-110 group-hover:-rotate-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
</div>
<div>
<h4 className="font-bold text-neutral-dark">Standort</h4>
<h4 className="font-bold text-neutral-dark group-hover:text-primary transition-colors">Standort</h4>
<p className="text-text-secondary leading-relaxed">
Gewerbestraße 22<br />
03172 Guben<br />
Deutschland
</p>
</div>
</div>
</motion.div>
</div>
</div>
</motion.div>
{showForm && (
<div className="lg:col-span-7 relative">
<motion.div
className="lg:col-span-7 relative"
variants={formVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-50px" }}
>
<Suspense
fallback={
<div className="animate-pulse bg-white border border-neutral-100 shadow-xl h-96 rounded-2xl md:rounded-3xl" />
@@ -79,12 +113,18 @@ export const ContactSection: React.FC<ContactSectionProps> = (props) => {
>
<ContactForm />
</Suspense>
</div>
</motion.div>
)}
</div>
</Container>
{showMap && (
<section className="mt-12 h-[300px] md:h-[500px] bg-neutral-medium relative overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000">
<motion.section
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 1 }}
className="mt-12 h-[300px] md:h-[500px] bg-neutral-medium relative overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000"
>
<Suspense
fallback={<div className="h-full w-full bg-neutral-medium animate-pulse" />}
>
@@ -94,7 +134,7 @@ export const ContactSection: React.FC<ContactSectionProps> = (props) => {
lng={14.7214}
/>
</Suspense>
</section>
</motion.section>
)}
</Section>
);

View File

@@ -1,6 +1,8 @@
'use client';
import React from 'react';
import Image from 'next/image';
import Reveal from '@/components/Reveal';
import { motion } from 'framer-motion';
import { Badge, Container, Heading } from '@/components/ui';
import { Button } from '@/components/ui/Button';
@@ -14,63 +16,97 @@ export interface HeroSectionProps {
ctaHref?: string;
}
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1] }
},
};
export const HeroSection: React.FC<HeroSectionProps> = (props) => {
const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props;
const bgSrc = backgroundImage?.sizes?.card?.url || backgroundImage?.url;
return (
<Reveal>
<section
className={`relative min-h-[50vh] md:min-h-[70vh] flex items-center pt-32 pb-20 md:pt-40 md:pb-32 overflow-hidden bg-primary-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`}
>
{bgSrc && (
<div className="absolute inset-0 z-0">
<Image
src={bgSrc}
alt={title || 'Hero Background'}
fill
className="object-cover opacity-30 md:opacity-40"
style={{
objectPosition: `${backgroundImage?.focalX ?? 50}% ${backgroundImage?.focalY ?? 50}%`,
}}
sizes="100vw"
priority
/>
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
</div>
)}
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
<div className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}>
{badge && (
<section
className={`relative min-h-[50vh] md:min-h-[70vh] flex items-center pt-32 pb-20 md:pt-40 md:pb-32 overflow-hidden bg-primary-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`}
>
{bgSrc && (
<motion.div
className="absolute inset-0 z-0"
initial={{ scale: 1.05, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut" }}
>
<Image
src={bgSrc}
alt={title || 'Hero Background'}
fill
className="object-cover opacity-30 md:opacity-40"
style={{
objectPosition: `${backgroundImage?.focalX ?? 50}% ${backgroundImage?.focalY ?? 50}%`,
}}
sizes="100vw"
priority
/>
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
</motion.div>
)}
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
<motion.div
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
variants={containerVariants}
initial="hidden"
animate="visible"
>
{badge && (
<motion.div variants={itemVariants}>
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">
{badge}
</Badge>
)}
</motion.div>
)}
<motion.div variants={itemVariants}>
<Heading level={1} className="text-white mb-4 md:mb-8">
{title}
</Heading>
{subtitle && (
</motion.div>
{subtitle && (
<motion.div variants={itemVariants}>
<p className="text-lg md:text-2xl text-white/70 font-medium leading-relaxed max-w-2xl">
{subtitle}
</p>
)}
{ctaLabel && ctaHref && (
<div className="mt-8">
<Button
href={ctaHref}
variant="accent"
size="lg"
>
{ctaLabel}
<span className="ml-3 transition-transform group-hover/btn:translate-x-2">
&rarr;
</span>
</Button>
</div>
)}
</div>
</Container>
</section>
</Reveal>
</motion.div>
)}
{ctaLabel && ctaHref && (
<motion.div variants={itemVariants} className="mt-8">
<Button
href={ctaHref}
variant="accent"
size="lg"
>
{ctaLabel}
<span className="ml-3 transition-transform group-hover/btn:translate-x-2">
&rarr;
</span>
</Button>
</motion.div>
)}
</motion.div>
</Container>
</section>
);
};

View File

@@ -5,7 +5,7 @@ import { motion, AnimatePresence } from 'framer-motion';
import { MapPin, Factory, Zap, CheckCircle2 } from 'lucide-react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { Location } from '@/lib/map-data';
import { Location, defaultLocations } from '@/lib/map-data';
interface Stat {
value: string;

View File

@@ -1,7 +1,10 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/Button';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
import { motion } from 'framer-motion';
export interface JobListingBlockProps {
title?: string;
@@ -21,7 +24,27 @@ export interface JobListingBlockProps {
emptyStateLinkHref?: string;
}
export const JobListingBlock = async (props: JobListingBlockProps) => {
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1] },
},
};
export const JobListingBlock = (props: JobListingBlockProps) => {
const {
title,
showJobs = true,
@@ -45,11 +68,23 @@ export const JobListingBlock = async (props: JobListingBlockProps) => {
<div className="container mx-auto">
{showFairs && (
<div className="mb-24 relative">
<h3 className="font-heading font-extrabold text-4xl text-neutral-dark mb-12 flex items-center gap-4">
<motion.h3
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }}
className="font-heading font-extrabold text-4xl text-neutral-dark mb-12 flex items-center gap-4"
>
<span className="w-12 h-1.5 bg-primary rounded-full" />
{fairsTitle}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
</motion.h3>
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
>
{fairs.map((messe, idx) => {
const isLink = !!messe.url;
const innerContent = (
@@ -120,58 +155,77 @@ export const JobListingBlock = async (props: JobListingBlockProps) => {
if (isLink) {
return (
<a key={`fair-${idx}`} href={messe.url} target="_blank" rel="noopener noreferrer" className={`${baseClasses} cursor-pointer`}>
<motion.a variants={itemVariants} key={`fair-${idx}`} href={messe.url} target="_blank" rel="noopener noreferrer" className={`${baseClasses} cursor-pointer`}>
{innerContent}
</a>
</motion.a>
);
}
return (
<div key={`fair-${idx}`} className={baseClasses}>
<motion.div variants={itemVariants} key={`fair-${idx}`} className={baseClasses}>
{innerContent}
</div>
</motion.div>
);
})}
</div>
</motion.div>
</div>
)}
{showJobs && (
<div className="mb-12">
<h2 className="font-heading font-extrabold text-4xl text-neutral-dark mb-8">
<motion.h2
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }}
className="font-heading font-extrabold text-4xl text-neutral-dark mb-8"
>
{title || 'Aktuelle Stellenangebote'}
</h2>
</motion.h2>
{jobs.length > 0 ? (
<div className="grid gap-6">
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
className="grid gap-6"
>
{jobs.map((job: any) => (
<Link
key={job.id}
href={`/karriere/${job.slug}`}
className="group bg-white border border-neutral-100 p-8 rounded-2xl shadow-sm hover:shadow-xl hover:border-primary/20 transition-all flex flex-col md:flex-row md:items-center justify-between gap-6"
>
<div>
<div className="flex items-center gap-3 mb-2">
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-bold rounded-full uppercase">{job.type}</span>
<span className="text-text-secondary text-sm font-medium">{job.department}</span>
<motion.div variants={itemVariants} key={job.id}>
<Link
href={`/karriere/${job.slug}`}
className="group bg-white border border-neutral-100 p-8 rounded-2xl shadow-sm hover:shadow-xl hover:border-primary/20 transition-all flex flex-col md:flex-row md:items-center justify-between gap-6 block"
>
<div>
<div className="flex items-center gap-3 mb-2">
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-bold rounded-full uppercase">{job.type}</span>
<span className="text-text-secondary text-sm font-medium">{job.department}</span>
</div>
<h4 className="font-heading font-bold text-2xl text-neutral-dark group-hover:text-primary transition-colors">{job.title}</h4>
<p className="text-text-secondary mt-1">{job.location}</p>
</div>
<h4 className="font-heading font-bold text-2xl text-neutral-dark group-hover:text-primary transition-colors">{job.title}</h4>
<p className="text-text-secondary mt-1">{job.location}</p>
</div>
<div className="flex items-center text-primary font-bold group-hover:translate-x-2 transition-transform">
Details ansehen
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 ml-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</Link>
<div className="flex items-center text-primary font-bold group-hover:translate-x-2 transition-transform">
Details ansehen
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 ml-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</Link>
</motion.div>
))}
</div>
</motion.div>
) : (
<div className="bg-neutral-50 border border-neutral-100 rounded-2xl p-12 text-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }}
className="bg-neutral-50 border border-neutral-100 rounded-2xl p-12 text-center"
>
<p className="text-text-secondary text-lg">{emptyStateMessage}</p>
<Button href={emptyStateLinkHref} variant="primary" className="mt-6">
{emptyStateLinkText}
</Button>
</div>
</motion.div>
)}
</div>
)}

View File

@@ -1,6 +1,9 @@
'use client';
import React from 'react';
import TrackedLink from '@/components/analytics/TrackedLink';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import { motion } from 'framer-motion';
export interface SupportCTAProps {
title: string;
@@ -9,31 +12,62 @@ export interface SupportCTAProps {
buttonHref: string;
}
const containerVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.8,
ease: [0.16, 1, 0.3, 1],
staggerChildren: 0.15,
delayChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1] },
},
};
export const SupportCTA: React.FC<SupportCTAProps> = (props) => {
const { title, description, buttonLabel, buttonHref } = props;
return (
<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" />
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
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 duration-700 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">{title}</h3>
<p className="text-lg text-white/70 mb-8">{description}</p>
<TrackedLink
href={buttonHref}
className={getButtonClasses('accent', 'lg')}
eventProperties={{
location: 'block_support_cta',
}}
>
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
{buttonLabel}
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
&rarr;
<motion.h3 variants={itemVariants} className="text-2xl md:text-3xl font-bold mb-4">{title}</motion.h3>
<motion.p variants={itemVariants} className="text-lg text-white/70 mb-8">{description}</motion.p>
<motion.div variants={itemVariants}>
<TrackedLink
href={buttonHref}
className={getButtonClasses('accent', 'lg')}
eventProperties={{
location: 'block_support_cta',
}}
>
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
{buttonLabel}
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
&rarr;
</span>
</span>
</span>
<ButtonOverlay variant="accent" />
</TrackedLink>
<ButtonOverlay variant="accent" />
</TrackedLink>
</motion.div>
</div>
</div>
</motion.div>
);
};

View File

@@ -3,6 +3,7 @@
import * as React from 'react';
import { motion } from 'framer-motion';
import Image from 'next/image';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
export interface TeamMember {
id: string;
@@ -25,12 +26,12 @@ export function TeamGrid({ members }: TeamGridProps) {
if (!members || members.length === 0) return null;
return (
<section className="py-24 bg-white">
<div className="container">
<section className="py-24 bg-white relative overflow-hidden">
<div className="container relative z-10">
<div className="mb-12">
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">Persönliche Beratung</h2>
<h3 className="font-heading text-4xl font-extrabold text-neutral-dark mb-4">Ihre Ansprechpartner</h3>
<p className="text-text-secondary max-w-2xl">
<p className="text-text-secondary max-w-2xl text-lg">
Sprechen Sie direkt mit unseren Experten für Ihr regionales Projekt.
</p>
</div>
@@ -39,48 +40,60 @@ export function TeamGrid({ members }: TeamGridProps) {
{members.map((member, i) => (
<motion.div
key={member.id}
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 }}
className="group bg-neutral-50 rounded-2xl p-6 border border-neutral-100 hover:shadow-xl transition-all"
viewport={{ once: true, margin: "-50px" }}
transition={{ delay: i * 0.1, duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
className="group relative bg-neutral-50 rounded-[2rem] p-8 border border-neutral-200/60 transition-all duration-500 hover:bg-white hover:border-primary/30 hover:shadow-[0_20px_40px_-15px_rgba(0,0,0,0.05)] hover:-translate-y-2 overflow-hidden"
>
<div className="flex items-center gap-6">
<div className="relative w-24 h-24 flex-shrink-0 rounded-xl overflow-hidden bg-neutral-200">
{member.image && typeof member.image === 'object' && member.image.url ? (
<Image
src={member.image.url}
alt={member.name}
fill
sizes="96px"
className="object-cover"
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-neutral-400">
<svg className="w-12 h-12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
</div>
<HoverShineOverlay shineColor="via-primary/5" />
<div className="relative z-10 flex flex-col h-full">
<div className="flex items-center gap-6 mb-6">
<div className="relative w-24 h-24 flex-shrink-0 rounded-[1.25rem] overflow-hidden bg-neutral-200 border border-neutral-100 shadow-sm group-hover:shadow-md transition-shadow duration-500">
{member.image && typeof member.image === 'object' && member.image.url ? (
<Image
src={member.image.url}
alt={member.name}
fill
sizes="96px"
className="object-cover group-hover:scale-110 transition-transform duration-700 ease-in-out"
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-neutral-400">
<svg className="w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
</div>
)}
</div>
<div>
<h4 className="font-heading font-bold text-2xl text-neutral-dark mb-1 leading-tight group-hover:text-primary transition-colors duration-300">{member.name}</h4>
<p className="text-primary font-medium text-sm mb-1">{member.position}</p>
<p className="inline-block px-2 py-0.5 mt-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' : 'Bohrtechnik'}
</p>
</div>
</div>
<div className="mt-auto pt-6 border-t border-neutral-200/60 space-y-3">
{member.email && (
<a href={`mailto:${member.email}`} className="group/link flex items-center gap-3 text-text-secondary hover:text-primary transition-colors text-sm font-medium">
<div className="w-8 h-8 rounded-full bg-neutral-100 flex items-center justify-center text-neutral-500 group-hover/link:bg-primary/10 group-hover/link:text-primary transition-colors">
<svg className="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
</div>
<span className="truncate">{member.email}</span>
<svg className="w-4 h-4 ml-auto opacity-0 -translate-x-2 group-hover/link:opacity-100 group-hover/link:translate-x-0 transition-all duration-300" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</a>
)}
{member.phone && (
<a href={`tel:${member.phone}`} className="group/link flex items-center gap-3 text-text-secondary hover:text-primary transition-colors text-sm font-medium">
<div className="w-8 h-8 rounded-full bg-neutral-100 flex items-center justify-center text-neutral-500 group-hover/link:bg-primary/10 group-hover/link:text-primary transition-colors">
<svg className="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
</div>
<span>{member.phone}</span>
<svg className="w-4 h-4 ml-auto opacity-0 -translate-x-2 group-hover/link:opacity-100 group-hover/link:translate-x-0 transition-all duration-300" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</a>
)}
</div>
<div>
<h4 className="font-heading font-bold text-xl text-neutral-dark">{member.name}</h4>
<p className="text-primary font-medium text-sm mb-2">{member.position}</p>
<p className="text-text-secondary text-xs uppercase tracking-widest">{member.branch === 'e-tib' ? 'E-TIB GmbH' : member.branch === 'ing' ? 'Ingenieurgesellschaft' : 'Bohrtechnik'}</p>
</div>
</div>
<div className="mt-6 pt-6 border-t border-neutral-200 space-y-3">
{member.email && (
<a href={`mailto:${member.email}`} className="flex items-center gap-3 text-text-secondary hover:text-primary transition-colors text-sm">
<svg className="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
{member.email}
</a>
)}
{member.phone && (
<a href={`tel:${member.phone}`} className="flex items-center gap-3 text-text-secondary hover:text-primary transition-colors text-sm">
<svg className="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
{member.phone}
</a>
)}
</div>
</motion.div>
))}

View File

@@ -179,25 +179,3 @@ description: Willkommen bei E-TIB GmbH
]}
/>
<FaqBlock
title="Häufig gestellte Fragen"
subtitle="Wissenswertes"
questions={[
{
question: "Was macht die E-TIB GmbH?",
answer: "Die E-TIB GmbH ist spezialisiert auf den Leitungs- und Kabeltiefbau. Zu den Kernleistungen zählen der klassische Kabelbau, hochpräzise Horizontalspülbohrungen, Kabelpflugarbeiten sowie Elektromontagen bis 110 kV. Zusammen mit unseren Tochtergesellschaften decken wir auch die komplette Planung und Dokumentation von Infrastrukturprojekten ab."
},
{
question: "Welche Vorteile bietet das Horizontalspülbohrverfahren (Bohrtechnik)?",
answer: "Die Horizontalspülbohrung ist ein grabenloses Verfahren zur unterirdischen Verlegung von Kabeln und Rohren. Der größte Vorteil ist die oberflächenschonende Arbeitsweise: Flüsse, Autobahnen, Bahntrassen oder Naturschutzgebiete können gekreuzt werden, ohne den Verkehr oder die Umwelt zu beeinträchtigen. Dies spart erhebliche Kosten bei der Oberflächenwiederherstellung."
},
{
question: "Seit wann gibt es die E-TIB GmbH und wie groß ist das Team?",
answer: "Die E-TIB GmbH wurde im Dezember 2015 in Guben gegründet. Heute beschäftigt die Unternehmensgruppe über 50 qualifizierte Mitarbeiter, die komplexe Infrastruktur- und Tiefbauprojekte für Energieversorger und Telekommunikationsanbieter bundesweit realisieren."
},
{
question: "Bieten Sie auch die Planung von Netzanschlüssen an?",
answer: "Ja, über die E-TIB Ingenieurgesellschaft mbH übernehmen wir die komplette Genehmigungs- und Ausführungsplanung. Dies beinhaltet Elektro- und Netzanschlussplanungen, Machbarkeitsstudien sowie die finale digitale Bestandsdokumentation per GIS."
}
]}
/>

View File

@@ -1,5 +1,5 @@
---
title: "Über E-TIB"
title: "Über uns"
date: "2024-03-20"
excerpt: "Die E-TIB Gruppe ist Ihr kompetenter Partner für Kabeltiefbau, Bohrtechnik und Ingenieurdienstleistungen."
layout: "fullBleed"