diff --git a/app/error.module.css b/app/error.module.css
index c2b8c7a..7075712 100644
--- a/app/error.module.css
+++ b/app/error.module.css
@@ -34,7 +34,7 @@
}
.label {
- font-size: var(--font-size-small);
+ font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase;
diff --git a/app/globals.css b/app/globals.css
index 785b825..3ca7dfb 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -19,19 +19,12 @@ html.has-menu-open {
scrollbar-gutter: stable;
}
-html,
-body {
- max-width: 100vw;
-}
-
body {
min-height: 100%;
- display: flex;
- flex-direction: column;
color: var(--font-body-color);
background: var(--color-background);
font-family: var(--font-body);
- font-size: 1rem;
+ font-size: var(--font-size-base);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: var(--line-height-body);
@@ -40,19 +33,10 @@ body {
main {
width: 100%;
- max-width: 1280px;
- margin-inline: auto;
- padding: 0 var(--space-4);
}
@media (min-width: 768px) {
- main {
- padding: 0 var(--space-4);
- }
}
@media (min-width: 1024px) {
- main {
- padding: 0 var(--space-6);
- }
}
diff --git a/app/not-found.module.css b/app/not-found.module.css
index 32368c7..3e6ca0d 100644
--- a/app/not-found.module.css
+++ b/app/not-found.module.css
@@ -34,7 +34,7 @@
}
.label {
- font-size: var(--font-size-small);
+ font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase;
diff --git a/app/page.tsx b/app/page.tsx
index f9921a8..c7e1ede 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,9 +1,25 @@
+import AboutUsSection from "@/components/AboutUsSection";
+import BenefitsSection from "@/components/benefitsSection";
+import ContactCtaSection from "@/components/contact/ContactCtaSection";
+import { homepageFaq } from "@/components/faq/faqData";
+import FaqSection from "@/components/faq/FaqSection";
import HeroSection from "@/components/HeroSection";
+import IndustrySolutionsSection from "@/components/IndustrySolutionsSection";
+import ServiceSection from "@/components/ServiceSection";
export default function Home() {
return (
+
+
+
+
+
+
);
}
diff --git a/components/AboutUsData.ts b/components/AboutUsData.ts
new file mode 100644
index 0000000..2168926
--- /dev/null
+++ b/components/AboutUsData.ts
@@ -0,0 +1,32 @@
+export type AboutUsMember = {
+ name: string;
+ imageSrc: string;
+ imageAlt: string;
+};
+
+export type AboutUsData = {
+ title: string;
+ paragraphs: string[];
+ members: AboutUsMember[];
+};
+
+export const aboutUsData: AboutUsData = {
+ title: "Ihre Reinigungsfirma für professionelle Ansprüche",
+ paragraphs: [
+ "Basiclean ist ein junges Reinigungsunternehmen mit Fokus auf gewerbliche Kunden in Hannover und der umliegenden Region.",
+ "Unser Ziel ist es, Unternehmen mit planbarer Sauberkeit, klaren Abläufen und zuverlässiger Zusammenarbeit zu entlasten. Statt starrer Standards bieten wir individuelle Reinigungslösungen, abgestimmt auf Ihr Objekt und Ihre Anforderungen.",
+ "Persönlicher Kontakt, transparente Leistungen und geschulte Reinigungskräfte stehen dabei im Mittelpunkt.",
+ ],
+ members: [
+ {
+ name: "Abdulkabi Dastan",
+ imageSrc: "/images/portrait/abdulkabi-dastan.jpg",
+ imageAlt: "Porträt von Abdulkabi Dastan",
+ },
+ {
+ name: "Gökhan Özsayiner",
+ imageSrc: "/images/portrait/goekhan-oezsayiner.jpg",
+ imageAlt: "Porträt von Gökhan Özsayiner",
+ },
+ ],
+};
diff --git a/components/AboutUsSection.module.css b/components/AboutUsSection.module.css
new file mode 100644
index 0000000..453a985
--- /dev/null
+++ b/components/AboutUsSection.module.css
@@ -0,0 +1,120 @@
+.section {
+ padding-block: var(--space-16);
+}
+
+.inner {
+ width: 100%;
+ max-width: var(--container-width);
+ margin-inline: auto;
+ padding-inline: var(--space-4);
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-8);
+ justify-items: center;
+}
+
+.heading {
+ max-width: 20ch;
+ text-align: center;
+}
+
+.team {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: var(--space-4);
+ max-width: 420px;
+ width: 100%;
+}
+
+.card {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ align-items: center;
+}
+
+.imageWrap {
+ position: relative;
+ aspect-ratio: 4 / 5;
+ overflow: hidden;
+ border-radius: 20px;
+ background: #e9e9e9;
+ width: 100%;
+}
+
+.image {
+ object-fit: cover;
+}
+
+.name {
+ font-size: var(----font-size-xs);
+ color: var(--color-text);
+ text-align: center;
+}
+
+.textBlock {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-5);
+ align-items: center;
+}
+
+.text {
+ max-width: 56ch;
+ color: var(--color-text-muted);
+ font-size: var(----font-size-xs);
+ text-align: center;
+}
+
+@media (min-width: 768px) {
+ .section {
+ padding-block: var(--space-24);
+ }
+
+ .inner {
+ grid-template-columns: 1fr 1fr;
+ column-gap: var(--space-16);
+ row-gap: var(--space-8);
+ align-items: start;
+ justify-items: stretch;
+ }
+
+ .heading {
+ grid-column: 1 / -1;
+ text-align: left;
+ }
+
+ .team {
+ grid-column: 1;
+ grid-row: 2;
+ }
+
+ .card {
+ align-items: stretch;
+ }
+
+ .textBlock {
+ grid-column: 2;
+ grid-row: 2;
+ align-self: center;
+ align-items: flex-start;
+ }
+
+ .text {
+ text-align: left;
+ }
+}
+
+@media (min-width: 1024px) {
+ .section {
+ padding-block: var(--space-32);
+ }
+
+ .inner {
+ padding-inline: var(--space-6);
+ }
+
+ .team {
+ max-width: 460px;
+ }
+}
diff --git a/components/AboutUsSection.tsx b/components/AboutUsSection.tsx
new file mode 100644
index 0000000..6fbe0d9
--- /dev/null
+++ b/components/AboutUsSection.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import Image from "next/image";
+import styles from "./AboutUsSection.module.css";
+import { aboutUsData } from "./AboutUsData";
+
+export default function AboutUsSection() {
+ const { title, paragraphs, members } = aboutUsData;
+
+ return (
+
+
+
+ {title}
+
+
+
+ {members.map((member) => (
+
+
+
+
+
+ {member.name}
+
+ ))}
+
+
+
+ {paragraphs.map((paragraph, index) => (
+
+ {paragraph}
+
+ ))}
+
+
+
+ );
+}
diff --git a/components/Footer.module.css b/components/Footer.module.css
index 4016256..cdf785c 100644
--- a/components/Footer.module.css
+++ b/components/Footer.module.css
@@ -26,7 +26,7 @@
}
.footer__inner {
- max-width: 1280px;
+ max-width: var(--container-width);
margin-inline: auto;
padding: var(--footer-padding-y) clamp(1rem, 5vw, 2rem);
}
diff --git a/components/Header.tsx b/components/Header.tsx
index e69f3aa..285724e 100644
--- a/components/Header.tsx
+++ b/components/Header.tsx
@@ -1,5 +1,5 @@
import styles from "./Header.module.css";
-import Navbar from "./Navbar/Navbar";
+import Navbar from "./navbar/Navbar";
export default function Header() {
return (
diff --git a/components/HeroSection.module.css b/components/HeroSection.module.css
index 548f1bc..3f28fb9 100644
--- a/components/HeroSection.module.css
+++ b/components/HeroSection.module.css
@@ -1,15 +1,11 @@
.hero {
position: relative;
- background: linear-gradient(to bottom, #f9f9f9 5%, #ffffff 50%, #f9f9f9 100%);
+ background: var(--color-background-linear);
padding-block: var(--space-16);
- width: 100vw;
- margin-left: calc(50% - 50vw);
- margin-right: calc(50% - 50vw);
}
.inner {
- width: 100%;
- max-width: 1280px;
+ max-width: var(--container-width);
margin-inline: auto;
padding-inline: var(--space-4);
}
@@ -35,25 +31,30 @@
}
.heading {
- max-width: 20ch;
+ max-width: 15ch;
}
.lead {
- max-width: 56ch;
+ max-width: 40ch;
}
.actions {
display: flex;
flex-direction: column;
align-items: stretch;
- gap: var(--space-3);
+ gap: var(--space-4);
margin-top: var(--space-2);
}
.actions :global(.btn) {
width: fit-content;
- justify-content: center;
- min-height: 44px;
+ font-weight: var(--font-weight-semibold);
+}
+
+.actions :global(.btn--tertiary) {
+ padding-top: 0;
+ padding-bottom: 0;
+ color: var(--font-body-color);
}
/* Tablet */
@@ -67,7 +68,7 @@
top: 50%;
transform: translateY(-50%);
height: 90%;
- width:50%;
+ width: 50%;
}
.actions {
flex-direction: row;
@@ -75,6 +76,13 @@
align-items: center;
gap: var(--space-6);
}
+ .heading {
+ max-width: 24ch;
+ }
+
+ .lead {
+ max-width: 48ch;
+ }
}
/* Desktop */
diff --git a/components/HeroSection.tsx b/components/HeroSection.tsx
index 80e9e4d..2cf6eb2 100644
--- a/components/HeroSection.tsx
+++ b/components/HeroSection.tsx
@@ -14,11 +14,11 @@ export default function HeroSection() {
- Mehr Sauberkeit, weniger Aufwand für ein sauberes Arbeitsumfeld
+ Gebäudereinigung für ein makelloses Arbeitsumfeld
- Professionelle Gebäudereinigung für Büros, Praxen und Gewerbeflächen
- nach festen Abläufen und höchsten Qualitätsstandards.
+ Mit festen Abläufen und hohen Qualitätsstandards – für ein sauberes,
+ professionelles Arbeitsumfeld.
+
diff --git a/components/IndustrySolutionsSection.module.css b/components/IndustrySolutionsSection.module.css
new file mode 100644
index 0000000..80ba5e8
--- /dev/null
+++ b/components/IndustrySolutionsSection.module.css
@@ -0,0 +1,166 @@
+.section {
+ padding-block: var(--space-16);
+}
+
+.inner {
+ max-width: var(--container-width);
+ margin-inline: auto;
+ padding-inline: var(--space-4);
+}
+
+.intro {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--space-4);
+ margin-bottom: var(--space-8);
+ text-align: center;
+}
+
+.heading {
+ max-width: 18ch;
+}
+
+.lead {
+ max-width: 58ch;
+ color: var(--color-text-muted);
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: var(--space-4);
+ justify-content: center;
+}
+
+.card {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-4);
+ background-color: #e2ebe5;
+ color: var(--color-accent);
+ border-radius: var(--radius-lg);
+ padding: var(--space-6) var(--space-6);
+ text-decoration: none;
+ min-height: 88px;
+ transition:
+ background-color 0.25s ease,
+ color 0.25s ease,
+ box-shadow 0.25s ease;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
+}
+
+.card:hover,
+.card:focus-visible {
+ background-color: var(--color-accent);
+ color: #ffffff;
+}
+
+.cardText {
+ font-size: var(--font-size-body);
+ line-height: 1.2;
+ font-weight: var(--font-weight-semibold);
+ max-width: 18ch;
+}
+
+.iconWrap {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ border-radius: 50px;
+ background-color: var(--color-accent);
+ color: #ffffff;
+ transition:
+ background-color 0.25s ease,
+ color 0.25s ease;
+}
+
+.card:hover .iconWrap,
+.card:focus-visible .iconWrap {
+ background-color: #ffffff;
+ color: var(--color-accent);
+}
+
+.icon {
+ display: block;
+}
+
+@media (min-width: 768px) {
+ .section {
+ padding-block: var(--space-24);
+ }
+
+ .intro {
+ margin-bottom: var(--space-10);
+ }
+
+ .heading {
+ max-width: 18ch;
+ }
+
+ .grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .card {
+ min-height: 96px;
+ }
+
+ .card:nth-child(4),
+ .card:nth-child(5) {
+ grid-column: span 1;
+ }
+}
+
+@media (min-width: 1024px) {
+ .section {
+ padding-block: var(--space-24);
+ }
+
+ .inner {
+ padding-inline: var(--space-6);
+ }
+
+ .grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ max-width: 860px;
+ margin-inline: auto;
+ }
+
+ .card {
+ padding: var(--space-12) var(--space-6);
+ }
+
+ .card:nth-child(4) {
+ grid-column: 1 / 2;
+ justify-self: end;
+ width: 100%;
+ max-width: 100%;
+ }
+
+ .card:nth-child(5) {
+ grid-column: 2 / 3;
+ justify-self: start;
+ width: 100%;
+ max-width: 100%;
+ }
+}
+
+@media (hover: none) {
+ .card:hover,
+ .card:focus-visible {
+ background-color: #e2ebe5;
+ color: var(--color-accent);
+ transform: none;
+ }
+
+ .card:hover .iconWrap,
+ .card:focus-visible .iconWrap {
+ background-color: var(--color-accent);
+ color: #ffffff;
+ }
+}
diff --git a/components/IndustrySolutionsSection.tsx b/components/IndustrySolutionsSection.tsx
new file mode 100644
index 0000000..a227bcd
--- /dev/null
+++ b/components/IndustrySolutionsSection.tsx
@@ -0,0 +1,79 @@
+import Link from "next/link";
+import styles from "./IndustrySolutionsSection.module.css";
+import {
+ BuildingsIcon,
+ StethoscopeIcon,
+ WarehouseIcon,
+ HouseLineIcon,
+ ArrowRightIcon,
+} from "@phosphor-icons/react/dist/ssr";
+
+const industries = [
+ {
+ title: "Büros",
+ href: "/branchen/bueros",
+ icon: BuildingsIcon,
+ },
+ {
+ title: "Arztpraxen",
+ href: "/branchen/arztpraxen",
+ icon: StethoscopeIcon,
+ },
+ {
+ title: "Hallen und Lagerflächen",
+ href: "/branchen/hallen-lagerflaechen",
+ icon: WarehouseIcon,
+ },
+ {
+ title: "Wohn- und Geschäftshäuser",
+ href: "/branchen/wohn-geschaeftshaeuser",
+ icon: HouseLineIcon,
+ },
+ {
+ title: "Zum Kontaktformular",
+ href: "/kontakt",
+ icon: ArrowRightIcon,
+ },
+];
+
+export default function IndustrySolutionsSection() {
+ return (
+
+
+
+
+ Reinigungsleistungen für verschiedene Branchen
+
+
+ Als Reinigungsfirma betreuen wir Unternehmen aus unterschiedlichen
+ Branchen. Unsere Leistungen passen wir individuell an Nutzung,
+ Objektgröße und branchenspezifische Anforderungen an.
+
+
+
+
+ {industries.map((industry) => {
+ const Icon = industry.icon;
+
+ return (
+
+ {industry.title}
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/components/Navbar/Navbar.module.css b/components/Navbar/Navbar.module.css
index 28b5b53..894c8e1 100644
--- a/components/Navbar/Navbar.module.css
+++ b/components/Navbar/Navbar.module.css
@@ -211,7 +211,10 @@
padding: 24px 12px;
}
- .nav-links > li:first-child,
+ .nav-links > li:first-child {
+ padding: 0;
+ }
+
.nav-links > li:last-child {
padding: 0;
}
diff --git a/components/Navbar/NavbarSubMenu.module.css b/components/Navbar/NavbarSubMenu.module.css
index f838184..188c308 100644
--- a/components/Navbar/NavbarSubMenu.module.css
+++ b/components/Navbar/NavbarSubMenu.module.css
@@ -104,6 +104,20 @@
box-shadow: 0 4px 6px -6px rgb(0 0 0 / 30%);
opacity: 0;
transform: translateY(-8px);
+ transition:
+ max-height 300ms ease,
+ opacity 300ms ease,
+ transform 300ms ease,
+ visibility 0s linear 300ms;
+ background-color: var(--nav-bg);
+ }
+
+ .nav-submenu.open {
+ transition:
+ max-height 300ms ease,
+ opacity 300ms ease,
+ transform 300ms ease,
+ visibility 0s linear 0s;
}
.nav-submenu-list li a:focus-visible {
diff --git a/components/ServiceSection.module.css b/components/ServiceSection.module.css
new file mode 100644
index 0000000..f409a63
--- /dev/null
+++ b/components/ServiceSection.module.css
@@ -0,0 +1,137 @@
+.services {
+ padding-block: var(--space-16);
+}
+
+.inner {
+ max-width: var(--container-width);
+ margin-inline: auto;
+ padding-inline: var(--space-4);
+}
+
+.intro {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-4);
+ margin-bottom: var(--space-8);
+}
+
+.heading,
+.lead {
+ text-align: center;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ justify-content: center;
+ gap: var(--space-8);
+}
+
+.card {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+ background: var(--color-surface);
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.05);
+}
+
+.imageWrap {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 16 / 7;
+ background: var(--color-surface-alt);
+ overflow: hidden;
+ border-bottom: 4px solid var(--color-accent);
+}
+
+.image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+.cardBody {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: var(--space-5);
+ padding: var(--space-5);
+}
+
+.cardContent {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+}
+
+.cardTitle {
+ font-size: var(--font-size-s);
+ background-color: var(--color-accent);
+ width: fit-content;
+ padding: 8px;
+ color: #ffffff;
+ letter-spacing: var(--letter-spacing-normal);
+ border-radius: 4px;
+ font-weight: var(--font-weight-regular);
+}
+
+.cardText {
+ max-width: 34ch;
+ color: var(--color-text-muted);
+ font-size: var(--font-size-xs);
+}
+
+.cardBody :global(.btn) {
+ width: fit-content;
+ padding: 0;
+ color: var(--font-body-color);
+ font-weight: var(--font-weight-semibold);
+}
+
+/* Tablet */
+@media (min-width: 768px) {
+ .services {
+ padding-block: var(--space-24);
+ }
+
+ .intro {
+ margin-bottom: var(--space-10);
+ }
+ .heading {
+ max-width: 22ch;
+ text-align: left;
+ }
+
+ .lead {
+ max-width: 62ch;
+ text-align: left;
+ }
+
+ /* .card {
+ max-width: 100%;
+ } */
+
+ .grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+/* Desktop */
+@media (min-width: 1024px) {
+ .services {
+ padding-block: var(--space-24);
+ }
+
+ .inner {
+ padding-inline: var(--space-6);
+ }
+
+ .grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ align-items: stretch;
+ }
+}
diff --git a/components/ServiceSection.tsx b/components/ServiceSection.tsx
new file mode 100644
index 0000000..2742ca5
--- /dev/null
+++ b/components/ServiceSection.tsx
@@ -0,0 +1,94 @@
+import { ArrowRightIcon } from "@phosphor-icons/react/dist/ssr";
+import Link from "next/link";
+import styles from "./ServiceSection.module.css";
+import Image from "next/image";
+
+const services = [
+ {
+ title: "Grundreinigung",
+ description:
+ "Regelmäßige Reinigung von Arbeitsplätzen, Besprechungsräumen und Gemeinschaftsbereichen für einen gepflegten Eindruck im Alltag.",
+ image: "/images/services/grundreinigung.jpg",
+ imageAlt: "Sauberer Büroarbeitsplatz nach der Reinigung",
+ href: "/leistungen/bueroreinigung",
+ },
+ {
+ title: "Glas- und Fensterreinigung",
+ description:
+ "Streifenfreie Reinigung von Fenstern, Glaselementen und Eingangsbereichen für einen professionellen ersten Eindruck.",
+ image: "/images/services/Fensterreinigung.jpg",
+ imageAlt: "Große gereinigte Fensterfront eines Geschäftsgebäudes",
+ href: "/leistungen/glasreinigung",
+ },
+ {
+ title: "Treppenhausreinigung",
+ description:
+ "Saubere Eingangs- und Treppenbereiche als Visitenkarte für Mitarbeitende, Kundschaft und Besucher.",
+ image: "/images/services/treppenhausreinigung.jpg",
+ imageAlt: "Sauberes Treppenhaus in einem Gewerbeobjekt",
+ href: "/leistungen/treppenhausreinigung",
+ },
+ {
+ title: "Unterhaltsreinigung",
+ description:
+ "Individuell abgestimmte Reinigungsintervalle für dauerhaft gepflegte Büro-, Praxis- und Gewerbeflächen.",
+ image: "/images/services/unterhaltsreinigung.jpg",
+ imageAlt: "Reinigungskraft bei der Unterhaltsreinigung in einem Büro",
+ href: "/leistungen/unterhaltsreinigung",
+ },
+ {
+ title: "Hausmeisterdienste",
+ description:
+ "Hausmeisterdienste für Kleinreparaturen bis zur Objektbetreuung – für reibungslose Abläufe.",
+ image: "/images/services/Hausmeisterdienste.jpg",
+ imageAlt: "Hausmeisterdienste für Gartenarbeit",
+ href: "/leistungen/Hausmeisterdienste",
+ },
+];
+
+export default function ServiceSection() {
+ return (
+
+
+
+
+ Professionelle Reinigung für Büros, Praxen und Geschäftsräume
+
+
+ Individuelle Reinigungskonzepte für Unternehmen – damit Ihre Räume sauber, zuverlässig und professions sorgenfrei bleiben.
+
+
+
+
+ {services.map((service) => (
+
+
+
+
+
+
+
+
{service.title}
+
{service.description}
+
+
+
+ Mehr
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/components/benefitsSection.module.css b/components/benefitsSection.module.css
new file mode 100644
index 0000000..6f7f97e
--- /dev/null
+++ b/components/benefitsSection.module.css
@@ -0,0 +1,122 @@
+.benefits {
+ background: var(--color-background-linear);
+ padding-block: var(--space-16);
+}
+
+.inner {
+ max-width: var(--container-width);
+ margin-inline: auto;
+ padding-inline: var(--space-4);
+}
+
+/* Mobile: Intro oben, Grid unten */
+.intro {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-4);
+ margin-bottom: var(--space-8);
+}
+
+.heading,
+.lead {
+ text-align: center;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: var(--space-5);
+}
+
+.card {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ gap: var(--space-4);
+ background: var(--color-surface);
+ border-radius: 12px;
+ padding: var(--space-5);
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.05);
+}
+
+.iconWrap {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ color: var(--color-accent);
+}
+
+.cardContent {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+}
+
+.cardTitle {
+ font-size: var(--font-size-base);
+ font-weight: var(--font-weight-semibold);
+ color: var(--font-heading-color);
+}
+
+.cardText {
+ color: var(--color-text-muted);
+ font-size: var(--font-size-xs);
+ max-width: 34ch;
+}
+
+/* Tablet */
+@media (min-width: 768px) {
+ .benefits {
+ padding-block: var(--space-24);
+ }
+
+ .intro {
+ margin-bottom: var(--space-10);
+ }
+
+ .heading {
+ max-width: 16ch;
+ text-align: left;
+ }
+
+ .lead {
+ max-width: 48ch;
+ text-align: left;
+ }
+
+ .grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+/* Desktop: Intro links, Grid rechts — nebeneinander */
+@media (min-width: 1024px) {
+ .benefits {
+ padding-block: var(--space-36);
+ }
+
+ .inner {
+ display: grid;
+ grid-template-columns: 280px 1fr;
+ gap: var(--space-16);
+ align-items: start;
+ padding-inline: var(--space-6);
+ }
+
+ .intro {
+ position: sticky;
+ top: var(--space-8);
+ margin-bottom: 0;
+ }
+
+ .heading {
+ text-align: left;
+ }
+
+ .lead {
+ text-align: left;
+ }
+}
diff --git a/components/benefitsSection.tsx b/components/benefitsSection.tsx
new file mode 100644
index 0000000..e74b15c
--- /dev/null
+++ b/components/benefitsSection.tsx
@@ -0,0 +1,83 @@
+import {
+ UsersThreeIcon,
+ GraduationCapIcon,
+ ClockIcon,
+ SealCheckIcon,
+ LeafIcon,
+ CurrencyCircleDollarIcon,
+} from "@phosphor-icons/react/dist/ssr";
+import styles from "./benefitsSection.module.css";
+
+const benefits = [
+ {
+ icon: UsersThreeIcon,
+ title: "Feste Ansprechpartner",
+ description:
+ "Keine wechselnden Zuständigkeiten – wir sind immer persönlich für Sie da.",
+ },
+ {
+ icon: GraduationCapIcon,
+ title: "Geschultes Personal",
+ description:
+ "Unsere Teams werden kontinuierlich in modernen Reinigungstechniken geschult.",
+ },
+ {
+ icon: ClockIcon,
+ title: "Flexible Zeiten",
+ description:
+ "Wir reinigen dann, wenn es Ihren Betriebsablauf am wenigsten stört.",
+ },
+ {
+ icon: SealCheckIcon,
+ title: "Höchste Qualität",
+ description:
+ "Strenges Qualitätsmanagement durch regelmäßige Objektkontrollen.",
+ },
+ {
+ icon: LeafIcon,
+ title: "Umweltfreundlich",
+ description:
+ "Einsatz von biologisch abbaubaren Reinigungsmitteln für die Umwelt.",
+ },
+ {
+ icon: CurrencyCircleDollarIcon,
+ title: "Transparente Kosten",
+ description:
+ "Klare Preisgestaltung ohne versteckte Nebenkosten für Ihre Planung.",
+ },
+];
+
+export default function BenefitsSection() {
+ return (
+
+
+
+
+ Ihre Vorteile mit uns
+
+
+ Wir verstehen die Anforderungen moderner Unternehmen an einen
+ Facility-Partner: Verlässlichkeit, Professionalität und Transparenz.
+
+
+
+
+ {benefits.map((benefit) => {
+ const Icon = benefit.icon;
+ return (
+
+
+
+
+
+
{benefit.title}
+
{benefit.description}
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/components/contact/ContactCtaData.ts b/components/contact/ContactCtaData.ts
new file mode 100644
index 0000000..b4cc7fe
--- /dev/null
+++ b/components/contact/ContactCtaData.ts
@@ -0,0 +1,48 @@
+import type { ElementType } from "react";
+import {
+ PhoneIcon,
+ EnvelopeSimpleIcon,
+ ClockIcon,
+} from "@phosphor-icons/react/dist/ssr";
+
+export type ContactInfo = {
+ icon: ElementType;
+ label: string;
+ href?: string;
+ obfuscated?: {
+ user?: string;
+ domain?: string;
+ tel?: string;
+ };
+};
+
+export type ContactCtaData = {
+ heading: string;
+ lead: string;
+ buttonLabel: string;
+ buttonHref: string;
+ contacts: ContactInfo[];
+};
+
+export const contactCtaData: ContactCtaData = {
+ heading: "Lassen Sie uns über Ihre Anforderungen sprechen",
+ lead: "Wir erstellen ein individuelles Reinigungskonzept für Ihr Unternehmen in Hannover und Umgebung. Unverbindlich, transparent und exakt auf Ihre Anforderung abgestimmt.",
+ buttonLabel: "Angebot anfragen",
+ buttonHref: "/kontakt",
+ contacts: [
+ {
+ icon: PhoneIcon,
+ label: "0172 621 0606",
+ obfuscated: { tel: "01726210606" },
+ },
+ {
+ icon: EnvelopeSimpleIcon,
+ label: "info@basiclean.de",
+ obfuscated: { user: "info", domain: "basiclean.de" },
+ },
+ {
+ icon: ClockIcon,
+ label: "Mo. - Fr. 08:00 - 17:00 Uhr",
+ },
+ ],
+};
diff --git a/components/contact/ContactCtaSection.module.css b/components/contact/ContactCtaSection.module.css
new file mode 100644
index 0000000..9d27a52
--- /dev/null
+++ b/components/contact/ContactCtaSection.module.css
@@ -0,0 +1,108 @@
+.section {
+ background: #ffffff;
+ padding-bottom: var(--space-16);
+}
+
+.inner {
+ max-width: var(--container-width);
+ margin-inline: auto;
+ padding-inline: var(--space-4);
+}
+
+.card {
+ background-color: var(--color-accent);
+ border-radius: var(--radius-lg);
+ padding: var(--space-8) var(--space-6);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-6);
+}
+
+.top {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-6);
+ align-items: flex-start;
+}
+
+.top :global(.btn) {
+ font-weight: var(--font-weight-semibold);
+}
+
+.text {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+}
+
+.heading {
+ color: #ffffff;
+ max-width: 24ch;
+}
+
+.lead {
+ color: rgba(255, 255, 255);
+ font-size: var(--font-size-xs);
+ max-width: 72ch;
+}
+
+.divider {
+ border: none;
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
+ margin: 0;
+}
+
+.contactRow {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+}
+
+.contactItem {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ color: #ffffff;
+ font-size: var(--font-size-xs);
+}
+
+@media (min-width: 768px) {
+ .section {
+ padding-bottom: var(--space-24);
+ }
+
+ .card {
+ padding: var(--space-10) var(--space-10);
+ }
+
+ .top {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-8);
+ }
+
+ .contactRow {
+ flex-direction: row;
+ gap: var(--space-8);
+ flex-wrap: wrap;
+ }
+}
+
+@media (min-width: 1024px) {
+ .inner {
+ padding-inline: var(--space-6);
+ }
+
+ .card {
+ padding: var(--space-12) var(--space-12);
+ }
+}
+
+@media (hover: none) {
+ .button:hover,
+ .button:focus-visible {
+ background: #ffffff;
+ box-shadow: none;
+ }
+}
diff --git a/components/contact/ContactCtaSection.tsx b/components/contact/ContactCtaSection.tsx
new file mode 100644
index 0000000..ab73207
--- /dev/null
+++ b/components/contact/ContactCtaSection.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import Link from "next/link";
+import { useEffect, useRef } from "react";
+import { ArrowRightIcon } from "@phosphor-icons/react/dist/ssr";
+import styles from "./ContactCtaSection.module.css";
+import { contactCtaData, type ContactInfo } from "./ContactCtaData";
+
+function ObfuscatedLink({ item }: { item: ContactInfo }) {
+ const ref = useRef
(null);
+
+ useEffect(() => {
+ if (!ref.current) return;
+
+ if (item.obfuscated?.tel) {
+ ref.current.href = `tel:${item.obfuscated.tel}`;
+ } else if (item.obfuscated?.user && item.obfuscated?.domain) {
+ ref.current.href = `mailto:${item.obfuscated.user}@${item.obfuscated.domain}`;
+ }
+ }, [item.obfuscated]);
+
+ const Icon = item.icon;
+
+ return (
+
+
+ {item.label}
+
+ );
+}
+
+export default function ContactCtaSection() {
+ const { heading, lead, buttonLabel, buttonHref, contacts } = contactCtaData;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {buttonLabel}
+
+
+
+
+
+
+ {contacts.map((item: ContactInfo) =>
+ item.obfuscated ? (
+
+ ) : (
+
+
+ {item.label}
+
+ ),
+ )}
+
+
+
+
+ );
+}
diff --git a/components/faq/FaqSection.module.css b/components/faq/FaqSection.module.css
new file mode 100644
index 0000000..9489caf
--- /dev/null
+++ b/components/faq/FaqSection.module.css
@@ -0,0 +1,137 @@
+.section {
+ background: var(--color-background-linear);
+ padding-block: var(--space-16);
+}
+
+.inner {
+ max-width: var(--container-width);
+ margin-inline: auto;
+ padding-inline: var(--space-4);
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-8);
+ align-items: center;
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.heading {
+ max-width: 11ch;
+ text-align: left;
+ color: var(--color-accent);
+}
+
+.accordion {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+}
+
+.item {
+ width: 100%;
+}
+
+.itemHeading {
+ margin: 0;
+}
+
+.trigger {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-4);
+ width: 100%;
+ min-height: 52px;
+ padding: var(--space-3) var(--space-5);
+ background: var(--color-accent);
+ color: #ffffff;
+ border: 0;
+ border-radius: 9999px;
+ text-align: left;
+ cursor: pointer;
+}
+
+.trigger:hover,
+.trigger:focus-visible {
+ background: #1f6152;
+}
+
+.triggerText {
+ font-size: var(--font-size-s);
+ font-weight: var(--font-weight-medium);
+ line-height: 1.2;
+}
+
+.iconWrap {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 24px;
+ height: 24px;
+ transition: transform 0.25s ease;
+}
+
+.trigger[aria-expanded="true"] .iconWrap {
+ transform: rotate(180deg);
+}
+
+.panel {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.4s ease;
+}
+
+.panelInner {
+ padding: var(--space-4) var(--space-5) var(--space-2);
+}
+
+.answer {
+ max-width: 52ch;
+ color: var(--color-text-muted);
+ font-size: var(----font-size-xs);
+ line-height: 1.5;
+}
+
+@media (min-width: 768px) {
+ .section {
+ padding-block: var(--space-24);
+ }
+
+ .inner {
+ grid-template-columns: 280px 1fr;
+ gap: var(--space-16);
+ }
+
+ .trigger {
+ min-height: 56px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .section {
+ padding-block: var(--space-36);
+ }
+
+ .inner {
+ padding-inline: var(--space-6);
+ }
+
+ .accordion {
+ max-width: 560px;
+ justify-self: end;
+ width: 100%;
+ }
+}
+
+@media (hover: none) {
+ .trigger:hover,
+ .trigger:focus-visible {
+ background: var(--color-accent);
+ box-shadow: 0 4px 14px rgba(38, 115, 97, 0.14);
+ }
+}
diff --git a/components/faq/FaqSection.tsx b/components/faq/FaqSection.tsx
new file mode 100644
index 0000000..a88f4e1
--- /dev/null
+++ b/components/faq/FaqSection.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+import { useId, useRef, useState } from "react";
+import { CaretDownIcon } from "@phosphor-icons/react/dist/ssr";
+import styles from "./FaqSection.module.css";
+
+export type FaqItem = {
+ question: string;
+ answer: string;
+};
+
+type FaqSectionProps = {
+ title: string;
+ items: FaqItem[];
+};
+
+type AccordionItemProps = {
+ item: FaqItem;
+ isOpen: boolean;
+ buttonId: string;
+ panelId: string;
+ onToggle: () => void;
+};
+
+function AccordionItem({
+ item,
+ isOpen,
+ buttonId,
+ panelId,
+ onToggle,
+}: AccordionItemProps) {
+ const panelRef = useRef(null);
+
+ const handleToggle = () => {
+ const panel = panelRef.current;
+ if (!panel) return;
+
+ if (panel.style.maxHeight) {
+ panel.style.maxHeight = "";
+ } else {
+ panel.style.maxHeight = panel.scrollHeight + "px";
+ }
+
+ onToggle();
+ };
+
+ return (
+
+
+
+ {item.question}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default function FaqSection({ title, items }: FaqSectionProps) {
+ const baseId = useId();
+ const [openIndex, setOpenIndex] = useState(null);
+
+ return (
+
+
+
+
+ {title}
+
+
+
+
+ {items.map((item, index) => {
+ const isOpen = openIndex === index;
+
+ return (
+
setOpenIndex(isOpen ? null : index)}
+ />
+ );
+ })}
+
+
+
+ );
+}
diff --git a/components/faq/faqData.ts b/components/faq/faqData.ts
new file mode 100644
index 0000000..ee7d558
--- /dev/null
+++ b/components/faq/faqData.ts
@@ -0,0 +1,17 @@
+export const homepageFaq = [
+ {
+ question: "Kostenlose Beratung und Angebotserstellung",
+ answer:
+ "Wir besprechen Ihr Objekt, den Reinigungsbedarf und die gewünschten Intervalle. Anschließend erhalten Sie ein transparentes Angebot.",
+ },
+ {
+ question: "Individuelle Reinigungslösungen",
+ answer:
+ "Unsere Leistungen passen wir an Ihre Branche, Objektgröße und Anforderungen an. So entsteht ein Reinigungsplan, der wirklich zu Ihrem Alltag passt.",
+ },
+ {
+ question: "Zeitverfügbare Umsetzung",
+ answer:
+ "Wir planen flexibel und reinigen dann, wenn es Ihren Betrieb am wenigsten stört. Auf Wunsch auch früh morgens, abends oder am Wochenende.",
+ },
+];
diff --git a/public/images/portrait/abdulkabi-dastan.jpg b/public/images/portrait/abdulkabi-dastan.jpg
new file mode 100644
index 0000000..a1d87d3
Binary files /dev/null and b/public/images/portrait/abdulkabi-dastan.jpg differ
diff --git a/public/images/portrait/goekhan-oezsayiner.jpg b/public/images/portrait/goekhan-oezsayiner.jpg
new file mode 100644
index 0000000..7625f0b
Binary files /dev/null and b/public/images/portrait/goekhan-oezsayiner.jpg differ
diff --git a/public/images/services/fensterreinigung.jpg b/public/images/services/fensterreinigung.jpg
new file mode 100644
index 0000000..0f186d5
Binary files /dev/null and b/public/images/services/fensterreinigung.jpg differ
diff --git a/public/images/services/grundreinigung.jpg b/public/images/services/grundreinigung.jpg
new file mode 100644
index 0000000..cdeafde
Binary files /dev/null and b/public/images/services/grundreinigung.jpg differ
diff --git a/public/images/services/hausmeisterdienste.jpg b/public/images/services/hausmeisterdienste.jpg
new file mode 100644
index 0000000..5a36b4d
Binary files /dev/null and b/public/images/services/hausmeisterdienste.jpg differ
diff --git a/public/images/services/treppenhausreinigung.jpg b/public/images/services/treppenhausreinigung.jpg
new file mode 100644
index 0000000..a068c84
Binary files /dev/null and b/public/images/services/treppenhausreinigung.jpg differ
diff --git a/public/images/services/unterhaltsreinigung.jpg b/public/images/services/unterhaltsreinigung.jpg
new file mode 100644
index 0000000..aa9b09b
Binary files /dev/null and b/public/images/services/unterhaltsreinigung.jpg differ
diff --git a/styles/buttons.css b/styles/buttons.css
index 3b6c74b..0365866 100644
--- a/styles/buttons.css
+++ b/styles/buttons.css
@@ -1,13 +1,13 @@
:root {
/* Colors */
- --btn-bg-primary: #267361;
- --btn-bg-primary-hover: #267361;
+ --btn-bg-primary: var(--color-accent);
+ --btn-bg-primary-hover: var(--color-accent);
--btn-bg-primary-active: #353030;
--btn-text-light: #fefcfb;
- --btn-text-dark: #267361;
+ --btn-text-dark: var(--color-accent);
- --btn-border-primary: #267361;
+ --btn-border-primary: var(--color-accent);
--btn-border-ghost: #e9e9e9;
--btn-border-ghost-hover: #ececec;
--btn-border-ghost-active: #e1e1e1;
@@ -25,20 +25,21 @@
--btn-focus-offset: 2px;
/* Layout */
- --btn-py: 12px;
- --btn-px: 16px;
+ --btn-py: 8px;
+ --btn-px: 12px;
--btn-py-lg: 16px;
- --btn-px-lg: 24px;
+ --btn-px-lg: 20px;
--btn-radius: 50px;
--btn-gap: 8px;
- --btn-max-height: 52px;
+ --btn-max-height: 46px;
+ --btn-min-height: 38px;
--btn-icon-size: 18px;
--btn-tertiary-px: 4px;
/* Typography */
--btn-font-family: var(--font-body);
- --btn-font-size: 0.889rem;
- --btn-font-weight: var(--font-weight-bold);
+ --btn-font-size: var(--font-size-xs);
+ --btn-font-weight: var(--font-weight-medium);
--btn-line-height: 1;
/* Shadow */
@@ -122,12 +123,37 @@
transition: none;
}
+/* Filled Inverse */
+.btn--filled-inverse {
+ background-color: #ffffff;
+ color: var(--color-accent);
+ border-color: #ffffff;
+ box-shadow: var(--btn-shadow);
+}
+
+.btn--filled-inverse .btn-icon {
+ color: currentColor;
+}
+
+.btn--filled-inverse:hover {
+ background-color: rgba(255, 255, 255, 0.9);
+ border-color: rgba(255, 255, 255, 0.9);
+ color: var(--color-accent);
+}
+
+.btn--filled-inverse:active {
+ background-color: rgba(255, 255, 255, 0.75);
+ border-color: rgba(255, 255, 255, 0.75);
+ transition: none;
+}
+
/* Outline */
.btn--outline {
background-color: transparent;
color: var(--btn-text-dark);
border-color: var(--btn-border-primary);
box-shadow: var(--btn-shadow-none);
+ min-height: var(--btn-min-height);
}
.btn--outline .btn-icon {
@@ -218,6 +244,12 @@
border-color: var(--btn-bg-primary);
}
+ .btn--filled-inverse:hover {
+ background-color: #ffffff;
+ border-color: #ffffff;
+ color: var(--color-accent);
+ }
+
.btn--outline:hover {
background-color: transparent;
border-color: var(--btn-border-primary);
@@ -241,6 +273,5 @@
@media (min-width: 768px) {
.btn {
padding: var(--btn-py-lg) var(--btn-px-lg);
- font-size: 1rem;
}
}
diff --git a/styles/tokens.css b/styles/tokens.css
index c8aa1f7..671f287 100644
--- a/styles/tokens.css
+++ b/styles/tokens.css
@@ -47,10 +47,17 @@
--color-surface-alt: #f9f9f9;
--color-background: #ffffff;
--color-background-alt: #f9f9f9;
+ --color-background-linear: linear-gradient(
+ to bottom,
+ #f9f9f9 5%,
+ #ffffff 50%,
+ #f9f9f9 100%
+ );
--color-text: #171717;
- --color-text-muted: #5f5f5f;
+ --color-text-muted: #4a4a4a;
--color-text-soft: #7a7a7a;
--color-heading: #171717;
+ --color-accent: #267361;
/* Radius */
--radius-none: 0;
diff --git a/styles/typography.css b/styles/typography.css
index e8df95f..4f337b2 100644
--- a/styles/typography.css
+++ b/styles/typography.css
@@ -1,8 +1,9 @@
:root {
/* Font sizes */
- --font-size-body: 1rem;
- --font-size-small: 0.875rem;
- --font-size-lead: clamp(1rem, 2.15vw, 1.125rem);
+ --font-size-xs: clamp(0.79rem, 2vw, 0.889rem);
+ --font-size-s: 0.889rem;
+ --font-size-base: clamp(0.889rem, 2.2vw, 1rem);
+ --font-size-m: clamp(1rem, 2.15vw, 1.125rem);
/* Fluid typography (768px to 1024px) */
--font-size-h1: clamp(1.802rem, 5.71vw, 3.247rem);
@@ -12,7 +13,8 @@
--font-size-h5: clamp(1.125rem, 2.82vw, 1.602rem);
--line-height-body: 1.5;
- --line-height-heading: 1.2;
+ --line-height-heading: 1.1;
+ --line-height-lead: 1.2;
--letter-spacing-tight: -0.02em;
--letter-spacing-normal: 0;
@@ -36,20 +38,20 @@ a {
p,
li {
- font-size: var(--font-size-body);
+ font-size: var(--font-size-base);
line-height: var(--line-height-body);
}
-small,
+.small,
.text-small {
- font-size: var(--font-size-small);
+ font-size: var(--font-size-xs);
line-height: 1.4;
}
.lead,
.text-lead {
- font-size: var(--font-size-lead);
- line-height: 1.4;
+ font-size: var(--font-size-m);
+ line-height: var(--line-height-lead);
}
h1,