Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 1m0s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
222 lines
9.0 KiB
TypeScript
222 lines
9.0 KiB
TypeScript
'use client';
|
|
|
|
import Scribble from '@/components/Scribble';
|
|
import { Button, Container, Heading, Section } from '@/components/ui';
|
|
import { useTranslations, useLocale } from 'next-intl';
|
|
import dynamic from 'next/dynamic';
|
|
import { useAnalytics } from '../analytics/useAnalytics';
|
|
import { AnalyticsEvents } from '../analytics/analytics-events';
|
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
import { ChevronRight } from 'lucide-react';
|
|
import { AISearchResults } from '../search/AISearchResults';
|
|
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
|
|
const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
|
|
|
|
export default function Hero({ data }: { data?: any }) {
|
|
const t = useTranslations('Home.hero');
|
|
const locale = useLocale();
|
|
const { trackEvent } = useAnalytics();
|
|
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
|
const [heroPlaceholder, setHeroPlaceholder] = useState(
|
|
'Projekt beschreiben oder Kabel suchen...',
|
|
);
|
|
const typingRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
const HERO_PLACEHOLDERS = [
|
|
'Querschnittsberechnung für 110kV Trasse', // Hochspannung
|
|
'Wie schwer ist NAYY 4x150?',
|
|
'Ich plane einen Solarpark, was brauche ich?', // Projekt Solar
|
|
'Unterschied zwischen N2XSY und NAY2XSY?', // Fach
|
|
'Mittelspannungskabel für Windkraftanlage', // Windpark
|
|
'Welches Aluminiumkabel für 20kV?', // Mittelspannung
|
|
];
|
|
|
|
// Typing animation for the hero search placeholder
|
|
useEffect(() => {
|
|
if (searchQuery) {
|
|
setHeroPlaceholder('Projekt beschreiben oder Kabel suchen...');
|
|
return;
|
|
}
|
|
|
|
let textIdx = 0;
|
|
let charIdx = 0;
|
|
let deleting = false;
|
|
|
|
const tick = () => {
|
|
const fullText = HERO_PLACEHOLDERS[textIdx];
|
|
|
|
if (deleting) {
|
|
charIdx--;
|
|
setHeroPlaceholder(fullText.substring(0, charIdx));
|
|
} else {
|
|
charIdx++;
|
|
setHeroPlaceholder(fullText.substring(0, charIdx));
|
|
}
|
|
|
|
let delay = deleting ? 30 : 70;
|
|
|
|
if (!deleting && charIdx === fullText.length) {
|
|
delay = 2500;
|
|
deleting = true;
|
|
} else if (deleting && charIdx === 0) {
|
|
deleting = false;
|
|
textIdx = (textIdx + 1) % HERO_PLACEHOLDERS.length;
|
|
delay = 400;
|
|
}
|
|
|
|
typingRef.current = setTimeout(tick, delay);
|
|
};
|
|
|
|
typingRef.current = setTimeout(tick, 1500);
|
|
|
|
return () => {
|
|
if (typingRef.current) clearTimeout(typingRef.current);
|
|
};
|
|
}, [searchQuery]);
|
|
|
|
const handleSearchSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (searchQuery.trim()) {
|
|
setIsSearchOpen(true);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Section className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
|
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
|
|
<div className="max-w-5xl mx-auto md:mx-0">
|
|
<div>
|
|
<Heading
|
|
level={1}
|
|
className="text-center md:text-left mb-6 md:mb-8 md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl font-extrabold [text-shadow:_-2px_-2px_0_#002b49,_2px_-2px_0_#002b49,_-2px_2px_0_#002b49,_2px_2px_0_#002b49,_-2px_0_0_#002b49,_2px_0_0_#002b49,_0_-2px_0_#002b49,_0_2px_0_#002b49]"
|
|
>
|
|
{data?.title ? (
|
|
<span
|
|
dangerouslySetInnerHTML={{
|
|
__html: data.title
|
|
.replace(
|
|
/<green>/g,
|
|
'<span class="relative inline-block"><span class="relative z-10 text-accent italic inline-block">',
|
|
)
|
|
.replace(
|
|
/<\/green>/g,
|
|
'</span><div class="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute -z-10 animate-in fade-in zoom-in-0 duration-1000 ease-out fill-mode-both" style="animation-delay: 500ms;"><Scribble variant="circle" /></div></span>',
|
|
),
|
|
}}
|
|
/>
|
|
) : (
|
|
t.rich('title', {
|
|
green: (chunks) => (
|
|
<span className="relative inline-block">
|
|
<span className="relative z-10 text-accent italic inline-block">
|
|
{chunks}
|
|
</span>
|
|
<div
|
|
className="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute -z-10 animate-in fade-in zoom-in-0 duration-1000 ease-out fill-mode-both"
|
|
style={{ animationDelay: '500ms' }}
|
|
>
|
|
<Scribble variant="circle" />
|
|
</div>
|
|
</span>
|
|
),
|
|
})
|
|
)}
|
|
</Heading>
|
|
</div>
|
|
<div>
|
|
<p className="text-lg md:text-xl text-white leading-relaxed max-w-2xl mb-10 md:mb-12">
|
|
{data?.subtitle || t('subtitle')}
|
|
</p>
|
|
</div>
|
|
<form
|
|
onSubmit={handleSearchSubmit}
|
|
className="w-full max-w-2xl bg-white/10 backdrop-blur-md border border-white/20 rounded-2xl p-2 flex items-center mt-8 mb-10 transition-all focus-within:bg-white/15 focus-within:border-accent shadow-lg relative"
|
|
>
|
|
<div className="absolute left-1 w-20 h-20 flex items-center justify-center z-10 overflow-visible">
|
|
<AIOrb isThinking={false} />
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder={heroPlaceholder}
|
|
className="flex-1 bg-transparent border-none text-white pl-20 pr-2 py-4 placeholder:text-white/50 focus:outline-none text-lg lg:text-xl"
|
|
autoFocus
|
|
/>
|
|
<Button
|
|
type="submit"
|
|
variant="accent"
|
|
size="lg"
|
|
className="rounded-xl px-6 py-4 shrink-0 flex items-center shadow-md font-bold cursor-pointer hover:bg-accent hover:brightness-110"
|
|
>
|
|
Fragen
|
|
<ChevronRight className="w-5 h-5 ml-2 -mr-1" />
|
|
</Button>
|
|
</form>
|
|
|
|
<div className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6">
|
|
<div>
|
|
<Button
|
|
href="/contact"
|
|
variant="white"
|
|
size="lg"
|
|
className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg hover:scale-105 transition-all outline-none"
|
|
onClick={() =>
|
|
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
|
|
label: data?.ctaLabel || t('cta'),
|
|
location: 'home_hero_primary',
|
|
})
|
|
}
|
|
>
|
|
{data?.ctaLabel || t('cta')}
|
|
<span className="transition-transform group-hover/btn:translate-x-1 ml-2">
|
|
→
|
|
</span>
|
|
</Button>
|
|
</div>
|
|
<div>
|
|
<Button
|
|
href={`/${locale}/${locale === 'de' ? 'produkte' : 'products'}`}
|
|
variant="outline"
|
|
size="lg"
|
|
className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg text-white border-white/30 hover:bg-white/10 hover:border-white transition-all"
|
|
onClick={() =>
|
|
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
|
|
label: data?.secondaryCtaLabel || t('exploreProducts'),
|
|
location: 'home_hero_secondary',
|
|
})
|
|
}
|
|
>
|
|
{data?.secondaryCtaLabel || t('exploreProducts')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
|
|
<div className="relative md:absolute inset-0 z-0 w-full h-[40vh] md:h-full order-1 md:order-none mb-[-80px] md:mb-0 mt-[80px] md:mt-0 overflow-visible pointer-events-none animate-in fade-in zoom-in-95 duration-1000 ease-out fill-mode-both">
|
|
<HeroIllustration />
|
|
</div>
|
|
|
|
<div
|
|
className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 hidden sm:block animate-in fade-in slide-in-from-bottom-4 duration-1000 ease-out fill-mode-both"
|
|
style={{ animationDelay: '2000ms' }}
|
|
>
|
|
<div className="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center p-1">
|
|
<div className="w-1 h-2 bg-white rounded-full animate-bounce" />
|
|
</div>
|
|
</div>
|
|
</Section>
|
|
<AISearchResults
|
|
isOpen={isSearchOpen}
|
|
onClose={() => setIsSearchOpen(false)}
|
|
initialQuery={searchQuery}
|
|
triggerSearch={true}
|
|
/>
|
|
</>
|
|
);
|
|
}
|