// app.jsx — root app. Owns route state (library | concept | solar) and concept data.
// Wires the Tweaks panel for theme, vibe, density, solar style, palette tint, onboarding.

const { useState: _a_us, useEffect: _a_ue, useMemo: _a_um, useRef: _a_ur } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "dark": false,
  "vibe": "default",
  "density": "regular",
  "solarStyle": "planets",
  "nucleusHue": "#FF8800",
  "showOnboarding": false
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
  const { concepts, loading, createConcept: dbCreate, updateConcept, deleteConcept, showUpgrade, closeUpgrade } = window.useDiseneDB();
  const sync = window.useSync ? window.useSync() : null;
  const [route, setRoute] = _a_us({ view: "library" });
  // ^ shape: {view: 'library'} | {view: 'concept', id, mode: 'edit'|'read'} | {view: 'solar', id} | {view: 'graph'}
  const [onboarding, setOnboarding] = _a_us(t.showOnboarding);

  _a_ue(() => { setOnboarding(t.showOnboarding); }, [t.showOnboarding]);

  function openConcept(id, mode = "edit") { setRoute({ view: "concept", id, mode }); }
  function openSolar(id, openPanel = false) { setRoute({ view: "solar", id, openPanel }); }
  function openGraph() { setRoute({ view: "graph" }); }
  function back() { setRoute({ view: "library" }); }

  async function createConcept(title) {
    const id = await dbCreate(title);
    if (id) openConcept(id, "edit");
  }

  const current = route.view !== "library" ? concepts.find(c => c.id === route.id) : null;

  if (loading) return (
    <div className="app-shell" data-theme={t.dark ? "dark" : "light"} style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100vh" }}>
      <window.Logo size={28} style={{ opacity: 0.4 }} />
    </div>
  );

  return (
    <div
      className="app-shell"
      data-theme={t.dark ? "dark" : "light"}
      data-vibe={t.vibe === "default" ? null : t.vibe}
      data-density={t.density}
      style={{ "--c-nucleus": t.nucleusHue }}
    >
      <Topbar route={route} onHome={() => setRoute({ view: "library" })} t={t} setTweak={setTweak} concepts={concepts} onOpenConcept={openConcept} sync={sync} />

      {route.view === "library" && (
        <window.Library
          concepts={concepts}
          onOpen={(id, mode) => openConcept(id, mode)}
          onCreate={createConcept}
          onDelete={deleteConcept}
          onOpenSolar={() => openSolar(concepts[0]?.id || "knife")}
          onOpenGraph={openGraph}
        />
      )}

      {route.view === "concept" && current && route.mode === "edit" && (
        <window.ConceptEdit
          concept={current}
          allConcepts={concepts}
          onChange={(patch) => updateConcept(current.id, patch)}
          onBack={back}
          onSwitchMode={(m) => setRoute({ view: "concept", id: current.id, mode: m })}
          onOpenSolar={() => openSolar(current.id)}
          onOpenConcept={(id) => openConcept(id, "edit")}
          onOpenGraph={openGraph}
          onDelete={() => { deleteConcept(current.id); back(); }}
          density={t.density}
        />
      )}

      {route.view === "concept" && current && route.mode === "read" && (
        <window.ConceptRead
          concept={current}
          allConcepts={concepts}
          onBack={back}
          onSwitchMode={(m) => setRoute({ view: "concept", id: current.id, mode: m })}
          onOpenSolar={() => openSolar(current.id)}
          onOpenConcept={(id) => openConcept(id, "read")}
          onOpenGraph={openGraph}
        />
      )}

      {route.view === "solar" && current && (
        <window.SolarScreen
          concept={current}
          allConcepts={concepts}
          onBack={back}
          onSwitchMode={(m) => setRoute({ view: "concept", id: current.id, mode: m })}
          styleVariant={t.solarStyle}
          themeDark={t.dark}
          onChange={(patch) => updateConcept(current.id, patch)}
          onOpenConcept={(id, mode) => openConcept(id, mode || "edit")}
          onUpdateConcept={updateConcept}
          onOpenSolar={openSolar}
          openPanel={!!route.openPanel}
        />
      )}

      {route.view === "graph" && (
        <window.Graph
          concepts={concepts}
          onUpdate={updateConcept}
          onBack={back}
          onOpenSolar={openSolar}
          onOpenConcept={(id, mode) => openConcept(id, mode || 'edit')}
          density={t.density}
        />
      )}

      {onboarding && (
        <Onboarding onClose={() => { setOnboarding(false); setTweak('showOnboarding', false); }} />
      )}

      {showUpgrade && <UpgradeModal onClose={closeUpgrade} />}

      {sync?.needsSetup && (
        <SetupDriveModal
          onSave={(id) => { sync.configure(id); sync.signIn(); }}
          onClose={() => sync.configure("")}
        />
      )}

      <SenesPanel t={t} setTweak={setTweak} />
    </div>
  );
}

function Topbar({ route, onHome, t, setTweak, concepts = [], onOpenConcept, sync }) {
  const [query, setQuery] = _a_us('');
  const [open, setOpen] = _a_us(false);
  const [ioOpen, setIoOpen] = _a_us(false);
  const fileInputRef = _a_ur(null);
  const ioRef = _a_ur(null);

  async function handleExport() {
    try {
      const data = await window.dbExport();
      const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `disene-${new Date().toISOString().slice(0, 10)}.json`;
      a.click();
      URL.revokeObjectURL(url);
    } catch (err) {
      console.error("Export failed:", err);
    }
  }

  async function handleImport(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    e.target.value = "";
    try {
      const text = await file.text();
      const data = JSON.parse(text);
      await window.dbMerge(data);
      window.dispatchEvent(new Event("disene:synced"));
    } catch (err) {
      console.error("Import failed:", err);
      alert("Import failed: " + err.message);
    }
  }

  const results = _a_um(() => {
    const q = query.trim().toLowerCase();
    if (!q) return [];
    return concepts.filter(c =>
      c.title.toLowerCase().includes(q) || (c.category || '').toLowerCase().includes(q)
    ).slice(0, 7);
  }, [query, concepts]);

  function pick(id) {
    onOpenConcept(id, 'edit');
    setQuery('');
    setOpen(false);
  }

  function onKey(e) {
    if (e.key === 'Escape') { setQuery(''); setOpen(false); }
  }

  _a_ue(() => {
    if (!ioOpen) return;
    function onDown(e) { if (ioRef.current && !ioRef.current.contains(e.target)) setIoOpen(false); }
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [ioOpen]);

  return (
    <header className="topbar" style={{ position: "relative" }}>
      {/* Sync — absolutely centered */}
      {sync && (
        <div className="tb-sync" style={{ position: "absolute", left: "50%", top: "50%", transform: "translate(-50%,-50%)", display: "flex", alignItems: "center", gap: 6, pointerEvents: "auto" }}>
          {sync.user ? (
            <>
              <button
                className="btn ghost icon"
                onClick={sync.triggerSync}
                disabled={sync.syncing}
                title={sync.lastSync ? `Last sync ${sync.lastSync.toLocaleTimeString()}` : "Sync to Google Drive"}
                style={{ opacity: sync.syncing ? 0.5 : 1 }}
              >
                {sync.syncing
                  ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" style={{ animation: "spin 1s linear infinite" }}><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
                  : <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
                }
              </button>
              <img
                src={sync.user.picture}
                alt={sync.user.name}
                title={`${sync.user.name} · ${sync.user.email}`}
                style={{ width: 22, height: 22, borderRadius: "50%", opacity: 0.85 }}
              />
              <button
                className="btn ghost tiny"
                onClick={sync.disconnect}
                title="Disconnect Google Drive"
                style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: ".06em", textTransform: "uppercase", color: "var(--fg-3)" }}
              >
                disconnect
              </button>
            </>
          ) : (
            <button
              className="btn ghost tiny"
              onClick={sync.signIn}
              title="Sign in with Google to sync your concepts"
              style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: ".06em", textTransform: "uppercase" }}
            >
              Sync
            </button>
          )}
        </div>
      )}

      <button
        onClick={() => route.view === "library" ? (window.location.href = "landing.html") : onHome()}
        className="btn ghost"
        style={{ padding: "6px 8px" }}
        title={route.view === "library" ? "Landing page" : "Library"}
      >
        <window.Logo size={28} />
      </button>

      {/* Search */}
      <div className="tb-search" style={{ position: "relative" }}>
        <input
          type="text"
          value={query}
          onChange={e => { setQuery(e.target.value); setOpen(true); }}
          onFocus={() => setOpen(true)}
          onBlur={() => setTimeout(() => setOpen(false), 150)}
          onKeyDown={onKey}
          placeholder="search concepts…"
          style={{
            background: "transparent",
            border: "none",
            padding: "4px 0",
            fontSize: 20,
            fontFamily: "var(--font-serif)",
            fontStyle: "italic",
            color: "var(--fg)",
            outline: "none",
            width: 300,
          }}
        />
        {open && results.length > 0 && (
          <div style={{
            position: "absolute", top: "calc(100% + 6px)", left: 0, zIndex: 200,
            background: "var(--bg-elev)", border: "1px solid var(--border)",
            borderRadius: 10, boxShadow: "var(--shadow-lift)",
            minWidth: 240, overflow: "hidden",
          }}>
            {results.map(c => (
              <div key={c.id}
                onMouseDown={() => pick(c.id)}
                style={{
                  padding: "8px 14px", cursor: "pointer",
                  borderBottom: "1px solid var(--border-soft)",
                  display: "flex", flexDirection: "column", gap: 2,
                }}
                onMouseEnter={e => e.currentTarget.style.background = "var(--bg-sunk)"}
                onMouseLeave={e => e.currentTarget.style.background = "transparent"}
              >
                <span style={{ fontSize: 13, fontWeight: 500, color: "var(--fg)" }}>{c.title}</span>
                {c.category && <span style={{ fontSize: 11, color: "var(--fg-3)", fontFamily: "var(--font-mono)", letterSpacing: ".04em" }}>{c.category}</span>}
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="topbar-spacer" />

      {/* breadcrumbs */}
      <span className="tb-bc" style={{ display: "flex", gap: 4, alignItems: "center", color: "var(--fg-3)", fontSize: 12, fontFamily: "var(--font-mono)", letterSpacing: ".04em" }}>
        <span className={`topbar-link ${route.view === "library" ? "active" : ""}`} onClick={onHome}>library</span>
        {route.view !== "library" && <span>/</span>}
        {route.view !== "library" && (
          <span className="topbar-link active">
            {route.view === "concept" ? `concept · ${route.mode}` : "system"}
          </span>
        )}
      </span>

      <input ref={fileInputRef} type="file" accept=".json" style={{ display: "none" }} onChange={handleImport} />
      <div style={{ display: "flex", alignItems: "center", gap: 2 }}>
        <div ref={ioRef} style={{ position: "relative" }}>
          <button className="btn ghost icon" onClick={() => setIoOpen(v => !v)} title="Import / Export">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 16V4m0 0L3 8m4-4 4 4"/><path d="M17 8v12m0 0 4-4m-4 4-4-4"/></svg>
          </button>
          {ioOpen && (
            <div style={{
              position: "absolute", top: "calc(100% + 6px)", right: 0,
              background: "var(--bg-elev)", border: "1px solid var(--border)",
              borderRadius: 10, boxShadow: "var(--shadow-lift)",
              overflow: "hidden", zIndex: 300, minWidth: 140,
            }}>
              <button className="btn ghost" onClick={() => { fileInputRef.current?.click(); setIoOpen(false); }}
                style={{ width: "100%", justifyContent: "flex-start", borderRadius: 0, padding: "9px 14px", gap: 8, fontSize: 13 }}>
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
                Import
              </button>
              <button className="btn ghost" onClick={() => { handleExport(); setIoOpen(false); }}
                style={{ width: "100%", justifyContent: "flex-start", borderRadius: 0, padding: "9px 14px", gap: 8, fontSize: 13 }}>
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
                Export
              </button>
            </div>
          )}
        </div>
        <button className="btn ghost icon" onClick={() => setTweak('dark', !t.dark)} title="Toggle theme">
          {t.dark ? window.Icons.Sun(14) : window.Icons.Moon(14)}
        </button>
      </div>
    </header>
  );
}

// ─────────────────────────────────────────────────────────────────
// Onboarding — short example walking the user through the 5 levels
function Onboarding({ onClose }) {
  const [step, setStep] = _a_us(0);
  const steps = [
    {
      tag: "intro",
      kind: "nucleus",
      title: "Five levels of one thing",
      body: "disene turns a word you've heard into a thing you understand. We'll walk through a single example — a knife — to show how.",
    },
    {
      tag: "01 · CONSIST",
      kind: "constant",
      title: "What is it made of?",
      body: "A hardened steel blade with a ground edge, joined to a handle. This part is objective. It's the same for everyone who's ever held one.",
    },
    {
      tag: "02 · FUNCTION",
      kind: "constant",
      title: "How does it work?",
      body: "It concentrates force along a thin line, so material parts ahead of the edge instead of resisting. Also objective. Also the same for everyone.",
    },
    {
      tag: "03 · PERSPECTIVE",
      kind: "perspective",
      title: "How have you met it?",
      body: "Slicing onions on a Sunday. Carrying your grandfather's pocket knife while hiking. Opening a hand-written envelope. These are subjective — they're yours.",
    },
    {
      tag: "04 · PATTERN",
      kind: "pattern",
      title: "What do your perspectives share?",
      body: "Two or more of them quietly agree on something. A knife is the body extended into the world with precision. That's a pattern in your experience.",
    },
    {
      tag: "05 · ESSENCE",
      kind: "essence",
      title: "What stays after everything else?",
      body: "A focused force that separates with intent. That's not the definition of a knife. That's your understanding of it. It's what you carry when nothing else of the knife is in the room.",
    },
    {
      tag: "begin",
      kind: "nucleus",
      title: "Now do this for something of your own.",
      body: "Open any concept in your library and begin. You don't have to fill every section. The empty page is also a thought.",
    }
  ];
  const current = steps[step];

  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, zIndex: 100,
        background: "color-mix(in oklab, var(--bg-sunk) 70%, transparent)",
        backdropFilter: "blur(10px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 24,
      }}>
      <div
        onClick={e => e.stopPropagation()}
        className="surface fade-up"
        style={{
          maxWidth: 540, width: "100%",
          padding: "40px 44px",
          boxShadow: "var(--shadow-lift)",
          background: "var(--bg-elev)"
        }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 28 }}>
          <window.Tag kind={current.kind === "constant" ? "constant" : current.kind === "nucleus" ? "nucleus" : current.kind}>{current.tag}</window.Tag>
          <button className="btn ghost tiny" onClick={onClose}>skip</button>
        </div>

        <h2 className="concept-title" style={{ fontSize: 38, lineHeight: 1.1, margin: "0 0 18px" }}>
          {current.title}
        </h2>
        <p style={{ fontSize: 16, lineHeight: 1.6, color: "var(--fg-2)", margin: "0 0 32px", maxWidth: 460, fontFamily: "var(--font-serif)" }}>
          {current.body}
        </p>

        {/* Step indicator */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14 }}>
          <div style={{ display: "flex", gap: 5 }}>
            {steps.map((_, i) => (
              <span key={i}
                onClick={() => setStep(i)}
                style={{
                  width: i === step ? 22 : 5, height: 5, borderRadius: 999,
                  background: i === step ? "var(--c-nucleus)" : i < step ? "var(--fg-3)" : "var(--border)",
                  transition: "width 220ms, background 220ms",
                  cursor: "pointer"
                }} />
            ))}
          </div>
          <div style={{ display: "flex", gap: 8 }}>
            {step > 0 && <button className="btn ghost" onClick={() => setStep(step - 1)}>back</button>}
            {step < steps.length - 1
              ? <button className="btn primary" onClick={() => setStep(step + 1)}>continue</button>
              : <button className="btn primary" onClick={onClose}>begin</button>
            }
          </div>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Tweaks panel
function SenesPanel({ t, setTweak }) {
  return (
    <window.TweaksPanel>
      <window.TweakSection label="Theme" />
      <window.TweakToggle label="Dark" value={t.dark} onChange={(v) => setTweak('dark', v)} />
      <window.TweakRadio label="Vibe" value={t.vibe}
        options={['default', 'journal', 'cosmic']}
        onChange={(v) => setTweak('vibe', v)} />
      <window.TweakColor label="Nucleus hue" value={t.nucleusHue}
        options={['#FF8800', '#E08C4A', '#C97A57', '#9BAF6E', '#8FA3C9']}
        onChange={(v) => setTweak('nucleusHue', v)} />

      <window.TweakSection label="Layout" />
      <window.TweakRadio label="Density" value={t.density}
        options={['compact', 'regular', 'comfy']}
        onChange={(v) => setTweak('density', v)} />

      <window.TweakSection label="Solar system" />
      <window.TweakRadio label="Style" value={t.solarStyle}
        options={['planets', 'rings', 'atoms']}
        onChange={(v) => setTweak('solarStyle', v)} />

      <window.TweakSection label="Help" />
      <window.TweakToggle label="Show onboarding" value={t.showOnboarding}
        onChange={(v) => setTweak('showOnboarding', v)} />
    </window.TweaksPanel>
  );
}

// ─────────────────────────────────────────────────────────────────
// Google Drive setup modal
function SetupDriveModal({ onSave, onClose }) {
  const [clientId, setClientId] = _a_us(
    localStorage.getItem("disene_gdrive_client_id") || ""
  );
  const [step, setStep] = _a_us(0);  // 0 = instructions, 1 = input

  const steps = [
    {
      title: "Connect Google Drive",
      body: "disene syncs your concepts to your personal Google Drive — in a hidden folder only this app can read. No server, no sharing. You need a free Google Cloud project to get started.",
      action: "Show me how",
      onAction: () => setStep(1),
    },
    {
      title: "Paste your Client ID",
      body: null,
      action: "Connect & Sign in",
      onAction: () => clientId.trim().length > 10 && onSave(clientId.trim()),
    },
  ];
  const s = steps[step];

  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, zIndex: 200,
        background: "color-mix(in oklab, var(--bg-sunk) 70%, transparent)",
        backdropFilter: "blur(10px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 24,
      }}
    >
      <div
        onClick={e => e.stopPropagation()}
        className="surface fade-up"
        style={{ maxWidth: 520, width: "100%", padding: "40px 44px", boxShadow: "var(--shadow-lift)", background: "var(--bg-elev)" }}
      >
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 24 }}>
          <window.Tag kind="function">Google Drive</window.Tag>
          <button className="btn ghost tiny" onClick={onClose}>cancel</button>
        </div>

        <h2 className="concept-title" style={{ fontSize: 32, lineHeight: 1.1, margin: "0 0 14px" }}>
          {s.title}
        </h2>

        {s.body && (
          <p style={{ fontSize: 15, lineHeight: 1.65, color: "var(--fg-2)", margin: "0 0 28px", fontFamily: "var(--font-serif)" }}>
            {s.body}
          </p>
        )}

        {step === 0 && (
          <ol style={{ paddingLeft: 18, display: "flex", flexDirection: "column", gap: 12, marginBottom: 32, color: "var(--fg-2)", fontSize: 14, lineHeight: 1.6 }}>
            <li>
              Open <a href="https://console.cloud.google.com" target="_blank" rel="noreferrer" style={{ color: "var(--c-function)", textDecoration: "none", borderBottom: "1px solid currentColor" }}>console.cloud.google.com</a> → create a new project
            </li>
            <li>
              <strong style={{ color: "var(--fg)" }}>APIs &amp; Services → Library</strong> → search <em>Google Drive API</em> → Enable
            </li>
            <li>
              <strong style={{ color: "var(--fg)" }}>APIs &amp; Services → OAuth consent screen</strong> → User type: <em>External</em> → fill in App name &amp; your email → Save
            </li>
            <li>
              <strong style={{ color: "var(--fg)" }}>APIs &amp; Services → Audience</strong> → scroll to <strong style={{ color: "var(--fg)" }}>Test users</strong> → <strong style={{ color: "var(--fg)" }}>+ Add users</strong> → add your Google email.{" "}
              <span style={{ color: "var(--fg-3)" }}>Required — without this Google blocks sign-in with error 403.</span>
            </li>
            <li>
              <strong style={{ color: "var(--fg)" }}>Credentials → + Create Credentials → OAuth 2.0 Client ID</strong> → Application type: <em>Web application</em> → add{" "}
              <code style={{ fontFamily: "var(--font-mono)", fontSize: 12, background: "var(--bg-sunk)", padding: "1px 6px", borderRadius: 4 }}>{window.location.origin}</code>{" "}
              to Authorized JavaScript origins
            </li>
            <li>
              Copy the <strong style={{ color: "var(--fg)" }}>Client ID</strong> → paste it on the next screen
            </li>
          </ol>
        )}

        {step === 1 && (
          <div style={{ marginBottom: 28 }}>
            <div className="field" style={{ marginBottom: 8 }}>
              <input
                autoFocus
                type="text"
                value={clientId}
                onChange={e => setClientId(e.target.value)}
                onKeyDown={e => e.key === "Enter" && clientId.trim().length > 10 && onSave(clientId.trim())}
                placeholder="123456789-abc…apps.googleusercontent.com"
                spellCheck={false}
                style={{ fontFamily: "var(--font-mono)", fontSize: 12 }}
              />
            </div>
            <p style={{ fontSize: 12, color: "var(--fg-3)", fontFamily: "var(--font-mono)", lineHeight: 1.5 }}>
              Stored only in your browser's localStorage. Never sent anywhere except Google.
            </p>
          </div>
        )}

        <div style={{ display: "flex", gap: 10, justifyContent: "space-between", alignItems: "center" }}>
          {step === 1 && (
            <button className="btn ghost tiny" onClick={() => setStep(0)}>← back</button>
          )}
          <div style={{ marginLeft: "auto" }}>
            <button
              className="btn primary"
              onClick={s.onAction}
              disabled={step === 1 && clientId.trim().length <= 10}
              style={{ opacity: step === 1 && clientId.trim().length <= 10 ? 0.45 : 1 }}
            >
              {s.action}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Upgrade modal — shown when concepts count >= 100
function UpgradeModal({ onClose }) {
  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, zIndex: 200,
        background: "color-mix(in oklab, var(--bg-sunk) 70%, transparent)",
        backdropFilter: "blur(10px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 24,
      }}>
      <div
        onClick={e => e.stopPropagation()}
        className="surface fade-up"
        style={{
          maxWidth: 440, width: "100%",
          padding: "40px 44px",
          boxShadow: "var(--shadow-lift)",
          background: "var(--bg-elev)",
        }}>
        <window.Tag kind="nucleus" style={{ marginBottom: 24 }}>limit reached</window.Tag>
        <h2 className="concept-title" style={{ fontSize: 34, lineHeight: 1.1, margin: "0 0 16px" }}>
          100 concepts
        </h2>
        <p style={{ fontSize: 15, lineHeight: 1.6, color: "var(--fg-2)", margin: "0 0 32px", fontFamily: "var(--font-serif)" }}>
          You've reached the concept limit. Remove some concepts to make room, or upgrade for unlimited storage.
        </p>
        <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
          <button className="btn ghost" onClick={onClose}>close</button>
        </div>
      </div>
    </div>
  );
}

// Boot
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
