diff --git a/.gitignore b/.gitignore
index 5ef6a52..f8b926f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,10 @@ yarn-error.log*
# vercel
.vercel
+# superdesign
+.superdesign
+
# typescript
*.tsbuildinfo
next-env.d.ts
+.env*.local
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..a9194aa 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,14 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- /* config options here */
+ images: {
+ remotePatterns: [
+ {
+ protocol: "https",
+ hostname: "images.unsplash.com",
+ },
+ ],
+ },
};
export default nextConfig;
diff --git a/src/app/globals.css b/src/app/globals.css
index a2dc41e..00fe54f 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,26 +1,314 @@
@import "tailwindcss";
-:root {
- --background: #ffffff;
- --foreground: #171717;
+@theme inline {
+ --color-c47-bg: #0c0a09;
+ --color-c47-accent: #FF6B50;
+ --color-c47-accent-dim: rgba(255, 107, 80, 0.15);
+ --color-c47-accent-glow: rgba(255, 107, 80, 0.4);
+ --color-c47-muted: #78716c;
+ --color-c47-border: #27272a;
+ --color-c47-card: #111111;
+ --color-c47-text: #ebebeb;
+
+ --font-sans: var(--font-satoshi), var(--font-inter), ui-sans-serif, system-ui, sans-serif;
+ --font-mono: var(--font-jetbrains-mono), ui-monospace, monospace;
}
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
+/* ── Base ────────────────────────────────────────── */
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ background: var(--color-c47-bg);
+ color: var(--color-c47-text);
+ font-family: var(--font-sans);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+::selection {
+ background: var(--color-c47-accent);
+ color: #fff;
+}
+
+/* ── Scroll Reveal ───────────────────────────────── */
+
+.reveal {
+ opacity: 0;
+ transform: translateY(32px);
+ transition: opacity 0.7s cubic-bezier(0.4, 0, 0.2, 1),
+ transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.reveal.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.reveal-delay-1 { transition-delay: 0.1s; }
+.reveal-delay-2 { transition-delay: 0.2s; }
+.reveal-delay-3 { transition-delay: 0.3s; }
+.reveal-delay-4 { transition-delay: 0.4s; }
+
+/* ── Terminal Line Stagger ───────────────────────── */
+
+.terminal-line {
+ opacity: 0;
+ transform: translateX(-8px);
+ transition: opacity 0.5s ease, transform 0.5s ease;
+}
+
+.terminal-active .terminal-line:nth-child(1) { opacity: 1; transform: translateX(0); transition-delay: 0s; }
+.terminal-active .terminal-line:nth-child(2) { opacity: 1; transform: translateX(0); transition-delay: 0.15s; }
+.terminal-active .terminal-line:nth-child(3) { opacity: 1; transform: translateX(0); transition-delay: 0.3s; }
+
+/* ── Card Shimmer ────────────────────────────────── */
+
+.card-shimmer {
+ position: relative;
+ overflow: hidden;
+}
+
+.card-shimmer::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 50%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 107, 80, 0.04), transparent);
+ transition: none;
+ pointer-events: none;
+}
+
+.card-shimmer:hover::after {
+ animation: shimmer 1.2s ease-out forwards;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
+@keyframes shimmer {
+ 0% { left: -50%; }
+ 100% { left: 150%; }
+}
+
+/* ── CTA Glow Pulse ──────────────────────────────── */
+
+@keyframes glow-pulse {
+ 0%, 100% {
+ box-shadow: 0 0 20px rgba(255, 107, 80, 0.2),
+ 0 0 40px rgba(255, 107, 80, 0.05);
+ }
+ 50% {
+ box-shadow: 0 0 28px rgba(255, 107, 80, 0.35),
+ 0 0 56px rgba(255, 107, 80, 0.1);
}
}
-body {
- background: var(--background);
- color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+.cta-glow {
+ animation: glow-pulse 3s ease-in-out infinite;
+}
+
+.cta-glow:hover {
+ animation: none;
+ box-shadow: 0 0 36px rgba(255, 107, 80, 0.5),
+ 0 0 72px rgba(255, 107, 80, 0.15);
+}
+
+/* ── Step Number Bounce ──────────────────────────── */
+
+@keyframes bounceStep {
+ 0% { transform: scale(1); }
+ 40% { transform: scale(1.08); }
+ 70% { transform: scale(0.98); }
+ 100% { transform: scale(1.05); }
+}
+
+.step-number {
+ transition: color 0.35s ease, transform 0.35s ease;
+ display: inline-block;
+}
+
+.group\/step:hover .step-number {
+ animation: bounceStep 0.35s ease-out forwards;
+ color: #FF6B50 !important;
+}
+
+/* ── Float Animation ───────────────────────────── */
+
+@keyframes float {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-8px); }
+}
+
+.animate-float {
+ animation: float 4s ease-in-out infinite;
+}
+
+.animate-float-delayed {
+ animation: float 4.5s ease-in-out 0.5s infinite;
+}
+
+.animate-float-delayed-2 {
+ animation: float 5s ease-in-out 1s infinite;
+}
+
+/* ── Newsletter Underline ────────────────────────── */
+
+.newsletter-input {
+ background-image: linear-gradient(to right, #FF6B50, #FF9A85);
+ background-size: 0% 2px;
+ background-position: left bottom;
+ background-repeat: no-repeat;
+ transition: background-size 0.4s cubic-bezier(0.4, 0, 0.2, 1),
+ border-color 0.4s ease;
+}
+
+.newsletter-input:focus {
+ background-size: 100% 2px;
+ border-color: transparent !important;
+}
+
+/* ── Gradient Border Glow ────────────────────────── */
+
+.gradient-border-glow {
+ position: relative;
+}
+
+.gradient-border-glow::before {
+ content: "";
+ position: absolute;
+ inset: -1px;
+ border-radius: inherit;
+ padding: 1px;
+ background: linear-gradient(135deg, rgba(255, 107, 80, 0.3), transparent 50%, rgba(255, 107, 80, 0.1));
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.5s ease;
+}
+
+.gradient-border-glow:hover::before {
+ opacity: 1;
+}
+
+/* ── Gradient text helpers ───────────────────────── */
+
+.gradient-text {
+ background: linear-gradient(135deg, #FF6B50 0%, #FF9A85 40%, #ffffff 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.text-gradient {
+ background: linear-gradient(135deg, #FF6B50 0%, #FF9A85 40%, #ffffff 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+/* ── Nav Separator ───────────────────────────────── */
+
+.nav-separator {
+ width: 1px;
+ background: linear-gradient(to bottom, transparent, rgba(255, 107, 80, 0.4), transparent);
+}
+
+/* ── Social Icon Glow ────────────────────────────── */
+
+.social-glow:hover {
+ box-shadow: 0 8px 24px rgba(255, 107, 80, 0.25);
+ border-color: rgba(255, 107, 80, 0.4) !important;
+}
+
+/* ── Animated Logo ──────────────────────────────── */
+
+.logo-animated {
+ --logo-glow: rgba(231, 229, 228, 0.3);
+ position: relative;
+ display: inline-flex;
+ flex-direction: column;
+ font-family: var(--font-mono);
+ font-size: 11px;
+ line-height: 1;
+ font-weight: 400;
+ color: var(--color-c47-text);
+ white-space: pre;
+ user-select: none;
+ letter-spacing: -0.08em;
+ cursor: pointer;
+ text-shadow: 0 0 8px rgba(231, 229, 228, 0.15);
+}
+
+.logo-animated__glyph {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ z-index: 1;
+}
+
+.logo-animated__line {
+ display: flex;
+ height: 1.1em;
+}
+
+.logo-animated__char {
+ display: inline-block;
+ min-width: 0.55em;
+ text-align: center;
+ opacity: 0.3;
+ transition: opacity 0.1s ease-out;
+}
+
+.logo-animated.is-animating .logo-animated__char:not(.is-locked) {
+ opacity: 0.5;
+}
+
+.logo-animated__char.is-locked {
+ opacity: 1;
+}
+
+.logo-animated__char.is-flashing {
+ text-shadow: 0 0 6px var(--logo-glow), 0 0 12px rgba(245, 158, 11, 0.25);
+}
+
+.logo-animated.is-complete .logo-animated__glyph {
+ text-shadow: 0 0 1px var(--logo-glow), 0 0 8px var(--logo-glow);
+ animation: logo-breathe 4s ease-in-out infinite;
+ animation-delay: 1s;
+}
+
+@keyframes logo-breathe {
+ 0%, to {
+ text-shadow: 0 0 1px var(--logo-glow), 0 0 8px var(--logo-glow);
+ }
+ 50% {
+ text-shadow: 0 0 2px var(--logo-glow), 0 0 12px var(--logo-glow), 0 0 20px rgba(231, 229, 228, 0.18);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .logo-animated__char {
+ opacity: 1 !important;
+ transition: none;
+ }
+ .logo-animated__char.is-flashing {
+ color: inherit;
+ text-shadow: none;
+ }
+ .logo-animated.is-complete .logo-animated__glyph {
+ animation: none;
+ }
+}
+
+/* ── View Transitions ────────────────────────────── */
+
+@view-transition { navigation: auto; }
+::view-transition-old(root),
+::view-transition-new(root) {
+ animation-duration: 0.4s;
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 976eb90..1a6189c 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,20 +1,54 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import localFont from "next/font/local";
+import { Inter, JetBrains_Mono } from "next/font/google";
import "./globals.css";
-const geistSans = Geist({
- variable: "--font-geist-sans",
+const satoshi = localFont({
+ src: [
+ { path: "../fonts/Satoshi-Light.woff2", weight: "300", style: "normal" },
+ { path: "../fonts/Satoshi-Regular.woff2", weight: "400", style: "normal" },
+ { path: "../fonts/Satoshi-Medium.woff2", weight: "500", style: "normal" },
+ { path: "../fonts/Satoshi-Bold.woff2", weight: "700", style: "normal" },
+ { path: "../fonts/Satoshi-Black.woff2", weight: "900", style: "normal" },
+ ],
+ variable: "--font-satoshi",
+ display: "swap",
+});
+
+const inter = Inter({
subsets: ["latin"],
+ variable: "--font-inter",
+ display: "swap",
});
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
+const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
+ variable: "--font-jetbrains-mono",
+ display: "swap",
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ other: {
+ "view-transition": "same-origin",
+ },
+ title: "channel47 — Turn Reddit threads and Amazon reviews into creative strategy",
+ description:
+ "A free Claude Code plugin that extracts real customer language from public data and turns it into personas and ad angles you can use today.",
+ metadataBase: new URL("https://channel47.dev"),
+ openGraph: {
+ title: "channel47 — Turn Reddit threads and Amazon reviews into creative strategy",
+ description:
+ "A free Claude Code plugin that extracts real customer language from public data and turns it into personas and ad angles you can use today.",
+ url: "https://channel47.dev",
+ siteName: "channel47",
+ type: "website",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "channel47",
+ description:
+ "A free Claude Code plugin that extracts real customer language from public data and turns it into personas and ad angles you can use today.",
+ },
};
export default function RootLayout({
@@ -25,7 +59,7 @@ export default function RootLayout({
return (
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 3f36f7c..bc3d36d 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,65 +1,275 @@
-import Image from "next/image";
+import ScrollReveal from "@/components/ScrollReveal";
+import TopNavigation from "@/components/TopNavigation";
+import FloatingBottomNav from "@/components/FloatingBottomNav";
+import HeroSection from "@/components/HeroSection";
+import SectionHeader from "@/components/SectionHeader";
+import StepCard from "@/components/StepCard";
+import TerminalQuoteWindow from "@/components/TerminalQuoteWindow";
+import SkillCard from "@/components/SkillCard";
+import ProofCard from "@/components/ProofCard";
+import FloatingGlowOrb from "@/components/FloatingGlowOrb";
+import CTAButton from "@/components/CTAButton";
+import NewsletterForm from "@/components/NewsletterForm";
+import FooterHero from "@/components/FooterHero";
+import SocialIconLink from "@/components/SocialIconLink";
export default function Home() {
return (
-
-
-
-
+
+
+ {/* ── Skills Gallery ────────────────────────────────── */}
+
+
+
+ The Skills
+
+
+ Claude Code Plugin — 2024
+
+
+
+
+
+
+ {/* ── Setup Section ($750) ──────────────────────────── */}
+
+
-
+
+
+
+
+
+
+
+ Want this running for your team{" "}
+ by end of day?
+
+
+ I will install the plugin, configure it for your accounts,
+ and walk your team through the first research run. You will
+ have personas and angles for your first product before our
+ call ends.
+
+
+
+
+
+ $750
+
+
+ one-time setup
+
+
+
+
+ One call. Done by end of day.
+
+
+
+
+
+
+
+ {/* ── Newsletter ────────────────────────────────────── */}
+
+
+
+
+ Build Notes
+
+
+ What I am building, what is working, what is not. Weekly.
+
+
+
+
+
+
+
+
+
+ {/* ── Footer ──────────────────────────────────────────── */}
+
);
}
diff --git a/src/components/AnimatedLogo.tsx b/src/components/AnimatedLogo.tsx
new file mode 100644
index 0000000..26c32e4
--- /dev/null
+++ b/src/components/AnimatedLogo.tsx
@@ -0,0 +1,155 @@
+'use client';
+
+import { useRef, useEffect, useCallback } from 'react';
+
+const GLYPHS = ['█','▓','▒','░','▀','▄','▌','▐','■','□','▪','▫','◼','◻','▰','▱'];
+const LINES = ['█ █ ▀▀█', '▀▀█ █'];
+
+function randomGlyph() {
+ return GLYPHS[Math.floor(Math.random() * GLYPHS.length)];
+}
+
+export default function AnimatedLogo({ delay = 0 }: { delay?: number }) {
+ const rootRef = useRef(null);
+ const startedRef = useRef(false);
+ const replayingRef = useRef(false);
+
+ const animate = useCallback((isReplay = false) => {
+ const root = rootRef.current;
+ if (!root) return;
+
+ const chars = root.querySelectorAll('.logo-animated__char');
+ const targets: string[] = [];
+ chars.forEach(c => targets.push(c.dataset.char || ' '));
+
+ const locked = new Array(chars.length).fill(false);
+
+ // Reset state
+ root.classList.remove('is-complete');
+ root.classList.add('is-animating');
+ if (!isReplay) {
+ chars.forEach(c => c.classList.remove('is-locked'));
+ } else {
+ locked.fill(false);
+ chars.forEach(c => c.classList.remove('is-locked'));
+ }
+
+ // Scramble non-space chars
+ chars.forEach((c, i) => {
+ if (targets[i] !== ' ') c.textContent = randomGlyph();
+ });
+
+ // Continuous scramble
+ const scrambleInterval = window.setInterval(() => {
+ chars.forEach((c, i) => {
+ if (!locked[i] && targets[i] !== ' ') {
+ c.textContent = randomGlyph();
+ }
+ });
+ }, 50);
+
+ // Lock characters with eased stagger
+ const nonSpaceIndices = targets
+ .map((ch, i) => (ch !== ' ' ? i : -1))
+ .filter(i => i !== -1)
+ .sort(() => Math.random() - 0.5);
+
+ const duration = isReplay ? 400 : 800;
+ const baseDelay = isReplay ? 100 : 300;
+ const timeouts: number[] = [];
+
+ nonSpaceIndices.forEach((charIdx, order) => {
+ const progress = order / nonSpaceIndices.length;
+ const eased = 1 - Math.pow(1 - progress, 2);
+ const t = window.setTimeout(() => {
+ locked[charIdx] = true;
+ const el = chars[charIdx];
+ el.textContent = targets[charIdx];
+ el.classList.add('is-locked');
+ el.classList.add('is-flashing');
+ setTimeout(() => el.classList.remove('is-flashing'), 100);
+
+ // Last char — complete
+ if (order === nonSpaceIndices.length - 1) {
+ setTimeout(() => {
+ clearInterval(scrambleInterval);
+ root.classList.add('is-complete');
+ replayingRef.current = false;
+ }, 150);
+ }
+ }, baseDelay + eased * duration);
+ timeouts.push(t);
+ });
+
+ return () => {
+ timeouts.forEach(clearTimeout);
+ clearInterval(scrambleInterval);
+ };
+ }, []);
+
+ useEffect(() => {
+ const root = rootRef.current;
+ if (!root) return;
+
+ const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+ if (prefersReduced) {
+ root.classList.add('is-complete');
+ root.querySelectorAll('.logo-animated__char').forEach(c => {
+ c.classList.add('is-locked');
+ });
+ return;
+ }
+
+ const observer = new IntersectionObserver(
+ entries => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting && !startedRef.current) {
+ startedRef.current = true;
+ setTimeout(() => animate(false), delay);
+ observer.disconnect();
+ }
+ });
+ },
+ { threshold: 0.5 }
+ );
+
+ observer.observe(root);
+ return () => observer.disconnect();
+ }, [animate, delay]);
+
+ const handleMouseEnter = useCallback(() => {
+ if (
+ !startedRef.current ||
+ replayingRef.current ||
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches
+ ) return;
+ replayingRef.current = true;
+ animate(true);
+ }, [animate]);
+
+ return (
+
+
+ {LINES.map((line, lineIdx) => (
+
+ {line.split('').map((ch, charIdx) => (
+
+ {ch === ' ' ? ' ' : randomGlyph()}
+
+ ))}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/CTAButton.tsx b/src/components/CTAButton.tsx
new file mode 100644
index 0000000..ffceaeb
--- /dev/null
+++ b/src/components/CTAButton.tsx
@@ -0,0 +1,10 @@
+export default function CTAButton({ href }: { href: string }) {
+ return (
+
+ Book a setup call
+
+ );
+}
diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx
new file mode 100644
index 0000000..fae0c3d
--- /dev/null
+++ b/src/components/CopyButton.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import { useState } from "react";
+
+export default function CopyButton({ text }: { text: string }) {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(text);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch {
+ // Fallback for older browsers
+ const textarea = document.createElement("textarea");
+ textarea.value = text;
+ document.body.appendChild(textarea);
+ textarea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textarea);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ }
+ };
+
+ return (
+
+ {copied ? (
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/FloatingBottomNav.tsx b/src/components/FloatingBottomNav.tsx
new file mode 100644
index 0000000..f02fc89
--- /dev/null
+++ b/src/components/FloatingBottomNav.tsx
@@ -0,0 +1,54 @@
+const navItems = [
+ { label: "Skills", icon: "M4 4h6v6H4V4zm10 0h6v6h-6V4zM4 14h6v6H4v-6zm10 0h6v6h-6v-6z", active: true },
+ { label: "Global", icon: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 2a8 8 0 0 1 5.29 2H6.71A8 8 0 0 1 12 4zM4 12a8 8 0 0 1 .34-2.3h15.32A8 8 0 0 1 20 12a8 8 0 0 1-.34 2.3H4.34A8 8 0 0 1 4 12zm2.71 6h10.58A8 8 0 0 1 12 20a8 8 0 0 1-5.29-2z", active: false },
+ { label: "Plugin", icon: "M4 17l6-6-6-6M12 19h8", active: false },
+ { label: "Services", icon: "M12 2l2.4 7.4H22l-6.2 4.5 2.4 7.4L12 16.8l-6.2 4.5 2.4-7.4L2 9.4h7.6z", active: false },
+ { label: "Updates", icon: "M4 19.5A2.5 2.5 0 0 1 6.5 17H20M4 14.5A2.5 2.5 0 0 1 6.5 12H20M4 4h16v6H4V4z", active: false },
+];
+
+export default function FloatingBottomNav() {
+ return (
+
+
+ {navItems.map((item) => (
+
+
+
+
+
+ ))}
+
+
+ Setup
+
+
+
+ );
+}
diff --git a/src/components/FloatingGlowOrb.tsx b/src/components/FloatingGlowOrb.tsx
new file mode 100644
index 0000000..c8c90ba
--- /dev/null
+++ b/src/components/FloatingGlowOrb.tsx
@@ -0,0 +1,20 @@
+export default function FloatingGlowOrb() {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/components/FooterHero.tsx b/src/components/FooterHero.tsx
new file mode 100644
index 0000000..4b96a41
--- /dev/null
+++ b/src/components/FooterHero.tsx
@@ -0,0 +1,35 @@
+export default function FooterHero() {
+ return (
+
+
+ LET’S
+
+ TALK.
+
+
+
+ );
+}
diff --git a/src/components/HeroSection.tsx b/src/components/HeroSection.tsx
new file mode 100644
index 0000000..bca3ec5
--- /dev/null
+++ b/src/components/HeroSection.tsx
@@ -0,0 +1,65 @@
+import CopyButton from "./CopyButton";
+import AnimatedLogo from "./AnimatedLogo";
+
+export default function HeroSection() {
+ return (
+
+ );
+}
diff --git a/src/components/NewsletterForm.tsx b/src/components/NewsletterForm.tsx
new file mode 100644
index 0000000..9a13946
--- /dev/null
+++ b/src/components/NewsletterForm.tsx
@@ -0,0 +1,62 @@
+"use client";
+
+import { useState, type FormEvent } from "react";
+
+export default function NewsletterForm() {
+ const [email, setEmail] = useState("");
+ const [status, setStatus] = useState<
+ "idle" | "loading" | "success" | "error"
+ >("idle");
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ if (!email) return;
+
+ setStatus("loading");
+
+ try {
+ await new Promise((resolve) => setTimeout(resolve, 800));
+ setStatus("success");
+ setEmail("");
+ } catch {
+ setStatus("error");
+ }
+ };
+
+ if (status === "success") {
+ return (
+
+ You’re in. Watch your inbox.
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/ProofCard.tsx b/src/components/ProofCard.tsx
new file mode 100644
index 0000000..f1cee95
--- /dev/null
+++ b/src/components/ProofCard.tsx
@@ -0,0 +1,50 @@
+export default function ProofCard() {
+ return (
+
+
+
+
+ “Built from running creative strategy across 25+ DTC brands.
+ Every framework was used in production before it became a
+ skill.”
+
+
+ // Used by marketers managing paid media across Google, Meta, and
+ Microsoft Ads.
+
+
+
+
+
+
+ BUILT FROM REAL WORK
+
+
+ 25+ DTC Brands / Production
+
+
+
+
+
+ );
+}
diff --git a/src/components/ScrollReveal.tsx b/src/components/ScrollReveal.tsx
new file mode 100644
index 0000000..f8dcee5
--- /dev/null
+++ b/src/components/ScrollReveal.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+import { useEffect } from "react";
+
+export default function ScrollReveal() {
+ useEffect(() => {
+ const initializeObservers = () => {
+ try {
+ if (!("IntersectionObserver" in window)) {
+ document.querySelectorAll(".reveal").forEach((el) => el.classList.add("visible"));
+ document.querySelectorAll(".terminal-container").forEach((el) => {
+ const lines = el.querySelector(".terminal-lines");
+ if (lines) lines.classList.add("terminal-active");
+ });
+ return;
+ }
+
+ const revealObserver = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry && entry.isIntersecting && entry.target) {
+ entry.target.classList.add("visible");
+ }
+ });
+ },
+ { threshold: 0.1, rootMargin: "0px 0px -40px 0px" }
+ );
+
+ const terminalObserver = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry && entry.isIntersecting && entry.target) {
+ const lines = entry.target.querySelector(".terminal-lines");
+ if (lines) lines.classList.add("terminal-active");
+ }
+ });
+ },
+ { threshold: 0.5 }
+ );
+
+ const observe = () => {
+ document.querySelectorAll(".reveal").forEach((el) => revealObserver.observe(el));
+ document.querySelectorAll(".terminal-container").forEach((el) => terminalObserver.observe(el));
+ };
+
+ observe();
+ setTimeout(observe, 800);
+ setTimeout(observe, 2000);
+
+ return () => {
+ revealObserver.disconnect();
+ terminalObserver.disconnect();
+ };
+ } catch (err) {
+ console.warn("ScrollReveal init failed gracefully:", err);
+ document.querySelectorAll(".reveal").forEach((el) => el.classList.add("visible"));
+ }
+ };
+
+ const cleanup = initializeObservers();
+ return cleanup;
+ }, []);
+
+ return null;
+}
diff --git a/src/components/SectionHeader.tsx b/src/components/SectionHeader.tsx
new file mode 100644
index 0000000..2fd1aa3
--- /dev/null
+++ b/src/components/SectionHeader.tsx
@@ -0,0 +1,13 @@
+export default function SectionHeader({ label }: { label: string }) {
+ return (
+
+ );
+}
diff --git a/src/components/SkillCard.tsx b/src/components/SkillCard.tsx
new file mode 100644
index 0000000..ba7c906
--- /dev/null
+++ b/src/components/SkillCard.tsx
@@ -0,0 +1,56 @@
+import Image from "next/image";
+
+export default function SkillCard({
+ title,
+ category,
+ description,
+ imageSrc,
+}: {
+ title: string;
+ category: string;
+ description: string;
+ imageSrc: string;
+}) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {category}
+
+
+
+
+
+ {description}
+
+
+ );
+}
diff --git a/src/components/SocialIconLink.tsx b/src/components/SocialIconLink.tsx
new file mode 100644
index 0000000..3ec83c4
--- /dev/null
+++ b/src/components/SocialIconLink.tsx
@@ -0,0 +1,21 @@
+export default function SocialIconLink({
+ href,
+ label,
+ icon,
+}: {
+ href: string;
+ label: string;
+ icon: React.ReactNode;
+}) {
+ return (
+
+ {icon}
+
+ );
+}
diff --git a/src/components/StepCard.tsx b/src/components/StepCard.tsx
new file mode 100644
index 0000000..7cfc18b
--- /dev/null
+++ b/src/components/StepCard.tsx
@@ -0,0 +1,24 @@
+export default function StepCard({
+ stepNumber,
+ title,
+ description,
+}: {
+ stepNumber: string;
+ title: string;
+ description: string;
+}) {
+ return (
+
+
+ {stepNumber}
+
+
+
{title}
+
{description}
+
+
+ );
+}
diff --git a/src/components/TerminalQuoteWindow.tsx b/src/components/TerminalQuoteWindow.tsx
new file mode 100644
index 0000000..6af28b6
--- /dev/null
+++ b/src/components/TerminalQuoteWindow.tsx
@@ -0,0 +1,56 @@
+export default function TerminalQuoteWindow() {
+ return (
+
+
+
+
+
+
+ creative-strategist extract --raw
+
+
+
+
+
[fire-3]
+
+
+ “Marketing has always been the hardest part for us solo
+ founders. You can build an amazing product but if nobody knows
+ about it, none of that matters.”
+
+
+
+ — Reddit, r/SaaS | Journey: Problem-aware
+
+
+
+
+
[fire-2]
+
+
+ “I was burning hours every week hunting for new
+ tools.”
+
+
+
+ — PPC.io | Journey: Problem-aware
+
+
+
+
+
[fire-3]
+
+
+ “I found $3,400/month in wasted spend. In one
+ prompt.”
+
+
+
+ — Adspirer | Journey: Post-purchase
+
+
+
+
+
+ );
+}
diff --git a/src/components/TopNavigation.tsx b/src/components/TopNavigation.tsx
new file mode 100644
index 0000000..f5231d2
--- /dev/null
+++ b/src/components/TopNavigation.tsx
@@ -0,0 +1,66 @@
+import AnimatedLogo from "./AnimatedLogo";
+
+export default function TopNavigation() {
+ return (
+
+
+
+ );
+}
diff --git a/src/fonts/Satoshi-Black.woff2 b/src/fonts/Satoshi-Black.woff2
new file mode 100644
index 0000000..64492d5
Binary files /dev/null and b/src/fonts/Satoshi-Black.woff2 differ
diff --git a/src/fonts/Satoshi-Bold.woff2 b/src/fonts/Satoshi-Bold.woff2
new file mode 100644
index 0000000..0a8db7a
Binary files /dev/null and b/src/fonts/Satoshi-Bold.woff2 differ
diff --git a/src/fonts/Satoshi-Light.woff2 b/src/fonts/Satoshi-Light.woff2
new file mode 100644
index 0000000..cf18cd4
Binary files /dev/null and b/src/fonts/Satoshi-Light.woff2 differ
diff --git a/src/fonts/Satoshi-Medium.woff2 b/src/fonts/Satoshi-Medium.woff2
new file mode 100644
index 0000000..ffd0ac9
Binary files /dev/null and b/src/fonts/Satoshi-Medium.woff2 differ
diff --git a/src/fonts/Satoshi-Regular.woff2 b/src/fonts/Satoshi-Regular.woff2
new file mode 100644
index 0000000..81c40ab
Binary files /dev/null and b/src/fonts/Satoshi-Regular.woff2 differ