Merge branch 'homepage-refactoring' into 'dev'

Homepage Refactoring - Pt. 1

See merge request rheinsw/website!25
This commit is contained in:
2025-04-08 21:03:41 +00:00
15 changed files with 263 additions and 106 deletions

View File

@@ -1,24 +1,29 @@
import type {Metadata} from "next"; import type {Metadata} from "next";
import "./globals.css"; import "../globals.css";
import Nav from "@/components/Home/Navbar/Nav"; import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Home/Footer/Footer"; import Footer from "@/components/Home/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider"; import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react"; import React from "react";
import {cookies} from "next/headers";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Rhein Software", title: "Rhein Software",
description: "Rhein Software Development", description: "Rhein Software Development",
}; };
export default function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
return ( return (
<html lang="de"> <html lang="de" data-theme={theme}>
<body className={'${font.className} antialiased'} style={{backgroundColor: "var(--primary-bg)"}}> <head/>
<body className="antialiased" style={{backgroundColor: "var(--primary-bg)"}}>
<ThemeProvider> <ThemeProvider>
<Nav/> <Nav/>
{children} {children}

View File

@@ -1,24 +1,29 @@
import type {Metadata} from "next"; import type {Metadata} from "next";
import '../(root)/globals.css'; import "../globals.css";
import Nav from "@/components/Home/Navbar/Nav"; import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Home/Footer/Footer"; import Footer from "@/components/Home/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider"; import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react"; import React from "react";
import {cookies} from "next/headers";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Rhein Software", title: "Rhein Software",
description: "Rhein Software Development", description: "Rhein Software Development",
}; };
export default function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
return ( return (
<html lang="de"> <html lang="de" data-theme={theme}>
<body className={'${font.className} antialiased'} style={{backgroundColor: "var(--primary-bg)"}}> <head/>
<body className="antialiased" style={{backgroundColor: "var(--primary-bg)"}}>
<ThemeProvider> <ThemeProvider>
<Nav/> <Nav/>
{children} {children}

View File

@@ -34,5 +34,15 @@
--footer-bg: #242424; /* Deep grey footer to add visual depth */ --footer-bg: #242424; /* Deep grey footer to add visual depth */
} }
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-6px);
}
}
.animate-float {
animation: float 3.5s ease-in-out infinite;
}

View File

@@ -1,24 +1,29 @@
import type {Metadata} from "next"; import type {Metadata} from "next";
import '../(root)/globals.css'; import "../globals.css";
import Nav from "@/components/Home/Navbar/Nav"; import Nav from "@/components/Navbar/Nav";
import Footer from "@/components/Home/Footer/Footer"; import Footer from "@/components/Home/Footer/Footer";
import {ThemeProvider} from "@/components/provider/ThemeProvider"; import {ThemeProvider} from "@/components/provider/ThemeProvider";
import React from "react"; import React from "react";
import {cookies} from "next/headers";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Rhein Software", title: "Rhein Software",
description: "Rhein Software Development", description: "Rhein Software Development",
}; };
export default function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
return ( return (
<html lang="de"> <html lang="de" data-theme={theme}>
<body className={'${font.className} antialiased'} style={{backgroundColor: "var(--primary-bg)"}}> <head/>
<body className="antialiased" style={{backgroundColor: "var(--primary-bg)"}}>
<ThemeProvider> <ThemeProvider>
<Nav/> <Nav/>
{children} {children}

35
app/services/layout.tsx Normal file
View File

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

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

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

View File

@@ -1,43 +1,82 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import {Typewriter} from "react-simple-typewriter";
const Hero = () => { const Hero = () => {
return ( return (
<div <div
className="relative w-full pt-[4vh] md:pt-[12vh] h-screen flex flex-col" className="relative w-full pt-[4vh] md:pt-[12vh] h-screen flex flex-col overflow-hidden"
style={{ style={{
backgroundColor: "var(--primary-bg)", backgroundColor: "var(--primary-bg)",
color: "var(--primary-text)", color: "white",
}} }}
> >
<div className="flex justify-center flex-col w-[90%] sm:w-[80%] h-full mx-auto"> {/* Background Image */}
<div className="absolute inset-0 z-0">
<Image
src="/images/home_hero.jpg"
alt="Rhein river aerial view"
layout="fill"
objectFit="cover"
className="blur-md scale-105"
priority
/>
<div className="absolute inset-0 bg-black/40"/>
</div>
{/* Main Content */}
<div className="relative z-10 flex justify-center flex-col w-[90%] sm:w-[80%] h-full mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 items-center gap-12"> <div className="grid grid-cols-1 lg:grid-cols-2 items-center gap-12">
{/* Text Content */} {/* Text Content */}
<div> <div>
<h1 <h1
data-aos="fade-up" data-aos="fade-up"
className="text-2xl sm:text-4xl md:text-5xl mt-6 mb-6 font-bold md:leading-[3rem] lg:leading-[3.5rem]" className="text-3xl sm:text-4xl md:text-5xl mt-6 mb-6 font-bold text-white"
> >
Rhein Software Development Rhein-Software Development
</h1> </h1>
<p data-aos="fade-up" data-aos-delay="200">
Wir entwickeln performante und zukunftssichere Software für deine Vision. <p
data-aos="fade-up"
data-aos-delay="200"
className="text-lg md:text-xl text-gray-400"
>
Digitale Lösungen für dein Unternehmen.
</p>
<p
data-aos="fade-up"
data-aos-delay="400"
className="mt-4 text-lg md:text-xl font-semibold text-white"
>
<Typewriter
words={["Beratung", "Entwicklung", "Wartung", "Fehlerbehebung"]}
loop={true}
cursor
cursorStyle="_"
typeSpeed={60}
deleteSpeed={40}
delaySpeed={1500} // ⬅️ Delay before it starts typing the FIRST word
/>
</p> </p>
</div> </div>
{/* Image Content */}
{/* Floating Image */}
<div <div
data-aos="fade-up" data-aos="fade-up"
data-aos-delay="400" data-aos-delay="400"
className="hidden lg:block" className="hidden lg:block animate-float"
> >
<Image src="/images/hero.png" alt="hero" width={700} height={700}/> <Image
src="/images/hero.png"
alt="hero graphic"
width={700}
height={700}
/>
</div> </div>
</div> </div>
</div> </div>
{/* Farbverlauf zum nächsten Bereich */}
{/*<div*/}
{/* className="absolute bottom-0 left-0 w-full h-20 bg-gradient-to-b from-[var(--primary-bg)] to-[var(--secondary-bg)]"*/}
{/*/>*/}
</div> </div>
); );
}; };

View File

@@ -1,43 +0,0 @@
import {navLinks} from "@/constant/Constant";
import Link from "next/link";
import React from "react";
import {CgClose} from "react-icons/cg";
type Props = {
showNav: boolean;
closeNav: () => void;
};
const MobileNav = ({closeNav, showNav}: Props) => {
const navOpen = showNav ? "translate-x-0" : "translate-x-[-100%]";
return (
<div>
{/* overlay */}
<div
className={`fixed ${navOpen} inset-0 transform transition-all duration-500 z-[10000] bg-black opacity-70 w-full h-screen`}
></div>
{/* Navlinks */}
<div
className={`text-white ${navOpen} fixed justify-center flex flex-col h-full transform transition-all duration-500 delay-300 w-[80%] sm:w-[60%] bg-indigo-900 space-y-6 z-[10006]`}
>
{navLinks.map((link) => {
return (
<Link href={link.url} key={link.id}>
<p className="nav__link text-white text-[20px] ml-12 border-b-[1.5px] pb-1 border-white sm:text-[30px]">
{link.label}
</p>
</Link>
);
})}
{/* Close icon */}
<CgClose
onClick={closeNav}
className="absolute top-[0.7rem] right-[1.4rem] sm:w-8 sm:h-8 w-6 h-6"
/>
</div>
</div>
);
};
export default MobileNav;

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import {usePathname} from "next/navigation";
import {navLinks} from "@/constant/Constant"; import {navLinks} from "@/constant/Constant";
import Link from "next/link"; import Link from "next/link";
import React, {useContext, useEffect, useState} from "react"; import React, {useContext, useEffect, useState} from "react";
@@ -18,6 +19,9 @@ const Nav = ({openNav}: Props) => {
const [buttonSize, setButtonSize] = useState("md:px-6 md:py-2 px-4 py-1 text-sm"); const [buttonSize, setButtonSize] = useState("md:px-6 md:py-2 px-4 py-1 text-sm");
const {theme, toggleTheme} = useContext(ThemeContext); const {theme, toggleTheme} = useContext(ThemeContext);
const colors = themeColors[theme]; const colors = themeColors[theme];
const pathname = usePathname();
const navColorClass = theme === "dark" || !navBg ? "text-white" : "text-black";
useEffect(() => { useEffect(() => {
const handler = () => { const handler = () => {
@@ -40,17 +44,17 @@ const Nav = ({openNav}: Props) => {
return ( return (
<div <div
className={`fixed ${navBg ? "shadow-md" : "fixed"} w-full transition-all duration-300 ease-in-out ${navHeight} z-[1000]`} className={`fixed w-full transition-all duration-300 ease-in-out ${navHeight} z-[1000] ${
navBg ? "shadow-md" : ""
}`}
style={{ style={{
backgroundColor: navBg ? "var(--nav-bg)" : "var(--primary-bg)", backgroundColor: navBg ? "var(--nav-bg)" : "transparent",
color: "var(--primary-text)",
}} }}
> >
<div <div
className="flex items-center h-full justify-between w-[90%] xl:w-[80%] mx-auto transition-all duration-300 ease-in-out"> className="flex items-center h-full justify-between w-[90%] xl:w-[80%] mx-auto transition-all duration-300 ease-in-out">
{/* LOGO */} {/* LOGO */}
<h1 className={`${contentSize} font-bold transition-all duration-300 ease-in-out`} <h1 className={`${contentSize} font-bold transition-all duration-300 ease-in-out ${navColorClass}`}>
style={{color: colors.primaryText}}>
<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>
@@ -58,26 +62,34 @@ const Nav = ({openNav}: Props) => {
<div className="hidden lg:flex items-center space-x-6 transition-all duration-300 ease-in-out"> <div className="hidden lg:flex items-center space-x-6 transition-all duration-300 ease-in-out">
{navLinks.map((link) => ( {navLinks.map((link) => (
<Link href={link.url} key={link.id}> <Link href={link.url} key={link.id}>
<p className={`nav_link ${contentSize} transition-all duration-300 ease-in-out`} <p
style={{color: colors.primaryText}}>{link.label}</p> className={`nav_link ${contentSize} uppercase transition-all duration-300 ease-in-out ${
pathname === link.url
? "text-white font-bold"
: "text-gray-300 font-medium"
}`}>
{link.label}
</p>
</Link> </Link>
))} ))}
</div> </div>
{/* Right Side Buttons */} {/* Right Side Buttons */}
<div className="flex items-center space-x-3 transition-all duration-300 ease-in-out"> <div className="flex items-center space-x-3 transition-all duration-300 ease-in-out">
{/* Portal Button */} {/* Contact Button */}
<Link href="/contact">
<button <button
className={`${buttonSize} text-white font-semibold bg-blue-700 hover:bg-blue-900 transition-all duration-300 ease-in-out rounded-full`} className={`${buttonSize} text-white font-semibold bg-blue-700 hover:bg-blue-900 transition-all duration-300 ease-in-out rounded-full`}
> >
Portal Kontakt
</button> </button>
</Link>
{/* Theme Toggle Button */} {/* Theme Toggle Button */}
<button <button
onClick={toggleTheme} onClick={toggleTheme}
className="w-7 h-7 flex items-center justify-center rounded-full transition-all duration-300 ease-in-out" className={`w-7 h-7 flex items-center justify-center rounded-full transition-all duration-300 ease-in-out ${navColorClass}`}
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}} style={{backgroundColor: colors.secondaryBg}}
> >
{theme === "dark" ? "🌙" : "☀️"} {theme === "dark" ? "🌙" : "☀️"}
</button> </button>
@@ -85,7 +97,7 @@ const Nav = ({openNav}: Props) => {
{/* Burger Menu (for mobile) */} {/* Burger Menu (for mobile) */}
<HiBars3BottomRight <HiBars3BottomRight
onClick={openNav} onClick={openNav}
className="w-6 h-6 cursor-pointer text-black lg:hidden transition-all duration-300 ease-in-out" className={`w-6 h-6 cursor-pointer lg:hidden transition-all duration-300 ease-in-out ${navColorClass}`}
/> />
</div> </div>
</div> </div>

View File

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

View File

@@ -11,7 +11,7 @@ export const navLinks = [
}, },
{ {
id: 3, id: 3,
url: 'contact', url: '/services',
label: 'Kontakt', label: 'Leistungen',
} }
]; ];

16
package-lock.json generated
View File

@@ -13,7 +13,8 @@
"next": "15.1.7", "next": "15.1.7",
"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",
"react-simple-typewriter": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@@ -5530,6 +5531,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-simple-typewriter": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-simple-typewriter/-/react-simple-typewriter-5.0.1.tgz",
"integrity": "sha512-vA5HkABwJKL/DJ4RshSlY/igdr+FiVY4MLsSQYJX6FZG/f1/VwN4y1i3mPXRyfaswrvI8xii1kOVe1dYtO2Row==",
"license": "MIT",
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View File

@@ -14,7 +14,8 @@
"next": "15.1.7", "next": "15.1.7",
"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",
"react-simple-typewriter": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",

BIN
public/images/home_hero.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB