This is the canonical spec for rebuilding the prototype in Lovable (React + Vite + TypeScript + Tailwind + shadcn/ui + Supabase). The clickable prototype is the reference for structure, flow, copy and compliance; this page is the written record. ← Back to the prototype
A two-audience website that forks on entry. A neutral splash routes visitors either to the open product experience or, through a one-screen self-certification gate, to the gated investor experience (a UK financial promotion).
/ SPLASH (Zone 0 · neutral router · sells nothing) ├─ PRIMARY "Investing in Unlock" → inline self-certification → INVESTOR AREA (Zone 3) └─ SECONDARY "Just here for the platform?" → PRODUCT AREA (Zone 1)
The splash states the two paths and the risk and sells nothing. The investor card is primary and carries the certification inline (category + acknowledgement + confirm) so there is no extra screen. Once certified, "Investors" navigation goes straight in.
Governed by the Unlock Financial Promotion Playbook. A s.21 FSMA breach is a criminal offence. The structure exists to make one rule provable: we may only communicate an inducement to invest to a person who has validly self-certified as high-net-worth or sophisticated, before they see the inducement.
| Zone | What | Rules |
|---|---|---|
| 0 — Splash | Neutral router | States paths + risk. Sells nothing. No returns/opportunity language. |
| 1 — Product | Open to all (= live unlockdd.com) | Factual product/education only. No raise, no returns, no "invest". CTA = book a demo. |
| 2 — Gate | Certification | Statutory HNW (FPO Art 48) / self-certified sophisticated (Art 50A) statement captured before any Zone 3 content. Timestamped + stored. |
| 3 — Investor | Certified only | Financial promotion. Risk warning on every view. Downside ≥ upside prominence. "Illustrative, not a forecast" on all figures. |
Boundary rule: content moves down the zones; persuasion never moves up. Any returns/deal/"opportunity" language reachable by an uncertified visitor breaks the exemption.
"22p per pound" (wrong SEIS loss figure; correct ≈ 27.5p) · "7.8× average EIS return" (unsourced). Additional-rate EIS loss figure ≈ 38.5p in the £.
"EIS income tax relief is 30% of the amount invested, up to £1,000,000 per tax year (£2,000,000 for Knowledge Intensive Companies)."
Answer-first (SCQA): the gated area opens with the headline answer in the first four sentences, then the body supports it as a pyramid (one governing thought per section, MECE, no repeats). Eleven movements, ending on the response/terms:
0 SCQA opener — answer in the first 4 sentences 1 The gap — the advice gap = where the returns sit 2 What you'd be backing — product, condensed (link out to Zone 1) 3 Why it's unsolved — incumbents + the empty conflict-free quadrant 4 Proof — founder origin compressed + 5 named founding investors 5 Why we win — owned data universe + AI-native 6 Why now — regulatory / competitive / round windows 7 Market — TAM / SAM / SOM 8 The bet + EIS model — downside ≥ upside, interactive 9 Returns & exit — traction, economics, comps, two horizons 10 Terms + how to act — terms, what you get (Sandbox), reserve (Risks · Team · FAQ as supporting groups)
Product-first ordering, persistent sub-nav with scroll-spy, a thin scroll-progress aid on the long page.
unlockdd.com is already a Lovable + shadcn/ui build. Reuse its tokens (shadcn HSL form):
--background: 80 27% 95%; /* #F3F6EF sage */ --foreground: 270 3% 13%; /* near-black ink */ --primary: 157 100% 37%; /* brand green ≈ #00BD74 */ --accent: 157 100% 27%; /* darker green */ --destructive: 0 84% 60%; --border: 0 0% 85%; --ring: 157 100% 37%; --radius: .5rem;
Type: Inter (400–800). Components map cleanly: cards → Card; pills/chips → Badge (unify the two systems); tabs → Tabs; gate → RadioGroup + Checkbox + react-hook-form/zod; tables → Table; risk bar → Alert; nav → NavigationMenu + Sheet (mobile). Replace the glyph icons with lucide-react. "Pimp up" with Framer Motion (route transitions, scroll reveals, KPI count-ups), gated by useReducedMotion.
A colour↔greyscale toggle exists in the prototype (bottom-right) for the hierarchy test — greyscale is scoped to a class with the green token swapped to a dark neutral so buttons keep contrast.
A React component (recharts is the lower-effort fit). Inputs: ticket (£10k–£250k), outcome multiple (0 = fails, 1 = holds, 3 = works, 6 = strong). Additional-rate illustrative.
effectiveCost = ticket × 0.70 // after 30% EIS income relief netLossIfFails = effectiveCost × 0.55 // loss relief; ≈ ticket × 0.385 exitValue = ticket × outcome // CGT-free after the qualifying hold
Render a value path across Years 0–5; show the effective-cost reference line; on failure show the loss-relief floor ("net loss ≈ £X, not £ticket"); on success show Y2 (BPR/IHT-free) and Y3 (CGT-free) markers. Always label "illustrative, not a forecast"; most early-stage companies fail; capital at risk.
| Step | Type | Effort |
|---|---|---|
Scaffold Vite + TS + Tailwind + shadcn; port :root tokens to shadcn HSL vars | Clean | S |
React Router zone routes: / /platform /how /pricing /about /invest (guarded); layout renders header/sub-nav per route | Clean | S |
Move focus to the route <h1> on navigation; one h1 per route | Rework | S |
Rebuild pages as shadcn components; replace <a onclick> with <Link>/<button>; add mobile Sheet | Rework | M |
Supabase: auth + investor_certifications table + RLS + server-guarded /invest | New build | L |
| Gate form: react-hook-form + zod + shadcn Form (fieldset/legend, role=alert, focus-to-error); write statement_version + timestamp | Rework | M |
| Interactive EIS model in recharts (§8) | New build | M-L |
Framer Motion: route transitions + scroll reveals + KPI count-ups, all prefers-reduced-motion aware | New build | M |
| A11y/perf polish: focus-visible, contrast on microcopy, trim Inter weights, label chart SVGs | Rework | S |
Supabase certification table sketch:
investor_certifications (
id uuid pk, user_id uuid → auth.users,
category text check (category in ('hnw','sophisticated')),
criterion text, statement_version text not null,
acknowledged boolean not null, ip inet, user_agent text,
certified_at timestamptz default now(), expires_at timestamptz
) -- RLS: user_id = auth.uid()
Must survive the port: the entrance fork; the one-screen gate; gated Zone 3; product-first ordering; the interactive EIS model; all verbatim disclaimers; no inducement pre-gate.