update
This commit is contained in:
1 parent
8757431c79
commit
b2a409e6b7
14 files changed
+286
-175
No files matched your search
@@ -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;
|
||||
}
|
||||
+108
-80
@@ -1,94 +1,122 @@
|
||||
"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(() => {
|
||||
CookieConsent.run({
|
||||
categories: {
|
||||
necessary: {
|
||||
enabled: true,
|
||||
readOnly: true,
|
||||
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: {
|
||||
enabled: true,
|
||||
readOnly: true,
|
||||
},
|
||||
analytics: {},
|
||||
marketing: {},
|
||||
},
|
||||
analytics: {},
|
||||
marketing: {},
|
||||
},
|
||||
language: {
|
||||
default: "de",
|
||||
translations: {
|
||||
de: {
|
||||
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.",
|
||||
acceptAllBtn: "Alle akzeptieren",
|
||||
acceptNecessaryBtn: "Nur notwendige",
|
||||
showPreferencesBtn: "Einstellungen anpassen",
|
||||
footer:
|
||||
"<a href='/impressum'>Impressum</a>" +
|
||||
"<a href='/datenschutz'>Datenschutzerklärung</a>",
|
||||
},
|
||||
preferencesModal: {
|
||||
title: "Cookie-Einstellungen",
|
||||
acceptAllBtn: "Alle akzeptieren",
|
||||
acceptNecessaryBtn: "Nur notwendige",
|
||||
savePreferencesBtn: "Auswahl speichern",
|
||||
closeIconLabel: "Schließen",
|
||||
sections: [
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
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).",
|
||||
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.",
|
||||
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>.",
|
||||
},
|
||||
],
|
||||
language: {
|
||||
default: "de",
|
||||
translations: {
|
||||
de: {
|
||||
consentModal: {
|
||||
title: "Ihre Privatsphäre ist uns wichtig",
|
||||
description:
|
||||
"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",
|
||||
footer:
|
||||
"<a href='/impressum'>Impressum</a>" +
|
||||
"<a href='/datenschutz'>Datenschutzerklärung</a>",
|
||||
},
|
||||
preferencesModal: {
|
||||
title: "Cookie-Einstellungen",
|
||||
acceptAllBtn: "Alle akzeptieren",
|
||||
acceptNecessaryBtn: "Nur notwendige",
|
||||
savePreferencesBtn: "Auswahl speichern",
|
||||
closeIconLabel: "Schließen",
|
||||
sections: [
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "Technisch notwendig",
|
||||
description:
|
||||
"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.",
|
||||
linkedCategory: "analytics",
|
||||
},
|
||||
{
|
||||
title: "Marketing & externe Medien",
|
||||
description:
|
||||
"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 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>.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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 />;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
:root {
|
||||
--footer-bg: #171717;
|
||||
--footer-bg: #272727 ;
|
||||
--footer-text: #a1a1aa;
|
||||
--footer-text-hover: #ffffff;
|
||||
--footer-title: #ffffff;
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function Footer() {
|
||||
{/* Bottom: Copyright + Social */}
|
||||
<div className={styles.footer__bottom}>
|
||||
<p className={styles.footer__copy}>
|
||||
© {currentYear} ENVU. Alle Rechte vorbehalten.
|
||||
© {currentYear}. Alle Rechte vorbehalten.
|
||||
</p>
|
||||
|
||||
<div className={styles.footer__social}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import styles from "./Header.module.css";
|
||||
import Navbar from "./navbar/Navbar";
|
||||
import Navbar from "./Navbar/Navbar";
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
position: relative;
|
||||
background: var(--color-background-linear);
|
||||
padding-block: var(--space-16);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: var(--container-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--space-4);
|
||||
@@ -14,68 +17,45 @@
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
transform: translate(-50%, -50%);
|
||||
right: var(--space-4);
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
opacity: 0.08;
|
||||
width: min(78vw, 360px);
|
||||
height: auto;
|
||||
opacity: 0.05;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.heading {
|
||||
max-width: 15ch;
|
||||
}
|
||||
|
||||
.lead {
|
||||
max-width: 40ch;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--space-4);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.actions :global(.btn) {
|
||||
width: fit-content;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.actions :global(.btn--tertiary) {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
color: var(--font-body-color);
|
||||
}
|
||||
|
||||
/* Tablet */
|
||||
@media (min-width: 768px) {
|
||||
.hero {
|
||||
padding-block: var(--space-28);
|
||||
}
|
||||
|
||||
.heroDecoration {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: auto;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
height: 90%;
|
||||
width: 50%;
|
||||
width: min(42vw, 420px);
|
||||
opacity: 0.06;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.heading {
|
||||
max-width: 24ch;
|
||||
}
|
||||
@@ -85,12 +65,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.hero {
|
||||
padding-block: var(--space-36);
|
||||
}
|
||||
|
||||
.inner {
|
||||
padding-inline: var(--space-6);
|
||||
}
|
||||
|
||||
.heroDecoration {
|
||||
width: min(44vw, 560px);
|
||||
opacity: 0.08;
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -1,17 +1,22 @@
|
||||
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
|
||||
src="/logo/logo-mininmal-basiclean-grey.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className={styles.heroDecoration}
|
||||
/>
|
||||
<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}>
|
||||
<h1 id="hero-heading" className={styles.heading}>
|
||||
Gebäudereinigung für ein makelloses Arbeitsumfeld
|
||||
@@ -28,18 +33,18 @@ export default function HeroSection() {
|
||||
Jetzt Angebot anfragen
|
||||
</Link>
|
||||
<Link href="/Leistungen" className="btn btn--outline">
|
||||
{/* <span className="btn-icon animate" aria-hidden="true">
|
||||
<ArrowRightIcon size={18} weight="bold" />
|
||||
</span> */}
|
||||
Leistungen ansehen
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<a href="tel:+491726210606" className="btn btn--tertiary">
|
||||
<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 6210606
|
||||
{companyData.contact.phone.display}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,17 +5,23 @@ import {
|
||||
ClockIcon,
|
||||
} from "@phosphor-icons/react/dist/ssr";
|
||||
|
||||
export type ContactInfo = {
|
||||
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;
|
||||
obfuscated?: {
|
||||
user?: string;
|
||||
domain?: string;
|
||||
tel?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ContactInfo = CompanyContactItem | StaticContactItem;
|
||||
|
||||
export type ContactCtaData = {
|
||||
heading: string;
|
||||
lead: string;
|
||||
@@ -31,18 +37,19 @@ export const contactCtaData: ContactCtaData = {
|
||||
buttonHref: "/kontakt",
|
||||
contacts: [
|
||||
{
|
||||
type: "company",
|
||||
contactKey: "phone",
|
||||
icon: PhoneIcon,
|
||||
label: "0172 621 0606",
|
||||
obfuscated: { tel: "01726210606" },
|
||||
},
|
||||
{
|
||||
type: "company",
|
||||
contactKey: "email",
|
||||
icon: EnvelopeSimpleIcon,
|
||||
label: "info@basiclean.de",
|
||||
obfuscated: { user: "info", domain: "basiclean.de" },
|
||||
},
|
||||
{
|
||||
type: "company",
|
||||
contactKey: "openingHours",
|
||||
icon: ClockIcon,
|
||||
label: "Mo. - Fr. 08:00 - 17:00 Uhr",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,32 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useRef } from "react";
|
||||
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 ObfuscatedLink({ item }: { item: ContactInfo }) {
|
||||
const ref = useRef<HTMLAnchorElement>(null);
|
||||
function resolveCompanyContact(
|
||||
item: Extract<ContactInfo, { type: "company" }>,
|
||||
) {
|
||||
if (item.contactKey === "phone") {
|
||||
return {
|
||||
label: companyData.contact.phone.display,
|
||||
href: `tel:${companyData.contact.phone.value}`,
|
||||
};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
if (item.contactKey === "email") {
|
||||
const email = `${companyData.contact.email.user}@${companyData.contact.email.domain}`;
|
||||
|
||||
if (item.obfuscated?.tel) {
|
||||
ref.current.href = `tel:${item.obfuscated.tel}`;
|
||||
} else if (item.obfuscated?.user && item.obfuscated?.domain) {
|
||||
ref.current.href = `mailto:${item.obfuscated.user}@${item.obfuscated.domain}`;
|
||||
}
|
||||
}, [item.obfuscated]);
|
||||
return {
|
||||
label: email,
|
||||
href: `mailto:${email}`,
|
||||
};
|
||||
}
|
||||
|
||||
const Icon = item.icon;
|
||||
if (item.contactKey === "openingHours") {
|
||||
return {
|
||||
label: companyData.contact.openingHours.display,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<a ref={ref} href="#" className={styles.contactItem}>
|
||||
<Icon size={20} weight="regular" aria-hidden="true" />
|
||||
{item.label}
|
||||
</a>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function ContactCtaSection() {
|
||||
@@ -55,16 +58,50 @@ export default function ContactCtaSection() {
|
||||
<hr className={styles.divider} aria-hidden="true" />
|
||||
|
||||
<div className={styles.contactRow}>
|
||||
{contacts.map((item: ContactInfo) =>
|
||||
item.obfuscated ? (
|
||||
<ObfuscatedLink key={item.label} item={item} />
|
||||
) : (
|
||||
{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}>
|
||||
<item.icon size={20} weight="regular" aria-hidden="true" />
|
||||
{icon}
|
||||
{item.label}
|
||||
</span>
|
||||
),
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user