Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8535731cd7 | ||
|
|
0280389f49 | ||
|
|
07fea547af | ||
|
|
b2a409e6b7 | ||
|
|
8757431c79 | ||
|
|
0b775a938d | ||
|
|
3953cd3976 | ||
|
|
4b43fc8e0a | ||
|
|
86558882f3 | ||
|
|
9df2bd8ce4 |
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import CookieConsentBanner from "@/components/CookieConsent";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Manrope, Nunito_Sans } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import CookieConsentLoader from "@/components/CookieConsentLoader";
|
||||
|
||||
const nunitoSans = Nunito_Sans({
|
||||
subsets: ["latin"],
|
||||
@@ -94,10 +94,10 @@ export default function RootLayout({
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<CookieConsentLoader />
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
<CookieConsentBanner />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<main>
|
||||
<HeroSection />
|
||||
<ServiceSection />
|
||||
<BenefitsSection />
|
||||
<IndustrySolutionsSection />
|
||||
<FaqSection
|
||||
title="In drei Schritten zu planbarer Sauberkeit"
|
||||
items={homepageFaq}
|
||||
/>
|
||||
<AboutUsSection />
|
||||
<ContactCtaSection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<section className={styles.section} aria-labelledby="about-us-heading">
|
||||
<div className={styles.inner}>
|
||||
<h2 id="about-us-heading" className={styles.heading}>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<div className={styles.team}>
|
||||
{members.map((member) => (
|
||||
<article key={member.name} className={styles.card}>
|
||||
<div className={styles.imageWrap}>
|
||||
<Image
|
||||
src={member.imageSrc}
|
||||
alt={member.imageAlt}
|
||||
fill
|
||||
className={styles.image}
|
||||
sizes="(max-width: 767px) 42vw, 180px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className={styles.name}>{member.name}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.textBlock}>
|
||||
{paragraphs.map((paragraph, index) => (
|
||||
<p key={index} className={styles.text}>
|
||||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -29,5 +29,5 @@
|
||||
}
|
||||
|
||||
:global(#cc-main .cm) {
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.08) !important;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
@@ -1,12 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import * as CookieConsent from "vanilla-cookieconsent";
|
||||
import "vanilla-cookieconsent/dist/cookieconsent.css";
|
||||
import "./CookieConsent.module.css";
|
||||
|
||||
export default function CookieConsentBanner() {
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
let idleId: number | undefined;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const loadConsent = async () => {
|
||||
const [cookieConsentModule] = await Promise.all([
|
||||
import("vanilla-cookieconsent"),
|
||||
import("vanilla-cookieconsent/dist/cookieconsent.css"),
|
||||
]);
|
||||
|
||||
const mod = cookieConsentModule as {
|
||||
run?: (config: unknown) => void;
|
||||
default?: {
|
||||
run?: (config: unknown) => void;
|
||||
};
|
||||
};
|
||||
|
||||
const CookieConsent = mod.run ? mod : mod.default;
|
||||
|
||||
if (!mounted || !CookieConsent?.run) return;
|
||||
|
||||
CookieConsent.run({
|
||||
categories: {
|
||||
necessary: {
|
||||
@@ -23,10 +42,7 @@ export default function CookieConsentBanner() {
|
||||
consentModal: {
|
||||
title: "Ihre Privatsphäre ist uns wichtig",
|
||||
description:
|
||||
"Wir verwenden Cookies, um Ihnen die bestmögliche Nutzung unserer Website zu ermöglichen. " +
|
||||
"Einige Cookies sind technisch notwendig, andere helfen uns, die Website zu verbessern " +
|
||||
"und Ihnen relevante Inhalte anzuzeigen. Sie können Ihre Auswahl jederzeit unter " +
|
||||
"<a href='/datenschutz'>Datenschutz</a> anpassen oder widerrufen.",
|
||||
"Wir setzen notwendige und optionale Cookies ein. Ihre Auswahl können Sie jederzeit unter <a href='/datenschutz'>Datenschutz</a> ändern.",
|
||||
acceptAllBtn: "Alle akzeptieren",
|
||||
acceptNecessaryBtn: "Nur notwendige",
|
||||
showPreferencesBtn: "Einstellungen anpassen",
|
||||
@@ -44,44 +60,30 @@ export default function CookieConsentBanner() {
|
||||
{
|
||||
title: "Ihre Privatsphäre",
|
||||
description:
|
||||
"Wir setzen Cookies ein, um die Funktionalität unserer Website sicherzustellen " +
|
||||
"und unser Angebot kontinuierlich zu verbessern. Sie entscheiden selbst, " +
|
||||
"welche Kategorien Sie zulassen möchten.",
|
||||
"Wir setzen Cookies ein, um die Funktionalität unserer Website sicherzustellen und unser Angebot kontinuierlich zu verbessern. Sie entscheiden selbst, welche Kategorien Sie zulassen möchten.",
|
||||
},
|
||||
{
|
||||
title: "Technisch notwendig",
|
||||
description:
|
||||
"Diese Cookies sind für den reibungslosen Betrieb der Website unbedingt erforderlich " +
|
||||
"und können nicht deaktiviert werden. Sie speichern keine personenbezogenen Daten " +
|
||||
"und werden ausschließlich als Reaktion auf von Ihnen durchgeführte Aktionen gesetzt " +
|
||||
"(z. B. Spracheinstellungen oder Sicherheits-Token).",
|
||||
"Diese Cookies sind für den reibungslosen Betrieb der Website unbedingt erforderlich und können nicht deaktiviert werden.",
|
||||
linkedCategory: "necessary",
|
||||
},
|
||||
{
|
||||
title: "Analyse & Statistik",
|
||||
description:
|
||||
"Diese Cookies helfen uns zu verstehen, wie Besucher mit unserer Website interagieren. " +
|
||||
"Alle Daten werden anonymisiert gesammelt und dienen ausschließlich zur Verbesserung " +
|
||||
"unseres Angebots. Wir nutzen Google Analytics (GA4) – Ihre IP-Adresse wird dabei " +
|
||||
"anonymisiert und nicht gespeichert.",
|
||||
"Diese Cookies helfen uns zu verstehen, wie Besucher mit unserer Website interagieren.",
|
||||
linkedCategory: "analytics",
|
||||
},
|
||||
{
|
||||
title: "Marketing & externe Medien",
|
||||
description:
|
||||
"Diese Cookies ermöglichen die Einbindung externer Inhalte wie YouTube-Videos, " +
|
||||
"Google Maps, Instagram-, Facebook- und X (Twitter)-Inhalte. " +
|
||||
"Ohne Ihre Zustimmung werden diese Inhalte nicht geladen und keine Daten übertragen. " +
|
||||
"Bitte beachten Sie, dass bei Aktivierung personenbezogene Daten " +
|
||||
"an Drittanbieter (ggf. auch in die USA) übertragen werden können.",
|
||||
"Diese Cookies ermöglichen die Einbindung externer Inhalte wie YouTube-Videos, Google Maps, Instagram-, Facebook- und X-Inhalte.",
|
||||
linkedCategory: "marketing",
|
||||
},
|
||||
{
|
||||
title: "Weitere Informationen",
|
||||
description:
|
||||
"Bei Fragen zu unserer Cookie-Richtlinie wenden Sie sich bitte an uns über das " +
|
||||
"<a href='/kontakt'>Kontaktformular</a>. Mehr Details finden Sie in unserer " +
|
||||
"<a href='/datenschutz'>Datenschutzerklärung</a>.",
|
||||
"Bei Fragen wenden Sie sich bitte über das <a href='/kontakt'>Kontaktformular</a> an uns. Mehr Details finden Sie in unserer <a href='/datenschutz'>Datenschutzerklärung</a>.",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -89,6 +91,32 @@ export default function CookieConsentBanner() {
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (window.requestIdleCallback) {
|
||||
idleId = window.requestIdleCallback(
|
||||
() => {
|
||||
void loadConsent();
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
} else {
|
||||
timeoutId = setTimeout(() => {
|
||||
void loadConsent();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
|
||||
if (idleId && window.cancelIdleCallback) {
|
||||
window.cancelIdleCallback(idleId);
|
||||
}
|
||||
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const CookieConsentBanner = dynamic(() => import("./CookieConsent"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export default function CookieConsentLoader() {
|
||||
return <CookieConsentBanner />;
|
||||
}
|
||||
@@ -1,15 +1,9 @@
|
||||
/*
|
||||
FOOTER
|
||||
Datei: components/Footer.module.css
|
||||
*/
|
||||
|
||||
:root {
|
||||
--footer-bg: #171717;
|
||||
--footer-text: #a1a1aa;
|
||||
--footer-bg: #272727;
|
||||
--footer-text: #adadb3;
|
||||
--footer-text-hover: #ffffff;
|
||||
--footer-title: #ffffff;
|
||||
--footer-divider: rgba(255, 255, 255, 0.1);
|
||||
--footer-padding-y: clamp(3rem, 6vw, 5rem);
|
||||
--footer-gap: clamp(2rem, 5vw, 4rem);
|
||||
}
|
||||
|
||||
@@ -17,7 +11,6 @@
|
||||
.footer {
|
||||
background-color: var(--footer-bg);
|
||||
color: var(--footer-text);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.footer a:focus-visible {
|
||||
@@ -26,9 +19,10 @@
|
||||
}
|
||||
|
||||
.footer__inner {
|
||||
max-width: 1280px;
|
||||
max-width: var(--container-width);
|
||||
margin-inline: auto;
|
||||
padding: var(--footer-padding-y) clamp(1rem, 5vw, 2rem);
|
||||
padding-inline: var(--space-4);
|
||||
padding-block: var(--space-16);
|
||||
}
|
||||
|
||||
/* Top */
|
||||
@@ -38,26 +32,16 @@
|
||||
gap: var(--footer-gap);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.footer__top {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.footer__top {
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Brand */
|
||||
.footer__brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.footer__logo {
|
||||
display: inline-block;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: var(--footer-title);
|
||||
margin-bottom: 0.75rem;
|
||||
transition: opacity 0.2s ease;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.footer__logo:hover {
|
||||
@@ -65,8 +49,6 @@
|
||||
}
|
||||
|
||||
.footer__tagline {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
max-width: 32ch;
|
||||
color: var(--footer-text);
|
||||
}
|
||||
@@ -74,34 +56,32 @@
|
||||
/* Navigation */
|
||||
.footer__nav {
|
||||
display: flex;
|
||||
gap: var(--footer-gap);
|
||||
flex-wrap: wrap;
|
||||
gap: var(--footer-gap);
|
||||
}
|
||||
|
||||
.footer__nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer__nav-group ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.footer__nav-title {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: var(--space-4);
|
||||
font-size: var(--font-size-xxs);
|
||||
letter-spacing: var(--letter-spacing-wide);
|
||||
color: var(--footer-title);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.footer__nav-group a {
|
||||
font-size: 0.9rem;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--footer-text);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
@@ -157,3 +137,31 @@
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Tablet */
|
||||
@media (min-width: 768px) {
|
||||
.footer__top {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.footer__nav {
|
||||
justify-self: end;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.footer__inner {
|
||||
padding-inline: var(--space-6);
|
||||
}
|
||||
|
||||
.footer__top {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.footer__nav {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import styles from "./Footer.module.css";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
InstagramLogoIcon,
|
||||
LinkedinLogoIcon,
|
||||
XLogoIcon,
|
||||
FacebookLogoIcon,
|
||||
} from "@phosphor-icons/react/dist/ssr";
|
||||
import { companyData } from "@/data/companyData";
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
@@ -13,22 +15,24 @@ export default function Footer() {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<div className={styles.footer__inner}>
|
||||
{/* Top: Brand + Navigation */}
|
||||
<div className={styles.footer__top}>
|
||||
{/* Brand */}
|
||||
<div className={styles.footer__brand}>
|
||||
<Link href="/" className={styles.footer__logo}>
|
||||
ENVU {/* ← Logo oder Bildkomponente hier */}
|
||||
<Image
|
||||
src="/logo/logo-basiclean-white.svg"
|
||||
alt={companyData.companyName}
|
||||
width={160}
|
||||
height={48}
|
||||
unoptimized
|
||||
/>
|
||||
</Link>
|
||||
<p className={styles.footer__tagline}>
|
||||
Maßgeschneiderte digitale Lösungen für Ihr Unternehmen.
|
||||
</p>
|
||||
|
||||
<p className={styles.footer__tagline}>{companyData.slogan}</p>
|
||||
</div>
|
||||
|
||||
{/* Navigation Spalten */}
|
||||
<nav className={styles.footer__nav} aria-label="Footer Navigation">
|
||||
<div className={styles.footer__nav_group}>
|
||||
<span className={styles.footer__nav_title}>Unternehmen</span>
|
||||
<div className={styles["footer__nav-group"]}>
|
||||
<span className={styles["footer__nav-title"]}>Unternehmen</span>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/ueberuns">Über uns</Link>
|
||||
@@ -36,14 +40,17 @@ export default function Footer() {
|
||||
<li>
|
||||
<Link href="/leistungen">Leistungen</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/branchenloesungen">Branchenlösungen</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/kontakt">Kontakt</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={styles.footer__nav_group}>
|
||||
<span className={styles.footer__nav_title}>Rechtliches</span>
|
||||
<div className={styles["footer__nav-group"]}>
|
||||
<span className={styles["footer__nav-title"]}>Rechtliches</span>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/impressum">Impressum</Link>
|
||||
@@ -56,13 +63,12 @@ export default function Footer() {
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className={styles.footer__divider} />
|
||||
|
||||
{/* Bottom: Copyright + Social */}
|
||||
<div className={styles.footer__bottom}>
|
||||
<p className={styles.footer__copy}>
|
||||
© {currentYear} ENVU. Alle Rechte vorbehalten.
|
||||
Copyright © {currentYear} {companyData.companyName}. Alle Rechte
|
||||
vorbehalten.
|
||||
</p>
|
||||
|
||||
<div className={styles.footer__social}>
|
||||
@@ -74,6 +80,7 @@ export default function Footer() {
|
||||
>
|
||||
<InstagramLogoIcon size={20} />
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://linkedin.com"
|
||||
target="_blank"
|
||||
@@ -82,6 +89,7 @@ export default function Footer() {
|
||||
>
|
||||
<LinkedinLogoIcon size={20} />
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://x.com"
|
||||
target="_blank"
|
||||
@@ -90,6 +98,7 @@ export default function Footer() {
|
||||
>
|
||||
<XLogoIcon size={20} />
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://facebook.com"
|
||||
target="_blank"
|
||||
|
||||
@@ -1,59 +1,55 @@
|
||||
.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);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inner {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
max-width: var(--container-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--space-4);
|
||||
}
|
||||
|
||||
.heroDecoration {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
right: var(--space-4);
|
||||
width: auto;
|
||||
height: 80%;
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
z-index: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.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 */
|
||||
@@ -63,17 +59,33 @@
|
||||
}
|
||||
|
||||
.heroDecoration {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: auto;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
height: 90%;
|
||||
width: min(42vw, 420px);
|
||||
height: auto;
|
||||
opacity: 0.06;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
.heading {
|
||||
max-width: 24ch;
|
||||
}
|
||||
|
||||
.lead {
|
||||
max-width: 48ch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@@ -84,4 +96,9 @@
|
||||
.inner {
|
||||
padding-inline: var(--space-6);
|
||||
}
|
||||
|
||||
.heroDecoration {
|
||||
width: min(44vw, 560px);
|
||||
opacity: 0.08;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,29 @@
|
||||
import { ArrowRightIcon, PhoneIcon } from "@phosphor-icons/react/dist/ssr";
|
||||
import Link from "next/link";
|
||||
import styles from "./HeroSection.module.css";
|
||||
import { companyData } from "@/data/companyData";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function HeroSection() {
|
||||
return (
|
||||
<section className={styles.hero} aria-labelledby="hero-heading">
|
||||
<img
|
||||
<div className={styles.inner}>
|
||||
<Image
|
||||
src="/logo/logo-mininmal-basiclean-grey.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className={styles.heroDecoration}
|
||||
width={620}
|
||||
height={620}
|
||||
unoptimized
|
||||
/>
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.content}>
|
||||
<h1 id="hero-heading" className={styles.heading}>
|
||||
Mehr Sauberkeit, weniger Aufwand für ein sauberes Arbeitsumfeld
|
||||
Gebäudereinigung für ein makelloses Arbeitsumfeld
|
||||
</h1>
|
||||
<p className={`lead ${styles.lead}`}>
|
||||
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.
|
||||
</p>
|
||||
<div className={styles.actions}>
|
||||
<Link href="/kontakt" className="btn btn--filled">
|
||||
@@ -27,11 +32,19 @@ export default function HeroSection() {
|
||||
</span>
|
||||
Jetzt Angebot anfragen
|
||||
</Link>
|
||||
<a href="tel:+495112345678" className="btn btn--outline">
|
||||
<Link href="/Leistungen" className="btn btn--outline">
|
||||
Leistungen ansehen
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<a
|
||||
href={`tel:${companyData.contact.phone.value}`}
|
||||
className="btn btn--tertiary"
|
||||
>
|
||||
<span className="btn-icon" aria-hidden="true">
|
||||
<PhoneIcon size={18} weight="bold" />
|
||||
</span>
|
||||
0172 621 0606
|
||||
{companyData.contact.phone.display}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<section
|
||||
className={styles.section}
|
||||
aria-labelledby="industry-solutions-heading"
|
||||
>
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.intro}>
|
||||
<h2 id="industry-solutions-heading" className={styles.heading}>
|
||||
Reinigungsleistungen für verschiedene Branchen
|
||||
</h2>
|
||||
<p className={styles.lead}>
|
||||
Als Reinigungsfirma betreuen wir Unternehmen aus unterschiedlichen
|
||||
Branchen. Unsere Leistungen passen wir individuell an Nutzung,
|
||||
Objektgröße und branchenspezifische Anforderungen an.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{industries.map((industry) => {
|
||||
const Icon = industry.icon;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={industry.title}
|
||||
href={industry.href}
|
||||
className={styles.card}
|
||||
>
|
||||
<span className={styles.cardText}>{industry.title}</span>
|
||||
|
||||
<span className={styles.iconWrap} aria-hidden="true">
|
||||
<Icon size={18} weight="bold" className={styles.icon} />
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,7 @@
|
||||
font-weight: var(--nav-font-weight);
|
||||
background-color: var(--nav-bg);
|
||||
color: var(--nav-font-color);
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
|
||||
.nav a:focus-visible,
|
||||
@@ -207,10 +208,13 @@
|
||||
}
|
||||
|
||||
.nav-links > li a {
|
||||
padding: 16px 12px;
|
||||
padding: 24px 12px;
|
||||
}
|
||||
|
||||
.nav-links > li:first-child {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-links > li:first-child,
|
||||
.nav-links > li:last-child {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import { useNavToggleMenu } from "@/hooks/useNavToggleMenu";
|
||||
import { useSupportsHover } from "@/hooks/useSupportsHover";
|
||||
import { useToggleBodyOverflow } from "@/hooks/useToggleBodyOverflow";
|
||||
import styles from "./Navbar.module.css";
|
||||
import { NavbarSubMenu } from "./NavbarSubMenu";
|
||||
import { useIsDesktop } from "@/hooks/useIsDesktop";
|
||||
import { NavbarSubMenu } from "./NavbarSubMenu";
|
||||
|
||||
type BasicLink = {
|
||||
label: string;
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
height: var(--nav-h);
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
|
||||
.nav-item-with-sub {
|
||||
@@ -103,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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<section className={styles.services} aria-labelledby="services-heading">
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.intro}>
|
||||
<h2 id="services-heading" className={styles.heading}>
|
||||
Professionelle Reinigung für Büros, Praxen und Geschäftsräume
|
||||
</h2>
|
||||
<p className={`lead ${styles.lead}`}>
|
||||
Individuelle Reinigungskonzepte für Unternehmen – damit Ihre Räume sauber, zuverlässig und professions sorgenfrei bleiben.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{services.map((service) => (
|
||||
<article key={service.title} className={styles.card}>
|
||||
<div className={styles.imageWrap}>
|
||||
<Image
|
||||
src={service.image}
|
||||
alt={service.imageAlt}
|
||||
fill
|
||||
sizes="(min-width: 1024px) 400px, (min-width: 768px) 50vw, 100vw"
|
||||
className={styles.image}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.cardBody}>
|
||||
<div className={styles.cardContent}>
|
||||
<h3 className={styles.cardTitle}>{service.title}</h3>
|
||||
<p className={styles.cardText}>{service.description}</p>
|
||||
</div>
|
||||
|
||||
<Link href={service.href} className="btn btn--tertiary">
|
||||
Mehr
|
||||
<span className="btn-icon animate" aria-hidden="true">
|
||||
<ArrowRightIcon size={16} weight="bold" />
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<section className={styles.benefits} aria-labelledby="benefits-heading">
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.intro}>
|
||||
<h2 id="benefits-heading" className={styles.heading}>
|
||||
Ihre Vorteile mit uns
|
||||
</h2>
|
||||
<p className={`lead ${styles.lead}`}>
|
||||
Wir verstehen die Anforderungen moderner Unternehmen an einen
|
||||
Facility-Partner: Verlässlichkeit, Professionalität und Transparenz.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{benefits.map((benefit) => {
|
||||
const Icon = benefit.icon;
|
||||
return (
|
||||
<article key={benefit.title} className={styles.card}>
|
||||
<span className={styles.iconWrap} aria-hidden="true">
|
||||
<Icon size={24} weight="duotone" />
|
||||
</span>
|
||||
<div className={styles.cardContent}>
|
||||
<h3 className={styles.cardTitle}>{benefit.title}</h3>
|
||||
<p className={styles.cardText}>{benefit.description}</p>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { ElementType } from "react";
|
||||
import {
|
||||
PhoneIcon,
|
||||
EnvelopeSimpleIcon,
|
||||
ClockIcon,
|
||||
} from "@phosphor-icons/react/dist/ssr";
|
||||
|
||||
export type CompanyContactKey = "phone" | "email" | "openingHours";
|
||||
|
||||
export type CompanyContactItem = {
|
||||
type: "company";
|
||||
contactKey: CompanyContactKey;
|
||||
icon: ElementType;
|
||||
};
|
||||
|
||||
export type StaticContactItem = {
|
||||
type: "static";
|
||||
icon: ElementType;
|
||||
label: string;
|
||||
href?: string;
|
||||
};
|
||||
|
||||
export type ContactInfo = CompanyContactItem | StaticContactItem;
|
||||
|
||||
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: [
|
||||
{
|
||||
type: "company",
|
||||
contactKey: "phone",
|
||||
icon: PhoneIcon,
|
||||
},
|
||||
{
|
||||
type: "company",
|
||||
contactKey: "email",
|
||||
icon: EnvelopeSimpleIcon,
|
||||
},
|
||||
{
|
||||
type: "company",
|
||||
contactKey: "openingHours",
|
||||
icon: ClockIcon,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import Link from "next/link";
|
||||
import { ArrowRightIcon } from "@phosphor-icons/react/dist/ssr";
|
||||
import { companyData } from "@/data/companyData";
|
||||
import styles from "./ContactCtaSection.module.css";
|
||||
import { contactCtaData, type ContactInfo } from "./ContactCtaData";
|
||||
|
||||
function resolveCompanyContact(
|
||||
item: Extract<ContactInfo, { type: "company" }>,
|
||||
) {
|
||||
if (item.contactKey === "phone") {
|
||||
return {
|
||||
label: companyData.contact.phone.display,
|
||||
href: `tel:${companyData.contact.phone.value}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.contactKey === "email") {
|
||||
const email = `${companyData.contact.email.user}@${companyData.contact.email.domain}`;
|
||||
|
||||
return {
|
||||
label: email,
|
||||
href: `mailto:${email}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (item.contactKey === "openingHours") {
|
||||
return {
|
||||
label: companyData.contact.openingHours.display,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function ContactCtaSection() {
|
||||
const { heading, lead, buttonLabel, buttonHref, contacts } = contactCtaData;
|
||||
|
||||
return (
|
||||
<section className={styles.section} aria-labelledby="contact-cta-heading">
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.text}>
|
||||
<h2 id="contact-cta-heading" className={styles.heading}>
|
||||
{heading}
|
||||
</h2>
|
||||
<p className={styles.lead}>{lead}</p>
|
||||
</div>
|
||||
|
||||
<Link href={buttonHref} className="btn btn--filled-inverse">
|
||||
<span className="btn-icon animate" aria-hidden="true">
|
||||
<ArrowRightIcon size={16} weight="bold" />
|
||||
</span>
|
||||
{buttonLabel}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<hr className={styles.divider} aria-hidden="true" />
|
||||
|
||||
<div className={styles.contactRow}>
|
||||
{contacts.map((item) => {
|
||||
const icon = (
|
||||
<item.icon size={20} weight="regular" aria-hidden="true" />
|
||||
);
|
||||
|
||||
if (item.type === "company") {
|
||||
const resolved = resolveCompanyContact(item);
|
||||
|
||||
if (!resolved) return null;
|
||||
|
||||
return (
|
||||
<a
|
||||
key={item.contactKey}
|
||||
href={resolved.href}
|
||||
className={styles.contactItem}
|
||||
aria-label={resolved.label}
|
||||
>
|
||||
{icon}
|
||||
{resolved.label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.href) {
|
||||
return (
|
||||
<a
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
className={styles.contactItem}
|
||||
aria-label={item.label}
|
||||
>
|
||||
{icon}
|
||||
{item.label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={item.label} className={styles.contactItem}>
|
||||
{icon}
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<HTMLDivElement | null>(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 (
|
||||
<div className={styles.item}>
|
||||
<h3 className={styles.itemHeading}>
|
||||
<button
|
||||
id={buttonId}
|
||||
type="button"
|
||||
className={styles.trigger}
|
||||
aria-expanded={isOpen}
|
||||
aria-controls={panelId}
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<span className={styles.triggerText}>{item.question}</span>
|
||||
|
||||
<span className={styles.iconWrap} aria-hidden="true">
|
||||
<CaretDownIcon size={14} weight="bold" />
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
id={panelId}
|
||||
ref={panelRef}
|
||||
role="region"
|
||||
aria-labelledby={buttonId}
|
||||
className={styles.panel}
|
||||
>
|
||||
<div className={styles.panelInner}>
|
||||
<p className={styles.answer}>{item.answer}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FaqSection({ title, items }: FaqSectionProps) {
|
||||
const baseId = useId();
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
|
||||
return (
|
||||
<section className={styles.section} aria-labelledby="faq-heading">
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.content}>
|
||||
<h2 id="faq-heading" className={styles.heading}>
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className={styles.accordion}>
|
||||
{items.map((item, index) => {
|
||||
const isOpen = openIndex === index;
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
key={item.question}
|
||||
item={item}
|
||||
isOpen={isOpen}
|
||||
buttonId={`${baseId}-btn-${index}`}
|
||||
panelId={`${baseId}-panel-${index}`}
|
||||
onToggle={() => setOpenIndex(isOpen ? null : index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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.",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
export const companyData = {
|
||||
name: "Basiclean",
|
||||
companyName: "Basiclean",
|
||||
slogan: "Wo Sauberkeit Standard ist.",
|
||||
contact: {
|
||||
email: {
|
||||
user: "info",
|
||||
domain: "basiclean.de",
|
||||
},
|
||||
phone: {
|
||||
display: "0172 621 0606",
|
||||
value: "+491726210606",
|
||||
},
|
||||
openingHours: {
|
||||
display: "Mo. - Fr. 08:00 - 17:00 Uhr",
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { companyData } from "./companyData";
|
||||
|
||||
export function getCompanyEmail() {
|
||||
return `${companyData.contact.email.user}@${companyData.contact.email.domain}`;
|
||||
}
|
||||
|
||||
export function getCompanyEmailHref() {
|
||||
return `mailto:${getCompanyEmail()}`;
|
||||
}
|
||||
|
||||
export function getCompanyPhoneDisplay() {
|
||||
return companyData.contact.phone.display;
|
||||
}
|
||||
|
||||
export function getCompanyPhoneHref() {
|
||||
return `tel:${companyData.contact.phone.value}`;
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const allowedOrigin = process.env.NEXT_ALLOWED_DEV_ORIGIN;
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: "standalone",
|
||||
trailingSlash: false,
|
||||
poweredByHeader: false,
|
||||
allowedDevOrigins: [
|
||||
"192.168.178.59", // deine PC-IP
|
||||
],
|
||||
};
|
||||
|
||||
if (allowedOrigin) {
|
||||
nextConfig.allowedDevOrigins = [allowedOrigin];
|
||||
}
|
||||
|
||||
export default nextConfig;
|
||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 120 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 748 167" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo-neben-Schriftzug" serif:id="Logo neben Schriftzug"><path d="M105.008,158.958c-22.419,11.972 -50.938,8.512 -69.831,-10.381c-39.541,-39.541 -35.021,-113.988 -35.021,-113.988c0.167,-2.605 2.243,-4.68 4.848,-4.848c0,0 74.447,-4.52 113.988,35.021c21.914,21.914 23.066,56.779 3.454,80.054c-0.362,-0.185 -0.737,-0.379 -1.127,-0.582c-5.154,-2.691 -5.16,-2.68 -5.604,-2.92c-1.436,-0.776 -1.97,-1.044 -2.742,-1.452c17.744,-19.163 17.304,-49.132 -1.323,-67.759c-30.978,-30.978 -85.143,-32.462 -101.303,-32.17c-0.292,16.16 1.193,70.325 32.17,101.303c13.793,13.793 33.808,17.614 51.047,11.462c0.145,0.095 0.291,0.19 0.439,0.286c2.484,1.615 5.959,3.52 6.376,3.731c1.806,0.917 3.237,1.615 4.628,2.243Z" style="fill:#298972;"/><path d="M189.124,165.924c-3.398,0.229 -16.488,1.214 -32.559,0.216c-2.19,-0.136 -9.695,-0.773 -15.864,-1.809c-10.966,-1.842 -17.633,-3.992 -21.329,-5.221c-7.711,-2.565 -18.01,-6.682 -29.769,-15.316c-4.985,-3.66 -9.537,-7.394 -15.842,-13.667c-0.927,-0.922 -5.602,-5.807 -9.04,-10.007c-4.591,-5.61 -8.504,-11.049 -11.653,-15.709c-3.199,-4.735 -7.124,-11.263 -10.135,-16.633c0.21,0.254 5.761,7.164 14.336,16.223c1.045,1.104 6.617,7.092 15.664,14.982c2.689,2.345 8.097,6.853 13.443,10.683c1.802,1.291 7.343,5.31 15.048,9.761c16.464,9.511 27.83,13.425 29.737,14.138c12.597,4.709 25.101,7.248 26.764,7.609c7.797,1.694 17.199,3.279 31.199,4.751Z" style="fill:#298972;"/><path d="M138.583,150.484c14.876,-8.386 26.33,-25.547 26.33,-43.826c0,-43.809 -37.251,-83.159 -48.884,-94.38c-4.908,4.734 -14.376,14.475 -23.683,27.446c-3.19,-1.426 -6.42,-2.72 -9.664,-3.896c14.593,-20.964 29.92,-34.535 29.92,-34.535c1.96,-1.724 4.895,-1.724 6.856,0c0,0 55.838,49.446 55.838,105.365c0,18.717 -10.095,36.63 -23.662,47.495c-3.502,-0.854 -8.107,-2.077 -13.05,-3.668Z" style="fill:#3166b3;"/></g><g><path d="M230.865,75.544c2.065,-3.614 5.089,-6.565 9.073,-8.851c3.983,-2.287 8.519,-3.43 13.609,-3.43c5.458,0 10.363,1.291 14.715,3.872c4.352,2.582 7.782,6.214 10.29,10.898c2.508,4.684 3.762,10.124 3.762,16.32c0,6.122 -1.254,11.58 -3.762,16.375c-2.508,4.794 -5.956,8.519 -10.345,11.175c-4.389,2.655 -9.275,3.983 -14.66,3.983c-5.237,0 -9.829,-1.143 -13.775,-3.43c-3.946,-2.287 -6.915,-5.2 -8.907,-8.741l0,11.175l-10.068,0l0,-81.875l10.068,0l0,32.529Zm41.159,18.809c0,-4.573 -0.922,-8.556 -2.766,-11.949c-1.844,-3.393 -4.333,-5.975 -7.468,-7.745c-3.135,-1.77 -6.583,-2.655 -10.345,-2.655c-3.688,0 -7.099,0.904 -10.234,2.711c-3.135,1.807 -5.643,4.426 -7.524,7.856c-1.881,3.43 -2.821,7.395 -2.821,11.894c0,4.573 0.94,8.575 2.821,12.005c1.881,3.43 4.389,6.048 7.524,7.856c3.135,1.807 6.546,2.711 10.234,2.711c3.762,0 7.21,-0.904 10.345,-2.711c3.135,-1.807 5.624,-4.426 7.468,-7.856c1.844,-3.43 2.766,-7.468 2.766,-12.115Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M291.829,94.353c0,-6.196 1.254,-11.636 3.762,-16.32c2.508,-4.684 5.956,-8.317 10.345,-10.898c4.389,-2.582 9.275,-3.872 14.66,-3.872c5.311,0 9.921,1.143 13.83,3.43c3.909,2.287 6.823,5.163 8.741,8.63l0,-11.064l10.179,0l0,60.631l-10.179,0l0,-11.285c-1.992,3.541 -4.96,6.473 -8.907,8.796c-3.946,2.323 -8.538,3.485 -13.775,3.485c-5.385,0 -10.253,-1.328 -14.605,-3.983c-4.352,-2.655 -7.782,-6.38 -10.29,-11.175c-2.508,-4.794 -3.762,-10.253 -3.762,-16.375Zm51.338,0.111c0,-4.573 -0.922,-8.556 -2.766,-11.949c-1.844,-3.393 -4.333,-5.993 -7.468,-7.8c-3.135,-1.807 -6.583,-2.711 -10.345,-2.711c-3.762,0 -7.192,0.885 -10.29,2.655c-3.098,1.77 -5.569,4.352 -7.413,7.745c-1.844,3.393 -2.766,7.376 -2.766,11.949c0,4.647 0.922,8.685 2.766,12.115c1.844,3.43 4.315,6.048 7.413,7.856c3.098,1.807 6.528,2.711 10.29,2.711c3.762,0 7.21,-0.904 10.345,-2.711c3.135,-1.807 5.624,-4.426 7.468,-7.856c1.844,-3.43 2.766,-7.431 2.766,-12.005Z" style="fill:#fff;fill-rule:nonzero;"/><path d="M391.686,125.886c-4.658,0 -8.836,-0.793 -12.534,-2.379c-3.696,-1.586 -6.617,-3.799 -8.761,-6.638c-2.144,-2.84 -3.216,-6.104 -3.216,-9.792l10.29,0c0.295,3.024 1.715,5.495 4.26,7.413c2.545,1.918 5.882,2.877 10.013,2.877c3.836,0 6.86,-0.848 9.073,-2.545c2.213,-1.696 3.319,-3.836 3.319,-6.417c0,-2.655 -1.18,-4.628 -3.541,-5.919c-2.36,-1.291 -6.012,-2.563 -10.953,-3.817c-4.499,-1.18 -8.169,-2.379 -11.009,-3.596c-2.84,-1.217 -5.274,-3.024 -7.302,-5.421c-2.028,-2.397 -3.043,-5.551 -3.043,-9.46c0,-3.098 0.922,-5.938 2.766,-8.519c1.844,-2.582 4.463,-4.628 7.856,-6.141c3.393,-1.512 7.265,-2.268 11.617,-2.268c6.712,0 12.134,1.696 16.264,5.089c4.131,3.393 6.343,8.04 6.638,13.941l-10.29,0c0,-3.172 -1.167,-5.716 -3.501,-7.634c-2.334,-1.918 -5.464,-2.877 -9.391,-2.877c-3.63,0 -6.52,0.774 -8.67,2.323c-2.148,1.549 -3.222,3.577 -3.222,6.0Line truncated
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -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 */
|
||||
@@ -72,6 +73,7 @@
|
||||
user-select: none;
|
||||
transition: var(--btn-transition);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
@@ -121,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 {
|
||||
@@ -217,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);
|
||||
@@ -240,6 +273,5 @@
|
||||
@media (min-width: 768px) {
|
||||
.btn {
|
||||
padding: var(--btn-py-lg) var(--btn-px-lg);
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
:root {
|
||||
/* Font sizes */
|
||||
--font-size-body: 1rem;
|
||||
--font-size-small: 0.875rem;
|
||||
--font-size-lead: 1.125rem;
|
||||
--font-size-xxs: 0.79rem;
|
||||
--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 +14,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 +39,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,
|
||||
|
||||