diff --git a/demos/.gitignore b/demos/.gitignore
new file mode 100644
index 0000000000..b947077876
--- /dev/null
+++ b/demos/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+dist/
diff --git a/demos/README.md b/demos/README.md
new file mode 100644
index 0000000000..09d7e17c5a
--- /dev/null
+++ b/demos/README.md
@@ -0,0 +1,20 @@
+# IPC JSX Demos (Local)
+
+This folder now has a tiny Vite + React host so both JSX demos can run locally.
+
+## Run
+
+```bash
+cd demos
+pnpm install --ignore-workspace
+pnpm --ignore-workspace dev
+```
+
+Open [http://127.0.0.1:5173](http://127.0.0.1:5173).
+
+## Demo URLs
+
+- `/?demo=reputation` for `dev-reputation-mock`
+- `/?demo=sp` for `sp-analysis`
+
+You can also switch between both demos using the top-right toggle in the UI.
diff --git a/demos/dev-reputation-mock/ipc_reputation_demo.jsx b/demos/dev-reputation-mock/ipc_reputation_demo.jsx
new file mode 100644
index 0000000000..608f960516
--- /dev/null
+++ b/demos/dev-reputation-mock/ipc_reputation_demo.jsx
@@ -0,0 +1,594 @@
+import { useState, useEffect, useRef } from "react";
+
+const FONTS = `
+ @import url('https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=DM+Sans:wght@300;400;500&family=DM+Mono:wght@400;500&display=swap');
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ @keyframes fadeUp { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
+ @keyframes pulse { 0%,100%{opacity:1;} 50%{opacity:0.4;} }
+ @keyframes flowDash { to { stroke-dashoffset: -24; } }
+ @keyframes flowDashFast { to { stroke-dashoffset: -16; } }
+ @keyframes nodePulse { 0%,100%{box-shadow:0 0 0 0 rgba(0,201,167,0.0);} 60%{box-shadow:0 0 0 6px rgba(0,201,167,0.10);} }
+ .fa { animation: fadeUp 0.35s ease both; }
+ .np { animation: nodePulse 2s ease-in-out infinite; }
+`;
+
+const C = {
+ bg:"#070810", surf:"#0d0f1a", surf2:"#111421", surf3:"#161b2e",
+ bdr:"#1c2138", bdrHi:"#2a3258",
+ teal:"#00c9a7", tealDim:"rgba(0,201,167,0.08)", tealBdr:"rgba(0,201,167,0.22)", tealGlow:"rgba(0,201,167,0.14)",
+ amber:"#f5a623", amberDim:"rgba(245,166,35,0.08)", amberBdr:"rgba(245,166,35,0.22)",
+ red:"#e05252", redDim:"rgba(224,82,82,0.08)", redBdr:"rgba(224,82,82,0.22)",
+ blue:"#4d9de0", blueDim:"rgba(77,157,224,0.08)", blueBdr:"rgba(77,157,224,0.22)",
+ violet:"#9b8cf8", violetDim:"rgba(155,140,248,0.08)", violetBdr:"rgba(155,140,248,0.22)",
+ txt:"#dde3f0", txt2:"#6b7a99", txt3:"#323c58",
+ mono:'"DM Mono","JetBrains Mono",monospace',
+ display:'"Syne",sans-serif',
+ sans:'"DM Sans",system-ui,sans-serif',
+};
+
+const sc = v => v >= 75 ? C.teal : v >= 50 ? C.amber : C.red;
+const sbg = v => v >= 75 ? C.tealDim : v >= 50 ? C.amberDim : C.redDim;
+const sbd = v => v >= 75 ? C.tealBdr : v >= 50 ? C.amberBdr : C.redBdr;
+
+const DEV = {
+ name:"Karel van Troost", handle:"karelvantroost", role:"Engineering Coordinator · IPC", av:"KV",
+ score:84, tier:"senior", period:"2026-Q1",
+ effPRs:11, totalPRs:14, rawCommits:83, wCommits:51,
+ rawLines:4821, effLines:2190, infPct:55,
+ cid:"bafybeig7xq2m...r4p9wk", block:412847,
+ dims:[["Problem-solving depth",91],["Code quality signals",87],["Review responsiveness",82],["Consistency over time",79],["Scope of contribution",74],["Documentation habit",61]],
+ prs:[
+ { t:"Refactor auth middleware to JWT RS256", s:91, w:1.0, a:312, d:287,
+ verdict:"Identified a security vulnerability in the existing symmetric JWT implementation and migrated to asymmetric RS256. Demonstrates threat modelling and backward compatibility constraints. Net line reduction despite new test coverage is a strong positive signal." },
+ { t:"Retry logic with exponential backoff", s:78, w:1.0, a:94, d:12,
+ verdict:"Implements exponential backoff with jitter for transient failures. Solid handling of idempotency edge cases. Somewhat narrow scope but well-executed. Review feedback addressed thoughtfully." },
+ { t:"Move helpers to lib/ directory", s:4, w:0.05, a:340, d:340, flagged:true,
+ flag:"94% token overlap between source and destination",
+ verdict:"Pure relocation without modification. 94% token similarity detected. No tests added, no interfaces changed. Weight: 0.05×" },
+ { t:"Fix race condition in queue worker", s:85, w:1.0, a:61, d:44,
+ verdict:"Resolved a subtle race condition where concurrent workers could process the same job twice. Correct use of optimistic locking with appropriate TTL handling." },
+ ],
+};
+
+const BOARD = [
+ { h:"0xGameMaster", av:"GM", c:847, s:23, bad:true },
+ { h:"CodeSpammer99", av:"CS", c:612, s:31, bad:true },
+ { h:"linefiller_dev", av:"LF", c:441, s:18, bad:true },
+ { h:"karelvantroost", av:"KV", c:83, s:84, bad:false },
+ { h:"sergeyp_dev", av:"SP", c:71, s:89, bad:false },
+];
+
+const STEPS = [
+ { id:"fetch", label:"Fetching GitHub data", ms:1800, layer:0,
+ out:"GET /users/karelvantroost/repos\nGET .../commits?author=karelvantroost&since=90d\nGET .../pulls?state=closed&creator=karelvantroost\nGET .../pulls/{n}/files ×14\nGET .../pulls/{n}/reviews ×14\n\n→ 5 repos 83 commits 14 merged PRs\n→ 31 review threads received" },
+ { id:"p0", label:"Analysing: JWT RS256 refactor", ms:2600, layer:0, pr:0 },
+ { id:"p1", label:"Analysing: Retry + backoff", ms:1800, layer:0, pr:1 },
+ { id:"p2", label:"Analysing: Move helpers to lib/",ms:2000, layer:0, pr:2 },
+ { id:"p3", label:"Analysing: Fix race condition", ms:1600, layer:0, pr:3 },
+ { id:"gaming", label:"Anti-gaming detection pass", ms:1600, layer:0,
+ out:"Scanning 14 PRs...\n\n[FLAG] PR #41: 94% token overlap → 0.05×\n[FLAG] 6 commits: formatter-only → excluded\n[FLAG] PR #38: generated schema → 0.1×\n\nRaw lines: 4,821\nEffective: 2,190\nInflation: -55%" },
+ { id:"score", label:"Computing weighted score", ms:1200, layer:0,
+ out:"Weighted PR score: 81.4\nConsistency score: 79.0\n\n(0.82 × 81.4) + (0.18 × 79.0)\n= 66.7 + 14.2\n= 84 tier: senior" },
+ { id:"storage",label:"Writing evidence to IPC Storage",ms:1000, layer:1,
+ out:"POST reputation_evidence_karelvantroost_2026Q1.json\nSize: 68.4 KB Replicas: 3/3\n\nCID: bafybeig7xq2m...r4p9wk\nStatus: confirmed ✓" },
+ { id:"chain", label:"WASM actor: on-chain commit", ms:2600, layer:2, f3:true },
+];
+
+const LAYERS = [
+ { id:0, label:"Off-chain agent", sub:"GitHub API · Claude AI", color:C.blue, dimColor:C.blueDim, bdrColor:C.blueBdr,
+ items:["Fetches GitHub data","Analyses PR diffs","Detects gaming patterns","Computes score"] },
+ { id:1, label:"IPC Storage", sub:"Content-addressed · Replicated", color:C.violet, dimColor:C.violetDim, bdrColor:C.violetBdr,
+ items:["Stores evidence doc (68 KB)","Returns CID","3× replicated","Permanently retrievable"] },
+ { id:2, label:"IPC Chain", sub:"WASM actor · F3 finality", color:C.teal, dimColor:C.tealDim, bdrColor:C.tealBdr,
+ items:["Validates agent signature","Writes on-chain record","F3 consensus","Registry queryable"] },
+];
+
+const APPS = [
+ { title:"DAO governance", badge:"voting weight = score/100",
+ desc:"Contribution replaces capital as the basis for influence. Voting power proportional to verified reputation.",
+ code:"Proposal #47 quorum: 60%\nkarelvantroost 0.84× weight\nsergeyp_dev 0.89× weight\n0xGameMaster 0.23× weight",
+ q:"How would IPC reputation scores work for weighted DAO governance voting?" },
+ { title:"Bounty platform", badge:"score gates eligibility",
+ desc:"Bounties gated by minimum reputation. No KYC — the on-chain record is the credential.",
+ code:"Bounty #447 — 500 FIL\nRequired score: 70+\n\nkarelvantroost 84 → eligible\n0xGameMaster 23 → blocked",
+ q:"How would IPC reputation scores work as a gating mechanism for a web3 bounty platform?" },
+ { title:"Grant allocation", badge:"score-weighted distribution",
+ desc:"Weight FIL+ grants by contribution quality, not first-come-first-served.",
+ code:"Round 12 — 10,000 FIL pool\nAllocation = (score/total) × pool\n\nkarelvantroost 84 → 840 FIL\nsergeyp_dev 89 → 890 FIL",
+ q:"How could IPC reputation scores weight grant allocation in the Filecoin ecosystem?" },
+ { title:"Hiring credential", badge:"verifiable on-chain",
+ desc:"A portable, tamper-proof developer profile. The IPC Storage CID lets anyone audit every verdict.",
+ code:"karelvantroost senior 84/100\nPeriod: 2026-Q1\nVerified: IPC Chain · Filecoin Calibration\nEvidence: bafybeig7xq2m...r4p9wk",
+ q:"What makes an IPC on-chain reputation score better than a GitHub profile for hiring?" },
+];
+
+const CHECKS = [
+ { l:"Content integrity", e:"keccak256(fetch(output_cid)) == result_hash", r:"0x9c3a88f2...f291a3 == 0x9c3a88f2...f291a3 ✓" },
+ { l:"Agent identity", e:"ecrecover(result_hash, signature) == agent_address", r:"recovered 0xd8e2...9f1c == registered agent ✓" },
+ { l:"Block timestamp", e:"block.timestamp verified at block 412,847", r:"2026-03-17T11:24:07Z confirmed ✓" },
+];
+
+const NAV = ["The problem","Live pipeline","Reputation profile","Audit trail","Applications"];
+
+function sendPrompt(prompt) {
+ if (typeof window === "undefined" || !prompt) return;
+ const q = encodeURIComponent(prompt);
+ window.open(`https://chatgpt.com/?q=${q}`, "_blank", "noopener,noreferrer");
+}
+
+/* ── IPC LAYER DIAGRAM ─────────────────────────────────── */
+function IPCLayerDiagram({ activeLayer, completedLayers }) {
+ // connector 0->1 flows when layer 0 done or layer 1 active
+ const flow01 = completedLayers.has(0) || activeLayer === 1;
+ const flow12 = completedLayers.has(1) || activeLayer === 2;
+
+ return (
+
+
IPC ARCHITECTURE
+
+ {LAYERS.map((l, i) => {
+ const isActive = activeLayer === l.id;
+ const isCompleted = completedLayers.has(l.id);
+ const isLit = isActive || isCompleted;
+ const col = isLit ? l.color : C.txt3;
+ const bg = isLit ? l.dimColor : "transparent";
+ const bdr = isLit ? l.bdrColor : C.bdr;
+ const isLast = i === LAYERS.length - 1;
+ const flowActive = i === 0 ? flow01 : flow12;
+
+ return (
+
+ {/* Node */}
+
+ {/* Header row */}
+
+
+
+ {isActive && (
+
+ )}
+ {isCompleted && (
+
✓
+ )}
+
+ {/* Items */}
+
+ {l.items.map((item, j) => (
+
+ ))}
+
+
+
+ {/* Connector arrow (between nodes) */}
+ {!isLast && (
+
+
+
+
+
+
+ {i === 0 ? "signed doc" : "CID + sig"}
+
+
+ )}
+
+ );
+ })}
+
+
+ );
+}
+
+function LayerIcon({ id, color, active }) {
+ const dim = 24;
+ const c = active ? color : C.txt3;
+ const bg = active ? `${color}18` : C.surf2;
+ const bd = active ? `${color}40` : C.bdr;
+
+ return (
+
+ {id === 0 && (
+
+
+
+
+ )}
+ {id === 1 && (
+
+
+
+
+
+
+ )}
+ {id === 2 && (
+
+
+
+
+ )}
+
+ );
+}
+
+/* ── SHARED ───────────────────────────────────────────── */
+function Card({ children, style, glow }) {
+ return (
+ {children}
+ );
+}
+function Badge({ children, color=C.teal, style }) {
+ return {children} ;
+}
+function ScoreChip({ score }) {
+ return {score} ;
+}
+function Btn({ children, onClick, style }) {
+ const [h, setH] = useState(false);
+ return (
+ setH(true)} onMouseLeave={() => setH(false)} style={{ fontFamily:C.sans, fontSize:12, padding:"7px 16px", borderRadius:8, border:`1px solid ${C.tealBdr}`, background: h ? C.tealDim : "transparent", color:C.teal, cursor:"pointer", transition:"background 0.15s", ...style }}>{children}
+ );
+}
+
+/* ── APP ──────────────────────────────────────────────── */
+export default function App() {
+ const [scene, setScene] = useState(0);
+ const [k, setK] = useState(0);
+ function go(i) { setScene(i); setK(n => n+1); }
+ return (
+
+
+
+ {NAV.map((l,i) => {
+ const active = scene===i, done = scene>i;
+ return go(i)} style={{ padding:"5px 14px", fontSize:11, fontFamily:C.sans, borderRadius:20, cursor:"pointer", transition:"all 0.2s", border: active ? `1px solid ${C.teal}` : done ? `1px solid ${C.tealBdr}` : `1px solid ${C.bdr}`, background: active ? C.tealDim : "transparent", color: active ? C.teal : done ? C.teal+"88" : C.txt2 }}>{done?"✓ ":`${i+1}. `}{l} ;
+ })}
+
+
+ {scene===0 &&
go(1)} />}
+ {scene===1 && go(2)} />}
+ {scene===2 && go(3)} />}
+ {scene===3 && go(4)} />}
+ {scene===4 && }
+
+
+ );
+}
+
+/* ── SCENE 1: PROBLEM ─────────────────────────────────── */
+function Problem({ next }) {
+ const [show, setShow] = useState(false);
+ useEffect(() => { const t = setTimeout(() => setShow(true), 900); return () => clearTimeout(t); }, []);
+ const ipc = [...BOARD].sort((a,b) => b.s - a.s);
+ return (
+
+
+
The commit leaderboard is broken
+
Volume beats quality. Developers who game the system outrank developers who do real work.
+
+
+
+
GITHUB — RANKED BY RAW COMMITS
+ {BOARD.map((d,i) => (
+
+
#{i+1}
+
{d.av}
+
@{d.h}
+
{d.c}
+ {d.bad &&
GAMED }
+
+ ))}
+
+
+
IPC REPUTATION — VERIFIED AI SCORE
+ {ipc.map((d,i) => (
+
+
#{i+1}
+
{d.av}
+
@{d.h}
+
{d.s}
+
/100
+
+ ))}
+
+
+ {show && (
+
+ 0xGameMaster has 847 commits and an IPC score of 23 .
+ karelvantroost has 83 commits and a score of 84 . The IPC agent reads the actual diff — it can tell the difference between moving files around and writing real code.
+
+ )}
+
Watch IPC score a developer →
+
+ );
+}
+
+/* ── SCENE 2: PIPELINE ────────────────────────────────── */
+function Pipeline({ next }) {
+ const [step, setStep] = useState(-1);
+ const [done, setDone] = useState(new Set());
+ const [running, setRun] = useState(false);
+ const [f3t, setF3t] = useState(0);
+ const [f3fin, setF3fin] = useState(false);
+ const tm = useRef(null);
+ const f3r = useRef(null);
+ const allDone = done.has(STEPS.length - 1);
+
+ // Derive which layer is currently active and which are completed
+ const curLayer = step >= 0 && step < STEPS.length ? STEPS[step].layer : -1;
+ const completedLayers = new Set();
+ STEPS.forEach((s, i) => { if (done.has(i)) completedLayers.add(s.layer); });
+ // A layer is only "completed" if ALL its steps are done
+ const layerComplete = new Set();
+ [0,1,2].forEach(lid => {
+ const layerSteps = STEPS.map((s,i) => ({s,i})).filter(({s}) => s.layer === lid);
+ if (layerSteps.every(({i}) => done.has(i))) layerComplete.add(lid);
+ });
+
+ function start() { setRun(true); setStep(0); }
+
+ useEffect(() => {
+ if (!running || step < 0 || step >= STEPS.length) return;
+ tm.current = setTimeout(() => {
+ setDone(p => new Set([...p, step]));
+ if (step + 1 < STEPS.length) setStep(step + 1); else setRun(false);
+ }, STEPS[step].ms);
+ return () => clearTimeout(tm.current);
+ }, [step, running]);
+
+ useEffect(() => {
+ if (!running || step !== STEPS.length - 1) return;
+ let t = 0;
+ f3r.current = setInterval(() => {
+ t = parseFloat(Math.min(t + 0.1, 2.4).toFixed(1));
+ setF3t(t);
+ if (t >= 2.4) { clearInterval(f3r.current); setF3fin(true); }
+ }, 70);
+ return () => clearInterval(f3r.current);
+ }, [step, running]);
+
+ const curS = step >= 0 && step < STEPS.length ? STEPS[step] : null;
+ const curPR = curS && curS.pr !== undefined ? DEV.prs[curS.pr] : null;
+ const activeLayer = running || allDone ? curLayer : -1;
+
+ return (
+
+ {/* IPC Architecture diagram — always visible, lights up as pipeline progresses */}
+
+
+ {/* Stepper + output */}
+
+
+
@KARELVANTROOST
+ {STEPS.map((s,i) => {
+ const isDone = done.has(i), isActive = i===step && !isDone && running;
+ const layerCol = LAYERS[s.layer].color;
+ return (
+
+
+ {isDone ? "✓" : ""}
+
+
{s.label}
+
+ );
+ })}
+
+
+
+
+ {!running && step < 0 ? (
+
+
Ready to analyse @karelvantroost
+
Run pipeline →
+
+ ) : curS ?
: null}
+
+ {allDone && (
+
+ View reputation profile →
+
+ )}
+
+
+
+ );
+}
+
+function StepOut({ step, pr, f3t, f3fin }) {
+ if (step.f3) return (
+
+
{`ReputationRegistry.setScore({\n developer: "0xd8e2...9f1c",\n score: 84,\n tier: "senior",\n evidence_cid: "bafybeig7xq2m...r4p9wk",\n period: "2026-Q1"\n})`}
+
+
F3 FAST FINALITY
+
{f3t.toFixed(1)}s
+ {f3fin
+ ?
FINALIZED ✓
+ :
waiting for F3 consensus... }
+
+
+ );
+ if (pr) return (
+
+
+ {pr.t}
+
+
+
+{pr.a} −{pr.d} · weight {pr.w}×
+ {pr.flagged &&
⚠ FLAGGED: {pr.flag}
}
+
{pr.verdict}
+
+ );
+ return {step.out} ;
+}
+
+/* ── SCENE 3: PROFILE ─────────────────────────────────── */
+function Profile({ next }) {
+ const [disp, setDisp] = useState(0);
+ const [bars, setBars] = useState(false);
+ const size=140, stroke=8, r=(size-stroke)/2, circ=2*Math.PI*r;
+ useEffect(() => {
+ let n = 0;
+ const id = setInterval(() => {
+ n = Math.min(n+1.6, DEV.score); setDisp(Math.round(n));
+ if (n >= DEV.score) { clearInterval(id); setTimeout(() => setBars(true), 200); }
+ }, 18);
+ return () => clearInterval(id);
+ }, []);
+ return (
+
+
+
+
+
+
{DEV.name}
+
{DEV.role}
+
+ {DEV.tier}
+ {DEV.period}
+ block {DEV.block.toLocaleString()}
+
+
+
+
+ {[["Effective PRs",`${DEV.effPRs}/${DEV.totalPRs}`],["Weighted commits",`${DEV.wCommits}/${DEV.rawCommits}`],["Inflation removed",`${DEV.infPct}%`],["IPC Storage CID",DEV.cid]].map(([l,v]) => (
+
+ ))}
+
+
+ {DEV.dims.map(([l,s]) => (
+
+ ))}
+
+
+ {DEV.prs.map((pr,i) => (
+
+
{pr.flagged?"⚠ ":""}{pr.t}
+
+ +{pr.a}/−{pr.d}
+
+ {pr.w}×
+
+
+ ))}
+
+
+ F3 finalized · IPC Chain · Filecoin Calibration
+ {DEV.cid}
+
+
+
Verify the evidence →
+
+ );
+}
+
+/* ── SCENE 4: AUDIT ───────────────────────────────────── */
+function Audit({ next }) {
+ const [states, setStates] = useState(["idle","idle","idle"]);
+ const [done, setDone] = useState(false);
+ function run() {
+ if (done || states[0] !== "idle") return;
+ CHECKS.forEach((_,i) => {
+ setTimeout(() => {
+ setStates(p => { const n=[...p]; n[i]="run"; return n; });
+ setTimeout(() => {
+ setStates(p => { const n=[...p]; n[i]="ok"; return n; });
+ if (i===2) setDone(true);
+ }, 700 + Math.random()*300);
+ }, i*1000);
+ });
+ }
+ return (
+
+
Three independent cryptographic checks. Anyone can run them — no trusted party required. The math either checks out or it doesn't.
+ {states[0]==="idle" &&
Run verification → }
+ {CHECKS.map((c,i) => {
+ const st = states[i];
+ return (
+
+
+ {st==="ok"?"✓":""}
+
+
+
{c.l}
+
{st==="ok"?c.r:c.e}
+
+
+ );
+ })}
+ {done && (
+
+
ALL CHECKS PASSED — RECORD CRYPTOGRAPHICALLY VERIFIED
+
See what this enables →
+
+ )}
+
+ );
+}
+
+/* ── SCENE 5: APPLICATIONS ────────────────────────────── */
+function Applications() {
+ return (
+
+
+ Any contract on IPC can now call ReputationRegistry.getScore(address) and get a trustless, auditable developer score. Four things that become immediately possible:
+
+
+ {APPS.map((a,i) => (
+
e.currentTarget.style.borderColor=C.tealBdr}
+ onMouseLeave={e => e.currentTarget.style.borderColor=C.bdr}>
+
+
{a.code}
+
+ {a.badge}
+ sendPrompt(a.q)} style={{ fontFamily:C.sans, fontSize:11, padding:"4px 10px", borderRadius:6, border:`1px solid ${C.tealBdr}`, background:"transparent", color:C.teal, cursor:"pointer", transition:"all 0.15s" }}
+ onMouseEnter={e => e.currentTarget.style.background=C.tealDim}
+ onMouseLeave={e => e.currentTarget.style.background="transparent"}>Explore this ↗
+
+
+ ))}
+
+
+ );
+}
diff --git a/demos/index.html b/demos/index.html
new file mode 100644
index 0000000000..ab2d75e5a8
--- /dev/null
+++ b/demos/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ IPC Demos
+
+
+
+
+
+
diff --git a/demos/package.json b/demos/package.json
new file mode 100644
index 0000000000..625224ce67
--- /dev/null
+++ b/demos/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "ipc-demos",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.1",
+ "vite": "^5.4.10"
+ }
+}
diff --git a/demos/pnpm-lock.yaml b/demos/pnpm-lock.yaml
new file mode 100644
index 0000000000..cbc53c21e8
--- /dev/null
+++ b/demos/pnpm-lock.yaml
@@ -0,0 +1,1026 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ devDependencies:
+ '@vitejs/plugin-react':
+ specifier: ^4.3.1
+ version: 4.7.0(vite@5.4.21)
+ vite:
+ specifier: ^5.4.10
+ version: 5.4.21
+
+packages:
+
+ '@babel/code-frame@7.29.0':
+ resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.29.0':
+ resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.29.0':
+ resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.1':
+ resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.28.6':
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.29.2':
+ resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.2':
+ resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.0':
+ resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.0':
+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+ engines: {node: '>=6.9.0'}
+
+ '@esbuild/aix-ppc64@0.21.5':
+ resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.21.5':
+ resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.21.5':
+ resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.21.5':
+ resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.21.5':
+ resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.21.5':
+ resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.21.5':
+ resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.21.5':
+ resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.21.5':
+ resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.21.5':
+ resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.21.5':
+ resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.21.5':
+ resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.21.5':
+ resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.21.5':
+ resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.21.5':
+ resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.21.5':
+ resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-x64@0.21.5':
+ resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-x64@0.21.5':
+ resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.21.5':
+ resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.21.5':
+ resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.21.5':
+ resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.21.5':
+ resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
+ '@rollup/rollup-android-arm-eabi@4.60.0':
+ resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.60.0':
+ resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.60.0':
+ resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.60.0':
+ resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.60.0':
+ resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.60.0':
+ resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.0':
+ resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.0':
+ resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.0':
+ resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.60.0':
+ resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.0':
+ resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.60.0':
+ resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.0':
+ resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.0':
+ resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.0':
+ resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.0':
+ resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.0':
+ resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.60.0':
+ resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.60.0':
+ resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.60.0':
+ resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.60.0':
+ resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.0':
+ resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.0':
+ resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.60.0':
+ resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.60.0':
+ resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==}
+ cpu: [x64]
+ os: [win32]
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ baseline-browser-mapping@2.10.11:
+ resolution: {integrity: sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ caniuse-lite@1.0.30001781:
+ resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ electron-to-chromium@1.5.328:
+ resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==}
+
+ esbuild@0.21.5:
+ resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-releases@2.0.36:
+ resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ postcss@8.5.8:
+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ rollup@4.60.0:
+ resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ vite@5.4.21:
+ resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+snapshots:
+
+ '@babel/code-frame@7.29.0':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.29.0': {}
+
+ '@babel/core@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helpers': 7.29.2
+ '@babel/parser': 7.29.2
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.1':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.29.0
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.28.6': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.29.2':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+
+ '@babel/parser@7.29.2':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+
+ '@babel/traverse@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.29.2
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.0':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@esbuild/aix-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm@0.21.5':
+ optional: true
+
+ '@esbuild/android-x64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-x64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/linux-loong64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-s390x@0.21.5':
+ optional: true
+
+ '@esbuild/linux-x64@0.21.5':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/sunos-x64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/win32-x64@0.21.5':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+ '@rollup/rollup-android-arm-eabi@4.60.0':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.60.0':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.60.0':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.60.0':
+ optional: true
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/estree@1.0.8': {}
+
+ '@vitejs/plugin-react@4.7.0(vite@5.4.21)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 5.4.21
+ transitivePeerDependencies:
+ - supports-color
+
+ baseline-browser-mapping@2.10.11: {}
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.10.11
+ caniuse-lite: 1.0.30001781
+ electron-to-chromium: 1.5.328
+ node-releases: 2.0.36
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ caniuse-lite@1.0.30001781: {}
+
+ convert-source-map@2.0.0: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ electron-to-chromium@1.5.328: {}
+
+ esbuild@0.21.5:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.21.5
+ '@esbuild/android-arm': 0.21.5
+ '@esbuild/android-arm64': 0.21.5
+ '@esbuild/android-x64': 0.21.5
+ '@esbuild/darwin-arm64': 0.21.5
+ '@esbuild/darwin-x64': 0.21.5
+ '@esbuild/freebsd-arm64': 0.21.5
+ '@esbuild/freebsd-x64': 0.21.5
+ '@esbuild/linux-arm': 0.21.5
+ '@esbuild/linux-arm64': 0.21.5
+ '@esbuild/linux-ia32': 0.21.5
+ '@esbuild/linux-loong64': 0.21.5
+ '@esbuild/linux-mips64el': 0.21.5
+ '@esbuild/linux-ppc64': 0.21.5
+ '@esbuild/linux-riscv64': 0.21.5
+ '@esbuild/linux-s390x': 0.21.5
+ '@esbuild/linux-x64': 0.21.5
+ '@esbuild/netbsd-x64': 0.21.5
+ '@esbuild/openbsd-x64': 0.21.5
+ '@esbuild/sunos-x64': 0.21.5
+ '@esbuild/win32-arm64': 0.21.5
+ '@esbuild/win32-ia32': 0.21.5
+ '@esbuild/win32-x64': 0.21.5
+
+ escalade@3.2.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ js-tokens@4.0.0: {}
+
+ jsesc@3.1.0: {}
+
+ json5@2.2.3: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ node-releases@2.0.36: {}
+
+ picocolors@1.1.1: {}
+
+ postcss@8.5.8:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
+ react-refresh@0.17.0: {}
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ rollup@4.60.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.60.0
+ '@rollup/rollup-android-arm64': 4.60.0
+ '@rollup/rollup-darwin-arm64': 4.60.0
+ '@rollup/rollup-darwin-x64': 4.60.0
+ '@rollup/rollup-freebsd-arm64': 4.60.0
+ '@rollup/rollup-freebsd-x64': 4.60.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.60.0
+ '@rollup/rollup-linux-arm64-gnu': 4.60.0
+ '@rollup/rollup-linux-arm64-musl': 4.60.0
+ '@rollup/rollup-linux-loong64-gnu': 4.60.0
+ '@rollup/rollup-linux-loong64-musl': 4.60.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.60.0
+ '@rollup/rollup-linux-ppc64-musl': 4.60.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.60.0
+ '@rollup/rollup-linux-riscv64-musl': 4.60.0
+ '@rollup/rollup-linux-s390x-gnu': 4.60.0
+ '@rollup/rollup-linux-x64-gnu': 4.60.0
+ '@rollup/rollup-linux-x64-musl': 4.60.0
+ '@rollup/rollup-openbsd-x64': 4.60.0
+ '@rollup/rollup-openharmony-arm64': 4.60.0
+ '@rollup/rollup-win32-arm64-msvc': 4.60.0
+ '@rollup/rollup-win32-ia32-msvc': 4.60.0
+ '@rollup/rollup-win32-x64-gnu': 4.60.0
+ '@rollup/rollup-win32-x64-msvc': 4.60.0
+ fsevents: 2.3.3
+
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
+ semver@6.3.1: {}
+
+ source-map-js@1.2.1: {}
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ vite@5.4.21:
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.5.8
+ rollup: 4.60.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ yallist@3.1.1: {}
diff --git a/demos/sp-analysis-web/ipc_compute.html b/demos/sp-analysis-web/ipc_compute.html
new file mode 100644
index 0000000000..e726438643
--- /dev/null
+++ b/demos/sp-analysis-web/ipc_compute.html
@@ -0,0 +1,809 @@
+
+
+
+
+
+IPC Compute
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/sp-analysis/ipc_compute_demo.jsx b/demos/sp-analysis/ipc_compute_demo.jsx
new file mode 100644
index 0000000000..683f070bcd
--- /dev/null
+++ b/demos/sp-analysis/ipc_compute_demo.jsx
@@ -0,0 +1,603 @@
+import { useState, useEffect, useRef } from "react";
+
+const FONTS = `
+ @import url('https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=DM+Sans:wght@300;400;500&family=DM+Mono:wght@400;500&display=swap');
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ @keyframes fadeUp { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
+ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.35} }
+ @keyframes flowDash { to{stroke-dashoffset:-20} }
+ @keyframes nodePop { 0%{transform:scale(1)} 50%{transform:scale(1.03)} 100%{transform:scale(1)} }
+ @keyframes scanLine { 0%{transform:translateY(-100%);opacity:0.6} 100%{transform:translateY(400%);opacity:0} }
+ .fa { animation: fadeUp 0.3s ease both; }
+`;
+
+const C = {
+ bg:"#070810", surf:"#0d0f1a", surf2:"#111421", surf3:"#161b2e",
+ bdr:"#1c2138", bdrHi:"#2a3258",
+ teal:"#00c9a7", tealDim:"rgba(0,201,167,0.08)", tealBdr:"rgba(0,201,167,0.22)", tealGlow:"rgba(0,201,167,0.14)",
+ amber:"#f5a623", amberDim:"rgba(245,166,35,0.08)", amberBdr:"rgba(245,166,35,0.22)", amberGlow:"rgba(245,166,35,0.12)",
+ violet:"#9b8cf8", violetDim:"rgba(155,140,248,0.08)",violetBdr:"rgba(155,140,248,0.22)",violetGlow:"rgba(155,140,248,0.10)",
+ blue:"#4d9de0", blueDim:"rgba(77,157,224,0.08)", blueBdr:"rgba(77,157,224,0.22)",
+ red:"#e05252", redDim:"rgba(224,82,82,0.08)", redBdr:"rgba(224,82,82,0.22)",
+ txt:"#dde3f0", txt2:"#6b7a99", txt3:"#323c58",
+ mono:'"DM Mono","Fira Code",monospace', display:'"Syne",sans-serif', sans:'"DM Sans",system-ui,sans-serif',
+};
+
+const SPS = [
+ { id:"f0116628", score:106.99, uptime:96.2, deals:106, power:6.69 },
+ { id:"f0149768", score:105.92, uptime:95.8, deals:83, power:2.00 },
+ { id:"f01393827", score:104.93, uptime:100, deals:7, power:0.98 },
+ { id:"f0134991", score:104.55, uptime:89.2, deals:47, power:6.63 },
+ { id:"f03339", score:103.57, uptime:89.6, deals:941, power:1.08 },
+ { id:"f065103", score:103.50, uptime:88.8, deals:86, power:2.16 },
+ { id:"f0230200", score:103.46, uptime:92.3, deals:39, power:0.72 },
+ { id:"f01690643", score:103.03, uptime:100, deals:675, power:0.34 },
+ { id:"f01694564", score:102.92, uptime:100, deals:212, power:0.31 },
+ { id:"f044160", score:102.84, uptime:93.8, deals:1268, power:0.34 },
+ { id:"f01690774", score:102.82, uptime:100, deals:706, power:0.27 },
+ { id:"f018501", score:102.80, uptime:94.9, deals:461, power:0.19 },
+ { id:"f01652952", score:102.70, uptime:100, deals:757, power:0.23 },
+ { id:"f01690781", score:102.69, uptime:100, deals:953, power:0.23 },
+ { id:"f0121768", score:102.66, uptime:90.4, deals:14, power:5.05 },
+];
+
+const LOGS = [
+ "[14:32:01.203] Container ipc-llm-worker:2.1 starting",
+ "[14:32:01.891] CUDA device: NVIDIA A100 (40 GB)",
+ "[14:32:02.114] Loading meta-llama/Llama-3.1-8B-Instruct",
+ "[14:32:04.732] Model ready 2.6s 14.2B params",
+ "[14:32:04.733] Job received: 0x4a3f...c91b prompt: 1,024 tokens",
+ "[14:32:05.102] Fetching Filrep API...",
+ "[14:32:05.889] Retrieved 597 storage providers",
+ "[14:32:06.221] Running reliability analysis...",
+ "[14:32:07.341] Scored 597/597 providers",
+ "[14:32:07.342] Top 15 identified avg score: 103.8",
+ "[14:32:08.001] Generating markdown report...",
+ "[14:32:09.221] Output complete 2,847 tokens",
+ "[14:32:09.223] Saved /output/report.md (68.4 KB) exit 0 ✓",
+];
+
+const PROMPT_SHORT = "You are a data analyst. Your goal is to identify the most reliable Filecoin Storage Providers — those with a strong track record of consistent uptime, sector maintenance, and deal fulfilment.\n\nSave your final report to report.md containing:\n- Brief methodology note\n- Ranked table of top 15 most reliable SPs\n- 2–3 paragraph summary of what distinguishes top performers\n\n[+ full Filrep dataset: 597 providers]";
+
+const VALIDATORS = [
+ { id:"v1", label:"val-1.ipc-fil.io", region:"us-east" },
+ { id:"v2", label:"val-2.ipc-fil.io", region:"eu-west", elected:true },
+ { id:"v3", label:"val-3.ipc-fil.io", region:"ap-south" },
+ { id:"v4", label:"val-4.ipc-fil.io", region:"us-west" },
+];
+
+const LAYERS = [
+ { id:0, label:"IPC Chain", sub:"Contract · Job dispatch", color:C.teal, dim:C.tealDim, bdr:C.tealBdr, glow:C.tealGlow,
+ items:["Receives contract call","Validates tx","Dispatches job to compute","Stores job_id on-chain"] },
+ { id:1, label:"IPC Compute", sub:"Validator · Docker · LLM", color:C.amber, dim:C.amberDim, bdr:C.amberBdr, glow:C.amberGlow,
+ items:["Validator elected executor","Spins up Docker container","Runs LLM with prompt","Returns structured output"] },
+ { id:2, label:"IPC Output", sub:"IPC Storage · Registry", color:C.violet, dim:C.violetDim, bdr:C.violetBdr, glow:C.violetGlow,
+ items:["Report → IPC Storage (CID)","Scores → SPScoreRegistry","F3 finality confirmed","Queryable by any contract"] },
+];
+
+const NAV = ["Contract call","Validator execution","Outputs committed","On-chain registry","Consumer app"];
+
+/* ── ARCH DIAGRAM ─────────────────────────────────────── */
+function ArchDiagram({ scene }) {
+ const active = scene < 3 ? scene : -1;
+ const completed = new Set(Array.from({length: Math.min(scene, 3)}, (_,i) => i));
+ return (
+
+
IPC ARCHITECTURE
+
+ {LAYERS.map((l,i) => {
+ const isActive = active === l.id;
+ const isDone = completed.has(l.id) && active !== l.id;
+ const lit = isActive || isDone;
+ const col = lit ? l.color : C.txt3;
+ const flowNext = i < LAYERS.length - 1 && (completed.has(l.id) || active > l.id);
+ return (
+
+
+
+
+
+ {isDone &&
✓
}
+ {isActive &&
}
+
+
+ {l.items.map((item,j) => (
+
+ ))}
+
+
+ {i < LAYERS.length - 1 && (
+
+
+
+
+
+
{i===0?"job":"output"}
+
+ )}
+
+ );
+ })}
+
+
+ );
+}
+
+function LayerDot({ id, color, active, pulse }) {
+ const icons = [
+ ,
+ ,
+ ,
+ ];
+ return (
+
+ {icons[id]}
+
+ );
+}
+
+/* ── SHARED ───────────────────────────────────────────── */
+function Badge({ children, color=C.teal, style }) {
+ return {children} ;
+}
+function Btn({ children, onClick, style }) {
+ const [h,setH] = useState(false);
+ return setH(true)} onMouseLeave={()=>setH(false)} style={{ fontFamily:C.sans, fontSize:12, padding:"7px 16px", borderRadius:8, border:`1px solid ${C.tealBdr}`, background: h?C.tealDim:"transparent", color:C.teal, cursor:"pointer", transition:"background 0.15s", ...style }}>{children} ;
+}
+function ScoreBar({ score }) {
+ const pct = Math.min((score - 100) / 8 * 100, 100);
+ const col = score >= 105 ? C.teal : score >= 103 ? C.amber : C.violet;
+ return (
+
+ );
+}
+
+/* ── APP ──────────────────────────────────────────────── */
+export default function App() {
+ const [scene, setScene] = useState(0);
+ const [k, setK] = useState(0);
+ function go(i) { setScene(i); setK(n=>n+1); }
+ return (
+
+
+
+ {NAV.map((l,i) => {
+ const active=scene===i, done=scene>i;
+ return go(i)} style={{ padding:"5px 14px", fontSize:11, fontFamily:C.sans, borderRadius:20, cursor:"pointer", transition:"all 0.2s", border:active?`1px solid ${C.teal}`:done?`1px solid ${C.tealBdr}`:`1px solid ${C.bdr}`, background:active?C.tealDim:"transparent", color:active?C.teal:done?C.teal+"88":C.txt2 }}>{done?"✓ ":`${i+1}. `}{l} ;
+ })}
+
+
+
+ {scene===0 && go(1)} />}
+ {scene===1 && go(2)} />}
+ {scene===2 && go(3)} />}
+ {scene===3 && go(4)} />}
+ {scene===4 && }
+
+
+ );
+}
+
+/* ── SCENE 1: CONTRACT CALL ───────────────────────────── */
+function ContractCall({ next }) {
+ const [showPrompt, setShowPrompt] = useState(false);
+ const [txState, setTxState] = useState("idle");
+ const [txHash] = useState("0x4a3f8b2e...c91b7d3a");
+
+ function submit() {
+ if (txState !== "idle") return;
+ setTxState("submitting");
+ setTimeout(() => setTxState("submitted"), 1400);
+ }
+
+ return (
+
+
+ A developer calls a single contract function on the IPC chain. The prompt and job parameters are encoded as calldata — the chain handles everything from here.
+
+
+
+
+
+
SPAnalysisJob.sol
+
· IPC Chain · Filecoin Calibration
+
+
0x7f3c...d4a2
+
+
+
AnalyseStorageProviders()
+
+ {[
+ ["job_type", '"llm_inference"', C.violet],
+ ["model", '"meta-llama/Llama-3.1-8B-Instruct"', C.amber],
+ ["timeout", "300", C.txt2],
+ ["callback", '"SPScoreRegistry.storeResults"', C.teal],
+ ].map(([k,v,col]) => (
+
+ {k}
+ {v}
+
+ ))}
+
+
+ prompt
+ setShowPrompt(s=>!s)} style={{ fontFamily:C.mono, fontSize:10, color:C.amber, background:"transparent", border:`1px solid ${C.amberBdr}`, borderRadius:4, padding:"1px 7px", cursor:"pointer" }}>{showPrompt?"hide ↑":"view ↓"}
+
+ {showPrompt && (
+
{PROMPT_SHORT}
+ )}
+
+
+ {txState === "idle" &&
Submit transaction → }
+ {txState === "submitting" && (
+
+
+ Broadcasting to IPC chain...
+
+ )}
+ {txState === "submitted" && (
+
+
+
TRANSACTION CONFIRMED
+
tx: {txHash}
+
job_id: 0x4a3f...c91b · block: 412,847
+
+
Watch validator execute →
+
+ )}
+
+
+
+ );
+}
+
+/* ── SCENE 2: VALIDATOR EXECUTION ─────────────────────── */
+function ValidatorExecution({ next }) {
+ const [phase, setPhase] = useState(0);
+ const [logLines, setLogLines] = useState([]);
+ const logRef = useRef(null);
+
+ useEffect(() => {
+ const t0 = setTimeout(() => setPhase(1), 600);
+ const t1 = setTimeout(() => setPhase(2), 1400);
+ const t2 = setTimeout(() => setPhase(3), 2000);
+ return () => { clearTimeout(t0); clearTimeout(t1); clearTimeout(t2); };
+ }, []);
+
+ useEffect(() => {
+ if (phase !== 3) return;
+ let i = 0;
+ const id = setInterval(() => {
+ if (i >= LOGS.length) { clearInterval(id); setPhase(4); return; }
+ setLogLines(p => [...p, LOGS[i]]);
+ i++;
+ }, 320);
+ return () => clearInterval(id);
+ }, [phase]);
+
+ useEffect(() => {
+ if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
+ }, [logLines]);
+
+ return (
+
+
+ The IPC chain elects a validator to execute the job. The elected validator spins up a Docker container with the LLM runtime and streams output back to the chain.
+
+ {/* Validator grid */}
+
+ {VALIDATORS.map(v => {
+ const elected = v.elected && phase >= 2;
+ const electing = v.elected && phase === 1;
+ const idle = !v.elected || phase < 1;
+ const col = elected ? C.amber : electing ? C.amber : C.txt3;
+ return (
+
+
+
+
+
{v.label}
+
{v.region}
+
+ {elected &&
EXECUTOR }
+ {!v.elected && phase >= 2 &&
standby }
+
+
+ );
+ })}
+
+
+ {/* Execution log */}
+ {phase >= 3 && (
+
+
+
+
val-2.ipc-fil.io
+
·
+
ipc-llm-worker:2.1
+ {phase===4 &&
complete ✓ }
+
+
+ {logLines.map((l,i) => (
+
logLines.length-3 ? C.txt2 : C.txt3, transition:"color 0.4s" }}>{l}
+ ))}
+
+
+ )}
+
+ {phase === 4 && (
+
+ See outputs committed →
+
+ )}
+
+ );
+}
+
+/* ── SCENE 3: OUTPUTS COMMITTED ───────────────────────── */
+function OutputsCommitted({ next }) {
+ const [storeDone, setStoreDone] = useState(false);
+ const [chainDone, setChainDone] = useState(false);
+ const [f3t, setF3t] = useState(0);
+ const [f3fin, setF3fin] = useState(false);
+
+ useEffect(() => {
+ setTimeout(() => setStoreDone(true), 1000);
+ setTimeout(() => setChainDone(true), 2200);
+ }, []);
+
+ useEffect(() => {
+ if (!chainDone) return;
+ let t = 0;
+ const id = setInterval(() => {
+ t = parseFloat(Math.min(t+0.1, 2.4).toFixed(1));
+ setF3t(t);
+ if (t >= 2.4) { clearInterval(id); setF3fin(true); }
+ }, 70);
+ return () => clearInterval(id);
+ }, [chainDone]);
+
+ return (
+
+
+ Two outputs are committed in sequence. The full report goes to IPC Storage as a permanent content-addressed document. The scored SP data is written on-chain to the registry contract.
+
+
+
+ {/* IPC Storage */}
+
+
+
{`POST report.md\nSize: 68.4 KB\nReplicas: 3/3`}
+ {storeDone && (
+
+
CID
+
bafybeig7xq2m...r4p9wk
+
confirmed ✓
+
+ )}
+
+
+ {/* On-chain registry */}
+
+
+
{`storeResults({\n f0116628: 106.99\n f0149768: 105.92\n f01393827: 104.93\n ... +12 more\n})`}
+ {chainDone && (
+
+
+ F3 FINALITY
+ {f3t.toFixed(1)}s
+ {f3fin && FINAL ✓ }
+
+
block 412,848 · 15 providers written
+
+ )}
+
+
+ {f3fin && (
+
+ View on-chain registry →
+
+ )}
+
+ );
+}
+
+/* ── SCENE 4: ON-CHAIN REGISTRY ───────────────────────── */
+function OnChainRegistry({ next }) {
+ const [queryVal, setQueryVal] = useState("");
+ const [queryResult, setQueryResult] = useState(null);
+ const [querying, setQuerying] = useState(false);
+
+ function runQuery() {
+ const id = queryVal.trim() || "f0116628";
+ setQuerying(true);
+ setQueryResult(null);
+ setTimeout(() => {
+ const sp = SPS.find(s => s.id === id) || SPS[0];
+ setQueryResult(sp);
+ setQuerying(false);
+ }, 800);
+ }
+
+ return (
+
+
+ The registry is now live on-chain. Any contract or app can call getScore() or getTopN() . The evidence CID traces every score back to the full LLM analysis in IPC Storage.
+
+
+ {/* Table */}
+
+
+ {["#","Provider ID","Score","Uptime","Deals","Power"].map(h => (
+ {h}
+ ))}
+
+ {SPS.map((sp,i) => (
+
+ #{i+1}
+ {sp.id}
+
+ {sp.uptime.toFixed(1)}%
+ {sp.deals.toLocaleString()}
+ {sp.power} PiB
+
+ ))}
+
+ evidence_cid: bafybeig7xq2m...r4p9wk · block 412,848 · job 0x4a3f...c91b
+
+
+
+ {/* Contract read demo */}
+
+
CONTRACT READ — SPScoreRegistry.getScore()
+
+ setQueryVal(e.target.value)} placeholder="f0116628" style={{ flex:1, fontFamily:C.mono, fontSize:11, padding:"6px 10px", background:C.surf2, border:`1px solid ${C.bdr}`, borderRadius:7, color:C.txt, outline:"none" }} />
+ Query →
+
+ {querying && (
+
+
+ Reading from IPC chain...
+
+ )}
+ {queryResult && !querying && (
+
+
{`{\n id: "${queryResult.id}",\n score: ${queryResult.score},\n uptime: ${queryResult.uptime}%,\n deals: ${queryResult.deals},\n power: ${queryResult.power} PiB,\n evidence: "bafybeig7xq2m...r4p9wk"\n}`}
+
+ )}
+
+
+
+ See a consuming app →
+
+
+ );
+}
+
+/* ── SCENE 5: CONSUMER APP ────────────────────────────── */
+function ConsumerApp() {
+ const [uploadState, setUploadState] = useState("idle");
+ const [providers, setProviders] = useState([]);
+ const [uploadPct, setUploadPct] = useState(0);
+ const [uploadDone, setUploadDone] = useState(false);
+
+ function startUpload() {
+ if (uploadState !== "idle") return;
+ setUploadState("routing");
+ setTimeout(() => {
+ setProviders(SPS.slice(0,3));
+ setUploadState("routed");
+ setTimeout(() => {
+ setUploadState("uploading");
+ let p = 0;
+ const id = setInterval(() => {
+ p = Math.min(p + 2, 100);
+ setUploadPct(p);
+ if (p >= 100) { clearInterval(id); setUploadDone(true); }
+ }, 45);
+ }, 900);
+ }, 1000);
+ }
+
+ return (
+
+
+ Any app can now route storage decisions using the on-chain scores — without running its own analysis, without trusting a third party. The LLM ran once; the result is permanent infrastructure.
+
+
+ {/* App UI */}
+
+
+
+
storage-app · upload.js
+
+
+ {/* File card */}
+
+
+
+
+
+
+
+
+
filecoin_datasets_2026Q1.tar.gz
+
2.4 GB · 14 files
+
+ {uploadState === "idle" &&
Upload → }
+ {uploadState !== "idle" && !uploadDone &&
{uploadState} }
+ {uploadDone &&
stored ✓ }
+
+
+ {/* Routing step */}
+ {uploadState !== "idle" && (
+
+
+ SPScoreRegistry .getTopN(3) →
+
+ {providers.length === 0 && (
+
+
+ Reading on-chain scores...
+
+ )}
+
+ )}
+
+ {/* Provider cards */}
+ {providers.length > 0 && (
+
+ {providers.map((sp,i) => (
+
+
{sp.id}
+
{sp.score.toFixed(2)}
+
rank #{i+1}
+
+ ))}
+
+ )}
+
+ {/* Upload progress */}
+ {uploadState === "uploading" && (
+
+
+ Distributing to 3 providers
+ {uploadPct}%
+
+
+
+ )}
+
+ {uploadDone && (
+
+ Stored across f0116628 · f0149768 · f01393827 · deal IDs on-chain ✓
+
+ )}
+
+
+
+
+ The LLM analysis ran once on IPC Compute. The scores are permanent on-chain state. Every app that uploads to Filecoin can now route to the best providers — for free, forever, with no API key and no trusted intermediary.
+
+
+ );
+}
diff --git a/demos/src/App.jsx b/demos/src/App.jsx
new file mode 100644
index 0000000000..c2ca76d515
--- /dev/null
+++ b/demos/src/App.jsx
@@ -0,0 +1,56 @@
+import { useMemo, useState } from "react";
+import ReputationDemo from "../dev-reputation-mock/ipc_reputation_demo.jsx";
+import SPAnalysisDemo from "../sp-analysis/ipc_compute_demo.jsx";
+
+const DEMOS = {
+ reputation: {
+ label: "dev-reputation-mock",
+ component: ReputationDemo,
+ },
+ sp: {
+ label: "sp-analysis",
+ component: SPAnalysisDemo,
+ },
+};
+
+function getDemoFromUrl() {
+ const params = new URLSearchParams(window.location.search);
+ return params.get("demo") || "reputation";
+}
+
+function setDemoInUrl(demo) {
+ const url = new URL(window.location.href);
+ url.searchParams.set("demo", demo);
+ window.history.replaceState(null, "", url.toString());
+}
+
+export default function App() {
+ const initialDemo = useMemo(() => {
+ const requested = getDemoFromUrl();
+ return DEMOS[requested] ? requested : "reputation";
+ }, []);
+ const [demoKey, setDemoKey] = useState(initialDemo);
+ const DemoComponent = DEMOS[demoKey].component;
+
+ return (
+
+
+ {Object.entries(DEMOS).map(([key, { label }]) => (
+ {
+ setDemoInUrl(key);
+ setDemoKey(key);
+ }}
+ type="button"
+ >
+ {label}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/demos/src/app.css b/demos/src/app.css
new file mode 100644
index 0000000000..f5ca0a8001
--- /dev/null
+++ b/demos/src/app.css
@@ -0,0 +1,40 @@
+html,
+body,
+#root {
+ margin: 0;
+ padding: 0;
+ min-height: 100%;
+ background: #070810;
+}
+
+button {
+ font: inherit;
+}
+
+.demo-switcher {
+ position: fixed;
+ top: 12px;
+ right: 12px;
+ z-index: 9999;
+ display: flex;
+ gap: 6px;
+ padding: 6px;
+ border: 1px solid rgba(0, 201, 167, 0.25);
+ border-radius: 10px;
+ background: rgba(8, 11, 19, 0.8);
+ backdrop-filter: blur(6px);
+}
+
+.demo-switcher button {
+ border: 1px solid rgba(0, 201, 167, 0.35);
+ color: #00c9a7;
+ background: transparent;
+ border-radius: 8px;
+ padding: 5px 10px;
+ font-size: 12px;
+ cursor: pointer;
+}
+
+.demo-switcher button.active {
+ background: rgba(0, 201, 167, 0.15);
+}
diff --git a/demos/src/main.jsx b/demos/src/main.jsx
new file mode 100644
index 0000000000..c5a144cad5
--- /dev/null
+++ b/demos/src/main.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App.jsx";
+import "./app.css";
+
+createRoot(document.getElementById("root")).render(
+
+
+ ,
+);
diff --git a/demos/vite.config.js b/demos/vite.config.js
new file mode 100644
index 0000000000..454e67f682
--- /dev/null
+++ b/demos/vite.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ host: "127.0.0.1",
+ port: 5173,
+ open: true,
+ },
+});