near-toll-audit / docs/app.js · raw
// Dashboard wiring. Reads the JSON the pipeline writes and draws every figure. All outside text goes through textContent.(function () {  "use strict";  var V = window.Viz, h = V.h, time = V.time;  var REPO = "https://code.swiftbotlab.workers.dev/near-toll-audit/blob/main/";  var M, C, MODEL, GRADES, STATUS;  function metric(k) { return M[k] ? M[k].value : null; }  function compact(v, digits) {    var a = Math.abs(v), d = digits === undefined ? 1 : digits;    if (a >= 1e12) return (v / 1e12).toFixed(d) + "T";    if (a >= 1e9) return (v / 1e9).toFixed(d) + "B";    if (a >= 1e6) return (v / 1e6).toFixed(d) + "M";    if (a >= 1e3) return (v / 1e3).toFixed(a >= 1e5 ? 0 : d) + "K";    return v.toFixed(a < 10 && v % 1 ? d : 0);  }  // Axis labels drop the decimal when it adds nothing: 500M, not 500.0M.  function tick(v) {    var a = Math.abs(v), div = a >= 1e12 ? 1e12 : a >= 1e9 ? 1e9 : a >= 1e6 ? 1e6 : a >= 1e3 ? 1e3 : 1;    var q = v / div;    return (q % 1 ? q.toFixed(1) : q.toFixed(0)) + (div === 1e12 ? "T" : div === 1e9 ? "B" : div === 1e6 ? "M" : div === 1e3 ? "K" : "");  }  var f = {    usd: function (v, full) { return "$" + (full ? compact(v) : tick(v)); },    near: function (v) { return compact(v) + " NEAR"; },    num: function (v, full) { return full ? compact(v) : tick(v); },    pct: function (v) { return v.toFixed(Math.abs(v) < 10 ? 1 : 0) + "%"; },    bps: function (v) { return v.toFixed(Math.abs(v) < 10 ? 1 : 0) + " bps"; },    px: function (v) { return "$" + v.toFixed(2); },    x: function (v) { return v >= 100 ? Math.round(v) + "x" : v.toFixed(1) + "x"; }  };  function tok(name) { return V.css(name); }  // Each figure gets a title, one plain sentence, a chart, a table view and a link to what built it.  function figure(id, title, text, sources) {    var fig = document.getElementById(id);    if (!fig) return null;    fig.textContent = "";    var cap = h("figcaption", null, null, fig);    h("h3", null, title, cap);    if (text) h("p", null, text, cap);    var chart = h("div", "chart", null, fig);    fig._foot = sources;    return chart;  }  function finish(id, headers, rows) {    var fig = document.getElementById(id);    if (!fig) return;    if (headers) {      var det = h("details", "tbl", null, fig);      h("summary", null, "Table", det);      table(det, headers, rows);    }    var foot = h("div", "fig-foot", null, fig);    (fig._foot || []).forEach(function (s) {      if (s[1]) {        var a = h("a", null, s[0], foot);        a.href = s[1].indexOf("http") === 0 ? s[1] : REPO + s[1];      } else h("span", null, s[0], foot);    });  }  function table(parent, headers, rows, opts) {    var wrap = h("div", "scroll", null, parent);    var t = h("table", "data", null, wrap);    var head = h("tr", null, null, h("thead", null, null, t));    headers.forEach(function (x) { h("th", null, x, head); });    var body = h("tbody", null, null, t);    rows.forEach(function (r) {      var tr = h("tr", null, null, body);      if (r.us) tr.className = "is-us";      (r.cells || r).forEach(function (c, i) {        var td = h("td", null, c === null || c === undefined ? "n/a" : String(c), tr);        if (opts && opts.wrap && opts.wrap.indexOf(i) >= 0) td.className = "wrap-cell";      });    });    return t;  }  var SRC = {    analyze: ["analyze.py", "pipeline/analyze.py"],    s: function (file) { return ["SQL: " + file, "pipeline/sql/" + file]; },    fetch: ["fetch_public.py", "pipeline/fetch_public.py"],    chain: ["fetch_chain.py", "pipeline/fetch_chain.py"],    ex: ["fetch_explorer.py", "pipeline/fetch_explorer.py"]  };  function base() {    var c = figure("f-tx", "NEAR transactions a day", "Weekly average. The drop is one account leaving, not a change in the chain.", [["NearBlocks charts"], SRC.fetch, SRC.analyze]);    var d = C.tx_weekly.map(function (r) { return [time(r.w), r.tx]; });    V.line(c, { series: [{ name: "Transactions a day", color: tok("--s1"), points: d }], yFormat: f.num, marks: [{ t: time("2026-03-30"), label: "hotwallet.kaiching stops" }], label: "Transactions per day by week" });    finish("f-tx", ["Week", "Tx a day", "Active accounts a day"], C.tx_weekly.slice(-16).map(function (r) { return [r.w, compact(r.tx), compact(r.active)]; }));    c = figure("f-tiers", "Accounts by how much they actually use NEAR", "Raw signers swing with airdrops and mass deletions, so we show repeat use. Engaged means 3+ days and 2+ contracts in a month.", [["BigQuery transactions"], SRC.s("05_signer_tiers_monthly.sql"), SRC.analyze]);    var months = C.signer_tiers;    V.line(c, {      monthly: true, series: [        { name: "Active on 2+ days", color: tok("--mute"), mute: true, points: months.map(function (r) { return [time(r.m), r.repeat]; }) },        { name: "Engaged", color: tok("--s1"), points: months.map(function (r) { return [time(r.m), r.engaged]; }) },        { name: "Engaged, self-custody", color: tok("--s2"), points: months.map(function (r) { return [time(r.m), r.self_engaged]; }) }      ], yFormat: f.num, label: "Signers by engagement tier"    });    finish("f-tiers", ["Month", "All signers", "2+ days", "Engaged", "Self-custody", "Top 10 tx share"], months.map(function (r) { return [r.m, compact(r.signers), compact(r.repeat), compact(r.engaged), compact(r.self_engaged), r.top10_share + "%"]; }));    c = figure("f-new", "New signers a month", "Log scale. New app-managed accounts (.tg, .sweat, kaiching) have almost stopped.", [["BigQuery transactions"], SRC.s("04_cohort_matrix.sql"), SRC.analyze]);    V.line(c, {      monthly: true, log: true, series: [        { name: "App-managed", color: tok("--s2"), points: C.new_signers.filter(function (r) { return r.app > 0; }).map(function (r) { return [time(r.m), r.app]; }) },        { name: "Self-custody", color: tok("--s1"), points: C.new_signers.filter(function (r) { return r.self > 0; }).map(function (r) { return [time(r.m), r.self]; }) }      ], yFormat: f.num, label: "New signers per month"    });    finish("f-new", ["Month", "App-managed", "Self-custody"], C.new_signers.slice(-14).map(function (r) { return [r.m, compact(r.app), compact(r.self)]; }));    c = figure("f-burn", "Who pays for gas", "Share of gas spent, top contracts, October 2026. In March two kaiching contracts paid " + metric("burn_kaiching_share_mar26").toFixed(0) + "%.", [["BigQuery execution_outcomes"], SRC.s("08_burn_by_contract.sql"), SRC.analyze]);    var burn = C.burn_by_contract["2026-10"].slice(0, 8);    V.bars(c, { rows: burn.map(function (r) { return { label: r.account.length > 30 ? r.account.slice(0, 12) + "..." + r.account.slice(-6) : r.account, value: r.share, text: r.share + "%", strong: r.account === "intents.near" }; }) });    finish("f-burn", ["Contract", "Oct 2026 NEAR", "Share"], C.burn_by_contract["2026-10"].map(function (r) { return [r.account, Math.round(r.near), r.share + "%"]; }));    c = figure("f-ret", "Do new signers come back?", "Named .near accounts active again 1, 3, 6 and 12 months after their first month. Hex accounts, over 90% of self-custody signers, return at about " + metric("ret_hex_m1").toFixed(0) + "% after a month.", [["BigQuery transactions"], SRC.s("04_cohort_matrix.sql"), SRC.analyze]);    var rows = C.retention.named.filter(function (r) { return r.cohort >= "2025-10"; });    var seq = [tok("--seq-1"), tok("--seq-2"), tok("--seq-3"), tok("--seq-4"), tok("--seq-5")];    V.heat(c, {      corner: "Cohort", rows: rows.map(function (r) { return r.cohort; }), cols: ["+1 mo", "+3 mo", "+6 mo", "+12 mo"], name: "still active",      values: rows.map(function (r) { return [r.m1, r.m3, r.m6, r.m12]; }),      text: function (v) { return v.toFixed(1) + "%"; },      color: function (v) { var i = v < 5 ? 0 : v < 10 ? 1 : v < 20 ? 2 : v < 40 ? 3 : 4; var dark = document.documentElement.getAttribute("data-theme") === "dark" || (matchMedia("(prefers-color-scheme: dark)").matches && document.documentElement.getAttribute("data-theme") !== "light"); return { bg: seq[i], fg: (dark ? i < 3 : i > 2) ? "#fff" : "#0b0b0b" }; }    });    finish("f-ret", ["Cohort", "Size", "+1", "+3", "+6", "+12"], C.retention.app.filter(function (r) { return r.cohort >= "2025-07"; }).map(function (r) { return ["app " + r.cohort, compact(r.size), r.m1, r.m3, r.m6, r.m12]; }));    c = figure("f-mix", "What people called in October", "Share of transactions by what they touched. AI-named contracts are a rounding error.", [["BigQuery transactions"], SRC.s("12_contracts_oct.sql"), SRC.analyze]);    V.bars(c, { rows: C.contract_mix.map(function (r) { return { label: r.k, value: r.share, text: r.share + "%" }; }) });    finish("f-mix", ["Group", "Transactions", "Share"], C.contract_mix.map(function (r) { return [r.k, compact(r.txs), r.share + "%"]; }));  }  function real() {    var c = figure("f-recon", "Reported volume against what we can see settle", "Weekly. One-way on-chain flow runs at about " + Math.round(metric("flow_ratio_median") * 100) + "% of reported volume. It includes solvers rebalancing, so it is not end-user demand.", [["BigQuery ft_events + DefiLlama"], SRC.s("09_intents_flows_weekly.sql"), SRC.analyze]);    var rec = C.flow_reconciliation.slice(-52);    V.line(c, {      series: [        { name: "Reported volume", color: tok("--s1"), points: rec.map(function (r) { return [time(r.w), r.reported]; }) },        { name: "One-way on-chain flow", color: tok("--s2"), points: rec.map(function (r) { return [time(r.w), (r.deposits + r.withdrawn + r.moved_out) / 2]; }) }      ], yFormat: f.usd, label: "Reported volume and on-chain flow"    });    finish("f-recon", ["Week", "Reported", "Deposits", "Withdrawn", "Moved out", "Ratio"], rec.slice(-16).map(function (r) { return [r.w, f.usd(r.reported), f.usd(r.deposits), f.usd(r.withdrawn), f.usd(r.moved_out), r.ratio]; }));    c = figure("f-rails", "Share of native cross-chain rails", "Four rails with public data, four-week average. Intents took the lead in late 2025. Its share was " + metric("rail_share_jun26").toFixed(0) + "% in June and " + metric("rail_share_4w").toFixed(0) + "% in the last four weeks.", [["DefiLlama"], SRC.fetch, SRC.analyze]);    var sh = C.rail_share.filter(function (r) { return r.w >= "2025-06-01"; });    function rolling(key) {      return sh.map(function (r, i) {        var part = sh.slice(Math.max(0, i - 3), i + 1), v = 0;        part.forEach(function (x) { v += x[key] || 0; });        return [time(r.w), +(v / part.length).toFixed(1)];      });    }    V.line(c, {      series: [        { name: "NEAR Intents", color: tok("--s1"), points: rolling("NEAR Intents") },        { name: "THORChain", color: tok("--s2"), points: rolling("THORChain") },        { name: "Chainflip", color: tok("--s3"), points: rolling("Chainflip") },        { name: "Maya", color: tok("--s4"), points: rolling("Maya") }      ], yFormat: function (v) { return v.toFixed(0) + "%"; }, label: "Rail share, 4-week average"    });    finish("f-rails", ["Week", "Intents", "THORChain", "Chainflip", "Maya"], sh.slice(-14).map(function (r) { return [r.w, r["NEAR Intents"], r.THORChain, r.Chainflip, r.Maya]; }));    c = figure("f-sizes", "Retail by count, whales by value", "Intents withdrawals in the week of 19 October, by size. The top 10 addresses took " + metric("wd_top10_value_share").toFixed(0) + "% of value that week and " + metric("wd_top10_value_share_prev").toFixed(0) + "% the week before.", [["BigQuery ft_events"], SRC.s("13_withdrawal_size_week.sql"), SRC.analyze]);    V.bars(c, {      rows: C.withdrawal_sizes.map(function (r) { return { label: r.k, value: r.n_share, text: r.n_share + "%", value2: r.usd_share, text2: r.usd_share + "%" }; }),      legend: [{ name: "Share of withdrawals", color: tok("--s1") }, { name: "Share of value", color: tok("--s2") }]    });    finish("f-sizes", ["Size", "Share of count", "Share of value"], C.withdrawal_sizes.map(function (r) { return [r.k, r.n_share + "%", r.usd_share + "%"]; }));    c = figure("f-assets", "What moves through Intents", "Share of on-chain flow by asset, last 13 weeks.", [["BigQuery ft_events"], SRC.s("09_intents_flows_weekly.sql"), SRC.analyze]);    V.bars(c, { rows: C.flow_assets.map(function (r) { return { label: r.k, value: r.share, text: r.share + "%" }; }) });    finish("f-assets", ["Chain", "Share"], C.flow_chains.map(function (r) { return [r.k, r.share + "%"]; }));    c = figure("f-dest", "Withdrawal addresses in a sampled week", "Distinct addresses in the first seven days of each month. Broad and growing, but few come back in later samples.", [["BigQuery ft_events"], SRC.s("10_intents_destinations_monthly.sql"), SRC.analyze]);    V.columns(c, { labels: C.destinations.map(function (r) { return r.m; }), series: [{ name: "Addresses", color: tok("--s1"), values: C.destinations.map(function (r) { return r.dests; }) }], yFormat: f.num, label: "Distinct withdrawal addresses per sampled week" });    finish("f-dest", ["Month", "Addresses", "Withdrawals", "Top 10 share", "Returning share"], C.destinations.map(function (r) {      var ret = (C.dest_returning || []).filter(function (x) { return x.m === r.m; })[0];      return [r.m, compact(r.dests), compact(r.withdrawals), r.top10 + "%", ret ? ret.returning_share + "%" : "n/a"];    }));    c = figure("f-users", "Swap senders on a sample day", "Distinct senders on 1Click swaps, one full day a week, from mid-January 2026 when the field begins. Senders are not people, and " + metric("ex_blank_sender_pct").toFixed(0) + "% of recent volume has no sender field.", [["Intents Explorer API"], SRC.ex, ["analyze_explorer.py", "pipeline/analyze_explorer.py"]]);    var ed = (C.ex_daily || []).filter(function (r) { return r.senders > 100; });  // the API has no sender field before mid-January 2026    V.line(c, { series: [{ name: "Distinct senders", color: tok("--s1"), points: ed.map(function (r) { return [time(r.day), r.senders]; }) }], yFormat: f.num, label: "Distinct senders per sample day" });    finish("f-users", ["Day", "Senders", "Swaps", "Explorer volume"], ed.slice(-14).map(function (r) { return [r.day, compact(r.senders), compact(r.swaps), f.usd(r.usd)]; }));  }  function who() {    var p = C.per_100;    var c = figure("f-ladder", "Follow $100 through Intents", "Fee rate from the last full month, split by NEAR's 30-day capture rate.", [["DefiLlama + BigQuery"], SRC.analyze]);    var box = h("div", "ladder", null, c);    [["$100.00", "Swapped by the user"], ["$" + p.gross.toFixed(3), "Paid in fees: protocol plus partner fees"], ["$" + p.others.toFixed(3), "Kept by wallets, solvers and partners"], ["$" + p.captured.toFixed(3), "Reaches NEAR-side wallets"], ["$" + p.protocol_fee.toFixed(4), "The protocol's own 1-pip fee, charged on chain"], ["$" + p.gas.toFixed(4), "Gas spent by intents.near, 70% of it burned"]].forEach(function (s) {      var st = h("div", "step", null, box);      h("div", "what", s[1], st);      h("div", "amt", s[0], st);    });    finish("f-ladder");    c = figure("f-owners", "Who keeps the gross fee", "NEAR's own split, last 30 days against all time. Capture is rising, led by NEAR's own front end.", [["revenue.near.org"], SRC.fetch, SRC.analyze]);    if (C.capture_split) V.bars(c, {      rows: C.capture_split.map(function (r) { return { label: r.k, value: r.d30, text: r.d30 + "%", value2: r.all_time, text2: r.all_time + "%", strong: r.k.indexOf("NEAR, all") === 0 }; }),      legend: [{ name: "Last 30 days", color: tok("--s1") }, { name: "All time", color: tok("--s2") }]    });    finish("f-owners", ["Stated app-fee recipient", "Group", "Share of app fees"], (C.ex_recipients || []).map(function (r) { return [r.recipient.length > 34 ? r.recipient.slice(0, 14) + "..." : r.recipient, r.owner, r.share + "%"]; }));    c = figure("f-partners", "Partners: share of volume against share of fees", "1Click swaps in the Explorer. Wallet channels charge the most, and their users rarely compare routes (our inference).", [["Intents Explorer API"], SRC.ex, SRC.analyze]);    table(c, ["Partner", "Volume", "Fees", "Avg fee"], (C.ex_partners || []).slice(0, 9).map(function (r) { return [r.partner === "(none)" ? "No referral tag" : r.partner.length > 24 ? r.partner.slice(0, 22) + "..." : r.partner, r.vol_share + "%", r.fee_share + "%", r.bps + " bps"]; }));    finish("f-partners");    c = figure("f-take", "What Intents charges and what NEAR keeps", "Monthly, basis points of reported volume. Capture is rising; the gross fee is not.", [["DefiLlama"], SRC.fetch, SRC.analyze]);    var t = C.intents_monthly.filter(function (r) { return r.m >= "2025-09" && r.m < metric("as_of").slice(0, 7) + "z"; });    V.line(c, {      monthly: true, series: [        { name: "Gross fee", color: tok("--s1"), points: t.map(function (r) { return [time(r.m), r.gross_bps]; }) },        { name: "Captured by NEAR", color: tok("--s2"), points: t.map(function (r) { return [time(r.m), r.net_bps]; }) }      ], yFormat: function (v, full) { return full ? v.toFixed(1) + " bps" : v.toFixed(0); }, left: 34, label: "Gross and captured basis points"    });    finish("f-take", ["Month", "Volume", "Fees", "Captured", "Gross bps", "Net bps", "Capture"], t.map(function (r) { return [r.m, f.usd(r.vol), f.usd(r.fees), f.usd(r.rev), r.gross_bps, r.net_bps, r.capture + "%"]; }));    c = figure("f-channels", "The fee base is narrower than the volume", "Explorer swaps are " + (metric("ex_coverage_pct") || 0).toFixed(0) + "% of reported volume and about " + (metric("ex_fee_coverage_pct") || 0).toFixed(0) + "% of reported fees. The rest pays at most about " + (metric("ex_rest_bps") || 0).toFixed(1) + " bps.", [["Intents Explorer API + DefiLlama"], SRC.analyze]);    var cov = metric("ex_coverage_pct") || 0, fc = Math.min(100, metric("ex_fee_coverage_pct") || 0);    V.bars(c, { rows: [{ label: "1Click swaps in the Explorer", value: cov, text: cov.toFixed(0) + "%", value2: fc, text2: fc.toFixed(0) + "%" }, { label: "Everything else reported", value: 100 - cov, text: (100 - cov).toFixed(0) + "%", value2: 100 - fc, text2: (100 - fc).toFixed(0) + "%" }], legend: [{ name: "Share of volume", color: tok("--s1") }, { name: "Share of fees", color: tok("--s2") }] });    finish("f-channels");    c = figure("f-cost", "What users really pay", "Median gap between dollars in and dollars out, by swap size, including every fee and spread.", [["Intents Explorer API"], SRC.ex, SRC.analyze]);    if (C.ex_sizes) V.bars(c, { rows: C.ex_sizes.map(function (r) { return { label: r.k, value: r.cost || 0, text: r.cost === null ? "n/a" : r.cost + " bps" }; }) });    finish("f-cost", ["Size", "Share of swaps", "Share of dollars", "Median cost"], (C.ex_sizes || []).map(function (r) { return [r.k, r.n_share + "%", r.usd_share + "%", r.cost + " bps"]; }));  }  function mixSeries() {    var c = figure("f-mix-series", "How concentrated the paying flow is", "Share of Explorer dollars on each sample day. Each point is one full day, so expect noise.", [["Intents Explorer API"], SRC.ex, SRC.analyze]);    var ms = C.ex_mix_series || [];    V.line(c, {      series: [        { name: "Through SwapKit", color: tok("--s1"), points: ms.map(function (r) { return [time(r.day), r.swapkit]; }) },        { name: "Zcash on one side", color: tok("--s2"), points: ms.map(function (r) { return [time(r.day), r.zec]; }) },        { name: "Stablecoin to stablecoin", color: tok("--s3"), points: ms.map(function (r) { return [time(r.day), r.stable]; }) }      ], yFormat: function (v) { return v.toFixed(0) + "%"; }, label: "Explorer mix by sample day"    });    finish("f-mix-series", ["Day", "SwapKit", "Zcash", "Stable to stable"], ms.slice(-16).map(function (r) { return [r.day, r.swapkit + "%", r.zec + "%", r.stable + "%"]; }));    c = figure("f-routes", "What is being swapped", "Share of Explorer dollars by route, sample days in the last 13 weeks.", [["Intents Explorer API"], SRC.ex, SRC.analyze]);    V.bars(c, { rows: (C.ex_routes || []).slice(0, 8).map(function (r) { return { label: r.k, value: r.share, text: r.share + "%" }; }) });    finish("f-routes", ["Chains", "Share"], (C.ex_chain_routes || []).map(function (r) { return [r.k, r.share + "%"]; }));  }  function token() {    var fl = C.flows_near_per_day;    var c = figure("f-flows", "NEAR created against NEAR offset, per day", "October 2026. Revenue is shown as if every dollar bought NEAR that is never sold, the best case.", [["BigQuery blocks + DefiLlama"], SRC.s("01_supply_daily.sql"), SRC.analyze]);    V.bars(c, {      rows: [{ label: "New NEAR issued", value: fl.issuance, text: compact(fl.issuance), color: tok("--neg") }, { label: "Revenue, in NEAR", value: fl.revenue_equiv, text: compact(fl.revenue_equiv) }, { label: "Fees burned", value: fl.burn, text: compact(fl.burn) }]    });    finish("f-flows", ["Flow", "NEAR a day"], [["Issued", fl.issuance], ["Revenue equivalent", fl.revenue_equiv], ["Burned", fl.burn]]);    c = figure("f-wallets", "The revenue wallets only fill up", "Weekly balances from archival RPC. The buyback multisig has not fallen by more than 1,000 NEAR in a week. Its jump in September is the 1Click wallet moving in.", [["NEAR archival RPC"], SRC.chain, SRC.analyze]);    var wh = C.wallet_history, ser = [];    [["buybacks.multisignature.near", "Buyback multisig", "--s1"], ["fefundsadmin.sputnik-dao.near", "Front-end fee DAO", "--s2"], ["1csfundsadmin.sputnik-dao.near", "1Click fee DAO", "--s3"]].forEach(function (x) {      if (wh[x[0]]) ser.push({ name: x[1], color: tok(x[2]), points: wh[x[0]].map(function (r) { return [time(r[0]), r[1]]; }) });    });    V.line(c, { series: ser, yFormat: f.num, label: "Revenue wallet balances" });    finish("f-wallets", ["Date"].concat(ser.map(function (s) { return s.name; })), (wh["buybacks.multisignature.near"] || []).slice(-10).map(function (r, i, a) {      return [r[0]].concat(ser.map(function (s) { var p = s.points.filter(function (q) { return q[0] === time(r[0]); })[0]; return p ? compact(p[1]) : "n/a"; }));    }));    c = figure("f-offset", "Captured revenue as a share of issuance", "Monthly, in NEAR. The official series, compared with October's issuance.", [["revenue.near.org"], SRC.fetch, SRC.analyze]);    var rev = (C.official_rev_monthly || []).filter(function (r) { return r.m < metric("as_of").slice(0, 7) + "z" && r.m >= "2025-10"; });    var issue = metric("mint_30d_per_day") * 30;    V.columns(c, { labels: rev.map(function (r) { return r.m; }), series: [{ name: "Share of issuance", color: tok("--s1"), values: rev.map(function (r) { return +(r.near / issue * 100).toFixed(1); }) }], yFormat: function (v) { return v.toFixed(0) + "%"; }, label: "Revenue as a share of issuance" });    finish("f-offset", ["Month", "NEAR", "USD"], rev.map(function (r) { return [r.m, compact(r.near), f.usd(r.usd)]; }));    c = figure("f-mint", "New NEAR against burned NEAR, per day", "Monthly average. Issuance halved in late October 2025. Burn is too small to see on the same scale, so it is listed in the table.", [["BigQuery blocks"], SRC.s("01_supply_daily.sql"), SRC.analyze]);    var mb = C.mint_burn_monthly.filter(function (r) { return r.m < metric("as_of").slice(0, 7) + "z"; });    V.columns(c, { labels: mb.map(function (r) { return r.m; }), series: [{ name: "Minted a day", color: tok("--s1"), values: mb.map(function (r) { return r.mint_per_day; }) }, { name: "Burned a day", color: tok("--s2"), values: mb.map(function (r) { return r.burn_per_day; }) }], stacked: false, yFormat: f.num, label: "Mint and burn per day" });    finish("f-mint", ["Month", "Minted a day", "Burned a day"], mb.slice(-14).map(function (r) { return [r.m, compact(r.mint_per_day), Math.round(r.burn_per_day)]; }));    c = figure("f-locked", "Supply not yet circulating", "From the NEAR public dataset. The original vesting overhang is gone, so unlocks are no longer a supply risk on this measure.", [["BigQuery circulating_supply"], SRC.s("02_circulating_supply.sql"), SRC.analyze]);    V.line(c, { series: [{ name: "Not circulating", color: tok("--s1"), points: C.locked_supply.map(function (r) { return [time(r.d), r.locked]; }) }], yFormat: f.num, label: "Locked supply" });    finish("f-locked", ["Measure", "NEAR"], [["Public dataset, circulating", compact(metric("circulating_bq"))], ["NearBlocks, circulating", compact(metric("circulating_nearblocks"))], ["Total", compact(metric("supply"))]]);    c = figure("f-claim", "What actually binds this money to holders", "A plain reading of the chain. No row has a rule that a holder could enforce.", [["NEAR RPC"], SRC.chain]);    var fe = (C.wallet_control || {})["fefundsadmin.sputnik-dao.near"] || {};    table(c, ["Money", "Who controls it", "Rule binding it"], [      ["Protocol fee, 1 pip", "fee_collector account, set by contract owner", "None to holders"],      ["1Click and partner fees", "Commercial terms, off-chain", "None"],      ["Buyback multisig, " + compact(metric("buyback_wallet_near")) + " NEAR", metric("buyback_confirmations") + " confirmations on a multisig contract", "None. Not burned"],      ["Front-end fee DAO, " + compact(metric("fe_wallet_near")) + " NEAR", (fe.admins || []).slice(0, 3).join(", ") + " and others", "None. Not burned"],      ["Gas fees", "Protocol", "Burned by rule, 70% today, 100% after protocol 87"]    ], { wrap: [0, 1, 2] });    finish("f-claim");  }  function pricing() {    var series = C.pricing_series;    var c = figure("f-event", "Rail share and NEAR's take around the fee switch", "Four-week average share against rival rails, with the two dates we tested.", [["DefiLlama"], SRC.analyze]);    var smooth = series.map(function (r, i) {      var part = series.slice(Math.max(0, i - 3), i + 1), v = 0;      part.forEach(function (x) { v += x.share; });      return [time(r.w), +(v / part.length).toFixed(1)];    });    V.line(c, { series: [{ name: "Intents share of rails", color: tok("--s1"), points: smooth }], yFormat: function (v) { return v.toFixed(0) + "%"; }, marks: [{ t: time("2026-02-23"), label: "Partner share live" }, { t: time("2026-08-31"), label: "Capture steps up", row: 1 }], label: "Rail share over time" });    finish("f-event", ["Event", "Share before", "Share after", "Net bps before", "Net bps after", "Gross bps before", "Gross bps after", "Volume against rivals, 95%"], C.pricing_events.map(function (r) { return [r.label, r.share_before + "%", r.share_after + "%", r.net_before, r.net_after, r.gross_before, r.gross_after, r.relative + "x (" + r.lo + "x to " + r.hi + "x)"]; }));    var evs = C.fee_events || [], txt = document.getElementById("elast-text");    c = figure("f-elast", "When a partner changes its posted fee, does its share move?", "A posted fee is the most common fee across a partner's swaps that day. We list every lasting step of 3 bps or more, and compare four sample weeks after with four before.", [["Intents Explorer API"], SRC.ex, SRC.analyze]);    var rise = metric("fee_rise_share_change_pct"), cut = metric("fee_cut_share_change_pct");    if (rise !== null) {      var tile = h("div", "tile-inline", null, c);      h("div", "value", (rise > 0 ? "+" : "") + rise.toFixed(0) + "%", tile);      h("div", "sub", "average change in partner share after a fee rise, " + metric("fee_events_rises") + " events", tile);      V.interval(h("div", null, null, c), { est: rise, lo: metric("fee_rise_lo_pct"), hi: metric("fee_rise_hi_pct"), fmt: function (v) { return (v > 0 ? "+" : "") + v.toFixed(0) + "%"; }, label: "Share change after a fee rise, 95 percent interval" });    }    if (txt) {      if (rise !== null) txt.textContent = "across " + metric("fee_events_rises") + " lasting rises in a partner's posted fee, its share of swaps changed " + (rise > 0 ? "+" : "") + rise.toFixed(0) + "% on average, with a 95% interval of " + metric("fee_rise_lo_pct").toFixed(0) + "% to +" + metric("fee_rise_hi_pct").toFixed(0) + "%. Too wide to read, and most of these partners are small.";      else txt.textContent = evs.length ? evs.length + " lasting fee changes so far, too few to average." : "The partner panel is still filling in.";    }    if (evs.length) {      var det = h("details", "tbl", null, document.getElementById("f-elast"));      h("summary", null, "Every fee change", det);      table(det, ["Partner", "Date", "Fee", "Share of swaps", "Change"], evs.map(function (r) {        return [r.partner.length > 22 ? r.partner.slice(0, 20) + "..." : r.partner, r.day, r.fee_before + " to " + r.fee_after, r.share_before + "% to " + r.share_after + "%", ((Math.exp(r.log_share_change) - 1) * 100).toFixed(0) + "%"];      }));    }    finish("f-elast");  }  function stake() {    var c = figure("f-stake", "NEAR staked", "Month ends, rebuilt from account changes. Flat for two years while supply kept growing.", [["BigQuery account_changes"], SRC.s("15_stake_history.sql"), SRC.analyze]);    V.line(c, { series: [{ name: "Staked", color: tok("--s1"), points: C.stake_history.map(function (r) { return [time(r.d), r.near]; }) }], yFormat: f.num, monthly: true, label: "Staked NEAR" });    finish("f-stake", ["Date", "Staked", "Accounts"], C.stake_history.slice(-12).map(function (r) { return [r.d, compact(r.near), r.accounts]; }));    c = figure("f-stakefacts", "Staking in numbers", "Read from RPC at the latest epoch.", [["NEAR RPC"], SRC.chain, SRC.analyze]);    table(c, ["Measure", "Value"], [      ["Staked share of supply", metric("stake_ratio_pct").toFixed(1) + "%"], ["Active validators", metric("validators")], ["Validators for 1/3 of stake", metric("nakamoto_third")], ["Top 10 share of stake", metric("top10_stake_pct").toFixed(1) + "%"],      ["Gross yield", metric("staking_yield_gross_pct").toFixed(2) + "%"], ["Typical commission", metric("commission_wavg_pct").toFixed(1) + "%"], ["Stake in 100% commission pools (self-stake)", metric("self_stake_share_pct").toFixed(0) + "%"],      ["Treasury share of issuance", "10% (RPC field reads 0; nearcore hardcodes 1/10)"], ["veNEAR supply", compact(metric("venear_supply")) + " across " + metric("venear_accounts") + " accounts"],      ["Vote to cut issuance to 1.6%", metric("issuance_vote_status") + ", " + metric("issuance_vote_for_pct").toFixed(1) + "% in favour, " + metric("issuance_vote_voters") + " voters"], ["Gas rebate removed (protocol 87)", metric("gas_rebate_removed") + ", live version " + metric("protocol_version")]    ].concat(metric("etf_near") ? [["Bitwise ETF holdings", compact(metric("etf_near")) + " NEAR, " + metric("etf_pct_supply").toFixed(2) + "% of supply"]] : []), { wrap: [0] });    finish("f-stakefacts");  }  function rivals() {    var c = figure("f-railtable", "Cross-chain rails, same measures", "Volume and fees over 30 days. The last column is market cap per dollar of annual volume. NEAR's cap also prices the chain and the AI story, and RUNE is a depressed pure rail.", [["DefiLlama + CoinGecko"], SRC.fetch, SRC.analyze]);    table(c, ["Rail", "Volume", "Take", "Market cap", "Cap / yearly vol"], C.rail_table.map(function (r) {      return { us: r.rail === "NEAR Intents", cells: [r.rail, f.usd(r.vol_30d), r.take_bps ? r.take_bps + " bps" : "n/a", r.mcap ? f.usd(r.mcap) : "no token", r.mcap_per_annual_volume === null ? "n/a" : "$" + r.mcap_per_annual_volume.toFixed(3)] };    }));    finish("f-railtable");    c = figure("f-comps", "Market cap over annualised holder revenue", "Lower is cheaper. NEAR's cap is a whole chain and its revenue is not bound to holders, so this is not like for like.", [["DefiLlama + CoinGecko"], SRC.fetch, SRC.analyze]);    var cp = C.comps.filter(function (r) { return r.multiple; }).sort(function (a, b) { return a.multiple - b.multiple; });    var capped = 300;    V.bars(c, { rows: cp.map(function (r) { return { label: r.symbol, value: Math.min(r.multiple, capped), text: f.x(r.multiple) + (r.multiple > capped ? " (bar capped)" : ""), strong: r.symbol === "NEAR", color: r.symbol === "NEAR" ? tok("--s2") : tok("--s1") }; }) });    finish("f-comps", ["Token", "Market cap", "Annualised holder revenue", "Multiple", "On last 12 months"], C.comps.map(function (r) { return [r.symbol, f.usd(r.mcap), f.usd(r.ann_holder_rev), r.multiple + "x", r.multiple_1y + "x"]; }));    c = figure("f-l1", "NEAR against other chains", "Fees are chain fees over 30 days. Count Intents gross fees and NEAR is at " + Math.round(metric("near_mcap_to_all_fees")) + "x, but most of those are not NEAR's. On what it keeps it is at " + Math.round(metric("near_mcap_to_kept_fees")) + "x.", [["DefiLlama + CoinGecko"], SRC.fetch, SRC.analyze]);    table(c, ["Chain", "Market cap", "Fees 30d", "Cap / fees", "TVL", "Stablecoins", "1 year"], C.l1_peers.map(function (r) {      return { us: r.chain === "NEAR", cells: [r.chain, f.usd(r.mcap), f.usd(r.fees_30d), r.mcap_to_fees + "x", f.usd(r.tvl), f.usd(r.stable), r.chg_1y === null ? "n/a" : (r.chg_1y > 0 ? "+" : "") + Math.round(r.chg_1y) + "%"] };    }));    finish("f-l1");    c = figure("f-stable", "Stablecoins on NEAR", "Weekly. NEAR ranks number " + metric("near_stable_rank") + " by float.", [["DefiLlama stablecoins"], SRC.fetch, SRC.analyze]);    V.line(c, { series: [{ name: "Stablecoin float", color: tok("--s1"), points: C.stable_near.map(function (r) { return [time(r.d), r.usd]; }) }], yFormat: f.usd, label: "Stablecoin float on NEAR" });    finish("f-stable", ["Week", "Float"], C.stable_near.slice(-10).map(function (r) { return [r.d, f.usd(r.usd)]; }));  }  function lead() {    var cc = C.ccf_volume_price;    var c = figure("f-ccf", "Volume and price: who moves first", "Correlation of weekly changes at each lag. Positive lag means volume moves first. Bars inside ±" + cc.band + " are noise.", [["DefiLlama"], SRC.analyze]);    V.columns(c, { labels: cc.points.map(function (r) { return String(r.lag); }), series: [{ name: "Correlation", color: tok("--s1"), values: cc.points.map(function (r) { return r.r; }) }], yFormat: function (v) { return v.toFixed(2); }, left: 40, label: "Cross-correlation" });    finish("f-ccf", ["Lag (weeks)", "r"], cc.points.map(function (r) { return [r.lag, r.r]; }));    c = figure("f-lead", "Six forward tests", "Extra R² from adding Intents volume or price to a usage-only model. The gap is volume minus price.", [["DefiLlama + BigQuery + NearBlocks"], SRC.analyze]);    var names = { burn_1w: "Burn, 1 week", burn_2w: "Burn, 2 weeks", burn_4w: "Burn, 4 weeks", active_1w: "Active accounts, 1 week", active_2w: "Active accounts, 2 weeks", active_4w: "Active accounts, 4 weeks" };    table(c, ["Usage measure", "Volume", "Price", "Gap, 95%"], Object.keys(names).map(function (k) { var r = C.lead_lag[k]; return [names[k], r.r2_extra_volume, r.r2_extra_price, r.gap + " (" + r.gap_lo + " to " + r.gap_hi + ")"]; }));    finish("f-lead");  }  function ai() {    var c = figure("f-ai", "AI and agents: measured", "What can be read publicly today.", [["BigQuery transactions"], SRC.s("12_contracts_oct.sql"), SRC.analyze]);    table(c, ["Measure", "Value"], [      ["AI or agent-named contract tx, October", compact(metric("ai_named_tx_oct"))], ["Share of all transactions", metric("ai_named_share_pct").toFixed(2) + "%"], ["Without the largest contract", metric("ai_named_ex_top_share_pct").toFixed(3) + "%"],      ["Direct tx to Chain Signatures, October", compact(metric("chain_sig_tx_oct"))], ["escrow.ai.near share of direct calls into intents.near", metric("escrow_ai_share_pct").toFixed(0) + "%"]    ], { wrap: [0] });    finish("f-ai");    c = figure("f-dev", "Core repositories, last 13 weeks", "Commits and authors in main protocol repos. This is not an ecosystem count: repo choice decides the ranking.", [["GitHub API"], SRC.fetch, SRC.analyze]);    table(c, ["Chain", "Commits", "Authors, 90 days"], C.github_chain.map(function (r) { return { us: r.chain === "NEAR", cells: [r.chain, r.commits_13w, r.authors_90d] }; }));    finish("f-dev", ["Repo", "Commits 13w", "Commits 52w", "Authors 90d"], C.github.map(function (r) { return [r.repo, r.commits_13w, r.commits_52w, r.active_authors_90d]; }));  }  // ------------------------------------------------------------------ live model  var state = null, active = "base";  var CTL = [    ["cagr", "Volume growth a year, 5 years", -0.3, 1.5, 0.01, "pct"], ["gross_bps_end", "Year-5 gross fee (bps)", 2, 30, 0.5, "bps"], ["capture_end", "Year-5 share NEAR captures", 0.05, 1, 0.01, "pct"],    ["claim", "Share of that reaching holders", 0, 1, 0.01, "pct"], ["exit_multiple", "Exit multiple on holder earnings", 3, 60, 1, "x"], ["discount", "Discount rate", 0.06, 0.3, 0.005, "pct"],    ["l1_value", "Value as a chain, apart from Intents", 0, 8e9, 1e8, "usd"], ["issuance_end", "Long-run issuance", 0, 0.03, 0.001, "pct1"]  ];  function fmtCtl(kind, v) {    if (kind === "pct") return Math.round(v * 100) + "%";    if (kind === "pct1") return (v * 100).toFixed(1) + "%";    if (kind === "bps") return v.toFixed(1);    if (kind === "x") return v + "x";    return "$" + compact(v);  }  function drawResult() {    var inputs = state[active].inputs, out = window.NearModel.value(inputs), price = MODEL.price;    var c = document.getElementById("f-result");    c.textContent = "";    var cap = h("figcaption", null, null, c);    h("h3", null, active.charAt(0).toUpperCase() + active.slice(1) + " case: value per NEAR", cap);    h("p", null, MODEL.scenarios[active].summary, cap);    var res = h("div", "result", null, c);    h("div", "price", "$" + out.price.toFixed(2), res);    var gap = (out.price / price - 1) * 100;    h("p", "vs", (gap >= 0 ? "+" : "") + gap.toFixed(0) + "% against the market price of $" + price.toFixed(2), res);    var split = h("div", "split", null, res), total = out.equity || 1;    [[out.pv_cash, tok("--s3"), "Five years of holder earnings"], [out.pv_terminal, tok("--s1"), "Exit value"], [out.l1_value, tok("--s2"), "Value as a chain"]].forEach(function (s) {      var i = h("i", null, null, split);      i.style.width = (s[0] / total * 100) + "%";      i.style.background = s[1];      i.title = s[2];    });    var leg = h("div", "viz-legend", null, res);    [[out.pv_cash, tok("--s3"), "Holder earnings"], [out.pv_terminal, tok("--s1"), "Exit value"], [out.l1_value, tok("--s2"), "Chain value"]].forEach(function (s) {      var it = h("span", "viz-legend-item", null, leg);      h("span", "viz-legend-mark is-bar", null, it).style.background = s[1];      h("span", null, s[2] + " " + "$" + compact(s[0]), it);    });    var imp = window.NearModel.impliedVolume(inputs, price);    h("p", "small", "Year-5 volume $" + compact(out.y5_volume) + ", captured revenue $" + compact(out.y5_captured) + ". To reach the market price with these inputs: " + (imp ? "$" + compact(imp.y5_volume) + " a year by year 5." : "no volume path gets there."), res);    var w = window.NearModel.weighted(state);    var b = window.NearModel.impliedBull(state, price);    h("p", "small", "Probability-weighted, all three cases: $" + w.toFixed(2) + ". Market-implied bull weight: " + (b === null ? "n/a" : Math.round(b * 100) + "%") + ".", res);    var foot = h("div", "fig-foot", null, c);    var a = h("a", null, "model.js", foot); a.href = REPO + "docs/model.js";    var b2 = h("a", null, "scenarios.json", foot); b2.href = REPO + "thesis/scenarios.json";    drawGrid();  }  function drawControls() {    var c = document.getElementById("f-controls");    c.textContent = "";    var cap = h("figcaption", null, null, c);    h("h3", null, "Assumptions", cap);    h("p", null, "Start from a case, then move anything. Nothing leaves your browser.", cap);    var tabs = h("div", "tabs", null, c);    ["bear", "base", "bull"].forEach(function (k) {      var b = h("button", "btn", k.charAt(0).toUpperCase() + k.slice(1) + " " + Math.round(state[k].probability * 100) + "%", tabs);      b.type = "button";      b.setAttribute("aria-pressed", String(k === active));      b.addEventListener("click", function () { active = k; drawControls(); drawResult(); });    });    var reset = h("button", "btn", "Reset", tabs);    reset.type = "button";    reset.addEventListener("click", function () { state = JSON.parse(JSON.stringify(MODEL.scenarios)); drawControls(); drawResult(); });    CTL.forEach(function (ct) {      var key = ct[0], row = h("div", "ctl", null, c), id = "ctl-" + key;      var lab = h("label", null, ct[1], row); lab.htmlFor = id;      var out = h("output", null, fmtCtl(ct[5], state[active].inputs[key]), row);      var inp = h("input", null, null, row);      inp.type = "range"; inp.id = id; inp.min = ct[2]; inp.max = ct[3]; inp.step = ct[4]; inp.value = state[active].inputs[key];      inp.addEventListener("input", function () {        state[active].inputs[key] = parseFloat(inp.value);        out.textContent = fmtCtl(ct[5], state[active].inputs[key]);        drawResult();      });    });    var key2 = h("p", "small", "Weights", c);    key2.style.margin = "10px 0 4px";    ["bear", "base", "bull"].forEach(function (k) {      var row = h("div", "ctl", null, c), lab = h("label", null, k.charAt(0).toUpperCase() + k.slice(1) + " probability", row);      lab.htmlFor = "w-" + k;      var out = h("output", null, Math.round(state[k].probability * 100) + "%", row);      var inp = h("input", null, null, row);      inp.type = "range"; inp.id = "w-" + k; inp.min = 0; inp.max = 1; inp.step = 0.01; inp.value = state[k].probability;      inp.addEventListener("input", function () {        var nv = parseFloat(inp.value), others = ["bear", "base", "bull"].filter(function (o) { return o !== k; });        var rest = others.reduce(function (a, o) { return a + state[o].probability; }, 0) || 1;        others.forEach(function (o) { state[o].probability = state[o].probability / rest * (1 - nv); });        state[k].probability = nv;        drawControls(); drawResult();        var again = document.getElementById("w-" + k); if (again) again.focus();      });    });  }  function drawGrid() {    var g = MODEL.grid, inputs = state[active].inputs;    var c = figure("f-grid", "Value per NEAR for any volume and take", "Year-5 annual volume across, NEAR's captured rate down. Blue is above the market price, red below. Uses the assumptions on the left.", [["model.py grid"], ["model.js", "docs/model.js"]]);    var rows = g.net_bps.map(function (n) { return n + " bps"; });    var values = g.net_bps.map(function (n) {      return g.volumes.map(function (v) {        var q = Object.assign({}, inputs);        q.cagr = Math.pow(v / q.vol0, 0.2) - 1;        q.gross_bps_start = q.gross_bps_end = n; q.capture_start = q.capture_end = 1;        return window.NearModel.value(q).price;      });    });    V.heat(c, {      corner: "Captured", rows: rows, cols: g.volumes.map(function (v) { return "$" + compact(v, 0); }), values: values, name: "value per NEAR",      text: function (v) { return "$" + v.toFixed(2); },      color: function (v) {        var r = v / MODEL.price;        if (r >= 1.5) return { bg: tok("--seq-4"), fg: "#fff" };        if (r >= 1) return { bg: tok("--seq-2"), fg: document.documentElement.getAttribute("data-theme") === "dark" ? "#fff" : "#0b0b0b" };        if (r >= 0.6) return { bg: tok("--wash"), fg: tok("--ink") };        return { bg: tok("--neg-soft"), fg: tok("--ink") };      }    });    finish("f-grid");  }  function drawTornado() {    var c = figure("f-tornado", "Which assumptions matter most", "Base case, one input moved at a time. Black tick: base value. Yellow tick: market price.", [["model.py tornado"], ["scenarios.json", "thesis/scenarios.json"]]);    var rows = MODEL.tornado.slice(0, 8), mid = rows[0].mid;    V.tornado(c, { mid: mid, market: MODEL.price, rows: rows.map(function (r) { return { label: r.label, low: r.low, high: r.high, lowText: "$" + r.low.toFixed(2), highText: "$" + r.high.toFixed(2) }; }) });    finish("f-tornado", ["Input", "Low", "High", "Value at low", "Value at high"], rows.map(function (r) { return [r.label, r.low_input, r.high_input, "$" + r.low.toFixed(2), "$" + r.high.toFixed(2)]; }));  }  function kill() {    var c = figure("f-kill", "Criteria, graded by code at each build", "Thresholds and dates are fixed. The value in each row updates by itself.", [["grade.py", "pipeline/grade.py"], ["kill_criteria.json", "thesis/kill_criteria.json"], ["Hash-chained ledger", "ledger/grades.jsonl"]]);    function val(r) {      if (r.value === null) return "n/a";      if (r.unit === "usd") return "$" + compact(r.value);      if (r.unit === "NEAR") return compact(r.value);      return (Math.abs(r.value) < 10 ? r.value.toFixed(2) : Math.round(r.value)) + (r.unit === "%" ? "%" : r.unit === "x" ? "x" : " " + r.unit);    }    function th(r) { return r.unit === "usd" ? "$" + compact(r.threshold, 0) : r.unit === "NEAR" ? compact(r.threshold, 0) : r.threshold + (r.unit === "%" ? "%" : r.unit === "x" ? "x" : " " + r.unit); }    ["toll", "token"].forEach(function (side) {      h("h4", "kill-head", side === "toll" ? "Would kill the toll road" : "Would kill our caution on the token", c);      var list = h("div", "kill-list", null, c);      GRADES.rows.filter(function (r) { return r.side === side; }).forEach(function (r) {        var card = h("div", "kill-row", null, list);        var top = h("div", "kill-top", null, card);        h("span", "kill-id", r.id, top);        h("span", "kill-claim", r.claim, top);        h("span", "pill " + r.status, r.status, top);        var facts = h("div", "kill-facts", null, card);        h("span", null, "Now " + val(r), facts);        h("span", null, "Trips if " + r.op + " " + th(r), facts);        h("span", null, "By " + r.deadline, facts);        h("div", "kill-effect", r.effect, card);      });    });    finish("f-kill");    var lede = document.getElementById("killlede");    if (lede) lede.textContent = GRADES.note;    var box = document.getElementById("killstate"), count = document.getElementById("killcount");    var tripped = GRADES.tripped.length;    if (tripped) {      box.classList.add("is-tripped");      count.textContent = tripped + " kill " + (tripped > 1 ? "criteria have" : "criterion has") + " tripped: " + GRADES.tripped.join(", ") + ".";    }  }  function drawAll() {    // One broken section must not blank the rest of the page.    [base, real, who, mixSeries, token, pricing, stake, rivals, lead, ai, kill, drawTornado, drawControls, drawResult].forEach(function (fn) {      try { fn(); } catch (e) { if (window.console) console.error(fn.name, e); }    });  }  function setupTheme() {    var b = document.getElementById("theme");    b.addEventListener("click", function () {      var cur = document.documentElement.getAttribute("data-theme");      var dark = cur ? cur === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;      var next = dark ? "light" : "dark";      document.documentElement.setAttribute("data-theme", next);      try { localStorage.setItem("theme", next); } catch (e) { /* storage may be blocked */ }      drawAll();    });  }  function freshness() {    var bad = Object.keys(STATUS.sources).filter(function (k) { return !STATUS.sources[k].ok; });    var el = document.getElementById("freshness");    el.textContent = "Page built " + STATUS.built_at + " from the committed snapshots. Prices and fees pulled " + metric("market_as_of") + ". Chain data through " + metric("as_of") + " (BigQuery snapshot " + STATUS.bigquery_pulled + "), swap sample through " + (metric("ex_last_day") || "n/a") + ". The memo is frozen on this same snapshot. " + (bad.length ? "Stale sources kept from their last good pull: " + bad.join(", ") + "." : "Every keyless source was fresh at that pull.");    var st = document.getElementById("stamp");    if (st) st.textContent = "Chain data through " + metric("as_of") + " · prices and fees " + metric("market_as_of");  }  Promise.all(["data/metrics.json", "data/charts.json", "data/model.json", "data/grades.json", "data/status.json"].map(function (u) {    return fetch(u, { cache: "no-cache" }).then(function (r) { return r.json(); });  })).then(function (all) {    M = all[0]; C = all[1]; MODEL = all[2]; GRADES = all[3]; STATUS = all[4];    state = JSON.parse(JSON.stringify(MODEL.scenarios));    setupTheme(); drawAll(); freshness();    var timer = null, lastW = window.innerWidth;    window.addEventListener("resize", function () {      if (window.innerWidth === lastW) return;      lastW = window.innerWidth;      clearTimeout(timer);      timer = setTimeout(drawAll, 200);    });  }).catch(function (e) {    var el = document.getElementById("freshness");    if (el) el.textContent = "Charts could not load: " + e.message + ". Numbers above come from the last build.";  });})();