Compare commits
5 Commits
feature/an
...
v2.2.13-rc
| Author | SHA1 | Date | |
|---|---|---|---|
| fcfb567991 | |||
| efb9bdbb3f | |||
| 11e7937e79 | |||
| 41158697fd | |||
| d00a4e29ff |
@@ -229,7 +229,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
||||
|
||||
{/* Support Section */}
|
||||
<Container>
|
||||
<div className="mt-24 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="mt-8 mb-8 md:mt-24 md: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>
|
||||
|
||||
@@ -150,7 +150,7 @@ export default async function ContactPage({ params }: ContactPageProps) {
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16">
|
||||
{/* Contact Info */}
|
||||
<div className="lg:col-span-5 space-y-6 md:space-y-12">
|
||||
<div className="lg:col-span-5 order-2 lg:order-1 space-y-6 md:space-y-12">
|
||||
<div className="animate-fade-in">
|
||||
<Heading level={3} subtitle={t('info.subtitle')} className="mb-6 md:mb-8">
|
||||
{t('info.howToReachUs')}
|
||||
@@ -249,7 +249,7 @@ export default async function ContactPage({ params }: ContactPageProps) {
|
||||
</div>
|
||||
|
||||
{/* Contact Form */}
|
||||
<div className="lg:col-span-7">
|
||||
<div className="lg:col-span-7 order-1 lg:order-2">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl"></div>
|
||||
|
||||
@@ -206,7 +206,7 @@ export default async function Layout(props: {
|
||||
|
||||
<main
|
||||
id="main-content"
|
||||
className="flex-grow animate-fade-in overflow-visible pb-[90px] md:pb-0"
|
||||
className="flex-grow animate-fade-in overflow-visible"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -3,19 +3,41 @@ import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import { getAllReferences } from '@/lib/references';
|
||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||
import { MapPin, Calendar, Briefcase } from 'lucide-react';
|
||||
import { MapPin, Calendar, Briefcase, Zap, Activity, ShieldCheck, Wrench, CheckCircle2 } from 'lucide-react';
|
||||
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import Image from 'next/image';
|
||||
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
||||
import { HeroSection } from '@/components/blocks/HeroSection';
|
||||
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
||||
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
|
||||
const CustomLi = ({ children, ...props }: any) => {
|
||||
let text = '';
|
||||
if (typeof children === 'string') text = children;
|
||||
else if (Array.isArray(children)) text = children.map((c: any) => typeof c === 'string' ? c : '').join(' ');
|
||||
else if (children?.props?.children) text = children.props.children;
|
||||
|
||||
const lowerText = String(text).toLowerCase();
|
||||
|
||||
let Icon = CheckCircle2;
|
||||
if (lowerText.includes('kabel')) Icon = Zap;
|
||||
else if (lowerText.includes('bohrung')) Icon = Activity;
|
||||
else if (lowerText.includes('rohr')) Icon = ShieldCheck;
|
||||
else if (lowerText.includes('montage')) Icon = Wrench;
|
||||
else if (lowerText.includes('vlf') || lowerText.includes('otdr')) Icon = Zap;
|
||||
|
||||
return (
|
||||
<li className="flex items-start gap-3 p-3 bg-neutral-50/50 border border-neutral-100 rounded-xl" {...props}>
|
||||
<Icon className="w-4 h-4 text-primary shrink-0 mt-[2px]" />
|
||||
<span className="text-sm font-medium text-neutral-700 leading-tight">{children}</span>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const mdxComponents = {
|
||||
ul: (props: any) => <ul className="list-disc pl-5 mb-6 text-neutral-600 text-sm space-y-2" {...props} />,
|
||||
li: (props: any) => <li className="pl-1" {...props} />,
|
||||
ul: (props: any) => <ul className="grid grid-cols-1 gap-2 mb-6" {...props} />,
|
||||
li: CustomLi,
|
||||
p: (props: any) => <p className="text-neutral-600 text-sm mb-4 leading-relaxed" {...props} />,
|
||||
};
|
||||
|
||||
@@ -80,7 +102,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24">
|
||||
<div className="flex flex-col min-h-screen bg-neutral-50 pb-8 md:pb-24">
|
||||
{/* Map Section */}
|
||||
<InteractiveGermanyMap
|
||||
isHero={true}
|
||||
@@ -165,7 +187,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
</div>
|
||||
|
||||
{/* Support Section */}
|
||||
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
|
||||
<div className="mt-8 md:mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
|
||||
<div className="relative z-10 max-w-2xl">
|
||||
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('nextProjectTitle')}</h3>
|
||||
|
||||
@@ -158,7 +158,7 @@ export default async function StandortDetail(props: { params: Promise<{ locale:
|
||||
|
||||
{/* 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="mt-8 mb-8 md:mt-24 md: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>
|
||||
|
||||
@@ -114,7 +114,7 @@ export default async function StandorteOverview(props: { params: Promise<{ local
|
||||
</div>
|
||||
|
||||
{/* Support Section */}
|
||||
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
|
||||
<div className="mt-12 md:mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
|
||||
<div className="relative z-10 max-w-2xl">
|
||||
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('nextProjectTitle')}</h3>
|
||||
|
||||
@@ -239,30 +239,31 @@ export function Annotator({ assets = [], onSubmit }: AnnotatorProps) {
|
||||
return (
|
||||
<div className="annotator-ui-ignore">
|
||||
{/* 1. Global Toolbar */}
|
||||
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-[9999] w-max max-w-[95vw]">
|
||||
<div className="bg-black/80 backdrop-blur-xl border border-white/10 p-2 rounded-2xl shadow-2xl flex items-center gap-1 sm:gap-2 overflow-x-auto hide-scrollbar" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
||||
<div className="fixed bottom-24 right-4 z-[9999] flex flex-col items-end gap-2">
|
||||
<div className="bg-black/80 backdrop-blur-xl border border-white/10 p-1.5 rounded-2xl shadow-2xl flex flex-col items-center gap-1">
|
||||
<button
|
||||
onClick={() => setIsActive(!isActive)}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-4 py-2 rounded-xl transition-all font-medium",
|
||||
"p-2.5 rounded-xl transition-all",
|
||||
isActive
|
||||
? "bg-blue-500 text-white shadow-lg shadow-blue-500/20"
|
||||
: "text-white/70 hover:text-white hover:bg-white/10"
|
||||
)}
|
||||
title={isActive ? "Modus beenden" : "Korrekturen erfassen"}
|
||||
>
|
||||
{isActive ? <X size={18} /> : <MessageSquare size={18} />}
|
||||
{isActive ? "Modus beenden" : "Korrekturen erfassen"}
|
||||
{isActive ? <X size={20} /> : <MessageSquare size={20} />}
|
||||
</button>
|
||||
|
||||
<div className="w-px h-6 bg-white/10 mx-1" />
|
||||
<div className="w-6 h-px bg-white/10 my-1" />
|
||||
|
||||
<button
|
||||
onClick={() => setShowList(!showList)}
|
||||
className="p-2 text-white/70 hover:text-white hover:bg-white/10 rounded-xl relative"
|
||||
className="p-2.5 text-white/70 hover:text-white hover:bg-white/10 rounded-xl relative"
|
||||
title="Liste anzeigen"
|
||||
>
|
||||
<List size={20} />
|
||||
{annotations.length > 0 && (
|
||||
<span className="absolute -top-1 -right-1 w-5 h-5 bg-blue-500 text-[10px] flex items-center justify-center rounded-full text-white font-bold border-2 border-[#1a1a1a]">
|
||||
<span className="absolute -top-1 -right-1 w-4 h-4 bg-blue-500 text-[9px] flex items-center justify-center rounded-full text-white font-bold border border-[#1a1a1a]">
|
||||
{annotations.length}
|
||||
</span>
|
||||
)}
|
||||
@@ -270,13 +271,13 @@ export function Annotator({ assets = [], onSubmit }: AnnotatorProps) {
|
||||
|
||||
{annotations.length > 0 && (
|
||||
<>
|
||||
<div className="w-px h-6 bg-white/10 mx-1" />
|
||||
<div className="w-6 h-px bg-white/10 my-1" />
|
||||
<button
|
||||
onClick={exportJSON}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-xl text-white/70 hover:text-white hover:bg-white/10 transition-all font-medium"
|
||||
className="p-2.5 rounded-xl text-white/70 hover:text-white hover:bg-white/10 transition-all"
|
||||
title="JSON Export"
|
||||
>
|
||||
<Download size={18} />
|
||||
JSON Export
|
||||
<Download size={20} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -99,7 +99,7 @@ export function BenefitGrid({ badge, title, description, benefits }: BenefitGrid
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
>
|
||||
{benefits.map((benefit, index) => {
|
||||
const bgPositionClasses = [
|
||||
|
||||
@@ -127,8 +127,8 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
|
||||
|
||||
<HoverShineOverlay shineColor="via-white/30" />
|
||||
<AnimatedGlossyBorder color="white" className="opacity-0 group-hover:opacity-100 transition-opacity duration-700" borderWidth={1} />
|
||||
<h4 className="font-bold text-xl md:text-2xl relative z-10 leading-snug">{item.title}</h4>
|
||||
{item.description && <p className="text-white/60 text-sm mt-3 relative z-10">{item.description}</p>}
|
||||
<h4 className="font-heading font-extrabold text-2xl md:text-3xl relative z-10 leading-snug">{item.title}</h4>
|
||||
{item.description && <p className="text-white/80 text-base md:text-lg mt-3 relative z-10 font-medium">{item.description}</p>}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -159,7 +159,7 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
|
||||
sizes={isLarge ? "(max-width: 768px) 100vw, 50vw" : "(max-width: 768px) 100vw, 25vw"}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-90 transition-opacity duration-500 z-0" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/60 to-black/30 opacity-100 transition-opacity duration-500 z-0 group-hover:from-black/80 group-hover:via-black/50 group-hover:to-black/20" />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -177,11 +177,11 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
|
||||
{item.tag}
|
||||
</span>
|
||||
)}
|
||||
<h4 className={`font-heading font-extrabold mb-2 ${isLarge ? 'text-2xl md:text-5xl' : 'text-xl md:text-2xl'}`}>
|
||||
<h4 className="font-heading font-extrabold mb-2 text-2xl md:text-3xl">
|
||||
{item.title}
|
||||
</h4>
|
||||
{item.description && (
|
||||
<p className={`text-white/80 ${isLarge ? 'text-lg md:text-xl max-w-sm' : 'text-sm'}`}>
|
||||
<p className="text-white/90 text-sm md:text-base max-w-md">
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -49,7 +49,7 @@ export const ContactSection: React.FC<ContactSectionProps> = (props) => {
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
>
|
||||
<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-3xl md:text-4xl font-extrabold text-neutral-dark mb-6">Wir sind für Sie da.</motion.h3>
|
||||
@@ -104,7 +104,7 @@ export const ContactSection: React.FC<ContactSectionProps> = (props) => {
|
||||
variants={formVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
>
|
||||
<Suspense
|
||||
fallback={
|
||||
|
||||
@@ -45,7 +45,7 @@ export function GrowthChart() {
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${kabelWidth}%` }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ duration: 1.5, delay: 0.1 * index, ease: "easeOut" }}
|
||||
className="absolute top-0 left-0 h-full bg-primary rounded-full"
|
||||
/>
|
||||
@@ -61,7 +61,7 @@ export function GrowthChart() {
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${offenerWidth}%` }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ duration: 1.5, delay: 0.15 + 0.1 * index, ease: "easeOut" }}
|
||||
className="absolute top-0 left-0 h-full bg-neutral-300 rounded-full"
|
||||
/>
|
||||
@@ -77,7 +77,7 @@ export function GrowthChart() {
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${hddWidth}%` }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ duration: 1.5, delay: 0.2 + 0.1 * index, ease: "easeOut" }}
|
||||
className="absolute top-0 left-0 h-full bg-neutral-dark rounded-full"
|
||||
/>
|
||||
|
||||
@@ -92,7 +92,7 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
||||
</motion.div>
|
||||
)}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Heading level={1} variant="white" align={alignment || 'left'} className="mb-4 md:mb-8">
|
||||
<Heading level={1} size="section" variant="white" align={alignment || 'left'} className="mb-4 md:mb-8">
|
||||
{title}
|
||||
</Heading>
|
||||
</motion.div>
|
||||
@@ -104,7 +104,7 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
||||
</motion.div>
|
||||
)}
|
||||
{ctaLabel && ctaHref && (
|
||||
<motion.div variants={itemVariants} className={`mt-8 ${alignment === 'center' ? 'flex justify-center' : ''}`}>
|
||||
<motion.div variants={itemVariants} className={`mt-8 ${alignment === 'center' ? 'flex flex-wrap justify-center gap-4' : 'flex flex-wrap gap-4'}`}>
|
||||
<Button
|
||||
href={ctaHref}
|
||||
variant="accent"
|
||||
|
||||
@@ -96,7 +96,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
||||
{subtitle}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-6">
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 md:gap-6">
|
||||
<Button
|
||||
href={ctaHref}
|
||||
variant="primary"
|
||||
|
||||
@@ -75,7 +75,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
<motion.h3
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
|
||||
className="font-heading font-extrabold text-3xl md:text-4xl text-neutral-dark mb-8 md:mb-12 flex items-center gap-4"
|
||||
>
|
||||
@@ -86,7 +86,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
|
||||
>
|
||||
{fairs.map((messe, idx) => {
|
||||
@@ -180,7 +180,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
|
||||
className="font-heading font-extrabold text-3xl md:text-4xl text-neutral-dark mb-6 md:mb-8"
|
||||
>
|
||||
@@ -192,7 +192,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
className="grid gap-6"
|
||||
>
|
||||
{jobs.map((job: any) => (
|
||||
@@ -221,7 +221,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
|
||||
className="bg-neutral-50 border border-neutral-100 rounded-2xl p-12 text-center"
|
||||
>
|
||||
|
||||
@@ -94,7 +94,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
|
||||
|
||||
return (
|
||||
<section id="referenzen" className="py-16 md:py-24 lg:py-32 bg-neutral-dark text-white relative overflow-hidden">
|
||||
<section id="referenzen" className="pt-16 pb-8 md:py-24 lg:py-32 bg-neutral-dark text-white relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-neutral-dark via-neutral-900 to-neutral-dark z-0" />
|
||||
|
||||
<div className="container relative z-10 mb-12 flex flex-col md:flex-row justify-between items-start md:items-end gap-6">
|
||||
@@ -115,7 +115,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseMove={onMouseMove}
|
||||
className={`select-none flex gap-6 overflow-x-auto pb-12 pt-4 px-[max(1rem,calc((100vw-1280px)/2))] md:px-[max(2rem,calc((100vw-1280px)/2))] lg:px-[max(2.5rem,calc((100vw-1280px)/2))] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
|
||||
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-6 md:px-[max(2rem,calc((100vw-1280px)/2))] lg:px-[max(2.5rem,calc((100vw-1280px)/2))] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
|
||||
>
|
||||
{references.map((ref, i) => {
|
||||
const imgSrc = (ref.image && typeof ref.image === 'object' && ref.image.url)
|
||||
|
||||
@@ -38,7 +38,7 @@ export function ScaleOfImpact() {
|
||||
{/* Foreground Content */}
|
||||
<div className="relative z-10 container max-w-7xl mx-auto px-4 flex flex-col items-center text-center">
|
||||
|
||||
<h2 className="text-2xl md:text-5xl font-heading font-extrabold text-white mb-6 tracking-tight">
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-heading font-extrabold text-white mb-6 tracking-tight leading-tight">
|
||||
{locale === 'en' ? (
|
||||
<>An infrastructure <br className="md:hidden"/> that connects the country.</>
|
||||
) : (
|
||||
|
||||
@@ -136,7 +136,7 @@ export function ServiceDetailGrid({
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="max-w-4xl mx-auto mb-20 grid md:grid-cols-2 gap-0 bg-white rounded-2xl overflow-hidden shadow-lg border border-neutral-200 text-left"
|
||||
>
|
||||
@@ -173,7 +173,7 @@ export function ServiceDetailGrid({
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
>
|
||||
{panels.map((panel, idx) => {
|
||||
const isFullWidth = panel.fullWidth;
|
||||
|
||||
@@ -68,7 +68,7 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
|
||||
})) || defaultCompanies;
|
||||
|
||||
return (
|
||||
<section id="unternehmen" className="py-16 md:py-24 lg:py-32 bg-neutral-dark border-b border-neutral-800 relative overflow-hidden">
|
||||
<section id="unternehmen" className="pt-12 pb-16 md:py-24 lg:py-32 bg-neutral-dark border-b border-neutral-800 relative overflow-hidden">
|
||||
<div className="container relative z-10 px-4">
|
||||
|
||||
{(badge || title) && (
|
||||
|
||||
@@ -44,7 +44,7 @@ export function TeamGrid({ members }: TeamGridProps) {
|
||||
key={member.id}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
viewport={{ once: true, amount: 0.1 }}
|
||||
transition={{ delay: i * 0.1, duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="group flex flex-col bg-white rounded-[2rem] border border-neutral-100 shadow-[0_8px_30px_rgb(0,0,0,0.04)] hover:shadow-[0_20px_40px_rgba(238,114,3,0.1)] transition-all duration-500 hover:-translate-y-2 overflow-hidden"
|
||||
>
|
||||
|
||||
@@ -24,8 +24,6 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
|
||||
const homeUrl = `/${currentLocale}`;
|
||||
|
||||
// Find the specific links from the provided navLinks (which might be translated)
|
||||
// We do a simple match based on index or keywords if needed, but since we know the order:
|
||||
// 0: Kompetenzen, 1: Über uns, 2: Karriere, 3: Messen
|
||||
const competenciesUrl = navLinks[0]?.url || `/${currentLocale}/kompetenzen`;
|
||||
const aboutUrl = navLinks[1]?.url || `/${currentLocale}/ueber-uns`;
|
||||
const contactUrl = `/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`;
|
||||
@@ -34,30 +32,30 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
|
||||
{
|
||||
label: currentLocale === 'de' ? 'Start' : 'Home',
|
||||
url: homeUrl,
|
||||
icon: <Home className="w-6 h-6 mb-1" strokeWidth={2} />,
|
||||
icon: <Home className="w-5 h-5 md:w-6 md:h-6" strokeWidth={2.5} />,
|
||||
exactMatch: true
|
||||
},
|
||||
{
|
||||
label: navLinks[0]?.label || (currentLocale === 'de' ? 'Kompetenzen' : 'Competencies'),
|
||||
url: competenciesUrl,
|
||||
icon: <Briefcase className="w-6 h-6 mb-1" strokeWidth={2} />
|
||||
icon: <Briefcase className="w-5 h-5 md:w-6 md:h-6" strokeWidth={2.5} />
|
||||
},
|
||||
{
|
||||
label: navLinks[1]?.label || (currentLocale === 'de' ? 'Über uns' : 'About Us'),
|
||||
url: aboutUrl,
|
||||
icon: <Users className="w-6 h-6 mb-1" strokeWidth={2} />
|
||||
icon: <Users className="w-5 h-5 md:w-6 md:h-6" strokeWidth={2.5} />
|
||||
},
|
||||
{
|
||||
label: currentLocale === 'de' ? 'Kontakt' : 'Contact',
|
||||
url: contactUrl,
|
||||
icon: <Mail className="w-6 h-6 mb-1" strokeWidth={2} />
|
||||
icon: <Mail className="w-5 h-5 md:w-6 md:h-6" strokeWidth={2.5} />
|
||||
}
|
||||
];
|
||||
}, [currentLocale, navLinks]);
|
||||
|
||||
return (
|
||||
<div className="md:hidden fixed bottom-0 left-0 right-0 z-50 px-2 pb-2 pt-2 bg-gradient-to-t from-white via-white to-white/90 backdrop-blur-xl border-t border-neutral-200/50 shadow-[0_-8px_32px_rgba(0,0,0,0.05)] safe-area-bottom">
|
||||
<nav className="flex justify-around items-center max-w-md mx-auto">
|
||||
<div className="md:hidden fixed bottom-0 left-0 right-0 z-[9999] px-4 pb-[calc(env(safe-area-inset-bottom)+1rem)] pt-4 pointer-events-none flex justify-center">
|
||||
<nav className="flex justify-between items-center bg-white/70 backdrop-blur-3xl border border-white/60 shadow-[0_8px_32px_rgba(0,0,0,0.12)] rounded-3xl p-1.5 pointer-events-auto w-full max-w-[400px]">
|
||||
{items.map((item) => {
|
||||
// Determine if active
|
||||
const mappedUrl = item.url.startsWith('/') && !item.url.match(/^\/(en|de)/)
|
||||
@@ -72,26 +70,34 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
|
||||
<TransitionLink
|
||||
key={item.label}
|
||||
href={mappedUrl}
|
||||
className={`relative flex flex-col items-center justify-center w-full min-h-[44px] py-1 transition-colors duration-300 select-none ${
|
||||
isActive ? 'text-primary' : 'text-neutral-500 hover:text-neutral-900'
|
||||
className={`relative flex flex-col items-center justify-center flex-1 h-[60px] transition-colors duration-300 select-none ${
|
||||
isActive ? 'text-white' : 'text-neutral-500 hover:text-neutral-900'
|
||||
}`}
|
||||
style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }}
|
||||
>
|
||||
<div className="relative flex flex-col items-center justify-center">
|
||||
{/* Active Icon Background */}
|
||||
{isActive && (
|
||||
<motion.div
|
||||
layoutId="mobile-nav-active-bg"
|
||||
className="absolute inset-0 bg-primary/10 rounded-full scale-150"
|
||||
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Icon wrapper to ensure it sits above the background */}
|
||||
<div className="relative z-10 transition-transform duration-300 active:scale-90">
|
||||
{isActive && (
|
||||
<motion.div
|
||||
layoutId="mobile-nav-active-bg"
|
||||
className="absolute inset-0 bg-primary rounded-2xl shadow-md"
|
||||
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="relative flex flex-col items-center justify-center z-10 w-full h-full">
|
||||
<motion.div
|
||||
whileTap={{ scale: 0.8 }}
|
||||
animate={{ y: isActive ? -2 : 0 }}
|
||||
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
|
||||
>
|
||||
{item.icon}
|
||||
</div>
|
||||
</motion.div>
|
||||
<motion.span
|
||||
animate={{ opacity: isActive ? 1 : 0.7, y: isActive ? 0 : 2 }}
|
||||
className="text-[10px] font-bold mt-1 tracking-wide"
|
||||
>
|
||||
{item.label}
|
||||
</motion.span>
|
||||
</div>
|
||||
<span className="text-[10px] font-semibold mt-0.5 tracking-wide z-10">{item.label}</span>
|
||||
</TransitionLink>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
'use client';
|
||||
|
||||
import React, { ReactNode, useState, useEffect, useRef } from 'react';
|
||||
|
||||
interface TooltipProps {
|
||||
children: ReactNode;
|
||||
@@ -6,10 +8,33 @@ interface TooltipProps {
|
||||
}
|
||||
|
||||
export function Tooltip({ children, content }: TooltipProps) {
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
||||
setIsActive(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.removeEventListener('touchstart', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="group relative flex items-center justify-center">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="group relative flex items-center justify-center cursor-help"
|
||||
onClick={() => setIsActive(!isActive)}
|
||||
onMouseEnter={() => setIsActive(true)}
|
||||
onMouseLeave={() => setIsActive(false)}
|
||||
>
|
||||
{children}
|
||||
<div className="absolute bottom-full mb-3 invisible opacity-0 translate-y-2 group-hover:visible group-hover:opacity-100 group-hover:translate-y-0 w-max max-w-[250px] px-4 py-2.5 text-[11px] md:text-xs text-white bg-neutral-900/95 backdrop-blur-md rounded-xl shadow-2xl border border-white/10 transition-all duration-300 pointer-events-none z-50 text-center leading-relaxed font-normal normal-case tracking-normal">
|
||||
<div className={`absolute bottom-full mb-3 w-max max-w-[250px] px-4 py-2.5 text-[11px] md:text-xs text-white bg-neutral-900/95 backdrop-blur-md rounded-xl shadow-2xl border border-white/10 transition-all duration-300 z-50 text-center leading-relaxed font-normal normal-case tracking-normal ${isActive ? 'visible opacity-100 translate-y-0 pointer-events-auto' : 'invisible opacity-0 translate-y-2 pointer-events-none'}`}>
|
||||
{content}
|
||||
{/* Triangle Arrow */}
|
||||
<div className="absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-neutral-900/95" />
|
||||
|
||||
Reference in New Issue
Block a user