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
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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
→
|
||||
</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">
|
||||
→
|
||||
</span>
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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">
|
||||
→
|
||||
<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">
|
||||
→
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<ButtonOverlay variant="accent" />
|
||||
</TrackedLink>
|
||||
<ButtonOverlay variant="accent" />
|
||||
</TrackedLink>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user