concept-collection / walnuts-interactive
Interactive WALNUTS sampling of a 2D banana target
A numbl + React demo: the WALNUTS sampler (within-orbit adaptive leapfrog No-U-Turn Sampler) draws from a banana target; the figure shows the density heatmap with samples, live controls (sample count, leapfrog step, max error, resample), and a step-by-step movie that traces the orbit-building leapfrog trajectories. MATLAB port of Brian Ward's WALNUTS.js (chi-feng/mcmc-demo).
Jeremy Magland <jmagland@flatironinstitute.org> committed commit 727e2e446dcb Browse files
21 changed files+3339−0
.github/workflows/deploy.ymladded+61−0View file
@@ -0,0 +1,61 @@
1+name: Deploy numbl project to GitHub Pages
2+
3+# Builds the single-file web app, bundles this numbl project with the browser
4+# IDE, and publishes it to GitHub Pages on every push to main.
5+#
6+# One-time setup: Settings → Pages → "Build and deployment" → Source →
7+# "GitHub Actions".
8+
9+on:
10+ push:
11+ branches: [main]
12+ workflow_dispatch:
13+
14+permissions:
15+ contents: read
16+ pages: write
17+ id-token: write
18+
19+concurrency:
20+ group: pages
21+ cancel-in-progress: false
22+
23+jobs:
24+ build:
25+ runs-on: ubuntu-latest
26+ steps:
27+ - uses: actions/checkout@v4
28+
29+ - uses: actions/setup-node@v6
30+ with:
31+ node-version: 24
32+
33+ # Build the single-file figure app (app/dist/index.html) that
34+ # walnuts_sampler.m loads via uihtml. build-site then bundles
35+ # app/dist/index.html into the project (the rest of app/ is excluded via
36+ # .numblignore).
37+ - name: Build figure app
38+ run: |
39+ cd app
40+ npm ci
41+ npm run build
42+
43+ - uses: flatironinstitute/numbl/.github/actions/build-site@main
44+ with:
45+ project-dir: .
46+ # Build numbl from the main branch (development version, which includes
47+ # the uihtml two-way data/event bridge) rather than the npm release.
48+ numbl-ref: main
49+ # Keep the (mostly empty) output panel small so the interactive figure
50+ # panel below it gets most of the height on first load.
51+ max-initial-output-panel-height: 100
52+
53+ deploy:
54+ needs: build
55+ runs-on: ubuntu-latest
56+ environment:
57+ name: github-pages
58+ url: ${{ steps.deployment.outputs.page_url }}
59+ steps:
60+ - id: deployment
61+ uses: actions/deploy-pages@v4
.gitignoreadded+4−0View file
@@ -0,0 +1,4 @@
1+# Local output from `numbl build-site`
2+/_site/
3+/dist/
4+node_modules/
.numblignoreadded+12−0View file
@@ -0,0 +1,12 @@
1+# Files NOT to bundle into the deployed numbl project. Only the built
2+# single-file app (app/dist/index.html) is needed at runtime — exclude the web
3+# app's source and config. (node_modules, .git, .github, and agent instruction
4+# files like CLAUDE.md are excluded by default.)
5+.gitignore
6+app/.gitignore
7+app/src
8+app/index.html
9+app/package.json
10+app/package-lock.json
11+app/tsconfig.json
12+app/vite.config.ts
CLAUDE.mdadded+84−0View file
@@ -0,0 +1,84 @@
1+# CLAUDE.md
2+
3+Tips for future agents. Patterned after the sibling `hitandrun-interactive`
4+project (same numbl + uihtml two-way bridge, 2D canvas figure).
5+
6+## Architecture
7+
8+- `app/` — React single-file widget (`npm run build` → `app/dist/index.html`).
9+- `*.m` at root — `walnuts_demo.m` (driver) and `walnuts_sampler.m` (figure plumbing).
10+- `helpers/` — `walnuts.m` (the sampler) and the target `log_density.m` / `grad_log_density.m`, put on the path by the driver via `addpath('helpers')`.
11+- `numbl-project.json` — project metadata; `entry` is the landing page.
12+
13+`addpath('helpers')` must be the **first statement** in the driver (numbl
14+resolves the search path before running, and addpath can't live inside a
15+function file). Run `walnuts_demo.m`, not the sampler directly.
16+
17+## Data flow (uihtml bridge)
18+
19+- **MATLAB → figure:** `uihtml(..., 'Data', struct)` / `sendEventToHTMLSource(src, name, data)`.
20+- **Figure → MATLAB:** `sendToMATLAB(name, value)` (see `app/src/bridge.ts`), received via `HTMLEventReceivedFcn`.
21+
22+The script sends the density grid + samples once, then handles two requests
23+(`walnuts_sampler.m` `on_event`):
24+
25+- `resample` `{n, dt, maxError}` → fresh chain → `samples` event. (Target is
26+ fixed, so the density is not re-sent.)
27+- `movie` `{dt, maxError}` → `walnuts(..., record=true)` for a few transitions →
28+ `movie` event carrying their orbit trajectories.
29+
30+The figure's controls (samples / Δt / max error sliders, Resample) drive
31+`resample`; the **▶ Movie** button drives `movie` and animates the returned
32+orbits (`App.tsx` + `DensityView.tsx`).
33+
34+### Trajectory recording
35+
36+`walnuts.m` records the orbit path only when called with `record=true`, via
37+globals (`WREC_*`): `macro_step` appends each *committed* macro-step's leapfrog
38+path (recomputed by `leapfrog_capture`), tagged with a segment id so the figure
39+breaks the polyline at orbit direction flips. Plain sampling (`record=false`)
40+takes the fast path with no recording overhead.
41+
42+## The algorithm
43+
44+`helpers/walnuts.m` is a direct port of Brian Ward's `algorithms/WALNUTS.js`
45+from chi-feng/mcmc-demo (based on Bob Carpenter's C++,
46+flatironinstitute/walnuts; paper arXiv:2506.18746). Spans carry both orbit
47+endpoints; `macro_step` does the within-orbit step halving; `build_span`
48+recurses (NUTS doubling); selection is Barker within sub-orbits, Metropolis at
49+the top. See README for full credits.
50+
51+## Performance
52+
53+Unlike the hitandrun kernel, WALNUTS uses **recursion + structs**, so it runs in
54+the numbl **interpreter** (it does not JS-JIT like a flat numeric loop). It's
55+still fine — banana orbits are shallow, ~1–2 ms/transition — so keep `N` modest
56+(the demo uses 1000 + 200 burn-in). Don't add a `%!numbl:assert_jit` guard here;
57+it would (correctly) fail.
58+
59+## Key files
60+
61+| File | Purpose |
62+|------|---------|
63+| `app/src/App.tsx` | Reads data, hosts the info panel |
64+| `app/src/render/DensityView.tsx` | Canvas: density heatmap + sample scatter |
65+| `app/src/bridge.ts` | `onData` / `onHostEvent` / `sendToMATLAB` (generic) |
66+| `helpers/walnuts.m` | The WALNUTS sampler |
67+| `helpers/log_density.m` / `grad_log_density.m` | Banana target |
68+| `walnuts_sampler.m` | Runs the chain, builds the density grid, opens the figure |
69+| `walnuts_demo.m` | Driver: addpath + seed + call the sampler |
70+
71+## Local iteration
72+
73+```
74+cd app && npm install && npm run build # → app/dist/index.html
75+cd ..
76+npx tsx $NUMBL/src/cli.ts run walnuts_demo.m --plot # quick loop
77+# or preview the GitHub Pages bundle:
78+( cd $NUMBL && npm run build:site-viewer ) # one-time
79+npx tsx $NUMBL/src/cli.ts build-site . --out _site --base /
80+( cd _site && python3 -m http.server 8080 )
81+```
82+
83+`$NUMBL` is a local clone of flatironinstitute/numbl. The real target is GitHub
84+Pages, where the deploy builds numbl from `main`.
README.mdadded+53−0View file
@@ -0,0 +1,53 @@
1+# Interactive WALNUTS sampling
2+
3+Runs in the browser via [numbl](https://numbl.org) — no install.
4+
5+## ▶ [Open `walnuts_demo.m`](walnuts_demo.m) and click **Run**
6+
7+Samples are drawn from a 2D "banana" target by **WALNUTS** (the within-orbit
8+adaptive leapfrog No-U-Turn Sampler). The figure shows the target density as a
9+heatmap with the samples scattered on top.
10+
11+WALNUTS is a NUTS variant that adapts the leapfrog step size *within* each
12+orbit: each macro-step halves the step (and doubles the count) until the energy
13+error is within tolerance, then grows the orbit by NUTS-style doubling until a
14+U-turn.
15+
16+Controls:
17+
18+- **Samples** / **Leapfrog Δt** / **Max error** — change a setting to re-run the
19+ sampler with it (lower Δt or max error → more, smaller leapfrog steps).
20+- **Resample** — a fresh chain with the current settings.
21+- **▶ Movie** — animate WALNUTS building orbits step by step: each transition
22+ traces its leapfrog path (amber), then circles the selected draw, which joins
23+ the chain and starts the next transition.
24+
25+## How it works
26+
27+- [`walnuts_demo.m`](walnuts_demo.m) — driver: `addpath('helpers')`, seed, call the sampler.
28+- [`walnuts_sampler.m`](walnuts_sampler.m) — runs the chain, evaluates the target on a grid, opens the figure.
29+- `helpers/` — [`walnuts.m`](helpers/walnuts.m) (the algorithm) and the target [`log_density.m`](helpers/log_density.m) / [`grad_log_density.m`](helpers/grad_log_density.m).
30+- `app/` — a single-file React app that draws the density heatmap + samples on a canvas.
31+
32+The script sends the density grid + samples via `uihtml(..., 'Data', ...)`. The
33+controls call back: `sendToMATLAB('resample', {n, dt, maxError})` re-runs the
34+chain, and `sendToMATLAB('movie', {dt, maxError})` records a few transitions'
35+orbit trajectories (`walnuts(..., record=true)`); the script replies with
36+`sendEventToHTMLSource`.
37+
38+## Credits
39+
40+- **Algorithm:** N. Bou-Rabee, B. Carpenter, T. S. Kleppe, and S. Liu, *The
41+ within-orbit adaptive leapfrog no-U-turn sampler*,
42+ [arXiv:2506.18746](https://arxiv.org/abs/2506.18746) (2025). Reference C++ at
43+ [flatironinstitute/walnuts](https://github.com/flatironinstitute/walnuts)
44+ (Bob Carpenter).
45+- **This MATLAB port** follows Brian Ward's JavaScript implementation in
46+ [WardBrian/mcmc-demo](https://github.com/WardBrian/mcmc-demo) (`algorithms/WALNUTS.js`).
47+- **Demo framework & banana target** from Chi Feng's
48+ [mcmc-demo](https://github.com/chi-feng/mcmc-demo).
49+
50+## Deploy
51+
52+Pushing to `main` builds the app and publishes the project to GitHub Pages via
53+the [deploy workflow](.github/workflows/deploy.yml).
app/.gitignoreadded+2−0View file
@@ -0,0 +1,2 @@
1+node_modules/
2+dist/
app/index.htmladded+22−0View file
@@ -0,0 +1,22 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="UTF-8" />
5+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+ <title>WALNUTS sampler</title>
7+ <style>
8+ html,
9+ body,
10+ #root {
11+ margin: 0;
12+ height: 100%;
13+ width: 100%;
14+ overflow: hidden;
15+ }
16+ </style>
17+ </head>
18+ <body>
19+ <div id="root"></div>
20+ <script type="module" src="/src/main.tsx"></script>
21+ </body>
22+</html>
app/package-lock.jsonadded+1987−0View file
@@ -0,0 +1,1987 @@
1+{
2+ "name": "walnuts-interactive-app",
3+ "version": "0.0.0",
4+ "lockfileVersion": 3,
5+ "requires": true,
6+ "packages": {
7+ "": {
8+ "name": "walnuts-interactive-app",
9+ "version": "0.0.0",
10+ "dependencies": {
11+ "react": "^18.3.1",
12+ "react-dom": "^18.3.1"
13+ },
14+ "devDependencies": {
15+ "@types/react": "^18.3.12",
16+ "@types/react-dom": "^18.3.1",
17+ "@vitejs/plugin-react": "^4.3.4",
18+ "typescript": "^5.6.3",
19+ "vite": "^6.0.7",
20+ "vite-plugin-singlefile": "^2.1.0"
21+ }
22+ },
23+ "node_modules/@babel/code-frame": {
24+ "version": "7.29.7",
25+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
26+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
27+ "dev": true,
28+ "license": "MIT",
29+ "dependencies": {
30+ "@babel/helper-validator-identifier": "^7.29.7",
31+ "js-tokens": "^4.0.0",
32+ "picocolors": "^1.1.1"
33+ },
34+ "engines": {
35+ "node": ">=6.9.0"
36+ }
37+ },
38+ "node_modules/@babel/compat-data": {
39+ "version": "7.29.7",
40+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
41+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
42+ "dev": true,
43+ "license": "MIT",
44+ "engines": {
45+ "node": ">=6.9.0"
46+ }
47+ },
48+ "node_modules/@babel/core": {
49+ "version": "7.29.7",
50+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
51+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
52+ "dev": true,
53+ "license": "MIT",
54+ "dependencies": {
55+ "@babel/code-frame": "^7.29.7",
56+ "@babel/generator": "^7.29.7",
57+ "@babel/helper-compilation-targets": "^7.29.7",
58+ "@babel/helper-module-transforms": "^7.29.7",
59+ "@babel/helpers": "^7.29.7",
60+ "@babel/parser": "^7.29.7",
61+ "@babel/template": "^7.29.7",
62+ "@babel/traverse": "^7.29.7",
63+ "@babel/types": "^7.29.7",
64+ "@jridgewell/remapping": "^2.3.5",
65+ "convert-source-map": "^2.0.0",
66+ "debug": "^4.1.0",
67+ "gensync": "^1.0.0-beta.2",
68+ "json5": "^2.2.3",
69+ "semver": "^6.3.1"
70+ },
71+ "engines": {
72+ "node": ">=6.9.0"
73+ },
74+ "funding": {
75+ "type": "opencollective",
76+ "url": "https://opencollective.com/babel"
77+ }
78+ },
79+ "node_modules/@babel/generator": {
80+ "version": "7.29.7",
81+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
82+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
83+ "dev": true,
84+ "license": "MIT",
85+ "dependencies": {
86+ "@babel/parser": "^7.29.7",
87+ "@babel/types": "^7.29.7",
88+ "@jridgewell/gen-mapping": "^0.3.12",
89+ "@jridgewell/trace-mapping": "^0.3.28",
90+ "jsesc": "^3.0.2"
91+ },
92+ "engines": {
93+ "node": ">=6.9.0"
94+ }
95+ },
96+ "node_modules/@babel/helper-compilation-targets": {
97+ "version": "7.29.7",
98+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
99+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
100+ "dev": true,
101+ "license": "MIT",
102+ "dependencies": {
103+ "@babel/compat-data": "^7.29.7",
104+ "@babel/helper-validator-option": "^7.29.7",
105+ "browserslist": "^4.24.0",
106+ "lru-cache": "^5.1.1",
107+ "semver": "^6.3.1"
108+ },
109+ "engines": {
110+ "node": ">=6.9.0"
111+ }
112+ },
113+ "node_modules/@babel/helper-globals": {
114+ "version": "7.29.7",
115+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
116+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
117+ "dev": true,
118+ "license": "MIT",
119+ "engines": {
120+ "node": ">=6.9.0"
121+ }
122+ },
123+ "node_modules/@babel/helper-module-imports": {
124+ "version": "7.29.7",
125+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
126+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
127+ "dev": true,
128+ "license": "MIT",
129+ "dependencies": {
130+ "@babel/traverse": "^7.29.7",
131+ "@babel/types": "^7.29.7"
132+ },
133+ "engines": {
134+ "node": ">=6.9.0"
135+ }
136+ },
137+ "node_modules/@babel/helper-module-transforms": {
138+ "version": "7.29.7",
139+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
140+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
141+ "dev": true,
142+ "license": "MIT",
143+ "dependencies": {
144+ "@babel/helper-module-imports": "^7.29.7",
145+ "@babel/helper-validator-identifier": "^7.29.7",
146+ "@babel/traverse": "^7.29.7"
147+ },
148+ "engines": {
149+ "node": ">=6.9.0"
150+ },
151+ "peerDependencies": {
152+ "@babel/core": "^7.0.0"
153+ }
154+ },
155+ "node_modules/@babel/helper-plugin-utils": {
156+ "version": "7.29.7",
157+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
158+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
159+ "dev": true,
160+ "license": "MIT",
161+ "engines": {
162+ "node": ">=6.9.0"
163+ }
164+ },
165+ "node_modules/@babel/helper-string-parser": {
166+ "version": "7.29.7",
167+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
168+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
169+ "dev": true,
170+ "license": "MIT",
171+ "engines": {
172+ "node": ">=6.9.0"
173+ }
174+ },
175+ "node_modules/@babel/helper-validator-identifier": {
176+ "version": "7.29.7",
177+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
178+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
179+ "dev": true,
180+ "license": "MIT",
181+ "engines": {
182+ "node": ">=6.9.0"
183+ }
184+ },
185+ "node_modules/@babel/helper-validator-option": {
186+ "version": "7.29.7",
187+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
188+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
189+ "dev": true,
190+ "license": "MIT",
191+ "engines": {
192+ "node": ">=6.9.0"
193+ }
194+ },
195+ "node_modules/@babel/helpers": {
196+ "version": "7.29.7",
197+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
198+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
199+ "dev": true,
200+ "license": "MIT",
201+ "dependencies": {
202+ "@babel/template": "^7.29.7",
203+ "@babel/types": "^7.29.7"
204+ },
205+ "engines": {
206+ "node": ">=6.9.0"
207+ }
208+ },
209+ "node_modules/@babel/parser": {
210+ "version": "7.29.7",
211+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
212+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
213+ "dev": true,
214+ "license": "MIT",
215+ "dependencies": {
216+ "@babel/types": "^7.29.7"
217+ },
218+ "bin": {
219+ "parser": "bin/babel-parser.js"
220+ },
221+ "engines": {
222+ "node": ">=6.0.0"
223+ }
224+ },
225+ "node_modules/@babel/plugin-transform-react-jsx-self": {
226+ "version": "7.29.7",
227+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
228+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
229+ "dev": true,
230+ "license": "MIT",
231+ "dependencies": {
232+ "@babel/helper-plugin-utils": "^7.29.7"
233+ },
234+ "engines": {
235+ "node": ">=6.9.0"
236+ },
237+ "peerDependencies": {
238+ "@babel/core": "^7.0.0-0"
239+ }
240+ },
241+ "node_modules/@babel/plugin-transform-react-jsx-source": {
242+ "version": "7.29.7",
243+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
244+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
245+ "dev": true,
246+ "license": "MIT",
247+ "dependencies": {
248+ "@babel/helper-plugin-utils": "^7.29.7"
249+ },
250+ "engines": {
251+ "node": ">=6.9.0"
252+ },
253+ "peerDependencies": {
254+ "@babel/core": "^7.0.0-0"
255+ }
256+ },
257+ "node_modules/@babel/template": {
258+ "version": "7.29.7",
259+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
260+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
261+ "dev": true,
262+ "license": "MIT",
263+ "dependencies": {
264+ "@babel/code-frame": "^7.29.7",
265+ "@babel/parser": "^7.29.7",
266+ "@babel/types": "^7.29.7"
267+ },
268+ "engines": {
269+ "node": ">=6.9.0"
270+ }
271+ },
272+ "node_modules/@babel/traverse": {
273+ "version": "7.29.7",
274+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
275+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
276+ "dev": true,
277+ "license": "MIT",
278+ "dependencies": {
279+ "@babel/code-frame": "^7.29.7",
280+ "@babel/generator": "^7.29.7",
281+ "@babel/helper-globals": "^7.29.7",
282+ "@babel/parser": "^7.29.7",
283+ "@babel/template": "^7.29.7",
284+ "@babel/types": "^7.29.7",
285+ "debug": "^4.3.1"
286+ },
287+ "engines": {
288+ "node": ">=6.9.0"
289+ }
290+ },
291+ "node_modules/@babel/types": {
292+ "version": "7.29.7",
293+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
294+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
295+ "dev": true,
296+ "license": "MIT",
297+ "dependencies": {
298+ "@babel/helper-string-parser": "^7.29.7",
299+ "@babel/helper-validator-identifier": "^7.29.7"
300+ },
301+ "engines": {
302+ "node": ">=6.9.0"
303+ }
304+ },
305+ "node_modules/@esbuild/aix-ppc64": {
306+ "version": "0.25.12",
307+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
308+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
309+ "cpu": [
310+ "ppc64"
311+ ],
312+ "dev": true,
313+ "license": "MIT",
314+ "optional": true,
315+ "os": [
316+ "aix"
317+ ],
318+ "engines": {
319+ "node": ">=18"
320+ }
321+ },
322+ "node_modules/@esbuild/android-arm": {
323+ "version": "0.25.12",
324+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
325+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
326+ "cpu": [
327+ "arm"
328+ ],
329+ "dev": true,
330+ "license": "MIT",
331+ "optional": true,
332+ "os": [
333+ "android"
334+ ],
335+ "engines": {
336+ "node": ">=18"
337+ }
338+ },
339+ "node_modules/@esbuild/android-arm64": {
340+ "version": "0.25.12",
341+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
342+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
343+ "cpu": [
344+ "arm64"
345+ ],
346+ "dev": true,
347+ "license": "MIT",
348+ "optional": true,
349+ "os": [
350+ "android"
351+ ],
352+ "engines": {
353+ "node": ">=18"
354+ }
355+ },
356+ "node_modules/@esbuild/android-x64": {
357+ "version": "0.25.12",
358+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
359+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
360+ "cpu": [
361+ "x64"
362+ ],
363+ "dev": true,
364+ "license": "MIT",
365+ "optional": true,
366+ "os": [
367+ "android"
368+ ],
369+ "engines": {
370+ "node": ">=18"
371+ }
372+ },
373+ "node_modules/@esbuild/darwin-arm64": {
374+ "version": "0.25.12",
375+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
376+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
377+ "cpu": [
378+ "arm64"
379+ ],
380+ "dev": true,
381+ "license": "MIT",
382+ "optional": true,
383+ "os": [
384+ "darwin"
385+ ],
386+ "engines": {
387+ "node": ">=18"
388+ }
389+ },
390+ "node_modules/@esbuild/darwin-x64": {
391+ "version": "0.25.12",
392+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
393+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
394+ "cpu": [
395+ "x64"
396+ ],
397+ "dev": true,
398+ "license": "MIT",
399+ "optional": true,
400+ "os": [
401+ "darwin"
402+ ],
403+ "engines": {
404+ "node": ">=18"
405+ }
406+ },
407+ "node_modules/@esbuild/freebsd-arm64": {
408+ "version": "0.25.12",
409+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
410+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
411+ "cpu": [
412+ "arm64"
413+ ],
414+ "dev": true,
415+ "license": "MIT",
416+ "optional": true,
417+ "os": [
418+ "freebsd"
419+ ],
420+ "engines": {
421+ "node": ">=18"
422+ }
423+ },
424+ "node_modules/@esbuild/freebsd-x64": {
425+ "version": "0.25.12",
426+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
427+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
428+ "cpu": [
429+ "x64"
430+ ],
431+ "dev": true,
432+ "license": "MIT",
433+ "optional": true,
434+ "os": [
435+ "freebsd"
436+ ],
437+ "engines": {
438+ "node": ">=18"
439+ }
440+ },
441+ "node_modules/@esbuild/linux-arm": {
442+ "version": "0.25.12",
443+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
444+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
445+ "cpu": [
446+ "arm"
447+ ],
448+ "dev": true,
449+ "license": "MIT",
450+ "optional": true,
451+ "os": [
452+ "linux"
453+ ],
454+ "engines": {
455+ "node": ">=18"
456+ }
457+ },
458+ "node_modules/@esbuild/linux-arm64": {
459+ "version": "0.25.12",
460+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
461+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
462+ "cpu": [
463+ "arm64"
464+ ],
465+ "dev": true,
466+ "license": "MIT",
467+ "optional": true,
468+ "os": [
469+ "linux"
470+ ],
471+ "engines": {
472+ "node": ">=18"
473+ }
474+ },
475+ "node_modules/@esbuild/linux-ia32": {
476+ "version": "0.25.12",
477+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
478+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
479+ "cpu": [
480+ "ia32"
481+ ],
482+ "dev": true,
483+ "license": "MIT",
484+ "optional": true,
485+ "os": [
486+ "linux"
487+ ],
488+ "engines": {
489+ "node": ">=18"
490+ }
491+ },
492+ "node_modules/@esbuild/linux-loong64": {
493+ "version": "0.25.12",
494+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
495+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
496+ "cpu": [
497+ "loong64"
498+ ],
499+ "dev": true,
500+ "license": "MIT",
501+ "optional": true,
502+ "os": [
503+ "linux"
504+ ],
505+ "engines": {
506+ "node": ">=18"
507+ }
508+ },
509+ "node_modules/@esbuild/linux-mips64el": {
510+ "version": "0.25.12",
511+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
512+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
513+ "cpu": [
514+ "mips64el"
515+ ],
516+ "dev": true,
517+ "license": "MIT",
518+ "optional": true,
519+ "os": [
520+ "linux"
521+ ],
522+ "engines": {
523+ "node": ">=18"
524+ }
525+ },
526+ "node_modules/@esbuild/linux-ppc64": {
527+ "version": "0.25.12",
528+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
529+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
530+ "cpu": [
531+ "ppc64"
532+ ],
533+ "dev": true,
534+ "license": "MIT",
535+ "optional": true,
536+ "os": [
537+ "linux"
538+ ],
539+ "engines": {
540+ "node": ">=18"
541+ }
542+ },
543+ "node_modules/@esbuild/linux-riscv64": {
544+ "version": "0.25.12",
545+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
546+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
547+ "cpu": [
548+ "riscv64"
549+ ],
550+ "dev": true,
551+ "license": "MIT",
552+ "optional": true,
553+ "os": [
554+ "linux"
555+ ],
556+ "engines": {
557+ "node": ">=18"
558+ }
559+ },
560+ "node_modules/@esbuild/linux-s390x": {
561+ "version": "0.25.12",
562+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
563+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
564+ "cpu": [
565+ "s390x"
566+ ],
567+ "dev": true,
568+ "license": "MIT",
569+ "optional": true,
570+ "os": [
571+ "linux"
572+ ],
573+ "engines": {
574+ "node": ">=18"
575+ }
576+ },
577+ "node_modules/@esbuild/linux-x64": {
578+ "version": "0.25.12",
579+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
580+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
581+ "cpu": [
582+ "x64"
583+ ],
584+ "dev": true,
585+ "license": "MIT",
586+ "optional": true,
587+ "os": [
588+ "linux"
589+ ],
590+ "engines": {
591+ "node": ">=18"
592+ }
593+ },
594+ "node_modules/@esbuild/netbsd-arm64": {
595+ "version": "0.25.12",
596+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
597+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
598+ "cpu": [
599+ "arm64"
600+ ],
601+ "dev": true,
602+ "license": "MIT",
603+ "optional": true,
604+ "os": [
605+ "netbsd"
606+ ],
607+ "engines": {
608+ "node": ">=18"
609+ }
610+ },
611+ "node_modules/@esbuild/netbsd-x64": {
612+ "version": "0.25.12",
613+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
614+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
615+ "cpu": [
616+ "x64"
617+ ],
618+ "dev": true,
619+ "license": "MIT",
620+ "optional": true,
621+ "os": [
622+ "netbsd"
623+ ],
624+ "engines": {
625+ "node": ">=18"
626+ }
627+ },
628+ "node_modules/@esbuild/openbsd-arm64": {
629+ "version": "0.25.12",
630+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
631+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
632+ "cpu": [
633+ "arm64"
634+ ],
635+ "dev": true,
636+ "license": "MIT",
637+ "optional": true,
638+ "os": [
639+ "openbsd"
640+ ],
641+ "engines": {
642+ "node": ">=18"
643+ }
644+ },
645+ "node_modules/@esbuild/openbsd-x64": {
646+ "version": "0.25.12",
647+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
648+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
649+ "cpu": [
650+ "x64"
651+ ],
652+ "dev": true,
653+ "license": "MIT",
654+ "optional": true,
655+ "os": [
656+ "openbsd"
657+ ],
658+ "engines": {
659+ "node": ">=18"
660+ }
661+ },
662+ "node_modules/@esbuild/openharmony-arm64": {
663+ "version": "0.25.12",
664+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
665+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
666+ "cpu": [
667+ "arm64"
668+ ],
669+ "dev": true,
670+ "license": "MIT",
671+ "optional": true,
672+ "os": [
673+ "openharmony"
674+ ],
675+ "engines": {
676+ "node": ">=18"
677+ }
678+ },
679+ "node_modules/@esbuild/sunos-x64": {
680+ "version": "0.25.12",
681+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
682+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
683+ "cpu": [
684+ "x64"
685+ ],
686+ "dev": true,
687+ "license": "MIT",
688+ "optional": true,
689+ "os": [
690+ "sunos"
691+ ],
692+ "engines": {
693+ "node": ">=18"
694+ }
695+ },
696+ "node_modules/@esbuild/win32-arm64": {
697+ "version": "0.25.12",
698+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
699+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
700+ "cpu": [
701+ "arm64"
702+ ],
703+ "dev": true,
704+ "license": "MIT",
705+ "optional": true,
706+ "os": [
707+ "win32"
708+ ],
709+ "engines": {
710+ "node": ">=18"
711+ }
712+ },
713+ "node_modules/@esbuild/win32-ia32": {
714+ "version": "0.25.12",
715+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
716+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
717+ "cpu": [
718+ "ia32"
719+ ],
720+ "dev": true,
721+ "license": "MIT",
722+ "optional": true,
723+ "os": [
724+ "win32"
725+ ],
726+ "engines": {
727+ "node": ">=18"
728+ }
729+ },
730+ "node_modules/@esbuild/win32-x64": {
731+ "version": "0.25.12",
732+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
733+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
734+ "cpu": [
735+ "x64"
736+ ],
737+ "dev": true,
738+ "license": "MIT",
739+ "optional": true,
740+ "os": [
741+ "win32"
742+ ],
743+ "engines": {
744+ "node": ">=18"
745+ }
746+ },
747+ "node_modules/@jridgewell/gen-mapping": {
748+ "version": "0.3.13",
749+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
750+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
751+ "dev": true,
752+ "license": "MIT",
753+ "dependencies": {
754+ "@jridgewell/sourcemap-codec": "^1.5.0",
755+ "@jridgewell/trace-mapping": "^0.3.24"
756+ }
757+ },
758+ "node_modules/@jridgewell/remapping": {
759+ "version": "2.3.5",
760+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
761+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
762+ "dev": true,
763+ "license": "MIT",
764+ "dependencies": {
765+ "@jridgewell/gen-mapping": "^0.3.5",
766+ "@jridgewell/trace-mapping": "^0.3.24"
767+ }
768+ },
769+ "node_modules/@jridgewell/resolve-uri": {
770+ "version": "3.1.2",
771+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
772+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
773+ "dev": true,
774+ "license": "MIT",
775+ "engines": {
776+ "node": ">=6.0.0"
777+ }
778+ },
779+ "node_modules/@jridgewell/sourcemap-codec": {
780+ "version": "1.5.5",
781+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
782+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
783+ "dev": true,
784+ "license": "MIT"
785+ },
786+ "node_modules/@jridgewell/trace-mapping": {
787+ "version": "0.3.31",
788+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
789+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
790+ "dev": true,
791+ "license": "MIT",
792+ "dependencies": {
793+ "@jridgewell/resolve-uri": "^3.1.0",
794+ "@jridgewell/sourcemap-codec": "^1.4.14"
795+ }
796+ },
797+ "node_modules/@rolldown/pluginutils": {
798+ "version": "1.0.0-beta.27",
799+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
800+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
801+ "dev": true,
802+ "license": "MIT"
803+ },
804+ "node_modules/@rollup/rollup-android-arm-eabi": {
805+ "version": "4.62.2",
806+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
807+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
808+ "cpu": [
809+ "arm"
810+ ],
811+ "dev": true,
812+ "license": "MIT",
813+ "optional": true,
814+ "os": [
815+ "android"
816+ ]
817+ },
818+ "node_modules/@rollup/rollup-android-arm64": {
819+ "version": "4.62.2",
820+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
821+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
822+ "cpu": [
823+ "arm64"
824+ ],
825+ "dev": true,
826+ "license": "MIT",
827+ "optional": true,
828+ "os": [
829+ "android"
830+ ]
831+ },
832+ "node_modules/@rollup/rollup-darwin-arm64": {
833+ "version": "4.62.2",
834+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
835+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
836+ "cpu": [
837+ "arm64"
838+ ],
839+ "dev": true,
840+ "license": "MIT",
841+ "optional": true,
842+ "os": [
843+ "darwin"
844+ ]
845+ },
846+ "node_modules/@rollup/rollup-darwin-x64": {
847+ "version": "4.62.2",
848+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
849+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
850+ "cpu": [
851+ "x64"
852+ ],
853+ "dev": true,
854+ "license": "MIT",
855+ "optional": true,
856+ "os": [
857+ "darwin"
858+ ]
859+ },
860+ "node_modules/@rollup/rollup-freebsd-arm64": {
861+ "version": "4.62.2",
862+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
863+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
864+ "cpu": [
865+ "arm64"
866+ ],
867+ "dev": true,
868+ "license": "MIT",
869+ "optional": true,
870+ "os": [
871+ "freebsd"
872+ ]
873+ },
874+ "node_modules/@rollup/rollup-freebsd-x64": {
875+ "version": "4.62.2",
876+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
877+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
878+ "cpu": [
879+ "x64"
880+ ],
881+ "dev": true,
882+ "license": "MIT",
883+ "optional": true,
884+ "os": [
885+ "freebsd"
886+ ]
887+ },
888+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
889+ "version": "4.62.2",
890+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
891+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
892+ "cpu": [
893+ "arm"
894+ ],
895+ "dev": true,
896+ "libc": [
897+ "glibc"
898+ ],
899+ "license": "MIT",
900+ "optional": true,
901+ "os": [
902+ "linux"
903+ ]
904+ },
905+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
906+ "version": "4.62.2",
907+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
908+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
909+ "cpu": [
910+ "arm"
911+ ],
912+ "dev": true,
913+ "libc": [
914+ "musl"
915+ ],
916+ "license": "MIT",
917+ "optional": true,
918+ "os": [
919+ "linux"
920+ ]
921+ },
922+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
923+ "version": "4.62.2",
924+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
925+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
926+ "cpu": [
927+ "arm64"
928+ ],
929+ "dev": true,
930+ "libc": [
931+ "glibc"
932+ ],
933+ "license": "MIT",
934+ "optional": true,
935+ "os": [
936+ "linux"
937+ ]
938+ },
939+ "node_modules/@rollup/rollup-linux-arm64-musl": {
940+ "version": "4.62.2",
941+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
942+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
943+ "cpu": [
944+ "arm64"
945+ ],
946+ "dev": true,
947+ "libc": [
948+ "musl"
949+ ],
950+ "license": "MIT",
951+ "optional": true,
952+ "os": [
953+ "linux"
954+ ]
955+ },
956+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
957+ "version": "4.62.2",
958+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
959+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
960+ "cpu": [
961+ "loong64"
962+ ],
963+ "dev": true,
964+ "libc": [
965+ "glibc"
966+ ],
967+ "license": "MIT",
968+ "optional": true,
969+ "os": [
970+ "linux"
971+ ]
972+ },
973+ "node_modules/@rollup/rollup-linux-loong64-musl": {
974+ "version": "4.62.2",
975+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
976+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
977+ "cpu": [
978+ "loong64"
979+ ],
980+ "dev": true,
981+ "libc": [
982+ "musl"
983+ ],
984+ "license": "MIT",
985+ "optional": true,
986+ "os": [
987+ "linux"
988+ ]
989+ },
990+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
991+ "version": "4.62.2",
992+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
993+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
994+ "cpu": [
995+ "ppc64"
996+ ],
997+ "dev": true,
998+ "libc": [
999+ "glibc"
1000+ ],
1001+ "license": "MIT",
1002+ "optional": true,
1003+ "os": [
1004+ "linux"
1005+ ]
1006+ },
1007+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
1008+ "version": "4.62.2",
1009+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
1010+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
1011+ "cpu": [
1012+ "ppc64"
1013+ ],
1014+ "dev": true,
1015+ "libc": [
1016+ "musl"
1017+ ],
1018+ "license": "MIT",
1019+ "optional": true,
1020+ "os": [
1021+ "linux"
1022+ ]
1023+ },
1024+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
1025+ "version": "4.62.2",
1026+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
1027+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
1028+ "cpu": [
1029+ "riscv64"
1030+ ],
1031+ "dev": true,
1032+ "libc": [
1033+ "glibc"
1034+ ],
1035+ "license": "MIT",
1036+ "optional": true,
1037+ "os": [
1038+ "linux"
1039+ ]
1040+ },
1041+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
1042+ "version": "4.62.2",
1043+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
1044+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
1045+ "cpu": [
1046+ "riscv64"
1047+ ],
1048+ "dev": true,
1049+ "libc": [
1050+ "musl"
1051+ ],
1052+ "license": "MIT",
1053+ "optional": true,
1054+ "os": [
1055+ "linux"
1056+ ]
1057+ },
1058+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
1059+ "version": "4.62.2",
1060+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
1061+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
1062+ "cpu": [
1063+ "s390x"
1064+ ],
1065+ "dev": true,
1066+ "libc": [
1067+ "glibc"
1068+ ],
1069+ "license": "MIT",
1070+ "optional": true,
1071+ "os": [
1072+ "linux"
1073+ ]
1074+ },
1075+ "node_modules/@rollup/rollup-linux-x64-gnu": {
1076+ "version": "4.62.2",
1077+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
1078+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
1079+ "cpu": [
1080+ "x64"
1081+ ],
1082+ "dev": true,
1083+ "libc": [
1084+ "glibc"
1085+ ],
1086+ "license": "MIT",
1087+ "optional": true,
1088+ "os": [
1089+ "linux"
1090+ ]
1091+ },
1092+ "node_modules/@rollup/rollup-linux-x64-musl": {
1093+ "version": "4.62.2",
1094+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
1095+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
1096+ "cpu": [
1097+ "x64"
1098+ ],
1099+ "dev": true,
1100+ "libc": [
1101+ "musl"
1102+ ],
1103+ "license": "MIT",
1104+ "optional": true,
1105+ "os": [
1106+ "linux"
1107+ ]
1108+ },
1109+ "node_modules/@rollup/rollup-openbsd-x64": {
1110+ "version": "4.62.2",
1111+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
1112+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
1113+ "cpu": [
1114+ "x64"
1115+ ],
1116+ "dev": true,
1117+ "license": "MIT",
1118+ "optional": true,
1119+ "os": [
1120+ "openbsd"
1121+ ]
1122+ },
1123+ "node_modules/@rollup/rollup-openharmony-arm64": {
1124+ "version": "4.62.2",
1125+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
1126+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
1127+ "cpu": [
1128+ "arm64"
1129+ ],
1130+ "dev": true,
1131+ "license": "MIT",
1132+ "optional": true,
1133+ "os": [
1134+ "openharmony"
1135+ ]
1136+ },
1137+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
1138+ "version": "4.62.2",
1139+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
1140+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
1141+ "cpu": [
1142+ "arm64"
1143+ ],
1144+ "dev": true,
1145+ "license": "MIT",
1146+ "optional": true,
1147+ "os": [
1148+ "win32"
1149+ ]
1150+ },
1151+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
1152+ "version": "4.62.2",
1153+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
1154+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
1155+ "cpu": [
1156+ "ia32"
1157+ ],
1158+ "dev": true,
1159+ "license": "MIT",
1160+ "optional": true,
1161+ "os": [
1162+ "win32"
1163+ ]
1164+ },
1165+ "node_modules/@rollup/rollup-win32-x64-gnu": {
1166+ "version": "4.62.2",
1167+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
1168+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
1169+ "cpu": [
1170+ "x64"
1171+ ],
1172+ "dev": true,
1173+ "license": "MIT",
1174+ "optional": true,
1175+ "os": [
1176+ "win32"
1177+ ]
1178+ },
1179+ "node_modules/@rollup/rollup-win32-x64-msvc": {
1180+ "version": "4.62.2",
1181+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
1182+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
1183+ "cpu": [
1184+ "x64"
1185+ ],
1186+ "dev": true,
1187+ "license": "MIT",
1188+ "optional": true,
1189+ "os": [
1190+ "win32"
1191+ ]
1192+ },
1193+ "node_modules/@types/babel__core": {
1194+ "version": "7.20.5",
1195+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1196+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1197+ "dev": true,
1198+ "license": "MIT",
1199+ "dependencies": {
1200+ "@babel/parser": "^7.20.7",
1201+ "@babel/types": "^7.20.7",
1202+ "@types/babel__generator": "*",
1203+ "@types/babel__template": "*",
1204+ "@types/babel__traverse": "*"
1205+ }
1206+ },
1207+ "node_modules/@types/babel__generator": {
1208+ "version": "7.27.0",
1209+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1210+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1211+ "dev": true,
1212+ "license": "MIT",
1213+ "dependencies": {
1214+ "@babel/types": "^7.0.0"
1215+ }
1216+ },
1217+ "node_modules/@types/babel__template": {
1218+ "version": "7.4.4",
1219+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1220+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1221+ "dev": true,
1222+ "license": "MIT",
1223+ "dependencies": {
1224+ "@babel/parser": "^7.1.0",
1225+ "@babel/types": "^7.0.0"
1226+ }
1227+ },
1228+ "node_modules/@types/babel__traverse": {
1229+ "version": "7.28.0",
1230+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
1231+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
1232+ "dev": true,
1233+ "license": "MIT",
1234+ "dependencies": {
1235+ "@babel/types": "^7.28.2"
1236+ }
1237+ },
1238+ "node_modules/@types/estree": {
1239+ "version": "1.0.9",
1240+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
1241+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
1242+ "dev": true,
1243+ "license": "MIT"
1244+ },
1245+ "node_modules/@types/prop-types": {
1246+ "version": "15.7.15",
1247+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
1248+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
1249+ "dev": true,
1250+ "license": "MIT"
1251+ },
1252+ "node_modules/@types/react": {
1253+ "version": "18.3.31",
1254+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
1255+ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
1256+ "dev": true,
1257+ "license": "MIT",
1258+ "dependencies": {
1259+ "@types/prop-types": "*",
1260+ "csstype": "^3.2.2"
1261+ }
1262+ },
1263+ "node_modules/@types/react-dom": {
1264+ "version": "18.3.7",
1265+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
1266+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
1267+ "dev": true,
1268+ "license": "MIT",
1269+ "peerDependencies": {
1270+ "@types/react": "^18.0.0"
1271+ }
1272+ },
1273+ "node_modules/@vitejs/plugin-react": {
1274+ "version": "4.7.0",
1275+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
1276+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
1277+ "dev": true,
1278+ "license": "MIT",
1279+ "dependencies": {
1280+ "@babel/core": "^7.28.0",
1281+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
1282+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
1283+ "@rolldown/pluginutils": "1.0.0-beta.27",
1284+ "@types/babel__core": "^7.20.5",
1285+ "react-refresh": "^0.17.0"
1286+ },
1287+ "engines": {
1288+ "node": "^14.18.0 || >=16.0.0"
1289+ },
1290+ "peerDependencies": {
1291+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1292+ }
1293+ },
1294+ "node_modules/baseline-browser-mapping": {
1295+ "version": "2.10.40",
1296+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz",
1297+ "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==",
1298+ "dev": true,
1299+ "license": "Apache-2.0",
1300+ "bin": {
1301+ "baseline-browser-mapping": "dist/cli.cjs"
1302+ },
1303+ "engines": {
1304+ "node": ">=6.0.0"
1305+ }
1306+ },
1307+ "node_modules/braces": {
1308+ "version": "3.0.3",
1309+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
1310+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
1311+ "dev": true,
1312+ "license": "MIT",
1313+ "dependencies": {
1314+ "fill-range": "^7.1.1"
1315+ },
1316+ "engines": {
1317+ "node": ">=8"
1318+ }
1319+ },
1320+ "node_modules/browserslist": {
1321+ "version": "4.28.4",
1322+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
1323+ "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
1324+ "dev": true,
1325+ "funding": [
1326+ {
1327+ "type": "opencollective",
1328+ "url": "https://opencollective.com/browserslist"
1329+ },
1330+ {
1331+ "type": "tidelift",
1332+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1333+ },
1334+ {
1335+ "type": "github",
1336+ "url": "https://github.com/sponsors/ai"
1337+ }
1338+ ],
1339+ "license": "MIT",
1340+ "dependencies": {
1341+ "baseline-browser-mapping": "^2.10.38",
1342+ "caniuse-lite": "^1.0.30001799",
1343+ "electron-to-chromium": "^1.5.376",
1344+ "node-releases": "^2.0.48",
1345+ "update-browserslist-db": "^1.2.3"
1346+ },
1347+ "bin": {
1348+ "browserslist": "cli.js"
1349+ },
1350+ "engines": {
1351+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1352+ }
1353+ },
1354+ "node_modules/caniuse-lite": {
1355+ "version": "1.0.30001799",
1356+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
1357+ "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
1358+ "dev": true,
1359+ "funding": [
1360+ {
1361+ "type": "opencollective",
1362+ "url": "https://opencollective.com/browserslist"
1363+ },
1364+ {
1365+ "type": "tidelift",
1366+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1367+ },
1368+ {
1369+ "type": "github",
1370+ "url": "https://github.com/sponsors/ai"
1371+ }
1372+ ],
1373+ "license": "CC-BY-4.0"
1374+ },
1375+ "node_modules/convert-source-map": {
1376+ "version": "2.0.0",
1377+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1378+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1379+ "dev": true,
1380+ "license": "MIT"
1381+ },
1382+ "node_modules/csstype": {
1383+ "version": "3.2.3",
1384+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1385+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1386+ "dev": true,
1387+ "license": "MIT"
1388+ },
1389+ "node_modules/debug": {
1390+ "version": "4.4.3",
1391+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1392+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1393+ "dev": true,
1394+ "license": "MIT",
1395+ "dependencies": {
1396+ "ms": "^2.1.3"
1397+ },
1398+ "engines": {
1399+ "node": ">=6.0"
1400+ },
1401+ "peerDependenciesMeta": {
1402+ "supports-color": {
1403+ "optional": true
1404+ }
1405+ }
1406+ },
1407+ "node_modules/electron-to-chromium": {
1408+ "version": "1.5.379",
1409+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.379.tgz",
1410+ "integrity": "sha512-v/qV5aV5EUA2pGilzUCq5/eyOloZAqDZBu9UMBIzgPpLlprjSR6zswsWBTv0KpqxLGUAZEwhO95ZCt7srymNVA==",
1411+ "dev": true,
1412+ "license": "ISC"
1413+ },
1414+ "node_modules/esbuild": {
1415+ "version": "0.25.12",
1416+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
1417+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
1418+ "dev": true,
1419+ "hasInstallScript": true,
1420+ "license": "MIT",
1421+ "bin": {
1422+ "esbuild": "bin/esbuild"
1423+ },
1424+ "engines": {
1425+ "node": ">=18"
1426+ },
1427+ "optionalDependencies": {
1428+ "@esbuild/aix-ppc64": "0.25.12",
1429+ "@esbuild/android-arm": "0.25.12",
1430+ "@esbuild/android-arm64": "0.25.12",
1431+ "@esbuild/android-x64": "0.25.12",
1432+ "@esbuild/darwin-arm64": "0.25.12",
1433+ "@esbuild/darwin-x64": "0.25.12",
1434+ "@esbuild/freebsd-arm64": "0.25.12",
1435+ "@esbuild/freebsd-x64": "0.25.12",
1436+ "@esbuild/linux-arm": "0.25.12",
1437+ "@esbuild/linux-arm64": "0.25.12",
1438+ "@esbuild/linux-ia32": "0.25.12",
1439+ "@esbuild/linux-loong64": "0.25.12",
1440+ "@esbuild/linux-mips64el": "0.25.12",
1441+ "@esbuild/linux-ppc64": "0.25.12",
1442+ "@esbuild/linux-riscv64": "0.25.12",
1443+ "@esbuild/linux-s390x": "0.25.12",
1444+ "@esbuild/linux-x64": "0.25.12",
1445+ "@esbuild/netbsd-arm64": "0.25.12",
1446+ "@esbuild/netbsd-x64": "0.25.12",
1447+ "@esbuild/openbsd-arm64": "0.25.12",
1448+ "@esbuild/openbsd-x64": "0.25.12",
1449+ "@esbuild/openharmony-arm64": "0.25.12",
1450+ "@esbuild/sunos-x64": "0.25.12",
1451+ "@esbuild/win32-arm64": "0.25.12",
1452+ "@esbuild/win32-ia32": "0.25.12",
1453+ "@esbuild/win32-x64": "0.25.12"
1454+ }
1455+ },
1456+ "node_modules/escalade": {
1457+ "version": "3.2.0",
1458+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1459+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1460+ "dev": true,
1461+ "license": "MIT",
1462+ "engines": {
1463+ "node": ">=6"
1464+ }
1465+ },
1466+ "node_modules/fdir": {
1467+ "version": "6.5.0",
1468+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1469+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1470+ "dev": true,
1471+ "license": "MIT",
1472+ "engines": {
1473+ "node": ">=12.0.0"
1474+ },
1475+ "peerDependencies": {
1476+ "picomatch": "^3 || ^4"
1477+ },
1478+ "peerDependenciesMeta": {
1479+ "picomatch": {
1480+ "optional": true
1481+ }
1482+ }
1483+ },
1484+ "node_modules/fill-range": {
1485+ "version": "7.1.1",
1486+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
1487+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
1488+ "dev": true,
1489+ "license": "MIT",
1490+ "dependencies": {
1491+ "to-regex-range": "^5.0.1"
1492+ },
1493+ "engines": {
1494+ "node": ">=8"
1495+ }
1496+ },
1497+ "node_modules/fsevents": {
1498+ "version": "2.3.3",
1499+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1500+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1501+ "dev": true,
1502+ "hasInstallScript": true,
1503+ "license": "MIT",
1504+ "optional": true,
1505+ "os": [
1506+ "darwin"
1507+ ],
1508+ "engines": {
1509+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1510+ }
1511+ },
1512+ "node_modules/gensync": {
1513+ "version": "1.0.0-beta.2",
1514+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1515+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1516+ "dev": true,
1517+ "license": "MIT",
1518+ "engines": {
1519+ "node": ">=6.9.0"
1520+ }
1521+ },
1522+ "node_modules/is-number": {
1523+ "version": "7.0.0",
1524+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1525+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1526+ "dev": true,
1527+ "license": "MIT",
1528+ "engines": {
1529+ "node": ">=0.12.0"
1530+ }
1531+ },
1532+ "node_modules/js-tokens": {
1533+ "version": "4.0.0",
1534+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1535+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1536+ "license": "MIT"
1537+ },
1538+ "node_modules/jsesc": {
1539+ "version": "3.1.0",
1540+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1541+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1542+ "dev": true,
1543+ "license": "MIT",
1544+ "bin": {
1545+ "jsesc": "bin/jsesc"
1546+ },
1547+ "engines": {
1548+ "node": ">=6"
1549+ }
1550+ },
1551+ "node_modules/json5": {
1552+ "version": "2.2.3",
1553+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1554+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1555+ "dev": true,
1556+ "license": "MIT",
1557+ "bin": {
1558+ "json5": "lib/cli.js"
1559+ },
1560+ "engines": {
1561+ "node": ">=6"
1562+ }
1563+ },
1564+ "node_modules/loose-envify": {
1565+ "version": "1.4.0",
1566+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1567+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1568+ "license": "MIT",
1569+ "dependencies": {
1570+ "js-tokens": "^3.0.0 || ^4.0.0"
1571+ },
1572+ "bin": {
1573+ "loose-envify": "cli.js"
1574+ }
1575+ },
1576+ "node_modules/lru-cache": {
1577+ "version": "5.1.1",
1578+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1579+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1580+ "dev": true,
1581+ "license": "ISC",
1582+ "dependencies": {
1583+ "yallist": "^3.0.2"
1584+ }
1585+ },
1586+ "node_modules/micromatch": {
1587+ "version": "4.0.8",
1588+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
1589+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
1590+ "dev": true,
1591+ "license": "MIT",
1592+ "dependencies": {
1593+ "braces": "^3.0.3",
1594+ "picomatch": "^2.3.1"
1595+ },
1596+ "engines": {
1597+ "node": ">=8.6"
1598+ }
1599+ },
1600+ "node_modules/micromatch/node_modules/picomatch": {
1601+ "version": "2.3.2",
1602+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
1603+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
1604+ "dev": true,
1605+ "license": "MIT",
1606+ "engines": {
1607+ "node": ">=8.6"
1608+ },
1609+ "funding": {
1610+ "url": "https://github.com/sponsors/jonschlinkert"
1611+ }
1612+ },
1613+ "node_modules/ms": {
1614+ "version": "2.1.3",
1615+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1616+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1617+ "dev": true,
1618+ "license": "MIT"
1619+ },
1620+ "node_modules/nanoid": {
1621+ "version": "3.3.15",
1622+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
1623+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
1624+ "dev": true,
1625+ "funding": [
1626+ {
1627+ "type": "github",
1628+ "url": "https://github.com/sponsors/ai"
1629+ }
1630+ ],
1631+ "license": "MIT",
1632+ "bin": {
1633+ "nanoid": "bin/nanoid.cjs"
1634+ },
1635+ "engines": {
1636+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1637+ }
1638+ },
1639+ "node_modules/node-releases": {
1640+ "version": "2.0.50",
1641+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
1642+ "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
1643+ "dev": true,
1644+ "license": "MIT",
1645+ "engines": {
1646+ "node": ">=18"
1647+ }
1648+ },
1649+ "node_modules/picocolors": {
1650+ "version": "1.1.1",
1651+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1652+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1653+ "dev": true,
1654+ "license": "ISC"
1655+ },
1656+ "node_modules/picomatch": {
1657+ "version": "4.0.4",
1658+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
1659+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
1660+ "dev": true,
1661+ "license": "MIT",
1662+ "engines": {
1663+ "node": ">=12"
1664+ },
1665+ "funding": {
1666+ "url": "https://github.com/sponsors/jonschlinkert"
1667+ }
1668+ },
1669+ "node_modules/postcss": {
1670+ "version": "8.5.15",
1671+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
1672+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
1673+ "dev": true,
1674+ "funding": [
1675+ {
1676+ "type": "opencollective",
1677+ "url": "https://opencollective.com/postcss/"
1678+ },
1679+ {
1680+ "type": "tidelift",
1681+ "url": "https://tidelift.com/funding/github/npm/postcss"
1682+ },
1683+ {
1684+ "type": "github",
1685+ "url": "https://github.com/sponsors/ai"
1686+ }
1687+ ],
1688+ "license": "MIT",
1689+ "dependencies": {
1690+ "nanoid": "^3.3.12",
1691+ "picocolors": "^1.1.1",
1692+ "source-map-js": "^1.2.1"
1693+ },
1694+ "engines": {
1695+ "node": "^10 || ^12 || >=14"
1696+ }
1697+ },
1698+ "node_modules/react": {
1699+ "version": "18.3.1",
1700+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1701+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1702+ "license": "MIT",
1703+ "dependencies": {
1704+ "loose-envify": "^1.1.0"
1705+ },
1706+ "engines": {
1707+ "node": ">=0.10.0"
1708+ }
1709+ },
1710+ "node_modules/react-dom": {
1711+ "version": "18.3.1",
1712+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1713+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1714+ "license": "MIT",
1715+ "dependencies": {
1716+ "loose-envify": "^1.1.0",
1717+ "scheduler": "^0.23.2"
1718+ },
1719+ "peerDependencies": {
1720+ "react": "^18.3.1"
1721+ }
1722+ },
1723+ "node_modules/react-refresh": {
1724+ "version": "0.17.0",
1725+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1726+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1727+ "dev": true,
1728+ "license": "MIT",
1729+ "engines": {
1730+ "node": ">=0.10.0"
1731+ }
1732+ },
1733+ "node_modules/rollup": {
1734+ "version": "4.62.2",
1735+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
1736+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
1737+ "dev": true,
1738+ "license": "MIT",
1739+ "dependencies": {
1740+ "@types/estree": "1.0.9"
1741+ },
1742+ "bin": {
1743+ "rollup": "dist/bin/rollup"
1744+ },
1745+ "engines": {
1746+ "node": ">=18.0.0",
1747+ "npm": ">=8.0.0"
1748+ },
1749+ "optionalDependencies": {
1750+ "@rollup/rollup-android-arm-eabi": "4.62.2",
1751+ "@rollup/rollup-android-arm64": "4.62.2",
1752+ "@rollup/rollup-darwin-arm64": "4.62.2",
1753+ "@rollup/rollup-darwin-x64": "4.62.2",
1754+ "@rollup/rollup-freebsd-arm64": "4.62.2",
1755+ "@rollup/rollup-freebsd-x64": "4.62.2",
1756+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
1757+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
1758+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
1759+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
1760+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
1761+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
1762+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
1763+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
1764+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
1765+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
1766+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
1767+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
1768+ "@rollup/rollup-linux-x64-musl": "4.62.2",
1769+ "@rollup/rollup-openbsd-x64": "4.62.2",
1770+ "@rollup/rollup-openharmony-arm64": "4.62.2",
1771+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
1772+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
1773+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
1774+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
1775+ "fsevents": "~2.3.2"
1776+ }
1777+ },
1778+ "node_modules/scheduler": {
1779+ "version": "0.23.2",
1780+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
1781+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
1782+ "license": "MIT",
1783+ "dependencies": {
1784+ "loose-envify": "^1.1.0"
1785+ }
1786+ },
1787+ "node_modules/semver": {
1788+ "version": "6.3.1",
1789+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1790+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1791+ "dev": true,
1792+ "license": "ISC",
1793+ "bin": {
1794+ "semver": "bin/semver.js"
1795+ }
1796+ },
1797+ "node_modules/source-map-js": {
1798+ "version": "1.2.1",
1799+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1800+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1801+ "dev": true,
1802+ "license": "BSD-3-Clause",
1803+ "engines": {
1804+ "node": ">=0.10.0"
1805+ }
1806+ },
1807+ "node_modules/tinyglobby": {
1808+ "version": "0.2.17",
1809+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
1810+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
1811+ "dev": true,
1812+ "license": "MIT",
1813+ "dependencies": {
1814+ "fdir": "^6.5.0",
1815+ "picomatch": "^4.0.4"
1816+ },
1817+ "engines": {
1818+ "node": ">=12.0.0"
1819+ },
1820+ "funding": {
1821+ "url": "https://github.com/sponsors/SuperchupuDev"
1822+ }
1823+ },
1824+ "node_modules/to-regex-range": {
1825+ "version": "5.0.1",
1826+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1827+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1828+ "dev": true,
1829+ "license": "MIT",
1830+ "dependencies": {
1831+ "is-number": "^7.0.0"
1832+ },
1833+ "engines": {
1834+ "node": ">=8.0"
1835+ }
1836+ },
1837+ "node_modules/typescript": {
1838+ "version": "5.9.3",
1839+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1840+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1841+ "dev": true,
1842+ "license": "Apache-2.0",
1843+ "bin": {
1844+ "tsc": "bin/tsc",
1845+ "tsserver": "bin/tsserver"
1846+ },
1847+ "engines": {
1848+ "node": ">=14.17"
1849+ }
1850+ },
1851+ "node_modules/update-browserslist-db": {
1852+ "version": "1.2.3",
1853+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
1854+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
1855+ "dev": true,
1856+ "funding": [
1857+ {
1858+ "type": "opencollective",
1859+ "url": "https://opencollective.com/browserslist"
1860+ },
1861+ {
1862+ "type": "tidelift",
1863+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1864+ },
1865+ {
1866+ "type": "github",
1867+ "url": "https://github.com/sponsors/ai"
1868+ }
1869+ ],
1870+ "license": "MIT",
1871+ "dependencies": {
1872+ "escalade": "^3.2.0",
1873+ "picocolors": "^1.1.1"
1874+ },
1875+ "bin": {
1876+ "update-browserslist-db": "cli.js"
1877+ },
1878+ "peerDependencies": {
1879+ "browserslist": ">= 4.21.0"
1880+ }
1881+ },
1882+ "node_modules/vite": {
1883+ "version": "6.4.3",
1884+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
1885+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
1886+ "dev": true,
1887+ "license": "MIT",
1888+ "dependencies": {
1889+ "esbuild": "^0.25.0",
1890+ "fdir": "^6.4.4",
1891+ "picomatch": "^4.0.2",
1892+ "postcss": "^8.5.3",
1893+ "rollup": "^4.34.9",
1894+ "tinyglobby": "^0.2.13"
1895+ },
1896+ "bin": {
1897+ "vite": "bin/vite.js"
1898+ },
1899+ "engines": {
1900+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
1901+ },
1902+ "funding": {
1903+ "url": "https://github.com/vitejs/vite?sponsor=1"
1904+ },
1905+ "optionalDependencies": {
1906+ "fsevents": "~2.3.3"
1907+ },
1908+ "peerDependencies": {
1909+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
1910+ "jiti": ">=1.21.0",
1911+ "less": "*",
1912+ "lightningcss": "^1.21.0",
1913+ "sass": "*",
1914+ "sass-embedded": "*",
1915+ "stylus": "*",
1916+ "sugarss": "*",
1917+ "terser": "^5.16.0",
1918+ "tsx": "^4.8.1",
1919+ "yaml": "^2.4.2"
1920+ },
1921+ "peerDependenciesMeta": {
1922+ "@types/node": {
1923+ "optional": true
1924+ },
1925+ "jiti": {
1926+ "optional": true
1927+ },
1928+ "less": {
1929+ "optional": true
1930+ },
1931+ "lightningcss": {
1932+ "optional": true
1933+ },
1934+ "sass": {
1935+ "optional": true
1936+ },
1937+ "sass-embedded": {
1938+ "optional": true
1939+ },
1940+ "stylus": {
1941+ "optional": true
1942+ },
1943+ "sugarss": {
1944+ "optional": true
1945+ },
1946+ "terser": {
1947+ "optional": true
1948+ },
1949+ "tsx": {
1950+ "optional": true
1951+ },
1952+ "yaml": {
1953+ "optional": true
1954+ }
1955+ }
1956+ },
1957+ "node_modules/vite-plugin-singlefile": {
1958+ "version": "2.3.3",
1959+ "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.3.tgz",
1960+ "integrity": "sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==",
1961+ "dev": true,
1962+ "license": "MIT",
1963+ "dependencies": {
1964+ "micromatch": "^4.0.8"
1965+ },
1966+ "engines": {
1967+ "node": ">18.0.0"
1968+ },
1969+ "peerDependencies": {
1970+ "rollup": "^4.59.0",
1971+ "vite": "^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0"
1972+ },
1973+ "peerDependenciesMeta": {
1974+ "rollup": {
1975+ "optional": true
1976+ }
1977+ }
1978+ },
1979+ "node_modules/yallist": {
1980+ "version": "3.1.1",
1981+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
1982+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
1983+ "dev": true,
1984+ "license": "ISC"
1985+ }
1986+ }
1987+}
app/package.jsonadded+23−0View file
@@ -0,0 +1,23 @@
1+{
2+ "name": "walnuts-interactive-app",
3+ "private": true,
4+ "version": "0.0.0",
5+ "type": "module",
6+ "scripts": {
7+ "dev": "vite",
8+ "build": "vite build",
9+ "preview": "vite preview"
10+ },
11+ "dependencies": {
12+ "react": "^18.3.1",
13+ "react-dom": "^18.3.1"
14+ },
15+ "devDependencies": {
16+ "@types/react": "^18.3.12",
17+ "@types/react-dom": "^18.3.1",
18+ "@vitejs/plugin-react": "^4.3.4",
19+ "typescript": "^5.6.3",
20+ "vite": "^6.0.7",
21+ "vite-plugin-singlefile": "^2.1.0"
22+ }
23+}
app/src/App.tsxadded+327−0View file
@@ -0,0 +1,327 @@
1+import { useEffect, useState, type CSSProperties } from "react";
2+import {
3+ DensityView,
4+ type Points,
5+ type DensityGrid,
6+ type OrbitPath,
7+ type Pt,
8+} from "./render/DensityView.js";
9+import { onData, onHostEvent, sendToMATLAB } from "./bridge.js";
10+
11+/** Payload from the numbl script: the target density (for the heatmap) plus the
12+ * WALNUTS samples. Mirrors what walnuts_sampler.m sends. */
13+interface WalnutsData {
14+ type: "walnuts";
15+ density: DensityGrid;
16+ samples: Points;
17+ n: number;
18+ dt: number;
19+ maxError: number;
20+}
21+
22+function isWalnutsData(d: unknown): d is WalnutsData {
23+ return (
24+ !!d &&
25+ typeof d === "object" &&
26+ (d as WalnutsData).type === "walnuts" &&
27+ !!(d as WalnutsData).density &&
28+ !!(d as WalnutsData).samples
29+ );
30+}
31+
32+interface SamplesEvent {
33+ x: number[];
34+ y: number[];
35+ n: number;
36+ dt: number;
37+ maxError: number;
38+}
39+
40+/** One recorded transition's orbit (from `walnuts(..., record=true)`). */
41+interface MovieStep {
42+ px: number[];
43+ py: number[];
44+ seg: number[];
45+ startX: number;
46+ startY: number;
47+ selX: number;
48+ selY: number;
49+}
50+
51+const SAMPLE_CHOICES = [100, 300, 1000, 3000];
52+const DEFAULT_N = 1000;
53+const DT_MIN = 0.05;
54+const DT_MAX = 1.2;
55+const ERR_MIN = 0.1;
56+const ERR_MAX = 4;
57+
58+// Movie pacing: reveal a couple of leapfrog points per tick, then linger on the
59+// selected draw before the next transition.
60+const MOVIE_TICK_MS = 55;
61+const MOVIE_REVEAL = 2;
62+const MOVIE_HOLD = 14; // extra k-units to hold the selected point
63+
64+export function App() {
65+ const [data, setData] = useState<WalnutsData | null>(null);
66+ const [n, setN] = useState(DEFAULT_N);
67+ const [dt, setDt] = useState(0.4);
68+ const [maxError, setMaxError] = useState(0.8);
69+ const [busy, setBusy] = useState(false);
70+ const [movieData, setMovieData] = useState<MovieStep[] | null>(null);
71+ const [movie, setMovie] = useState<{ si: number; k: number } | null>(null);
72+
73+ useEffect(() => {
74+ const offData = onData(d => {
75+ if (isWalnutsData(d)) {
76+ setData(d);
77+ setN(d.n);
78+ setDt(d.dt);
79+ setMaxError(d.maxError);
80+ }
81+ });
82+ // Resample: same target, new draws.
83+ const offSamples = onHostEvent("samples", ev => {
84+ const s = ev as SamplesEvent;
85+ if (!s || !Array.isArray(s.x)) return;
86+ setData(prev =>
87+ prev
88+ ? { ...prev, samples: { x: s.x, y: s.y }, n: s.n, dt: s.dt, maxError: s.maxError }
89+ : prev
90+ );
91+ setBusy(false);
92+ });
93+ // Movie: an array of recorded transition orbits to animate.
94+ const offMovie = onHostEvent("movie", ev => {
95+ if (!Array.isArray(ev) || ev.length === 0) {
96+ setBusy(false);
97+ return;
98+ }
99+ setMovieData(ev as MovieStep[]);
100+ setMovie({ si: 0, k: 0 });
101+ setBusy(false);
102+ });
103+ return () => {
104+ offData();
105+ offSamples();
106+ offMovie();
107+ };
108+ }, []);
109+
110+ // Movie clock.
111+ useEffect(() => {
112+ if (!movie || !movieData) return;
113+ const id = setTimeout(() => {
114+ setMovie(m => {
115+ if (!m) return m;
116+ const step = movieData[m.si];
117+ const count = step.px.length;
118+ if (m.k < count + MOVIE_HOLD) return { si: m.si, k: m.k + MOVIE_REVEAL };
119+ const nextSi = m.si + 1;
120+ return nextSi >= movieData.length ? null : { si: nextSi, k: 0 };
121+ });
122+ }, MOVIE_TICK_MS);
123+ return () => clearTimeout(id);
124+ }, [movie, movieData]);
125+
126+ const stopMovie = () => {
127+ setMovie(null);
128+ setMovieData(null);
129+ };
130+
131+ const resample = (count: number, step: number, err: number) => {
132+ if (!data || busy) return;
133+ stopMovie();
134+ setBusy(true);
135+ sendToMATLAB("resample", { n: count, dt: step, maxError: err });
136+ };
137+
138+ const playMovie = () => {
139+ if (movie) {
140+ stopMovie();
141+ return;
142+ }
143+ if (!data || busy) return;
144+ setBusy(true); // until the trajectory arrives
145+ sendToMATLAB("movie", { dt, maxError });
146+ };
147+
148+ // ── derive the movie overlay for the current frame ──
149+ let cloud: Points = data ? data.samples : { x: [], y: [] };
150+ let orbit: OrbitPath | null = null;
151+ let start: Pt | null = null;
152+ let lead: Pt | null = null;
153+ let chainPts: Points | null = null;
154+ let selected: Pt | null = null;
155+ if (movie && movieData) {
156+ const step = movieData[movie.si];
157+ const count = step.px.length;
158+ const k = Math.min(movie.k, count);
159+ orbit = { x: step.px.slice(0, k), y: step.py.slice(0, k), seg: step.seg.slice(0, k) };
160+ start = { x: step.startX, y: step.startY };
161+ if (k > 0) lead = { x: step.px[k - 1], y: step.py[k - 1] };
162+ const cx: number[] = [];
163+ const cy: number[] = [];
164+ for (let j = 0; j < movie.si; j++) {
165+ cx.push(movieData[j].selX);
166+ cy.push(movieData[j].selY);
167+ }
168+ chainPts = { x: cx, y: cy };
169+ if (movie.k >= count) selected = { x: step.selX, y: step.selY };
170+ cloud = data ? data.samples : { x: [], y: [] };
171+ }
172+
173+ const controlsDisabled = !data || busy || !!movie;
174+ const status = busy
175+ ? "sampling…"
176+ : movie && movieData
177+ ? `movie · transition ${movie.si + 1}/${movieData.length}`
178+ : `${data ? data.n.toLocaleString() : "—"} samples`;
179+
180+ return (
181+ <div style={rootStyle}>
182+ {data ? (
183+ <DensityView
184+ density={data.density}
185+ samples={cloud}
186+ orbit={orbit}
187+ start={start}
188+ lead={lead}
189+ chainPts={chainPts}
190+ selected={selected}
191+ />
192+ ) : (
193+ <div style={waitingStyle}>Waiting for samples from the script…</div>
194+ )}
195+
196+ <div style={panelStyle}>
197+ <div style={{ fontWeight: 600, marginBottom: 4 }}>WALNUTS</div>
198+ <div style={{ fontSize: 11, color: "#475569", marginBottom: 8 }}>
199+ sampling a banana target
200+ </div>
201+
202+ <label style={labelStyle}>
203+ Samples: <b>{n.toLocaleString()}</b>
204+ <input
205+ type="range"
206+ min={0}
207+ max={SAMPLE_CHOICES.length - 1}
208+ step={1}
209+ value={Math.max(0, SAMPLE_CHOICES.indexOf(n))}
210+ disabled={controlsDisabled}
211+ onChange={e => setN(SAMPLE_CHOICES[Number(e.target.value)])}
212+ onPointerUp={e =>
213+ resample(SAMPLE_CHOICES[Number(e.currentTarget.value)], dt, maxError)
214+ }
215+ style={sliderStyle}
216+ />
217+ </label>
218+
219+ <label style={labelStyle}>
220+ Leapfrog Δt: <b>{dt.toFixed(2)}</b>
221+ <input
222+ type="range"
223+ min={DT_MIN}
224+ max={DT_MAX}
225+ step={0.05}
226+ value={dt}
227+ disabled={controlsDisabled}
228+ onChange={e => setDt(Number(e.target.value))}
229+ onPointerUp={e => resample(n, Number(e.currentTarget.value), maxError)}
230+ style={sliderStyle}
231+ />
232+ </label>
233+
234+ <label style={labelStyle}>
235+ Max error: <b>{maxError.toFixed(2)}</b>
236+ <input
237+ type="range"
238+ min={ERR_MIN}
239+ max={ERR_MAX}
240+ step={0.1}
241+ value={maxError}
242+ disabled={controlsDisabled}
243+ onChange={e => setMaxError(Number(e.target.value))}
244+ onPointerUp={e => resample(n, dt, Number(e.currentTarget.value))}
245+ style={sliderStyle}
246+ />
247+ </label>
248+
249+ <div style={{ display: "flex", gap: 6, marginTop: 8 }}>
250+ <button
251+ style={btnStyle}
252+ disabled={controlsDisabled}
253+ onClick={() => resample(n, dt, maxError)}
254+ title="Draw a fresh chain with these settings"
255+ >
256+ Resample
257+ </button>
258+ <button
259+ style={btnStyle}
260+ disabled={!data || busy}
261+ onClick={playMovie}
262+ title="Animate WALNUTS building orbits step by step"
263+ >
264+ {movie ? "■ Stop" : "▶ Movie"}
265+ </button>
266+ </div>
267+
268+ <div style={{ fontSize: 10, color: "#64748b", marginTop: 8 }}>
269+ {status}
270+ </div>
271+ </div>
272+ </div>
273+ );
274+}
275+
276+const rootStyle: CSSProperties = {
277+ position: "absolute",
278+ inset: 0,
279+ overflow: "hidden",
280+ background: "#ffffff",
281+ fontFamily: "system-ui, -apple-system, Arial, sans-serif",
282+};
283+
284+const waitingStyle: CSSProperties = {
285+ position: "absolute",
286+ inset: 0,
287+ display: "flex",
288+ alignItems: "center",
289+ justifyContent: "center",
290+ color: "#94a3b8",
291+};
292+
293+const panelStyle: CSSProperties = {
294+ position: "absolute",
295+ top: 8,
296+ left: 8,
297+ width: 168,
298+ padding: "8px 10px",
299+ background: "rgba(255,255,255,0.92)",
300+ border: "1px solid #e2e8f0",
301+ borderRadius: 6,
302+ boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
303+ color: "#0f172a",
304+};
305+
306+const labelStyle: CSSProperties = {
307+ display: "block",
308+ fontSize: 11,
309+ marginTop: 6,
310+};
311+
312+const sliderStyle: CSSProperties = {
313+ width: "100%",
314+ marginTop: 2,
315+};
316+
317+const btnStyle: CSSProperties = {
318+ flex: 1,
319+ padding: "4px 6px",
320+ fontSize: 11,
321+ whiteSpace: "nowrap",
322+ cursor: "pointer",
323+ background: "#f8fafc",
324+ border: "1px solid #cbd5e1",
325+ borderRadius: 5,
326+ color: "#0f172a",
327+};
app/src/bridge.tsadded+71−0View file
@@ -0,0 +1,71 @@
1+// Bridge to the numbl/MATLAB `uihtml` host. The host calls a global
2+// `setup(htmlComponent)` once the page loads; we expose small subscribe/send
3+// helpers so React components don't race that callback (latest Data is buffered,
4+// and host-event listeners attach whenever the component becomes available).
5+
6+interface HtmlComponent {
7+ Data: unknown;
8+ addEventListener(name: string, fn: (e: { Data?: unknown }) => void): void;
9+ sendEventToMATLAB(name: string, data: unknown): void;
10+}
11+
12+type DataListener = (data: unknown) => void;
13+type EventListener = (data: unknown) => void;
14+
15+let htmlComponent: HtmlComponent | null = null;
16+let latestData: unknown = undefined;
17+const dataListeners = new Set<DataListener>();
18+const eventListeners = new Map<string, Set<EventListener>>();
19+const attachedNames = new Set<string>();
20+
21+function deliverData(d: unknown): void {
22+ latestData = d;
23+ dataListeners.forEach(fn => fn(d));
24+}
25+
26+/** Attach a single host listener for `name` that fans out to our listener set. */
27+function attach(name: string): void {
28+ if (!htmlComponent || attachedNames.has(name)) return;
29+ attachedNames.add(name);
30+ htmlComponent.addEventListener(name, e => {
31+ const fns = eventListeners.get(name);
32+ if (fns) fns.forEach(fn => fn(e?.Data));
33+ });
34+}
35+
36+// Register the global the host calls after the page loads.
37+(window as unknown as { setup: (hc: HtmlComponent) => void }).setup = hc => {
38+ htmlComponent = hc;
39+ hc.addEventListener("DataChanged", () => deliverData(hc.Data));
40+ for (const name of eventListeners.keys()) attach(name);
41+ if (hc.Data != null) deliverData(hc.Data);
42+};
43+
44+/** Subscribe to the `Data` channel (script → page). Fires immediately with the
45+ * latest data if it has already arrived. */
46+export function onData(fn: DataListener): () => void {
47+ dataListeners.add(fn);
48+ if (latestData !== undefined) fn(latestData);
49+ return () => {
50+ dataListeners.delete(fn);
51+ };
52+}
53+
54+/** Subscribe to a named host event (from MATLAB `sendEventToHTMLSource`). */
55+export function onHostEvent(name: string, fn: EventListener): () => void {
56+ let set = eventListeners.get(name);
57+ if (!set) {
58+ set = new Set();
59+ eventListeners.set(name, set);
60+ }
61+ set.add(fn);
62+ attach(name);
63+ return () => {
64+ set!.delete(fn);
65+ };
66+}
67+
68+/** Send an event back to the interpreter (page → script). */
69+export function sendToMATLAB(name: string, data: unknown): void {
70+ htmlComponent?.sendEventToMATLAB(name, data);
71+}
app/src/main.tsxadded+12−0View file
@@ -0,0 +1,12 @@
1+import { StrictMode } from "react";
2+import { createRoot } from "react-dom/client";
3+// Import the bridge first so `window.setup` is defined before the host's
4+// bootstrap calls it (it defers to DOMContentLoaded, after module scripts run).
5+import "./bridge.js";
6+import { App } from "./App.js";
7+
8+createRoot(document.getElementById("root")!).render(
9+ <StrictMode>
10+ <App />
11+ </StrictMode>
12+);
app/src/render/DensityView.tsxadded+228−0View file
@@ -0,0 +1,228 @@
1+import { useEffect, useRef, type CSSProperties } from "react";
2+
3+export interface Points {
4+ x: number[];
5+ y: number[];
6+}
7+
8+export interface Pt {
9+ x: number;
10+ y: number;
11+}
12+
13+/** A recorded orbit path: positions x/y with a per-point macro-step id `seg`,
14+ * so the polyline breaks where the orbit direction flips. */
15+export interface OrbitPath {
16+ x: number[];
17+ y: number[];
18+ seg: number[];
19+}
20+
21+export interface DensityGrid {
22+ /** Row-major log-density: index (iy)*nx + ix, iy from ymin (0) to ymax. */
23+ values: number[];
24+ nx: number;
25+ ny: number;
26+ xmin: number;
27+ xmax: number;
28+ ymin: number;
29+ ymax: number;
30+}
31+
32+interface DensityViewProps {
33+ density: DensityGrid;
34+ samples: Points;
35+ // ── movie overlay (all optional) ──
36+ orbit?: OrbitPath | null; // revealed orbit path of the current transition
37+ start?: Pt | null; // start point of the current transition (ringed)
38+ lead?: Pt | null; // current frontier point of the revealed path
39+ chainPts?: Points | null; // accepted draws so far (the Markov chain)
40+ selected?: Pt | null; // the just-selected draw (circled)
41+}
42+
43+const DOT = "rgba(15, 23, 42, 0.5)";
44+const DOT_RADIUS = 1.5;
45+const MARGIN = 16;
46+// Heatmap colour ramp: white (low density) → blue (high density).
47+const LO: [number, number, number] = [255, 255, 255];
48+const HI: [number, number, number] = [37, 99, 235];
49+const ORBIT = "#f59e0b"; // amber: the leapfrog orbit path
50+const START = "#334155"; // slate ring: where the transition started
51+const SELECTED = "#0f172a"; // near-black: the selected draw (circled)
52+
53+/** Renders the target density as a heatmap with samples scattered on top, plus
54+ * an optional orbit overlay for the step-by-step movie. Fits the density's
55+ * world bounds to the canvas (aspect preserved, y up). DPR-aware; redraws on
56+ * data change and resize. */
57+export function DensityView({
58+ density,
59+ samples,
60+ orbit,
61+ start,
62+ lead,
63+ chainPts,
64+ selected,
65+}: DensityViewProps) {
66+ const canvasRef = useRef<HTMLCanvasElement>(null);
67+ const containerRef = useRef<HTMLDivElement>(null);
68+
69+ useEffect(() => {
70+ const canvas = canvasRef.current;
71+ const container = containerRef.current;
72+ if (!canvas || !container) return;
73+
74+ const draw = () => {
75+ const ctx = canvas.getContext("2d");
76+ if (!ctx) return;
77+
78+ const dpr = window.devicePixelRatio || 1;
79+ const cssW = container.clientWidth;
80+ const cssH = container.clientHeight;
81+ if (cssW === 0 || cssH === 0) return;
82+
83+ canvas.width = Math.round(cssW * dpr);
84+ canvas.height = Math.round(cssH * dpr);
85+ canvas.style.width = `${cssW}px`;
86+ canvas.style.height = `${cssH}px`;
87+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
88+ ctx.clearRect(0, 0, cssW, cssH);
89+
90+ const { nx, ny, xmin, xmax, ymin, ymax, values } = density;
91+ if (!nx || !ny) return;
92+
93+ const worldW = xmax - xmin || 1;
94+ const worldH = ymax - ymin || 1;
95+ const scale = Math.min(
96+ (cssW - 2 * MARGIN) / worldW,
97+ (cssH - 2 * MARGIN) / worldH
98+ );
99+ const destW = worldW * scale;
100+ const destH = worldH * scale;
101+ const destX = (cssW - destW) / 2;
102+ const destY = (cssH - destH) / 2;
103+ const toPx = (x: number) => destX + (x - xmin) * scale;
104+ const toPy = (y: number) => destY + (ymax - y) * scale; // y up
105+
106+ // Heatmap: paint the grid into an offscreen nx×ny image, then scale it in.
107+ let maxLp = -Infinity;
108+ for (let i = 0; i < values.length; i++) {
109+ if (values[i] > maxLp) maxLp = values[i];
110+ }
111+ const off = document.createElement("canvas");
112+ off.width = nx;
113+ off.height = ny;
114+ const offCtx = off.getContext("2d");
115+ if (offCtx) {
116+ const img = offCtx.createImageData(nx, ny);
117+ for (let r = 0; r < ny; r++) {
118+ const iy = ny - 1 - r; // image row 0 = top = ymax
119+ for (let c = 0; c < nx; c++) {
120+ const t = Math.pow(Math.exp(values[iy * nx + c] - maxLp), 0.4);
121+ const p = (r * nx + c) * 4;
122+ img.data[p] = LO[0] + t * (HI[0] - LO[0]);
123+ img.data[p + 1] = LO[1] + t * (HI[1] - LO[1]);
124+ img.data[p + 2] = LO[2] + t * (HI[2] - LO[2]);
125+ img.data[p + 3] = 255;
126+ }
127+ }
128+ offCtx.putImageData(img, 0, 0);
129+ ctx.imageSmoothingEnabled = true;
130+ ctx.drawImage(off, destX, destY, destW, destH);
131+ }
132+
133+ ctx.strokeStyle = "rgba(15,23,42,0.15)";
134+ ctx.lineWidth = 1;
135+ ctx.strokeRect(destX, destY, destW, destH);
136+
137+ // Sample cloud (dimmed when the movie overlay is active).
138+ ctx.fillStyle = orbit || chainPts ? "rgba(15,23,42,0.18)" : DOT;
139+ const n = Math.min(samples.x.length, samples.y.length);
140+ for (let i = 0; i < n; i++) {
141+ ctx.beginPath();
142+ ctx.arc(toPx(samples.x[i]), toPy(samples.y[i]), DOT_RADIUS, 0, 2 * Math.PI);
143+ ctx.fill();
144+ }
145+
146+ // Accepted draws so far (the Markov chain).
147+ if (chainPts) {
148+ ctx.fillStyle = SELECTED;
149+ const m = Math.min(chainPts.x.length, chainPts.y.length);
150+ for (let i = 0; i < m; i++) {
151+ ctx.beginPath();
152+ ctx.arc(toPx(chainPts.x[i]), toPy(chainPts.y[i]), 2, 0, 2 * Math.PI);
153+ ctx.fill();
154+ }
155+ }
156+
157+ // The orbit being traced: polyline (broken at segment changes) + a dot at
158+ // each leapfrog step, so the discrete steps and step-size are visible.
159+ if (orbit && orbit.x.length > 0) {
160+ ctx.strokeStyle = ORBIT;
161+ ctx.lineWidth = 1.5;
162+ ctx.beginPath();
163+ for (let i = 0; i < orbit.x.length; i++) {
164+ const px = toPx(orbit.x[i]);
165+ const py = toPy(orbit.y[i]);
166+ if (i === 0 || orbit.seg[i] !== orbit.seg[i - 1]) ctx.moveTo(px, py);
167+ else ctx.lineTo(px, py);
168+ }
169+ ctx.stroke();
170+ ctx.fillStyle = ORBIT;
171+ for (let i = 0; i < orbit.x.length; i++) {
172+ ctx.beginPath();
173+ ctx.arc(toPx(orbit.x[i]), toPy(orbit.y[i]), 1.7, 0, 2 * Math.PI);
174+ ctx.fill();
175+ }
176+ }
177+
178+ // Start of the current transition (open ring).
179+ if (start) {
180+ ctx.strokeStyle = START;
181+ ctx.lineWidth = 1.5;
182+ ctx.beginPath();
183+ ctx.arc(toPx(start.x), toPy(start.y), 4, 0, 2 * Math.PI);
184+ ctx.stroke();
185+ }
186+
187+ // Current frontier of the orbit.
188+ if (lead) {
189+ ctx.fillStyle = ORBIT;
190+ ctx.beginPath();
191+ ctx.arc(toPx(lead.x), toPy(lead.y), 3, 0, 2 * Math.PI);
192+ ctx.fill();
193+ }
194+
195+ // The selected draw: a circled black point.
196+ if (selected) {
197+ const cx = toPx(selected.x);
198+ const cy = toPy(selected.y);
199+ ctx.strokeStyle = SELECTED;
200+ ctx.lineWidth = 1.5;
201+ ctx.beginPath();
202+ ctx.arc(cx, cy, 6.5, 0, 2 * Math.PI);
203+ ctx.stroke();
204+ ctx.fillStyle = SELECTED;
205+ ctx.beginPath();
206+ ctx.arc(cx, cy, 3.2, 0, 2 * Math.PI);
207+ ctx.fill();
208+ }
209+ };
210+
211+ draw();
212+ const ro = new ResizeObserver(draw);
213+ ro.observe(container);
214+ return () => ro.disconnect();
215+ }, [density, samples, orbit, start, lead, chainPts, selected]);
216+
217+ return (
218+ <div ref={containerRef} style={containerStyle}>
219+ <canvas ref={canvasRef} style={{ display: "block" }} />
220+ </div>
221+ );
222+}
223+
224+const containerStyle: CSSProperties = {
225+ position: "absolute",
226+ inset: 0,
227+ overflow: "hidden",
228+};
app/tsconfig.jsonadded+20−0View file
@@ -0,0 +1,20 @@
1+{
2+ "compilerOptions": {
3+ "target": "ESNext",
4+ "useDefineForClassFields": true,
5+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
6+ "module": "ESNext",
7+ "skipLibCheck": true,
8+ "moduleResolution": "bundler",
9+ "allowImportingTsExtensions": true,
10+ "resolveJsonModule": true,
11+ "isolatedModules": true,
12+ "noEmit": true,
13+ "jsx": "react-jsx",
14+ "strict": true,
15+ "noUnusedLocals": false,
16+ "noUnusedParameters": false,
17+ "noFallthroughCasesInSwitch": true
18+ },
19+ "include": ["src"]
20+}
app/vite.config.tsadded+16−0View file
@@ -0,0 +1,16 @@
1+import { defineConfig } from "vite";
2+import react from "@vitejs/plugin-react";
3+import { viteSingleFile } from "vite-plugin-singlefile";
4+
5+// Builds the whole app (JS + CSS) inlined into a single dist/index.html, so the
6+// numbl `.m` can read it with `fileread` and pass it to uihtml (HTMLSource) —
7+// no supporting files, works in numbl (browser and CLI) and real MATLAB.
8+export default defineConfig({
9+ plugins: [react(), viteSingleFile()],
10+ build: {
11+ target: "esnext",
12+ cssCodeSplit: false,
13+ assetsInlineLimit: 100_000_000,
14+ chunkSizeWarningLimit: 100_000_000,
15+ },
16+});
helpers/grad_log_density.madded+18−0View file
@@ -0,0 +1,18 @@
1+function g = grad_log_density(theta)
2+%GRAD_LOG_DENSITY Gradient of the banana log density (see log_density.m).
3+a = 2;
4+b = 0.2;
5+x1 = theta(1);
6+x2 = theta(2);
7+y1 = x1 / a;
8+y2 = x2 * a + a * b * (x1.^2 + a.^2);
9+d1 = y1 - 0;
10+d2 = y2 - 4;
11+% Gradient w.r.t. y of the Gaussian log density is -inv(Sigma)*d.
12+gy1 = -((4 / 3) * d1 - (2 / 3) * d2);
13+gy2 = -(-(2 / 3) * d1 + (4 / 3) * d2);
14+% Chain rule back through the twist y(x).
15+gx1 = gy1 / a + gy2 * a * b * 2 * x1;
16+gx2 = gy2 * a;
17+g = [gx1; gx2];
18+end
helpers/log_density.madded+19−0View file
@@ -0,0 +1,19 @@
1+function lp = log_density(theta)
2+%LOG_DENSITY Unnormalized log density of the "banana" target.
3+% The standard banana from chi-feng's mcmc-demo: a correlated Gaussian
4+% (mean [0; 4], covariance [1 0.5; 0.5 1]) pulled into a banana shape by a
5+% quadratic twist. theta is a 2x1 column vector. The normalizing constant is
6+% dropped (irrelevant for sampling and for the heatmap).
7+a = 2;
8+b = 0.2;
9+x1 = theta(1);
10+x2 = theta(2);
11+% Twist x -> y, then evaluate the Gaussian at y.
12+y1 = x1 / a;
13+y2 = x2 * a + a * b * (x1.^2 + a.^2);
14+d1 = y1 - 0;
15+d2 = y2 - 4;
16+% Q = d' * inv(Sigma) * d, with inv([1 .5; .5 1]) = [4/3 -2/3; -2/3 4/3].
17+Q = (4 / 3) * (d1.^2 + d2.^2) - (4 / 3) * d1 .* d2;
18+lp = -0.5 * Q;
19+end
helpers/walnuts.madded+275−0View file
@@ -0,0 +1,275 @@
1+function [chain, traj] = walnuts(theta0, n_samples, dt, max_error, record)
2+%WALNUTS Within-orbit adaptive No-U-Turn Sampler.
3+% CHAIN = WALNUTS(THETA0, N_SAMPLES, DT, MAX_ERROR) runs N_SAMPLES WALNUTS
4+% transitions starting from column vector THETA0 and returns CHAIN, a
5+% dim-by-N_SAMPLES matrix of draws. DT is the base leapfrog step and MAX_ERROR
6+% the per-macro-step energy-error tolerance that drives the within-orbit step
7+% halving.
8+%
9+% [CHAIN, TRAJ] = WALNUTS(..., true) additionally records, per transition, the
10+% leapfrog path of the orbit it builds (for the figure's step-by-step movie).
11+% TRAJ{i} has fields px/py (orbit positions), seg (per-point macro-step id, so
12+% the path breaks at direction flips), startX/startY and selX/selY. Recording
13+% adds overhead, so leave it off for plain sampling.
14+%
15+% This is a direct port of Brian Ward's JavaScript implementation in
16+% chi-feng/mcmc-demo (algorithms/WALNUTS.js), itself based on Bob Carpenter's
17+% C++ (flatironinstitute/walnuts) for the paper arXiv:2506.18746. The target
18+% density is supplied by log_density.m / grad_log_density.m on the path.
19+%
20+% A Span carries the two orbit endpoints (backward "_bk", forward "_fw"),
21+% each with position/momentum/gradient/joint-logp, plus a selected draw
22+% theta_select and the log of the summed orbit weight (logp).
23+
24+if nargin < 5 || isempty(record)
25+ record = false;
26+end
27+% Trajectory recorder (used only when record is true). macro_step appends each
28+% committed leapfrog path to these as it grows the orbit.
29+global WREC_ON WREC_X WREC_Y WREC_SEG WREC_SEGID
30+WREC_ON = record;
31+
32+dim = numel(theta0);
33+chain = zeros(dim, n_samples);
34+traj = {};
35+theta = theta0;
36+for i = 1:n_samples
37+ if record
38+ start_pt = theta;
39+ WREC_X = [];
40+ WREC_Y = [];
41+ WREC_SEG = [];
42+ WREC_SEGID = 0;
43+ end
44+ theta = transition(theta, dt, max_error);
45+ chain(:, i) = theta;
46+ if record
47+ traj{i} = struct('px', WREC_X, 'py', WREC_Y, 'seg', WREC_SEG, ...
48+ 'startX', start_pt(1), 'startY', start_pt(2), ...
49+ 'selX', theta(1), 'selY', theta(2));
50+ end
51+end
52+WREC_ON = false;
53+end
54+
55+function theta_select = transition(theta, dt, max_error)
56+% One WALNUTS transition: sample momentum, grow the orbit by repeated doubling
57+% in random directions until a U-turn (or a failed step / max depth), choosing
58+% the draw by a Metropolis update against the growing orbit.
59+rho = randn(numel(theta), 1);
60+grad = grad_log_density(theta);
61+logp = log_density(theta) - sum(rho.^2) / 2;
62+span_accum = make_leaf_span(theta, rho, grad, logp);
63+
64+for depth = 0:11
65+ if rand < 0.5
66+ direction = -1;
67+ else
68+ direction = 1;
69+ end
70+ [ok, next_span] = build_span(span_accum, direction, depth, dt, max_error);
71+ if ~ok
72+ break
73+ end
74+ combined_uturn = uturn(span_accum, next_span, direction);
75+ % Top-level selection is a Metropolis update (use_barker = false).
76+ span_accum = combine(span_accum, next_span, false, direction);
77+ if combined_uturn
78+ break
79+ end
80+end
81+theta_select = span_accum.theta_select;
82+end
83+
84+function [ok, span] = build_span(span_in, direction, depth, dt, max_error)
85+% Recursively build a balanced orbit of 2^depth macro-steps. Returns ok=false
86+% if any macro-step fails or a sub-orbit U-turns.
87+if depth == 0
88+ [ok, span] = build_leaf(span_in, direction, dt, max_error);
89+ return
90+end
91+[ok, left] = build_span(span_in, direction, depth - 1, dt, max_error);
92+if ~ok
93+ span = left;
94+ return
95+end
96+[ok, right] = build_span(left, direction, depth - 1, dt, max_error);
97+if ~ok
98+ span = right;
99+ return
100+end
101+if uturn(left, right, direction)
102+ ok = false;
103+ span = left;
104+ return
105+end
106+% Sub-orbit selection is a Barker update (use_barker = true).
107+span = combine(left, right, true, direction);
108+ok = true;
109+end
110+
111+function [ok, span] = build_leaf(span_in, direction, dt, max_error)
112+[success, theta_next, rho_next, grad_next, logp_next] = ...
113+ macro_step(span_in, direction, dt, max_error);
114+if ~success
115+ ok = false;
116+ span = span_in;
117+ return
118+end
119+span = make_leaf_span(theta_next, rho_next, grad_next, logp_next);
120+ok = true;
121+end
122+
123+function [success, theta_next, rho_next, grad_next, logp_next] = ...
124+ macro_step(span, direction, dt, max_error)
125+% Take one macro-step off the orbit's leading endpoint, adaptively halving the
126+% leapfrog step (and doubling the count) until the energy error is within
127+% tolerance, then check the choice is reversible.
128+global WREC_ON WREC_X WREC_Y WREC_SEG WREC_SEGID
129+if direction == 1
130+ theta = span.theta_fw;
131+ rho = span.rho_fw;
132+ grad = span.grad_fw;
133+ logp = span.logp_fw;
134+ step = dt;
135+else
136+ theta = span.theta_bk;
137+ rho = span.rho_bk;
138+ grad = span.grad_bk;
139+ logp = span.logp_bk;
140+ step = -dt;
141+end
142+
143+num_steps = 1;
144+for halvings = 0:9
145+ theta_next = theta;
146+ rho_next = rho;
147+ grad_next = grad;
148+ [theta_next, rho_next, grad_next] = leapfrog(theta_next, rho_next, grad_next, step, num_steps);
149+ logp_next = log_density(theta_next) - sum(rho_next.^2) / 2;
150+ if abs(logp - logp_next) <= max_error
151+ success = reversible(step, num_steps, theta_next, rho_next, grad_next, logp_next, max_error);
152+ if success && WREC_ON
153+ % Record this committed macro-step's leapfrog path for the movie.
154+ path = leapfrog_capture(theta, rho, grad, step, num_steps);
155+ WREC_SEGID = WREC_SEGID + 1;
156+ WREC_X = [WREC_X, theta(1), path(1, :)];
157+ WREC_Y = [WREC_Y, theta(2), path(2, :)];
158+ WREC_SEG = [WREC_SEG, WREC_SEGID * ones(1, num_steps + 1)];
159+ end
160+ return
161+ end
162+ num_steps = num_steps * 2;
163+ step = step * 0.5;
164+end
165+% No step count met the tolerance.
166+success = false;
167+theta_next = theta;
168+rho_next = rho;
169+grad_next = grad;
170+logp_next = logp;
171+end
172+
173+function [theta, rho, grad] = leapfrog(theta, rho, grad, step, num_steps)
174+half_step = 0.5 * step;
175+for n = 1:num_steps
176+ rho = rho + half_step * grad;
177+ theta = theta + step * rho;
178+ grad = grad_log_density(theta);
179+ rho = rho + half_step * grad;
180+end
181+end
182+
183+function path = leapfrog_capture(theta, rho, grad, step, num_steps)
184+% Re-run a committed macro-step, returning the position after each leapfrog
185+% step (dim-by-num_steps). Used only while recording the movie trajectory.
186+half_step = 0.5 * step;
187+path = zeros(numel(theta), num_steps);
188+for n = 1:num_steps
189+ rho = rho + half_step * grad;
190+ theta = theta + step * rho;
191+ grad = grad_log_density(theta);
192+ rho = rho + half_step * grad;
193+ path(:, n) = theta;
194+end
195+end
196+
197+function ok = reversible(step, num_steps, theta, rho, grad, logp_next, max_error)
198+% The adaptive step count is reversible only if no coarser (doubled-step)
199+% backward integration would itself have been accepted.
200+if num_steps == 1
201+ ok = true;
202+ return
203+end
204+ok = true;
205+while num_steps >= 2
206+ num_steps = floor(num_steps / 2);
207+ step = step * 2;
208+ if within_tolerance(step, num_steps, theta, -rho, grad, logp_next, max_error)
209+ ok = false;
210+ return
211+ end
212+end
213+end
214+
215+function ok = within_tolerance(step, num_steps, theta, rho, grad, logp, max_error)
216+[theta, rho, ~] = leapfrog(theta, rho, grad, step, num_steps);
217+final_logp = log_density(theta) - sum(rho.^2) / 2;
218+ok = abs(final_logp - logp) <= max_error;
219+end
220+
221+function u = uturn(span1, span2, direction)
222+if direction == 1
223+ span_bk = span1;
224+ span_fw = span2;
225+else
226+ span_bk = span2;
227+ span_fw = span1;
228+end
229+scaled_diff = span_fw.theta_fw - span_bk.theta_bk;
230+u = (dot(span_fw.rho_fw, scaled_diff) < 0) || (dot(span_bk.rho_bk, scaled_diff) < 0);
231+end
232+
233+function span = combine(span_old, span_new, use_barker, direction)
234+logp_old = span_old.logp;
235+logp_new = span_new.logp;
236+logp_total = log_sum_exp(logp_old, logp_new);
237+if use_barker
238+ log_denominator = logp_total;
239+else
240+ log_denominator = logp_old;
241+end
242+update = log(rand) < (logp_new - log_denominator);
243+if update
244+ theta_select = span_new.theta_select;
245+else
246+ theta_select = span_old.theta_select;
247+end
248+if direction == 1
249+ span_bk = span_old;
250+ span_fw = span_new;
251+else
252+ span_bk = span_new;
253+ span_fw = span_old;
254+end
255+span = make_combined_span(span_bk, span_fw, theta_select, logp_total);
256+end
257+
258+function r = log_sum_exp(x, y)
259+m = max(x, y);
260+r = m + log(exp(x - m) + exp(y - m));
261+end
262+
263+function s = make_leaf_span(theta, rho, grad, logp)
264+s = struct('theta_bk', theta, 'rho_bk', rho, 'grad_bk', grad, 'logp_bk', logp, ...
265+ 'theta_fw', theta, 'rho_fw', rho, 'grad_fw', grad, 'logp_fw', logp, ...
266+ 'theta_select', theta, 'logp', logp);
267+end
268+
269+function s = make_combined_span(span_bk, span_fw, theta_select, logp_total)
270+s = struct('theta_bk', span_bk.theta_bk, 'rho_bk', span_bk.rho_bk, ...
271+ 'grad_bk', span_bk.grad_bk, 'logp_bk', span_bk.logp_bk, ...
272+ 'theta_fw', span_fw.theta_fw, 'rho_fw', span_fw.rho_fw, ...
273+ 'grad_fw', span_fw.grad_fw, 'logp_fw', span_fw.logp_fw, ...
274+ 'theta_select', theta_select, 'logp', logp_total);
275+end
numbl-project.jsonadded+4−0View file
@@ -0,0 +1,4 @@
1+{
2+ "title": "Interactive WALNUTS sampling",
3+ "entry": "README.md"
4+}
walnuts_demo.madded+14−0View file
@@ -0,0 +1,14 @@
1+% WALNUTS sampling of a 2D "banana" target.
2+%
3+% WALNUTS (Within-orbit Adaptive No-U-Turn Sampler) is a NUTS variant that
4+% adapts the leapfrog step size within each orbit. This draws samples from the
5+% banana target and shows the density with the samples on top.
6+%
7+% The algorithm lives in helpers/walnuts.m; the target in helpers/log_density.m
8+% and helpers/grad_log_density.m. `addpath` must be the first statement.
9+
10+addpath('helpers'); % walnuts, log_density, grad_log_density
11+
12+rng(1); % reproducible
13+
14+walnuts_sampler(1000, 0.4, 0.8); % N samples, leapfrog dt, max energy error
walnuts_sampler.madded+87−0View file
@@ -0,0 +1,87 @@
1+function walnuts_sampler(N, dt, max_error)
2+%WALNUTS_SAMPLER Interactive figure: WALNUTS sampling of a 2D banana target.
3+% WALNUTS_SAMPLER(N, DT, MAX_ERROR) draws N samples from the banana target
4+% (helpers/log_density.m) with the WALNUTS sampler (helpers/walnuts.m) and
5+% opens a figure showing the target density and the samples.
6+%
7+% This is the wiring: it runs the sampler, evaluates the target on a grid for
8+% the heatmap, loads the prebuilt figure app, and sends both to it. It also
9+% re-samples on request (figure -> script), wired so adding controls later is
10+% a UI-only change. Run walnuts_demo.m (which addpath's helpers/).
11+
12+if nargin < 1 || isempty(N); N = 1000; end
13+if nargin < 2 || isempty(dt); dt = 0.4; end
14+if nargin < 3 || isempty(max_error); max_error = 0.8; end
15+
16+burnin = 200;
17+chain = walnuts(randn(2, 1), burnin + N, dt, max_error);
18+samples = chain(:, burnin + 1:end);
19+
20+dens = density_grid();
21+
22+html = fileread(fullfile('app', 'dist', 'index.html'));
23+fig = figure;
24+gl = uigridlayout(fig, [1 1], 'Padding', [0 0 0 0], ...
25+ 'RowHeight', {'1x'}, 'ColumnWidth', {'1x'});
26+uihtml(gl, 'HTMLSource', html, 'Data', pack_data(samples, dens, N, dt, max_error), ...
27+ 'HTMLEventReceivedFcn', @(src, ev) on_event(src, ev));
28+end
29+
30+function on_event(src, ev)
31+% Figure -> script. The target is fixed, so the figure only ever needs new
32+% samples or a movie trajectory back.
33+% 'resample' {n, dt, maxError} -> draw a fresh chain; reply with 'samples'.
34+% 'movie' {dt, maxError} -> record a short chain's orbit trajectories;
35+% reply with 'movie'.
36+d = ev.HTMLEventData;
37+N = 1000; dt = 0.4; max_error = 0.8;
38+if isstruct(d)
39+ if isfield(d, 'n'); N = max(1, round(d.n)); end
40+ if isfield(d, 'dt'); dt = d.dt; end
41+ if isfield(d, 'maxError'); max_error = d.maxError; end
42+end
43+switch ev.HTMLEventName
44+ case 'resample'
45+ burnin = 200;
46+ chain = walnuts(randn(2, 1), burnin + N, dt, max_error);
47+ samples = chain(:, burnin + 1:end);
48+ sendEventToHTMLSource(src, 'samples', ...
49+ struct('x', samples(1, :), 'y', samples(2, :), 'n', N, 'dt', dt, 'maxError', max_error));
50+ case 'movie'
51+ % Record the orbit-building trajectory of a few transitions for the
52+ % step-by-step animation. Each traj{i} = {px, py, seg, startX/Y, selX/Y}.
53+ n_steps = 12;
54+ [~, traj] = walnuts(randn(2, 1), n_steps, dt, max_error, true);
55+ sendEventToHTMLSource(src, 'movie', traj);
56+end
57+end
58+
59+function dens = density_grid()
60+%DENSITY_GRID Evaluate the target log density on a grid for the heatmap.
61+% Row-major flat values: index (iy-1)*nx + ix, iy from ymin (1) to ymax (ny).
62+xmin = -6; xmax = 6;
63+ymin = -7; ymax = 3;
64+nx = 100; ny = 100;
65+xs = linspace(xmin, xmax, nx);
66+ys = linspace(ymin, ymax, ny);
67+values = zeros(1, nx * ny);
68+k = 1;
69+for iy = 1:ny
70+ for ix = 1:nx
71+ values(k) = log_density([xs(ix); ys(iy)]);
72+ k = k + 1;
73+ end
74+end
75+dens = struct('values', values, 'nx', nx, 'ny', ny, ...
76+ 'xmin', xmin, 'xmax', xmax, 'ymin', ymin, 'ymax', ymax);
77+end
78+
79+function data = pack_data(samples, dens, N, dt, max_error)
80+data = struct();
81+data.type = 'walnuts';
82+data.samples = struct('x', samples(1, :), 'y', samples(2, :));
83+data.density = dens;
84+data.n = N;
85+data.dt = dt;
86+data.maxError = max_error;
87+end