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:
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 385 B |
+1793
-15
File diff suppressed because it is too large
Load Diff
+25
-15
@@ -1,29 +1,39 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const inter = Inter({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-inter",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
display: "swap",
|
||||||
});
|
});
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
const description =
|
||||||
variable: "--font-geist-mono",
|
"AI Growth Technologies connects paid media, SEO, AI visibility, web, attribution, and technology into one accountable digital growth system.";
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
metadataBase: new URL("https://www.aigrowthtechnologies.com"),
|
||||||
description: "Generated by create next app",
|
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<"/">) {
|
export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||||
return (
|
return (
|
||||||
<html
|
<html lang="en" className={inter.variable}>
|
||||||
lang="en"
|
<body>{children}</body>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
|
||||||
>
|
|
||||||
<body className="min-h-full flex flex-col">{children}</body>
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+456
-57
@@ -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() {
|
export default function Home() {
|
||||||
return (
|
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">
|
<a href="#main" className="skip-link">
|
||||||
<Image
|
Skip to content
|
||||||
className="dark:invert h-5 w-[100px]"
|
</a>
|
||||||
src="/next.svg"
|
<SiteHeader />
|
||||||
alt="Next.js logo"
|
<RevealObserver />
|
||||||
width={100}
|
|
||||||
height={20}
|
<main id="main">
|
||||||
priority
|
{/* ───────────────────────── Hero ───────────────────────── */}
|
||||||
/>
|
<section className="hero" id="top" aria-labelledby="hero-title">
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
<GlobeCanvas />
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
<div className="wrap hero-inner">
|
||||||
To get started, edit the{" "}
|
<div data-reveal>
|
||||||
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
|
<Eyebrow>Digital Growth Architects</Eyebrow>
|
||||||
page.tsx
|
</div>
|
||||||
</code>{" "}
|
<h1 id="hero-title" className="display h1" data-reveal style={delay(120)}>
|
||||||
file.
|
Where Search, AI, Media, and Technology Work Together.
|
||||||
</h1>
|
</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{" "}
|
<div className="hero-grid">
|
||||||
<a
|
<div className="hero-lead lead" data-reveal style={delay(260)}>
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<p>
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
Your customer might find you through Google today, an AI answer tomorrow, and a paid ad three
|
||||||
>
|
days later.
|
||||||
Templates
|
</p>
|
||||||
</a>{" "}
|
<p className="muted">They don’t think in channels. Your growth strategy shouldn’t either.</p>
|
||||||
or the{" "}
|
</div>
|
||||||
<a
|
<div className="hero-body" data-reveal style={delay(380)}>
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<div className="copy">
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
<p>
|
||||||
>
|
AI Growth Technologies brings paid media, search, AI visibility, technology, and attribution
|
||||||
Learning
|
together so each part strengthens the others.
|
||||||
</a>{" "}
|
</p>
|
||||||
center.
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
<div className="copy" data-reveal style={delay(140)}>
|
||||||
<a
|
<p>To your customer, that is not SEO, paid media, AI, and web.</p>
|
||||||
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]"
|
<p className="hl">It is one experience with your business.</p>
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<p>Our capabilities are designed to work that way too.</p>
|
||||||
target="_blank"
|
</div>
|
||||||
rel="noopener noreferrer"
|
</div>
|
||||||
>
|
<div data-reveal>
|
||||||
<Image
|
<Capabilities />
|
||||||
className="dark:invert h-[14px] w-4"
|
</div>
|
||||||
src="/vercel.svg"
|
</div>
|
||||||
alt="Vercel logomark"
|
</section>
|
||||||
width={16}
|
|
||||||
height={14}
|
{/* ───────────────────────── Proprietary technology ───────────────────────── */}
|
||||||
/>
|
<section className="section" id="technology" aria-labelledby="tech-title">
|
||||||
Deploy Now
|
<div className="wrap">
|
||||||
</a>
|
<div className="section-head">
|
||||||
<a
|
<div className="title" data-reveal>
|
||||||
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]"
|
<Eyebrow index="03">Proprietary Technology</Eyebrow>
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<h2 id="tech-title" className="display h2">
|
||||||
target="_blank"
|
We Wanted Better Answers, So We Built Better Tools.
|
||||||
rel="noopener noreferrer"
|
</h2>
|
||||||
>
|
</div>
|
||||||
Documentation
|
</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>
|
</a>
|
||||||
</div>
|
</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>
|
</main>
|
||||||
|
|
||||||
|
{/* ───────────────────────── 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>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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" />;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 />;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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"],
|
||||||
|
},
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user