Compare commits

...

13 Commits

Author SHA1 Message Date
6adf97a096 wip
Some checks failed
Build & Deploy / deploy (push) Failing after 20m37s
2026-01-19 20:15:14 +01:00
4abcc3fdf5 wip 2026-01-19 19:31:53 +01:00
6797303628 wip 2026-01-19 19:29:44 +01:00
2feb73b982 wip 2026-01-19 19:22:01 +01:00
b99258f886 wip 2026-01-19 19:16:42 +01:00
5c9b2e3f5a wip 2026-01-19 19:09:32 +01:00
e8b6b13a3b wip 2026-01-19 18:07:39 +01:00
1f624f3d7f wip 2026-01-19 17:43:23 +01:00
40c553d6f6 wip 2026-01-19 14:11:41 +01:00
a32c12692c wip 2026-01-19 02:23:42 +01:00
79016fbe97 wip 2026-01-19 02:19:11 +01:00
4f6264f2e2 wip 2026-01-19 02:05:30 +01:00
46266a7bbc components 2026-01-19 01:46:07 +01:00
89 changed files with 1993 additions and 1652 deletions

View File

@@ -2,6 +2,7 @@ import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { Section, Container, Heading, Badge } from '@/components/ui';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
interface PageProps {
params: {
@@ -10,6 +11,36 @@ interface PageProps {
};
}
export async function generateMetadata({ params: { locale, slug } }: PageProps): Promise<Metadata> {
const { getPageBySlug } = await import('@/lib/pages');
const pageData = await getPageBySlug(slug, locale);
if (!pageData) return {};
return {
title: pageData.frontmatter.title,
description: pageData.frontmatter.excerpt || '',
alternates: {
canonical: `/${locale}/${slug}`,
languages: {
'de': `/de/${slug}`,
'en': `/en/${slug}`,
'x-default': `/en/${slug}`,
},
},
openGraph: {
title: `${pageData.frontmatter.title} | KLZ Cables`,
description: pageData.frontmatter.excerpt || '',
url: `https://klz-cables.com/${locale}/${slug}`,
},
twitter: {
card: 'summary_large_image',
title: `${pageData.frontmatter.title} | KLZ Cables`,
description: pageData.frontmatter.excerpt || '',
},
};
}
export default async function StandardPage({ params: { locale, slug } }: PageProps) {
const { getPageBySlug } = await import('@/lib/pages');
const pageData = await getPageBySlug(slug, locale);

View File

@@ -1,6 +1,6 @@
import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { getPostBySlug, getAdjacentPosts } from '@/lib/blog';
import { getPostBySlug, getAdjacentPosts, getReadingTime, getHeadings } from '@/lib/blog';
import { Metadata } from 'next';
interface BlogPostProps {
@@ -16,12 +16,19 @@ export async function generateMetadata({ params: { locale, slug } }: BlogPostPro
if (!post) return {};
const description = post.frontmatter.excerpt || '';
return {
title: post.frontmatter.title,
description: description,
alternates: {
canonical: `/${locale}/blog/${slug}`,
languages: {
'de': `/de/blog/${slug}`,
'en': `/en/blog/${slug}`,
'x-default': `/en/blog/${slug}`,
},
},
openGraph: {
title: post.frontmatter.title,
title: `${post.frontmatter.title} | KLZ Cables`,
description: description,
type: 'article',
publishedTime: post.frontmatter.date,
@@ -30,7 +37,7 @@ export async function generateMetadata({ params: { locale, slug } }: BlogPostPro
},
twitter: {
card: 'summary_large_image',
title: post.frontmatter.title,
title: `${post.frontmatter.title} | KLZ Cables`,
description: description,
},
};
@@ -38,7 +45,7 @@ export async function generateMetadata({ params: { locale, slug } }: BlogPostPro
import Link from 'next/link';
import VisualLinkPreview from '@/components/blog/VisualLinkPreview';
import Callout from '@/components/blog/Callout';
import { Callout } from '@/components/ui';
import HighlightBox from '@/components/blog/HighlightBox';
import Stats from '@/components/blog/Stats';
import AnimatedImage from '@/components/blog/AnimatedImage';
@@ -46,7 +53,10 @@ import ChatBubble from '@/components/blog/ChatBubble';
import SplitHeading from '@/components/blog/SplitHeading';
import PostNavigation from '@/components/blog/PostNavigation';
import PowerCTA from '@/components/blog/PowerCTA';
import ShareButton from '@/components/blog/ShareButton';
import TableOfContents from '@/components/blog/TableOfContents';
import StickyNarrative from '@/components/blog/StickyNarrative';
import TechnicalGrid from '@/components/blog/TechnicalGrid';
import ComparisonGrid from '@/components/blog/ComparisonGrid';
const components = {
VisualLinkPreview,
@@ -57,6 +67,10 @@ const components = {
ChatBubble,
PowerCTA,
SplitHeading,
StickyNarrative,
TechnicalGrid,
ComparisonGrid,
h1: () => null,
a: ({ href, children, ...props }: any) => {
if (href?.startsWith('/')) {
return (
@@ -182,77 +196,63 @@ export default async function BlogPost({ params: { locale, slug } }: BlogPostPro
notFound();
}
const headings = getHeadings(post.content);
return (
<article className="bg-white min-h-screen font-sans">
<article className="bg-white min-h-screen font-sans selection:bg-primary/10 selection:text-primary">
{/* Featured Image Header */}
{post.frontmatter.featuredImage && (
<div className="relative w-full h-[60vh] min-h-[400px] overflow-hidden group">
{post.frontmatter.featuredImage ? (
<div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group">
<div
className="absolute inset-0 bg-cover bg-center transition-transform duration-[2s] ease-out scale-105 group-hover:scale-100"
className="absolute inset-0 bg-cover bg-center transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100"
style={{ backgroundImage: `url(${post.frontmatter.featuredImage})` }}
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-t from-neutral-dark via-neutral-dark/40 to-transparent" />
{/* Title overlay on image */}
<div className="absolute inset-0 flex flex-col justify-end p-8 md:p-16 lg:p-24">
<div className="container mx-auto max-w-3xl">
{post.frontmatter.category && (
<span className="inline-block px-4 py-1.5 bg-primary/90 backdrop-blur-sm text-white text-sm font-bold uppercase tracking-wider rounded-full mb-6 shadow-lg transform transition-transform hover:scale-105">
{post.frontmatter.category}
</span>
)}
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white mb-6 leading-tight drop-shadow-xl">
{post.frontmatter.title}
</h1>
<div className="flex items-center gap-4 text-white/90 text-sm md:text-base font-medium">
<time dateTime={post.frontmatter.date}>
{new Date(post.frontmatter.date).toLocaleDateString(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
<span className="w-1.5 h-1.5 bg-primary rounded-full" />
<span>KLZ Cables</span>
<div className="ml-auto hidden md:block">
<ShareButton
title={post.frontmatter.title}
text={post.frontmatter.excerpt || ''}
url={`https://klz-cables.com/${locale}/blog/${slug}`}
locale={locale}
/>
<div className="absolute inset-0 flex flex-col justify-end pb-16 md:pb-24">
<div className="container mx-auto px-4">
<div className="max-w-4xl">
{post.frontmatter.category && (
<div className="overflow-hidden mb-6">
<span className="inline-block px-4 py-1.5 bg-accent text-neutral-dark text-xs font-bold uppercase tracking-[0.2em] rounded-sm animate-slight-fade-in-from-bottom">
{post.frontmatter.category}
</span>
</div>
)}
<h1 className="text-4xl md:text-6xl lg:text-7xl font-bold text-white mb-8 leading-[1.1] drop-shadow-2xl animate-slight-fade-in-from-bottom [animation-delay:200ms]">
{post.frontmatter.title}
</h1>
<div className="flex flex-wrap items-center gap-6 text-white/80 text-sm md:text-base font-medium animate-slight-fade-in-from-bottom [animation-delay:400ms]">
<time dateTime={post.frontmatter.date}>
{new Date(post.frontmatter.date).toLocaleDateString(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
<span className="w-1 h-1 bg-white/30 rounded-full" />
<span>{getReadingTime(post.content)} min read</span>
</div>
</div>
</div>
</div>
</div>
)}
{/* Content */}
<div className="container mx-auto px-4 py-16 md:py-24 max-w-3xl">
{/* Mobile Share Button */}
<div className="md:hidden mb-8 flex justify-end">
<ShareButton
title={post.frontmatter.title}
text={post.frontmatter.excerpt || ''}
url={`https://klz-cables.com/${locale}/blog/${slug}`}
locale={locale}
/>
</div>
{/* If no featured image, show header here */}
{!post.frontmatter.featuredImage && (
<header className="mb-16 text-center">
) : (
<header className="pt-32 pb-16 bg-neutral-50 border-b border-neutral-100">
<div className="container mx-auto px-4 max-w-4xl">
{post.frontmatter.category && (
<div className="mb-6">
<span className="inline-block px-4 py-1.5 bg-primary/10 text-primary text-sm font-bold uppercase tracking-wider rounded-full">
<span className="inline-block px-4 py-1.5 bg-primary/10 text-primary text-xs font-bold uppercase tracking-[0.2em] rounded-sm">
{post.frontmatter.category}
</span>
</div>
)}
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-text-primary mb-8 leading-tight">
<h1 className="text-4xl md:text-6xl font-bold text-text-primary mb-8 leading-tight">
{post.frontmatter.title}
</h1>
<div className="flex items-center justify-center gap-4 text-text-secondary font-medium">
<div className="flex items-center gap-6 text-text-secondary font-medium">
<time dateTime={post.frontmatter.date}>
{new Date(post.frontmatter.date).toLocaleDateString(locale, {
year: 'numeric',
@@ -260,75 +260,92 @@ export default async function BlogPost({ params: { locale, slug } }: BlogPostPro
day: 'numeric'
})}
</time>
<span className="w-1.5 h-1.5 bg-primary rounded-full" />
<span>KLZ Cables</span>
<span className="w-1 h-1 bg-neutral-300 rounded-full" />
<span>{getReadingTime(post.content)} min read</span>
</div>
</header>
)}
{/* Excerpt/Lead paragraph if available */}
{post.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-6 py-2">
{post.frontmatter.excerpt}
</p>
</div>
)}
</header>
)}
{/* Main content with enhanced styling */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-xl prose-img:shadow-lg">
<MDXRemote source={post.content} components={components} />
</div>
{/* Main Content Area with Sticky Narrative Layout */}
<div className="container mx-auto px-4 py-16 md:py-24">
<div className="sticky-narrative-container">
{/* Left Column: Content */}
<div className="sticky-narrative-content">
{/* Excerpt/Lead paragraph if available */}
{post.frontmatter.excerpt && (
<div className="mb-16 animate-slight-fade-in-from-bottom [animation-delay:600ms]">
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
{post.frontmatter.excerpt}
</p>
</div>
)}
{/* Structured Data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.frontmatter.title,
datePublished: post.frontmatter.date,
image: post.frontmatter.featuredImage ? `https://klz-cables.com${post.frontmatter.featuredImage}` : undefined,
author: {
'@type': 'Organization',
name: 'KLZ Cables',
url: 'https://klz-cables.com',
},
publisher: {
'@type': 'Organization',
name: 'KLZ Cables',
logo: {
'@type': 'ImageObject',
url: 'https://klz-cables.com/logo.png', // Assuming logo exists
},
},
description: post.frontmatter.excerpt,
}),
}}
/>
{/* Main content with enhanced styling */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary animate-slight-fade-in-from-bottom [animation-delay:800ms]">
<MDXRemote source={post.content} components={components} />
</div>
{/* Power CTA */}
<div className="mt-20">
<PowerCTA locale={locale} />
</div>
{/* Power CTA */}
<div className="mt-24 animate-slight-fade-in-from-bottom">
<PowerCTA locale={locale} />
</div>
{/* Post Navigation */}
<PostNavigation prev={prev} next={next} locale={locale} />
{/* Post Navigation */}
<div className="mt-16">
<PostNavigation prev={prev} next={next} locale={locale} />
</div>
{/* Back to blog link */}
<div className="mt-16 pt-10 border-t border-neutral-200 text-center">
<Link
href={`/${locale}/blog`}
className="inline-flex items-center gap-2 text-text-secondary hover:text-primary font-medium text-lg transition-colors group"
>
<svg className="w-5 h-5 transition-transform group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
{locale === 'de' ? 'Zurück zur Übersicht' : 'Back to Overview'}
</Link>
{/* Back to blog link */}
<div className="mt-16 pt-10 border-t border-neutral-100">
<Link
href={`/${locale}/blog`}
className="inline-flex items-center gap-3 text-text-secondary hover:text-primary font-bold text-sm uppercase tracking-widest transition-all group"
>
<svg className="w-5 h-5 transition-transform group-hover:-translate-x-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
{locale === 'de' ? 'Zurück zur Übersicht' : 'Back to Overview'}
</Link>
</div>
</div>
{/* Right Column: Sticky Sidebar */}
<aside className="sticky-narrative-sidebar hidden lg:block">
<div className="space-y-12">
<TableOfContents headings={headings} locale={locale} />
</div>
</aside>
</div>
</div>
{/* Structured Data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.frontmatter.title,
datePublished: post.frontmatter.date,
image: post.frontmatter.featuredImage ? `https://klz-cables.com${post.frontmatter.featuredImage}` : undefined,
author: {
'@type': 'Organization',
name: 'KLZ Cables',
url: 'https://klz-cables.com',
},
publisher: {
'@type': 'Organization',
name: 'KLZ Cables',
logo: {
'@type': 'ImageObject',
url: 'https://klz-cables.com/logo.png',
},
},
description: post.frontmatter.excerpt,
}),
}}
/>
</article>
);
}

View File

@@ -15,6 +15,24 @@ export async function generateMetadata({ params: { locale } }: BlogIndexProps) {
return {
title: t('title'),
description: t('description'),
alternates: {
canonical: `/${locale}/blog`,
languages: {
'de': '/de/blog',
'en': '/en/blog',
'x-default': '/en/blog',
},
},
openGraph: {
title: `${t('title')} | KLZ Cables`,
description: t('description'),
url: `https://klz-cables.com/${locale}/blog`,
},
twitter: {
card: 'summary_large_image',
title: `${t('title')} | KLZ Cables`,
description: t('description'),
},
};
}
@@ -47,7 +65,7 @@ export default async function BlogIndex({ params: { locale } }: BlogIndexProps)
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<Badge variant="accent" className="mb-4 md:mb-6">{t('featuredPost')}</Badge>
<Badge variant="saturated" className="mb-4 md:mb-6">{t('featuredPost')}</Badge>
{featuredPost && (
<>
<h1 className="text-3xl md:text-7xl font-extrabold text-white mb-4 md:mb-8 leading-[1.1] line-clamp-3 md:line-clamp-none">
@@ -119,10 +137,10 @@ export default async function BlogIndex({ params: { locale } }: BlogIndexProps)
{post.frontmatter.excerpt}
</p>
<div className="mt-auto pt-4 md:pt-8 border-t border-neutral-medium flex items-center justify-between">
<span className="text-primary text-sm md:text-base font-extrabold group-hover:text-accent-dark transition-colors">
<span className="text-saturated text-sm md:text-base font-extrabold group-hover:text-accent-dark transition-colors">
{t('readMore')}
</span>
<div className="w-8 h-8 md:w-10 md:h-10 rounded-full bg-primary-light flex items-center justify-center text-primary group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300">
<div className="w-8 h-8 md:w-10 md:h-10 rounded-full bg-primary-light flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300">
<svg className="w-4 h-4 md:w-5 md:h-5 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>

View File

@@ -1,5 +1,39 @@
import { useTranslations } from 'next-intl';
import { Section, Container, Button, Heading, Card } from '@/components/ui';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
import { Section, Container, Button, Heading, Card, Input, Textarea, Label } from '@/components/ui';
interface ContactPageProps {
params: {
locale: string;
};
}
export async function generateMetadata({ params: { locale } }: ContactPageProps): Promise<Metadata> {
const t = await getTranslations({ locale, namespace: 'Contact' });
return {
title: t('title'),
description: t('subtitle'),
alternates: {
canonical: `/${locale}/contact`,
languages: {
'de': '/de/contact',
'en': '/en/contact',
'x-default': '/en/contact',
},
},
openGraph: {
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
url: `https://klz-cables.com/${locale}/contact`,
},
twitter: {
card: 'summary_large_image',
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
},
};
}
export default function ContactPage() {
const t = useTranslations('Contact');
@@ -34,7 +68,7 @@ export default function ContactPage() {
</Heading>
<div className="space-y-4 md:space-y-8">
<div className="flex items-start gap-4 md:gap-6 group">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-primary-light flex items-center justify-center text-primary group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-saturated/10 flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
<svg className="w-5 h-5 md:w-7 md:h-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
@@ -49,7 +83,7 @@ export default function ContactPage() {
</div>
<div className="flex items-start gap-4 md:gap-6 group">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-primary-light flex items-center justify-center text-primary group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-saturated/10 flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
<svg className="w-5 h-5 md:w-7 md:h-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
@@ -61,7 +95,7 @@ export default function ContactPage() {
</div>
<div className="flex items-start gap-4 md:gap-6 group">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-primary-light flex items-center justify-center text-primary group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-saturated/10 flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
<svg className="w-5 h-5 md:w-7 md:h-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
@@ -97,46 +131,43 @@ export default function ContactPage() {
</Heading>
<form className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8">
<div className="space-y-1 md:space-y-2">
<label htmlFor="name" className="text-[10px] md:text-xs font-extrabold text-primary uppercase tracking-widest">{t('form.name')}</label>
<input
<Label htmlFor="name">{t('form.name')}</Label>
<Input
type="text"
id="name"
name="name"
autoComplete="name"
enterKeyHint="next"
className="w-full px-4 md:px-6 py-2.5 md:py-4 bg-neutral rounded-xl md:rounded-2xl border-2 border-transparent focus:border-primary focus:bg-white transition-all outline-none text-sm md:text-lg"
placeholder={t('form.namePlaceholder')}
required
/>
</div>
<div className="space-y-1 md:space-y-2">
<label htmlFor="email" className="text-[10px] md:text-xs font-extrabold text-primary uppercase tracking-widest">{t('form.email')}</label>
<input
<Label htmlFor="email">{t('form.email')}</Label>
<Input
type="email"
id="email"
name="email"
autoComplete="email"
inputMode="email"
enterKeyHint="next"
className="w-full px-4 md:px-6 py-2.5 md:py-4 bg-neutral rounded-xl md:rounded-2xl border-2 border-transparent focus:border-primary focus:bg-white transition-all outline-none text-sm md:text-lg"
placeholder={t('form.emailPlaceholder')}
required
/>
</div>
<div className="md:col-span-2 space-y-1 md:space-y-2">
<label htmlFor="message" className="text-[10px] md:text-xs font-extrabold text-primary uppercase tracking-widest">{t('form.message')}</label>
<textarea
<Label htmlFor="message">{t('form.message')}</Label>
<Textarea
id="message"
name="message"
rows={4}
enterKeyHint="send"
className="w-full px-4 md:px-6 py-2.5 md:py-4 bg-neutral rounded-xl md:rounded-2xl border-2 border-transparent focus:border-primary focus:bg-white transition-all outline-none text-sm md:text-lg resize-none"
placeholder={t('form.messagePlaceholder')}
required
></textarea>
/>
</div>
<div className="md:col-span-2 pt-2 md:pt-4">
<Button type="submit" size="lg" className="w-full shadow-xl shadow-primary/20 md:h-16 md:px-10 md:text-xl active:scale-[0.98] transition-transform">
<Button type="submit" variant="saturated" size="lg" className="w-full shadow-xl shadow-saturated/20 md:h-16 md:px-10 md:text-xl active:scale-[0.98] transition-transform">
{t('form.submit')}
</Button>
</div>

View File

@@ -13,15 +13,20 @@ export async function generateMetadata({params: {locale}}: {params: {locale: str
return {
title: {
default: t('title'),
template: `%s | ${t('title')}`
template: `%s | KLZ Cables`
},
description: t('description'),
metadataBase: new URL('https://klz-cables.com'),
icons: {
icon: '/favicon.ico',
apple: '/apple-touch-icon.png',
},
alternates: {
canonical: `/${locale}`,
languages: {
'de-DE': '/de',
'en-US': '/en',
'de': '/de',
'en': '/en',
'x-default': '/en',
},
},
openGraph: {

View File

@@ -1,4 +1,3 @@
import { useTranslations } from 'next-intl';
import Hero from '@/components/home/Hero';
import ProductCategories from '@/components/home/ProductCategories';
import WhatWeDo from '@/components/home/WhatWeDo';

View File

@@ -1,16 +1,16 @@
import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { getProductBySlug, getAllProducts } from '@/lib/mdx';
import ProductTechnicalData from '@/components/ProductTechnicalData';
import ProductSidebar from '@/components/ProductSidebar';
import ProductTabs from '@/components/ProductTabs';
import RequestQuoteForm from '@/components/RequestQuoteForm';
import ProductTechnicalData from '@/components/ProductTechnicalData';
import RelatedProducts from '@/components/RelatedProducts';
import Link from 'next/link';
import Image from 'next/image';
import { getTranslations } from 'next-intl/server';
import { Section, Container, Heading, Badge, Button } from '@/components/ui';
import Scribble from '@/components/Scribble';
import { Badge, Container, Section } from '@/components/ui';
import { getDatasheetPath } from '@/lib/datasheets';
import { getAllProducts, getProductBySlug } from '@/lib/mdx';
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { MDXRemote } from 'next-mdx-remote/rsc';
import Image from 'next/image';
import Link from 'next/link';
import { notFound } from 'next/navigation';
interface ProductPageProps {
params: {
@@ -34,11 +34,24 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
return {
title: categoryTitle,
description: categoryDesc,
alternates: {
canonical: `/${locale}/products/${productSlug}`,
languages: {
'de': `/de/products/${productSlug}`,
'en': `/en/products/${productSlug}`,
'x-default': `/en/products/${productSlug}`,
},
},
openGraph: {
title: categoryTitle,
title: `${categoryTitle} | KLZ Cables`,
description: categoryDesc,
url: `https://klz-cables.com/${locale}/products/${productSlug}`,
}
},
twitter: {
card: 'summary_large_image',
title: `${categoryTitle} | KLZ Cables`,
description: categoryDesc,
},
};
}
@@ -48,15 +61,23 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
return {
title: product.frontmatter.title,
description: product.frontmatter.description,
alternates: {
canonical: `/${locale}/products/${slug.join('/')}`,
languages: {
'de': `/de/products/${slug.join('/')}`,
'en': `/en/products/${slug.join('/')}`,
'x-default': `/en/products/${slug.join('/')}`,
},
},
openGraph: {
title: product.frontmatter.title,
title: `${product.frontmatter.title} | KLZ Cables`,
description: product.frontmatter.description,
type: 'website',
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
},
twitter: {
card: 'summary_large_image',
title: product.frontmatter.title,
title: `${product.frontmatter.title} | KLZ Cables`,
description: product.frontmatter.description,
},
};
@@ -65,28 +86,35 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
const components = {
ProductTechnicalData,
ProductTabs,
p: (props: any) => <p {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed mb-8" />,
p: (props: any) => <p {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed mb-8 font-medium" />,
h2: (props: any) => (
<div className="relative mt-20 mb-10">
<h2 {...props} className="text-3xl md:text-4xl lg:text-5xl font-extrabold text-primary tracking-tight" />
<div className="absolute -bottom-4 left-0 w-24 h-1.5 bg-accent rounded-full" />
<div className="relative mb-16">
<h2 {...props} className="text-4xl md:text-5xl font-black text-primary tracking-tighter uppercase mb-6" />
<div className="w-20 h-1.5 bg-accent rounded-full" />
</div>
),
h3: (props: any) => <h3 {...props} className="text-2xl md:text-3xl lg:text-4xl font-bold text-primary mt-16 mb-6 tracking-tight" />,
ul: (props: any) => <ul {...props} className="list-disc pl-8 mb-10 space-y-4 text-text-secondary text-lg md:text-xl" />,
li: (props: any) => <li {...props} className="pl-2 marker:text-accent marker:font-bold" />,
h3: (props: any) => <h3 {...props} className="text-2xl md:text-3xl font-black text-primary mb-10 tracking-tight uppercase" />,
ul: (props: any) => <ul {...props} className="list-none pl-0 mb-10" />,
section: (props: any) => <div {...props} className="block" />,
li: (props: any) => (
<li className="flex items-start gap-4 group mb-4 last:mb-0">
<div className="mt-2.5 w-2 h-2 rounded-full bg-accent flex-shrink-0 group-hover:scale-125 transition-transform" />
<span {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed font-medium" />
</li>
),
strong: (props: any) => <strong {...props} className="font-black text-primary" />,
table: (props: any) => (
<div className="overflow-x-auto my-16 rounded-3xl border border-neutral-dark/10 shadow-xl bg-white p-1">
<div className="overflow-x-auto my-20 rounded-[32px] border border-neutral-dark/10 shadow-xl bg-white p-1">
<table {...props} className="min-w-full divide-y divide-neutral-dark/10" />
</div>
),
th: (props: any) => <th {...props} className="px-8 py-6 bg-neutral-light/50 text-left text-xs font-black uppercase tracking-[0.25em] text-primary/60" />,
td: (props: any) => <td {...props} className="px-8 py-6 text-text-secondary border-t border-neutral-dark/5 text-lg md:text-xl" />,
hr: () => <hr className="my-20 border-t-2 border-neutral-dark/5" />,
th: (props: any) => <th {...props} className="px-8 py-6 bg-neutral-light/50 text-left text-[10px] font-black uppercase tracking-[0.25em] text-primary/60" />,
td: (props: any) => <td {...props} className="px-8 py-6 text-text-secondary border-t border-neutral-dark/5 text-lg md:text-xl font-medium" />,
hr: () => <hr className="my-24 border-t-2 border-neutral-dark/5" />,
blockquote: (props: any) => (
<div className="my-12 p-8 md:p-12 bg-primary-dark rounded-3xl relative overflow-hidden group">
<div className="absolute top-0 right-0 w-48 h-48 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" />
<div className="relative z-10 italic text-2xl md:text-3xl text-white/90 leading-relaxed font-medium" {...props} />
<div className="my-20 p-10 md:p-16 bg-primary-dark rounded-[40px] relative overflow-hidden group">
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" />
<div className="relative z-10 italic text-2xl md:text-4xl text-white/90 leading-relaxed font-black tracking-tight" {...props} />
</div>
),
};
@@ -105,13 +133,16 @@ export default async function ProductPage({ params }: ProductPageProps) {
// Filter products for this category
const filteredProducts = allProducts.filter(p =>
p.frontmatter.categories.some(cat => cat.toLowerCase().replace(/\s+/g, '-') === productSlug)
p.frontmatter.categories.some(cat =>
cat.toLowerCase().replace(/\s+/g, '-') === productSlug ||
cat === categoryTitle
)
);
return (
<div className="flex flex-col min-h-screen bg-white">
<section className="relative min-h-[50vh] flex items-center pt-32 pb-20 overflow-hidden bg-primary-dark">
<Container className="relative z-10">
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<nav className="flex items-center mb-8 text-white/40 text-sm font-bold uppercase tracking-widest">
<Link href={`/${locale}/products`} className="hover:text-accent transition-colors">{t('title')}</Link>
@@ -126,7 +157,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
</Container>
</section>
<Section className="bg-neutral-light relative z-20 -mt-12 rounded-t-[48px] md:rounded-t-[64px]">
<Section className="bg-neutral-light relative">
<Container>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredProducts.map((product) => (
@@ -187,55 +218,87 @@ export default async function ProductPage({ params }: ProductPageProps) {
notFound();
}
const datasheetPath = getDatasheetPath(productSlug, locale);
const isFallback = (product.frontmatter as any).isFallback;
const categorySlug = slug[0];
const categoryKey = categorySlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t(`categories.${categoryKey}.title`);
const sidebar = (
<ProductSidebar
productName={product.frontmatter.title}
productImage={product.frontmatter.images?.[0]}
datasheetPath={datasheetPath}
/>
);
const productComponents = {
...components,
ProductTabs: (props: any) => <ProductTabs {...props} sidebar={sidebar} />,
};
// Pre-process content to convert raw HTML tags to Markdown so they use our custom components
const processedContent = product.content
.replace(/<h2[^>]*>(.*?)<\/h2>/g, '\n## $1\n')
.replace(/<h3[^>]*>(.*?)<\/h3>/g, '\n### $1\n')
.replace(/<p[^>]*>(.*?)<\/p>/g, '\n$1\n')
.replace(/<ul[^>]*>(.*?)<\/ul>/gs, '\n$1\n')
.replace(/<li[^>]*>(.*?)<\/li>/g, '\n- $1\n')
.replace(/<strong[^>]*>(.*?)<\/strong>/g, '**$1**')
.replace(/<section[^>]*>/g, '')
.replace(/<\/section>/g, '');
return (
<div className="flex flex-col min-h-screen bg-neutral-light">
<div className="flex flex-col min-h-screen bg-white relative">
{/* Product Hero */}
<section className="relative pt-40 pb-32 overflow-hidden bg-primary-dark">
<Container className="relative z-10">
<div className="max-w-5xl animate-slide-up">
<nav className="flex items-center mb-8 text-white/40 text-sm font-bold uppercase tracking-widest">
<section className="relative pt-40 pb-24 overflow-hidden bg-primary-dark">
{/* Background Decorative Elements */}
<div className="absolute top-0 right-0 w-1/2 h-full bg-gradient-to-l from-accent/5 to-transparent pointer-events-none" />
<div className="absolute -top-24 -right-24 w-96 h-96 bg-accent/10 rounded-full blur-3xl pointer-events-none" />
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<nav className="flex items-center mb-12 text-white/40 text-[10px] font-black uppercase tracking-[0.2em]">
<Link href={`/${locale}/products`} className="hover:text-accent transition-colors">{t('title')}</Link>
<span className="mx-3 opacity-30">/</span>
<span className="mx-4 opacity-20">/</span>
<Link href={`/${locale}/products/${categorySlug}`} className="hover:text-accent transition-colors">{categoryTitle}</Link>
<span className="mx-3 opacity-30">/</span>
<span className="mx-4 opacity-20">/</span>
<span className="text-white/90">{product.frontmatter.title}</span>
</nav>
<div className="flex flex-col lg:flex-row lg:items-end justify-between gap-12">
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-12">
<div className="flex-1">
{isFallback && (
<div className="mb-6 inline-flex items-center px-3 py-1 rounded-md bg-accent/10 border border-accent/20 text-accent text-[10px] font-bold uppercase tracking-widest">
<span className="w-1.5 h-1.5 rounded-full bg-accent mr-2 animate-pulse" />
<div className="mb-8 inline-flex items-center px-4 py-2 rounded-full bg-accent/10 border border-accent/20 text-accent text-[10px] font-black uppercase tracking-[0.2em] backdrop-blur-md">
<span className="w-2 h-2 rounded-full bg-accent mr-3 animate-pulse" />
{t('englishVersion')}
</div>
)}
<div className="flex flex-wrap gap-3 mb-8">
{product.frontmatter.categories.map((cat, idx) => (
<Badge key={idx} variant="accent" className="bg-white/10 text-white/90 border-white/10 backdrop-blur-md px-4 py-1.5">
<Badge key={idx} variant="accent" className="bg-white/5 text-white/80 border-white/10 backdrop-blur-md px-5 py-2 text-[10px] font-black tracking-[0.15em]">
{cat}
</Badge>
))}
</div>
<h1 className="text-5xl md:text-7xl lg:text-8xl font-extrabold text-white mb-0 tracking-tight leading-[1.05]">
<h1 className="text-6xl md:text-8xl lg:text-9xl font-black text-white mb-8 tracking-tighter leading-[0.9] uppercase">
{product.frontmatter.title}
</h1>
<p className="text-xl md:text-2xl text-white/60 max-w-2xl leading-relaxed font-medium">
{product.frontmatter.description}
</p>
</div>
</div>
</div>
</Container>
</section>
<Section className="bg-neutral-light relative z-20 -mt-16 pt-0">
<Container>
<Section className="bg-white relative">
<Container className="relative">
{/* Large Product Image Section */}
{product.frontmatter.images && product.frontmatter.images.length > 0 && (
<div className="relative -mt-24 mb-24 animate-slide-up" style={{ animationDelay: '200ms' }}>
<div className="bg-white shadow-2xl border border-neutral-dark/5 overflow-hidden p-10 md:p-16 lg:p-20">
<div className="relative -mt-32 mb-32 animate-slide-up" style={{ animationDelay: '200ms' }}>
<div className="bg-white shadow-[0_32px_64px_-12px_rgba(0,0,0,0.1)] rounded-[48px] border border-neutral-dark/5 overflow-hidden p-12 md:p-20 lg:p-24">
<div className="relative w-full aspect-[21/9]">
<Image
src={product.frontmatter.images[0]}
@@ -249,10 +312,10 @@ export default async function ProductPage({ params }: ProductPageProps) {
</div>
{product.frontmatter.images.length > 1 && (
<div className="flex justify-center gap-6 mt-16">
<div className="flex justify-center gap-8 mt-20">
{product.frontmatter.images.slice(0, 5).map((img, idx) => (
<div key={idx} className="relative w-24 h-24 md:w-32 md:h-32 border-2 border-neutral-dark/10 rounded-xl overflow-hidden bg-neutral-light/20 hover:border-accent transition-all duration-300 cursor-pointer group">
<Image src={img} alt="" fill className="object-cover transition-transform duration-500 group-hover:scale-110" />
<div key={idx} className="relative w-24 h-24 md:w-32 md:h-32 border-2 border-neutral-dark/5 rounded-3xl overflow-hidden bg-neutral-light/30 hover:border-accent transition-all duration-500 cursor-pointer group p-4">
<Image src={img} alt="" fill className="object-contain p-4 transition-transform duration-700 group-hover:scale-110" />
</div>
))}
</div>
@@ -261,11 +324,11 @@ export default async function ProductPage({ params }: ProductPageProps) {
</div>
)}
<div className="flex flex-col lg:flex-row gap-16 lg:gap-24 relative items-start overflow-visible">
<div className="flex-1 w-full">
<div className="relative">
<div className="w-full">
{/* Main Content Area */}
<div className="prose prose-lg md:prose-xl prose-slate max-w-none prose-headings:text-primary prose-headings:font-extrabold prose-a:text-primary prose-strong:text-primary prose-img:rounded-3xl prose-img:shadow-2xl">
<MDXRemote source={product.content} components={components} />
<div className="max-w-none">
<MDXRemote source={processedContent} components={productComponents} />
</div>
{/* Structured Data */}
@@ -292,43 +355,16 @@ export default async function ProductPage({ params }: ProductPageProps) {
}),
}}
/>
{/* Related Products */}
<RelatedProducts
currentSlug={productSlug}
categories={product.frontmatter.categories}
locale={locale}
/>
</div>
<div className="w-full lg:w-[450px] xl:w-[500px] lg:sticky lg:top-32">
<div className="lg:translate-x-12 xl:translate-x-20">
{/* Request Quote Form */}
<div className="bg-white rounded-3xl shadow-2xl border border-neutral-dark/5 overflow-hidden">
<div className="bg-primary-dark p-6 md:p-8 text-white relative overflow-hidden">
<div className="absolute top-0 right-0 w-40 h-40 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl" />
{/* Product Thumbnail for Sticky State */}
{product.frontmatter.images?.[0] && (
<div className="relative w-full aspect-[16/9] mb-6 rounded-xl overflow-hidden bg-white/10 backdrop-blur-md p-3 border border-white/10 z-10">
<Image
src={product.frontmatter.images[0]}
alt=""
fill
className="object-contain p-1"
/>
</div>
)}
</div>
<h3 className="text-xl md:text-2xl font-extrabold mb-2 relative z-10 tracking-tight">{t('requestQuote')}</h3>
<p className="text-white/60 text-sm relative z-10 leading-relaxed">{t('requestQuoteDesc')}</p>
</div>
<div className="p-6 md:p-8">
<RequestQuoteForm productName={product.frontmatter.title} />
</div>
</div>
</div>
</div>
{/* Related Products Section */}
<div className="mt-16 pt-16 border-t border-neutral-dark/5">
<RelatedProducts
currentSlug={productSlug}
categories={product.frontmatter.categories}
locale={locale}
/>
</div>
</Container>
</Section>

View File

@@ -1,135 +0,0 @@
import { ImageResponse } from 'next/og';
import { getProductBySlug } from '@/lib/mdx';
export const runtime = 'nodejs';
export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string } }) {
const productSlug = slug;
const product = await getProductBySlug(productSlug, locale);
if (!product) {
return new ImageResponse(
<div style={{ display: 'flex', width: '100%', height: '100%', backgroundColor: '#001a4d' }} />
);
}
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: 'white',
padding: '60px',
position: 'relative',
}}
>
{/* Left Side: Content */}
<div style={{ display: 'flex', flexDirection: 'column', width: '55%' }}>
<div
style={{
fontSize: '20px',
fontWeight: 'bold',
color: '#001a4d',
textTransform: 'uppercase',
letterSpacing: '0.2em',
marginBottom: '20px',
opacity: 0.6,
}}
>
KLZ Cables | {product.frontmatter.categories[0]}
</div>
<div
style={{
fontSize: '72px',
fontWeight: '900',
color: '#001a4d',
lineHeight: '1.1',
marginBottom: '30px',
}}
>
{product.frontmatter.title}
</div>
<div
style={{
fontSize: '28px',
color: '#4a5568',
lineHeight: '1.4',
}}
>
{product.frontmatter.description}
</div>
{/* Accent Line */}
<div
style={{
marginTop: '40px',
width: '100px',
height: '8px',
backgroundColor: '#00ff99',
borderRadius: '4px',
}}
/>
</div>
{/* Right Side: Product Image */}
<div
style={{
display: 'flex',
width: '40%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
}}
>
{product.frontmatter.images?.[0] && (
<img
src={product.frontmatter.images[0].startsWith('http') ? product.frontmatter.images[0] : `https://klz-cables.com${product.frontmatter.images[0]}`}
alt=""
style={{
maxWidth: '100%',
maxHeight: '80%',
objectFit: 'contain',
}}
/>
)}
{/* Subtle shadow under product */}
<div
style={{
position: 'absolute',
bottom: '10%',
width: '60%',
height: '20px',
backgroundColor: 'rgba(0,0,0,0.05)',
borderRadius: '50%',
filter: 'blur(10px)',
}}
/>
</div>
{/* Branding Corner */}
<div
style={{
position: 'absolute',
bottom: '40px',
right: '60px',
display: 'flex',
alignItems: 'center',
}}
>
<div style={{ fontSize: '24px', fontWeight: 'bold', color: '#001a4d' }}>KLZ</div>
<div style={{ fontSize: '24px', fontWeight: 'normal', color: '#00ff99', marginLeft: '4px' }}>Cables</div>
</div>
</div>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -1,9 +1,11 @@
import Link from 'next/link';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
import { Section, Container, Card, Badge, Button } from '@/components/ui';
import Scribble from '@/components/Scribble';
import Reveal from '@/components/Reveal';
import Scribble from '@/components/Scribble';
import { Badge, Button, Card, Container, Section } from '@/components/ui';
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';
interface ProductsPageProps {
params: {
@@ -11,6 +13,32 @@ interface ProductsPageProps {
};
}
export async function generateMetadata({ params: { locale } }: ProductsPageProps): Promise<Metadata> {
const t = await getTranslations({ locale, namespace: 'Products' });
return {
title: t('title'),
description: t('subtitle'),
alternates: {
canonical: `/${locale}/products`,
languages: {
'de': '/de/products',
'en': '/en/products',
'x-default': '/en/products',
},
},
openGraph: {
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
url: `https://klz-cables.com/${locale}/products`,
},
twitter: {
card: 'summary_large_image',
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
},
};
}
export default function ProductsPage({ params }: ProductsPageProps) {
const t = useTranslations('Products');
@@ -51,7 +79,7 @@ export default function ProductsPage({ params }: ProductsPageProps) {
<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">
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<Badge variant="accent" className="mb-4 md:mb-8 bg-white/10 text-white border border-white/20 backdrop-blur-md px-3 py-1 md:px-4 md:py-1.5">
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg px-3 py-1 md:px-4 md:py-1.5">
{t('heroSubtitle')}
</Badge>
<h1 className="text-4xl md:text-7xl lg:text-8xl font-extrabold text-white mb-4 md:mb-8 tracking-tight leading-[1.05]">
@@ -77,7 +105,7 @@ export default function ProductsPage({ params }: ProductsPageProps) {
</Container>
</section>
<Section id="categories" className="bg-neutral-light relative z-20 -mt-8 md:-mt-16 rounded-t-[32px] md:rounded-t-[64px] pt-12 md:pt-24">
<Section id="categories" className="bg-neutral-light relative">
<Container>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8 lg:gap-12">
{categories.map((category, idx) => (
@@ -112,11 +140,11 @@ export default function ProductsPage({ params }: ProductsPageProps) {
<p className="text-text-secondary text-sm md:text-lg leading-relaxed mb-4 md:mb-8 line-clamp-2 md:line-clamp-none">
{category.desc}
</p>
<div className="flex items-center text-primary font-bold text-base md:text-lg group-hover:text-accent-dark transition-colors">
<span className="border-b-2 border-primary/10 group-hover:border-accent-dark transition-colors pb-1">
<div className="flex items-center text-saturated font-bold text-base md:text-lg group-hover:text-accent-dark transition-colors">
<span className="border-b-2 border-saturated/10 group-hover:border-accent-dark transition-colors pb-1">
{t('viewProducts')}
</span>
<div className="ml-3 md:ml-4 w-8 h-8 md:w-10 md:h-10 rounded-full bg-primary-light flex items-center justify-center text-primary group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm">
<div className="ml-3 md:ml-4 w-8 h-8 md:w-10 md:h-10 rounded-full bg-primary-light flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm">
<svg className="w-4 h-4 md:w-5 md:h-5 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>

View File

@@ -1,8 +1,42 @@
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
import { Section, Container, Heading, Badge, Button } from '@/components/ui';
import Image from 'next/image';
import Reveal from '@/components/Reveal';
interface TeamPageProps {
params: {
locale: string;
};
}
export async function generateMetadata({ params: { locale } }: TeamPageProps): Promise<Metadata> {
const t = await getTranslations({ locale, namespace: 'Team' });
return {
title: t('hero.subtitle'),
description: t('hero.title'),
alternates: {
canonical: `/${locale}/team`,
languages: {
'de': '/de/team',
'en': '/en/team',
'x-default': '/en/team',
},
},
openGraph: {
title: `${t('hero.subtitle')} | KLZ Cables`,
description: t('hero.title'),
url: `https://klz-cables.com/${locale}/team`,
},
twitter: {
card: 'summary_large_image',
title: `${t('hero.subtitle')} | KLZ Cables`,
description: t('hero.title'),
},
};
}
export default function TeamPage() {
const t = useTranslations('Team');
@@ -22,7 +56,7 @@ export default function TeamPage() {
</div>
<Container className="relative z-10 text-center text-white max-w-5xl animate-slide-up">
<Badge variant="accent" className="mb-4 md:mb-8 shadow-lg">{t('hero.badge')}</Badge>
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">{t('hero.badge')}</Badge>
<h1 className="text-3xl md:text-7xl lg:text-8xl font-extrabold tracking-tight leading-[1.1] mb-4 md:mb-8">
{t('hero.subtitle')}
</h1>
@@ -45,7 +79,7 @@ export default function TeamPage() {
<div className="relative mb-6 md:mb-12">
<div className="absolute -left-4 md:-left-8 top-0 bottom-0 w-1 md:w-1.5 bg-accent rounded-full" />
<p className="text-lg md:text-3xl font-bold italic leading-relaxed pl-5 md:pl-8 text-white/90">
"{t('michael.quote')}"
{t('michael.quote')}
</p>
</div>
<p className="text-base md:text-xl leading-relaxed text-white/70 mb-6 md:mb-12 max-w-xl">
@@ -129,17 +163,17 @@ export default function TeamPage() {
/>
<div className="absolute inset-0 bg-gradient-to-t from-white/60 lg:bg-gradient-to-l lg:from-primary-dark/20 to-transparent" />
</div>
<Reveal className="w-full lg:w-1/2 p-6 md:p-24 lg:p-32 flex flex-col justify-center bg-neutral-light text-primary relative order-2">
<div className="absolute top-0 left-0 w-32 h-full bg-primary/5 skew-x-12 -translate-x-1/2" />
<Reveal className="w-full lg:w-1/2 p-6 md:p-24 lg:p-32 flex flex-col justify-center bg-neutral-light text-saturated relative order-2">
<div className="absolute top-0 left-0 w-32 h-full bg-saturated/5 skew-x-12 -translate-x-1/2" />
<div className="relative z-10">
<Badge variant="primary" className="mb-4 md:mb-8">{t('klaus.role')}</Badge>
<Heading level={2} className="text-primary mb-6 md:mb-10 text-3xl md:text-6xl">
<Badge variant="saturated" className="mb-4 md:mb-8">{t('klaus.role')}</Badge>
<Heading level={2} className="text-saturated mb-6 md:mb-10 text-3xl md:text-6xl">
{t('klaus.name')}
</Heading>
<div className="relative mb-6 md:mb-12">
<div className="absolute -left-4 md:-left-8 top-0 bottom-0 w-1 md:w-1.5 bg-primary rounded-full" />
<div className="absolute -left-4 md:-left-8 top-0 bottom-0 w-1 md:w-1.5 bg-saturated rounded-full" />
<p className="text-lg md:text-3xl font-bold italic leading-relaxed pl-5 md:pl-8 text-text-secondary">
"{t('klaus.quote')}"
{t('klaus.quote')}
</p>
</div>
<p className="text-base md:text-xl leading-relaxed text-text-secondary mb-6 md:mb-12 max-w-xl">
@@ -147,7 +181,7 @@ export default function TeamPage() {
</p>
<Button
href="https://www.linkedin.com/in/klaus-mintel-b80a8b193/"
variant="primary"
variant="saturated"
size="lg"
className="group w-full md:w-auto md:h-16 md:px-10 md:text-xl active:scale-95 transition-transform"
>

25
app/manifest.ts Normal file
View File

@@ -0,0 +1,25 @@
import { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'KLZ Cables',
short_name: 'KLZ',
description: 'Premium Cable Solutions',
start_url: '/',
display: 'standalone',
background_color: '#001a4d',
theme_color: '#001a4d',
icons: [
{
src: '/favicon.ico',
sizes: 'any',
type: 'image/x-icon',
},
{
src: '/apple-touch-icon.png',
sizes: '180x180',
type: 'image/png',
},
],
};
}

12
app/robots.ts Normal file
View File

@@ -0,0 +1,12 @@
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/health/'],
},
sitemap: 'https://klz-cables.com/sitemap.xml',
};
}

74
app/sitemap.ts Normal file
View File

@@ -0,0 +1,74 @@
import { MetadataRoute } from 'next';
import { getAllProducts } from '@/lib/mdx';
import { getAllPosts } from '@/lib/blog';
import { getAllPages } from '@/lib/pages';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://klz-cables.com';
const locales = ['de', 'en'];
const routes = [
'',
'/blog',
'/contact',
'/team',
'/products',
'/products/low-voltage-cables',
'/products/medium-voltage-cables',
'/products/high-voltage-cables',
'/products/solar-cables',
];
const sitemapEntries: MetadataRoute.Sitemap = [];
for (const locale of locales) {
// Static routes
for (const route of routes) {
sitemapEntries.push({
url: `${baseUrl}/${locale}${route}`,
lastModified: new Date(),
changeFrequency: route === '' ? 'daily' : 'weekly',
priority: route === '' ? 1 : 0.8,
});
}
// Products
const products = await getAllProducts(locale);
for (const product of products) {
// We need to find the category for the product to build the URL
// In this project, products are under /products/[category]/[slug]
// The category is in product.frontmatter.categories
const category = product.frontmatter.categories[0]?.toLowerCase().replace(/\s+/g, '-') || 'other';
sitemapEntries.push({
url: `${baseUrl}/${locale}/products/${category}/${product.slug}`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.7,
});
}
// Blog posts
const posts = await getAllPosts(locale);
for (const post of posts) {
sitemapEntries.push({
url: `${baseUrl}/${locale}/blog/${post.slug}`,
lastModified: new Date(post.frontmatter.date),
changeFrequency: 'monthly',
priority: 0.6,
});
}
// Static pages
const pages = await getAllPages(locale);
for (const page of pages) {
sitemapEntries.push({
url: `${baseUrl}/${locale}/${page.slug}`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.5,
});
}
}
return sitemapEntries;
}

View File

@@ -10,7 +10,7 @@ export default function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="bg-primary-dark text-white py-24 relative overflow-hidden">
<footer className="bg-primary text-white py-24 relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-white/20 to-transparent" />
<Container>

View File

@@ -147,7 +147,7 @@ export default function Header() {
{/* Mobile Menu Overlay */}
<div className={cn(
"fixed inset-0 bg-primary-dark z-40 lg:hidden transition-all duration-500 ease-in-out flex flex-col",
"fixed inset-0 bg-primary z-40 lg:hidden transition-all duration-500 ease-in-out flex flex-col",
isMobileMenuOpen ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-full pointer-events-none"
)}>
<div className="flex-grow flex flex-col justify-center items-center p-8 space-y-8">

View File

@@ -0,0 +1,97 @@
'use client';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
import RequestQuoteForm from '@/components/RequestQuoteForm';
import Scribble from '@/components/Scribble';
import { cn } from '@/components/ui/utils';
interface ProductSidebarProps {
productName: string;
productImage?: string;
datasheetPath?: string | null;
className?: string;
}
export default function ProductSidebar({ productName, productImage, datasheetPath, className }: ProductSidebarProps) {
const t = useTranslations('Products');
return (
<div className={cn("flex flex-col gap-4 animate-slight-fade-in-from-bottom", className)}>
{/* Request Quote Form Card */}
<div className="bg-white rounded-3xl border border-neutral-medium shadow-sm transition-all duration-500 hover:shadow-2xl hover:-translate-y-1 overflow-hidden group/card">
<div className="bg-primary p-6 text-white relative overflow-hidden">
{/* Background Accent - Saturated Blue Glow */}
<div className="absolute top-0 right-0 w-40 h-40 bg-saturated/30 rounded-full -translate-y-1/2 translate-x-1/2 blur-[80px] pointer-events-none" />
{/* Product Thumbnail with Reflection */}
{productImage && (
<div className="relative w-full aspect-[16/10] mb-6 rounded-2xl overflow-hidden bg-white/5 backdrop-blur-md p-4 border border-white/10 z-10 group">
<div className="relative w-full h-full transition-transform duration-1000 ease-out group-hover:scale-105">
<Image
src={productImage}
alt={productName}
fill
className="object-contain p-2 drop-shadow-[0_20px_30px_rgba(0,0,0,0.4)]"
/>
{/* Subtle Reflection Overlay */}
<div className="absolute inset-0 bg-gradient-to-tr from-white/20 via-transparent to-transparent opacity-30 pointer-events-none" />
</div>
</div>
)}
<div className="relative z-10">
<div className="inline-block relative mb-2">
<h3 className="text-xl font-heading font-black m-0 tracking-tighter uppercase leading-none">
{t('requestQuote')}
</h3>
<Scribble
variant="underline"
className="w-full h-3 -bottom-3 left-0 text-accent/80"
color="var(--color-accent)"
/>
</div>
<p className="text-white/60 text-xs m-0 mt-2 leading-relaxed font-medium max-w-[90%]">
{t('requestQuoteDesc')}
</p>
</div>
</div>
<div className="p-6 bg-neutral-light/50">
<RequestQuoteForm productName={productName} />
</div>
</div>
{/* Datasheet Download */}
{datasheetPath && (
<a
href={datasheetPath}
target="_blank"
rel="noopener noreferrer"
className="block bg-white rounded-2xl border border-neutral-medium overflow-hidden group transition-all duration-500 hover:shadow-xl hover:border-saturated/30 hover:-translate-y-0.5"
>
<div className="p-4 flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-neutral-medium/20 flex items-center justify-center flex-shrink-0 group-hover:bg-saturated group-hover:text-white transition-all duration-500 text-saturated border border-transparent group-hover:border-white/20">
<svg className="w-6 h-6 transition-transform duration-500 group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-heading font-black text-neutral-dark m-0 uppercase tracking-tighter leading-tight group-hover:text-saturated transition-colors duration-300">
{t('downloadDatasheet')}
</h3>
<p className="text-text-secondary text-[10px] m-0 mt-0.5 font-semibold leading-tight truncate uppercase tracking-widest opacity-60">
{t('downloadDatasheetDesc')}
</p>
</div>
<div className="text-neutral-dark/20 group-hover:text-saturated transition-all duration-500 transform group-hover:translate-x-1">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</a>
)}
</div>
);
}

View File

@@ -3,18 +3,37 @@ import React from 'react';
interface ProductTabsProps {
children: React.ReactNode;
technicalData?: React.ReactNode;
sidebar?: React.ReactNode;
}
export default function ProductTabs({ children, technicalData }: ProductTabsProps) {
export default function ProductTabs({ children, technicalData, sidebar }: ProductTabsProps) {
return (
<div className="space-y-12">
<div className="prose max-w-none">
{children}
<div className="space-y-24">
<div className="flex flex-col lg:flex-row gap-12 items-start">
<div className="flex-1 min-w-0">
{sidebar && (
<div className="lg:hidden mb-12">
{sidebar}
</div>
)}
<div className="max-w-none">
{children}
</div>
</div>
{sidebar && (
<div className="hidden lg:block sticky top-32 w-[350px] xl:w-[400px] flex-shrink-0">
{sidebar}
</div>
)}
</div>
{technicalData && (
<div className="pt-8 border-t border-neutral-dark">
<h2 className="text-2xl font-bold text-primary-dark mb-6">Technical Specifications</h2>
<div className="pt-24 border-t-2 border-neutral-dark/5">
<div className="mb-16">
<h2 className="text-4xl md:text-5xl font-black text-primary tracking-tighter uppercase mb-4">Technical Specifications</h2>
<div className="h-1.5 w-24 bg-accent rounded-full" />
</div>
<div className="not-prose">
{technicalData}
</div>

View File

@@ -1,4 +1,7 @@
import React from 'react';
'use client';
import React, { useState } from 'react';
import { useTranslations } from 'next-intl';
interface KeyValueItem {
label: string;
@@ -21,20 +24,33 @@ interface ProductTechnicalDataProps {
}
export default function ProductTechnicalData({ data }: ProductTechnicalDataProps) {
const t = useTranslations('Products');
const [expandedTables, setExpandedTables] = useState<Record<number, boolean>>({});
if (!data) return null;
const { technicalItems = [], voltageTables = [] } = data;
const toggleTable = (idx: number) => {
setExpandedTables(prev => ({
...prev,
[idx]: !prev[idx]
}));
};
return (
<div className="space-y-8">
<div className="space-y-16">
{technicalItems.length > 0 && (
<div className="bg-neutral-light p-6 rounded-lg shadow-sm">
<h3 className="text-xl font-semibold mb-4">General Data</h3>
<dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-4">
<div className="bg-white p-8 md:p-12 rounded-[32px] shadow-sm border border-neutral-dark/5">
<h3 className="text-2xl font-bold text-primary mb-8 flex items-center gap-3">
<div className="w-2 h-8 bg-accent rounded-full" />
General Data
</h3>
<dl className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-12 gap-y-8">
{technicalItems.map((item, idx) => (
<div key={idx} className="flex flex-col border-b border-neutral-dark pb-2 last:border-0">
<dt className="text-sm text-text-secondary">{item.label}</dt>
<dd className="text-base font-medium text-text-primary">
{item.value} {item.unit && <span className="text-sm text-text-secondary ml-1">{item.unit}</span>}
<div key={idx} className="flex flex-col group">
<dt className="text-sm font-bold uppercase tracking-widest text-primary/40 mb-2 group-hover:text-accent transition-colors">{item.label}</dt>
<dd className="text-lg font-semibold text-text-primary">
{item.value} {item.unit && <span className="text-sm font-normal text-text-secondary ml-1">{item.unit}</span>}
</dd>
</div>
))}
@@ -42,57 +58,84 @@ export default function ProductTechnicalData({ data }: ProductTechnicalDataProps
</div>
)}
{voltageTables.map((table, idx) => (
<div key={idx} className="bg-neutral-light p-6 rounded-lg shadow-sm overflow-hidden">
<h3 className="text-xl font-semibold mb-4">
{table.voltageLabel !== 'Voltage unknown' && table.voltageLabel !== 'Spannung unbekannt'
? table.voltageLabel
: 'Technical Specifications'}
</h3>
{table.metaItems.length > 0 && (
<dl className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 mb-6 bg-neutral p-4 rounded">
{table.metaItems.map((item, mIdx) => (
<div key={mIdx}>
<dt className="text-xs text-text-secondary uppercase tracking-wider">{item.label}</dt>
<dd className="font-medium">{item.value} {item.unit}</dd>
</div>
))}
</dl>
)}
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-neutral-dark">
<thead className="bg-neutral">
<tr>
<th scope="col" className="px-3 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider sticky left-0 bg-neutral z-10">
Configuration
</th>
{table.columns.map((col, cIdx) => (
<th key={cIdx} scope="col" className="px-3 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider whitespace-nowrap">
{col.label}
</th>
))}
</tr>
</thead>
<tbody className="bg-white divide-y divide-neutral-dark">
{table.rows.map((row, rIdx) => (
<tr key={rIdx} className="hover:bg-neutral-light transition-colors">
<td className="px-3 py-3 text-sm font-medium text-text-primary sticky left-0 bg-white z-10 whitespace-nowrap">
{row.configuration}
</td>
{row.cells.map((cell, cellIdx) => (
<td key={cellIdx} className="px-3 py-3 text-sm text-text-secondary whitespace-nowrap">
{cell}
</td>
))}
</tr>
{voltageTables.map((table, idx) => {
const isExpanded = expandedTables[idx];
const hasManyRows = table.rows.length > 10;
return (
<div key={idx} className="bg-white p-8 md:p-12 rounded-[32px] shadow-sm border border-neutral-dark/5 overflow-hidden">
<h3 className="text-2xl font-bold text-primary mb-8 flex items-center gap-3">
<div className="w-2 h-8 bg-accent rounded-full" />
{table.voltageLabel !== 'Voltage unknown' && table.voltageLabel !== 'Spannung unbekannt'
? table.voltageLabel
: 'Technical Specifications'}
</h3>
{table.metaItems.length > 0 && (
<dl className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8 mb-12 bg-neutral-light/50 p-8 rounded-2xl border border-neutral-dark/5">
{table.metaItems.map((item, mIdx) => (
<div key={mIdx}>
<dt className="text-[10px] font-black uppercase tracking-[0.2em] text-primary/40 mb-1">{item.label}</dt>
<dd className="font-bold text-primary">{item.value} {item.unit}</dd>
</div>
))}
</tbody>
</table>
</dl>
)}
<div className="relative">
<div
className={`overflow-x-auto -mx-8 md:-mx-12 px-8 md:px-12 transition-all duration-500 ease-in-out ${
!isExpanded && hasManyRows ? 'max-h-[400px] overflow-y-hidden' : 'max-h-[none]'
}`}
>
<table className="min-w-full border-separate border-spacing-0">
<thead>
<tr>
<th scope="col" className="px-3 py-3 text-left text-[10px] font-black text-primary/40 uppercase tracking-[0.2em] sticky left-0 bg-white z-10 border-b border-neutral-dark/10">
Config.
</th>
{table.columns.map((col, cIdx) => (
<th key={cIdx} scope="col" className="px-3 py-3 text-left text-[10px] font-black text-primary/40 uppercase tracking-[0.2em] whitespace-nowrap border-b border-neutral-dark/10">
{col.label}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-neutral-dark/5">
{table.rows.map((row, rIdx) => (
<tr key={rIdx} className="hover:bg-neutral-light/50 transition-colors group">
<td className="px-3 py-2 text-xs font-bold text-primary sticky left-0 bg-white group-hover:bg-neutral-light/50 z-10 whitespace-nowrap">
{row.configuration}
</td>
{row.cells.map((cell, cellIdx) => (
<td key={cellIdx} className="px-3 py-2 text-xs text-text-secondary whitespace-nowrap">
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{!isExpanded && hasManyRows && (
<div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-white via-white/80 to-transparent z-20 pointer-events-none" />
)}
</div>
{hasManyRows && (
<div className="mt-8 flex justify-center">
<button
onClick={() => toggleTable(idx)}
className="px-8 py-3 rounded-full bg-primary text-white text-sm font-bold uppercase tracking-widest hover:bg-accent hover:text-primary transition-all duration-300 shadow-lg hover:shadow-accent/20"
>
{isExpanded ? t('showLess') : t('showMore')}
</button>
</div>
)}
</div>
</div>
))}
);
})}
</div>
);
}

View File

@@ -1,7 +1,7 @@
import Link from 'next/link';
import Image from 'next/image';
import { getAllProducts } from '@/lib/mdx';
import { getTranslations } from 'next-intl/server';
import Image from 'next/image';
import Link from 'next/link';
interface RelatedProductsProps {
currentSlug: string;
@@ -24,7 +24,7 @@ export default async function RelatedProducts({ currentSlug, categories, locale
if (related.length === 0) return null;
return (
<div className="mt-32 pt-32 border-t border-neutral-dark/10">
<div className="">
<div className="flex items-end justify-between mb-12">
<div>
<h2 className="text-3xl md:text-4xl font-extrabold text-primary tracking-tight mb-4">
@@ -37,7 +37,14 @@ export default async function RelatedProducts({ currentSlug, categories, locale
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{related.map((product) => {
// Find the category slug for the link
const catSlug = product.frontmatter.categories[0].toLowerCase().replace(/\s+/g, '-');
const categorySlugs = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
const catSlug = categorySlugs.find(slug => {
const key = slug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const title = t(`categories.${key}.title`);
return product.frontmatter.categories.some(cat =>
cat.toLowerCase().replace(/\s+/g, '-') === slug || cat === title
);
}) || 'low-voltage-cables';
return (
<Link

View File

@@ -2,6 +2,7 @@
import React, { useState } from 'react';
import { useTranslations } from 'next-intl';
import { Input, Textarea, Button } from '@/components/ui';
interface RequestQuoteFormProps {
productName: string;
@@ -30,19 +31,19 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
if (status === 'success') {
return (
<div className="bg-accent/5 border border-accent/20 text-primary-dark p-6 rounded-[32px] text-center animate-fade-in">
<div className="w-12 h-12 bg-accent rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg shadow-accent/20">
<svg className="w-6 h-6 text-primary-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="bg-accent/5 border border-accent/20 text-primary-dark p-4 rounded-xl text-center animate-fade-in !mt-0">
<div className="w-10 h-10 bg-accent rounded-full flex items-center justify-center mx-auto mb-3 shadow-lg shadow-accent/20">
<svg className="w-5 h-5 text-primary-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</div>
<h3 className="text-xl font-extrabold mb-2 tracking-tight">{t('successTitle')}</h3>
<p className="text-text-secondary text-sm leading-relaxed mb-6">
<h3 className="text-base font-extrabold mb-1 tracking-tight !mt-0">{t('successTitle')}</h3>
<p className="text-text-secondary text-xs leading-tight mb-4 !mt-0">
{t('successDesc', { productName })}
</p>
<button
onClick={() => setStatus('idle')}
className="inline-flex items-center text-[10px] font-bold uppercase tracking-[0.2em] text-primary hover:text-accent transition-colors group"
className="inline-flex items-center text-[9px] font-bold uppercase tracking-[0.2em] text-primary hover:text-accent transition-colors group"
>
<span className="border-b-2 border-primary/10 group-hover:border-accent transition-colors pb-1">
{t('sendAnother')}
@@ -53,58 +54,58 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
<div className="space-y-1.5">
<input
<form onSubmit={handleSubmit} className="space-y-3 !mt-0">
<div className="space-y-2 !mt-0">
<div className="space-y-1 !mt-0">
<Input
type="email"
id="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-3 bg-neutral-light/50 border border-neutral-dark/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent focus:bg-white transition-all duration-300 placeholder:text-neutral-dark/40 text-sm"
placeholder={t('email')}
className="h-9 text-xs !mt-0"
/>
</div>
<div className="space-y-1.5">
<textarea
<div className="space-y-1 !mt-0">
<Textarea
id="request"
required
rows={4}
rows={3}
value={request}
onChange={(e) => setRequest(e.target.value)}
className="w-full px-4 py-3 bg-neutral-light/50 border border-neutral-dark/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent focus:bg-white transition-all duration-300 placeholder:text-neutral-dark/40 text-sm resize-none"
placeholder={t('message')}
className="text-xs !mt-0"
/>
</div>
</div>
<div className="space-y-4">
<button
<div className="space-y-2 !mt-0">
<Button
type="submit"
disabled={status === 'submitting'}
className="w-full bg-primary text-white font-bold py-3.5 px-6 rounded-xl hover:bg-primary-dark hover:shadow-lg active:scale-[0.98] transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 group"
className="w-full py-2 rounded-lg flex items-center justify-center gap-2 group !mt-0"
>
{status === 'submitting' ? (
<>
<svg className="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<svg className="animate-spin h-3 w-3 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span className="text-sm">{t('submitting')}</span>
<span className="text-xs">{t('submitting')}</span>
</>
) : (
<>
<span className="text-sm">{t('submit')}</span>
<svg className="w-4 h-4 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<span className="text-xs">{t('submit')}</span>
<svg className="w-3 h-3 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</>
)}
</button>
</Button>
<p className="text-[8px] text-center text-text-secondary/40 uppercase tracking-[0.15em] font-medium px-2">
<p className="text-[7px] text-center text-text-secondary/40 uppercase tracking-[0.15em] font-medium px-2 !mt-1 !mb-0">
{t('privacyNote')}
</p>
</div>

View File

@@ -1,4 +1,7 @@
'use client';
import React from 'react';
import { motion, Variants } from 'framer-motion';
import { cn } from '@/components/ui';
interface ScribbleProps {
@@ -8,6 +11,18 @@ interface ScribbleProps {
}
export default function Scribble({ variant, className, color = '#82ed20' }: ScribbleProps) {
const pathVariants: Variants = {
hidden: { pathLength: 0, opacity: 0 },
visible: {
pathLength: 1,
opacity: 1,
transition: {
duration: 1.8,
ease: "easeInOut",
}
}
};
if (variant === 'circle') {
return (
<svg
@@ -16,12 +31,14 @@ export default function Scribble({ variant, className, color = '#82ed20' }: Scri
viewBox="0 0 800 350"
preserveAspectRatio="none"
>
<path
style={{ animationDuration: '1.8s' }}
<motion.path
variants={pathVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transform="matrix(0.9791300296783447,0,0,0.9791300296783447,400,179)"
strokeLinejoin="miter"
fillOpacity="0"
pathLength="1"
strokeMiterlimit="4"
stroke={color}
strokeOpacity="1"
@@ -40,11 +57,13 @@ export default function Scribble({ variant, className, color = '#82ed20' }: Scri
viewBox="-400 -55 730 60"
preserveAspectRatio="none"
>
<path
style={{ animationDuration: '1.8s' }}
<motion.path
variants={pathVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
d="m -383.25 -6 c 55.25 -22 130.75 -33.5 293.25 -38 c 54.5 -0.5 195 -2.5 401 15"
stroke={color}
pathLength="1"
strokeWidth="20"
fill="none"
/>

View File

@@ -47,24 +47,36 @@ export default function AnimatedImage({
return (
<div
ref={containerRef}
className={`relative overflow-hidden rounded-xl shadow-lg my-12 ${className}`}
className={`relative overflow-hidden rounded-2xl shadow-2xl my-16 group ${className}`}
>
<div className={`
absolute inset-0 bg-primary/10 z-10 pointer-events-none transition-opacity duration-1000
${isLoaded && isInView ? 'opacity-0' : 'opacity-100'}
`} />
<Image
src={src}
alt={alt}
width={width}
height={height}
className={`
duration-1000 ease-out w-full h-auto
${isLoaded && isInView ? 'scale-100 blur-0 opacity-100' : 'scale-110 blur-xl opacity-0'}
duration-[1.5s] ease-out w-full h-auto transition-all
${isLoaded && isInView ? 'scale-100 blur-0 opacity-100' : 'scale-110 blur-2xl opacity-0'}
group-hover:scale-105
`}
onLoad={() => setIsLoaded(true)}
priority={priority}
/>
{/* Subtle reflection overlay */}
<div className="absolute inset-0 bg-gradient-to-tr from-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" />
{alt && (
<p className="text-sm text-text-secondary text-center mt-3 italic px-4 pb-4">
{alt}
</p>
<div className="absolute bottom-0 left-0 right-0 p-6 bg-gradient-to-t from-black/60 to-transparent translate-y-full group-hover:translate-y-0 transition-transform duration-500">
<p className="text-sm text-white font-medium italic">
{alt}
</p>
</div>
)}
</div>
);

View File

@@ -46,15 +46,18 @@ export default function ChatBubble({
</div>
<div className={`
relative px-6 py-4 rounded-2xl shadow-sm border
relative px-8 py-6 rounded-3xl shadow-sm border transition-all duration-300 hover:shadow-md
${isRight
? 'bg-primary text-white rounded-tr-none border-primary'
? 'bg-neutral-dark text-white rounded-tr-none border-neutral-dark'
: 'bg-white text-text-primary rounded-tl-none border-neutral-200'
}
`}>
<div className={`prose prose-sm max-w-none ${isRight ? 'prose-invert' : ''}`}>
<div className={`prose prose-lg max-w-none ${isRight ? 'prose-invert' : ''}`}>
{children}
</div>
{/* Industrial accent dot */}
<div className={`absolute top-4 ${isRight ? 'left-4' : 'right-4'} w-1.5 h-1.5 rounded-full ${isRight ? 'bg-primary' : 'bg-primary/30'}`} />
</div>
</div>
</div>

View File

@@ -0,0 +1,50 @@
import React from 'react';
interface ComparisonGridItem {
label: string;
leftValue: string;
rightValue: string;
}
interface ComparisonGridProps {
title?: string;
leftLabel: string;
rightLabel: string;
items: ComparisonGridItem[];
}
export default function ComparisonGrid({ title, leftLabel, rightLabel, items }: ComparisonGridProps) {
return (
<div className="my-16 not-prose">
{title && (
<h3 className="text-2xl font-bold text-text-primary mb-8">
{title}
</h3>
)}
<div className="border border-neutral-200 rounded-3xl overflow-hidden shadow-sm bg-white">
<div className="grid grid-cols-3 bg-neutral-50 border-b border-neutral-200">
<div className="p-4 md:p-6"></div>
<div className="p-4 md:p-6 text-center font-bold text-primary uppercase tracking-widest border-l border-neutral-200 text-xs md:text-sm">
{leftLabel}
</div>
<div className="p-4 md:p-6 text-center font-bold text-primary uppercase tracking-widest border-l border-neutral-200 text-xs md:text-sm">
{rightLabel}
</div>
</div>
{items.map((item, index) => (
<div key={index} className="grid grid-cols-3 border-b border-neutral-200 last:border-0 hover:bg-neutral-50 transition-colors group">
<div className="p-4 md:p-6 font-bold text-text-primary flex items-center text-sm md:text-base">
{item.label}
</div>
<div className="p-4 md:p-6 text-center text-text-secondary border-l border-neutral-200 flex items-center justify-center group-hover:text-text-primary transition-colors text-sm md:text-base">
{item.leftValue}
</div>
<div className="p-4 md:p-6 text-center text-text-secondary border-l border-neutral-200 flex items-center justify-center group-hover:text-text-primary transition-colors text-sm md:text-base">
{item.rightValue}
</div>
</div>
))}
</div>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import Scribble from '@/components/Scribble';
interface HighlightBoxProps {
title?: string;
@@ -7,21 +8,31 @@ interface HighlightBoxProps {
}
const colorStyles = {
primary: 'bg-gradient-to-br from-primary/10 to-primary/5 border-primary/30',
secondary: 'bg-gradient-to-br from-blue-50 to-blue-100/50 border-blue-200',
accent: 'bg-gradient-to-br from-green-50 to-green-100/50 border-green-200',
primary: 'bg-gradient-to-br from-primary/5 to-transparent border-primary/20',
secondary: 'bg-gradient-to-br from-blue-50/50 to-transparent border-blue-200/50',
accent: 'bg-gradient-to-br from-accent/5 to-transparent border-accent/20',
};
export default function HighlightBox({ title, children, color = 'primary' }: HighlightBoxProps) {
return (
<div className={`my-10 p-8 rounded-2xl border-2 ${colorStyles[color]} shadow-sm`}>
<div className={`my-12 p-8 md:p-10 rounded-3xl border ${colorStyles[color]} shadow-sm relative overflow-hidden group`}>
{/* Industrial accent corner */}
<div className="absolute top-0 right-0 w-16 h-16 bg-primary/5 -mr-8 -mt-8 rotate-45 transition-transform group-hover:scale-110" />
{title && (
<h3 className="text-2xl font-bold text-text-primary mb-4 flex items-center gap-3">
<span className="w-1.5 h-8 bg-primary rounded-full"></span>
{title}
<h3 className="text-2xl font-bold text-text-primary mb-6 flex items-center gap-4 relative">
<span className="relative">
{title}
{color === 'accent' && (
<Scribble
variant="underline"
className="absolute -bottom-2 left-0 w-full h-3 text-accent/40"
/>
)}
</span>
</h3>
)}
<div className="prose prose-lg max-w-none">
<div className="prose prose-lg max-w-none relative z-10">
{children}
</div>
</div>

View File

@@ -9,47 +9,63 @@ export default function PowerCTA({ locale }: PowerCTAProps) {
const isDe = locale === 'de';
return (
<div className="my-12 p-8 md:p-10 bg-white rounded-xl shadow-lg border border-neutral-100 relative overflow-hidden">
{/* Decorative background element */}
<div className="absolute top-0 right-0 w-32 h-32 bg-primary/5 rounded-bl-full -mr-10 -mt-10" />
<div className="my-16 p-10 md:p-16 bg-neutral-dark rounded-[2rem] shadow-2xl relative overflow-hidden group">
{/* Industrial background pattern */}
<div className="absolute inset-0 opacity-10 pointer-events-none">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(#3b82f6_1px,transparent_1px)] [background-size:40px_40px]" />
</div>
{/* Decorative accent */}
<div className="absolute top-0 right-0 w-64 h-64 bg-primary/20 rounded-full blur-3xl -mr-32 -mt-32 transition-transform group-hover:scale-110 duration-1000" />
<div className="relative z-10">
<h3 className="text-2xl font-bold text-text-primary mb-4 flex items-center gap-3">
<span className="text-3xl">💡</span>
{isDe ? 'Benötigen Sie Strom?' : 'Need power?'}
<span className="text-primary">{isDe ? 'Wir sind für Sie da!' : "We've got you covered!"}</span>
<div className="inline-block px-4 py-1 bg-accent/20 text-accent text-xs font-bold uppercase tracking-[0.2em] rounded-full mb-8">
{isDe ? 'Lösungen' : 'Solutions'}
</div>
<h3 className="text-3xl md:text-5xl font-bold text-white mb-8 leading-tight">
{isDe ? 'Bereit für die' : 'Ready for the'}
<span className="text-accent block">{isDe ? 'Energiewende?' : 'Energy Transition?'}</span>
</h3>
<p className="text-lg text-text-secondary mb-6 leading-relaxed">
<p className="text-xl text-white/70 mb-10 leading-relaxed max-w-2xl">
{isDe
? 'Von der Planung von Wind- und Solarparks bis zur Lieferung hochwertiger Energiekabel wie NA2XS(F)2Y, NAYY und NA2XY erwecken wir Energienetze zum Leben.'
: 'From wind and solar park planning to delivering high-quality energy cables like NA2XS(F)2Y, NAYY, and NA2XY, we bring energy networks to life.'
? 'Von der Planung von Wind- und Solarparks bis zur Lieferung hochwertiger Energiekabel erwecken wir Ihre Projekte zum Leben.'
: 'From wind and solar park planning to delivering high-quality energy cables, we bring your projects to life.'
}
</p>
<ul className="space-y-2 mb-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
{[
isDe ? 'Schnelle Lieferung dank unseres strategischen Hubs.' : 'Fast delivery thanks to our strategic hub.',
isDe ? 'Nachhaltige Lösungen für eine grünere Zukunft.' : 'Sustainable solutions for a greener tomorrow.',
isDe ? 'Vertraut von Branchenführern in Deutschland, Österreich und den Niederlanden.' : 'Trusted by industry leaders in Germany, Austria and the Netherlands.'
isDe ? 'Strategischer Hub für schnelle Lieferung' : 'Strategic hub for fast delivery',
isDe ? 'Nachhaltige Kabelinfrastruktur' : 'Sustainable cable infrastructure',
isDe ? 'Expertenberatung für Großprojekte' : 'Expert consulting for large-scale projects',
isDe ? 'Zertifizierte Qualität nach EU-Standards' : 'Certified quality according to EU standards'
].map((item, i) => (
<li key={i} className="flex items-start gap-2 text-text-secondary">
<span className="text-green-500 mt-1"></span>
<span>{item}</span>
</li>
<div key={i} className="flex items-center gap-4 text-white/80">
<div className="w-6 h-6 rounded-full bg-accent/20 flex items-center justify-center flex-shrink-0">
<svg className="w-3 h-3 text-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</div>
<span className="text-sm font-medium">{item}</span>
</div>
))}
</ul>
</div>
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center">
<p className="font-medium text-text-primary">
{isDe ? 'Lassen Sie uns gemeinsam eine grünere Zukunft gestalten.' : "Let's power a greener future together."}
</p>
<div className="flex flex-col sm:flex-row gap-6 items-start sm:items-center pt-8 border-t border-white/10">
<Link
href={`/${locale}/contact`}
className="inline-flex items-center gap-2 px-6 py-3 bg-primary text-white font-bold rounded-lg hover:bg-primary/90 transition-all shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
className="inline-flex items-center gap-3 px-8 py-4 bg-primary text-white font-bold rounded-full hover:bg-primary/90 transition-all shadow-xl hover:shadow-primary/20 transform hover:-translate-y-1 group/btn"
>
{isDe ? '👉 Kontakt aufnehmen' : '👉 Get in touch'}
{isDe ? 'Projekt anfragen' : 'Inquire Project'}
<svg className="w-5 h-5 transition-transform group-hover/btn:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</Link>
<p className="text-white/50 text-sm font-medium">
{isDe ? 'Kostenlose Erstberatung für Ihr Vorhaben.' : 'Free initial consultation for your project.'}
</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
'use client';
import React, { useEffect, useState } from 'react';
export default function ReadingProgressBar() {
const [completion, setCompletion] = useState(0);
useEffect(() => {
const updateScrollCompletion = () => {
const currentProgress = window.scrollY;
const scrollHeight = document.body.scrollHeight - window.innerHeight;
if (scrollHeight) {
setCompletion(
Number((currentProgress / scrollHeight).toFixed(2)) * 100
);
}
};
window.addEventListener('scroll', updateScrollCompletion);
return () => {
window.removeEventListener('scroll', updateScrollCompletion);
};
}, []);
return (
<div className="fixed top-0 left-0 w-full h-1 z-50 bg-neutral-100">
<div
className="h-full bg-primary transition-all duration-150 ease-out"
style={{ width: `${completion}%` }}
/>
</div>
);
}

View File

@@ -1,46 +1,22 @@
'use client';
import React, { useEffect, useRef, useState } from 'react';
import React from 'react';
interface SplitHeadingProps {
children: React.ReactNode;
className?: string;
id?: string;
}
export default function SplitHeading({ children, className = '' }: SplitHeadingProps) {
const elementRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
});
},
{ threshold: 0.1 }
);
if (elementRef.current) {
observer.observe(elementRef.current);
}
return () => observer.disconnect();
}, []);
export default function SplitHeading({ children, className = '', id }: SplitHeadingProps) {
return (
<div
ref={elementRef}
className={`transform transition-all duration-1000 ease-out ${
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'
} ${className}`}
id={id}
className={className}
>
<h3 className="text-2xl md:text-3xl font-bold leading-tight text-text-primary">
<h2 className="text-2xl md:text-3xl font-bold leading-tight text-text-primary">
{children}
</h3>
</h2>
</div>
);
}

View File

@@ -12,23 +12,28 @@ interface StatsProps {
export default function Stats({ stats }: StatsProps) {
return (
<div className="my-12 grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="my-16 grid grid-cols-1 md:grid-cols-3 border border-neutral-200 rounded-2xl overflow-hidden shadow-sm">
{stats.map((stat, index) => (
<div
key={index}
className="bg-gradient-to-br from-primary/5 to-primary/10 p-6 rounded-xl border border-primary/20 text-center hover:shadow-md transition-shadow"
className={`p-8 flex flex-col items-center text-center transition-all duration-500 hover:bg-neutral-50 group ${
index !== stats.length - 1 ? 'border-b md:border-b-0 md:border-r border-neutral-200' : ''
}`}
>
{stat.icon && (
<div className="text-primary mb-3 flex justify-center">
<div className="text-primary mb-4 transform transition-transform group-hover:scale-110 duration-500">
{stat.icon}
</div>
)}
<div className="text-4xl font-bold text-primary mb-2">
<div className="text-5xl font-bold text-text-primary mb-3 tracking-tight">
{stat.value}
</div>
<div className="text-text-secondary font-medium">
<div className="text-xs font-bold text-text-secondary uppercase tracking-[0.2em]">
{stat.label}
</div>
{/* Industrial accent line */}
<div className="w-8 h-[2px] bg-primary/20 mt-6 transition-all group-hover:w-16 group-hover:bg-primary duration-500" />
</div>
))}
</div>

View File

@@ -0,0 +1,36 @@
import React from 'react';
interface StickyNarrativeItem {
title: string;
content: string;
}
interface StickyNarrativeProps {
title: string;
items: StickyNarrativeItem[];
}
export default function StickyNarrative({ title, items }: StickyNarrativeProps) {
return (
<div className="my-24 grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20 items-start not-prose">
<div className="lg:col-span-4 lg:sticky lg:top-32">
<h3 className="text-3xl md:text-4xl font-bold text-primary leading-tight">
{title}
</h3>
<div className="w-16 h-1.5 bg-accent mt-8 rounded-full" />
</div>
<div className="lg:col-span-8 space-y-12 md:space-y-16">
{items.map((item, index) => (
<div key={index} className="group border-b border-neutral-200 pb-12 last:border-0 last:pb-0">
<h4 className="text-xl md:text-2xl font-bold text-text-primary mb-4 group-hover:text-primary transition-colors">
{item.title}
</h4>
<div className="text-lg text-text-secondary leading-relaxed">
{item.content}
</div>
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,86 @@
'use client';
import React, { useEffect, useState } from 'react';
import { cn } from '@/components/ui/utils';
interface TocItem {
id: string;
text: string;
level: number;
}
interface TableOfContentsProps {
headings: TocItem[];
locale: string;
}
export default function TableOfContents({ headings, locale }: TableOfContentsProps) {
const [activeId, setActiveId] = useState<string>('');
useEffect(() => {
const observerOptions = {
rootMargin: '-100px 0% -80% 0%',
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveId(entry.target.id);
}
});
}, observerOptions);
const elements = headings.map((h) => document.getElementById(h.id));
elements.forEach((el) => {
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, [headings]);
if (headings.length === 0) return null;
return (
<nav className="hidden lg:block w-full ml-12">
<div className="relative pl-6 border-l border-neutral-200">
<h4 className="text-xs font-bold uppercase tracking-[0.2em] text-text-primary/50 mb-6">
{locale === 'de' ? 'Inhalt' : 'Table of Contents'}
</h4>
<ul className="space-y-4">
{headings.map((heading) => (
<li
key={heading.id}
style={{ paddingLeft: `${(heading.level - 2) * 1}rem` }}
className="relative"
>
{activeId === heading.id && (
<div className="absolute -left-[25px] top-0 w-[2px] h-full bg-primary transition-all duration-300" />
)}
<a
href={`#${heading.id}`}
className={cn(
"text-sm transition-all duration-300 hover:text-primary block leading-snug",
activeId === heading.id
? "text-primary font-bold translate-x-1"
: "text-text-secondary font-medium hover:translate-x-1"
)}
onClick={(e) => {
e.preventDefault();
const element = document.getElementById(heading.id);
if (element) {
const yOffset = -100;
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({ top: y, behavior: 'smooth' });
}
}}
>
{heading.text}
</a>
</li>
))}
</ul>
</div>
</nav>
);
}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import Scribble from '@/components/Scribble';
interface TechnicalGridItem {
label: string;
value: string;
}
interface TechnicalGridProps {
title?: string;
items: TechnicalGridItem[];
}
export default function TechnicalGrid({ title, items }: TechnicalGridProps) {
return (
<div className="my-16 not-prose">
{title && (
<h3 className="text-2xl font-bold text-text-primary mb-8 flex items-center gap-4 relative">
<span className="relative inline-block">
{title}
<Scribble
variant="underline"
className="absolute -bottom-2 left-0 w-full h-3 text-accent/40"
/>
</span>
</h3>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{items.map((item, index) => (
<div key={index} className="bg-white p-8 rounded-2xl border border-neutral-200 shadow-sm hover:shadow-md transition-all duration-300 group relative overflow-hidden">
<div className="absolute top-0 right-0 w-16 h-16 bg-primary/5 -mr-8 -mt-8 rotate-45 transition-transform group-hover:scale-110" />
<span className="block text-xs font-bold text-primary uppercase tracking-[0.2em] mb-3 opacity-70">
{item.label}
</span>
<span className="text-lg text-text-secondary leading-relaxed group-hover:text-text-primary transition-colors">
{item.value}
</span>
</div>
))}
</div>
</div>
);
}

View File

@@ -10,36 +10,64 @@ interface VisualLinkPreviewProps {
}
export default function VisualLinkPreview({ url, title, summary, image }: VisualLinkPreviewProps) {
const hostname = (() => {
try {
return new URL(url).hostname;
} catch {
return url;
}
})();
return (
<Link href={url} target="_blank" rel="noopener noreferrer" className="block my-8 no-underline group">
<div className="flex flex-col md:flex-row border border-neutral-dark rounded-lg overflow-hidden bg-white hover:shadow-md transition-shadow">
<div className="relative w-full md:w-48 h-48 md:h-auto flex-shrink-0 bg-neutral-light flex items-center justify-center">
<Link href={url} target="_blank" rel="noopener noreferrer" className="block my-12 no-underline group">
<div className="flex flex-col md:flex-row border border-neutral-200 rounded-2xl overflow-hidden bg-white transition-all duration-500 hover:shadow-2xl hover:border-primary/20 hover:-translate-y-1 group">
<div className="relative w-full md:w-64 h-48 md:h-auto flex-shrink-0 bg-neutral-50 overflow-hidden">
{image ? (
<img
<Image
src={image}
alt={title}
className="w-full h-full object-cover"
fill
unoptimized
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
) : (
<div className="text-neutral-dark">No Image</div>
<div className="w-full h-full flex items-center justify-center bg-primary/5">
<svg className="w-12 h-12 text-primary/20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
</svg>
</div>
)}
{/* Industrial overlay */}
<div className="absolute inset-0 bg-primary/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none" />
</div>
<div className="p-4 flex flex-col justify-center">
<h3 className="text-lg font-bold text-primary mb-2 group-hover:underline line-clamp-2">
<div className="p-8 flex flex-col justify-center relative">
{/* Industrial accent corner */}
<div className="absolute top-0 right-0 w-12 h-12 bg-primary/5 -mr-6 -mt-6 rotate-45 transition-transform group-hover:scale-110" />
<div className="flex items-center gap-2 mb-3">
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary/60 bg-primary/5 px-2 py-0.5 rounded">
External Link
</span>
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-text-secondary/40">
{hostname}
</span>
</div>
<h3 className="text-xl font-bold text-text-primary mb-3 group-hover:text-primary transition-colors line-clamp-2 leading-tight">
{title}
</h3>
<p className="text-text-secondary text-sm line-clamp-3">
<p className="text-text-secondary text-base line-clamp-2 leading-relaxed mb-4">
{summary}
</p>
<span className="text-xs text-text-secondary mt-2 opacity-70">
{(() => {
try {
return new URL(url).hostname;
} catch (e) {
return url;
}
})()}
</span>
<div className="flex items-center gap-2 text-primary font-bold text-xs uppercase tracking-widest">
<span>Read more</span>
<svg className="w-4 h-4 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</div>
</div>
</div>
</Link>

View File

@@ -7,7 +7,7 @@ export default function CTA() {
const locale = useLocale();
return (
<Section className="bg-primary-dark text-white py-32 relative overflow-hidden">
<Section className="bg-primary text-white py-32 relative overflow-hidden">
<div className="absolute top-0 right-0 w-1/3 h-full bg-accent/5 -skew-x-12 translate-x-1/2" />
<div className="absolute bottom-0 left-0 w-1/4 h-1/2 bg-primary/10 rounded-full blur-3xl -translate-x-1/2 translate-y-1/2" />

View File

@@ -16,8 +16,8 @@ export default function Experience() {
className="object-cover object-center scale-105 animate-slow-zoom"
unoptimized
/>
<div className="absolute inset-0 bg-primary-dark/80 mix-blend-multiply" />
<div className="absolute inset-0 bg-gradient-to-r from-primary-dark via-primary-dark/40 to-transparent" />
<div className="absolute inset-0 bg-primary/80 mix-blend-multiply" />
<div className="absolute inset-0 bg-gradient-to-r from-primary via-primary/40 to-transparent" />
</div>
<Container className="relative z-10">

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { useTranslations } from 'next-intl';
import { Container, Button } from '@/components/ui';
import { Container, Button, Section, Heading } from '@/components/ui';
import Scribble from '@/components/Scribble';
import HeroIllustration from './HeroIllustration';
@@ -8,12 +8,12 @@ export default function Hero() {
const t = useTranslations('Home.hero');
return (
<section className="relative h-[70vh] md:h-[90vh] flex items-center justify-center overflow-hidden bg-primary-dark">
<Section className="relative h-[70vh] md:h-[90vh] flex items-center justify-center overflow-hidden bg-primary py-0 md:py-0 lg:py-0">
<HeroIllustration />
<Container className="relative z-10 text-left text-white w-full">
<div className="max-w-5xl animate-slide-up">
<h1 className="text-4xl md:text-7xl lg:text-8xl font-extrabold mb-4 md:mb-8 tracking-tight leading-[1.05] max-w-[15ch] md:max-w-none">
<Heading level={1} className="mb-4 md:mb-8 tracking-tight leading-[1.05] max-w-[15ch] md:max-w-none text-white">
{t.rich('title', {
green: (chunks) => (
<span className="relative inline-block">
@@ -22,8 +22,8 @@ export default function Hero() {
</span>
)
})}
</h1>
<p className="text-lg md:text-2xl text-white/70 leading-relaxed max-w-2xl mb-8 md:mb-12 line-clamp-2 md:line-clamp-none">
</Heading>
<p className="text-lg md:text-2xl text-white leading-relaxed max-w-2xl mb-8 md:mb-12 line-clamp-2 md:line-clamp-none">
{t('subtitle')}
</p>
<div className="flex flex-col md:flex-row gap-3 md:gap-6">
@@ -31,7 +31,7 @@ export default function Hero() {
{t('cta')}
<span className="ml-3 transition-transform group-hover:translate-x-1">&rarr;</span>
</Button>
<Button href="/products" variant="ghost" size="lg" className="group w-full md:w-auto text-white hover:bg-white/10 md:bg-white md:text-primary md:hover:bg-neutral-light md:h-16 md:px-10 md:text-xl">
<Button href="/products" variant="ghost" size="lg" className="group w-full md:w-auto text-white hover:bg-white/10 md:bg-white md:text-saturated md:hover:bg-neutral-light md:h-16 md:px-10 md:text-xl">
{t('exploreProducts')}
</Button>
</div>
@@ -43,6 +43,6 @@ export default function Hero() {
<div className="w-1 h-2 bg-white rounded-full" />
</div>
</div>
</section>
</Section>
);
}

View File

@@ -18,8 +18,8 @@ export default function MeetTheTeam() {
className="object-cover scale-105 animate-slow-zoom"
unoptimized
/>
<div className="absolute inset-0 bg-primary-dark/70 mix-blend-multiply" />
<div className="absolute inset-0 bg-gradient-to-t from-primary-dark via-primary-dark/20 to-transparent" />
<div className="absolute inset-0 bg-primary/70 mix-blend-multiply" />
<div className="absolute inset-0 bg-gradient-to-t from-primary via-primary/20 to-transparent" />
</div>
<Container className="relative z-10">
@@ -43,10 +43,10 @@ export default function MeetTheTeam() {
<div className="flex items-center gap-4">
<div className="flex -space-x-4">
<div className="w-14 h-14 rounded-full border-4 border-primary-dark overflow-hidden relative">
<div className="w-14 h-14 rounded-full border-4 border-primary overflow-hidden relative">
<Image src="/uploads/2024/12/DSC07768-Large.webp" alt={teamT('michael.name')} fill className="object-cover" />
</div>
<div className="w-14 h-14 rounded-full border-4 border-primary-dark overflow-hidden relative">
<div className="w-14 h-14 rounded-full border-4 border-primary overflow-hidden relative">
<Image src="/uploads/2024/12/DSC07963-Large.webp" alt={teamT('klaus.name')} fill className="object-cover" />
</div>
</div>

View File

@@ -40,7 +40,7 @@ export default function ProductCategories() {
];
return (
<Section className="bg-neutral-light py-0 -mt-px">
<Section className="bg-neutral-light py-0 md:py-0 lg:py-0 -mt-px">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
{categories.map((category, idx) => (
<Link key={idx} href={category.href} className="group block relative h-[400px] md:h-[500px] lg:h-[650px] overflow-hidden border-b md:border-b-0 md:border-r border-white/10 last:border-0">

View File

@@ -5,7 +5,7 @@ export default function VideoSection() {
const t = useTranslations('Home.video');
return (
<section className="relative h-[70vh] overflow-hidden bg-primary-dark">
<section className="relative h-[70vh] overflow-hidden bg-primary">
<video
className="w-full h-full object-cover opacity-60"
autoPlay
@@ -15,7 +15,7 @@ export default function VideoSection() {
>
<source src="/uploads/2024/12/making-of-metal-cable-on-factory-2023-11-27-04-55-16-utc-2.webm" type="video/mp4" />
</video>
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/60 via-transparent to-primary-dark/60 flex items-center justify-center">
<div className="absolute inset-0 bg-gradient-to-b from-primary/60 via-transparent to-primary/60 flex items-center justify-center">
<div className="max-w-5xl px-6 text-center animate-slide-up">
<h2 className="text-4xl md:text-6xl lg:text-7xl font-extrabold text-white leading-[1.1]">
{t.rich('title', {

View File

@@ -17,8 +17,8 @@ export default function WhatWeDo() {
<p className="text-lg md:text-xl text-text-secondary leading-relaxed">
{t('subtitle')}
</p>
<div className="mt-8 md:mt-12 p-6 md:p-8 bg-primary-light rounded-2xl border border-primary/10">
<p className="text-primary font-bold text-base md:text-lg italic">
<div className="mt-8 md:mt-12 p-6 md:p-8 bg-saturated/10 rounded-2xl border border-saturated/10">
<p className="text-saturated font-bold text-base md:text-lg italic">
"{t('quote')}"
</p>
</div>
@@ -28,12 +28,12 @@ export default function WhatWeDo() {
{[0, 1, 2, 3].map((idx) => (
<div key={idx} className="group">
<div className="flex items-center gap-4 mb-4 md:mb-6">
<span className="flex items-center justify-center w-10 h-10 md:w-12 md:h-12 rounded-full bg-primary text-white font-bold text-base md:text-lg shadow-lg shadow-primary/20 group-hover:scale-110 transition-transform">
<span className="flex items-center justify-center w-10 h-10 md:w-12 md:h-12 rounded-full bg-saturated text-white font-bold text-base md:text-lg shadow-lg shadow-saturated/20 group-hover:scale-110 transition-transform">
{idx + 1}
</span>
<div className="h-px flex-grow bg-neutral-medium" />
</div>
<h3 className="text-xl md:text-2xl font-bold mb-3 md:mb-4 text-primary group-hover:text-accent-dark transition-colors">{t(`items.${idx}.title`)}</h3>
<h3 className="text-xl md:text-2xl font-bold mb-3 md:mb-4 text-saturated group-hover:text-accent-dark transition-colors">{t(`items.${idx}.title`)}</h3>
<p className="text-text-secondary text-base md:text-lg leading-relaxed">{t(`items.${idx}.description`)}</p>
</div>
))}

View File

@@ -26,7 +26,7 @@ export default function WhyChooseUs() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</div>
<span className="font-bold text-primary">{t(`features.${i}`)}</span>
<span className="font-bold text-saturated">{t(`features.${i}`)}</span>
</div>
))}
</div>
@@ -35,10 +35,10 @@ export default function WhyChooseUs() {
<div className="lg:col-span-8 grid grid-cols-1 md:grid-cols-2 gap-8 order-2 lg:order-1">
{[0, 1, 2, 3].map((idx) => (
<div key={idx} className="p-10 bg-white rounded-3xl border border-neutral-medium hover:border-accent transition-all duration-500 hover:shadow-xl group">
<div className="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-8 group-hover:bg-accent transition-colors duration-500">
<span className="text-primary font-bold text-xl group-hover:text-primary-dark">0{idx + 1}</span>
<div className="w-14 h-14 bg-saturated/10 rounded-2xl flex items-center justify-center mb-8 group-hover:bg-accent transition-colors duration-500">
<span className="text-saturated font-bold text-xl group-hover:text-primary-dark">0{idx + 1}</span>
</div>
<h3 className="text-2xl font-bold mb-4 text-primary">{t(`items.${idx}.title`)}</h3>
<h3 className="text-2xl font-bold mb-4 text-saturated">{t(`items.${idx}.title`)}</h3>
<p className="text-text-secondary text-lg leading-relaxed">{t(`items.${idx}.description`)}</p>
</div>
))}

View File

@@ -1,130 +1 @@
import React from 'react';
import Link from 'next/link';
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'accent' | 'outline' | 'ghost' | 'white';
size?: 'sm' | 'md' | 'lg' | 'xl';
href?: string;
className?: string;
children?: React.ReactNode;
}
export function Button({ className, variant = 'primary', size = 'md', href, ...props }: ButtonProps) {
const baseStyles = 'inline-flex items-center justify-center rounded-full font-semibold transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none active:scale-95';
const variants = {
primary: 'bg-primary text-white hover:bg-primary-dark shadow-md hover:shadow-lg',
secondary: 'bg-secondary text-white hover:bg-secondary-light shadow-md hover:shadow-lg',
accent: 'bg-accent text-primary-dark hover:bg-accent-dark shadow-md hover:shadow-lg',
outline: 'border-2 border-primary bg-transparent hover:bg-primary hover:text-white text-primary',
ghost: 'hover:bg-primary-light text-primary',
white: 'bg-white text-primary hover:bg-neutral-light shadow-md hover:shadow-lg',
};
const sizes = {
sm: 'h-9 px-4 text-sm',
md: 'h-11 px-6 text-base',
lg: 'h-14 px-8 text-lg',
xl: 'h-16 px-10 text-xl',
};
const styles = cn(baseStyles, variants[variant], sizes[size], className);
if (href) {
return (
<Link href={href} className={styles}>
{props.children}
</Link>
);
}
return (
<button className={styles} {...props} />
);
}
export function Section({ className, children, ...props }: React.HTMLAttributes<HTMLElement>) {
return (
<section className={cn('py-16 md:py-28 lg:py-36 overflow-hidden content-visibility-auto', className)} {...props}>
{children}
</section>
);
}
export function Container({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div className={cn('container mx-auto px-4 md:px-12 lg:px-16 max-w-7xl', className)} {...props}>
{children}
</div>
);
}
export function Heading({
level = 2,
children,
className,
subtitle,
align = 'left'
}: {
level?: 1 | 2 | 3 | 4;
children: React.ReactNode;
className?: string;
subtitle?: string;
align?: 'left' | 'center' | 'right';
}) {
const Tag = `h${level}` as any;
const sizes = {
1: 'text-4xl md:text-6xl lg:text-7xl xl:text-8xl font-extrabold leading-[1.1]',
2: 'text-3xl md:text-5xl lg:text-6xl font-bold leading-tight',
3: 'text-2xl md:text-3xl lg:text-4xl font-bold',
4: 'text-xl md:text-2xl font-bold',
};
const alignments = {
left: 'text-left',
center: 'text-center mx-auto',
right: 'text-right',
};
return (
<div className={cn('mb-8 md:mb-16', alignments[align], className)}>
{subtitle && (
<span className="inline-block text-accent font-bold tracking-widest uppercase text-xs md:text-sm mb-3 md:mb-4 animate-fade-in">
{subtitle}
</span>
)}
<Tag className={cn(sizes[level as keyof typeof sizes], 'text-primary')}>
{children}
</Tag>
</div>
);
}
export function Card({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div className={cn('premium-card overflow-hidden', className)} {...props}>
{children}
</div>
);
}
export function Badge({ children, className, variant = 'primary' }: { children: React.ReactNode, className?: string, variant?: 'primary' | 'accent' | 'neutral' }) {
const variants = {
primary: 'bg-primary-light text-primary',
accent: 'bg-accent-light text-accent-dark',
neutral: 'bg-neutral-medium text-text-secondary',
};
return (
<span className={cn('inline-flex items-center px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider', variants[variant], className)}>
{children}
</span>
);
}
export * from './ui/index';

17
components/ui/Badge.tsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import { cn } from './utils';
export function Badge({ children, className, variant = 'primary' }: { children: React.ReactNode, className?: string, variant?: 'primary' | 'accent' | 'neutral' | 'saturated' }) {
const variants = {
primary: 'bg-primary-light text-primary',
accent: 'bg-accent-light text-accent-dark',
neutral: 'bg-neutral-medium text-text-secondary',
saturated: 'bg-saturated text-white',
};
return (
<span className={cn('inline-flex items-center px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider', variants[variant], className)}>
{children}
</span>
);
}

46
components/ui/Button.tsx Normal file
View File

@@ -0,0 +1,46 @@
import React from 'react';
import Link from 'next/link';
import { cn } from './utils';
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'accent' | 'saturated' | 'outline' | 'ghost' | 'white';
size?: 'sm' | 'md' | 'lg' | 'xl';
href?: string;
className?: string;
children?: React.ReactNode;
}
export function Button({ className, variant = 'primary', size = 'md', href, ...props }: ButtonProps) {
const baseStyles = 'inline-flex items-center justify-center rounded-full font-semibold transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none active:scale-95';
const variants = {
primary: 'bg-primary text-white hover:bg-primary-dark shadow-md hover:shadow-lg',
secondary: 'bg-secondary text-white hover:bg-secondary-light shadow-md hover:shadow-lg',
accent: 'bg-accent text-primary-dark hover:bg-accent-dark shadow-md hover:shadow-lg',
saturated: 'bg-saturated text-white hover:bg-primary shadow-md hover:shadow-lg',
outline: 'border-2 border-primary bg-transparent hover:bg-primary hover:text-white text-primary',
ghost: 'hover:bg-primary-light text-primary',
white: 'bg-white text-primary hover:bg-neutral-light shadow-md hover:shadow-lg',
};
const sizes = {
sm: 'h-9 px-4 text-sm',
md: 'h-11 px-6 text-base',
lg: 'h-14 px-8 text-lg',
xl: 'h-16 px-10 text-xl',
};
const styles = cn(baseStyles, variants[variant], sizes[size], className);
if (href) {
return (
<Link href={href} className={styles}>
{props.children}
</Link>
);
}
return (
<button className={styles} {...props} />
);
}

View File

@@ -1,9 +1,11 @@
import React from 'react';
import { cn } from './utils';
interface CalloutProps {
type?: 'info' | 'warning' | 'success' | 'tip';
title?: string;
children: React.ReactNode;
className?: string;
}
const icons = {
@@ -43,11 +45,11 @@ const iconColors = {
tip: 'text-purple-500',
};
export default function Callout({ type = 'info', title, children }: CalloutProps) {
export function Callout({ type = 'info', title, children, className }: CalloutProps) {
return (
<div className={`my-8 p-6 rounded-xl border-2 ${styles[type]}`}>
<div className={cn('my-8 p-6 rounded-xl border-2', styles[type], className)}>
<div className="flex gap-4">
<div className={`flex-shrink-0 ${iconColors[type]}`}>
<div className={cn('flex-shrink-0', iconColors[type])}>
{icons[type]}
</div>
<div className="flex-1">

10
components/ui/Card.tsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react';
import { cn } from './utils';
export function Card({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div className={cn('premium-card overflow-hidden', className)} {...props}>
{children}
</div>
);
}

View File

@@ -0,0 +1,10 @@
import React from 'react';
import { cn } from './utils';
export function Container({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div className={cn('container mx-auto px-4 md:px-12 lg:px-16 max-w-7xl', className)} {...props}>
{children}
</div>
);
}

44
components/ui/Heading.tsx Normal file
View File

@@ -0,0 +1,44 @@
import React from 'react';
import { cn } from './utils';
export function Heading({
level = 2,
children,
className,
subtitle,
align = 'left'
}: {
level?: 1 | 2 | 3 | 4;
children: React.ReactNode;
className?: string;
subtitle?: string;
align?: 'left' | 'center' | 'right';
}) {
const Tag = `h${level}` as any;
const sizes = {
1: 'text-4xl md:text-6xl lg:text-7xl xl:text-8xl font-extrabold leading-[1.1]',
2: 'text-3xl md:text-5xl lg:text-6xl font-bold leading-tight',
3: 'text-2xl md:text-3xl lg:text-4xl font-bold',
4: 'text-xl md:text-2xl font-bold',
};
const alignments = {
left: 'text-left',
center: 'text-center mx-auto',
right: 'text-right',
};
return (
<div className={cn('mb-8 md:mb-16 text-primary', alignments[align], className)}>
{subtitle && (
<span className="inline-block text-accent font-bold tracking-widest uppercase text-xs md:text-sm mb-3 md:mb-4 animate-fade-in">
{subtitle}
</span>
)}
<Tag className={cn(sizes[level as keyof typeof sizes])}>
{children}
</Tag>
</div>
);
}

18
components/ui/Input.tsx Normal file
View File

@@ -0,0 +1,18 @@
import React from 'react';
import { cn } from './utils';
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
className?: string;
}
export function Input({ className, ...props }: InputProps) {
return (
<input
className={cn(
'w-full px-4 md:px-6 py-2.5 md:py-4 bg-neutral-light/50 border border-neutral-dark/10 rounded-xl md:rounded-2xl focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent focus:bg-white transition-all duration-300 placeholder:text-neutral-dark/40 text-sm md:text-lg',
className
)}
{...props}
/>
);
}

18
components/ui/Label.tsx Normal file
View File

@@ -0,0 +1,18 @@
import React from 'react';
import { cn } from './utils';
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
className?: string;
}
export function Label({ className, ...props }: LabelProps) {
return (
<label
className={cn(
'text-[10px] md:text-xs font-extrabold text-primary uppercase tracking-widest',
className
)}
{...props}
/>
);
}

10
components/ui/Section.tsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react';
import { cn } from './utils';
export function Section({ className, children, ...props }: React.HTMLAttributes<HTMLElement>) {
return (
<section className={cn('py-16 md:py-28 lg:py-36 overflow-hidden content-visibility-auto', className)} {...props}>
{children}
</section>
);
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { cn } from './utils';
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
className?: string;
}
export function Textarea({ className, ...props }: TextareaProps) {
return (
<textarea
className={cn(
'w-full px-4 md:px-6 py-2.5 md:py-4 bg-neutral-light/50 border border-neutral-dark/10 rounded-xl md:rounded-2xl focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent focus:bg-white transition-all duration-300 placeholder:text-neutral-dark/40 text-sm md:text-lg resize-none',
className
)}
{...props}
/>
);
}

11
components/ui/index.ts Normal file
View File

@@ -0,0 +1,11 @@
export * from './utils';
export * from './Button';
export * from './Section';
export * from './Container';
export * from './Heading';
export * from './Card';
export * from './Badge';
export * from './Input';
export * from './Textarea';
export * from './Label';
export * from './Callout';

6
components/ui/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -11,33 +11,16 @@ Die Vision ist klar: Ein Europa, das seinen Strom zu 100 % aus erneuerbaren Ener
Am Ende geht es nicht nur um mehr Strom, sondern um kluge Netze, die ihn zuverlässig und verlustarm transportieren können.
## **Das Problem: Alte Netze für eine neue Energiezukunft**
Die heutige Strominfrastruktur wurde für zentrale Großkraftwerke gebaut. Doch erneuerbare Energien funktionieren anders: Sie sind dezentral, wetterabhängig und benötigen flexible Netze. Das führt zu einem massiven **Umbau-Bedarf**.
### **Warum unser Netz aktuell überfordert ist:**
<table>
<thead>
<tr>
<th>Problem</th>
<th>Ursache</th>
<th>Lösung?</th>
</tr>
</thead>
<tbody>
<tr>
<td>Netzengpässe</td>
<td>Alte Leitungen für zentrale Kraftwerke, nicht für dezentrale Energie</td>
<td>Neue Hoch- &amp; Mittelspannungskabel</td>
</tr>
<tr>
<td>Abregelung von Solar- &amp; Windstrom</td>
<td>Netz kann nicht genug Strom aufnehmen</td>
<td>Smart Grids &amp; Speicherlösungen</td>
</tr>
<tr>
<td>Lange Transportwege</td>
<td>Erzeugung oft weit entfernt vom Verbrauch</td>
<td>Hochleistungskabel &amp; lokale Netze</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Herausforderungen im Stromnetz"
items={[
{ title: "Netzengpässe", content: "**Ursache:** Alte Leitungen für zentrale Kraftwerke, nicht für dezentrale Energie. **Lösung:** Neue Hoch- & Mittelspannungskabel." },
{ title: "Abregelung von Solar- & Windstrom", content: "**Ursache:** Netz kann nicht genug Strom aufnehmen. **Lösung:** Smart Grids & Speicherlösungen." },
{ title: "Lange Transportwege", content: "**Ursache:** Erzeugung oft weit entfernt vom Verbrauch. **Lösung:** Hochleistungskabel & lokale Netze." }
]}
/>
⚠️ Ein Netz aus der Vergangenheit kann keine Zukunftsenergie transportieren!
Wer heute nur in erneuerbare Energieanlagen investiert, aber die Kabelinfrastruktur ignoriert, wird morgen teuren ungenutzten Strom haben.
## **Welche Kabel brauchen wir für die Energiewende?**
@@ -51,39 +34,19 @@ Nicht jedes Kabel ist gleich und nicht jedes Kabel ist für die Herausforder
💡 Je besser das Kabel, desto weniger Strom geht unterwegs verloren und desto grüner wird die Energie!
## Solar- und Windparks allein reichen nicht
Ohne passende Kabel bleibt der Strom dort, wo er erzeugt wird. Doch welcher Netzausbau macht wirklich Sinn?
### Erdkabel vs. Freileitungen Was ist die bessere Wahl?
Ein zentrales Thema beim Netzausbau ist die Frage, ob neue Stromtrassen als Freileitungen oder als Erdkabel verlegt werden sollen. Beide Varianten haben ihre Vor- und Nachteile, doch langfristig bietet die Erdverkabelung deutliche Vorteile in Sachen Zuverlässigkeit, Umweltschutz und Netzstabilität.
<table>
<thead>
<tr>
<th>Kriterium</th>
<th>Erdkabel</th>
<th>Freileitung</th>
</tr>
</thead>
<tbody>
<tr>
<td>Netzstabilität</td>
<td>Sehr hoch</td>
<td>Mittel</td>
</tr>
<tr>
<td>Umweltverträglichkeit</td>
<td>Unauffällig, keine Eingriffe ins Landschaftsbild</td>
<td>Sichtbar, problematisch für Vögel</td>
</tr>
<tr>
<td>Wartung &amp; Lebensdauer</td>
<td>Kaum Wartung, langlebig</td>
<td>Witterungsanfällig, kürzere Lebensdauer</td>
</tr>
<tr>
<td>Kosten</td>
<td>Höher in der Installation, aber effizienter im Betrieb</td>
<td>Günstiger zu errichten, aber höhere Folgekosten</td>
</tr>
</tbody>
</table>
<ComparisonGrid
title="Erdkabel vs. Freileitungen Was ist die bessere Wahl?"
leftLabel="Erdkabel"
rightLabel="Freileitung"
items={[
{ label: "Netzstabilität", leftValue: "Sehr hoch", rightValue: "Mittel" },
{ label: "Umweltverträglichkeit", leftValue: "Unauffällig, keine Eingriffe ins Landschaftsbild", rightValue: "Sichtbar, problematisch für Vögel" },
{ label: "Wartung & Lebensdauer", leftValue: "Kaum Wartung, langlebig", rightValue: "Witterungsanfällig, kürzere Lebensdauer" },
{ label: "Kosten", leftValue: "Höher in der Installation, aber effizienter im Betrieb", rightValue: "Günstiger zu errichten, aber höhere Folgekosten" }
]}
/>
Während Freileitungen in der Vergangenheit aufgrund der geringeren Baukosten bevorzugt wurden, sprechen moderne Anforderungen an Netzstabilität, Umweltschutz und Ästhetik zunehmend für Erdkabel. In vielen Ländern setzt sich daher die Erdverkabelung als Standard für neue Hoch- und Mittelspannungstrassen durch.
Wer sich detaillierter mit diesem Thema befassen möchte, findet hier eine umfassende Analyse der Unterschiede zwischen Erdkabeln und Freileitungen. [https://www.hochspannungsblog.at/wissenswertes/netzaufbau/vergleich-freileitung-erdkabel](https://www.hochspannungsblog.at/wissenswertes/netzaufbau/vergleich-freileitung-erdkabel)
<VisualLinkPreview

View File

@@ -17,33 +17,17 @@ Moderne Onshore-Windparks bestehen nicht nur aus Turbinen, sondern aus einem kom
Was viele unterschätzen: Die Kabelstrecken in einem Windpark machen oft einen erheblichen Teil der Gesamtinvestition aus. Sie sind nicht nur Verbindungsglied sie sind die **kritische Infrastruktur**, auf der alles aufbaut.
## Ganzheitliche Planung: Grundlage für nachhaltige Infrastruktur
Die Integration von Windparks in das Stromnetz erfordert eine systemische Herangehensweise. Eine fundierte Planung berücksichtigt dabei nicht nur die Leistungsanforderungen, sondern auch Umgebungsbedingungen, Ausbauszenarien und Genehmigungsprozesse.
**Zu den zentralen Planungsaspekten zählen:**
<table>
<thead>
<tr>
<th>Bereich</th>
<th>Planungsschwerpunkt</th>
</tr>
</thead>
<tbody>
<tr>
<td>Trassenführung</td>
<td>Geologie, Eigentumsverhältnisse, Schutzgebiete</td>
</tr>
<tr>
<td>Netzanschluss</td>
<td>Spannungsebene, Einspeisepunkte, Redundanz</td>
</tr>
<tr>
<td>Lastprofil</td>
<td>Bemessung für Dauer- und Spitzenlasten</td>
</tr>
<tr>
<td>Skalierbarkeit</td>
<td>Erweiterungspotenziale für zukünftige Anlagen</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Zentrale Planungsaspekte"
items={[
{ title: "Trassenführung", content: "Geologie, Eigentumsverhältnisse, Schutzgebiete" },
{ title: "Netzanschluss", content: "Spannungsebene, Einspeisepunkte, Redundanz" },
{ title: "Lastprofil", content: "Bemessung für Dauer- und Spitzenlasten" },
{ title: "Skalierbarkeit", content: "Erweiterungspotenziale für zukünftige Anlagen" }
]}
/>
Professionelle Planung schafft nicht nur Versorgungssicherheit, sondern senkt langfristig die Betriebskosten und ermöglicht eine flexible Reaktion auf Netzanforderungen.
Hier finden Sie weitere Informationen, wie Windenergie grundlegend funktioniert:

View File

@@ -9,62 +9,32 @@ category: Kabel Technologie
### **Wachstum braucht Struktur**
**Wachstum klingt gut ** mehr Projekte, mehr Kunden, mehr Umsatz.<br />Aber echtes, nachhaltiges Wachstum braucht mehr als nur Tempo: Es braucht **Transparenz, Planung und Kontrolle**.
Damit aus ehrgeizigen Zielen kein Blindflug wird, haben wir entschieden, uns gezielt zu verstärken. Denn je größer die Projekte werden, desto wichtiger wird die Fähigkeit, Entwicklungen frühzeitig zu erkennen und gezielt zu steuern.
<table>
<thead>
<tr>
<th>**Warum wir unser Controlling ausbauen**</th>
<th>**Was wir damit erreichen wollen**</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mehr Projekte im In- und Ausland</td>
<td>Klare Zahlen und belastbare Prognosen</td>
</tr>
<tr>
<td>Steigende Anforderungen im Vertrieb</td>
<td>Bessere Übersicht über Trends und Margen</td>
</tr>
<tr>
<td>Komplexere Prozesse</td>
<td>Schnellere, fundierte Entscheidungen</td>
</tr>
<tr>
<td>Nachhaltiges Wachstum</td>
<td>Stabilität statt Zufall</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Warum wir unser Controlling ausbauen"
items={[
{ label: "Mehr Projekte im In- und Ausland", value: "Klare Zahlen und belastbare Prognosen" },
{ label: "Steigende Anforderungen im Vertrieb", value: "Bessere Übersicht über Trends und Margen" },
{ label: "Komplexere Prozesse", value: "Schnellere, fundierte Entscheidungen" },
{ label: "Nachhaltiges Wachstum", value: "Stabilität statt Zufall" }
]}
/>
**Kurz gesagt:** Wir wollen nicht einfach nur wachsen wir wollen verstehen, <em>wie</em> wir wachsen.<br />Deshalb setzen wir künftig noch stärker auf **qualitatives Controlling** und freuen uns über Unterstützung, die genau das möglich macht.
### **Neue Stärke im Team**
Mit [**Julia Havasi**](https://www.linkedin.com/in/julia-havasi-18556b233/) haben wir genau die Verstärkung gefunden, die wir gesucht haben: analytisch stark, strukturiert im Denken und mit einem guten Gespür für die Dynamik zwischen Zahlen und Menschen.
Als **Senior Financial &amp; Sales Controller** übernimmt Julia künftig die Verantwortung für unser Finanz- und Vertriebscontrolling. Ihr Ziel: **mehr Klarheit, mehr Weitblick, mehr Substanz** in jeder Entscheidung.
<table>
<thead>
<tr>
<th>**Aufgabenbereich**</th>
<th>**Ziel**</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Finanzcontrolling**</td>
<td>Saubere Zahlen, klare Strukturen und nachvollziehbare Reports</td>
</tr>
<tr>
<td>**Sales Controlling**</td>
<td>Vertriebszahlen analysieren, Potenziale erkennen, Trends ableiten</td>
</tr>
<tr>
<td>**Prognosen &amp; Analysen**</td>
<td>Frühzeitige Einschätzung von Marktbewegungen und Investitionschancen</td>
</tr>
<tr>
<td>**Reporting &amp; Kommunikation**</td>
<td>Komplexe Daten so aufbereiten, dass sie jeder versteht schnell und präzise</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Aufgabenbereiche & Ziele"
items={[
{ label: "Finanzcontrolling", value: "Saubere Zahlen, klare Strukturen und nachvollziehbare Reports" },
{ label: "Sales Controlling", value: "Vertriebszahlen analysieren, Potenziale erkennen, Trends ableiten" },
{ label: "Prognosen & Analysen", value: "Frühzeitige Einschätzung von Marktbewegungen und Investitionschancen" },
{ label: "Reporting & Kommunikation", value: "Komplexe Daten so aufbereiten, dass sie jeder versteht schnell und präzise" }
]}
/>
Julia wird damit eine zentrale Rolle in der weiteren Entwicklung von KLZ spielen als Schnittstelle zwischen **Management, Vertrieb und Strategie**.<br />Oder, um es etwas lockerer zu sagen: Sie sorgt dafür, dass wir nicht nur wissen, **wo wir stehen**, sondern auch **wohin wir gehen**.
### **Erfahrung, die verbindet**
**Zahlenverständnis trifft Praxiserfahrung.**<br />Mit über 13 Jahren Erfahrung im Controlling und Vertrieb bringt [**Julia Havasi**](https://www.linkedin.com/in/julia-havasi-18556b233/) das ideale Zusammenspiel aus analytischer Präzision und unternehmerischem Denken mit.

View File

@@ -9,48 +9,21 @@ category: Kabel Technologie
Gerade bei Kabeln wie **NA2XS(F)2Y** oder **NAYY **für** Windkraftanlagen** entscheidet die Materialwahl über Kosten, Leistung und Lebensdauer. Kupfer überzeugt durch eine hohe elektrische Leitfähigkeit, während Aluminium mit niedrigen Kosten und geringem Gewicht punktet. Doch welches Material ist **technisch **und** wirtschaftlich** langfristig die bessere Wahl? Dieser Artikel liefert eine detaillierte Analyse der Vor- und Nachteile beider Optionen.
## Elektrische und mechanische Eigenschaften im Vergleich
Kupfer ist seit Jahrzehnten das bevorzugte Material für elektrische Leitungen. Es besitzt eine hohe Leitfähigkeit und eine ausgezeichnete mechanische Stabilität. Aluminium hingegen ist deutlich leichter, hat aber eine geringere elektrische Leitfähigkeit. Das bedeutet, dass Aluminiumkabel für die gleiche Stromübertragung einen größeren Querschnitt benötigen.
### Vergleich der Eigenschaften
<table>
<thead>
<tr>
<th>Eigenschaft</th>
<th>Kupfer</th>
<th>Aluminium</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Elektrische Leitfähigkeit**</td>
<td>58 MS/m</td>
<td>35 MS/m</td>
</tr>
<tr>
<td>**Dichte (g/cm³)**</td>
<td>8,96</td>
<td>2,70</td>
</tr>
<tr>
<td>**Korrosionsbeständigkeit**</td>
<td>Sehr hoch</td>
<td>Mittel (Oxidbildung)</td>
</tr>
<tr>
<td>**Mechanische Festigkeit**</td>
<td>Hoch</td>
<td>Mittel</td>
</tr>
<tr>
<td>**Gewicht**</td>
<td>Hoch</td>
<td>Gering</td>
</tr>
<tr>
<td>**Preis pro Tonne**</td>
<td>8.000 9.000 €</td>
<td>2.300 2.500 €</td>
</tr>
</tbody>
</table>
<ComparisonGrid
title="Vergleich der Eigenschaften"
leftLabel="Kupfer"
rightLabel="Aluminium"
items={[
{ label: "Elektrische Leitfähigkeit", leftValue: "58 MS/m", rightValue: "35 MS/m" },
{ label: "Dichte (g/cm³)", leftValue: "8,96", rightValue: "2,70" },
{ label: "Korrosionsbeständigkeit", leftValue: "Sehr hoch", rightValue: "Mittel (Oxidbildung)" },
{ label: "Mechanische Festigkeit", leftValue: "Hoch", rightValue: "Mittel" },
{ label: "Gewicht", leftValue: "Hoch", rightValue: "Gering" },
{ label: "Preis pro Tonne", leftValue: "8.000 9.000 €", rightValue: "2.300 2.500 €" }
]}
/>
Aluminium kann zwar durch seine Gewichtsersparnis bei Transport und Installation Vorteile bieten, benötigt aber größere Querschnitte, um die gleiche Leistung zu übertragen. Dies kann sich auf die Platzanforderungen in Kabeltrassen und die mechanische Stabilität auswirken. Zudem neigt Aluminium stärker zur Oxidation, was zu Kontaktproblemen führen kann, während Kupfer seine Leitfähigkeit über lange Zeiträume ohne große Qualitätseinbußen behält. Besonders in feuchten oder salzhaltigen Umgebungen, wie Offshore-Windparks, kann dies ein entscheidender Faktor sein.
## Kosten: Anschaffung, Installation und Betrieb
### Materialkosten
@@ -106,53 +79,22 @@ Sowohl Kupfer als auch Aluminium sind vollständig recycelbar, allerdings gibt e
### Langfristige Nachhaltigkeit in Windparks
Aluminium bietet klare Vorteile in der Herstellung und im Recycling, während Kupfer aufgrund seiner Langlebigkeit und geringen Wartungsanforderungen langfristig nachhaltiger sein kann. Für Windparks bedeutet dies, dass die Wahl des richtigen Materials auch eine ökologische Entscheidung ist.
Fazit: Aluminium punktet durch seinen geringeren CO₂-Fußabdruck in der Herstellung und seine hervorragende Recyclingfähigkeit, während Kupfer durch seine Langlebigkeit weniger häufig ersetzt werden muss und dadurch ebenfalls zur Nachhaltigkeit beiträgt.
## Welche Lösung ist die beste für Windparks?
<table>
<thead>
<tr>
<th>Faktor</th>
<th>Kupfer</th>
<th>Aluminium</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Effizienz**</td>
<td>Besser</td>
<td>Höhere Verluste</td>
</tr>
<tr>
<td>**Kosten (Material &amp; Anschaffung)**</td>
<td>Teurer</td>
<td>Günstiger</td>
</tr>
<tr>
<td>**Installationsaufwand**</td>
<td>Schwerer, aufwendiger</td>
<td>Leichter, einfacher</td>
</tr>
<tr>
<td>**Betriebskosten (Verluste &amp; Wartung)**</td>
<td>Geringer</td>
<td>Höher</td>
</tr>
<tr>
<td>**Korrosionsbeständigkeit**</td>
<td>Sehr gut</td>
<td>Mittel</td>
</tr>
<tr>
<td>**Lebensdauer**</td>
<td>Länger</td>
<td>Kürzer</td>
</tr>
<tr>
<td>**Umweltfreundlichkeit**</td>
<td>Hoher Energieaufwand</td>
<td>Besser mit Recycling</td>
</tr>
</tbody>
</table>
<ComparisonGrid
title="Welche Lösung ist die beste für Windparks?"
leftLabel="Kupfer"
rightLabel="Aluminium"
items={[
{ label: "Effizienz", leftValue: "Besser", rightValue: "Höhere Verluste" },
{ label: "Kosten (Material & Anschaffung)", leftValue: "Teurer", rightValue: "Günstiger" },
{ label: "Installationsaufwand", leftValue: "Schwerer, aufwendiger", rightValue: "Leichter, einfacher" },
{ label: "Betriebskosten (Verluste & Wartung)", leftValue: "Geringer", rightValue: "Höher" },
{ label: "Korrosionsbeständigkeit", leftValue: "Sehr gut", rightValue: "Mittel" },
{ label: "Lebensdauer", leftValue: "Länger", rightValue: "Kürzer" },
{ label: "Umweltfreundlichkeit", leftValue: "Hoher Energieaufwand", rightValue: "Besser mit Recycling" }
]}
/>
### Empfohlene Anwendung je nach Einsatzzweck
- Aluminium eignet sich für lange **Mittelspannungs-Trassen**, wo Gewicht und Kosten entscheidend sind.
- Kupfer ist ideal für **Netzübergänge, Umspannwerke **und** kritische Bereiche**, wo Effizienz und Langlebigkeit im Fokus stehen.

View File

@@ -20,37 +20,18 @@ Projekte im Bereich von 10 bis 30kV bringen wiederkehrende Anforderungen mit
Die Realität auf der Baustelle zeigt: Ein Kabel, das nicht flexibel genug ist, zu hohe Biegeradien hat oder thermisch schnell an seine Grenzen kommt, verzögert nicht nur die Umsetzung es gefährdet auch die Betriebssicherheit.
<h4>Was das NA2XS(F)2Y für moderne Energieinfrastruktur interessant macht</h4>
Das [**NA2XS(F)2Y**](/de/products/medium-voltage-cables/na2xsf2y/) begegnet diesen Anforderungen mit einer durchdachten, praxisbewährten Konstruktion. Es ist ausgelegt für den **langfristigen Einsatz unter Last** und zeigt seine Stärken besonders in industriellen und energietechnischen Netzen.
**Wichtige Merkmale im Überblick:**
<table>
<thead>
<tr>
<th>Merkmal</th>
<th>Vorteil für Ihr Projekt</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Aluminiumleiter**</td>
<td>Hohe Leitfähigkeit, geringe Übertragungsverluste</td>
</tr>
<tr>
<td>**XLPE-Isolierung**</td>
<td>Hohe thermische Belastbarkeit, langlebig und stabil</td>
</tr>
<tr>
<td>**EMV-optimiertes Design**</td>
<td>Störungsfreier Betrieb in sensiblen Netzumgebungen</td>
</tr>
<tr>
<td>**Normgerechter Aufbau (IEC)**</td>
<td>Sicherheit bei Ausschreibung, Prüfung und Betrieb</td>
</tr>
<tr>
<td>**Robuste Außenkonstruktion**</td>
<td>Geeignet für alle gängigen Verlegeverfahren</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Wichtige Merkmale im Überblick"
items={[
{ label: "Aluminiumleiter", value: "Hohe Leitfähigkeit, geringe Übertragungsverluste" },
{ label: "XLPE-Isolierung", value: "Hohe thermische Belastbarkeit, langlebig und stabil" },
{ label: "EMV-optimiertes Design", value: "Störungsfreier Betrieb in sensiblen Netzumgebungen" },
{ label: "Normgerechter Aufbau (IEC)", value: "Sicherheit bei Ausschreibung, Prüfung und Betrieb" },
{ label: "Robuste Außenkonstruktion", value: "Geeignet für alle gängigen Verlegeverfahren" }
]}
/>
Das bedeutet: Mit dem [NA2XS(F)2Y](/de/products/medium-voltage-cables/na2xsf2y/) lassen sich Netze realisieren, die nicht nur auf dem Papier funktionieren, sondern auch im praktischen Betrieb dauerhaft, wartungsarm und sicher.
Warum der Netzausbau wichtig ist, erfahren Sie hier:
<VisualLinkPreview

View File

@@ -13,33 +13,16 @@ Ohne ein durchdachtes Recyclingkonzept würden enorme Mengen an Holz, Stahl und
##
### Materialien und ihre Wiederverwertung
Kabeltrommeln bestehen aus unterschiedlichen Materialien, die jeweils verschiedene Recyclingmöglichkeiten bieten. Eine gezielte Rückführung hängt davon ab, ob das Material wiederverwertet oder weiterverarbeitet werden kann.
<h4>Hauptmaterialien von Kabeltrommeln und ihre Recyclingoptionen</h4>
<table>
<thead>
<tr>
<th>**Material**</th>
<th>**Eigenschaften**</th>
<th>**Recyclingmöglichkeiten**</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Holz**</td>
<td>Biologisch abbaubar, leicht zu reparieren</td>
<td>Upcycling, Biomasse, Palettenbau</td>
</tr>
<tr>
<td>**Stahl**</td>
<td>Stabil, wiederverwendbar, korrosionsbeständig</td>
<td>Einschmelzen, Wiederverwertung</td>
</tr>
<tr>
<td>**Kunststoff**</td>
<td>Witterungsbeständig, leicht, langlebig</td>
<td>Granulat-Herstellung, Upcycling</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Materialien und ihre Wiederverwertung"
items={[
{ title: "Holz", content: "**Eigenschaften:** Biologisch abbaubar, leicht zu reparieren. **Recycling:** Upcycling, Biomasse, Palettenbau." },
{ title: "Stahl", content: "**Eigenschaften:** Stabil, wiederverwendbar, korrosionsbeständig. **Recycling:** Einschmelzen, Wiederverwertung." },
{ title: "Kunststoff", content: "**Eigenschaften:** Witterungsbeständig, leicht, langlebig. **Recycling:** Granulat-Herstellung, Upcycling." }
]}
/>
Je nach Zustand der Trommeln können sie direkt wiederverwendet, repariert oder in ihre Einzelbestandteile zerlegt werden. Besonders Holz kann vielseitig genutzt werden, während Stahl und Kunststoff wertvolle Rohstoffe für neue Produkte liefern.
Um Rohstoffverluste zu vermeiden, ist es entscheidend, beschädigte Kabeltrommeln nicht als Abfall zu betrachten, sondern als wertvolle Ressource.
### Der Recyclingprozess: Von der Rücknahme zur Wiederverwertung

View File

@@ -10,33 +10,16 @@ Eine sichere und nachhaltige Energiezukunft ist nur mit neuen Technologien, smar
Doch wie sieht die Energieversorgung der Zukunft aus? Welche Rolle spielen Solarenergie, Windkraft und Kabelinfrastruktur? In diesem Artikel werfen wir einen Blick auf die wichtigsten Entwicklungen von intelligenter Netzsteuerung bis hin zu nachhaltigen Kabelsystemen.
## Solarenergie: Die Revolution auf unseren Dächern und Feldern
Solarenergie hat sich längst von einer Nischenlösung zur tragenden Säule der Energiewende entwickelt. Neue Technologien machen Photovoltaik effizienter, flexibler und wirtschaftlicher und das nicht nur auf Hausdächern, sondern auch auf Ackerflächen, in Fassaden und schwimmend auf Seen.
### Die wichtigsten Innovationen in der Photovoltaik
<table>
<thead>
<tr>
<th>Technologie</th>
<th>Beschreibung</th>
<th>Vorteil</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Tandem-Solarzellen**</td>
<td>Kombination aus Silizium und Perowskit für höhere Effizienz</td>
<td>Bis zu 30 % mehr Leistung</td>
</tr>
<tr>
<td>**Agri-PV**</td>
<td>Solarmodule über landwirtschaftlichen Flächen</td>
<td>Doppelte Flächennutzung für Energie und Ernte</td>
</tr>
<tr>
<td>**Bifaziale Module**</td>
<td>Nutzen Licht von beiden Seiten</td>
<td>1020 % mehr Ertrag durch Reflexion</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Innovationen in der Photovoltaik"
items={[
{ title: "Tandem-Solarzellen", content: "**Beschreibung:** Kombination aus Silizium und Perowskit für höhere Effizienz. **Vorteil:** Bis zu 30 % mehr Leistung." },
{ title: "Agri-PV", content: "**Beschreibung:** Solarmodule über landwirtschaftlichen Flächen. **Vorteil:** Doppelte Flächennutzung für Energie und Ernte." },
{ title: "Bifaziale Module", content: "**Beschreibung:** Nutzen Licht von beiden Seiten. **Vorteil:** 1020 % mehr Ertrag durch Reflexion." }
]}
/>
Doch die größte Herausforderung bleibt die Netzintegration: Solarenergie wird vor allem tagsüber produziert doch unser Strombedarf ist morgens und abends am höchsten. Die Lösung? Smarte Speichertechnologien und intelligente Netzsteuerung, die Sonnenstrom genau dann verfügbar macht, wenn er gebraucht wird.
## Windkraft: Höher, stärker, effizienter
Windkraft ist neben der Solarenergie der wichtigste Pfeiler der erneuerbaren Energien. Während Offshore-Windparks auf dem Meer gigantische Mengen Strom liefern, sind Onshore-Windkraftanlagen nach wie vor das Rückgrat der nachhaltigen Energieversorgung.

View File

@@ -17,29 +17,15 @@ Netzanschlusskabel für Windparks sind nicht einfach nur dickere Versionen von S
- **Teilentladungen**, die über Jahre hinweg die Isolierung schädigen können
- Elektromagnetische Einflüsse, die **Schirmung und Erdung** der Kabel erforderlich machen
### Thermische Belastungen
<table>
<thead>
<tr>
<th>Belastungsfaktor</th>
<th>Auswirkung auf das Kabel</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Temperaturwechsel**</td>
<td>Materialausdehnung, Risse in der Isolation</td>
</tr>
<tr>
<td>**Dauerbelastung durch hohe Ströme**</td>
<td>Erwärmung der Kabeladern</td>
</tr>
<tr>
<td>**Wärmeableitung**</td>
<td>Entscheidend für die zulässige Strombelastbarkeit</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Thermische Belastungen"
items={[
{ label: "Temperaturwechsel", value: "Materialausdehnung, Risse in der Isolation" },
{ label: "Dauerbelastung", value: "Erwärmung der Kabeladern durch hohe Ströme" },
{ label: "Wärmeableitung", value: "Entscheidend für die zulässige Strombelastbarkeit" }
]}
/>
### Umwelteinflüsse
🌧 **Feuchtigkeit &amp; Wasser** Eindringen von Wasser kann die Isolation zerstören<br />🔥 **UV-Strahlung &amp; extreme Temperaturen** Gerade bei oberirdischer Verlegung relevant<br />🌍 **Chemische Einwirkungen &amp; Bodenbewegungen** Besonders bei Erdkabeln ein kritischer Faktor
## Material und Konstruktion Was macht ein gutes Netzanschlusskabel aus?

View File

@@ -37,37 +37,18 @@ Niederspannung ist der Einstiegspunkt jeder elektrischen Infrastruktur. Kabel di
In der Windkraftinfrastruktur wird das **NAYY** häufig für Beleuchtung, Kontrollsysteme oder die interne Stromverteilung in Betriebsgebäuden genutzt. Es ist robust, wartungsarm und bewährt sich seit Jahrzehnten in der Praxis.
## Mittelspannungskabel Die Arbeitstiere im Windpark
Mittelspannungskabel sind das Rückgrat eines jeden Windparks. Sie decken den Spannungsbereich von **1 kV bis 45 kV** ab und sind essenziell für die Energieverteilung zwischen Windenergieanlagen und den Sammelpunkten. Diese Kabel sind enorm belastbar, müssen hohen Temperaturen, Spannungsfeldern und mechanischen Einflüssen standhalten.
### Aufbau (am Beispiel NA2XS(F)2Y):
<table>
<thead>
<tr>
<th>Komponente</th>
<th>Funktion</th>
</tr>
</thead>
<tbody>
<tr>
<td>Leiter</td>
<td>Stromübertragung (Kupfer oder Aluminium)</td>
</tr>
<tr>
<td>Innenleiterschicht</td>
<td>Feldsteuerung, Spannungsoptimierung</td>
</tr>
<tr>
<td>Isolierung (XLPE)</td>
<td>Hohe elektrische Festigkeit, temperaturbeständig</td>
</tr>
<tr>
<td>Schirmung</td>
<td>Schutz vor Störungen, Erdung</td>
</tr>
<tr>
<td>Außenmantel</td>
<td>Mechanischer Schutz, UV- und wasserresistent</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Aufbau (am Beispiel NA2XS(F)2Y)"
items={[
{ label: "Leiter", value: "Stromübertragung (Kupfer oder Aluminium)" },
{ label: "Innenleiterschicht", value: "Feldsteuerung, Spannungsoptimierung" },
{ label: "Isolierung (XLPE)", value: "Hohe elektrische Festigkeit, temperaturbeständig" },
{ label: "Schirmung", value: "Schutz vor Störungen, Erdung" },
{ label: "Außenmantel", value: "Mechanischer Schutz, UV- und wasserresistent" }
]}
/>
**Typische Kabeltypen:**
- NA2XS(F)2Y (Aluminiumleiter, mit Feldsteuerung)
- N2XSY (Kupferleiter, besonders leitfähig)

View File

@@ -26,33 +26,17 @@ Typische Ursachen für Verzögerungen:
- fehlende Abstimmung zwischen Lieferanten, Tiefbau und Montage
Gerade bei **Windparkprojekten** mit mehreren Kilometern des [**NA2XS(F)2Y**](/de/products/medium-voltage-cables/na2xsf2y/) ist eine exakte **Lieferkoordination** entscheidend. Teil- und Komplettlieferungen müssen so geplant sein, dass sie sich an den tatsächlichen **Baufortschritt** anpassen.
**Effiziente Logistiklösungen können hier den Unterschied machen:**
<table>
<thead>
<tr>
<th>Herausforderung</th>
<th>Lösung</th>
</tr>
</thead>
<tbody>
<tr>
<td>Unterschiedliche Baufortschritte pro Turbine</td>
<td>Teil- und Abschnittslieferungen passend zum Bauplan</td>
</tr>
<tr>
<td>Eng getaktete Montagefenster</td>
<td>Just-in-Time-Kabellieferung auf Baustelle</td>
</tr>
<tr>
<td>Fehlende Lagerflächen vor Ort</td>
<td>Temporäre, projektbezogene Zwischenlagerung</td>
</tr>
<tr>
<td>Witterungsabhängige Arbeiten</td>
<td>Flexible Umlenkung von Lieferterminen und Materialzuteilung</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Effiziente Logistiklösungen"
items={[
{ label: "Unterschiedliche Baufortschritte pro Turbine", value: "Teil- und Abschnittslieferungen passend zum Bauplan" },
{ label: "Eng getaktete Montagefenster", value: "Just-in-Time-Kabellieferung auf Baustelle" },
{ label: "Fehlende Lagerflächen vor Ort", value: "Temporäre, projektbezogene Zwischenlagerung" },
{ label: "Witterungsabhängige Arbeiten", value: "Flexible Umlenkung von Lieferterminen und Materialzuteilung" }
]}
/>
Mit einer präzisen Planung der [**Kabelkapazitäten**](https://www.a-eberle.de/infobrief/infobrief-20/) und einer reaktionsfähigen Logistik lässt sich auch unter hohem Zeitdruck effizient arbeiten. So bleibt der **Netzanschluss des Windparks** termingerecht und der Energiefluss gesichert.
Sie möchten wissen, welche Kabelarten in einem Windpark verlegt werden? Dann schauen Sie sich diesen Artikel an:
@@ -62,32 +46,17 @@ Sie möchten wissen, welche Kabelarten in einem Windpark verlegt werden? Dann sc
summary="Die Verkabelung ist ein zentrales Element jeder Windkraftanlage und beeinflusst maßgeblich die Effizienz, Sicherheit und Wirtschaftlichkeit eines Windparks.…"
image="https://wind-turbine.com/i/53689/68738caa5e58ffdf06031cf2/2/1200/630/68738c85497af_KabelfreinenWindparkpng.png"
/>
<table>
<thead>
<tr>
<th>Herausforderung</th>
<th>Praxisgerechte Lösung</th>
</tr>
</thead>
<tbody>
<tr>
<td>Hohe Liefermengen auf engem Baustellenareal</td>
<td>Projektbezogene Lagerung in regionalen Hubs</td>
</tr>
<tr>
<td>Unterschiedliche Trommelgrößen für Mittelspannung und Niederspannung</td>
<td>Abstimmung der Trommelabmessungen auf Zugkraft und Trommelgewicht</td>
</tr>
<tr>
<td>Empfindliche Kabelmäntel bei Lagerung im Freien</td>
<td>Witterungsbeständige Spezialverpackung und UV-Schutz</td>
</tr>
<tr>
<td>Fehlende Übersicht bei vielen Kabellieferungen</td>
<td>Digitale Lieferübersichten und klare Trommelkennzeichnung</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Herausforderungen in der Praxis"
items={[
{ label: "Hohe Liefermengen auf engem Baustellenareal", value: "Projektbezogene Lagerung in regionalen Hubs" },
{ label: "Unterschiedliche Trommelgrößen", value: "Abstimmung der Trommelabmessungen auf Zugkraft und Trommelgewicht" },
{ label: "Empfindliche Kabelmäntel", value: "Witterungsbeständige Spezialverpackung und UV-Schutz" },
{ label: "Fehlende Übersicht", value: "Digitale Lieferübersichten und klare Trommelkennzeichnung" }
]}
/>
Eine klare [**Kabellogistikstrategie**](https://logistik-heute.de/galerien/mammutprojekt-kabellogistik-wie-kommen-tausende-tonnen-hgue-erdkabel-fuer-die-energiewende-zum-einsatzort-40875.html) ist der Schlüssel, um Materialengpässe und teure Stillstände zu vermeiden. So bleibt der Überblick gewahrt selbst bei Projekten mit mehreren dutzend Kilometern **Windparkverkabelung**.
Wer die **Verpackung, Lagerung und Kennzeichnung** frühzeitig in die Planung integriert, stellt sicher, dass die **Windpark Kabel** genau dort ankommen, wo sie gebraucht werden ohne Zeitverlust und ohne Risiko für die Bauabfolge.
## Herausforderung 3: Kurzfristige Projektänderungen

View File

@@ -35,48 +35,21 @@ Während Standardleitungen bei extremen Temperaturen, mechanischen Belastungen o
Kurz gesagt: Das H1Z2Z2-K 6mm² ist keine Lösung von der Stange es ist ein spezialisiertes Energiekabel für eine Branche, die keine Kompromisse kennt.
## Technische Daten und Aufbau im Detail
Eine der Stärken dieses Kabels liegt in seinem Materialaufbau und der daraus resultierenden thermischen und mechanischen Belastbarkeit.
<table>
<thead>
<tr>
<th>**Eigenschaft**</th>
<th>**Wert / Beschreibung**</th>
</tr>
</thead>
<tbody>
<tr>
<td>Leiter</td>
<td>Feindrähtiger, verzinnter Kupferleiter (Klasse 5)</td>
</tr>
<tr>
<td>Nennspannung</td>
<td>1500 V DC (EN 50618 konform)</td>
</tr>
<tr>
<td>Prüfspannung</td>
<td>6.5 kV</td>
</tr>
<tr>
<td>Temperaturbereich Betrieb</td>
<td>-40 °C bis +90 °C (Leiter max. +120 °C)</td>
</tr>
<tr>
<td>Isolierung und Mantel</td>
<td>Vernetztes Polyolefin, halogenfrei</td>
</tr>
<tr>
<td>Außendurchmesser (6mm²)</td>
<td>ca. 6,4 mm</td>
</tr>
<tr>
<td>Biegeradius</td>
<td>min. 4 × Kabeldurchmesser</td>
</tr>
<tr>
<td>Max. Strombelastbarkeit (frei verlegt)</td>
<td>bis 70 A (je nach Umgebungstemperatur)</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Technische Daten & Aufbau"
items={[
{ label: "Leiter", value: "Feindrähtiger, verzinnter Kupferleiter (Klasse 5)" },
{ label: "Nennspannung", value: "1500 V DC (EN 50618 konform)" },
{ label: "Prüfspannung", value: "6.5 kV" },
{ label: "Temperaturbereich Betrieb", value: "-40 °C bis +90 °C (Leiter max. +120 °C)" },
{ label: "Isolierung und Mantel", value: "Vernetztes Polyolefin, halogenfrei" },
{ label: "Außendurchmesser (6mm²)", value: "ca. 6,4 mm" },
{ label: "Biegeradius", value: "min. 4 × Kabeldurchmesser" },
{ label: "Max. Strombelastbarkeit", value: "bis 70 A (je nach Umgebungstemperatur)" }
]}
/>
## Normen und Zertifizierungen: EN 50618 &amp; Co.
Das H1Z2Z2-K 6mm² erfüllt alle wesentlichen Standards für den Einsatz in Photovoltaikanlagen. Diese Normen garantieren Sicherheit, Langlebigkeit und Konformität mit gesetzlichen Vorgaben.
### EN 50618 europäischer Standard für Solarkabel

View File

@@ -11,33 +11,16 @@ The vision is clear: A Europe powered 100% by renewable energy. But while solar
In the end, its not just about generating more power, but about smart grids that can transport it reliably and with minimal losses.
## The problem: Old grids for a new energy future
Today&#8217;s power infrastructure was built for centralized large-scale power plants. But renewable energy works differently: It is decentralized, weather-dependent, and requires flexible grids. This creates a massive need for restructuring.
### Why our grid is currently overwhelmed:
<table>
<thead>
<tr>
<th>Problem</th>
<th>Cause</th>
<th>Solution?</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Grid bottlenecks**</td>
<td>Old power lines designed for central plants, not decentralized energy</td>
<td>New high- &amp; medium-voltage cables</td>
</tr>
<tr>
<td>**Curtailment of solar &amp; wind power**</td>
<td>Grid cannot absorb enough electricity</td>
<td>Smart grids &amp; storage solutions</td>
</tr>
<tr>
<td>**Long transmission distances**</td>
<td>Generation is often far from consumption</td>
<td>High-performance cables &amp; local grids</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Why our grid is currently overwhelmed"
items={[
{ title: "Grid bottlenecks", content: "**Cause:** Old power lines designed for central plants, not decentralized energy. **Solution:** New high- & medium-voltage cables." },
{ title: "Curtailment of solar & wind power", content: "**Cause:** Grid cannot absorb enough electricity. **Solution:** Smart grids & storage solutions." },
{ title: "Long transmission distances", content: "**Cause:** Generation is often far from consumption. **Solution:** High-performance cables & local grids." }
]}
/>
⚠️ A grid from the past cannot transport the energy of the future!
Anyone investing only in renewable energy systems today while ignoring cable infrastructure will be left with expensive, unused electricity tomorrow.
## Which cables do we need for the energy transition?
@@ -48,39 +31,19 @@ Not all cables are created equal and not every cable is suited for the chall
💡 The better the cable, the less electricity is lost along the way and the greener the energy becomes!
## Solar and wind farms arent enough
Without the right cables, electricity stays where it&#8217;s generated. But what kind of grid expansion really makes sense?
### Underground cables vs. overhead lines which is the better choice?
A key question in grid expansion is whether new power lines should be built as overhead lines or underground cables. Both options have pros and cons, but in the long run, underground cabling offers significant advantages in terms of reliability, environmental protection, and grid stability.
<table>
<thead>
<tr>
<th>Criteria</th>
<th>Underground Cable</th>
<th>Overhead Line</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Grid stability**</td>
<td>Very high</td>
<td>Moderate</td>
</tr>
<tr>
<td>**Environmental impact**</td>
<td>Unobtrusive, no disruption to landscapes</td>
<td>Visible, problematic for birds</td>
</tr>
<tr>
<td>**Maintenance &amp; lifespan**</td>
<td>Minimal maintenance, long lifespan</td>
<td>Weather-sensitive, shorter lifespan</td>
</tr>
<tr>
<td>**Costs**</td>
<td>Higher installation costs, but more efficient operation</td>
<td>Cheaper to build, but higher long-term costs</td>
</tr>
</tbody>
</table>
<ComparisonGrid
title="Underground cables vs. overhead lines which is the better choice?"
leftLabel="Underground Cable"
rightLabel="Overhead Line"
items={[
{ label: "Grid stability", leftValue: "Very high", rightValue: "Moderate" },
{ label: "Environmental impact", leftValue: "Unobtrusive, no disruption to landscapes", rightValue: "Visible, problematic for birds" },
{ label: "Maintenance & lifespan", leftValue: "Minimal maintenance, long lifespan", rightValue: "Weather-sensitive, shorter lifespan" },
{ label: "Costs", leftValue: "Higher installation costs, but more efficient operation", rightValue: "Cheaper to build, but higher long-term costs" }
]}
/>
In the past, overhead lines were favored due to lower construction costs. However, modern demands for grid stability, environmental protection, and aesthetics increasingly support underground cables. As a result, many countries are now adopting underground cabling as the standard for new high- and medium-voltage power lines.
For those who want to dive deeper into the topic, heres a **detailed analysis** comparing overhead lines and underground cables:
<VisualLinkPreview

View File

@@ -9,48 +9,21 @@ category: Kabel Technologie
Particularly with cables such as **NA2XS(F)2Y** or **NAYY** for **wind turbines**, the choice of material determines costs, performance and service life. Copper impresses with its high electrical conductivity, while aluminum scores with low costs and low weight. But which material is technically and economically the better choice in the long term? This article provides a detailed analysis of the advantages and disadvantages of both options.
### Electrical and Mechanical Properties Compared
Copper has been the preferred material for electrical wiring for decades. It offers high conductivity and excellent mechanical stability. Aluminum, on the other hand, is significantly lighter but has lower electrical conductivity. This means aluminum cables require a larger cross-section to transmit the same current.
<h4>Comparison of Properties</h4>
<table>
<thead>
<tr>
<th>Property</th>
<th>Copper</th>
<th>Aluminum</th>
</tr>
</thead>
<tbody>
<tr>
<td>Electrical Conductivity</td>
<td>58 MS/m</td>
<td>35 MS/m</td>
</tr>
<tr>
<td>Density (g/cm³)</td>
<td>8.96</td>
<td>2.70</td>
</tr>
<tr>
<td>Corrosion Resistance</td>
<td>Very high</td>
<td>Medium (oxidation)</td>
</tr>
<tr>
<td>Mechanical Strength</td>
<td>High</td>
<td>Medium</td>
</tr>
<tr>
<td>Weight</td>
<td>High</td>
<td>Low</td>
</tr>
<tr>
<td>Price per ton</td>
<td>€8,000 9,000</td>
<td>€2,300 2,500</td>
</tr>
</tbody>
</table>
<ComparisonGrid
title="Comparison of Properties"
leftLabel="Copper"
rightLabel="Aluminum"
items={[
{ label: "Electrical Conductivity", leftValue: "58 MS/m", rightValue: "35 MS/m" },
{ label: "Density (g/cm³)", leftValue: "8.96", rightValue: "2.70" },
{ label: "Corrosion Resistance", leftValue: "Very high", rightValue: "Medium (oxidation)" },
{ label: "Mechanical Strength", leftValue: "High", rightValue: "Medium" },
{ label: "Weight", leftValue: "High", rightValue: "Low" },
{ label: "Price per ton", leftValue: "€8,000 9,000", rightValue: "€2,300 2,500" }
]}
/>
Although aluminum offers weight savings in transport and installation, it requires larger cross-sections to achieve the same performance. This can impact space requirements in cable trays and mechanical stability. Additionally, aluminum is more prone to oxidation, which can lead to contact issues, whereas copper maintains its conductivity over long periods without significant quality loss. In humid or salty environments, such as offshore wind farms, this can be a crucial factor.
### Costs: Acquisition, Installation, and Operation
<h4>Material Costs</h4>
@@ -110,54 +83,22 @@ Aluminum excels in **production efficiency and recycling**, while **coppers d
- **Copper** lasts longer, requires **fewer replacements**, and thus also contributes to sustainability.
Ultimately, the best choice depends on **whether short-term efficiency or long-term durability** is the priority.
### Which Solution is Best for Wind Farms?
<h4>Comparison of Key Factors</h4>
<table>
<thead>
<tr>
<th>Factor</th>
<th>Copper</th>
<th>Aluminum</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Efficiency**</td>
<td>Better</td>
<td>Higher losses</td>
</tr>
<tr>
<td>**Cost (Material &amp; Purchase)**</td>
<td>More expensive</td>
<td>Cheaper</td>
</tr>
<tr>
<td>**Installation Effort**</td>
<td>Heavier, more complex</td>
<td>Lighter, easier</td>
</tr>
<tr>
<td>**Operating Costs (Losses &amp; Maintenance)**</td>
<td>Lower</td>
<td>Higher</td>
</tr>
<tr>
<td>**Corrosion Resistance**</td>
<td>Very good</td>
<td>Medium</td>
</tr>
<tr>
<td>**Lifespan**</td>
<td>Longer</td>
<td>Shorter</td>
</tr>
<tr>
<td>**Environmental Impact**</td>
<td>High energy consumption</td>
<td>Better with recycling</td>
</tr>
</tbody>
</table>
<ComparisonGrid
title="Which Solution is Best for Wind Farms?"
leftLabel="Copper"
rightLabel="Aluminum"
items={[
{ label: "Efficiency", leftValue: "Better", rightValue: "Higher losses" },
{ label: "Cost (Material & Purchase)", leftValue: "More expensive", rightValue: "Cheaper" },
{ label: "Installation Effort", leftValue: "Heavier, more complex", rightValue: "Lighter, easier" },
{ label: "Operating Costs (Losses & Maintenance)", leftValue: "Lower", rightValue: "Higher" },
{ label: "Corrosion Resistance", leftValue: "Very good", rightValue: "Medium" },
{ label: "Lifespan", leftValue: "Longer", rightValue: "Shorter" },
{ label: "Environmental Impact", leftValue: "High energy consumption", rightValue: "Better with recycling" }
]}
/>
### Recommended Applications
- **Aluminum** is ideal for **long medium-voltage routes**, where weight and cost are crucial factors.
- **Copper** is the better choice for **grid connections, substations, and critical areas**, where **efficiency and longevity** matter most.

View File

@@ -56,34 +56,15 @@ Typical causes of delays:
Especially for **wind farm projects** involving several kilometers of [**NA2XS(F)2Y**](/en/products/medium-voltage-cables/na2xsf2y/), precise **delivery coordination** is essential. Partial and complete deliveries must be scheduled to match the actual **construction progress**.
**Efficient logistics solutions can make the difference:**
<table>
<thead>
<tr>
<th>Challenge</th>
<th>Solution</th>
</tr>
</thead>
<tbody>
<tr>
<td>Different construction progress per turbine</td>
<td>Partial and phased deliveries matched to the build schedule</td>
</tr>
<tr>
<td>Tight installation windows</td>
<td>Just-in-time cable delivery to site</td>
</tr>
<tr>
<td>Limited storage space on site</td>
<td>Temporary, project-specific intermediate storage</td>
</tr>
<tr>
<td>Weather-dependent operations</td>
<td>Flexible adjustment of delivery schedules and material allocation</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Efficient Logistics Solutions"
items={[
{ label: "Different construction progress per turbine", value: "Partial and phased deliveries matched to the build schedule" },
{ label: "Tight installation windows", value: "Just-in-time cable delivery to site" },
{ label: "Limited storage space on site", value: "Temporary, project-specific intermediate storage" },
{ label: "Weather-dependent operations", value: "Flexible adjustment of delivery schedules and material allocation" }
]}
/>
With precise [**cable capacity**](https://www.a-eberle.de/infobrief/infobrief-20/) planning and responsive logistics, even high-pressure timelines can be handled efficiently. This ensures the **wind farms grid connection** stays on schedule and energy flows reliably.
@@ -114,34 +95,15 @@ The bigger the project, the more complex the **material coordination** becomes:
**Our experience shows:** Planning for storage and packaging units in advance not only saves time but also reduces the risk of material loss and reorders.
**Typical requirements and solutions:**
<table>
<thead>
<tr>
<th>Challenge</th>
<th>Practical solution</th>
</tr>
</thead>
<tbody>
<tr>
<td>High delivery volumes on tight site space</td>
<td>Project-specific storage in regional hubs</td>
</tr>
<tr>
<td>Different drum sizes for medium- and low-voltage</td>
<td>Adjust drum dimensions to pulling force and weight</td>
</tr>
<tr>
<td>Sensitive cable sheaths when stored outdoors</td>
<td>Weatherproof packaging and UV protection</td>
</tr>
<tr>
<td>Lack of overview with many cable deliveries</td>
<td>Digital delivery summaries and clear drum labeling</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Typical Requirements and Solutions"
items={[
{ label: "High delivery volumes on tight site space", value: "Project-specific storage in regional hubs" },
{ label: "Different drum sizes", value: "Adjust drum dimensions to pulling force and weight" },
{ label: "Sensitive cable sheaths", value: "Weatherproof packaging and UV protection" },
{ label: "Lack of overview", value: "Digital delivery summaries and clear drum labeling" }
]}
/>
A clear [**cable logistics strategy**](https://logistik-heute.de/galerien/mammutprojekt-kabellogistik-wie-kommen-tausende-tonnen-hgue-erdkabel-fuer-die-energiewende-zum-einsatzort-40875.html) is the key to avoiding material shortages and costly downtime. This helps maintain control even for projects involving dozens of kilometers of **wind farm cabling**.

View File

@@ -10,33 +10,16 @@ A secure and sustainable energy future is only possible with new technologies, s
But what will the energy supply of the future look like? What role will solar energy, wind power and cable infrastructure play? In this article, we take a look at the most important developments from intelligent grid control to sustainable cable systems.
## Solar Energy: the revolution on our roofs and fields
Solar energy has long evolved from a niche solution into a cornerstone of the energy transition. New technologies are making photovoltaics more efficient, flexible, and economical—not just on rooftops but also on farmland, building facades, and even floating on lakes.
### The most important innovations in photovoltaics
<table>
<thead>
<tr>
<th>Technology</th>
<th>Description</th>
<th>Advantage</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tandem solar cells</td>
<td>Combination of silicon and perovskite for higher efficiency</td>
<td>Up to 30% more power output</td>
</tr>
<tr>
<td>Agri-PV</td>
<td>Solar panels above agricultural land</td>
<td>Dual land use for energy and crops</td>
</tr>
<tr>
<td>Bifacial modules</td>
<td>Capture light from both sides</td>
<td>1020% higher yield through reflection</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Innovations in Photovoltaics"
items={[
{ title: "Tandem solar cells", content: "**Description:** Combination of silicon and perovskite for higher efficiency. **Advantage:** Up to 30% more power output." },
{ title: "Agri-PV", content: "**Description:** Solar panels above agricultural land. **Advantage:** Dual land use for energy and crops." },
{ title: "Bifacial modules", content: "**Description:** Capture light from both sides. **Advantage:** 1020% higher yield through reflection." }
]}
/>
However, the biggest challenge remains grid integration: Solar energy is primarily generated during the day, but our electricity demand peaks in the morning and evening. The solution? Smart storage technologies and intelligent grid management that make solar power available exactly when its needed.
## Wind Power: higher, stronger, more efficient
Wind power is, alongside solar energy, the most important pillar of renewable energy. While offshore wind farms at sea generate massive amounts of electricity, onshore wind turbines remain the backbone of sustainable energy supply.

View File

@@ -17,33 +17,17 @@ Modern onshore wind farms consist not only of turbines, but of a complex network
What many underestimate: The cable routes in a wind farm often make up a significant part of the total investment. They are not just a link they are the **critical infrastructure** on which everything is built.
## Holistic Planning: Foundation for Sustainable Infrastructure
Integrating wind farms into the power grid requires a systemic approach. Sound planning takes into account not only performance requirements, but also environmental conditions, expansion scenarios and approval processes.
**Key planning aspects include:**
<table>
<thead>
<tr>
<th>Area</th>
<th>Planning Focus</th>
</tr>
</thead>
<tbody>
<tr>
<td>Route Guidance</td>
<td>Geology, ownership, protected areas</td>
</tr>
<tr>
<td>Grid Connection</td>
<td>Voltage level, feed-in points, redundancy</td>
</tr>
<tr>
<td>Load Profile</td>
<td>Design for base and peak loads</td>
</tr>
<tr>
<td>Scalability</td>
<td>Expansion potential for future systems</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Key Planning Aspects"
items={[
{ title: "Route Guidance", content: "Geology, ownership, protected areas" },
{ title: "Grid Connection", content: "Voltage level, feed-in points, redundancy" },
{ title: "Load Profile", content: "Design for base and peak loads" },
{ title: "Scalability", content: "Expansion potential for future systems" }
]}
/>
Professional planning not only ensures security of supply, but also reduces operating costs in the long term and enables flexible responses to grid requirements.
You can find more information here on how wind energy basically works:

View File

@@ -9,62 +9,32 @@ category: Cable Technology
### **Growth needs structure**
**Growth sounds good ** more projects, more customers, more revenue.<br />But real, sustainable growth needs more than just speed: it needs **transparency, planning, and control**.
To ensure that ambitious goals don't turn into a blind flight, we have decided to specifically strengthen our team. Because the larger the projects become, the more important the ability to recognize developments early and steer them specifically becomes.
<table>
<thead>
<tr>
<th>**Why we are expanding our controlling**</th>
<th>**What we want to achieve with it**</th>
</tr>
</thead>
<tbody>
<tr>
<td>More projects at home and abroad</td>
<td>Clear figures and reliable forecasts</td>
</tr>
<tr>
<td>Increasing requirements in sales</td>
<td>Better overview of trends and margins</td>
</tr>
<tr>
<td>More complex processes</td>
<td>Faster, well-founded decisions</td>
</tr>
<tr>
<td>Sustainable growth</td>
<td>Stability instead of coincidence</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Why we are expanding our controlling"
items={[
{ label: "More projects at home and abroad", value: "Clear figures and reliable forecasts" },
{ label: "Increasing requirements in sales", value: "Better overview of trends and margins" },
{ label: "More complex processes", value: "Faster, well-founded decisions" },
{ label: "Sustainable growth", value: "Stability instead of coincidence" }
]}
/>
**In short:** We don't just want to grow we want to understand <em>how</em> we grow.<br />That's why we will rely even more on **qualitative controlling** in the future and are happy about support that makes exactly that possible.
### **New strength in the team**
With [**Julia Havasi**](https://www.linkedin.com/in/julia-havasi-18556b233/) we have found exactly the reinforcement we were looking for: analytically strong, structured in thinking, and with a good sense for the dynamics between numbers and people.
As **Senior Financial & Sales Controller**, Julia will be responsible for our financial and sales controlling in the future. Her goal: **more clarity, more foresight, more substance** in every decision.
<table>
<thead>
<tr>
<th>**Area of responsibility**</th>
<th>**Goal**</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Financial Controlling**</td>
<td>Clean figures, clear structures, and comprehensible reports</td>
</tr>
<tr>
<td>**Sales Controlling**</td>
<td>Analyze sales figures, identify potential, derive trends</td>
</tr>
<tr>
<td>**Forecasts & Analyses**</td>
<td>Early assessment of market movements and investment opportunities</td>
</tr>
<tr>
<td>**Reporting & Communication**</td>
<td>Prepare complex data so that everyone understands it quickly and precisely</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Areas of Responsibility & Goals"
items={[
{ label: "Financial Controlling", value: "Clean figures, clear structures, and comprehensible reports" },
{ label: "Sales Controlling", value: "Analyze sales figures, identify potential, derive trends" },
{ label: "Forecasts & Analyses", value: "Early assessment of market movements and investment opportunities" },
{ label: "Reporting & Communication", value: "Prepare complex data so that everyone understands it quickly and precisely" }
]}
/>
Julia will thus play a central role in the further development of KLZ as an interface between **management, sales, and strategy**.<br />Or, to put it more casually: she ensures that we not only know **where we stand**, but also **where we are going**.
### **Experience that connects**
**Understanding of numbers meets practical experience.**<br />With over 13 years of experience in controlling and sales, [**Julia Havasi**](https://www.linkedin.com/in/julia-havasi-18556b233/) brings the ideal combination of analytical precision and entrepreneurial thinking.

View File

@@ -11,33 +11,16 @@ Cable drums play a crucial role in the wind power industry—they ensure the saf
Without a well-thought-out recycling concept, vast amounts of wood, steel, and plastic would go to waste. However, efficient solutions already exist to return cable drums to the raw material cycle and minimize environmental impact.
### Materials and their reuse
Cable drums are made from different materials, each offering unique recycling possibilities. The way they are reintegrated into the circular economy depends on whether the material can be directly reused or needs further processing.
<h4>Main materials of cable drums and their recycling options</h4>
<table>
<thead>
<tr>
<th>**Material**</th>
<th>**Properties**</th>
<th>**Recycling options**</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Wood**</td>
<td>Biodegradable, easy to repair</td>
<td>Upcycling, biomass, pallet production</td>
</tr>
<tr>
<td>**Steel**</td>
<td>Durable, reusable, corrosion-resistant</td>
<td>Melting down, reprocessing</td>
</tr>
<tr>
<td>**Plastic**</td>
<td>Weather-resistant, lightweight, long-lasting</td>
<td>Granulate production, upcycling</td>
</tr>
</tbody>
</table>
<StickyNarrative
title="Materials and their Reuse"
items={[
{ title: "Wood", content: "**Properties:** Biodegradable, easy to repair. **Recycling:** Upcycling, biomass, pallet production." },
{ title: "Steel", content: "**Properties:** Durable, reusable, corrosion-resistant. **Recycling:** Melting down, reprocessing." },
{ title: "Plastic", content: "**Properties:** Weather-resistant, lightweight, long-lasting. **Recycling:** Granulate production, upcycling." }
]}
/>
Depending on their condition, cable drums can be directly reused, repaired, or dismantled into their individual components. Wood, in particular, offers versatile reuse possibilities, while steel and plastic serve as valuable raw materials for new products.
To prevent raw material waste, it is essential to view damaged cable drums not as disposable waste but as a valuable resource.
### The recycling process: from return to reuse

View File

@@ -42,48 +42,21 @@ While standard cables quickly reach their limits under extreme temperatures, mec
In short: the H1Z2Z2-K 6mm² is no off-the-shelf solution its a specialized energy cable for an industry that doesnt compromise.
## Technical specifications and construction in detail
One of the strengths of this cable lies in its material structure and the resulting thermal and mechanical durability.
<table>
<thead>
<tr>
<th>**Property**</th>
<th>**Value / Description**</th>
</tr>
</thead>
<tbody>
<tr>
<td>Conductor</td>
<td>Fine-stranded, tinned copper conductor (Class 5)</td>
</tr>
<tr>
<td>Rated voltage</td>
<td>1500 V DC (compliant with EN 50618)</td>
</tr>
<tr>
<td>Test voltage</td>
<td>6.5 kV</td>
</tr>
<tr>
<td>Operating temperature range</td>
<td>-40 °C to +90 °C (conductor max. +120 °C)</td>
</tr>
<tr>
<td>Insulation and sheath</td>
<td>Cross-linked polyolefin, halogen-free</td>
</tr>
<tr>
<td>Outer diameter (6mm²)</td>
<td>approx. 6.4 mm</td>
</tr>
<tr>
<td>Bending radius</td>
<td>min. 4 × cable diameter</td>
</tr>
<tr>
<td>Max. current capacity (free laid)</td>
<td>up to 70 A (depending on ambient temperature)</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Technical Specifications & Construction"
items={[
{ label: "Conductor", value: "Fine-stranded, tinned copper conductor (Class 5)" },
{ label: "Rated voltage", value: "1500 V DC (compliant with EN 50618)" },
{ label: "Test voltage", value: "6.5 kV" },
{ label: "Operating temperature range", value: "-40 °C to +90 °C (conductor max. +120 °C)" },
{ label: "Insulation and sheath", value: "Cross-linked polyolefin, halogen-free" },
{ label: "Outer diameter (6mm²)", value: "approx. 6.4 mm" },
{ label: "Bending radius", value: "min. 4 × cable diameter" },
{ label: "Max. current capacity", value: "up to 70 A (depending on ambient temperature)" }
]}
/>
## Standards and certifications: EN 50618 &amp; more
The H1Z2Z2-K 6mm² meets all key standards for use in photovoltaic systems. These standards ensure safety, durability, and compliance with legal requirements.
### EN 50618 European standard for solar cables

View File

@@ -20,37 +20,18 @@ Projects in the 10 to 30 kV range bring recurring demands regardless of whet
The reality on construction sites shows: a cable that lacks flexibility, has large bending radii, or reaches its thermal limits too quickly not only delays implementation it also endangers operational safety.
<h4>Why the NA2XS(F)2Y is ideal for modern energy infrastructure</h4>
The [**NA2XS(F)2Y**](/en/products/medium-voltage-cables/na2xsf2y/) meets these requirements with a well-thought-out, field-proven design. It is built for **long-term operation under load** and shows its strengths particularly in industrial and energy networks.
**Key features at a glance:**
<table>
<thead>
<tr>
<th>Feature</th>
<th>Benefit for your project</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Aluminum conductor**</td>
<td>High conductivity, low transmission losses</td>
</tr>
<tr>
<td>**XLPE insulation**</td>
<td>High thermal resistance, durable and stable</td>
</tr>
<tr>
<td>**EMC-optimized design**</td>
<td>Interference-free operation in sensitive network environments</td>
</tr>
<tr>
<td>**Standard-compliant design (IEC)**</td>
<td>Safety in tenders, testing, and operation</td>
</tr>
<tr>
<td>**Robust outer construction**</td>
<td>Suitable for all common installation methods</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Key Features at a Glance"
items={[
{ label: "Aluminum conductor", value: "High conductivity, low transmission losses" },
{ label: "XLPE insulation", value: "High thermal resistance, durable and stable" },
{ label: "EMC-optimized design", value: "Interference-free operation in sensitive network environments" },
{ label: "Standard-compliant design (IEC)", value: "Safety in tenders, testing, and operation" },
{ label: "Robust outer construction", value: "Suitable for all common installation methods" }
]}
/>
In other words: with the [NA2XS(F)2Y](/en/products/medium-voltage-cables/na2xsf2y/), you can build networks that not only work on paper but also perform reliably in practice long-term, low-maintenance, and safe.
Learn more about why grid expansion is so important here:

View File

@@ -14,29 +14,16 @@ Grid connection cables for wind farms are not just thicker versions of standard
✔ High tensile forces when pulling and laying the cables<br />✔ Bending radii that must be maintained to prevent insulation damage<br />✔ Vibrations from wind turbines that transfer through the foundations to the cables
### **Electrical stress**
High voltage spikes due to sudden feed-in fluctuations<br />Partial discharges that can damage insulation over the years<br />Electromagnetic influences requiring shielding and grounding of the cables
### **Thermal loads**
<table>
<thead>
<tr>
<th>Load factor</th>
<th>Impact on the cable</th>
</tr>
</thead>
<tbody>
<tr>
<td>Temperature fluctuations</td>
<td>Material expansion, cracks in the insulation</td>
</tr>
<tr>
<td>Continuous high current load</td>
<td>Heating of the cable conductors</td>
</tr>
<tr>
<td>Heat dissipation</td>
<td>Crucial for permissible current capacity</td>
</tr>
</tbody>
</table>
<TechnicalGrid
title="Thermal Loads"
items={[
{ label: "Temperature fluctuations", value: "Material expansion, cracks in the insulation" },
{ label: "Continuous high current load", value: "Heating of the cable conductors" },
{ label: "Heat dissipation", value: "Crucial for permissible current capacity" }
]}
/>
### **Environmental influences**
🌧 Moisture &amp; water Water ingress can destroy insulation<br />🔥 UV radiation &amp; extreme temperatures Particularly relevant for above-ground installation<br />🌍 Chemical exposure &amp; ground movements A critical factor, especially for underground cables
## **Material and construction What makes a good grid connection cable?**

View File

@@ -80,7 +80,7 @@ locale: de
<div class="min-h-8 text-message flex w-full flex-col items-end gap-2 whitespace-normal break-words text-start [.text-message+&amp;]:mt-5" dir="auto" data-message-author-role="assistant" data-message-id="66eb3f45-dd35-419f-8c8e-be50fee94d71" data-message-model-slug="gpt-4o">
<div class="flex w-full flex-col gap-1 empty:hidden first:pt-[3px]">
<div class="markdown prose w-full break-words dark:prose-invert dark">
<h2>Herausforderungen sind da, um gelöst zu werden nicht, um über ihre Komplexität zu diskutieren.</h2>
<h2>Herausforderungen sind da, um gelöst zu werden nicht, um über ihre Komplexität zu diskutieren.</h2>
</div>
</div>
</div>
@@ -105,7 +105,7 @@ locale: de
<div class="min-h-8 text-message flex w-full flex-col items-end gap-2 whitespace-normal break-words text-start [.text-message+&amp;]:mt-5" dir="auto" data-message-author-role="assistant" data-message-id="d3bd1bc9-d279-4699-991f-cd5809bda6d7" data-message-model-slug="gpt-4o">
<div class="flex w-full flex-col gap-1 empty:hidden first:pt-[3px]">
<div class="markdown prose w-full break-words dark:prose-invert dark">
<h2>Manchmal braucht es nur einen klaren Kopf und das richtige Kabel, um die Welt ein Stück besser zu machen.</h2>
<h2>Manchmal braucht es nur einen klaren Kopf und das richtige Kabel, um die Welt ein Stück besser zu machen.</h2>
</div>
</div>
</div>

View File

@@ -60,13 +60,13 @@ locale: en
<h1>Michael Bodemer</h1>
<h2>&#8220;Challenges exist to be solved, not to debate how complicated they are.&#8221;</h2>
<h2>Challenges exist to be solved, not to debate how complicated they are.</h2>
Michael Bodemer is the go-to guy when things get complicated—and lets face it, thats often the case with cable networks. With sharp insight and a knack for practical solutions, Michael is one of our key players. Hes not just detail-oriented; hes a driving force—whether its in planning, customer interactions, or securing the best cables for every project.
<h3><strong>A Legacy of Excellence in Every Connection</strong></h3>
<p>At KLZ, our expertise is built on generations of dedication to the energy industry. With decades of hands-on experience, weve grown alongside the evolution of cable technology, combining traditional craftsmanship with modern innovation. Each project we take on reflects a deep understanding of what it takes to create lasting, reliable energy solutions.</p>
<p>Paired with historic illustrations from the industrys early days, our story is a reminder of how far cables have come and how much care has always gone into connecting the world.
<h1>Klaus Mintel</h1>
<h2>&#8220;Sometimes all it takes is a clear head and a good cable to make the world a little better.&#8221;</h2>
<h2>Sometimes all it takes is a clear head and a good cable to make the world a little better.</h2>
Klaus is the man with the experience, bringing perspective and calm to the table—even when cable chaos threatens to take over. With impressive industry knowledge and a network as solid as our cables, he ensures everything runs smoothly. Klaus isnt just a problem solver; hes a strategic thinker who knows how to get to the point with a touch of humor.
<h2>Our manifesto</h2>

View File

@@ -19,6 +19,7 @@ The design should feel **Industrial yet Modern**, **Reliable**, and **Sustainabl
### 2.2 Color Strategy
- **Trust (Primary Blue)**: Deep, vibrant blue used for core branding and primary actions. Represents stability and the flow of electricity.
- **Saturated Blue (#011dff)**: A high-intensity blue used for specific high-impact accents and digital-first elements.
- **Growth (Accent Green)**: A bright, energetic green used exclusively for highlights related to renewable energy and the future.
- **Foundation (Neutrals)**: A range of blacks, dark grays, and clean whites to provide a professional, industrial backdrop.

View File

@@ -73,3 +73,25 @@ export async function getAdjacentPosts(slug: string, locale: string): Promise<{
return { prev, next };
}
export function getReadingTime(content: string): number {
const wordsPerMinute = 200;
const noOfWords = content.split(/\s/g).length;
const minutes = noOfWords / wordsPerMinute;
return Math.ceil(minutes);
}
export function getHeadings(content: string): { id: string; text: string; level: number }[] {
const headingLines = content.split('\n').filter((line) => line.match(/^#{2,3}\s/));
return headingLines.map((line) => {
const level = line.match(/^#+/)?.[0].length || 0;
const text = line.replace(/^#+\s/, '').trim();
const id = text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
return { id, text, level };
});
}

54
lib/datasheets.ts Normal file
View File

@@ -0,0 +1,54 @@
import fs from 'fs';
import path from 'path';
/**
* Finds the datasheet PDF path for a given product slug and locale.
* Checks public/datasheets for matching files.
*/
export function getDatasheetPath(slug: string, locale: string): string | null {
const datasheetsDir = path.join(process.cwd(), 'public', 'datasheets');
if (!fs.existsSync(datasheetsDir)) {
return null;
}
// Normalize slug: remove common suffixes that might not be in the PDF filename
const normalizedSlug = slug.replace(/-hv$|-mv$/, '');
// List of patterns to try for the current locale
const patterns = [
`${slug}-${locale}.pdf`,
`${slug}-2-${locale}.pdf`,
`${slug}-3-${locale}.pdf`,
`${normalizedSlug}-${locale}.pdf`,
`${normalizedSlug}-2-${locale}.pdf`,
`${normalizedSlug}-3-${locale}.pdf`,
];
for (const pattern of patterns) {
const filePath = path.join(datasheetsDir, pattern);
if (fs.existsSync(filePath)) {
return `/datasheets/${pattern}`;
}
}
// Fallback to English if locale is not 'en'
if (locale !== 'en') {
const enPatterns = [
`${slug}-en.pdf`,
`${slug}-2-en.pdf`,
`${slug}-3-en.pdf`,
`${normalizedSlug}-en.pdf`,
`${normalizedSlug}-2-en.pdf`,
`${normalizedSlug}-3-en.pdf`,
];
for (const pattern of enPatterns) {
const filePath = path.join(datasheetsDir, pattern);
if (fs.existsSync(filePath)) {
return `/datasheets/${pattern}`;
}
}
}
return null;
}

View File

@@ -73,6 +73,13 @@ export async function getAllProductSlugs(locale: string): Promise<string[]> {
export async function getAllProducts(locale: string): Promise<ProductMdx[]> {
const slugs = await getAllProductSlugs(locale);
const products = await Promise.all(slugs.map(slug => getProductBySlug(slug, locale)));
let allSlugs = slugs;
if (locale !== 'en') {
const enSlugs = await getAllProductSlugs('en');
allSlugs = Array.from(new Set([...slugs, ...enSlugs]));
}
const products = await Promise.all(allSlugs.map(slug => getProductBySlug(slug, locale)));
return products.filter((p): p is ProductMdx => p !== null);
}

View File

@@ -32,3 +32,17 @@ export async function getPageBySlug(slug: string, locale: string): Promise<PageM
content,
};
}
export async function getAllPages(locale: string): Promise<PageMdx[]> {
const pagesDir = path.join(process.cwd(), 'data', 'pages', locale);
if (!fs.existsSync(pagesDir)) return [];
const files = fs.readdirSync(pagesDir);
const pages = await Promise.all(
files
.filter(file => file.endsWith('.mdx'))
.map(file => getPageBySlug(file.replace(/\.mdx$/, ''), locale))
);
return pages.filter((p): p is PageMdx => p !== null);
}

View File

@@ -3,8 +3,8 @@
"title": "KLZ Cables - Hochwertige Kabel",
"description": "Ihr Partner für hochwertige Kabel.",
"meta": {
"title": "KLZ Cables",
"description": "Premium-Kabellösungen"
"title": "KLZ Cables | Hochwertige Strom- & Solarkabel",
"description": "Ihr Experte für hochwertige Stromkabel, Mittelspannungslösungen und Solarkabel. Zuverlässige Infrastruktur für eine grüne Energiezukunft."
}
},
"Navigation": {
@@ -46,7 +46,7 @@
},
"michael": {
"name": "Michael Bodemer",
"quote": "Herausforderungen sind da, um gelöst zu werden nicht, um über ihre Komplexität zu diskutieren.",
"quote": "Herausforderungen sind da, um gelöst zu werden nicht, um über ihre Komplexität zu diskutieren.",
"description": "Michael Bodemer ist unser Mann, wenn es kompliziert wird und das ist bei Kabelnetzen oft der Fall. Mit seinem scharfen Blick und einem Händchen für praktikable Lösungen ist er eine unserer zentralen Säulen. Michael denkt nicht nur an Details, er treibt Projekte voran sei es in der Planung, im Kundengespräch oder bei der Auswahl der besten Kabel für jedes Vorhaben.",
"linkedin": "Michael's LinkedIn",
"role": "Geschäftsführer"
@@ -63,7 +63,7 @@
},
"klaus": {
"name": "Klaus Mintel",
"quote": "Manchmal braucht es nur einen klaren Kopf und das richtige Kabel, um die Welt ein Stück besser zu machen.",
"quote": "Manchmal braucht es nur einen klaren Kopf und das richtige Kabel, um die Welt ein Stück besser zu machen.",
"description": "Klaus ist der Fels in der Brandung selbst wenn das Kabelchaos überhandnimmt. Mit jahrzehntelanger Erfahrung und einem stabilen Netzwerk sorgt er dafür, dass alles glatt läuft. Er denkt nicht nur in Lösungen, sondern bringt auch Humor und den nötigen Weitblick mit, um selbst komplexe Themen locker auf den Punkt zu bringen.",
"linkedin": "Klaus' LinkedIn",
"role": "Gründer & Visionär"
@@ -155,6 +155,8 @@
"relatedProductsTitle": "Ähnliche Produkte",
"requestQuote": "Angebot anfordern",
"requestQuoteDesc": "Erhalten Sie technische Spezifikationen und Preise für Ihr Projekt.",
"downloadDatasheet": "Datenblatt herunterladen",
"downloadDatasheetDesc": "Erhalten Sie die vollständigen technischen Spezifikationen als PDF.",
"form": {
"contactInfo": "Kontaktinformationen",
"projectDetails": "Projektdetails",
@@ -170,6 +172,8 @@
"privacyNote": "Mit dem Absenden erklären Sie sich mit unserer Datenschutzerklärung einverstanden"
},
"englishVersion": "Englische Version",
"showMore": "Mehr anzeigen",
"showLess": "Weniger anzeigen",
"categories": {
"lowVoltage": {
"title": "Niederspannungskabel",
@@ -283,8 +287,8 @@
},
"Blog": {
"meta": {
"title": "Neuigkeiten zu Kabeln und Energielösungen",
"description": "Bleiben Sie auf dem Laufenden! Lesen Sie aktuelle Themen und Insights zu Kabeltechnologie, Energielösungen und branchenspezifischen Innovationen."
"title": "Kabel-News & Energie-Insights | KLZ Cables Blog",
"description": "Bleiben Sie informiert über die neuesten Trends in der Kabeltechnologie, Energieinfrastruktur und nachhaltigen Stromlösungen. Expertenwissen von KLZ Cables."
},
"featuredPost": "Hervorgehobener Beitrag",
"readFullArticle": "Vollständigen Artikel lesen",

View File

@@ -3,8 +3,8 @@
"title": "KLZ Cables - High Quality Cables",
"description": "Your partner for high quality cables.",
"meta": {
"title": "KLZ Cables",
"description": "Premium Cable Solutions"
"title": "KLZ Cables | High-Quality Power & Solar Cables",
"description": "Your expert partner for high-quality power cables, medium voltage solutions, and solar cables. Reliable infrastructure for a green energy future."
}
},
"Navigation": {
@@ -46,7 +46,7 @@
},
"michael": {
"name": "Michael Bodemer",
"quote": "\"Challenges exist to be solved, not to debate how complicated they are.\"",
"quote": "Challenges exist to be solved, not to debate how complicated they are.",
"description": "Michael Bodemer is the go-to guy when things get complicated—and lets face it, thats often the case with cable networks. With sharp insight and a knack for practical solutions, Michael is one of our key players. Hes not just detail-oriented; hes a driving force—whether its in planning, customer interactions, or securing the best cables for every project.",
"linkedin": "Check Michael's LinkedIn",
"role": "Managing Director"
@@ -63,7 +63,7 @@
},
"klaus": {
"name": "Klaus Mintel",
"quote": "\"Sometimes all it takes is a clear head and a good cable to make the world a little better.\"",
"quote": "Sometimes all it takes is a clear head and a good cable to make the world a little better.",
"description": "Klaus is the man with the experience, bringing perspective and calm to the table—even when cable chaos threatens to take over. With impressive industry knowledge and a network as solid as our cables, he ensures everything runs smoothly. Klaus isnt just a problem solver; hes a strategic thinker who knows how to get to the point with a touch of humor.",
"linkedin": "Check Klaus' LinkedIn",
"role": "Founder & Visionary"
@@ -155,6 +155,8 @@
"relatedProductsTitle": "Related Products",
"requestQuote": "Request a Quote",
"requestQuoteDesc": "Get technical specifications and pricing for your project.",
"downloadDatasheet": "Download Datasheet",
"downloadDatasheetDesc": "Get the full technical specifications in PDF format.",
"form": {
"contactInfo": "Contact Information",
"projectDetails": "Project Details",
@@ -170,6 +172,8 @@
"privacyNote": "By submitting you agree to our privacy policy"
},
"englishVersion": "English Version",
"showMore": "Show More",
"showLess": "Show Less",
"categories": {
"lowVoltage": {
"title": "Low Voltage Cables",
@@ -283,8 +287,8 @@
},
"Blog": {
"meta": {
"title": "News on Cables and Energy Solutions",
"description": "Stay up to date! Read current topics and insights on cable technology, energy solutions and industry-specific innovations."
"title": "Cable Industry News & Energy Insights | KLZ Cables Blog",
"description": "Stay informed with the latest trends in cable technology, energy infrastructure, and sustainable power solutions. Expert insights from KLZ Cables."
},
"featuredPost": "Featured Post",
"readFullArticle": "Read Full Article",

43
package-lock.json generated
View File

@@ -16,6 +16,7 @@
"cheerio": "^1.1.2",
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"framer-motion": "^12.27.1",
"gray-matter": "^4.0.3",
"i18next": "^25.7.3",
"jsdom": "^27.4.0",
@@ -8547,6 +8548,33 @@
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/framer-motion": {
"version": "12.27.1",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.27.1.tgz",
"integrity": "sha512-cEAqO69kcZt3gL0TGua8WTgRQfv4J57nqt1zxHtLKwYhAwA0x9kDS/JbMa1hJbwkGY74AGJKvZ9pX/IqWZtZWQ==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.27.1",
"motion-utils": "^12.24.10",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -11436,6 +11464,21 @@
"integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
"license": "MIT"
},
"node_modules/motion-dom": {
"version": "12.27.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.27.1.tgz",
"integrity": "sha512-V/53DA2nBqKl9O2PMJleSUb/G0dsMMeZplZwgIQf5+X0bxIu7Q1cTv6DrjvTTGYRm3+7Y5wMlRZ1wT61boU/bQ==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.24.10"
}
},
"node_modules/motion-utils": {
"version": "12.24.10",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz",
"integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==",
"license": "MIT"
},
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",

View File

@@ -8,6 +8,7 @@
"cheerio": "^1.1.2",
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"framer-motion": "^12.27.1",
"gray-matter": "^4.0.3",
"i18next": "^25.7.3",
"jsdom": "^27.4.0",

View File

@@ -9,6 +9,8 @@
--color-primary-dark: #000d26;
--color-primary-light: #e6ebf5;
--color-saturated: #011dff; /* Saturated Blue Accent */
--color-secondary: #003d82;
--color-secondary-light: #0056b3;
@@ -34,6 +36,7 @@
--animate-slide-up: slide-up 0.6s ease-out;
--animate-slow-zoom: slow-zoom 20s linear infinite;
--animate-reveal: reveal 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
--animate-slight-fade-in-from-bottom: slight-fade-in-from-bottom 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
@keyframes fade-in {
from { opacity: 0; }
@@ -51,6 +54,10 @@
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slight-fade-in-from-bottom {
from { opacity: 0; transform: translateY(15px); }
to { opacity: 1; transform: translateY(0); }
}
}
@layer base {

View File

@@ -19,7 +19,7 @@ module.exports = {
colors: {
// Brand Colors
primary: {
DEFAULT: '#0117bf',
DEFAULT: '#011dff',
dark: '#000e7a',
light: '#3344cc',
},

File diff suppressed because one or more lines are too long