3-Tier Pricing Page — Design
The design record for the three-tier pricing page. Written 2026-04-11.
Date: 2026-04-11 Status: Draft — ready for review Author: Brainstorm session with James
Context
The current pricing page at apps/statshub/src/pages/pricing.tsx offers two tiers (Pro £29.99, Pro+ £59.99). James wants to experiment with a 3-tier structure that adds an entry-level Core tier at £12.99 and repositions the middle and top tiers. The existing page stays untouched while the new page is evaluated.
This spec covers a pure visual / static page. No Stripe wiring, no subscription state, no checkout logic. Buttons are cosmetic — the goal is to see and iterate on the layout before committing to billing changes.
Goals
- Ship a new pricing page at
/pricing/3-tierthat showcases 3 tiers in a feature-comparison grid as the hero element. - Preserve proven conversion elements from the existing page (countdown banner, hero, results chart, bottom CTA) so the two pages can be compared like-for-like.
- Make the grid fully readable on mobile without awkward horizontal scroll.
- Keep the new page 100% independent from the existing page so iteration on one can't break the other.
Non-Goals (explicit YAGNI)
- No Stripe checkout,
useUser()state, or "current plan" detection. - No A/B testing infrastructure or analytics events.
- No annual/monthly toggle.
- No tooltips on feature rows (type reserves the field; not rendered).
- No "How It Works" section — the feature grid replaces it.
- No FAQ, testimonials, or changes to the results chart beyond a possible restyle.
- No shared-component extraction with the existing pricing page — duplicate inline.
File & Route
- File:
src/pages/pricing/3-tier.tsx - Route:
/pricing/3-tier - Layout: Wraps in
HeaderLayoutvia.getLayout(matches existing page) - Nav: No changes to top-nav or footer links. Page is reachable only via direct URL during the experiment.
Page Structure
Top to bottom:
CountdownBanner— Early Access 40% off countdown (duplicated inline from existing page)PricingHero— Bebas headline + subtitle + chip row (duplicated inline)FeatureGrid— NEW — the primary content of the pageResultsChart— +213u profit chart (duplicated inline)BottomCTA— "Ready to find your edge?" bar; scroll target =FeatureGrid(duplicated inline)
"How It Works" is intentionally dropped — the feature grid already communicates what each tier includes, and having both would be redundant.
Data Shape
Two flat constants at the top of the file:
type TierId = "core" | "sharp" | "sharp_pro";
type Tier = {
id: TierId;
name: string;
tagline: string;
price: string; // "12.99"
originalPrice?: string; // "19.99"
popular?: boolean;
};
type CellValue =
| { kind: "check" }
| { kind: "cross" }
| { kind: "text"; text: string };
type FeatureRow = {
label: string;
tooltip?: string; // reserved, not rendered yet
values: Record<TierId, CellValue>;
};
type FeatureCategory = {
title: string; // "Core Scanning", etc.
rows: FeatureRow[];
};
const TIERS: Tier[] = [...];
const CATEGORIES: FeatureCategory[] = [...];Tiers (Draft Content)
| ID | Name | Price | Original | Tagline | Popular |
|---|---|---|---|---|---|
core | Core | £12.99 | £19.99 | The essentials — find mispriced props fast | — |
sharp | Sharp | £29.99 | £49.99 | Macca's pre-built playbook, ready to bet | ★ |
sharp_pro | Sharp Pro | £59.99 | £99.99 | Full control, full transparency, full access | — |
Original prices are retained for the crossed-out "40% off" styling consistent with the existing page's early-access treatment.
Feature Grid Content (Draft — 12 rows across 3 categories)
Core Scanning
| Row | Core | Sharp | Sharp Pro |
|---|---|---|---|
| AI Bet Scanner | ✓ | ✓ | ✓ |
| PropHunter / Prop Screener | ✓ | ✓ | ✓ |
| Lineup Alerts (Out of Pos.) | ✓ | ✓ | ✓ |
| Leagues scanned daily | 80+ | 150+ | 150+ |
| Hit rate history (L10/20/30) | ✓ | ✓ | ✓ |
| Multi-book odds comparison | ✓ | ✓ | ✓ |
Macca's Models
| Row | Core | Sharp | Sharp Pro |
|---|---|---|---|
| Macca's Private Models | ✗ | Preconfigured (Macca's settings) | Full access + custom control |
| Markets Available | ✗ | Player markets only | Player + Team markets |
| Model Transparency (EV, Fair Odds) | ✗ | ✗ | ✓ |
| Custom Filters / Strategy Building | ✗ | ✗ | ✓ |
Community & Access
| Row | Core | Sharp | Sharp Pro |
|---|---|---|---|
| Private Sharps Community Chat | ✓ | ✓ | ✓ |
| Early access to new features | ✗ | ✓ | ✓ |
Desktop Layout (≥ 768px)
A 4-column grid: [Feature label] [Core] [Sharp] [Sharp Pro].
Header row (sticky on scroll):
- Row 1: tier name + "Most Popular" badge on Sharp
- Row 2: big price (£29 with
.99smaller) + crossed-out original +/monthly - Row 3: tagline
- Row 4: CTA button
Body:
- Category divider rows (small uppercase muted label, full-width, zinc-50/800 background)
- Feature rows: label in col 1, cell values in cols 2–4
- Cells:
- ✓ → filled primary-color circle with white Check icon (same pattern as existing page lines 362–364)
- ✗ → muted zinc-300/700 X icon
- text → 13px zinc-700/300, left-aligned, allows wrap
Sharp (middle) column emphasis:
border-[2px] border-amber-400/60 dark:border-amber-500/40- Subtle gradient wash
from-amber-50/50 via-white to-white(same as existing.popularcards) - Full-height column background extends through header and body
-translate-y-1on the header to lift it visually
CTA buttons:
- Rounded-full, 56px tall, matches existing button styling families
- Core/Sharp Pro:
bg-zinc-900 dark:bg-zinc-100dark variant - Sharp:
bg-gradient-to-r from-amber-500 to-amber-600amber variant - All buttons are
<Link href="/auth/signup">— cosmetic, no Stripe
Sticky header behavior:
- When user scrolls past the tier headers, the header row sticks to
top-0so tier names + prices + CTAs remain visible - Implemented with
position: stickyon the header row, with a background so it occludes rows passing behind it
Mobile Layout (< 768px)
The 4-column grid is unusable on narrow screens. Collapse to a 2-column grid (Feature | SelectedTier) controlled by a segmented tier switcher.
Structure:
- Segmented tier switcher — 3 buttons (Core / Sharp ★ / Sharp Pro), pill-shaped, default selection = Sharp
- Selected tier detail card — name, price, tagline, CTA (duplicated from the switcher state)
- 2-column feature grid — left = feature label, right = value for the selected tier, grouped by category
Interaction:
- Tapping a switcher button updates
activeTierstate; the detail card and the value column re-render - Switcher is
sticky top-0when scrolled so users can compare tiers without scrolling back to the top - Active pill has the Sharp amber treatment if Sharp is selected, otherwise zinc-900
- No page transition / animation on switch (may add later if feels flat)
State:
const [activeTier, setActiveTier] = useState<TierId>("sharp");Rendering strategy:
- Dual-render desktop and mobile trees with
hidden md:block/md:hiddenclasses - Avoids
useMediaQueryhydration flicker - State for the mobile tier switcher lives in the mobile tree only
Component Breakdown
Everything lives in the single file src/pages/pricing/3-tier.tsx:
- Top of file:
TIERS,CATEGORIESconstants and type defs CountdownBanner— inline copy of the existingCountdownBanner(lines 103–154 ofpricing.tsx). Small, self-contained, fine to duplicate.PricingHero— inline, copied from the existing page's hero JSX (lines 234–275).FeatureGridDesktop— new inline component. Renders the 4-column grid.FeatureGridMobile— new inline component. Renders the switcher + 2-column grid. OwnsactiveTierstate.ResultsChart— inline copy of the existing results chart (lines 477–584).BottomCTA— inline. Scroll target selector updated to match the new feature grid's class/id.- Default export:
ThreeTierPricingcomponent that composes all of the above. .getLayoutassignsHeaderLayout.
No extraction of shared components between old and new pricing pages. Duplication is the feature during the experimentation phase. Changes to one page must not affect the other. When a winner is chosen, a cleanup pass can DRY it up.
Styling Notes
- All colors via existing Tailwind tokens:
bg-primary,text-primary,bg-zinc-*, amber scale - Dark mode support on every element (follow existing page patterns)
- No new dependencies, no new
@/components/uiprimitives - Icons:
Check,X,ArrowRight,TrendingUpfromlucide-react(all already used in the existing page) - Typography: Bebas for big headlines, default sans for body (matches existing page)
- Spacing:
max-w-5xlcontainer matches existing page width
Accessibility
- Tier switcher uses
role="tablist"with each button asrole="tab"andaria-selected - Feature grid rows use semantic
<table>on desktop for screen-reader clarity; mobile uses a<dl>list - ✓ and ✗ cells include visually-hidden text (
sr-only"Included" / "Not included") so screen readers don't just announce "checkmark" - Sticky headers have proper
z-indexso they don't trap focus - CTA buttons have descriptive labels (
aria-label="Get started with Sharp plan")
Out-of-Scope / Follow-ups
Parked for later consideration:
- Tooltip popovers on feature rows (field reserved in type)
- Annual billing toggle with savings %
- Animated tier-switcher transitions on mobile
- Actual Stripe wiring once a winning design is chosen
- Shared-component extraction between
/pricingand/pricing/3-tier - A/B test infrastructure to route traffic between the two pages
Open Questions for Reviewer (James)
- Feature rows — the 12 rows above. Any to cut, add, or re-label? Especially "Leagues scanned daily — 80+ for Core" — is 80 accurate, or should Core have a different league count?
- Taglines — "essentials / pre-built playbook / full control" — any rewording?
- Original prices — I've invented £19.99 as Core's original. Should Core also have a crossed-out "Early Access 40% off" treatment, or should it feel different (no strike-through, positioned as "just £12.99 forever")?
- Sticky mobile tier switcher — good call or too busy / too much sticky UI?
- Inline duplication vs extraction — happy with the duplication-for-now approach?
Success Criteria
- Page renders at
/pricing/3-tierwithout errors, in light and dark mode - Desktop grid is visually comparable to the reference screenshot — 4 columns, middle column emphasized, prices and CTAs visible
- Mobile switcher works: tapping Core/Sharp/Sharp Pro updates the displayed feature values
- No regression to the existing
/pricingpage (since it's untouched) - Countdown banner, hero, results chart, and bottom CTA render correctly on both pages
References
- Existing pricing page: apps/statshub/src/pages/pricing.tsx
- Reference screenshot: provided by James 2026-04-11 (StatsHub Pricing & Feature Grid)