diff --git a/EDITING.md b/EDITING.md new file mode 100644 index 00000000..1fddc852 --- /dev/null +++ b/EDITING.md @@ -0,0 +1,166 @@ +# Editing The Website + +This is a friendly guide for changing the words on the Novarix Networks website. You don't need to be a developer to use it. + +## What You Need + +- A text editor — `VS Code`, `Sublime Text`, or even `Notepad++` are all fine. +- The repo cloned to your computer (ask if you don't have it yet). + +That's it. You don't need Node.js installed locally if you only want to change text — the build runs on the Ubuntu Nginx VM. + +## The Only File You Need To Touch + +```text +content.ts +``` + +This file lives at the top of the project, next to `README.md`. Open it in your text editor. + +You will see sections like this: + +```ts +hero: { + badge: "Onboarding new accounts", + eyebrow: "Network Consulting · Remote Support · Engineering", + ... +}, +``` + +To change a piece of text, change what's inside the `"double quotes"`. **Keep the quotes**. **Keep the comma at the end of the line.** + +## Common Edits + +### Change the headline + +Find this block near the top of `content.ts`: + +```ts +headlineBefore: "Network expertise, on tap, for teams running", +headlineAccent: "production", +headlineAfter: "infrastructure.", +``` + +The headline is split into three parts so the middle word can be coloured with the brand gradient. Change any of the three pieces. + +### Change a service description + +Find the `services` section. Inside the square brackets `[ ... ]` you will see three blocks like this: + +```ts +{ + index: "01", + title: "Network Consulting", + description: "Architecture review, design, and second-opinion engineering...", +}, +``` + +Edit the `title` or `description`. The `index` is just the small "01 / 02 / 03" label on the card. + +### Add a fourth service + +Copy one of the existing service blocks (the whole thing from `{` to `},`), paste it inside the same `[ ... ]`, and change the text. Don't forget the comma after the closing `}`. Example: + +```ts +items: [ + { index: "01", title: "Network Consulting", description: "..." }, + { index: "02", title: "Remote Network Support", description: "..." }, + { index: "03", title: "Architecture & Design", description: "..." }, + { index: "04", title: "Your New Service", description: "Your description here." }, +], +``` + +### Remove a service + +Delete one of the blocks (from `{` all the way through `},`). Make sure each remaining block still ends with a comma. + +### Change the contact email + +Find the `contact` and `footer` sections and update the `email` field in both: + +```ts +email: "hello@novarix.uk", +``` + +### Change the company number + +Find the `footer` section: + +```ts +registered: "Registered in England · Company No. 17047180", +``` + +### Hide the "Onboarding new accounts" pill + +Set the `badge` field to an empty pair of quotes: + +```ts +badge: "", +``` + +### Hide the capabilities line + +Same trick — set `capabilities` to an empty pair of quotes: + +```ts +capabilities: "", +``` + +### Add or remove a navigation link + +Find the `nav` section: + +```ts +nav: [ + { label: "Services", href: "#services" }, + { label: "How we engage", href: "#engage" }, +], +``` + +Each link has a `label` (the text shown) and an `href` (where it jumps to). For links to sections of this page, use `"#services"`, `"#engage"`, or `"#contact"`. For external links use a full URL like `"https://example.com"`. + +## Special Characters + +To put a hyphen-like dot between words use the middle dot `·` (option-shift-9 on Mac, alt+0183 on Windows). To use a curly apostrophe, type `’` not `'` — both work but the curly one looks better. + +If you need an em dash, use `—` not `--`. + +## Saving And Publishing + +After editing `content.ts`, save the file and: + +1. Open a terminal in the project folder. +2. Commit your changes: + ```bash + git add content.ts + git commit -m "Update homepage copy" + git push + ``` +3. SSH into the Nginx VM and run: + ```bash + cd /var/www/novarix.uk + ./deploy.sh + ``` + +The site will be rebuilt and reloaded — usually within 30 seconds. + +## If You Want To Preview Changes Locally First + +You'll need Node.js 20 or newer installed once. Then in the project folder: + +```bash +npm install +npm run dev +``` + +Open `http://localhost:3000` in your browser. The page updates as you save `content.ts`. + +## When Things Go Wrong + +If the build fails after you save, you have probably: + +- Removed a quote `"` somewhere +- Removed a comma at the end of a line +- Removed a closing brace `}` or square bracket `]` + +The error message in the terminal will usually tell you which line in `content.ts` is the problem. Open the file, find that line, and look for one of the things above. If in doubt, undo your change with `git checkout content.ts` and start again with one small change at a time. diff --git a/README.md b/README.md index 4f597a20..9450ccc2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,199 @@ -# novarix-uk +# Novarix Networks Website +`Next.js 16` + `Tailwind CSS v4` website for `Novarix Networks`, built as a fully static site. + +The site is designed for this workflow: + +- edit locally +- push changes to your internal `Gitea` repo +- run one deploy command on the Ubuntu Nginx VM +- serve the static `out/` directory from `/var/www/novarix.uk/out` +- expose it publicly through `Nginx Proxy Manager` + +## Recommended Repo Name + +```text +novarix-networks-homepage +``` + +Keep all the Novarix domains in repos under the same convention so paths and clone URLs stay predictable, e.g. + +```text +http://10.10.10.11:3000/kismet.hasanaj/novarix-networks-homepage.git +``` + +## Project Structure + +- `content.ts` **all the editable text on the website lives here** +- `app/page.tsx` page layout and styling (React + Tailwind utilities) +- `app/layout.tsx` site-wide metadata, fonts, html shell +- `app/globals.css` Tailwind v4 entrypoint and design tokens +- `app/sitemap.ts` generates `/sitemap.xml` at build time +- `app/robots.ts` generates `/robots.txt` at build time +- `public/` static assets (logos, branding, favicon) +- `next.config.ts` configured for static export to `out/` +- `EDITING.md` plain-English guide for changing content +- `deploy.sh` server-side update script +- `ops/nginx/novarix.uk.conf.example` example Nginx config + +## Important Tailwind / Next.js Note + +This project uses the current Tailwind CSS v4 setup via `@tailwindcss/postcss`, configured entirely inside `app/globals.css` (no `tailwind.config.js`). + +The build is a **static export**. `npm run build` produces a fully self-contained `out/` directory. Nginx serves files from that directory directly — there is no Node runtime on the production server. + +## Ubuntu 24.04 Nginx Server Setup + +Install Nginx and Git: + +```bash +sudo apt update +sudo apt install -y nginx git +sudo systemctl enable nginx +sudo systemctl start nginx +``` + +Install Node.js 20 or newer. NodeSource currently supports Ubuntu 24.04 with Node 22, which is a good choice for this server: + +```bash +sudo apt install -y curl +curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh +sudo -E bash nodesource_setup.sh +sudo apt install -y nodejs +``` + +After installing, confirm: + +```bash +node --version +npm --version +``` + +Create the web directory and make your normal sudo user the owner: + +```bash +sudo mkdir -p /var/www +sudo chown -R $USER:$USER /var/www +``` + +Clone the Gitea repo: + +```bash +git clone http://10.10.10.11:3000/kismet.hasanaj/novarix-networks-homepage.git /var/www/novarix.uk +``` + +Install the website dependencies and build the static site: + +```bash +cd /var/www/novarix.uk +npm install --no-package-lock +npm run build +``` + +After the build completes, the static site lives in: + +```text +/var/www/novarix.uk/out +``` + +Use the Nginx config in: + +```text +ops/nginx/novarix.uk.conf.example +``` + +The important Nginx root is: + +```nginx +root /var/www/novarix.uk/out; +``` + +Enable the site: + +```bash +sudo cp ops/nginx/novarix.uk.conf.example /etc/nginx/sites-available/novarix.uk +sudo ln -s /etc/nginx/sites-available/novarix.uk /etc/nginx/sites-enabled/ +sudo rm -f /etc/nginx/sites-enabled/default +sudo nginx -t +sudo systemctl reload nginx +``` + +## Updating The Live Website + +After pushing changes to Gitea from your local machine, SSH into the Nginx VM and run: + +```bash +cd /var/www/novarix.uk +./deploy.sh +``` + +The deploy script does this: + +```bash +git pull origin main +npm install --no-package-lock +npm run build +sudo nginx -t +sudo systemctl reload nginx +``` + +If the script is not executable yet, run this once: + +```bash +chmod +x /var/www/novarix.uk/deploy.sh +``` + +## Editing The Site + +For text content (headlines, services, contact details), edit: + +```text +content.ts +``` + +For visual styling, layout, or new sections, edit: + +```text +app/page.tsx +app/globals.css +``` + +The friendly walk-through for non-developers is: + +```text +EDITING.md +``` + +## Local Development + +For previewing changes on your own machine before pushing: + +```bash +npm install +npm run dev +``` + +Then open `http://localhost:3000` in your browser. The page reloads automatically as you save files. + +## Nginx Proxy Manager + +Create a proxy host: + +- Domain: `novarix.uk` +- Scheme: `http` +- Forward hostname/IP: private IP of the Ubuntu Nginx VM +- Forward port: `80` +- SSL: request certificate and force SSL +- Enable `Block Common Exploits` + +If you also want to redirect `www.novarix.uk` and any defensive domains (e.g. `novarixnet.com`), add them as additional proxy hosts pointing at the same backend, or use NPM's redirect host feature. + +## Canonical Domain + +Recommended primary domain: + +```text +novarix.uk +``` + +Redirect any other Novarix domains to the primary domain once DNS and Nginx Proxy Manager are ready. diff --git a/app/cookies/page.tsx b/app/cookies/page.tsx new file mode 100644 index 00000000..9813e4eb --- /dev/null +++ b/app/cookies/page.tsx @@ -0,0 +1,217 @@ +// ============================================================================= +// Cookie & Privacy Policy +// ============================================================================= +// +// A plain, honest policy page describing exactly what (very little) is +// stored in the visitor's browser. Update the `lastUpdated` field below +// whenever the policy changes. +// +// The wording is deliberately written in first-person plain English rather +// than legalese — Novarix is small enough to be transparent and brief. +// ============================================================================= + +import type { Metadata } from "next"; +import Link from "next/link"; +import { site } from "@/content"; + +const lastUpdated = "2 May 2026"; + +export const metadata: Metadata = { + title: "Cookie & Privacy Policy", + description: + "How Novarix Networks uses cookies and browser storage on this website.", +}; + +export default function CookiePolicyPage() { + return ( +
+ {/* Ambient background, matching the homepage */} +
+ ); +} diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 00000000..cab13389 Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 00000000..b89e9094 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,338 @@ +/* ============================================================================ + Novarix Networks — Global stylesheet + ============================================================================ + + This file controls the visual *theme* of the site (colours, fonts, + background effects, animations). Most page layout is done with Tailwind + utility classes directly inside `app/page.tsx` — this file only handles + things that don't fit neatly into utilities. + + What's in here, top to bottom: + 1. Design tokens — brand colours, font, shadows (@theme block) + 2. Theme variables — light + dark mode CSS variables + 3. Base styles — body font, focus rings, text selection + 4. Site shell + ambient — background gradient, floating orbs, grid + 5. Brand wordmark swap — light/dark logo switcher + 6. Intro overlay — first-visit animated logo + 7. Animations — keyframes used above + 8. Reduced motion — respect "prefers-reduced-motion" setting + + To change the brand colour palette, edit the `--color-brand-*` lines in + the @theme block below. Everything else cascades from those. + ============================================================================ */ + +@import "tailwindcss"; + +/* --------------------------------------------------------------------------- + 1. Design tokens — registered with Tailwind v4 via @theme. + These become utility classes (e.g. `bg-brand-500`, `text-ink-900`) and + custom CSS variables you can use anywhere in the stylesheet. + --------------------------------------------------------------------------- */ +@theme { + --font-sans: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", sans-serif; + + --color-brand-50: oklch(0.97 0.015 240); + --color-brand-100: oklch(0.93 0.04 240); + --color-brand-200: oklch(0.86 0.07 240); + --color-brand-300: oklch(0.78 0.11 240); + --color-brand-400: oklch(0.7 0.14 240); + --color-brand-500: oklch(0.62 0.16 240); + --color-brand-600: oklch(0.54 0.18 245); + --color-brand-700: oklch(0.46 0.18 250); + + --color-ink-50: oklch(0.985 0.003 250); + --color-ink-100: oklch(0.96 0.005 250); + --color-ink-200: oklch(0.92 0.008 250); + --color-ink-300: oklch(0.84 0.012 250); + --color-ink-400: oklch(0.65 0.018 250); + --color-ink-500: oklch(0.5 0.02 250); + --color-ink-600: oklch(0.38 0.022 250); + --color-ink-700: oklch(0.28 0.024 250); + --color-ink-800: oklch(0.2 0.026 250); + --color-ink-900: oklch(0.13 0.028 255); + --color-ink-950: oklch(0.08 0.03 260); + + --radius-card: 1.25rem; + --shadow-card: 0 1px 0 0 oklch(1 0 0 / 0.04) inset, 0 12px 32px -12px oklch(0 0 0 / 0.18); +} + +/* --------------------------------------------------------------------------- + 2. Theme variables (light + dark) + These are the runtime CSS variables read by both this file and + app/page.tsx (via classes like `bg-[var(--surface)]`). Each variable has + a light value here and a dark override further down inside the + `@media (prefers-color-scheme: dark)` block. + --------------------------------------------------------------------------- */ +:root { + color-scheme: light dark; + + --bg: var(--color-ink-50); + --surface: oklch(1 0 0 / 0.7); + --surface-strong: oklch(1 0 0 / 0.92); + --text: var(--color-ink-900); + --text-soft: var(--color-ink-500); + --border: oklch(0.13 0.028 255 / 0.1); + --border-strong: oklch(0.13 0.028 255 / 0.18); + --accent: var(--color-brand-500); + --accent-soft: oklch(0.62 0.16 240 / 0.12); + --ring: oklch(0.62 0.16 240 / 0.4); + --grid: oklch(0.5 0.02 250 / 0.08); + + --button-bg: var(--color-ink-900); + --button-fg: var(--color-ink-50); + --button-hover: var(--color-ink-800); +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: var(--color-ink-950); + --surface: oklch(0.13 0.028 255 / 0.55); + --surface-strong: oklch(0.13 0.028 255 / 0.85); + --text: var(--color-ink-100); + --text-soft: var(--color-ink-400); + --border: oklch(1 0 0 / 0.08); + --border-strong: oklch(1 0 0 / 0.16); + --accent: var(--color-brand-400); + --accent-soft: oklch(0.7 0.14 240 / 0.18); + --ring: oklch(0.7 0.14 240 / 0.5); + --grid: oklch(0.84 0.012 250 / 0.06); + + --button-bg: var(--color-ink-100); + --button-fg: var(--color-ink-950); + --button-hover: var(--color-ink-50); + } +} + +/* --------------------------------------------------------------------------- + 3. Base styles — applied to plain HTML elements before any classes hit. + --------------------------------------------------------------------------- */ +@layer base { + html { + scroll-behavior: smooth; + } + + body { + background: var(--bg); + color: var(--text); + font-family: var(--font-sans); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + overflow-x: hidden; + } + + ::selection { + background: var(--accent-soft); + color: var(--text); + } + + :focus-visible { + outline: 2px solid var(--ring); + outline-offset: 3px; + border-radius: 4px; + } +} + +/* --------------------------------------------------------------------------- + 4. Site shell + ambient effects + The .site-shell class is on the
element in page.tsx. The two + radial gradients here follow the user's mouse via the --mx / --my CSS + variables that page.tsx writes on every mousemove. + --------------------------------------------------------------------------- */ +.site-shell { + position: relative; + isolation: isolate; + min-height: 100vh; + background: + radial-gradient( + circle at var(--mx, 50%) var(--my, 22%), + oklch(0.62 0.16 240 / 0.12), + transparent 20% + ), + radial-gradient( + circle at calc(var(--mx, 50%) * 0.65) calc(var(--my, 22%) * 1.2), + oklch(0.6 0.14 280 / 0.09), + transparent 24% + ), + var(--bg); +} + +.ambient-grid { + position: absolute; + inset: 0; + background-image: + linear-gradient(to right, var(--grid) 1px, transparent 1px), + linear-gradient(to bottom, var(--grid) 1px, transparent 1px); + background-size: 56px 56px; + mask-image: linear-gradient(to bottom, oklch(0 0 0 / 0.5), transparent 80%); +} + +.ambient-orb { + position: absolute; + border-radius: 9999px; + filter: blur(80px); + opacity: 0.22; + animation: drift 18s ease-in-out infinite; + will-change: transform; +} + +.ambient-orb-a { + width: 32rem; + height: 32rem; + top: 4rem; + right: -10rem; + background: linear-gradient( + 135deg, + oklch(0.7 0.14 240 / 0.55), + oklch(0.6 0.14 200 / 0.1) + ); +} + +.ambient-orb-b { + width: 26rem; + height: 26rem; + top: 28rem; + left: -8rem; + background: linear-gradient( + 135deg, + oklch(0.6 0.14 280 / 0.35), + oklch(0.78 0.11 200 / 0.12) + ); + animation-duration: 22s; +} + +/* --------------------------------------------------------------------------- + 5. Brand wordmark colour-scheme swap + The header logo has two PNG variants — a colour one for light mode and a + white one for dark mode. The `brand-light` / `brand-dark` classes (set + in page.tsx) live on both tags; only the matching one is shown. + --------------------------------------------------------------------------- */ +.brand-light { + display: block; +} + +.brand-dark { + display: none; +} + +@media (prefers-color-scheme: dark) { + .brand-light { + display: none; + } + + .brand-dark { + display: block; + } +} + +/* --------------------------------------------------------------------------- + 6. Intro overlay + The first-visit animated SVG wordmark. The SVG itself runs a 3-second + stroke-then-fill animation (defined inside + /public/branding/animated_logo_intro.svg). The overlay fades out at the + 3-second mark so the SVG completes before it disappears. Total intro + length is matched in page.tsx's setTimeout (3600ms). + + When .intro-running is on the
element, the page content + underneath is hidden so it doesn't flash through during the animation. + --------------------------------------------------------------------------- */ +.intro-overlay { + position: fixed; + inset: 0; + z-index: 2000; + display: grid; + place-items: center; /* dead-centre the SVG in the viewport */ + animation: intro-fade-out 600ms ease 3s forwards; +} + +.intro-backdrop { + position: absolute; + inset: 0; + background: var(--bg); +} + +.intro-svg { + position: relative; /* sits above .intro-backdrop in the stacking order */ + display: block; + width: min(70vw, 900px); + max-height: 70vh; + height: auto; + object-fit: contain; +} + +.intro-running .site-header, +.intro-running main > section, +.intro-running main > footer { + opacity: 0; +} + +@media (max-width: 720px) { + .intro-svg { + width: min(86vw, 560px); + } +} + +@media (max-width: 480px) { + .intro-svg { + width: 92vw; + } +} + +/* --------------------------------------------------------------------------- + 7. Animations — keyframes used by the orbs and the intro overlay above. + --------------------------------------------------------------------------- */ +@keyframes drift { + 0%, + 100% { + transform: translate3d(0, 0, 0) scale(1); + } + 50% { + transform: translate3d(0, 18px, 0) scale(1.06); + } +} + +@keyframes intro-wordmark { + 0% { + opacity: 0; + transform: translateY(110%) scale(0.98); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes intro-fade-out { + to { + opacity: 0; + visibility: hidden; + pointer-events: none; + } +} + +/* --------------------------------------------------------------------------- + 8. Reduced motion + Respects the user's OS-level "reduce motion" preference by disabling + the orb drift, the intro animation, and any transition/animation + durations across the site. + --------------------------------------------------------------------------- */ +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + .ambient-orb, + .intro-overlay, + .intro-wordmark { + animation: none !important; + } + + *, + *::before, + *::after { + transition-duration: 0ms !important; + animation-duration: 0ms !important; + } +} diff --git a/app/globals.css.bak b/app/globals.css.bak new file mode 100644 index 00000000..a2dc41ec --- /dev/null +++ b/app/globals.css.bak @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 00000000..79814b20 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,72 @@ +import type { Metadata, Viewport } from "next"; +import "./globals.css"; +import CookieBanner from "@/components/CookieBanner"; + +export const metadata: Metadata = { + metadataBase: new URL("https://novarix.uk"), + title: { + default: "Novarix Networks", + template: "%s | Novarix Networks", + }, + description: + "Novarix Networks provides network consulting, remote network support, and architecture services for organisations running production network infrastructure.", + applicationName: "Novarix Networks", + keywords: [ + "Novarix Networks", + "ISP", + "Managed Service Provider", + "MSP", + "Network Consulting", + "Internet Connectivity", + "BGP", + "IXP", + "CDN Edge", + "Network Engineering", + ], + authors: [{ name: "Novarix Networks" }], + creator: "Novarix Networks", + publisher: "Novarix Networks", + alternates: { + canonical: "/", + }, + openGraph: { + type: "website", + url: "https://novarix.uk", + siteName: "Novarix Networks", + title: "Novarix Networks", + description: + "Engineering-led network consulting, remote support, and architecture for production networks.", + }, + twitter: { + card: "summary_large_image", + title: "Novarix Networks", + description: + "Engineering-led network consulting, remote support, and architecture for production networks.", + }, + robots: { + index: true, + follow: true, + }, +}; + +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#020617" }, + ], +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + + {children} + + + + ); +} \ No newline at end of file diff --git a/app/layout.tsx.bak b/app/layout.tsx.bak new file mode 100644 index 00000000..f7fa87eb --- /dev/null +++ b/app/layout.tsx.bak @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 00000000..4981ba5a --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,499 @@ +"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 }; + +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(false); + 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 remember they've seen it using + // sessionStorage so it doesn't replay on every navigation. The intro lasts + // ~3.2 seconds, then fades out. + // --------------------------------------------------------------------------- + useEffect(() => { + try { + const introSeen = window.sessionStorage.getItem("novarix-intro-seen"); + // Only set the "seen" flag if the user has accepted cookies. Without + // consent we still play the intro — we just don't remember it played. + const consent = window.localStorage.getItem("novarix-cookie-consent"); + + if (!introSeen) { + // Sync once with sessionStorage on first client mount. + // eslint-disable-next-line react-hooks/set-state-in-effect + setShowIntro(true); + if (consent === "ack") { + window.sessionStorage.setItem("novarix-intro-seen", "true"); + } + + // The SVG's built-in stroke + fill animation lasts ~3s. + // We hold for an extra ~600ms so the CSS fade-out can complete. + const timer = window.setTimeout(() => { + setShowIntro(false); + }, 3600); + + return () => window.clearTimeout(timer); + } + } catch { + /* storage unavailable — silently skip the intro */ + } + }, []); + + // --------------------------------------------------------------------------- + // 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 ~3.6s + (see the useEffect above + the CSS fade-out timing). + ===================================================================== */} + {showIntro && ( +