feat: complete migration to static MDX, stabilize local infrastructure and fix i18n issues
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 4s
🚀 Build & Deploy / 🚀 Deploy (push) Has been cancelled
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
🚀 Build & Deploy / 🏗️ Build (push) Has been cancelled
🚀 Build & Deploy / 🔔 Notify (push) Has been cancelled
🚀 Build & Deploy / 🧪 QA (push) Has been cancelled

This commit is contained in:
2026-05-04 10:26:45 +02:00
parent 41ef9092d6
commit 5fb73d57dd
43 changed files with 2304 additions and 532 deletions

View File

@@ -11,10 +11,10 @@ import {
ShieldCheck,
Truck,
} from "lucide-react";
import { Reveal } from "./Reveal";
import { TechBackground } from "./TechBackground";
import { Counter } from "./Counter";
import { Button } from "./Button";
import { Reveal } from "@/components/Reveal";
import { TechBackground } from "@/components/TechBackground";
import { Counter } from "@/components/Counter";
import { Button } from "@/components/Button";
import { useTranslations } from "next-intl";
export default function About() {

View File

@@ -1,7 +1,7 @@
"use client";
import React, { useState } from "react";
import { m } from "framer-motion";
import { motion } from "framer-motion";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
@@ -62,7 +62,7 @@ export const Button = ({
);
const spotlight = (
<m.div
<motion.div
className="absolute inset-0 z-0 pointer-events-none transition-opacity duration-500"
style={{
opacity: isHovered ? 1 : 0,

View File

@@ -4,11 +4,11 @@ import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { Mail, MapPin, CheckCircle } from "lucide-react";
import { Button } from "./Button";
import { Counter } from "./Counter";
import { Reveal } from "./Reveal";
import { TechBackground } from "./TechBackground";
import { StatusModal } from "./StatusModal";
import { Button } from "@/components/Button";
import { Counter } from "@/components/Counter";
import { Reveal } from "@/components/Reveal";
import { TechBackground } from "@/components/TechBackground";
import { StatusModal } from "@/components/StatusModal";
import { useTranslations } from "next-intl";
export default function Contact() {

View File

@@ -1,10 +1,10 @@
"use client";
import Image from "next/image";
import { Button } from "./Button";
import { Counter } from "./Counter";
import { Reveal } from "./Reveal";
import { TechBackground } from "./TechBackground";
import { Button } from "@/components/Button";
import { Counter } from "@/components/Counter";
import { Reveal } from "@/components/Reveal";
import { TechBackground } from "@/components/TechBackground";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";

View File

@@ -1,13 +1,13 @@
"use client";
import { AnimatePresence, m } from "framer-motion";
import { AnimatePresence, motion } from "framer-motion";
import { ArrowUp, Home, Info, Menu, X } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import React, { useEffect, useState } from "react";
import { Button } from "./Button";
import { Reveal } from "./Reveal";
import { Button } from "@/components/Button";
import { Reveal } from "@/components/Reveal";
import { useTranslations } from "next-intl";
const Layout = ({ children }: { children: React.ReactNode }) => {
@@ -118,7 +118,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
{/* Mobile Menu Overlay */}
<AnimatePresence>
{isMobileMenuOpen && (
<m.div
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
@@ -143,7 +143,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
{t("nav.cta")}
</Button>
</nav>
</m.div>
</motion.div>
)}
</AnimatePresence>
@@ -166,12 +166,12 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
<div className="absolute inset-0 grid-pattern opacity-[0.08] pointer-events-none" />
{/* Animated Tech Lines */}
<m.div
<motion.div
animate={{ x: ["-100%", "100%"] }}
transition={{ duration: 15, repeat: Infinity, ease: "linear" }}
className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-accent/30 to-transparent"
/>
<m.div
<motion.div
animate={{ x: ["100%", "-100%"] }}
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
className="absolute bottom-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-accent/20 to-transparent"

View File

@@ -0,0 +1,39 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import { TechBackground } from "@/components/TechBackground";
interface LegalLayoutProps {
title: string;
heroAction?: React.ReactNode;
children: React.ReactNode;
}
export function LegalLayout({ title, heroAction, children }: LegalLayoutProps) {
return (
<div className="bg-slate-50 min-h-screen pt-40 pb-20 relative overflow-hidden">
<TechBackground />
<div className="container-custom relative z-10">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 50, damping: 20 }}
className="max-w-4xl mx-auto bg-white p-8 md:p-12 rounded-[2.5rem] shadow-sm border border-slate-100 relative overflow-hidden group"
>
<div className="tech-corner top-8 left-8 border-t-2 border-l-2 opacity-20" />
<div className="tech-corner bottom-8 right-8 border-b-2 border-r-2 opacity-20" />
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8 relative z-10">
<h1 className="text-4xl font-extrabold text-primary">{title}</h1>
{heroAction}
</div>
<div className="space-y-8 text-slate-600 leading-relaxed relative z-10 prose prose-slate max-w-none prose-h1:hidden prose-h2:text-xl prose-h2:font-bold prose-h2:text-primary prose-h2:mb-4">
{children}
</div>
</motion.div>
</div>
</div>
);
}

View File

@@ -1,7 +1,7 @@
"use client";
import React from "react";
import { m } from "framer-motion";
import { motion } from "framer-motion";
interface RevealProps {
children: React.ReactNode;
@@ -30,7 +30,7 @@ export const Reveal = ({
};
return (
<m.div
<motion.div
initial={{
opacity: 0,
...directions[direction],
@@ -68,7 +68,7 @@ export const Reveal = ({
className={`${fullWidth ? "w-full" : ""} ${className} motion-fix will-change-[transform,opacity]`}
>
{children}
</m.div>
</motion.div>
);
};
@@ -84,7 +84,7 @@ export const Stagger = ({
staggerDelay = 0.1,
}: StaggerProps) => {
return (
<m.div
<motion.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@@ -98,6 +98,6 @@ export const Stagger = ({
className={className}
>
{children}
</m.div>
</motion.div>
);
};

View File

@@ -1,9 +1,9 @@
"use client";
import React from "react";
import { m, AnimatePresence, LazyMotion, domAnimation } from "framer-motion";
import { motion, AnimatePresence } from "framer-motion";
import { CheckCircle, AlertCircle, X } from "lucide-react";
import { Button } from "./Button";
import { Button } from "@/components/Button";
interface StatusModalProps {
isOpen: boolean;
@@ -23,82 +23,80 @@ export const StatusModal = ({
buttonText,
}: StatusModalProps) => {
return (
<LazyMotion features={domAnimation}>
<AnimatePresence>
{isOpen && (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 md:p-6">
{/* Backdrop */}
<m.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
<AnimatePresence>
{isOpen && (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 md:p-6">
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="absolute inset-0 bg-slate-950/80 backdrop-blur-sm"
/>
{/* Modal Content */}
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
className="relative w-full max-w-lg bg-white rounded-[2.5rem] border border-slate-100 shadow-2xl overflow-hidden group"
>
{/* Tech Decoration */}
<div className="absolute top-0 left-0 w-full h-2 bg-slate-100 overflow-hidden">
<motion.div
initial={{ x: "-100%" }}
animate={{ x: "100%" }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
className={`absolute inset-0 w-1/2 ${type === "success" ? "bg-accent" : "bg-red-500"} opacity-30`}
/>
</div>
<button
onClick={onClose}
className="absolute inset-0 bg-slate-950/80 backdrop-blur-sm"
/>
{/* Modal Content */}
<m.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
className="relative w-full max-w-lg bg-white rounded-[2.5rem] border border-slate-100 shadow-2xl overflow-hidden group"
className="absolute top-6 right-6 p-2 text-slate-400 hover:text-primary transition-colors hover:bg-slate-50 rounded-xl"
>
{/* Tech Decoration */}
<div className="absolute top-0 left-0 w-full h-2 bg-slate-100 overflow-hidden">
<m.div
initial={{ x: "-100%" }}
animate={{ x: "100%" }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
className={`absolute inset-0 w-1/2 ${type === "success" ? "bg-accent" : "bg-red-500"} opacity-30`}
/>
</div>
<X size={20} />
</button>
<button
onClick={onClose}
className="absolute top-6 right-6 p-2 text-slate-400 hover:text-primary transition-colors hover:bg-slate-50 rounded-xl"
<div className="p-8 md:p-12 text-center">
<div
className={`w-20 h-20 rounded-full ${type === "success" ? "bg-accent/10 text-accent" : "bg-red-50 text-red-500"} flex items-center justify-center mx-auto mb-8 relative`}
>
<X size={20} />
</button>
<div className="p-8 md:p-12 text-center">
<div
className={`w-20 h-20 rounded-full ${type === "success" ? "bg-accent/10 text-accent" : "bg-red-50 text-red-500"} flex items-center justify-center mx-auto mb-8 relative`}
>
<div
className={`absolute inset-0 ${type === "success" ? "bg-accent/20" : "bg-red-500/20"} rounded-full animate-ping opacity-20`}
/>
{type === "success" ? (
<CheckCircle size={40} />
) : (
<AlertCircle size={40} />
)}
</div>
<h2 className="text-3xl font-extrabold text-primary mb-4 leading-tight">
{title}
</h2>
<p className="text-slate-600 text-lg mb-10 leading-relaxed">
{message}
</p>
<Button
onClick={onClose}
variant={type === "success" ? "accent" : "primary"}
className="w-full py-5 text-lg"
showArrow
>
{buttonText}
</Button>
className={`absolute inset-0 ${type === "success" ? "bg-accent/20" : "bg-red-500/20"} rounded-full animate-ping opacity-20`}
/>
{type === "success" ? (
<CheckCircle size={40} />
) : (
<AlertCircle size={40} />
)}
</div>
{/* Decorative Corners */}
<div className="tech-corner top-4 left-4 border-t-2 border-l-2 opacity-20" />
<div className="tech-corner bottom-4 right-4 border-b-2 border-r-2 opacity-20" />
</m.div>
</div>
)}
</AnimatePresence>
</LazyMotion>
<h2 className="text-3xl font-extrabold text-primary mb-4 leading-tight">
{title}
</h2>
<p className="text-slate-600 text-lg mb-10 leading-relaxed">
{message}
</p>
<Button
onClick={onClose}
variant={type === "success" ? "accent" : "primary"}
className="w-full py-5 text-lg"
showArrow
>
{buttonText}
</Button>
</div>
{/* Decorative Corners */}
<div className="tech-corner top-4 left-4 border-t-2 border-l-2 opacity-20" />
<div className="tech-corner bottom-4 right-4 border-b-2 border-r-2 opacity-20" />
</motion.div>
</div>
)}
</AnimatePresence>
);
};

View File

@@ -0,0 +1,52 @@
"use client";
import React from "react";
import Image from "next/image";
import { Reveal } from "@/components/Reveal";
import { TechBackground } from "@/components/TechBackground";
import { Counter } from "@/components/Counter";
interface AboutHeroProps {
tagline: string;
title: React.ReactNode;
subtitle: string;
}
export function AboutHero({ tagline, title, subtitle }: AboutHeroProps) {
return (
<section className="relative min-h-[60vh] flex items-center pt-44 pb-20 overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src="/media/drums/about-hero.jpg"
alt="About MB Grid Solutions"
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-gradient-to-r from-white via-white/95 to-white/40" />
<TechBackground />
</div>
<div className="container-custom relative z-10">
<div className="text-left relative">
<Counter value={1} className="section-number" />
<Reveal delay={0.1}>
<span className="text-accent font-bold uppercase tracking-widest text-sm mb-4 block">
{tagline}
</span>
</Reveal>
<Reveal delay={0.2}>
<h1 className="text-4xl sm:text-5xl md:text-6xl font-extrabold text-primary mb-6 md:mb-8 leading-tight">
{title}
</h1>
</Reveal>
<Reveal delay={0.3}>
<p className="text-slate-600 text-lg md:text-2xl leading-relaxed mb-8">
{subtitle}
</p>
</Reveal>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,63 @@
"use client";
import React from "react";
import { Linkedin } from "lucide-react";
import { Reveal } from "@/components/Reveal";
import { TechBackground } from "@/components/TechBackground";
interface Person {
name: string;
role: string;
linkedin: string;
}
interface AboutIntroProps {
paragraphs: string[];
team: Person[];
}
export function AboutIntro({ paragraphs, team }: AboutIntroProps) {
return (
<section className="bg-white relative overflow-hidden">
<TechBackground />
<div className="container-custom relative z-10">
<div className="section-number">02</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
<Reveal direction="right">
<div className="space-y-6 text-lg text-slate-600 leading-relaxed relative">
<div className="absolute -left-4 top-0 w-1 h-full bg-accent/10" />
{paragraphs.map((p, i) => (
<p key={i}>{p}</p>
))}
</div>
</Reveal>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{team.map((person, i) => (
<Reveal key={i} delay={i * 0.1}>
<div className="card-modern !p-6 hover:-translate-y-1 transition-[box-shadow,transform] duration-300 relative overflow-hidden tech-card-border">
<div className="flex justify-between items-start mb-4 relative z-10">
<h2 className="text-xl font-bold text-primary">
{person.name}
</h2>
<a
href={person.linkedin}
target="_blank"
rel="noopener noreferrer"
className="text-[#0077b5] hover:scale-110 transition-transform"
aria-label={`LinkedIn Profile of ${person.name}`}
>
<Linkedin size={20} />
</a>
</div>
<p className="text-accent text-sm font-bold uppercase tracking-wider relative z-10">
{person.role}
</p>
</div>
</Reveal>
))}
</div>
</div>
</div>
</section>
);
}

View File

@@ -1,16 +1,30 @@
"use client";
import React from "react";
import { m } from "framer-motion";
import { Reveal } from "../Reveal";
import { Counter } from "../Counter";
import { TechBackground } from "../TechBackground";
import { Button } from "../Button";
import { motion } from "framer-motion";
import { Reveal } from "@/components/Reveal";
import { Counter } from "@/components/Counter";
import { TechBackground } from "@/components/TechBackground";
import { Button } from "@/components/Button";
import { useTranslations } from "next-intl";
export const CTASection = () => {
interface CTASectionProps {
title?: string;
subtitle?: string;
buttonText?: string;
}
export const CTASection = ({
title,
subtitle,
buttonText,
}: CTASectionProps) => {
const t = useTranslations("Index");
const displayTitle = title || t("cta.title");
const displaySubtitle = subtitle || t("cta.subtitle");
const displayButtonText = buttonText || t("cta.button");
return (
<section className="bg-white relative overflow-hidden">
<TechBackground />
@@ -34,7 +48,7 @@ export const CTASection = () => {
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<m.circle
<motion.circle
animate={{ r: [400, 410, 400], opacity: [0.1, 0.2, 0.1] }}
transition={{
duration: 5,
@@ -47,7 +61,7 @@ export const CTASection = () => {
stroke="white"
strokeWidth="2"
/>
<m.circle
<motion.circle
animate={{ r: [300, 310, 300], opacity: [0.1, 0.2, 0.1] }}
transition={{
duration: 4,
@@ -61,7 +75,7 @@ export const CTASection = () => {
stroke="white"
strokeWidth="2"
/>
<m.circle
<motion.circle
animate={{ r: [200, 210, 200], opacity: [0.1, 0.2, 0.1] }}
transition={{
duration: 3,
@@ -80,10 +94,10 @@ export const CTASection = () => {
<div className="relative z-10">
<h2 className="text-3xl md:text-6xl font-bold text-white mb-6 md:mb-8 leading-tight">
{t("cta.title")}
{displayTitle}
</h2>
<p className="text-slate-300 text-lg md:text-xl mb-8 md:mb-12 leading-relaxed">
{t("cta.subtitle")}
{displaySubtitle}
</p>
<Button
href="/kontakt"
@@ -91,7 +105,7 @@ export const CTASection = () => {
showArrow
className="w-full sm:w-auto !px-10 !py-5 text-lg"
>
{t("cta.button")}
{displayButtonText}
</Button>
</div>
</div>

View File

@@ -3,14 +3,31 @@
import React from "react";
import Image from "next/image";
import { CheckCircle2 } from "lucide-react";
import { Reveal } from "../Reveal";
import { Counter } from "../Counter";
import { TechBackground } from "../TechBackground";
import { Reveal } from "@/components/Reveal";
import { Counter } from "@/components/Counter";
import { TechBackground } from "@/components/TechBackground";
import { useTranslations } from "next-intl";
export const ExpertiseSection = () => {
interface ExpertiseSectionProps {
tag?: string;
title?: string;
description?: string;
groups?: string[];
}
export const ExpertiseSection = ({
tag,
title,
description,
groups,
}: ExpertiseSectionProps) => {
const t = useTranslations("Index");
const displayTag = tag || t("expertise.tag");
const displayTitle = title || t("expertise.title");
const displayDescription = description || t("expertise.description");
const displayGroups = groups || t.raw("expertise.groups");
return (
<section className="bg-white relative overflow-hidden">
<TechBackground />
@@ -34,17 +51,17 @@ export const ExpertiseSection = () => {
<div>
<Reveal>
<span className="text-accent font-bold uppercase tracking-widest text-sm mb-4 block">
{t("expertise.tag")}
{displayTag}
</span>
<h2 className="text-3xl md:text-5xl font-bold text-primary mb-6 md:mb-8">
{t("expertise.title")}
{displayTitle}
</h2>
<p className="text-slate-600 text-base md:text-xl mb-8 md:mb-12">
{t("expertise.description")}
{displayDescription}
</p>
</Reveal>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{t.raw("expertise.groups").map((item: string, i: number) => (
{displayGroups.map((item: string, i: number) => (
<Reveal key={i} delay={i * 0.05}>
<div className="flex items-center gap-4 p-4 bg-slate-50 rounded-xl border border-slate-100 hover:border-accent/30 transition-colors group relative overflow-hidden">
<div className="absolute top-0 left-0 w-1 h-full bg-accent/0 group-hover:bg-accent/100 transition-all duration-300" />

View File

@@ -0,0 +1,79 @@
"use client";
import React from "react";
import Image from "next/image";
import { Button } from "@/components/Button";
import { Counter } from "@/components/Counter";
import { Reveal } from "@/components/Reveal";
import { TechBackground } from "@/components/TechBackground";
interface HeroProps {
tag: string;
title: React.ReactNode;
subtitle: string;
ctaPrimary: string;
ctaSecondary: string;
}
export function Hero({
tag,
title,
subtitle,
ctaPrimary,
ctaSecondary,
}: HeroProps) {
return (
<section className="relative min-h-[90vh] flex items-center pt-44 pb-20 overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src="/media/business/hero-bg.jpg"
alt="MB Grid Solutions Hero"
fill
className="object-cover"
priority
quality={75}
/>
<div className="absolute inset-0 bg-gradient-to-r from-slate-100/80 via-white/90 to-white/40 md:to-transparent" />
<TechBackground />
</div>
<div className="container-custom relative z-10">
<div className="text-left relative">
<Counter value={1} className="section-number" />
<Reveal delay={0.1}>
<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-accent/10 text-accent text-xs font-bold uppercase tracking-wider mb-6">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-accent opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-accent"></span>
</span>
{tag}
</span>
</Reveal>
<Reveal delay={0.2}>
<h1 className="text-4xl sm:text-5xl md:text-7xl font-extrabold text-primary mb-6 md:mb-8 leading-[1.1]">
{title}
</h1>
</Reveal>
<Reveal delay={0.3}>
<p className="text-slate-600 text-lg md:text-2xl leading-relaxed mb-8 md:mb-12 max-w-2xl">
{subtitle}
</p>
</Reveal>
<Reveal delay={0.4}>
<div className="flex flex-wrap gap-4">
<Button href="/kontakt" variant="accent" showArrow>
{ctaPrimary}
</Button>
<Button href="/ueber-uns" variant="ghost">
{ctaSecondary}
</Button>
</div>
</Reveal>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,80 @@
"use client";
import React from "react";
import {
Award,
Clock,
Lightbulb,
Truck,
MessageSquare,
ShieldCheck,
} from "lucide-react";
import { Reveal } from "@/components/Reveal";
import { TechBackground } from "@/components/TechBackground";
import { Counter } from "@/components/Counter";
const manifestIcons = [
Award,
Clock,
Lightbulb,
Truck,
MessageSquare,
ShieldCheck,
];
interface ManifestItem {
title: string;
desc: string;
}
interface ManifestSectionProps {
tagline: string;
title: string;
subtitle: string;
items: ManifestItem[];
}
export function ManifestSection({
tagline,
title,
subtitle,
items,
}: ManifestSectionProps) {
return (
<section className="bg-slate-950 text-accent relative overflow-hidden">
<TechBackground />
<div className="container-custom relative z-10">
<Counter value={3} className="section-number !text-white/5" />
<Reveal className="mb-20">
<span className="text-accent font-bold uppercase tracking-widest text-sm mb-4 block">
{tagline}
</span>
<h2 className="text-3xl md:text-5xl font-bold text-white mb-6">
{title}
</h2>
<p className="text-slate-400 text-base md:text-lg">{subtitle}</p>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{items.map((item, i) => {
const Icon = manifestIcons[i % manifestIcons.length];
return (
<Reveal key={i} delay={i * 0.1}>
<div className="bg-white/5 p-10 rounded-3xl border border-white/10 group hover:-translate-y-1 transition-[box-shadow,transform] duration-300 h-full motion-fix relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-1 bg-accent/0 group-hover:bg-accent/50 transition-all duration-500" />
<div className="text-accent mb-6">
<Icon size={32} />
</div>
<h3 className="text-xl font-bold text-white mb-4">
{i + 1}. {item.title}
</h3>
<p className="text-slate-400 leading-relaxed">{item.desc}</p>
</div>
</Reveal>
);
})}
</div>
</div>
</section>
);
}

View File

@@ -3,14 +3,59 @@
import React from "react";
import Link from "next/link";
import { ChevronRight, Zap, Shield, BarChart3 } from "lucide-react";
import { Reveal } from "../Reveal";
import { Counter } from "../Counter";
import { TechBackground } from "../TechBackground";
import { Reveal } from "@/components/Reveal";
import { Counter } from "@/components/Counter";
import { TechBackground } from "@/components/TechBackground";
import { useTranslations } from "next-intl";
export const PortfolioSection = () => {
interface PortfolioItem {
icon: React.ReactNode;
title: string;
desc: string;
}
interface PortfolioSectionProps {
tag?: string;
title?: string;
description?: string;
linkText?: string;
items?: PortfolioItem[];
}
export const PortfolioSection = ({
tag,
title,
description,
linkText,
items,
}: PortfolioSectionProps) => {
const t = useTranslations("Index");
const displayTag = tag || t("portfolio.tag");
const displayTitle = title || t("portfolio.title");
const displayDescription = description || t("portfolio.description");
const displayLinkText = linkText || t("portfolio.link");
const defaultItems = [
{
icon: <Zap size={32} />,
title: t("portfolio.items.beratung.title"),
desc: t("portfolio.items.beratung.desc"),
},
{
icon: <Shield size={32} />,
title: t("portfolio.items.begleitung.title"),
desc: t("portfolio.items.begleitung.desc"),
},
{
icon: <BarChart3 size={32} />,
title: t("portfolio.items.beschaffung.title"),
desc: t("portfolio.items.beschaffung.desc"),
},
];
const displayItems = items || defaultItems;
return (
<section className="bg-slate-950 text-accent relative overflow-hidden">
<TechBackground />
@@ -19,20 +64,20 @@ export const PortfolioSection = () => {
<Reveal className="flex flex-col md:flex-row md:items-end justify-between gap-8 mb-16">
<div>
<span className="text-accent font-bold uppercase tracking-widest text-sm mb-4 block">
{t("portfolio.tag")}
{displayTag}
</span>
<h2 className="text-3xl md:text-5xl font-bold text-white mb-6">
{t("portfolio.title")}
{displayTitle}
</h2>
<p className="text-slate-400 text-base md:text-xl">
{t("portfolio.description")}
{displayDescription}
</p>
</div>
<Link
href="/ueber-uns"
className="text-accent font-bold flex items-center gap-2 hover:text-white transition-colors group"
>
{t("portfolio.link")}{" "}
{displayLinkText}{" "}
<ChevronRight
className="transition-transform group-hover:translate-x-1"
size={20}
@@ -41,23 +86,7 @@ export const PortfolioSection = () => {
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{[
{
icon: <Zap size={32} />,
title: t("portfolio.items.beratung.title"),
desc: t("portfolio.items.beratung.desc"),
},
{
icon: <Shield size={32} />,
title: t("portfolio.items.begleitung.title"),
desc: t("portfolio.items.begleitung.desc"),
},
{
icon: <BarChart3 size={32} />,
title: t("portfolio.items.beschaffung.title"),
desc: t("portfolio.items.beschaffung.desc"),
},
].map((item, i) => (
{displayItems.map((item, i) => (
<Reveal key={i} delay={i * 0.1}>
<div className="bg-white/5 p-8 rounded-2xl border border-white/10 group hover:-translate-y-2 transition-[box-shadow,transform] duration-300 h-full relative overflow-hidden">
<div className="absolute top-0 right-0 w-16 h-16 bg-accent/5 -mr-8 -mt-8 rounded-full group-hover:bg-accent/10 transition-colors" />

View File

@@ -2,14 +2,53 @@
import React from "react";
import Image from "next/image";
import { Reveal } from "../Reveal";
import { Counter } from "../Counter";
import { TechBackground } from "../TechBackground";
import { Reveal } from "@/components/Reveal";
import { Counter } from "@/components/Counter";
import { TechBackground } from "@/components/TechBackground";
import { useTranslations } from "next-intl";
export const TechnicalSpecsSection = () => {
interface SpecItem {
label: string;
value: string;
desc: string;
}
interface TechnicalSpecsSectionProps {
tag?: string;
title?: string;
items?: SpecItem[];
}
export const TechnicalSpecsSection = ({
tag,
title,
items,
}: TechnicalSpecsSectionProps) => {
const t = useTranslations("Index");
const displayTag = tag || t("specs.tag");
const displayTitle = title || t("specs.title");
const defaultItems = [
{
label: t("specs.items.kabel.label"),
value: t("specs.items.kabel.value"),
desc: t("specs.items.kabel.desc"),
},
{
label: t("specs.items.spannung.label"),
value: t("specs.items.spannung.value"),
desc: t("specs.items.spannung.desc"),
},
{
label: t("specs.items.technologie.label"),
value: t("specs.items.technologie.value"),
desc: t("specs.items.technologie.desc"),
},
];
const displayItems = items || defaultItems;
return (
<section className="relative py-24 md:py-32 text-white overflow-hidden bg-slate-900">
<div className="absolute inset-0 opacity-20">
@@ -31,31 +70,15 @@ export const TechnicalSpecsSection = () => {
<Reveal className="mb-20">
<span className="text-accent font-bold uppercase tracking-widest text-sm mb-4 block">
{t("specs.tag")}
{displayTag}
</span>
<h2 className="text-3xl md:text-5xl font-bold text-white mb-6">
{t("specs.title")}
{displayTitle}
</h2>
</Reveal>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8">
{[
{
label: t("specs.items.kabel.label"),
value: t("specs.items.kabel.value"),
desc: t("specs.items.kabel.desc"),
},
{
label: t("specs.items.spannung.label"),
value: t("specs.items.spannung.value"),
desc: t("specs.items.spannung.desc"),
},
{
label: t("specs.items.technologie.label"),
value: t("specs.items.technologie.value"),
desc: t("specs.items.technologie.desc"),
},
].map((item, i) => (
{displayItems.map((item, i) => (
<Reveal key={i} delay={i * 0.1}>
<div className="p-10 rounded-3xl bg-white/5 border border-white/10 backdrop-blur-sm hover:bg-white/10 transition-colors h-full relative group overflow-hidden">
<div className="absolute top-0 left-0 w-full h-1 bg-accent/0 group-hover:bg-accent/50 transition-all duration-500" />