// EC2 Spot Launcher — step-by-step: GPU → Size → Image → Config → Review

const { Panel, Bar, Pill, MoneyBig } = window.SEIS_PRIM;

const _ADJ  = ["fast","dark","cold","bold","deep","wild","iron","soft","raw","sharp","dry","dim","hot","slim","nova","arc","void","flux","peak","echo"];
const _NOUN = ["bake","forge","pulse","node","run","core","shard","burst","wave","draft","batch","fire","loop","spark","probe","task","build","push","job","scan"];
const randName = () => `${_ADJ[Math.random()*_ADJ.length|0]}-${_NOUN[Math.random()*_NOUN.length|0]}`;
const GPU_COLORS = {
  test:        "var(--gpu-test)",
  hermes:     "var(--gpu-hermes)",
  hephaestus: "var(--gpu-hephaestus)",
  apollo:     "var(--gpu-apollo)",
  selene:     "var(--gpu-selene)",
  artemis:    "var(--gpu-artemis)",
  athena:     "var(--gpu-athena)",
  prometheus: "var(--gpu-prometheus)",
  ares:       "var(--gpu-ares)",
  zeus:       "var(--gpu-zeus)",
  kronos:     "var(--gpu-kronos)",
};

const GPU_GROUPS = [
  { key: "test",        name: "SAND",        gpu: "No GPU · testing only", vram: "—",         cuda: "—",    families: ["t3"],                      color: GPU_COLORS.test,        test: true },
  { key: "hermes",     name: "HERMES",     gpu: "NVIDIA T4",            vram: "16 GB",      cuda: "7.5",  families: ["g4dn"],                    color: GPU_COLORS.hermes },
  { key: "hephaestus", name: "HEPHAESTUS", gpu: "AMD Radeon V520",      vram: "8 GB",       cuda: "—",    families: ["g4ad"],                    color: GPU_COLORS.hephaestus },
  { key: "apollo",     name: "APOLLO",     gpu: "NVIDIA A10G",          vram: "24 GB",      cuda: "8.6",  families: ["g5"],                      color: GPU_COLORS.apollo },
  { key: "selene",     name: "SELENE",     gpu: "NVIDIA T4g",           vram: "16 GB",      cuda: "7.5",  families: ["g5g"],                     color: GPU_COLORS.selene },
  { key: "artemis",    name: "ARTEMIS",    gpu: "NVIDIA L4",            vram: "24 GB",      cuda: "8.9",  families: ["g6","g6f","gr6","gr6f"],    color: GPU_COLORS.artemis },
  { key: "athena",     name: "ATHENA",     gpu: "NVIDIA L40S",          vram: "48 GB",      cuda: "8.9",  families: ["g6e"],                     color: GPU_COLORS.athena },
  { key: "prometheus", name: "PROMETHEUS", gpu: "NVIDIA RTX PRO 6000",  vram: "96 GB",      cuda: "10.0", families: ["g7e"],                     color: GPU_COLORS.prometheus },
  { key: "ares",       name: "ARES",       gpu: "NVIDIA V100",          vram: "16–32 GB",   cuda: "7.0",  families: ["p3"],                      color: GPU_COLORS.ares },
  { key: "zeus",       name: "ZEUS",       gpu: "NVIDIA A100",          vram: "320–640 GB", cuda: "8.0",  families: ["p4d","p4de"],              color: GPU_COLORS.zeus },
  { key: "kronos",     name: "KRONOS",     gpu: "NVIDIA H100",          vram: "640 GB",     cuda: "9.0",  families: ["p5","p5e"],                color: GPU_COLORS.kronos },
];

const REGIONS = ["us-east-1", "us-east-2", "us-west-1", "us-west-2"];
const REGION_LIMITS = { "us-west-1": "Limited: g4dn + p5 only" };
const STEP_LABELS = ["GPU", "SIZE", "IMAGE", "CONFIG", "REVIEW"];

const AMI_RE = /^ami-[0-9a-f]{8,17}$/;
const SESSION_KEY = "seis_draft";

function EC2Launcher({ open, onClose, instanceTypes, budget, regionLatency = {} }) {
  const free     = (budget?.monthly || 600) - (budget?.used || 0);
  const allTypes = instanceTypes && instanceTypes.length ? instanceTypes : [];

  const [step,        setStep]        = React.useState(1);
  const [gpuKey,      setGpuKey]      = React.useState(null);
  const [instanceId,  setInstanceId]  = React.useState("");
  const [platform,    setPlatform]    = React.useState("linux");
  const [amiId,       setAmiId]       = React.useState("");
  const [hours,       setHours]       = React.useState(8);
  const [durMode,     setDurMode]     = React.useState("slider"); // "slider" | "picker"
  const [durD,        setDurD]        = React.useState(0);
  const [durH,        setDurH]        = React.useState(8);
  const [durM,        setDurM]        = React.useState(0);
  const [durS,        setDurS]        = React.useState(0);
  const [name,        setName]        = React.useState(randName);
  const [region,      setRegion]      = React.useState("us-east-1");
  const [az,          setAz]          = React.useState("");
  const [reason,      setReason]      = React.useState("");

  const [diskGb,      setDiskGb]      = React.useState(200);

  const [amis,        setAmis]        = React.useState([]);
  const [amisLoading, setAmisLoading] = React.useState(false);
  const [amisError,   setAmisError]   = React.useState("");
  const [customAmi,   setCustomAmi]   = React.useState("");
  const [compare,     setCompare]     = React.useState(null);
  const [quota,       setQuota]       = React.useState(null);
  const latency = regionLatency;
  const [winAzPrices, setWinAzPrices] = React.useState({});

  const [submitting, setSubmitting] = React.useState(false);
  const [stage,      setStage]      = React.useState("form");
  const [errorMsg,   setErrorMsg]   = React.useState("");
  const [requestId,  setRequestId]  = React.useState("");

  const [amiValid, setAmiValid] = React.useState(null);
  const modalRef = React.useRef(null);
  const escCountRef = React.useRef(0);

  // AMI cache keyed by region
  const _amiCache = React.useRef({});

  // Restore draft from sessionStorage
  React.useEffect(() => {
    if (!open) return;
    try {
      const saved = sessionStorage.getItem(SESSION_KEY);
      if (saved) {
        const d = JSON.parse(saved);
        if (d.gpuKey)      setGpuKey(d.gpuKey);
        if (d.instanceId)  setInstanceId(d.instanceId);
        if (d.platform)    setPlatform(d.platform);
        if (d.region)      setRegion(d.region);
        if (d.hours)       setHours(d.hours);
        if (d.name)        setName(d.name);
        if (d.reason)      setReason(d.reason);
      }
    } catch {}
  }, [open]);

  // Persist draft to sessionStorage
  React.useEffect(() => {
    if (!open || stage !== "form") return;
    try {
      sessionStorage.setItem(SESSION_KEY, JSON.stringify({
        gpuKey, instanceId, platform, region, hours, name, reason,
      }));
    } catch {}
  }, [gpuKey, instanceId, platform, region, hours, name, reason, stage, open]);

  // Clear draft on submit or close
  const clearDraft = React.useCallback(() => {
    try { sessionStorage.removeItem(SESSION_KEY); } catch {}
  }, []);

  // AMIs — reload on region or open (with cache)
  React.useEffect(() => {
    if (!open) return;
    const cache = _amiCache.current;
    if (cache[region]) {
      setAmis(cache[region]);
      const pf = cache[region].filter(a => a.platform === platform);
      if (pf.length) setAmiId(pf[0].id);
      else if (cache[region].length) setAmiId(cache[region][0].id);
      return;
    }
    setAmisLoading(true);
    setAmisError("");
    setAmiId("");
    fetch(`/api/ec2/amis?region=${region}`, { credentials: "include" })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(data => {
        cache[region] = data;
        setAmis(data);
        const pf = data.filter(a => a.platform === platform);
        if (pf.length) setAmiId(pf[0].id);
        else if (data.length) setAmiId(data[0].id);
      })
      .catch(() => setAmisError("Failed to load AMIs"))
      .finally(() => setAmisLoading(false));
  }, [region, open]);

  // Compare + quota
  React.useEffect(() => {
    if (!open) return;
    fetch(`/api/ec2/prices/compare`, { credentials: "include" })
      .then(r => r.ok ? r.json() : null).then(d => setCompare(d)).catch(() => {});
    fetch(`/api/ec2/quota?region=${region}`, { credentials: "include" })
      .then(r => r.ok ? r.json() : null).then(d => setQuota(d)).catch(() => {});
  }, [open, region]);

  // Auto-select cheapest region + AZ when compare data arrives for a new instanceId
  const _autoSelectedFor = React.useRef(null);
  React.useEffect(() => {
    if (!compare || !instanceId || _autoSelectedFor.current === instanceId) return;
    const candidates = REGIONS.map(reg => ({
      reg,
      current: compare[reg]?.[instanceId]?.current ?? Infinity,
      az:      compare[reg]?.[instanceId]?.az ?? "",
    })).filter(r => r.current < Infinity && r.az);
    if (!candidates.length) return;
    const best = candidates.reduce((a, b) => a.current < b.current ? a : b);
    _autoSelectedFor.current = instanceId;
    setRegion(best.reg);
    setAz(best.az);
  }, [compare, instanceId]);

  // Disk default when instance changes
  React.useEffect(() => {
    if (!instanceId) return;
  }, [instanceId]);

  // Windows AZ prices
  React.useEffect(() => {
    if (platform !== "windows" || !instanceId || !open) return;
    setWinAzPrices({});
    fetch(`/api/ec2/prices/az?instance_type=${instanceId}&region=${region}&platform=windows`, { credentials: "include" })
      .then(r => r.ok ? r.json() : {})
      .then(d => setWinAzPrices(d))
      .catch(() => {});
  }, [platform, instanceId, region, open]);

  // Focus trap + keyboard
  React.useEffect(() => {
    if (!open) return;
    const handleKey = (e) => {
      if (e.key === "Escape") {
        e.preventDefault();
        escCountRef.current += 1;
        if (escCountRef.current === 1) {
          setTimeout(() => { escCountRef.current = 0; }, 400);
        }
        if (stage !== "form" || step === 1) {
          handleClose();
        } else {
          setStep(s => s - 1);
        }
      }
      if (e.key === "Enter" && stage === "form" && !e.target.closest("input") && !e.target.closest("select")) {
        if (canAdvance) handleNext();
      }
      if (e.key === "Enter" && e.target.closest("input") && step === 4 && reason.trim() && name.trim()) {
        e.preventDefault();
        setStep(5);
      }
    };
    document.addEventListener("keydown", handleKey);
    modalRef.current?.focus();
    return () => document.removeEventListener("keydown", handleKey);
  }, [open, step, stage, canAdvance]);

  const formatDuration = (h) => {
    const totalS = Math.round(h * 3600);
    const d  = Math.floor(totalS / 86400);
    const hh = Math.floor((totalS % 86400) / 3600);
    const mm = Math.floor((totalS % 3600) / 60);
    const ss = totalS % 60;
    if (d > 0)  return `${d}D ${hh}H ${mm}M`;
    if (ss > 0) return `${hh}H ${mm}M ${ss}S`;
    if (mm > 0) return `${hh}H ${mm}M`;
    return `${hh}H`;
  };

  React.useEffect(() => {
    if (durMode !== "picker") return;
    setHours(+(durD * 24 + durH + durM / 60 + durS / 3600).toFixed(4));
  }, [durMode, durD, durH, durM, durS]);

  if (!open) return null;

  // ── Derived ──────────────────────────────────────────────────────────────────

  const selectedGroup = GPU_GROUPS.find(g => g.key === gpuKey) || null;
  const filteredTypes = selectedGroup
    ? allTypes.filter(t => selectedGroup.families.includes(t.id.split(".")[0]))
        .sort((a, b) => (a.vcpu || 0) - (b.vcpu || 0))
    : [];

  const inst = allTypes.find(i => i.id === instanceId) || { spot7d: 0, spotCurrent: 0, spotByAz: {}, vcpu: 0 };

  const azMap        = platform === "windows" ? winAzPrices : (inst.spotByAz || {});
  const azOptions    = Object.entries(azMap).sort((a, b) => a[1].current - b[1].current);
  const effectiveAz  = az || (azOptions[0]?.[0] ?? "");
  const azData       = azMap[effectiveAz] || {};
  const spotPrice    = azData.current || inst.spotCurrent || inst.spot7d || 0;
  const cost         = spotPrice * hours;
  const HOURS_CAP    = 336;
  const maxHrs       = Math.min(HOURS_CAP, spotPrice > 0 ? Math.floor(free / spotPrice) : HOURS_CAP);
  const overBudget   = free <= 0 || cost > free;
  const filteredAmis  = amis.filter(a => a.platform === platform);
  const isCustomAmi   = amiId === "__custom__";
  const customValid   = isCustomAmi ? AMI_RE.test(customAmi.trim()) : true;
  const effectiveAmiId = isCustomAmi ? customAmi.trim() : amiId;
  const ami           = isCustomAmi
    ? (customAmi.trim() ? { id: customAmi.trim(), name: customAmi.trim(), os: platform, framework: "Custom" } : null)
    : amis.find(a => a.id === amiId) || null;

  const priceRangeFor = (group) => {
    const types  = allTypes.filter(t => group.families.includes(t.id.split(".")[0]));
    const prices = types.map(t => t.spot7d).filter(p => p > 0);
    if (!prices.length) return null;
    return `$${Math.min(...prices).toFixed(2)}–$${Math.max(...prices).toFixed(2)}/h`;
  };

  const canAdvance = [!!gpuKey, !!instanceId, !!(isCustomAmi ? customValid && customAmi.trim() : amiId), !!reason.trim() && !!name.trim(), false][step - 1];

  const latAvg   = (reg) => { const s = latency[reg]; return s && s.length ? Math.round(s.reduce((a,b)=>a+b,0)/s.length) : null; };
  const latCount  = (reg) => (latency[reg] || []).length;
  const LAT_MAX   = 10;
  const msColor = (ms) => ms == null ? "var(--muted)" : ms < 60 ? "var(--lat-good)" : ms < 150 ? "var(--lat-warn)" : "var(--lat-bad)";

  const _regionCandidates = compare && instanceId
    ? REGIONS.map(reg => ({ reg, current: compare[reg]?.[instanceId]?.current ?? Infinity, az: compare[reg]?.[instanceId]?.az ?? "" }))
        .filter(r => r.current < Infinity)
    : [];
  const cheapestRegion = _regionCandidates.length
    ? _regionCandidates.reduce((a, b) => a.current < b.current ? a : b).reg
    : null;
  const cheapestAzOf   = (reg) => compare?.[reg]?.[instanceId]?.az ?? null;
  const cheapestAzLocal = azOptions.length ? azOptions[0][0] : null;

  // ── Handlers ─────────────────────────────────────────────────────────────────

  const retryAmis = () => {
    setAmisLoading(true);
    setAmisError("");
    fetch(`/api/ec2/amis?region=${region}`, { credentials: "include" })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => {
        setAmis(d);
        const pf = d.filter(a => a.platform === platform);
        if (pf.length) setAmiId(pf[0].id);
      })
      .catch(() => setAmisError("Failed to load AMIs"))
      .finally(() => setAmisLoading(false));
  };

  const handleClose = () => {
    clearDraft();
    setStage("form"); setErrorMsg(""); onClose();
  };

  const handleBackdropClick = (e) => {
    if (e.target !== e.currentTarget) return;
    if (step > 1 && stage === "form") {
      if (!window.confirm("Discard your launcher draft?")) return;
    }
    handleClose();
  };

  const handleReset = () => {
    setStep(1); setGpuKey(null); setInstanceId(""); setPlatform("linux");
    setAmiId(""); setAz(""); setReason(""); setStage("form"); setErrorMsg("");
    clearDraft();
  };

  const handleNext = () => {
    if (step === 1 && gpuKey)                        setStep(2);
    else if (step === 2 && instanceId)               setStep(3);
    else if (step === 3 && amiId)                    setStep(4);
    else if (step === 4 && reason.trim() && name.trim()) setStep(5);
  };

  const submit = async () => {
    if (!reason.trim() || !amiId || !instanceId || overBudget || submitting) return;
    setSubmitting(true);
    try {
      const r = await fetch("/api/ec2/launch", {
        method: "POST",
        credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          instanceType: instanceId,
          amiId: effectiveAmiId,
          amiName: ami ? ami.name : "",
          name,
          region,
          az: effectiveAz || undefined,
          hours,
          reason: reason.trim(),
          platform,
          diskGb,
          rootDeviceName: ami?.rootDeviceName || undefined,
        }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => ({}));
        setErrorMsg(body.detail || `Error ${r.status}`);
        setStage("error");
      } else {
        const data = await r.json();
        setRequestId(data.requestId || "");
        setStage("success");
        clearDraft();
      }
    } catch {
      setErrorMsg("Network error — try again");
      setStage("error");
    } finally {
      setSubmitting(false);
    }
  };

  // ── Render ───────────────────────────────────────────────────────────────────

  return (
    <div className="modal-back" onClick={handleBackdropClick}>
      <div className="modal" ref={modalRef} role="dialog" aria-modal="true" aria-label="Request Spot Instance"
           tabIndex={-1} onClick={e => e.stopPropagation()}>

        {/* Header */}
        <div className="panel-head">
          <span className="idx">EC2-LNCH-001</span>
          <span className="title row gap-8" style={{ alignItems: "center" }}>
            REQUEST · SPOT INSTANCE
            {selectedGroup && <span style={{ color: selectedGroup.color, fontStyle: "italic" }}>· {selectedGroup.name}</span>}
            {instanceId    && <span style={{ color: "var(--muted)", fontWeight: 400 }}>· {instanceId}</span>}
            {stage === "form" && <Pill dotColor="pulse">DRAFT</Pill>}
          </span>
          <span className="right row gap-12">
            {step > 1 && stage === "form" && (
              <button onClick={handleReset} className="micro" style={{ cursor: "pointer", color: "var(--muted)" }}>RESET</button>
            )}
            <button onClick={handleClose} className="micro" style={{ cursor: "pointer" }}>CLOSE ✕</button>
          </span>
        </div>

        {/* Step indicator */}
        {stage === "form" && (
          <div className="row micro" style={{ justifyContent: "center", gap: 0, padding: "10px 20px", borderBottom: "1px solid var(--rule-soft)", flexWrap: "wrap" }}>
            {STEP_LABELS.map((label, i) => {
              const n    = i + 1;
              const done = step > n;
              const cur  = step === n;
              return (
                <React.Fragment key={n}>
                  {i > 0 && <span style={{ color: "var(--rule-soft)", margin: "0 10px" }}>——</span>}
                  <span
                    style={{ color: done ? "var(--accent)" : cur ? "var(--ink)" : "var(--muted)", fontWeight: cur ? 700 : 400, cursor: done ? "pointer" : "default" }}
                    onClick={() => { if (done) setStep(n); }}
                  >
                    {done ? "✓" : `0${n}`} · {label}
                  </span>
                </React.Fragment>
              );
            })}
          </div>
        )}

        {/* ── FORM ── */}
        {stage === "form" && (
          <>
            <div className="panel-body" style={{ minHeight: 440 }}>

              {/* STEP 1 — GPU Architecture */}
              {step === 1 && (
                <div style={{ maxWidth: 960, margin: "0 auto" }}>
                  <div className="micro" style={{ marginBottom: 16 }}>01 · SELECT GPU ARCHITECTURE</div>
                  <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 10 }}>
                    {GPU_GROUPS.map(g => {
                      const range = priceRangeFor(g);
                      const avail = allTypes.some(t => g.families.includes(t.id.split(".")[0]));
                      return (
                        <button key={g.key}
                          onClick={() => { if (!avail) return; setGpuKey(g.key); setInstanceId(""); setAz(""); setStep(2); }}
                          aria-checked={gpuKey === g.key}
                          style={{
                            border: `1px solid ${g.color}`,
                            background: gpuKey === g.key ? "var(--bg-2)" : "transparent",
                            padding: "16px 14px",
                            cursor: avail ? "pointer" : "default",
                            textAlign: "left",
                            opacity: avail ? 1 : 0.4,
                            transition: "background 120ms",
                          }}
                          onMouseEnter={e => { if (avail) e.currentTarget.style.background = "var(--bg-2)"; }}
                          onMouseLeave={e => { if (gpuKey !== g.key) e.currentTarget.style.background = "transparent"; }}
                        >
                          <div style={{ fontWeight: 700, fontSize: 13, letterSpacing: "0.08em", color: g.color }}>{g.name}</div>
                          {g.test && <div className="tiny" style={{ color: g.color, marginTop: 3, fontWeight: 600 }}>NO GPU · SANDBOX</div>}
                          <div className="tiny" style={{ marginTop: g.test ? 2 : 5, color: "var(--ink-2)" }}>{g.gpu}</div>
                          {!g.test && <div className="tiny" style={{ color: "var(--muted)", marginTop: 2 }}>{g.vram} · CUDA {g.cuda}</div>}
                          <div className="tiny" style={{ color: "var(--muted)", marginTop: 2 }}>{g.families.join(", ")}</div>
                          {range && <div className="num tiny" style={{ marginTop: 8, fontWeight: 600 }}>{range}</div>}
                        </button>
                      );
                    })}
                  </div>
                  <div className="micro" style={{ color: "var(--muted)", marginTop: 12 }}>CLICK A CARD TO CONTINUE · DIMMED = NO INSTANCES IN CURRENT DATA FOR {region}</div>
                </div>
              )}

              {/* STEP 2 — Machine Size */}
              {step === 2 && (
                <div style={{ maxWidth: 960, margin: "0 auto" }}>
                  {selectedGroup && (
                    <div style={{ border: `1px solid ${selectedGroup.color}`, padding: "10px 16px", marginBottom: 16, display: "flex", alignItems: "center", gap: 20 }}>
                      <div style={{ fontWeight: 700, color: selectedGroup.color, letterSpacing: "0.08em", fontSize: 13 }}>{selectedGroup.name}</div>
                      <div className="tiny" style={{ color: "var(--muted)" }}>{selectedGroup.test ? "NO GPU · SANDBOX · <$0.02/hr" : `${selectedGroup.gpu} · ${selectedGroup.vram} · CUDA ${selectedGroup.cuda}`}</div>
                    </div>
                  )}
                  <div className="micro" style={{ marginBottom: 10 }}>02 · SELECT MACHINE SIZE <span style={{ color: "var(--muted)" }}>SORTED BY vCPU · LINUX SPOT PRICE</span></div>
                  <div style={{ border: "1px solid var(--ink)", maxHeight: "calc(90vh - 340px)", overflow: "auto" }}>
                    {filteredTypes.length === 0 && (
                      <div className="tiny" style={{ color: "var(--muted)", padding: "16px" }}>NO INSTANCES AVAILABLE IN {region}</div>
                    )}
                    {filteredTypes.map(i => (
                      <label key={i.id} style={{
                        display: "grid",
                        gridTemplateColumns: "auto 1fr auto auto",
                        alignItems: "center",
                        gap: 16,
                        padding: "12px 16px",
                        borderBottom: "1px solid var(--rule-soft)",
                        cursor: "pointer",
                        background: i.id === instanceId ? "var(--bg-3)" : "transparent",
                      }}>
                        <input type="radio" name="inst" checked={i.id === instanceId}
                          onChange={() => { setInstanceId(i.id); setAz(""); }} />
                        <div>
                          <div style={{ fontWeight: 500 }}>{i.id}</div>
                          <div className="tiny" style={{ color: "var(--muted)", marginTop: 2 }}>{i.gpu} · {i.vcpu} vCPU · {i.ram}</div>
                        </div>
                        <div className="num tiny" style={{ textAlign: "right" }}>
                          <div style={{ fontSize: 13, fontWeight: 500 }}>${i.spot7d.toFixed(2)}<span style={{ color: "var(--muted)", fontSize: 9 }}>/h 7d</span></div>
                          <div style={{ color: "var(--muted)" }}>now ${(i.spotCurrent || 0).toFixed(2)}</div>
                        </div>
                        {i.id === instanceId && (
                          <button className="btn solid" style={{ padding: "4px 12px", fontSize: 10 }} onClick={() => setStep(3)}>
                            SELECT <span className="arr">→</span>
                          </button>
                        )}
                        {i.id !== instanceId && <div style={{ width: 72 }} />}
                      </label>
                    ))}
                  </div>
                </div>
              )}

              {/* DISK SIZE (shown when instance is selected in step 2) */}
              {step === 2 && instanceId && (
                <div style={{ maxWidth: 960, margin: "12px auto 0" }}>
                  <div style={{ border: "1px solid var(--rule-soft)", padding: "12px 16px", display: "flex", alignItems: "center", gap: 20 }}>
                    <div className="micro" style={{ color: "var(--muted)", whiteSpace: "nowrap" }}>ROOT DISK · gp3</div>
                    {(() => {
                      const t = allTypes.find(i => i.id === instanceId);
                      const hasStore = t?.storage?.supported && t?.storage?.totalGb > 0;
                      return (
                        <>
                           <input type="number" min="200" max="16384" value={diskGb}
                             onChange={e => setDiskGb(Math.max(200, Math.min(16384, parseInt(e.target.value) || 200)))}
                            style={{ width: 80, textAlign: "right" }} />
                          <span className="tiny" style={{ color: "var(--muted)" }}>GB</span>
                          {hasStore && (
                            <span className="tiny" style={{ color: "var(--muted)" }}>
                              + {t.storage.totalGb} GB NVMe instance store (ephemeral)
                            </span>
                          )}
                          {!hasStore && (
                            <span className="tiny" style={{ color: "var(--muted)" }}>EBS-only · no instance store</span>
                          )}
                        </>
                      );
                    })()}
                  </div>
                </div>
              )}

              {/* STEP 3 — OS + AMI */}
              {step === 3 && (
                <div style={{ maxWidth: 680, margin: "0 auto" }}>
                  <div className="micro" style={{ marginBottom: 16 }}>03 · OPERATING SYSTEM & IMAGE</div>

                  <div className="row gap-12" style={{ marginBottom: 24 }}>
                    {[
                      { key: "linux",   label: "LINUX",   sub: "Ubuntu / Amazon Linux · lower spot price" },
                      { key: "windows", label: "WINDOWS", sub: "Windows Server 2019/2022 · higher spot price" },
                    ].map(p => (
                      <button key={p.key}
                        onClick={() => {
                          setPlatform(p.key);
                          const pf = amis.filter(a => a.platform === p.key);
                          if (pf.length) setAmiId(pf[0].id); else setAmiId("");
                        }}
                        style={{
                          flex: 1,
                          border: `1px solid ${platform === p.key ? "var(--ink)" : "var(--rule-soft)"}`,
                          background: platform === p.key ? "var(--bg-3)" : "transparent",
                          padding: "18px 20px",
                          cursor: "pointer",
                          textAlign: "left",
                          transition: "all 120ms",
                        }}
                      >
                        <div style={{ fontWeight: platform === p.key ? 700 : 400, letterSpacing: "0.1em", fontSize: 13 }}>{p.label}</div>
                        <div className="tiny" style={{ color: "var(--muted)", marginTop: 5 }}>{p.sub}</div>
                        {platform === p.key && <div className="micro" style={{ color: "var(--accent)", marginTop: 8 }}>● SELECTED</div>}
                      </button>
                    ))}
                  </div>

                  <div className="micro" style={{ marginBottom: 8 }}>AMI / IMAGE · {platform.toUpperCase()}</div>
                  {amisLoading && (
                    <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                      <div className="skeleton" style={{ height: 18, width: "60%" }}></div>
                      <div className="skeleton" style={{ height: 36, width: "100%" }}></div>
                      <div className="skeleton" style={{ height: 14, width: "40%" }}></div>
                    </div>
                  )}
                  {amisError   && <div className="tiny" style={{ color: "var(--accent)" }}>{amisError} <button className="micro" style={{ cursor: "pointer", color: "var(--accent)", textDecoration: "underline" }} onClick={retryAmis}>RETRY</button></div>}
                  {!amisLoading && !amisError && (
                    <>
                      <select value={amiId} onChange={e => setAmiId(e.target.value)} style={{ width: "100%", marginBottom: 6 }}>
                        {filteredAmis.map(a => (
                          <option key={a.id} value={a.id}>
                            {a.os}{a.framework !== "Base" ? ` · ${a.framework}` : ""}{a.sizeGb ? ` · ${a.sizeGb}GB` : ""}
                          </option>
                        ))}
                        <option value="__custom__">— CUSTOM AMI ID —</option>
                      </select>
                      {!isCustomAmi && ami && (
                        <div className="micro" style={{ color: "var(--muted)" }}>{ami.id} · {ami.created} · {ami.arch}</div>
                      )}
                      {isCustomAmi && (
                        <div style={{ marginTop: 8 }}>
                          <input
                            type="text"
                            value={customAmi}
                            onChange={e => setCustomAmi(e.target.value)}
                            placeholder="ami-0xxxxxxxxxxxxxxxxx"
                            style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12, borderColor: customAmi && !customValid ? "var(--danger)" : undefined }}
                            autoFocus
                          />
                          {customAmi && !customValid && <div className="micro" style={{ color: "var(--danger)", marginTop: 4 }}>INVALID AMI FORMAT · MUST MATCH ami-xxxxxxxxxxxxx</div>}
                          <div className="micro" style={{ color: "var(--muted)", marginTop: 4 }}>PASTE AMI ID · MUST BE IN {region}</div>
                        </div>
                      )}
                    </>
                  )}

                  {platform === "windows" && (
                    <div className="micro" style={{ color: "var(--muted)", marginTop: 14, padding: "10px 14px", border: "1px solid var(--rule-soft)" }}>
                      ⚠ WINDOWS SPOT PRICES LOAD IN THE NEXT STEP — TYPICALLY 1.5–4× LINUX PRICE
                    </div>
                  )}
                </div>
              )}

              {/* STEP 4 — Config */}
              {step === 4 && (
                <div style={{ maxWidth: 900, margin: "0 auto", display: "grid", gridTemplateColumns: "1.1fr 1fr", gap: 32 }}>

                  {/* Left — Region + AZ */}
                  <div className="stack-4">
                    <div>
                      <div className="micro" style={{ marginBottom: 8 }}>
                        04 · REGION
                        {REGIONS.some(r => latCount(r) < LAT_MAX) && (
                          <span style={{ color: "var(--muted)" }}> · PROBING...</span>
                        )}
                      </div>
                      <div style={{ border: "1px solid var(--ink)" }}>
                        {REGIONS.map(reg => {
                          const avg        = latAvg(reg);
                          const cnt        = latCount(reg);
                          const isCheapest = cheapestRegion === reg;
                          const regPrice   = compare?.[reg]?.[instanceId];
                          const limitNote  = REGION_LIMITS[reg];
                          return (
                            <label key={reg} style={{
                              display: "flex", alignItems: "center", gap: 10,
                              padding: "10px 14px", borderBottom: "1px solid var(--rule-soft)",
                              cursor: "pointer",
                              background: isCheapest ? "color-mix(in oklch, var(--cheapest) 8%, transparent)" : region === reg ? "var(--bg-3)" : "transparent",
                              borderLeft: isCheapest ? "2px solid var(--cheapest)" : "2px solid transparent",
                            }}>
                              <input type="radio" name="region" checked={region === reg}
                                onChange={() => { setRegion(reg); setAz(cheapestAzOf(reg) || ""); }} />
                              <span className="tiny" style={{ flex: 1, fontWeight: region === reg ? 600 : 400 }}>{reg}</span>
                              {isCheapest && <span style={{ fontSize: 8, letterSpacing: "0.12em", color: "var(--cheapest)", fontWeight: 700 }}>CHEAPEST</span>}
                              {regPrice && instanceId && (
                                <span className="num tiny" style={{ color: "var(--muted)" }}>${regPrice.current.toFixed(4)}</span>
                              )}
                              <span className="num tiny" style={{ color: msColor(avg), minWidth: 60, textAlign: "right" }}>
                                {avg != null ? `${avg}ms` : "—"}
                                {cnt > 0 && cnt < LAT_MAX && <span style={{ color: "var(--muted)", fontSize: 9, marginLeft: 3 }}>{cnt}/{LAT_MAX}</span>}
                                {cnt >= LAT_MAX && <span style={{ color: "var(--muted)", fontSize: 9, marginLeft: 3 }}>avg</span>}
                              </span>
                            </label>
                          );
                        })}
                      </div>
                      {REGION_LIMITS[region] && (
                        <div className="micro" style={{ color: "var(--warn)", marginTop: 4 }}>⚠ {REGION_LIMITS[region]}</div>
                      )}
                      <div className="micro" style={{ color: "var(--muted)", marginTop: 4 }}>AVG LATENCY · 10 PROBES · MEASURED ON PAGE LOAD</div>
                    </div>

                    <div>
                      <div className="micro" style={{ marginBottom: 8 }}>
                        05 · AVAILABILITY ZONE · {platform.toUpperCase()} PRICE
                        {platform === "windows" && Object.keys(winAzPrices).length === 0 && (
                          <span style={{ color: "var(--muted)" }}> · LOADING...</span>
                        )}
                      </div>
                      {azOptions.length > 0 ? (
                        <div style={{ border: "1px solid var(--ink)" }}>
                          {azOptions.map(([zone, prices]) => {
                            const isAzCheapest  = zone === cheapestAzLocal;
                            const isGlobalBest  = isAzCheapest && region === cheapestRegion;
                            return (
                              <label key={zone} style={{
                                display: "grid", gridTemplateColumns: "auto 1fr auto",
                                alignItems: "center", gap: 10,
                                padding: "10px 14px", borderBottom: "1px solid var(--rule-soft)",
                                cursor: "pointer",
                                background: isGlobalBest ? "color-mix(in oklch, var(--cheapest) 8%, transparent)" : effectiveAz === zone ? "var(--bg-3)" : "transparent",
                                borderLeft: isGlobalBest ? "2px solid var(--cheapest)" : isAzCheapest ? "2px solid var(--cheapest-local)" : "2px solid transparent",
                              }}>
                                <input type="radio" name="az" checked={effectiveAz === zone} onChange={() => setAz(zone)} />
                                <div>
                                  <span className="tiny">{zone}</span>
                                  {isGlobalBest  && <span style={{ fontSize: 8, letterSpacing: "0.12em", color: "var(--cheapest)", fontWeight: 700, marginLeft: 8 }}>CHEAPEST</span>}
                                  {isAzCheapest && !isGlobalBest && <span style={{ fontSize: 8, letterSpacing: "0.12em", color: "var(--cheapest-local)", marginLeft: 8 }}>CHEAPEST</span>}
                                </div>
                                <div className="num tiny" style={{ textAlign: "right" }}>
                                  <div style={{ fontWeight: 500 }}>${prices.current.toFixed(4)}<span style={{ color: "var(--muted)", fontSize: 9 }}>/h now</span></div>
                                  <div style={{ color: "var(--muted)" }}>7d ${prices.median7d.toFixed(4)}</div>
                                </div>
                              </label>
                            );
                          })}
                        </div>
                      ) : (
                        <div className="tiny" style={{ color: "var(--muted)" }}>
                          {platform === "windows" ? "Windows AZ prices loading..." : "Select instance type to see AZ prices"}
                        </div>
                      )}
                    </div>
                  </div>

                  {/* Right — Name / Reason / Hours */}
                  <div className="stack-4">
                    <div>
                      <div className="row between micro" style={{ marginBottom: 8 }}>
                        <span>06 · NAME TAG</span>
                        <button className="micro" style={{ cursor: "pointer", color: "var(--muted)" }} onClick={() => setName(randName())}>⟳ RANDOM</button>
                      </div>
                      <input type="text" value={name} onChange={e => setName(e.target.value)} style={{ width: "100%" }} autoFocus />
                    </div>
                    <div>
                      <div className="micro" style={{ marginBottom: 8 }}>07 · REASON / PURPOSE <span style={{ color: "var(--accent)" }}>*</span></div>
                      <input
                        type="text"
                        value={reason}
                        onChange={e => setReason(e.target.value)}
                        placeholder="e.g. render bake pass 07, audio stems training"
                        style={{ width: "100%", fontSize: 12 }}
                      />
                      <div style={{ display: "flex", gap: 4, marginTop: 4, flexWrap: "wrap" }}>
                        {["render bake", "training run", "stem export", "dev testing"].map(t => (
                          <button key={t} className="micro" style={{ cursor: "pointer", padding: "1px 6px", border: "1px solid var(--rule-soft)", color: "var(--muted)" }}
                            onClick={() => setReason(t)}>{t}</button>
                        ))}
                      </div>
                    </div>
                    <div>
                      <div className="row between micro" style={{ marginBottom: 8 }}>
                        <span>08 · RUNTIME</span>
                        <div className="row gap-8" style={{ alignItems: "center" }}>
                          <span style={{ color: "var(--muted)" }}>MAX {maxHrs}H</span>
                          <button className="micro" style={{
                            cursor: "pointer", padding: "2px 7px",
                            border: "1px solid var(--rule-soft)",
                            color: durMode === "picker" ? "var(--accent)" : "var(--muted)",
                          }} onClick={() => setDurMode(m => m === "slider" ? "picker" : "slider")}>
                            {durMode === "picker" ? "SLIDER" : "D:H:M:S"}
                          </button>
                        </div>
                      </div>
                      {durMode === "slider" ? (
                        <>
                          <input type="range" min="0.5" step="0.5" max={Math.max(maxHrs, 1)} value={hours}
                            onChange={e => setHours(+e.target.value)} style={{ width: "100%" }} />
                          <div className="row between" style={{ marginTop: 4 }}>
                            <span className="tiny" style={{ color: "var(--muted)" }}>1H</span>
                            <span className="num" style={{ fontWeight: 600 }}>{formatDuration(hours)}</span>
                            <span className="tiny" style={{ color: "var(--muted)" }}>{maxHrs}H</span>
                          </div>
                        </>
                      ) : (
                        <div>
                          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 8, marginBottom: 6 }}>
                            {[["DD", durD, setDurD, 99], ["HH", durH, setDurH, 23], ["MM", durM, setDurM, 59], ["SS", durS, setDurS, 59]].map(([lbl, val, setter, max]) => (
                              <div key={lbl} style={{ textAlign: "center" }}>
                                <div className="micro" style={{ color: "var(--muted)", marginBottom: 4 }}>{lbl}</div>
                                <input type="number" min="0" max={max} value={val}
                                  onChange={e => setter(Math.max(0, Math.min(max, +e.target.value || 0)))}
                                  style={{ width: "100%", textAlign: "center", fontSize: 15, padding: "8px 4px" }} />
                              </div>
                            ))}
                          </div>
                          <div className="row between tiny" style={{ color: "var(--muted)", marginTop: 4 }}>
                            <span>= {formatDuration(hours)}</span>
                            <span>{hours.toFixed(4)}H TOTAL</span>
                          </div>
                        </div>
                      )}
                    </div>
                    <div style={{ paddingTop: 12, borderTop: "1px solid var(--rule-soft)" }}>
                      <div className="micro" style={{ marginBottom: 6 }}>COST PREVIEW · {platform.toUpperCase()}</div>
                      <div className="num" style={{ fontSize: 28, fontWeight: 700 }}>
                        <span style={{ color: "var(--muted)", fontSize: "0.45em", verticalAlign: "top" }}>$</span>
                        {cost.toFixed(4)}
                        <span style={{ color: "var(--muted)", fontSize: 13, fontWeight: 400, marginLeft: 8 }}>/ ${free.toFixed(4)} free</span>
                      </div>
                      {spotPrice > 0 && <div className="micro" style={{ color: "var(--muted)", marginTop: 2 }}>${spotPrice.toFixed(4)}/h × {formatDuration(hours)}</div>}
                      {overBudget && <div className="micro" style={{ color: "var(--accent)", marginTop: 6 }}>⚠ EXCEEDS MONTHLY BUDGET</div>}
                    </div>
                    {!reason.trim() && <div className="micro" style={{ color: "var(--accent)" }}>REASON IS REQUIRED TO CONTINUE</div>}
                  </div>
                </div>
              )}

              {/* STEP 5 — Review & Launch */}
              {step === 5 && (
                <div style={{ maxWidth: 1020, margin: "0 auto", display: "grid", gridTemplateColumns: "1fr 1fr", gap: 32 }}>

                  {/* Left — Summary */}
                  <div className="stack-4">
                    {selectedGroup && (
                      <div style={{ border: `1px solid ${selectedGroup.color}`, padding: "16px 18px" }}>
                        <div style={{ fontWeight: 700, fontSize: 15, color: selectedGroup.color, letterSpacing: "0.08em" }}>{selectedGroup.name}</div>
                        <div style={{ fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 28, lineHeight: 1.2, marginTop: 8 }}>{instanceId}</div>
                        <div className="tiny" style={{ color: "var(--muted)", marginTop: 6 }}>{selectedGroup.gpu} · {selectedGroup.vram} · CUDA {selectedGroup.cuda}</div>
                      </div>
                    )}
                    <div>
                      <div className="micro" style={{ marginBottom: 6 }}>HARDWARE</div>
                      <table className="idx" style={{ fontSize: 11 }}>
                        <tbody>
                          <tr><td style={{ color: "var(--muted)" }}>GPU</td><td>{inst.gpu || "—"}</td></tr>
                          <tr><td style={{ color: "var(--muted)" }}>VRAM</td><td>{inst.vram || "—"}</td></tr>
                          <tr><td style={{ color: "var(--muted)" }}>CUDA</td><td>{inst.cuda || "—"}</td></tr>
                          <tr><td style={{ color: "var(--muted)" }}>vCPU</td><td>{inst.vcpu || "—"}</td></tr>
                          <tr><td style={{ color: "var(--muted)" }}>RAM</td><td>{inst.ram || "—"}</td></tr>
                          <tr><td style={{ color: "var(--muted)" }}>REGION</td><td>{region}</td></tr>
                          <tr><td style={{ color: "var(--muted)" }}>AZ</td><td>{effectiveAz || "—"}</td></tr>
                          <tr><td style={{ color: "var(--muted)" }}>OS</td><td>{platform.toUpperCase()}{ami ? ` · ${ami.framework}` : ""}</td></tr>
                          {ami && <tr><td style={{ color: "var(--muted)" }}>AMI</td><td style={{ fontSize: 10 }}>{ami.id} · {ami.created}</td></tr>}
                          <tr><td style={{ color: "var(--muted)" }}>DISK</td><td>{diskGb} GB gp3{inst?.storage?.totalGb ? ` + ${inst.storage.totalGb} GB NVMe` : ""}</td></tr>
                        </tbody>
                      </table>
                    </div>
                    {reason.trim() && (
                      <div>
                        <div className="micro" style={{ marginBottom: 4 }}>REASON</div>
                        <div style={{ fontSize: 12, fontStyle: "italic", color: "var(--ink-2)" }}>"{reason}"</div>
                      </div>
                    )}
                    <div className="micro" style={{ color: "var(--muted)", padding: "8px 12px", border: "1px solid var(--rule-soft)" }}>
                      ⚠ Spot instances may be interrupted with 2-minute notice. Save work to S3.
                    </div>
                  </div>

                  {/* Right — Cost + Compare + Quota */}
                  <div className="stack-4">
                    <div>
                      <div className="micro" style={{ marginBottom: 6 }}>COST · PROJECTED · {platform.toUpperCase()}</div>
                      <div className="bignum num" style={{ fontSize: 36 }}>
                        <span style={{ color: "var(--muted)", fontSize: "0.42em", verticalAlign: "top" }}>$</span>
                        {cost.toFixed(4)}
                        <span className="of">/ ${free.toFixed(4)} FREE</span>
                      </div>
                      <div style={{ marginTop: 8 }}>
                        <Bar value={cost} max={Math.max(free, cost)} tone={overBudget ? "danger" : "default"} />
                      </div>
                      <div className="tiny" style={{ marginTop: 6, color: overBudget ? "var(--accent)" : "var(--muted)" }}>
                        {overBudget ? "⚠ EXCEEDS FREE BUDGET" : "✓ WITHIN MONTHLY BUDGET"}
                      </div>
                      <div className="tiny" style={{ color: "var(--muted)" }}>${spotPrice.toFixed(4)}/h × {formatDuration(hours)}</div>
                    </div>

                    {compare && instanceId && (
                      <div>
                        <div className="micro" style={{ marginBottom: 6 }}>REGION COMPARE · {instanceId} · LINUX</div>
                        <table className="idx" style={{ fontSize: 11, width: "100%" }}>
                          <thead>
                            <tr>
                              <td style={{ color: "var(--muted)" }}>REGION</td>
                              <td className="num" style={{ color: "var(--muted)" }}>NOW</td>
                              <td className="num" style={{ color: "var(--muted)" }}>7D MED</td>
                              <td className="num" style={{ color: "var(--muted)" }}>LATENCY</td>
                              <td></td>
                            </tr>
                          </thead>
                          <tbody>
                            {REGIONS.map(reg => {
                              const entry      = compare[reg]?.[instanceId];
                              const avg        = latAvg(reg);
                              const isCheapest = cheapestRegion === reg;
                              return (
                                <tr key={reg} style={{
                                  background: isCheapest ? "color-mix(in oklch, var(--cheapest) 8%, transparent)" : reg === region ? "var(--bg-3)" : "transparent",
                                  borderLeft: isCheapest ? "2px solid var(--cheapest)" : "none",
                                }}>
                                  <td style={{ fontWeight: reg === region ? 600 : 400 }}>
                                    {reg}
                                    {isCheapest && <span style={{ fontSize: 8, color: "var(--cheapest)", fontWeight: 700, marginLeft: 6, letterSpacing: "0.1em" }}>CHEAPEST</span>}
                                  </td>
                                  <td className="num">{entry ? `$${entry.current.toFixed(4)}` : "—"}</td>
                                  <td className="num" style={{ color: "var(--muted)" }}>{entry ? `$${entry.median7d.toFixed(4)}` : "—"}</td>
                                  <td className="num" style={{ color: msColor(avg) }}>{avg != null ? `${avg}ms` : "—"}</td>
                                  <td>
                                    {entry && reg !== region && (
                                      <button className="micro" style={{ cursor: "pointer", padding: "1px 4px" }}
                                        onClick={() => { setRegion(reg); setAz(""); }}>USE</button>
                                    )}
                                  </td>
                                </tr>
                              );
                            })}
                          </tbody>
                        </table>
                        <div className="micro" style={{ color: "var(--muted)", marginTop: 4 }}>LINUX PRICING · CACHED 1H</div>
                      </div>
                    )}

                    {quota && quota.length > 0 && instanceId && (
                      <div>
                        <div className="micro" style={{ marginBottom: 6 }}>SPOT QUOTA · {region}</div>
                        <table className="idx" style={{ fontSize: 11, width: "100%" }}>
                          <tbody>
                            {quota.map(q => {
                              const relevant = instanceId.charAt(0).toUpperCase() === q.family.charAt(0);
                              const capacity = (q.vcpus && inst.vcpu) ? Math.floor(q.vcpus / inst.vcpu) : null;
                              return (
                                <tr key={q.code} style={{ fontWeight: relevant ? 600 : 400 }}>
                                  <td style={{ color: "var(--muted)" }}>{q.label}</td>
                                  <td className="num">{q.vcpus != null ? `${q.vcpus} vCPU` : "—"}</td>
                                  <td className="num" style={{ color: "var(--muted)" }}>{capacity != null ? `~${capacity}× max` : ""}</td>
                                </tr>
                              );
                            })}
                          </tbody>
                        </table>
                        <div className="micro" style={{ color: "var(--muted)", marginTop: 4 }}>MAX CONCURRENT · NOT CURRENT USAGE</div>
                      </div>
                    )}

                    {!instanceId && <div className="micro" style={{ color: "var(--accent)" }}>SELECT GPU + SIZE</div>}
                    {!reason.trim() && <div className="micro" style={{ color: "var(--accent)" }}>REASON IS REQUIRED</div>}
                    {!amiId && <div className="micro" style={{ color: "var(--accent)" }}>SELECT AN AMI</div>}
                  </div>
                </div>
              )}
            </div>

            {/* Bottom nav */}
            <div style={{ padding: "14px 20px", borderTop: "1px solid var(--rule-soft)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <div>
                {step > 1 && (
                  <button className="btn ghost" onClick={() => setStep(s => s - 1)}>← BACK</button>
                )}
              </div>
              <div className="row gap-12">
                {step < 5 && (
                  <button className="btn solid"
                    style={{ opacity: canAdvance ? 1 : 0.4 }}
                    disabled={!canAdvance}
                    onClick={handleNext}>
                    NEXT <span className="arr">→</span>
                  </button>
                )}
                {step === 5 && (
                  <button className="btn solid"
                    style={{ opacity: (overBudget || !reason.trim() || !amiId || !instanceId || submitting || (isCustomAmi && !customValid)) ? 0.4 : 1 }}
                    disabled={overBudget || !reason.trim() || !amiId || !instanceId || submitting || (isCustomAmi && !customValid)}
                    onClick={submit}>
                    {submitting ? "REQUESTING..." : "REQUEST SPOT"} <span className="arr">→</span>
                  </button>
                )}
              </div>
            </div>
          </>
        )}

        {/* ── SUCCESS ── */}
        {stage === "success" && (
          <div className="panel-body" style={{ textAlign: "center", padding: "60px 40px" }}>
            <div className="micro" style={{ color: "var(--accent)" }}>● REQUEST SUBMITTED</div>
            <div style={{ fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 42, lineHeight: 1.1, marginTop: 18 }}>
              Spinning up.
            </div>
            <div style={{ fontSize: 13, color: "var(--muted)", marginTop: 16, maxWidth: 480, margin: "16px auto 0" }}>
              Request {requestId || "—"} queued.<br />
              {instanceId} · {effectiveAz || region} · {formatDuration(hours)} · ~${cost.toFixed(4)}.<br />
              Reason: "{reason}"
            </div>
            <div style={{ fontSize: 11, color: "var(--muted)", marginTop: 12, padding: "8px 12px", border: "1px solid var(--rule-soft)", maxWidth: 480, margin: "0 auto" }}>
              <div className="micro" style={{ marginBottom: 4 }}>SSH ACCESS</div>
              <div style={{ fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-2)" }}>
                ssh ubuntu@&lt;public-ip&gt;
              </div>
              <div className="tiny" style={{ color: "var(--muted)", marginTop: 6 }}>
                Public IP appears in the EC2 panel once the instance is running. Open the instance from § SERVERS to connect.
              </div>
            </div>
            <div style={{ fontSize: 11, color: "var(--muted)", marginTop: 12 }}>
<a href={`https://${region}.console.aws.amazon.com/ec2/home?region=${region}#Instances:`}
                  target="_blank" rel="noopener" style={{ color: "var(--accent)", textDecoration: "underline" }}>
                Open AWS Console → {region}
              </a>
            </div>
            <button className="btn solid" style={{ marginTop: 28 }} onClick={handleClose}>CLOSE <span className="arr">→</span></button>
          </div>
        )}

        {/* ── ERROR ── */}
        {stage === "error" && (
          <div className="panel-body" style={{ textAlign: "center", padding: "60px 40px" }}>
            <div className="micro" style={{ color: "var(--accent)" }}>⚠ REQUEST FAILED</div>
            <div style={{ fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 32, lineHeight: 1.1, marginTop: 18 }}>
              Something went wrong.
            </div>
            <div style={{ fontSize: 13, color: "var(--muted)", marginTop: 16 }}>{errorMsg}</div>
            <div className="row gap-12" style={{ justifyContent: "center", marginTop: 28 }}>
              <button className="btn ghost" onClick={handleClose}>CANCEL</button>
              <button className="btn solid" onClick={() => setStage("form")}>RETRY <span className="arr">→</span></button>
            </div>
          </div>
        )}

      </div>
    </div>
  );
}

window.SEIS_LAUNCHER = { EC2Launcher };