/* ===============================================================
   AGENTICS.CREDIT — CAMPAIGN · ONBOARDING MODULE
   Split out of campaign.jsx on 2026-07-03. Contains:
     - EmailVerify / TelegramVerify — verify components used on step 1
     - CampaignOnboarding — the 3-page signup wizard
     - CampaignLogin — the returning-user login modal
     - IdentityMintBadge — mints participant KYA on step 2
     - AgentDesignPanel + ProposalRow + fmtBias — design chat on step 3
     - OnboardingLaunchSequence — the closing animation before landing
       on the dashboard
   Load AFTER campaign.jsx so apiPost / acGet / acSend / TurnstileWidget
   are already defined.
   =============================================================== */

function EmailVerify({ email, onEmail, verified, onVerified, onSeed, username, captchaToken }) {
  const [phase, setPhase] = useState("entry");    // entry | code | done
  const [code, setCode] = useState("");
  const [sending, setSending] = useState(false);
  const [error, setError] = useState("");
  const [cooldown, setCooldown] = useState(0);
  const stubCode = useRef(null);
  const toast = useToast && useToast();

  useEffect(() => {
    if (cooldown <= 0) return;
    const t = setTimeout(() => setCooldown(c => c - 1), 1000);
    return () => clearTimeout(t);
  }, [cooldown]);

  const valid = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.trim());

  // Turnstile token comes from the parent's captchaToken prop (React state
  // set by the widget callback). Fall back to window.AC_LAST_CAPTCHA for
  // any code path that still uses it. Whichever source has it wins.
  const effectiveToken = captchaToken || (typeof window !== "undefined" && window.AC_LAST_CAPTCHA) || null;
  const captchaReady   = !!effectiveToken;

  // Scroll the Turnstile box into view and pulse it — used when a user tries
  // to send the code before ticking the security check further down the form.
  function nudgeCaptcha() {
    if (typeof document === "undefined") return;
    const el = document.querySelector(".cg-turnstile");
    if (!el) return;
    el.scrollIntoView({ behavior: "smooth", block: "center" });
    el.classList.add("cg-nudge");
    setTimeout(() => el.classList.remove("cg-nudge"), 1800);
  }

  async function sendCode() {
    if (!valid || sending) return;
    if (!captchaReady) {
      // Users kept getting stuck here: the code never sends until the Turnstile
      // box (step 06, further down the form) is ticked, but nothing told them
      // so — they'd assume it was broken and leave. Give a clear pop-up + inline
      // prompt and scroll the security check into view.
      setError("Please complete the security check below, then tap SEND CODE.");
      toast && toast.push({
        title: "One quick step first",
        meta: "Tick the “security check” box below to get your code",
        duration: 5000,
      });
      nudgeCaptcha();
      return;
    }
    setSending(true); setError("");
    try {
      const res = await apiPost("/auth/email/start", { email: email.trim(), captcha_token: effectiveToken });
      if (res.__stub) { stubCode.current = "424242"; }   // demo code in prototype
      setPhase("code"); setCooldown(30);
      toast && toast.push({ title: "Code sent", meta: res.__stub ? "Prototype code: 424242" : "Check your inbox", duration: 4500 });
    } catch (e) {
      // Surface the actual backend detail so users see something useful
      // ("missing captcha token", "too many verification requests…", etc)
      const msg = (e && e.message) || "";
      setError(msg && msg !== "request_failed" ? msg : "Couldn't send code. Try again.");
    }
    setSending(false);
  }

  const [verifying, setVerifying] = useState(false);
  async function verify() {
    if (verifying) return;                  // guard against double-click
    setError(""); setVerifying(true);
    try {
      // Username is REQUIRED by the backend for new signups (existing-email
      // logins ignore it). Pass through whatever the parent collected.
      const body = {
        email:              email.trim(),
        code:               code.trim(),
        referral_code:      refCodeFromURL(),
        // Skip the boilerplate preset agent — the wizard's design chat on
        // step 3 creates a personalised agent via /agents/design/commit
        // once the user is happy with the strategy.
        skip_default_agent: true,
        // Partner-event promo (e.g. /coinbase). Backend maps CODE → seed
        // amount via SIGNUP_PROMO_CODES env var.
        promo_code:         promoCodeFromURL(),
      };
      if (username && username.trim()) body.username = username.trim().toLowerCase();
      const res = await apiPost("/auth/email/verify", body);
      // Backend returns `jwt`; the legacy stub returned `token`. Accept either.
      const jwt = res.jwt || res.token || null;
      const ok = res.__stub ? code.trim() === stubCode.current : !!jwt;
      if (ok) {
        if (jwt) storeAuth(jwt);
        // Surface the actual seed the backend assigned (promo / referral /
        // standard) so the confirmation screen shows the real number instead
        // of a hardcoded one. Backend returns it as `paper_balance`.
        const seedGot = (res.paper_balance != null ? res.paper_balance
                       : (res.participant && res.participant.initial_seed));
        if (seedGot != null && typeof onSeed === "function") onSeed(Number(seedGot));
        setPhase("done"); onVerified(true);
      }
      else setError("Invalid code. Check and try again.");
    } catch (e) {
      // Surface the backend's specific error when possible (e.g. "username required")
      setError(e && e.message && e.message !== "request_failed" ? e.message : "Verification failed. Try again.");
    }
    finally { setVerifying(false); }
  }

  if (verified || phase === "done") {
    return (
      <div className="cg-verify-done">
        <span className="cg-verify-badge">✓ VERIFIED</span>
        <span className="cg-verify-email">{email}</span>
      </div>
    );
  }

  if (phase === "code") {
    return (
      <div className="cg-verify-code">
        <div className="cg-verify-sent">Code sent to <strong>{email}</strong></div>
        <div className="cg-connect-row">
          <input
            value={code}
            onChange={e => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
            placeholder="6-digit code" inputMode="numeric" maxLength={6}
            style={{ letterSpacing: "4px", fontFamily: "'DM Mono', monospace" }}
          />
          <button className="cg-connect-btn" disabled={code.length !== 6 || verifying} onClick={verify}>
            {verifying ? "VERIFYING…" : "VERIFY"}
          </button>
        </div>
        {error && <div className="cg-verify-err">{error}</div>}
        <button className="cg-verify-resend" disabled={cooldown > 0} onClick={sendCode}>
          {cooldown > 0 ? `Resend code in ${cooldown}s` : "Resend code"}
        </button>
      </div>
    );
  }

  return (
    <div>
      <div className="cg-connect-row">
        <input type="email" value={email} onChange={e => onEmail(e.target.value)} placeholder="you@example.com" />
        <button
          className="cg-connect-btn"
          disabled={!valid || sending}
          onClick={sendCode}
          title={!captchaReady ? "Complete the security check below first" : undefined}
        >
          {sending ? "SENDING…" : "SEND CODE"}
        </button>
      </div>
      {valid && !captchaReady && (
        <div className="cg-captcha-hint" onClick={nudgeCaptcha}>
          ↓ One step left — tick the security check below to enable sending.
        </div>
      )}
      {error && <div style={{ marginTop: 6, color: "#e97", fontSize: 12, fontFamily: "'DM Mono', monospace" }}>// {error}</div>}
    </div>
  );
}

/* ===============================================================
   TELEGRAM VERIFICATION — official Login Widget (Brief §5.2 Path B)
   Set window.AC_TG_BOT = "YourBotUsername" (without @) to enable the
   real widget. Telegram returns a signed `user` payload which we POST
   in full to /auth/telegram/widget (+ referral_code from URL). The
   backend verifies the HMAC with the bot token and returns { token }.
   =============================================================== */
function TelegramVerify({ verified, tgUser, onVerified }) {
  const ref = useRef(null);
  const bot = (typeof window !== "undefined" && window.AC_TG_BOT) || null;
  const toast = useToast && useToast();

  useEffect(() => {
    if (verified || !bot || !ref.current) return;
    // expose the global callback Telegram calls back into
    window.onTelegramAuth = async (u) => {
      // Signup flow: if we already have a JWT (from email verify) we
      // must LINK this Telegram to that account, not silently swap into
      // whatever account this Telegram used to belong to. /auth/telegram
      // /link rejects with 409 when the Telegram is already linked to
      // a different participant.
      const jwt = _acJwt();
      try {
        if (jwt) {
          await acSend("/auth/telegram/link", u);
          // Successfully linked to the JWT-authed participant. No new
          // JWT to store — the existing one is still valid.
        } else {
          // Pure Telegram login (no email flow) — legacy /widget path.
          const res = await apiPost("/auth/telegram/widget", { ...u, referral_code: refCodeFromURL(), promo_code: promoCodeFromURL() });
          const newJwt = (res && (res.jwt || res.token)) || null;
          if (newJwt) storeAuth(newJwt);
        }
      } catch (e) {
        // Show the user what went wrong instead of silently succeeding
        // into the wrong account.
        const msg = (e && e.detail) || (e && e.message) || "Telegram link failed";
        toast && toast.push({ title: "Couldn't link Telegram", meta: msg, duration: 6000 });
        return;   // don't mark verified — the user needs to see the failure
      }
      onVerified({ id: u.id, username: u.username || (u.first_name || "telegram") });
      toast && toast.push({ title: "Telegram connected", meta: "@" + (u.username || u.first_name), duration: 3500 });
    };
    const s = document.createElement("script");
    s.src = "https://telegram.org/js/telegram-widget.js?22";
    s.async = true;
    s.setAttribute("data-telegram-login", bot);
    s.setAttribute("data-size", "medium");
    s.setAttribute("data-radius", "6");
    s.setAttribute("data-onauth", "onTelegramAuth(user)");
    s.setAttribute("data-request-access", "write");
    ref.current.innerHTML = "";
    ref.current.appendChild(s);
  }, [bot, verified]);

  if (verified) {
    return (
      <div className="cg-verify-done">
        <span className="cg-verify-badge">✓ CONNECTED</span>
        <span className="cg-verify-email">@{tgUser?.username || "telegram"}</span>
      </div>
    );
  }

  // Real widget when a bot username is configured; otherwise a clear stub.
  if (bot) return <div ref={ref} className="cg-tg-widget" />;

  return (
    <div className="cg-connect-row">
      <input disabled placeholder="Telegram Login Widget loads here at launch" />
      <button
        className="cg-connect-btn"
        onClick={() => { onVerified({ id: "demo", username: "you" }); toast && toast.push({ title: "Telegram connected (demo)", duration: 3000 }); }}
      >
        CONNECT
      </button>
    </div>
  );
}

function CampaignOnboarding({ onComplete, onCancel }) {
  // page: 1 = welcome+form, 2 = account confirmation, 3 = describe+launch agent
  const [page, setPage] = useState(1);
  const [form, setForm] = useState({
    username: "", email: "", telegram: "",
    emailVerified: false, telegramVerified: false, telegramUser: null,
    experience: "", goals: "",
    agentName: "", description: "",
    risk: "Moderate", markets: ["BTC", "ETH"],
    // Pine Script path — when set, the launch sequence sends this as the
    // primary agent's strategy_state (overriding the preset assigned at
    // email-verification time).
    pineStrategy: null, pineWarnings: [],
  });
  const [launching, setLaunching] = useState(false);
  const [captchaToken, setCaptchaToken] = useState(null);
  // Builder mode on page 3 — "describe" (plain English) vs "pine" (paste script).
  const [builderMode, setBuilderMode] = useState("describe");

  // Pull ?ref=CODE from URL once + resolve the referrer for the "you've been
  // invited" banner on step 1.
  const refCode = (() => {
    try {
      const m = new URLSearchParams(window.location.search).get("ref")
             || new URLSearchParams(window.location.search).get("referral_code");
      return m ? m.toUpperCase() : null;
    } catch (_) { return null; }
  })();
  const [referrer, setReferrer] = useState(null);
  useEffect(() => {
    if (!refCode) return;
    const base = ((typeof window !== "undefined" && window.AC_API) || "").replace(/\/$/, "");
    if (!base) return;
    let cancelled = false;
    (async () => {
      try {
        const r = await fetch(`${base}/referrals/resolve/${encodeURIComponent(refCode)}`,
          { credentials: "omit" });
        if (cancelled || !r.ok) return;
        setReferrer(await r.json());
      } catch (_) {}
    })();
    return () => { cancelled = true; };
  }, [refCode]);
  const isInvited = referrer && referrer.valid;

  // Partner-event promo code (path-based: /coinbase → "COINBASE").
  // Maps to a display label + seed amount below. When present, shows a
  // dedicated banner that overrides the referral banner.
  const promoCode = promoCodeFromURL();
  const PROMO_UI = {
    COINBASE: { label: "COINBASE",       seed: 100000, welcome: "Coinbase Demo" },
    DEMO:     { label: "PARTNER · DEMO", seed:  50000, welcome: "Partner Demo" },
  };
  const promo = promoCode && PROMO_UI[promoCode] ? { code: promoCode, ...PROMO_UI[promoCode] } : null;

  const update = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const OnbLogo = () => (
    <div className="cg-onb-logo">
      <AgenticsLogo size={26} />
    </div>
  );

  const canPage1 =
    form.username.trim().length >= 2 &&
    form.emailVerified &&
    form.telegramVerified &&
    form.experience.trim().length >= 10 &&
    form.goals.trim().length >= 15;
  // Either builder path must produce something launchable. Pine wins if
  // a translated strategy is sitting on the form; otherwise we require
  // the agent_name + description path.
  const canLaunchDescribe = form.agentName.trim().length >= 2 && form.description.trim().length >= 20;
  const canLaunchPine     = form.agentName.trim().length >= 2 && !!form.pineStrategy;
  const canLaunch = builderMode === "pine" ? canLaunchPine : canLaunchDescribe;

  if (launching) return <OnboardingLaunchSequence form={form} onDone={() => onComplete({ ...CAMPAIGN_DEFAULT_USER, ...form, captchaToken })} />;

  /* ---------- PAGE 1 — WELCOME + PROFILE ---------- */
  if (page === 1) {
    return (
      <div className="cg-modal-backdrop">
        <div className="cg-modal cg-onb">
          <OnbLogo />
          <div className="cg-modal-head">
            <div>
              <div className="cg-modal-eyebrow">
                {promo
                  ? <>// {promo.label} · STEP_01 / 03</>
                  : isInvited
                  ? <>// INVITE_FROM <span className="accent">@{referrer.username}</span> · STEP_01 / 03</>
                  : <>// WELCOME_TO_AGENTICS · STEP_01 / 03</>}
              </div>
              <div className="cg-modal-title">
                {promo
                  ? `${promo.welcome} — claim $${promo.seed.toLocaleString()}`
                  : isInvited ? `You've been invited — claim $10,000` : "The first agentic prop shop"}
              </div>
            </div>
            <button className="cg-modal-x" onClick={onCancel}>×</button>
          </div>

          <div className="cg-modal-body">
            {promo && (
              <div style={{
                padding: "14px 16px",
                marginBottom: 18,
                background: "linear-gradient(135deg, rgba(29,158,117,0.22) 0%, rgba(29,158,117,0.08) 100%)",
                border: "1px solid var(--accent)",
                borderRadius: 6,
                fontFamily: "JetBrains Mono, monospace",
                fontSize: 13,
                lineHeight: 1.7,
              }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4 }}>
                  <span><strong className="accent">{promo.welcome} attendee</strong> — welcome</span>
                  <span className="accent" style={{ fontWeight: 700, fontSize: 14 }}>+${promo.seed.toLocaleString()} PAPER</span>
                </div>
                <div className="muted" style={{ fontSize: 11 }}>
                  You'll design your agent on step 3 — it launches with the full ${promo.seed.toLocaleString()} to trade.
                </div>
              </div>
            )}
            {!promo && isInvited && (
              <div style={{
                padding: "14px 16px",
                marginBottom: 18,
                background: "linear-gradient(135deg, rgba(29,158,117,0.18) 0%, rgba(29,158,117,0.08) 100%)",
                border: "1px solid var(--accent)",
                borderRadius: 6,
                fontFamily: "JetBrains Mono, monospace",
                fontSize: 13,
                lineHeight: 1.7,
              }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4 }}>
                  <span><strong className="accent">@{referrer.username}</strong> invited you</span>
                  <span className="accent" style={{ fontWeight: 700, fontSize: 14 }}>+$10,000 BONUS</span>
                </div>
                <div className="muted" style={{ fontSize: 11 }}>
                  Direct signups start at $1,000. You're starting with 10×.
                </div>
              </div>
            )}
            <p className="cg-onb-welcome">
              {promo
                ? <>Sign up below — once your email is verified you'll design your agent with <span className="accent">${promo.seed.toLocaleString()}</span> in paper capital ready to trade.</>
                : isInvited
                ? <>Sign up below — once you verify your email, your agent will be funded with the full <span className="accent">$10,000</span> automatically.</>
                : <>You're about to automate and scale your trading using the most advanced risk management and AI models. <span className="accent">Let's get started.</span></>}
            </p>

            <div className="cg-field">
              <label>01 · PUBLIC_NAME <span className="opt">— shown across the platform</span></label>
              <input value={form.username} onChange={e => update("username", e.target.value)} placeholder="alpha47" />
            </div>

            <div className="cg-field">
              <label>02 · EMAIL <span className="opt">— we'll send a 6-digit verification code</span></label>
              <EmailVerify
                email={form.email}
                onEmail={v => update("email", v)}
                verified={form.emailVerified}
                onVerified={v => update("emailVerified", v)}
                onSeed={s => update("seed", s)}
                username={form.username}
                captchaToken={captchaToken}
              />
            </div>

            <div className="cg-field">
              <label>03 · TELEGRAM <span className="opt">— alerts on every trade opened & closed (required)</span></label>
              {!form.emailVerified ? (
                <div style={{
                  padding: "12px 14px",
                  border: "1px dashed var(--border)",
                  background: "var(--bg-2)",
                  borderRadius: 6,
                  fontFamily: "'DM Mono', monospace",
                  fontSize: 12,
                  color: "var(--cg-muted)",
                  lineHeight: 1.6,
                }}>
                  // LOCKED — verify your email above first, then this
                  // widget lets you link Telegram to that account.
                </div>
              ) : (
                <TelegramVerify
                  verified={form.telegramVerified}
                  tgUser={form.telegramUser}
                  onVerified={u => setForm(f => ({ ...f, telegramVerified: true, telegramUser: u, telegram: "@" + (u.username || "telegram") }))}
                />
              )}
            </div>

            <div className="cg-field">
              <label>04 · TRADING_EXPERIENCE <span className="opt">— tell us a bit about yourself</span></label>
              <textarea
                value={form.experience}
                onChange={e => update("experience", e.target.value)}
                placeholder='"5 years trading crypto perps, mostly momentum and breakout setups. Comfortable with leverage but hate big drawdowns."'
                rows={3}
              />
            </div>

            <div className="cg-field">
              <label>05 · YOUR_GOALS <span className="opt">— what to trade, daily target, max loss per trade. More detail = better agent.</span></label>
              <textarea
                value={form.goals}
                onChange={e => update("goals", e.target.value)}
                placeholder='"Trade BTC, ETH and SOL perps. Target ~2% account growth per day. Never lose more than 1.5% on a single trade. Avoid trading during low-volume hours."'
                rows={4}
              />
            </div>

            <div className="cg-field">
              <label>06 · VERIFY <span className="opt">— quick security check</span></label>
              <TurnstileWidget onToken={setCaptchaToken} />
            </div>

            <button className="cg-cta-primary" disabled={!canPage1 || !captchaToken} onClick={() => setPage(2)}>
              CREATE MY ACCOUNT →
            </button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- PAGE 2 — ACCOUNT CONFIRMATION ---------- */
  if (page === 2) {
    // Show the real seed the account received (captured from the verify
    // response). Fall back to the promo amount, then the standard grant.
    const seedAmount = form.seed != null ? form.seed : (promo ? promo.seed : 10000);
    const seedLabel  = Number(seedAmount).toLocaleString();
    return (
      <div className="cg-modal-backdrop">
        <div className="cg-modal cg-onb">
          <OnbLogo />
          <div className="cg-modal-head">
            <div>
              <div className="cg-modal-eyebrow">// ACCOUNT_CREATED · STEP_02 / 03</div>
              <div className="cg-modal-title">Awesome — you're in, {form.username || "trader"}.</div>
            </div>
          </div>

          <div className="cg-modal-body">
            <p className="cg-onb-welcome">
              Your account is set up. You can edit any of this later in the <span className="accent">Profile</span> area.
              Here's what's next:
            </p>

            <div className="cg-onb-card highlight">
              <div className="cg-onb-card-big">{seedLabel} <span className="accent">$CREDIT</span></div>
              <div className="cg-onb-card-sub">
                We start you with {seedLabel} $CREDIT. For the purposes of this platform it's pegged to <span className="accent">$1</span> each.
              </div>
            </div>

            <div className="cg-onb-points">
              <div className="cg-onb-point">
                <span className="cg-onb-num">01</span>
                <div>
                  <div className="cg-onb-point-h">90 days sets your credit line</div>
                  <p>At the end of 90 days, the balance in your $CREDIT account is the maximum you can borrow from the vaults.</p>
                </div>
              </div>
              <div className="cg-onb-point">
                <span className="cg-onb-num">02</span>
                <div>
                  <div className="cg-onb-point-h">Coach your agent</div>
                  <p>Monitor what your agent is doing and prompt it to do better. Give it instructions and feedback — it learns and improves. You're training your model. Those who put in the time get a better result.</p>
                </div>
              </div>
              <div className="cg-onb-point">
                <span className="cg-onb-num">03</span>
                <div>
                  <div className="cg-onb-point-h">We're building this together</div>
                  <p>This product is the first of its kind. Nothing like this has ever existed before — we are learning right alongside you.</p>
                </div>
              </div>
            </div>

            <div style={{ margin: "18px 0" }}>
              <IdentityMintBadge />
            </div>

            <div className="cg-modal-actions">
              <button className="cg-cta-ghost" onClick={() => setPage(1)}>← Back</button>
              <button className="cg-cta-primary" onClick={() => setPage(3)}>DESIGN MY AGENT →</button>
            </div>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- PAGE 3 — DESIGN CHAT + LAUNCH AGENT ---------- */
  // Wizard defaults live inside the PULSE-derivative family. Promo signups
  // land on BLITZ for maximum on-stage trading activity; other users default
  // to the risk-appropriate PULSE variant based on their Page 1 self-select.
  const initialPreset = promo ? "blitz"
                      : form.risk === "Conservative" ? "scalper"   // PULSE
                      : form.risk === "Aggressive"   ? "volt"      // 10x tight-trail
                      : "flash";                                    // 5x tight-trail (moderate default)

  return (
    <div className="cg-modal-backdrop">
      <div className="cg-modal cg-onb" style={{ maxWidth: 960 }}>
        <OnbLogo />
        <div className="cg-modal-head">
          <div>
            <div className="cg-modal-eyebrow">// DESIGN_YOUR_AGENT · STEP_03 / 03</div>
            <div className="cg-modal-title">Design your agent's strategy</div>
          </div>
        </div>

        <div className="cg-modal-body">
          <p className="cg-onb-welcome" style={{ marginBottom: 16 }}>
            Tell your agent what kind of trader you want it to be. It'll read the live tape,
            propose a specific strategy, and forecast plausible outcomes. Push back until it's
            what you want — then launch.
          </p>

          <AgentDesignPanel
            preset={initialPreset}
            agentName={form.agentName}
            onAgentName={v => update("agentName", v)}
            onCommitted={(agent) => {
              // Set the committed agent id/name onto the form so the launch
              // sequence + onComplete callback have them in scope.
              setForm(f => ({
                ...f,
                agentName:     agent.name || f.agentName,
                committedAgent: agent,
                description:   f.description || (agent.strategy_state && agent.strategy_state.personality) || "",
              }));
              setLaunching(true);
            }}
          />

          <div className="cg-modal-actions" style={{ marginTop: 24 }}>
            <button className="cg-cta-ghost" onClick={() => setPage(2)}>← Back</button>
            <div />
          </div>
        </div>
      </div>
    </div>
  );
}

/* ===============================================================
   CAMPAIGN LOGIN — existing-user entry point.
   Two-step: email → 6-digit code → JWT.
   Hits the same /auth/email/start + /auth/email/verify endpoints as
   signup; the backend recognises an existing email and returns the
   participant without needing a username (see routes/auth.py:124).
   On success, calls onComplete(user) — the parent (main.jsx) treats
   the user-object the same as a freshly-onboarded signup.
   =============================================================== */
function CampaignLogin({ onComplete, onCancel }) {
  const [email, setEmail] = useState("");
  const [code, setCode] = useState("");
  const [phase, setPhase] = useState("email");      // email → code → done
  const [sending, setSending] = useState(false);
  const [verifying, setVerifying] = useState(false);
  const [error, setError] = useState("");
  const [cooldown, setCooldown] = useState(0);
  const [captchaToken, setCaptchaToken] = useState(null);
  const stubCode = useRef(null);
  const toast = useToast && useToast();

  useEffect(() => {
    if (cooldown <= 0) return;
    const t = setTimeout(() => setCooldown(c => c - 1), 1000);
    return () => clearTimeout(t);
  }, [cooldown]);

  const validEmail = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.trim());
  const effectiveToken = captchaToken || (typeof window !== "undefined" && window.AC_LAST_CAPTCHA) || null;
  const captchaReady   = !!effectiveToken;

  async function sendCode() {
    if (!validEmail || sending) return;
    if (!captchaReady) {
      setError("Waiting for the security check — try again in a second.");
      return;
    }
    setSending(true); setError("");
    try {
      const res = await apiPost("/auth/email/start", { email: email.trim(), captcha_token: effectiveToken });
      if (res.__stub) { stubCode.current = "424242"; }
      setPhase("code"); setCooldown(30);
      toast && toast.push({ title: "Code sent", meta: res.__stub ? "Prototype code: 424242" : "Check your inbox", duration: 4500 });
    } catch (e) {
      const msg = (e && e.message) || "";
      setError(msg && msg !== "request_failed" ? msg : "Couldn't send code. Try again.");
    }
    setSending(false);
  }

  async function verify() {
    if (verifying) return;
    setError(""); setVerifying(true);
    try {
      // No username on this path — backend returns the existing participant
      // when the email is recognised (and 400s if it isn't, which we
      // surface as "no account for this email").
      const res = await apiPost("/auth/email/verify", { email: email.trim(), code: code.trim() });
      const jwt = res.jwt || res.token || null;
      const ok = res.__stub ? code.trim() === stubCode.current : !!jwt;
      if (!ok) { setError("Invalid code. Check and try again."); return; }
      if (jwt) storeAuth(jwt);
      // The verify response shape is the same one CampaignOnboarding
      // hands to its parent — pass it through so main.jsx can populate
      // the user object identically to a signup flow.
      const u = {
        ...CAMPAIGN_DEFAULT_USER,
        ...(res.participant || {}),
        // these keys come from existing_participant_response()
        username: res.participant && res.participant.username,
        email:    res.participant && res.participant.email,
        referralCode: res.referral_code || (res.participant && res.participant.referral_code),
        paperBalance: res.paper_balance || 0,
      };
      onComplete(u);
    } catch (e) {
      const msg = e && e.message && e.message !== "request_failed" ? e.message : "";
      // 400 from the verify endpoint on an unknown email reads as
      // "username required for new account" — translate that to
      // something a returning user can act on.
      if (/username/i.test(msg)) setError("No account for this email — sign up first.");
      else setError("Verification failed. Try again.");
    } finally { setVerifying(false); }
  }

  return (
    <div className="cg-modal-backdrop" onClick={onCancel}>
      <div className="cg-modal" onClick={e => e.stopPropagation()} style={{ width: 460 }}>
        <div className="cg-modal-head">
          <div>
            <div className="cg-modal-eyebrow">// LOG_IN</div>
            <div className="cg-modal-title">{phase === "email" ? "Welcome back" : "Check your inbox"}</div>
          </div>
          <button className="cg-modal-x" onClick={onCancel}>×</button>
        </div>
        <div className="cg-modal-body">
          {phase === "email" && (
            <>
              <p style={{ fontSize: 13, color: "var(--cg-fg-soft, #A6B0BE)", margin: 0, lineHeight: 1.55 }}>
                Enter the email you signed up with. We'll send a 6-digit code.
              </p>
              <div className="cg-field">
                <label>EMAIL</label>
                <input
                  type="email"
                  autoFocus
                  value={email}
                  onChange={e => setEmail(e.target.value)}
                  onKeyDown={e => { if (e.key === "Enter" && validEmail) sendCode(); }}
                  placeholder="you@example.com"
                />
              </div>
              {/* Turnstile — required by /auth/email/start in production.
                  Without this widget, every login attempt returned 400
                  "missing captcha token". */}
              <div style={{ margin: "12px 0" }}>
                <TurnstileWidget onToken={setCaptchaToken} />
              </div>
              {error && <div className="cg-modal-error">{error}</div>}
              <button
                className="cg-cta-primary"
                disabled={!validEmail || sending || !captchaReady}
                onClick={sendCode}
                title={!captchaReady ? "Waiting for the security check to finish" : undefined}
              >
                {sending ? "SENDING…" : !captchaReady && validEmail ? "WAITING…" : "SEND CODE →"}
              </button>
            </>
          )}

          {phase === "code" && (
            <>
              <p style={{ fontSize: 13, color: "var(--cg-fg-soft, #A6B0BE)", margin: 0, lineHeight: 1.55 }}>
                We sent a 6-digit code to <strong>{email}</strong>.
              </p>
              <div className="cg-field">
                <label>VERIFICATION_CODE</label>
                <input
                  type="text"
                  inputMode="numeric"
                  autoFocus
                  maxLength={6}
                  value={code}
                  onChange={e => setCode(e.target.value.replace(/\D/g, ""))}
                  onKeyDown={e => { if (e.key === "Enter" && code.length === 6) verify(); }}
                  placeholder="••••••"
                  style={{ letterSpacing: 8, fontSize: 18, textAlign: "center" }}
                />
              </div>
              {error && <div className="cg-modal-error">{error}</div>}
              <div className="cg-pine-actions">
                <button className="cg-cta-secondary" onClick={() => { setPhase("email"); setCode(""); setError(""); }}>← Change email</button>
                <button className="cg-cta-primary" disabled={code.length !== 6 || verifying} onClick={verify}>
                  {verifying ? "VERIFYING…" : "LOG IN →"}
                </button>
              </div>
              <button
                className="cg-cta-ghost"
                disabled={cooldown > 0 || sending}
                onClick={sendCode}
                style={{ marginTop: 4 }}
              >
                {cooldown > 0 ? `Resend code in ${cooldown}s` : "Resend code"}
              </button>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

/* ===============================================================
   IDENTITY MINT BADGE
   Auto-mints the participant's Know-Your-User identity on mount and
   displays the resulting Ethereum-shaped address. Idempotent — safe
   to render multiple times, only mints if not already assigned.
   =============================================================== */
function IdentityMintBadge({ onMinted }) {
  const [state, setState] = useState({ status: "loading", kya_id: null, error: null });

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await acSend("/auth/identity/mint", {});
        if (cancelled) return;
        if (res && res.__stub) { setState({ status: "stub", kya_id: null, error: null }); return; }
        setState({
          status: res.kya_status || "assigned",
          kya_id: res.kya_id,
          error: null,
        });
        if (onMinted) onMinted(res.kya_id);
      } catch (e) {
        if (cancelled) return;
        setState({ status: "error", kya_id: null, error: (e && e.message) || "mint_failed" });
      }
    })();
    return () => { cancelled = true; };
  }, []);

  if (state.status === "loading") {
    return (
      <div style={{ padding: "10px 14px", border: "1px solid var(--border)", background: "var(--bg-2)", borderRadius: 6, fontFamily: "'DM Mono', monospace", fontSize: 12, color: "var(--muted)" }}>
        // MINTING_IDENTITY…
      </div>
    );
  }
  if (state.status === "stub") return null;
  if (state.status === "error") {
    return (
      <div style={{ padding: "10px 14px", border: "1px solid #c25", background: "var(--bg-2)", borderRadius: 6, fontFamily: "'DM Mono', monospace", fontSize: 12, color: "#e97" }}>
        // IDENTITY_MINT_FAILED — {state.error}
      </div>
    );
  }
  const short = state.kya_id ? state.kya_id.slice(0, 6) + "…" + state.kya_id.slice(-4) : "";
  return (
    <div style={{ padding: "12px 14px", border: "1px solid var(--accent)", background: "linear-gradient(135deg, rgba(29,158,117,0.14), rgba(29,158,117,0.05))", borderRadius: 6, fontFamily: "'DM Mono', monospace", fontSize: 12, lineHeight: 1.6 }}>
      <div style={{ color: "var(--muted)", marginBottom: 3 }}>// IDENTITY_ASSIGNED via OpenClaw</div>
      <div style={{ color: "var(--fg)", fontSize: 13 }}><span className="accent">{short}</span> — your verifiable identity across Agentics + Billions Network.</div>
    </div>
  );
}


/* ===============================================================
   AGENT DESIGN PANEL
   The heart of the new wizard: collaborative strategy design with
   Claude. User writes their mandate + picks a risk preset → Claude
   proposes a strategy + forecast → user refines or accepts. On
   accept, the design commits and an agent is created with the
   negotiated strategy_state as its opening state.

   Props:
     - preset: 'low'|'medium'|'high'|'very_high' (initial default)
     - onCommitted(agent): fires once the user launches the agent.
                           agent = {id, name, strategy_state, forecast}
   =============================================================== */
/* Loading screen for the first design call. Opus 4.7 + adaptive thinking
   on this per-agent context takes 15-30s — a bare "DESIGNING…" button
   feels broken. This component cycles through what's actually happening
   server-side so the wait feels intentional. */
function DesignLoading({ userIntent, preset }) {
  const STAGES = [
    { label: "Pulling live market snapshot", detail: "price / regime / ATR across the tradable universe" },
    { label: "Reading your mandate",         detail: "parsing intent, risk tolerance, target return" },
    { label: "Thinking through the setup",   detail: "Opus 4.7 reasoning about strategy vs. current tape" },
    { label: "Building the strategy proposal", detail: "sizing, stops, TP, trailing, market bias" },
    { label: "Forecasting outcomes",         detail: "expected return, drawdown, win rate, confidence" },
    { label: "Wrapping up",                  detail: "any second now…" },
  ];
  const [stage, setStage] = useState(0);
  const [elapsed, setElapsed] = useState(0);

  useEffect(() => {
    // Cycle stages roughly every 4s so a 20s call walks through most of them
    // without racing past the end and looking stuck. Elapsed timer runs 1s.
    const stageTick = setInterval(() => {
      setStage(s => Math.min(s + 1, STAGES.length - 1));
    }, 4000);
    const elapsedTick = setInterval(() => {
      setElapsed(e => e + 1);
    }, 1000);
    return () => { clearInterval(stageTick); clearInterval(elapsedTick); };
  }, []);

  const presetLabel = ({
    // Legacy keys — still resolve for existing agents even though they're not in the picker.
    low: "CIPHER", medium: "AXIOM", high: "DELTA", very_high: "SURGE", rider: "RIDER",
    // Current PULSE-derivative family shown in the wizard.
    scalper: "PULSE", flash: "FLASH", volt: "VOLT", blitz: "BLITZ",
  })[preset] || preset;

  return (
    <div style={{
      display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
      minHeight: 460, padding: "40px 24px",
      fontFamily: "'DM Mono', monospace",
    }}>
      {/* Pulse ring */}
      <div style={{
        width: 72, height: 72, borderRadius: "50%",
        border: "2px solid var(--accent)",
        marginBottom: 24,
        position: "relative",
        animation: "acDesignPulse 2.4s ease-in-out infinite",
      }}>
        <style>{`
          @keyframes acDesignPulse {
            0%, 100% { transform: scale(1);   opacity: 0.35; }
            50%      { transform: scale(1.2); opacity: 1;    }
          }
          @keyframes acDesignBar {
            0%   { transform: translateX(-100%); }
            100% { transform: translateX(300%);  }
          }
        `}</style>
      </div>

      <div style={{ fontSize: 11, color: "var(--cg-muted, var(--muted))", letterSpacing: 2, marginBottom: 8 }}>
        // DESIGNING · {presetLabel}
      </div>
      <div style={{ fontSize: 22, color: "var(--fg)", fontWeight: 600, letterSpacing: -0.3, marginBottom: 6, textAlign: "center" }}>
        {STAGES[stage].label}…
      </div>
      <div style={{ fontSize: 13, color: "var(--cg-muted, var(--muted))", marginBottom: 24, textAlign: "center", maxWidth: 460, lineHeight: 1.5 }}>
        {STAGES[stage].detail}
      </div>

      {/* Indeterminate progress bar */}
      <div style={{
        width: "min(360px, 80%)", height: 3, background: "var(--border)",
        borderRadius: 3, overflow: "hidden", position: "relative", marginBottom: 20,
      }}>
        <div style={{
          position: "absolute", top: 0, left: 0,
          width: "40%", height: "100%",
          background: "linear-gradient(90deg, transparent, var(--accent), transparent)",
          animation: "acDesignBar 1.8s ease-in-out infinite",
        }}/>
      </div>

      <div style={{ display: "flex", gap: 12, alignItems: "center", fontSize: 11, color: "var(--cg-muted, var(--muted))" }}>
        <span>elapsed {elapsed}s</span>
        <span style={{ opacity: 0.4 }}>·</span>
        <span>typically 15-30s</span>
      </div>

      {userIntent && (
        <div style={{
          marginTop: 32, padding: "14px 18px",
          border: "1px dashed var(--border)", borderRadius: 6,
          background: "var(--bg-2)",
          maxWidth: 560, fontSize: 12, lineHeight: 1.6,
          color: "var(--cg-muted, var(--muted))",
        }}>
          <div style={{ fontSize: 10, color: "var(--accent)", letterSpacing: 1.5, marginBottom: 6 }}>// YOUR_MANDATE</div>
          <div style={{ color: "var(--fg)", fontStyle: "italic" }}>{userIntent}</div>
        </div>
      )}
    </div>
  );
}


function AgentDesignPanel({ preset: initialPreset, agentName, onAgentName, onCommitted }) {
  const [preset, setPreset] = useState(initialPreset || "flash");
  const [userIntent, setUserIntent] = useState("");
  const [sessionId, setSessionId] = useState(null);
  const [conversation, setConversation] = useState([]);  // [{role, content}]
  const [proposal, setProposal] = useState(null);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);
  const [refineText, setRefineText] = useState("");

  // historical_metrics come from backtests/marketai_phase0/preset_backtest.py —
  // a rule-based simulation of each preset's guardrails on 7mo of HL data.
  // Live Claude decisions differ from the rule heuristics, but the DIRECTION
  // of risk (drawdown, blowup rate, trade frequency) transfers. Kept in sync
  // with campaign/api/presets.py::historical_metrics.
  //
  // Only the PULSE-derivative family (tight-trailing) is shown in the picker
  // — those are the presets that turned net-positive on backtest. The older
  // asymmetric-reward family (CIPHER/AXIOM/DELTA/SURGE) and the trend-rider
  // (RIDER) are kept in the backend presets.py so existing agents don't
  // break, but new users don't see them.
  const PRESETS = [
    { key: "scalper", label: "PULSE", lev: 3,
      desc: "Base scalper · BTC + ETH · many small trades, tight trailing locks gains fast",
      m: { win: 59.4, dd: 0.3, pnl: 2.6, trades: 131, blowup: 0.0 } },
    { key: "flash", label: "FLASH", lev: 5,
      desc: "PULSE-tight at 5x · BTC + ETH + SOL · moderate step up",
      m: { win: 55.6, dd: 0.9, pnl: 3.7, trades: 147, blowup: 0.0 } },
    { key: "volt", label: "VOLT", lev: 10,
      desc: "PULSE-tight at 10x · BTC + ETH · triple the return per trade",
      m: { win: 55.6, dd: 1.8, pnl: 7.7, trades: 150, blowup: 0.0 } },
    { key: "blitz", label: "BLITZ", lev: 20, warn: true,
      desc: "PULSE-tight at max aggression · 20x · 5 markets · gap-move blowup risk",
      m: { win: 55.5, dd: 2.9, pnl: 9.7, trades: 158, blowup: 0.0 } },
  ];

  const STARTERS = [
    "I want to make 15% a month at moderate risk. Focus on BTC and ETH, momentum-following. I hate seeing drawdowns above 8%.",
    "Aggressive scalper — many small trades, tight stops. Comfortable with high leverage on liquid markets only.",
    "Patient swing trader. Hold 4-24h. Wait for high-conviction setups. Avoid overtrading in chop.",
    "Mean-reversion contrarian. Fade extremes, take profit at the mean. Small size, tight risk.",
  ];

  async function startDesign() {
    if (userIntent.trim().length < 10) { setError("Say a bit more about what you want."); return; }
    setBusy(true); setError(null);
    try {
      const res = await acSend("/agents/design", {
        user_intent: userIntent.trim(),
        preset,
      });
      if (res && res.__stub) throw new Error("Design service not reachable");
      setSessionId(res.session_id);
      setConversation([
        { role: "user",      content: userIntent.trim() },
        { role: "assistant", content: res.message, proposal: res.proposal },
      ]);
      setProposal(res.proposal);
    } catch (e) {
      setError((e && e.message) || "Design service failed. Try again.");
    } finally { setBusy(false); }
  }

  async function sendRefine() {
    if (!sessionId || refineText.trim().length < 1) return;
    const msg = refineText.trim();
    setBusy(true); setError(null); setRefineText("");
    setConversation(c => [...c, { role: "user", content: msg }]);
    try {
      const res = await acSend("/agents/design/refine", { session_id: sessionId, message: msg });
      if (res && res.__stub) throw new Error("Design service not reachable");
      setConversation(c => [...c, { role: "assistant", content: res.message, proposal: res.proposal }]);
      if (res.proposal) setProposal(res.proposal);
    } catch (e) {
      setError((e && e.message) || "Refinement failed. Try again.");
    } finally { setBusy(false); }
  }

  async function commit() {
    if (!sessionId || !proposal) return;
    setBusy(true); setError(null);
    try {
      const name = (agentName || "").trim() || undefined;
      const res = await acSend("/agents/design/commit", { session_id: sessionId, name });
      if (res && res.__stub) throw new Error("Commit failed — service unreachable");
      onCommitted && onCommitted(res);
    } catch (e) {
      setError((e && e.message) || "Launch failed. Try again.");
    } finally { setBusy(false); }
  }

  return (
    <div className="ac-design">
      {/* Loading overlay for the FIRST design call — Opus + adaptive thinking
          on a per-agent context runs 15-30s. A spinner + rotating status
          messages make the wait feel intentional instead of broken. Only
          shown before the first proposal lands (busy && !sessionId); refine
          calls are shorter and use the inline "// thinking…" indicator. */}
      {busy && !sessionId && (
        <DesignLoading userIntent={userIntent} preset={preset} />
      )}

      {/* Pre-design: preset + mandate — hidden while the first design is in-flight
          so the user sees the loading screen, not a stale form */}
      {!sessionId && !busy && (
        <>
          <div className="cg-field">
            <label>AGENT_NAME <span className="opt">— shown across the platform</span></label>
            <input value={agentName || ""} onChange={e => onAgentName && onAgentName(e.target.value)} placeholder="ALPHA-7" />
          </div>

          <div className="cg-field">
            <label>RISK_PRESET <span className="opt">— the ceiling the risk desk enforces. Your agent can go lower.</span></label>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
              {PRESETS.map(p => (
                <button
                  key={p.key}
                  onClick={() => setPreset(p.key)}
                  className={"cg-preset-card" + (preset === p.key ? " active" : "")}
                  style={{
                    textAlign: "left", padding: "12px 14px", borderRadius: 6,
                    border: "1px solid " + (preset === p.key ? "var(--accent)" : "var(--border)"),
                    background: preset === p.key ? "linear-gradient(135deg, rgba(29,158,117,0.14), rgba(29,158,117,0.05))" : "var(--bg-2)",
                    fontFamily: "'DM Mono', monospace", cursor: "pointer",
                  }}
                >
                  <div style={{ color: "var(--fg)", fontWeight: 600, fontSize: 14, marginBottom: 4 }}>
                    {p.label} <span style={{ opacity: 0.5, fontWeight: 400 }}>· max {p.lev}×</span>
                    {p.warn && (
                      <span style={{
                        marginLeft: 6, fontSize: 9, padding: "1px 5px", borderRadius: 3,
                        background: "rgba(233, 87, 63, 0.18)", color: "#e97", fontWeight: 700,
                        letterSpacing: 0.5, verticalAlign: "middle",
                      }}>BLOWUP RISK</span>
                    )}
                  </div>
                  <div style={{ color: "var(--muted)", fontSize: 12, lineHeight: 1.5 }}>{p.desc}</div>
                  {/* Backtest evidence — honest numbers so users pick with information, not vibes. */}
                  <div style={{
                    marginTop: 10, paddingTop: 8, borderTop: "1px dashed var(--border)",
                    display: "grid", gridTemplateColumns: "1fr 1fr", rowGap: 3, columnGap: 8,
                    fontSize: 11, lineHeight: 1.4,
                  }}>
                    <div style={{ color: "var(--muted)" }}>win rate</div>
                    <div style={{ color: "var(--fg)", textAlign: "right" }}>{p.m.win.toFixed(1)}%</div>
                    <div style={{ color: "var(--muted)" }}>max drawdown</div>
                    <div style={{ color: "var(--fg)", textAlign: "right" }}>{p.m.dd.toFixed(1)}%</div>
                    <div style={{ color: "var(--muted)" }}>median return</div>
                    <div style={{ color: p.m.pnl >= 0 ? "var(--accent)" : "#e97", textAlign: "right" }}>
                      {p.m.pnl >= 0 ? "+" : ""}{p.m.pnl.toFixed(1)}%
                    </div>
                    <div style={{ color: "var(--muted)" }}>blowup rate</div>
                    <div style={{ color: p.m.blowup === 0 ? "var(--accent)" : "#e97", textAlign: "right" }}>
                      {p.m.blowup.toFixed(1)}%
                    </div>
                  </div>
                  <div style={{ marginTop: 6, color: "var(--muted)", fontSize: 10, opacity: 0.7 }}>
                    7mo backtest · rule sim · Claude live differs
                  </div>
                </button>
              ))}
            </div>
          </div>

          <div className="cg-field">
            <label>TRADING_MANDATE <span className="opt">— tell your agent what kind of trader to be</span></label>
            <textarea
              value={userIntent}
              onChange={e => setUserIntent(e.target.value)}
              placeholder="I want a patient swing trader that focuses on BTC and ETH momentum breakouts on the 4h timeframe. I want to make 10% a month but never draw down more than 6%..."
              rows={5}
            />
          </div>

          <div className="cg-helper-prompts">
            {STARTERS.map((s, i) => (
              <button key={i} className="cg-prompt-chip" onClick={() => setUserIntent(s)}>
                {s.split(" ").slice(0, 5).join(" ")}…
              </button>
            ))}
          </div>

          {error && <div style={{ color: "#e97", fontFamily: "'DM Mono', monospace", fontSize: 12, marginTop: 8 }}>// {error}</div>}

          <div className="cg-modal-actions" style={{ marginTop: 20 }}>
            <div />
            <button
              className="cg-cta-primary"
              disabled={busy || userIntent.trim().length < 10}
              onClick={startDesign}
            >
              {busy ? "DESIGNING…" : "DESIGN MY STRATEGY →"}
            </button>
          </div>
        </>
      )}

      {/* Post-design: chat + proposal card + commit */}
      {sessionId && (
        <>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 320px", gap: 20 }}>
            {/* CHAT */}
            <div>
              <div style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: "var(--muted)", marginBottom: 8 }}>
                // CONVERSATION — design your agent with Claude
              </div>
              <div style={{ maxHeight: 380, overflowY: "auto", border: "1px solid var(--border)", background: "var(--bg-2)", borderRadius: 6, padding: 12 }}>
                {conversation.map((turn, i) => (
                  <div key={i} style={{ marginBottom: 14 }}>
                    <div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: "var(--muted)", marginBottom: 4 }}>
                      {turn.role === "user" ? "YOU" : "AGENT"}
                    </div>
                    <div style={{ fontSize: 13.5, color: "var(--fg)", lineHeight: 1.6, whiteSpace: "pre-wrap" }}>
                      {turn.content}
                    </div>
                  </div>
                ))}
                {busy && (
                  <div style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: "var(--muted)", fontStyle: "italic" }}>
                    // thinking…
                  </div>
                )}
              </div>
              <div style={{ display: "flex", gap: 8, marginTop: 10 }}>
                <input
                  value={refineText}
                  onChange={e => setRefineText(e.target.value)}
                  onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); sendRefine(); } }}
                  placeholder="Push back, ask for a change, or hit Launch →"
                  style={{ flex: 1 }}
                  disabled={busy}
                />
                <button className="cg-cta-secondary" onClick={sendRefine} disabled={busy || refineText.trim().length < 1}>
                  SEND
                </button>
              </div>
              {error && <div style={{ color: "#e97", fontFamily: "'DM Mono', monospace", fontSize: 12, marginTop: 8 }}>// {error}</div>}
            </div>

            {/* PROPOSAL CARD */}
            <div>
              <div style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: "var(--muted)", marginBottom: 8 }}>
                // PROPOSED_STRATEGY
              </div>
              {proposal && proposal.strategy_state && (
                <div style={{ border: "1px solid var(--accent)", background: "var(--bg-2)", borderRadius: 6, padding: 14, fontFamily: "'DM Mono', monospace", fontSize: 12, lineHeight: 1.7 }}>
                  <ProposalRow k="Markets"       v={(proposal.strategy_state.markets || []).join(", ") || "—"} />
                  <ProposalRow k="Direction"     v={fmtBias(proposal.strategy_state.market_bias)} />
                  <ProposalRow k="Leverage"      v={proposal.strategy_state.leverage != null ? proposal.strategy_state.leverage + "×" : "—"} />
                  <ProposalRow k="Position size" v={proposal.strategy_state.target_position_size_pct != null ? proposal.strategy_state.target_position_size_pct + "%" : "—"} />
                  <ProposalRow k="Stop loss"     v={proposal.strategy_state.stop_loss_pct != null ? proposal.strategy_state.stop_loss_pct + "%" : "—"} />
                  <ProposalRow k="Take profit"   v={proposal.strategy_state.take_profit_pct != null ? proposal.strategy_state.take_profit_pct + "%" : "—"} />
                  <ProposalRow k="Trailing"      v={proposal.strategy_state.trailing_stop ? `trigger ${proposal.strategy_state.trailing_trigger_pct}% / lock ${proposal.strategy_state.trailing_lock_pct}%` : "off"} />
                  <ProposalRow k="Max hold"      v={proposal.strategy_state.max_hold_hours != null ? proposal.strategy_state.max_hold_hours + "h" : "—"} />
                  {proposal.forecast && (
                    <>
                      <div style={{ margin: "10px 0 6px", borderTop: "1px solid var(--border)", paddingTop: 8, color: "var(--muted)", fontSize: 10 }}>// FORECAST</div>
                      <ProposalRow k="Return / mo"  v={proposal.forecast.expected_return_pct_monthly != null ? proposal.forecast.expected_return_pct_monthly + "%" : "—"} />
                      <ProposalRow k="Max DD"       v={proposal.forecast.expected_max_drawdown_pct != null ? proposal.forecast.expected_max_drawdown_pct + "%" : "—"} />
                      <ProposalRow k="Win rate"     v={proposal.forecast.expected_win_rate != null ? proposal.forecast.expected_win_rate + "%" : "—"} />
                      <ProposalRow k="Trades / day" v={proposal.forecast.expected_daily_trades != null ? proposal.forecast.expected_daily_trades : "—"} />
                      <ProposalRow k="Confidence"   v={proposal.forecast.confidence || "—"} />
                    </>
                  )}
                </div>
              )}
              <button
                className="cg-cta-primary"
                onClick={commit}
                disabled={busy || !proposal || !proposal.strategy_state}
                style={{ width: "100%", marginTop: 14 }}
              >
                {busy ? "LAUNCHING…" : "LAUNCH THIS AGENT →"}
              </button>
              <div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: "var(--muted)", marginTop: 8, lineHeight: 1.6 }}>
                Not quite right? Send a refinement message and the agent will revise.
                Once launched, your agent starts trading within the risk guardrails.
              </div>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

function ProposalRow({ k, v }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", gap: 8, marginBottom: 3 }}>
      <span style={{ color: "var(--muted)" }}>{k}</span>
      <span style={{ color: "var(--fg)", textAlign: "right", maxWidth: "60%", wordBreak: "break-word" }}>{v}</span>
    </div>
  );
}

function fmtBias(bias) {
  if (!bias || typeof bias !== "object") return "—";
  const parts = Object.entries(bias).map(([m, side]) => `${m.replace("-PERP", "")}:${side}`);
  return parts.length ? parts.join(", ") : "—";
}


function OnboardingLaunchSequence({ form, onDone }) {
  const lines = [
    "Reading your description...",
    "Configuring your agent...",
    "Loading paper USDT...",
    "Connecting to Hyperliquid feed...",
    "Agent is live. First trade incoming.",
  ];
  const [n, setN] = useState(0);
  useEffect(() => {
    if (n >= lines.length) { const t = setTimeout(onDone, 700); return () => clearTimeout(t); }
    const t = setTimeout(() => setN(n + 1), 700);
    return () => clearTimeout(t);
  }, [n]);
  return (
    <div className="cg-modal-backdrop">
      <div className="cg-launch-modal">
        <div className="cg-launch-title">LAUNCHING <span className="accent">{form.agentName || "your agent"}</span></div>
        <div className="cg-launch-feed">
          {lines.slice(0, n + 1).map((l, i) => (
            <div key={i} className={"cg-launch-line" + (i === n ? " active" : "")}>
              {i < n ? "✓" : "▸"} {l}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ===============================================================
   REASONING FEED (live)
   =============================================================== */

/* ===============================================================
   EXPORTS
   =============================================================== */
Object.assign(window, {
  CampaignOnboarding, CampaignLogin,
  EmailVerify, TelegramVerify,
  IdentityMintBadge, AgentDesignPanel, DesignLoading, OnboardingLaunchSequence,
});
