This is the redesigned marketing and account site for the Aether client, built on top of the frontend design copy you provided. All pages have been rebuilt to match the pixel/terminal aesthetic from the design handoff, and the integration stubs are ready for you to wire up.
- Font: MaruMinya pixel bitmap font (
/public/fonts/x12y12pxMaruMinya.ttf) across all pages. - Palette: Near-black blue-tinted background (
#0a0b10), single cyan accent (#22d3ee), periwinkle logo (#A0B4FA). All tokens inapp/globals.css:root. - Dark/light theme:
data-themeattribute on<html>, bootstrapped fromlocalStoragebefore first paint to avoid flash. Toggle in the nav.
| Page | File |
|---|---|
| Landing (hero, about, features, pricing) | app/page.tsx |
| Login / sign-up | app/login/page.tsx + components/login-shell.tsx |
| Terms of service | app/tos/page.tsx (original markup preserved, new shell) |
| Discord verify | app/discord/verify/page.tsx |
| Dashboard shell + all sub-pages | app/dashboard/** |
| Admin shell + all sub-pages | app/admin/** |
| Component | What it does |
|---|---|
components/starfield.tsx |
Canvas animated starfield — mounts once in root layout, full-doc-height stars, hover repel, click shockwaves, hyperspace streaks |
components/scroll-reveal.tsx |
IntersectionObserver that adds .up to .reveal elements for scroll-in animations |
components/site-nav-links.tsx |
Client nav links — in-page anchors with hyperspace on the landing page, cross-page links on login |
components/design-page-nav.tsx |
Dev-only bottom-left page switcher. Remove this component and its usage in app/layout.tsx before shipping. |
All dashboard/admin UI components (workspace-shell, license-manager, checkout-form, admin-*, etc.) are unchanged. Only the shell around them was redesigned.
lib/website-auth.ts and lib/supabase/browser.ts are currently stubs that return mock data. Replace them with your real @supabase/ssr client.
lib/supabase/browser.ts — replace the mock with:
import { createBrowserClient } from "@supabase/ssr";
export function createBrowserSupabaseClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
);
}lib/website-auth.ts — replace mocks with real session reads using your server client (cookies-based). Example pattern:
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export async function getWebsiteUser() {
const cookieStore = await cookies();
const supabase = createServerClient(..., { cookies: { getAll: () => cookieStore.getAll() } });
const { data: { user } } = await supabase.auth.getUser();
return user ? { id: user.id, email: user.email ?? null } : null;
}
export async function requireWebsiteUser() {
const user = await getWebsiteUser();
if (!user) redirect("/login");
return user;
}app/api/auth/login/route.ts, signup/route.ts, magiclink/route.ts, and recovery/route.ts are stub handlers that accept requests and return { success: true }. Wire your Supabase calls in the // TODO spots in each file.
The login-shell.tsx form posts to these routes and expects:
- Signup:
{ success: true, needsEmailVerification?: boolean }or{ success: false, error: string }(non-2xx) - Login / magic link / recovery:
{ success: true }or{ success: false, error: string }(non-2xx)
Set NEXT_PUBLIC_TURNSTILE_SITE_KEY in your env. The login form automatically shows the Turnstile widget when this key is present. The turnstileToken is already passed to all four auth API routes — verify it server-side using TURNSTILE_SECRET_KEY before processing.
// Verify in your API route before calling Supabase:
const verifyRes = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
body: new URLSearchParams({ secret: process.env.TURNSTILE_SECRET_KEY!, response: turnstileToken }),
});
const { success } = await verifyRes.json();
if (!success) return NextResponse.json({ success: false, error: "Captcha failed." }, { status: 400 });lib/stripe-catalog.ts returns hardcoded mock prices. Replace each function with a real stripe.prices.retrieve(process.env.STRIPE_PRICE_*) call. Price IDs are in .env.example.
Admin pages (/admin/*) are protected by lib/admin.ts. Set ADMIN_EMAILS in your env to a comma-separated list of admin email addresses. Anyone not on the list is redirected to /dashboard. See lib/admin.ts for the full logic — swap the email check for a role check if you use Supabase roles.
The following lib files are stubs with the right function signatures — drop in your real implementations:
lib/discord.ts— Discord role sync and verificationlib/licenses.ts— license types only (no server logic)lib/stripe-fulfillment.ts— webhook fulfillmentlib/oxapay.ts— OxaPay crypto checkoutlib/feature-payload-store.ts— feature binary upload/managementlib/loader-payload-store.ts— loader JAR upload/managementlib/github-direct-upload.ts— GitHub release upload helperlib/supabase-direct-upload.ts— Supabase Storage upload helperlib/minecraft.ts— Minecraft UUID/username lookup
cp .env.example .env.local
# Fill in .env.local with real or test values
npm install
npm run dev
# → http://localhost:3000Without real env values the site still runs — auth routes return mock successes, prices show placeholder amounts, and the dashboard shows mock data.
- Remove
<DesignPageNav />fromapp/layout.tsxand deletecomponents/design-page-nav.tsx - Remove
app/globals-design.cssand its import inapp/layout.tsx - Remove
lib/mock-data.tsafter all dashboard pages are wired to real data - Verify the MaruMinya font license (
/public/fonts/x12y12pxMaruMinya.ttf) — confirm permitted for web use or swap to a licensed alternative - Set
ADMIN_EMAILSbefore deploying admin routes - Add Turnstile secret verification to all four auth API routes
- Set up Supabase auth callback route (
/auth/callback) for email confirmation and magic link flows