diff --git a/app/cookies/page.tsx b/app/cookies/page.tsx
index 9813e4eb..0c341521 100644
--- a/app/cookies/page.tsx
+++ b/app/cookies/page.tsx
@@ -78,8 +78,8 @@ export default function CookiePolicyPage() {
What we store
- We use two small browser storage entries — both first-party,
- both confined to your browser, neither shared with anyone:
+ We use one small browser storage entry. It is first-party,
+ confined to your browser, and never shared with anyone:
@@ -96,22 +96,6 @@ export default function CookiePolicyPage() {
every page. Persists until you clear your browser data.
-
-
-
- novarix-intro-seen
-
-
-
- Preference.
- {" "}
- Stored in sessionStorage only when you accept
- cookies. Lets us skip the animated logo intro for the rest
- of your browsing session. Cleared automatically when you
- close the browser tab. If you decline cookies, this is
- never set and the intro may play again on a fresh tab.
-
-
diff --git a/app/globals.css b/app/globals.css
index cf77b524..65b57a42 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -229,79 +229,7 @@
}
/* ---------------------------------------------------------------------------
- 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 begins fading once
- the wordmark finishes drawing, while the page underneath eases in. Total
- intro length is matched in page.tsx's setTimeout (3950ms).
-
- When .intro-running is on the element, the page content
- underneath is gently softened so it can reveal itself without a hard cut.
- --------------------------------------------------------------------------- */
-.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 900ms cubic-bezier(0.22, 1, 0.36, 1) 3.05s forwards;
- will-change: opacity;
-}
-
-.intro-backdrop {
- position: absolute;
- inset: 0;
- background:
- radial-gradient(
- circle at 50% 32%,
- oklch(0.62 0.16 240 / 0.08),
- transparent 30%
- ),
- 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;
-}
-
-.site-header,
-main > section,
-main > footer {
- transition:
- opacity 900ms cubic-bezier(0.22, 1, 0.36, 1),
- transform 900ms cubic-bezier(0.22, 1, 0.36, 1),
- filter 900ms cubic-bezier(0.22, 1, 0.36, 1);
- will-change: opacity, transform, filter;
-}
-
-.intro-running .site-header,
-.intro-running main > section,
-.intro-running main > footer {
- opacity: 0.16;
- transform: translateY(10px) scale(0.99);
- filter: blur(8px);
-}
-
-@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.
+ 6. Animations — keyframes used by the ambient orbs above.
--------------------------------------------------------------------------- */
@keyframes drift {
0%,
@@ -313,39 +241,17 @@ main > footer {
}
}
-@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
+ 7. 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.
+ the orb drift and any transition/animation durations across the site.
--------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
- .ambient-orb,
- .intro-overlay,
- .intro-wordmark {
+ .ambient-orb {
animation: none !important;
}
diff --git a/app/page.tsx b/app/page.tsx
index 1d947041..a4c15adf 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -9,94 +9,36 @@
//
// 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)
+// 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 animated intro and the mouse-tracking glow).
+// browser (needed for the mouse-tracking glow).
// =============================================================================
import Image from "next/image";
import Link from "next/link";
-import { useEffect, useLayoutEffect, useMemo, useState } from "react";
+import { 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.
+ // 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. The initial check runs in useLayoutEffect so
- // the intro can mount before the first browser paint, avoiding a visible
- // flash of the homepage underneath.
- // ---------------------------------------------------------------------------
- useLayoutEffect(() => {
- function playIntro() {
- setShowIntro(true);
- try {
- window.sessionStorage.setItem(INTRO_SEEN_KEY, "true");
- } catch {
- /* storage unavailable — silently ignore */
- }
-
- const timer = window.setTimeout(() => {
- 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 = consent === "ack" && !introSeen;
-
- if (shouldPlayNow) {
- timer = playIntro();
- } else {
- setShowIntro(false);
- }
- } catch {
- 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)
@@ -113,44 +55,17 @@ export default function HomePage() {
);
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 && (
-
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
- )}
-
- {/* =====================================================================
- SITE SHELL
- The wraps the whole page. It tracks the mouse so the
- background glow can follow the cursor, and applies the .site-shell
- class (defined in globals.css) for the background gradient.
- ===================================================================== */}
- {
- 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 });
- }}
- >
+ {
+ 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.
@@ -512,7 +427,6 @@ export default function HomePage() {
-
- >
+
);
}
diff --git a/components/CookieBanner.tsx b/components/CookieBanner.tsx
index 75fb16ad..0204c2e1 100644
--- a/components/CookieBanner.tsx
+++ b/components/CookieBanner.tsx
@@ -10,11 +10,9 @@
// Behaviour:
// - On first load, if no consent decision is stored, the banner appears.
// - "ACK" stores `novarix-cookie-consent = "ack"` in localStorage and the
-// banner closes. The site then behaves as before (intro animation
-// remembers it has played using sessionStorage).
+// banner closes.
// - "RST" stores `novarix-cookie-consent = "rst"` in localStorage and the
-// banner closes. We also clear the intro-seen flag so we don't keep any
-// non-consent storage around.
+// banner closes.
// - The footer "Cookie preferences" link dispatches a window event that
// re-opens this banner so users can change their mind.
//
@@ -26,9 +24,7 @@ import { useEffect, useLayoutEffect, useState } from "react";
import { site } from "@/content";
const CONSENT_KEY = "novarix-cookie-consent";
-const INTRO_SEEN_KEY = "novarix-intro-seen";
const REOPEN_EVENT = "novarix:open-cookie-banner";
-const INTRO_EVENT = "novarix:start-intro";
type Consent = "unknown" | "ack" | "rst";
@@ -61,12 +57,6 @@ export default function CookieBanner() {
function decide(choice: "ack" | "rst") {
try {
window.localStorage.setItem(CONSENT_KEY, choice);
- if (choice === "rst") {
- // Honour the rejection by clearing any non-consent storage.
- window.sessionStorage.removeItem(INTRO_SEEN_KEY);
- } else {
- window.dispatchEvent(new Event(INTRO_EVENT));
- }
} catch {
/* storage unavailable — nothing to do */
}
diff --git a/content.ts b/content.ts
index cd8750b5..1ef96b76 100644
--- a/content.ts
+++ b/content.ts
@@ -153,9 +153,9 @@ export const site = {
// ---------------------------------------------------------------------------
cookies: {
// Short message shown in the banner. Keep it honest — this site does not
- // currently use any tracking cookies, only one preference for the intro.
+ // currently use any tracking cookies, only one preference for consent.
message:
- "Heads up — this site stores one tiny browser preference to remember you’ve seen the intro animation. We don’t run analytics, advertising, or third-party tracking.",
+ "Heads up — this site stores one tiny browser preference to remember your cookie choice. We don’t run analytics, advertising, or third-party tracking.",
// The two buttons. Labels are small jokes for network folk; subtitles
// and aria-labels make them clear to everyone else.