"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({ 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) { 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 (
{ 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. ------------------------------------------------------------------- */}