concept-collection / hitandrun-interactive
Interactive hit-and-run sampling of a 2D convex region
A numbl + React demo: a random convex region is sampled by the hit-and-run MCMC algorithm; the figure draws the region and samples, with live controls (sample count, resample, new region, and a step-by-step movie). The sampling kernel JS-JIT-compiles in numbl.
Jeremy Magland <jmagland@flatironinstitute.org> committed commit 9fbdc3e166e4 Browse files
20 changed files+3134−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+ # hitandrun_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+11−0View file
@@ -0,0 +1,11 @@
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 are excluded by default.)
4+.gitignore
5+app/.gitignore
6+app/src
7+app/index.html
8+app/package.json
9+app/package-lock.json
10+app/tsconfig.json
11+app/vite.config.ts
CLAUDE.mdadded+135−0View file
@@ -0,0 +1,135 @@
1+# CLAUDE.md
2+
3+Tips for future agents working in this repo. Patterned after the sibling
4+`surfacefun-interactive` project (same numbl + uihtml two-way bridge), but the
5+figure is a 2D canvas instead of a three.js surface.
6+
7+## Architecture
8+
9+- `app/` — React single-file widget (`npm run build` → `app/dist/index.html`)
10+- `*.m` at root — scripts users open and run in [numbl](https://numbl.org)
11+- `helpers/` — the region/sampling algorithm (`make_region`, `hit_and_run`),
12+ put on the path by the driver via `addpath('helpers')`
13+- `numbl-project.json` — project metadata; `entry` is the landing page
14+
15+### Multi-file layout / addpath
16+
17+`hitandrun_demo.m` is the **driver**; its first statement is
18+`addpath('helpers')` so `make_region` / `hit_and_run` resolve from the subdir.
19+Two constraints make that placement mandatory:
20+
21+- `addpath(<literal>)` must be a **leading** statement in the driver, before any
22+ other non-comment statement (numbl's static path resolution requires it; the
23+ runtime/interpreter path is fine with it too). It also cannot live inside a
24+ function file — only the driver. So `hitandrun_sampler.m` can't add the path
25+ itself; run `hitandrun_demo.m`, not the sampler directly.
26+- Plain subdirectories are otherwise invisible to function resolution — only
27+ `addpath`'d dirs (plus root, `+pkg`, `@Class`, `private/`) are searched.
28+
29+This works the same in the browser deploy (the worker runs the interpreter,
30+which honors the runtime `addpath`).
31+
32+## Data flow
33+
34+**MATLAB → figure:** set `uihtml(..., 'Data', struct)` or call
35+`sendEventToHTMLSource(src, 'eventName', data)`
36+
37+**Figure → MATLAB:** call `sendToMATLAB('eventName', value)` (see `bridge.ts`);
38+receive via `HTMLEventReceivedFcn`
39+
40+## The demo, built in stages (all done)
41+
42+1. **(done)** Generate a convex region + `N` hit-and-run samples in the script;
43+ show region + samples in the figure. The samples are drawn server-side and
44+ sent via `Data`.
45+2. **(done)** Add controls to the figure panel: a **Samples** slider, a
46+ **Resample** button, and a **New region** button.
47+3. **(done)** Wire those controls to the sampler (two-way bridge).
48+
49+The figure → script protocol:
50+
51+- **Samples** slider (on release) / **Resample** →
52+ `sendToMATLAB('resample', { n, x, y })`. `x, y` are the *current region's*
53+ vertices — the script is **stateless**, so it samples the region it's given
54+ and replies with `sendEventToHTMLSource(src, 'samples', { x, y, n })`.
55+- **New region** → `sendToMATLAB('newRegion', { n })`. The script builds a new
56+ region and replies with a full `sendEventToHTMLSource(src, 'data', <payload>)`
57+ (same shape as the initial `Data`).
58+
59+Ideas for the next iteration: show the chain path / burn-in, animate the walk,
60+a "uniform vs. non-uniform target" toggle, or a non-convex region (hit-and-run
61+chords become multiple segments).
62+
63+## Client-side vs. server-side interactivity
64+
65+- **Low-latency / view-only** (e.g. dot size, zoom): update React state only —
66+ instant, no MATLAB round-trip.
67+- **New samples** (depends on the RNG and the algorithm): `sendToMATLAB` →
68+ script re-runs → `sendEventToHTMLSource` → React re-renders.
69+
70+## Key files
71+
72+| File | Purpose |
73+|------|---------|
74+| `app/src/App.tsx` | Main React component; reads data, hosts the panel |
75+| `app/src/render/RegionView.tsx` | 2D canvas renderer (region outline + sample dots) |
76+| `app/src/bridge.ts` | `onData` / `onHostEvent` / `sendToMATLAB` helpers (generic; identical across these projects) |
77+| `hitandrun_sampler.m` | Opens the figure, sends data, handles `resample`/`newRegion` (figure plumbing: `on_event`, `pack_data`, `pack_samples`) |
78+| `helpers/make_region.m` | Random convex region (convex hull of disk points) |
79+| `helpers/hit_and_run.m` | The hit-and-run sampler (half-plane chord intersection) |
80+| `hitandrun_demo.m` | User-facing driver: `addpath('helpers')` + seed + call the sampler |
81+
82+## Local iteration
83+
84+Always rebuild the figure app first — both workflows read the prebuilt
85+`app/dist/index.html`:
86+
87+```
88+cd app && npm install && npm run build # produces app/dist/index.html
89+cd ..
90+```
91+
92+`NUMBL` below is a local clone of https://github.com/flatironinstitute/numbl
93+(e.g. `~/src/numbl`). Run its CLI with `npx tsx $NUMBL/src/cli.ts …` — no
94+global install.
95+
96+### A. Quick run via the CLI (fastest inner loop)
97+
98+```
99+npx tsx $NUMBL/src/cli.ts run hitandrun_demo.m --plot
100+```
101+
102+`--plot` opens the figure in a browser and keeps the script's session alive so
103+figure → script callbacks work (Ctrl+C to stop). Good for iterating on the `.m`
104++ app together.
105+
106+### B. Preview the actual GitHub Pages site locally
107+
108+This reproduces what the [deploy workflow](.github/workflows/deploy.yml) ships
109+(it calls `flatironinstitute/numbl/.github/actions/build-site@main`, which is
110+just `cli.ts build-site` after building the site-viewer). The numbl in the
111+deployed site runs the `.m` in a **web worker in the browser**, and it keeps a
112+live uihtml session there — so figure → script callbacks (`resample`) round-trip
113+in the deployed site exactly like in the CLI `--plot` path. (That two-way bridge
114+is why the workflow builds numbl from `main` rather than the npm release.)
115+
116+```
117+# one-time, in the numbl clone: build the browser site-viewer
118+( cd $NUMBL && npm ci && npm run build:site-viewer )
119+
120+# from this project: bundle project + viewer into _site/
121+npx tsx $NUMBL/src/cli.ts build-site . --out _site --base /
122+
123+# serve it (localhost is a secure context, so coi-serviceworker.js can set the
124+# COOP/COEP headers numbl's WASM needs)
125+( cd _site && python3 -m http.server 8080 )
126+# → open http://localhost:8080/
127+```
128+
129+Notes:
130+- `--base /` is for serving at localhost root. GitHub Pages serves this project
131+ at `/<repo>/`, so the workflow auto-detects `--base /hitandrun-interactive/`.
132+ Content is identical; only asset paths differ.
133+- `_site/` and `dist/` are git-ignored (see `.gitignore`).
134+- The real target is GitHub Pages, where the same single-file app runs against
135+ numbl compiled in the browser.
README.mdadded+44−0View file
@@ -0,0 +1,44 @@
1+# Interactive hit-and-run sampling
2+
3+Runs in the browser via [numbl](https://numbl.org) — no install.
4+
5+## ▶ [Open `hitandrun_demo.m`](hitandrun_demo.m) and click **Run**
6+
7+A random 2D convex region is generated and `N` points are drawn uniformly from
8+it by **hit-and-run**: from the current point, pick a random direction, take the
9+chord where that line crosses the region, and jump to a uniform point on it.
10+Repeat. The figure shows the region and the samples.
11+
12+Controls:
13+
14+- **Samples** — set `N` (re-runs the sampler).
15+- **Resample** — new samples, same region.
16+- **New region** — a fresh region.
17+- **Play movie** — step through the algorithm: each step draws the chord and the
18+ point that landed on it.
19+
20+## How it works
21+
22+- [`hitandrun_demo.m`](hitandrun_demo.m) — driver: `addpath('helpers')`, seed, call the sampler.
23+- [`hitandrun_sampler.m`](hitandrun_sampler.m) — opens the figure, sends data, handles resample requests.
24+- `helpers/` — [`make_region.m`](helpers/make_region.m) and [`hit_and_run.m`](helpers/hit_and_run.m).
25+- `app/` — a single-file React app that draws the region and samples on a canvas.
26+
27+The script and figure talk both ways: the script sends the region + samples via
28+`uihtml(..., 'Data', ...)`, and the controls call back with
29+`sendToMATLAB('resample' | 'newRegion', ...)`, which re-runs the sampler and
30+returns new points via `sendEventToHTMLSource`. The script is stateless — the
31+figure owns the region and passes it back with each request.
32+
33+## JIT-compiled kernel
34+
35+The loop in [`hit_and_run.m`](helpers/hit_and_run.m) runs once per sample, so
36+numbl JS-JIT-compiles it to JavaScript — about 30× faster than its interpreter,
37+which is what keeps large `N` instant. The `%!numbl:assert_jit` directive
38+asserts this happens (it errors rather than silently falling back). It relies on
39+numbl's scalar-`rand()` JIT support; `rng(seed)` still controls the shared PRNG.
40+
41+## Deploy
42+
43+Pushing to `main` builds the app and publishes the project to GitHub Pages via
44+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>hit-and-run 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": "hitandrun-interactive-app",
3+ "version": "0.0.0",
4+ "lockfileVersion": 3,
5+ "requires": true,
6+ "packages": {
7+ "": {
8+ "name": "hitandrun-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": "hitandrun-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+326−0View file
@@ -0,0 +1,326 @@
1+import { useEffect, useState, type CSSProperties } from "react";
2+import {
3+ RegionView,
4+ type Points,
5+ type Segment,
6+ type Pt,
7+} from "./render/RegionView.js";
8+import { onData, onHostEvent, sendToMATLAB } from "./bridge.js";
9+
10+/** Payload from the numbl script: a convex region (CCW polygon) plus the
11+ * hit-and-run samples drawn from it. Mirrors what hitandrun_sampler.m sends,
12+ * both as the initial `Data` and as the `data` event after "New region". */
13+interface HitAndRunData {
14+ type: "hitandrun";
15+ region: Points;
16+ samples: Points;
17+ n: number;
18+}
19+
20+function isHitAndRunData(d: unknown): d is HitAndRunData {
21+ return (
22+ !!d &&
23+ typeof d === "object" &&
24+ (d as HitAndRunData).type === "hitandrun" &&
25+ !!(d as HitAndRunData).region &&
26+ !!(d as HitAndRunData).samples
27+ );
28+}
29+
30+/** `samples` event payload: fresh points for the *same* region (after Resample
31+ * or moving the samples slider). */
32+interface SamplesEvent {
33+ x: number[];
34+ y: number[];
35+ n: number;
36+}
37+
38+// Discrete sample-count choices; the slider indexes into this array.
39+const SAMPLE_CHOICES = [10, 100, 1000, 10000, 100000, 1000000];
40+const DEFAULT_N = 10000;
41+
42+// Sampling movie: reveal points one at a time. Each frame shows the chord the
43+// step samples along AND the point that landed on it, together.
44+const MOVIE_STEP_MS = 750;
45+const MOVIE_LAST_INDEX = 41; // animate sampling of points up to this index
46+
47+interface MovieState {
48+ step: number; // index of the point currently being sampled
49+}
50+
51+/** The chord that hit-and-run samples along: the line through (px,py) with
52+ * direction (dx,dy), clipped to the convex region. Same inward-half-plane
53+ * intersection the sampler uses, so it reproduces the step exactly. */
54+function regionChord(
55+ region: Points,
56+ px: number,
57+ py: number,
58+ dx: number,
59+ dy: number
60+): Segment | null {
61+ if (Math.hypot(dx, dy) < 1e-12) return null;
62+ let tmin = -Infinity;
63+ let tmax = Infinity;
64+ const m = region.x.length;
65+ for (let i = 0; i < m; i++) {
66+ const j = (i + 1) % m;
67+ const ex = region.x[j] - region.x[i];
68+ const ey = region.y[j] - region.y[i];
69+ // Inward normal of a CCW edge. inside: n·p >= n·v_i -> t·a >= rhs.
70+ const nx = -ey;
71+ const ny = ex;
72+ const a = nx * dx + ny * dy;
73+ const rhs = nx * region.x[i] + ny * region.y[i] - (nx * px + ny * py);
74+ if (a > 1e-12) tmin = Math.max(tmin, rhs / a);
75+ else if (a < -1e-12) tmax = Math.min(tmax, rhs / a);
76+ }
77+ if (!(tmax > tmin) || !isFinite(tmin) || !isFinite(tmax)) return null;
78+ return {
79+ x0: px + tmin * dx,
80+ y0: py + tmin * dy,
81+ x1: px + tmax * dx,
82+ y1: py + tmax * dy,
83+ };
84+}
85+
86+const prefixPoints = (p: Points, k: number): Points => ({
87+ x: p.x.slice(0, k),
88+ y: p.y.slice(0, k),
89+});
90+
91+export function App() {
92+ const [data, setData] = useState<HitAndRunData | null>(null);
93+ const [n, setN] = useState(DEFAULT_N);
94+ const [busy, setBusy] = useState(false);
95+ const [movie, setMovie] = useState<MovieState | null>(null);
96+
97+ useEffect(() => {
98+ // Initial region + samples (script → page via `Data`).
99+ const offData = onData(d => {
100+ if (isHitAndRunData(d)) {
101+ setData(d);
102+ setN(d.n);
103+ setMovie(null);
104+ }
105+ });
106+ // "New region": the script sends a full fresh payload.
107+ const offFull = onHostEvent("data", d => {
108+ if (isHitAndRunData(d)) {
109+ setData(d);
110+ setN(d.n);
111+ setBusy(false);
112+ setMovie(null);
113+ }
114+ });
115+ // Resample (same region): only the points change.
116+ const offSamples = onHostEvent("samples", ev => {
117+ const s = ev as SamplesEvent;
118+ if (!s || !Array.isArray(s.x)) return;
119+ setData(prev =>
120+ prev ? { ...prev, samples: { x: s.x, y: s.y }, n: s.n } : prev
121+ );
122+ setBusy(false);
123+ setMovie(null);
124+ });
125+ return () => {
126+ offData();
127+ offFull();
128+ offSamples();
129+ };
130+ }, []);
131+
132+ // Movie clock: advance phase/step on a timer (a setTimeout chain — the effect
133+ // reruns whenever `movie` changes). Ends (→ full cloud) past the last index.
134+ useEffect(() => {
135+ if (!movie || !data) return;
136+ const lastIndex = Math.min(data.samples.x.length - 1, MOVIE_LAST_INDEX);
137+ const id = setTimeout(() => {
138+ setMovie(m => {
139+ if (!m) return m;
140+ const next = m.step + 1;
141+ return next > lastIndex ? null : { step: next };
142+ });
143+ }, MOVIE_STEP_MS);
144+ return () => clearTimeout(id);
145+ }, [movie, data]);
146+
147+ // Re-draw `count` samples in the current region (script round-trip).
148+ const resample = (count: number) => {
149+ if (!data) return;
150+ setBusy(true);
151+ sendToMATLAB("resample", { n: count, x: data.region.x, y: data.region.y });
152+ };
153+
154+ // Generate a brand new region and sample it (script round-trip).
155+ const newRegion = (count: number) => {
156+ setBusy(true);
157+ sendToMATLAB("newRegion", { n: count });
158+ };
159+
160+ const toggleMovie = () => {
161+ if (movie) {
162+ setMovie(null);
163+ } else if (data && data.samples.x.length >= 3) {
164+ setMovie({ step: 2 });
165+ }
166+ };
167+
168+ // Overlay for the current movie frame (settled points + chord + highlights).
169+ let cloud: Points = data ? data.samples : { x: [], y: [] };
170+ let chord: Segment | null = null;
171+ let from: Pt | null = null;
172+ let newPoint: Pt | null = null;
173+ if (movie && data) {
174+ const i = movie.step; // point being sampled
175+ const f = i - 1; // point the step starts from
176+ const { x, y } = data.samples;
177+ cloud = prefixPoints(data.samples, i); // settled points 0..i-1
178+ from = { x: x[f], y: y[f] };
179+ chord = regionChord(data.region, x[f], y[f], x[i] - x[f], y[i] - y[f]);
180+ newPoint = { x: x[i], y: y[i] };
181+ }
182+
183+ const canPlay = !!data && data.samples.x.length >= 3;
184+
185+ return (
186+ <div style={rootStyle}>
187+ {data ? (
188+ <RegionView
189+ region={data.region}
190+ samples={cloud}
191+ chord={chord}
192+ from={from}
193+ newPoint={newPoint}
194+ />
195+ ) : (
196+ <div style={waitingStyle}>Waiting for the region from the script…</div>
197+ )}
198+
199+ <div style={panelStyle}>
200+ <label style={labelStyle}>
201+ Samples: <b>{n.toLocaleString()}</b>
202+ <input
203+ type="range"
204+ min={0}
205+ max={SAMPLE_CHOICES.length - 1}
206+ step={1}
207+ value={Math.max(0, SAMPLE_CHOICES.indexOf(n))}
208+ disabled={!data || busy || !!movie}
209+ // Drag updates the label live; the script round-trip fires on
210+ // release (and on arrow-key release) to avoid flooding it.
211+ onChange={e => setN(SAMPLE_CHOICES[Number(e.target.value)])}
212+ onPointerUp={e =>
213+ resample(SAMPLE_CHOICES[Number(e.currentTarget.value)])
214+ }
215+ onKeyUp={e => {
216+ if (e.key.startsWith("Arrow")) {
217+ resample(SAMPLE_CHOICES[Number(e.currentTarget.value)]);
218+ }
219+ }}
220+ style={sliderStyle}
221+ />
222+ </label>
223+
224+ <div style={{ display: "flex", gap: 6, marginTop: 6 }}>
225+ <button
226+ style={btnStyle}
227+ disabled={!data || busy || !!movie}
228+ onClick={() => resample(n)}
229+ title="Draw a fresh set of samples in the same region"
230+ >
231+ Resample
232+ </button>
233+ <button
234+ style={btnStyle}
235+ disabled={busy || !!movie}
236+ onClick={() => newRegion(n)}
237+ title="Generate a new convex region and sample it"
238+ >
239+ New region
240+ </button>
241+ </div>
242+
243+ <button
244+ style={playBtnStyle}
245+ disabled={!canPlay || busy}
246+ onClick={toggleMovie}
247+ title="Animate the hit-and-run steps: chord, then the sampled point"
248+ >
249+ {movie ? "■ Stop movie" : "▶ Play movie"}
250+ </button>
251+
252+ <div style={{ fontSize: 10, color: "#64748b", marginTop: 6 }}>
253+ {busy
254+ ? "sampling…"
255+ : movie
256+ ? `movie · point ${movie.step + 1}`
257+ : `${data ? data.n.toLocaleString() : "—"} points`}
258+ </div>
259+ </div>
260+ </div>
261+ );
262+}
263+
264+const rootStyle: CSSProperties = {
265+ position: "absolute",
266+ inset: 0,
267+ overflow: "hidden",
268+ background: "#ffffff",
269+ fontFamily: "system-ui, -apple-system, Arial, sans-serif",
270+};
271+
272+const waitingStyle: CSSProperties = {
273+ position: "absolute",
274+ inset: 0,
275+ display: "flex",
276+ alignItems: "center",
277+ justifyContent: "center",
278+ color: "#94a3b8",
279+};
280+
281+const panelStyle: CSSProperties = {
282+ position: "absolute",
283+ top: 8,
284+ left: 8,
285+ width: 150,
286+ padding: "7px 9px",
287+ background: "rgba(255,255,255,0.9)",
288+ border: "1px solid #e2e8f0",
289+ borderRadius: 6,
290+ boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
291+ color: "#0f172a",
292+};
293+
294+const labelStyle: CSSProperties = {
295+ display: "block",
296+ fontSize: 11,
297+};
298+
299+const sliderStyle: CSSProperties = {
300+ width: "100%",
301+ marginTop: 2,
302+};
303+
304+const btnStyle: CSSProperties = {
305+ flex: 1,
306+ padding: "3px 4px",
307+ fontSize: 10,
308+ whiteSpace: "nowrap",
309+ cursor: "pointer",
310+ background: "#f8fafc",
311+ border: "1px solid #cbd5e1",
312+ borderRadius: 5,
313+ color: "#0f172a",
314+};
315+
316+const playBtnStyle: CSSProperties = {
317+ width: "100%",
318+ marginTop: 6,
319+ padding: "4px 6px",
320+ fontSize: 10,
321+ cursor: "pointer",
322+ background: "#eff6ff",
323+ border: "1px solid #bfdbfe",
324+ borderRadius: 5,
325+ color: "#1e3a8a",
326+};
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/RegionView.tsxadded+198−0View file
@@ -0,0 +1,198 @@
1+import { useEffect, useRef, type CSSProperties } from "react";
2+
3+export interface Points {
4+ x: number[];
5+ y: number[];
6+}
7+
8+export interface Segment {
9+ x0: number;
10+ y0: number;
11+ x1: number;
12+ y1: number;
13+}
14+
15+export interface Pt {
16+ x: number;
17+ y: number;
18+}
19+
20+interface RegionViewProps {
21+ /** Convex region boundary, in counterclockwise world coordinates. */
22+ region: Points;
23+ /** Sample points to scatter inside the region. */
24+ samples: Points;
25+ /** Animation overlay: the chord the current step samples along (a line
26+ * through `from` clipped to the region). */
27+ chord?: Segment | null;
28+ /** Animation overlay: the current point the step starts from (ringed). */
29+ from?: Pt | null;
30+ /** Animation overlay: the freshly sampled point (highlighted). */
31+ newPoint?: Pt | null;
32+}
33+
34+const FILL = "rgba(37, 99, 235, 0.08)";
35+const STROKE = "#2563eb";
36+const DOT = "rgba(15, 23, 42, 0.55)";
37+const DOT_RADIUS = 1.6;
38+const MARGIN = 28;
39+
40+const CHORD = "#f59e0b"; // amber: the candidate segment + its boundary hits
41+const PREV = "#d97706"; // deeper amber dot: the sample the chord starts from
42+const NEW = "#0f172a"; // near-black: the freshly sampled point (circled)
43+
44+/** Renders the region outline and the samples on a 2D canvas, fitting the
45+ * region to the available space (aspect-ratio preserved, y pointing up). The
46+ * optional `chord` / `from` / `newPoint` overlay drives the sampling movie.
47+ * The canvas is redrawn on data change and on resize, and is devicePixelRatio
48+ * aware so dots and edges stay crisp. */
49+export function RegionView({
50+ region,
51+ samples,
52+ chord,
53+ from,
54+ newPoint,
55+}: RegionViewProps) {
56+ const canvasRef = useRef<HTMLCanvasElement>(null);
57+ const containerRef = useRef<HTMLDivElement>(null);
58+
59+ useEffect(() => {
60+ const canvas = canvasRef.current;
61+ const container = containerRef.current;
62+ if (!canvas || !container) return;
63+
64+ const draw = () => {
65+ const ctx = canvas.getContext("2d");
66+ if (!ctx) return;
67+
68+ const dpr = window.devicePixelRatio || 1;
69+ const cssW = container.clientWidth;
70+ const cssH = container.clientHeight;
71+ if (cssW === 0 || cssH === 0) return;
72+
73+ canvas.width = Math.round(cssW * dpr);
74+ canvas.height = Math.round(cssH * dpr);
75+ canvas.style.width = `${cssW}px`;
76+ canvas.style.height = `${cssH}px`;
77+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
78+ ctx.clearRect(0, 0, cssW, cssH);
79+
80+ if (region.x.length < 3) return;
81+
82+ // World bounds from the region (samples lie inside it).
83+ let minX = Infinity;
84+ let maxX = -Infinity;
85+ let minY = Infinity;
86+ let maxY = -Infinity;
87+ for (let i = 0; i < region.x.length; i++) {
88+ minX = Math.min(minX, region.x[i]);
89+ maxX = Math.max(maxX, region.x[i]);
90+ minY = Math.min(minY, region.y[i]);
91+ maxY = Math.max(maxY, region.y[i]);
92+ }
93+ const worldW = maxX - minX || 1;
94+ const worldH = maxY - minY || 1;
95+
96+ // Fit preserving aspect ratio; center within the margins.
97+ const scale = Math.min(
98+ (cssW - 2 * MARGIN) / worldW,
99+ (cssH - 2 * MARGIN) / worldH
100+ );
101+ const offX = (cssW - worldW * scale) / 2;
102+ const offY = (cssH - worldH * scale) / 2;
103+ // World (x right, y up) → pixel (x right, y down).
104+ const toPx = (x: number) => offX + (x - minX) * scale;
105+ const toPy = (y: number) => cssH - (offY + (y - minY) * scale);
106+
107+ // Region: translucent fill + crisp outline.
108+ ctx.beginPath();
109+ ctx.moveTo(toPx(region.x[0]), toPy(region.y[0]));
110+ for (let i = 1; i < region.x.length; i++) {
111+ ctx.lineTo(toPx(region.x[i]), toPy(region.y[i]));
112+ }
113+ ctx.closePath();
114+ ctx.fillStyle = FILL;
115+ ctx.fill();
116+ ctx.lineWidth = 2;
117+ ctx.strokeStyle = STROKE;
118+ ctx.stroke();
119+
120+ // Samples: small filled dots.
121+ ctx.fillStyle = DOT;
122+ const n = Math.min(samples.x.length, samples.y.length);
123+ for (let i = 0; i < n; i++) {
124+ const cx = toPx(samples.x[i]);
125+ const cy = toPy(samples.y[i]);
126+ ctx.beginPath();
127+ ctx.arc(cx, cy, DOT_RADIUS, 0, 2 * Math.PI);
128+ ctx.fill();
129+ }
130+
131+ // Animation overlay (movie mode).
132+ if (chord) {
133+ const x0 = toPx(chord.x0);
134+ const y0 = toPy(chord.y0);
135+ const x1 = toPx(chord.x1);
136+ const y1 = toPy(chord.y1);
137+ // The chord itself.
138+ ctx.beginPath();
139+ ctx.moveTo(x0, y0);
140+ ctx.lineTo(x1, y1);
141+ ctx.strokeStyle = CHORD;
142+ ctx.lineWidth = 1.5;
143+ ctx.setLineDash([4, 3]);
144+ ctx.stroke();
145+ ctx.setLineDash([]);
146+ // Open circles where the line crosses the region boundary.
147+ ctx.strokeStyle = CHORD;
148+ ctx.lineWidth = 1.25;
149+ for (const [ex, ey] of [
150+ [x0, y0],
151+ [x1, y1],
152+ ]) {
153+ ctx.beginPath();
154+ ctx.arc(ex, ey, 3, 0, 2 * Math.PI);
155+ ctx.stroke();
156+ }
157+ }
158+ // Previous sample: the point the chord starts from — a filled amber dot.
159+ if (from) {
160+ ctx.beginPath();
161+ ctx.arc(toPx(from.x), toPy(from.y), 4, 0, 2 * Math.PI);
162+ ctx.fillStyle = PREV;
163+ ctx.fill();
164+ }
165+ // New sample: a circled black point.
166+ if (newPoint) {
167+ const cx = toPx(newPoint.x);
168+ const cy = toPy(newPoint.y);
169+ ctx.beginPath();
170+ ctx.arc(cx, cy, 6.5, 0, 2 * Math.PI);
171+ ctx.strokeStyle = NEW;
172+ ctx.lineWidth = 1.5;
173+ ctx.stroke();
174+ ctx.beginPath();
175+ ctx.arc(cx, cy, 3.2, 0, 2 * Math.PI);
176+ ctx.fillStyle = NEW;
177+ ctx.fill();
178+ }
179+ };
180+
181+ draw();
182+ const ro = new ResizeObserver(draw);
183+ ro.observe(container);
184+ return () => ro.disconnect();
185+ }, [region, samples, chord, from, newPoint]);
186+
187+ return (
188+ <div ref={containerRef} style={containerStyle}>
189+ <canvas ref={canvasRef} style={{ display: "block" }} />
190+ </div>
191+ );
192+}
193+
194+const containerStyle: CSSProperties = {
195+ position: "absolute",
196+ inset: 0,
197+ overflow: "hidden",
198+};
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/hit_and_run.madded+67−0View file
@@ -0,0 +1,67 @@
1+function [sx, sy] = hit_and_run(vx, vy, N, nBurn)
2+%HIT_AND_RUN Draw N uniform samples from a convex polygon by hit-and-run.
3+% [SX, SY] = HIT_AND_RUN(VX, VY, N, NBURN) draws N samples (after NBURN
4+% burn-in steps) from the uniform distribution on the convex polygon with
5+% counterclockwise vertices (VX, VY).
6+%
7+% Represents the polygon as a set of half-planes (inside iff n_i . p >= c_i
8+% for every edge i). From the current interior point, pick a random
9+% direction, intersect the line with every half-plane to get the chord
10+% [tmin, tmax], then jump to a uniform-random point on it.
11+%
12+
13+% Guard: this hot loop must JS-JIT-compile (it's ~30x slower in the
14+% interpreter).
15+%!numbl:assert_jit
16+
17+nv = numel(vx);
18+
19+% Inward half-plane form for each edge: the left normal of a CCW edge points
20+% into the interior.
21+nx = zeros(nv, 1);
22+ny = zeros(nv, 1);
23+c = zeros(nv, 1);
24+for i = 1:nv
25+ j = mod(i, nv) + 1;
26+ ex = vx(j) - vx(i);
27+ ey = vy(j) - vy(i);
28+ len = hypot(ex, ey);
29+ n1 = -ey / len;
30+ n2 = ex / len;
31+ nx(i) = n1;
32+ ny(i) = n2;
33+ c(i) = n1 * vx(i) + n2 * vy(i);
34+end
35+
36+% Start at the centroid (always interior for a convex polygon).
37+px = mean(vx);
38+py = mean(vy);
39+
40+total = nBurn + N;
41+sx = zeros(N, 1);
42+sy = zeros(N, 1);
43+for s = 1:total
44+ th = 2 * pi * rand;
45+ dx = cos(th);
46+ dy = sin(th);
47+ % Chord [tmin, tmax] of the line p + t*d that stays inside the region.
48+ tmin = -inf;
49+ tmax = inf;
50+ for i = 1:nv
51+ a = nx(i) * dx + ny(i) * dy;
52+ rhs = c(i) - (nx(i) * px + ny(i) * py); % <= 0 since p is interior
53+ if a > 1e-12
54+ tmin = max(tmin, rhs / a);
55+ elseif a < -1e-12
56+ tmax = min(tmax, rhs / a);
57+ end
58+ end
59+ t = tmin + (tmax - tmin) * rand;
60+ px = px + t * dx;
61+ py = py + t * dy;
62+ if s > nBurn
63+ sx(s - nBurn) = px;
64+ sy(s - nBurn) = py;
65+ end
66+end
67+end
helpers/make_region.madded+32−0View file
@@ -0,0 +1,32 @@
1+function [vx, vy] = make_region()
2+%MAKE_REGION Random convex polygon = convex hull of random points in a disk.
3+% [VX, VY] = MAKE_REGION() returns the counterclockwise vertices of a random
4+% convex region. Anisotropic scaling makes it a bit more interesting than a
5+% circle. The vertices are ordered CCW so the interior is to the left of each
6+% edge (which hit_and_run relies on for its inward half-plane normals).
7+m = 12;
8+ang = 2 * pi * rand(m, 1);
9+r = sqrt(rand(m, 1)); % sqrt -> uniform over the disk
10+px = 1.4 * r .* cos(ang);
11+py = 1.0 * r .* sin(ang);
12+k = convhull(px, py); % boundary indices, closed (last == first)
13+k = k(1:end-1); % drop the repeated closing vertex
14+vx = px(k);
15+vy = py(k);
16+% Ensure counterclockwise.
17+if signed_area(vx, vy) < 0
18+ vx = vx(end:-1:1);
19+ vy = vy(end:-1:1);
20+end
21+end
22+
23+function A = signed_area(vx, vy)
24+%SIGNED_AREA Shoelace area; positive when the vertices run counterclockwise.
25+n = numel(vx);
26+A = 0;
27+for i = 1:n
28+ j = mod(i, n) + 1;
29+ A = A + (vx(i) * vy(j) - vx(j) * vy(i));
30+end
31+A = A / 2;
32+end
hitandrun_demo.madded+17−0View file
@@ -0,0 +1,17 @@
1+% Hit-and-run MCMC sampling of a 2D convex region.
2+%
3+% A random convex polygon is generated, then N points are drawn from the
4+% uniform distribution on it with the hit-and-run algorithm: from the current
5+% point, pick a random direction, find the chord where that line crosses the
6+% region, and jump to a uniform-random point on the chord. Repeat. The figure
7+% shows the region outline and the resulting samples.
8+%
9+% The region + sampling functions live in helpers/. `addpath` must be the
10+% first statement (numbl resolves the driver's search path before running),
11+% so it comes before everything else.
12+
13+addpath('helpers'); % make_region, hit_and_run
14+
15+rng(1); % reproducible region + samples
16+
17+hitandrun_sampler(10000); % matches the figure's default samples control
hitandrun_sampler.madded+82−0View file
@@ -0,0 +1,82 @@
1+function hitandrun_sampler(N)
2+%HITANDRUN_SAMPLER Interactive figure: hit-and-run sampling of a 2D convex region.
3+% HITANDRUN_SAMPLER(N) generates a random convex polygon, draws N samples
4+% from the uniform distribution on it using the hit-and-run algorithm, and
5+% opens a figure that shows the region and the samples.
6+%
7+% This holds all the wiring: it builds the region, runs the sampler, loads
8+% the prebuilt figure app, and sends region + samples to it (script ->
9+% figure). It also re-samples on request (figure -> script) so the controls
10+% in the figure drive the algorithm.
11+%
12+% The region/sampling algorithm lives in helpers/ (make_region, hit_and_run);
13+% the end-user script (hitandrun_demo.m) puts that folder on the path with
14+% `addpath('helpers')` and calls this. Run hitandrun_demo.m, not this file
15+% directly, so the path is set up.
16+%
17+% The script is a stateless sampling service: the figure owns the current
18+% region and sends it back with each resample request, so there is no
19+% server-side state to keep in sync across callbacks.
20+
21+if nargin < 1 || isempty(N)
22+ N = 1500;
23+end
24+
25+% Build a random convex region and draw N uniform samples from it.
26+[vx, vy] = make_region();
27+tic;
28+[sx, sy] = hit_and_run(vx, vy, N, 50);
29+fprintf('hit_and_run: N=%d sampled in %.3f s\n', N, toc);
30+
31+% The figure app is the prebuilt single-file page, relative to the project root
32+% (the current working directory when a top-level script is run).
33+html = fileread(fullfile('app', 'dist', 'index.html'));
34+
35+fig = figure;
36+gl = uigridlayout(fig, [1 1], 'Padding', [0 0 0 0], ...
37+ 'RowHeight', {'1x'}, 'ColumnWidth', {'1x'});
38+uihtml(gl, 'HTMLSource', html, 'Data', pack_data(vx, vy, sx, sy, N), ...
39+ 'HTMLEventReceivedFcn', @(src, ev) on_event(src, ev));
40+end
41+
42+function on_event(src, ev)
43+% Figure -> script. Two requests the controls send:
44+% 'resample' {n, x, y} -> draw n fresh samples in the given region; reply
45+% with a 'samples' event (region is unchanged).
46+% 'newRegion' {n} -> build a new region, draw n samples in it; reply
47+% with a full 'data' event (region + samples).
48+d = ev.HTMLEventData;
49+n = 10000;
50+if isstruct(d) && isfield(d, 'n')
51+ n = max(1, round(d.n));
52+end
53+switch ev.HTMLEventName
54+ case 'resample'
55+ vx = d.x(:);
56+ vy = d.y(:);
57+ tic;
58+ [sx, sy] = hit_and_run(vx, vy, n, 50);
59+ fprintf('resample: N=%d sampled in %.3f s\n', n, toc);
60+ sendEventToHTMLSource(src, 'samples', pack_samples(sx, sy, n));
61+ case 'newRegion'
62+ [vx, vy] = make_region();
63+ tic;
64+ [sx, sy] = hit_and_run(vx, vy, n, 50);
65+ fprintf('newRegion: N=%d sampled in %.3f s\n', n, toc);
66+ sendEventToHTMLSource(src, 'data', pack_data(vx, vy, sx, sy, n));
67+end
68+end
69+
70+function data = pack_data(vx, vy, sx, sy, N)
71+%PACK_DATA Full payload (region + samples) sent once when the figure opens.
72+data = struct();
73+data.type = 'hitandrun';
74+data.region = struct('x', vx(:).', 'y', vy(:).');
75+data.samples = struct('x', sx(:).', 'y', sy(:).');
76+data.n = N;
77+end
78+
79+function s = pack_samples(sx, sy, N)
80+%PACK_SAMPLES Just the samples, sent on a resample (region is unchanged).
81+s = struct('x', sx(:).', 'y', sy(:).', 'n', N);
82+end
numbl-project.jsonadded+4−0View file
@@ -0,0 +1,4 @@
1+{
2+ "title": "Hit-and-run sampling of a 2D convex region",
3+ "entry": "README.md"
4+}