Merge branch 'homepage-refactoring-pt4' into 'dev'

Homepage refactoring pt4

See merge request rheinsw/website!31
This commit is contained in:
2025-04-20 09:42:40 +00:00
parent 5e1bca87a3
commit f2221815c8
24 changed files with 797 additions and 211 deletions

37
app/about/layout.tsx Normal file
View File

@@ -0,0 +1,37 @@
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>
);
}

12
app/about/page.tsx Normal file
View File

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

View File

@@ -0,0 +1,47 @@
'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

@@ -0,0 +1,19 @@
'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

@@ -0,0 +1,68 @@
'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

@@ -0,0 +1,158 @@
'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

@@ -0,0 +1,94 @@
'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,97 @@
'use client';
import React from "react";
import Image from "next/image";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
const team = [
{
name: "Thatsaphorn",
role: "Gründer & Entwickler",
picture: "",
},
{
name: "Anonym",
role: "Vertrieb",
picture: "",
},
];
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">
<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}}
transition={{duration: 0.5}}
>
Das Team
</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}}
/>
<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)}`}
>
{team.map((member, idx) => (
<motion.div
key={member.name}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
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}}
>
<motion.div
whileHover={{scale: 1.05}}
transition={{type: "spring", stiffness: 300, damping: 20}}
className="w-28 h-28 relative mb-4"
>
<Image
src={member.picture || fallbackImage}
alt={member.name}
fill
sizes="112px"
className="rounded-full object-cover shadow"
/>
</motion.div>
<div className="h-px w-8 bg-gray-300 dark:bg-gray-600 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}}>
{member.role}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default TeamSection;

View File

@@ -1,99 +1,30 @@
"use client";
'use client';
import React, {useEffect, useContext} from "react";
import AOS from "aos";
import "aos/dist/aos.css";
import SmallHero from "@/components/Helper/SmallHero";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
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 {theme} = useContext(ThemeContext);
const colors = themeColors[theme];
useEffect(() => {
AOS.init({
duration: 1000,
easing: "ease",
once: true,
anchorPlacement: "top-bottom",
});
}, []);
const colors = useThemeColors();
return (
<div className="overflow-hidden transition-colors duration-500"
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}>
<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>
{/* Hero Section */}
<div className="mt-[10vh]">
<SmallHero
title="Kontakt"
subtitle="Hast du Fragen? Wir sind für dich da!"
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>
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
<ContactFormSection/>
</Section>
</motion.div>
);
};

View File

@@ -0,0 +1,101 @@
'use client';
import React from "react";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
const ContactFormSection = () => {
const colors = useThemeColors();
return (
<div className="w-full px-6 sm:px-12 py-20 text-left transition-theme">
<motion.h2
className="text-2xl sm:text-3xl font-bold mb-2"
style={{color: colors.primaryText}}
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5}}
>
Schreib uns eine Nachricht
</motion.h2>
<motion.p
className="text-sm mb-8 max-w-xl"
style={{color: colors.secondaryText}}
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.2}}
>
Wir freuen uns über dein Interesse und melden uns schnellstmöglich bei dir zurück.
</motion.p>
<form className="space-y-6">
<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.1}}
>
<label className="block font-semibold mb-1" style={{color: colors.primaryText}}>
{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,
}}
/>
</motion.div>
))}
</div>
<motion.div
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.3}}
>
<label className="block font-semibold mb-1" style={{color: colors.primaryText}}>
Deine Nachricht
</label>
<textarea
rows={4}
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,
}}
/>
</motion.div>
<motion.div
className="pt-4 flex justify-end"
initial={{opacity: 0, y: 10}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.5, delay: 0.4}}
>
<button
type="submit"
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"
>
📩 Nachricht senden
</button>
</motion.div>
</form>
</div>
);
};
export default ContactFormSection;

View File

@@ -0,0 +1,19 @@
'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

@@ -1,38 +1,56 @@
import React from "react";
'use client';
import React, {useContext} from "react";
import {ThemeContext} from "@/components/provider/ThemeProvider";
import {themeColors} from "@/components/Helper/ThemeColors";
import {motion} from "framer-motion";
type SmallHeroProps = {
title: string;
subtitle?: string;
backgroundImage?: string; // Optional background image
backgroundImage?: string;
blurBackground?: boolean;
};
const SmallHero = ({title, subtitle, backgroundImage}: SmallHeroProps) => {
const SmallHero = ({title, subtitle, backgroundImage, blurBackground}: SmallHeroProps) => {
const {theme} = useContext(ThemeContext);
const colors = themeColors[theme];
const primaryTextColor = backgroundImage ? "#ffffff" : colors.primaryText;
const secondaryTextColor = backgroundImage ? "rgba(255, 255, 255, 0.8)" : "#6B7280"; // Tailwind gray-500
return (
<div
className="w-full py-20 text-center flex flex-col items-center justify-center bg-cover bg-center"
style={{
backgroundColor: backgroundImage ? "transparent" : "var(--primary-bg)", // Fallback if no image
color: "var(--primary-text)",
backgroundImage: backgroundImage ? `url(${backgroundImage})` : "none",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundBlendMode: "overlay",
transition: "background-color 0.4s ease-in-out, color 0.4s ease-in-out",
}}
>
<h1 className="text-3xl sm:text-4xl font-bold"
data-aos="fade-up"
>
{title}
</h1>
{subtitle &&
<p className="mt-2 text-lg text-[var(--secondary-text)]"
data-aos="fade-up"
data-aos-delay="200"
<div className="relative w-full py-36 overflow-hidden">
{backgroundImage && 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">
<motion.h1
className="text-3xl sm:text-4xl font-bold text-left"
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6}}
style={{color: primaryTextColor}}
>
{subtitle}
</p>
}
{title}
</motion.h1>
{subtitle && (
<motion.p
className="mt-3 text-lg text-left"
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>
);
};

View File

@@ -1,7 +1,6 @@
'use client';
import React from "react";
import About from "@/components/Home/Sections/About";
import ContactCTA from "@/components/Home/Sections/ContactCTA";
import HomeServices from "@/components/Home/Sections/HomeServices";
import TechStack from "@/components/Home/Sections/TechStack";
@@ -24,9 +23,9 @@ const Home = () => {
<Hero/>
</Section>
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
<About/>
</Section>
{/*<Section style={{backgroundColor: colors.secondaryBg}} shadow>*/}
{/* <About/>*/}
{/*</Section>*/}
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<HomeServices/>

View File

@@ -5,7 +5,17 @@ import {motion} from 'framer-motion';
import {FiArrowRight} from 'react-icons/fi';
import {useThemeColors} from '@/utils/useThemeColors';
const ContactCTA = () => {
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 (
@@ -14,26 +24,21 @@ const ContactCTA = () => {
style={{backgroundColor: colors.primaryBg, color: colors.primaryText}}
>
<div className="w-full max-w-4xl px-6 md:px-10 mx-auto text-center">
{/* Headline */}
<motion.h2
className="text-3xl md:text-4xl font-bold transition-colors duration-700 ease-in-out"
>
Interesse geweckt?
<motion.h2 className="text-3xl md:text-4xl font-bold">
{title}
</motion.h2>
{/* Description */}
<motion.p
className="mt-4 text-sm md:text-base max-w-xl mx-auto transition-colors duration-700 ease-in-out"
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}}
>
Lass uns über dein Projekt sprechen. Wir freuen uns darauf, deine Ideen in die Realität umzusetzen.
{description}
</motion.p>
{/* CTA Button */}
<motion.div
className="mt-8 flex justify-center"
initial={{opacity: 0, y: 20}}
@@ -45,7 +50,7 @@ const ContactCTA = () => {
<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"
>
Jetzt Kontakt aufnehmen <FiArrowRight size={18}/>
{buttonLabel} <FiArrowRight size={18}/>
</button>
</Link>
</motion.div>

View File

@@ -51,9 +51,11 @@ const Nav = ({openNav}: Props) => {
style={{backgroundColor: navBg ? colors.navBg : "transparent"}}
>
<div className="flex items-center h-full justify-between w-[90%] xl:w-[80%] mx-auto">
<h1 className={`${contentSize} font-bold ${navColorClass}`}>
<span className="text-lg md:text-xl text-pink-700">R</span>hein Software
</h1>
<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) => (
@@ -70,14 +72,13 @@ const Nav = ({openNav}: Props) => {
</div>
<div className="flex items-center space-x-3">
{pathname !== "/contact" && (
<Link href="/contact">
<button
className={`${buttonSize} text-white font-semibold bg-blue-700 hover:bg-blue-900 rounded-full`}>
Kontakt
</button>
</Link>
)}
<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}`}

View File

@@ -1,9 +1,8 @@
// components/Leistungen/Section/OverviewTabs.tsx
'use client';
import React, {useState} from 'react';
import {motion, AnimatePresence} from 'framer-motion';
import {FiMonitor, FiUsers, FiSettings, FiTool} from 'react-icons/fi';
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";
@@ -11,53 +10,47 @@ import BugFixing from "@/components/Services/Section/overview/BugFixing";
import {useThemeColors} from "@/utils/useThemeColors";
const tabs = [
{key: 'entwicklung', label: 'Entwicklung', icon: <FiMonitor size={20}/>},
{key: 'beratung', label: 'Beratung', icon: <FiUsers size={20}/>},
{key: 'services', label: 'Managed Services', icon: <FiSettings size={20}/>},
{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/>
),
entwicklung: <Development/>,
beratung: <Consulting/>,
services: <ManagedServices/>,
support: <BugFixing/>,
};
const OverviewTabs = () => {
const [activeTab, setActiveTab] = useState("entwicklung");
const colors = useThemeColors();
return (
<div className="max-w-4xl mx-auto px-6 py-16 text-center transition-theme">
<h2
className="text-3xl font-bold mb-4"
style={{color: colors.primaryText}}
>
<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-8 bg-amber-500 mx-auto"
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 justify-center gap-4 mb-8 flex-wrap">
<div className="flex flex-wrap gap-4 mb-10">
{tabs.map((tab) => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className="flex items-center gap-2 px-4 py-2 rounded-full border text-sm font-medium transition-all"
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',

View File

@@ -1,29 +1,18 @@
// components/Leistungen/Section/Hero.tsx
'use client';
import React from "react";
import {motion} from "framer-motion";
import SmallHero from "@/components/Helper/SmallHero";
const ServiceHero = () => {
return (
<section className="py-24 text-center max-w-4xl mx-auto">
<motion.h1
className="text-4xl md:text-5xl font-bold mb-6"
initial={{opacity: 0, y: 20}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6}}
>
Unsere Leistungen
</motion.h1>
<motion.p
className="text-lg text-gray-500"
initial={{opacity: 0, y: 10}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.6, delay: 0.2}}
>
Wir bieten maßgeschneiderte Lösungen von der Beratung bis zum Betrieb.
</motion.p>
</section>
<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>
);
};

View File

@@ -1,7 +1,6 @@
'use client';
import React from "react";
import Image from "next/image";
import {motion} from "framer-motion";
import {useThemeColors} from "@/utils/useThemeColors";
@@ -10,7 +9,7 @@ const BugFixing = () => {
return (
<div
className="w-[95%] sm:w-[80%] mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8 mb-16"
className="w-[95%] mx-auto grid grid-cols-1 gap-6 mt-8 mb-16 text-left"
style={{color: colors.primaryText}}
>
<div>
@@ -22,6 +21,7 @@ const BugFixing = () => {
>
🐞 Fehlerbehebung & Optimierung
</motion.h2>
<motion.p
className="text-sm font-medium leading-7"
style={{color: colors.secondaryText}}
@@ -41,28 +41,17 @@ const BugFixing = () => {
>
🔎 Fokusbereiche
</motion.h3>
<ul className="list-disc list-inside text-sm mt-4 space-y-1" style={{color: colors.secondaryText}}>
<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 className="justify-self-center">
<motion.div
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
transition={{duration: 0.5, delay: 0.3}}
>
<Image
src="/images/bug_fixing.jpg"
alt="Bug fixing illustration"
width={300}
height={300}
className="object-contain"
/>
</motion.div>
</div>
</div>
);
};

View File

@@ -9,7 +9,7 @@ const Consulting = () => {
return (
<div
className="w-[95%] sm:w-[80%] mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8 mb-16 text-left"
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>

View File

@@ -101,7 +101,7 @@ const Development = () => {
return (
<div
className="w-[95%] sm:w-[80%] mx-auto grid grid-cols-1 gap-6 mt-8 mb-16 text-left"
className="w-[95%] mx-auto grid grid-cols-1 gap-6 mt-8 mb-16 text-left"
style={{color: colors.primaryText}}
>
<div>

View File

@@ -9,7 +9,7 @@ const ManagedServices = () => {
return (
<div
className="w-[95%] sm:w-[80%] mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8 mb-16 text-left"
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>

View File

@@ -25,7 +25,11 @@ const Home = () => {
<OverviewTabs/>
</Section>
<Section style={{backgroundColor: colors.primaryBg}} shadow>
<ContactCTA/>
<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>
);

View File

@@ -6,6 +6,11 @@ export const navLinks = [
},
{
id: 2,
url: '/about',
label: 'Über Uns',
},
{
id: 3,
url: '/services',
label: 'Leistungen',
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB