Compare commits
68 Commits
production
...
contact-fe
| Author | SHA1 | Date | |
|---|---|---|---|
| 8990548c6a | |||
| ddad30bcf8 | |||
| c497657093 | |||
| e5b40ba9f7 | |||
| 31df2409de | |||
| 055455db6e | |||
| 01492fbe18 | |||
| 3ecdde1455 | |||
| 21e83d6ee7 | |||
| 14de4c8d88 | |||
| ecef42837b | |||
| 5c168b4ceb | |||
| f2221815c8 | |||
|
|
5e1bca87a3 | ||
| 5970898bc9 | |||
| 5c60594a4f | |||
| 66cdadea16 | |||
| f7b17e239c | |||
| 14ec81f4c2 | |||
| d19a90ce9a | |||
| f0a401a9a4 | |||
| 6535bf37ec | |||
| 493d5bcfa2 | |||
| 5226cd1bd4 | |||
| 2d89fcfbb8 | |||
| 63062734d2 | |||
| c44c713925 | |||
| a5454882ed | |||
| c9eb4e3c42 | |||
| f581783abf | |||
| 4193e45592 | |||
| 35e3632011 | |||
| 82a43f48fd | |||
| 481aa0fe63 | |||
| 261354e96d | |||
| 0835299999 | |||
| 4fd4c0e53c | |||
| 576d784874 | |||
| 2ba3576be9 | |||
| a37ef7c199 | |||
| 5f492e2e63 | |||
| 1cc3f4857f | |||
| f5dc50a488 | |||
| abcdc926ba | |||
| 90e7fcced0 | |||
| 33192468a9 | |||
| 3ea1a264e9 | |||
| 668605522b | |||
| ee7cf14598 | |||
| 26837bf575 | |||
| 2eb936fddf | |||
| 3c25d1a351 | |||
| 6882e6b460 | |||
| 81729693bd | |||
| 1867d10b78 | |||
| 915d6d1bcb | |||
| 9125dd91d7 | |||
| 4566f2559f | |||
|
|
ce90c48825 | ||
| 91460f6f7a | |||
| 03c06d23ad | |||
|
|
b054401ab0 | ||
| 221b50c363 | |||
| b1cc973853 | |||
|
|
df69bd3aab | ||
| 2e0679732e | |||
| 35d5fa8884 | |||
|
|
6a401bfd63 |
37
app/about/layout.tsx
Normal file
37
app/about/layout.tsx
Normal 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
12
app/about/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import AboutContent from "@/components/About/AboutContent";
|
||||||
|
|
||||||
|
const AboutPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AboutContent/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AboutPage;
|
||||||
54
app/api/contact/route.ts
Normal file
54
app/api/contact/route.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import {NextRequest, NextResponse} from 'next/server';
|
||||||
|
|
||||||
|
const HCAPTCHA_SECRET = process.env.HCAPTCHA_SECRET ?? '';
|
||||||
|
const SHARED_API_KEY = process.env.SHARED_API_KEY ?? '';
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await req.json();
|
||||||
|
const origin = req.headers.get("origin") || "http://localhost:3000";
|
||||||
|
const captchaToken = body.captcha;
|
||||||
|
|
||||||
|
if (!captchaToken) {
|
||||||
|
return NextResponse.json({success: false, error: 'Captcha is required'}, {status: 400});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Verify hCaptcha token with their API
|
||||||
|
const verifyResponse = await fetch('https://api.hcaptcha.com/siteverify', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
secret: HCAPTCHA_SECRET,
|
||||||
|
response: captchaToken,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const captchaResult = await verifyResponse.json();
|
||||||
|
|
||||||
|
if (!captchaResult.success) {
|
||||||
|
return NextResponse.json({success: false, error: 'Captcha verification failed'}, {status: 403});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Forward valid contact request to Spring Boot backend
|
||||||
|
const backendRes = await fetch('http://localhost:8080/api/contact', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Origin": origin,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Frontend-Key': SHARED_API_KEY,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
const backendText = await backendRes.text();
|
||||||
|
|
||||||
|
if (!backendRes.ok) {
|
||||||
|
return NextResponse.json({success: false, error: backendText}, {status: backendRes.status});
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({success: true, message: backendText});
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('[ContactAPI] error:', err);
|
||||||
|
return NextResponse.json({success: false, error: err.message}, {status: 500});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImprintComp from "@/components/Legal/ImprintComp";
|
import ImprintComp from "@/components/Legal/Imprint/ImprintComp";
|
||||||
|
|
||||||
const ImprintPage = () => {
|
const ImprintPage = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PrivacyComp from "@/components/Legal/PrivacyComp";
|
import PrivacyComp from "@/components/Legal/Privacy/PrivacyComp";
|
||||||
|
|
||||||
const PrivacyPage = () => {
|
const PrivacyPage = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
47
components/About/AboutContent.tsx
Normal file
47
components/About/AboutContent.tsx
Normal 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;
|
||||||
19
components/About/Section/AboutHero.tsx
Normal file
19
components/About/Section/AboutHero.tsx
Normal 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;
|
||||||
68
components/About/Section/AboutIntro.tsx
Normal file
68
components/About/Section/AboutIntro.tsx
Normal 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;
|
||||||
158
components/About/Section/AboutProcess.tsx
Normal file
158
components/About/Section/AboutProcess.tsx
Normal 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>
|
||||||
|
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;
|
||||||
94
components/About/Section/AboutTimeline.tsx
Normal file
94
components/About/Section/AboutTimeline.tsx
Normal 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;
|
||||||
97
components/About/Section/TeamSection.tsx
Normal file
97
components/About/Section/TeamSection.tsx
Normal 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;
|
||||||
@@ -1,99 +1,30 @@
|
|||||||
"use client";
|
'use client';
|
||||||
|
|
||||||
import React, {useEffect, useContext} from "react";
|
import React from "react";
|
||||||
import AOS from "aos";
|
import {motion} from "framer-motion";
|
||||||
import "aos/dist/aos.css";
|
import {useThemeColors} from "@/utils/useThemeColors";
|
||||||
import SmallHero from "@/components/Helper/SmallHero";
|
import ContactHero from "@/components/Contact/Section/ContactHero";
|
||||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
import ContactFormSection from "@/components/Contact/Section/ContactFormSection";
|
||||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
import Section from "@/components/Section";
|
||||||
|
|
||||||
const Contact = () => {
|
const Contact = () => {
|
||||||
const {theme} = useContext(ThemeContext);
|
const colors = useThemeColors();
|
||||||
const colors = themeColors[theme];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
AOS.init({
|
|
||||||
duration: 1000,
|
|
||||||
easing: "ease",
|
|
||||||
once: true,
|
|
||||||
anchorPlacement: "top-bottom",
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden transition-colors duration-500"
|
<motion.div
|
||||||
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}>
|
initial={{opacity: 0, y: 20}}
|
||||||
|
animate={{opacity: 1, y: 0}}
|
||||||
{/* Hero Section */}
|
transition={{duration: 0.7, ease: "easeOut"}}
|
||||||
<div className="mt-[10vh]">
|
className="overflow-hidden"
|
||||||
<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
|
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||||
</h2>
|
<ContactHero/>
|
||||||
<p data-aos="fade-up" data-aos-delay="600"
|
</Section>
|
||||||
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">
|
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
|
||||||
{/* Name & Email */}
|
<ContactFormSection/>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
</Section>
|
||||||
{["Dein Name", "Deine E-Mail"].map((label, index) => (
|
</motion.div>
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
192
components/Contact/Section/ContactFormSection.tsx
Normal file
192
components/Contact/Section/ContactFormSection.tsx
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useThemeColors } from "@/utils/useThemeColors";
|
||||||
|
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||||
|
|
||||||
|
const ContactFormSection = () => {
|
||||||
|
const colors = useThemeColors();
|
||||||
|
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
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 isDev = process.env.NODE_ENV === "development";
|
||||||
|
const hCaptchaSiteKey = isDev
|
||||||
|
? "10000000-ffff-ffff-ffff-000000000001" // hCaptcha test sitekey
|
||||||
|
: "ES_ff59a664dc764f92870bf2c7b4eab7c5";
|
||||||
|
|
||||||
|
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("");
|
||||||
|
|
||||||
|
if (!captchaToken) {
|
||||||
|
setError("Bitte löse das CAPTCHA, um fortzufahren.");
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
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: "" });
|
||||||
|
} else {
|
||||||
|
const resJson = await res.json();
|
||||||
|
setError(resJson?.error || "Ein Fehler ist aufgetreten. Bitte versuche es später erneut.");
|
||||||
|
}
|
||||||
|
} catch (_err) {
|
||||||
|
setError("Serverfehler. Bitte versuche es später erneut.");
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
{submitted ? (
|
||||||
|
<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", required: true, placeholder: "Max Mustermann" },
|
||||||
|
{ label: "Deine E-Mail *", name: "email", type: "email", required: true, placeholder: "max@example.com" },
|
||||||
|
{ label: "Firmenname (optional)", name: "company", type: "text", required: false, placeholder: "Mustermann GmbH" },
|
||||||
|
{ label: "Telefonnummer (optional)", name: "phone", type: "tel", required: false, placeholder: "+49 123 456789" },
|
||||||
|
{ label: "Webseite (optional)", name: "website", type: "url", required: false, placeholder: "https://..." },
|
||||||
|
].map((field, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={field.name}
|
||||||
|
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 }}>
|
||||||
|
{field.label}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type={field.type}
|
||||||
|
name={field.name}
|
||||||
|
value={form[field.name as keyof typeof form]}
|
||||||
|
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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.6 }}
|
||||||
|
>
|
||||||
|
<label className="block font-semibold mb-1" style={{ color: colors.primaryText }}>
|
||||||
|
Deine Nachricht *
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
name="message"
|
||||||
|
rows={4}
|
||||||
|
required
|
||||||
|
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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<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.8 }}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
{loading ? "Sende..." : "📩 Nachricht senden"}
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContactFormSection;
|
||||||
19
components/Contact/Section/ContactHero.tsx
Normal file
19
components/Contact/Section/ContactHero.tsx
Normal 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;
|
||||||
@@ -36,13 +36,13 @@ const Footer = () => {
|
|||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
{/*<li>*/}
|
||||||
<Link href="/contact">
|
{/* <Link href="/contact">*/}
|
||||||
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
|
||||||
Zahlung und Versand
|
{/* Zahlung und Versand*/}
|
||||||
</p>
|
{/* </p>*/}
|
||||||
</Link>
|
{/* </Link>*/}
|
||||||
</li>
|
{/*</li>*/}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -50,20 +50,20 @@ const Footer = () => {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-white">Rechtliches</h3>
|
<h3 className="text-lg font-semibold text-white">Rechtliches</h3>
|
||||||
<ul className="mt-4 space-y-4 text-sm font-semibold text-gray-400">
|
<ul className="mt-4 space-y-4 text-sm font-semibold text-gray-400">
|
||||||
<li>
|
{/*<li>*/}
|
||||||
<Link href="/legal/terms-of-use">
|
{/* <Link href="/legal/terms-of-use">*/}
|
||||||
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
|
||||||
AGB
|
{/* AGB*/}
|
||||||
</p>
|
{/* </p>*/}
|
||||||
</Link>
|
{/* </Link>*/}
|
||||||
</li>
|
{/*</li>*/}
|
||||||
<li>
|
{/*<li>*/}
|
||||||
<Link href="/legal/revocation">
|
{/* <Link href="/legal/revocation">*/}
|
||||||
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
|
||||||
Widerruf
|
{/* Widerruf*/}
|
||||||
</p>
|
{/* </p>*/}
|
||||||
</Link>
|
{/* </Link>*/}
|
||||||
</li>
|
{/*</li>*/}
|
||||||
<li>
|
<li>
|
||||||
<Link href="/legal/privacy">
|
<Link href="/legal/privacy">
|
||||||
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
||||||
|
|||||||
@@ -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 = {
|
type SmallHeroProps = {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: 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 (
|
return (
|
||||||
|
<div className="relative w-full py-36 overflow-hidden">
|
||||||
|
{backgroundImage && blurBackground && (
|
||||||
<div
|
<div
|
||||||
className="w-full py-20 text-center flex flex-col items-center justify-center bg-cover bg-center"
|
className="absolute inset-0 bg-cover bg-center blur-sm scale-[1.05] z-0 will-change-transform"
|
||||||
style={{
|
style={{backgroundImage: `url(${backgroundImage})`}}
|
||||||
backgroundColor: backgroundImage ? "transparent" : "var(--primary-bg)", // Fallback if no image
|
/>
|
||||||
color: "var(--primary-text)",
|
)}
|
||||||
backgroundImage: backgroundImage ? `url(${backgroundImage})` : "none",
|
|
||||||
backgroundSize: "cover",
|
{/* Text content */}
|
||||||
backgroundPosition: "center",
|
<div className="relative z-10 px-6 sm:px-12 max-w-5xl mx-auto">
|
||||||
backgroundBlendMode: "overlay",
|
<motion.h1
|
||||||
transition: "background-color 0.4s ease-in-out, color 0.4s ease-in-out",
|
className="text-3xl sm:text-4xl font-bold text-left"
|
||||||
}}
|
initial={{opacity: 0, y: 20}}
|
||||||
>
|
animate={{opacity: 1, y: 0}}
|
||||||
<h1 className="text-3xl sm:text-4xl font-bold"
|
transition={{duration: 0.6}}
|
||||||
data-aos="fade-up"
|
style={{color: primaryTextColor}}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</motion.h1>
|
||||||
{subtitle &&
|
{subtitle && (
|
||||||
<p className="mt-2 text-lg text-[var(--secondary-text)]"
|
<motion.p
|
||||||
data-aos="fade-up"
|
className="mt-3 text-lg text-left"
|
||||||
data-aos-delay="200"
|
initial={{opacity: 0, y: 10}}
|
||||||
|
animate={{opacity: 1, y: 0}}
|
||||||
|
transition={{duration: 0.6, delay: 0.2}}
|
||||||
|
style={{color: secondaryTextColor}}
|
||||||
>
|
>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</p>
|
</motion.p>
|
||||||
}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import About from "@/components/Home/Sections/About";
|
|
||||||
import ContactCTA from "@/components/Home/Sections/ContactCTA";
|
import ContactCTA from "@/components/Home/Sections/ContactCTA";
|
||||||
import HomeServices from "@/components/Home/Sections/HomeServices";
|
import HomeServices from "@/components/Home/Sections/HomeServices";
|
||||||
import TechStack from "@/components/Home/Sections/TechStack";
|
import TechStack from "@/components/Home/Sections/TechStack";
|
||||||
@@ -24,9 +23,9 @@ const Home = () => {
|
|||||||
<Hero/>
|
<Hero/>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
|
{/*<Section style={{backgroundColor: colors.secondaryBg}} shadow>*/}
|
||||||
<About/>
|
{/* <About/>*/}
|
||||||
</Section>
|
{/*</Section>*/}
|
||||||
|
|
||||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||||
<HomeServices/>
|
<HomeServices/>
|
||||||
|
|||||||
@@ -5,7 +5,17 @@ import {motion} from 'framer-motion';
|
|||||||
import {FiArrowRight} from 'react-icons/fi';
|
import {FiArrowRight} from 'react-icons/fi';
|
||||||
import {useThemeColors} from '@/utils/useThemeColors';
|
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();
|
const colors = useThemeColors();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -14,26 +24,21 @@ const ContactCTA = () => {
|
|||||||
style={{backgroundColor: colors.primaryBg, color: colors.primaryText}}
|
style={{backgroundColor: colors.primaryBg, color: colors.primaryText}}
|
||||||
>
|
>
|
||||||
<div className="w-full max-w-4xl px-6 md:px-10 mx-auto text-center">
|
<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">
|
||||||
<motion.h2
|
{title}
|
||||||
className="text-3xl md:text-4xl font-bold transition-colors duration-700 ease-in-out"
|
|
||||||
>
|
|
||||||
Interesse geweckt?
|
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
<motion.p
|
<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}}
|
style={{color: colors.secondaryText}}
|
||||||
initial={{opacity: 0, y: 20}}
|
initial={{opacity: 0, y: 20}}
|
||||||
whileInView={{opacity: 1, y: 0}}
|
whileInView={{opacity: 1, y: 0}}
|
||||||
viewport={{once: true}}
|
viewport={{once: true}}
|
||||||
transition={{duration: 0.5, delay: 0.2}}
|
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>
|
</motion.p>
|
||||||
|
|
||||||
{/* CTA Button */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="mt-8 flex justify-center"
|
className="mt-8 flex justify-center"
|
||||||
initial={{opacity: 0, y: 20}}
|
initial={{opacity: 0, y: 20}}
|
||||||
@@ -45,7 +50,7 @@ const ContactCTA = () => {
|
|||||||
<button
|
<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"
|
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>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
131
components/Legal/Imprint/ImprintComp.tsx
Normal file
131
components/Legal/Imprint/ImprintComp.tsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
"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;
|
||||||
@@ -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 ImprintComp = () => {
|
|
||||||
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="Impressum"
|
|
||||||
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 ImprintComp;
|
|
||||||
145
components/Legal/Privacy/PrivacyComp.tsx
Normal file
145
components/Legal/Privacy/PrivacyComp.tsx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
"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 PrivacyComp = () => {
|
||||||
|
const {theme} = useContext(ThemeContext);
|
||||||
|
const colors = themeColors[theme];
|
||||||
|
|
||||||
|
const sections = [
|
||||||
|
{
|
||||||
|
title: "1. Datenschutz auf einen Blick",
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen
|
||||||
|
Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit
|
||||||
|
denen Sie persönlich identifiziert werden können.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten
|
||||||
|
finden Sie im Abschnitt „Hinweis zur Verantwortlichen Stelle“.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "2. Allgemeine Hinweise und Pflichtinformationen",
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen
|
||||||
|
Datenschutzvorschriften sowie dieser Datenschutzerklärung. Wir weisen darauf hin, dass die
|
||||||
|
Datenübertragung im Internet Sicherheitslücken aufweisen kann.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Verantwortlich für die Datenverarbeitung:
|
||||||
|
<br/>
|
||||||
|
Rhein-Software Development
|
||||||
|
<br/>
|
||||||
|
Mühlenstrasse 13
|
||||||
|
<br/>
|
||||||
|
79664 Wehr
|
||||||
|
<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>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "3. Ihre Rechte",
|
||||||
|
content: (
|
||||||
|
<p>
|
||||||
|
Sie haben das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung,
|
||||||
|
Datenübertragbarkeit sowie auf Widerspruch gegen die Verarbeitung Ihrer Daten. Zudem haben Sie
|
||||||
|
ein Beschwerderecht bei der zuständigen Aufsichtsbehörde.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "4. Analyse-Tools und Cookies",
|
||||||
|
content: (
|
||||||
|
<p>
|
||||||
|
Beim Besuch dieser Website kann Ihr Surfverhalten statistisch ausgewertet werden. Das geschieht
|
||||||
|
vor allem mit sogenannten Analyseprogrammen und Cookies. Detaillierte Informationen hierzu
|
||||||
|
entnehmen Sie bitte unserer vollständigen Datenschutzerklärung.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "5. Sicherheit",
|
||||||
|
content: (
|
||||||
|
<p>
|
||||||
|
Diese Seite nutzt eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen
|
||||||
|
Sie an der Adresszeile des Browsers („https://“) und dem Schloss-Symbol.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="overflow-hidden transition-colors duration-500"
|
||||||
|
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
|
||||||
|
>
|
||||||
|
{/* Privacy Content */}
|
||||||
|
<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>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrivacyComp;
|
||||||
@@ -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 PrivacyComp = () => {
|
|
||||||
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="Datenschutz"
|
|
||||||
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 PrivacyComp;
|
|
||||||
@@ -51,9 +51,11 @@ const Nav = ({openNav}: Props) => {
|
|||||||
style={{backgroundColor: navBg ? colors.navBg : "transparent"}}
|
style={{backgroundColor: navBg ? colors.navBg : "transparent"}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center h-full justify-between w-[90%] xl:w-[80%] mx-auto">
|
<div className="flex items-center h-full justify-between w-[90%] xl:w-[80%] mx-auto">
|
||||||
<h1 className={`${contentSize} font-bold ${navColorClass}`}>
|
<Link href="/">
|
||||||
|
<h1 className={`${contentSize} font-bold cursor-pointer ${navColorClass}`}>
|
||||||
<span className="text-lg md:text-xl text-pink-700">R</span>hein Software
|
<span className="text-lg md:text-xl text-pink-700">R</span>hein Software
|
||||||
</h1>
|
</h1>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className="hidden lg:flex items-center space-x-6">
|
<div className="hidden lg:flex items-center space-x-6">
|
||||||
{navLinks.map((link) => (
|
{navLinks.map((link) => (
|
||||||
@@ -70,14 +72,13 @@ const Nav = ({openNav}: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
{pathname !== "/contact" && (
|
|
||||||
<Link href="/contact">
|
<Link href="/contact">
|
||||||
<button
|
<button
|
||||||
className={`${buttonSize} text-white font-semibold bg-blue-700 hover:bg-blue-900 rounded-full`}>
|
className={`${buttonSize} text-white font-semibold bg-blue-700 hover:bg-blue-900 rounded-full`}>
|
||||||
Kontakt
|
Kontakt
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
className={`w-7 h-7 flex items-center justify-center rounded-full ${navColorClass}`}
|
className={`w-7 h-7 flex items-center justify-center rounded-full ${navColorClass}`}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// components/Leistungen/Section/OverviewTabs.tsx
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, {useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
import {motion, AnimatePresence} from 'framer-motion';
|
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 Development from "@/components/Services/Section/overview/Development";
|
||||||
import Consulting from "@/components/Services/Section/overview/Consulting";
|
import Consulting from "@/components/Services/Section/overview/Consulting";
|
||||||
import ManagedServices from "@/components/Services/Section/overview/ManagedServices";
|
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";
|
import {useThemeColors} from "@/utils/useThemeColors";
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{key: 'entwicklung', label: 'Entwicklung', icon: <FiMonitor size={20}/>},
|
{key: 'entwicklung', label: 'Entwicklung', icon: <FiZap size={20}/>},
|
||||||
{key: 'beratung', label: 'Beratung', icon: <FiUsers size={20}/>},
|
{key: 'beratung', label: 'Beratung', icon: <FiMonitor size={20}/>},
|
||||||
{key: 'services', label: 'Managed Services', icon: <FiSettings size={20}/>},
|
{key: 'services', label: 'Managed Services', icon: <FiServer size={20}/>},
|
||||||
{key: 'support', label: 'Fehlerbehebung', icon: <FiTool size={20}/>},
|
{key: 'support', label: 'Fehlerbehebung', icon: <FiTool size={20}/>},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tabContent: Record<string, React.ReactNode> = {
|
const tabContent: Record<string, React.ReactNode> = {
|
||||||
entwicklung: (
|
entwicklung: <Development/>,
|
||||||
<Development/>
|
beratung: <Consulting/>,
|
||||||
),
|
services: <ManagedServices/>,
|
||||||
beratung: (
|
support: <BugFixing/>,
|
||||||
<Consulting/>
|
|
||||||
),
|
|
||||||
services: (
|
|
||||||
<ManagedServices/>
|
|
||||||
),
|
|
||||||
support: (
|
|
||||||
<BugFixing/>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const OverviewTabs = () => {
|
const OverviewTabs = () => {
|
||||||
const [activeTab, setActiveTab] = useState("entwicklung");
|
const [activeTab, setActiveTab] = useState("entwicklung");
|
||||||
const colors = useThemeColors();
|
const colors = useThemeColors();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto px-6 py-16 text-center transition-theme">
|
<div className="max-w-5xl mx-auto px-6 py-16 transition-theme text-left">
|
||||||
<h2
|
<h2 className="text-3xl font-bold mb-2" style={{color: colors.primaryText}}>
|
||||||
className="text-3xl font-bold mb-4"
|
|
||||||
style={{color: colors.primaryText}}
|
|
||||||
>
|
|
||||||
Was wir tun
|
Was wir tun
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<motion.div
|
<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}}
|
initial={{opacity: 0, x: -20}}
|
||||||
whileInView={{opacity: 1, x: 0}}
|
whileInView={{opacity: 1, x: 0}}
|
||||||
viewport={{once: true}}
|
viewport={{once: true}}
|
||||||
transition={{duration: 0.4, delay: 0.1}}
|
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) => (
|
{tabs.map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
onClick={() => setActiveTab(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={{
|
style={{
|
||||||
color: activeTab === tab.key ? '#ffffff' : colors.primaryText,
|
color: activeTab === tab.key ? '#ffffff' : colors.primaryText,
|
||||||
backgroundColor: activeTab === tab.key ? '#1D4ED8' : 'transparent',
|
backgroundColor: activeTab === tab.key ? '#1D4ED8' : 'transparent',
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
// components/Leistungen/Section/Hero.tsx
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {motion} from "framer-motion";
|
import SmallHero from "@/components/Helper/SmallHero";
|
||||||
|
|
||||||
const ServiceHero = () => {
|
const ServiceHero = () => {
|
||||||
return (
|
return (
|
||||||
<section className="py-24 text-center max-w-4xl mx-auto">
|
<div className="relative overflow-hidden">
|
||||||
<motion.h1
|
<SmallHero
|
||||||
className="text-4xl md:text-5xl font-bold mb-6"
|
title="Unsere Leistungen"
|
||||||
initial={{opacity: 0, y: 20}}
|
subtitle="Wir bieten maßgeschneiderte Lösungen – von der Beratung bis zum Betrieb."
|
||||||
animate={{opacity: 1, y: 0}}
|
backgroundImage="/images/contact.png"
|
||||||
transition={{duration: 0.6}}
|
blurBackground
|
||||||
>
|
/>
|
||||||
Unsere Leistungen
|
</div>
|
||||||
</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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Image from "next/image";
|
|
||||||
import {motion} from "framer-motion";
|
import {motion} from "framer-motion";
|
||||||
import {useThemeColors} from "@/utils/useThemeColors";
|
import {useThemeColors} from "@/utils/useThemeColors";
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ const BugFixing = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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}}
|
style={{color: colors.primaryText}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
@@ -22,6 +21,7 @@ const BugFixing = () => {
|
|||||||
>
|
>
|
||||||
🐞 Fehlerbehebung & Optimierung
|
🐞 Fehlerbehebung & Optimierung
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
<motion.p
|
<motion.p
|
||||||
className="text-sm font-medium leading-7"
|
className="text-sm font-medium leading-7"
|
||||||
style={{color: colors.secondaryText}}
|
style={{color: colors.secondaryText}}
|
||||||
@@ -41,28 +41,17 @@ const BugFixing = () => {
|
|||||||
>
|
>
|
||||||
🔎 Fokusbereiche
|
🔎 Fokusbereiche
|
||||||
</motion.h3>
|
</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>Debugging & Troubleshooting</li>
|
||||||
<li>Performance-Analyse</li>
|
<li>Performance-Analyse</li>
|
||||||
<li>Refactoring von Legacy-Code</li>
|
<li>Refactoring von Legacy-Code</li>
|
||||||
<li>Stabilitäts- und Sicherheitsupdates</li>
|
<li>Stabilitäts- und Sicherheitsupdates</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const Consulting = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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}}
|
style={{color: colors.primaryText}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ const Development = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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}}
|
style={{color: colors.primaryText}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const ManagedServices = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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}}
|
style={{color: colors.primaryText}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ const Home = () => {
|
|||||||
<OverviewTabs/>
|
<OverviewTabs/>
|
||||||
</Section>
|
</Section>
|
||||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
<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>
|
</Section>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ export const navLinks = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
url: '/about',
|
||||||
|
label: 'Über Uns',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
url: '/services',
|
url: '/services',
|
||||||
label: 'Leistungen',
|
label: 'Leistungen',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ const compat = new FlatCompat({
|
|||||||
|
|
||||||
const eslintConfig = [
|
const eslintConfig = [
|
||||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
...compat.config({
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|||||||
64
package-lock.json
generated
64
package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"framer-motion": "^12.6.5",
|
"framer-motion": "^12.6.5",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"next": "15.1.7",
|
"next": "15.1.7",
|
||||||
|
"nodemailer": "^6.10.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
@@ -20,10 +21,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@hcaptcha/react-hcaptcha": "^1.12.0",
|
||||||
"@tailwindcss/postcss": "^4.0.17",
|
"@tailwindcss/postcss": "^4.0.17",
|
||||||
"@types/aos": "^3.0.7",
|
"@types/aos": "^3.0.7",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
@@ -47,6 +50,19 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||||
|
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.0.tgz",
|
||||||
@@ -218,6 +234,28 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hcaptcha/loader": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hcaptcha/loader/-/loader-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-fFQH6ApU/zCCl6Y1bnbsxsp1Er/lKX+qlgljrpWDeFcenpEtoP68hExlKSXECospzKLeSWcr06cbTjlR/x3IJA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@hcaptcha/react-hcaptcha": {
|
||||||
|
"version": "1.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.12.0.tgz",
|
||||||
|
"integrity": "sha512-QiHnQQ52k8SJJSHkc3cq4TlYzag7oPd4f5ZqnjVSe4fJDSlZaOQFtu5F5AYisVslwaitdDELPVLRsRJxiiI0Aw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.17.9",
|
||||||
|
"@hcaptcha/loader": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3.0",
|
||||||
|
"react-dom": ">= 16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
@@ -1268,6 +1306,16 @@
|
|||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/nodemailer": {
|
||||||
|
"version": "6.4.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
|
||||||
|
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.0.12",
|
"version": "19.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz",
|
||||||
@@ -5010,6 +5058,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nodemailer": {
|
||||||
|
"version": "6.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz",
|
||||||
|
"integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
|
||||||
|
"license": "MIT-0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
@@ -5643,6 +5700,13 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"framer-motion": "^12.6.5",
|
"framer-motion": "^12.6.5",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"next": "15.1.7",
|
"next": "15.1.7",
|
||||||
|
"nodemailer": "^6.10.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
@@ -21,10 +22,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@hcaptcha/react-hcaptcha": "^1.12.0",
|
||||||
"@tailwindcss/postcss": "^4.0.17",
|
"@tailwindcss/postcss": "^4.0.17",
|
||||||
"@types/aos": "^3.0.7",
|
"@types/aos": "^3.0.7",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
|||||||
BIN
public/images/team/default-avatar.jpg
Normal file
BIN
public/images/team/default-avatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Reference in New Issue
Block a user