Compare commits

..
10 Commits
Author SHA1 Message Date
egnbkts 8535731cd7 Startseite fertiggestellt. 2026-05-13 15:40:06 +02:00
egnbkts 0280389f49 designänderung 2026-05-13 13:25:18 +02:00
egnbkts 07fea547af opacity verbessert 2026-05-13 13:15:02 +02:00
egnbkts b2a409e6b7 update 2026-05-13 13:13:57 +02:00
egnbkts 8757431c79 Update 2026-05-13 09:08:53 +02:00
egnbkts 0b775a938d Navbar vergrößert 2026-05-03 15:44:35 +02:00
egnbkts 3953cd3976 letterspacing 2026-05-03 15:32:03 +02:00
egnbkts 4b43fc8e0a Watermark breite gesetzt 2026-05-03 15:26:57 +02:00
egnbkts 86558882f3 schriftgröße für lead angepasst 2026-05-03 15:26:45 +02:00
egnbkts 9df2bd8ce4 z-index gesetzt 2026-05-03 15:26:36 +02:00
44 changed files with 1822 additions and 218 deletions

No files matched your search

+1 -1
View File
@@ -34,7 +34,7 @@
} }
.label { .label {
font-size: var(--font-size-small); font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold);
letter-spacing: var(--letter-spacing-wide); letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase; text-transform: uppercase;
+1 -17
View File
@@ -19,19 +19,12 @@ html.has-menu-open {
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
html,
body {
max-width: 100vw;
}
body { body {
min-height: 100%; min-height: 100%;
display: flex;
flex-direction: column;
color: var(--font-body-color); color: var(--font-body-color);
background: var(--color-background); background: var(--color-background);
font-family: var(--font-body); font-family: var(--font-body);
font-size: 1rem; font-size: var(--font-size-base);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
line-height: var(--line-height-body); line-height: var(--line-height-body);
@@ -40,19 +33,10 @@ body {
main { main {
width: 100%; width: 100%;
max-width: 1280px;
margin-inline: auto;
padding: 0 var(--space-4);
} }
@media (min-width: 768px) { @media (min-width: 768px) {
main {
padding: 0 var(--space-4);
}
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {
main {
padding: 0 var(--space-6);
}
} }
+2 -2
View File
@@ -1,10 +1,10 @@
import CookieConsentBanner from "@/components/CookieConsent";
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Manrope, Nunito_Sans } from "next/font/google"; import { Manrope, Nunito_Sans } from "next/font/google";
import "./globals.css"; import "./globals.css";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import Header from "@/components/Header"; import Header from "@/components/Header";
import CookieConsentLoader from "@/components/CookieConsentLoader";
const nunitoSans = Nunito_Sans({ const nunitoSans = Nunito_Sans({
subsets: ["latin"], subsets: ["latin"],
@@ -94,10 +94,10 @@ export default function RootLayout({
type="application/ld+json" type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/> />
<CookieConsentLoader />
<Header /> <Header />
{children} {children}
<Footer /> <Footer />
<CookieConsentBanner />
</body> </body>
</html> </html>
); );
+1 -1
View File
@@ -34,7 +34,7 @@
} }
.label { .label {
font-size: var(--font-size-small); font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold);
letter-spacing: var(--letter-spacing-wide); letter-spacing: var(--letter-spacing-wide);
text-transform: uppercase; text-transform: uppercase;
+16
View File
@@ -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 HeroSection from "@/components/HeroSection";
import IndustrySolutionsSection from "@/components/IndustrySolutionsSection";
import ServiceSection from "@/components/ServiceSection";
export default function Home() { export default function Home() {
return ( return (
<main> <main>
<HeroSection /> <HeroSection />
<ServiceSection />
<BenefitsSection />
<IndustrySolutionsSection />
<FaqSection
title="In drei Schritten zu planbarer Sauberkeit"
items={homepageFaq}
/>
<AboutUsSection />
<ContactCtaSection />
</main> </main>
); );
} }
+32
View File
@@ -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",
},
],
};
+120
View File
@@ -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;
}
}
+45
View File
@@ -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>
);
}
+1 -1
View File
@@ -29,5 +29,5 @@
} }
:global(#cc-main .cm) { :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;
} }
+108 -80
View File
@@ -1,94 +1,122 @@
"use client"; "use client";
import { useEffect } from "react"; import { useEffect } from "react";
import * as CookieConsent from "vanilla-cookieconsent";
import "vanilla-cookieconsent/dist/cookieconsent.css";
import "./CookieConsent.module.css"; import "./CookieConsent.module.css";
export default function CookieConsentBanner() { export default function CookieConsentBanner() {
useEffect(() => { useEffect(() => {
CookieConsent.run({ let mounted = true;
categories: { let idleId: number | undefined;
necessary: { let timeoutId: ReturnType<typeof setTimeout> | undefined;
enabled: true,
readOnly: true, 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: {
enabled: true,
readOnly: true,
},
analytics: {},
marketing: {},
}, },
analytics: {}, language: {
marketing: {}, default: "de",
}, translations: {
language: { de: {
default: "de", consentModal: {
translations: { title: "Ihre Privatsphäre ist uns wichtig",
de: { description:
consentModal: { "Wir setzen notwendige und optionale Cookies ein. Ihre Auswahl können Sie jederzeit unter <a href='/datenschutz'>Datenschutz</a> ändern.",
title: "Ihre Privatsphäre ist uns wichtig", acceptAllBtn: "Alle akzeptieren",
description: acceptNecessaryBtn: "Nur notwendige",
"Wir verwenden Cookies, um Ihnen die bestmögliche Nutzung unserer Website zu ermöglichen. " + showPreferencesBtn: "Einstellungen anpassen",
"Einige Cookies sind technisch notwendig, andere helfen uns, die Website zu verbessern " + footer:
"und Ihnen relevante Inhalte anzuzeigen. Sie können Ihre Auswahl jederzeit unter " + "<a href='/impressum'>Impressum</a>" +
"<a href='/datenschutz'>Datenschutz</a> anpassen oder widerrufen.", "<a href='/datenschutz'>Datenschutzerklärung</a>",
acceptAllBtn: "Alle akzeptieren", },
acceptNecessaryBtn: "Nur notwendige", preferencesModal: {
showPreferencesBtn: "Einstellungen anpassen", title: "Cookie-Einstellungen",
footer: acceptAllBtn: "Alle akzeptieren",
"<a href='/impressum'>Impressum</a>" + acceptNecessaryBtn: "Nur notwendige",
"<a href='/datenschutz'>Datenschutzerklärung</a>", savePreferencesBtn: "Auswahl speichern",
}, closeIconLabel: "Schließen",
preferencesModal: { sections: [
title: "Cookie-Einstellungen", {
acceptAllBtn: "Alle akzeptieren", title: "Ihre Privatsphäre",
acceptNecessaryBtn: "Nur notwendige", description:
savePreferencesBtn: "Auswahl speichern", "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.",
closeIconLabel: "Schließen", },
sections: [ {
{ title: "Technisch notwendig",
title: "Ihre Privatsphäre", description:
description: "Diese Cookies sind für den reibungslosen Betrieb der Website unbedingt erforderlich und können nicht deaktiviert werden.",
"Wir setzen Cookies ein, um die Funktionalität unserer Website sicherzustellen " + linkedCategory: "necessary",
"und unser Angebot kontinuierlich zu verbessern. Sie entscheiden selbst, " + },
"welche Kategorien Sie zulassen möchten.", {
}, title: "Analyse & Statistik",
{ description:
title: "Technisch notwendig", "Diese Cookies helfen uns zu verstehen, wie Besucher mit unserer Website interagieren.",
description: linkedCategory: "analytics",
"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 " + title: "Marketing & externe Medien",
"(z. B. Spracheinstellungen oder Sicherheits-Token).", description:
linkedCategory: "necessary", "Diese Cookies ermöglichen die Einbindung externer Inhalte wie YouTube-Videos, Google Maps, Instagram-, Facebook- und X-Inhalte.",
}, linkedCategory: "marketing",
{ },
title: "Analyse & Statistik", {
description: title: "Weitere Informationen",
"Diese Cookies helfen uns zu verstehen, wie Besucher mit unserer Website interagieren. " + description:
"Alle Daten werden anonymisiert gesammelt und dienen ausschließlich zur Verbesserung " + "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>.",
"unseres Angebots. Wir nutzen Google Analytics (GA4) – Ihre IP-Adresse wird dabei " + },
"anonymisiert und nicht gespeichert.", ],
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.",
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>.",
},
],
}, },
}, },
}, },
}, });
}); };
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; return null;
+11
View File
@@ -0,0 +1,11 @@
"use client";
import dynamic from "next/dynamic";
const CookieConsentBanner = dynamic(() => import("./CookieConsent"), {
ssr: false,
});
export default function CookieConsentLoader() {
return <CookieConsentBanner />;
}
+48 -40
View File
@@ -1,15 +1,9 @@
/*
FOOTER
Datei: components/Footer.module.css
*/
:root { :root {
--footer-bg: #171717; --footer-bg: #272727;
--footer-text: #a1a1aa; --footer-text: #adadb3;
--footer-text-hover: #ffffff; --footer-text-hover: #ffffff;
--footer-title: #ffffff; --footer-title: #ffffff;
--footer-divider: rgba(255, 255, 255, 0.1); --footer-divider: rgba(255, 255, 255, 0.1);
--footer-padding-y: clamp(3rem, 6vw, 5rem);
--footer-gap: clamp(2rem, 5vw, 4rem); --footer-gap: clamp(2rem, 5vw, 4rem);
} }
@@ -17,7 +11,6 @@
.footer { .footer {
background-color: var(--footer-bg); background-color: var(--footer-bg);
color: var(--footer-text); color: var(--footer-text);
font-family: inherit;
} }
.footer a:focus-visible { .footer a:focus-visible {
@@ -26,9 +19,10 @@
} }
.footer__inner { .footer__inner {
max-width: 1280px; max-width: var(--container-width);
margin-inline: auto; margin-inline: auto;
padding: var(--footer-padding-y) clamp(1rem, 5vw, 2rem); padding-inline: var(--space-4);
padding-block: var(--space-16);
} }
/* Top */ /* Top */
@@ -38,26 +32,16 @@
gap: var(--footer-gap); 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 */ /* Brand */
.footer__brand {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.footer__logo { .footer__logo {
display: inline-block; display: inline-block;
font-size: 1.4rem; width: fit-content;
font-weight: 700;
color: var(--footer-title);
margin-bottom: 0.75rem;
transition: opacity 0.2s ease;
} }
.footer__logo:hover { .footer__logo:hover {
@@ -65,8 +49,6 @@
} }
.footer__tagline { .footer__tagline {
font-size: 0.9rem;
line-height: 1.6;
max-width: 32ch; max-width: 32ch;
color: var(--footer-text); color: var(--footer-text);
} }
@@ -74,34 +56,32 @@
/* Navigation */ /* Navigation */
.footer__nav { .footer__nav {
display: flex; display: flex;
gap: var(--footer-gap);
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--footer-gap);
} }
.footer__nav ul { .footer__nav ul {
list-style: none; list-style: none;
padding: 0;
margin: 0;
} }
.footer__nav-group ul { .footer__nav-group ul {
padding: 0;
margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.6rem; gap: var(--space-2);
} }
.footer__nav-title { .footer__nav-title {
display: block; display: block;
font-size: 0.8rem; margin-bottom: var(--space-4);
font-weight: 700; font-size: var(--font-size-xxs);
text-transform: uppercase; letter-spacing: var(--letter-spacing-wide);
letter-spacing: 0.08em;
color: var(--footer-title); color: var(--footer-title);
margin-bottom: 1rem;
} }
.footer__nav-group a { .footer__nav-group a {
font-size: 0.9rem; font-size: var(--font-size-xs);
color: var(--footer-text); color: var(--footer-text);
transition: color 0.2s ease; transition: color 0.2s ease;
} }
@@ -157,3 +137,31 @@
border-color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.05); 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;
}
}
+23 -14
View File
@@ -1,11 +1,13 @@
import styles from "./Footer.module.css"; import styles from "./Footer.module.css";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
import { import {
InstagramLogoIcon, InstagramLogoIcon,
LinkedinLogoIcon, LinkedinLogoIcon,
XLogoIcon, XLogoIcon,
FacebookLogoIcon, FacebookLogoIcon,
} from "@phosphor-icons/react/dist/ssr"; } from "@phosphor-icons/react/dist/ssr";
import { companyData } from "@/data/companyData";
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
@@ -13,22 +15,24 @@ export default function Footer() {
return ( return (
<footer className={styles.footer}> <footer className={styles.footer}>
<div className={styles.footer__inner}> <div className={styles.footer__inner}>
{/* Top: Brand + Navigation */}
<div className={styles.footer__top}> <div className={styles.footer__top}>
{/* Brand */}
<div className={styles.footer__brand}> <div className={styles.footer__brand}>
<Link href="/" className={styles.footer__logo}> <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> </Link>
<p className={styles.footer__tagline}>
Maßgeschneiderte digitale Lösungen für Ihr Unternehmen. <p className={styles.footer__tagline}>{companyData.slogan}</p>
</p>
</div> </div>
{/* Navigation Spalten */}
<nav className={styles.footer__nav} aria-label="Footer Navigation"> <nav className={styles.footer__nav} aria-label="Footer Navigation">
<div className={styles.footer__nav_group}> <div className={styles["footer__nav-group"]}>
<span className={styles.footer__nav_title}>Unternehmen</span> <span className={styles["footer__nav-title"]}>Unternehmen</span>
<ul> <ul>
<li> <li>
<Link href="/ueberuns">Über uns</Link> <Link href="/ueberuns">Über uns</Link>
@@ -36,14 +40,17 @@ export default function Footer() {
<li> <li>
<Link href="/leistungen">Leistungen</Link> <Link href="/leistungen">Leistungen</Link>
</li> </li>
<li>
<Link href="/branchenloesungen">Branchenlösungen</Link>
</li>
<li> <li>
<Link href="/kontakt">Kontakt</Link> <Link href="/kontakt">Kontakt</Link>
</li> </li>
</ul> </ul>
</div> </div>
<div className={styles.footer__nav_group}> <div className={styles["footer__nav-group"]}>
<span className={styles.footer__nav_title}>Rechtliches</span> <span className={styles["footer__nav-title"]}>Rechtliches</span>
<ul> <ul>
<li> <li>
<Link href="/impressum">Impressum</Link> <Link href="/impressum">Impressum</Link>
@@ -56,13 +63,12 @@ export default function Footer() {
</nav> </nav>
</div> </div>
{/* Divider */}
<div className={styles.footer__divider} /> <div className={styles.footer__divider} />
{/* Bottom: Copyright + Social */}
<div className={styles.footer__bottom}> <div className={styles.footer__bottom}>
<p className={styles.footer__copy}> <p className={styles.footer__copy}>
© {currentYear} ENVU. Alle Rechte vorbehalten. Copyright © {currentYear} {companyData.companyName}. Alle Rechte
vorbehalten.
</p> </p>
<div className={styles.footer__social}> <div className={styles.footer__social}>
@@ -74,6 +80,7 @@ export default function Footer() {
> >
<InstagramLogoIcon size={20} /> <InstagramLogoIcon size={20} />
</a> </a>
<a <a
href="https://linkedin.com" href="https://linkedin.com"
target="_blank" target="_blank"
@@ -82,6 +89,7 @@ export default function Footer() {
> >
<LinkedinLogoIcon size={20} /> <LinkedinLogoIcon size={20} />
</a> </a>
<a <a
href="https://x.com" href="https://x.com"
target="_blank" target="_blank"
@@ -90,6 +98,7 @@ export default function Footer() {
> >
<XLogoIcon size={20} /> <XLogoIcon size={20} />
</a> </a>
<a <a
href="https://facebook.com" href="https://facebook.com"
target="_blank" target="_blank"
+40 -23
View File
@@ -1,59 +1,55 @@
.hero { .hero {
position: relative; position: relative;
background: linear-gradient(to bottom, #f9f9f9 5%, #ffffff 50%, #f9f9f9 100%); background: var(--color-background-linear);
padding-block: var(--space-16); padding-block: var(--space-16);
width: 100vw; overflow: hidden;
margin-left: calc(50% - 50vw);
margin-right: calc(50% - 50vw);
} }
.inner { .inner {
width: 100%; z-index: 1;
max-width: 1280px; position: relative;
max-width: var(--container-width);
margin-inline: auto; margin-inline: auto;
padding-inline: var(--space-4); padding-inline: var(--space-4);
} }
.heroDecoration { .heroDecoration {
position: absolute; display: none;
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;
} }
.content { .content {
position: relative;
z-index: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-5); gap: var(--space-5);
} }
.heading { .heading {
max-width: 20ch; max-width: 15ch;
} }
.lead { .lead {
max-width: 56ch; max-width: 40ch;
} }
.actions { .actions {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
gap: var(--space-3); gap: var(--space-4);
margin-top: var(--space-2); margin-top: var(--space-2);
} }
.actions :global(.btn) { .actions :global(.btn) {
width: fit-content; width: fit-content;
justify-content: center; font-weight: var(--font-weight-semibold);
min-height: 44px; }
.actions :global(.btn--tertiary) {
padding-top: 0;
padding-bottom: 0;
color: var(--font-body-color);
} }
/* Tablet */ /* Tablet */
@@ -63,17 +59,33 @@
} }
.heroDecoration { .heroDecoration {
display: block;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: auto;
right: 0;
transform: translateY(-50%); transform: translateY(-50%);
height: 90%; width: min(42vw, 420px);
height: auto;
opacity: 0.06;
pointer-events: none;
user-select: none;
z-index: 0;
} }
.actions { .actions {
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
gap: var(--space-6); gap: var(--space-6);
} }
.heading {
max-width: 24ch;
}
.lead {
max-width: 48ch;
}
} }
/* Desktop */ /* Desktop */
@@ -84,4 +96,9 @@
.inner { .inner {
padding-inline: var(--space-6); padding-inline: var(--space-6);
} }
.heroDecoration {
width: min(44vw, 560px);
opacity: 0.08;
}
} }
+24 -11
View File
@@ -1,24 +1,29 @@
import { ArrowRightIcon, PhoneIcon } from "@phosphor-icons/react/dist/ssr"; import { ArrowRightIcon, PhoneIcon } from "@phosphor-icons/react/dist/ssr";
import Link from "next/link"; import Link from "next/link";
import styles from "./HeroSection.module.css"; import styles from "./HeroSection.module.css";
import { companyData } from "@/data/companyData";
import Image from "next/image";
export default function HeroSection() { export default function HeroSection() {
return ( return (
<section className={styles.hero} aria-labelledby="hero-heading"> <section className={styles.hero} aria-labelledby="hero-heading">
<img
src="/logo/logo-mininmal-basiclean-grey.svg"
alt=""
aria-hidden="true"
className={styles.heroDecoration}
/>
<div className={styles.inner}> <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.content}> <div className={styles.content}>
<h1 id="hero-heading" className={styles.heading}> <h1 id="hero-heading" className={styles.heading}>
Mehr Sauberkeit, weniger Aufwand für ein sauberes Arbeitsumfeld Gebäudereinigung für ein makelloses Arbeitsumfeld
</h1> </h1>
<p className={`lead ${styles.lead}`}> <p className={`lead ${styles.lead}`}>
Professionelle Gebäudereinigung für Büros, Praxen und Gewerbeflächen Mit festen Abläufen und hohen Qualitätsstandards – für ein sauberes,
nach festen Abläufen und höchsten Qualitätsstandards. professionelles Arbeitsumfeld.
</p> </p>
<div className={styles.actions}> <div className={styles.actions}>
<Link href="/kontakt" className="btn btn--filled"> <Link href="/kontakt" className="btn btn--filled">
@@ -27,11 +32,19 @@ export default function HeroSection() {
</span> </span>
Jetzt Angebot anfragen Jetzt Angebot anfragen
</Link> </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"> <span className="btn-icon" aria-hidden="true">
<PhoneIcon size={18} weight="bold" /> <PhoneIcon size={18} weight="bold" />
</span> </span>
0172 621 0606 {companyData.contact.phone.display}
</a> </a>
</div> </div>
</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;
}
}
+79
View File
@@ -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>
);
}
+6 -2
View File
@@ -26,6 +26,7 @@
font-weight: var(--nav-font-weight); font-weight: var(--nav-font-weight);
background-color: var(--nav-bg); background-color: var(--nav-bg);
color: var(--nav-font-color); color: var(--nav-font-color);
letter-spacing: 0.1px;
} }
.nav a:focus-visible, .nav a:focus-visible,
@@ -207,10 +208,13 @@
} }
.nav-links > li a { .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 { .nav-links > li:last-child {
padding: 0; padding: 0;
} }
+1 -1
View File
@@ -10,8 +10,8 @@ import { useNavToggleMenu } from "@/hooks/useNavToggleMenu";
import { useSupportsHover } from "@/hooks/useSupportsHover"; import { useSupportsHover } from "@/hooks/useSupportsHover";
import { useToggleBodyOverflow } from "@/hooks/useToggleBodyOverflow"; import { useToggleBodyOverflow } from "@/hooks/useToggleBodyOverflow";
import styles from "./Navbar.module.css"; import styles from "./Navbar.module.css";
import { NavbarSubMenu } from "./NavbarSubMenu";
import { useIsDesktop } from "@/hooks/useIsDesktop"; import { useIsDesktop } from "@/hooks/useIsDesktop";
import { NavbarSubMenu } from "./NavbarSubMenu";
type BasicLink = { type BasicLink = {
label: string; label: string;
@@ -78,6 +78,7 @@
font: inherit; font: inherit;
color: inherit; color: inherit;
height: var(--nav-h); height: var(--nav-h);
letter-spacing: 0.1px;
} }
.nav-item-with-sub { .nav-item-with-sub {
@@ -103,6 +104,20 @@
box-shadow: 0 4px 6px -6px rgb(0 0 0 / 30%); box-shadow: 0 4px 6px -6px rgb(0 0 0 / 30%);
opacity: 0; opacity: 0;
transform: translateY(-8px); 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 { .nav-submenu-list li a:focus-visible {
+137
View File
@@ -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;
}
}
+94
View File
@@ -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>
);
}
+122
View File
@@ -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;
}
}
+83
View File
@@ -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>
);
}
+55
View File
@@ -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;
}
}
+110
View File
@@ -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>
);
}
+137
View File
@@ -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);
}
}
+113
View File
@@ -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>
);
}
+17
View File
@@ -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.",
},
];
+18
View File
@@ -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;
+17
View File
@@ -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}`;
}
+7 -4
View File
@@ -1,12 +1,15 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const allowedOrigin = process.env.NEXT_ALLOWED_DEV_ORIGIN;
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ output: "standalone",
trailingSlash: false, trailingSlash: false,
poweredByHeader: false, poweredByHeader: false,
allowedDevOrigins: [
"192.168.178.59", // deine PC-IP
],
}; };
if (allowedOrigin) {
nextConfig.allowedDevOrigins = [allowedOrigin];
}
export default nextConfig; export default nextConfig;
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

+1
View File
@@ -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

+43 -11
View File
@@ -1,13 +1,13 @@
:root { :root {
/* Colors */ /* Colors */
--btn-bg-primary: #267361; --btn-bg-primary: var(--color-accent);
--btn-bg-primary-hover: #267361; --btn-bg-primary-hover: var(--color-accent);
--btn-bg-primary-active: #353030; --btn-bg-primary-active: #353030;
--btn-text-light: #fefcfb; --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: #e9e9e9;
--btn-border-ghost-hover: #ececec; --btn-border-ghost-hover: #ececec;
--btn-border-ghost-active: #e1e1e1; --btn-border-ghost-active: #e1e1e1;
@@ -25,20 +25,21 @@
--btn-focus-offset: 2px; --btn-focus-offset: 2px;
/* Layout */ /* Layout */
--btn-py: 12px; --btn-py: 8px;
--btn-px: 16px; --btn-px: 12px;
--btn-py-lg: 16px; --btn-py-lg: 16px;
--btn-px-lg: 24px; --btn-px-lg: 20px;
--btn-radius: 50px; --btn-radius: 50px;
--btn-gap: 8px; --btn-gap: 8px;
--btn-max-height: 52px; --btn-max-height: 46px;
--btn-min-height: 38px;
--btn-icon-size: 18px; --btn-icon-size: 18px;
--btn-tertiary-px: 4px; --btn-tertiary-px: 4px;
/* Typography */ /* Typography */
--btn-font-family: var(--font-body); --btn-font-family: var(--font-body);
--btn-font-size: 0.889rem; --btn-font-size: var(--font-size-xs);
--btn-font-weight: var(--font-weight-bold); --btn-font-weight: var(--font-weight-medium);
--btn-line-height: 1; --btn-line-height: 1;
/* Shadow */ /* Shadow */
@@ -72,6 +73,7 @@
user-select: none; user-select: none;
transition: var(--btn-transition); transition: var(--btn-transition);
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
z-index: 999;
} }
.btn:focus { .btn:focus {
@@ -121,12 +123,37 @@
transition: none; 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 */ /* Outline */
.btn--outline { .btn--outline {
background-color: transparent; background-color: transparent;
color: var(--btn-text-dark); color: var(--btn-text-dark);
border-color: var(--btn-border-primary); border-color: var(--btn-border-primary);
box-shadow: var(--btn-shadow-none); box-shadow: var(--btn-shadow-none);
min-height: var(--btn-min-height);
} }
.btn--outline .btn-icon { .btn--outline .btn-icon {
@@ -217,6 +244,12 @@
border-color: var(--btn-bg-primary); border-color: var(--btn-bg-primary);
} }
.btn--filled-inverse:hover {
background-color: #ffffff;
border-color: #ffffff;
color: var(--color-accent);
}
.btn--outline:hover { .btn--outline:hover {
background-color: transparent; background-color: transparent;
border-color: var(--btn-border-primary); border-color: var(--btn-border-primary);
@@ -240,6 +273,5 @@
@media (min-width: 768px) { @media (min-width: 768px) {
.btn { .btn {
padding: var(--btn-py-lg) var(--btn-px-lg); padding: var(--btn-py-lg) var(--btn-px-lg);
font-size: 1rem;
} }
} }
+8 -1
View File
@@ -47,10 +47,17 @@
--color-surface-alt: #f9f9f9; --color-surface-alt: #f9f9f9;
--color-background: #ffffff; --color-background: #ffffff;
--color-background-alt: #f9f9f9; --color-background-alt: #f9f9f9;
--color-background-linear: linear-gradient(
to bottom,
#f9f9f9 5%,
#ffffff 50%,
#f9f9f9 100%
);
--color-text: #171717; --color-text: #171717;
--color-text-muted: #5f5f5f; --color-text-muted: #4a4a4a;
--color-text-soft: #7a7a7a; --color-text-soft: #7a7a7a;
--color-heading: #171717; --color-heading: #171717;
--color-accent: #267361;
/* Radius */ /* Radius */
--radius-none: 0; --radius-none: 0;
+12 -9
View File
@@ -1,8 +1,10 @@
:root { :root {
/* Font sizes */ /* Font sizes */
--font-size-body: 1rem; --font-size-xxs: 0.79rem;
--font-size-small: 0.875rem; --font-size-xs: clamp(0.79rem, 2vw, 0.889rem);
--font-size-lead: 1.125rem; --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) */ /* Fluid typography (768px to 1024px) */
--font-size-h1: clamp(1.802rem, 5.71vw, 3.247rem); --font-size-h1: clamp(1.802rem, 5.71vw, 3.247rem);
@@ -12,7 +14,8 @@
--font-size-h5: clamp(1.125rem, 2.82vw, 1.602rem); --font-size-h5: clamp(1.125rem, 2.82vw, 1.602rem);
--line-height-body: 1.5; --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-tight: -0.02em;
--letter-spacing-normal: 0; --letter-spacing-normal: 0;
@@ -36,20 +39,20 @@ a {
p, p,
li { li {
font-size: var(--font-size-body); font-size: var(--font-size-base);
line-height: var(--line-height-body); line-height: var(--line-height-body);
} }
small, .small,
.text-small { .text-small {
font-size: var(--font-size-small); font-size: var(--font-size-xs);
line-height: 1.4; line-height: 1.4;
} }
.lead, .lead,
.text-lead { .text-lead {
font-size: var(--font-size-lead); font-size: var(--font-size-m);
line-height: 1.4; line-height: var(--line-height-lead);
} }
h1, h1,