Files
shadow-gunbuilder-ai-proto/app/page.tsx
T
2026-01-18 09:09:56 -05:00

441 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { FormEvent, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import {
WrenchScrewdriverIcon,
ShieldExclamationIcon,
CurrencyDollarIcon,
BookmarkSquareIcon,
BuildingStorefrontIcon,
ChatBubbleLeftRightIcon,
} from "@heroicons/react/20/solid";
export default function HomePage() {
const [email, setEmail] = useState("");
const [useCase, setUseCase] = useState("");
const [status, setStatus] = useState<
"idle" | "loading" | "success" | "error"
>("idle");
const [message, setMessage] = useState<string | null>(null);
const [accepted, setAccepted] = useState(false);
const TOS_VERSION = "2025-12-27";
async function handleSubmit(e: FormEvent) {
e.preventDefault();
if (!email) {
setMessage("Drop an email in first, operator.");
setStatus("error");
return;
}
if (!accepted) {
setMessage(
"You must accept the Terms and confirm youre responsible for safe/legal assembly."
);
setStatus("error");
return;
}
try {
setStatus("loading");
setMessage(null);
const res = await fetch("/api/beta-signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email,
useCase,
acceptedTos: accepted,
tosVersion: TOS_VERSION,
}),
});
if (!res.ok) {
const txt = await res.text().catch(() => "");
throw new Error(txt || "Failed to save your signup.");
}
setStatus("success");
setMessage(
"✅ Youre on the list. Invites drop soon — well email your access link when its go-time."
);
setEmail("");
setUseCase("");
} catch (err) {
console.error(err);
setStatus("error");
setMessage("Something broke. Try again in a minute.");
}
}
const features = [
{
name: "Guided part roles",
description: "Pick parts by role so youre not guessing what fits where.",
icon: WrenchScrewdriverIcon,
},
{
name: "Basic compatibility warnings",
description:
"Early guardrails to catch obvious mismatches before you buy.",
icon: ShieldExclamationIcon,
},
{
name: "Live pricing totals",
description:
"See the running total as you build, with real retailer listings.",
icon: CurrencyDollarIcon,
},
{
name: "Save builds",
description: "Save a build to your account so you can finish it later.",
icon: BookmarkSquareIcon,
},
{
name: "Limited retailers (for now)",
description:
"Were starting small with a couple merchants and expanding fast.",
icon: BuildingStorefrontIcon,
},
{
name: "Feedback-driven roadmap",
description:
"Tell us whats confusing, wrong, or missing — well fix that first.",
icon: ChatBubbleLeftRightIcon,
},
];
return (
<main className="min-h-screen bg-black text-zinc-50">
{/* Abstract Brand Background */}
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-[0.55]">
<svg
width="100%"
height="100%"
preserveAspectRatio="xMidYMid slice"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<pattern
id="battlGrid"
x="0"
y="0"
width="80"
height="80"
patternUnits="userSpaceOnUse"
>
<path
d="M 80 0 L 0 0 0 80"
fill="none"
stroke="#fbbf24"
strokeWidth="0.5"
strokeOpacity="0.35"
/>
</pattern>
<pattern
id="battlHex"
width="140"
height="140"
patternUnits="userSpaceOnUse"
>
<path
d="M70 4 L136 40 L136 110 L70 146 L4 110 L4 40 Z"
fill="none"
stroke="#fbbf24"
strokeWidth="0.4"
strokeOpacity="0.25"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#battlGrid)" />
<rect width="100%" height="100%" fill="url(#battlHex)" />
</svg>
</div>
<div className="mx-auto flex min-h-screen max-w-5xl flex-col px-4 py-10">
{/* Top Bar / Logo */}
<header className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<Image
src="/battl/battl-logo-mark-f.svg"
alt="Battl Builders Logo"
width={260}
height={48}
className="block"
/>
</div>
<nav className="flex items-center gap-2 sm:gap-3">
<a
href="#request-access"
className="rounded-md border border-zinc-800 bg-zinc-950/60 px-3 py-2 text-xs font-medium text-zinc-200 hover:bg-zinc-900 hover:text-white"
>
Request Access
</a>
<Link
href="/login"
className="rounded-md border border-amber-500/60 bg-amber-500/15 px-3 py-2 text-xs font-medium text-amber-200 hover:bg-amber-500/20"
>
Sign In
</Link>
</nav>
</header>
{/* Hero */}
<section className="mt-16 grid gap-10 md:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)] md:items-start">
<div>
<div className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-black/30 px-3 py-1 text-xs uppercase tracking-[0.25em] text-white/70">
<span className="h-1.5 w-1.5 rounded-full bg-amber-400" />
Private Beta
<span className="text-white/40"></span>
<span className="text-white/60">AR-15 focus</span>
</div>
<h1 className="mt-6 text-balance text-3xl font-semibold tracking-tight sm:text-4xl md:text-5xl">
Stop Guessing.
<span className="block text-amber-300">Start Building.</span>
</h1>
<p className="mt-4 max-w-xl text-sm text-zinc-400 sm:text-base">
Battl Builders helps you plan an AR-15 build faster: choose parts
by role, get basic compatibility guidance, and compare real
listingswithout juggling tabs or spreadsheets.
</p>
<ul className="mt-6 space-y-2 text-sm text-zinc-300">
<li> Guided part roles (no where does this go?)</li>
<li> Basic compatibility warnings</li>
<li> Live pricing from current retailers</li>
<li> Save builds to your account</li>
</ul>
<p className="mt-4 text-xs text-white/50">
Early access beta. Data, pricing, and available parts are still
evolving.
</p>
</div>
{/* Beta Form */}
<div
id="request-access"
className="scroll-mt-24 rounded-lg border border-zinc-800 bg-zinc-950/70 p-5 shadow-[0_0_0_1px_rgba(24,24,27,0.9)]"
>
<h2 className="text-sm font-semibold uppercase tracking-[0.18em] text-zinc-500">
Join the Beta List
</h2>
<p className="mt-2 text-xs text-zinc-400">
Drop your email and we&apos;ll send early access when the Builder
goes hot. No spam, just builds.
</p>
<form onSubmit={handleSubmit} className="mt-4 space-y-3">
<div>
<label
htmlFor="email"
className="text-xs font-medium text-zinc-300"
>
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@gearjunkie.com"
disabled={status === "loading" || status === "success"}
className="mt-1 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 py-2 text-sm text-zinc-50 outline-none ring-amber-500/30 placeholder:text-zinc-600 focus:border-amber-400/80 focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60"
required
/>
</div>
<div>
<label
htmlFor="useCase"
className="text-xs font-medium text-zinc-300"
>
What will you use Battl Builders for?
<span className="text-zinc-500"> (optional)</span>
</label>
<textarea
id="useCase"
value={useCase}
onChange={(e) => setUseCase(e.target.value)}
rows={3}
disabled={status === "loading" || status === "success"}
placeholder="E.g. Planning a build, comparing costs, sanity-checking compatibility..."
className="mt-1 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 py-2 text-xs text-zinc-50 outline-none ring-amber-500/30 placeholder:text-zinc-600 focus:border-amber-400/80 focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60"
/>
</div>
<div className="rounded-md border border-zinc-800 bg-black/30 p-3">
<label className="flex items-start gap-2 text-[11px] text-zinc-400">
<input
type="checkbox"
checked={accepted}
onChange={(e) => setAccepted(e.target.checked)}
disabled={status === "loading" || status === "success"}
className="mt-0.5 h-4 w-4 rounded border-zinc-700 bg-zinc-950 text-amber-400 focus:ring-2 focus:ring-amber-500/40"
required
/>
<span>
I agree to the{" "}
<Link
href="/tos"
className="text-amber-300 underline hover:text-amber-200"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="text-amber-300 underline hover:text-amber-200"
>
Privacy Policy
</Link>
. I understand Battl Builders is informational only and Im
solely responsible for legality, compatibility, and safe
assembly/use of any firearm or components.
</span>
</label>
</div>
<button
type="submit"
disabled={
status === "loading" || status === "success" || !accepted
}
className="flex w-full items-center justify-center rounded-md border border-amber-500/70 bg-amber-500/90 px-3 py-2 text-sm font-medium text-black transition hover:bg-amber-400 disabled:cursor-not-allowed disabled:opacity-70"
>
{status === "loading"
? "Enlisting…"
: status === "success"
? "On the List ✅"
: "Get Beta Access"}
</button>
{message && (
<p
className={`text-xs ${
status === "success"
? "text-emerald-400"
: status === "error"
? "text-red-400"
: "text-zinc-400"
}`}
>
{message}
</p>
)}
<div className="mt-6 text-center text-[11px] text-zinc-500">
Already invited?{" "}
<a href="/login" className="underline hover:text-zinc-300">
Log in
</a>
</div>
</form>
</div>
</section>
{/* Screen Recording Section */}
<section className="relative mt-24">
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div className="relative overflow-hidden rounded-2xl border border-white/10 bg-black shadow-2xl">
{/* Screen Recording */}
<video
autoPlay
loop
muted
playsInline
className="w-full object-cover"
>
<source src="/builder-preview.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
{/* Bottom fade */}
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 bottom-0 h-24 bg-gradient-to-t from-black"
/>
</div>
</div>
</section>
{/* Info Section */}
<section className="mt-14 rounded-2xl border border-white/10 bg-white/5 px-6 py-14 sm:px-8">
<div className="mx-auto max-w-2xl text-left sm:text-center">
<p className="text-xs font-semibold uppercase tracking-[0.25em] text-amber-300/90">
Everything you need to test the core
</p>
<h2 className="mt-3 text-balance text-3xl font-semibold tracking-tight sm:text-4xl">
Build smarter rifles without the spreadsheet lifestyle.
</h2>
<p className="mt-4 text-sm leading-relaxed text-white/70 sm:text-base">
This private beta is focused on one thing: helping you plan an
AR-15 build faster and with fewer mistakes. Its early, its
evolving, and your feedback will decide what we build next.
</p>
</div>
<dl className="mx-auto mt-10 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-8 text-sm text-white/70 sm:grid-cols-2 lg:max-w-none lg:grid-cols-3">
{features.map((feature) => (
<div
key={feature.name}
className="relative rounded-xl border border-white/10 bg-black/30 p-5"
>
<dt className="flex items-center gap-2 text-sm font-semibold text-white">
<feature.icon
aria-hidden="true"
className="h-5 w-5 text-amber-300"
/>
{feature.name}
</dt>
<dd className="mt-2 text-sm text-white/70">
{feature.description}
</dd>
</div>
))}
</dl>
<div className="mx-auto mt-10 max-w-2xl rounded-xl border border-white/10 bg-black/30 p-5">
<div className="text-xs font-semibold uppercase tracking-[0.25em] text-white/60">
What feedback we want
</div>
<ul className="mt-3 space-y-2 text-sm text-white/80">
<li> Where you got confused or stuck</li>
<li> Warnings that felt wrong (or missing)</li>
<li> Parts you expected to find but couldnt</li>
<li> If this beats your current build-planning process</li>
</ul>
<p className="mt-3 text-xs text-white/50">
By joining the private beta, you agree features, data, and pricing
may change.
</p>
</div>
</section>
{/* Footer strip */}
<footer className="mt-12 border-t border-zinc-900 pt-4 text-xs text-zinc-500">
<div className="flex flex-wrap items-center justify-between gap-3">
<span>
© {new Date().getFullYear()} BATTL BUILDERS
<span className="align-super text-[0.9em] ml-0.5"></span>
</span>
<span className="text-zinc-600">
v0.1 Import engine online Builder UI in progress
</span>
</div>
</footer>
</div>
</main>
);
}