All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 53s
Build & Deploy / 🧪 QA (push) Successful in 1m31s
Build & Deploy / 🏗️ Build (push) Successful in 2m58s
Build & Deploy / 🚀 Deploy (push) Successful in 38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m1s
Build & Deploy / 🔔 Notify (push) Successful in 3s
- fix: bottom nav and footer padding collision - fix: reveal animation flickering (Certificates, SubCompanyTiles) - fix: tooltip flyout overflow on mobile in Germany map - fix: flex direction for 'Projekt anfragen' and Service Detail icons - fix: text alignment and spacing issues in ReferencesSlider - fix: reduce excessive whitespace above footer
227 lines
8.6 KiB
TypeScript
227 lines
8.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import { Container } from '@/components/ui/Container';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
|
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
|
import { FileText, Award, Download, ShieldCheck } from 'lucide-react';
|
|
import { LogoArcs } from '@/components/ui/LogoArcs';
|
|
|
|
interface Certificate {
|
|
title: string;
|
|
description: string;
|
|
pdfUrl?: string;
|
|
type: 'iso' | 'tax' | 'general';
|
|
date?: string;
|
|
}
|
|
|
|
interface CertificatesBlockProps {
|
|
badge?: string;
|
|
title?: string;
|
|
description?: string;
|
|
certificates?: Certificate[];
|
|
hideHeader?: boolean;
|
|
}
|
|
|
|
const defaultCertificates: Certificate[] = [
|
|
{
|
|
title: 'ISO 14001:2015',
|
|
description: 'Umweltmanagementsystem',
|
|
pdfUrl: '/assets/certificates/iso14001.pdf',
|
|
type: 'iso',
|
|
date: '14.12.2023',
|
|
},
|
|
{
|
|
title: 'ISO 9001:2015',
|
|
description: 'Qualitätsmanagementsystem',
|
|
pdfUrl: '/assets/certificates/iso9001.pdf',
|
|
type: 'iso',
|
|
date: '14.12.2023',
|
|
},
|
|
{
|
|
title: 'DIN EN ISO 45001:2018',
|
|
description: 'Arbeits- und Gesundheitsschutz',
|
|
pdfUrl: '/assets/certificates/iso45001.pdf',
|
|
type: 'iso',
|
|
date: '05.12.2025',
|
|
},
|
|
{
|
|
title: 'Freistellungsbescheinigung',
|
|
description: 'Nach § 48 b EStG',
|
|
pdfUrl: '/assets/certificates/freistellung.pdf',
|
|
type: 'tax',
|
|
date: '09.02.2024',
|
|
},
|
|
{
|
|
title: 'Nachweis § 13b UStG',
|
|
description: 'Steuerschuldnerschaft des Leistungsempfängers',
|
|
pdfUrl: '/assets/certificates/nachweis13b.pdf',
|
|
type: 'tax',
|
|
date: '09.02.2024',
|
|
},
|
|
{
|
|
title: 'Bescheinigung in Steuersachen',
|
|
description: 'Zertifikat des Finanzamtes',
|
|
pdfUrl: '/assets/certificates/bescheinigung.pdf',
|
|
type: 'tax',
|
|
date: '13.02.2025',
|
|
},
|
|
|
|
];
|
|
|
|
export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {
|
|
const [isMounted, setIsMounted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setIsMounted(true);
|
|
}, []);
|
|
|
|
const badgeText = badge || 'Zertifizierungen & Nachweise';
|
|
const titleText = title || 'Geprüfte Qualität und Sicherheit';
|
|
const descriptionText = description || 'Transparenz und höchste Standards sind das Fundament unserer Arbeit. Hier finden Sie unsere aktuellen Zertifikate und Bescheinigungen als PDF-Download.';
|
|
|
|
const containerVariants = {
|
|
hidden: { opacity: 0 },
|
|
show: {
|
|
opacity: 1,
|
|
transition: {
|
|
staggerChildren: 0.1,
|
|
},
|
|
},
|
|
};
|
|
|
|
const itemVariants = {
|
|
hidden: { opacity: 0, y: 20 },
|
|
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: 'easeOut' as const } },
|
|
};
|
|
|
|
if (!isMounted) {
|
|
return (
|
|
<section className="py-16 md:py-24 lg:py-32 bg-neutral-50 relative overflow-hidden">
|
|
<Container>
|
|
{!hideHeader && (
|
|
<div className="text-center max-w-3xl mx-auto mb-16">
|
|
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badgeText}</h2>
|
|
<h3 className="font-heading text-2xl md:text-5xl font-extrabold text-neutral-dark mb-6">{titleText}</h3>
|
|
<p className="text-lg text-neutral-600">{descriptionText}</p>
|
|
</div>
|
|
)}
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="py-12 md:py-24 bg-neutral-50 relative overflow-hidden">
|
|
{/* Background Accents */}
|
|
<div className="absolute top-0 right-0 w-1/3 h-full bg-primary/5 -skew-x-12 translate-x-1/2" />
|
|
<div className="absolute bottom-0 left-0 w-1/4 h-1/2 bg-accent/5 skew-y-12 -translate-x-1/2 blur-3xl rounded-full" />
|
|
|
|
<Container className="relative z-10">
|
|
{!hideHeader && (
|
|
<div className="text-center max-w-3xl mx-auto mb-16">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badgeText}</h2>
|
|
<h3 className="font-heading text-2xl md:text-5xl font-extrabold text-neutral-dark mb-6">{titleText}</h3>
|
|
<p className="text-lg text-neutral-600">{descriptionText}</p>
|
|
</motion.div>
|
|
</div>
|
|
)}
|
|
|
|
<motion.div
|
|
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
variants={containerVariants}
|
|
initial="hidden"
|
|
whileInView="show"
|
|
viewport={{ once: true, amount: 0.1 }}
|
|
>
|
|
{certificates.map((cert, index) => {
|
|
const isIso = cert.type === 'iso';
|
|
const Wrapper = cert.pdfUrl ? motion.a : motion.div;
|
|
const wrapperProps = cert.pdfUrl ? {
|
|
href: encodeURI(cert.pdfUrl),
|
|
target: "_blank",
|
|
rel: "noopener noreferrer"
|
|
} : {};
|
|
|
|
return (
|
|
<Wrapper
|
|
key={index}
|
|
{...wrapperProps as any}
|
|
variants={itemVariants}
|
|
className={`group relative rounded-2xl p-6 md:p-8 flex flex-col justify-between overflow-hidden transition-all duration-500 hover:shadow-xl hover:-translate-y-1 border ${
|
|
isIso
|
|
? 'bg-neutral-dark text-white border-neutral-800 shadow-lg'
|
|
: 'bg-white text-neutral-dark border-neutral-200 shadow-md'
|
|
}`}
|
|
>
|
|
{/* Background Decorative Elements */}
|
|
<div className={`absolute top-0 right-0 w-32 h-32 blur-3xl rounded-full -mr-10 -mt-10 transition-colors duration-500 ${
|
|
isIso ? 'bg-primary/20 group-hover:bg-primary/40' : 'bg-primary/10 group-hover:bg-primary/20'
|
|
}`} />
|
|
|
|
{isIso && (
|
|
<div className="absolute -bottom-[20%] -right-[20%] w-[100%] h-[100%] z-0 opacity-10 group-hover:opacity-20 transition-opacity duration-1000 pointer-events-none mix-blend-overlay">
|
|
<LogoArcs className="w-full h-full text-white animate-[spin_60s_linear_infinite]" />
|
|
</div>
|
|
)}
|
|
|
|
<HoverShineOverlay shineColor={isIso ? 'via-white/20' : 'via-black/5'} />
|
|
<AnimatedGlossyBorder color={isIso ? 'primary' : 'primary'} className="opacity-0 group-hover:opacity-100 transition-opacity duration-700" borderWidth={2} />
|
|
|
|
<div className="relative z-10 flex flex-col h-full">
|
|
<div className="flex justify-between items-start mb-8">
|
|
<div className={`p-3 rounded-xl inline-flex ${
|
|
isIso ? 'bg-primary/20 text-primary-light backdrop-blur-md' : 'bg-primary/10 text-primary'
|
|
}`}>
|
|
{isIso ? <Award size={28} /> : <ShieldCheck size={28} />}
|
|
</div>
|
|
{cert.date && (
|
|
<span className={`text-xs font-bold uppercase tracking-wider px-3 py-1 rounded-full ${
|
|
isIso ? 'bg-neutral-800 text-neutral-400 border border-neutral-700' : 'bg-neutral-100 text-neutral-500 border border-neutral-200'
|
|
}`}>
|
|
{cert.date}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className={`text-xl font-bold mb-2 ${isIso ? 'text-white' : 'text-neutral-dark'}`}>
|
|
{cert.title}
|
|
</h4>
|
|
<p className={`text-sm mb-6 ${isIso ? 'text-neutral-400' : 'text-neutral-600'}`}>
|
|
{cert.description}
|
|
</p>
|
|
</div>
|
|
|
|
<div className={`mt-auto inline-flex items-center text-sm font-bold uppercase tracking-wider transition-all ${
|
|
cert.pdfUrl ? 'group-hover:gap-3' : ''
|
|
} ${
|
|
isIso ? 'text-primary-light' : 'text-primary'
|
|
}`}>
|
|
{cert.pdfUrl ? (
|
|
<>
|
|
<span>Download PDF</span>
|
|
<Download size={16} className="ml-2 group-hover:translate-y-0.5 transition-transform" />
|
|
</>
|
|
) : (
|
|
<span className="text-neutral-400">Nachweis liegt vor</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Wrapper>
|
|
);
|
|
})}
|
|
</motion.div>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|