// SQSEO onboarding wizard (Phase 10, BLG-inspired guided setup). A four-step
// flow — Website -> Business -> Search Console -> Cadence — that writes to real
// endpoints (PATCH /api/onboarding/wizard, POST /api/onboarding/wizard/complete,
// POST /api/sync, /oauth/google/authorize). Cadence is the last step; its
// "Launch workspace" button completes onboarding. Progress is drafted to
// localStorage so a live Search Console OAuth round-trip resumes here instead of
// dropping the user. Registered as window.LTQ.Onboarding.
//
// Honesty guardrails (house rule): we only claim what we support. Search Console
// is a real connection. Cadence is stored as a PREFERENCE that tailors briefs
// and exports, never as a live third-party integration.
//
// I18N L2: every string here goes through t() (wizard.* keys), and the Business
// step asks for company country + platform language. Picking a language flips
// the whole wizard live (the first magic moment for a non-English signup).
(function init(){
if(!window.LongtailIQDesignSystem_ae8f12 || !window.LTQ || !window.LTQ.api || !window.LTQ.useApi || !window.LTQ.t || !window.LTQ.CountrySelect){return setTimeout(init,30);}
const React = window.React;
const DS = window.LongtailIQDesignSystem_ae8f12;
const { Icon } = DS;
const { api, useApi, toast, reloadBoot, t, setLocale, getLocale, CountrySelect, LOCALE_OPTIONS, localeForCountry, countryName } = window.LTQ;

const LS_STEP = "sqseo_wizard_step", LS_FORM = "sqseo_wizard_form", LS_ACTIVE = "sqseo_wizard_active";

// Autofill (scan → prefilled Business fields) is OFF for now: the suggestions
// weren't reliable enough. The site scan still RUNS (it feeds Site Intelligence
// + seed keywords later) and the ScanThinking moment still plays — we just don't
// prefill the fields, so the user fills them in themselves. Flip back to true to
// re-enable once autofill quality is good.
const AUTOFILL_ENABLED = false;

const STEPS = [
  { id: "site", icon: "globe" },
  { id: "business", icon: "building-2" },
  { id: "search", icon: "line-chart" },
  { id: "cadence", icon: "calendar-clock" },
];

// Publishing targets. `soon` = we don't yet auto-publish there (honest: selecting
// records a preference, it does not open a live connection). Brand names stay
// untranslated; only the generic entries have wizard.* keys.
const CMS = [
  { id: "wordpress", name: () => "WordPress", icon: "layout-template", soon: true },
  { id: "webflow", name: () => "Webflow", icon: "pen-tool", soon: true },
  { id: "shopify", name: () => "Shopify", icon: "shopping-bag", soon: true },
  { id: "wix", name: () => "Wix", icon: "layout", soon: true },
  { id: "ghost", name: () => "Ghost", icon: "ghost", soon: true },
  { id: "api", name: () => t("wizard.publish.api"), icon: "webhook", soon: false },
  { id: "none", name: () => t("wizard.publish.copy_paste"), icon: "clipboard-copy", soon: false },
];
const cmsLabel = (id) => { const c = CMS.find((x) => x.id === id); return c ? c.name() : ""; };

const CADENCE = [
  { id: "daily", icon: "flame" },
  { id: "3x_week", icon: "zap" },
  { id: "weekly", icon: "calendar" },
  { id: "biweekly", icon: "calendar-days" },
  { id: "manual", icon: "hand" },
];
const cadenceLabel = (id) => (id ? t("wizard.cadence." + id) : "");

// ---- shared styles (match the app's inline-token convention) ----
const field = { width: "100%", minHeight: 44, padding: "11px 14px", borderRadius: 10, background: "var(--surface-card)",
  border: "1px solid var(--border-strong)", fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-strong)", outline: "none" };
const label = { display: "block", fontSize: 12.5, fontWeight: 600, color: "var(--text-body)", marginBottom: 8 };
const hint = { fontSize: 12, color: "var(--text-faint)", marginTop: 8, lineHeight: 1.5 };

// Client-side domain normalizer/corrector — forgives very messy input (protocol,
// path, query, www, email, spaces, doubled dots) and catches obvious TLD typos
// instantly, before the server reachability check. Returns { clean, suggestion?,
// kind: empty|invalid|typo|subdomain|ok }.
const TLD_FIX = { comb: "com", con: "com", cmo: "com", vom: "com", xom: "com", ocm: "com", copm: "com", comm: "com", cim: "com", som: "com", conm: "com", coom: "com", cpm: "com", dom: "com", ogr: "org", rog: "org", orgg: "org", nte: "net", ent: "net", nett: "net", ner: "net" };
function normDomain(raw) {
  let s = (raw || "").trim().toLowerCase();
  if (!s) return { clean: "", kind: "empty" };
  if (s.includes("@")) s = s.split("@").pop();
  s = s.replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/^\/\//, "");
  s = s.split(/[\/?#]/)[0].replace(/\s+/g, "");
  s = s.replace(/^www\./, "").replace(/^\.+/, "").replace(/\.+$/, "");
  if (!s) return { clean: "", kind: "empty" };
  if (!/^[a-z0-9.-]+$/.test(s)) return { clean: s, kind: "invalid" };
  const labels = s.split(".").filter(Boolean);
  s = labels.join(".");
  if (labels.length < 2) {
    for (const tld of ["com", "org", "net", "io", "co"]) {
      if (s.length > tld.length + 1 && s.endsWith(tld)) {
        const base = s.slice(0, -tld.length);
        if (/^[a-z0-9-]{2,}$/.test(base)) return { clean: s, suggestion: base + "." + tld, kind: "typo" };
      }
    }
    return { clean: s, kind: "invalid" };
  }
  const tld = labels[labels.length - 1];
  if (TLD_FIX[tld]) return { clean: s, suggestion: labels.slice(0, -1).concat(TLD_FIX[tld]).join("."), kind: "typo" };
  if (!/^[a-z]{2,}$/.test(tld)) return { clean: s, kind: "invalid" };
  if (labels.some((l) => !/^[a-z0-9-]+$/.test(l) || l.startsWith("-") || l.endsWith("-"))) return { clean: s, kind: "invalid" };
  if (labels.length >= 3) return { clean: s, kind: "subdomain", isSubdomain: true };
  return { clean: s, kind: "ok" };
}

// Onboarding motion (2026-07-12): a dual, counter-drifting emerald+ink aurora
// behind the wizard, a soft step-to-step transition, staggered field reveals, and
// a pulse on the active step dot. Emerald/ink only (brand), heavily blurred + low
// opacity so it reads as ambient light, and fully static under reduced motion.
const ONB_CSS = `
  .ltob-aurora{position:absolute;inset:-70px -50px;z-index:0;overflow:hidden;pointer-events:none;border-radius:32px;}
  .ltob-aurora span{position:absolute;width:62%;height:72%;border-radius:50%;filter:blur(88px);will-change:transform;}
  .ltob-a1{background:radial-gradient(circle,var(--accent-300,#6ee7b7),transparent 70%);opacity:0.14;top:-14%;left:-10%;animation:ltob-drift-a 19s ease-in-out infinite alternate;}
  .ltob-a2{background:radial-gradient(circle,var(--accent-200,#9cd8bb),transparent 70%);opacity:0.09;bottom:-16%;right:-8%;animation:ltob-drift-b 24s ease-in-out infinite alternate;}
  @keyframes ltob-drift-a{from{transform:translate(0,0) scale(1);}to{transform:translate(20%,14%) scale(1.18);}}
  @keyframes ltob-drift-b{from{transform:translate(0,0) scale(1.12);}to{transform:translate(-18%,-11%) scale(0.94);}}
  @keyframes ltob-step-in{from{opacity:0;transform:translateY(12px);filter:blur(3px);}to{opacity:1;transform:none;filter:blur(0);}}
  .ltob-step{animation:ltob-step-in .5s cubic-bezier(0.22,1,0.36,1) both;}
  .ltob-step > *{animation:ltob-step-in .52s cubic-bezier(0.22,1,0.36,1) both;}
  .ltob-step > *:nth-child(1){animation-delay:.04s;}
  .ltob-step > *:nth-child(2){animation-delay:.10s;}
  .ltob-step > *:nth-child(3){animation-delay:.17s;}
  .ltob-step > *:nth-child(4){animation-delay:.24s;}
  .ltob-step > *:nth-child(5){animation-delay:.31s;}
  .ltob-step > *:nth-child(n+6){animation-delay:.37s;}
  @keyframes ltob-pulse{0%,100%{box-shadow:0 0 0 0 rgba(14,138,95,0);}50%{box-shadow:0 0 0 6px rgba(14,138,95,0.14);}}
  .ltob-active-dot{animation:ltob-pulse 2.4s ease-in-out infinite;}
  /* Configuring-screen bouncing dots (Duolingo-style "working" beat). */
  .cfg-dots span{width:7px;height:7px;border-radius:999px;background:var(--accent-500);display:inline-block;animation:cfg-bob 1.1s ease-in-out infinite;}
  .cfg-dots span:nth-child(2){animation-delay:.15s;}
  .cfg-dots span:nth-child(3){animation-delay:.3s;}
  @keyframes cfg-bob{0%,100%{opacity:.3;transform:translateY(0);}50%{opacity:1;transform:translateY(-4px);}}
  /* Field feedback: gentle pop-in for hints/errors + a soft shake on invalid. */
  @keyframes ob-pop{from{opacity:0;transform:translateY(-5px);}to{opacity:1;transform:none;}}
  .ob-pop{animation:ob-pop .26s cubic-bezier(.22,1,.36,1) both;}
  @keyframes ob-shake{10%,90%{transform:translateX(-1px);}20%,80%{transform:translateX(2px);}30%,50%,70%{transform:translateX(-4px);}40%,60%{transform:translateX(4px);}}
  .ob-shake{animation:ob-shake .42s cubic-bezier(.36,.07,.19,.97) both;}
  @media (prefers-reduced-motion: reduce){
    .ltob-a1,.ltob-a2,.ltob-step,.ltob-step > *,.ltob-active-dot,.cfg-dots span,.ob-pop,.ob-shake{animation:none !important;opacity:1 !important;transform:none !important;}
    .ltob-step,.ltob-step > *{opacity:1 !important;transform:none !important;filter:none !important;}
  }
`;

// The delightful in-between beat: after "Launch workspace", SQ configures
// everything with rotating speech balloons while /complete + reloadBoot run
// (held a minimum time so it reads as care, not a flash). Duolingo-style.
function ConfiguringStage() {
  const ref = React.useRef(null);
  React.useEffect(function () {
    const host = ref.current;
    if (!host || !window.SQRobot) return;
    const inst = window.SQRobot.mount(host, { width: 168, state: "work", interactive: false, track: false, bubble: false });
    const lines = [t("cfg.line1"), t("cfg.line2"), t("cfg.line3"), t("cfg.line4")];
    const states = ["work", "search", "work", "cheer"];
    let i = 0;
    const tick = function () {
      if (inst._destroyed) return;
      inst.setState(states[i % states.length]);
      inst.say(lines[i % lines.length], { side: "top" });
      i++;
    };
    tick();
    const iv = setInterval(tick, 2600);
    return function () { clearInterval(iv); try { inst.destroy(); } catch (e) {} };
  }, []);
  return (
    <div style={{ position: "relative", maxWidth: 520, margin: "0 auto", paddingBottom: 20 }}>
      <style>{ONB_CSS}</style>
      <div className="ltob-aurora" aria-hidden="true"><span className="ltob-a1" /><span className="ltob-a2" /></div>
      <div style={{ position: "relative", zIndex: 1, paddingTop: 88, textAlign: "center" }}>
        <div ref={ref} aria-hidden="true" style={{ display: "flex", justifyContent: "center", minHeight: 200 }} />
        <h2 style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, letterSpacing: "-0.02em", color: "var(--text-strong)", marginTop: 6 }}>{t("cfg.title")}</h2>
        <p style={{ fontSize: 13.5, color: "var(--text-muted)", marginTop: 6 }}>{t("cfg.sub")}</p>
        <div className="cfg-dots" style={{ display: "inline-flex", gap: 7, marginTop: 20 }} aria-hidden="true"><span /><span /><span /></div>
      </div>
    </div>
  );
}

function Primary({ children, onClick, busy, disabled, icon, trailingIcon }) {
  return (
    <button onClick={onClick} disabled={busy || disabled} className={busy || disabled ? "" : "lt-btn-3d"} style={{ height: 42, padding: "0 18px", borderRadius: 10, border: "none",
      background: "var(--ink-900)", color: "#fff", cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.5 : 1,
      fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 600, display: "inline-flex", alignItems: "center", gap: 8 }}>
      {busy ? <span className="lt-spin" style={{ width: 15, height: 15, border: "2px solid rgba(255,255,255,0.4)", borderTopColor: "#fff", borderRadius: 999 }} /> : icon ? <Icon name={icon} size={16} /> : null}
      {children}
      {trailingIcon && !busy ? <Icon name={trailingIcon} size={16} /> : null}
    </button>
  );
}
function Ghost({ children, onClick, icon }) {
  return (
    <button onClick={onClick} style={{ height: 42, padding: "0 16px", borderRadius: 10, border: "1px solid var(--border-strong)",
      background: "var(--paper)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 600,
      color: "var(--text-body)", display: "inline-flex", alignItems: "center", gap: 7 }}>
      {icon ? <Icon name={icon} size={16} /> : null}{children}
    </button>
  );
}

// The BLG "ga4-initial-sync-bar" shimmer, shown while a sync is in flight.
function SyncOverlay({ label }) {
  return (
    <div style={{ position: "fixed", inset: 0, zIndex: 120, display: "grid", placeItems: "center", padding: 20,
      background: "rgba(12,16,14,0.42)", backdropFilter: "blur(2px)" }}>
      <div className="lt-pop" style={{ width: "100%", maxWidth: 380, background: "var(--paper)", border: "1px solid var(--border-subtle)",
        borderRadius: "var(--r-xl)", boxShadow: "var(--shadow-lg)", padding: 24, textAlign: "center" }}>
        <div style={{ width: 44, height: 44, margin: "0 auto 14px", borderRadius: 12, display: "grid", placeItems: "center", background: "var(--ink-900)", color: "#fff" }}>
          <Icon name="refresh-cw" size={20} className="lt-spin" />
        </div>
        <div style={{ fontSize: 15, fontWeight: 700, color: "var(--text-strong)" }}>{label}</div>
        <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 4 }}>{t("wizard.search.pulling")}</div>
        <div style={{ marginTop: 16, height: 6, borderRadius: 999, background: "var(--ink-100)", overflow: "hidden" }}>
          <div className="fx-syncbar" style={{ height: "100%", width: "42%", borderRadius: 999,
            background: "linear-gradient(90deg, transparent, var(--accent-500), transparent)" }} />
        </div>
      </div>
    </div>
  );
}

// A generic selectable tile (used by both the publishing grid and cadence list).
function Tile({ icon, title, sub, selected, badge, badgeTone, onClick }) {
  const tones = { ok: ["var(--viz-green)", "var(--accent-50,#ecfdf5)", "var(--accent-100,#bbf7d0)"], soon: ["var(--viz-amber)", "var(--ink-50)", "var(--border-subtle)"] };
  const bt = tones[badgeTone] || tones.soon;
  return (
    <button onClick={onClick} style={{ position: "relative", textAlign: "left", padding: "14px 15px", borderRadius: 12, cursor: "pointer",
      border: "1.5px solid " + (selected ? "var(--accent-500)" : "var(--border-subtle)"),
      background: selected ? "var(--accent-50,#ecfdf5)" : "var(--paper)", boxShadow: selected ? "0 0 0 3px var(--accent-100,#d1fae5)" : "var(--shadow-xs)",
      transition: "border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out)", display: "flex", alignItems: "center", gap: 12 }}>
      <span style={{ width: 38, height: 38, flex: "none", borderRadius: 10, display: "grid", placeItems: "center",
        background: selected ? "var(--accent-500)" : "var(--ink-50)", color: selected ? "#fff" : "var(--text-muted)" }}>
        <Icon name={icon} size={18} />
      </span>
      <span style={{ flex: 1, minWidth: 0 }}>
        <span style={{ display: "flex", alignItems: "center", gap: 7 }}>
          <span style={{ fontSize: 13.5, fontWeight: 700, color: "var(--text-strong)" }}>{title}</span>
          {badge && <span style={{ fontSize: 10, fontWeight: 700, color: bt[0], background: bt[1], border: "1px solid " + bt[2], borderRadius: 999, padding: "1px 7px" }}>{badge}</span>}
        </span>
        {sub && <span style={{ display: "block", fontSize: 11.5, color: "var(--text-faint)", marginTop: 2 }}>{sub}</span>}
      </span>
      {selected && <Icon name="check-circle-2" size={18} style={{ color: "var(--accent-600,var(--accent-500))", flex: "none" }} />}
    </button>
  );
}

// -------------------------------------------------- step: Search Console ------
function SearchStep({ gsc, form }) {
  const st = gsc.data || {};
  const [syncing, setSyncing] = React.useState(false);
  const [result, setResult] = React.useState(null);

  const connectLive = () => {
    try { localStorage.setItem(LS_ACTIVE, "1"); localStorage.setItem(LS_STEP, "search"); } catch (e) {}
    window.location.href = "/oauth/google/authorize"; // resumes at ?gsc=connected
  };
  const runSync = async () => {
    setSyncing(true);
    const r = await api.post("/api/sync");
    setSyncing(false);
    const d = r.data || {};
    if (r.ok && d.ok) {
      setResult(d);
      gsc.reload();
      reloadBoot();
      toast(t("wizard.search.synced_toast", { pages: d.pages, queries: d.queries, source: t("wizard.search.live") }), { tone: "success" });
    } else if (d.error === "not_connected") {
      toast(t("wizard.search.title"), { tone: "warning" });
    } else toast(r.error || t("wizard.search.sync_failed"), { tone: "error" });
  };

  const connected = !!st.connected;
  const configured = !!st.configured;
  const last = result || st.lastSync;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      {syncing && <SyncOverlay label={t("wizard.search.syncing")} />}

      {/* Live status card */}
      <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "16px 18px", borderRadius: 14,
        border: "1px solid var(--border-subtle)", background: connected ? "var(--accent-50,#ecfdf5)" : "var(--paper)" }}>
        <span style={{ width: 44, height: 44, flex: "none", borderRadius: 11, display: "grid", placeItems: "center",
          background: connected ? "var(--viz-green)" : "var(--ink-900)", color: "#fff" }}>
          <Icon name={connected ? "check" : "line-chart"} size={20} />
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14.5, fontWeight: 700, color: "var(--text-strong)" }}>
            {connected ? t("wizard.search.connected") : configured ? t("wizard.search.connect_title") : t("wizard.search.off")}
          </div>
          <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>
            {connected
              ? (st.property ? t("wizard.search.property", { p: st.property }) : t("wizard.search.choose_property"))
              : configured
                ? t("wizard.search.grounds")
                : t("wizard.search.sample_note")}
          </div>
        </div>
        {!connected && configured && <Primary onClick={connectLive} icon="plug">{t("wizard.search.connect_btn")}</Primary>}
      </div>

      {/* Real sync — only once the property is connected. No synthetic fallback:
          performance data comes from the connected Search Console property alone. */}
      {connected && (
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14, flexWrap: "wrap",
          padding: "14px 18px", borderRadius: 14, border: "1px solid var(--border-subtle)", background: "var(--ink-50)" }}>
          <div style={{ minWidth: 0, fontSize: 12.5, color: "var(--text-muted)" }}>
            {last
              ? <>{t("wizard.search.last_sync")} <b style={{ color: "var(--text-strong)" }}>{t("wizard.search.live")}</b> · {t("wizard.search.pages_queries", { pages: last.pages, queries: last.queries })}</>
              : t("wizard.search.grounds")}
          </div>
          <Ghost onClick={runSync} icon="refresh-cw">{last ? t("wizard.search.resync") : t("perf.sync_now")}</Ghost>
        </div>
      )}

    </div>
  );
}

// ---------------------------------------------------------- the wizard --------
function Onboarding({ onNavigate, onExit }) {
  const boot = window.LTQ.boot || {};
  const project = boot.project || null;
  const ob = (project && project.onboarding) || {};

  const [form, setForm] = React.useState(() => {
    const base = {
      name: (project && project.name) || "",
      site_url: (project && project.site_url) || "",
      description: ob.description || "",
      audience: ob.audience || "",
      competitors: Array.isArray(ob.competitors) ? ob.competitors : [],
      cms: ob.cms || "",
      cadence: ob.cadence || "",
      country: ob.country || "",
      locale: (boot.user && boot.user.locale) || "",
      locale_touched: false,
    };
    try { const d = JSON.parse(localStorage.getItem(LS_FORM) || "null"); if (d && typeof d === "object") return { ...base, ...d }; } catch (e) {}
    return base;
  });
  const [stepIdx, setStepIdx] = React.useState(() => {
    try { const i = STEPS.findIndex((x) => x.id === localStorage.getItem(LS_STEP)); if (i >= 0) return i; } catch (e) {}
    return 0;
  });
  const [saving, setSaving] = React.useState(false);
  const [compInput, setCompInput] = React.useState("");
  const [phase, setPhase] = React.useState("wizard"); // wizard | scanning | deepdive
  const [domainHint, setDomainHint] = React.useState(null); // { suggestion, message } | null
  const [domainOverride, setDomainOverride] = React.useState(false); // user kept their own after a hint
  const [domainErr, setDomainErr] = React.useState(""); // inline "that's not a website" message
  const [subNote, setSubNote] = React.useState(false); // gentle "this is a subdomain" note
  const [errs, setErrs] = React.useState({}); // per-field "please fill this in" messages
  const clearErr = (k) => setErrs((e) => (e[k] ? { ...e, [k]: undefined } : e));
  // Which Business fields the site scan filled in (Phase 10 autofill) — drives
  // the "we filled these in" banner and the subtle field highlight.
  const [autofilled, setAutofilled] = React.useState(() => new Set());
  const [autofillNoteDismissed, setAutofillNoteDismissed] = React.useState(false);
  const gsc = useApi("/api/gsc/status");

  // Mark active (resume point for the GSC OAuth redirect) + persist the draft.
  React.useEffect(() => { try { localStorage.setItem(LS_ACTIVE, "1"); } catch (e) {} }, []);
  React.useEffect(() => { try { localStorage.setItem(LS_FORM, JSON.stringify(form)); } catch (e) {} }, [form]);
  React.useEffect(() => { try { localStorage.setItem(LS_STEP, STEPS[stepIdx].id); } catch (e) {} }, [stepIdx]);

  const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));
  const step = STEPS[stepIdx];
  const isLast = stepIdx === STEPS.length - 1;
  const clearDraft = () => { try { [LS_STEP, LS_FORM, LS_ACTIVE].forEach((k) => localStorage.removeItem(k)); } catch (e) {} };

  const saveFields = async (partial) => {
    setSaving(true);
    const r = await api.patch("/api/onboarding/wizard", partial);
    setSaving(false);
    if (!r.ok) toast(r.error || t("wizard.toast.save_failed"), { tone: "error" });
    return r.ok;
  };

  const addComp = () => {
    const v = compInput.trim().replace(/,$/, "");
    if (!v) return;
    if (form.competitors.includes(v)) { setCompInput(""); return; }
    if (form.competitors.length >= 10) { toast(t("wizard.toast.comp_max"), { tone: "warning" }); return; }
    set("competitors", [...form.competitors, v]);
    setCompInput("");
  };

  // Country picked -> suggest the matching platform language (never force: a
  // manual language click wins, and stays won across draft reloads).
  const pickCountry = (code) => {
    setForm((f) => ({ ...f, country: code, locale: f.locale_touched ? f.locale : localeForCountry(code) }));
  };
  const pickLocale = (id) => setForm((f) => ({ ...f, locale: id, locale_touched: true }));

  const goNext = async () => {
    if (step.id === "site") {
      // Client-side normalize + typo-catch FIRST (instant, forgives messy input).
      const d = normDomain(form.site_url);
      if (d.kind === "empty") { setDomainErr(t("wizard.site.err_empty")); return; }
      if (d.kind === "invalid") { setDomainErr(t("wizard.site.err_invalid")); return; }
      setDomainErr("");
      if (d.suggestion && !domainOverride) { setDomainHint({ suggestion: d.suggestion, message: "" }); return; }
      // Silently tidy the field to the clean domain (drops www/https/path/etc).
      const clean = d.clean;
      if (clean !== form.site_url.trim()) set("site_url", clean);
      setSubNote(!!d.isSubdomain);
      // "Did you mean …?": server reachability gate — catches resolving typos the
      // client heuristics miss. Halts once; the user accepts or keeps theirs.
      if (!domainOverride) {
        setSaving(true);
        const chk = await api.post("/api/tools/check-domain", { url: clean });
        setSaving(false);
        if (chk.ok && chk.data && chk.data.suggestion) {
          setDomainHint({ suggestion: normDomain(chk.data.suggestion).clean || chk.data.suggestion, message: chk.data.message });
          return; // wait for the user to choose
        }
      }
      setDomainHint(null);
      // The website IS the project name now (the name field was removed).
      if (!(await saveFields({ name: clean, site_url: clean }))) return;
      // Saving the URL kicked the Site Intelligence scan server-side. Hand the
      // stage to ScanThinking, which polls the autofill endpoint and returns
      // with suggestions (or null after its max-wait — never trapped).
      if (window.LTQ.ScanThinking) { setPhase("scanning"); return; }
    } else if (step.id === "business") {
      // Required-field gate: show "please fill this in" under each empty field.
      const e = {};
      if (!form.description.trim()) e.description = t("wizard.err_required");
      if (!form.audience.trim()) e.audience = t("wizard.err_required");
      if (Object.keys(e).length) {
        setErrs(e);
        try { const el = document.querySelector('textarea[aria-invalid="true"]'); if (el) { el.focus(); el.scrollIntoView({ behavior: "smooth", block: "center" }); } } catch (er) {}
        return;
      }
      setErrs({});
      const payload = { description: form.description.trim(), audience: form.audience.trim(), competitors: form.competitors };
      if (form.country) payload.country = form.country;
      if (form.locale) payload.locale = form.locale;
      if (!(await saveFields(payload))) return;
    } else if (step.id === "cadence") {
      if (form.cadence && !(await saveFields({ cadence: form.cadence }))) return;
    }
    setStepIdx((i) => Math.min(i + 1, STEPS.length - 1));
    try { document.getElementById("main") && document.getElementById("main").scrollTo({ top: 0, behavior: "smooth" }); } catch (e) {}
    // The magic moment: leaving the Business step flips the wizard (and the
    // whole app) to the chosen language. After the step advance, so the boot
    // reload can never swallow the navigation.
    if (step.id === "business" && form.locale && form.locale !== getLocale()) await setLocale(form.locale);
  };
  const goBack = () => setStepIdx((i) => Math.max(i - 1, 0));

  // ScanThinking handed back what the crawler learned. Merge into EMPTY fields
  // only — anything the user already typed always wins — then land on the
  // Business step with the banner explaining what we filled in.
  const applyAutofill = (sugg) => {
    const filled = new Set();
    if (AUTOFILL_ENABLED && sugg) {
      setForm((f) => {
        const n = { ...f };
        if (sugg.description && !f.description.trim()) { n.description = sugg.description; filled.add("description"); }
        if (sugg.audience && !f.audience.trim()) { n.audience = sugg.audience; filled.add("audience"); }
        if (sugg.country && !f.country) { n.country = sugg.country; filled.add("country"); }
        if (sugg.locale && !f.locale && !f.locale_touched) { n.locale = sugg.locale; filled.add("locale"); }
        if (Array.isArray(sugg.competitors) && sugg.competitors.length && f.competitors.length === 0) {
          n.competitors = sugg.competitors.slice(0, 10);
          filled.add("competitors");
        }
        return n;
      });
    }
    setAutofilled(filled);
    setAutofillNoteDismissed(false);
    setPhase("wizard");
    setStepIdx(1);
    try { document.getElementById("main") && document.getElementById("main").scrollTo({ top: 0 }); } catch (e) {}
  };

  // Subtle highlight for fields the scan filled in (cleared on user edit).
  const preStyle = (k) => (autofilled.has(k) ? { borderColor: "var(--accent-300,#6ee7b7)", background: "var(--accent-50,#ecfdf5)" } : {});
  const unmark = (k) => { if (autofilled.has(k)) setAutofilled((s) => { const n = new Set(s); n.delete(k); return n; }); };

  const activate = async () => {
    setSaving(true);
    setPhase("configuring"); // SQ's "we're configuring everything now" moment while we finish
    const started = Date.now();
    const patch = {};
    if (form.cms) patch.cms = form.cms;
    if (form.cadence) patch.cadence = form.cadence;
    if (Object.keys(patch).length) await api.patch("/api/onboarding/wizard", patch);
    const r = await api.post("/api/onboarding/wizard/complete");
    if (!r.ok) { setSaving(false); setPhase("wizard"); toast(r.error || t("wizard.toast.finish_failed"), { tone: "error" }); return; }
    clearDraft();
    await reloadBoot();
    // Let the moment land — hold the configuring screen a beat even if the API
    // returned instantly, so it reads as care rather than a flash.
    const elapsed = Date.now() - started;
    if (elapsed < 3600) await new Promise((res) => setTimeout(res, 3600 - elapsed));
    setSaving(false);
    toast(t("wizard.toast.welcome"), { tone: "success" });
    // The Deep Dive: while the wizard was filled in, the Site Intelligence
    // scanner crawled their site in the background — reveal what it found.
    if (form.site_url.trim() && window.LTQ.DeepDive) setPhase("deepdive");
    else onNavigate && onNavigate("dashboard");
  };
  // Skip policy (Phase 11): the wizard has no global escape hatch anymore —
  // setup IS the first-run product. Only Search Console (an external OAuth,
  // genuinely optional) offers a "skip for now", which simply advances.

  const gscState = gsc.data || {};
  const freshProject = (window.LTQ.boot || {}).project || null; // reloadBoot keeps synced_at authoritative
  const gscLabel = gscState.connected
    ? t("wizard.review.connected_live")
    : gscState.lastSync
      ? (gscState.lastSync.source === "gsc" ? t("wizard.review.connected_live") : t("wizard.review.sample_synced"))
      : (freshProject && freshProject.synced_at ? t("wizard.review.sample_synced") : t("wizard.review.not_connected"));

  const localeName = (id) => { const o = LOCALE_OPTIONS.find((x) => x.id === id); return o ? o.name : id; };

  // After "Launch workspace": SQ configures everything (P7 in-between beat).
  if (phase === "configuring") {
    return <ConfiguringStage />;
  }

  // Between Website and Business: the ScanThinking stage (Phase 9/10).
  if (phase === "scanning" && window.LTQ.ScanThinking) {
    return (
      <div style={{ position: "relative", maxWidth: 760, margin: "0 auto", paddingBottom: 20 }}>
        <style>{ONB_CSS}</style>
        <div className="ltob-aurora" aria-hidden="true"><span className="ltob-a1" /><span className="ltob-a2" /></div>
        <div style={{ position: "relative", zIndex: 1, paddingTop: 30 }}>
          <window.LTQ.ScanThinking onDone={applyAutofill} />
        </div>
      </div>
    );
  }

  // Post-activation: the Deep Dive reveal replaces the wizard entirely.
  if (phase === "deepdive" && window.LTQ.DeepDive) {
    return (
      <window.LTQ.DeepDive
        siteUrl={form.site_url}
        onFinish={() => (onNavigate ? onNavigate("dashboard") : null)}
        onResearch={(seed) => {
          window.LTQ.seedPrefill = seed; // consumed once by KeywordResearch
          onNavigate && onNavigate("research");
        }}
      />
    );
  }

  return (
    <div style={{ position: "relative", maxWidth: 760, margin: "0 auto", paddingBottom: 20 }}>
      <style>{ONB_CSS}</style>
      {/* Dual counter-drifting emerald+ink aurora behind the wizard (ambient). */}
      <div className="ltob-aurora" aria-hidden="true"><span className="ltob-a1" /><span className="ltob-a2" /></div>
      <div style={{ position: "relative", zIndex: 1 }}>
      {/* Header — no global "do this later": setup is the first-run product. */}
      <div style={{ marginBottom: 32 }}>
        <div className="lt-pill-label" style={{ marginBottom: 14 }}>{t("wizard.pill")}</div>
        <h1 style={{ fontFamily: "var(--font-display)", fontSize: 25, fontWeight: 700, letterSpacing: "-0.02em", color: "var(--text-strong)" }}>{t("wizard.heading")}</h1>
        <p style={{ fontSize: 13.5, color: "var(--text-muted)", marginTop: 7 }}>{t("wizard.tagline")}</p>
      </div>

      {/* Stepper — dots evenly distributed across the full width with the label
          centered beneath each, so the progress colour spreads instead of
          bunching. Each step owns half of the connector on either side. */}
      <div style={{ display: "flex", alignItems: "flex-start", marginBottom: 40 }}>
        {STEPS.map((s, i) => {
          const done = i < stepIdx, active = i === stepIdx, first = i === 0, last = i === STEPS.length - 1;
          const conn = (green) => ({ flex: 1, height: 2, borderRadius: 2, background: green ? "var(--accent-500)" : "var(--border-subtle)", transition: "background var(--dur-slow) var(--ease-out)" });
          return (
            <div key={s.id} style={{ flex: "1 1 0", minWidth: 0, display: "flex", flexDirection: "column", alignItems: "center", gap: 8 }}>
              <div style={{ display: "flex", alignItems: "center", width: "100%" }}>
                <span style={first ? { flex: 1 } : conn(i <= stepIdx)} />
                <button onClick={() => { if (i <= stepIdx) setStepIdx(i); }} disabled={i > stepIdx} aria-label={t("wizard.steps." + s.id)}
                  className={done ? "lt-tick-pop" : active ? "ltob-active-dot" : undefined}
                  style={{ width: 30, height: 30, flex: "none", margin: "0 5px", borderRadius: 999, display: "grid", placeItems: "center",
                    background: done ? "var(--accent-500)" : active ? "var(--accent-50,#ecfdf5)" : "var(--ink-50)",
                    color: done ? "#fff" : active ? "var(--accent-600,var(--accent-500))" : "var(--text-faint)", border: "2px solid " + (done ? "transparent" : active ? "var(--accent-500)" : "var(--border-strong)"),
                    cursor: i <= stepIdx ? "pointer" : "default", transition: "all var(--dur-base) var(--ease-out)", padding: 0 }}>
                  {done ? <Icon name="check" size={15} /> : <Icon name={s.icon} size={15} />}
                </button>
                <span style={last ? { flex: 1 } : conn(i < stepIdx)} />
              </div>
              <span className="lt-hide-mobile" style={{ fontSize: 12, fontWeight: active ? 700 : 600, color: active ? "var(--text-strong)" : "var(--text-faint)", textAlign: "center", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "100%" }}>{t("wizard.steps." + s.id)}</span>
            </div>
          );
        })}
      </div>

      {/* Card */}
      <div style={{ background: "var(--paper)", border: "1px solid var(--border-subtle)", borderRadius: "var(--r-xl)", boxShadow: "var(--shadow-sm)", padding: "34px 36px" }}>
        <div key={step.id} className="ltob-step">
          <h2 style={{ fontSize: 19, fontWeight: 700, letterSpacing: "-0.01em", color: "var(--text-strong)", marginBottom: 7 }}>{t("wizard." + step.id + ".title")}</h2>
          <p style={{ fontSize: 13.5, color: "var(--text-muted)", marginBottom: 28, lineHeight: 1.55 }}>{t("wizard." + step.id + ".sub")}</p>

          {/* ---- Website ---- */}
          {/* One field only: the site becomes the project name automatically and,
              once entered, we scan it to fill in the rest (autoscan, Phase 10). */}
          {step.id === "site" && (
            <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
              <div>
                <label style={label}>{t("wizard.site.url_label")}</label>
                <input value={form.site_url} autoFocus placeholder="yourbrand.com"
                  onChange={(e) => { set("site_url", e.target.value); setDomainErr(""); setSubNote(false); if (domainHint) setDomainHint(null); setDomainOverride(false); }}
                  onBlur={(e) => { const v = e.target.value.trim(); if (!v) return; const d = normDomain(v); if (d.kind === "invalid") setDomainErr(t("wizard.site.err_invalid")); else if (d.suggestion && !domainOverride) setDomainHint({ suggestion: d.suggestion, message: "" }); else setSubNote(!!d.isSubdomain); }}
                  style={{ ...field, ...(domainErr ? { borderColor: "var(--red-500)" } : {}) }} />
                {domainErr ? (
                  <div key={domainErr} className="ob-pop ob-shake" style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 7, fontSize: 12.5, fontWeight: 500, color: "var(--red-500)" }}>
                    <Icon name="alert-circle" size={14} style={{ flex: "none" }} />{domainErr}
                  </div>
                ) : domainHint ? (
                  <div className="ob-pop" style={{ marginTop: 9, display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", padding: "10px 12px", borderRadius: 10, border: "1px solid var(--accent-200,var(--accent-100))", background: "var(--accent-50,#ecfdf5)" }}>
                    <Icon name="lightbulb" size={15} style={{ color: "var(--accent-600,var(--accent-500))", flex: "none" }} />
                    <span style={{ fontSize: 13, color: "var(--text-body)" }}>
                      {t("wizard.site.did_you_mean")} <b style={{ color: "var(--text-strong)" }}>{domainHint.suggestion}</b>?
                    </span>
                    <span style={{ display: "inline-flex", gap: 7, marginLeft: "auto" }}>
                      <button onClick={() => { set("site_url", domainHint.suggestion); setDomainHint(null); setDomainErr(""); setDomainOverride(true); }} style={{ height: 30, padding: "0 12px", borderRadius: 8, border: "none", background: "var(--accent-500)", color: "#fff", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 12.5, fontWeight: 600 }}>{t("wizard.site.use_suggestion")}</button>
                      <button onClick={() => { setDomainHint(null); setDomainOverride(true); }} style={{ height: 30, padding: "0 12px", borderRadius: 8, border: "1px solid var(--border-strong)", background: "var(--paper)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 12.5, fontWeight: 600, color: "var(--text-body)" }}>{t("wizard.site.keep_mine")}</button>
                    </span>
                  </div>
                ) : subNote ? (
                  <div className="ob-pop" style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 7, fontSize: 12.5, color: "var(--accent-700)" }}>
                    <Icon name="git-branch" size={14} style={{ flex: "none" }} />{t("wizard.site.subdomain_note")}
                  </div>
                ) : (
                  <div style={hint}>{t("wizard.site.url_hint")}</div>
                )}
              </div>
            </div>
          )}

          {/* ---- Business ---- */}
          {step.id === "business" && (
            <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
              {/* The scan filled these in — say so, honestly and dismissibly. */}
              {autofilled.size > 0 && !autofillNoteDismissed && (
                <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 13px", borderRadius: 10,
                  border: "1px solid var(--accent-200,var(--accent-100))", background: "var(--accent-50,#ecfdf5)" }}>
                  <Icon name="sparkles" size={15} style={{ color: "var(--accent-600,var(--accent-500))", flex: "none" }} />
                  <span style={{ flex: 1, fontSize: 13, color: "var(--text-body)" }}>{t("wizard.autofill.banner")}</span>
                  <button onClick={() => setAutofillNoteDismissed(true)} aria-label={t("chrome.close")}
                    style={{ width: 26, height: 26, flex: "none", display: "grid", placeItems: "center", border: "none", background: "transparent", cursor: "pointer", color: "var(--text-faint)", borderRadius: 8 }}>
                    <Icon name="x" size={14} />
                  </button>
                </div>
              )}
              <div>
                <label style={label}>{t("wizard.business.desc_label")}</label>
                <textarea value={form.description} aria-invalid={!!errs.description} onChange={(e) => { set("description", e.target.value); unmark("description"); clearErr("description"); }} rows={3} placeholder={t("wizard.business.desc_ph")} style={{ ...field, resize: "vertical", ...preStyle("description"), ...(errs.description ? { borderColor: "var(--red-500)" } : {}) }} />
                {errs.description && <div key={errs.description} className="ob-pop ob-shake" style={{ marginTop: 7, display: "flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 500, color: "var(--red-500)" }}><Icon name="alert-circle" size={13} style={{ flex: "none" }} />{errs.description}</div>}
              </div>
              <div>
                <label style={label}>{t("wizard.business.audience_label")}</label>
                <textarea value={form.audience} aria-invalid={!!errs.audience} onChange={(e) => { set("audience", e.target.value); unmark("audience"); clearErr("audience"); }} rows={2} placeholder={t("wizard.business.audience_ph")} style={{ ...field, resize: "vertical", ...preStyle("audience"), ...(errs.audience ? { borderColor: "var(--red-500)" } : {}) }} />
                {errs.audience && <div key={errs.audience} className="ob-pop ob-shake" style={{ marginTop: 7, display: "flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 500, color: "var(--red-500)" }}><Icon name="alert-circle" size={13} style={{ flex: "none" }} />{errs.audience}</div>}
              </div>
              <div data-tour="wizard-country">
                <label style={label}>{t("wizard.business.country_label")}</label>
                <CountrySelect value={form.country} onChange={pickCountry} placeholder={t("wizard.business.country_ph")} />
                <div style={hint}>{t("wizard.business.country_hint")}</div>
              </div>
              <div data-tour="wizard-language">
                <label style={label}>{t("wizard.business.language_label")}</label>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
                  {LOCALE_OPTIONS.map((o) => {
                    const selected = (form.locale || "en") === o.id;
                    return (
                      <button key={o.id} data-locale-option={o.id} onClick={() => pickLocale(o.id)}
                        style={{ height: 38, padding: "0 14px", borderRadius: 10, cursor: "pointer",
                          border: "1.5px solid " + (selected ? "var(--accent-500)" : "var(--border-subtle)"),
                          background: selected ? "var(--accent-50,#ecfdf5)" : "var(--paper)",
                          fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: selected ? 700 : 600,
                          color: "var(--text-strong)", display: "inline-flex", alignItems: "center", gap: 7 }}>
                        {selected && <Icon name="check" size={14} style={{ color: "var(--accent-600,var(--accent-500))" }} />}
                        {o.name}
                      </button>
                    );
                  })}
                </div>
                <div style={hint}>{t("wizard.business.language_hint")}</div>
              </div>
              <div>
                <label style={label}>{t("wizard.business.comp_label")} <span style={{ color: "var(--text-faint)", fontWeight: 500 }}>{t("wizard.business.comp_optional")}</span></label>
                <div style={{ display: "flex", gap: 8 }}>
                  <input value={compInput} onChange={(e) => setCompInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === ",") { e.preventDefault(); addComp(); } }} placeholder="competitor.com" style={{ ...field, minHeight: 42 }} />
                  <Ghost onClick={addComp} icon="plus">{t("wizard.business.comp_add")}</Ghost>
                </div>
                {form.competitors.length > 0 && (
                  <div style={{ display: "flex", flexWrap: "wrap", gap: 7, marginTop: 10 }}>
                    {form.competitors.map((c) => (
                      <span key={c} style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "4px 6px 4px 11px", borderRadius: 999, background: "var(--ink-50)", border: "1px solid var(--border-subtle)", fontSize: 12.5, fontWeight: 600, color: "var(--text-body)" }}>
                        {c}
                        <button onClick={() => set("competitors", form.competitors.filter((x) => x !== c))} aria-label={t("wizard.business.comp_remove", { name: c })} style={{ width: 18, height: 18, display: "grid", placeItems: "center", border: "none", background: "transparent", cursor: "pointer", color: "var(--text-faint)", borderRadius: 999 }}><Icon name="x" size={13} /></button>
                      </span>
                    ))}
                  </div>
                )}
                <div style={hint}>{t("wizard.business.comp_hint")}</div>
              </div>
            </div>
          )}

          {/* ---- Search Console ---- */}
          {step.id === "search" && <SearchStep gsc={gsc} form={form} />}

          {/* ---- Cadence — three light segmented buttons, not heavy tiles. ---- */}
          {step.id === "cadence" && (
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              <div style={{ display: "flex", gap: 10 }}>
                {[["daily", "flame"], ["weekly", "calendar"], ["manual", "hand"]].map(([id, icon]) => {
                  const on = form.cadence === id;
                  return (
                    <button key={id} onClick={() => set("cadence", id)} style={{
                      flex: "1 1 0", display: "flex", flexDirection: "column", alignItems: "center", gap: 9, padding: "18px 12px", borderRadius: 12, cursor: "pointer",
                      border: "1.5px solid " + (on ? "var(--accent-500)" : "var(--border-subtle)"),
                      background: on ? "var(--accent-50,#ecfdf5)" : "var(--paper)",
                      boxShadow: on ? "0 0 0 3px var(--accent-100,#d1fae5)" : "var(--shadow-xs)",
                      transition: "border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out)", fontFamily: "var(--font-sans)" }}>
                      <span style={{ width: 34, height: 34, borderRadius: 10, display: "grid", placeItems: "center", background: on ? "var(--accent-500)" : "var(--ink-50)", color: on ? "#fff" : "var(--text-muted)" }}><Icon name={icon} size={17} /></span>
                      <span style={{ fontSize: 13.5, fontWeight: 600, color: on ? "var(--text-strong)" : "var(--text-body)" }}>{t("wizard.cadence." + id)}</span>
                    </button>
                  );
                })}
              </div>
              <div style={hint}>{t("wizard.cadence.hint")}</div>
            </div>
          )}

        </div>

        {/* Footer nav. Search Console is the ONE optional step (external
            OAuth), so it carries an explicit "skip for now" beside Continue. */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, marginTop: 30, paddingTop: 24, borderTop: "1px solid var(--border-subtle)" }}>
          {stepIdx > 0 ? <Ghost onClick={goBack} icon="arrow-left">{t("wizard.back")}</Ghost> : <span />}
          <span style={{ display: "inline-flex", alignItems: "center", gap: 10 }}>
            {step.id === "search" && !(gsc.data || {}).connected && (
              <button onClick={goNext} style={{ height: 42, padding: "0 14px", borderRadius: 10, border: "none", background: "transparent", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, color: "var(--text-faint)" }}>{t("wizard.skip_for_now")}</button>
            )}
            {isLast
              ? <Primary onClick={activate} busy={saving} icon="rocket">{t("wizard.activate")}</Primary>
              : <Primary onClick={goNext} busy={saving} trailingIcon="arrow-right">{t("wizard.continue")}</Primary>}
          </span>
        </div>
      </div>
      </div>
    </div>
  );
}

function ReviewRow({ icon, k, v, sub, onEdit, last }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 15px", borderBottom: last ? "none" : "1px solid var(--border-subtle)" }}>
      <span style={{ width: 30, height: 30, flex: "none", borderRadius: 8, display: "grid", placeItems: "center", background: "var(--ink-50)", color: "var(--text-muted)" }}><Icon name={icon} size={15} /></span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 11.5, fontWeight: 700, color: "var(--text-faint)", textTransform: "uppercase", letterSpacing: "0.04em" }}>{k}</div>
        <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--text-strong)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{v}{sub ? <span style={{ color: "var(--text-faint)", fontWeight: 500 }}> · {sub}</span> : null}</div>
      </div>
      <button onClick={onEdit} style={{ border: "none", background: "transparent", cursor: "pointer", color: "var(--accent-600,var(--accent-500))", fontSize: 12.5, fontWeight: 600, fontFamily: "var(--font-sans)", flex: "none" }}>{t("wizard.edit")}</button>
    </div>
  );
}

window.LTQ.Onboarding = Onboarding;
})();
