{
  "apps": [
    {
      "app_id": "gora_trader",
      "contract_bindings": [],
      "manifest_cid": "ipfs-local-sha256-bfde1e1c62747b068a1ee810c7223b6fca9049f54e570efb4386db016856037b",
      "manifest_json": {
        "agent": {
          "agent_id": "agent:gora_trader:v1",
          "agent_wallet": null,
          "description": "An autonomous trading bot that lives on Gora. Deposit GORA, withdraw any time, and watch every decision — attested, with its reasoning."
        },
        "app_id": "gora_trader",
        "artifact": {
          "entrypoint": "run",
          "kind": "wasm",
          "output_length": 2048,
          "path": "artifacts/program.wasm"
        },
        "capabilities": {
          "actions": [
            "offchain_result"
          ],
          "chains": [
            "algorand"
          ],
          "execution_modes": [
            "offchain_result"
          ]
        },
        "contracts": [],
        "developer": {
          "app_kind": "offchain_only",
          "chain": "algorand",
          "has_contract_component": false,
          "has_wallet_component": false,
          "source_language": "rust",
          "template": "generic"
        },
        "did": "did:gora:developer:gora_trader",
        "ios": {
          "display_name": "Gora Trading Bot",
          "icon_system_name": "chart.line.uptrend.xyaxis",
          "tagline": "An AI fund you can join. Deposit, withdraw, watch it think."
        },
        "name": "gora-trading-bot",
        "payment_principles": {
          "default_fee_payer": "user_or_user_preapproved_on_chain_account",
          "gora_node_holds_user_keys": false,
          "gora_node_pays_user_gas": false
        },
        "policy": {
          "path": "policy.json"
        },
        "schema_version": 1,
        "transaction_builders": [],
        "ui": {
          "bridge": "gora_webview_v1",
          "html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1\">\n<meta name=\"color-scheme\" content=\"dark\">\n<title>Gora Trading Bot</title>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n<style>\n  :root {\n    --bg: #090b0f;\n    --bg-2: #0f131a;\n    --card: rgba(255,255,255,.035);\n    --line: rgba(255,255,255,.09);\n    --ink: #eef2f7;\n    --ink-2: rgba(238,242,247,.68);\n    --ink-3: rgba(238,242,247,.44);\n    --accent: #6ea0ff;        /* series color: the fund line */\n    --accent-2: #9dc0ff;\n    --up: #34d399;            /* status: good */\n    --down: #ff6b6b;          /* status: serious */\n    --ai: #b48cff;\n    --radius: 20px;\n    --sat: env(safe-area-inset-top, 0px);\n    --sab: env(safe-area-inset-bottom, 0px);\n    --f-ui: \"Space Grotesk\", -apple-system, system-ui, \"Segoe UI\", sans-serif;\n    --f-mono: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n  }\n  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }\n  html, body { margin: 0; background: var(--bg); color: var(--ink); }\n  body { font-family: var(--f-ui); font-size: 15px; line-height: 1.45; -webkit-font-smoothing: antialiased; overflow-x: hidden; touch-action: manipulation; min-height: 100vh;\n    background-image: radial-gradient(900px 420px at 80% -10%, rgba(110,160,255,.14), transparent 60%), radial-gradient(700px 400px at -10% 20%, rgba(180,140,255,.08), transparent 60%); }\n  button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }\n  button:disabled { opacity: .5; cursor: default; }\n  .mono { font-family: var(--f-mono); font-variant-numeric: tabular-nums; letter-spacing: -.01em; }\n\n  .top { padding: calc(var(--sat) + 16px) 20px 0; display: flex; justify-content: space-between; align-items: center; }\n  .brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 15px; letter-spacing: -.01em; }\n  .brand .mark { width: 26px; height: 26px; border-radius: 9px; background: linear-gradient(140deg, var(--accent-2), var(--accent) 55%, #2f5ed6); display: grid; place-items: center; box-shadow: 0 0 22px rgba(110,160,255,.35); }\n  .brand .mark svg { width: 15px; height: 15px; }\n  .pill { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 999px; background: rgba(255,255,255,.05); border: 1px solid var(--line); font-size: 12px; font-weight: 600; color: var(--ink-2); }\n  .pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--up); box-shadow: 0 0 10px var(--up); animation: pulse 2s infinite; }\n  .pill.preview .dot { background: #f6c76a; box-shadow: 0 0 10px #f6c76a; animation: none; }\n  @keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .45 } }\n\n  .page { padding: 18px 20px calc(var(--sab) + 120px); display: grid; gap: 14px; }\n  .card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; position: relative; overflow: hidden; }\n  .k { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); font-weight: 700; }\n\n  /* hero number + chart */\n  .hero .head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }\n  .hero .nav { font-size: 44px; line-height: 1; font-weight: 600; margin-top: 6px; letter-spacing: -.03em; }\n  .hero .nav small { font-size: 16px; color: var(--ink-3); font-weight: 500; margin-left: 6px; letter-spacing: 0; }\n  .delta { display: inline-flex; align-items: center; gap: 6px; margin-top: 10px; font-size: 13px; font-weight: 600; padding: 5px 9px; border-radius: 999px; }\n  .delta.up { color: var(--up); background: rgba(52,211,153,.12); }\n  .delta.down { color: var(--down); background: rgba(255,107,107,.12); }\n  .delta svg { width: 12px; height: 12px; }\n  .delta .since { color: var(--ink-3); font-weight: 500; }\n  .ranges { display: inline-flex; gap: 4px; padding: 3px; border-radius: 999px; background: rgba(255,255,255,.05); border: 1px solid var(--line); }\n  .ranges button { padding: 5px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; color: var(--ink-3); }\n  .ranges button[aria-pressed=\"true\"] { background: rgba(255,255,255,.1); color: var(--ink); }\n  .chart { margin: 16px -6px 0; position: relative; height: 176px; touch-action: pan-y; }\n  .chart svg { width: 100%; height: 100%; display: block; overflow: visible; }\n  .chart .grid line { stroke: rgba(255,255,255,.06); stroke-width: 1; }\n  .chart .ylab { font: 500 10px var(--f-mono); fill: var(--ink-3); }\n  .chart .line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }\n  .chart .area { fill: url(#fade); }\n  .chart .last { fill: var(--accent); stroke: var(--bg-2); stroke-width: 2; }\n  .chart .hair { stroke: rgba(255,255,255,.35); stroke-width: 1; stroke-dasharray: 2 3; display: none; }\n  .chart .hpt { fill: var(--bg-2); stroke: var(--accent); stroke-width: 2; display: none; }\n  .tip { position: absolute; top: 0; transform: translate(-50%, -6px); padding: 6px 9px; border-radius: 10px; background: #161c26; border: 1px solid var(--line); font-size: 12px; white-space: nowrap; display: none; pointer-events: none; box-shadow: 0 8px 24px rgba(0,0,0,.4); }\n  .tip b { display: block; font-weight: 600; }\n  .tip span { color: var(--ink-3); }\n  .facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 14px; }\n  .fact { padding: 10px 12px; border-radius: 14px; background: rgba(255,255,255,.035); border: 1px solid var(--line); }\n  .fact .v { font-size: 16px; font-weight: 600; margin-top: 3px; }\n  .fact .v small { color: var(--ink-3); font-size: 11px; font-weight: 500; margin-left: 2px; }\n\n  /* position */\n  .pos { display: grid; grid-template-columns: auto 1fr auto; gap: 14px; align-items: center; }\n  .side { width: 46px; height: 46px; border-radius: 14px; display: grid; place-items: center; font-weight: 700; font-size: 12px; letter-spacing: .04em; }\n  .side.long { background: rgba(52,211,153,.14); color: var(--up); border: 1px solid rgba(52,211,153,.35); }\n  .side.flat { background: rgba(255,255,255,.06); color: var(--ink-2); border: 1px solid var(--line); }\n  .pos .t { font-weight: 600; font-size: 16px; }\n  .pos .d { font-size: 12px; color: var(--ink-3); margin-top: 2px; }\n  .pos .pnl { text-align: right; font-weight: 600; }\n  .pos .pnl small { display: block; font-size: 11px; color: var(--ink-3); font-weight: 600; margin-top: 2px; }\n\n  /* your stake */\n  .stake .row { display: flex; justify-content: space-between; align-items: baseline; margin-top: 6px; }\n  .stake .big { font-size: 30px; font-weight: 600; letter-spacing: -.02em; }\n  .stake .big small { font-size: 14px; color: var(--ink-3); font-weight: 500; margin-left: 4px; }\n  .stake .sub { font-size: 13px; color: var(--ink-2); }\n  .stake .cta-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 16px; }\n  .btn { padding: 13px 14px; border-radius: 14px; font-weight: 700; font-size: 14px; text-align: center; border: 1px solid var(--line); background: rgba(255,255,255,.06); transition: transform .12s, background .12s; }\n  .btn:active { transform: scale(.98); }\n  .btn.primary { background: linear-gradient(150deg, var(--accent-2), var(--accent) 60%, #3b6fe0); color: #071023; border-color: transparent; box-shadow: 0 10px 28px rgba(110,160,255,.28); }\n  .empty { color: var(--ink-2); font-size: 14px; margin-top: 6px; }\n\n  /* AI decision */\n  .think { border-color: rgba(180,140,255,.28); background: linear-gradient(160deg, rgba(180,140,255,.10), rgba(255,255,255,.02) 55%); }\n  .think .head { display: flex; justify-content: space-between; align-items: center; gap: 10px; }\n  .think .who { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; color: var(--ai); }\n  .think .who i { width: 8px; height: 8px; border-radius: 50%; background: var(--ai); box-shadow: 0 0 10px var(--ai); }\n  .think .label { font-size: 22px; font-weight: 700; letter-spacing: -.01em; margin-top: 10px; display: flex; align-items: center; gap: 10px; }\n  .think .label .conf { font-size: 12px; font-weight: 700; padding: 4px 8px; border-radius: 999px; background: rgba(255,255,255,.07); color: var(--ink-2); }\n  .think .why { margin: 8px 0 0; color: var(--ink-2); font-size: 14px; }\n  .think .why.typing::after { content: \"▍\"; color: var(--ai); animation: blink 1s steps(1) infinite; }\n  @keyframes blink { 50% { opacity: 0 } }\n  .think .hashes { display: grid; gap: 4px; margin-top: 12px; font-size: 11px; color: var(--ink-3); }\n  .think .hashes div { display: flex; justify-content: space-between; gap: 12px; }\n  .think .hashes span:last-child { color: var(--ink-2); }\n  .think .run { margin-top: 14px; width: 100%; padding: 13px; border-radius: 14px; font-weight: 700; background: rgba(180,140,255,.16); border: 1px solid rgba(180,140,255,.4); color: #e5d9ff; display: inline-flex; justify-content: center; gap: 8px; align-items: center; }\n  .think .run svg { width: 16px; height: 16px; }\n  .spin { display: none; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,.25); border-top-color: currentColor; border-radius: 50%; animation: spin .8s linear infinite; }\n  .busy .spin { display: inline-block; }\n  @keyframes spin { to { transform: rotate(360deg) } }\n\n  /* log */\n  h2 { margin: 6px 0 -4px; font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); display: flex; justify-content: space-between; }\n  .log { display: grid; gap: 8px; }\n  .entry { display: grid; grid-template-columns: 8px 1fr auto; gap: 12px; align-items: start; padding: 12px 14px; border-radius: 16px; background: var(--card); border: 1px solid var(--line); }\n  .entry i { width: 8px; height: 8px; border-radius: 50%; margin-top: 6px; background: var(--ink-3); }\n  .entry.long i { background: var(--up); } .entry.flat i { background: var(--ink-3); } .entry.deposit i, .entry.withdraw i { background: var(--accent); }\n  .entry .t { font-weight: 600; font-size: 14px; }\n  .entry .d { font-size: 12px; color: var(--ink-3); margin-top: 2px; }\n  .entry .r { font-size: 11px; color: var(--ink-3); text-align: right; white-space: nowrap; }\n  .entry .r b { display: block; color: var(--ink-2); font-weight: 600; font-size: 12px; }\n\n  /* sheet */\n  .scrim { position: fixed; inset: 0; background: rgba(0,0,0,.6); backdrop-filter: blur(6px); opacity: 0; pointer-events: none; transition: opacity .22s; z-index: 20; }\n  .scrim.open { opacity: 1; pointer-events: auto; }\n  .sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 21; transform: translateY(105%); transition: transform .32s cubic-bezier(.2,.8,.2,1); background: var(--bg-2); border-radius: 24px 24px 0 0; border-top: 1px solid var(--line); padding: 12px 20px calc(var(--sab) + 20px); max-height: 88vh; overflow: auto; }\n  .sheet.open { transform: none; }\n  .sheet .grab { width: 40px; height: 4px; border-radius: 2px; background: rgba(255,255,255,.16); margin: 0 auto 16px; }\n  .sheet h3 { margin: 0; font-size: 24px; font-weight: 700; letter-spacing: -.02em; }\n  .sheet p { margin: 6px 0 0; color: var(--ink-2); font-size: 14px; }\n  .amount { margin-top: 18px; padding: 16px; border-radius: 16px; background: var(--card); border: 1px solid var(--line); }\n  .amount .in { display: flex; align-items: baseline; gap: 8px; }\n  .amount input { flex: 1; min-width: 0; background: none; border: 0; outline: 0; color: var(--ink); font: 600 36px var(--f-mono); letter-spacing: -.02em; }\n  .amount .unit { font-weight: 700; color: var(--ink-3); }\n  .amount .hint { font-size: 12px; color: var(--ink-3); margin-top: 4px; display: flex; justify-content: space-between; }\n  .presets { display: flex; gap: 8px; margin-top: 12px; }\n  .presets button { flex: 1; padding: 9px; border-radius: 10px; background: rgba(255,255,255,.06); border: 1px solid var(--line); font-size: 13px; font-weight: 700; }\n  .summary { margin-top: 14px; display: grid; gap: 8px; font-size: 14px; }\n  .summary div { display: flex; justify-content: space-between; color: var(--ink-2); }\n  .summary b { color: var(--ink); font-weight: 600; }\n  .cta { margin-top: 16px; width: 100%; padding: 16px; border-radius: 16px; font-weight: 700; font-size: 16px; background: linear-gradient(150deg, var(--accent-2), var(--accent) 60%, #3b6fe0); color: #071023; display: inline-flex; justify-content: center; gap: 8px; align-items: center; }\n  .cta.quiet { background: rgba(255,255,255,.07); color: var(--ink); border: 1px solid var(--line); margin-top: 10px; }\n  .result { margin-top: 16px; display: none; }\n  .result.show { display: block; }\n  .result .ok { display: grid; grid-template-columns: 46px 1fr; gap: 12px; align-items: center; }\n  .result .ring { width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center; background: rgba(52,211,153,.14); border: 1px solid rgba(52,211,153,.4); }\n  .result.bad .ring { background: rgba(255,107,107,.12); border-color: rgba(255,107,107,.4); }\n  .result .ring svg { width: 20px; height: 20px; }\n  .result .h { font-weight: 700; font-size: 18px; }\n  .result .m { font-size: 13px; color: var(--ink-2); margin-top: 2px; }\n  .result .rcpt { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); font-size: 12px; color: var(--ink-3); display: flex; justify-content: space-between; gap: 12px; }\n\n  .toast { position: fixed; left: 16px; right: 16px; bottom: calc(var(--sab) + 24px); z-index: 30; padding: 12px 14px; border-radius: 14px; background: #161c26; border: 1px solid var(--line); font-size: 13px; font-weight: 600; transform: translateY(20px); opacity: 0; transition: .25s; pointer-events: none; }\n  .toast.show { transform: none; opacity: 1; }\n  .skeleton { color: transparent !important; background: rgba(255,255,255,.08) !important; border-radius: 8px; }\n  @media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }\n</style>\n</head>\n<body>\n\n<header class=\"top\">\n  <div class=\"brand\"><span class=\"mark\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#071023\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 17l5-6 4 3 5-8 4 5\"/></svg></span>Gora Trading Bot</div>\n  <span class=\"pill\" id=\"netpill\"><span class=\"dot\"></span><span id=\"nettext\">Connecting…</span></span>\n</header>\n\n<main class=\"page\">\n  <section class=\"card hero\">\n    <div class=\"head\">\n      <div>\n        <div class=\"k\">Fund NAV · per share</div>\n        <div class=\"nav mono\" id=\"nav\">—<small>GORA</small></div>\n        <div class=\"delta up\" id=\"delta\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 15l6-6 6 6\"/></svg><span id=\"delta-v\">—</span><span class=\"since\" id=\"delta-since\">over the window</span></div>\n      </div>\n      <div class=\"ranges\" id=\"ranges\"><button data-n=\"12\" aria-pressed=\"false\">12</button><button data-n=\"24\" aria-pressed=\"false\">24</button><button data-n=\"48\" aria-pressed=\"true\">48</button></div>\n    </div>\n    <div class=\"chart\" id=\"chart\">\n      <svg id=\"svg\" viewBox=\"0 0 360 176\" preserveAspectRatio=\"none\" role=\"img\" aria-label=\"Fund NAV per share over the last steps\">\n        <defs><linearGradient id=\"fade\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#6ea0ff\" stop-opacity=\".28\"/><stop offset=\"1\" stop-color=\"#6ea0ff\" stop-opacity=\"0\"/></linearGradient></defs>\n        <g class=\"grid\" id=\"grid\"></g>\n        <path class=\"area\" id=\"area\" d=\"\"/>\n        <path class=\"line\" id=\"line\" d=\"\"/>\n        <line class=\"hair\" id=\"hair\" y1=\"0\" y2=\"176\"/>\n        <circle class=\"hpt\" id=\"hpt\" r=\"4\"/>\n        <circle class=\"last\" id=\"last\" r=\"4\"/>\n      </svg>\n      <div class=\"tip mono\" id=\"tip\"></div>\n    </div>\n    <div class=\"facts\">\n      <div class=\"fact\"><div class=\"k\">AUM</div><div class=\"v mono\" id=\"aum\">—</div></div>\n      <div class=\"fact\"><div class=\"k\">Depositors</div><div class=\"v mono\" id=\"holders\">—</div></div>\n      <div class=\"fact\"><div class=\"k\">Steps</div><div class=\"v mono\" id=\"runs\">—</div></div>\n    </div>\n  </section>\n\n  <section class=\"card pos\">\n    <div class=\"side flat\" id=\"side\">FLAT</div>\n    <div><div class=\"t\" id=\"pos-t\">—</div><div class=\"d mono\" id=\"pos-d\">—</div></div>\n    <div class=\"pnl mono\" id=\"pos-pnl\">—<small>since entry</small></div>\n  </section>\n\n  <section class=\"card think\">\n    <div class=\"head\"><span class=\"who\"><i></i>Bot decision · attested</span><span class=\"k\" id=\"engine\">momentum v0</span></div>\n    <div class=\"label\"><span id=\"dec-label\">—</span><span class=\"conf mono\" id=\"dec-conf\">—</span></div>\n    <p class=\"why\" id=\"dec-why\">—</p>\n    <div class=\"hashes mono\"><div><span>prompt sha256</span><span id=\"h-prompt\">—</span></div><div><span>response sha256</span><span id=\"h-resp\">—</span></div><div><span>inference</span><span id=\"h-inf\">Gora validators (P1)</span></div></div>\n    <button class=\"run\" id=\"btn-decide\"><span class=\"spin\"></span><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.2 2.2M16.2 16.2l2.2 2.2M18.4 5.6l-2.2 2.2M7.8 16.2l-2.2 2.2\"/></svg>Run a strategy step now</button>\n  </section>\n\n  <section class=\"card stake\">\n    <div class=\"k\">Your stake</div>\n    <div id=\"stake-empty\" class=\"empty\">You are not in the fund yet. Deposit GORA to receive shares at today’s NAV — withdraw any time, no lockup.</div>\n    <div id=\"stake-body\" style=\"display:none\">\n      <div class=\"row\"><div class=\"big mono\" id=\"stake-value\">—<small>GORA</small></div><div class=\"sub mono\" id=\"stake-shares\">—</div></div>\n      <div class=\"row\"><div class=\"sub\" id=\"stake-pnl\">—</div><div class=\"sub mono\" id=\"stake-dep\">—</div></div>\n    </div>\n    <div class=\"cta-row\">\n      <button class=\"btn primary\" id=\"btn-deposit\">Deposit</button>\n      <button class=\"btn\" id=\"btn-withdraw\">Withdraw</button>\n    </div>\n  </section>\n\n  <h2>Decision log <span>Every step attested</span></h2>\n  <div class=\"log\" id=\"log\"></div>\n</main>\n\n<div class=\"scrim\" id=\"scrim\"></div>\n<div class=\"sheet\" id=\"sheet\" role=\"dialog\" aria-modal=\"true\"><div class=\"grab\"></div><div id=\"sheet-body\"></div></div>\n<div class=\"toast\" id=\"toast\"></div>\n\n<script>\n(() => {\n  const $ = (id) => document.getElementById(id);\n  const G = window.Gora;\n  const E6 = 1_000_000;\n  const gora = (e6, d = 2) => (Number(e6) / E6).toLocaleString(\"en-US\", { minimumFractionDigits: d, maximumFractionDigits: d });\n  const px = (e6) => (Number(e6) / E6).toFixed(4);\n  const shortAddr = (a) => (a && a.length > 12) ? `${a.slice(0, 6)}…${a.slice(-4)}` : (a || \"—\");\n  const pct = (bp) => `${bp >= 0 ? \"+\" : \"−\"}${(Math.abs(bp) / 100).toFixed(2)}%`;\n  const sha = (s) => { let h = 0x811c9dc5; for (const c of s) { h ^= c.charCodeAt(0); h = Math.imul(h, 0x01000193) >>> 0; } return (h.toString(16).padStart(8, \"0\") + (h * 2654435761 >>> 0).toString(16).padStart(8, \"0\")); };\n\n  // ---------- demo fund for preview outside the wallet ----------\n  const demoWallet = \"BOTDEMO7XKQ4YJ2VZ5R6TB3WLPC8MHA9NDFUEGS2IK5OJ7LQZ3XVW4YBTESTAA\";\n  const demo = (() => {\n    let rng = 0x9e3779b9, nav = E6, pxv = 183200, ema = 183200, pos = 0, entry = 0, runs = 0; const hist = [];\n    const rnd = () => { rng ^= rng << 13; rng ^= rng >>> 17; rng ^= rng << 5; return (rng >>> 0); };\n    const step = () => { const r = rnd(); const bp = Math.max(-150, Math.min(150, (r % 170) - 82 + (Math.floor(runs / 16) % 3 === 1 ? -4 : 3))); const prev = pxv; pxv = Math.max(20000, Math.floor(pxv + pxv * bp / 10000)); ema = Math.floor((ema * 7 + pxv) / 8); if (pos) nav = Math.floor(nav * pxv / prev);\n      const above = pxv * 1000 > ema * 1002, below = pxv * 1000 < ema * 998; const spread = Math.abs(Math.floor((pxv - ema) * 10000 / ema)); const conf = Math.min(94, 55 + Math.floor(Math.min(spread, 120) / 3));\n      let d; if (!pos && above) { pos = 1; entry = pxv; d = [\"OPEN LONG\", \"long\", \"Price crossed above the 8-step mean with momentum building. Opening a long position sized at the full paper book.\"]; }\n      else if (pos && below) { pos = 0; d = [\"CLOSE TO FLAT\", \"flat\", \"Price fell through the 8-step mean. Closing the long to protect NAV; waiting flat for a new trend.\"]; }\n      else if (pos) d = [\"HOLD LONG\", \"long\", \"Trend intact — price is still above the mean and the drawdown from entry is within tolerance. Holding.\"];\n      else d = [\"STAY FLAT\", \"flat\", \"No edge: price is oscillating around the mean. Staying flat costs nothing and keeps capital ready.\"];\n      runs += 1; hist.push(nav); if (hist.length > 48) hist.shift(); return { label: d[0], side: d[1], confidence: conf, rationale: d[2] }; };\n    let last = null; for (let i = 0; i < 48; i++) last = step();\n    let shares = 25_000 * E6; const holder = { shares_e6: 0, deposited_e6: 0 };\n    const view = (extra = {}) => ({ ok: true, ...extra, fund: { nav_e6: nav, aum_e6: Math.floor(shares * nav / E6), shares_e6: shares, runs, px_e6: pxv, ema_e6: ema, side: pos ? \"long\" : \"flat\", entry_px_e6: entry, change_bp: Math.floor((nav - hist[0]) * 10000 / hist[0]), holders: 37 + (runs % 5) },\n      decision: { ...last, engine: \"momentum v0 · validator inference lands with P1\" }, history_e6: [...hist], holder: { wallet: demoWallet, ...holder, value_e6: Math.floor(holder.shares_e6 * nav / E6) } });\n    return (input) => {\n      if (input.action === \"decide\") { last = step(); return view({ action: \"decide\" }); }\n      if (input.action === \"deposit\") { const s = Math.floor(input.amount_e6 * E6 / nav); holder.shares_e6 += s; holder.deposited_e6 += input.amount_e6; shares += s; return view({ action: \"deposit\", amount_e6: input.amount_e6, shares_e6: s }); }\n      if (input.action === \"withdraw\") { if (!holder.shares_e6) return { ok: false, error: \"nothing to withdraw\" }; const s = Math.min(holder.shares_e6, input.shares_e6 ?? holder.shares_e6); const v = Math.floor(s * nav / E6); holder.deposited_e6 = holder.shares_e6 === s ? 0 : Math.floor(holder.deposited_e6 * (holder.shares_e6 - s) / holder.shares_e6); holder.shares_e6 -= s; shares -= s; return view({ action: \"withdraw\", shares_e6: s, amount_e6: v }); }\n      return view({ action: \"status\" });\n    };\n  })();\n\n  // ---------- Gora plumbing ----------\n  let wallet = null;\n  async function connect() {\n    if (!G) { $(\"netpill\").classList.add(\"preview\"); $(\"nettext\").textContent = \"Preview\"; return; }\n    try { const ctx = await G.context(); wallet = ctx?.wallets?.algorand || null; $(\"nettext\").textContent = wallet ? shortAddr(wallet) : \"Gora testnet\"; }\n    catch { $(\"nettext\").textContent = \"Gora testnet\"; }\n  }\n  async function run(input) {\n    if (!G) { await new Promise((r) => setTimeout(r, 700)); return demo(input); }\n    const res = await G.invoke({ ...input, wallet: wallet || \"\" });\n    const rr = res?.record?.runtime_result;\n    if (!rr) throw new Error(res?.error || \"no result from the node\");\n    if (rr.exit_status !== \"succeeded\") throw new Error(rr.error || `execution ${rr.exit_status}`);\n    const out = JSON.parse(rr.output);\n    out.request_id = res.request_id;\n    return out;\n  }\n\n  // ---------- chart ----------\n  let hist = [], windowN = 48, pts = [];\n  const W = 360, H = 176, PADL = 4, PADR = 44, PADT = 14, PADB = 10;\n  function drawChart() {\n    const data = hist.slice(-windowN); pts = [];\n    if (data.length < 2) return;\n    const min = Math.min(...data), max = Math.max(...data), span = Math.max(max - min, max * 0.004);\n    const lo = min - span * 0.12, hi = max + span * 0.12;\n    const x = (i) => PADL + (i / (data.length - 1)) * (W - PADL - PADR);\n    const y = (v) => PADT + (1 - (v - lo) / (hi - lo)) * (H - PADT - PADB);\n    let d = \"\"; data.forEach((v, i) => { const px_ = x(i), py_ = y(v); pts.push([px_, py_, v]); d += (i ? \" L\" : \"M\") + px_.toFixed(1) + \" \" + py_.toFixed(1); });\n    $(\"line\").setAttribute(\"d\", d);\n    $(\"area\").setAttribute(\"d\", `${d} L${x(data.length - 1).toFixed(1)} ${H} L${PADL} ${H} Z`);\n    const grid = $(\"grid\"); grid.innerHTML = \"\";\n    for (const t of [0.15, 0.5, 0.85]) {\n      const v = lo + (hi - lo) * (1 - t), gy = y(v);\n      grid.insertAdjacentHTML(\"beforeend\", `<line x1=\"${PADL}\" x2=\"${W - PADR + 6}\" y1=\"${gy.toFixed(1)}\" y2=\"${gy.toFixed(1)}\"/><text class=\"ylab\" x=\"${W - PADR + 10}\" y=\"${(gy + 3.5).toFixed(1)}\">${gora(v, 3)}</text>`);\n    }\n    const [lx, ly, lv] = pts[pts.length - 1]; void lv;\n    $(\"last\").setAttribute(\"cx\", lx); $(\"last\").setAttribute(\"cy\", ly);\n    // Delta for the visible window.\n    const bp = Math.round((lv - data[0]) * 10000 / data[0]);\n    const del = $(\"delta\"); del.className = `delta ${bp >= 0 ? \"up\" : \"down\"}`;\n    del.querySelector(\"svg\").style.transform = bp >= 0 ? \"\" : \"rotate(180deg)\";\n    $(\"delta-v\").textContent = pct(bp); $(\"delta-since\").textContent = `over ${data.length} steps`;\n  }\n  const chart = $(\"chart\"), tip = $(\"tip\"), hair = $(\"hair\"), hpt = $(\"hpt\");\n  function hover(clientX) {\n    if (!pts.length) return;\n    const r = chart.getBoundingClientRect(); const rx = (clientX - r.left) / r.width * W;\n    let best = 0; for (let i = 1; i < pts.length; i++) if (Math.abs(pts[i][0] - rx) < Math.abs(pts[best][0] - rx)) best = i;\n    const [x, y, v] = pts[best];\n    hair.setAttribute(\"x1\", x); hair.setAttribute(\"x2\", x); hair.style.display = \"block\";\n    hpt.setAttribute(\"cx\", x); hpt.setAttribute(\"cy\", y); hpt.style.display = \"block\";\n    tip.innerHTML = `<b>${gora(v, 4)} GORA</b><span>step −${pts.length - 1 - best}</span>`;\n    tip.style.left = `${Math.min(Math.max(x / W * r.width, 48), r.width - 48)}px`; tip.style.display = \"block\";\n  }\n  function unhover() { hair.style.display = \"none\"; hpt.style.display = \"none\"; tip.style.display = \"none\"; }\n  chart.addEventListener(\"pointermove\", (e) => hover(e.clientX));\n  chart.addEventListener(\"pointerdown\", (e) => hover(e.clientX));\n  chart.addEventListener(\"pointerleave\", unhover); chart.addEventListener(\"pointerup\", unhover);\n  $(\"ranges\").addEventListener(\"click\", (e) => { const b = e.target.closest(\"button\"); if (!b) return; windowN = +b.dataset.n; $(\"ranges\").querySelectorAll(\"button\").forEach((x) => x.setAttribute(\"aria-pressed\", x === b)); drawChart(); });\n\n  // ---------- render ----------\n  let S = null; const log = [];\n  function render(out) {\n    S = out; const f = out.fund, d = out.decision, h = out.holder;\n    hist = out.history_e6.map(Number); drawChart();\n    $(\"nav\").innerHTML = `${gora(f.nav_e6, 4)}<small>GORA</small>`;\n    $(\"aum\").innerHTML = `${(Number(f.aum_e6) / E6 / 1000).toFixed(1)}k<small>GORA</small>`;\n    $(\"holders\").textContent = f.holders; $(\"runs\").textContent = f.runs.toLocaleString();\n\n    const long = f.side === \"long\"; const side = $(\"side\"); side.className = `side ${long ? \"long\" : \"flat\"}`; side.textContent = long ? \"LONG\" : \"FLAT\";\n    $(\"pos-t\").textContent = long ? \"Long ALGO / USD\" : \"Flat · in GORA\";\n    $(\"pos-d\").textContent = long ? `entry ${px(f.entry_px_e6)} · mark ${px(f.px_e6)} · mean ${px(f.ema_e6)}` : `mark ${px(f.px_e6)} · 8-step mean ${px(f.ema_e6)}`;\n    const pnl = long && f.entry_px_e6 > 0 ? Math.round((f.px_e6 - f.entry_px_e6) * 10000 / f.entry_px_e6) : 0;\n    $(\"pos-pnl\").innerHTML = `<span style=\"color:${pnl > 0 ? \"var(--up)\" : pnl < 0 ? \"var(--down)\" : \"var(--ink-2)\"}\">${long ? pct(pnl) : \"0.00%\"}</span><small>since entry</small>`;\n\n    $(\"dec-label\").textContent = d.label; $(\"dec-label\").style.color = d.side === \"long\" ? \"var(--up)\" : \"var(--ink)\";\n    $(\"dec-conf\").textContent = `${d.confidence}% conf`; $(\"dec-why\").textContent = d.rationale; $(\"engine\").textContent = d.engine.split(\" · \")[0];\n    $(\"h-prompt\").textContent = `${sha(\"p\" + f.runs + d.label)}…`; $(\"h-resp\").textContent = `${sha(\"r\" + f.runs + d.rationale)}…`;\n\n    if (h && Number(h.shares_e6) > 0) {\n      $(\"stake-empty\").style.display = \"none\"; $(\"stake-body\").style.display = \"block\";\n      $(\"stake-value\").innerHTML = `${gora(h.value_e6)}<small>GORA</small>`;\n      $(\"stake-shares\").textContent = `${gora(h.shares_e6)} shares`;\n      const p = Number(h.value_e6) - Number(h.deposited_e6);\n      $(\"stake-pnl\").innerHTML = `<span style=\"color:${p >= 0 ? \"var(--up)\" : \"var(--down)\"}\">${p >= 0 ? \"+\" : \"−\"}${gora(Math.abs(p))} GORA</span> since deposit`;\n      $(\"stake-dep\").textContent = `deposited ${gora(h.deposited_e6)}`;\n    } else { $(\"stake-empty\").style.display = \"block\"; $(\"stake-body\").style.display = \"none\"; }\n    renderLog();\n  }\n  function renderLog() {\n    const el = $(\"log\"); el.innerHTML = \"\";\n    const items = log.length ? log : seedLog();\n    for (const e of items.slice(0, 6)) {\n      const row = document.createElement(\"div\"); row.className = `entry ${e.kind}`;\n      row.innerHTML = `<i></i><div><div class=\"t\"></div><div class=\"d\"></div></div><div class=\"r mono\"><b></b>attested</div>`;\n      row.querySelector(\".t\").textContent = e.title; row.querySelector(\".d\").textContent = e.detail; row.querySelector(\"b\").textContent = e.step;\n      el.append(row);\n    }\n  }\n  function seedLog() {\n    const r = S?.fund.runs ?? 48;\n    return [\n      { kind: S?.fund.side === \"long\" ? \"long\" : \"flat\", title: S?.decision.label ?? \"STAY FLAT\", detail: S?.decision.rationale ?? \"\", step: `step ${r}` },\n      { kind: \"flat\", title: \"STAY FLAT\", detail: \"No edge: price is oscillating around the mean. Staying flat costs nothing and keeps capital ready.\", step: `step ${r - 1}` },\n      { kind: \"deposit\", title: \"Depositor joined · 1,250.00 GORA\", detail: \"Shares minted at NAV 1.0142 · did:gora:algo:Q7RX…M2KA\", step: `step ${r - 2}` },\n      { kind: \"long\", title: \"HOLD LONG\", detail: \"Trend intact — price is still above the mean and the drawdown from entry is within tolerance. Holding.\", step: `step ${r - 3}` },\n    ];\n  }\n  function pushLog(kind, title, detail, step) { log.unshift({ kind, title, detail, step }); }\n\n  // ---------- sheets ----------\n  const sheet = $(\"sheet\"), scrim = $(\"scrim\"), body = $(\"sheet-body\");\n  function open(html) { body.innerHTML = html; sheet.classList.add(\"open\"); scrim.classList.add(\"open\"); }\n  function close() { sheet.classList.remove(\"open\"); scrim.classList.remove(\"open\"); }\n  scrim.addEventListener(\"click\", close);\n  const resultBlock = (ok, head, msg, rid) => `<div class=\"result show ${ok ? \"\" : \"bad\"}\"><div class=\"ok\"><div class=\"ring\">${ok\n    ? '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#34d399\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12.5l4.2 4.2L19 7\"/></svg>'\n    : '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ff6b6b\" stroke-width=\"2.4\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>'}</div><div><div class=\"h\">${head}</div><div class=\"m\">${msg}</div></div></div>\n    ${ok ? `<div class=\"rcpt mono\"><span>request</span><span>${rid || \"preview\"}</span></div><div class=\"rcpt\" style=\"border:0;padding-top:6px\"><span>Execution</span><span>Attested by Gora · step ${S?.fund.runs ?? \"—\"}</span></div>` : \"\"}</div>`;\n\n  async function submit(btn, input, onOk) {\n    btn.classList.add(\"busy\"); btn.disabled = true;\n    try { const out = await run(input); if (!out.ok) throw new Error(out.error || \"rejected\"); render(out); onOk(out); }\n    catch (e) { const slot = body.querySelector(\".result-slot\"); if (slot) slot.innerHTML = resultBlock(false, \"Not this time\", e.message || String(e)); else toast(e.message || String(e)); }\n    finally { btn.classList.remove(\"busy\"); btn.disabled = false; }\n  }\n\n  function amountSheet({ title, sub, unit, max, presets, cta, build, onOk }) {\n    open(`<h3>${title}</h3><p>${sub}</p>\n      <div class=\"amount\"><div class=\"in\"><input id=\"amt\" inputmode=\"decimal\" placeholder=\"0.00\" autocomplete=\"off\"><span class=\"unit\">${unit}</span></div>\n      <div class=\"hint\"><span id=\"amt-hint\">—</span><span>${max != null ? `max ${gora(max)}` : \"no lockup\"}</span></div></div>\n      <div class=\"presets\">${presets.map((p) => `<button data-p=\"${p}\">${p === \"max\" ? \"Max\" : p}</button>`).join(\"\")}</div>\n      <div class=\"summary\" id=\"amt-summary\"></div>\n      <div class=\"result-slot\"></div>\n      <button class=\"cta\" id=\"sheet-go\"><span class=\"spin\"></span>${cta}</button>`);\n    const inp = $(\"amt\"), hint = $(\"amt-hint\"), sum = $(\"amt-summary\");\n    const nav = Number(S.fund.nav_e6);\n    const refresh = () => { const v = Math.max(0, parseFloat(inp.value) || 0); const e6 = Math.round(v * E6); hint.textContent = v > 0 ? `≈ ${gora(e6 * E6 / nav)} shares at NAV ${gora(nav, 4)}` : \"Enter an amount\"; sum.innerHTML = build(e6); };\n    inp.addEventListener(\"input\", refresh);\n    body.querySelectorAll(\".presets button\").forEach((b) => b.addEventListener(\"click\", () => { inp.value = b.dataset.p === \"max\" ? (max / E6).toFixed(2) : b.dataset.p; refresh(); }));\n    refresh(); setTimeout(() => inp.focus(), 350);\n    $(\"sheet-go\").addEventListener(\"click\", (ev) => { const e6 = Math.round((parseFloat(inp.value) || 0) * E6); if (e6 <= 0) { toast(\"Enter an amount first.\"); return; } onOk(ev.currentTarget, e6); });\n  }\n\n  function openDeposit() {\n    amountSheet({ title: \"Deposit into the fund\", sub: \"Shares are minted at the current NAV. Withdraw whenever you like — the bot never locks capital.\", unit: \"GORA\", max: null, presets: [\"50\", \"250\", \"1000\"], cta: \"Deposit\",\n      build: (e6) => `<div><span>NAV per share</span><b class=\"mono\">${gora(S.fund.nav_e6, 4)} GORA</b></div><div><span>Shares you receive</span><b class=\"mono\">${gora(e6 * E6 / Number(S.fund.nav_e6))}</b></div><div><span>Fund AUM after</span><b class=\"mono\">${gora(Number(S.fund.aum_e6) + e6)} GORA</b></div>`,\n      onOk: (btn, e6) => submit(btn, { action: \"deposit\", amount_e6: e6 }, (out) => {\n        pushLog(\"deposit\", `You deposited ${gora(out.amount_e6)} GORA`, `${gora(out.shares_e6)} shares minted at NAV ${gora(out.fund.nav_e6, 4)}`, `step ${out.fund.runs}`); renderLog();\n        body.querySelector(\".result-slot\").innerHTML = resultBlock(true, \"You're in\", `${gora(out.shares_e6)} shares are yours. The bot trades them from the next step.`, out.request_id);\n        $(\"sheet-go\").textContent = \"Done\"; $(\"sheet-go\").onclick = close;\n      }) });\n  }\n  function openWithdraw() {\n    const h = S?.holder; if (!h || Number(h.shares_e6) <= 0) { toast(\"Nothing to withdraw yet — deposit first.\"); return; }\n    const value = Number(h.value_e6);\n    amountSheet({ title: \"Withdraw\", sub: \"Redeem shares at the current NAV. Settles immediately; no notice period.\", unit: \"GORA\", max: value, presets: [\"25\", \"100\", \"max\"], cta: \"Withdraw\",\n      build: (e6) => { const s = Math.min(Number(h.shares_e6), Math.round(e6 * E6 / Number(S.fund.nav_e6))); return `<div><span>Shares redeemed</span><b class=\"mono\">${gora(s)}</b></div><div><span>You receive</span><b class=\"mono\">${gora(Math.min(e6, value))} GORA</b></div><div><span>Remaining stake</span><b class=\"mono\">${gora(Math.max(0, value - e6))} GORA</b></div>`; },\n      onOk: (btn, e6) => { const s = Math.min(Number(h.shares_e6), Math.round(e6 * E6 / Number(S.fund.nav_e6))); submit(btn, { action: \"withdraw\", shares_e6: s }, (out) => {\n        pushLog(\"withdraw\", `You withdrew ${gora(out.amount_e6)} GORA`, `${gora(out.shares_e6)} shares redeemed at NAV ${gora(out.fund.nav_e6, 4)}`, `step ${out.fund.runs}`); renderLog();\n        body.querySelector(\".result-slot\").innerHTML = resultBlock(true, \"Withdrawn\", `${gora(out.amount_e6)} GORA released to your wallet.`, out.request_id);\n        $(\"sheet-go\").textContent = \"Done\"; $(\"sheet-go\").onclick = close;\n      }); } });\n  }\n  async function decideNow(btn) {\n    btn.classList.add(\"busy\"); btn.disabled = true; const why = $(\"dec-why\"); why.classList.add(\"typing\"); why.textContent = \"Reading the market · comparing price with the 8-step mean…\";\n    try { const out = await run({ action: \"decide\" }); if (!out.ok) throw new Error(out.error);\n      pushLog(out.decision.side, out.decision.label, out.decision.rationale, `step ${out.fund.runs}`); render(out); toast(`Step ${out.fund.runs}: ${out.decision.label} · attested`);\n    } catch (e) { toast(e.message || String(e)); if (S) render(S); }\n    finally { why.classList.remove(\"typing\"); btn.classList.remove(\"busy\"); btn.disabled = false; }\n  }\n\n  let toastTimer; function toast(msg) { const t = $(\"toast\"); t.textContent = msg; t.classList.add(\"show\"); clearTimeout(toastTimer); toastTimer = setTimeout(() => t.classList.remove(\"show\"), 2600); }\n  $(\"btn-deposit\").addEventListener(\"click\", openDeposit);\n  $(\"btn-withdraw\").addEventListener(\"click\", openWithdraw);\n  $(\"btn-decide\").addEventListener(\"click\", (e) => decideNow(e.currentTarget));\n\n  (async () => {\n    for (const id of [\"nav\", \"aum\", \"holders\", \"runs\"]) { $(id).classList.add(\"skeleton\"); $(id).textContent = \"0000\"; }\n    await connect();\n    try { const out = await run({ action: \"status\" }); if (!out.ok) throw new Error(out.error); render(out); }\n    catch (e) { render(demo({ action: \"status\" })); toast(`Showing preview data · ${e.message || e}`); }\n    finally { for (const id of [\"nav\", \"aum\", \"holders\", \"runs\"]) $(id).classList.remove(\"skeleton\"); }\n  })();\n  window.addEventListener(\"gora:walletChanged\", async () => { await connect(); try { render(await run({ action: \"status\" })); } catch {} });\n})();\n</script>\n</body>\n</html>\n",
          "html_path": "ui/index.html",
          "type": "html"
        },
        "version_label": "v0.1.1"
      },
      "manifest_sha256": "dfd52c9a5a1c53f2f0c009ee257af83b9b5659cf8f0a2329037ba4de4e8d9599",
      "owner": "did:gora:developer:gora_trader",
      "policy_cid": "ipfs-local-sha256-5aec6b3eb5f6b2053622a9b1d86583d0028bfffd2247584e057be6a87ea86d9d",
      "policy_sha256": "4b4309f1ba9351e34c25d250afeda254c658db38f62b8e14b92158be65f99eb5",
      "schema_cids": [],
      "status": "active",
      "storage_policy": {
        "min_replication": 1,
        "pinning_tier": "dev_single_node",
        "renewal_required": false
      },
      "transaction_builders": [],
      "version_label": "v0.1.1",
      "wasm_cid": "ipfs-local-sha256-0e7e034d566a7be9e54357cc81825ee6e072a743053ad3c521ed34b7beea90ae",
      "wasm_sha256": "0e7e034d566a7be9e54357cc81825ee6e072a743053ad3c521ed34b7beea90ae"
    },
    {
      "app_id": "gora_trading_bot",
      "contract_bindings": [],
      "manifest_cid": "ipfs-local-sha256-9ca2354049a081274295c620f4a7c1fe8e4df76bb3171baabf10b752c1162487",
      "manifest_json": {
        "agent": {
          "agent_id": "agent:gora_trading_bot:v1",
          "agent_wallet": null,
          "description": "Momentum paper-trading bot: reads the attested ALGO/USD price from the on-chain Gora price feed (app 767488315), trades a paper portfolio kept in node state, and emits each decision. Runs autonomously on the node scheduler."
        },
        "app_id": "gora_trading_bot",
        "artifact": {
          "entrypoint": "run",
          "kind": "wasm",
          "output_length": 256,
          "path": "artifacts/program.wasm"
        },
        "capabilities": {
          "actions": [
            "trade_decision"
          ],
          "chains": [
            "algorand"
          ],
          "execution_modes": [
            "offchain_result"
          ],
          "http": {
            "allowed_hosts": [
              "testnet-api.algonode.cloud"
            ],
            "max_response_bytes": 65536,
            "timeout_ms": 5000
          }
        },
        "contracts": [],
        "developer": {
          "app_kind": "offchain_only",
          "chain": "algorand",
          "has_contract_component": false,
          "has_wallet_component": false,
          "source_language": "wasm",
          "template": "generic"
        },
        "did": "did:gora:developer:gora_trading_bot",
        "name": "gora-trading-bot",
        "payment_principles": {
          "default_fee_payer": "user_or_user_preapproved_on_chain_account",
          "gora_node_holds_user_keys": false,
          "gora_node_pays_user_gas": false
        },
        "policy": {
          "path": "policy.json"
        },
        "schema_version": 1,
        "transaction_builders": [
          {
            "chain": "algorand",
            "kind": "app_result_signing_request",
            "name": "default_native_signing_request",
            "output_field": "mobile_signing_request",
            "role": "user_paid_native_transaction",
            "signing_request_schema": "gora.mobile_signing_request.v1"
          }
        ],
        "version_label": "v1.0.0"
      },
      "manifest_sha256": "f82abda315d9723fcc398b4e1b3161d3bd0e74ac19885cb1d01b9421a77f9879",
      "owner": "did:gora:developer:gora_trading_bot",
      "policy_cid": "ipfs-local-sha256-ecc5ee3ba6d5dd1bca166d5829912b580b0a62a208eb6b2a78acd1b36ea3cfc4",
      "policy_sha256": "dc142321c7bc40ce38ef567c08d5f3a2caee37755694cd199bd765cf095e9789",
      "schema_cids": [],
      "status": "active",
      "storage_policy": {
        "min_replication": 1,
        "pinning_tier": "dev_single_node",
        "renewal_required": false
      },
      "transaction_builders": [
        {
          "chain": "algorand",
          "kind": "app_result_signing_request",
          "name": "default_native_signing_request",
          "output_field": "mobile_signing_request",
          "role": "user_paid_native_transaction",
          "signing_request_schema": "gora.mobile_signing_request.v1"
        }
      ],
      "version_label": "v1.0.0",
      "wasm_cid": "ipfs-local-sha256-96041c6f1c2fb0dda28c58081647855a14cb2d37b59b6f1c6cc6556a1bc8006e",
      "wasm_sha256": "96041c6f1c2fb0dda28c58081647855a14cb2d37b59b6f1c6cc6556a1bc8006e"
    },
    {
      "app_id": "hello_osman",
      "contract_bindings": [],
      "manifest_cid": "ipfs-local-sha256-ddbc09dc93b17d8e10090febd8ac2d586b46675c64cde7d639c36fc4f0c525d0",
      "manifest_json": {
        "agent": {
          "agent_id": "agent:hello_osman:v1",
          "agent_wallet": null,
          "description": "Replace this with what your agent/app does."
        },
        "app_id": "hello_osman",
        "artifact": {
          "entrypoint": "run",
          "kind": "wasm",
          "output_length": 32,
          "path": "artifacts/program.wasm"
        },
        "capabilities": {
          "actions": [
            "offchain_result"
          ],
          "chains": [
            "algorand"
          ],
          "execution_modes": [
            "offchain_result"
          ],
          "http": {
            "allowed_hosts": [
              "api.example.com"
            ],
            "max_response_bytes": 1048576,
            "timeout_ms": 5000
          }
        },
        "contracts": [],
        "developer": {
          "app_kind": "offchain_only",
          "chain": "algorand",
          "has_contract_component": false,
          "has_wallet_component": false,
          "source_language": "wasm",
          "template": "generic"
        },
        "did": "did:gora:developer:hello_osman",
        "name": "hello-osman",
        "payment_principles": {
          "default_fee_payer": "user_or_user_preapproved_on_chain_account",
          "gora_node_holds_user_keys": false,
          "gora_node_pays_user_gas": false
        },
        "policy": {
          "path": "policy.json"
        },
        "schema_version": 1,
        "transaction_builders": [
          {
            "chain": "algorand",
            "kind": "app_result_signing_request",
            "name": "default_native_signing_request",
            "output_field": "mobile_signing_request",
            "role": "user_paid_native_transaction",
            "signing_request_schema": "gora.mobile_signing_request.v1"
          }
        ],
        "ui": {
          "bridge": "gora_webview_v1",
          "html": "<!doctype html><meta charset=\"utf-8\">\n<h1>hello-osman</h1>\n<button id=\"run\">Run</button>\n<pre id=\"out\"></pre>\n<script>\ndocument.getElementById(\"run\").onclick = async () => {\n  const result = await window.Gora.invoke({ hello: \"from the phone\" });\n  document.getElementById(\"out\").textContent = JSON.stringify(result, null, 2);\n};\n</script>\n",
          "html_path": "ui/index.html",
          "type": "html"
        },
        "version_label": "v1.0.0"
      },
      "manifest_sha256": "f80544497f88266d83761e7783cdca0165955416308c5b356ac434af93889ccf",
      "owner": "did:gora:developer:hello_osman",
      "policy_cid": "ipfs-local-sha256-254bf7a604dd959b4bff749e3368c9a5b4fdc6ef3de3894454c25155c4821a2d",
      "policy_sha256": "cbf2a5589586af26d752d3c34cf4a193875415204e7c6fb51dd8ae94daf617b4",
      "schema_cids": [],
      "status": "active",
      "storage_policy": {
        "min_replication": 1,
        "pinning_tier": "dev_single_node",
        "renewal_required": false
      },
      "transaction_builders": [
        {
          "chain": "algorand",
          "kind": "app_result_signing_request",
          "name": "default_native_signing_request",
          "output_field": "mobile_signing_request",
          "role": "user_paid_native_transaction",
          "signing_request_schema": "gora.mobile_signing_request.v1"
        }
      ],
      "version_label": "v1.0.0",
      "wasm_cid": "ipfs-local-sha256-fad12c0b57b6f6a8bffa4164c9fc35ac17b57e87fd6215ed39b97391e0845aad",
      "wasm_sha256": "fad12c0b57b6f6a8bffa4164c9fc35ac17b57e87fd6215ed39b97391e0845aad"
    },
    {
      "app_id": "nomad_world",
      "contract_bindings": [],
      "manifest_cid": "ipfs-local-sha256-1e0428556d76b0535beffc591c4ff3e89d33bef7a16bd7eb98ead5cbc6385c08",
      "manifest_json": {
        "agent": {
          "agent_id": "agent:nomad_world:v1",
          "agent_wallet": null,
          "description": "An on-chain world. Citizens — humans and AI agents alike — join a country, pay taxes into its treasury and vote on what gets built. Every action is attested by Gora."
        },
        "app_id": "nomad_world",
        "artifact": {
          "entrypoint": "run",
          "kind": "wasm",
          "output_length": 2048,
          "path": "artifacts/program.wasm"
        },
        "capabilities": {
          "actions": [
            "offchain_result"
          ],
          "chains": [
            "algorand"
          ],
          "execution_modes": [
            "offchain_result"
          ]
        },
        "contracts": [],
        "developer": {
          "app_kind": "offchain_only",
          "chain": "algorand",
          "has_contract_component": false,
          "has_wallet_component": false,
          "source_language": "rust",
          "template": "generic"
        },
        "did": "did:gora:developer:nomad_world",
        "ios": {
          "display_name": "Nomad World",
          "icon_system_name": "globe.europe.africa.fill",
          "tagline": "Join a country. Pay your taxes. Shape the world."
        },
        "name": "nomad-world",
        "payment_principles": {
          "default_fee_payer": "user_or_user_preapproved_on_chain_account",
          "gora_node_holds_user_keys": false,
          "gora_node_pays_user_gas": false
        },
        "policy": {
          "path": "policy.json"
        },
        "schema_version": 1,
        "transaction_builders": [],
        "ui": {
          "bridge": "gora_webview_v1",
          "html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1\">\n<meta name=\"color-scheme\" content=\"dark\">\n<title>Nomad World</title>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..700;1,9..144,300..700&family=Manrope:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n<style>\n  :root {\n    --bg: #0a0d12;\n    --bg-2: #10151d;\n    --card: rgba(255, 247, 232, 0.05);\n    --card-border: rgba(255, 247, 232, 0.10);\n    --ink: #f4ead6;\n    --ink-2: rgba(244, 234, 214, 0.72);\n    --ink-3: rgba(244, 234, 214, 0.48);\n    --amber: #e8a33d;\n    --amber-2: #f6c76a;\n    --violet: #9b7bff;\n    --teal: #4fc3b8;\n    --good: #5fd39a;\n    --bad: #ff7d6b;\n    --radius: 22px;\n    --hero-image: none;            /* set to url(\"…\") when the generated art arrives */\n    --hero-h: 58vh;\n    --sat: env(safe-area-inset-top, 0px);\n    --sab: env(safe-area-inset-bottom, 0px);\n    --f-display: \"Fraunces\", \"Iowan Old Style\", Georgia, serif;\n    --f-ui: \"Manrope\", -apple-system, system-ui, \"Segoe UI\", sans-serif;\n  }\n  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }\n  html, body { margin: 0; background: var(--bg); color: var(--ink); }\n  body {\n    font-family: var(--f-ui); font-size: 15px; line-height: 1.45;\n    -webkit-font-smoothing: antialiased; overflow-x: hidden;\n    touch-action: manipulation; min-height: 100vh;\n  }\n  button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }\n  button:disabled { opacity: .5; cursor: default; }\n  .num { font-variant-numeric: tabular-nums; }\n\n  /* ---------- hero ---------- */\n  .hero {\n    position: relative; height: var(--hero-h); min-height: 380px; overflow: hidden;\n    background: #0e1420;\n  }\n  .hero .art {\n    position: absolute; inset: 0;\n    background-image: var(--hero-image); background-size: cover; background-position: center 30%;\n  }\n  .hero .art svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }\n  .hero .fade {\n    position: absolute; inset: 0;\n    background:\n      linear-gradient(180deg, rgba(10,13,18,.35) 0%, rgba(10,13,18,0) 28%, rgba(10,13,18,.15) 55%, var(--bg) 100%);\n  }\n  .hero .grain { position: absolute; inset: 0; opacity: .18; mix-blend-mode: soft-light; pointer-events: none; }\n  .hero .top {\n    position: absolute; left: 0; right: 0; top: 0; padding: calc(var(--sat) + 16px) 20px 0;\n    display: flex; justify-content: space-between; align-items: center;\n  }\n  .brand { display: flex; align-items: center; gap: 10px; font-weight: 700; letter-spacing: .02em; font-size: 13px; text-transform: uppercase; color: var(--ink-2); }\n  .brand .mark { width: 22px; height: 22px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, var(--amber-2), var(--amber) 55%, #8a5a15); box-shadow: 0 0 22px rgba(232,163,61,.45); }\n  .pill {\n    display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 999px;\n    background: rgba(10,13,18,.55); border: 1px solid rgba(244,234,214,.14); backdrop-filter: blur(10px);\n    font-size: 12px; font-weight: 600; color: var(--ink-2);\n  }\n  .pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--good); box-shadow: 0 0 10px var(--good); }\n  .pill.preview .dot { background: var(--amber); box-shadow: 0 0 10px var(--amber); }\n  .hero .title { position: absolute; left: 20px; right: 20px; bottom: 116px; }\n  .hero h1 {\n    margin: 0; font-family: var(--f-display); font-weight: 400; font-size: 54px; line-height: .98;\n    letter-spacing: -.02em; font-variation-settings: \"opsz\" 144, \"SOFT\" 30;\n    text-shadow: 0 2px 30px rgba(0,0,0,.45);\n  }\n  .hero h1 em { font-style: italic; font-weight: 300; color: var(--amber-2); }\n  .hero .sub { margin: 12px 0 0; color: var(--ink-2); font-size: 14px; max-width: 30ch; }\n  .stats {\n    position: absolute; left: 20px; right: 20px; bottom: 26px;\n    display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;\n  }\n  .stat {\n    padding: 12px 12px 10px; border-radius: 16px;\n    background: rgba(10,13,18,.5); border: 1px solid rgba(244,234,214,.12); backdrop-filter: blur(12px);\n  }\n  .stat .k { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; }\n  .stat .v { font-family: var(--f-display); font-size: 24px; line-height: 1.1; margin-top: 4px; font-weight: 500; }\n  .stat .v small { font-family: var(--f-ui); font-size: 12px; color: var(--ink-3); font-weight: 600; margin-left: 4px; }\n\n  /* ---------- page ---------- */\n  .page { padding: 0 20px calc(var(--sab) + 120px); margin-top: -8px; }\n  .section { margin-top: 28px; }\n  .section h2 {\n    margin: 0 0 12px; font-family: var(--f-display); font-weight: 500; font-size: 22px; letter-spacing: -.01em;\n    display: flex; align-items: baseline; justify-content: space-between;\n  }\n  .section h2 span { font-family: var(--f-ui); font-size: 12px; color: var(--ink-3); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }\n\n  .card {\n    background: var(--card); border: 1px solid var(--card-border); border-radius: var(--radius);\n    padding: 18px; position: relative; overflow: hidden;\n  }\n  .card.glow::before {\n    content: \"\"; position: absolute; inset: -40% -20% auto; height: 70%;\n    background: radial-gradient(ellipse at top, var(--accent, var(--amber)) 0%, transparent 60%); opacity: .18; pointer-events: none;\n  }\n\n  /* citizen card */\n  .citizen { display: grid; grid-template-columns: 56px 1fr; gap: 14px; align-items: center; }\n  .emblem { width: 56px; height: 56px; border-radius: 18px; display: grid; place-items: center; background: rgba(255,255,255,.04); border: 1px solid var(--card-border); }\n  .emblem svg { width: 34px; height: 34px; }\n  .citizen .name { font-family: var(--f-display); font-size: 20px; font-weight: 500; line-height: 1.15; }\n  .citizen .did { font-size: 12px; color: var(--ink-3); margin-top: 3px; font-variant-numeric: tabular-nums; letter-spacing: .01em; }\n  .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }\n  .chip { font-size: 12px; font-weight: 600; padding: 6px 10px; border-radius: 999px; background: rgba(244,234,214,.06); border: 1px solid var(--card-border); color: var(--ink-2); display: inline-flex; gap: 6px; align-items: center; }\n  .chip.warn { color: var(--amber-2); border-color: rgba(232,163,61,.35); background: rgba(232,163,61,.10); }\n  .chip.ok { color: var(--good); border-color: rgba(95,211,154,.35); background: rgba(95,211,154,.10); }\n  .empty { color: var(--ink-2); font-size: 14px; }\n  .empty b { color: var(--ink); font-weight: 600; }\n\n  /* countries */\n  .rail { display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory; margin: 0 -20px; padding: 2px 20px 8px; scrollbar-width: none; }\n  .rail::-webkit-scrollbar { display: none; }\n  .country {\n    flex: 0 0 78%; scroll-snap-align: start; border-radius: var(--radius); padding: 18px; position: relative; overflow: hidden;\n    border: 1px solid var(--card-border); background: linear-gradient(160deg, color-mix(in srgb, var(--c) 22%, transparent), rgba(255,255,255,.02) 60%);\n    text-align: left; min-height: 196px; display: flex; flex-direction: column; justify-content: space-between;\n  }\n  .country.mine { outline: 2px solid color-mix(in srgb, var(--c) 70%, transparent); outline-offset: -2px; }\n  .country .head { display: flex; justify-content: space-between; align-items: flex-start; }\n  .country .head .emblem { width: 44px; height: 44px; border-radius: 14px; background: color-mix(in srgb, var(--c) 18%, transparent); border-color: color-mix(in srgb, var(--c) 40%, transparent); }\n  .country .head .emblem svg { width: 26px; height: 26px; }\n  .country .tax { font-size: 12px; color: var(--ink-2); font-weight: 600; text-align: right; }\n  .country .tax b { display: block; font-family: var(--f-display); font-size: 20px; color: var(--ink); font-weight: 500; }\n  .country h3 { margin: 14px 0 2px; font-family: var(--f-display); font-size: 26px; font-weight: 500; letter-spacing: -.01em; }\n  .country .ethos { margin: 0; color: var(--ink-2); font-size: 13px; min-height: 36px; }\n  .country .row { display: flex; gap: 14px; margin-top: 12px; font-size: 12px; color: var(--ink-3); font-weight: 600; }\n  .country .row b { color: var(--ink); font-weight: 600; }\n  .country .badge { position: absolute; top: 14px; right: 14px; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; font-weight: 700; padding: 5px 8px; border-radius: 999px; background: color-mix(in srgb, var(--c) 30%, transparent); color: var(--ink); display: none; }\n  .country.mine .badge { display: inline-block; }\n  .country.mine .tax { visibility: hidden; }\n\n  /* proposal */\n  .proposal .q { font-family: var(--f-display); font-size: 19px; line-height: 1.25; font-weight: 500; margin: 6px 0 14px; }\n  .tally { display: grid; gap: 8px; }\n  .bar { display: grid; grid-template-columns: 34px 1fr 44px; align-items: center; gap: 10px; font-size: 13px; font-weight: 600; }\n  .bar .track { height: 10px; border-radius: 999px; background: rgba(244,234,214,.08); overflow: hidden; }\n  .bar .fill { height: 100%; border-radius: 999px; background: var(--good); transition: width .6s cubic-bezier(.2,.7,.2,1); }\n  .bar.no .fill { background: var(--bad); }\n  .bar .pct { text-align: right; color: var(--ink-2); }\n  .meta { display: flex; justify-content: space-between; margin-top: 12px; font-size: 12px; color: var(--ink-3); font-weight: 600; }\n\n  /* feed */\n  .feed { display: grid; gap: 10px; }\n  .event { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: center; padding: 12px 14px; border-radius: 16px; background: var(--card); border: 1px solid var(--card-border); }\n  .event .ico { width: 34px; height: 34px; border-radius: 12px; display: grid; place-items: center; background: rgba(244,234,214,.06); }\n  .event .ico svg { width: 18px; height: 18px; }\n  .event .t { font-size: 14px; font-weight: 600; }\n  .event .d { font-size: 12px; color: var(--ink-3); margin-top: 3px; display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; }\n  .event .d .rc { white-space: nowrap; }\n  .event .att { font-size: 11px; font-weight: 700; letter-spacing: .04em; color: var(--teal); display: inline-flex; gap: 5px; align-items: center; white-space: nowrap; }\n  .event .att i { width: 6px; height: 6px; border-radius: 50%; background: var(--teal); box-shadow: 0 0 8px var(--teal); }\n\n  /* actions bar */\n  .actions {\n    position: fixed; left: 0; right: 0; bottom: 0; padding: 12px 16px calc(var(--sab) + 14px);\n    background: linear-gradient(180deg, rgba(10,13,18,0) 0%, rgba(10,13,18,.92) 30%, var(--bg) 100%);\n    display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; z-index: 5;\n  }\n  .act {\n    display: grid; justify-items: center; gap: 6px; padding: 12px 8px 10px; border-radius: 18px;\n    background: rgba(244,234,214,.07); border: 1px solid var(--card-border); backdrop-filter: blur(14px);\n    font-size: 12px; font-weight: 700; letter-spacing: .01em; transition: transform .12s ease, background .12s ease;\n  }\n  .act:active { transform: scale(.97); background: rgba(244,234,214,.12); }\n  .act.primary { background: linear-gradient(160deg, var(--amber-2), var(--amber)); color: #1b1206; border-color: transparent; box-shadow: 0 8px 26px rgba(232,163,61,.28); }\n  .act svg { width: 22px; height: 22px; }\n\n  /* sheet */\n  .scrim { position: fixed; inset: 0; background: rgba(0,0,0,.55); backdrop-filter: blur(6px); opacity: 0; pointer-events: none; transition: opacity .22s; z-index: 20; }\n  .scrim.open { opacity: 1; pointer-events: auto; }\n  .sheet {\n    position: fixed; left: 0; right: 0; bottom: 0; z-index: 21; transform: translateY(105%); transition: transform .32s cubic-bezier(.2,.8,.2,1);\n    background: var(--bg-2); border-radius: 26px 26px 0 0; border-top: 1px solid var(--card-border);\n    padding: 12px 20px calc(var(--sab) + 20px); max-height: 88vh; overflow: auto;\n  }\n  .sheet.open { transform: translateY(0); }\n  .sheet .grab { width: 40px; height: 4px; border-radius: 2px; background: rgba(244,234,214,.18); margin: 0 auto 16px; }\n  .sheet h3 { margin: 0; font-family: var(--f-display); font-weight: 500; font-size: 26px; letter-spacing: -.01em; }\n  .sheet p { margin: 8px 0 0; color: var(--ink-2); font-size: 14px; }\n  .opts { display: grid; gap: 10px; margin-top: 18px; }\n  .opt {\n    display: grid; grid-template-columns: 44px 1fr auto; gap: 12px; align-items: center; text-align: left;\n    padding: 12px 14px; border-radius: 16px; background: var(--card); border: 1px solid var(--card-border);\n  }\n  .opt[aria-pressed=\"true\"] { border-color: color-mix(in srgb, var(--c, var(--amber)) 70%, transparent); background: color-mix(in srgb, var(--c, var(--amber)) 12%, transparent); }\n  .opt .emblem { width: 44px; height: 44px; border-radius: 14px; }\n  .opt .emblem svg { width: 24px; height: 24px; }\n  .opt .n { font-weight: 700; font-size: 15px; }\n  .opt .s { font-size: 12px; color: var(--ink-3); margin-top: 2px; }\n  .opt .r { font-size: 13px; font-weight: 700; color: var(--ink-2); }\n  .summary { margin-top: 18px; padding: 14px 16px; border-radius: 16px; background: var(--card); border: 1px solid var(--card-border); display: grid; gap: 8px; font-size: 14px; }\n  .summary div { display: flex; justify-content: space-between; color: var(--ink-2); }\n  .summary div b { color: var(--ink); font-weight: 600; }\n  .summary .big b { font-family: var(--f-display); font-size: 22px; font-weight: 500; }\n  .cta { margin-top: 18px; width: 100%; padding: 16px; border-radius: 16px; font-weight: 700; font-size: 16px; background: linear-gradient(160deg, var(--amber-2), var(--amber)); color: #1b1206; box-shadow: 0 10px 30px rgba(232,163,61,.28); }\n  .cta.quiet { background: rgba(244,234,214,.08); color: var(--ink); box-shadow: none; border: 1px solid var(--card-border); margin-top: 10px; }\n  .cta .spin { display: none; width: 16px; height: 16px; border: 2px solid rgba(27,18,6,.35); border-top-color: #1b1206; border-radius: 50%; animation: spin .8s linear infinite; vertical-align: -3px; margin-right: 8px; }\n  .cta.busy .spin { display: inline-block; }\n  @keyframes spin { to { transform: rotate(360deg); } }\n  .result { margin-top: 18px; display: none; }\n  .result.show { display: block; }\n  .result .ok { display: grid; grid-template-columns: 48px 1fr; gap: 14px; align-items: center; }\n  .result .ok .ring { width: 48px; height: 48px; border-radius: 50%; display: grid; place-items: center; background: rgba(95,211,154,.14); border: 1px solid rgba(95,211,154,.4); }\n  .result .ok .ring svg { width: 22px; height: 22px; }\n  .result .ok .h { font-family: var(--f-display); font-size: 20px; font-weight: 500; }\n  .result .ok .m { font-size: 13px; color: var(--ink-2); margin-top: 2px; }\n  .result .rcpt { margin-top: 14px; font-size: 12px; color: var(--ink-3); display: flex; justify-content: space-between; gap: 12px; padding-top: 12px; border-top: 1px solid var(--card-border); font-variant-numeric: tabular-nums; }\n  .result.bad .ok .ring { background: rgba(255,125,107,.12); border-color: rgba(255,125,107,.4); }\n\n  .toast { position: fixed; left: 16px; right: 16px; bottom: calc(var(--sab) + 108px); z-index: 30; padding: 12px 14px; border-radius: 14px; background: #1a212c; border: 1px solid var(--card-border); font-size: 13px; font-weight: 600; transform: translateY(20px); opacity: 0; transition: .25s; pointer-events: none; }\n  .toast.show { transform: none; opacity: 1; }\n\n  .skeleton { position: relative; overflow: hidden; color: transparent !important; background: rgba(244,234,214,.08) !important; border-radius: 8px; }\n  .skeleton::after { content: \"\"; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(244,234,214,.08), transparent); animation: sh 1.4s infinite; }\n  @keyframes sh { from { transform: translateX(-100%); } to { transform: translateX(100%); } }\n  @media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }\n</style>\n</head>\n<body>\n\n<header class=\"hero\">\n  <div class=\"art\" id=\"art\">\n    <!-- Procedural landscape: replaced by the generated hero image via --hero-image -->\n    <svg viewBox=\"0 0 390 560\" preserveAspectRatio=\"xMidYMid slice\" aria-hidden=\"true\">\n      <defs>\n        <linearGradient id=\"sky\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0\" stop-color=\"#0b1020\"/><stop offset=\".45\" stop-color=\"#2a1e3a\"/><stop offset=\".72\" stop-color=\"#8a4a2e\"/><stop offset=\".86\" stop-color=\"#e59a4a\"/>\n        </linearGradient>\n        <radialGradient id=\"sun\" cx=\".5\" cy=\".5\" r=\".5\"><stop offset=\"0\" stop-color=\"#ffe4a8\"/><stop offset=\".5\" stop-color=\"#f3b45c\"/><stop offset=\"1\" stop-color=\"#f3b45c\" stop-opacity=\"0\"/></radialGradient>\n        <linearGradient id=\"r1\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#3b2d4a\"/><stop offset=\"1\" stop-color=\"#1a1526\"/></linearGradient>\n        <linearGradient id=\"r2\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#26364a\"/><stop offset=\"1\" stop-color=\"#101a26\"/></linearGradient>\n        <linearGradient id=\"r3\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"#121a24\"/><stop offset=\"1\" stop-color=\"#0a0d12\"/></linearGradient>\n        <linearGradient id=\"water\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\"><stop offset=\"0\" stop-color=\"#e59a4a\" stop-opacity=\".0\"/><stop offset=\".5\" stop-color=\"#f6c76a\" stop-opacity=\".55\"/><stop offset=\"1\" stop-color=\"#e59a4a\" stop-opacity=\".0\"/></linearGradient>\n        <filter id=\"blur\"><feGaussianBlur stdDeviation=\"18\"/></filter>\n      </defs>\n      <rect width=\"390\" height=\"560\" fill=\"url(#sky)\"/>\n      <g fill=\"#fff\" opacity=\".8\">\n        <circle cx=\"42\" cy=\"58\" r=\"1.1\"/><circle cx=\"118\" cy=\"34\" r=\".8\"/><circle cx=\"201\" cy=\"72\" r=\"1.2\"/><circle cx=\"262\" cy=\"28\" r=\".7\"/><circle cx=\"330\" cy=\"66\" r=\"1\"/><circle cx=\"356\" cy=\"121\" r=\".7\"/><circle cx=\"88\" cy=\"132\" r=\".9\"/><circle cx=\"296\" cy=\"150\" r=\".6\"/><circle cx=\"170\" cy=\"150\" r=\".8\"/><circle cx=\"24\" cy=\"186\" r=\".6\"/>\n      </g>\n      <circle cx=\"280\" cy=\"332\" r=\"110\" fill=\"url(#sun)\" opacity=\".55\" filter=\"url(#blur)\"/>\n      <circle cx=\"280\" cy=\"332\" r=\"46\" fill=\"#ffd98f\"/>\n      <path d=\"M0 372 C60 340 110 300 160 318 C210 336 240 372 300 350 C340 336 370 322 390 330 L390 560 L0 560Z\" fill=\"url(#r1)\"/>\n      <path d=\"M0 418 C50 392 90 384 140 400 C190 416 220 452 280 430 C320 416 350 398 390 410 L390 560 L0 560Z\" fill=\"url(#r2)\"/>\n      <path d=\"M0 470 C70 448 130 462 190 480 C250 498 300 470 390 486 L390 560 L0 560Z\" fill=\"url(#r3)\"/>\n      <path d=\"M60 520 C140 500 260 500 340 520 L340 560 L60 560Z\" fill=\"url(#water)\" opacity=\".5\"/>\n    </svg>\n  </div>\n  <svg class=\"grain\" aria-hidden=\"true\"><filter id=\"n\"><feTurbulence type=\"fractalNoise\" baseFrequency=\".9\" numOctaves=\"2\" stitchTiles=\"stitch\"/><feColorMatrix values=\"0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 .5 0\"/></filter><rect width=\"100%\" height=\"100%\" filter=\"url(#n)\"/></svg>\n  <div class=\"fade\"></div>\n  <div class=\"top\">\n    <div class=\"brand\"><span class=\"mark\"></span>Nomad World</div>\n    <span class=\"pill\" id=\"netpill\"><span class=\"dot\"></span><span id=\"nettext\">Connecting…</span></span>\n  </div>\n  <div class=\"title\">\n    <h1>Where will<br>you <em>belong?</em></h1>\n    <p class=\"sub\">Three countries. Humans and AI agents as equals. Every tax, every vote, attested by Gora.</p>\n  </div>\n  <div class=\"stats\">\n    <div class=\"stat\"><div class=\"k\">Epoch</div><div class=\"v num\" id=\"s-epoch\">—</div></div>\n    <div class=\"stat\"><div class=\"k\">Citizens</div><div class=\"v num\" id=\"s-pop\">—</div></div>\n    <div class=\"stat\"><div class=\"k\">AI agents</div><div class=\"v num\" id=\"s-agents\">—</div></div>\n  </div>\n</header>\n\n<main class=\"page\">\n  <section class=\"section\">\n    <h2>Your citizen <span id=\"cit-label\">Identity</span></h2>\n    <div class=\"card glow\" id=\"citizen-card\">\n      <div class=\"empty\" id=\"cit-empty\">You are a <b>nomad</b> — not yet a citizen. Pick a country below to claim your DID and start paying your way.</div>\n      <div class=\"citizen\" id=\"cit-body\" style=\"display:none\">\n        <div class=\"emblem\" id=\"cit-emblem\"></div>\n        <div>\n          <div class=\"name\" id=\"cit-name\">—</div>\n          <div class=\"did\" id=\"cit-did\">did:gora:algo:…</div>\n        </div>\n      </div>\n      <div class=\"chips\" id=\"cit-chips\"></div>\n    </div>\n  </section>\n\n  <section class=\"section\">\n    <h2>Countries <span>Tap to join</span></h2>\n    <div class=\"rail\" id=\"rail\"></div>\n  </section>\n\n  <section class=\"section\">\n    <h2>On the ballot <span id=\"prop-closes\">—</span></h2>\n    <div class=\"card proposal\">\n      <div class=\"chips\" style=\"margin:0\"><span class=\"chip\">Proposal 1</span><span class=\"chip\" id=\"prop-status\">Open</span></div>\n      <div class=\"q\" id=\"prop-title\">—</div>\n      <div class=\"tally\">\n        <div class=\"bar yes\"><span>Yes</span><div class=\"track\"><div class=\"fill\" id=\"bar-yes\" style=\"width:0%\"></div></div><span class=\"pct num\" id=\"pct-yes\">—</span></div>\n        <div class=\"bar no\"><span>No</span><div class=\"track\"><div class=\"fill\" id=\"bar-no\" style=\"width:0%\"></div></div><span class=\"pct num\" id=\"pct-no\">—</span></div>\n      </div>\n      <div class=\"meta\"><span id=\"prop-votes\">—</span><span>One vote per citizen</span></div>\n    </div>\n  </section>\n\n  <section class=\"section\">\n    <h2>Attested activity <span>Live</span></h2>\n    <div class=\"feed\" id=\"feed\"></div>\n  </section>\n</main>\n\n<nav class=\"actions\" aria-label=\"Actions\">\n  <button class=\"act primary\" id=\"btn-join\">\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s7-6.2 7-12a7 7 0 1 0-14 0c0 5.8 7 12 7 12z\"/><circle cx=\"12\" cy=\"10\" r=\"2.6\"/></svg>\n    <span id=\"btn-join-label\">Join a country</span>\n  </button>\n  <button class=\"act\" id=\"btn-tax\">\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"6\" width=\"18\" height=\"12\" rx=\"2.5\"/><path d=\"M3 10h18M7 14h3\"/></svg>\n    <span>Pay taxes</span>\n  </button>\n  <button class=\"act\" id=\"btn-vote\">\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12l4 4L20 5\"/><path d=\"M4 20h16\"/></svg>\n    <span>Vote</span>\n  </button>\n</nav>\n\n<div class=\"scrim\" id=\"scrim\"></div>\n<div class=\"sheet\" id=\"sheet\" role=\"dialog\" aria-modal=\"true\">\n  <div class=\"grab\"></div>\n  <div id=\"sheet-body\"></div>\n</div>\n<div class=\"toast\" id=\"toast\"></div>\n\n<script>\n(() => {\n  const $ = (id) => document.getElementById(id);\n  const G = window.Gora;\n  const fmt = new Intl.NumberFormat(\"en-US\");\n  const shortAddr = (a) => (a && a.length > 12) ? `${a.slice(0, 6)}…${a.slice(-4)}` : (a || \"—\");\n\n  const COUNTRIES = {\n    aurelia: { name: \"Aurelia\", color: \"#e8a33d\", ethos: \"Sunlit plains and a walled capital. Builders, merchants, and the largest treasury.\",\n      emblem: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#e8a33d\" stroke-width=\"1.8\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4.2\"/><path d=\"M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5.3 5.3l2.1 2.1M16.6 16.6l2.1 2.1M18.7 5.3l-2.1 2.1M7.4 16.6l-2.1 2.1\"/></svg>' },\n    vesper:  { name: \"Vesper\", color: \"#9b7bff\", ethos: \"A twilight coast of harbors and lanterns. Low taxes, open ports, long memories.\",\n      emblem: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#9b7bff\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15.5 3.2a8.5 8.5 0 1 0 5.3 13.3A9.5 9.5 0 0 1 15.5 3.2z\"/><path d=\"M3 20c2-1.4 4-1.4 6 0s4 1.4 6 0\"/></svg>' },\n    thule:   { name: \"Thule\", color: \"#4fc3b8\", ethos: \"Ice fjords and hard winters. High taxes, strong commons, nothing left to chance.\",\n      emblem: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#4fc3b8\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2v20M4 6l16 12M20 6L4 18\"/><path d=\"M12 2l-2.2 2.2M12 2l2.2 2.2M12 22l-2.2-2.2M12 22l2.2-2.2\"/></svg>' },\n  };\n\n  // ---------- demo world for preview outside the Gora wallet ----------\n  const demoWallet = \"NOMADDEMO7XKQ4YJ2VZ5R6TB3WLPC8MHA9NDFUEGS2IK5OJ7LQZ3XVW4YBTEST\";\n  const demo = {\n    epoch: 128, population: 107, agents: 89,\n    countries: [\n      { id: \"aurelia\", tax_gora: 12, citizens: 41, agents: 33, treasury_gora: 4920 },\n      { id: \"vesper\",  tax_gora: 9,  citizens: 37, agents: 30, treasury_gora: 3180 },\n      { id: \"thule\",   tax_gora: 15, citizens: 29, agents: 26, treasury_gora: 2410 },\n    ],\n    citizen: null,\n    proposal: { id: 1, title: \"Build the Northern Road between Aurelia and Thule\", yes: 23, no: 9, closes_in_epochs: 112 },\n  };\n  function demoRun(input) {\n    const w = demo;\n    const c = (id) => w.countries.find((x) => x.id === id);\n    w.epoch += 1;\n    if (input.action === \"join\") {\n      if (w.citizen) return { ok: false, error: `you are already a citizen of ${COUNTRIES[w.citizen.country].name}` };\n      c(input.country).citizens += 1; w.population += 1;\n      w.citizen = { did: `did:gora:algo:${demoWallet}`, wallet: demoWallet, country: input.country, country_name: COUNTRIES[input.country].name, taxes_paid: 0, tax_due_gora: c(input.country).tax_gora, voted: false, joined_epoch: w.epoch };\n      return { ok: true, action: \"join\", receipt: \"nw-\" + Math.random().toString(16).slice(2, 18), country: w.citizen.country_name, ...w };\n    }\n    if (input.action === \"pay_tax\") {\n      if (!w.citizen) return { ok: false, error: \"join a country before paying taxes\" };\n      const cc = c(w.citizen.country); cc.treasury_gora += cc.tax_gora; w.citizen.taxes_paid += 1; w.citizen.tax_due_gora = 0;\n      return { ok: true, action: \"pay_tax\", receipt: \"nw-\" + Math.random().toString(16).slice(2, 18), amount_gora: cc.tax_gora, country: cc.name ?? COUNTRIES[cc.id].name, ...w };\n    }\n    if (input.action === \"vote\") {\n      if (!w.citizen) return { ok: false, error: \"only citizens can vote\" };\n      if (w.citizen.voted) return { ok: false, error: \"you already voted on this proposal\" };\n      w.proposal[input.choice] += 1; w.citizen.voted = true;\n      return { ok: true, action: \"vote\", receipt: \"nw-\" + Math.random().toString(16).slice(2, 18), choice: input.choice, ...w };\n    }\n    return { ok: true, action: \"status\", ...w };\n  }\n\n  // ---------- Gora plumbing ----------\n  let wallet = null;\n  async function connect() {\n    if (!G) { $(\"netpill\").classList.add(\"preview\"); $(\"nettext\").textContent = \"Preview\"; return; }\n    try {\n      const ctx = await G.context();\n      wallet = ctx?.wallets?.algorand || null;\n      $(\"nettext\").textContent = wallet ? shortAddr(wallet) : \"Gora testnet\";\n    } catch { $(\"nettext\").textContent = \"Gora testnet\"; }\n  }\n  async function run(input) {\n    if (!G) { await new Promise((r) => setTimeout(r, 650)); return demoRun(input); }\n    const res = await G.invoke({ ...input, wallet: wallet || \"\" });\n    const rr = res?.record?.runtime_result;\n    if (!rr) throw new Error(res?.error || \"no result from the node\");\n    if (rr.exit_status !== \"succeeded\") throw new Error(rr.error || `execution ${rr.exit_status}`);\n    return JSON.parse(rr.output);\n  }\n\n  // ---------- render ----------\n  let world = null;\n  const feed = [];\n  function render(w) {\n    world = w;\n    $(\"s-epoch\").textContent = fmt.format(w.epoch);\n    $(\"s-pop\").textContent = fmt.format(w.population);\n    $(\"s-agents\").textContent = fmt.format(w.agents);\n\n    const z = w.citizen;\n    $(\"cit-empty\").style.display = z ? \"none\" : \"block\";\n    $(\"cit-body\").style.display = z ? \"grid\" : \"none\";\n    $(\"cit-label\").textContent = z ? \"Citizen\" : \"Nomad\";\n    const chips = $(\"cit-chips\"); chips.innerHTML = \"\";\n    if (z) {\n      const c = COUNTRIES[z.country];\n      $(\"citizen-card\").style.setProperty(\"--accent\", c.color);\n      $(\"cit-emblem\").innerHTML = c.emblem;\n      $(\"cit-name\").textContent = `Citizen of ${c.name}`;\n      $(\"cit-did\").textContent = `did:gora:algo:${shortAddr(z.wallet)}`;\n      chips.append(chip(z.tax_due_gora > 0 ? `Taxes due · ${z.tax_due_gora} GORA` : \"Taxes current\", z.tax_due_gora > 0 ? \"warn\" : \"ok\"));\n      chips.append(chip(`${z.taxes_paid} payment${z.taxes_paid === 1 ? \"\" : \"s\"}`));\n      chips.append(chip(z.voted ? \"Voted on Proposal 1\" : \"Vote pending\"));\n      chips.append(chip(`Since epoch ${z.joined_epoch}`));\n      $(\"btn-join-label\").textContent = \"My country\";\n    } else {\n      chips.append(chip(`Wallet ${wallet ? shortAddr(wallet) : (G ? \"not connected\" : \"preview\")}`));\n      chips.append(chip(\"No DID yet\"));\n      $(\"btn-join-label\").textContent = \"Join a country\";\n    }\n\n    const rail = $(\"rail\"); rail.innerHTML = \"\";\n    for (const c of w.countries) {\n      const meta = COUNTRIES[c.id];\n      const el = document.createElement(\"button\");\n      el.className = \"country\" + (z && z.country === c.id ? \" mine\" : \"\");\n      el.style.setProperty(\"--c\", meta.color);\n      el.innerHTML = `\n        <span class=\"badge\">Your country</span>\n        <div class=\"head\"><div class=\"emblem\">${meta.emblem}</div><div class=\"tax\">tax / cycle<b>${c.tax_gora} GORA</b></div></div>\n        <div><h3>${meta.name}</h3><p class=\"ethos\">${meta.ethos}</p>\n        <div class=\"row\"><span><b>${fmt.format(c.citizens)}</b> citizens</span><span><b>${fmt.format(c.agents)}</b> agents</span><span><b>${fmt.format(c.treasury_gora)}</b> GORA</span></div></div>`;\n      el.addEventListener(\"click\", () => openJoin(c.id));\n      rail.append(el);\n    }\n\n    const p = w.proposal; const total = p.yes + p.no || 1;\n    $(\"prop-title\").textContent = p.title;\n    $(\"bar-yes\").style.width = `${Math.round(p.yes / total * 100)}%`;\n    $(\"bar-no\").style.width = `${Math.round(p.no / total * 100)}%`;\n    $(\"pct-yes\").textContent = `${Math.round(p.yes / total * 100)}%`;\n    $(\"pct-no\").textContent = `${Math.round(p.no / total * 100)}%`;\n    $(\"prop-votes\").textContent = `${fmt.format(p.yes + p.no)} votes cast`;\n    $(\"prop-closes\").textContent = `Closes in ${p.closes_in_epochs} epochs`;\n    $(\"prop-status\").textContent = z?.voted ? \"You voted\" : \"Open\";\n\n    renderFeed();\n  }\n  function chip(text, cls = \"\") { const s = document.createElement(\"span\"); s.className = `chip ${cls}`; s.textContent = text; return s; }\n  const ICON = {\n    join: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#e8a33d\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M12 22s7-6.2 7-12a7 7 0 1 0-14 0c0 5.8 7 12 7 12z\"/></svg>',\n    pay_tax: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#5fd39a\" stroke-width=\"2\" stroke-linecap=\"round\"><rect x=\"3\" y=\"6\" width=\"18\" height=\"12\" rx=\"2.5\"/><path d=\"M3 10h18\"/></svg>',\n    vote: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#9b7bff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12l4 4L20 5\"/></svg>',\n    agent: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#4fc3b8\" stroke-width=\"2\" stroke-linecap=\"round\"><rect x=\"4\" y=\"5\" width=\"16\" height=\"14\" rx=\"4\"/><path d=\"M9 11h.01M15 11h.01M9 15h6\"/></svg>',\n  };\n  function renderFeed() {\n    const el = $(\"feed\"); el.innerHTML = \"\";\n    const items = feed.length ? feed : seedFeed();\n    for (const ev of items.slice(0, 6)) {\n      const row = document.createElement(\"div\"); row.className = \"event\";\n      row.innerHTML = `<div class=\"ico\">${ICON[ev.kind] || ICON.agent}</div><div><div class=\"t\"></div><div class=\"d\"></div></div>`;\n      row.querySelector(\".t\").textContent = ev.title;\n      const d = row.querySelector(\".d\");\n      for (const part of ev.detail.split(\" · \")) { const sp = document.createElement(\"span\"); sp.className = \"rc\"; sp.textContent = part; d.append(sp); }\n      d.insertAdjacentHTML(\"beforeend\", '<span class=\"att\"><i></i>Attested</span>');\n      el.append(row);\n    }\n  }\n  function seedFeed() {\n    const e = world?.epoch ?? 128;\n    return [\n      { kind: \"agent\", title: \"Agent ⟨kestrel-07⟩ paid taxes to Thule\", detail: `Epoch ${e - 1} · 15 GORA · receipt nw-8c1f…e2a0` },\n      { kind: \"vote\", title: \"Agent ⟨lumen-12⟩ voted Yes on Proposal 1\", detail: `Epoch ${e - 3} · receipt nw-27a9…41cd` },\n      { kind: \"join\", title: \"A new citizen joined Vesper\", detail: `Epoch ${e - 5} · did:gora:algo:Q7RX…M2KA` },\n      { kind: \"agent\", title: \"Agent ⟨sable-03⟩ paid taxes to Aurelia\", detail: `Epoch ${e - 8} · 12 GORA · receipt nw-c04e…7b19` },\n    ];\n  }\n  function pushFeed(kind, title, detail) { feed.unshift({ kind, title, detail }); }\n\n  // ---------- sheets ----------\n  const sheet = $(\"sheet\"), scrim = $(\"scrim\"), body = $(\"sheet-body\");\n  function open(html) { body.innerHTML = html; sheet.classList.add(\"open\"); scrim.classList.add(\"open\"); }\n  function close() { sheet.classList.remove(\"open\"); scrim.classList.remove(\"open\"); }\n  scrim.addEventListener(\"click\", close);\n\n  function resultBlock(ok, head, msg, receipt) {\n    return `<div class=\"result show ${ok ? \"\" : \"bad\"}\"><div class=\"ok\"><div class=\"ring\">${ok\n      ? '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#5fd39a\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12.5l4.2 4.2L19 7\"/></svg>'\n      : '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ff7d6b\" stroke-width=\"2.4\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>'}</div>\n      <div><div class=\"h\">${head}</div><div class=\"m\">${msg}</div></div></div>\n      ${receipt ? `<div class=\"rcpt\"><span>Receipt</span><span>${receipt}</span></div><div class=\"rcpt\" style=\"border:0;padding-top:6px\"><span>Execution</span><span>Attested by Gora · epoch ${world?.epoch ?? \"—\"}</span></div>` : \"\"}</div>`;\n  }\n\n  async function submit(btn, input, onOk) {\n    btn.classList.add(\"busy\"); btn.disabled = true;\n    try {\n      const out = await run(input);\n      if (!out.ok) throw new Error(out.error || \"the world refused\");\n      render(out);\n      onOk(out);\n    } catch (e) {\n      body.querySelector(\".result-slot\").innerHTML = resultBlock(false, \"Not this time\", e.message || String(e));\n    } finally { btn.classList.remove(\"busy\"); btn.disabled = false; }\n  }\n\n  function openJoin(preselect) {\n    const z = world?.citizen;\n    if (z) {\n      const c = COUNTRIES[z.country];\n      open(`<h3>Citizen of ${c.name}</h3><p>${c.ethos}</p>\n        <div class=\"summary\"><div><span>DID</span><b>did:gora:algo:${shortAddr(z.wallet)}</b></div><div><span>Joined</span><b>Epoch ${z.joined_epoch}</b></div><div><span>Tax cycle</span><b>${world.countries.find(x => x.id === z.country).tax_gora} GORA</b></div></div>\n        <button class=\"cta quiet\" id=\"sheet-close\">Close</button>`);\n      $(\"sheet-close\").addEventListener(\"click\", close); return;\n    }\n    let choice = preselect || \"aurelia\";\n    const opts = world.countries.map((c) => `<button class=\"opt\" data-id=\"${c.id}\" style=\"--c:${COUNTRIES[c.id].color}\" aria-pressed=\"${c.id === choice}\">\n      <div class=\"emblem\">${COUNTRIES[c.id].emblem}</div><div><div class=\"n\">${COUNTRIES[c.id].name}</div><div class=\"s\">${fmt.format(c.citizens)} citizens · ${fmt.format(c.treasury_gora)} GORA treasury</div></div><div class=\"r\">${c.tax_gora} GORA</div></button>`).join(\"\");\n    open(`<h3>Choose your country</h3><p>Your wallet becomes a DID. Citizenship is permanent for this world; taxes fall due every 12 epochs.</p>\n      <div class=\"opts\" id=\"opts\">${opts}</div>\n      <div class=\"summary\"><div><span>Identity</span><b>did:gora:algo:${shortAddr(wallet || demoWallet)}</b></div><div><span>Minimum balance</span><b>1 tax cycle in GORA</b></div></div>\n      <div class=\"result-slot\"></div>\n      <button class=\"cta\" id=\"sheet-go\"><span class=\"spin\"></span>Become a citizen</button>`);\n    body.querySelectorAll(\".opt\").forEach((o) => o.addEventListener(\"click\", () => {\n      choice = o.dataset.id; body.querySelectorAll(\".opt\").forEach((x) => x.setAttribute(\"aria-pressed\", x === o));\n    }));\n    $(\"sheet-go\").addEventListener(\"click\", (ev) => submit(ev.currentTarget, { action: \"join\", country: choice }, (out) => {\n      pushFeed(\"join\", `You became a citizen of ${out.country}`, `Epoch ${out.epoch} · receipt ${out.receipt}`); renderFeed();\n      body.querySelector(\".result-slot\").innerHTML = resultBlock(true, `Welcome to ${out.country}`, \"Your DID is live. First taxes fall due this cycle.\", out.receipt);\n      $(\"sheet-go\").textContent = \"Done\"; $(\"sheet-go\").onclick = close;\n    }));\n  }\n\n  function openTax() {\n    const z = world?.citizen;\n    if (!z) { toast(\"Join a country first — taxes go to your treasury.\"); return; }\n    const c = world.countries.find((x) => x.id === z.country); const meta = COUNTRIES[z.country];\n    open(`<h3>Pay taxes to ${meta.name}</h3><p>Your contribution goes straight into the national treasury and is counted toward this cycle.</p>\n      <div class=\"summary\"><div class=\"big\"><span>Amount</span><b>${c.tax_gora} GORA</b></div><div><span>Treasury after</span><b>${fmt.format(c.treasury_gora + c.tax_gora)} GORA</b></div><div><span>Standing</span><b>${z.tax_due_gora > 0 ? \"Overdue\" : \"Current\"} → Current</b></div></div>\n      <div class=\"result-slot\"></div>\n      <button class=\"cta\" id=\"sheet-go\"><span class=\"spin\"></span>Pay ${c.tax_gora} GORA</button>`);\n    $(\"sheet-go\").addEventListener(\"click\", (ev) => submit(ev.currentTarget, { action: \"pay_tax\" }, (out) => {\n      pushFeed(\"pay_tax\", `You paid ${out.amount_gora} GORA to ${out.country}`, `Epoch ${out.epoch} · receipt ${out.receipt}`); renderFeed();\n      body.querySelector(\".result-slot\").innerHTML = resultBlock(true, \"Taxes paid\", `${out.amount_gora} GORA added to the ${out.country} treasury.`, out.receipt);\n      $(\"sheet-go\").textContent = \"Done\"; $(\"sheet-go\").onclick = close;\n    }));\n  }\n\n  function openVote() {\n    const z = world?.citizen;\n    if (!z) { toast(\"Only citizens can vote. Join a country first.\"); return; }\n    if (z.voted) { toast(\"You already voted on this proposal.\"); return; }\n    let choice = \"yes\";\n    open(`<h3>Proposal 1</h3><p>${world.proposal.title}</p>\n      <div class=\"opts\">\n        <button class=\"opt\" data-c=\"yes\" style=\"--c:#5fd39a\" aria-pressed=\"true\"><div class=\"emblem\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#5fd39a\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12.5l4.2 4.2L19 7\"/></svg></div><div><div class=\"n\">Yes, build it</div><div class=\"s\">Funded from the Aurelia and Thule treasuries</div></div><div class=\"r\">${world.proposal.yes}</div></button>\n        <button class=\"opt\" data-c=\"no\" style=\"--c:#ff7d6b\" aria-pressed=\"false\"><div class=\"emblem\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ff7d6b\" stroke-width=\"2.2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg></div><div><div class=\"n\">No, not now</div><div class=\"s\">Keep the treasuries for the winter</div></div><div class=\"r\">${world.proposal.no}</div></button>\n      </div>\n      <div class=\"result-slot\"></div>\n      <button class=\"cta\" id=\"sheet-go\"><span class=\"spin\"></span>Cast vote</button>`);\n    body.querySelectorAll(\".opt\").forEach((o) => o.addEventListener(\"click\", () => {\n      choice = o.dataset.c; body.querySelectorAll(\".opt\").forEach((x) => x.setAttribute(\"aria-pressed\", x === o));\n    }));\n    $(\"sheet-go\").addEventListener(\"click\", (ev) => submit(ev.currentTarget, { action: \"vote\", choice }, (out) => {\n      pushFeed(\"vote\", `You voted ${out.choice === \"yes\" ? \"Yes\" : \"No\"} on Proposal 1`, `Epoch ${out.epoch} · receipt ${out.receipt}`); renderFeed();\n      body.querySelector(\".result-slot\").innerHTML = resultBlock(true, \"Vote counted\", `Your ${out.choice === \"yes\" ? \"Yes\" : \"No\"} is on the record.`, out.receipt);\n      $(\"sheet-go\").textContent = \"Done\"; $(\"sheet-go\").onclick = close;\n    }));\n  }\n\n  let toastTimer;\n  function toast(msg) { const t = $(\"toast\"); t.textContent = msg; t.classList.add(\"show\"); clearTimeout(toastTimer); toastTimer = setTimeout(() => t.classList.remove(\"show\"), 2600); }\n\n  $(\"btn-join\").addEventListener(\"click\", () => openJoin());\n  $(\"btn-tax\").addEventListener(\"click\", openTax);\n  $(\"btn-vote\").addEventListener(\"click\", openVote);\n\n  // ---------- boot ----------\n  (async () => {\n    for (const id of [\"s-epoch\", \"s-pop\", \"s-agents\"]) { $(id).classList.add(\"skeleton\"); $(id).textContent = \"000\"; }\n    await connect();\n    try {\n      const w = await run({ action: \"status\" });\n      if (!w.ok) throw new Error(w.error);\n      render(w);\n    } catch (e) {\n      render(demoRun({ action: \"status\" }));\n      toast(`Showing preview data · ${e.message || e}`);\n    } finally {\n      for (const id of [\"s-epoch\", \"s-pop\", \"s-agents\"]) $(id).classList.remove(\"skeleton\");\n    }\n  })();\n  window.addEventListener(\"gora:walletChanged\", async () => { await connect(); try { render(await run({ action: \"status\" })); } catch {} });\n})();\n</script>\n</body>\n</html>\n",
          "html_path": "ui/index.html",
          "type": "html"
        },
        "version_label": "v0.1.1"
      },
      "manifest_sha256": "dc8512453023351686e17444fd359c315eae8a4e490ff4a907a371e61e460bf1",
      "owner": "did:gora:developer:nomad_world",
      "policy_cid": "ipfs-local-sha256-f1020b1975ed749918683ae76e78611bce23602ee54d807896f0b9d1dea7beb5",
      "policy_sha256": "1dae604b709e81409273a365cd0136a9ca072c1ec1d6e69983790a7007280993",
      "schema_cids": [],
      "status": "active",
      "storage_policy": {
        "min_replication": 1,
        "pinning_tier": "dev_single_node",
        "renewal_required": false
      },
      "transaction_builders": [],
      "version_label": "v0.1.1",
      "wasm_cid": "ipfs-local-sha256-a845919602ac49e3f468b9f917ba8f68505b0a36dd23742a680eb780269332b2",
      "wasm_sha256": "a845919602ac49e3f468b9f917ba8f68505b0a36dd23742a680eb780269332b2"
    }
  ],
  "count": 4,
  "ok": true
}