-
-
A live 3D hero in the playground — no sign-in needed.
-
One prompt → an Interstellar-style black hole, shipped in one line of code.
-
Gemini's output is schema-validated before it ever renders.
-
Ten AI generated Three.js templates, one performance contract.
-
Ship it with a single <script> tag — no iframe.
-
Credit-based, no subscription — failed generations never charged.
-
Your dashboard — every saved scene renders live, ready to remix or ship.
-
Refine in plain language, publish a one-line embed, or download the code you own.
Hero3D — a living 3D hero from one prompt, where the AI never writes code
Inspiration
Every AI website builder demo ends the same way: the page generates, and the hero — the first thing a visitor actually looks at — is still flat. Copy and layout now take minutes. A living 3D hero still costs a landing‑page team roughly two weeks of WebGL work: shaders, dark/light theming, mobile performance, reduced‑motion handling, and a fallback for the devices where WebGL simply fails.
We wanted that to be one prompt. And we wanted to build it so that the AI could never break production. That second constraint became the product's entire architecture — one rule we refused to bend:
The AI generates configuration, not code.
Gemini emits a JSON document validated by a strict schema. The rendering code the customer ships is hand‑written and reviewed by people. The model's worst failure isn't a broken page on someone's site — it's a schema validation error we retry and log. That single decision is what let a solo founder plus agents sell the output B2B.
What it does
Hero3D turns a one‑line prompt into a production‑ready 3D hero section.
- Type a prompt — "a violet particle field for a dark SaaS", "a soft aurora gradient behind my headline." No editor, no timeline, no nodes.
- Refine it in plain language — "make it slower, shift to indigo." Every revision is another sentence.
- Ship it — paste one line of code. The scene renders directly on the page (no iframe), follows the host's dark/light theme with a live crossfade, adapts quality per device to hold 60 fps on mobile, shows a static frame under
prefers-reduced-motion, and falls back to a CSS gradient if WebGL fails so the page never breaks.
Two delivery modes: a hosted embed (free, always carries a small badge) and a self‑contained code download (paid, no watermark, zero network requests — it keeps working even if our servers vanish). Pricing is credit‑based with no subscription: 10 free credits daily, packs at \$10 / 100 credits, and a limited Founding seat at \$50 / 1,000 (list \$100). Failed generations are never charged.
Ten hand‑written scene templates ship today: particle field · gradient blob · black hole · sun surface · galaxy · bubbles · waves · aurora · portal · water.
How we built it
The pipeline, end to end:
prompt → Hono API on Google Cloud Run → Gemini API (structured output)
→ zod strictObject validation (SceneConfig JSON)
→ one of 10 hand-written Three.js scene templates
→ watermarked hosted embed | self-contained, no-watermark export
Gemini runs in the production path of every generation, but its output is a JSON
SceneConfigchecked by astrictObject: unknown keys rejected, every knob range‑bound, both a dark and a light palette required. If the reply doesn't validate, we retry exactly once — feeding the failed output and the reason back as conversation turns — otherwise we surface a clean failure. The system prompt's enums and ranges are derived from the schema, and a byte‑level sync test fails the build if prompt and validator ever drift.All rendering is hand‑written, vanilla TypeScript + Three.js, no framework runtime in the delivered artifact. Every template obeys one shell contract: theme crossfade, reduced‑motion frame, device‑pixel‑ratio caps, visibility pause, WebGL fallback. The shaders are their own discipline — for example, the
watertemplate drives a fragment height‑field whose ripples are wave packets that decay by both viscosity and geometry:
$$A(r,t) = A_0 \, e^{-\beta t} \, \frac{1}{\sqrt{r}}$$
with a sign convention where a negative amplitude slot is a cursor wake (a settling trough that trails the pointer) and a positive slot is a click or raindrop (an expanding ring). The idle surface is lit by deviation — the difference from a flat plane — so a still scene is pixel‑identical to the background, and you only know the hero is 3D when you move.
Delivery is gated by commands, not promises. The embed loader is held to a hard 6 KB gzip budget, enforced in CI on every push alongside typecheck, lint, and ~2,400 unit tests. The downloadable export must boot from
file://fully offline with zero non‑file requests — no CDN, no web fonts, nothing — enforced by an automated gate script; browser e2e and a binary pass/fail generation eval run in the task‑completion verification loop. The delivered artifact is tuned for size rather than sold on a byte count.Money is a ledger, not a flag. Multi‑tenant Postgres (every table carries an
org_id), an append‑only credit ledger with idempotency keys, refund handling with order tombstones so a refunded order can never re‑credit, and download entitlements. Payments run through a merchant‑of‑record (Polar) behind an adapter; webhook signatures are verified in constant time within a bounded replay window. The credit unit price was chosen to satisfy two inequalities at once —
$$\text{free daily} \; < \; \text{download} \; < \; \text{pack} \qquad (10 < 30 < 100)$$
so a free tier can never quietly harvest watermark‑free downloads, and one pack always buys more than a single scene.
- Dashboard: Next.js 14. Auth: Firebase. DB: Supabase Postgres + Drizzle. Assets: Google Cloud Storage. Everything Gemini touches goes through the one deployed Cloud Run service.
There's a second layer to how we built it: the company itself runs as an agent loop, and the repository is the evidence. IMPLEMENTATION_PLAN.md is a state machine — an agent takes the topmost open task, implements it, and may only mark it done when the verification commands pass. Humans own specs, pricing, and refund policy (flagged [HUMAN]); agents execute the rest, including work usually reserved for senior engineers: an adversarial review hardened the refund webhook before real refunds existed; a 31‑agent audit told the human, with evidence, that his instruction to "widen the transactions" would pin a row lock across an entire Gemini round‑trip — and named the real defects instead; and an 18‑agent security review the night before submission triaged findings into what was safe to fix under deadline (a CORS‑middleware ReDoS patch) versus what to defer.
What we learned
- Make the AI's failure mode a validation error. It converts "the model got weird" from a customer‑facing incident into a retry. This one decision is what made B2B sales, unattended operations, and agent‑driven development tractable.
- In money paths, silent success is worse than failure. Every "safely ignored" branch needs a log line — a
200with the reason only in the response body is invisible in production. - A notification is only as good as the ledger behind it. Free‑tier generations initially weren't written to the credit ledger, so the activity badge that read from it stayed dark no matter which notification channel we tried.
- Adapter boundaries are insurance you buy before you need it. When our first payment rail's store review didn't finish in time, swapping providers cost one adapter and one webhook parser — not a re‑architecture.
- Agents can hold judgment against instruction. The best audit outcome we got all month was a swarm disagreeing with the human, with evidence.
Challenges we ran into
- Constrained decoding fights your prompt. Enabling structured output eliminated syntactically broken JSON outright — but empty schema
descriptionfields let it push out the prompt's formatting rules, and hex colors lost their#prefix in 12.5 % of calls. Restating the format inside the schema cut that to 3 %, and an adapter‑level retry absorbs the rest. - The first real payment disappeared into a silent 200. Our webhook was subscribed to an event that fires before payment completes, so the very first production order was classified "ignored" — HTTP 200, reason only in the body, zero trace in our logs. We now log every ignored path, subscribe to the paid event, and reconcile using the order id as an idempotency key so a redelivery can't double‑credit.
- We swapped the payment rail two days before the deadline. The first provider's store‑activation review stalled. Because every external call lives behind an adapter, the ledger, tombstones, entitlements, and idempotency logic didn't change by a line — one adapter, one parser, live the same day.
- Cache correctness beat cache performance. A 24‑hour edge cache on scene config meant a customer who fixed their scene could serve the old version for a day — the embed URL is fixed but its content is versioned. It's now a 60‑second TTL with stale‑while‑revalidate.
- Aesthetics don't pass a numeric gate. The
portalandwatertemplates each took several rounds of rendering to a PNG and judging by eye — "type‑checks but can't be sold" is the failure mode a bundle‑size gate will never catch.
Honesty note on revenue
Production checkout is live and has processed real transactions end to end — order, signed webhook, ledger credit. To date those are our own verification purchases, which are related‑party by definition and reported separately; arms‑length revenue through the submission cutoff is \$0, and we report zero months as zero. We'd rather report an honest zero than an inflated number — the payment rail, ledger, and refund machinery are real and verifiable against the provider's dashboard and our production logs.
What's next
The same principle, one level up: today Hero3D generates the hero scene; the next layer is a PageConfig — headline, subline, CTA, brand tokens, and section layout as validated configuration over hand‑written page templates, with the scene config nested unchanged inside. URL or prompt in, full 3D hero landing page out — and the failure mode stays a schema error, never broken markup.
Built With
- drizzle-orm
- firebase
- gemini
- glsl
- google-cloud
- google-cloud-run
- hono
- next.js
- node.js
- playwright
- pnpm
- polar
- postgresql
- supabase
- tailwindcss
- three.js
- typescript
- vite
- vitest
- webgl
- zod
Log in or sign up for Devpost to join the conversation.