animations
This commit is contained in:
@@ -30,12 +30,12 @@ export default function Footer() {
|
||||
Leading the way in cable infrastructure and sustainable energy solutions. Quality, innovation, and reliability since 1998.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
{['linkedin', 'twitter', 'facebook'].map((social) => (
|
||||
<a key={social} href="#" className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center hover:bg-accent hover:text-primary-dark transition-all duration-300 border border-white/10">
|
||||
<span className="sr-only">{social}</span>
|
||||
<div className="w-5 h-5 bg-current rounded-sm opacity-80" />
|
||||
</a>
|
||||
))}
|
||||
<a href="https://www.linkedin.com/company/klz-vertriebs-gmbh/" target="_blank" rel="noopener noreferrer" className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center hover:bg-accent hover:text-primary-dark transition-all duration-300 border border-white/10">
|
||||
<span className="sr-only">LinkedIn</span>
|
||||
<svg className="w-5 h-5 fill-current" viewBox="0 0 24 24">
|
||||
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -46,14 +46,12 @@ export default function Header() {
|
||||
"fixed top-0 left-0 right-0 z-50 transition-all duration-500",
|
||||
{
|
||||
"bg-transparent py-8": isHomePage && !isScrolled,
|
||||
"bg-white/90 backdrop-blur-xl shadow-2xl py-4 border-b border-primary/5": !isHomePage || isScrolled,
|
||||
"bg-primary py-4 shadow-2xl": !isHomePage || isScrolled,
|
||||
}
|
||||
);
|
||||
|
||||
const textColorClass = (isHomePage && !isScrolled) ? "text-white" : "text-primary";
|
||||
const logoSrc = (isHomePage && !isScrolled)
|
||||
? "/logo-white.svg"
|
||||
: "/logo-blue.svg";
|
||||
const textColorClass = "text-white";
|
||||
const logoSrc = "/logo-white.svg";
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -107,7 +105,7 @@ export default function Header() {
|
||||
|
||||
<Button
|
||||
href={`/${currentLocale}/contact`}
|
||||
variant={isHomePage && !isScrolled ? "white" : "primary"}
|
||||
variant="white"
|
||||
size="md"
|
||||
className="px-8 shadow-xl"
|
||||
>
|
||||
@@ -124,7 +122,7 @@ export default function Header() {
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{!isHomePage && <div className="h-32 md:h-40" />}
|
||||
{/* Removed spacer div that caused white gap */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { getAllProducts } from '@/lib/mdx';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
interface RelatedProductsProps {
|
||||
currentSlug: string;
|
||||
@@ -9,6 +11,7 @@ interface RelatedProductsProps {
|
||||
|
||||
export default async function RelatedProducts({ currentSlug, categories, locale }: RelatedProductsProps) {
|
||||
const allProducts = await getAllProducts(locale);
|
||||
const t = await getTranslations('Products');
|
||||
|
||||
// Filter products: same category, not current product
|
||||
const related = allProducts
|
||||
@@ -16,47 +19,72 @@ export default async function RelatedProducts({ currentSlug, categories, locale
|
||||
p.slug !== currentSlug &&
|
||||
p.frontmatter.categories.some(cat => categories.includes(cat))
|
||||
)
|
||||
.slice(0, 4); // Limit to 4
|
||||
.slice(0, 3); // Limit to 3 for better spacing
|
||||
|
||||
if (related.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-16 pt-8 border-t border-neutral-dark">
|
||||
<h2 className="text-2xl font-bold text-primary-dark mb-6">Related Products</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{related.map((product) => (
|
||||
<Link
|
||||
key={product.slug}
|
||||
href={`/${locale}/products/${product.slug}`}
|
||||
className="group block bg-white rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow border border-neutral-dark"
|
||||
>
|
||||
<div className="aspect-[4/3] relative bg-neutral-light">
|
||||
{product.frontmatter.images?.[0] ? (
|
||||
<img
|
||||
src={product.frontmatter.images[0]}
|
||||
alt={product.frontmatter.title}
|
||||
className="w-full h-full object-contain p-4 group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-text-secondary">
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3 className="font-semibold text-lg text-text-primary group-hover:text-primary transition-colors">
|
||||
{product.frontmatter.title}
|
||||
</h3>
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{product.frontmatter.categories.slice(0, 1).map((cat, idx) => (
|
||||
<span key={idx} className="text-xs bg-neutral text-text-secondary px-2 py-1 rounded">
|
||||
{cat}
|
||||
</span>
|
||||
))}
|
||||
<div className="mt-32 pt-32 border-t border-neutral-dark/10">
|
||||
<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">
|
||||
{t('relatedProductsTitle') || 'Related Products'}
|
||||
</h2>
|
||||
<div className="h-1.5 w-20 bg-accent rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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, '-');
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={product.slug}
|
||||
href={`/${locale}/products/${catSlug}/${product.slug}`}
|
||||
className="group block bg-white rounded-[32px] overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-500 border border-neutral-dark/5"
|
||||
>
|
||||
<div className="aspect-[16/10] relative bg-neutral-light/30 p-8 overflow-hidden">
|
||||
{product.frontmatter.images?.[0] ? (
|
||||
<>
|
||||
<Image
|
||||
src={product.frontmatter.images[0]}
|
||||
alt={product.frontmatter.title}
|
||||
fill
|
||||
className="object-contain p-4 transition-transform duration-700 group-hover:scale-110 z-10"
|
||||
/>
|
||||
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 w-2/3 h-3 bg-black/5 blur-lg rounded-full" />
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-text-secondary/30 font-bold uppercase tracking-widest text-xs">
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<div className="p-8">
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{product.frontmatter.categories.slice(0, 1).map((cat, idx) => (
|
||||
<span key={idx} className="text-[10px] font-bold uppercase tracking-widest text-primary/40">
|
||||
{cat}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-text-primary group-hover:text-primary transition-colors leading-tight">
|
||||
{product.frontmatter.title}
|
||||
</h3>
|
||||
<div className="mt-6 flex items-center text-primary text-sm font-bold group-hover:text-accent-dark transition-colors">
|
||||
<span className="border-b-2 border-primary/10 group-hover:border-accent-dark transition-colors pb-0.5">
|
||||
{t('details')}
|
||||
</span>
|
||||
<svg className="w-4 h-4 ml-2 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>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface RequestQuoteFormProps {
|
||||
productName: string;
|
||||
}
|
||||
|
||||
export default function RequestQuoteForm({ productName }: RequestQuoteFormProps) {
|
||||
const t = useTranslations('Products.form');
|
||||
const [email, setEmail] = useState('');
|
||||
const [request, setRequest] = useState('');
|
||||
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
|
||||
@@ -28,74 +30,84 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
|
||||
|
||||
if (status === 'success') {
|
||||
return (
|
||||
<div className="bg-green-50 border border-green-200 text-green-800 p-6 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2">Request Sent!</h3>
|
||||
<p>Thank you for your interest in {productName}. We will get back to you shortly.</p>
|
||||
<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">
|
||||
<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">
|
||||
{t('successDesc', { productName })}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setStatus('idle')}
|
||||
className="mt-4 text-sm font-medium underline hover:text-green-900"
|
||||
className="inline-flex items-center text-[10px] font-bold uppercase tracking-[0.2em] text-primary hover:text-accent transition-colors group"
|
||||
>
|
||||
Send another request
|
||||
<span className="border-b-2 border-primary/10 group-hover:border-accent transition-colors pb-1">
|
||||
{t('sendAnother')}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-neutral-light p-6 rounded-lg border border-neutral-dark">
|
||||
<h3 className="text-xl font-semibold mb-4 text-primary-dark">Request Cable</h3>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="product" className="block text-sm font-medium text-text-secondary mb-1">
|
||||
Product
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="product"
|
||||
value={productName}
|
||||
disabled
|
||||
className="w-full px-3 py-2 bg-neutral border border-neutral-dark rounded text-text-secondary cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-text-primary mb-1">
|
||||
Email <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-white border border-neutral-dark rounded focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
placeholder="your@email.com"
|
||||
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')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="request" className="block text-sm font-medium text-text-primary mb-1">
|
||||
Request <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="space-y-1.5">
|
||||
<textarea
|
||||
id="request"
|
||||
required
|
||||
rows={4}
|
||||
value={request}
|
||||
onChange={(e) => setRequest(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-white border border-neutral-dark rounded focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
placeholder="Describe your request, e.g. the cross section etc."
|
||||
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')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={status === 'submitting'}
|
||||
className="w-full bg-primary text-white font-medium py-2 px-4 rounded hover:bg-primary-dark transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
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"
|
||||
>
|
||||
{status === 'submitting' ? 'Sending...' : 'Request Cable'}
|
||||
{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">
|
||||
<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-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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
||||
</svg>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p className="text-[8px] text-center text-text-secondary/40 uppercase tracking-[0.15em] font-medium px-2">
|
||||
{t('privacyNote')}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,18 +34,15 @@ export default function Experience() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
{[
|
||||
{ label: 'Years Experience', value: '25+' },
|
||||
{ label: 'Projects Completed', value: '1.2k' },
|
||||
{ label: 'Global Partners', value: '85' },
|
||||
{ label: 'Cable Types', value: '450+' },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className="animate-fade-in" style={{ animationDelay: `${i * 100}ms` }}>
|
||||
<div className="text-4xl font-extrabold text-accent mb-2">{stat.value}</div>
|
||||
<div className="text-sm font-bold uppercase tracking-widest text-white/60">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-16 grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div className="animate-fade-in">
|
||||
<div className="text-4xl font-extrabold text-accent mb-4">Certified Quality</div>
|
||||
<div className="text-lg font-bold uppercase tracking-widest text-white/60">VDE Approved & Trusted by Major Energy Suppliers</div>
|
||||
</div>
|
||||
<div className="animate-fade-in" style={{ animationDelay: '100ms' }}>
|
||||
<div className="text-4xl font-extrabold text-accent mb-4">Full Spectrum</div>
|
||||
<div className="text-lg font-bold uppercase tracking-widest text-white/60">From 1kV to 220kV Solutions</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -2,26 +2,14 @@ import React from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Container, Button } from '@/components/ui';
|
||||
import Scribble from '@/components/Scribble';
|
||||
import HeroIllustration from './HeroIllustration';
|
||||
|
||||
export default function Hero() {
|
||||
const t = useTranslations('Home.hero');
|
||||
|
||||
return (
|
||||
<section className="relative h-[90vh] flex items-center justify-center overflow-hidden bg-primary-dark">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<video
|
||||
className="w-full h-full object-cover scale-105 animate-slow-zoom"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
poster="/uploads/2025/02/Still-2025-02-10-104337_1.1.1.webp"
|
||||
>
|
||||
<source src="/uploads/2025/02/header.webm" type="video/webm" />
|
||||
<source src="/uploads/2025/02/header.mp4" type="video/mp4" />
|
||||
</video>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/40 via-primary-dark/20 to-primary-dark/60" />
|
||||
</div>
|
||||
<HeroIllustration />
|
||||
|
||||
<Container className="relative z-10 text-left text-white w-full">
|
||||
<div className="max-w-5xl animate-slide-up">
|
||||
@@ -41,7 +29,7 @@ export default function Hero() {
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-1">→</span>
|
||||
</Button>
|
||||
<Button href="/products" variant="white" size="xl" className="group">
|
||||
Explore Products
|
||||
{t('exploreProducts')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
747
components/home/HeroIllustration.tsx
Normal file
747
components/home/HeroIllustration.tsx
Normal file
@@ -0,0 +1,747 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// Isometric grid configuration - true 2:1 isometric projection
|
||||
const CELL_WIDTH = 120;
|
||||
const CELL_HEIGHT = 60; // Half of width for 2:1 isometric
|
||||
|
||||
// Convert grid coordinates to isometric screen coordinates
|
||||
// Using standard isometric projection where x goes right-down, y goes right-up
|
||||
function gridToScreen(col: number, row: number): { x: number; y: number } {
|
||||
return {
|
||||
x: (col - row) * (CELL_WIDTH / 2),
|
||||
y: (col + row) * (CELL_HEIGHT / 2),
|
||||
};
|
||||
}
|
||||
|
||||
// Grid layout (10 columns x 8 rows)
|
||||
// Energy flow: Solar/Wind (left) → Substations (center) → Transmission → City (right)
|
||||
const GRID = {
|
||||
cols: 10,
|
||||
rows: 8,
|
||||
};
|
||||
|
||||
// Infrastructure positions - precisely on grid intersections
|
||||
const INFRASTRUCTURE = {
|
||||
// Solar panels (two groups)
|
||||
solar: [
|
||||
// Group 1 - bottom-left
|
||||
{ col: 0, row: 5 },
|
||||
{ col: 1, row: 5 },
|
||||
{ col: 0, row: 6 },
|
||||
{ col: 1, row: 6 },
|
||||
// Group 2 - middle-bottom
|
||||
{ col: 2, row: 7 },
|
||||
{ col: 3, row: 7 },
|
||||
{ col: 2, row: 8 },
|
||||
{ col: 3, row: 8 },
|
||||
],
|
||||
// Wind turbines (two groups)
|
||||
wind: [
|
||||
// Group 1 - top-left
|
||||
{ col: 0, row: 1 },
|
||||
{ col: 1, row: 2 },
|
||||
{ col: 2, row: 1 },
|
||||
// Group 2 - top-center
|
||||
{ col: 3, row: 0 },
|
||||
{ col: 4, row: 1 },
|
||||
{ col: 5, row: 0 },
|
||||
],
|
||||
// Substations
|
||||
substations: [
|
||||
{ col: 3, row: 3, type: 'collection' }, // Main collection substation
|
||||
{ col: 6, row: 4, type: 'distribution' }, // Distribution substation (right)
|
||||
{ col: 5, row: 7, type: 'distribution' }, // Distribution substation (bottom-left)
|
||||
],
|
||||
// Transmission towers (along the routes)
|
||||
towers: [
|
||||
{ col: 4, row: 3 },
|
||||
{ col: 5, row: 4 },
|
||||
{ col: 4, row: 5 },
|
||||
{ col: 5, row: 6 },
|
||||
],
|
||||
// City/Buildings (right side)
|
||||
city: [
|
||||
{ col: 8, row: 3, type: 'tall' },
|
||||
{ col: 9, row: 4, type: 'medium' },
|
||||
{ col: 8, row: 5, type: 'small' },
|
||||
{ col: 9, row: 5, type: 'medium' },
|
||||
],
|
||||
// City 2 (bottom-left area)
|
||||
city2: [
|
||||
{ col: 6, row: 8, type: 'medium' },
|
||||
{ col: 7, row: 7, type: 'tall' },
|
||||
{ col: 7, row: 8, type: 'small' },
|
||||
],
|
||||
// Trees (decorative, scattered around)
|
||||
trees: [
|
||||
{ col: 0, row: 3 },
|
||||
{ col: 2, row: 6 },
|
||||
{ col: 3, row: 1 },
|
||||
{ col: 6, row: 2 },
|
||||
{ col: 6, row: 6 },
|
||||
],
|
||||
};
|
||||
|
||||
// Power line connections - grid-aligned paths only (no diagonals)
|
||||
// Each group meets at a collection point, then flows to main substation
|
||||
const POWER_LINES = [
|
||||
// === WIND GROUP 1 (top-left) - meet at (1,1) then to substation ===
|
||||
// Turbine at (0,1) → collection point (1,1)
|
||||
{ from: { col: 0, row: 1 }, to: { col: 1, row: 1 } },
|
||||
// Turbine at (1,2) → up to (1,1)
|
||||
{ from: { col: 1, row: 2 }, to: { col: 1, row: 1 } },
|
||||
// Turbine at (2,1) → left to (1,1)
|
||||
{ from: { col: 2, row: 1 }, to: { col: 1, row: 1 } },
|
||||
// Collection point (1,1) → down to (1,3) → right to substation (3,3)
|
||||
{ from: { col: 1, row: 1 }, to: { col: 1, row: 3 } },
|
||||
{ from: { col: 1, row: 3 }, to: { col: 3, row: 3 } },
|
||||
|
||||
// === WIND GROUP 2 (top-center) - meet at (4,1) then to substation ===
|
||||
// Turbine at (3,0) → right to (4,0) → down to (4,1)
|
||||
{ from: { col: 3, row: 0 }, to: { col: 4, row: 0 } },
|
||||
{ from: { col: 4, row: 0 }, to: { col: 4, row: 1 } },
|
||||
// Turbine at (4,1) is the collection point
|
||||
// Turbine at (5,0) → down to (5,1) → left to (4,1)
|
||||
{ from: { col: 5, row: 0 }, to: { col: 5, row: 1 } },
|
||||
{ from: { col: 5, row: 1 }, to: { col: 4, row: 1 } },
|
||||
// Collection point (4,1) → down to (4,3) → left to substation (3,3)
|
||||
{ from: { col: 4, row: 1 }, to: { col: 4, row: 3 } },
|
||||
{ from: { col: 4, row: 3 }, to: { col: 3, row: 3 } },
|
||||
|
||||
// === SOLAR GROUP 1 (bottom-left) - meet at (1,5) then to substation ===
|
||||
// Panels at (0,5), (1,5), (0,6), (1,6) → collection at (1,5)
|
||||
{ from: { col: 0, row: 5 }, to: { col: 1, row: 5 } },
|
||||
{ from: { col: 0, row: 6 }, to: { col: 0, row: 5 } },
|
||||
{ from: { col: 1, row: 6 }, to: { col: 1, row: 5 } },
|
||||
// Collection point (1,5) → up to (1,3) → right to substation (3,3)
|
||||
{ from: { col: 1, row: 5 }, to: { col: 1, row: 3 } },
|
||||
|
||||
// === SOLAR GROUP 2 (middle-bottom) - meet at (3,7) then to substation ===
|
||||
// Panels at (2,7), (3,7), (2,8), (3,8) → collection at (3,7)
|
||||
{ from: { col: 2, row: 7 }, to: { col: 3, row: 7 } },
|
||||
{ from: { col: 2, row: 8 }, to: { col: 2, row: 7 } },
|
||||
{ from: { col: 3, row: 8 }, to: { col: 3, row: 7 } },
|
||||
// Collection point (3,7) → up to (3,3) substation
|
||||
{ from: { col: 3, row: 7 }, to: { col: 3, row: 5 } },
|
||||
{ from: { col: 3, row: 5 }, to: { col: 3, row: 3 } },
|
||||
|
||||
// === MAIN TRANSMISSION: Substation (3,3) → Towers → Distribution → City ===
|
||||
// Substation to first tower
|
||||
{ from: { col: 3, row: 3 }, to: { col: 4, row: 3 } },
|
||||
// First tower to second tower (grid-aligned)
|
||||
{ from: { col: 4, row: 3 }, to: { col: 5, row: 3 } },
|
||||
{ from: { col: 5, row: 3 }, to: { col: 5, row: 4 } },
|
||||
// Second tower to distribution substation (right)
|
||||
{ from: { col: 5, row: 4 }, to: { col: 6, row: 4 } },
|
||||
// Distribution to city 1 (grid-aligned)
|
||||
{ from: { col: 6, row: 4 }, to: { col: 7, row: 4 } },
|
||||
{ from: { col: 7, row: 4 }, to: { col: 8, row: 4 } },
|
||||
// Branch to buildings (city 1)
|
||||
{ from: { col: 8, row: 4 }, to: { col: 8, row: 3 } },
|
||||
{ from: { col: 8, row: 4 }, to: { col: 8, row: 5 } },
|
||||
{ from: { col: 8, row: 3 }, to: { col: 9, row: 3 } },
|
||||
{ from: { col: 9, row: 3 }, to: { col: 9, row: 4 } },
|
||||
{ from: { col: 8, row: 5 }, to: { col: 9, row: 5 } },
|
||||
|
||||
// === SECOND ROUTE: Substation (3,3) → Towers → Distribution (5,7) → City 2 ===
|
||||
// Branch from main substation down
|
||||
{ from: { col: 3, row: 3 }, to: { col: 3, row: 5 } },
|
||||
{ from: { col: 3, row: 5 }, to: { col: 4, row: 5 } },
|
||||
// Tower at (4,5) to tower at (5,6)
|
||||
{ from: { col: 4, row: 5 }, to: { col: 5, row: 5 } },
|
||||
{ from: { col: 5, row: 5 }, to: { col: 5, row: 6 } },
|
||||
// Tower to distribution substation (bottom-left)
|
||||
{ from: { col: 5, row: 6 }, to: { col: 5, row: 7 } },
|
||||
// Distribution to city 2
|
||||
{ from: { col: 5, row: 7 }, to: { col: 6, row: 7 } },
|
||||
{ from: { col: 6, row: 7 }, to: { col: 6, row: 8 } },
|
||||
{ from: { col: 6, row: 7 }, to: { col: 7, row: 7 } },
|
||||
{ from: { col: 7, row: 7 }, to: { col: 7, row: 8 } },
|
||||
];
|
||||
|
||||
export default function HeroIllustration() {
|
||||
return (
|
||||
<div className="absolute inset-0 z-0 overflow-hidden bg-primary">
|
||||
<svg
|
||||
viewBox="-400 -200 1800 1100"
|
||||
className="w-full h-full"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
{/* Electric energy flow gradient */}
|
||||
<linearGradient id="energy-pulse" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#82ed20" stopOpacity="0" />
|
||||
<stop offset="30%" stopColor="#82ed20" stopOpacity="0.6" />
|
||||
<stop offset="50%" stopColor="#9bf14d" stopOpacity="1" />
|
||||
<stop offset="70%" stopColor="#82ed20" stopOpacity="0.6" />
|
||||
<stop offset="100%" stopColor="#82ed20" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
|
||||
{/* Wind flow gradient */}
|
||||
<linearGradient id="wind-flow" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="white" stopOpacity="0" />
|
||||
<stop offset="30%" stopColor="white" stopOpacity="0.4" />
|
||||
<stop offset="50%" stopColor="white" stopOpacity="0.6" />
|
||||
<stop offset="70%" stopColor="white" stopOpacity="0.4" />
|
||||
<stop offset="100%" stopColor="white" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
|
||||
{/* Sun ray gradient */}
|
||||
<linearGradient id="sun-ray" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#FFD700" stopOpacity="0.6" />
|
||||
<stop offset="50%" stopColor="#FFD700" stopOpacity="0.3" />
|
||||
<stop offset="100%" stopColor="#82ed20" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
|
||||
{/* Glow filter */}
|
||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="3" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
{/* Soft glow for nodes */}
|
||||
<filter id="soft-glow" x="-100%" y="-100%" width="300%" height="300%">
|
||||
<feGaussianBlur stdDeviation="2" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
{/* Sun glow filter */}
|
||||
<filter id="sun-glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="4" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
{/* Main scene container - positioned to the right */}
|
||||
<g transform="translate(900, 100)">
|
||||
|
||||
{/* === ISOMETRIC GRID === */}
|
||||
<g opacity="0.15">
|
||||
{/* Horizontal grid lines (going from top-left to bottom-right) */}
|
||||
{[...Array(GRID.rows + 1)].map((_, row) => {
|
||||
const start = gridToScreen(0, row);
|
||||
const end = gridToScreen(GRID.cols, row);
|
||||
return (
|
||||
<line
|
||||
key={`h-${row}`}
|
||||
x1={start.x}
|
||||
y1={start.y}
|
||||
x2={end.x}
|
||||
y2={end.y}
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* Vertical grid lines (going from top-right to bottom-left) */}
|
||||
{[...Array(GRID.cols + 1)].map((_, col) => {
|
||||
const start = gridToScreen(col, 0);
|
||||
const end = gridToScreen(col, GRID.rows);
|
||||
return (
|
||||
<line
|
||||
key={`v-${col}`}
|
||||
x1={start.x}
|
||||
y1={start.y}
|
||||
x2={end.x}
|
||||
y2={end.y}
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
|
||||
{/* Grid intersection nodes */}
|
||||
<g opacity="0.2">
|
||||
{[...Array(GRID.cols + 1)].map((_, col) =>
|
||||
[...Array(GRID.rows + 1)].map((_, row) => {
|
||||
const pos = gridToScreen(col, row);
|
||||
return (
|
||||
<circle
|
||||
key={`node-${col}-${row}`}
|
||||
cx={pos.x}
|
||||
cy={pos.y}
|
||||
r="2"
|
||||
fill="white"
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</g>
|
||||
|
||||
{/* === POWER LINES (Base cables) === */}
|
||||
<g stroke="white" strokeWidth="2" strokeOpacity="0.25">
|
||||
{POWER_LINES.map((line, i) => {
|
||||
const from = gridToScreen(line.from.col, line.from.row);
|
||||
const to = gridToScreen(line.to.col, line.to.row);
|
||||
return (
|
||||
<line
|
||||
key={`cable-${i}`}
|
||||
x1={from.x}
|
||||
y1={from.y}
|
||||
x2={to.x}
|
||||
y2={to.y}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
|
||||
{/* === ANIMATED ENERGY FLOW === */}
|
||||
<g filter="url(#glow)">
|
||||
{POWER_LINES.map((line, i) => {
|
||||
const from = gridToScreen(line.from.col, line.from.row);
|
||||
const to = gridToScreen(line.to.col, line.to.row);
|
||||
const length = Math.sqrt(
|
||||
Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2)
|
||||
);
|
||||
return (
|
||||
<line
|
||||
key={`flow-${i}`}
|
||||
x1={from.x}
|
||||
y1={from.y}
|
||||
x2={to.x}
|
||||
y2={to.y}
|
||||
stroke="url(#energy-pulse)"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={`${length * 0.2} ${length * 0.8}`}
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
from={length}
|
||||
to={0}
|
||||
dur={`${1.5 + (i % 3) * 0.5}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
|
||||
{/* === SOLAR PANELS === */}
|
||||
{INFRASTRUCTURE.solar.map((panel, i) => {
|
||||
const pos = gridToScreen(panel.col, panel.row);
|
||||
return (
|
||||
<g key={`solar-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Panel base */}
|
||||
<path
|
||||
d="M -20 0 L 0 -10 L 20 0 L 0 10 Z"
|
||||
fill="white"
|
||||
fillOpacity="0.1"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.4"
|
||||
/>
|
||||
{/* Panel surface (tilted) */}
|
||||
<path
|
||||
d="M -15 -5 L 0 -15 L 15 -5 L 0 5 Z"
|
||||
fill="white"
|
||||
fillOpacity="0.15"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.5"
|
||||
/>
|
||||
{/* Panel grid lines */}
|
||||
<line x1="-7" y1="-10" x2="7" y2="0" stroke="white" strokeWidth="0.5" strokeOpacity="0.3" />
|
||||
<line x1="0" y1="-15" x2="0" y2="5" stroke="white" strokeWidth="0.5" strokeOpacity="0.3" />
|
||||
{/* Connection glow */}
|
||||
<circle r="4" fill="#82ed20" fillOpacity="0.4" filter="url(#soft-glow)">
|
||||
<animate attributeName="fillOpacity" values="0.3;0.6;0.3" dur="2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === WIND TURBINES === */}
|
||||
{INFRASTRUCTURE.wind.map((turbine, i) => {
|
||||
const pos = gridToScreen(turbine.col, turbine.row);
|
||||
return (
|
||||
<g key={`wind-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Base */}
|
||||
<ellipse cx="0" cy="0" rx="10" ry="5" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="1" strokeOpacity="0.3" />
|
||||
{/* Tower */}
|
||||
<line x1="0" y1="0" x2="0" y2="-60" stroke="white" strokeWidth="2" strokeOpacity="0.5" />
|
||||
{/* Nacelle */}
|
||||
<ellipse cx="0" cy="-60" rx="6" ry="3" fill="white" fillOpacity="0.3" stroke="white" strokeWidth="1" />
|
||||
{/* Blades */}
|
||||
<g transform="translate(0, -60)">
|
||||
{[0, 120, 240].map((angle, j) => (
|
||||
<line
|
||||
key={`blade-${i}-${j}`}
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="-30"
|
||||
stroke="white"
|
||||
strokeWidth="1.5"
|
||||
strokeOpacity="0.6"
|
||||
transform={`rotate(${angle})`}
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from={`${angle} 0 0`}
|
||||
to={`${angle + 360} 0 0`}
|
||||
dur={`${3 + i}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
))}
|
||||
<circle r="3" fill="white" fillOpacity="0.4" />
|
||||
</g>
|
||||
{/* Connection glow */}
|
||||
<circle r="5" fill="#82ed20" fillOpacity="0.4" filter="url(#soft-glow)">
|
||||
<animate attributeName="fillOpacity" values="0.3;0.6;0.3" dur="2.5s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === SUBSTATIONS === */}
|
||||
{INFRASTRUCTURE.substations.map((sub, i) => {
|
||||
const pos = gridToScreen(sub.col, sub.row);
|
||||
const isCollection = sub.type === 'collection';
|
||||
return (
|
||||
<g key={`substation-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Base platform */}
|
||||
<path
|
||||
d="M -25 0 L 0 -12 L 25 0 L 0 12 Z"
|
||||
fill="white"
|
||||
fillOpacity="0.1"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.4"
|
||||
/>
|
||||
{/* Building */}
|
||||
<path
|
||||
d={isCollection
|
||||
? "M -18 0 L -18 -20 L 0 -32 L 18 -20 L 18 0"
|
||||
: "M -22 0 L -22 -25 L 0 -37 L 22 -25 L 22 0"
|
||||
}
|
||||
fill="white"
|
||||
fillOpacity="0.08"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.5"
|
||||
/>
|
||||
{/* Equipment */}
|
||||
<rect x="-10" y="-12" width="6" height="8" fill="white" fillOpacity="0.2" stroke="white" strokeWidth="0.5" />
|
||||
<rect x="4" y="-12" width="6" height="8" fill="white" fillOpacity="0.2" stroke="white" strokeWidth="0.5" />
|
||||
{/* Insulators */}
|
||||
<line x1="-7" y1="-12" x2="-7" y2="-22" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
|
||||
<line x1="7" y1="-12" x2="7" y2="-22" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
|
||||
<circle cx="-7" cy="-22" r="2" fill="white" fillOpacity="0.4" />
|
||||
<circle cx="7" cy="-22" r="2" fill="white" fillOpacity="0.4" />
|
||||
{/* Connection glow */}
|
||||
<circle r="8" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
|
||||
<animate attributeName="r" values="6;10;6" dur="3s" repeatCount="indefinite" />
|
||||
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur="3s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === TRANSMISSION TOWERS === */}
|
||||
{INFRASTRUCTURE.towers.map((tower, i) => {
|
||||
const pos = gridToScreen(tower.col, tower.row);
|
||||
return (
|
||||
<g key={`tower-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Base */}
|
||||
<ellipse cx="0" cy="0" rx="8" ry="4" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="1" strokeOpacity="0.3" />
|
||||
{/* Tower legs */}
|
||||
<path d="M -6 0 L -3 -45 M 6 0 L 3 -45" stroke="white" strokeWidth="1.5" strokeOpacity="0.5" />
|
||||
{/* Cross braces */}
|
||||
<path d="M -5 -10 L 5 -10 M -4 -20 L 4 -20 M -3 -30 L 3 -30 M -3 -45 L 3 -45" stroke="white" strokeWidth="1" strokeOpacity="0.3" />
|
||||
{/* Cross arms */}
|
||||
<line x1="-12" y1="-40" x2="12" y2="-40" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
|
||||
<line x1="-10" y1="-32" x2="10" y2="-32" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
|
||||
{/* Insulators */}
|
||||
<circle cx="-10" cy="-40" r="1.5" fill="white" fillOpacity="0.4" />
|
||||
<circle cx="10" cy="-40" r="1.5" fill="white" fillOpacity="0.4" />
|
||||
{/* Connection glow */}
|
||||
<circle r="5" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
|
||||
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur="2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === CITY BUILDINGS === */}
|
||||
{INFRASTRUCTURE.city.map((building, i) => {
|
||||
const pos = gridToScreen(building.col, building.row);
|
||||
const heights = { tall: 70, medium: 45, small: 30 };
|
||||
const height = heights[building.type as keyof typeof heights];
|
||||
return (
|
||||
<g key={`building-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Base */}
|
||||
<path
|
||||
d="M -12 0 L 0 -6 L 12 0 L 0 6 Z"
|
||||
fill="white"
|
||||
fillOpacity="0.1"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.3"
|
||||
/>
|
||||
{/* Building front */}
|
||||
<path
|
||||
d={`M -12 0 L -12 -${height} L 0 -${height + 6} L 0 -6 Z`}
|
||||
fill="white"
|
||||
fillOpacity="0.1"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.4"
|
||||
/>
|
||||
{/* Building side */}
|
||||
<path
|
||||
d={`M 0 -6 L 0 -${height + 6} L 12 -${height} L 12 0 Z`}
|
||||
fill="white"
|
||||
fillOpacity="0.05"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.3"
|
||||
/>
|
||||
{/* Windows */}
|
||||
{[...Array(Math.floor(height / 15))].map((_, w) => (
|
||||
<g key={`window-${i}-${w}`}>
|
||||
<rect x="-9" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
|
||||
<rect x="-4" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
|
||||
<rect x="3" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
|
||||
<rect x="7" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
|
||||
</g>
|
||||
))}
|
||||
{/* Connection glow */}
|
||||
<circle r="4" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
|
||||
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur={`${2 + i * 0.3}s`} repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === CITY 2 BUILDINGS (bottom-left) === */}
|
||||
{INFRASTRUCTURE.city2.map((building, i) => {
|
||||
const pos = gridToScreen(building.col, building.row);
|
||||
const heights = { tall: 70, medium: 45, small: 30 };
|
||||
const height = heights[building.type as keyof typeof heights];
|
||||
return (
|
||||
<g key={`building2-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Base */}
|
||||
<path
|
||||
d="M -12 0 L 0 -6 L 12 0 L 0 6 Z"
|
||||
fill="white"
|
||||
fillOpacity="0.1"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.3"
|
||||
/>
|
||||
{/* Building front */}
|
||||
<path
|
||||
d={`M -12 0 L -12 -${height} L 0 -${height + 6} L 0 -6 Z`}
|
||||
fill="white"
|
||||
fillOpacity="0.1"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.4"
|
||||
/>
|
||||
{/* Building side */}
|
||||
<path
|
||||
d={`M 0 -6 L 0 -${height + 6} L 12 -${height} L 12 0 Z`}
|
||||
fill="white"
|
||||
fillOpacity="0.05"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.3"
|
||||
/>
|
||||
{/* Windows */}
|
||||
{[...Array(Math.floor(height / 15))].map((_, w) => (
|
||||
<g key={`window2-${i}-${w}`}>
|
||||
<rect x="-9" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
|
||||
<rect x="-4" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
|
||||
<rect x="3" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
|
||||
<rect x="7" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
|
||||
</g>
|
||||
))}
|
||||
{/* Connection glow */}
|
||||
<circle r="4" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
|
||||
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur={`${2.5 + i * 0.3}s`} repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === TREES === */}
|
||||
{INFRASTRUCTURE.trees.map((tree, i) => {
|
||||
const pos = gridToScreen(tree.col, tree.row);
|
||||
return (
|
||||
<g key={`tree-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Trunk */}
|
||||
<line x1="0" y1="0" x2="0" y2="-15" stroke="white" strokeWidth="2" strokeOpacity="0.3" />
|
||||
{/* Foliage - layered circles for tree crown */}
|
||||
<ellipse cx="0" cy="-22" rx="10" ry="8" fill="white" fillOpacity="0.12" stroke="white" strokeWidth="0.5" strokeOpacity="0.2" />
|
||||
<ellipse cx="-5" cy="-26" rx="7" ry="6" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="0.5" strokeOpacity="0.15" />
|
||||
<ellipse cx="5" cy="-26" rx="7" ry="6" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="0.5" strokeOpacity="0.15" />
|
||||
<ellipse cx="0" cy="-30" rx="6" ry="5" fill="white" fillOpacity="0.08" stroke="white" strokeWidth="0.5" strokeOpacity="0.1" />
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === ABSTRACT WIND EFFECTS === */}
|
||||
{INFRASTRUCTURE.wind.map((turbine, i) => {
|
||||
const pos = gridToScreen(turbine.col, turbine.row);
|
||||
return (
|
||||
<g key={`wind-effect-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Wind swoosh lines - curved paths flowing toward turbine */}
|
||||
{[0, 1, 2].map((j) => (
|
||||
<path
|
||||
key={`wind-line-${i}-${j}`}
|
||||
d={`M ${-80 - j * 15} ${-70 - j * 8} Q ${-50 - j * 10} ${-65 - j * 5} ${-20} ${-60}`}
|
||||
stroke="url(#wind-flow)"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
opacity="0"
|
||||
>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0;0.6;0"
|
||||
dur={`${2 + j * 0.5}s`}
|
||||
begin={`${j * 0.7 + i * 0.3}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
from="100"
|
||||
to="0"
|
||||
dur={`${2 + j * 0.5}s`}
|
||||
begin={`${j * 0.7 + i * 0.3}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</path>
|
||||
))}
|
||||
{/* Additional wind particles */}
|
||||
{[0, 1, 2, 3].map((j) => (
|
||||
<circle
|
||||
key={`wind-particle-${i}-${j}`}
|
||||
r="1.5"
|
||||
fill="white"
|
||||
opacity="0"
|
||||
>
|
||||
<animate
|
||||
attributeName="cx"
|
||||
values={`${-70 - j * 10};${-10}`}
|
||||
dur={`${1.5 + j * 0.3}s`}
|
||||
begin={`${j * 0.4 + i * 0.2}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="cy"
|
||||
values={`${-75 - j * 5};${-60}`}
|
||||
dur={`${1.5 + j * 0.3}s`}
|
||||
begin={`${j * 0.4 + i * 0.2}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0;0.5;0"
|
||||
dur={`${1.5 + j * 0.3}s`}
|
||||
begin={`${j * 0.4 + i * 0.2}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === SCHEMATIC SUN RAYS === */}
|
||||
{/* Simple downward rays above each solar panel */}
|
||||
{INFRASTRUCTURE.solar.map((panel, i) => {
|
||||
const pos = gridToScreen(panel.col, panel.row);
|
||||
return (
|
||||
<g key={`sun-ray-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
|
||||
{/* Three short schematic rays coming down to panel */}
|
||||
{[-8, 0, 8].map((offset, j) => (
|
||||
<line
|
||||
key={`ray-${i}-${j}`}
|
||||
x1={offset}
|
||||
y1={-45}
|
||||
x2={offset * 0.3}
|
||||
y2={-18}
|
||||
stroke="#FFD700"
|
||||
strokeWidth="1.5"
|
||||
strokeOpacity="0.4"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray="4 6"
|
||||
>
|
||||
<animate
|
||||
attributeName="strokeOpacity"
|
||||
values="0.2;0.5;0.2"
|
||||
dur={`${2 + j * 0.3}s`}
|
||||
begin={`${i * 0.2}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
from="10"
|
||||
to="0"
|
||||
dur="1.5s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* === ENERGY PARTICLES === */}
|
||||
{POWER_LINES.map((line, i) => {
|
||||
const from = gridToScreen(line.from.col, line.from.row);
|
||||
const to = gridToScreen(line.to.col, line.to.row);
|
||||
return (
|
||||
<circle
|
||||
key={`particle-${i}`}
|
||||
r="3"
|
||||
fill="#82ed20"
|
||||
filter="url(#soft-glow)"
|
||||
>
|
||||
<animate
|
||||
attributeName="cx"
|
||||
values={`${from.x};${to.x}`}
|
||||
dur={`${1 + (i % 4) * 0.3}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="cy"
|
||||
values={`${from.y};${to.y}`}
|
||||
dur={`${1 + (i % 4) * 0.3}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0;0.8;0"
|
||||
dur={`${1 + (i % 4) * 0.3}s`}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
);
|
||||
})}
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-primary/10 via-transparent to-primary/90" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -40,17 +40,18 @@ export default function MeetTheTeam() {
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-2">→</span>
|
||||
</Button>
|
||||
|
||||
<div className="flex -space-x-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="w-14 h-14 rounded-full border-4 border-primary-dark bg-neutral-medium overflow-hidden">
|
||||
<div className="w-full h-full bg-primary-light flex items-center justify-center text-primary font-bold text-xs">
|
||||
KLZ
|
||||
</div>
|
||||
<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">
|
||||
<Image src="/uploads/2024/12/DSC07768-Large.webp" alt="Michael Bodemer" fill className="object-cover" />
|
||||
</div>
|
||||
<div className="w-14 h-14 rounded-full border-4 border-primary-dark overflow-hidden relative">
|
||||
<Image src="/uploads/2024/12/DSC07963-Large.webp" alt="Klaus Mintel" fill className="object-cover" />
|
||||
</div>
|
||||
))}
|
||||
<div className="w-14 h-14 rounded-full border-4 border-primary-dark bg-accent flex items-center justify-center text-primary-dark font-bold text-sm">
|
||||
+12
|
||||
</div>
|
||||
<span className="text-white/60 font-bold text-sm uppercase tracking-widest">
|
||||
{t('andNetwork')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function ProductCategories() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Section className="bg-neutral-light py-0">
|
||||
<Section className="bg-neutral-light 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-[500px] lg:h-[650px] overflow-hidden border-r border-white/10 last:border-r-0">
|
||||
@@ -63,7 +63,7 @@ export default function ProductCategories() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center text-accent font-bold tracking-wider uppercase text-sm opacity-0 group-hover:opacity-100 transition-all duration-500 delay-100">
|
||||
Explore Category <span className="ml-2 transition-transform group-hover:translate-x-2">→</span>
|
||||
{t('exploreCategory')} <span className="ml-2 transition-transform group-hover:translate-x-2">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function WhatWeDo() {
|
||||
</p>
|
||||
<div className="mt-12 p-8 bg-primary-light rounded-2xl border border-primary/10">
|
||||
<p className="text-primary font-bold text-lg italic">
|
||||
"We don't just deliver cables; we deliver the infrastructure for a sustainable future."
|
||||
"{t('quote')}"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
100
components/products/ProductsIllustration.tsx
Normal file
100
components/products/ProductsIllustration.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default function ProductsIllustration() {
|
||||
return (
|
||||
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none bg-primary">
|
||||
<svg
|
||||
viewBox="0 0 1000 500"
|
||||
className="w-full h-full"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="scan-beam" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#82ed20" stopOpacity="0" />
|
||||
<stop offset="50%" stopColor="#82ed20" stopOpacity="0.5" />
|
||||
<stop offset="100%" stopColor="#82ed20" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
{/* 3D Cable Wireframe Construction */}
|
||||
<g transform="translate(100, 250) rotate(-10)">
|
||||
|
||||
{/* Inner Core Strands (Twisted) */}
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<path
|
||||
key={`strand-${i}`}
|
||||
d={`M 0 ${i*10 - 20} Q 400 ${i*10 - 60} 800 ${i*10 - 20}`}
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeOpacity="0.8"
|
||||
fill="none"
|
||||
>
|
||||
<animate attributeName="d"
|
||||
values={`M 0 ${i*10 - 20} Q 400 ${i*10 - 60} 800 ${i*10 - 20};
|
||||
M 0 ${i*10 - 20} Q 400 ${i*10 + 20} 800 ${i*10 - 20};
|
||||
M 0 ${i*10 - 20} Q 400 ${i*10 - 60} 800 ${i*10 - 20}`}
|
||||
dur="4s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</path>
|
||||
))}
|
||||
|
||||
{/* Insulation Layers (Ellipses along the path) */}
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<ellipse
|
||||
key={`ring-${i}`}
|
||||
cx={100 + i * 100}
|
||||
cy="0"
|
||||
rx="40"
|
||||
ry="80"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
strokeOpacity="0.2"
|
||||
fill="none"
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Outer Sheath (Top and Bottom Lines) */}
|
||||
<path d="M 0 -80 L 800 -80" stroke="white" strokeWidth="1" strokeOpacity="0.3" strokeDasharray="10 5" />
|
||||
<path d="M 0 80 L 800 80" stroke="white" strokeWidth="1" strokeOpacity="0.3" strokeDasharray="10 5" />
|
||||
|
||||
{/* Scanning Ring (Animated) */}
|
||||
<g>
|
||||
<ellipse cx="0" cy="0" rx="50" ry="90" stroke="#82ed20" strokeWidth="2" strokeOpacity="0.8" fill="none">
|
||||
<animate attributeName="cx" from="0" to="800" dur="3s" repeatCount="indefinite" />
|
||||
<animate attributeName="rx" values="50;45;50" dur="0.5s" repeatCount="indefinite" />
|
||||
</ellipse>
|
||||
{/* Scan Beam */}
|
||||
<rect x="-50" y="-100" width="100" height="200" fill="url(#scan-beam)" opacity="0.3">
|
||||
<animate attributeName="x" from="-50" to="750" dur="3s" repeatCount="indefinite" />
|
||||
</rect>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
{/* Floating Data/Tech Elements */}
|
||||
<g transform="translate(0, 0)">
|
||||
{[...Array(20)].map((_, i) => (
|
||||
<rect
|
||||
key={`bit-${i}`}
|
||||
x={Math.random() * 1000}
|
||||
y={Math.random() * 500}
|
||||
width={Math.random() * 20 + 5}
|
||||
height="2"
|
||||
fill="white"
|
||||
fillOpacity="0.3"
|
||||
>
|
||||
<animate attributeName="opacity" values="0;0.5;0" dur={`${Math.random() * 2 + 1}s`} repeatCount="indefinite" />
|
||||
<animate attributeName="width" values="5;30;5" dur={`${Math.random() * 2 + 1}s`} repeatCount="indefinite" />
|
||||
</rect>
|
||||
))}
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-primary/80 via-transparent to-primary/80" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user