578 lines
26 KiB
TypeScript
578 lines
26 KiB
TypeScript
"use client";
|
|
|
|
// =============================================================================
|
|
// Novarix Networks — Homepage
|
|
// =============================================================================
|
|
//
|
|
// This file controls the LAYOUT and STYLING of the homepage.
|
|
// All editable TEXT lives in `/content.ts` at the project root.
|
|
//
|
|
// How this file is organised, top to bottom:
|
|
// 1. Imports + setup
|
|
// 2. Site shell (background gradient + ambient orbs + grid)
|
|
// 3. Header (logo + navigation)
|
|
// 4. Hero (big headline + buttons)
|
|
// 5. Services (three cards under "What we do")
|
|
// 6. How we engage (three cards under "Working with us")
|
|
// 7. Contact (the dark contact card)
|
|
// 8. Footer (company details + copyright)
|
|
//
|
|
// Each section is marked with a clear comment header you can search for.
|
|
// The "use client" line at the top tells Next.js this page runs in the
|
|
// browser (needed for the mouse-tracking glow).
|
|
// =============================================================================
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import React, { useMemo, useState } from "react";
|
|
import { site } from "@/content";
|
|
import { openCookieBanner } from "@/components/CookieBanner";
|
|
import ThemeToggle from "@/components/ThemeToggle";
|
|
|
|
// Type for the mouse-pointer position used to move the background glow.
|
|
type PointerState = { x: number; y: number };
|
|
const WEB3FORMS_ENDPOINT = "https://api.web3forms.com/submit";
|
|
|
|
export default function HomePage() {
|
|
// ---------------------------------------------------------------------------
|
|
// STATE
|
|
// ---------------------------------------------------------------------------
|
|
// pointer — the mouse position (in % of page width/height). Used by the
|
|
// soft glow that follows the cursor in the background.
|
|
// ---------------------------------------------------------------------------
|
|
const [pointer, setPointer] = useState<PointerState>({ x: 50, y: 22 });
|
|
const [copiedEmail, setCopiedEmail] = useState(false);
|
|
const [formStatus, setFormStatus] = useState<
|
|
"idle" | "submitting" | "success" | "error"
|
|
>("idle");
|
|
const [formMessage, setFormMessage] = useState("");
|
|
const web3FormsKey = process.env.NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BACKGROUND POSITION
|
|
// Translates the current mouse pointer into two CSS variables (--mx, --my)
|
|
// that the .site-shell background gradient reads. Memoised so React doesn't
|
|
// rebuild the style object on every render.
|
|
// ---------------------------------------------------------------------------
|
|
const backgroundStyle = useMemo(
|
|
() =>
|
|
({
|
|
["--mx"]: `${pointer.x}%`,
|
|
["--my"]: `${pointer.y}%`,
|
|
}) as React.CSSProperties,
|
|
[pointer.x, pointer.y]
|
|
);
|
|
|
|
async function copyContactEmail() {
|
|
try {
|
|
await navigator.clipboard.writeText(site.contact.email);
|
|
setCopiedEmail(true);
|
|
window.setTimeout(() => setCopiedEmail(false), 1800);
|
|
} catch {
|
|
setCopiedEmail(false);
|
|
}
|
|
}
|
|
|
|
async function handleContactSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
|
|
if (!web3FormsKey) {
|
|
setFormStatus("error");
|
|
setFormMessage(
|
|
"Contact form is not configured yet. Please use the copy email button for now."
|
|
);
|
|
return;
|
|
}
|
|
|
|
const form = event.currentTarget;
|
|
const formData = new FormData(form);
|
|
formData.append("access_key", web3FormsKey);
|
|
formData.append("subject", "Novarix website enquiry");
|
|
formData.append("from_name", "Novarix website");
|
|
formData.append("replyto", String(formData.get("email") ?? ""));
|
|
|
|
const payload = Object.fromEntries(formData);
|
|
|
|
setFormStatus("submitting");
|
|
setFormMessage("Sending message...");
|
|
|
|
try {
|
|
const response = await fetch(WEB3FORMS_ENDPOINT, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
const result = (await response.json()) as {
|
|
success?: boolean;
|
|
message?: string;
|
|
};
|
|
|
|
if (response.ok && result.success) {
|
|
form.reset();
|
|
setFormStatus("success");
|
|
setFormMessage("Message sent. We will get back to you within one working day.");
|
|
return;
|
|
}
|
|
|
|
setFormStatus("error");
|
|
setFormMessage(
|
|
result.message || "The message could not be sent. Please try again."
|
|
);
|
|
} catch {
|
|
setFormStatus("error");
|
|
setFormMessage(
|
|
"The message could not be sent right now. Please try again or copy the email address instead."
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<main
|
|
id="top"
|
|
className="site-shell"
|
|
style={backgroundStyle}
|
|
onMouseMove={(event) => {
|
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
const x = ((event.clientX - rect.left) / rect.width) * 100;
|
|
const y = ((event.clientY - rect.top) / rect.height) * 100;
|
|
setPointer({ x, y });
|
|
}}
|
|
>
|
|
{/* -------------------------------------------------------------------
|
|
AMBIENT LAYER
|
|
Two soft floating "orbs" and a faint grid behind everything.
|
|
Purely decorative. All styles live in globals.css under
|
|
.ambient-orb / .ambient-grid. Sits at z-index -10 so it's
|
|
under the page content.
|
|
------------------------------------------------------------------- */}
|
|
<div
|
|
className="pointer-events-none fixed inset-0 -z-10 overflow-hidden"
|
|
aria-hidden="true"
|
|
>
|
|
<div className="ambient-orb ambient-orb-a" />
|
|
<div className="ambient-orb ambient-orb-b" />
|
|
<div className="ambient-grid" />
|
|
</div>
|
|
|
|
{/* ===================================================================
|
|
HEADER
|
|
Sticky at the top of the page. Contains the wordmark logo (links
|
|
back to top) and the primary navigation pulled from
|
|
content.ts -> site.nav. Two logo images swap automatically for
|
|
light/dark mode.
|
|
=================================================================== */}
|
|
<header className="site-header sticky top-0 z-50 border-b border-[var(--border)] bg-[color-mix(in_srgb,var(--bg)_82%,transparent)] backdrop-blur-xl">
|
|
<div className="mx-auto flex h-20 w-full max-w-6xl items-center justify-between gap-6 px-6 sm:h-24 sm:px-8">
|
|
{/* Logo / wordmark */}
|
|
<a
|
|
href="#top"
|
|
aria-label="Novarix Networks — back to top"
|
|
className="inline-flex shrink-0 items-center"
|
|
>
|
|
<Image
|
|
src="/branding/novarix-wordmark-colour.png"
|
|
alt="Novarix Networks"
|
|
width={2048}
|
|
height={430}
|
|
priority
|
|
className="brand-light h-9 w-auto max-w-[42vw] object-contain sm:h-12"
|
|
/>
|
|
<Image
|
|
src="/branding/novarix-wordmark-white.png"
|
|
alt="Novarix Networks"
|
|
width={2048}
|
|
height={430}
|
|
priority
|
|
className="brand-dark h-9 w-auto max-w-[42vw] object-contain sm:h-12"
|
|
/>
|
|
</a>
|
|
|
|
{/* Desktop navigation — hidden on small screens (sm:flex) */}
|
|
<nav
|
|
aria-label="Primary"
|
|
className="hidden items-center gap-7 text-sm text-[var(--text-soft)] sm:flex"
|
|
>
|
|
{site.nav.map((item) => (
|
|
<a
|
|
key={item.href}
|
|
href={item.href}
|
|
className="transition-colors hover:text-[var(--text)]"
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
{/* Pill-style Contact button on the right */}
|
|
<a
|
|
href="#contact"
|
|
className="inline-flex items-center gap-2 rounded-full border border-[var(--border-strong)] px-4 py-1.5 text-[var(--text)] transition-all hover:border-[var(--accent)] hover:text-[var(--accent)]"
|
|
>
|
|
Contact
|
|
<span aria-hidden="true">→</span>
|
|
</a>
|
|
<ThemeToggle />
|
|
</nav>
|
|
|
|
{/* Mobile controls — simplified for smaller screens */}
|
|
<div className="flex items-center gap-2 sm:hidden">
|
|
<ThemeToggle className="text-xs" />
|
|
<a
|
|
href="#contact"
|
|
className="inline-flex items-center rounded-full border border-[var(--border-strong)] px-3 py-1.5 text-xs text-[var(--text)] transition-colors hover:border-[var(--accent)] hover:text-[var(--accent)]"
|
|
>
|
|
Contact
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* ===================================================================
|
|
HERO
|
|
The first thing visitors see: status pill, eyebrow, big headline,
|
|
descriptive paragraph, and two call-to-action buttons.
|
|
All text comes from content.ts -> site.hero.
|
|
=================================================================== */}
|
|
<section className="relative pt-24 pb-20 sm:pt-32 sm:pb-28">
|
|
<div className="mx-auto w-full max-w-6xl px-6 sm:px-8">
|
|
<div className="max-w-4xl">
|
|
{/* Optional pulsing-dot pill. Hidden if site.hero.badge is "" */}
|
|
{site.hero.badge && (
|
|
<span className="inline-flex items-center gap-2 rounded-full border border-[var(--border)] bg-[var(--surface)] px-3 py-1 text-xs font-medium text-[var(--text-soft)] backdrop-blur">
|
|
<span className="relative flex h-2 w-2">
|
|
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--accent)] opacity-75" />
|
|
<span className="relative inline-flex h-2 w-2 rounded-full bg-[var(--accent)]" />
|
|
</span>
|
|
{site.hero.badge}
|
|
</span>
|
|
)}
|
|
|
|
{/* Small uppercase eyebrow */}
|
|
<p className="mt-7 text-xs font-semibold tracking-[0.18em] text-[var(--accent)] uppercase">
|
|
{site.hero.eyebrow}
|
|
</p>
|
|
|
|
{/* Main headline. The middle word ("accent") is shown in a
|
|
brand-coloured gradient. Font size scales with viewport. */}
|
|
<h1 className="mt-5 text-[clamp(2.5rem,6vw,4.8rem)] leading-[1.04] font-semibold tracking-[-0.04em] text-balance">
|
|
{site.hero.headlineBefore}{" "}
|
|
<span className="bg-gradient-to-r from-brand-500 to-brand-700 bg-clip-text text-transparent dark:from-brand-300 dark:to-brand-500">
|
|
{site.hero.headlineAccent}
|
|
</span>{" "}
|
|
{site.hero.headlineAfter}
|
|
</h1>
|
|
|
|
{/* Supporting paragraph */}
|
|
<p className="mt-7 max-w-2xl text-lg leading-relaxed text-[var(--text-soft)] sm:text-xl">
|
|
{site.hero.description}
|
|
</p>
|
|
|
|
{/* Call-to-action buttons */}
|
|
<div className="mt-10 flex flex-wrap items-center gap-3">
|
|
{/* Primary button — solid background */}
|
|
<a
|
|
href={site.hero.primaryCta.href}
|
|
className="group inline-flex h-12 items-center gap-2 rounded-xl bg-[var(--button-bg)] px-5 text-sm font-semibold text-[var(--button-fg)] shadow-sm transition-all hover:-translate-y-0.5 hover:bg-[var(--button-hover)] hover:shadow-md"
|
|
>
|
|
{site.hero.primaryCta.label}
|
|
<span
|
|
aria-hidden="true"
|
|
className="transition-transform group-hover:translate-x-0.5"
|
|
>
|
|
→
|
|
</span>
|
|
</a>
|
|
{/* Secondary button — outlined */}
|
|
<a
|
|
href={site.hero.secondaryCta.href}
|
|
className="inline-flex h-12 items-center rounded-xl border border-[var(--border-strong)] bg-[var(--surface)] px-5 text-sm font-semibold text-[var(--text)] backdrop-blur transition-all hover:-translate-y-0.5 hover:border-[var(--accent)] hover:text-[var(--accent)]"
|
|
>
|
|
{site.hero.secondaryCta.label}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ===================================================================
|
|
SERVICES
|
|
"What we do" — section heading then a 3-column grid of cards
|
|
(1 column on mobile). The number of cards comes from
|
|
content.ts -> site.services.items, so adding a fourth card there
|
|
automatically appears here. Below the grid sits an optional
|
|
dashed-border "Capabilities" line.
|
|
=================================================================== */}
|
|
<section
|
|
id="services"
|
|
className="relative border-t border-[var(--border)] py-24 sm:py-28"
|
|
>
|
|
<div className="mx-auto w-full max-w-6xl px-6 sm:px-8">
|
|
{/* Section heading */}
|
|
<div className="max-w-3xl">
|
|
<p className="text-xs font-semibold tracking-[0.18em] text-[var(--accent)] uppercase">
|
|
{site.services.eyebrow}
|
|
</p>
|
|
<h2 className="mt-3 text-[clamp(1.9rem,3.2vw,2.75rem)] leading-tight font-semibold tracking-[-0.03em]">
|
|
{site.services.title}
|
|
</h2>
|
|
<p className="mt-5 max-w-2xl text-base leading-relaxed text-[var(--text-soft)] sm:text-lg">
|
|
{site.services.description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Service cards grid — 1 column on mobile, 3 columns on md+ */}
|
|
<div className="mt-12 grid gap-5 sm:gap-6 md:grid-cols-3">
|
|
{site.services.items.map((service) => (
|
|
<article
|
|
key={service.title}
|
|
className="group relative overflow-hidden rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-7 backdrop-blur transition-all hover:-translate-y-1 hover:border-[var(--border-strong)] hover:shadow-[0_20px_40px_-20px_oklch(0_0_0/0.25)]"
|
|
>
|
|
{/* Thin gradient hairline at the top of each card on hover */}
|
|
<div
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-[var(--border-strong)] to-transparent opacity-0 transition-opacity group-hover:opacity-100"
|
|
/>
|
|
{/* Card top row: index number + arrow that appears on hover */}
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-mono text-xs tracking-widest text-[var(--text-soft)]">
|
|
{service.index}
|
|
</span>
|
|
<span
|
|
aria-hidden="true"
|
|
className="text-[var(--text-soft)] opacity-0 transition-all group-hover:translate-x-0.5 group-hover:opacity-100"
|
|
>
|
|
→
|
|
</span>
|
|
</div>
|
|
{/* Card title + body */}
|
|
<h3 className="mt-5 text-lg font-semibold tracking-tight">
|
|
{service.title}
|
|
</h3>
|
|
<p className="mt-3 text-sm leading-relaxed text-[var(--text-soft)]">
|
|
{service.description}
|
|
</p>
|
|
</article>
|
|
))}
|
|
</div>
|
|
|
|
{/* Optional capabilities strip. Hidden if capabilities is "". */}
|
|
{site.services.capabilities && (
|
|
<div className="mt-10 rounded-2xl border border-dashed border-[var(--border)] p-5 text-sm leading-relaxed text-[var(--text-soft)] sm:p-6">
|
|
<span className="font-semibold tracking-wide text-[var(--text)] uppercase">
|
|
Capabilities ·
|
|
</span>{" "}
|
|
{site.services.capabilities}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
|
|
{/* ===================================================================
|
|
HOW WE ENGAGE
|
|
Three engagement-model cards. Same shape as Services but the
|
|
cards are simpler (no number, no hover arrow). Cards come from
|
|
content.ts -> site.engage.items.
|
|
=================================================================== */}
|
|
<section
|
|
id="engage"
|
|
className="relative border-t border-[var(--border)] py-24 sm:py-28"
|
|
>
|
|
<div className="mx-auto w-full max-w-6xl px-6 sm:px-8">
|
|
{/* Section heading */}
|
|
<div className="max-w-3xl">
|
|
<p className="text-xs font-semibold tracking-[0.18em] text-[var(--accent)] uppercase">
|
|
{site.engage.eyebrow}
|
|
</p>
|
|
<h2 className="mt-3 text-[clamp(1.9rem,3.2vw,2.75rem)] leading-tight font-semibold tracking-[-0.03em]">
|
|
{site.engage.title}
|
|
</h2>
|
|
<p className="mt-5 max-w-2xl text-base leading-relaxed text-[var(--text-soft)] sm:text-lg">
|
|
{site.engage.description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Engagement cards grid */}
|
|
<div className="mt-12 grid gap-5 sm:gap-6 md:grid-cols-3">
|
|
{site.engage.items.map((engagement) => (
|
|
<div
|
|
key={engagement.title}
|
|
className="rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-7 backdrop-blur"
|
|
>
|
|
<h3 className="text-lg font-semibold tracking-tight">
|
|
{engagement.title}
|
|
</h3>
|
|
<p className="mt-3 text-sm leading-relaxed text-[var(--text-soft)]">
|
|
{engagement.description}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ===================================================================
|
|
CONTACT
|
|
A single dark, rounded "callout" card with the contact email and
|
|
response-time note. The button uses a `mailto:` link so clicking
|
|
opens the visitor's mail app pre-addressed to us.
|
|
=================================================================== */}
|
|
<section
|
|
id="contact"
|
|
className="relative border-t border-[var(--border)] py-24 sm:py-28"
|
|
>
|
|
<div className="mx-auto w-full max-w-4xl px-6 sm:px-8">
|
|
<div className="overflow-hidden rounded-3xl border border-[var(--border)] bg-[var(--surface)] p-8 backdrop-blur sm:p-12">
|
|
{/* Section heading inside the card */}
|
|
<p className="text-xs font-semibold tracking-[0.18em] text-[var(--accent)] uppercase">
|
|
{site.contact.eyebrow}
|
|
</p>
|
|
<h2 className="mt-3 text-[clamp(1.9rem,3.2vw,2.75rem)] leading-tight font-semibold tracking-[-0.03em]">
|
|
{site.contact.title}
|
|
</h2>
|
|
<p className="mt-5 max-w-2xl text-base leading-relaxed text-[var(--text-soft)] sm:text-lg">
|
|
{site.contact.description}
|
|
</p>
|
|
|
|
<form className="mt-8 space-y-4" onSubmit={handleContactSubmit}>
|
|
<input
|
|
type="checkbox"
|
|
name="botcheck"
|
|
className="hidden"
|
|
tabIndex={-1}
|
|
autoComplete="off"
|
|
/>
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<label className="block">
|
|
<span className="mb-2 block text-sm font-medium text-[var(--text)]">
|
|
Name
|
|
</span>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
required
|
|
className="h-12 w-full rounded-xl border border-[var(--border-strong)] bg-[var(--surface-strong)] px-4 text-sm text-[var(--text)] outline-none transition-colors placeholder:text-[var(--text-soft)] focus:border-[var(--accent)]"
|
|
placeholder="Your name"
|
|
/>
|
|
</label>
|
|
<label className="block">
|
|
<span className="mb-2 block text-sm font-medium text-[var(--text)]">
|
|
Email
|
|
</span>
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
required
|
|
className="h-12 w-full rounded-xl border border-[var(--border-strong)] bg-[var(--surface-strong)] px-4 text-sm text-[var(--text)] outline-none transition-colors placeholder:text-[var(--text-soft)] focus:border-[var(--accent)]"
|
|
placeholder="you@example.com"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<label className="block">
|
|
<span className="mb-2 block text-sm font-medium text-[var(--text)]">
|
|
Message
|
|
</span>
|
|
<textarea
|
|
name="message"
|
|
required
|
|
rows={6}
|
|
className="w-full rounded-xl border border-[var(--border-strong)] bg-[var(--surface-strong)] px-4 py-3 text-sm text-[var(--text)] outline-none transition-colors placeholder:text-[var(--text-soft)] focus:border-[var(--accent)]"
|
|
placeholder="Tell us a little about what you need."
|
|
/>
|
|
</label>
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
<button
|
|
type="submit"
|
|
disabled={formStatus === "submitting"}
|
|
className="inline-flex h-12 items-center rounded-xl bg-[var(--button-bg)] px-5 text-sm font-semibold text-[var(--button-fg)] shadow-sm transition-all hover:-translate-y-0.5 hover:bg-[var(--button-hover)] hover:shadow-md disabled:translate-y-0 disabled:opacity-60 disabled:shadow-none"
|
|
>
|
|
{formStatus === "submitting" ? "Sending..." : "Send message"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={copyContactEmail}
|
|
className="inline-flex h-12 items-center rounded-xl border border-[var(--border-strong)] bg-[var(--surface)] px-5 text-sm font-semibold text-[var(--text)] backdrop-blur transition-all hover:-translate-y-0.5 hover:border-[var(--accent)] hover:text-[var(--accent)]"
|
|
>
|
|
{copiedEmail ? "Copied" : "Copy email"}
|
|
</button>
|
|
<span className="text-sm text-[var(--text-soft)]">
|
|
{site.contact.note}
|
|
</span>
|
|
</div>
|
|
|
|
{formMessage && (
|
|
<p
|
|
className={`text-sm ${
|
|
formStatus === "error"
|
|
? "text-red-500"
|
|
: formStatus === "success"
|
|
? "text-emerald-500"
|
|
: "text-[var(--text-soft)]"
|
|
}`}
|
|
>
|
|
{formMessage}
|
|
</p>
|
|
)}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ===================================================================
|
|
FOOTER
|
|
Two columns on desktop: company details on the left, tagline +
|
|
copyright on the right. Stacks to one column on mobile.
|
|
All text comes from content.ts -> site.footer.
|
|
=================================================================== */}
|
|
<footer className="border-t border-[var(--border)] py-12">
|
|
<div className="mx-auto grid w-full max-w-6xl gap-8 px-6 sm:grid-cols-[1fr_auto] sm:px-8">
|
|
{/* Left column: company name, registration, contact email */}
|
|
<div className="text-sm leading-relaxed text-[var(--text-soft)]">
|
|
<p className="font-semibold text-[var(--text)]">
|
|
{site.footer.company}
|
|
</p>
|
|
<p className="mt-1">{site.footer.registered}</p>
|
|
<p className="mt-1">
|
|
<a
|
|
href={`mailto:${site.footer.email}`}
|
|
className="transition-colors hover:text-[var(--accent)]"
|
|
>
|
|
{site.footer.email}
|
|
</a>
|
|
</p>
|
|
</div>
|
|
{/* Right column: tagline, cookie links, copyright */}
|
|
<div className="flex flex-col gap-2 text-sm text-[var(--text-soft)] sm:items-end sm:text-right">
|
|
<span className="text-xs tracking-[0.14em] uppercase">
|
|
{site.footer.tagline}
|
|
</span>
|
|
{/* Cookie & privacy links — keep them small and unobtrusive */}
|
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs">
|
|
<Link
|
|
href={site.footer.cookiePolicyHref}
|
|
className="transition-colors hover:text-[var(--accent)]"
|
|
>
|
|
{site.footer.cookiePolicyLabel}
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
onClick={openCookieBanner}
|
|
className="cursor-pointer text-left transition-colors hover:text-[var(--accent)]"
|
|
>
|
|
{site.footer.cookiePrefsLabel}
|
|
</button>
|
|
</div>
|
|
<span>
|
|
© {new Date().getFullYear()} {site.footer.company}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</main>
|
|
);
|
|
}
|