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 (
{CFG.APP_NAME} Game Creator OS
Forge • Variants • Canon • Regions • OS • NPCs
{me?.active ? "Subscribed" : "Locked"} • {me?.email || "Guest"} Live Monitor
); } 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 (
{NAV.map(item => { const active = current.startsWith(item.to.replace("#","")); return ( {item.icon} {item.label} ); })}
); } function Shell({ me, children }){ return (
{children}
); } function Card({ title, subtitle, children, right }){ return (
{title}
{subtitle &&
{subtitle}
}
{right}
{children}
); } function KVPill({ k, v }){ return (
{k} {v}
); } 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 ( ); } if(def.type === "range"){ return (
onChange(parseFloat(e.target.value))} /> {String(value)}
); } return ( 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 (
Engine: {engineKey}} >
{def.settings.map(s => (
{s.label}
setSettings(prev=>({...prev,[s.key]:v}))} />
))}
Placement Prompt