website refactoring #7

Merged
boom merged 25 commits from dev into production 2025-06-28 14:05:50 +02:00
78 changed files with 3858 additions and 2722 deletions
Showing only changes of commit 366ce30e79 - Show all commits

View File

@@ -0,0 +1,45 @@
'use client';
import React, {useEffect} from "react";
import HomeServices from "@/app/(root)/sections/HomeServices";
import {motion} from "framer-motion";
import Hero from "@/app/(root)/sections/Hero";
import About from "@/app/(root)/sections/About";
import ProcessSection from "@/app/(root)/sections/ProcessSection";
import WhyUs from "@/app/(root)/sections/WhyUs";
import Faq from "@/app/(root)/sections/Faq";
import ReferralSection from "@/app/(root)/sections/ReferralSection";
const Home = () => {
useEffect(() => {
const scrollToId = localStorage.getItem('scrollToId')
if (scrollToId) {
localStorage.removeItem('scrollToId')
const el = document.getElementById(scrollToId)
if (el) {
setTimeout(() => {
el.scrollIntoView({behavior: 'smooth', block: 'start'})
}, 200)
}
}
}, [])
return (
<motion.div
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{duration: 0.7, ease: "easeOut"}}
className="overflow-hidden"
>
<Hero/>
<HomeServices/>
<About/>
<ProcessSection/>
<WhyUs/>
<ReferralSection/>
<Faq/>
</motion.div>
);
};
export default Home;

View File

@@ -1,37 +0,0 @@
import type {Metadata} from "next";
import "../globals.css";
import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react";
import {cookies} from "next/headers";
import {themeColors} from "@/components/Helper/ThemeColors";
export const metadata: Metadata = {
title: "Rhein Software",
description: "Rhein Software Development",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
const bgColor = themeColors[theme].primaryBg;
return (
<html lang="de" data-theme={theme}>
<head/>
<body className="antialiased" style={{backgroundColor: bgColor}}>
<ThemeProvider>
<Nav/>
{children}
<Footer/>
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,12 +1,10 @@
import React from 'react';
import Home from "@/components/Home/Home";
import Home from "@/app/(root)/Home";
const HomePage = () => {
return (
<div>
<Home />
</div>
);
return (
<Home/>
);
};
export default HomePage;

View File

@@ -1,23 +1,21 @@
'use client';
// import Link from 'next/link';
// import {FiArrowRight} from 'react-icons/fi';
import {motion} from 'framer-motion';
import {useThemeColors} from '@/utils/useThemeColors';
const About = () => {
const colors = useThemeColors();
return (
<section
className="relative w-full py-24 transition-colors duration-700 ease-in-out"
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
>
id="about"
className="relative w-full py-24 bg-background text-foreground transition-colors duration-700 ease-in-out">
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto">
<div className="flex flex-col">
{/* Title */}
<motion.h2
className="text-3xl md:text-4xl font-bold mb-1 text-left transition-colors duration-700 ease-in-out"
className="text-3xl md:text-4xl font-bold mb-1 text-left"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4}}
>
Über uns
</motion.h2>
@@ -33,33 +31,43 @@ const About = () => {
{/* Text */}
<div className="p-0 max-w-4xl">
<motion.p
className="text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}
className="text-base md:text-lg leading-relaxed text-muted-foreground"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir sind Rhein-Software ein Team, das sich auf individuelle Softwarelösungen und digitale
Services spezialisiert hat. Unsere Anwendungen sind technisch solide, skalierbar und
durchdacht gebaut für langfristigen Erfolg.
Wir sind Rhein-Software ein Team, das sich auf individuelle Softwarelösungen spezialisiert
hat. Unsere Anwendungen sind technisch solide, skalierbar und durchdacht gebaut für
langfristigen Erfolg.
</motion.p>
<motion.p
className="mt-6 text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}
className="mt-6 text-base md:text-lg leading-relaxed text-muted-foreground"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.3}}
>
Von der ersten Idee bis zum Go-Live begleiten wir Unternehmen und Startups mit einem
flexiblen Netzwerk, klarer Kommunikation und einem hohen Anspruch an Qualität.
Unsere Lösungen sind intuitiv, effizient und genau auf deine Anforderungen zugeschnitten.
flexiblen Netzwerk, klarer Kommunikation und einem hohen Anspruch an Qualität. Gemeinsam
realisieren wir digitale Produkte, die wirklich passen.
</motion.p>
<motion.p
className="mt-6 text-base md:text-lg leading-relaxed text-muted-foreground"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.4}}
>
Egal ob App-Entwicklung, interne Tools, Web-Plattformen oder komplexe Schnittstellen wir
entwickeln Softwarelösungen, die intuitiv, effizient und exakt auf deine Anforderungen
zugeschnitten sind.
</motion.p>
</div>
{/* CTA Button */}
{/* CTA Placeholder */}
<motion.div
className="mt-10 flex justify-end"
initial={{opacity: 0, y: 10}}
@@ -67,13 +75,7 @@ const About = () => {
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.5}}
>
{/*<Link href="/about">*/}
{/* <button*/}
{/* className="flex items-center gap-2 bg-blue-700 hover:bg-blue-900 text-white font-semibold px-5 py-2 rounded-full shadow-lg transition-all"*/}
{/* >*/}
{/* Mehr über uns <FiArrowRight size={18}/>*/}
{/* </button>*/}
{/*</Link>*/}
{/* CTA button can go here */}
</motion.div>
</div>
</div>

View File

@@ -0,0 +1,109 @@
'use client'
import {Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"
import {motion} from "framer-motion"
const faqItems = [
{
id: "dauer",
question: "Wie lange dauert es, bis meine Website oder App online ist?",
answers: [
"Das hängt vom Umfang des Projekts ab einfache Websites oder MVPs sind meist innerhalb von 46 Wochen realisierbar.",
"Komplexere Anwendungen oder individuelle Features benötigen entsprechend mehr Zeit. Wir geben dir zu Beginn eine realistische Einschätzung.",
],
},
{
id: "inhalte",
question: "Muss ich Texte und Bilder selbst liefern?",
answers: [
"Wenn du Inhalte hast, bauen wir diese gerne ein. Falls nicht, unterstützen wir dich mit Textvorschlägen, Icons oder lizenzfreien Bildern.",
"Bei Apps helfen wir dir auch bei der Strukturierung und Formulierung von App-Inhalten, z.B. Onboarding-Texte oder UI-Texte.",
],
},
{
id: "technik",
question: "Ich habe keine Ahnung von Technik funktioniert das trotzdem?",
answers: [
"Auf jeden Fall. Wir begleiten dich Schritt für Schritt und erklären alles verständlich ganz ohne Fachkenntnisse..",
"Du bekommst eine Lösung, die für dich funktioniert egal ob Website, App oder Backend.",
],
},
{
id: "änderungen",
question: "Was ist, wenn ich im Nachhinein etwas ändern möchte?",
answers: [
"Kein Problem. Du kannst jederzeit neue Inhalte, Features oder Anpassungen beauftragen.",
"Auf Wunsch übernehmen wir auch die laufende Wartung oder stellen dir ein CMS bzw. Admin-Interface bereit.",
],
},
{
id: "seo",
question: "Wird meine Website oder App auch für Suchmaschinen optimiert?",
answers: [
"Ja. Jede Website wird suchmaschinenfreundlich aufgebaut inkl. technischer SEO-Basics wie saubere Struktur, schnelle Ladezeit und mobile Optimierung.",
"Bei Apps unterstützen wir dich z.B. auch mit App Store Optimierung (ASO), damit du besser gefunden wirst.",
],
},
]
export default function Faq() {
return (
<section id="faq" className="py-24 px-4 bg-background text-foreground">
<div className="max-w-3xl mx-auto">
<motion.h2
className="text-3xl md:text-4xl font-bold mb-2 text-center"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4}}
>
Fragen? Antworten.
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-6 bg-amber-500 mx-auto"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<motion.p
className="text-sm md:text-base mb-10 text-muted-foreground text-center"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.2}}
>
Hier beantworten wir häufige Fragen rund um Web- und App-Projekte klar gegliedert nach Themen.
Wenn du darüber hinaus etwas wissen möchtest, melde dich gerne persönlich bei uns.
</motion.p>
<motion.div
className="text-sm md:text-base mb-10 text-muted-foreground text-center"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.4}}
>
<Accordion
type="single"
collapsible
className="w-full"
>
{faqItems.map((item, index) => (
<AccordionItem key={index} value={`faq-${index}`}>
<AccordionTrigger>{item.question}</AccordionTrigger>
<AccordionContent className="flex flex-col gap-4 text-left">
{item.answers.map((text, idx) => (
<p key={idx}>{text}</p>
))}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</motion.div>
</div>
</section>
)
}

View File

@@ -0,0 +1,76 @@
'use client';
import {motion} from 'framer-motion';
import Image from 'next/image';
import {Typewriter} from 'react-simple-typewriter';
import PulsatingButton from "@/components/PulsatingButton";
const Hero = () => {
return (
<section id="start" className="relative w-full h-screen overflow-hidden">
{/* Background */}
<div className="absolute inset-0 z-0">
<Image
src="/images/home_hero.jpg"
alt="Rhein river aerial view"
fill
className="object-cover scale-105 blur-sm"
priority
/>
<div className="absolute inset-0 bg-black/60"/>
</div>
{/* Content */}
<div
className="relative z-10 flex flex-col justify-center items-start h-full w-[90%] sm:w-[80%] max-w-6xl mx-auto text-white">
<motion.h1
className="text-3xl sm:text-5xl font-bold mb-6 leading-tight"
initial={{opacity: 0, y: 40}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6}}
>
Digitale Lösungen, <br/> die wirklich passen.
</motion.h1>
<motion.p
className="text-lg sm:text-xl text-gray-300 mb-6 max-w-2xl"
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6, delay: 0.2}}
>
Wir entwickeln individuelle Softwarelösungen für Unternehmen und Startups.
</motion.p>
<motion.div
className="text-xl font-semibold text-white"
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{delay: 0.6}}
>
<Typewriter
words={['Webdesign', 'App-Entwicklung', 'Interne Tools']}
loop={true}
cursor
cursorStyle="_"
typeSpeed={60}
deleteSpeed={40}
delaySpeed={2000}
/>
</motion.div>
<div className="mt-10 relative flex items-center justify-center">
<PulsatingButton
label="Jetzt Kontakt aufnehmen"
href="/contact"
color="#2563eb" // Tailwind blue-600
width={256}
pulse
/>
</div>
</div>
</section>
);
};
export default Hero;

View File

@@ -0,0 +1,115 @@
'use client';
import {motion} from 'framer-motion';
import {ChevronRight} from 'lucide-react';
import Link from 'next/link';
const services = [
{
title: 'Webdesign',
description: 'Moderne Websites, die Vertrauen schaffen und verkaufen.',
bullets: [
'Maßgeschneidertes Design',
'Klare Struktur & überzeugende Inhalte',
'Nutzerführung mit System & Strategie',
'Für alle Geräte optimiert',
],
},
{
title: 'App-Entwicklung',
description: 'Skalierbare Apps für Web und Mobile von der Idee bis zum Launch.',
bullets: [
'Plattformübergreifend mit modernen Technologien',
'Backend & API-Entwicklung inklusive',
'Individuelle Funktionen & Logik',
'Stabil, performant & wartbar',
],
},
{
title: 'Interne Tools',
description: 'Digitale Werkzeuge, die Prozesse vereinfachen und Zeit sparen.',
bullets: [
'Prozessdigitalisierung & Automatisierung',
'Zugeschnitten auf eure Workflows',
'Skalierbar & zukunftssicher',
'Intuitiv & effizient bedienbar',
],
},
];
const HomeServices = () => {
return (
<section id="services"
className="w-full py-24 bg-background text-foreground">
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto">
<motion.h2
className="text-3xl md:text-4xl font-bold mb-1 text-left"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4}}
>
Leistungen
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-10 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{services.map((service, index) => (
<motion.div
key={service.title}
className="flex flex-col justify-between h-full p-6 rounded-3xl border bg-muted text-foreground"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: index * 0.1}}
whileHover={{
scale: 1.03,
boxShadow: '0px 12px 30px rgba(0, 0, 0, 0.08)',
}}
>
<div>
<h3 className="text-xl font-semibold mb-2">{service.title}</h3>
<p className="text-muted-foreground mb-4">{service.description}</p>
<ul className="space-y-3">
{service.bullets.map((point, i) => (
<li key={i} className="flex items-start gap-2">
<ChevronRight className="w-4 h-4 text-primary mt-1"/>
<span className="text-sm text-foreground">{point}</span>
</li>
))}
</ul>
</div>
</motion.div>
))}
</div>
<motion.div
className="mt-12 text-center"
initial={{opacity: 0}}
whileInView={{opacity: 1}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.3}}
>
<p className="text-muted-foreground mb-4 text-base md:text-lg">
Du möchtest mehr über unsere Leistungen erfahren oder hast ein konkretes Projekt im Kopf?
</p>
<Link
href="/contact"
className="text-sm font-semibold text-primary hover:underline"
>
Jetzt Kontakt aufnehmen
</Link>
</motion.div>
</div>
</section>
);
};
export default HomeServices;

View File

@@ -0,0 +1,88 @@
'use client';
import React from 'react';
import {VerticalTimeline, VerticalTimelineElement} from 'react-vertical-timeline-component';
import {FaRocket, FaLightbulb, FaCode, FaPaperPlane} from 'react-icons/fa';
import 'react-vertical-timeline-component/style.min.css';
import {motion} from 'framer-motion';
const steps = [
{
title: 'Kick-Off & Strategie',
description:
'In einem gemeinsamen Auftakt klären wir deine Ziele, Zielgruppen und Herausforderungen. Daraus entsteht ein strukturierter Plan als Basis für alles Weitere.',
icon: <FaRocket/>,
},
{
title: 'Konzept & Inhalte',
description:
'Wir erarbeiten eine klare Struktur und passende Inhalte abgestimmt auf deine Botschaft und deine Nutzer. So entsteht ein roter Faden für Design und Umsetzung.',
icon: <FaLightbulb/>,
},
{
title: 'Design & Entwicklung',
description:
'Wir gestalten ein modernes Design und setzen es technisch um. Durch regelmäßige Feedback-Schleifen bist du jederzeit im Prozess eingebunden.',
icon: <FaCode/>,
},
{
title: 'Go-Live',
description:
'Nach erfolgreichen Tests geht dein Projekt live. Auch danach begleiten wir dich weiter für einen reibungslosen Betrieb und mögliche Weiterentwicklungen.',
icon: <FaPaperPlane/>,
},
];
const ProcessSection = () => {
return (
<section id="process" className="w-full py-24 bg-background text-foreground">
<div className="max-w-6xl px-6 md:px-10 mx-auto">
<motion.h2
className="text-3xl md:text-4xl font-bold mb-1"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4}}
>
Unser Prozess
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-12 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<VerticalTimeline
lineColor="#eab308"
animate={true}
>
{steps.map((step, idx) => (
<VerticalTimelineElement
key={idx}
contentStyle={{
background: 'hsl(var(--muted))',
color: 'hsl(var(--foreground))',
border: '1px solid hsl(var(--border))',
}}
contentArrowStyle={{borderRight: '7px solid hsl(var(--muted))'}}
iconStyle={{
background: '#eab308',
color: '#fff',
}}
icon={step.icon}
>
<h3 className="text-xl font-semibold">{step.title}</h3>
<p className="text-muted-foreground mt-2">{step.description}</p>
</VerticalTimelineElement>
))}
</VerticalTimeline>
</div>
</section>
);
};
export default ProcessSection;

View File

@@ -0,0 +1,60 @@
'use client'
import {motion} from 'framer-motion'
import Link from 'next/link'
export default function ReferralSection() {
return (
<section id="referral" className="py-24 px-4 bg-background/80 text-foreground">
<div className="max-w-3xl mx-auto text-center">
<motion.h2
className="text-3xl md:text-4xl font-bold mb-2"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4}}
>
Weiterempfehlen lohnt sich
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-6 bg-amber-500 mx-auto"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<motion.div
className="text-sm md:text-base mb-8 text-muted-foreground space-y-4"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.2}}
>
<p>
Du empfiehlst uns weiter und dein Kontakt wird Kunde?
Als Dank erhältst du <strong>10 % Rabatt</strong> auf dein nächstes Projekt bei uns.
</p>
<p>
Einfach, fair und lohnend ideal für alle, die mit unserer Arbeit zufrieden sind.
</p>
</motion.div>
<motion.div
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.3}}
>
<Link
href="/contact"
className="inline-block bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-full font-semibold transition"
>
Jetzt empfehlen
</Link>
</motion.div>
</div>
</section>
)
}

View File

@@ -0,0 +1,87 @@
'use client';
import Image from 'next/image';
import {motion} from 'framer-motion';
import {techStack} from "@/constant/TechStack";
const TechStack = () => {
return (
<section className="w-full py-20 bg-background text-foreground transition-colors duration-700 ease-in-out">
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto">
<motion.h2
className="text-3xl md:text-4xl font-bold mb-1 text-left"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4}}
>
Technologien
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-6 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<motion.p
className="text-sm md:text-base mb-10 text-muted-foreground"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.3, delay: 0.2}}
>
Mit diesen Technologien realisieren wir moderne, leistungsstarke Softwarelösungen.
</motion.p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
{techStack.row1.map((group, index) => (
<TechCard key={group.category} group={group} delay={index * 0.2}/>
))}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{techStack.row2.map((group, index) => (
<TechCard key={group.category} group={group} delay={index * 0.2 + 0.4}/>
))}
</div>
</div>
</section>
);
};
const TechCard = ({
group,
delay,
}: {
group: { category: string; items: { id: string; label: string }[] };
delay: number;
}) => (
<motion.div
className="p-4 rounded-lg border bg-muted shadow-md text-foreground"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
whileHover={{scale: 1.03, boxShadow: '0 10px 20px rgba(0,0,0,0.1)'}}
viewport={{once: true}}
transition={{duration: 0.4, delay}}
>
<h3 className="text-base font-semibold mb-4">{group.category}</h3>
<div className="grid grid-cols-3 gap-3">
{group.items.map(({id, label}) => (
<div key={id} className="flex flex-col items-center text-center">
<Image
src={`/images/svg/${id}.svg`}
alt={label}
width={32}
height={32}
className="object-contain"
/>
<span className="text-[10px] mt-1 text-muted-foreground">{label}</span>
</div>
))}
</div>
</motion.div>
);
export default TechStack;

View File

@@ -0,0 +1,69 @@
'use client';
import {CheckCircle} from 'lucide-react';
import {motion} from 'framer-motion';
import Link from 'next/link';
import Image from 'next/image';
const points = [
'Fertigstellung in 48 Wochen',
'Fester Ansprechpartner von Anfang bis Ende',
'Struktur & Klarheit',
'Starkes Alleinstellungsmerkmal',
'Zuverlässiges Team mit Weitblick',
];
export default function WhyUs() {
return (
<section id="whyus" className="py-24 px-4 bg-background text-foreground">
<div className="max-w-xl mx-auto">
<motion.h2
className="text-3xl md:text-4xl font-bold mb-1 text-center"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4}}
>
Warum wir?
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-10 bg-amber-500 mx-auto"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<motion.div
className="rounded-2xl p-8 md:p-10 bg-gradient-to-br from-[#1e3a8a] to-[#2563eb] text-white shadow-xl relative overflow-hidden"
initial={{opacity: 0, y: 40}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5}}
>
{/* Logo */}
<div className="mb-6">
<Image src="/logo.svg" alt="Rhein Software Logo" width={120} height={32}/>
</div>
<ul className="space-y-4 mb-8">
{points.map((point, index) => (
<li key={index} className="flex items-start gap-2">
<CheckCircle className="text-white w-5 h-5 flex-shrink-0 mt-0.5"/>
<span>{point}</span>
</li>
))}
</ul>
<Link
href="/contact"
className="inline-block bg-white text-blue-700 font-semibold px-6 py-3 rounded-full text-center shadow-md hover:bg-slate-100 transition"
>
Kostenlose Beratung anfragen
</Link>
</motion.div>
</div>
</section>
);
}

View File

@@ -1,33 +1,30 @@
'use client';
import React from "react";
import Image from "next/image";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
import React from 'react';
import Image from 'next/image';
import {motion} from 'framer-motion';
const team = [
{
name: "Thatsaphorn",
role: "Gründer & Entwickler",
picture: "",
name: 'Thatsaphorn',
role: 'Gründer & Entwickler',
picture: '',
},
{
name: "Anonym",
role: "Vertrieb",
picture: "",
name: 'Anonym',
role: 'Vertrieb',
picture: '',
},
];
const fallbackImage = "/images/team/default-avatar.jpg";
const fallbackImage = '/images/team/default-avatar.jpg';
const TeamSection = () => {
const colors = useThemeColors();
return (
<section className="w-full px-6 sm:px-12 py-16 max-w-6xl mx-auto">
<section
className="w-full px-6 sm:px-12 py-24 bg-background text-foreground transition-colors duration-700 ease-in-out">
<motion.h2
className="text-2xl sm:text-3xl font-bold text-left"
style={{color: colors.primaryText}}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
@@ -35,6 +32,7 @@ const TeamSection = () => {
>
Das Team
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-12 bg-amber-500"
initial={{opacity: 0, x: -20}}
@@ -45,11 +43,10 @@ const TeamSection = () => {
<div className="flex justify-center">
<div
className={`grid gap-8
grid-cols-1
sm:grid-cols-${Math.min(team.length, 2)}
md:grid-cols-${Math.min(team.length, 3)}
lg:grid-cols-${Math.min(team.length, 4)}`}
className={`grid gap-8 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-${Math.min(
team.length,
4
)}`}
>
{team.map((member, idx) => (
<motion.div
@@ -59,14 +56,11 @@ const TeamSection = () => {
viewport={{once: true}}
transition={{duration: 0.4, delay: idx * 0.1}}
whileHover={{scale: 1.015}}
className="flex flex-col items-center text-center
rounded-xl border border-gray-200 dark:border-gray-700
shadow-md hover:shadow-lg transition-all p-6"
style={{backgroundColor: colors.secondaryBg}}
className="flex flex-col items-center text-center rounded-xl border border-border shadow-md hover:shadow-lg transition-all p-6 bg-muted"
>
<motion.div
whileHover={{scale: 1.05}}
transition={{type: "spring", stiffness: 300, damping: 20}}
transition={{type: 'spring', stiffness: 300, damping: 20}}
className="w-28 h-28 relative mb-4"
>
<Image
@@ -78,12 +72,10 @@ const TeamSection = () => {
/>
</motion.div>
<div className="h-px w-8 bg-gray-300 dark:bg-gray-600 my-4"/>
<div className="h-px w-8 bg-border my-4"/>
<div className="text-lg font-semibold" style={{color: colors.primaryText}}>
{member.name}
</div>
<div className="text-sm mt-1" style={{color: colors.secondaryText}}>
<div className="text-lg font-semibold">{member.name}</div>
<div className="text-sm mt-1 text-muted-foreground">
{member.role}
</div>
</motion.div>

View File

@@ -1,37 +0,0 @@
import type {Metadata} from "next";
import "../globals.css";
import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react";
import {cookies} from "next/headers";
import {themeColors} from "@/components/Helper/ThemeColors";
export const metadata: Metadata = {
title: "Über Uns | Rhein Software",
description: "Rhein Software Development",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
const bgColor = themeColors[theme].primaryBg;
return (
<html lang="de" data-theme={theme}>
<head/>
<body className="antialiased" style={{backgroundColor: bgColor}}>
<ThemeProvider>
<Nav/>
{children}
<Footer/>
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,12 +0,0 @@
import React from 'react';
import AboutContent from "@/components/About/AboutContent";
const AboutPage = () => {
return (
<div>
<AboutContent/>
</div>
);
};
export default AboutPage;

View File

@@ -1,37 +0,0 @@
import type {Metadata} from "next";
import "../globals.css";
import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react";
import {cookies} from "next/headers";
import {themeColors} from "@/components/Helper/ThemeColors";
export const metadata: Metadata = {
title: "Kontakt | Rhein Software",
description: "Rhein Software Development",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
const bgColor = themeColors[theme].primaryBg;
return (
<html lang="de" data-theme={theme}>
<head/>
<body className="antialiased" style={{backgroundColor: bgColor}}>
<ThemeProvider>
<Nav/>
{children}
<Footer/>
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,5 +1,12 @@
import React from 'react';
import Contact from "@/components/Contact/Contact";
import type {Metadata} from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Kontakt | Rhein Software",
};
}
const ContactPage = () => {
return (

View File

@@ -25,3 +25,71 @@
.animate-float {
animation: float 3.5s ease-in-out infinite;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

58
frontend/app/layout.tsx Normal file
View File

@@ -0,0 +1,58 @@
import type {Metadata} from "next";
import "./globals.css";
import Footer from "@/components/Footer/Footer";
import {ThemeProvider} from "@/components/theme-provider";
import React from "react";
import CookieConsentBanner from "@/components/Cookie/CookieConsentBanner";
import Navbar from "@/components/Navbar/Navbar";
export const metadata: Metadata = {
title: "Rhein Software Maßgeschneiderte Softwarelösung",
description: "Rhein Software bietet individuelle Softwarelösungen für moderne Unternehmen.",
keywords: ["Webentwicklung", "Software", "Next.js", "Full Stack", "Rhein Software"],
authors: [{name: "Rhein Software"}],
creator: "Rhein Software",
robots: "index, follow",
openGraph: {
title: "Rhein Software Maßgeschneiderte Softwarelösung",
description: "Individuelle Softwarelösungen für Unternehmen mit Fokus auf Qualität und Performance.",
url: "https://www.rhein-software.dev",
siteName: "Rhein Software",
locale: "de_DE",
type: "website",
images: [
{
url: "https://www.rhein-software.dev/og-image.jpg",
width: 1200,
height: 630,
alt: "Rhein Software Individuelle Softwarelösung",
},
],
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="de" suppressHydrationWarning>
<body className="antialiased">
<ThemeProvider
attribute="class"
defaultTheme="system"
// disableTransitionOnChange
>
<Navbar/>
<main>
{children}
</main>
<Footer/>
</ThemeProvider>
<CookieConsentBanner/>
</body>
</html>
);
}

View File

@@ -0,0 +1,117 @@
"use client";
import {motion} from "framer-motion";
const fadeInUp = {
hidden: {opacity: 0, y: 30},
visible: (i: number) => ({
opacity: 1,
y: 0,
transition: {
duration: 0.6,
delay: i * 0.2,
ease: "easeOut",
},
}),
};
const ImprintComp = () => {
const sections = [
{
title: "Impressum",
content: (
<>
Thatsaphorn Atchariyaphap<br/>
Rhein-Software (Einzelunternehmer)<br/>
Mühlenstrasse 13<br/>
79664 Wehr
</>
),
},
{
title: "Kontakt",
content: (
<>
Telefon: +49 (0) 151 24003632<br/>
E-Mail:{" "}
<a
href="mailto:contact@rhein-software.dev"
className="underline text-primary"
>
contact@rhein-software.dev
</a>
</>
),
},
{
title: "EU-Streitschlichtung",
content: (
<>
Die Europäische Kommission stellt eine Plattform zur
Online-Streitbeilegung (OS) bereit:{" "}
<a
href="https://ec.europa.eu/consumers/odr/"
target="_blank"
rel="noopener noreferrer"
className="underline text-primary"
>
https://ec.europa.eu/consumers/odr/
</a>
.<br/>
Unsere E-Mail-Adresse finden Sie oben im Impressum.
</>
),
},
{
title: "Verbraucherstreitbeilegung / Universalschlichtungsstelle",
content: (
<>
Wir sind nicht bereit oder verpflichtet, an
Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle teilzunehmen.
</>
),
},
];
return (
<div className="overflow-hidden bg-background text-foreground">
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12 space-y-10 text-base leading-relaxed">
{sections.map((section, i) => (
<motion.div
key={section.title}
custom={i}
initial="hidden"
whileInView="visible"
viewport={{once: true, amount: 0.2}}
variants={fadeInUp}
>
<h2 className="text-2xl font-bold mb-4">{section.title}</h2>
<div className="space-y-4">{section.content}</div>
</motion.div>
))}
<motion.p
className="text-sm text-muted-foreground"
custom={4}
initial="hidden"
whileInView="visible"
viewport={{once: true, amount: 0.2}}
variants={fadeInUp}
>
Quelle:{" "}
<a
href="https://www.e-recht24.de"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
www.e-recht24.de
</a>
</motion.p>
</div>
</div>
);
};
export default ImprintComp;

View File

@@ -1,10 +1,17 @@
import React from 'react';
import ImprintComp from "@/components/Legal/Imprint/ImprintComp";
import ImprintComp from "@/app/legal/imprint/ImprintComp";
import type {Metadata} from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Impressum | Rhein Software",
};
}
const ImprintPage = () => {
return (
<div>
<ImprintComp />
<ImprintComp/>
</div>
);
};

View File

@@ -1,38 +0,0 @@
import type {Metadata} from "next";
import "../globals.css";
import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react";
import {cookies} from "next/headers";
import {themeColors} from "@/components/Helper/ThemeColors";
export const metadata: Metadata = {
title: "Rechtliches | Rhein Software",
description: "Rhein Software Development",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
const bgColor = themeColors[theme].primaryBg;
return (
<html lang="de" data-theme={theme}>
<head/>
<body className="antialiased" style={{backgroundColor: bgColor}}>
<ThemeProvider>
<Nav/>
{children}
<Footer/>
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,10 +1,60 @@
import React from 'react';
'use client'
const LegalPage = () => {
import Link from 'next/link'
import {motion} from 'framer-motion'
import SmallHero from '@/components/Helper/SmallHero'
import {Card, CardContent} from '@/components/ui/card'
const legalLinks = [
{
label: 'Impressum',
href: '/legal/imprint',
},
{
label: 'Datenschutz',
href: '/legal/privacy',
},
{
label: 'Widerrufsrecht',
href: '/legal/revocation',
},
{
label: 'Nutzungsbedingungen',
href: '/legal/terms-of-use',
},
]
export default function LegalOverviewPage() {
return (
<div>
</div>
);
};
<>
<SmallHero
title="Rechtliches"
subtitle="Alle rechtlich relevanten Informationen auf einen Blick."
backgroundImage="/images/contact.png"
blurBackground
/>
export default LegalPage;
<section className="px-6 sm:px-12 py-16 max-w-6xl mx-auto">
<motion.div
className="grid grid-cols-1 sm:grid-cols-2 gap-6"
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.3}}
>
{legalLinks.map(({label, href}) => (
<Card key={href} className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<Link
href={href}
className="text-primary font-medium text-lg hover:underline"
>
{label}
</Link>
</CardContent>
</Card>
))}
</motion.div>
</section>
</>
)
}

View File

@@ -1,8 +1,5 @@
"use client";
import React, {useContext} from "react";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
import {motion} from "framer-motion";
const fadeInUp = {
@@ -19,9 +16,6 @@ const fadeInUp = {
};
const PrivacyComp = () => {
const {theme} = useContext(ThemeContext);
const colors = themeColors[theme];
const sections = [
{
title: "1. Datenschutz auf einen Blick",
@@ -56,16 +50,12 @@ const PrivacyComp = () => {
Mühlenstrasse 13
<br/>
79664 Wehr
<br/><br/>
<br/>
<br/>
Telefon: +49 (0) 151 24003632
<br/>
E-Mail:{" "}
<a
href="mailto:contact@rhein-software.dev"
className="underline text-blue-500"
>
contact@rhein-software.dev
</a>
E-Mail: <a href="mailto:contact@rhein-software.dev"
className="underline text-primary">contact@rhein-software.dev</a>
</p>
</>
),
@@ -102,27 +92,16 @@ const PrivacyComp = () => {
{
title: "Quelle",
content: (
<p className="text-sm text-gray-500">
Quelle:{" "}
<a
href="https://www.e-recht24.de"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
www.e-recht24.de
</a>
<p className="text-sm text-muted-foreground">
Quelle: <a href="https://www.e-recht24.de" target="_blank" rel="noopener noreferrer"
className="underline">www.e-recht24.de</a>
</p>
),
},
];
return (
<div
className="overflow-hidden transition-colors duration-500"
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
>
{/* Privacy Content */}
<div className="overflow-hidden bg-background text-foreground">
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12 space-y-10 text-base leading-relaxed">
{sections.map((section, i) => (
<motion.div
@@ -142,4 +121,4 @@ const PrivacyComp = () => {
);
};
export default PrivacyComp;
export default PrivacyComp;

View File

@@ -1,5 +1,12 @@
import React from 'react';
import PrivacyComp from "@/components/Legal/Privacy/PrivacyComp";
import PrivacyComp from "@/app/legal/privacy/PrivacyComp";
import type {Metadata} from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Datenschutz | Rhein Software",
};
}
const PrivacyPage = () => {
return (

View File

@@ -1,12 +0,0 @@
import React from 'react';
import RevocationComp from "@/components/Legal/RevocationComp";
const RevocationPage = () => {
return (
<div>
<RevocationComp />
</div>
);
};
export default RevocationPage;

View File

@@ -0,0 +1,98 @@
'use client';
import SmallHero from '@/components/Helper/SmallHero';
import React from 'react';
import {motion} from 'framer-motion';
const TermsOfUseComp = () => {
return (
<div className="overflow-hidden bg-background text-foreground">
{/* Hero Section */}
<div className="mt-[10vh]">
<SmallHero
title="AGB"
subtitle=""
backgroundImage="/images/contact.png"
/>
</div>
{/* Contact Form */}
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12">
<motion.h2
className="text-2xl md:text-3xl font-bold text-center"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.6}}
>
Schreib uns eine Nachricht
</motion.h2>
<motion.p
className="text-center mt-3 text-muted-foreground"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir melden uns schnellstmöglich bei dir!
</motion.p>
<form className="mt-8 max-w-2xl mx-auto space-y-6">
{/* Name & Email */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{["Dein Name", "Deine E-Mail"].map((label, index) => (
<motion.div
key={index}
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: index * 0.2}}
>
<label className="block font-semibold">{label}</label>
<input
type={index === 0 ? "text" : "email"}
placeholder={index === 0 ? "Max Mustermann" : "max@example.com"}
className="w-full p-3 rounded-lg border border-border bg-card text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
/>
</motion.div>
))}
</div>
{/* Message */}
<motion.div
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.5}}
>
<label className="block font-semibold">Deine Nachricht</label>
<textarea
rows={4}
placeholder="Schreibe deine Nachricht..."
className="w-full p-3 rounded-lg border border-border bg-card text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
/>
</motion.div>
{/* Submit Button */}
<motion.div
className="text-center"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.7}}
>
<button
type="submit"
className="px-6 py-3 bg-blue-600 text-white text-lg font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all"
>
📩 Nachricht senden
</button>
</motion.div>
</form>
</div>
</div>
);
};
export default TermsOfUseComp;

View File

@@ -1,5 +1,12 @@
import React from 'react';
import TermsOfUseComp from "@/components/Legal/TermsOfUseComp";
import TermsOfUseComp from "@/app/legal/terms-of-use/TermsOfUseComp";
import type {Metadata} from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Nutzungsbedingungen | Rhein Software",
};
}
const TermsOfUsePage = () => {
return (

View File

@@ -1,37 +0,0 @@
import type {Metadata} from "next";
import "../globals.css";
import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react";
import {cookies} from "next/headers";
import {themeColors} from "@/components/Helper/ThemeColors";
export const metadata: Metadata = {
title: "Leistungen | Rhein Software",
description: "Rhein Software Development",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
const bgColor = themeColors[theme].primaryBg;
return (
<html lang="de" data-theme={theme}>
<head/>
<body className="antialiased" style={{backgroundColor: bgColor}}>
<ThemeProvider>
<Nav/>
{children}
<Footer/>
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,12 +0,0 @@
import React from 'react';
import Services from "@/components/Services/Services";
const ContactPage = () => {
return (
<div>
<Services/>
</div>
);
};
export default ContactPage;

21
frontend/components.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@@ -1,47 +0,0 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
import Section from "@/components/Section";
import AboutHero from "@/components/About/Section/AboutHero";
import AboutTimeline from "@/components/About/Section/AboutTimeline";
import TeamSection from "@/components/About/Section/TeamSection";
import AboutIntro from "@/components/About/Section/AboutIntro";
import AboutProcess from "@/components/About/Section/AboutProcess";
const AboutContent = () => {
const colors = useThemeColors();
return (
<motion.div
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.7, ease: "easeOut"}}
className="overflow-hidden"
>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<AboutHero/>
</Section>
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
<AboutIntro/>
</Section>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<AboutProcess/>
</Section>
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
<AboutTimeline/>
</Section>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<TeamSection/>
</Section>
</motion.div>
);
};
export default AboutContent;

View File

@@ -1,19 +0,0 @@
'use client';
import React from "react";
import SmallHero from "@/components/Helper/SmallHero";
const AboutHero = () => {
return (
<div className="relative overflow-hidden">
<SmallHero
title="Über uns"
subtitle="Digitaler Partner für individuelle Softwarelösungen."
backgroundImage="/images/contact.png"
blurBackground
/>
</div>
);
};
export default AboutHero;

View File

@@ -1,68 +0,0 @@
'use client';
import {motion} from 'framer-motion';
import {useThemeColors} from '@/utils/useThemeColors';
const About = () => {
const colors = useThemeColors();
return (
<section
className="relative w-full py-24 transition-colors duration-700 ease-in-out"
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
>
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto">
<div className="flex flex-col">
{/* Text */}
<div className="p-0 max-w-4xl">
<motion.p
className="text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir sind Rhein-Software Ihr Partner für digitale Produkte und individuelle
Softwarelösungen.
Wir entwickeln skalierbare, wartbare Anwendungen mit klarem Fokus: Technik, die begeistert
von der Architektur bis zum Go-Live.
</motion.p>
<motion.p
className="mt-6 text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.3}}
>
Ob Start-up oder etabliertes Unternehmen: Wir begleiten Sie mit einem flexiblen Netzwerk,
klarer Kommunikation und hohem Qualitätsanspruch agil, lösungsorientiert und nah am
Projekt.
</motion.p>
</div>
{/* CTA Button */}
<motion.div
className="mt-10 flex justify-end"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.5}}
>
{/*<Link href="/about">*/}
{/* <button*/}
{/* className="flex items-center gap-2 bg-blue-700 hover:bg-blue-900 text-white font-semibold px-5 py-2 rounded-full shadow-lg transition-all"*/}
{/* >*/}
{/* Mehr über uns <FiArrowRight size={18}/>*/}
{/* </button>*/}
{/*</Link>*/}
</motion.div>
</div>
</div>
</section>
);
};
export default About;

View File

@@ -1,158 +0,0 @@
'use client';
import React, {useState} from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
const processSteps = [
{
title: "Beratung",
description: (
<>
In der <strong>Beratungsphase</strong> analysieren wir gemeinsam Ihre Anforderungen und
Geschäftsziele. Dabei identifizieren wir Herausforderungen und definieren die Zielsetzung
für Ihr Projekt.
</>
),
},
{
title: "Planung",
description: (
<>
Wir erarbeiten ein <strong>technisches Konzept</strong> mit klarer Struktur, Meilensteinen und
Ressourcenplanung. Eine solide Architektur bildet die Grundlage für ein skalierbares und wartbares
System.
</>
),
},
{
title: "Entwicklung",
description: (
<>
In iterativen Zyklen setzen wir das Projekt um. Regelmäßige <strong>Feedbackschleifen</strong>&nbsp;
sorgen dafür, dass das Ergebnis Ihren Erwartungen entspricht und flexibel angepasst werden kann.
</>
),
},
{
title: "Test",
description: (
<>
Durch umfangreiche <strong>Tests und Optimierungen</strong> stellen wir sicher, dass Ihre
Anwendung robust, performant und benutzerfreundlich ist noch vor dem Go-Live.
</>
),
},
{
title: "Go-Live",
description: (
<>
Wir begleiten Sie beim <strong>produktiven Einsatz</strong> Ihrer Anwendung und unterstützen Sie
auch nach dem Go-Live mit Support und Weiterentwicklungsmöglichkeiten.
</>
),
},
];
const AboutProcess: React.FC = () => {
const colors = useThemeColors();
const [activeIndex, setActiveIndex] = useState<number>(0);
return (
<section className="w-full px-6 sm:px-12 py-20 max-w-6xl mx-auto">
<h2
className="text-2xl sm:text-3xl font-bold text-left"
style={{color: colors.primaryText}}
>
Unser Prozess
</h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-12 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
{/* Mobile View: Tab buttons */}
<div className="block md:hidden mb-6">
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
{processSteps.map((step, idx) => (
<button
key={idx}
onClick={() => setActiveIndex(idx)}
className={`w-full px-4 py-2 text-sm border rounded-full transition-colors ${
activeIndex === idx
? 'bg-blue-600 text-white border-blue-600'
: 'border-gray-300 dark:border-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
{step.title}
</button>
))}
</div>
</div>
{/* Desktop View: 2-column layout */}
<div className="hidden md:grid grid-cols-3 gap-8">
{/* Left: Step List */}
<div className="flex flex-col space-y-4">
{processSteps.map((step, idx) => (
<button
key={idx}
onClick={() => setActiveIndex(idx)}
className={`text-left px-4 py-3 border rounded-lg transition-colors ${
activeIndex === idx
? 'border-blue-600 bg-blue-50 dark:bg-gray-800'
: 'border-gray-300 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
style={{color: colors.primaryText}}
>
<span className="font-semibold">{idx + 1}. {step.title}</span>
</button>
))}
</div>
{/* Right: Step Content */}
<div className="md:col-span-2 p-6 border border-gray-300 dark:border-gray-700 rounded-lg"
style={{backgroundColor: colors.primaryBg}}>
<motion.div
key={activeIndex}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.4}}
>
<h3 className="text-xl font-bold mb-4" style={{color: colors.primaryText}}>
{processSteps[activeIndex].title}
</h3>
<div className="text-base space-y-1" style={{color: colors.secondaryText}}>
{processSteps[activeIndex].description}
</div>
</motion.div>
</div>
</div>
{/* Mobile View: Content Below Tabs */}
<div className="block md:hidden">
<div className="p-6 border border-gray-300 dark:border-gray-700 rounded-lg"
style={{backgroundColor: colors.primaryBg}}>
<motion.div
key={activeIndex + '-mobile'}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.4}}
>
<h3 className="text-xl font-bold mb-4" style={{color: colors.primaryText}}>
{activeIndex + 1}. {processSteps[activeIndex].title}
</h3>
<div className="text-base space-y-1" style={{color: colors.secondaryText}}>
{processSteps[activeIndex].description}
</div>
</motion.div>
</div>
</div>
</section>
);
};
export default AboutProcess;

View File

@@ -1,94 +0,0 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
const timeline = [
{
date: "Oktober 2024",
title: "Projektgründung",
description: "Entwicklung der Idee und erste Umsetzungsschritte inspiriert durch Technik und Nachhaltigkeit.",
current: false,
},
{
date: "Mai 2025",
title: "Go-Live",
description: "Offizieller Start mit Kundenprojekten und einem umfassenden Full-Service-Angebot.",
current: true,
},
];
const AboutTimeline3 = () => {
const colors = useThemeColors();
return (
<div className="relative w-full px-6 sm:px-12 py-8 max-w-5xl mx-auto">
<h2
className="text-2xl sm:text-3xl font-bold mt-10"
style={{color: colors.primaryText}}
>
Von der Idee bis heute
</h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-12 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<div className="relative border-l-2 border-gray-300 dark:border-gray-700 ml-6">
{timeline.map((item, idx) => (
<motion.div
key={idx}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: idx * 0.2}}
className="relative mb-10 pl-12"
>
{/* Timeline dot */}
<div className="absolute left-[-22px] top-0 z-10">
<div
className={`w-10 h-10 rounded-full border-2 flex items-center justify-center bg-white dark:bg-gray-900 ${
item.current
? "border-blue-600"
: "border-gray-400 dark:border-gray-600"
}`}
>
{item.current && (
<motion.span
className="absolute w-10 h-10 rounded-full bg-blue-600 opacity-40"
animate={{scale: [1, 1.6, 1], opacity: [0.4, 0, 0.4]}}
transition={{repeat: Infinity, duration: 1.6}}
/>
)}
</div>
</div>
{/* Timeline card */}
<motion.div
whileHover={{scale: 1.02, translateX: 4}}
transition={{type: "spring", stiffness: 260, damping: 20}}
className="bg-white dark:bg-gray-900 rounded-lg shadow-md p-5 border border-gray-200 dark:border-gray-700 cursor-default"
>
<div className="text-sm text-blue-600 font-semibold mb-1">{item.date}</div>
<div
className="text-lg font-bold mb-2"
style={{color: colors.primaryText}}
>
{item.title}
</div>
<div className="text-sm" style={{color: colors.secondaryText}}>
{item.description}
</div>
</motion.div>
</motion.div>
))}
</div>
</div>
);
};
export default AboutTimeline3;

View File

@@ -0,0 +1,66 @@
'use client';
import Link from 'next/link';
import {motion} from 'framer-motion';
import {ArrowRight} from 'lucide-react';
export default function DualCTA() {
return (
<section
className="relative py-24 px-6 md:px-16 lg:px-36 bg-gradient-to-br from-background via-muted to-background">
<motion.div
initial={{opacity: 0, y: 30}}
whileInView={{opacity: 1, y: 0}}
transition={{duration: 0.8, ease: 'easeOut'}}
viewport={{once: true, amount: 0.3}}
className="grid md:grid-cols-2 gap-8 max-w-7xl mx-auto"
>
<CTABox
title="Fündig geworden?"
text="Zögern Sie nicht, uns zu kontaktieren wir helfen Ihnen gerne weiter."
href="/contact"
linkText="Zum Kontakt"
/>
<CTABox
title="Mehr über uns erfahren?"
text="Wir stehen für nahe und zuverlässige Betreuung. Erfahren Sie mehr über unsere Werte."
href="/about"
linkText="Über uns"
/>
</motion.div>
</section>
);
}
function CTABox({
title,
text,
href,
linkText,
}: Readonly<{
title: string;
text: string;
href: string;
linkText: string;
}>) {
return (
<motion.div
whileHover={{scale: 1.015}}
className="flex flex-col justify-between h-full border border-border/50 backdrop-blur-sm bg-background/70 text-foreground px-8 py-10 rounded-2xl shadow-lg transition-all duration-300"
>
<div className="space-y-4 mb-6">
<h3 className="text-2xl md:text-3xl font-bold tracking-tight">{title}</h3>
<p className="text-base md:text-lg leading-relaxed text-muted-foreground">{text}</p>
</div>
<div className="w-full flex justify-end">
<Link
href={href}
className="group inline-flex items-center font-semibold text-base md:text-lg hover:text-primary/80 transition-colors text-blue-600"
>
<span className="group-hover:underline">{linkText}</span>
<ArrowRight className="ml-2 h-5 w-5 transition-transform group-hover:translate-x-1"/>
</Link>
</div>
</motion.div>
);
}

View File

@@ -1,31 +1,32 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
import ContactHero from "@/components/Contact/Section/ContactHero";
import ContactFormSection from "@/components/Contact/Section/ContactFormSection";
import Section from "@/components/Section";
const Contact = () => {
const colors = useThemeColors();
import {motion} from 'framer-motion'
import ContactFormSection from '@/components/Contact/Section/ContactFormSection'
import SmallHero from '@/components/Helper/SmallHero'
export default function Contact() {
return (
<motion.div
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.7, ease: "easeOut"}}
className="overflow-hidden"
>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<ContactHero/>
</Section>
<>
<SmallHero
title="Kontakt"
subtitle="Du hast Fragen oder möchtest ein Projekt besprechen? Schreib uns!"
backgroundImage="/images/contact.png"
blurBackground
/>
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
<ContactFormSection/>
</Section>
</motion.div>
);
};
export default Contact;
<section className="bg-background text-foreground">
<div className="px-4 sm:px-6 lg:px-8">
<div className="max-w-6xl mx-auto">
<motion.div
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6}}
>
<ContactFormSection/>
</motion.div>
</div>
</div>
</section>
</>
)
}

View File

@@ -1,69 +1,56 @@
'use client';
'use client'
import React, {useState} from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
import HCaptcha from "@hcaptcha/react-hcaptcha";
import React, {useState} from 'react'
import {motion} from 'framer-motion'
// import HCaptcha from '@hcaptcha/react-hcaptcha'
const ContactFormSection = () => {
const colors = useThemeColors();
const [form, setForm] = useState({
name: "",
email: "",
company: "",
phone: "",
website: "",
message: "",
});
name: '',
email: '',
company: '',
phone: '',
website: '',
message: '',
})
const [captchaToken, setCaptchaToken] = useState("");
const [submitted, setSubmitted] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [captchaToken, setCaptchaToken] = useState('')
const [submitted, setSubmitted] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
// const hCaptchaSiteKey: string = process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY ?? "null";
//
// console.log(hCaptchaSiteKey);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setForm({...form, [e.target.name]: e.target.value});
};
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setForm({...form, [e.target.name]: e.target.value})
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");
e.preventDefault()
setLoading(true)
setError('')
// if (!captchaToken) {
// setError("Bitte löse das CAPTCHA, um fortzufahren.");
// setLoading(false);
// return;
// }
const res = await fetch("/api/contact", {
method: "POST",
headers: {"Content-Type": "application/json"},
const res = await fetch('/api/contact', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({...form, captcha: captchaToken}),
});
})
if (res.ok) {
setSubmitted(true);
setForm({name: "", email: "", company: "", phone: "", website: "", message: ""});
setSubmitted(true)
setForm({name: '', email: '', company: '', phone: '', website: '', message: ''})
} else {
const resJson = await res.json();
setError(resJson?.error || "Ein Fehler ist aufgetreten. Bitte versuche es später erneut.");
const resJson = await res.json()
setError(resJson?.error || 'Ein Fehler ist aufgetreten. Bitte versuche es später erneut.')
}
setLoading(false);
};
setLoading(false)
}
return (
<div className="w-full px-6 sm:px-12 py-20 text-left transition-theme">
<div className="w-full px-6 sm:px-12 py-20 text-left">
<motion.h2
className="text-2xl sm:text-3xl font-bold mb-2"
style={{color: colors.primaryText}}
className="text-2xl sm:text-3xl font-bold mb-2 text-foreground"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
@@ -73,8 +60,7 @@ const ContactFormSection = () => {
</motion.h2>
<motion.p
className="text-sm mb-8 max-w-xl"
style={{color: colors.secondaryText}}
className="text-sm mb-8 max-w-xl text-muted-foreground"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
@@ -84,46 +70,47 @@ const ContactFormSection = () => {
</motion.p>
{submitted ? (
<div className="text-green-600 font-semibold text-lg"> Deine Nachricht wurde erfolgreich
gesendet!</div>
<div className="text-green-600 font-semibold text-lg">
Deine Nachricht wurde erfolgreich gesendet!
</div>
) : (
<form className="space-y-6" onSubmit={handleSubmit}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[
{
label: "Dein Name *",
name: "name",
type: "text",
label: 'Dein Name *',
name: 'name',
type: 'text',
required: true,
placeholder: "Max Mustermann"
placeholder: 'Max Mustermann'
},
{
label: "Deine E-Mail *",
name: "email",
type: "email",
label: 'Deine E-Mail *',
name: 'email',
type: 'email',
required: true,
placeholder: "max@example.com"
placeholder: 'max@example.com'
},
{
label: "Firmenname (optional)",
name: "company",
type: "text",
label: 'Firmenname (optional)',
name: 'company',
type: 'text',
required: false,
placeholder: "Mustermann GmbH"
placeholder: 'Mustermann GmbH'
},
{
label: "Telefonnummer (optional)",
name: "phone",
type: "tel",
label: 'Telefonnummer (optional)',
name: 'phone',
type: 'tel',
required: false,
placeholder: "+49 123 456789"
placeholder: '+49 123 456789'
},
{
label: "Webseite (optional)",
name: "website",
type: "url",
label: 'Webseite (optional)',
name: 'website',
type: 'url',
required: false,
placeholder: "https://..."
placeholder: 'https://...'
},
].map((field, index) => (
<motion.div
@@ -133,7 +120,7 @@ const ContactFormSection = () => {
viewport={{once: true}}
transition={{duration: 0.5, delay: index * 0.1}}
>
<label className="block font-semibold mb-1" style={{color: colors.primaryText}}>
<label className="block font-semibold mb-1 text-foreground">
{field.label}
</label>
<input
@@ -143,12 +130,7 @@ const ContactFormSection = () => {
onChange={handleChange}
required={field.required}
placeholder={field.placeholder}
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
style={{
backgroundColor: colors.inputFieldBg,
border: `1px solid ${colors.inputBorder}`,
color: colors.primaryText,
}}
className="w-full p-3 rounded-md border bg-background text-foreground border-muted focus:outline-none focus:ring-2 focus:ring-primary transition"
/>
</motion.div>
))}
@@ -160,9 +142,7 @@ const ContactFormSection = () => {
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.6}}
>
<label className="block font-semibold mb-1" style={{color: colors.primaryText}}>
Deine Nachricht *
</label>
<label className="block font-semibold mb-1 text-foreground">Deine Nachricht *</label>
<textarea
name="message"
rows={4}
@@ -170,30 +150,27 @@ const ContactFormSection = () => {
value={form.message}
onChange={handleChange}
placeholder="Worum geht es?"
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
style={{
backgroundColor: colors.inputFieldBg,
border: `1px solid ${colors.inputBorder}`,
color: colors.primaryText,
}}
className="w-full p-3 rounded-md border bg-background text-foreground border-muted focus:outline-none focus:ring-2 focus:ring-primary transition"
/>
</motion.div>
{/*<motion.div*/}
{/* className="pt-2"*/}
{/* initial={{opacity: 0, y: 10}}*/}
{/* whileInView={{opacity: 1, y: 0}}*/}
{/* viewport={{once: true}}*/}
{/* transition={{duration: 0.5, delay: 0.7}}*/}
{/*>*/}
{/* <HCaptcha sitekey={hCaptchaSiteKey} onVerify={setCaptchaToken}/>*/}
{/*</motion.div>*/}
{/*
<motion.div
className="pt-2"
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.7 }}
>
<HCaptcha sitekey={hCaptchaSiteKey} onVerify={setCaptchaToken} />
</motion.div>
*/}
{/*{error && (*/}
{/* <div className="text-red-600 font-medium pt-2">*/}
{/* ❌ {error}*/}
{/* </div>*/}
{/*)}*/}
{error && (
<div className="text-red-600 font-medium pt-2">
{error}
</div>
)}
<motion.div
className="pt-4 flex justify-end"
@@ -205,15 +182,15 @@ const ContactFormSection = () => {
<button
type="submit"
disabled={loading}
className="px-6 py-3 bg-blue-600 text-white text-sm sm:text-base font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all disabled:opacity-50"
className="px-6 py-3 bg-primary text-white text-sm sm:text-base font-semibold rounded-md shadow hover:bg-primary/90 transition-all disabled:opacity-50"
>
{loading ? "Sende..." : "📩 Nachricht senden"}
{loading ? 'Sende...' : '📩 Nachricht senden'}
</button>
</motion.div>
</form>
)}
</div>
);
};
)
}
export default ContactFormSection;
export default ContactFormSection

View File

@@ -1,19 +0,0 @@
'use client';
import React from "react";
import SmallHero from "@/components/Helper/SmallHero";
const ContactHero = () => {
return (
<div className="relative overflow-hidden">
<SmallHero
title="Kontakt"
subtitle="Du hast Fragen oder möchtest ein Projekt besprechen? Schreib uns!"
backgroundImage="/images/contact.png"
blurBackground
/>
</div>
);
};
export default ContactHero;

View File

@@ -0,0 +1,128 @@
// CookieConsentBanner.tsx
'use client';
import {useEffect, useState} from 'react';
export default function CookieConsentBanner() {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [stats, setStats] = useState(false);
const [personalization, setPersonalization] = useState(false);
useEffect(() => {
const consent = localStorage.getItem('cookie_consent');
if (!consent) setVisible(true);
const show = () => {
setVisible(true);
};
window.addEventListener('show-cookie-banner', show);
return () => window.removeEventListener('show-cookie-banner', show);
}, []);
const handleAccept = () => {
setLoading(true);
setTimeout(() => {
localStorage.setItem('cookie_consent', 'true');
localStorage.setItem('cookie_stats', stats.toString());
localStorage.setItem('cookie_personalization', personalization.toString());
setVisible(false);
setLoading(false);
}, 1000);
};
const handleAcceptAll = () => {
setLoading(true);
setStats(true);
setPersonalization(true);
setTimeout(() => {
localStorage.setItem('cookie_consent', 'true');
localStorage.setItem('cookie_stats', 'true');
localStorage.setItem('cookie_personalization', 'true');
setVisible(false);
setLoading(false);
}, 1000);
};
const handleDecline = () => {
localStorage.setItem('cookie_consent', 'false');
localStorage.setItem('cookie_stats', 'false');
localStorage.setItem('cookie_personalization', 'false');
setVisible(false);
};
return visible ? (
<div className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center px-4">
<div className="bg-white max-w-2xl w-full rounded-xl shadow-lg overflow-hidden text-sm text-gray-800">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-semibold">Wir nutzen Cookies und andere Technologien.</h2>
</div>
<div className="px-6 py-4 space-y-4 max-h-[60vh] overflow-y-auto">
<p>
Diese Website nutzt Cookies und vergleichbare Funktionen zur Verarbeitung von
Endgeräteinformationen und personenbezogenen Daten. Die Verarbeitung dient der Einbindung von
Inhalten, externen Diensten und Elementen Dritter, der statistischen Analyse/Messung, der
personalisierten Werbung sowie der Einbindung sozialer Medien.
</p>
<p>
Je nach Funktion werden dabei Daten an Dritte weitergegeben und in Länder außerhalb der EU
übertragen, in denen kein angemessenes Datenschutzniveau besteht z. B. die USA. Ihre
Einwilligung ist freiwillig und kann jederzeit über das Symbol unten links widerrufen werden.
</p>
<div className="space-y-2">
<label className="block">
<input type="checkbox" defaultChecked disabled className="mr-2"/>
Notwendige Cookies (immer aktiv)
</label>
<label className="block">
<input
type="checkbox"
checked={stats}
onChange={(e) => setStats(e.target.checked)}
className="mr-2"
/>
Statistiken (z. B. Besuchertracking)
</label>
<label className="block">
<input
type="checkbox"
checked={personalization}
onChange={(e) => setPersonalization(e.target.checked)}
className="mr-2"
/>
Personalisierung (z. B. eingebettete Medien, Google Maps)
</label>
</div>
</div>
<div className="px-6 py-4 border-t border-gray-200 flex flex-col sm:flex-row sm:justify-end gap-2">
<div className="flex flex-wrap gap-2">
<button
onClick={handleDecline}
className="px-4 py-2 border rounded text-gray-600 hover:bg-gray-100"
>
Ablehnen
</button>
<button
onClick={handleAccept}
className="px-4 py-2 border rounded text-gray-700 hover:bg-gray-100 disabled:opacity-60"
disabled={loading}
>
{loading ? 'Speichere ...' : 'Akzeptieren'}
</button>
<button
onClick={handleAcceptAll}
className="px-4 py-2 bg-red-700 text-white rounded hover:bg-red-800 disabled:opacity-60"
disabled={loading}
>
{loading ? 'Speichere ...' : 'Alles akzeptieren'}
</button>
</div>
</div>
<div className="px-6 py-3 border-t border-gray-100 text-xs text-gray-500 flex justify-start gap-4">
<a href="/legal/imprint" className="hover:underline">Impressum</a>
<a href="/legal/privacy" className="hover:underline">Datenschutzerklärung</a>
</div>
</div>
</div>
) : null;
}

View File

@@ -3,95 +3,109 @@
import React from 'react';
import Link from 'next/link';
import {motion} from 'framer-motion';
import {Mail, Gavel, ShieldCheck, Cookie} from 'lucide-react';
const Footer = () => {
const openCookieSettings = () => {
window.dispatchEvent(new Event('show-cookie-banner'));
};
return (
<motion.footer
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.6, ease: 'easeOut'}}
className="py-10 transition-theme text-white"
style={{
backgroundColor: '#16171f', // modern dark blue-purple tone
}}
className="py-12 text-white"
style={{backgroundColor: '#16171f'}}
>
<div className="w-[90%] mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-8">
{/* Logo and description */}
<div>
<h1 className="text-xl md:text-2xl font-bold text-white">
<span className="text-3xl md:text-4xl text-pink-700">R</span>hein Software
<motion.div
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.6, delay: 0.2}}
className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"
>
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
{/* Logo */}
<motion.div
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.3}}
>
<h1 className="text-2xl font-bold">
<span className="text-pink-700 text-4xl">R</span>hein Software
</h1>
</div>
<p className="mt-4 text-sm text-gray-400">
Individuelle Web- und Appentwicklung mit Qualität und Weitblick.
</p>
</motion.div>
{/* Informationen */}
<div>
<h3 className="text-lg font-semibold text-white">Informationen</h3>
<ul className="mt-4 space-y-4 text-sm font-semibold text-gray-400">
<li>
<Link href="/contact">
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
Kontakt
</p>
<motion.div
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.4}}
>
<h3 className="text-lg font-semibold mb-4">Informationen</h3>
<ul className="space-y-3 text-sm text-gray-300">
<li className="flex items-center gap-2">
<Mail className="w-4 h-4"/>
<Link href="/contact" className="hover:underline">
Kontakt
</Link>
</li>
{/*<li>*/}
{/* <Link href="/contact">*/}
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
{/* Zahlung und Versand*/}
{/* </p>*/}
{/* </Link>*/}
{/*</li>*/}
</ul>
</div>
</motion.div>
{/* Rechtliches */}
<div>
<h3 className="text-lg font-semibold text-white">Rechtliches</h3>
<ul className="mt-4 space-y-4 text-sm font-semibold text-gray-400">
{/*<li>*/}
{/* <Link href="/legal/terms-of-use">*/}
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
{/* AGB*/}
{/* </p>*/}
{/* </Link>*/}
{/*</li>*/}
{/*<li>*/}
{/* <Link href="/legal/revocation">*/}
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
{/* Widerruf*/}
{/* </p>*/}
{/* </Link>*/}
{/*</li>*/}
<li>
<Link href="/legal/privacy">
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
Datenschutz
</p>
<motion.div
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.5}}
>
<h3 className="text-lg font-semibold mb-4">Rechtliches</h3>
<ul className="space-y-3 text-sm text-gray-300">
<li className="flex items-center gap-2">
<ShieldCheck className="w-4 h-4"/>
<Link href="/legal/privacy" className="hover:underline">
Datenschutz
</Link>
</li>
<li>
<Link href="/legal/imprint">
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
Impressum
</p>
<li className="flex items-center gap-2">
<Gavel className="w-4 h-4"/>
<Link href="/legal/imprint" className="hover:underline">
Impressum
</Link>
</li>
<li className="flex items-center gap-2">
<Cookie className="w-4 h-4"/>
<button
onClick={openCookieSettings}
className="hover:underline text-left"
>
Cookie-Einstellungen
</button>
</li>
</ul>
</div>
</motion.div>
</div>
{/* Bottom Section */}
<div
className="mt-8 border-t border-gray-600 pt-8 flex flex-col md:flex-row justify-between items-center text-sm text-gray-400">
<p className="text-center md:text-left">
© 2025 Rhein Software Development. All rights reserved.
</p>
</div>
</div>
<motion.div
className="mt-12 border-t border-gray-700 pt-6 text-center text-sm text-gray-500"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.6}}
>
© 2025 Rhein Software Development. Alle Rechte vorbehalten.
</motion.div>
</motion.div>
</motion.footer>
);
};
export default Footer;
export default Footer;

View File

@@ -1,58 +1,59 @@
'use client';
'use client'
import React, {useContext} from "react";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
import {motion} from "framer-motion";
import {motion} from 'framer-motion'
import clsx from 'clsx'
type SmallHeroProps = {
title: string;
subtitle?: string;
backgroundImage?: string;
blurBackground?: boolean;
};
title: string
subtitle?: string
backgroundImage?: string
blurBackground?: boolean
className?: string
}
const SmallHero = ({title, subtitle, backgroundImage, blurBackground}: SmallHeroProps) => {
const {theme} = useContext(ThemeContext);
const colors = themeColors[theme];
const SmallHero = ({
title,
subtitle,
backgroundImage,
blurBackground,
className = 'py-36'
}: SmallHeroProps) => {
const hasImage = !!backgroundImage
const primaryTextColor = backgroundImage ? "#ffffff" : colors.primaryText;
const secondaryTextColor = backgroundImage ? "rgba(255, 255, 255, 0.8)" : "#6B7280"; // Tailwind gray-500
const baseTextColor = hasImage ? 'text-white' : 'text-foreground'
const subtitleTextColor = hasImage ? 'text-white/80' : 'text-muted-foreground'
return (
<div className="relative w-full py-36 overflow-hidden">
{backgroundImage && blurBackground && (
<div className={clsx('relative w-full overflow-hidden', className)}>
{hasImage && blurBackground && (
<div
className="absolute inset-0 bg-cover bg-center blur-sm scale-[1.05] z-0 will-change-transform"
style={{backgroundImage: `url(${backgroundImage})`}}
/>
)}
{/* Text content */}
<div className="relative z-10 px-6 sm:px-12 max-w-5xl mx-auto">
<div className="relative z-10 px-6 sm:px-12 max-w-6xl mx-auto">
<motion.h1
className="text-3xl sm:text-4xl font-bold text-left"
className={clsx('text-3xl sm:text-4xl font-bold text-left', baseTextColor)}
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6}}
style={{color: primaryTextColor}}
>
{title}
</motion.h1>
{subtitle && (
<motion.p
className="mt-3 text-lg text-left"
className={clsx('mt-3 text-lg text-left', subtitleTextColor)}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6, delay: 0.2}}
style={{color: secondaryTextColor}}
>
{subtitle}
</motion.p>
)}
</div>
</div>
);
};
)
}
export default SmallHero;
export default SmallHero

View File

@@ -1,34 +0,0 @@
export const themeColors: Record<
"light" | "dark",
{
primaryBg: string;
secondaryBg: string;
navBg: string;
footerBg: string;
primaryText: string;
secondaryText: string;
inputFieldBg: string;
inputBorder: string;
}
> = {
light: {
primaryBg: "#F3F4F6",
secondaryBg: "#eff1f3",
navBg: "#F9FAFB",
footerBg: "#E5E7EB",
primaryText: "#1E293B",
secondaryText: "#475569",
inputFieldBg: "#ffffff",
inputBorder: "#cbd5e1",
},
dark: {
primaryBg: "#1A1A23",
secondaryBg: "#22222C",
navBg: "#2A2A35",
footerBg: "#1F1F29",
primaryText: "#F0F0F3",
secondaryText: "#C0C2CC",
inputFieldBg: "#2D2D38",
inputBorder: "#4B4B5A",
},
};

View File

@@ -1,45 +0,0 @@
'use client';
import React from "react";
import ContactCTA from "@/components/Home/Sections/ContactCTA";
import HomeServices from "@/components/Home/Sections/HomeServices";
import TechStack from "@/components/Home/Sections/TechStack";
import Section from "@/components/Section";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
import Hero from "@/components/Home/Sections/Hero";
const Home = () => {
const colors = useThemeColors();
return (
<motion.div
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.7, ease: "easeOut"}}
className="overflow-hidden"
>
<Section style={{backgroundColor: colors.primaryBg}}>
<Hero/>
</Section>
{/*<Section style={{backgroundColor: colors.secondaryBg}} shadow>*/}
{/* <About/>*/}
{/*</Section>*/}
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<HomeServices/>
</Section>
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
<TechStack/>
</Section>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<ContactCTA/>
</Section>
</motion.div>
);
};
export default Home;

View File

@@ -1,62 +0,0 @@
'use client';
import Link from 'next/link';
import {motion} from 'framer-motion';
import {FiArrowRight} from 'react-icons/fi';
import {useThemeColors} from '@/utils/useThemeColors';
type ContactCTAProps = {
title?: string;
description?: string;
buttonLabel?: string;
};
const ContactCTA = ({
title = "Interesse geweckt?",
description = "Lass uns über dein Projekt sprechen. Wir freuen uns darauf, deine Ideen in die Realität umzusetzen.",
buttonLabel = "Jetzt Kontakt aufnehmen",
}: ContactCTAProps) => {
const colors = useThemeColors();
return (
<section
className="relative w-full py-24 overflow-hidden transition-colors duration-700 ease-in-out"
style={{backgroundColor: colors.primaryBg, color: colors.primaryText}}
>
<div className="w-full max-w-4xl px-6 md:px-10 mx-auto text-center">
<motion.h2 className="text-3xl md:text-4xl font-bold">
{title}
</motion.h2>
<motion.p
className="mt-4 text-sm md:text-base max-w-xl mx-auto"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.2}}
>
{description}
</motion.p>
<motion.div
className="mt-8 flex justify-center"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.3}}
>
<Link href="/contact">
<button
className="inline-flex items-center gap-2 px-6 py-3 text-sm md:text-base font-semibold rounded-full bg-blue-700 hover:bg-blue-900 text-white shadow-md transition-all duration-300"
>
{buttonLabel} <FiArrowRight size={18}/>
</button>
</Link>
</motion.div>
</div>
</section>
);
};
export default ContactCTA;

View File

@@ -1,100 +0,0 @@
'use client';
import Image from 'next/image';
import {Typewriter} from 'react-simple-typewriter';
import {motion} from 'framer-motion';
const Hero = () => {
return (
<div
className="relative w-full pt-[4vh] md:pt-[12vh] h-screen flex flex-col overflow-hidden"
style={{
backgroundColor: 'var(--primary-bg)',
color: 'white',
}}
>
{/* Background Image */}
<div className="absolute inset-0 z-0">
<Image
src="/images/home_hero.jpg"
alt="Rhein river aerial view"
layout="fill"
objectFit="cover"
className="blur-md scale-105"
priority
/>
<div className="absolute inset-0 bg-black/40"/>
</div>
{/* Main content */}
<div className="relative z-10 flex justify-center flex-col w-[90%] sm:w-[80%] h-full mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 items-center gap-12">
{/* Text Content */}
<div>
<motion.h1
className="text-3xl sm:text-4xl md:text-5xl mt-6 mb-6 font-bold text-white"
initial={{opacity: 0, y: 30}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6, ease: 'easeOut'}}
>
Rhein-Software Development
</motion.h1>
<motion.p
className="text-lg md:text-xl text-gray-400"
initial={{opacity: 0, y: 30}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6, delay: 0.2, ease: 'easeOut'}}
>
Digitale Lösungen für dein Unternehmen.
</motion.p>
<motion.p
className="mt-4 text-lg md:text-xl font-semibold text-white"
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{delay: 0.9, duration: 0.6}}
>
<Typewriter
words={['Beratung', 'Entwicklung', 'Wartung', 'Fehlerbehebung']}
loop={true}
cursor
cursorStyle="_"
typeSpeed={60}
deleteSpeed={40}
delaySpeed={1500}
/>
</motion.p>
</div>
{/* Floating Image */}
<motion.div
className="hidden lg:block"
initial={{opacity: 0, y: 30}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.8, ease: 'easeOut', delay: 0.3}}
>
<motion.div
animate={{y: [0, -10, 0]}}
transition={{
duration: 4,
repeat: Infinity,
ease: 'easeInOut',
}}
className="animate-float"
>
<Image
src="/images/hero.png"
alt="hero graphic"
width={700}
height={700}
/>
</motion.div>
</motion.div>
</div>
</div>
</div>
);
};
export default Hero;

View File

@@ -1,105 +0,0 @@
'use client';
import {FiServer, FiTool, FiMonitor, FiZap, FiArrowRight} from 'react-icons/fi';
import {motion} from 'framer-motion';
import {useThemeColors} from '@/utils/useThemeColors';
const services = [
{
title: 'Beratung',
icon: <FiMonitor size={24}/>,
description:
'Strategische und technische Beratung rund um digitale Produkte und Prozesse. Wir analysieren bestehende Systeme, identifizieren Potenziale und helfen dir, die passende Architektur für dein Projekt zu finden.',
},
{
title: 'Entwicklung',
icon: <FiZap size={24}/>,
description:
'Individuelle Softwareentwicklung skalierbar, wartbar, zukunftssicher. Ob Web-App, API oder internes Tool: Wir setzen moderne Technologien ein, um genau die Lösung zu bauen, die du brauchst.',
},
{
title: 'Managed Services',
icon: <FiServer size={24}/>,
description:
'Wir betreuen Infrastruktur, Server und Systeme verlässlich und performant. Unser Team kümmert sich um Hosting, Monitoring, Backups und sorgt für einen reibungslosen Betrieb deiner Plattform.',
},
{
title: 'Fehlerbehebung',
icon: <FiTool size={24}/>,
description:
'Schnelle Hilfe bei Bugs, Performance-Problemen oder Sicherheitslücken. Wir analysieren Probleme, beheben sie gezielt und sorgen dafür, dass dein System wieder stabil läuft langfristig und zuverlässig.',
},
];
const HomeServices = () => {
const colors = useThemeColors();
return (
<section
className="w-full py-24 transition-colors duration-700 ease-in-out"
style={{backgroundColor: colors.primaryBg}}
>
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto" style={{color: colors.primaryText}}>
<motion.h2
className="text-3xl md:text-4xl font-bold mb-1 text-left transition-colors duration-700 ease-in-out"
>
Leistungen
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-10 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{services.map((service, index) => (
<motion.div
key={service.title}
className="p-6 rounded-xl border shadow-md transition-colors duration-700 ease-in-out"
style={{
backgroundColor: colors.secondaryBg,
borderColor: colors.secondaryBg,
color: colors.primaryText,
}}
whileHover={{
scale: 1.03,
boxShadow: '0px 10px 20px rgba(0,0,0,0.1)',
}}
initial={{opacity: 0, y: 30}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: index * 0.1}}
>
<div className="mb-3 text-blue-600">{service.icon}</div>
<h3 className="text-xl font-semibold mb-2">{service.title}</h3>
<p className="text-sm leading-relaxed transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}>
{service.description}
</p>
</motion.div>
))}
</div>
<motion.div
className="mt-10 flex justify-end"
initial={{opacity: 0}}
whileInView={{opacity: 1}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.3}}
>
<a
href="/services"
className="text-sm font-semibold text-blue-600 hover:underline flex items-center gap-1"
>
Weitere Leistungen <FiArrowRight size={16}/>
</a>
</motion.div>
</div>
</section>
);
};
export default HomeServices;

View File

@@ -1,139 +0,0 @@
'use client';
import Image from 'next/image';
import {motion} from 'framer-motion';
import {useThemeColors} from "@/utils/useThemeColors";
const techStack = {
row1: [
{
category: 'Programmiersprachen & Frameworks Backend',
items: [
{id: 'java', label: 'Java'},
{id: 'dart', label: 'Dart'},
{id: 'kotlin', label: 'Kotlin'},
{id: 'spring', label: 'Spring'},
],
},
{
category: 'Programmiersprachen & Frameworks Frontend',
items: [
{id: 'html', label: 'HTML'},
{id: 'css', label: 'CSS'},
{id: 'bootstrap', label: 'Bootstrap'},
{id: 'nextjs', label: 'Next.js'},
{id: 'typescript', label: 'TypeScript'},
{id: 'flutter', label: 'Flutter'},
],
},
],
row2: [
{
category: 'Betriebssysteme',
items: [
{id: 'macos', label: 'macOS'},
{id: 'debian', label: 'Debian'},
{id: 'ubuntu', label: 'Ubuntu'},
],
},
{
category: 'Version Control & Collaboration',
items: [
{id: 'gitlab', label: 'GitLab'},
{id: 'outline', label: 'Outline'},
],
},
{
category: 'DevOps & Infrastruktur',
items: [
{id: 'gitlab-ci', label: 'GitLab CI'},
{id: 'docker', label: 'Docker'},
{id: 'proxmox', label: 'Proxmox'},
],
},
],
};
const TechStack = () => {
const colors = useThemeColors();
return (
<section
className="w-full py-20 transition-colors duration-700 ease-in-out"
style={{backgroundColor: colors.secondaryBg}}
>
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto" style={{color: colors.primaryText}}>
<motion.h2
className="text-3xl md:text-4xl font-bold mb-1 text-left transition-colors duration-700 ease-in-out"
>
Technologien
</motion.h2>
<motion.div
className="w-12 h-[2px] mt-2 mb-6 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<motion.p
className="text-sm md:text-base mb-10 transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}
>
Mit diesen Technologien realisieren wir moderne, leistungsstarke Softwarelösungen.
</motion.p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
{techStack.row1.map((group, index) => (
<TechCard key={group.category} group={group} delay={index * 0.2} colors={colors}/>
))}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{techStack.row2.map((group, index) => (
<TechCard key={group.category} group={group} delay={index * 0.2 + 0.4} colors={colors}/>
))}
</div>
</div>
</section>
);
};
const TechCard = ({
group,
delay,
colors,
}: {
group: { category: string; items: { id: string; label: string }[] };
delay: number;
colors: ReturnType<typeof useThemeColors>;
}) => (
<motion.div
className="p-4 rounded-lg border shadow-md transition-colors duration-700 ease-in-out"
style={{
backgroundColor: colors.primaryBg,
borderColor: colors.primaryBg,
color: colors.primaryText,
}}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
whileHover={{scale: 1.03, boxShadow: '0 10px 20px rgba(0,0,0,0.1)'}}
viewport={{once: true}}
transition={{duration: 0.4, delay}}
>
<h3 className="text-base font-semibold mb-4">{group.category}</h3>
<div className="grid grid-cols-3 gap-3">
{group.items.map(({id, label}) => (
<div key={id} className="flex flex-col items-center text-center">
<Image src={`/images/svg/${id}.svg`} alt={label} width={32} height={32} className="object-contain"/>
<span className="text-[10px] mt-1 transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}>
{label}
</span>
</div>
))}
</div>
</motion.div>
);
export default TechStack;

View File

@@ -1,131 +0,0 @@
"use client";
import React, {useContext} from "react";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
import {motion} from "framer-motion";
const fadeInUp = {
hidden: {opacity: 0, y: 30},
visible: (i: number) => ({
opacity: 1,
y: 0,
transition: {
duration: 0.6,
delay: i * 0.2,
ease: "easeOut",
},
}),
};
const ImprintComp = () => {
const {theme} = useContext(ThemeContext);
const colors = themeColors[theme];
return (
<div
className="overflow-hidden transition-colors duration-500"
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
>
{/* Imprint Content */}
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12 space-y-10 text-base leading-relaxed">
{[
{
title: "Impressum",
content: (
<>
Thatsaphorn Atchariyaphap<br/>
Rhein-Software (Einzelunternehmer)<br/>
Mühlenstrasse 13<br/>
79664 Wehr
</>
),
},
{
title: "Kontakt",
content: (
<>
Telefon: +49 (0) 151 24003632<br/>
E-Mail:{" "}
<a
href="mailto:contact@rhein-software.dev"
className="underline text-blue-500"
>
contact@rhein-software.dev
</a>
</>
),
},
{
title: "EU-Streitschlichtung",
content: (
<>
Die Europäische Kommission stellt eine Plattform zur
Online-Streitbeilegung (OS) bereit:{" "}
<a
href="https://ec.europa.eu/consumers/odr/"
target="_blank"
rel="noopener noreferrer"
className="underline text-blue-500"
>
https://ec.europa.eu/consumers/odr/
</a>
.<br/>
Unsere E-Mail-Adresse finden Sie oben im Impressum.
</>
),
},
{
title:
"Verbraucherstreitbeilegung / Universalschlichtungsstelle",
content: (
<>
Wir sind nicht bereit oder verpflichtet, an
Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle teilzunehmen.
</>
),
},
].map((section, i) => (
<motion.div
key={section.title}
custom={i}
initial="hidden"
animate="visible"
variants={fadeInUp}
>
<h2 className="text-2xl font-bold">{section.title}</h2>
<motion.div
className="w-6 h-[2px] mt-2 mb-6 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<p>{section.content}</p>
</motion.div>
))}
<motion.p
className="text-sm text-gray-500"
custom={4}
initial="hidden"
animate="visible"
variants={fadeInUp}
>
Quelle:{" "}
<a
href="https://www.e-recht24.de"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
www.e-recht24.de
</a>
</motion.p>
</div>
</div>
);
};
export default ImprintComp;

View File

@@ -1,98 +0,0 @@
"use client";
import SmallHero from "@/components/Helper/SmallHero";
import React, {useContext, useEffect} from "react";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
import AOS from "aos";
const RevocationComp = () => {
const {theme} = useContext(ThemeContext);
const colors = themeColors[theme];
useEffect(() => {
AOS.init({
duration: 1000,
easing: "ease",
once: true,
anchorPlacement: "top-bottom",
});
}, []);
return (
<div className="overflow-hidden transition-colors duration-500"
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}>
{/* Hero Section */}
<div className="mt-[10vh]">
<SmallHero
title="Widerruf"
subtitle=""
backgroundImage="/images/contact.png"
/>
</div>
{/* Contact Form */}
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12">
<h2 className="text-2xl md:text-3xl font-bold text-center"
data-aos="fade-up"
data-aos-delay="400"
>
Schreib uns eine Nachricht
</h2>
<p data-aos="fade-up" data-aos-delay="600"
className="text-center mt-3 text-[var(--secondary-text)]">
Wir melden uns schnellstmöglich bei dir!
</p>
<form className="mt-8 max-w-2xl mx-auto space-y-6">
{/* Name & Email */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{["Dein Name", "Deine E-Mail"].map((label, index) => (
<div key={index} data-aos="fade-up" data-aos-delay={index * 100}>
<label className="block font-semibold">{label}</label>
<input
type={index === 0 ? "text" : "email"}
placeholder={index === 0 ? "Max Mustermann" : "max@example.com"}
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
style={{
backgroundColor: colors.inputFieldBg,
border: `1px solid ${colors.inputBorder}`,
color: colors.primaryText
}}
/>
</div>
))}
</div>
{/* Message */}
<div data-aos="fade-up" data-aos-delay="300">
<label className="block font-semibold">Deine Nachricht</label>
<textarea
rows={4}
placeholder="Schreibe deine Nachricht..."
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
style={{
backgroundColor: colors.inputFieldBg,
border: `1px solid ${colors.inputBorder}`,
color: colors.primaryText
}}
/>
</div>
{/* Submit Button */}
<div className="text-center" data-aos="fade-up" data-aos-delay="400">
<button
type="submit"
className="px-6 py-3 bg-blue-600 text-white text-lg font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all"
>
📩 Nachricht senden
</button>
</div>
</form>
</div>
</div>
);};
export default RevocationComp;

View File

@@ -1,99 +0,0 @@
"use client";
import SmallHero from "@/components/Helper/SmallHero";
import React, {useContext, useEffect} from "react";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
import AOS from "aos";
const TermsOfUseComp = () => {
const {theme} = useContext(ThemeContext);
const colors = themeColors[theme];
useEffect(() => {
AOS.init({
duration: 1000,
easing: "ease",
once: true,
anchorPlacement: "top-bottom",
});
}, []);
return (
<div className="overflow-hidden transition-colors duration-500"
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}>
{/* Hero Section */}
<div className="mt-[10vh]">
<SmallHero
title="AGB"
subtitle=""
backgroundImage="/images/contact.png"
/>
</div>
{/* Contact Form */}
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12">
<h2 className="text-2xl md:text-3xl font-bold text-center"
data-aos="fade-up"
data-aos-delay="400"
>
Schreib uns eine Nachricht
</h2>
<p data-aos="fade-up" data-aos-delay="600"
className="text-center mt-3 text-[var(--secondary-text)]">
Wir melden uns schnellstmöglich bei dir!
</p>
<form className="mt-8 max-w-2xl mx-auto space-y-6">
{/* Name & Email */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{["Dein Name", "Deine E-Mail"].map((label, index) => (
<div key={index} data-aos="fade-up" data-aos-delay={index * 100}>
<label className="block font-semibold">{label}</label>
<input
type={index === 0 ? "text" : "email"}
placeholder={index === 0 ? "Max Mustermann" : "max@example.com"}
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
style={{
backgroundColor: colors.inputFieldBg,
border: `1px solid ${colors.inputBorder}`,
color: colors.primaryText
}}
/>
</div>
))}
</div>
{/* Message */}
<div data-aos="fade-up" data-aos-delay="300">
<label className="block font-semibold">Deine Nachricht</label>
<textarea
rows={4}
placeholder="Schreibe deine Nachricht..."
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
style={{
backgroundColor: colors.inputFieldBg,
border: `1px solid ${colors.inputBorder}`,
color: colors.primaryText
}}
/>
</div>
{/* Submit Button */}
<div className="text-center" data-aos="fade-up" data-aos-delay="400">
<button
type="submit"
className="px-6 py-3 bg-blue-600 text-white text-lg font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all"
>
📩 Nachricht senden
</button>
</div>
</form>
</div>
</div>
);
};
export default TermsOfUseComp;

View File

@@ -1,110 +0,0 @@
"use client";
import {usePathname} from "next/navigation";
import Link from "next/link";
import React, {useContext, useEffect, useState} from "react";
import {HiBars3BottomRight} from "react-icons/hi2";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {useThemeColors} from "@/utils/useThemeColors";
import {navLinks} from "@/constant/Constant";
type Props = {
openNav: () => void;
};
const Nav = ({openNav}: Props) => {
const [navBg, setNavBg] = useState(false);
const [navHeight, setNavHeight] = useState("h-[10vh]");
const [contentSize, setContentSize] = useState("text-base md:text-lg");
const [buttonSize, setButtonSize] = useState("md:px-6 md:py-2 px-4 py-1 text-sm");
const {theme, toggleTheme} = useContext(ThemeContext);
const colors = useThemeColors();
const pathname = usePathname();
const navColorClass = theme === "dark" || !navBg ? "text-white" : "text-black";
useEffect(() => {
const handler = () => {
if (window.scrollY >= 90) {
setNavBg(true);
setNavHeight("h-[8vh]");
setContentSize("text-sm md:text-base");
setButtonSize("md:px-5 md:py-1.5 px-3 py-1 text-xs");
} else {
setNavBg(false);
setNavHeight("h-[10vh]");
setContentSize("text-base md:text-lg");
setButtonSize("md:px-6 md:py-2 px-4 py-1 text-sm");
}
};
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
}, []);
return (
<div
className={`fixed w-full transition-all duration-300 ease-in-out ${navHeight} z-[1000] ${
navBg ? "shadow-md" : ""
}`}
style={{backgroundColor: navBg ? colors.navBg : "transparent"}}
>
<div className="flex items-center h-full justify-between w-[90%] xl:w-[80%] mx-auto">
<Link href="/">
<h1 className={`${contentSize} font-bold cursor-pointer ${navColorClass}`}>
<span className="text-lg md:text-xl text-pink-700">R</span>hein Software
</h1>
</Link>
<div className="hidden lg:flex items-center space-x-6">
{navLinks.map((link) => (
<Link href={link.url} key={link.id}>
<p className={`relative group ${contentSize} uppercase ${getNavLinkClasses(pathname === link.url, navBg, theme, navColorClass)}`}>
{link.label}
{pathname !== link.url && (
<span
className="absolute bottom-0 left-0 w-full h-[2px] bg-current transform transition-transform duration-300 origin-right scale-x-0 group-hover:scale-x-100"/>
)}
</p>
</Link>
))}
</div>
<div className="flex items-center space-x-3">
<Link href="/contact">
<button
className={`${buttonSize} text-white font-semibold bg-blue-700 hover:bg-blue-900 rounded-full`}>
Kontakt
</button>
</Link>
<button
onClick={toggleTheme}
className={`w-7 h-7 flex items-center justify-center rounded-full ${navColorClass}`}
style={{backgroundColor: colors.secondaryBg}}
>
{theme === "dark" ? "🌙" : "☀️"}
</button>
<HiBars3BottomRight
onClick={openNav}
className={`w-6 h-6 cursor-pointer lg:hidden ${navColorClass}`}
/>
</div>
</div>
</div>
);
};
const getNavLinkClasses = (
isActive: boolean,
navBg: boolean,
theme: string,
navColorClass: string
): string => {
if (isActive) return !navBg ? "text-white font-bold" : `${navColorClass} font-bold`;
if (!navBg) return "text-white font-medium";
return theme === "dark" ? "text-gray-300 font-medium" : "text-gray-700 font-medium";
};
export default Nav;

View File

@@ -1,62 +0,0 @@
'use client';
import {navLinks} from "@/constant/Constant";
import Link from "next/link";
import React, {useContext} from "react";
import {CgClose} from "react-icons/cg";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {useThemeColors} from "@/utils/useThemeColors";
type Props = {
showNav: boolean;
closeNav: () => void;
};
const MobileNav = ({closeNav, showNav}: Props) => {
const navOpen = showNav ? "translate-y-0 opacity-100" : "-translate-y-20 opacity-0 pointer-events-none";
const {theme, toggleTheme} = useContext(ThemeContext);
const colors = useThemeColors();
const textClass = theme === "dark" ? "text-white" : "text-black";
return (
<div className="lg:hidden">
<div
className={`fixed inset-0 z-[10000] transition-opacity duration-500 ${
showNav ? "opacity-60 bg-black" : "opacity-0 pointer-events-none"
}`}
onClick={closeNav}
/>
<div
className={`fixed top-0 left-0 w-full z-[10006] transform ${navOpen} transition-all duration-500 ease-in-out shadow-md rounded-b-2xl`}
style={{backgroundColor: colors.navBg}}
>
<div className={`flex flex-col items-center justify-center py-8 space-y-4 px-4 relative ${textClass}`}>
<CgClose
onClick={closeNav}
className={`absolute top-4 right-6 sm:right-8 sm:w-7 sm:h-7 w-6 h-6 cursor-pointer p-1 ${textClass}`}
/>
{navLinks.map((link) => (
<Link href={link.url} key={link.id}>
<p className="nav__link uppercase text-[14px] sm:text-[16px] border-b pb-1 border-gray-400 transition-all duration-300 ease-in-out hover:scale-105">
{link.label}
</p>
</Link>
))}
<button
onClick={toggleTheme}
className={`mt-4 w-8 h-8 flex items-center justify-center rounded-full border border-gray-400 transition-all duration-300 ${textClass}`}
style={{backgroundColor: colors.secondaryBg}}
>
{theme === "dark" ? "🌙" : "☀️"}
</button>
</div>
</div>
</div>
);
};
export default MobileNav;

View File

@@ -1,24 +0,0 @@
"use client";
import React, {useState} from "react";
import DesktopNav from "./DesktopNav";
import MobileNav from "./MobileNav";
const Nav = () => {
const [showNav, setShowNav] = useState(false);
const handleNavShow = () => {
setShowNav(true);
};
const handleNavHide = () => {
setShowNav(false);
};
return (
<div>
<DesktopNav openNav={handleNavShow}/>
<MobileNav showNav={showNav} closeNav={handleNavHide}/>
</div>
);
};
export default Nav;

View File

@@ -0,0 +1,102 @@
'use client';
import React from 'react';
import Link from 'next/link';
import {usePathname, useRouter} from 'next/navigation';
import {Button} from '@/components/ui/button';
import {Sheet, SheetContent, SheetTrigger} from '@/components/ui/sheet';
import {Menu} from 'lucide-react';
import {ThemeToggle} from '@/components/theme-toggle';
const navLinks = [
{id: 'services', label: 'Leistungen'},
{id: 'about', label: 'Über uns'},
{id: 'process', label: 'Ablauf'},
{id: 'whyus', label: 'Warum wir'},
{id: 'referral', label: 'Empfehlung'},
{id: 'faq', label: 'FAQ'},
];
const Navbar = () => {
const pathname = usePathname();
const router = useRouter();
const handleNavClick = (id: string) => {
if (typeof window === 'undefined') return
if (pathname === '/') {
const el = document.getElementById(id)
if (el) {
el.scrollIntoView({behavior: 'smooth', block: 'start'})
}
} else {
localStorage.setItem('scrollToId', id)
router.push('/')
}
}
return (
<div className="w-full px-4 sm:px-6 lg:px-8 flex justify-center mt-4 z-50 fixed">
<header
className="bg-background/50 backdrop-blur-md border shadow-lg rounded-xl w-full max-w-screen-xl py-3 px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
<button
onClick={() => handleNavClick('start')}
className="text-xl font-bold cursor-pointer"
>
<span className="text-pink-600">R</span>hein Software
</button>
{/* Desktop nav */}
<nav className="hidden lg:flex items-center gap-6">
{navLinks.map((link) => (
<button
key={link.id}
onClick={() => handleNavClick(link.id)}
className="cursor-pointer text-sm font-medium text-muted-foreground hover:text-primary transition-colors"
>
{link.label}
</button>
))}
<Button asChild>
<Link href="/contact">Kontakt</Link>
</Button>
<ThemeToggle/>
</nav>
{/* Mobile nav */}
<div className="lg:hidden flex items-center gap-3">
<ThemeToggle/>
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" size="icon">
<Menu className="h-5 w-5"/>
</Button>
</SheetTrigger>
<SheetContent side="top" className="pt-10">
<div className="flex flex-col space-y-4 text-center">
{navLinks.map((link) => (
<button
key={link.id}
onClick={() => handleNavClick(link.id)}
className="cursor-pointer text-base font-semibold text-muted-foreground hover:text-primary transition-colors"
>
{link.label}
</button>
))}
<Button asChild className="mt-4 w-full">
<Link href="/contact">Kontakt</Link>
</Button>
</div>
</SheetContent>
</Sheet>
</div>
</div>
</header>
</div>
);
};
export default Navbar;

View File

@@ -0,0 +1,136 @@
'use client';
import React from 'react';
import Link from 'next/link';
import styled, {keyframes} from 'styled-components';
type PulsatingButtonProps = {
label: string;
href: string;
color?: string;
textColor?: string;
width?: number;
height?: number;
pulse?: boolean;
};
const MAX_LAYERS = 2;
const pulse = keyframes`
0%, 10% {
opacity: 0;
transform: scale(1.1);
}
80% {
opacity: 0.7;
transform: scale(1.15, 1.4);
}
81%, 100% {
opacity: 0;
transform: scale(1);
}
`;
const scale = keyframes`
0% {
transform: scale(1);
}
35%, 80% {
transform: scale(1.1, 1.35);
}
100% {
transform: scale(1);
}
`;
const Wrapper = styled.div`
position: relative;
display: inline-flex;
justify-content: center;
align-items: center;
`;
const Layer = styled.div<{
color: string;
$width: number;
$height: number;
$layer: number;
}>`
position: absolute;
background: transparent;
border: 1px solid ${({color, $layer}) =>
$layer === 0 ? `${color}AA` : `${color}66`}; // L0: ~67%, L1: ~40%
border-radius: 9999px;
animation: ${({$layer}) => ($layer ? pulse : scale)} 1.5s infinite;
width: ${({$width, $layer}) => $width + $layer * 8}px;
height: ${({$height, $layer}) => $height + $layer * 8}px;
z-index: ${({$layer}) => MAX_LAYERS - $layer};
`;
// Use $-prefix to avoid prop leaking
const StyledButton = styled.a.withConfig({
shouldForwardProp: (prop) =>
!['$bgColor', '$textColor', '$width', '$height'].includes(prop),
})<{
$bgColor: string;
$textColor: string;
$width: number;
$height: number;
}>`
position: relative;
z-index: ${MAX_LAYERS + 1};
display: inline-flex;
justify-content: center;
align-items: center;
background-color: ${({$bgColor}) => $bgColor};
color: ${({$textColor}) => $textColor};
border-radius: 9999px;
padding: 0.75rem 1.5rem;
font-weight: 600;
text-decoration: none;
transition: background-color 0.3s ease;
width: ${({$width}) => $width}px;
height: ${({$height}) => $height}px;
&:hover {
opacity: 0.9;
}
`;
const PulsatingButton: React.FC<PulsatingButtonProps> = ({
label,
href,
color = '#3B82F6', // Tailwind blue-500
textColor = '#ffffff',
width = 160,
height = 48,
pulse = true,
}) => {
return (
<Wrapper>
{pulse &&
Array.from({length: MAX_LAYERS}).map((_, index) => (
<Layer
key={index}
color={color}
$width={width}
$height={height}
$layer={index}
/>
))}
<Link href={href} passHref legacyBehavior>
<StyledButton
$bgColor={color}
$textColor={textColor}
$width={width}
$height={height}
>
{label}
</StyledButton>
</Link>
</Wrapper>
);
};
export default PulsatingButton;

View File

@@ -1,81 +0,0 @@
'use client';
import React, {useState} from 'react';
import {motion, AnimatePresence} from 'framer-motion';
import {FiZap, FiMonitor, FiServer, FiTool} from 'react-icons/fi';
import Development from "@/components/Services/Section/overview/Development";
import Consulting from "@/components/Services/Section/overview/Consulting";
import ManagedServices from "@/components/Services/Section/overview/ManagedServices";
import BugFixing from "@/components/Services/Section/overview/BugFixing";
import {useThemeColors} from "@/utils/useThemeColors";
const tabs = [
{key: 'entwicklung', label: 'Entwicklung', icon: <FiZap size={20}/>},
{key: 'beratung', label: 'Beratung', icon: <FiMonitor size={20}/>},
{key: 'services', label: 'Managed Services', icon: <FiServer size={20}/>},
{key: 'support', label: 'Fehlerbehebung', icon: <FiTool size={20}/>},
];
const tabContent: Record<string, React.ReactNode> = {
entwicklung: <Development/>,
beratung: <Consulting/>,
services: <ManagedServices/>,
support: <BugFixing/>,
};
const OverviewTabs = () => {
const [activeTab, setActiveTab] = useState("entwicklung");
const colors = useThemeColors();
return (
<div className="max-w-5xl mx-auto px-6 py-16 transition-theme text-left">
<h2 className="text-3xl font-bold mb-2" style={{color: colors.primaryText}}>
Was wir tun
</h2>
<motion.div
className="w-12 h-[2px] mb-6 bg-amber-500"
initial={{opacity: 0, x: -20}}
whileInView={{opacity: 1, x: 0}}
viewport={{once: true}}
transition={{duration: 0.4, delay: 0.1}}
/>
<p className="text-sm mb-10" style={{color: colors.secondaryText}}>
In diesem Abschnitt geben wir dir einen Überblick über unsere zentralen Leistungen von der technischen
Entwicklung über Beratung bis hin zu Betrieb und Support.
</p>
<div className="flex flex-wrap gap-4 mb-10">
{tabs.map((tab) => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className="flex-1 min-w-[150px] flex items-center justify-start gap-2 px-4 py-2 rounded-full border text-sm font-medium transition-all"
style={{
color: activeTab === tab.key ? '#ffffff' : colors.primaryText,
backgroundColor: activeTab === tab.key ? '#1D4ED8' : 'transparent',
borderColor: activeTab === tab.key ? '#1D4ED8' : colors.inputBorder,
}}
>
{tab.icon}
{tab.label}
</button>
))}
</div>
<AnimatePresence mode="wait">
<motion.div
key={activeTab}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -10}}
transition={{duration: 0.4}}
>
{tabContent[activeTab]}
</motion.div>
</AnimatePresence>
</div>
);
};
export default OverviewTabs;

View File

@@ -1,19 +0,0 @@
'use client';
import React from "react";
import SmallHero from "@/components/Helper/SmallHero";
const ServiceHero = () => {
return (
<div className="relative overflow-hidden">
<SmallHero
title="Unsere Leistungen"
subtitle="Wir bieten maßgeschneiderte Lösungen von der Beratung bis zum Betrieb."
backgroundImage="/images/contact.png"
blurBackground
/>
</div>
);
};
export default ServiceHero;

View File

@@ -1,59 +0,0 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
const BugFixing = () => {
const colors = useThemeColors();
return (
<div
className="w-[95%] mx-auto grid grid-cols-1 gap-6 mt-8 mb-16 text-left"
style={{color: colors.primaryText}}
>
<div>
<motion.h2
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
transition={{duration: 0.5}}
>
🐞 Fehlerbehebung & Optimierung
</motion.h2>
<motion.p
className="text-sm font-medium leading-7"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir analysieren und beheben Fehler in bestehenden Systemen, optimieren die Performance und sorgen
dafür, dass deine Software stabil und zuverlässig läuft.
</motion.p>
<motion.h3
className="mt-8 text-lg font-semibold"
initial={{opacity: 0}}
whileInView={{opacity: 1}}
transition={{duration: 0.4, delay: 0.2}}
>
🔎 Fokusbereiche
</motion.h3>
<ul
className="list-disc list-inside text-sm mt-4 space-y-1"
style={{color: colors.secondaryText}}
>
<li>Debugging & Troubleshooting</li>
<li>Performance-Analyse</li>
<li>Refactoring von Legacy-Code</li>
<li>Stabilitäts- und Sicherheitsupdates</li>
</ul>
</div>
</div>
);
};
export default BugFixing;

View File

@@ -1,60 +0,0 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
const Consulting = () => {
const colors = useThemeColors();
return (
<div
className="w-[95%] mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8 mb-16 text-left"
style={{color: colors.primaryText}}
>
<div>
<motion.h2
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
transition={{duration: 0.5}}
>
🧠 Technische Beratung
</motion.h2>
<motion.p
className="text-sm font-medium leading-7"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir unterstützen dich dabei, technische Entscheidungen fundiert zu treffen von der Auswahl
geeigneter Technologien bis hin zur Planung skalierbarer Architekturen. Gemeinsam finden wir den
effizientesten Weg von der Idee bis zur Umsetzung praxisnah, zielgerichtet und verständlich.
</motion.p>
<motion.h3
className="mt-8 text-lg font-semibold"
initial={{opacity: 0}}
whileInView={{opacity: 1}}
transition={{duration: 0.4, delay: 0.2}}
>
🔍 Themenbereiche
</motion.h3>
<ul
className="list-disc list-inside text-sm mt-4 space-y-1"
style={{color: colors.secondaryText}}
>
<li>Software-Architektur & Microservices</li>
<li>Technologie- und Framework-Auswahl</li>
<li>Prototyping & Machbarkeitsanalysen</li>
<li>Projektplanung und agile Methodik</li>
</ul>
</div>
</div>
);
};
export default Consulting;

View File

@@ -1,210 +0,0 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
import Image from "next/image";
const techStack = {
row1: [
{
category: 'Programmiersprachen & Frameworks Backend',
items: [
{id: 'java', label: 'Java'},
{id: 'dart', label: 'Dart'},
{id: 'kotlin', label: 'Kotlin'},
{id: 'spring', label: 'Spring'},
],
},
{
category: 'Programmiersprachen & Frameworks Frontend',
items: [
{id: 'html', label: 'HTML'},
{id: 'css', label: 'CSS'},
{id: 'bootstrap', label: 'Bootstrap'},
{id: 'nextjs', label: 'Next.js'},
{id: 'typescript', label: 'TypeScript'},
{id: 'flutter', label: 'Flutter'},
],
},
],
row2: [
{
category: 'Betriebssysteme',
items: [
{id: 'macos', label: 'macOS'},
{id: 'debian', label: 'Debian'},
{id: 'ubuntu', label: 'Ubuntu'},
],
},
{
category: 'Version Control & Collaboration',
items: [
{id: 'gitlab', label: 'GitLab'},
{id: 'outline', label: 'Outline'},
],
},
{
category: 'DevOps & Infrastruktur',
items: [
{id: 'gitlab-ci', label: 'GitLab CI'},
{id: 'docker', label: 'Docker'},
{id: 'proxmox', label: 'Proxmox'},
],
},
],
};
const TechCard = ({group}: {
group: { category: string; items: { id: string; label: string }[] };
}) => {
const colors = useThemeColors();
return (
<motion.div
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.4}}
className="p-4 rounded-lg border shadow-md transition-colors duration-700 ease-in-out"
style={{
backgroundColor: colors.primaryBg,
borderColor: colors.primaryBg,
color: colors.primaryText,
}}
>
<h3 className="text-base font-semibold mb-4">{group.category}</h3>
<div className="grid grid-cols-3 gap-3">
{group.items.map(({id, label}) => (
<div key={id} className="flex flex-col items-center text-center">
<Image
src={`/images/svg/${id}.svg`}
alt={label}
width={32}
height={32}
className="object-contain"
/>
<span
className="text-[10px] mt-1 transition-colors duration-700 ease-in-out"
style={{color: colors.secondaryText}}
>
{label}
</span>
</div>
))}
</div>
</motion.div>
);
};
const Development = () => {
const colors = useThemeColors();
return (
<div
className="w-[95%] mx-auto grid grid-cols-1 gap-6 mt-8 mb-16 text-left"
style={{color: colors.primaryText}}
>
<div>
<motion.h2
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5}}
>
💻 Full-Stack Entwicklung
</motion.h2>
<motion.p
className="text-sm font-medium leading-7"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir entwickeln individuelle Softwarelösungen von der nativen Mobile-App über moderne Webseiten bis
hin zu internen Tools.
Unser Fokus liegt auf skalierbaren Architekturen, performanten Frontends und wartbaren Backends.
<br/><br/>
Egal ob API-Entwicklung, Admin-Dashboard oder komplexe Plattform wir setzen moderne Technologien
gezielt ein, um robuste, zukunftssichere Anwendungen zu realisieren.
</motion.p>
<motion.div
className="mt-6 text-sm font-medium space-y-3 pl-2"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.3}}
>
<div className="flex items-center gap-2">
<span className="text-xs">🚀</span>
<p>Native Mobile-Apps mit Flutter</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs">🌐</span>
<p>Webseiten & Web-Portale mit Next.js</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs">🧩</span>
<p>Skalierbare Backends mit Spring Boot</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs">📊</span>
<p>Individuelle Dashboards & Admin-Panels</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs">🔌</span>
<p>API-Entwicklung (REST & GraphQL)</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs"></span>
<p>Automatisierte interne Tools</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs">📦</span>
<p>CI/CD & Container mit GitLab CI & Docker</p>
</div>
</motion.div>
<motion.h3
className="mt-10 text-lg font-semibold"
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{duration: 0.4, delay: 0.2}}
>
🔧 Unser Tech Stack im Überblick
</motion.h3>
<motion.p
className="text-sm font-medium mb-4"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.2}}
>
Mit diesem Stack entwickeln wir robuste, moderne Softwarelösungen abgestimmt auf deine
Anforderungen.
</motion.p>
<motion.div
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{duration: 0.5, delay: 0.1}}
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
{techStack.row1.map((group) => (
<TechCard key={group.category} group={group}/>
))}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{techStack.row2.map((group) => (
<TechCard key={group.category} group={group}/>
))}
</div>
</motion.div>
</div>
</div>
);
};
export default Development;

View File

@@ -1,62 +0,0 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
const ManagedServices = () => {
const colors = useThemeColors();
return (
<div
className="w-[95%] mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8 mb-16 text-left"
style={{color: colors.primaryText}}
>
<div>
<motion.h2
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5}}
>
🛠 Managed Services
</motion.h2>
<motion.p
className="text-sm font-medium leading-7"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir übernehmen den Betrieb und die Wartung deiner Anwendungen zuverlässig, sicher und skalierbar.
So kannst du dich voll auf dein Geschäft konzentrieren.
</motion.p>
<motion.h3
className="mt-8 text-lg font-semibold"
initial={{opacity: 0}}
animate={{opacity: 1}}
transition={{duration: 0.4, delay: 0.2}}
>
🧰 Leistungen
</motion.h3>
<motion.ul
className="list-disc list-inside text-sm mt-4 space-y-1"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.3}}
>
<li>Monitoring & Logging</li>
<li>Security Updates & Wartung</li>
<li>Cloud Deployment & Hosting</li>
<li>24/7 Systemüberwachung</li>
</motion.ul>
</div>
</div>
);
};
export default ManagedServices;

View File

@@ -1,38 +0,0 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
import ServiceHero from "@/components/Services/Section/ServiceHero";
import OverviewTabs from "@/components/Services/Section/OverviewTabs";
import ContactCTA from "@/components/Home/Sections/ContactCTA";
import Section from "@/components/Section";
const Home = () => {
const colors = useThemeColors();
return (
<motion.div
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.7, ease: "easeOut"}}
className="overflow-hidden"
>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<ServiceHero/>
</Section>
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
<OverviewTabs/>
</Section>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<ContactCTA
title="Nichts Passendes gefunden?"
description="Nimm einfach Kontakt mit uns auf gemeinsam finden wir die passende Lösung für dein Vorhaben."
buttonLabel="Kontakt aufnehmen"
/>
</Section>
</motion.div>
);
};
export default Home;

View File

@@ -1,50 +0,0 @@
'use client';
import {createContext, useEffect, useState} from "react";
import Cookies from "js-cookie";
type ThemeType = "light" | "dark";
export const ThemeContext = createContext<{
theme: ThemeType;
toggleTheme: () => void;
}>({
theme: "light",
toggleTheme: () => {
},
});
export const ThemeProvider = ({children}: { children: React.ReactNode }) => {
const [theme, setTheme] = useState<ThemeType | null>(null);
useEffect(() => {
const saved = Cookies.get("theme") as ThemeType | undefined;
if (saved === "dark" || saved === "light") {
setTheme(saved);
} else {
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const defaultTheme: ThemeType = prefersDark ? "dark" : "light";
setTheme(defaultTheme);
Cookies.set("theme", defaultTheme, {expires: 365});
}
}, []);
useEffect(() => {
if (!theme) return;
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
const toggleTheme = () => {
const next = theme === "dark" ? "light" : "dark";
setTheme(next);
Cookies.set("theme", next, {expires: 365});
};
if (!theme) return null;
return (
<ThemeContext.Provider value={{theme, toggleTheme}}>
{children}
</ThemeContext.Provider>
);
};

View File

@@ -0,0 +1,11 @@
"use client"
import * as React from "react"
import {ThemeProvider as NextThemesProvider} from "next-themes"
export function ThemeProvider({
children,
...props
}: Readonly<React.ComponentProps<typeof NextThemesProvider>>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View File

@@ -0,0 +1,26 @@
'use client';
import * as React from 'react';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const toggleTheme = () => {
setTheme(theme === 'dark' ? 'light' : 'dark');
};
return (
<Button
variant="outline"
size="icon"
onClick={toggleTheme}
aria-label="Toggle theme"
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:scale-100 dark:rotate-0" />
</Button>
);
}

View File

@@ -0,0 +1,57 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,201 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@@ -0,0 +1,128 @@
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}

View File

@@ -0,0 +1,140 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -0,0 +1,49 @@
export const techStack = {
row1: [
{
category: 'Programmiersprachen & Frameworks Backend',
items: [
{id: 'java', label: 'Java'},
{id: 'dart', label: 'Dart'},
{id: 'kotlin', label: 'Kotlin'},
{id: 'spring', label: 'Spring'},
],
},
{
category: 'Programmiersprachen & Frameworks Frontend',
items: [
{id: 'html', label: 'HTML'},
{id: 'css', label: 'CSS'},
{id: 'bootstrap', label: 'Bootstrap'},
{id: 'nextjs', label: 'Next.js'},
{id: 'typescript', label: 'TypeScript'},
{id: 'flutter', label: 'Flutter'},
],
},
],
row2: [
{
category: 'Betriebssysteme',
items: [
{id: 'macos', label: 'macOS'},
{id: 'debian', label: 'Debian'},
{id: 'ubuntu', label: 'Ubuntu'},
],
},
{
category: 'Version Control & Collaboration',
items: [
{id: 'gitlab', label: 'GitLab'},
{id: 'outline', label: 'Outline'},
],
},
{
category: 'DevOps & Infrastruktur',
items: [
{id: 'gitlab-ci', label: 'GitLab CI'},
{id: 'docker', label: 'Docker'},
{id: 'proxmox', label: 'Proxmox'},
],
},
],
};

6
frontend/lib/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))
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,16 +9,30 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-navigation-menu": "^1.2.13",
"@radix-ui/react-slot": "^1.2.3",
"aos": "^2.3.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.6.5",
"js-cookie": "^3.0.5",
"next": "15.1.7",
"lucide-react": "^0.523.0",
"next": "15.2.4",
"next-themes": "^0.4.6",
"nodemailer": "^6.10.1",
"prop-types": "^15.8.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.4.0",
"react-simple-typewriter": "^5.0.1"
"react-scroll": "^1.9.3",
"react-simple-typewriter": "^5.0.1",
"react-vertical-timeline-component": "^3.5.3",
"styled-components": "^6.1.19",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -30,6 +44,8 @@
"@types/nodemailer": "^6.4.17",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react-scroll": "^1.8.10",
"@types/react-vertical-timeline-component": "^3.3.6",
"autoprefixer": "^10.4.21",
"eslint": "^9",
"eslint-config-next": "15.1.7",

View File

@@ -1,12 +1,85 @@
import type { Config } from "tailwindcss";
export default {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./utils/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {},
plugins: [],
theme: {
extend: {
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
plugins: [require("tailwindcss-animate")],
} satisfies Config;

View File

@@ -1,10 +0,0 @@
'use client';
import {useContext} from "react";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
export const useThemeColors = () => {
const {theme} = useContext(ThemeContext);
return themeColors[theme];
};