Skip to content

feat(varitea-landing): motion-led landing + business-email-only conversion#17

Open
ninan-versioning wants to merge 1 commit into
mainfrom
jared-shoploop/varitea-landing
Open

feat(varitea-landing): motion-led landing + business-email-only conversion#17
ninan-versioning wants to merge 1 commit into
mainfrom
jared-shoploop/varitea-landing

Conversation

@ninan-versioning

Copy link
Copy Markdown
Owner

Summary

New Next.js 15 app at apps/varitea-landing/ — the motion-led Varitea dogfood landing, with conversion gated to business email only. Text guides; images / video / the 3D twin are the medium.

The hero embeds the parallel @shoploop/twin-renderer. Until that package lands on disk it resolves to a typed stub (lib/twin-renderer.stub.tsx) via an alias in tsconfig.json / vitest.config.ts. Swapping in the real renderer = add the dep, delete the stub, drop the aliases (TODO noted in-file).

Page sections

  • Hero — full-viewport <ProductTwin> (auto-rotate, pauses on interaction, subtle scroll drift). One-line headline + subline; bottom-right "Get early access" scrolls to #reserve.
  • Flow journey — Discover → Brew → Belong as three video loops (placeholder MP4s; TODO to wire real footage from google_drive). A journey, not a feature list.
  • Variant showcase<ProductTwin> + <VariantSelector>; loose-leaf flavors switch the twin live.
  • Conversion — single business-email input, centered. Trust strip: "Sourced in Darjeeling. Brewed in Houston."
  • Footer — brand mark, social, legal.

Conversion gate rationale

Business-email-only is a positioning move, not a security one: we're starting with operators and teams, so free/consumer and disposable mailboxes get warm rejection copy ("please use a work email"). The serious-operator filter is the message.

Validation runs twice:

  1. Client (EmailGate.tsx) — HTML5 type=email + regex + free-provider check for instant feedback. Never trusted.
  2. Server (app/api/waitlist/route.ts, source of truth) — same shape + free + disposable checks, then DNS MX lookup (lib/mx.ts, skippable via WAITLIST_SKIP_MX=true), then Klaviyo subscribe (409 = success, idempotent), then best-effort append to var/waitlist.jsonl.

BLOCKED_FREE_DOMAINS is a single editable array in lib/email-gate.ts; disposable domains are a vendored snapshot (source cited in-file).

Response contract: always 200. { ok: true } on success. Rejections return { ok: false } — except free_domain / invalid_shape (client-actionable, reveal nothing about mailbox existence) which include reason. disposable / no_mx / klaviyo_error are logged server-side only to avoid email enumeration + infra leakage.

GA4: waitlist_submit fires client-side after success with email_domain only — never the full address.

Env vars

Var Scope Purpose
KLAVIYO_API_KEY server Klaviyo private key for subscribe
WAITLIST_LIST_ID server List signups are added to
WAITLIST_SKIP_MX server true skips DNS MX (CI/test)
NEXT_PUBLIC_GA4_ID public GA4 measurement id

Accessibility checklist

  • prefers-reduced-motion disables auto-rotate, scroll transforms, smooth scroll, video autoplay
  • Renderer exposes role="img" + aria-label ("Interactive 3D product view …")
  • Visible focus rings on every control + skip-link to conversion
  • Email errors: role="alert" + aria-invalid + aria-describedby
  • Variant selector is a labelled radiogroup

Verification

  • pnpm typecheck ✓ · pnpm lint ✓ · pnpm test ✓ (40 tests: email-gate, mx, route happy-path + every reject reason + Klaviyo error + MX-skip flag, Hero mount/CTA) · pnpm build ✓ · dev server smoke-tested (home 200, API reject paths correct).

Decision gate

  • Varitea dogfood critical path? Yes — dogfood conversion surface.
  • Ships ≤2 weeks solo? Yes.
  • Monthly cost: ~$0 infra beyond existing Klaviyo/GA4; landing is static + one serverless route.

Screenshots

  • placeholder — hero with rotating twin
  • placeholder — flow journey
  • placeholder — conversion section

🤖 Generated with Claude Code

…rsion

Dogfood Varitea landing page. Hero embeds @shoploop/twin-renderer (typed
stub + alias until the real package lands). Waitlist conversion is gated to
business email: client check for instant feedback, server re-validates as the
source of truth (shape + free-provider + disposable + DNS MX), subscribes to
Klaviyo (409 = success), backs up to var/waitlist.jsonl, and fires a GA4
waitlist_submit event with email_domain only. Generic 200 to the client for
existence-revealing rejects to avoid enumeration; specific reasons logged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant