Living Word — Lead-Gen Phase 1B
What shipped
The full scope of knowledge/acceptance/living-word-leadgen-phase1b.md:
-
Scene-completion modal + bloom CTA card (
SceneCompletionModal.tsx+SceneCompletionCtaCard.tsx)- Brief celebratory modal that wraps the scene transition. "Walk on" stays the primary action (autoFocus, navy button).
- The bloom card renders BELOW Walk-on starting at the 3rd scene completion (decision B2). Counter persists in
localStorageaslw-scene-completions-shown:ctaand survives page reloads. - First 2 completions: modal renders, card hidden, NO
scene_completion_impressionevent logged. - From completion 3+: card renders with sacred-gold "BUILT BY CHURCHWISEAI" badge, adult-targeted copy ("your congregation"), and a CTA link to
churchwiseai.com/from-living-word. dismissedThisSessionlives in component state only — the card returns on next scene completion (a new modal mount).- Wired into
LivingWordAdventure.tsx → handleReachExit()— instead of immediately callinggoToScene, the handler setspendingSceneTransitionand the modal becomes the transition affordance.
-
NPC-chat-close CTA line (extends
NpcChatModal.tsx)- Single italic stone-500 text-xs line appended INSIDE the existing end-conversation block, BELOW the "Walk on" button.
- Voice tier is "builder credits" — clearly authored-by-the-builders, never NPC-voiced. Typographically separate from the in-character "Walk on" copy above.
- Fires on every end-state path (turn-cap, user × close, crisis deflection) — anywhere
endConversationflips true. - CTA link tagged with
npc_idfor funnel attribution.
-
Legacy Bloom finale panel (
LegacyBloomFinalePanel.tsx)- Mounted by
LivingWordAdventure.tsxexactly 1500ms AFTER the existingLegacyBloomCinematic.onComplete()fires. The 1500ms silence beat is in the parent — the cinematic itself is byte-for-byte unchanged. - Full-width navy-on-cream panel with Playfair heading ("You just walked from Eden to the New Creation."), two prose paragraphs, sacred-gold primary CTA ("See what it does for your church"), and a ghost-outline share CTA (mailto: with pre-filled subject + body containing
https://livingword.bible/?ref=share). - Share click fires BOTH
legacy_bloom_click(action: 'share') ANDshare_clickevents per spec §5. - Panel does NOT auto-dismiss; player chooses one of three actions (primary CTA, share, ×).
- Mounted by
-
No new migration. All 7 Phase-1B event types (
scene_completion_impression,scene_completion_click,npc_chat_close_impression,npc_chat_close_click,legacy_bloom_impression,legacy_bloom_click,share_click) were already in the Phase 1Alw_lead_gen_eventstable's accepted-values list (migrations/2026-05-23c-lw-lead-gen-events.sql). No schema change required. -
Test harness at
/living-word/e2e-leadgen(noindex'd, not linked from production navigation). Mounts the modal + finale panel in isolation so the Playwright spec can verify the contract without driving the 5+ minute canvas walk. -
Playwright spec at
e2e/living-word/leadgen-phase1b.spec.ts— 7 tests, all passing. Covers visibility logic (counter < 3 → card hidden; counter ≥ 3 → card visible), impression + click event shape, UTM param contract, mailto: share contract, dismiss behavior, and cross-surface ≥3-distinct-event-types verification.
Founder-locked decisions reflected
| Decision | Answer | How it's reflected in code |
|---|---|---|
| B1 — Domain routing | route to churchwiseai.com | Every CTA href is an absolute https://churchwiseai.com/from-living-word?... URL, even when the player is on livingword.bible. Asserted by the Playwright spec for each surface. |
| B2 — Early-scene CTA prominence | hide first 2 scenes | SceneCompletionModal increments a localStorage counter on every mount; card renders only when counter >= 3. First-scene mount → counter 0→1 (card hidden); third mount → counter 2→3 (card visible). |
| B3 — Legacy Bloom finale panel | keep the panel | LegacyBloomFinalePanel ships, mounted 1500ms after the cinematic completes. The cinematic is unchanged. |
| E2 — Scene-completion CTA mount point | introduce a brief scene-completion modal | SceneCompletionModal.tsx exists; Walk-on inside the modal commits the scene transition; bloom card hosted below. |
UTM tagging summary (matches spec §8)
| Surface | utm_source | utm_medium | utm_campaign | Extra params |
|---|---|---|---|---|
| Scene completion bloom card | living-word | in-game | scene-completion-v1 | &scene_id=<active_scene_id> |
| NPC chat-close line | living-word | in-game | npc-chat-close-v1 | &npc_id=<active_npc_id> |
| Legacy Bloom finale panel (primary) | living-word | in-game | legacy-bloom-finale-v1 | — |
| Legacy Bloom share CTA | (mailto: — no UTMs on share, link inside body uses ?ref=share) |
Anti-goals honored (spec §9)
- ✓ NO CTAs during gameplay or during NPC chat — completion moments only.
- ✓ NO NPC-voiced cross-sell (the "Built by ChurchWiseAI" badge is typographically separated; the chat-close line is
text-xs italic sans-serif stone-500to set it apart from the body in-character stone-600). - ✓ NO popup interstitials or page-blocking lightboxes outside completion moments.
- ✓ NO "limited time" / "act now" / fake urgency copy.
- ✓ NO third-party tracking pixels — first-party only via
lw_lead_gen_events. - ✓ NO persistent banner ads (footer credit from Phase 1A is the only persistent surface).
- ✓ NO segmentation by visitor type in Phase 1B.
- ✓ NO mid-cinematic interruption — 1500ms post-cinematic silence is the floor.
- ✓ NO behavioural / geo / time-of-day targeting — all players see the same surfaces under the same rules.
- ✓ NO A/B testing copy in Phase 1B.
- ✓ NO email capture inside the game.
- ✓ NO mirroring of
/from-living-wordonlivingword.bible.
Voice rules honored
- "Never evolves" — copy uses "carry" / "hold" / "talks like a pastor who's read the whole book" (not "evolves").
- No kid-targeted copy — "your congregation", "your church", "for ministry" — adult-targeted everywhere.
- No "lost your streak" shame — Phase 1B doesn't touch streaks (live-ops Phase 1B work).
React 19 StrictMode guard
All three impression-firing effects (SceneCompletionModal, LegacyBloomFinalePanel, NpcChatModal's chat-close hook) use a useRef guard to prevent React 19 StrictMode's intentional double-mount in development from double-counting events. The guard is per-mount, so distinct scene completions / panel mounts / chat-end events still each fire exactly once.
Phase 1C (next)
- Founder lead-gen dashboard for the funnel data — 7+ days of data, then CTR analysis per surface.
- Spec lives in a future
acceptance/living-word-leadgen-phase1c.md(not written yet).
Verification
pnpm buildclean.pnpm lintshows zero new errors/warnings in Phase-1B files.- 7 Playwright tests pass in ~6 seconds against the local dev server (
e2e/living-word/leadgen-phase1b.spec.ts). - DB spot-check (Supabase MCP) — all 7 event types landed in
lw_lead_gen_eventsafter the test run, withutm_campaign+utm_mediumcorrectly populated as columns andmetadataJSONB containingscene_id/npc_id/actionfields per spec.