@funnelsgrove/cli 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/analyticsOutput.d.ts +276 -0
- package/dist/analyticsOutput.js +254 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +288 -3
- package/package.json +1 -1
- package/template_docs/AGENTS.md +40 -91
- package/template_docs/docs/ab-experiments.md +39 -20
- package/template_docs/docs/editing-flow.md +20 -5
- package/template_docs/docs/editing-step.md +105 -46
- package/template_docs/docs/payment-plans-and-discounts.md +11 -0
- package/template_docs/docs/publishing-and-versioning.md +12 -0
- package/template_docs/docs/qa-checklist.md +73 -0
- package/template_docs/docs/step-ui-guidelines.md +106 -0
|
@@ -1,66 +1,125 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Adding or Editing a Step
|
|
2
2
|
|
|
3
|
-
A step is one screen
|
|
3
|
+
A step is one screen, one decision. Target: a new step compiles and renders in local preview in under 3 minutes. Exactly six files are involved — no others.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
| # | File | What goes there |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| 1 | `src/steps/step-NN-<name>.tsx` | View, step meta, local state, one-off CSS |
|
|
8
|
+
| 2 | `src/steps/content/step-NN-<name>.content.ts` | All copy and image refs (localized) |
|
|
9
|
+
| 3 | `src/steps/editor/step-NN-<name>.editor.ts` | Which content fields the builder may edit |
|
|
10
|
+
| 4 | `src/runtime/step-registry.ts` | `componentKey` → component + meta |
|
|
11
|
+
| 5 | `src/steps/step-content.registry.ts` | step id → content/editor file paths |
|
|
12
|
+
| 6 | `src/config/funnel.manifest.ts` | Step entry + `edgesByStepId` routing |
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
Before writing UI, read [step-ui-guidelines.md](step-ui-guidelines.md) — it defines the layout shell, the sticky action bar, and the viewport rules every step must pass.
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
- `steps[].path`: URL path, such as `/paywall`.
|
|
11
|
-
- `steps[].filePath`: React view file, such as `src/steps/step-32-paywall.tsx`.
|
|
12
|
-
- `steps[].componentKey`: key registered in `src/runtime/step-registry.ts`.
|
|
16
|
+
## Naming and URLs
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
Files may use ordered names like `step-07-motivation.tsx`, and step ids may stay
|
|
19
|
+
sequential if the existing funnel uses sequential ids. Public route paths must
|
|
20
|
+
be meaningful product slugs. Use `/motivation`, `/fitness-goal`, or
|
|
21
|
+
`/email-capture`; do not create routes like `/step-1`, `/step-07`, or
|
|
22
|
+
`/question-3`.
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
- `src/steps/editor/<step>.editor.ts`: builder editor fields using `StepEditorSection`.
|
|
18
|
-
- `src/steps/step-content.registry.ts`: maps the runtime step id to those content/editor files.
|
|
24
|
+
## Recipe: New Step
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
Copy the closest existing step as the starting point (`step-02` for selections, `step-03` for interstitials). Then:
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
**1. View** — `src/steps/step-07-motivation.tsx`:
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
```tsx
|
|
31
|
+
export const stepMotivationId = 'motivation';
|
|
32
|
+
|
|
33
|
+
export const stepMotivation: FunnelStepMeta = {
|
|
34
|
+
id: stepMotivationId,
|
|
35
|
+
name: 'motivation',
|
|
36
|
+
type: 'single_step_choice',
|
|
37
|
+
title: 'Motivation',
|
|
38
|
+
description: 'Asks the user what motivates them.',
|
|
39
|
+
actionBar: { buttonText: 'Continue' }, // omit for auto-advance; { hidden: true } if the step owns its CTA
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function StepMotivation() {
|
|
43
|
+
const { attributes, setAnswer, goNext } = useFunnel();
|
|
44
|
+
const content = usePreviewStepLocalizedContent(
|
|
45
|
+
stepMotivationId,
|
|
46
|
+
stepMotivationContent,
|
|
47
|
+
getStepContentLocale(attributes),
|
|
48
|
+
);
|
|
49
|
+
// render options; on select: setAnswer('motivation', id) then goNext() for auto-advance
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<section className='motivation-step'>...</section>
|
|
53
|
+
<style>{stepStyles}</style>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stepStyles = `...one-off CSS only...`;
|
|
59
|
+
```
|
|
27
60
|
|
|
28
|
-
|
|
61
|
+
**2. Content** — `src/steps/content/step-07-motivation.content.ts`: export a `LocalizedStepContent` object holding every user-visible string and image. The view must not contain literal copy.
|
|
29
62
|
|
|
30
|
-
- `
|
|
31
|
-
- `setAnswer(key, value)` or `setAttribute(key, value)`: store data another step needs.
|
|
32
|
-
- `goNext()`, `goToStep(stepId)`, or `goChoice('yes' | 'no')`: navigate through the manifest.
|
|
33
|
-
- `user`, `setUser`, `completeStep`: only when the step explicitly needs user/session behavior.
|
|
63
|
+
**3. Editor** — `src/steps/editor/step-07-motivation.editor.ts`: export `StepEditorSection[]` exposing only the fields a non-developer should edit (see an existing editor file for the field kinds).
|
|
34
64
|
|
|
35
|
-
|
|
65
|
+
**4. Register the component** — `src/runtime/step-registry.ts`:
|
|
36
66
|
|
|
37
|
-
|
|
67
|
+
```ts
|
|
68
|
+
stepMotivation: { component: StepMotivation, meta: stepMotivation },
|
|
69
|
+
```
|
|
38
70
|
|
|
39
|
-
|
|
71
|
+
**5. Register content/editor** — `src/steps/step-content.registry.ts`:
|
|
40
72
|
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</>
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const stepStyles = `
|
|
50
|
-
.claimbee-step { ... }
|
|
51
|
-
`;
|
|
73
|
+
```ts
|
|
74
|
+
motivation: {
|
|
75
|
+
contentPath: 'src/steps/content/step-07-motivation.content.ts',
|
|
76
|
+
editorPath: 'src/steps/editor/step-07-motivation.editor.ts',
|
|
77
|
+
},
|
|
52
78
|
```
|
|
53
79
|
|
|
54
|
-
|
|
80
|
+
**6. Manifest + routing** — `src/config/funnel.manifest.ts`:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// steps[]
|
|
84
|
+
{
|
|
85
|
+
id: 'motivation',
|
|
86
|
+
path: '/motivation', // meaningful public URL, not '/step-07'
|
|
87
|
+
filePath: 'src/steps/step-07-motivation.tsx',
|
|
88
|
+
componentKey: 'stepMotivation',
|
|
89
|
+
type: 'single_step_choice',
|
|
90
|
+
title: 'Motivation',
|
|
91
|
+
},
|
|
92
|
+
// edgesByStepId — wire it in AND give it an exit
|
|
93
|
+
'step-2': [{ toStepId: 'motivation' }],
|
|
94
|
+
motivation: [{ toStepId: 'step-3' }],
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Verify:** `npm run dev`, open `/motivation`, confirm it renders at 430x932 and 390x844, Continue (or auto-advance) lands on the next step, and `npm run test:run && npm run lint` pass.
|
|
98
|
+
|
|
99
|
+
## Editing an Existing Step
|
|
100
|
+
|
|
101
|
+
Find the step in `src/config/funnel.manifest.ts` by `id` — `filePath` points at the view, `componentKey` at the registry entry. Copy changes go in the `content/` file, never inline in the view. Behavior changes go in the view file. Re-run the verify line above for the edited step.
|
|
102
|
+
|
|
103
|
+
## Navigation Inside a Step
|
|
104
|
+
|
|
105
|
+
Use `useFunnel()` only:
|
|
106
|
+
|
|
107
|
+
- `setAnswer(key, value)` / `setAttribute(key, value)` — store data another step needs.
|
|
108
|
+
- `goNext()` — normal manifest progression.
|
|
109
|
+
- `goChoice('yes' | 'no')` — when the manifest has conditional edges for this step.
|
|
110
|
+
- `goToStep(stepId)` — explicit jumps (paywall success, manage-subscription return).
|
|
111
|
+
|
|
112
|
+
Never navigate with raw `window.location`, and never encode experiment routing in the step ([ab-experiments.md](ab-experiments.md) handles that in config).
|
|
113
|
+
|
|
114
|
+
## CSS
|
|
55
115
|
|
|
56
|
-
|
|
116
|
+
One-off styling lives in a `const stepStyles` string at the bottom of the step file. Shared styling under `src/steps/styles/shared/` only when several steps already use the same pattern. Do not move paywall-specific styling into shared files.
|
|
57
117
|
|
|
58
|
-
|
|
59
|
-
2. Add `content/<step>.content.ts` with the exact content shape the view needs.
|
|
60
|
-
3. Add `editor/<step>.editor.ts` exposing only fields that should be editable.
|
|
61
|
-
4. Register the component in `src/runtime/step-registry.ts`.
|
|
62
|
-
5. Add content/editor paths in `src/steps/step-content.registry.ts`.
|
|
63
|
-
6. Add the step and route edge in `src/config/funnel.manifest.ts`.
|
|
64
|
-
7. Add focused tests when changing routing, state, checkout, parsing, or non-trivial UI behavior.
|
|
118
|
+
## Rules
|
|
65
119
|
|
|
66
|
-
|
|
120
|
+
- Keep `id`, `path`, and answer keys stable — renames break routes, persisted answers, analytics, and publish history.
|
|
121
|
+
- For new steps, use a meaningful `path`. Sequential ids and filenames are fine
|
|
122
|
+
when they match the project, but public URLs should not be `/step-1` style
|
|
123
|
+
routes.
|
|
124
|
+
- Every step needs an outgoing edge (except terminal steps like subscription handoff).
|
|
125
|
+
- Add a focused test when changing routing, state, checkout, parsing, or non-trivial UI logic.
|
|
@@ -33,6 +33,17 @@ Discounts use `BillingDiscountList` or `BillingDiscountCatalog` and are resolved
|
|
|
33
33
|
|
|
34
34
|
Use `resolvePaywallDiscountState(...)`, `advancePaywallDiscountState(...)`, `activateSecondPaywallDiscount(...)`, `serializePaywallDiscountState(...)`, and `buildDiscountedPaywallPlans(...)`. Store discount state through runtime paywall state helpers (`readPaywallStateValue`, `updatePaywallStateValue`) so it is scoped by funnel id.
|
|
35
35
|
|
|
36
|
+
### Discount on Closing Checkout
|
|
37
|
+
|
|
38
|
+
The standard two-stage flow, wired in the paywall step:
|
|
39
|
+
|
|
40
|
+
1. Paywall opens with the first-stage discount applied to plan cards.
|
|
41
|
+
2. The checkout controller's `onFirstCheckoutClosed` callback fires when the visitor closes the card checkout without paying. The paywall opens `SharedCheckoutSpecialOfferDialog` (image, discount label, accept button — copy lives in the paywall content file).
|
|
42
|
+
3. Accepting calls `activateSecondPaywallDiscount(...)`, which swaps to the stronger second-stage coupon, restarts its window, and re-renders plans via `buildDiscountedPaywallPlans(...)`.
|
|
43
|
+
4. The next checkout open carries the second-stage coupon id in its payload.
|
|
44
|
+
|
|
45
|
+
Keep both stages testable: QA must verify stage one on open and stage two after a checkout close ([qa-checklist.md](qa-checklist.md), paywall item 5).
|
|
46
|
+
|
|
36
47
|
## Checkout
|
|
37
48
|
|
|
38
49
|
Prefer shared Stripe surfaces:
|
|
@@ -26,6 +26,18 @@ fgrove publish --env production --domain <domain> --message '<summary>'
|
|
|
26
26
|
|
|
27
27
|
Production publish requires a domain. Preview publish returns a deployment URL, version sequence, and version id.
|
|
28
28
|
|
|
29
|
+
## Publish Image Optimization
|
|
30
|
+
|
|
31
|
+
Publish builds reduce raster image size before upload. Keep this path enabled:
|
|
32
|
+
PNG/JPEG sources are compressed, AVIF/WebP variants are generated for supported
|
|
33
|
+
browsers, and the original image remains the fallback. Do not bypass this with
|
|
34
|
+
remote funnel-critical image URLs.
|
|
35
|
+
|
|
36
|
+
After publishing image edits or a production candidate, check the deployment
|
|
37
|
+
metadata, CLI output, or `publishBuild.stageTimings.imageVariants`. If the
|
|
38
|
+
image-variant stage is missing or unavailable, report that explicitly before
|
|
39
|
+
calling the publish ready.
|
|
40
|
+
|
|
29
41
|
## Local Sync Contract
|
|
30
42
|
|
|
31
43
|
The CLI writes `.funnelsgrove-sync.json` into the local tree. Keep it there. It records workspace id, funnel id, and current draft version id so later `sync up` can patch only changed/deleted files.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# QA Checklist
|
|
2
|
+
|
|
3
|
+
What to test, where to test it, and what blocks a publish. QA can take as long as it needs — it is the gate before anything user-facing ships. Scale the scope to the edit: a copy tweak needs the affected step only; anything touching flow, paywall, checkout, pricing, identity, or experiments needs the full list.
|
|
4
|
+
|
|
5
|
+
## Where
|
|
6
|
+
|
|
7
|
+
| Stage | URL | When |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Local | `npm run dev` → `http://localhost:3000` | Always, before sync |
|
|
10
|
+
| Preview | URL returned by `fgrove publish --env preview` | Before any production publish |
|
|
11
|
+
| Production | Live domain | After production publish |
|
|
12
|
+
|
|
13
|
+
A production publish without a QA-passed matching preview build is a blocker unless the user explicitly accepts the risk.
|
|
14
|
+
|
|
15
|
+
## 1. Flow Coverage
|
|
16
|
+
|
|
17
|
+
- Open the first step and walk every step to the end.
|
|
18
|
+
- Exercise every branch (`goChoice` yes/no paths) — check both sides.
|
|
19
|
+
- For each experiment in `src/config/experiments.ts`, force both variants (`?editor=true&experimentVariant=control|variant_b`) and confirm each renders and advances.
|
|
20
|
+
- Submit email/identity capture with a test address and confirm advancement.
|
|
21
|
+
- Keep the browser console open the whole walk: zero uncaught errors and no failed requests (ignore third-party analytics noise).
|
|
22
|
+
|
|
23
|
+
## 2. Visual Pass
|
|
24
|
+
|
|
25
|
+
At **430x932**, then **390x844** (rules in [step-ui-guidelines.md](step-ui-guidelines.md)):
|
|
26
|
+
|
|
27
|
+
- Nothing intersects or overlaps: text never collides with images, cards, badges, or the action bar; modals and dialogs fit the viewport.
|
|
28
|
+
- Continue button sits on an opaque bar at the bottom and stays visible on every step, including while content scrolls.
|
|
29
|
+
- No horizontal scroll, no clipped or truncated content, no broken images.
|
|
30
|
+
- Disabled CTA states render correctly and enable when valid.
|
|
31
|
+
|
|
32
|
+
## 3. Image Performance
|
|
33
|
+
|
|
34
|
+
For image edits and every preview-to-production candidate:
|
|
35
|
+
|
|
36
|
+
- Confirm edited step images are listed in `funnelManifest.assets` and attached
|
|
37
|
+
to the relevant manifest steps with `assetIds`.
|
|
38
|
+
- Confirm first-viewport images use the framework's priority/preload mechanism.
|
|
39
|
+
- Confirm the shell warms only likely next-step images at low priority, not the
|
|
40
|
+
full funnel image set.
|
|
41
|
+
- After preview or production publish, confirm build metadata includes the
|
|
42
|
+
`imageVariants` stage or name why it is unavailable.
|
|
43
|
+
- In the browser/network panel, confirm AVIF/WebP variants are served when the
|
|
44
|
+
browser advertises support, with the original raster image as fallback.
|
|
45
|
+
|
|
46
|
+
## 4. Paywall and Checkout (most important)
|
|
47
|
+
|
|
48
|
+
Test the paywall on every QA run that touches it, pricing, discounts, or experiments — on the paywall step **and** every live paywall variant:
|
|
49
|
+
|
|
50
|
+
1. **Plans render correctly** — titles, struck-through old price, discounted price, per-day price, featured/preselected plan match `src/config/billing.plans.ts` exactly. Verify discount math.
|
|
51
|
+
2. **Countdown / discount state** — first-stage discount applies on open; timer counts down.
|
|
52
|
+
3. **Checkout opens** — CTA opens the checkout with the selected plan; itemized totals match the selected plan and applied coupon.
|
|
53
|
+
4. **Apple Pay / Google Pay buttons** — wallet slots render (Apple Pay needs Safari/iOS context, Google Pay needs Chrome; on unsupported browsers expect the documented fallback, not a broken gap). If wallets never appear in a supported context, treat as a defect.
|
|
54
|
+
**Note:** wallet buttons silently fail when the domain and checkout return URLs are not configured in the Stripe dashboard (payment method domains + return URL allowlist). New funnels and new domains must have Stripe configured before wallet QA can pass — if it is not configured, report it as a named blocker, not a pass.
|
|
55
|
+
5. **Close-checkout discount** — close the checkout without paying: the special-offer dialog must appear; accepting it must apply the second-stage (larger) discount to the plan cards and the next checkout's payload. Verify the new prices and coupon id.
|
|
56
|
+
6. **Test payment** — complete a payment with Stripe test card `4242 4242 4242 4242` (test mode) or the approved payment path. Confirm redirect to the subscription-started step with `user_id` preserved.
|
|
57
|
+
7. **Success state** — subscription-started renders, registration/next-step handoff works.
|
|
58
|
+
|
|
59
|
+
If checkout, payment mode, or test credentials are unavailable, name the skipped item explicitly — a silent skip counts as a fail.
|
|
60
|
+
|
|
61
|
+
## 5. Registration and Links
|
|
62
|
+
|
|
63
|
+
- Complete the registration page with valid test data; confirm validation errors on invalid data.
|
|
64
|
+
- Click every legal/support/account link on paywall, checkout, and registration: terms, privacy, subscription policy, money-back, support email.
|
|
65
|
+
|
|
66
|
+
## 6. Subscription Management
|
|
67
|
+
|
|
68
|
+
- Open `/manage-subscription?user_id=<test-user>` (or with `stripe_customer_id`).
|
|
69
|
+
- Confirm the subscription list loads and the cancellation flow completes when a test subscription is available; otherwise name it as a skipped item.
|
|
70
|
+
|
|
71
|
+
## Reporting
|
|
72
|
+
|
|
73
|
+
Every QA run ends with a short report: stage + URLs tested, steps/branches/variants covered, viewports checked, image optimization/preload result when relevant, paywall items 1–7 pass/fail, payment method used, console findings, and named blockers or explicitly skipped items with the reason. Blockers block production unless the user accepts the risk in so many words.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Step UI Guidelines
|
|
2
|
+
|
|
3
|
+
Every step must pass these rules before it counts as done. They are distilled from high-performing quiz funnels (BetterMe-class) and apply to new steps, edited steps, and paywalls alike.
|
|
4
|
+
|
|
5
|
+
## Viewports
|
|
6
|
+
|
|
7
|
+
Funnels are mobile-first. Build and verify at:
|
|
8
|
+
|
|
9
|
+
| Viewport | Size | Role |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| iPhone 16 Pro | **430 x 932** | Default. Matches `viewport` in `funnel.manifest.ts`. |
|
|
12
|
+
| iPhone 12 / 13 / 14 | **390 x 844** | Required spot-check on every created or edited step. |
|
|
13
|
+
|
|
14
|
+
Pass criteria at both sizes: no horizontal scroll, no clipped or overlapping content, the primary CTA visible without scrolling on selection/input steps, and tap targets at least 44px tall.
|
|
15
|
+
|
|
16
|
+
## Layout Shell Contract
|
|
17
|
+
|
|
18
|
+
The shell (`src/components/FunnelFlow.tsx`) owns the frame; steps fill the middle:
|
|
19
|
+
|
|
20
|
+
1. **Fixed top bar** — opaque background, back arrow, brand or section title, optional progress. Thin bottom hairline.
|
|
21
|
+
2. **Scrollable content** — the step body. Must add bottom padding so the last element clears the action bar.
|
|
22
|
+
3. **Fixed bottom action bar** — the Continue button area. **The bar is opaque, never transparent** — content scrolling under a floating button is a defect. Reference implementation:
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
position: fixed;
|
|
26
|
+
bottom: 0;
|
|
27
|
+
left: 0;
|
|
28
|
+
right: 0;
|
|
29
|
+
background: var(--color-surface); /* solid — no alpha */
|
|
30
|
+
border-top: 1px solid rgba(23, 23, 23, 0.08);
|
|
31
|
+
padding: 16px 20px calc(16px + env(safe-area-inset-bottom));
|
|
32
|
+
z-index: 11;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The button itself: full width, ~50px tall, pill radius, theme primary color. Disabled state stays visible (washed-out), it never disappears.
|
|
36
|
+
|
|
37
|
+
Steps control the shell bar through `FunnelStepMeta.actionBar`:
|
|
38
|
+
|
|
39
|
+
- omit → default Continue button;
|
|
40
|
+
- `{ buttonText: '...' }` → custom label;
|
|
41
|
+
- `{ hidden: true }` → step owns its CTAs (paywalls);
|
|
42
|
+
- auto-advance steps (single-select) need no bar at all.
|
|
43
|
+
|
|
44
|
+
## Step Patterns
|
|
45
|
+
|
|
46
|
+
One decision per screen. Pick the matching pattern; do not invent hybrids.
|
|
47
|
+
|
|
48
|
+
**Single-select** — tap an option → store answer → `goNext()` immediately. No Continue button. Option cards: icon or image + label + radio indicator, ~16px gap, selected state with tinted background.
|
|
49
|
+
|
|
50
|
+
**Multi-select** — "Choose all that apply" subtitle, checkbox cards, sticky Continue **disabled until at least one selection**. A "None of the above" option clears and excludes the others.
|
|
51
|
+
|
|
52
|
+
**Scale question** — statement in quotes as the headline, 1–5 buttons in a row, anchored labels ("Strongly disagree" / "Strongly agree") at the ends. Auto-advance on tap.
|
|
53
|
+
|
|
54
|
+
**Numeric input** (height, weight, age) — large centered input, unit toggle (kg/lbs, cm/ft) where relevant, visible validation range hint ("enter a value from 90 cm to 243 cm"), Continue disabled until valid. When the value enables personalization, show instant inline feedback below the input (e.g. a BMI callout: amber card, icon, bold key phrase, supportive copy that says how the data will be used). Hard-stop ineligible values with a clear message, not a silent disable.
|
|
55
|
+
|
|
56
|
+
**Info interstitial** — headline, hero image card, 2–3 short supporting lines, sticky Continue. Insert one every 3–5 questions to re-sell the promise and break monotony; tie it to the answer just given ("Keep your back and knees strong → your plan will include…").
|
|
57
|
+
|
|
58
|
+
**Personalized summary** — mirror the user's data back (profile card, gauge with the user's position, attribute list, projection chart with goal badge and date). Charts that predict results need a compliance footnote ("illustrative purposes only / results vary").
|
|
59
|
+
|
|
60
|
+
**Loading / plan-building** — animated percent ring or bar plus rotating social proof (ratings, testimonials) during the wait. Auto-advance on completion.
|
|
61
|
+
|
|
62
|
+
**Email capture** — single email field, nothing else on screen. Headline names the deliverable ("Enter your email to get your plan"). Padlock icon + one privacy reassurance line + Privacy Policy link. Marketing opt-in is its own subsequent step, never a pre-checked box.
|
|
63
|
+
|
|
64
|
+
## Paywall Anatomy
|
|
65
|
+
|
|
66
|
+
The proven block order, top to bottom:
|
|
67
|
+
|
|
68
|
+
1. **Sticky offer header** — compact bar with countdown timer ("Reserved price for 09:59") and a small CTA, always visible while scrolling.
|
|
69
|
+
2. **Before/after comparison** — "Now" vs "Your Goal" with attribute deltas.
|
|
70
|
+
3. **Applied-discount card** — promo code chip, "applied automatically" copy, countdown.
|
|
71
|
+
4. **Plan cards** — usually three; mark one "MOST POPULAR" and preselect it; show struck-through old total → discounted total and a large per-day price anchor.
|
|
72
|
+
5. **Primary CTA** directly under the cards.
|
|
73
|
+
6. **Auto-renewal disclosure** — full renewal price, interval, cancellation path. Required, directly below the CTA.
|
|
74
|
+
7. **Plan highlights** — icon + bold benefit + one-line explanation.
|
|
75
|
+
8. **Trust blocks** — press logos, testimonials with concrete results, results disclaimer.
|
|
76
|
+
9. **FAQ** ("What happens after payment?") and **money-back guarantee** with conditions.
|
|
77
|
+
10. Repeat plan cards + CTA at the bottom for long pages.
|
|
78
|
+
|
|
79
|
+
Checkout opens as a modal over the paywall: itemized breakdown (regular price, discount line, promo chip, VAT, total, "you save" line), wallet buttons first (Apple Pay / Google Pay / PayPal as available), card entry collapsed behind a radio. Closing the checkout without paying triggers the second-stage discount dialog ([payment-plans-and-discounts.md](payment-plans-and-discounts.md)) — verify both discount stages whenever you touch the paywall.
|
|
80
|
+
|
|
81
|
+
## Image Performance
|
|
82
|
+
|
|
83
|
+
Use the ClaimBee/Blessly image loading pattern:
|
|
84
|
+
|
|
85
|
+
- Keep raster artwork on the build-time optimization path so publish can shrink
|
|
86
|
+
PNG/JPEG sources and create AVIF/WebP variants.
|
|
87
|
+
- Put step image metadata in `funnelManifest.assets` and reference those images
|
|
88
|
+
from each step with `assetIds`.
|
|
89
|
+
- Above-the-fold images on the current step use the framework's priority/preload
|
|
90
|
+
mechanism.
|
|
91
|
+
- The flow shell may warm likely next-step images at low priority shortly after
|
|
92
|
+
the active step loads. Do not preload the whole funnel image set on first load.
|
|
93
|
+
- Any image-heavy funnel should keep a contract test that every `assetId`
|
|
94
|
+
resolves and the shell uses manifest-driven next-step preloads.
|
|
95
|
+
|
|
96
|
+
## Content-Fit Audit
|
|
97
|
+
|
|
98
|
+
Run on every created or edited step, at 430x932 then 390x844:
|
|
99
|
+
|
|
100
|
+
1. Open the step in local preview (`npm run dev`).
|
|
101
|
+
2. Check: nothing clipped, nothing overlapping, no text truncated mid-word, images loaded with correct aspect, CTA fully visible above the fold on selection/input steps.
|
|
102
|
+
3. Scroll to both ends — last content clears the action bar; nothing hides under the top bar.
|
|
103
|
+
4. Check disabled→enabled CTA transition where applicable.
|
|
104
|
+
5. Fix and re-check before moving to another step.
|
|
105
|
+
|
|
106
|
+
Report the audit (both viewports, pass/fail per step) in your summary.
|