const { useEffect, useMemo, useRef, useState } = React;

const CFG = window.OLYARI_CONFIG || { API_BASE: "", PRICE_USD: "9.99", APP_NAME: "Olyari" };

function cx(...xs){ return xs.filter(Boolean).join(" "); }

function parseHash(){
  const h = (location.hash || "#/dashboard").replace(/^#/, "");
  const [path, query] = h.split("?");
  const parts = path.split("/").filter(Boolean);
  const q = new URLSearchParams(query || "");
  return { parts, q, raw: h };
}

function useRoute(){
  const [r, setR] = useState(parseHash());
  useEffect(() => {
    const on = () => setR(parseHash());
    window.addEventListener("hashchange", on);
    return () => window.removeEventListener("hashchange", on);
  }, []);
  return r;
}

async function api(path, opts={}){
  const url = (CFG.API_BASE || "").replace(/\/$/, "") + path;
  const res = await fetch(url, {
    credentials: "include",
    ...opts,
    headers: {
      "Content-Type": "application/json",
      ...(opts.headers || {})
    }
  });
  const text = await res.text();
  let json = null;
  try { json = text ? JSON.parse(text) : null; } catch {}
  if(!res.ok){
    const msg = (json && (json.error || json.message)) || text || `HTTP ${res.status}`;
    throw new Error(msg);
  }
  return json;
}

function TopBar({ me }){
  return (
    <div className="flex items-center justify-between px-4 py-3 border-b" style={{borderColor:"var(--line)", background:"rgba(7,11,20,.35)"}}>
      <div className="flex items-center gap-3">
        <div className="w-9 h-9 rounded-xl flex items-center justify-center"
             style={{background:"linear-gradient(180deg, rgba(251,191,36,.18), rgba(245,158,11,.08))", border:"1px solid rgba(251,191,36,.25)"}}>
          <span className="text-lg">⟁</span>
        </div>
        <div>
          <div className="font-semibold tracking-wide">{CFG.APP_NAME} <span className="text-sm opacity-70">Game Creator OS</span></div>
          <div className="text-xs opacity-70">Forge • Variants • Canon • Regions • OS • NPCs</div>
        </div>
      </div>

      <div className="flex items-center gap-2">
        <span className="oly-chip px-3 py-1 text-xs">
          {me?.active ? "Subscribed" : "Locked"} • {me?.email || "Guest"}
        </span>
        <a className="oly-btn px-3 py-1.5 text-sm" href="#/monitor">Live Monitor</a>
      </div>
    </div>
  );
}

const NAV = [
  { to:"#/dashboard", label:"Dashboard", icon:"⌂" },

  { to:"#/engine/pixel", label:"Pixel Forge", icon:"🎨" },
  { to:"#/engine/variants", label:"Variants", icon:"🧬" },
  { to:"#/engine/narrative", label:"Quest + Dialogue", icon:"🧩" },
  { to:"#/engine/world", label:"Region Kit", icon:"🌍" },
  { to:"#/engine/os", label:"Game OS Templates", icon:"🧱" },
  { to:"#/engine/npc", label:"NPC Engine", icon:"🧠" },

  { to:"#/profile", label:"Profile", icon:"👤" },
  { to:"#/forum", label:"Community", icon:"💬" },
  { to:"#/billing", label:"Billing", icon:"💳" },
];

function SideNav({ current }){
  return (
    <div className="p-3 flex flex-col gap-1">
      {NAV.map(item => {
        const active = current.startsWith(item.to.replace("#",""));
        return (
          <a key={item.to} href={item.to}
             className={cx("px-3 py-2 rounded-xl flex items-center gap-2 text-sm",
                          active ? "text-white" : "text-white/70 hover:text-white")}
             style={{
               background: active ? "rgba(251,191,36,.10)" : "transparent",
               border: active ? "1px solid rgba(251,191,36,.18)" : "1px solid transparent"
             }}>
            <span className="w-6 text-center">{item.icon}</span>
            <span className="truncate">{item.label}</span>
          </a>
        );
      })}
    </div>
  );
}

function Shell({ me, children }){
  return (
    <div className="h-screen w-screen flex flex-col">
      <TopBar me={me} />
      <div className="flex min-h-0 flex-1">
        <div className="w-60 shrink-0 border-r" style={{borderColor:"var(--line)", background:"rgba(7,11,20,.25)"}}>
          <SideNav current={location.hash.replace(/^#/,"")} />
        </div>
        <div className="flex-1 min-w-0 p-4 overflow-auto">
          {children}
        </div>
      </div>
    </div>
  );
}

function Card({ title, subtitle, children, right }){
  return (
    <div className="oly-card p-4">
      <div className="flex items-start justify-between gap-3 mb-3">
        <div>
          <div className="font-semibold">{title}</div>
          {subtitle && <div className="text-xs opacity-70 mt-0.5">{subtitle}</div>}
        </div>
        {right}
      </div>
      {children}
    </div>
  );
}

function KVPill({ k, v }){
  return (
    <div className="oly-chip px-3 py-1 text-xs flex items-center gap-2">
      <span className="opacity-70">{k}</span>
      <span className="font-semibold">{v}</span>
    </div>
  );
}

function useMe(){
  const [me, setMe] = useState(null);
  const [loading, setLoading] = useState(true);
  const refresh = async () => {
    setLoading(true);
    try {
      const data = await api("/me");
      setMe(data);
    } catch {
      setMe(null);
    } finally {
      setLoading(false);
    }
  };
  useEffect(() => { refresh(); }, []);
  return { me, loading, refresh };
}

const ENGINE_DEFS = {
  pixel: {
    title: "Pixel-Art Asset Generator",
    tagline: "Style DNA + placement prompt → modular prompt pack + palette + constraints.",
    settings: [
      { key:"styleProfile", label:"Style Profile", type:"select", options:["Olyari Noir", "High-Fantasy Grit", "Cozy Medieval", "Gothic Silver"], default:"Olyari Noir" },
      { key:"tileSize", label:"Tile Size", type:"select", options:["16", "24", "32", "48"], default:"32" },
      { key:"dither", label:"Dithering (0–1)", type:"range", min:0, max:1, step:0.05, default:0.35 },
      { key:"shade", label:"Shading (0–1)", type:"range", min:0, max:1, step:0.05, default:0.55 },
      { key:"palette", label:"Palette Hints", type:"text", default:"dark navy, silver highlights, warm orange torchlight, muted stone" },
      { key:"negatives", label:"Avoid", type:"text", default:"photorealism, blurry, noisy JPEG, text, watermark" },
    ],
    example: "A medieval apothecary shop sign hanging from a wrought-iron bracket; readable silhouette; 3/4 view; warm lantern glow."
  },
  variants: {
    title: "Recoloring + Variant Engine",
    tagline: "One base asset → controlled variants by biome, season, faction, rarity.",
    settings: [
      { key:"variantCount", label:"Variants", type:"select", options:["6","8","12"], default:"8" },
      { key:"variantAxes", label:"Axes", type:"text", default:"biome, season, rarity, faction" },
      { key:"keepSilhouette", label:"Keep silhouette?", type:"select", options:["yes","no"], default:"yes" },
      { key:"paletteRules", label:"Palette Rules", type:"text", default:"keep values consistent; swap hue families only; preserve highlight/shadow ratios" },
    ],
    example: "Base asset: leather satchel. Generate variants for: swamp, tundra, desert, city, royal, bandit."
  },
  narrative: {
    title: "Quest / Dialogue Generator (Canon Memory)",
    tagline: "Quest structure + dialogue beats + canon rules + tags you can store.",
    settings: [
      { key:"tone", label:"Tone", type:"select", options:["mysterious","cozy","dark","epic"], default:"mysterious" },
      { key:"length", label:"Quest Length", type:"select", options:["short","medium","long"], default:"medium" },
      { key:"canonStrict", label:"Canon Strictness", type:"select", options:["high","medium","low"], default:"high" },
      { key:"characters", label:"Key Characters", type:"text", default:"Maeve-type protagonist, archivist NPC, vendor NPC" },
    ],
    example: "Quest about a town fountain that whispers at night; player must collect 3 glyph fragments and confront a memory-eating echo."
  },
  world: {
    title: "Region-by-Region Worldbuilding Kit",
    tagline: "Biome schema + palette + tileset list + landmarks + encounter table.",
    settings: [
      { key:"biome", label:"Biome", type:"select", options:["coastal","mountain","forest","swamp","tundra","desert"], default:"forest" },
      { key:"mood", label:"Mood", type:"select", options:["dreamlike","ominous","peaceful","decayed","mythic"], default:"dreamlike" },
      { key:"scale", label:"Scale", type:"select", options:["small","medium","large"], default:"medium" },
      { key:"tileNeeds", label:"Tile Needs", type:"text", default:"ground, path, water edge, cliff, wall, roof, door, window, foliage" },
    ],
    example: "Design a fog-draped coastal hamlet with silver rock beaches and a plateau city above it."
  },
  os: {
    title: "Modular Game OS Templates",
    tagline: "Starter folder tree + manifests + systems list + starter content pack.",
    settings: [
      { key:"engine", label:"Target Stack", type:"select", options:["Web (Canvas)","Unity 2D","Godot 2D","Custom"], default:"Web (Canvas)" },
      { key:"systems", label:"Core Systems", type:"text", default:"inventory, quests, dialogue, crafting, save/load, map, time/weather" },
      { key:"dataFormat", label:"Data Format", type:"select", options:["JSON","YAML"], default:"JSON" },
    ],
    example: "Generate a starter OS template for a cozy mystery farm RPG with a glyph puzzle layer."
  },
  npc: {
    title: "NPC Personality + Behavior Engine",
    tagline: "Sliders + schedules + memory fragments + dialogue style + hooks.",
    settings: [
      { key:"npcCount", label:"NPCs", type:"select", options:["1","3","5"], default:"3" },
      { key:"complexity", label:"Complexity", type:"select", options:["lite","standard","deep"], default:"standard" },
      { key:"memoryMode", label:"Memory Mode", type:"select", options:["none","local","canon"], default:"canon" },
      { key:"themes", label:"Themes", type:"text", default:"loyalty, secrecy, folklore, guilt, tenderness" },
    ],
    example: "Create an apothecary mentor who is ancient, calm, and slightly terrifying when truth is threatened."
  }
};

function Field({ def, value, onChange }){
  const common = "oly-input w-full px-3 py-2 text-sm";
  if(def.type === "select"){
    return (
      <select className={common} value={value} onChange={e=>onChange(e.target.value)}>
        {def.options.map(o => <option key={o} value={o}>{o}</option>)}
      </select>
    );
  }
  if(def.type === "range"){
    return (
      <div className="flex items-center gap-3">
        <input className="w-full" type="range" min={def.min} max={def.max} step={def.step}
               value={value} onChange={e=>onChange(parseFloat(e.target.value))} />
        <span className="oly-chip px-2 py-1 text-xs w-14 text-center">{String(value)}</span>
      </div>
    );
  }
  return (
    <input className={common} value={value} onChange={e=>onChange(e.target.value)} placeholder={def.default || ""} />
  );
}

function EnginePage({ engineKey, me }){
  const def = ENGINE_DEFS[engineKey];
  const [prompt, setPrompt] = useState(def.example);
  const [settings, setSettings] = useState(() => {
    const o = {};
    def.settings.forEach(s => o[s.key] = s.type==="range" ? s.default : (s.default ?? ""));
    return o;
  });
  const [busy, setBusy] = useState(false);
  const [out, setOut] = useState(null);
  const [history, setHistory] = useState([]);
  const [err, setErr] = useState("");

  const load = async () => {
    try {
      const data = await api(`/engine/${engineKey}/list?limit=12`);
      setHistory(data.items || []);
    } catch {}
  };
  useEffect(() => { load(); }, [engineKey]);

  const generate = async () => {
    setErr("");
    setBusy(true);
    try {
      const data = await api(`/engine/${engineKey}/generate`, {
        method:"POST",
        body: JSON.stringify({ prompt, settings })
      });
      setOut(data);
      await load();
    } catch(e){
      setErr(e.message || "Generation failed");
    } finally {
      setBusy(false);
    }
  };

  const downloadJSON = () => {
    const blob = new Blob([JSON.stringify(out, null, 2)], { type:"application/json" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = `olyari_${engineKey}_${Date.now()}.json`;
    a.click();
    URL.revokeObjectURL(a.href);
  };

  return (
    <div className="grid grid-cols-12 gap-4">
      <div className="col-span-12 xl:col-span-4 space-y-4">
        <Card title={def.title} subtitle={def.tagline}
          right={<span className="oly-chip px-3 py-1 text-xs">Engine: {engineKey}</span>}
        >
          <div className="space-y-3">
            {def.settings.map(s => (
              <div key={s.key}>
                <div className="text-xs opacity-75 mb-1">{s.label}</div>
                <Field def={s} value={settings[s.key]} onChange={(v)=>setSettings(prev=>({...prev,[s.key]:v}))} />
              </div>
            ))}

            <div className="pt-2">
              <div className="text-xs opacity-75 mb-1">Placement Prompt</div>
              <textarea className="oly-input w-full px-3 py-2 text-sm min-h-[120px]"
                        value={prompt} onChange={e=>setPrompt(e.target.value)} />
            </div>

            <div className="flex items-center gap-2 pt-1">
              <button className="oly-btn px-4 py-2 text-sm font-semibold"
                      onClick={generate} disabled={busy}>
                {busy ? "Forging…" : "Generate"}
              </button>
              <a className="oly-btn px-4 py-2 text-sm" href="#/monitor">Monitor</a>
              {out && <button className="oly-btn px-4 py-2 text-sm" onClick={downloadJSON}>Export JSON</button>}
            </div>

            {err && (
              <div className="oly-panel p-3 text-sm" style={{borderColor:"rgba(249,115,22,.35)"}}>
                <div className="font-semibold" style={{color:"var(--orange)"}}>Error</div>
                <div className="opacity-80 mt-1">{err}</div>
              </div>
            )}
          </div>
        </Card>

        <Card title="Asset / Output Library" subtitle="Your last 12 runs (stored server-side)">
          <div className="space-y-2">
            {history.length === 0 && <div className="text-sm opacity-70">No outputs yet.</div>}
            {history.map(it => (
              <div key={it.id} className="oly-panel p-3">
                <div className="flex items-start justify-between gap-2">
                  <div>
                    <div className="text-sm font-semibold">{it.title || it.id}</div>
                    <div className="text-xs opacity-70">{new Date(it.createdAt).toLocaleString()}</div>
                  </div>
                  <button className="oly-btn px-3 py-1.5 text-xs" onClick={()=>setOut(it.data)}>Load</button>
                </div>
              </div>
            ))}
          </div>
        </Card>
      </div>

      <div className="col-span-12 xl:col-span-8 space-y-4">
        <Card title="Preview Canvas" subtitle="MVP preview = structured JSON + prompt packs (image generation can be plugged in next).">
          {!out ? (
            <div className="oly-panel p-6 text-sm opacity-75">
              Generate once and your forged output appears here: prompt pack, palette, modules, rules, manifests, etc.
            </div>
          ) : (
            <div className="grid grid-cols-1 gap-3">
              <div className="flex flex-wrap gap-2">
                <KVPill k="User" v={me?.email || "Guest"} />
                <KVPill k="Plan" v={me?.active ? "Active" : "Locked"} />
                <KVPill k="Stored" v="KV" />
              </div>
              <pre className="oly-panel p-4 overflow-auto oly-code" style={{maxHeight: "65vh"}}>
{JSON.stringify(out, null, 2)}
              </pre>
            </div>
          )}
        </Card>
      </div>
    </div>
  );
}

function Dashboard({ me }){
  return (
    <div className="grid grid-cols-12 gap-4">
      <div className="col-span-12 xl:col-span-7">
        <Card title="Nexus Dashboard" subtitle="Your bridge. Your engines. Your canon.">
          <div className="flex flex-wrap gap-2 mb-4">
            <KVPill k="Subscription" v={me?.active ? "Active" : "Locked"} />
            <KVPill k="Email" v={me?.email || "—"} />
            <KVPill k="Tier" v={`$${CFG.PRICE_USD}/mo`} />
          </div>
          <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
            {[
              ["#/engine/pixel","🎨","Pixel Forge"],
              ["#/engine/variants","🧬","Variants"],
              ["#/engine/narrative","🧩","Quest+Dialogue"],
              ["#/engine/world","🌍","Region Kit"],
              ["#/engine/os","🧱","OS Templates"],
              ["#/engine/npc","🧠","NPC Engine"],
            ].map(([to,icon,label])=>(
              <a key={to} href={to} className="oly-panel p-4 hover:opacity-95">
                <div className="text-2xl">{icon}</div>
                <div className="mt-2 font-semibold">{label}</div>
                <div className="text-xs opacity-70 mt-1">Open engine</div>
              </a>
            ))}
          </div>
        </Card>
      </div>

      <div className="col-span-12 xl:col-span-5 space-y-4">
        <Card title="Quick Actions" subtitle="Zero-CLI survival kit.">
          <div className="space-y-2">
            <a href="#/billing" className="oly-btn inline-block px-4 py-2 text-sm">Billing / Paywall</a>
            <a href="#/forum" className="oly-btn inline-block px-4 py-2 text-sm ml-2">Community</a>
            <a href="#/monitor" className="oly-btn inline-block px-4 py-2 text-sm ml-2">Live Monitor</a>
          </div>
          <div className="text-sm opacity-75 mt-3">
            Monitor gives you health + logs so you can debug without DevTools.
          </div>
        </Card>

        <Card title="Design Note" subtitle="Olyari palette (navy + gold/orange + silver).">
          <div className="text-sm opacity-80">
            Your engines are wired so the backend returns structured outputs first.
            Image generation (pixel renders) can be swapped in later without changing the UI.
          </div>
        </Card>
      </div>
    </div>
  );
}

function Profile({ me }){
  return (
    <Card title="Creator Profile" subtitle="Level, collections, worlds (MVP scaffolding).">
      <div className="flex flex-wrap gap-2 mb-4">
        <KVPill k="Creator" v={me?.email || "Guest"} />
        <KVPill k="Level" v={me?.active ? "Founder" : "Observer"} />
        <KVPill k="Collections" v="Engines x6" />
      </div>
      <div className="oly-panel p-4 text-sm opacity-80">
        Next step: show your saved outputs by engine + allow “pin to canon” tags.
      </div>
    </Card>
  );
}

function Forum(){
  const [posts, setPosts] = useState([]);
  const [text, setText] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState("");

  const load = async () => {
    try {
      const data = await api("/forum/posts?limit=50");
      setPosts(data.posts || []);
    } catch {}
  };
  useEffect(()=>{ load(); }, []);

  const submit = async () => {
    setErr("");
    setBusy(true);
    try{
      await api("/forum/post", { method:"POST", body: JSON.stringify({ text }) });
      setText("");
      await load();
    }catch(e){
      setErr(e.message || "Post failed");
    }finally{
      setBusy(false);
    }
  };

  return (
    <div className="grid grid-cols-12 gap-4">
      <div className="col-span-12 xl:col-span-5">
        <Card title="Community Forum" subtitle="Share kits, NPC cores, prompt packs (KV-based MVP).">
          <div className="text-xs opacity-70 mb-1">New post</div>
          <textarea className="oly-input w-full px-3 py-2 text-sm min-h-[120px]"
                    value={text} onChange={e=>setText(e.target.value)} />
          <div className="flex items-center gap-2 mt-2">
            <button className="oly-btn px-4 py-2 text-sm font-semibold" onClick={submit} disabled={busy || !text.trim()}>
              {busy ? "Posting…" : "Post"}
            </button>
            <button className="oly-btn px-4 py-2 text-sm" onClick={load}>Refresh</button>
          </div>
          {err && <div className="text-sm mt-3" style={{color:"var(--orange)"}}>{err}</div>}
        </Card>
      </div>

      <div className="col-span-12 xl:col-span-7">
        <Card title="Feed" subtitle="Latest 50">
          <div className="space-y-2">
            {posts.length === 0 && <div className="text-sm opacity-70">No posts yet.</div>}
            {posts.map(p => (
              <div key={p.id} className="oly-panel p-3">
                <div className="text-xs opacity-70">{new Date(p.createdAt).toLocaleString()} • {p.author || "anon"}</div>
                <div className="text-sm mt-1 whitespace-pre-wrap">{p.text}</div>
              </div>
            ))}
          </div>
        </Card>
      </div>
    </div>
  );
}

function Monitor({ refreshMe }){
  const [health, setHealth] = useState(null);
  const [logs, setLogs] = useState([]);
  const [err, setErr] = useState("");

  const load = async () => {
    setErr("");
    try{
      const h = await api("/health");
      setHealth(h);
      const l = await api("/logs?limit=120");
      setLogs(l.logs || []);
    }catch(e){
      setErr(e.message || "Monitor failed");
    }
  };

  useEffect(()=>{ load(); }, []);

  return (
    <div className="space-y-4">
      <Card title="Live Monitor" subtitle="Health + backend logs (because you refused DevTools like a legend)."
        right={<button className="oly-btn px-4 py-2 text-sm" onClick={load}>Refresh</button>}
      >
        {err && <div className="text-sm mb-3" style={{color:"var(--orange)"}}>{err}</div>}
        <div className="flex flex-wrap gap-2">
          <KVPill k="API" v={CFG.API_BASE || "not set"} />
          <KVPill k="Health" v={health?.ok ? "OK" : "—"} />
          <KVPill k="Time" v={health?.time || "—"} />
        </div>
        <div className="mt-3 text-sm opacity-80">
          Tip: If Health works but engines fail, your OpenAI/Stripe env vars are the usual culprit.
        </div>
      </Card>

      <Card title="Server Logs" subtitle="Latest events (stored in KV)">
        <pre className="oly-panel p-4 oly-code overflow-auto" style={{maxHeight:"65vh"}}>
{(logs || []).map(l => `[${l.ts}] ${l.level.toUpperCase()} ${l.msg} ${l.data ? JSON.stringify(l.data) : ""}`).join("\n")}
        </pre>
      </Card>

      <Card title="Session Controls" subtitle="Force refresh your /me state">
        <button className="oly-btn px-4 py-2 text-sm" onClick={refreshMe}>Refresh /me</button>
      </Card>
    </div>
  );
}

function Billing({ me, refreshMe, route }){
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState("");

  const startCheckout = async () => {
    setErr("");
    setBusy(true);
    try{
      const data = await api("/stripe/checkout", { method:"POST", body: JSON.stringify({}) });
      if(!data?.url) throw new Error("No checkout URL returned");
      location.href = data.url;
    }catch(e){
      setErr(e.message || "Checkout failed");
      setBusy(false);
    }
  };

  const verifyIfNeeded = async () => {
    const parts = route.parts;
    const isSuccess = parts[0]==="billing" && parts[1]==="success";
    if(!isSuccess) return;
    const sessionId = route.q.get("session_id");
    if(!sessionId) return;
    setBusy(true);
    setErr("");
    try{
      await api(`/stripe/verify?session_id=${encodeURIComponent(sessionId)}`);
      await refreshMe();
      location.hash = "#/dashboard";
    }catch(e){
      setErr(e.message || "Verify failed");
      setBusy(false);
    }
  };

  useEffect(()=>{ verifyIfNeeded(); }, [route.raw]);

  return (
    <Card title="Billing / Paywall" subtitle={`Subscription: $${CFG.PRICE_USD}/month`}>
      <div className="flex flex-wrap gap-2 mb-4">
        <KVPill k="Status" v={me?.active ? "Active" : "Locked"} />
        <KVPill k="Email" v={me?.email || "—"} />
      </div>

      <div className="oly-panel p-4 text-sm opacity-85">
  This MVP uses Stripe Checkout to create a monthly subscription, then sets an app session cookie after verification.
  Webhooks keep your subscription status synced on renewals/cancellations.
</div>

      <div className="mt-4 flex items-center gap-2">
        {!me?.active && (
          <button className="oly-btn px-4 py-2 text-sm font-semibold" onClick={startCheckout} disabled={busy}>
            {busy ? "Opening Stripe…" : "Subscribe & Unlock"}
          </button>
        )}
        <a className="oly-btn px-4 py-2 text-sm" href="#/dashboard">Back</a>
      </div>

      {err && <div className="text-sm mt-4" style={{color:"var(--orange)"}}>{err}</div>}
      <div className="text-xs opacity-70 mt-3">
        Success URL auto-verifies: <span className="oly-code">#/billing/success?session_id=...</span>
      </div>
    </Card>
  );
}

function LockedOverlay(){
  return (
    <div className="fixed inset-0 flex items-center justify-center p-4"
         style={{background:"rgba(0,0,0,.60)", backdropFilter:"blur(6px)"}}>
      <div className="oly-card p-6 max-w-xl w-full">
        <div className="text-xl font-semibold">Olyari is sealed.</div>
        <div className="text-sm opacity-80 mt-2">
          Subscribe to unlock the engines. Your outputs will then store server-side and appear in your library.
        </div>
        <div className="mt-4 flex gap-2">
          <a className="oly-btn px-4 py-2 text-sm font-semibold" href="#/billing">Go to Billing</a>
          <a className="oly-btn px-4 py-2 text-sm" href="#/monitor">Open Monitor</a>
        </div>
      </div>
    </div>
  );
}

function App(){
  const route = useRoute();
  const { me, loading, refresh } = useMe();

  const view = () => {
    const p = route.parts;

    if(p[0] === "dashboard") return <Dashboard me={me} />;
    if(p[0] === "engine") {
      const key = p[1];
      return <EnginePage engineKey={key} me={me} />;
    }
    if(p[0] === "profile") return <Profile me={me} />;
    if(p[0] === "forum") return <Forum />;
    if(p[0] === "monitor") return <Monitor refreshMe={refresh} />;
    if(p[0] === "billing") return <Billing me={me} refreshMe={refresh} route={route} />;

    return <Dashboard me={me} />;
  };

  const requiresActive =
    route.parts[0] === "engine" ||
    route.parts[0] === "forum" ||
    route.parts[0] === "profile";

  return (
    <Shell me={me}>
      {view()}
      {!loading && requiresActive && !me?.active && <LockedOverlay />}
    </Shell>
  );
}

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