"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. Intro overlay (the animated logo shown on first visit) // 3. Site shell (background gradient + ambient orbs + grid) // 4. Header (logo + navigation) // 5. Hero (big headline + buttons) // 6. Services (three cards under "What we do") // 7. How we engage (three cards under "Working with us") // 8. Contact (the dark contact card) // 9. 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 animated intro and the mouse-tracking glow). // ============================================================================= import Image from "next/image"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { site } from "@/content"; import { openCookieBanner } from "@/components/CookieBanner"; // Type for the mouse-pointer position used to move the background glow. type PointerState = { x: number; y: number }; const INTRO_SEEN_KEY = "novarix-intro-seen"; const INTRO_EVENT = "novarix:start-intro"; export default function HomePage() { // --------------------------------------------------------------------------- // STATE // --------------------------------------------------------------------------- // showIntro — true while the animated logo intro is playing. // pointer — the mouse position (in % of page width/height). Used by the // soft glow that follows the cursor in the background. // --------------------------------------------------------------------------- const [showIntro, setShowIntro] = useState(() => { if (typeof document === "undefined") return false; return document.documentElement.dataset.showIntro === "1"; }); const [pointer, setPointer] = useState({ x: 50, y: 22 }); // --------------------------------------------------------------------------- // INTRO OVERLAY EFFECT // Plays the animated logo + wordmark the first time someone visits the // site in this browser tab. We decide before hydration whether to play it, // which avoids the homepage flashing briefly before the intro appears. // After the user accepts cookies on their first visit, we trigger the intro // immediately from the banner close action. // --------------------------------------------------------------------------- useEffect(() => { function playIntro() { document.documentElement.dataset.showIntro = "1"; setShowIntro(true); try { window.sessionStorage.setItem(INTRO_SEEN_KEY, "true"); } catch { /* storage unavailable — silently ignore */ } const timer = window.setTimeout(() => { document.documentElement.dataset.showIntro = "0"; setShowIntro(false); }, 3950); return timer; } let timer: number | null = null; try { const introSeen = window.sessionStorage.getItem(INTRO_SEEN_KEY); const consent = window.localStorage.getItem("novarix-cookie-consent"); const shouldPlayNow = document.documentElement.dataset.showIntro === "1" && consent === "ack" && !introSeen; if (shouldPlayNow) { timer = playIntro(); } else { document.documentElement.dataset.showIntro = "0"; // eslint-disable-next-line react-hooks/set-state-in-effect setShowIntro(false); } } catch { document.documentElement.dataset.showIntro = "0"; // eslint-disable-next-line react-hooks/set-state-in-effect setShowIntro(false); } function handleStartIntro() { if (timer !== null) window.clearTimeout(timer); timer = playIntro(); } window.addEventListener(INTRO_EVENT, handleStartIntro); return () => { if (timer !== null) window.clearTimeout(timer); window.removeEventListener(INTRO_EVENT, handleStartIntro); }; }, []); // --------------------------------------------------------------------------- // 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] ); return ( <> {/* ===================================================================== INTRO OVERLAY A single self-contained animated SVG of the Novarix wordmark, drawn dead-centre in the viewport. The SVG itself contains all the animation (strokes draw over 2s, fill in over the next 1s — see /public/branding/animated_logo_intro.svg). Hidden after ~4s (see the useEffect above + the CSS fade-out timing). ===================================================================== */} {showIntro && (