Build the AI Growth Technologies homepage

Dark cinematic redesign: animated globe hero, stats, interactive
capabilities engine, proprietary technology visuals, AI discovery,
attribution journey, connections manifesto, CTA and footer.
This commit is contained in:
Alejandro Veras
2026-10-08 15:44:57 -04:00
parent 62539b2929
commit ddeecef631
17 changed files with 3242 additions and 98 deletions
+1793 -15
View File
File diff suppressed because it is too large Load Diff
+25 -15
View File
@@ -1,29 +1,39 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
display: "swap",
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
const description =
"AI Growth Technologies connects paid media, SEO, AI visibility, web, attribution, and technology into one accountable digital growth system.";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
metadataBase: new URL("https://www.aigrowthtechnologies.com"),
title: "AI Growth Technologies | Digital Growth for the AI Era",
description,
alternates: { canonical: "/" },
openGraph: {
type: "website",
url: "/",
siteName: "AI Growth Technologies",
title: "AI Growth Technologies | Digital Growth for the AI Era",
description,
},
};
export const viewport: Viewport = {
themeColor: "#000000",
colorScheme: "dark",
};
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
);
}
+462 -63
View File
@@ -1,69 +1,468 @@
import Image from "next/image";
import type { CSSProperties, ReactNode } from "react";
import SiteHeader from "@/components/site-header";
import GlobeCanvas from "@/components/globe-canvas";
import RevealObserver from "@/components/reveal-observer";
import StatCounter from "@/components/stat-counter";
import Capabilities from "@/components/capabilities";
import QueryTicker from "@/components/query-ticker";
import ProductVisual from "@/components/product-visuals";
import Logo from "@/components/logo";
import { stats, products, journeySteps, gaps, footerColumns } from "@/content/home";
type Vars = CSSProperties & Record<`--${string}`, string | number>;
const delay = (ms: number) => ({ "--d": `${ms}ms` }) as Vars;
const Arrow = () => <span className="arrow">→</span>;
function Eyebrow({ index, children }: { index?: string; children: ReactNode }) {
return (
<p className="eyebrow">
{index && <span className="idx">{index}</span>}
{children}
</p>
);
}
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert h-5 w-[100px]"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the{" "}
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
page.tsx
</code>{" "}
file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert h-[14px] w-4"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={14}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
<>
<a href="#main" className="skip-link">
Skip to content
</a>
<SiteHeader />
<RevealObserver />
<main id="main">
{/* ───────────────────────── Hero ───────────────────────── */}
<section className="hero" id="top" aria-labelledby="hero-title">
<GlobeCanvas />
<div className="wrap hero-inner">
<div data-reveal>
<Eyebrow>Digital Growth Architects</Eyebrow>
</div>
<h1 id="hero-title" className="display h1" data-reveal style={delay(120)}>
Where Search, AI, Media, and Technology Work Together.
</h1>
<div className="hero-grid">
<div className="hero-lead lead" data-reveal style={delay(260)}>
<p>
Your customer might find you through Google today, an AI answer tomorrow, and a paid ad three
days later.
</p>
<p className="muted">They don’t think in channels. Your growth strategy shouldn’t either.</p>
</div>
<div className="hero-body" data-reveal style={delay(380)}>
<div className="copy">
<p>
AI Growth Technologies brings paid media, search, AI visibility, technology, and attribution
together so each part strengthens the others.
</p>
<p className="hl">One strategy. Connected execution. A clearer view of what is actually driving growth.</p>
</div>
<div className="hero-actions">
<a href="#contact" className="btn btn-solid">
Book a Strategy Call <Arrow />
</a>
<a href="#capabilities" className="btn btn-ghost">
Explore Services <Arrow />
</a>
</div>
</div>
</div>
<div className="hero-foot" data-reveal style={delay(520)}>
<div className="hero-channels" aria-hidden="true">
<span>Paid Media</span>
<i />
<span>Search</span>
<i />
<span>AI Visibility</span>
<i />
<span>Technology</span>
<i />
<span>Attribution</span>
</div>
<a href="#evidence" className="scroll-cue">
Scroll <i />
</a>
</div>
</div>
</section>
{/* ───────────────────────── Numbers ───────────────────────── */}
<section className="section" id="evidence" aria-labelledby="evidence-title">
<div className="wrap">
<div className="section-head">
<div className="title" data-reveal>
<Eyebrow index="01">The Numbers Behind the Work</Eyebrow>
<h2 id="evidence-title" className="display h2">
Good Growth Leaves Evidence.
</h2>
</div>
</div>
<div className="stats">
{stats.map((s, i) => (
<div key={s.label} className="stat" data-reveal style={delay(i * 120)}>
<StatCounter value={s.value} suffix={s.suffix} />
<div className="stat-label">{s.label}</div>
</div>
))}
</div>
</div>
</section>
{/* ───────────────────────── Capabilities ───────────────────────── */}
<section className="section section-night" id="capabilities" aria-labelledby="cap-title">
<div className="wrap">
<div className="section-head">
<div className="title" data-reveal>
<Eyebrow index="02">Everything Works Better Connected</Eyebrow>
<h2 id="cap-title" className="display h2">
Eight Capabilities.
<br />
One Growth Engine.
</h2>
</div>
</div>
<div className="split">
<div className="lead" data-reveal>
<p>
A search can lead to an ad. An ad can lead to a website visit. An AI answer can introduce the brand
before either happens.
</p>
</div>
<div className="copy" data-reveal style={delay(140)}>
<p>To your customer, that is not SEO, paid media, AI, and web.</p>
<p className="hl">It is one experience with your business.</p>
<p>Our capabilities are designed to work that way too.</p>
</div>
</div>
<div data-reveal>
<Capabilities />
</div>
</div>
</section>
{/* ───────────────────────── Proprietary technology ───────────────────────── */}
<section className="section" id="technology" aria-labelledby="tech-title">
<div className="wrap">
<div className="section-head">
<div className="title" data-reveal>
<Eyebrow index="03">Proprietary Technology</Eyebrow>
<h2 id="tech-title" className="display h2">
We Wanted Better Answers, So We Built Better Tools.
</h2>
</div>
</div>
<div className="split">
<div className="lead" data-reveal>
<p>Marketing produces plenty of numbers.</p>
<p className="muted">The harder part is knowing which ones matter.</p>
</div>
<div className="copy" data-reveal style={delay(140)}>
<p>
Our proprietary technology gives our teams and clients a clearer view across attribution, organic and
AI visibility, and digital resilience.
</p>
<p>
Not more data for the sake of it. <span className="hl">Better information for the decisions that come next.</span>
</p>
</div>
</div>
<div className="products">
{products.map((p, i) => (
<article key={p.name} className="product" aria-labelledby={`product-${i}`}>
<div className="product-visual" data-reveal>
<ProductVisual kind={p.visual} />
</div>
<div className="product-body" data-reveal style={delay(160)}>
<div className="product-kind">
<span>{String(i + 1).padStart(2, "0")} / 03</span>
<span className="pill">{p.kind}</span>
</div>
<h3 id={`product-${i}`} className="product-name">
{p.name}
</h3>
<h4 className="display h3">{p.headline}</h4>
<div className="copy">
{p.body.map((b) => (
<p key={b}>{b}</p>
))}
</div>
<a href="#" className="arrow-link">
{p.cta} <Arrow />
</a>
</div>
</article>
))}
</div>
</div>
</section>
{/* ───────────────────────── Discovery ───────────────────────── */}
<section className="section section-night discovery" id="discovery" aria-labelledby="disc-title">
<div className="wrap">
<div className="section-head">
<div className="title" data-reveal>
<Eyebrow index="04">Discovery Is Changing</Eyebrow>
<h2 id="disc-title" className="display h2">
Your Next Customer May Ask Before They Search.
</h2>
</div>
</div>
<div className="discovery-stage" data-reveal>
<div className="query-label">People now ask AI platforms</div>
<QueryTicker />
<div className="surfaces">
<div className="surface left">
<span className="pill">Google</span>
<span className="pill">Rankings</span>
<span className="pill">Authority</span>
</div>
<div className="surface-core">Your Brand</div>
<div className="surface">
<span className="pill">ChatGPT</span>
<span className="pill">Gemini</span>
<span className="pill">AI Overviews</span>
<span className="pill">Perplexity</span>
</div>
</div>
</div>
<div className="split">
<div className="lead" data-reveal>
<p>People still search Google.</p>
<p className="muted">
Now they also ask AI platforms which companies to consider, how products compare, what to buy, and
who to trust.
</p>
</div>
<div data-reveal style={delay(140)}>
<div className="copy">
<p className="hl">That changes what visibility means.</p>
<p>
Rankings still matter. But so do the information, authority, context, and signals that help AI
systems understand a brand well enough to surface it.
</p>
<p>Our SEO and LLMO work connects both sides of that shift.</p>
</div>
<a href="#" className="arrow-link" style={{ marginTop: 28 }}>
Explore AI Visibility <Arrow />
</a>
</div>
</div>
</div>
</section>
{/* ───────────────────────── Attention to action ───────────────────────── */}
<section className="section" id="approach" aria-labelledby="journey-title">
<div className="wrap">
<div className="section-head">
<div className="title" data-reveal>
<Eyebrow index="05">From Attention to Action</Eyebrow>
<h2 id="journey-title" className="display h2">
The Last Click Rarely Tells the Whole Story.
</h2>
</div>
</div>
<div className="journey" data-reveal-group>
<ol>
{journeySteps.map((s, i) => (
<li key={s.label} style={{ "--i": i } as Vars}>
<span className="dot" />
<span className="channel">
{String(i + 1).padStart(2, "0")} — {s.channel}
</span>
<span className="step">{s.label}</span>
</li>
))}
</ol>
</div>
<div className="credit split">
<h3 className="display question" data-reveal>
Which interaction gets the credit?
</h3>
<div className="copy" data-reveal style={delay(140)}>
<p>Too often, the answer depends on which dashboard you open.</p>
<p>
We bring media, search, AI visibility, and attribution closer together to answer a more useful
question:
</p>
</div>
</div>
<figure className="pullquote" data-reveal>
<Eyebrow>The more useful question</Eyebrow>
<blockquote>What actually helped move the customer toward a decision?</blockquote>
<figcaption className="copy">
<p>Better attribution isn’t about producing a prettier report.</p>
<p className="hl">
It is about knowing where the next dollar has the best chance of doing something useful.
</p>
</figcaption>
<a href="#" className="arrow-link">
Explore Our Approach <Arrow />
</a>
</figure>
</div>
</section>
{/* ───────────────────────── Connections ───────────────────────── */}
<section className="section section-night" id="difference" aria-labelledby="diff-title">
<div className="wrap">
<div className="section-head">
<div className="title" data-reveal>
<Eyebrow index="06">The Difference Is in the Connections</Eyebrow>
<h2 id="diff-title" className="display h2">
Good Channels Don’t Automatically Make a Good System.
</h2>
</div>
</div>
<ul className="gaps">
{gaps.map((g, i) => (
<li key={g} data-reveal style={delay(i * 80)}>
<span>{g}</span>
<svg className="break" viewBox="0 0 120 14" aria-hidden="true">
<line x1="0" y1="7" x2="50" y2="7" />
<line x1="70" y1="7" x2="120" y2="7" />
<line className="cut" x1="55" y1="1" x2="65" y2="13" />
</svg>
</li>
))}
</ul>
<div className="split" style={{ marginTop: "clamp(80px, 10vw, 144px)" }}>
<h3 className="display h2" data-reveal>
That is the gap we care about.
</h3>
<div className="copy" data-reveal style={delay(140)}>
<p>We look at how discovery, acquisition, experience, data, technology, and security affect one another.</p>
<p className="hl">Then we strengthen the connections that make the whole system perform better.</p>
</div>
</div>
<div className="nos">
<div data-reveal>
<span>No. 01</span>
No channel fighting for credit.
</div>
<div data-reveal style={delay(120)}>
<span>No. 02</span>
No technology added just because it is new.
</div>
<div data-reveal style={delay(240)}>
<span>No. 03</span>
No dashboard pretending the numbers mean more than they do.
</div>
</div>
<p className="closing" data-reveal>
Just a clearer system and a team accountable for making it work.
</p>
</div>
</section>
{/* ───────────────────────── CTA ───────────────────────── */}
<section className="cta" id="contact" aria-labelledby="cta-title">
<GlobeCanvas layout="center" />
<div className="wrap">
<div data-reveal>
<Eyebrow>Ready When You Are</Eyebrow>
</div>
<h2 id="cta-title" className="display h2" data-reveal style={delay(120)}>
Bring Us the Growth Problem You Can’t Quite Pin Down.
</h2>
<div className="copy" data-reveal style={delay(240)}>
<p>
Maybe acquisition is getting more expensive. Maybe organic visibility has stalled. Maybe AI is changing
how customers find you.
</p>
<p>Or maybe every dashboard looks healthy, but the business is telling you something different.</p>
</div>
<div className="lead" data-reveal style={delay(320)}>
<p>You don’t need to diagnose it before the call.</p>
<p className="muted">Bring us what you’re seeing. We’ll start there.</p>
</div>
<div data-reveal style={delay(400)}>
<a href="#" className="btn btn-solid">
Book Your Strategy Call <Arrow />
</a>
</div>
</div>
</section>
</main>
</div>
{/* ───────────────────────── Footer ───────────────────────── */}
<footer className="footer">
<div className="wrap">
<div className="footer-top">
<div className="footer-brand">
<Logo />
<p>
Paid media, SEO, AI visibility, web, attribution, and technology — connected into one accountable
digital growth system.
</p>
<div className="socials">
<a href="#" aria-label="LinkedIn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
<rect x="3" y="3" width="18" height="18" />
<path d="M8 10v7M8 7v.01M12 17v-4a2 2 0 0 1 4 0v4M12 10v7" />
</svg>
</a>
<a href="#" aria-label="Instagram">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
<rect x="3" y="3" width="18" height="18" rx="5" />
<circle cx="12" cy="12" r="4" />
<path d="M17.5 6.5v.01" />
</svg>
</a>
<a href="#" aria-label="X">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
<path d="M4 4l16 16M20 4L4 20" />
</svg>
</a>
<a href="#" aria-label="YouTube">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
<rect x="2.5" y="5.5" width="19" height="13" rx="3" />
<path d="M10 9.5v5l4.5-2.5z" />
</svg>
</a>
</div>
</div>
<div className="footer-cols">
{footerColumns.map((col) => (
<div key={col.title}>
<h4>{col.title}</h4>
<ul>
{col.links.map((l) => (
<li key={l}>
<a href="#">{l}</a>
</li>
))}
</ul>
</div>
))}
</div>
</div>
<p className="footer-word" aria-hidden="true">
AI Growth Technologies
</p>
<div className="footer-bottom">
<span>© 2026 AI Growth Technologies. All rights reserved.</span>
<nav aria-label="Legal">
<a href="#">Privacy</a>
<a href="#">Terms</a>
<a href="#">Cookies</a>
</nav>
</div>
</div>
</footer>
</>
);
}
+107
View File
@@ -0,0 +1,107 @@
"use client";
import { useState } from "react";
import { capabilities } from "@/content/home";
const pad = (i: number) => String(i + 1).padStart(2, "0");
// Eight nodes on a ring around a core — every capability connected to every other.
const SIZE = 360;
const C = SIZE / 2;
const RING = 132;
const nodes = capabilities.map((_, i) => {
const a = (i / capabilities.length) * Math.PI * 2 - Math.PI / 2;
return { x: C + Math.cos(a) * RING, y: C + Math.sin(a) * RING };
});
function EngineMap({ active }: { active: number }) {
return (
<svg className="cap-map" viewBox={`0 0 ${SIZE} ${SIZE}`} aria-hidden="true">
{nodes.map((a, i) =>
nodes.slice(i + 1).map((b, j) => {
const k = i + 1 + j;
return (
<line key={`${i}-${k}`} x1={a.x} y1={a.y} x2={b.x} y2={b.y} className={i === active || k === active ? "on" : ""} />
);
}),
)}
{nodes.map((n, i) => (
<line key={`c${i}`} x1={C} y1={C} x2={n.x} y2={n.y} className={i === active ? "on" : ""} />
))}
<circle className="core" cx={C} cy={C} r={30} />
<text x={C} y={C + 2.5} textAnchor="middle">
GROWTH
</text>
{nodes.map((n, i) => (
<g key={`n${i}`}>
{i === active && <rect key={`p${active}`} className="pulse" x={n.x - 5} y={n.y - 5} width={10} height={10} />}
<rect className={`node${i === active ? " on" : ""}`} x={n.x - 5} y={n.y - 5} width={10} height={10} />
<text
x={C + Math.cos((i / nodes.length) * Math.PI * 2 - Math.PI / 2) * (RING + 22)}
y={C + Math.sin((i / nodes.length) * Math.PI * 2 - Math.PI / 2) * (RING + 22) + 2.5}
textAnchor="middle"
>
{pad(i)}
</text>
</g>
))}
</svg>
);
}
export default function Capabilities() {
const [active, setActive] = useState(0);
const cap = capabilities[active];
return (
<div className="cap-layout">
<ul className="cap-list">
{capabilities.map((c, i) => (
<li key={c.name} className={`cap-item${i === active ? " is-active" : ""}`}>
<button
type="button"
className="cap-trigger"
aria-pressed={i === active}
onMouseEnter={() => setActive(i)}
onFocus={() => setActive(i)}
onClick={() => setActive(i)}
>
<span className="num">{pad(i)}</span>
<span className="name">{c.name}</span>
<span className="tick" />
</button>
<div className="cap-inline">
<h3>{c.headline}</h3>
<div className="copy">
{c.body.map((p) => (
<p key={p}>{p}</p>
))}
</div>
<a href="#" className="arrow-link">
{c.cta} <span className="arrow">→</span>
</a>
</div>
</li>
))}
</ul>
<div className="cap-panel" aria-live="polite">
<EngineMap active={active} />
<div key={active} className="cap-detail">
<div className="kicker">
{pad(active)} / {cap.name}
</div>
<h3 className="display h3">{cap.headline}</h3>
<div className="copy">
{cap.body.map((p) => (
<p key={p}>{p}</p>
))}
</div>
<a href="#" className="arrow-link">
{cap.cta} <span className="arrow">→</span>
</a>
</div>
</div>
</div>
);
}
+270
View File
@@ -0,0 +1,270 @@
"use client";
import { useEffect, useRef } from "react";
type Vec = [number, number, number];
// Market hubs (lat, lon) the arcs travel between.
const HUBS: [number, number][] = [
[40.7, -74.0], // New York
[25.8, -80.2], // Miami
[18.5, -69.9], // Santo Domingo
[19.4, -99.1], // Mexico City
[4.7, -74.1], // Bogotá
[40.4, -3.7], // Madrid
[51.5, -0.1], // London
];
const ROUTES: [number, number][] = [
[0, 6],
[1, 2],
[2, 4],
[3, 1],
[5, 2],
[6, 5],
[0, 3],
[4, 5],
];
function fromLatLon(lat: number, lon: number): Vec {
const phi = (lat * Math.PI) / 180;
const theta = (lon * Math.PI) / 180;
return [Math.cos(phi) * Math.sin(theta), Math.sin(phi), Math.cos(phi) * Math.cos(theta)];
}
function slerp(a: Vec, b: Vec, t: number): Vec {
const dot = Math.min(1, Math.max(-1, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]));
const omega = Math.acos(dot);
if (omega < 1e-4) return a;
const s = Math.sin(omega);
const k1 = Math.sin((1 - t) * omega) / s;
const k2 = Math.sin(t * omega) / s;
return [a[0] * k1 + b[0] * k2, a[1] * k1 + b[1] * k2, a[2] * k1 + b[2] * k2];
}
type Props = {
/** "hero" sits the globe to the right; "center" centres it behind content. */
layout?: "hero" | "center";
className?: string;
};
export default function GlobeCanvas({ layout = "hero", className = "hero-canvas" }: Props) {
const ref = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = ref.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
// Fibonacci sphere
const COUNT = 1500;
const points: Vec[] = [];
const golden = Math.PI * (3 - Math.sqrt(5));
for (let i = 0; i < COUNT; i++) {
const y = 1 - (i / (COUNT - 1)) * 2;
const r = Math.sqrt(1 - y * y);
const th = golden * i;
points.push([Math.cos(th) * r, y, Math.sin(th) * r]);
}
const hubs = HUBS.map(([la, lo]) => fromLatLon(la, lo));
const arcs = ROUTES.map(([a, b], i) => {
const steps = 64;
const pts: Vec[] = [];
for (let s = 0; s <= steps; s++) {
const t = s / steps;
const p = slerp(hubs[a], hubs[b], t);
const lift = 1 + Math.sin(Math.PI * t) * 0.22;
pts.push([p[0] * lift, p[1] * lift, p[2] * lift]);
}
return { pts, offset: i * 0.37, speed: 0.11 + (i % 3) * 0.03 };
});
let w = 0;
let h = 0;
let dpr = 1;
let cx = 0;
let cy = 0;
let R = 0;
const resize = () => {
dpr = Math.min(window.devicePixelRatio || 1, 2);
const rect = canvas.getBoundingClientRect();
w = rect.width;
h = rect.height;
canvas.width = Math.round(w * dpr);
canvas.height = Math.round(h * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const mobile = w < 768;
// Size against the viewport, not the (possibly taller) section.
const vh = Math.min(h, window.innerHeight);
if (layout === "center") {
cx = w / 2;
cy = h / 2;
R = Math.min(w, vh) * (mobile ? 0.48 : 0.4);
} else if (mobile) {
cx = w * 0.66;
cy = vh * 0.3;
R = w * 0.6;
} else {
cx = w * 0.72;
cy = vh * 0.54;
R = Math.min(vh * 0.4, w * 0.27);
}
};
let rotY = -1.25; // start facing the Americas, drifting toward Europe
const tiltX = 0.42;
let mx = 0;
let my = 0;
let tx = 0;
let ty = 0;
const project = (v: Vec, ry: number, rx: number) => {
const cosY = Math.cos(ry);
const sinY = Math.sin(ry);
const x1 = v[0] * cosY - v[2] * sinY;
const z1 = v[0] * sinY + v[2] * cosY;
const cosX = Math.cos(rx);
const sinX = Math.sin(rx);
const y2 = v[1] * cosX - z1 * sinX;
const z2 = v[1] * sinX + z1 * cosX;
const persp = 3.2 / (3.2 - z2);
return { x: cx + x1 * R * persp, y: cy - y2 * R * persp, z: z2, s: persp };
};
let last = performance.now();
let elapsed = 0;
let raf = 0;
let running = true;
const draw = (now: number) => {
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
if (!reduced) {
elapsed += dt;
rotY += dt * 0.06;
}
mx += (tx - mx) * 0.04;
my += (ty - my) * 0.04;
const ry = rotY + mx * 0.25;
const rx = tiltX + my * 0.12;
ctx.clearRect(0, 0, w, h);
// soft halo
const halo = ctx.createRadialGradient(cx, cy, R * 0.7, cx, cy, R * 1.45);
halo.addColorStop(0, "rgba(255,255,255,0.045)");
halo.addColorStop(1, "rgba(255,255,255,0)");
ctx.fillStyle = halo;
ctx.fillRect(0, 0, w, h);
// outline
ctx.beginPath();
ctx.arc(cx, cy, R * 1.0, 0, Math.PI * 2);
ctx.strokeStyle = "rgba(255,255,255,0.08)";
ctx.lineWidth = 1;
ctx.stroke();
// dots
for (let i = 0; i < points.length; i++) {
const p = project(points[i], ry, rx);
const front = p.z > 0;
const a = front ? 0.18 + p.z * 0.62 : 0.05 + (1 + p.z) * 0.05;
const size = (front ? 1.15 : 0.8) * p.s;
ctx.fillStyle = `rgba(255,255,255,${a.toFixed(3)})`;
ctx.fillRect(p.x - size / 2, p.y - size / 2, size, size);
}
// arcs
for (const arc of arcs) {
const proj = arc.pts.map((v) => project(v, ry, rx));
ctx.lineWidth = 1;
for (let s = 1; s < proj.length; s++) {
const a = proj[s - 1];
const b = proj[s];
const vis = (a.z + b.z) / 2;
if (vis < -0.15) continue;
ctx.strokeStyle = `rgba(255,255,255,${(0.08 + Math.max(0, vis) * 0.22).toFixed(3)})`;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.stroke();
}
// traveling pulse
const t = ((elapsed * arc.speed + arc.offset) % 1) * (proj.length - 1);
const head = Math.floor(t);
const tail = Math.max(0, head - 10);
for (let s = tail + 1; s <= head; s++) {
const a = proj[s - 1];
const b = proj[s];
if ((a.z + b.z) / 2 < -0.1) continue;
const k = (s - tail) / (head - tail || 1);
ctx.strokeStyle = `rgba(218,41,28,${(k * 0.95).toFixed(3)})`;
ctx.lineWidth = 1.6;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.stroke();
}
}
// hubs
for (let i = 0; i < hubs.length; i++) {
const p = project(hubs[i], ry, rx);
if (p.z < 0) continue;
const pulse = (elapsed * 0.6 + i * 0.21) % 1;
ctx.strokeStyle = `rgba(218,41,28,${((1 - pulse) * 0.8 * p.z).toFixed(3)})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(p.x, p.y, 3 + pulse * 14, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = `rgba(255,255,255,${(0.4 + p.z * 0.6).toFixed(3)})`;
ctx.fillRect(p.x - 2.5, p.y - 2.5, 5, 5);
}
if (running && !reduced) raf = requestAnimationFrame(draw);
};
const onMove = (e: PointerEvent) => {
tx = (e.clientX / window.innerWidth) * 2 - 1;
ty = (e.clientY / window.innerHeight) * 2 - 1;
};
resize();
const ro = new ResizeObserver(() => {
resize();
if (reduced) draw(performance.now());
});
ro.observe(canvas);
const io = new IntersectionObserver(([entry]) => {
const visible = entry.isIntersecting;
if (visible && !running) {
running = true;
last = performance.now();
raf = requestAnimationFrame(draw);
} else if (!visible) {
running = false;
cancelAnimationFrame(raf);
}
});
io.observe(canvas);
window.addEventListener("pointermove", onMove, { passive: true });
raf = requestAnimationFrame(draw);
return () => {
running = false;
cancelAnimationFrame(raf);
ro.disconnect();
io.disconnect();
window.removeEventListener("pointermove", onMove);
};
}, [layout]);
return <canvas ref={ref} className={className} aria-hidden="true" />;
}
+22
View File
@@ -0,0 +1,22 @@
export function LogoMark({ className = "logo-mark" }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 32 32" fill="none" aria-hidden="true">
<rect x="0.5" y="0.5" width="31" height="31" stroke="currentColor" strokeOpacity="0.35" />
<path d="M6 25 L16 7 L26 25" stroke="currentColor" strokeWidth="1.4" />
<path d="M10.5 18.5 H21.5" stroke="currentColor" strokeWidth="1.4" />
<rect x="23.5" y="5.5" width="3" height="3" fill="#da291c" />
</svg>
);
}
export default function Logo() {
return (
<a href="#top" className="logo" aria-label="AI Growth Technologies — home">
<LogoMark />
<span className="logo-type">
<b>AI GROWTH</b>
<span>TECHNOLOGIES</span>
</span>
</a>
);
}
+186
View File
@@ -0,0 +1,186 @@
import type { CSSProperties } from "react";
type Vars = CSSProperties & Record<`--${string}`, string>;
const W = 600;
const H = 412;
/* Atrilyx — many touchpoints converging on one conversion */
function JourneyViz() {
const sources = ["Ad", "Search", "Page", "Brand", "AI Answer", "Social"];
const gx = 500;
const gy = H / 2;
const top = 70;
const gap = (H - top * 2) / (sources.length - 1);
return (
<svg className="viz viz-journey" viewBox={`0 0 ${W} ${H}`} aria-hidden="true">
{[180, 280, 380].map((x) => (
<line key={x} x1={x} y1={28} x2={x} y2={H - 28} stroke="rgba(255,255,255,0.05)" />
))}
{sources.map((s, i) => {
const y = top + i * gap;
const d = `M92 ${y} C 290 ${y}, 300 ${gy}, ${gx} ${gy}`;
return (
<g key={s}>
<path className="path" d={d} />
<path className={`flow${i === 4 ? " red" : ""}`} d={d} style={{ "--t": `${-i * 0.8}s` } as Vars} />
<rect className="node" x={86} y={y - 6} width={12} height={12} />
<text x={74} y={y + 3} textAnchor="end">
{s.toUpperCase()}
</text>
</g>
);
})}
<rect className="goal-ring" x={gx - 7} y={gy - 7} width={14} height={14} />
<rect className="goal" x={gx - 7} y={gy - 7} width={14} height={14} />
<text x={gx + 18} y={gy + 3}>
CONVERSION
</text>
<text className="faint" x={24} y={H - 20}>
TOUCHPOINTS → WEIGHTED CONTRIBUTION
</text>
</svg>
);
}
/* meSEO — search + AI surfaces on a radar */
function RadarViz() {
const cx = 300;
const cy = 206;
const dots = [
{ a: 18, r: 150, label: "SEARCH" },
{ a: 64, r: 92, label: "AI OVERVIEWS" },
{ a: 118, r: 170, label: "CHATGPT" },
{ a: 162, r: 64, label: "" },
{ a: 205, r: 128, label: "GEMINI" },
{ a: 248, r: 178, label: "" },
{ a: 292, r: 110, label: "PERPLEXITY", red: true },
{ a: 334, r: 60, label: "" },
{ a: 86, r: 190, label: "" },
{ a: 228, r: 40, label: "" },
];
const rad = (deg: number) => (deg * Math.PI) / 180;
const sweepR = 240;
const trail = 46;
const ex = cx + Math.cos(rad(-trail)) * sweepR;
const ey = cy + Math.sin(rad(-trail)) * sweepR;
return (
<svg className="viz viz-radar" viewBox={`0 0 ${W} ${H}`} aria-hidden="true">
<defs>
<linearGradient id="sweepGrad" x1="0" y1="1" x2="0.4" y2="0">
<stop offset="0" stopColor="#fff" stopOpacity="0.16" />
<stop offset="1" stopColor="#fff" stopOpacity="0" />
</linearGradient>
</defs>
{[50, 100, 150, 200].map((r) => (
<circle key={r} className="ring" cx={cx} cy={cy} r={r} />
))}
{[0, 45, 90, 135].map((a) => (
<line
key={a}
className="axis"
x1={cx - Math.cos(rad(a)) * 220}
y1={cy - Math.sin(rad(a)) * 220}
x2={cx + Math.cos(rad(a)) * 220}
y2={cy + Math.sin(rad(a)) * 220}
/>
))}
<g className="sweep">
<path d={`M${cx} ${cy} L${cx + sweepR} ${cy} A${sweepR} ${sweepR} 0 0 0 ${ex} ${ey} Z`} fill="url(#sweepGrad)" />
<line x1={cx} y1={cy} x2={cx + sweepR} y2={cy} stroke="rgba(255,255,255,0.6)" />
</g>
{dots.map((d, i) => {
const x = cx + Math.cos(rad(d.a)) * d.r;
const y = cy + Math.sin(rad(d.a)) * d.r;
return (
<g key={i}>
<rect
className={`dot${d.red ? " red" : ""}`}
x={x - 3}
y={y - 3}
width={6}
height={6}
style={{ "--t": `${((d.a / 360) * 7).toFixed(2)}s` } as Vars}
/>
{d.label && (
<text x={x + 10} y={y + 3}>
{d.label}
</text>
)}
</g>
);
})}
<rect x={cx - 3} y={cy - 3} width={6} height={6} fill="#fff" />
<text className="faint" x={24} y={H - 20}>
VISIBILITY SURFACE — SEARCH + GENERATIVE
</text>
</svg>
);
}
/* Korrith — monitored infrastructure grid with a scanning pass */
function GridViz() {
const cols = 14;
const rows = 8;
const size = 28;
const gap = 8;
const ox = (W - (cols * (size + gap) - gap)) / 2;
const oy = 52;
const cells = [];
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = ox + c * (size + gap);
const y = oy + r * (size + gap);
const on = (r * 37 + c * 17) % 7 === 0;
const alert = r === 3 && c === 9;
const delay = (((y + 80) / 520) * 4.8 - 2.4).toFixed(2);
cells.push(
<rect
key={`${r}-${c}`}
className={`cell${on || alert ? " on" : ""}${alert ? " alert" : ""}`}
x={x}
y={y}
width={size}
height={size}
style={on || alert ? ({ "--t": `${delay}s` } as Vars) : undefined}
/>,
);
}
}
const bx = ox - 14;
const by = oy - 14;
const bw = cols * (size + gap) - gap + 28;
const bh = rows * (size + gap) - gap + 28;
const k = 16;
return (
<svg className="viz viz-grid" viewBox={`0 0 ${W} ${H}`} aria-hidden="true">
<defs>
<linearGradient id="scanGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#fff" stopOpacity="0" />
<stop offset="0.85" stopColor="#fff" stopOpacity="0.05" />
<stop offset="1" stopColor="#fff" stopOpacity="0.28" />
</linearGradient>
</defs>
{cells}
<path
className="frame"
d={`M${bx} ${by + k}V${by}H${bx + k} M${bx + bw - k} ${by}H${bx + bw}V${by + k} M${bx + bw} ${by + bh - k}V${by + bh}H${bx + bw - k} M${bx + k} ${by + bh}H${bx}V${by + bh - k}`}
/>
<g className="scan">
<rect x={0} y={0} width={W} height={64} fill="url(#scanGrad)" />
</g>
<text className="faint" x={24} y={H - 20}>
RESILIENCE LAYER — CONTINUOUS MONITORING
</text>
</svg>
);
}
export default function ProductVisual({ kind }: { kind: "journey" | "radar" | "grid" }) {
if (kind === "journey") return <JourneyViz />;
if (kind === "radar") return <RadarViz />;
return <GridViz />;
}
+57
View File
@@ -0,0 +1,57 @@
"use client";
import { useEffect, useState } from "react";
// The kinds of questions the copy describes people now asking AI platforms.
const QUERIES = [
"Which companies should I consider?",
"How do these products compare?",
"What should I buy?",
"Who should I trust?",
];
export default function QueryTicker() {
const [text, setText] = useState(QUERIES[0]);
useEffect(() => {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let q = 0;
let i = QUERIES[0].length;
let deleting = true;
let timer: ReturnType<typeof setTimeout>;
const step = () => {
const target = QUERIES[q];
if (deleting) {
i--;
setText(target.slice(0, i));
if (i <= 0) {
deleting = false;
q = (q + 1) % QUERIES.length;
}
timer = setTimeout(step, 28);
} else {
const next = QUERIES[q];
i++;
setText(next.slice(0, i));
if (i >= next.length) {
deleting = true;
timer = setTimeout(step, 2600);
} else {
timer = setTimeout(step, 55 + Math.random() * 45);
}
}
};
timer = setTimeout(step, 2600);
return () => clearTimeout(timer);
}, []);
return (
<p className="query" aria-label={QUERIES.join(" ")}>
<span aria-hidden="true">{text}</span>
<span className="caret" aria-hidden="true" />
</p>
);
}
+25
View File
@@ -0,0 +1,25 @@
"use client";
import { useEffect } from "react";
/** Adds `is-in` to every `[data-reveal]` / `[data-reveal-group]` element as it scrolls into view. */
export default function RevealObserver() {
useEffect(() => {
const targets = document.querySelectorAll<HTMLElement>("[data-reveal], [data-reveal-group]");
const io = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add("is-in");
io.unobserve(entry.target);
}
}
},
{ rootMargin: "0px 0px -12% 0px", threshold: 0.12 },
);
targets.forEach((el) => io.observe(el));
return () => io.disconnect();
}, []);
return null;
}
+86
View File
@@ -0,0 +1,86 @@
"use client";
import { useEffect, useState } from "react";
import Logo from "./logo";
import { navLinks } from "@/content/home";
export default function SiteHeader() {
const [scrolled, setScrolled] = useState(false);
const [hidden, setHidden] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
let lastY = window.scrollY;
const onScroll = () => {
const y = window.scrollY;
setScrolled(y > 24);
setHidden(y > 480 && y > lastY + 4);
if (y < lastY - 4 || y < 480) setHidden(false);
lastY = y;
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
useEffect(() => {
document.body.style.overflow = open ? "hidden" : "";
const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false);
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open]);
const classes = ["site-header", scrolled || open ? "is-scrolled" : "", hidden && !open ? "is-hidden" : ""]
.filter(Boolean)
.join(" ");
return (
<>
<header className={classes}>
<div className="wrap bar">
<Logo />
<nav className="nav" aria-label="Primary">
{navLinks.map((l) => (
<a key={l.href} href={l.href}>
{l.label}
</a>
))}
</nav>
<a href="#contact" className="btn btn-solid header-cta">
Book a Strategy Call <span className="arrow">→</span>
</a>
<button
type="button"
className="menu-toggle"
aria-expanded={open}
aria-controls="mobile-menu"
aria-label={open ? "Close menu" : "Open menu"}
onClick={() => setOpen((v) => !v)}
>
<span />
<span />
</button>
</div>
</header>
<div id="mobile-menu" className={`mobile-menu${open ? " is-open" : ""}`} aria-hidden={!open}>
<ul>
{navLinks.map((l) => (
<li key={l.href}>
<a href={l.href} onClick={() => setOpen(false)} tabIndex={open ? 0 : -1}>
{l.label} <span aria-hidden="true">→</span>
</a>
</li>
))}
</ul>
<a
href="#contact"
className="btn btn-solid"
onClick={() => setOpen(false)}
tabIndex={open ? 0 : -1}
>
Book a Strategy Call <span className="arrow">→</span>
</a>
</div>
</>
);
}
+53
View File
@@ -0,0 +1,53 @@
"use client";
import { useEffect, useRef } from "react";
export default function StatCounter({ value, suffix }: { value: number; suffix: string }) {
const ref = useRef<HTMLSpanElement>(null);
const numRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
const el = ref.current;
const num = numRef.current;
if (!el || !num) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let raf = 0;
num.textContent = "0";
const io = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) return;
io.disconnect();
const start = performance.now();
const duration = 1800;
const tick = (now: number) => {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 4);
num.textContent = String(Math.round(eased * value));
if (t < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
},
{ threshold: 0.4 },
);
io.observe(el);
return () => {
io.disconnect();
cancelAnimationFrame(raf);
num.textContent = String(value);
};
}, [value]);
return (
<span ref={ref} className="stat-value">
<span ref={numRef} aria-hidden="true">
{value}
</span>
<span className="sr-only">
{value}
{suffix}
</span>
{suffix && <small aria-hidden="true">{suffix}</small>}
</span>
);
}
+156
View File
@@ -0,0 +1,156 @@
export const navLinks = [
{ label: "Services", href: "#capabilities" },
{ label: "Technology", href: "#technology" },
{ label: "AI Visibility", href: "#discovery" },
{ label: "Approach", href: "#approach" },
];
export const stats = [
{ value: 120, suffix: "%", label: "Average ROI Increase" },
{ value: 7, suffix: "", label: "Global Markets" },
{ value: 24, suffix: "", label: "Specialized Channels" },
{ value: 99, suffix: "%", label: "Platform Uptime" },
];
export type Capability = {
name: string;
headline: string;
body: string[];
cta: string;
};
export const capabilities: Capability[] = [
{
name: "Paid Media",
headline: "Make Every Dollar Work Harder.",
body: [
"Plan and manage campaigns across Google, Meta, and other relevant platforms around qualified demand, conversion, and measurable return.",
],
cta: "Explore Paid Media",
},
{
name: "SEO",
headline: "Be There When Someone Goes Looking.",
body: [
"Connect technical SEO, content, authority, and search intent to strengthen organic visibility where it can influence real business decisions.",
],
cta: "Explore SEO",
},
{
name: "LLMO & AI Visibility",
headline: "Be Part of the Answer.",
body: [
"People are asking ChatGPT, Gemini, AI Overviews, Perplexity, and other generative platforms questions they once took straight to search.",
"We help brands become easier for these systems to understand, retrieve, reference, and surface.",
],
cta: "Explore LLMO & AI",
},
{
name: "Web Development",
headline: "Give the Click Somewhere Worth Landing.",
body: [
"Create fast, scalable digital experiences around the people using them, the actions you want them to take, and the marketing bringing them there.",
],
cta: "Explore Web Development",
},
{
name: "Social Media",
headline: "Stay Relevant Between Searches.",
body: [
"Build familiarity, stay part of relevant conversations, and give people another reason to remember the brand when they are ready to act.",
],
cta: "Explore Social Media",
},
{
name: "Cybersecurity",
headline: "Protect What Growth Depends On.",
body: [
"Strengthen the systems, access, monitoring, and digital infrastructure supporting the business as it grows.",
],
cta: "Explore Cybersecurity",
},
{
name: "Brand",
headline: "Give People Something to Remember.",
body: [
"Bring positioning, messaging, identity, and design together so the company feels like the same brand wherever people find it.",
],
cta: "Explore Brand",
},
{
name: "Consulting",
headline: "Some Problems Don’t Belong to One Channel.",
body: [
"Sometimes traffic is not the problem.",
"Sometimes it is the offer. The website. The measurement. Or several good ideas that simply are not working well together.",
"We look across the wider system to find what actually deserves attention.",
],
cta: "Explore Consulting",
},
];
export const products = [
{
name: "Atrilyx",
kind: "Media & Attribution Engine",
headline: "Follow the Journey, Not Just the Last Click.",
body: [
"A unified media and attribution engine built to connect signals across the customer journey and give a clearer view of what helped move someone toward conversion.",
],
cta: "Explore Atrilyx",
visual: "journey",
},
{
name: "meSEO",
kind: "Search & AI Visibility",
headline: "Search Has a New Surface Area.",
body: [
"Organic visibility no longer stops at the traditional results page.",
"meSEO brings search and emerging AI visibility into a more connected view, helping brands understand where they appear, where they do not, and where the next opportunity may be.",
],
cta: "Explore meSEO",
visual: "radar",
},
{
name: "Korrith",
kind: "Security & Resilience",
headline: "Keep the Digital Foundation Strong.",
body: [
"A security and resilience layer built around the systems businesses increasingly depend on as they grow.",
],
cta: "Explore Korrith",
visual: "grid",
},
] as const;
export const journeySteps = [
{ channel: "Paid Media", label: "A customer sees an ad." },
{ channel: "Search", label: "A week later, they search your name." },
{ channel: "Website", label: "They read a page." },
{ channel: "Brand", label: "They encounter the brand again." },
{ channel: "AI Platform", label: "Maybe they ask an AI platform about your category." },
{ channel: "Direct", label: "Then they come back directly and convert." },
];
export const gaps = [
"You can have strong SEO and a website that loses the visitor.",
"Great ads and weak attribution.",
"A memorable brand nobody can find.",
"Plenty of data and no clear idea what to do with it.",
"Or an AI strategy sitting in a presentation while the rest of the business keeps working exactly as it did before.",
];
export const footerColumns = [
{
title: "Services",
links: capabilities.map((c) => c.name),
},
{
title: "Technology",
links: ["Atrilyx", "meSEO", "Korrith"],
},
{
title: "Company",
links: ["About", "Our Approach", "Insights", "Careers", "Contact"],
},
];