// funnel-engine.jsx — config-driven six-stage funnel engine.
// entry → framing → exercise → results → insight → cta
// Implements the LIFE180 Funnel Architecture: progress always visible,
// "why we ask" tooltips, anonymous-capture-late (email at results), the three
// priority-ordered CTAs, save-and-resume (localStorage), and an agent overlay.
// Exports window.L180FunnelEngine.

const STAGE_KEYS = ["entry", "framing", "exercise", "results", "insight", "cta"];
const STAGE_LABELS = ["Welcome", "The framework", "The exercise", "Your snapshot", "What it means", "Next step"];
const RATE_LABELS = ["Strongly disagree", "Disagree", "Neutral", "Agree", "Strongly agree"];

function L180FunnelEngine({ cfg, tier, mode, onExit, onContinueNext }) {
  const hue = window.l180TierHue(cfg.tierN);
  const lsKey = "l180_funnel_" + cfg.key;

  const [stage, setStage] = React.useState(0);
  const [qi, setQi] = React.useState(0);            // exercise question index
  const [answers, setAnswers] = React.useState({});
  const isTool = cfg.type === "tool";
  const isRanker = cfg.type === "rank";
  const isReflect = cfg.type === "reflect";
  const [ranks, setRanks] = React.useState([]);
  const [reflectSel, setReflectSel] = React.useState([]);
  const [reflectText, setReflectText] = React.useState("");
  const [aiBusy, setAiBusy] = React.useState(false);
  const [toolVals, setToolVals] = React.useState(function () {
    if (!isTool) return {};
    const v = { income: cfg.tool.income.default };
    cfg.tool.categories.forEach((c) => { v[c.key] = c.default; });
    return v;
  });
  const [email, setEmail] = React.useState("");
  const [emailDone, setEmailDone] = React.useState(false);
  const [flags, setFlags] = React.useState({});     // agent: flagged question ids
  const [restored, setRestored] = React.useState(false);
  const [openWhy, setOpenWhy] = React.useState(null);

  // resume
  React.useEffect(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(lsKey) || "null");
      if (saved) {
        if (saved.answers) setAnswers(saved.answers);
        if (saved.toolVals) setToolVals(saved.toolVals);
        if (saved.ranks) setRanks(saved.ranks);
        if (saved.reflectSel) setReflectSel(saved.reflectSel);
        if (typeof saved.reflectText === "string") setReflectText(saved.reflectText);
        setStage(saved.stage || 0); setQi(saved.qi || 0);
      }
    } catch (e) {}
    setRestored(true);
  }, [lsKey]);
  // save
  React.useEffect(() => {
    if (!restored) return;
    try { localStorage.setItem(lsKey, JSON.stringify({ answers, toolVals, ranks, reflectSel, reflectText, stage, qi })); } catch (e) {}
  }, [answers, toolVals, ranks, reflectSel, reflectText, stage, qi, restored, lsKey]);

  const diagResult = React.useMemo(() => (isTool || isRanker || isReflect) ? null : window.l180ScoreFunnel(cfg, answers), [cfg, answers, isTool, isRanker, isReflect]);
  const toolResult = React.useMemo(() => isTool ? window.l180ComputeTool(cfg, toolVals) : null, [cfg, toolVals, isTool]);
  const rankResult = React.useMemo(() => isRanker ? window.l180ComputeRanker(cfg, ranks) : null, [cfg, ranks, isRanker]);
  const reflectResult = React.useMemo(() => isReflect ? window.l180ComputeReflect(cfg, reflectSel) : null, [cfg, reflectSel, isReflect]);
  const result = isTool ? toolResult : isRanker ? rankResult : isReflect ? reflectResult : diagResult;
  const arche = cfg.archetypes[result.archetypeKey];
  const answeredCount = (isTool || isRanker || isReflect) ? 0 : cfg.questions.filter((q) => answers[q.id] != null).length;

  // Claude-assisted reflection helper (Type D)
  async function aiAssist() {
    if (aiBusy) return;
    setAiBusy(true);
    try {
      const txt = await window.claude.complete(cfg.reflect.aiContext);
      if (txt) setReflectText((t) => (t ? t + "\n\n" : "") + txt.trim());
    } catch (e) {
      setReflectText((t) => t || "I tend to believe… and it shows up when I… What I'd rather believe is…");
    }
    setAiBusy(false);
  }

  function setAns(id, v) { setAnswers((a) => ({ ...a, [id]: v })); }
  function resetFunnel() { setAnswers({}); setStage(0); setQi(0); setEmailDone(false); try { localStorage.removeItem(lsKey); } catch (e) {} }

  // ---------- agent overlay ----------
  const agentPrompts = {
    entry: "Set the tone: this isn't a quiz, it's an investigation. Ask what prompted them to look at money now.",
    framing: "Walk them through the four dimensions in your words. Ask which one they suspect runs the show.",
    exercise: "Let them answer honestly — resist guiding. Flag any answer that surprises you for follow-up.",
    results: "Read the profile out loud as a human, not an algorithm. Pause on the dominant dimension.",
    insight: "Connect the result to a decision they're facing right now. Don't pitch — get curious.",
    cta: "Offer the call as the natural next step. The snapshot is theirs regardless.",
  };
  function AgentPanel() {
    if (mode !== "agent") return null;
    return (
      <div className="l180-agentpanel">
        <div className="l180-agentpanel-h">Agent · conversation prompt</div>
        <p>{agentPrompts[STAGE_KEYS[stage]]}</p>
      </div>
    );
  }

  // ---------- stage bodies ----------
  function Body() {
    switch (STAGE_KEYS[stage]) {
      case "entry":
        return (
          <div className="l180-fstep" style={{ textAlign: "center", maxWidth: 640 }}>
            <div className="l180-eyebrow" style={{ color: hue }}>Tier 0{cfg.tierN} · {cfg.streamName}</div>
            <h2 className="l180-fbig">{cfg.streamName}</h2>
            <p className="l180-flead" style={{ margin: "0 auto 18px" }}>{cfg.entry.hook}</p>
            <p className="l180-fnote" style={{ maxWidth: 520, margin: "0 auto 22px" }}>{cfg.entry.why}</p>
            {/* faint 6-step preview */}
            <div className="l180-journey">
              {STAGE_LABELS.map((l, i) => (
                <div key={i} className="l180-journey-step">
                  <span className="l180-journey-dot" style={{ borderColor: hue }}>{i + 1}</span>
                  <span>{l}</span>
                </div>
              ))}
            </div>
            <div className="l180-timepill">{cfg.timeText}</div>
          </div>
        );

      case "framing":
        return (
          <div className="l180-fstep" style={{ maxWidth: 680 }}>
            <div className="l180-eyebrow" style={{ color: hue }}>The framework</div>
            <p className="l180-flead">{cfg.framing.concept}</p>
            <div className="l180-framegrid">
              {cfg.framing.framework.map((f, i) => (
                <div key={i} className="l180-framecard" style={{ borderColor: `color-mix(in oklch, ${f.color} 45%, transparent)` }}>
                  <span className="l180-framedot" style={{ background: f.color }} />
                  <strong>{f.name}</strong>
                  <span className="l180-framedesc">{f.desc}</span>
                </div>
              ))}
            </div>
            <p className="l180-fnote" style={{ marginTop: 16 }}>{cfg.framing.receive}</p>
          </div>
        );

      case "exercise": {
        if (isTool) {
          const r = toolResult;
          const fmt = (n) => "$" + Math.round(n).toLocaleString();
          return (
            <div className="l180-fstep" style={{ maxWidth: 640 }}>
              <div className="l180-eyebrow" style={{ color: hue }}>The calculator</div>
              <h2 className="l180-fbig" style={{ fontSize: 26 }}>Map your monthly money</h2>
              <div className="l180-toolinputs">
                <div className="l180-toolrow l180-toolrow-income">
                  <label>{cfg.tool.income.label}</label>
                  <div className="l180-toolfield">
                    <input type="range" min="0" max={cfg.tool.income.max} step={cfg.tool.income.step}
                           value={toolVals.income} style={{ accentColor: hue }}
                           onChange={(e) => setToolVals((v) => ({ ...v, income: +e.target.value }))} />
                    <span className="l180-toolval" style={{ color: hue }}>{fmt(toolVals.income)}</span>
                  </div>
                </div>
                {cfg.tool.categories.map((c) => (
                  <div key={c.key} className="l180-toolrow">
                    <label>{c.label}{c.working && <span className="l180-workingtag">working</span>}</label>
                    <div className="l180-toolfield">
                      <input type="range" min="0" max={c.max} step={c.step}
                             value={toolVals[c.key]} style={{ accentColor: c.working ? "#24CE62" : hue }}
                             onChange={(e) => setToolVals((v) => ({ ...v, [c.key]: +e.target.value }))} />
                      <span className="l180-toolval">{fmt(toolVals[c.key])}</span>
                    </div>
                  </div>
                ))}
              </div>
              {/* live bar */}
              <div className="l180-cfbar">
                <div style={{ width: r.pct.committed + "%", background: "#1FC9E5" }} title="Committed" />
                <div style={{ width: r.pct.working + "%", background: "#24CE62" }} title="Working" />
                <div style={{ width: r.pct.sleeping + "%", background: "#FBBF24" }} title="Sleeping" />
              </div>
              <div className="l180-cflegend">
                <span><i style={{ background: "#1FC9E5" }} />Committed {fmt(r.committed)}</span>
                <span><i style={{ background: "#24CE62" }} />Working {fmt(r.working)}</span>
                <span><i style={{ background: "#FBBF24" }} />Sleeping {fmt(r.sleeping)}</span>
              </div>
              <div className={"l180-surplusline" + (r.surplus < 0 ? " neg" : "")}>
                Monthly surplus: <strong>{fmt(r.surplus)}</strong>
                {r.surplus < 0 && <span> · spending exceeds income</span>}
              </div>
            </div>
          );
        }
        if (isRanker) {
          const req = cfg.rank.required;
          const itemBy = (k) => cfg.rank.items.find((it) => it.key === k);
          const themeColor = (k) => { const it = itemBy(k); const t = cfg.rank.themes.find((x) => x.key === it.theme); return t ? t.color : hue; };
          const unpicked = cfg.rank.items.filter((it) => !ranks.includes(it.key));
          const full = ranks.length >= req;
          const move = (i, d) => setRanks((r) => { const a = [...r]; const j = i + d; if (j < 0 || j >= a.length) return r; const tmp = a[i]; a[i] = a[j]; a[j] = tmp; return a; });
          const add = (k) => setRanks((r) => (r.length >= req || r.includes(k)) ? r : [...r, k]);
          const remove = (k) => setRanks((r) => r.filter((x) => x !== k));
          return (
            <div className="l180-fstep" style={{ maxWidth: 680 }}>
              <div className="l180-exrow">
                <div className="l180-eyebrow" style={{ color: hue }}>The exercise</div>
                <span className="l180-rankcount" style={{ color: full ? "#24CE62" : "rgba(255,255,255,.76)" }}>{ranks.length} / {req} chosen</span>
              </div>
              <h2 className="l180-fbig" style={{ fontSize: 26 }}>Choose the {req} that matter most — then rank them</h2>
              {ranks.length > 0 && (
                <div className="l180-ranklist">
                  {ranks.map((k, i) => (
                    <div key={k} className="l180-rankitem" style={{ borderColor: `color-mix(in oklch, ${themeColor(k)} 50%, transparent)` }}>
                      <span className="l180-rankpos" style={{ background: themeColor(k) }}>{i + 1}</span>
                      <span className="l180-rankname">{itemBy(k).label}</span>
                      <span className="l180-rankctrls">
                        <button onClick={() => move(i, -1)} disabled={i === 0} aria-label="Up">↑</button>
                        <button onClick={() => move(i, 1)} disabled={i === ranks.length - 1} aria-label="Down">↓</button>
                        <button onClick={() => remove(k)} aria-label="Remove" className="l180-rankx">✕</button>
                      </span>
                    </div>
                  ))}
                </div>
              )}
              {!full && (
                <div className="l180-rankgrid">
                  {unpicked.map((it) => (
                    <button key={it.key} className="l180-rankchip" onClick={() => add(it.key)}
                            style={{ "--c": themeColor(it.key) }}>
                      <i style={{ background: themeColor(it.key) }} />{it.label}
                    </button>
                  ))}
                </div>
              )}
            </div>
          );
        }
        if (isReflect) {
          const sel = reflectSel;
          const toggle = (id) => setReflectSel((s) => s.includes(id) ? s.filter((x) => x !== id) : [...s, id]);
          const hasAI = !!(window.claude && window.claude.complete);
          return (
            <div className="l180-fstep" style={{ maxWidth: 660 }}>
              <div className="l180-eyebrow" style={{ color: hue }}>The reflection</div>
              <h2 className="l180-fbig" style={{ fontSize: 24 }}>{cfg.reflect.selectPrompt}</h2>
              <div className="l180-beliefs">
                {cfg.reflect.beliefs.map((b) => (
                  <button key={b.id} className={"l180-belief" + (sel.includes(b.id) ? " on" : "")} style={{ "--hue": hue }}
                          onClick={() => toggle(b.id)}>
                    <span className="l180-belief-check" />
                    <span>{b.text}</span>
                  </button>
                ))}
              </div>
              <div className="l180-reflectrow">
                <label>{cfg.reflect.prompt}</label>
                {hasAI && (
                  <button className="l180-aibtn" onClick={aiAssist} disabled={aiBusy}>
                    {aiBusy ? "Thinking…" : "✦ Help me start"}
                  </button>
                )}
              </div>
              <textarea className="l180-reflecttext" rows={4} value={reflectText}
                        placeholder="Optional — write as much or as little as you like."
                        onChange={(e) => setReflectText(e.target.value)} />
              <p className="l180-fnote">You can skip the writing — selecting the beliefs is enough to build your inventory.</p>
            </div>
          );
        }
        const q = cfg.questions[qi];
        const chosen = answers[q.id];
        return (
          <div className="l180-fstep" style={{ maxWidth: 660 }}>
            <div className="l180-exrow">
              <div className="l180-eyebrow" style={{ color: hue }}>Question {qi + 1} / {cfg.questions.length}</div>
              <button className="l180-whybtn" onClick={() => setOpenWhy(openWhy === q.id ? null : q.id)}>Why we ask</button>
            </div>
            {openWhy === q.id && <div className="l180-whybox">{q.why}</div>}
            <h2 className="l180-fbig" style={{ fontSize: 27, lineHeight: 1.25 }}>{q.q}</h2>

            {q.type === "rate" && (
              <div className="l180-ratescale">
                {RATE_LABELS.map((lab, i) => {
                  const val = i + 1; const sel = chosen === val;
                  return (
                    <button key={i} className={"l180-ratebtn" + (sel ? " sel" : "")} style={{ "--hue": hue }}
                            onClick={() => { setAns(q.id, val); setTimeout(nextQ, 220); }}>
                      <span className="l180-ratedot" />
                      <span className="l180-ratelab">{lab}</span>
                    </button>
                  );
                })}
              </div>
            )}

            {q.type === "multiple" && (
              <div className="l180-dnaopts" style={{ marginTop: 6 }}>
                {q.opts.map((o, i) => (
                  <button key={i} className={"l180-dnaopt" + (chosen === i ? " sel" : "")} style={{ "--hue": hue }}
                          onClick={() => { setAns(q.id, i); setTimeout(nextQ, 220); }}>
                    <span className="l180-dnaopt-dot" />
                    <span>{o.t}</span>
                  </button>
                ))}
              </div>
            )}

            {mode === "agent" && (
              <button className={"l180-flagbtn" + (flags[q.id] ? " on" : "")}
                      onClick={() => setFlags((f) => ({ ...f, [q.id]: !f[q.id] }))}>
                {flags[q.id] ? "✓ Flagged for follow-up" : "⚑ Flag this answer"}
              </button>
            )}
          </div>
        );
      }

      case "results":
        if (isTool) {
          const r = toolResult;
          const fmt = (n) => "$" + Math.round(n).toLocaleString();
          return (
            <div className="l180-fstep" style={{ maxWidth: 640, textAlign: "center" }}>
              <div className="l180-eyebrow" style={{ color: hue }}>Your Cash Flow Snapshot</div>
              <h2 className="l180-fbig" style={{ fontSize: 30 }}>{arche.headline}</h2>
              <div className="l180-cfstats">
                <div className="l180-cfstat">
                  <span className="l180-cfstat-num" style={{ color: r.surplus < 0 ? "#DC2626" : "#24CE62" }}>{fmt(r.surplus)}</span>
                  <span className="l180-cfstat-lab">Monthly surplus</span>
                </div>
                <div className="l180-cfstat">
                  <span className="l180-cfstat-num" style={{ color: "#24CE62" }}>{fmt(r.working)}</span>
                  <span className="l180-cfstat-lab">Working / mo</span>
                </div>
                <div className="l180-cfstat">
                  <span className="l180-cfstat-num" style={{ color: "#FBBF24" }}>{fmt(r.sleeping)}</span>
                  <span className="l180-cfstat-lab">Sleeping / mo</span>
                </div>
              </div>
              {r.sleeping > 0 && (
                <p className="l180-fnote" style={{ maxWidth: 460, margin: "0 auto 6px" }}>
                  At a modest 6%, the {fmt(r.sleeping)}/mo currently sleeping could be ~{fmt(r.sleeping * 12 * 0.06)} of growth you're leaving on the table each year.
                </p>
              )}
              <ul className="l180-observe">
                {arche.observations.map((o, i) => <li key={i}>{o}</li>)}
              </ul>
              <div className="l180-pdfteaser" style={{ "--hue": hue, marginTop: 22 }}>
                <div className="l180-pdficon">PDF</div>
                {emailDone ? (
                  <div><strong>{cfg.pdfName} — on its way ✓</strong><span>Sent to {email}. Yours to keep.</span></div>
                ) : (
                  <div style={{ flex: 1 }}>
                    <strong>Get your {cfg.pdfName}</strong>
                    <div className="l180-emailrow">
                      <input type="email" placeholder="you@email.com" value={email} onChange={(e) => setEmail(e.target.value)} />
                      <button style={{ "--hue": hue }} disabled={!/.+@.+\..+/.test(email)} onClick={() => setEmailDone(true)}>Email it</button>
                    </div>
                  </div>
                )}
              </div>
            </div>
          );
        }
        if (isRanker) {
          const r = rankResult;
          const itemBy = (k) => cfg.rank.items.find((it) => it.key === k);
          const themeOf = (k) => { const it = itemBy(k); return cfg.rank.themes.find((x) => x.key === it.theme); };
          const themeColor = (k) => { const t = themeOf(k); return t ? t.color : hue; };
          const maxT = Math.max.apply(null, Object.values(r.themeScores)) || 1;
          return (
            <div className="l180-fstep" style={{ maxWidth: 600, textAlign: "center" }}>
              <div className="l180-eyebrow" style={{ color: hue }}>Your Values Hierarchy</div>
              <h2 className="l180-fbig" style={{ fontSize: 30 }}>{arche.headline}</h2>
              <div className="l180-hierarchy">
                {r.ranks.map((k, i) => (
                  <div key={k} className="l180-hieritem" style={{ borderLeftColor: themeColor(k) }}>
                    <span className="l180-hierpos" style={{ background: themeColor(k) }}>{i + 1}</span>
                    <span className="l180-hiername">{itemBy(k).label}</span>
                    <span className="l180-hiertheme" style={{ color: themeColor(k) }}>{themeOf(k).name}</span>
                  </div>
                ))}
              </div>
              <div className="l180-dnabars" style={{ maxWidth: 420, margin: "20px auto 6px" }}>
                {cfg.rank.themes.map((t) => (
                  <div key={t.key} className="l180-dnabar">
                    <span className="l180-dnabar-name">{t.name}</span>
                    <div className="l180-dnabar-track">
                      <div className="l180-dnabar-fill" style={{ width: `${(r.themeScores[t.key] / maxT) * 100}%`, background: t.color }} />
                    </div>
                  </div>
                ))}
              </div>
              <ul className="l180-observe">
                {arche.observations.map((o, i) => <li key={i}>{o}</li>)}
              </ul>
              <div className="l180-pdfteaser" style={{ "--hue": hue, marginTop: 22 }}>
                <div className="l180-pdficon">PDF</div>
                {emailDone ? (
                  <div><strong>{cfg.pdfName} — on its way ✓</strong><span>Sent to {email}. Yours to keep.</span></div>
                ) : (
                  <div style={{ flex: 1 }}>
                    <strong>Get your {cfg.pdfName}</strong>
                    <div className="l180-emailrow">
                      <input type="email" placeholder="you@email.com" value={email} onChange={(e) => setEmail(e.target.value)} />
                      <button style={{ "--hue": hue }} disabled={!/.+@.+\..+/.test(email)} onClick={() => setEmailDone(true)}>Email it</button>
                    </div>
                  </div>
                )}
              </div>
            </div>
          );
        }
        if (isReflect) {
          const r = reflectResult;
          const picked = cfg.reflect.beliefs.filter((b) => r.selected.includes(b.id));
          return (
            <div className="l180-fstep" style={{ maxWidth: 620, textAlign: "center" }}>
              <div className="l180-eyebrow" style={{ color: hue }}>Your Belief Inventory</div>
              <h2 className="l180-fbig" style={{ fontSize: 30 }}>{arche.headline}</h2>
              {picked.length > 0 ? (
                <div className="l180-reframes">
                  {picked.map((b) => (
                    <div key={b.id} className="l180-reframe">
                      <div className="l180-reframe-belief">{b.text}</div>
                      <div className="l180-reframe-arrow" style={{ color: hue }}>→ reframed</div>
                      <div className="l180-reframe-new">{b.reframe}</div>
                    </div>
                  ))}
                </div>
              ) : (
                <p className="l180-flead" style={{ maxWidth: 480, margin: "8px auto" }}>You didn't flag any of the common limiting beliefs — a genuinely clear starting point.</p>
              )}
              {reflectText.trim() && (
                <div className="l180-reflectback">
                  <span className="l180-eyebrow" style={{ color: hue, fontSize: 9 }}>In your words</span>
                  <p>“{reflectText.trim()}”</p>
                </div>
              )}
              <ul className="l180-observe">
                {arche.observations.map((o, i) => <li key={i}>{o}</li>)}
              </ul>
              <div className="l180-pdfteaser" style={{ "--hue": hue, marginTop: 22 }}>
                <div className="l180-pdficon">PDF</div>
                {emailDone ? (
                  <div><strong>{cfg.pdfName} — on its way ✓</strong><span>Sent to {email}. Yours to keep.</span></div>
                ) : (
                  <div style={{ flex: 1 }}>
                    <strong>Get your {cfg.pdfName}</strong>
                    <div className="l180-emailrow">
                      <input type="email" placeholder="you@email.com" value={email} onChange={(e) => setEmail(e.target.value)} />
                      <button style={{ "--hue": hue }} disabled={!/.+@.+\..+/.test(email)} onClick={() => setEmailDone(true)}>Email it</button>
                    </div>
                  </div>
                )}
              </div>
            </div>
          );
        }
        return (
          <div className="l180-fstep" style={{ maxWidth: 640, textAlign: "center" }}>
            <div className="l180-eyebrow" style={{ color: hue }}>Your Money Story</div>
            <h2 className="l180-fbig" style={{ fontSize: 30 }}>{arche.headline}</h2>
            {/* dimension bars */}
            <div className="l180-dnabars" style={{ maxWidth: 460, margin: "20px auto" }}>
              {cfg.dimensions.map((d) => (
                <div key={d.key} className="l180-dnabar">
                  <span className="l180-dnabar-name">{d.name}</span>
                  <div className="l180-dnabar-track">
                    <div className="l180-dnabar-fill" style={{ width: `${result.dimScores[d.key]}%`, background: d.color }} />
                    <div className="l180-bench" style={{ left: `${d.benchmark}%` }} title={`Most clients ≈ ${d.benchmark}`} />
                  </div>
                  <span className="l180-dnabar-val" style={{ color: d.color }}>{result.dimScores[d.key]}</span>
                </div>
              ))}
            </div>
            <div className="l180-bench-key"><span className="l180-bench-mark" /> typical client</div>
            <ul className="l180-observe">
              {arche.observations.map((o, i) => <li key={i}>{o}</li>)}
            </ul>
            {/* anonymous-capture-late: email gate for the PDF */}
            <div className="l180-pdfteaser" style={{ "--hue": hue, marginTop: 22 }}>
              <div className="l180-pdficon">PDF</div>
              {emailDone ? (
                <div><strong>{cfg.pdfName} — on its way ✓</strong><span>Sent to {email}. Yours to keep.</span></div>
              ) : (
                <div style={{ flex: 1 }}>
                  <strong>Get your {cfg.pdfName}</strong>
                  <div className="l180-emailrow">
                    <input type="email" placeholder="you@email.com" value={email} onChange={(e) => setEmail(e.target.value)} />
                    <button style={{ "--hue": hue }} disabled={!/.+@.+\..+/.test(email)} onClick={() => setEmailDone(true)}>Email it</button>
                  </div>
                </div>
              )}
            </div>
          </div>
        );

      case "insight":
        return (
          <div className="l180-fstep" style={{ maxWidth: 640 }}>
            <div className="l180-eyebrow" style={{ color: hue }}>What it means</div>
            <p className="l180-flead">{arche.insight}</p>
            <div className="l180-implications">
              {arche.implications.map((im, i) => (
                <div key={i} className="l180-impcard"><span style={{ color: hue }}>→</span> {im}</div>
              ))}
            </div>
            <div className="l180-bridge" style={{ borderColor: `color-mix(in oklch, ${hue} 40%, transparent)` }}>
              <span className="l180-eyebrow" style={{ color: hue, fontSize: 9 }}>The conversation worth having</span>
              <p>{arche.agentBridge}</p>
            </div>
          </div>
        );

      case "cta":
        return (
          <div className="l180-fstep" style={{ maxWidth: 540, textAlign: "center" }}>
            <div className="l180-eyebrow" style={{ color: hue }}>Next step</div>
            <h2 className="l180-fbig">Turn your story into a plan.</h2>
            <div className="l180-ctastack">
              <button className="l180-bigcta" style={{ "--hue": hue }}>Talk to an advisor about your results →</button>
              {onContinueNext && (
                <button className="l180-ghostbtn l180-ctawide" onClick={onContinueNext}>Continue the process · next assessment in this tier</button>
              )}
              <button className="l180-textcta" onClick={() => setStage(3)}>
                {emailDone ? "✓ Snapshot emailed" : "Email me my full snapshot"}
              </button>
            </div>
          </div>
        );
      default: return null;
    }
  }

  // ---------- navigation ----------
  function nextQ() {
    if (qi < cfg.questions.length - 1) setQi((i) => i + 1);
    else setStage(3); // -> results
  }
  function advance() {
    if (STAGE_KEYS[stage] === "exercise") { setStage(3); return; }
    setStage((s) => Math.min(STAGE_KEYS.length - 1, s + 1));
  }
  function back() {
    if (STAGE_KEYS[stage] === "exercise" && qi > 0) { setQi((i) => i - 1); return; }
    setStage((s) => Math.max(0, s - 1));
  }

  const showNext = !(STAGE_KEYS[stage] === "exercise" && !isTool && !isRanker && !isReflect); // diagnostic exercise auto-advances on pick
  const isLast = stage === STAGE_KEYS.length - 1;
  const rankerLocked = isRanker && STAGE_KEYS[stage] === "exercise" && ranks.length !== cfg.rank.required;

  return (
    <div className="l180-funnel" style={{ background: `radial-gradient(120% 80% at 50% -5%, #1c1c1e, ${L180_BRAND.navy} 60%, ${L180_BRAND.ink})` }}>
      <div className="l180-funnel-top">
        <button className="l180-back" onClick={onExit}>← Back to pyramid</button>
        <div className="l180-progress">
          {STAGE_KEYS.map((_, i) => (
            <span key={i} className="l180-progdot" style={{ background: i <= stage ? hue : "rgba(255,255,255,.26)", width: i === stage ? 26 : 8 }} />
          ))}
        </div>
        <div className="l180-stagelabel">{STAGE_LABELS[stage]}{answeredCount > 0 && stage < 3 ? "  ·  resume saved" : ""}</div>
      </div>

      <div className="l180-funnel-body">
        <div key={stage + "-" + qi} style={{ width: "100%", display: "flex", justifyContent: "center" }}>
          <Body />
        </div>
      </div>

      {mode === "agent" && <div className="l180-funnel-agentbar"><AgentPanel /></div>}

      <div className="l180-funnel-nav">
        {(stage > 0) ? <button className="l180-ghostbtn" onClick={back}>Back</button> : <button className="l180-ghostbtn" onClick={resetFunnel}>Restart</button>}
        {showNext ? (
          isLast
            ? <button className="l180-ghostbtn" onClick={onExit}>Done · return to pyramid</button>
            : <button className="l180-solidbtn" style={{ "--hue": hue, opacity: rankerLocked ? .4 : 1, cursor: rankerLocked ? "not-allowed" : "pointer" }} disabled={rankerLocked} onClick={advance}>
                {STAGE_KEYS[stage] === "entry" ? "Let's begin" : STAGE_KEYS[stage] === "framing" ? "Continue" : STAGE_KEYS[stage] === "exercise" ? (isRanker ? "Reveal my hierarchy" : isReflect ? "Build my inventory" : "See my snapshot") : STAGE_KEYS[stage] === "results" ? "What this means" : "Continue"} →
              </button>
        ) : <span className="l180-fnote" style={{ alignSelf: "center" }}>Pick the answer that fits</span>}
      </div>
    </div>
  );
}

window.L180FunnelEngine = L180FunnelEngine;
