# Life Simulator — "Why Insurance Matters" gamified flow

Gamified insurance-education flow launched from the Kinko insurance home page. Recreated from 34 Figma nodes (Insurance-B2B file `CCgaF9bHzMRPFMRRfbTt6H`).

## Decision: HTML prototype FIRST, Figma later
User directive: build HTML prototype using Kinko DS first; only create Figma screens AFTER all screens look good. Use the EXISTING Kinko koala mascot (do NOT recreate it); illustrations may be exported/recreated/mildly animated.

## Files (in legacy web project `Kinko_Design 2/screens/`)
- `life-simulator.html` — shell: inlined :root KDS tokens + all component CSS + `<template id="koala-tpl">` (animated koala SVG). Loads data.js then engine.js.
- `life-simulator.data.js` — PROFILES (Kavita 25 / Suresh 27), ART (per-theme SVG motifs), FRAMES registry, RESULT chart/decisions/lessons. Helper builders ev()/rx()/oc().
- `life-simulator.engine.js` — IIFE render engine: THEME map, ICON map (inline SVGs), koala()/statusbar/header/chips/chrome/scene builders, renderEvent/Reaction/Outcome/Intro/Income/Load/Result, render/go/back navigation with back-stack. Delegated click handler on `.phone` for [data-go]/[data-act]/[data-prof]/[data-inc].
- Mascot source: `Kinko_Design 2/screens/assets/koala.svg` (animated grey/navy koala, SMIL blink/breathe/ear/mouth — inlined into template so it animates).

## Journey (9 events, A/B branching)
e1 First Job(25,90%) → e2 Kidney Stone(26,80%) → e3 Marriage(28,70%) → e4 Spouse Medical(29,60%) → e5 Add Spouse(30,50%) → e6 Promotion(31,40%) → e7 Bike Accident(32,30%) → e8 Final Reminder(33,20%) → e9 Colleague Layoff(34,10%) → load_result → result.
Each event: themed motif scene + body copy + A/B options → reaction (dimmed scene + koala overlay + insight + Next) → some have outcome (bill-breakdown rows + koala bubble).
Result: SVG line chart (with vs without insurance), decision tiles (Medical ₹760k, Premiums ₹100k, Claims ₹300k, Final Savings ₹560k), social proof, lessons.

## Key implementation gotchas (FIXED)
- **Header readability on light themes:** `.screen` bg is ALWAYS navy `#02090d`; theme tint applied only to `.body` via inline style. Keeps header/chips/chrome white-on-navy and readable. (dark themes: firstjob/bike/layoff; light: kidney/marriage/spousemed/spouseopp/promotion.)
- **renderIncome sheet injection:** use `intro.lastIndexOf('</div>')` slice-insert, NOT brittle `.replace()`.
- **NO EMOJIS rule** (recurring user pref): replaced 🎉 (e3 marriage copy) and 👍 (result note-good) — note-good now uses `ICON.bulb` with `.ng-ic` class. Verified zero emojis in result screen.
- **Single-accent rule:** result chart "with insurance" line = Kinko green `#009b1a` (was off-brand blue `#3b82f6`); "without" = error red `#f3473c`.

## Real Figma illustrations (downloaded → `screens/assets/sim/`)
Figma uses RICH per-scene character illustrations (raster), NOT abstract motifs. Downloaded each event's image-rect node as PNG@2x. Wired via `scene()` → `<img class="art-img" src="assets/sim/{theme}.png">` (object-fit:cover, mild ken-burns `@keyframes kb`). Filenames == theme keys.
| theme | file | Figma event frame | image-rect node |
|---|---|---|---|
| firstjob | firstjob.png | 2082:25681 | 2082:25712 |
| kidney | kidney.png | 2082:25862 | 2082:25893 |
| marriage | marriage.png | 2082:25922 | 2082:25953 |
| spousemed | spousemed.png | 2082:26976 | 2082:27007 |
| spouseopp | spouseopp.png | 2082:25982 | 2082:26013 |
| promotion | promotion.png | 2082:26295 | 2082:26326 |
| bike | bike.png | 2082:26355 | 2082:26386 |
| layoff | layoff.png | 2082:26102 | 2082:26133 |

## 34-node Figma catalog (file CCgaF9bHzMRPFMRRfbTt6H, node prefix 2082-)
Events (+ button-highlight variants), reactions (owl mascot in Figma → I use koala), outcomes (bill breakdown), intro/splash/result. Key nodes: 27680=splash "Starting your Game"; 27838/28038=intro "How it works"+income sheet; 27478=result (tall, line chart+decisions+social+lessons); 27376=kidney outcome; 27243=marriage reaction. Full ID list: extract via `grep -oE 'node-id=[0-9]+-[0-9]+'` on the transcript. Header text in Figma reads "Why Insurance Matter (Life Simulator)" (sic, no 's').
**Download method:** `mcp__figma__download_assets(nodeId=image-rect, format png, scale 2)` → use `export.url` → curl to assets/sim/. OAuth figma server works without desktop open.

## Preview
Server "coverage-calculator" (serverId `dff95d5e-e377-447a-b10a-6e4882cd98fe`), port 3460, serves `Kinko_Design 2`. URL: `http://localhost:3460/screens/life-simulator.html`. Phone shell 360×780.

## Dynamic player state (added 2026-06-12) — choices now drive top chips
- engine.js: `let state={personal,spouse}` + `resetState()` on `load_start`/`intro`. `BUYS_PERSONAL=['r1a','r6a','r9a']`, `ADDS_SPOUSE=['r3a','r5a','r8a']` mutated in `go(id)`. `insLabel()` → 'None' | 'Self' | 'Spouse' | 'Self + Spouse'. `chrome()` calls `chips(f.savings,f.trend,insLabel())` (was hardcoded `f.ins`).
- **Savings rupee numbers stay AUTHORED** (canonical cautionary path) — only the Insurance status chip is dynamic. Full savings-economy simulation NOT built (would require rewriting all outcome bill math + result totals; flagged to user as a larger optional upgrade).
- **Contradiction fix:** e4 (spouse medical) + e7 (bike) used to punish the player ("she wasn't added") even if spouse was added. Now data has `alt:{warn,options}` on e4/e7; renderEvent uses it when `state.spouse`. New covered outcomes `o4c`/`o7c` (Full Paid, "Savings: Protected"). renderEvent colors the alt note GREEN (success) not red when covered.
- chip CSS: `min-height:32px;padding:5px 10px;line-height:1.25;text-align:center;radius-16` so "Self + Spouse" wraps cleanly.

## Component fidelity pass (2026-06-15)
- **Question options:** both A/B now identical `.opt` white cards. Removed the `o.hot ? 'dark'` navy-highlight on the recommended option (user: options must be same color). `.opt.dark` CSS now unused.
- **CTA buttons = KDS Button · SECONDARY variant** (user picked KDS secondary, NOT green primary). `.btn` now: height 52px (LG), radius-8, font 16/600, transition bg+shadow. `.btn-primary` = `--action-secondary` (navy-500) + 3D ring/shine box-shadow (`0 0 0 1px pressed, inset 0 -1px 0 pressed, inset 0 1px .75px rgba(255,255,255,.12)`), hover→navy-400+.18 shine, active→navy-600+inset. Removed unused `.btn-green`.
- Added missing inlined tokens: primitive `--color-primary-navy-400:#3c5566`; semantics `--action-primary-pressed`(green-600), `--action-secondary`(navy-500), `--action-secondary-hover`(navy-400), `--action-secondary-pressed`(navy-600).
- **Component audit verdict:** prototype REIMPLEMENTS KDS components inline w/ tokens (doesn't import css/*.css). Covers relevant set: Button, Chip, Alert(.insight), Chat-bubble(.bubble, no tail), Radio, Progress-bar, Stepper, Card, Bottom-sheet, Divider, Avatar, Header. Remaining minor drift (NOT yet aligned, low priority): chip uses radius-16 not pill (for 2-line wrap); koala bubble lacks KDS chat-bubble tail.
- **Preview gotcha:** `location.reload()` from inside the sim lands on the insurance HOME page (sim is launched from there). To reload the sim, navigate explicitly to `http://localhost:3460/screens/life-simulator.html`.

## Reaction screen redesign (2026-06-12)
renderReaction no longer uses absolute `.overlay` (caused big empty middle void). Now flows like renderOutcome: dimmed `.scene` (opacity .5) → `.scard light` card with koala+bubble+insight → `.dock` Next at bottom. Removed unused `renderEventStatic`.

## QA findings (full functional pass 2026-06-15) — open defects
Both paths walked end-to-end via eval-driven clicks (preview_click + screenshots intermittently switch to the React home tab on same origin :3460 → URL flips to /screens/coverage-calculator-kds; NOT a sim bug, sim HTML/JS has zero navigation code, no service worker). Chain all-synchronous clicks inside ONE eval to avoid the tab-switch artifact.
Working correctly: 9-event order, ages 25→34, remaining 90→10%, authored savings, mascot SMIL (5 indefinite transforms), dynamic insurance chip None→Self→Self+Spouse, e4/e7 covered branching (alt options + green success warn + o4c/o7c), reactions/outcomes, back nav (reaction→event via .ret, event→prev via stack), double-click safe (DOM rebuild detaches stale node), no emojis, responsive (375px centers 327×780, profile row h-scrolls), income radio select.
DEFECTS — FIXED 2026-06-15 (all in life-simulator.engine.js):
1. **Static result → now DYNAMIC.** Added `tally{bill,covered,paid,premiums,seen}` reset in resetState; accumulate in `go(id)` from `NUM` map (per-outcome ₹) + `PREM` map (r1a 72k/r6a 30k/r9a 10k), deduped via seen Set. renderResult computes tiles (Medical Bills/Claims Settled/Premiums/Final Savings=GROSS 760k−paid−premiums), conditional note/sub/lessons (wellCovered vs partial vs unprotected), impact row reflects o4c(covered) vs o4(paid). Verified: cautionary all-skip = Claims ₹0/Premiums ₹0/Final ₹525k/cautionary lessons; protected = Claims ₹235k/Premiums ₹112k/Final ₹648k/positive lessons. `fmtK(0)`→"₹0".
2. **Calculate Coverage CTA** → now data-act="coverage" → `location.href='coverage-calculator-kds'` (relative to /screens/). Replay Journey stays data-act="replay".
3. **"Why this matters" link REMOVED** from renderEvent (was silently navigating to option A).
4. **Income validation added** — Continue `disabled` (opacity .45, pointer-events none) until selIncome>=0. (Profile/income still cosmetic to the journey — making journey profile-driven is the large rewrite, NOT done.)
5. **Result axis** now "Age 25"→"Age 34" (was 27).
6. **Header spacing** — .sub now `margin-left:6px` (title/sub no longer collide; wraps cleanly).
STILL OPEN (low priority): profile selection cosmetic (journey always age-25 authored); intro back button inert (stack empty, harmless).

## Motion/Lottie fidelity pass (2026-06-15) — god-mode deep QA upgrade
User demanded deeper design/motion QA (functional pass wasn't enough). Grounded against Figma file CCgaF9bHzMRPFMRRfbTt6H screens (reaction 2082:27243, event 25922, kidney outcome 27376, result 27478, intro 27838). Implemented:
- **Lottie koala poses now USED** (replaced inline SMIL `koala()` — function + `koala-tpl` removed). Copied `koala-pose{1,4,7}-rigged.json` from `bear-output/` → `screens/assets/sim/`. Loaded lottie-web 5.12.2 in HTML. Engine: `POSE={reflect:1,wave:4,celebrate:7}`, `koalaLot(kind,cls)` emits `<div data-pose=N>`, `mountKoalas(root)` loads after each innerHTML (guards `data-mounted`), `destroyAnims()` in render() prevents leaks. reaction=Pose1, good outcome=Pose7 (matches Figma celebrating mascot w/ stars), intro hero=Pose4 wave.
- **Progress bar FIXED**: was `width:rem%` (backwards, shrinking, never animated since innerHTML births at final width). Now `progressOf(f)=100-rem` (forward: Age25/rem90→10%, Age34/rem10→90%, matches Figma green growth). `prevBar` module var: chrome() renders `<i width:prevBar% data-bar=target>`, `animateBar()` uses `setTimeout(60ms)` (NOT rAF — rAF is throttled/frozen in background preview tab) to set target so CSS `transition:width .6s` animates each step. Reset prevBar=0 on load_start/intro.
- **Staged popup reveal + breathing time**: CSS `@keyframes pop`(scale+slide, back-ease) / `slideUp` / `dim`. Classes `.reveal`/`.reveal-up` + delay classes `.r-d1..d4` (.12/.42/.72/1s). reaction & outcome: scene `.dimming` (animates 1→.5), scard r-d1, bubble+koala r-d2, rows r-d3, insight+dock r-d4 → reveals in sequence.
- **Correct-answer celebration**: `celebrate(msg,sub)` appends `.celebrate` overlay to `.phone` (Pose7 lottie + 12 CSS `.spark` burst, `@keyframes spark` radial fling), auto-fades after 1.45s. Fires in `go()` ONLY first time a protective choice is made: `boughtSelf` (BUYS_PERSONAL & !state.personal)→"Smart move!", `addedSpouse` (ADDS_SPOUSE & !state.spouse)→"Family protected!".
- **Scalable question template**: extracted `feedbackTemplate(cfg)` — one reusable learning-popup used by BOTH renderReaction & renderOutcome (cfg: base,tone,h,say,pose,tip,body?,next,btn). Drop-in for any quiz/question flow. body optional (outcome passes bill rows wrapped in `.rows`).
- VERIFIED in preview (server dff95d5e port 3460): intro Pose4 mounts, e1→r1a fires celebration (Pose7+12 sparks+"Smart move!") over reflect-Pose1 reaction w/ dimming scene, progress bar visibly green-fills 10%→20% across e1→e2, kidney o2a outcome = "Hurray!!"+Pose7+star twinkles+correct rows (1:1 w/ Figma), back nav outcome→event works, ZERO console errors. animateBar inline width confirms correct forward target.
- **Preview gotcha (NEW):** background/headless preview tab freezes rAF AND CSS-transition painting — computed width reads mid-transition (0px) while inline reaches target. Logic is correct; visual animates only when tab is foregrounded. Use inline-style assertions, not computed, to verify motion in preview.

## Result-page fidelity round (2026-06-15) — user re-QA of persona/salary/result
User flagged 4 result-page defects; all fixed + verified at result frame in preview:
1. **Graph now animates (draw-in)**: chart polylines get `class="cl" data-draw="0|1"`; CSS `.chartwrap .cl{stroke-dasharray:600;stroke-dashoffset:600;transition:stroke-dashoffset 1.4s ease}`. `playResultFx(id,delay)` sets `transitionDelay=draw*0.4s` then `strokeDashoffset='0'` (red `#f3473c` "your path" draws first, green `#009b1a` "with insurance" second). render() calls playResultFx(120ms) for result, (1000ms) for outcome.
2. **4 distinct decision-tile icons** (were all ICON.shield): added ICON.bill/card/shieldCheck/piggy + `coin` glyph (gold `#f5c54a`). decisions order matches Figma: Medical Bills(bill)→Premiums Paid(card)→Claims Settled(shieldCheck,green)→Final Savings(piggy,green,coin). `.dtile .di` = 30px rounded icon holder; `.g` variant = green tint.
3. **Heart → people icon**: social row was ICON.heart (weird). Now `ICON.people` (two-head group glyph) for "90% people took the insurance…". `.social svg{width:34px;height:34px;flex:0 0 34px}` (icon was unconstrained → rendered 115px; capped to 34px).
4. **Coin animation on savings increase**: `coinDrop(el,n)` spawns n gold rupee coins at element center, `@keyframes coinfall` (drop+settle bounce, cubic-bezier back-ease), auto-removed. Fires via playResultFx on any `[data-coin]` el. Tagged: result Final Savings tile (`data-coin="1"`) + outcome savings-up rows (`<span class="pv up" data-coin="1">`).
- VERIFIED at result frame (walked full 9-event journey, picked protective A choices): 2 chart lines drawn (dashoffset 0), 4 distinct icon `d` paths, people SVG + correct social copy, Final Savings tile data-coin set. Screenshots confirm. NOTE coin fall animation freezes in background preview tab (same rAF/transition-freeze gotcha) — logic verified via DOM/inline assertions, visual confirmed by injecting static coins.

## Status (2026-06-12)
HTML prototype COMPLETE + verified: default cautionary path AND protected path (buy personal + add spouse) both correct end-to-end. Dynamic insurance chip + e4/e7 branching verified in preview. Awaiting user sign-off before Phase 2 (Figma screens). OPEN DECISION for user: keep authored savings numbers (current) vs full dynamic savings economy.
