diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 47123ca..58bbbf2 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -26,10 +26,28 @@ jobs: with: node-version: "22" - uses: actions/configure-pages@v6 - - name: Build Astro site + # A version-bump commit can reach main before trusted publishing has + # finished. Never deploy copy that says "PyPI published" until PyPI, + # GitHub assets, and the checksum ledger already agree with the manifest. + # Ordinary docs-only updates pass on the first attempt; a release + # transition waits for the release-published workflow instead of briefly + # making a future version look stable. + - name: Wait for the public release ledger run: | cd docs-site npm install + for attempt in $(seq 1 30); do + if npm run check:release:live; then + exit 0 + fi + if [ "$attempt" -lt 30 ]; then + sleep 20 + fi + done + exit 1 + - name: Build Astro site + run: | + cd docs-site npm run build - uses: actions/upload-pages-artifact@v5 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e8dad..5ffab9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning: ## [Unreleased] +## [0.20.0] - 2026-08-24 + +### Added +- The Architecture and Workflow maps now use a complete, viewport-rendered + canvas surface. The default all-language map keeps every parser-owned module + and route represented, searchable, keyboard reachable, and recoverable; + explicit language focus keeps the complete named projection. Neither path + creates one DOM element per item. +- The canvas Map exposes one stable keyboard focus target with directional, + Home/End, and Enter/Space navigation, plus a full-label readout for clipped + identifiers. + +### Changed +- The default supported-project cap moves from 1,000 to 5,000 files after the + complete Map passed the same predeclared backend, Chromium, and WebKit gate. +- Empty canvas space preserves drag-to-pan while emitted box coordinates and + renderer-owned painted-row bounds retain direct activation. Architecture + and Workflow keep their existing parser-owned coordinates, certainty, + progress, and language channels. + +### Fixed +- Map recovery now gates on the first visible canvas slice rather than merely + the presence of an empty canvas element. + ## [0.19.2] - 2026-08-21 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index 5c74296..383c908 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -203,13 +203,15 @@ evidence continues in parallel — exercise the loop on real learner projects. The v0.1.0 Python learner-acceptance issue stays open; technical completion does not claim those external runs passed. -**NEXT — scale, and the deepening that is genuinely left.** The slate is now -mostly done: Python's call resolution and lens (v0.11.0), and JS/TS builtin -classification plus test-demoted entrypoint ranking (v0.12.0). **The -TypeScript "deepening" was measured and largely refused** — see the Decision -Log; TS's unproven edges are 80% honest boundaries and 1% fan-out, so copying -Python's receiver resolution would have been work against a problem that is not -there. What actually remains: +**NEXT — the privacy boundary for read-only sharing.** The parser and scale +slate is now complete through the v0.20.0 candidate: Python's call resolution +and lens (v0.11.0), JS/TS builtin classification and entrypoint ranking +(v0.12.0), bounded parser evidence (v0.19.0), and a complete canvas Map that +passes the 5,000-module backend, Chromium, and WebKit gate. The next build phase +must define provenance, expiry, deletion, and source-exclusion for the planned +shareable read-only galaxy link before adding its only cloud touch. + +The prior scale decisions remain recorded because the measurements are reusable: **All three items previously listed here were measured, and two of them were refused on the evidence.** Recorded because the measurements are reusable: @@ -232,12 +234,11 @@ refused on the evidence.** Recorded because the measurements are reusable: 5,000 modules, but WebKit could not stabilize the first-run Skip control within the 5 s interaction budget while 5,000 SVG boxes committed. -What is genuinely next: a canvas-backed **complete Map**, then the same 5,000- -module Chromium and WebKit gate. Delivery may change, but every module remains -represented, searchable, keyboard reachable, and recoverable; logical LOD that -hides modules is not an acceptable shortcut. The ordinary cap stays 1,000 -until that gate passes. Phase 3 remains the shareable read-only galaxy link and -extra quest types. +The canvas Map changes delivery only: every module remains represented, +searchable, keyboard reachable, and recoverable; logical LOD that hides modules +is still not acceptable. The ordinary cap is 5,000 after the schema-4 candidate +gate passed both engines. Phase 3 remains the shareable read-only galaxy link +and extra quest types. **LATER — Phase 3 (months ~7–9).** Shareable read-only galaxy link (the only cloud touch). Extra quest types: trace-a-request, fix-the-failing-test. @@ -245,12 +246,33 @@ Polish, then the coordinated launch (Show HN / X; lit-galaxy GIF as hero). ## Current State **[AGENT-MAINTAINED]** -**Current milestone: M16 parser evidence and scale gates** · Last updated: -2026-08-21 · Session note: two compact user-flow repairs shipped as verified -stable v0.19.2 without changing parser, graph, checks, progress, providers, -or release-scale truth. - -**The candidate closes two measured P2 gaps.** WebKit may leave focus on +**Current milestone: M17 complete canvas Map and 5,000-file scale** · Last updated: +2026-08-24 · Session note: the v0.20.0 candidate replaces per-item Map SVG DOM +with complete viewport canvas delivery, improves keyboard/pointer/readout flow, +and promotes the ordinary supported-file cap only after the full gate passes. + +**The v0.20.0 candidate completes the next measured phase.** Architecture and +Workflow retain backend coordinates and every module/route in one complete +prepared scene for the current language projection while drawing only the +native-scroll viewport. The default all-language projection retains the entire +Map; an explicit language focus remains a named learner-controlled filter. One +listbox focus target replaces thousands of tab stops; arrows, Home/End, and +Enter/Space remain direct, clipped canvas labels get a complete wrapping +contextual readout, and empty space alone owns drag-to-pan. The revised schema-4 +5,000-module receipt passes at 4.870 s cold, 1.668 s no-change, and 170,606,592 +bytes process RSS. +Chromium/WebKit each hold 99 DOM elements, all 5,000 boxes and 4,999 routes, +direct final-module keyboard/Finder arrival, visible recovery in 76–82 ms, +and zero page overflow at 320 px. The semantic oracle retains zero regressions. +Native Safari visual acceptance was unavailable because the Mac was locked; +WebKit engine acceptance and inspected 1440/320 captures passed without being +misreported as Safari. + +Previously (2026-08-21) · Session note: two compact user-flow repairs shipped +as verified stable v0.19.2 without changing parser, graph, checks, progress, +providers, or then-current release-scale truth. + +**The v0.19.2 candidate closed two measured P2 gaps.** WebKit may leave focus on `` after a pointer activates Menu, so the disclosure's subtree handler never received Escape; the existing window arbiter now owns that dismissal and returns focus without retreating a level. At 320–405px widths, the sticky quiz @@ -1703,6 +1725,24 @@ Chromium's complete 5,000-module view passes every declared budget, WebKit's failure remains visible and prevents a cap increase, and the production SPA is rebuilt from the reviewed source. +### M17 — Complete canvas Map and 5,000-file scale ✅ (2026-08-24) +- [x] Replace per-module/per-route SVG DOM with complete viewport-rendered + Architecture and Workflow canvas scenes while preserving backend layout +- [x] Keep every module represented, searchable, keyboard reachable, and + recoverable; add no logical LOD or second source of truth +- [x] Provide one focusable Map surface with directional, Home/End, and + Enter/Space navigation plus a full-label readout +- [x] Preserve native scroll extent, zoom, fit, empty-space drag-to-pan, + parser-owned pointer hits, responsive geometry, and reduced motion +- [x] Pass the schema-4 complete 5,000-module backend, Chromium, and WebKit gate + before moving the ordinary cap from 1,000 to 5,000 + +**Acceptance:** 532 Python tests, Ruff, the complete frontend contract/build, +the semantic oracle, and exact 5,000-module source-scene counts pass; both +engines remain below the DOM, resource, usable-time, input, keyboard, recovery, +event-loop, memory, and 320 px overflow budgets; the production SPA and public +evidence are rebuilt from the reviewed source. + ## Decision Log **[AGENT-MAINTAINED — append only]** | Date | Decision | Why | @@ -1912,6 +1952,11 @@ rebuilt from the reviewed source. | 2026-08-21 | Compact Menu dismissal is owned by the window Escape arbiter, and the compact quiz owns the footer row below the rail | WebKit does not guarantee that a pointer-clicked button receives focus, so the rail subtree could not hear Escape when focus stayed on body; one ordered window owner closes it and restores Menu focus without navigation. The quiz's absolute panel ended above the footer with 437px at 320x640, so top-aligning the question still put the final answer under the sticky action through 405px. Using the same fixed below-rail boundary as Study gains the otherwise redundant status row while preserving the rail, sticky action, parser-owned question, and all four complete answer targets | | 2026-08-21 | Compact quiz arrival sets the panel scroll position directly and repeats alignment after local fonts settle; its browser gate measures after the 420ms panel entrance | The first v0.19.2 exact-main CI run proved that Ubuntu WebKit could ignore `scrollIntoView({behavior: "instant"})` during panel entry even though Chromium and macOS WebKit accepted it: at 320x640 all four 84px answer rows existed, but the last sat at 779-863px behind an action beginning at 588px. Direct scroll ownership removes that engine-dependent request, and the post-font alignment makes late identifier wrapping explicit. The repair preserves the 124px navigation rail, complete parser-owned identifiers, and 44px targets rather than passing by truncating or shrinking the choices | | 2026-08-21 | v0.19.2 becomes stable only after the failed first candidate is repaired and the replacement tag passes exact-SHA outside-in proof | The first main candidate correctly remained unreleased when Ubuntu WebKit exposed the hidden fourth quiz answer. Replacement tag `v0.19.2` (`af33aa0`) peels to `466c130`; main CI `32547669499`, Pages `32547669498`, and trusted publish `32548094677` are green. Fresh GitHub bytes, PyPI, and `SHA256SUMS.txt` agree on wheel `8d6183d2…7492a6` and sdist `b570ecc3…50ad7`; a cold Python 3.11 install reports 0.19.2 and carries `index-BpwjRXVi.js` plus `index-DgSklSeG.css`. The follow-up changes only this operating truth and must never move the release tag | +| 2026-08-24 | The complete Map uses one complete prepared scene per explicit language projection and a native-scroll viewport canvas; culling is draw delivery only, never logical LOD | A 5,000-box SVG created 35,102 DOM elements and destabilized WebKit before first-run interaction. The default all-language canvas retains all 5,000 boxes and 4,999 routes while drawing five or six visible boxes with 99 DOM elements; language focus remains a named learner-controlled projection. Backend coordinates, certainty, language, progress, Finder, recovery, and accessibility remain authoritative; one position-aware active descendant replaces thousands of tab stops, and hit testing accepts only emitted box coordinates plus renderer-owned painted-row bounds. Removing the canvas still leaves the complete current projection and every navigation target in data | +| 2026-08-24 | The ordinary supported-file cap moves from 1,000 to 5,000 only after the schema-4 backend, Chromium, and WebKit receipt passes | The final candidate records 4.870 s cold, 1.668 s no-change, 170,606,592-byte process RSS high-water, complete 5,000/4,999 source-scene counts, 99 DOM elements per engine, 32.9–40.9 ms canvas arrival with item-specific active-descendant identity and 5,000-of-5,000 set position, 30.9–31.0 ms Finder p95, 76.0–81.1 ms recovery, and zero 320 px overflow. The previous one-engine result did not authorize promotion; this cross-engine result does. Above 5,000 remains an explicit scope prompt, not a hidden or unbounded claim | +| 2026-08-24 | Pages proves the live release ledger before deploying release copy | A main version-bump commit can land before trusted publishing finishes, while the site already says the new version is stable and PyPI-published. The Pages workflow now retries the outside-in release check for a bounded ten minutes before building: ordinary docs changes pass immediately, and a release transition waits until PyPI metadata, GitHub release assets, downloaded hashes, and `SHA256SUMS.txt` match the committed manifest. This preserves exact-main CI and tag ordering without briefly publishing a future version as stable | +| 2026-08-24 | A release candidate proves hosted CI on a PR, then publishes its exact local-main tag before moving origin/main | GitHub renders default-branch README copy independently of Pages, so pushing a version-bump commit to main before PyPI would still label an unpublished version stable. The candidate branch and PR give the exact commit hosted CI; local main then fast-forwards to that reviewed commit and owns the annotated tag while origin/main remains on the prior stable release. Only after trusted publishing and outside-in artifact proof does that same commit move origin/main. README media is tag-pinned, so PyPI never borrows older default-branch screenshots during the transition | +| 2026-08-24 | Source archives exclude and reject parser-fixture outputs that are intentionally ignored by the fixture repository | Hatch's sdist selection could include `sampleproj/generated/` and `sampleproj/ignored.py` after the local parser tests exercised that fixture, even though a clean checkout of the same commit had neither file. The exact-tree gate stopped publication when the two archives differed. Explicit build exclusions now make exercised and clean checkouts byte-identical, while the release-fact gate rejects either path if it ever returns; developer/runtime marker exclusions remain independently enforced | ## Non-Goals — do NOT build (point here when asked) @@ -1933,7 +1978,7 @@ rebuilt from the reviewed source. 2026-07-29** (see Decision Log): deterministic procedural celestial art is approved **at the System tier only**. Bodies there carry an fBm crust, a rim atmosphere and slow surface rotation. What remains OUT: procedural surfaces - at Galaxy range (a level-of-detail limit, not a taste one — up to ~1,000 + at Galaxy range (a level-of-detail limit, not a taste one — up to ~5,000 systems draw there), and any decoration that is not seeded purely by node id. Decoration may never encode a fact; every semantic channel stays parser-owned @@ -1971,13 +2016,12 @@ rebuilt from the reviewed source. ## Edge cases & limits -- >~1,000 supported source files → prompt to scope to a subdirectory; a complete - canvas Map plus the two-engine 5,000-module gate is required before this moves +- >~5,000 supported source files → prompt to scope to a subdirectory - No clear entrypoint → ranked candidates; user picks Home - Syntax errors / partial parses → parse what you can, flag the rest, never crash - Missing/invalid key → galaxy + structure + checks work; explanations show "add your key" - Unsupported-language files → outside the graph and never guessed -- No WebGL → the plain-SVG Map remains available; the 3D Galaxy does not +- No WebGL → the complete Canvas 2D Map remains available; the 3D Galaxy does not ## Definition of done — Phase 0 diff --git a/README.md b/README.md index 69288e3..e37394b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Codemble — an open lapis ensō whose amber star systems light up + Codemble — an open lapis ensō whose amber star systems light up

@@ -16,7 +16,7 @@

- Stable release v0.19.2 + Stable release v0.20.0 CI status Python 3.11 or newer Maps seven languages @@ -32,17 +32,17 @@

- Codemble v0.19.2 showing 199 represented systems across seven languages, with community-tinted route corridors, ranked labels, 35 systems charted, six unreadable test fixtures called out, Home resolved to codemble.cli and still unlit. + Codemble v0.20.0 showing 203 represented systems across seven languages, with community-tinted route corridors, ranked labels, 35 systems charted, six unreadable test fixtures called out, Home resolved to codemble.cli and still unlit.

- Codemble v0.19.2 · every module is represented before the first lesson · + Codemble v0.20.0 · every module is represented before the first lesson · related systems and proven routes share a subtle colour cue · visiting charts a route; passing checks lights a system amber

> [!IMPORTANT] -> **The screen above and the packaged app are both v0.19.2.** The PyPI release +> **The screen above and the packaged app are both v0.20.0.** The PyPI release > maps Python, JavaScript, TypeScript, Go, Java, Rust, C#, and mixed projects; > it includes First Flight, the local project-brief export, proven import-cycle > facts, parser-owned feature journeys, deeper application/test role detection @@ -52,43 +52,43 @@ ## Start here -### Run v0.19.2 — recommended +### Run v0.20.0 — recommended Install [uv](https://docs.astral.sh/uv/) once, then open Codemble whenever you need it: | | Step | Command | | :---: | --- | --- | -| | **Install uv** — a clean Python app runner | `brew install uv` | -| | **Open this release** — pick a project in the browser | `uvx --from codemble==0.19.2 codemble` | +| | **Install uv** — a clean Python app runner | `brew install uv` | +| | **Open this release** — pick a project in the browser | `uvx --from codemble==0.20.0 codemble` | No Homebrew? Use uv's [official installer](https://docs.astral.sh/uv/getting-started/installation/), -or install permanently with `pipx install codemble==0.19.2` and run `codemble`. +or install permanently with `pipx install codemble==0.20.0` and run `codemble`. Pass a folder to skip the project picker: -`uvx --from codemble==0.19.2 codemble ./my-project`. +`uvx --from codemble==0.20.0 codemble ./my-project`. Use the shorter `uvx codemble` when you intentionally want whatever release is newest on PyPI.

- - Download Codemble — wheel, source archive, SHA256 digests, and release notes + + Download Codemble — wheel, source archive, SHA256 digests, and release notes

- Wheel · - Source archive · - SHA256SUMS · - PyPI files · - Release notes + Wheel · + Source archive · + SHA256SUMS · + PyPI files · + Release notes

-### Build the same v0.19.2 app from source +### Build the same v0.20.0 app from source Use this route when you want an editable checkout: ```bash -git clone --branch v0.19.2 --depth 1 https://github.com/udhawan97/Codemble.git +git clone --branch v0.20.0 --depth 1 https://github.com/udhawan97/Codemble.git cd Codemble python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate @@ -104,13 +104,13 @@ the verification commands. | | Plain-English answer | | :---: | --- | -| | **Explore first.** Every module is represented on the first frame. Subtle colour joins systems inside the same import community, while proven bridges stay neutral and possible routes stay dashed. First Flight gives Home a short, repeatable orientation path. | -| | **See how it fits together.** Switch between a 3D galaxy, an import architecture map, and the call workflow from Home. | -| | **Follow one feature end to end.** Study lays out a parser-proven route from Home through files and calls to the selected feature. Easy guides one step at a time; Expert adds the current step's parser evidence and clearly scoped selected-feature impact and verification candidates. | -| | **Know what a change touches.** Impact traces what depends on a structure and what it depends on, with real file locations and certainty labels. | -| | **Prove what you understand.** Graph-derived checks—not a narrator—are the only way to light a system amber. | -| | **Keep the project local.** Parsing, maps, source, Impact, checks, and progress stay on your machine. | -| | **Read mixed projects.** Python, JavaScript, TypeScript, Go, Java, Rust, and C# share one graph and one honesty contract. | +| | **Explore first.** Every module is represented on the first frame. Subtle colour joins systems inside the same import community, while proven bridges stay neutral and possible routes stay dashed. First Flight gives Home a short, repeatable orientation path. | +| | **See how it fits together.** Switch between a 3D galaxy, an import architecture map, and the call workflow from Home. | +| | **Follow one feature end to end.** Study lays out a parser-proven route from Home through files and calls to the selected feature. Easy guides one step at a time; Expert adds the current step's parser evidence and clearly scoped selected-feature impact and verification candidates. | +| | **Know what a change touches.** Impact traces what depends on a structure and what it depends on, with real file locations and certainty labels. | +| | **Prove what you understand.** Graph-derived checks—not a narrator—are the only way to light a system amber. | +| | **Keep the project local.** Parsing, maps, source, Impact, checks, and progress stay on your machine. | +| | **Read mixed projects.** Python, JavaScript, TypeScript, Go, Java, Rust, and C# share one graph and one honesty contract. | Codemble reads supported source. It does **not** run your app, package scripts, compilers, or tests. @@ -119,12 +119,12 @@ compilers, or tests. | 01 · Explore | 02 · Map | | --- | --- | -| Current Codemble galaxy with visible named modules and selective import routes. | Current Codemble architecture map with Home, connected modules, and a counted shelf for modules without a proven import route. | +| Current Codemble galaxy with visible named modules and selective import routes. | Current Codemble canvas Architecture map with Home, connected modules, and complete bottom rows for modules without a proven import route. | | Follow a colour corridor or take First Flight; keyboard focus keeps the active system and its neighbours on the current route mesh named. | Follow real imports from Home; unreachable modules are counted, not erased. | | 03 · Inspect | 04 · Prove | | --- | --- | -| Current Codemble Expert study panel showing parser-owned Impact lists over the architecture map. | Current Codemble Home system after its graph-derived checks were passed, with all four parser-proven structures glowing amber. | +| Current Codemble Expert study panel showing parser-owned Impact lists over the architecture map. | Current Codemble Home system after its graph-derived checks were passed, with all four parser-proven structures glowing amber. | | See what a structure controls and what can break it—no model needed. | Pass checks drawn from the graph; only then does the system turn amber. | ### One graph, two useful views @@ -137,8 +137,11 @@ compilers, or tests. | **Star chart** | Project overview, proven import cycles, progress, and a local Markdown export | Carry parser-owned facts into a handoff | | **Study** | One feature journey, real source, integrated Impact and Connections, Lens notes, and optional narration | Understand how one structure reaches the application | -The Map is plain SVG, so it remains usable on a machine that cannot render the -WebGL galaxy. Easy mode shows an overview and guides one cited journey step at a +The default all-language Map is a complete viewport-rendered canvas; an explicit +language focus keeps every item in that named projection. Both remain usable on +a machine that cannot render the WebGL galaxy without creating one DOM element +per module. +Easy mode shows an overview and guides one cited journey step at a time. Expert mode keeps that exact step selected and adds its parser rule and observation evidence. Impact, Connections, and bounded test candidates describe the selected feature as a whole, not the current route step. Neither mode @@ -187,7 +190,7 @@ export CODEMBLE_OLLAMA_MODEL=gemma4:12b - **Supported languages:** `.py`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.mts`, `.cts`, `.go`, `.java`, `.rs`, and `.cs`. -- **Scale:** above roughly 1,000 supported files, choose a subdirectory in the +- **Scale:** above roughly 5,000 supported files, choose a subdirectory in the picker or pass `--path ./project/src`. - **Ambiguous Home:** choose a parser-ranked candidate in the app or pass `--entrypoint NODE_ID`. @@ -234,8 +237,8 @@ project: | Horizon | Work | | --- | --- | -| **Now** | Collect unaided learner evidence and correctness reports on v0.19.2 | -| **Next** | A canvas-backed complete Map, then the same 5,000-module Chromium and WebKit gate | +| **Now** | Collect unaided learner evidence and correctness reports on v0.20.0 | +| **Next** | Design the privacy boundary for the planned read-only galaxy link | | **Later** | Read-only sharing, new quest types, and a coordinated public launch | Milestones move only when their acceptance evidence exists. See the diff --git a/codemble/adapters/project.py b/codemble/adapters/project.py index 1149729..cb107cb 100644 --- a/codemble/adapters/project.py +++ b/codemble/adapters/project.py @@ -155,11 +155,12 @@ def cache_info(self) -> dict[str, int]: return self._evidence_cache.info() - # Raised from 300 with the Phase C threaded parse and staged loading screen. - # The 2026-08-14 complete 5k Map gate passed Chromium but failed WebKit's - # interaction budget, so explicit --path scopes may go larger while the - # ordinary picker stays here until a complete canvas Map passes both. - scale_cap = 1000 + # Raised from 1,000 only after the complete canvas Map kept every one of + # 5,000 modules and 4,999 routes represented while the same backend, + # Chromium, WebKit, Finder, direct-keyboard, recovery, and 320px budgets + # passed. Explicit --path scopes may still go larger; the ordinary picker + # stops at the largest complete cross-engine receipt. + scale_cap = 5000 def intake(self, path: Path, *, explicit: bool = False) -> ProjectIntake: """Resolve one project scope and every adapter's owned files.""" diff --git a/codemble/web_dist/assets/index-BpwjRXVi.js b/codemble/web_dist/assets/index-DzCSIcHk.js similarity index 95% rename from codemble/web_dist/assets/index-BpwjRXVi.js rename to codemble/web_dist/assets/index-DzCSIcHk.js index 35df61f..9573d70 100644 --- a/codemble/web_dist/assets/index-BpwjRXVi.js +++ b/codemble/web_dist/assets/index-DzCSIcHk.js @@ -5449,5 +5449,5 @@ void main(){ gl_FragColor = vec4(color, 1.0); } -`;Object.freeze({vertex:$W,fragment:eG});function tG(e){let t=2166136261,n=String(e??``);for(let e=0;e>>0)/4294967296}function nG(e=32){return new yo(1,e,Math.max(8,e/2))}function rG({node:e,color:t,palette:n}){return new jo({vertexShader:$W,fragmentShader:eG,uniforms:{uBase:{value:new H(t)},uAmber:{value:new H(n.star)},uSeed:{value:tG(e.id)},uLit:{value:+!!e.understood},uPartial:{value:+!!e.partial},uClass:{value:+(e.kind===`class`)},uDim:{value:+!!e.focusDim}}})}function iG({node:e,color:t,palette:n,radius:r,geometry:i}){let a=new na(i,rG({node:e,color:t,palette:n}));return a.scale.setScalar(r),a.rotation.set(tG(`${e.id}:tilt`)*.9-.45,tG(e.id)*Math.PI*2,0),a.userData.codembleBody=!0,a}function aG(e,{reducedMotion:t=!1}={}){if(t)return()=>{};let n=0,r=performance.now(),i=()=>{let t=performance.now(),a=Math.min(.05,(t-r)/1e3);r=t,e.traverse(e=>{e.userData?.codembleBody&&(e.rotation.y+=a*.06)}),n=requestAnimationFrame(i)};return n=requestAnimationFrame(i),()=>cancelAnimationFrame(n)}var oG=`.orientation-copy button, .legend-toggle`,sG=`.orientation-bar, .keyboard-focus`;function cG({host:e,renderer:t,getStyle:n=e=>getComputedStyle(e)}){let r=e?.getBoundingClientRect?.(),i=lG(r,t),a=e?.closest?.(`.map-stage`);return Object.freeze(!a||!r?{viewport:i,clickObstructions:Object.freeze([]),nameObstructions:Object.freeze([])}:{viewport:i,clickObstructions:uG(a,oG,r,{accepts:e=>n(e).pointerEvents!==`none`}),nameObstructions:uG(a,sG,r)})}function lG(e,t){if(e?.width&&e?.height)return Object.freeze({width:e.width,height:e.height});let n=t?.width?.(),r=t?.height?.();return n&&r?Object.freeze({width:n,height:r}):null}function uG(e,t,n,{accepts:r=()=>!0}={}){let i=[];for(let a of e.querySelectorAll(t)){if(!r(a))continue;let e=a.getBoundingClientRect();!e.width||!e.height||i.push(Object.freeze({left:e.left-n.left,right:e.right-n.left,top:e.top-n.top,bottom:e.bottom-n.top}))}return Object.freeze(i)}var dG=1600,fG=.22,pG=.52,mG=.74,hG=3,gG=7;function _G(e){let t=Math.min(1,Math.max(0,e)),n=(e,n)=>Math.min(1,Math.max(0,(t-e)/(n-e)));return Object.freeze({ignite:n(0,fG),travel:n(fG,pG),flare:n(pG,mG),settle:n(mG,1)})}function vG(e,t,n=new Map){let r=[];for(let n of t??[]){if(!n?.certain)continue;let t=n.dst===e?n.src:n.src===e?n.dst:null;t===null||t===e||r.push(t)}let i=[...new Set(r)];return i.sort((e,t)=>{let r=n.get(e),i=n.get(t);return(typeof r==`number`?r:2**53-1)-(typeof i==`number`?i:2**53-1)||e.localeCompare(t)}),i.slice(0,hG)}function yG({scene:e,regionId:t,palette:n,dressing:r,routes:i=[],hopsById:a=new Map}){if(Bd())return()=>{};let o=e.getObjectByName(`codemble-system-${t}`);if(!o)return()=>{};let s=[];o.traverse(e=>{e.isSprite&&s.push([e,e.material.opacity,e.scale.clone()])});let c=s.map(([e])=>e.material.color.clone()),l=new H(n.star),u=new V;o.getWorldPosition(u);let d=[];if(r?.spark)for(let n of vG(t,i,a)){let t=e.getObjectByName(`codemble-system-${n}`);if(!t)continue;let i=new V;t.getWorldPosition(i);let a=r.spark();a.scale.setScalar(gG),a.position.copy(i),e.add(a),d.push({sprite:a,origin:i})}let f=()=>{s.forEach(([e,t,n],r)=>{e.material.color.copy(c[r]),e.material.opacity=t,e.scale.copy(n)});for(let{sprite:t}of d)e.remove(t),t.material.opacity=0;d.length=0},p=0,m=performance.now(),h=()=>{let e=Math.min(1,(performance.now()-m)/dG),t=_G(e),n=1-(1-t.travel)**3;for(let{sprite:e,origin:r}of d)e.position.lerpVectors(r,u,n),e.material.opacity=t.flare>0?0:Math.min(1,t.ignite)*.9;let r=Math.sin(Math.min(1,t.flare)*Math.PI*.5)*(1-t.settle);if(s.forEach(([e,t,n],i)=>{e.material.color.copy(c[i]).lerp(l,r*.85),e.material.opacity=t+r*.5,e.scale.copy(n).multiplyScalar(1+r*.45)}),e<1){p=requestAnimationFrame(h);return}f()};return p=requestAnimationFrame(h),()=>{cancelAnimationFrame(p),f()}}function bG(e){if(!e?.selected_entrypoint)return[];let t=new Map((e.nodes??[]).map(e=>[e.id,e])),n=new Map((e.regions??[]).map(e=>[e.id,e])),r=t.get(e.selected_entrypoint),i=r?n.get(r.region):null;if(!i?.home)return[];let a=new Map;for(let r of e.edges??[]){if(r.kind!==`import`||r.external||r.certain!==!0)continue;let e=t.get(r.src)?.region,i=t.get(r.dst)?.region;!e||!i||e===i||!n.has(e)||!n.has(i)||(a.has(e)||a.set(e,new Set),a.get(e).add(i))}let o=new Map,s=new Map;for(let t of e.region_edges??[])!n.has(t.src)||!n.has(t.dst)||t.src===t.dst||(o.has(t.src)||o.set(t.src,new Set),s.has(t.dst)||s.set(t.dst,new Set),o.get(t.src).add(t.dst),s.get(t.dst).add(t.src));return[i,...[...a.get(i.id)??[]].map(e=>n.get(e)).filter(Boolean).sort((e,t)=>(t.centrality??0)-(e.centrality??0)||String(e.id).localeCompare(String(t.id)))].slice(0,6).map(e=>({id:e.id,language:e.language,usedBy:s.get(e.id)?.size??0,uses:o.get(e.id)?.size??0,home:e.id===i.id}))}function xG(e,{active:t,reducedMotion:n}){return t&&n?0:e}var SG=(e,t)=>e.x*t.x+e.y*t.y+e.z*t.z,CG=(e,t)=>({x:e.y*t.z-e.z*t.y,y:e.z*t.x-e.x*t.z,z:e.x*t.y-e.y*t.x});function wG(e){if(!e)return null;let{x:t,y:n,z:r}=e;if(!Number.isFinite(t)||!Number.isFinite(n)||!Number.isFinite(r))return null;let i=Math.hypot(t,n,r);return i>0?{x:t/i,y:n/i,z:r/i}:null}function TG({points:e,direction:t,fov:n,aspect:r,margin:i=.1}){let a=wG(t);if(!a||!Array.isArray(e)||e.length===0||!Number.isFinite(n)||n<=0||n>=180||!Number.isFinite(r)||r<=0)return null;let o=1-Math.min(Math.max(i,0),.9),s=Math.tan(n*Math.PI/360)*o,c=s*r,l=wG(CG(Math.abs(a.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},a));if(!l)return null;let u=CG(a,l),d=0;for(let t of e){if(!t)continue;let{x:e,y:n,z:r}=t;if(!Number.isFinite(e)||!Number.isFinite(n)||!Number.isFinite(r))continue;let i=SG(t,a),o=Number.isFinite(t.radius)?Math.max(0,t.radius):0,f=(Math.abs(SG(t,l))+o)/c,p=(Math.abs(SG(t,u))+o)/s;d=Math.max(d,i+f,i+p)}return d>0?d:null}function EG(e,t){let n=wG(e);return!n||!Number.isFinite(t)?null:{x:n.x*t,y:n.y*t,z:n.z*t}}var DG=24,OG=30;function kG(e,t){if(!e.length)return 0;let n=n=>{let r=-1/0,i=1/0;for(let a=0;ar&&(r=o),o0?r=e:i=e}return(r+i)/2}function AG({points:e,direction:t,fov:n,aspect:r,margin:i=.1}){let a=wG(t);if(!a||!Array.isArray(e)||e.length===0)return null;let o=e.filter(e=>e&&Number.isFinite(e.x)&&Number.isFinite(e.y)&&Number.isFinite(e.z));if(!o.length)return null;let s=wG(CG(Math.abs(a.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},a));if(!s)return null;let c=CG(a,s),l=e=>{let t=1/0,n=-1/0;for(let r of o){let i=SG(r,e);in&&(n=i)}return(t+n)/2},u={x:s.x*l(s)+c.x*l(c)+a.x*l(a),y:s.y*l(s)+c.y*l(c)+a.y*l(a),z:s.z*l(s)+c.z*l(c)+a.z*l(a)},d=()=>o.map(e=>({x:e.x-u.x,y:e.y-u.y,z:e.z-u.z,radius:e.radius})),f=TG({points:d(),direction:t,fov:n,aspect:r,margin:i});if(f===null)return null;for(let e=0;ee.language===t),r=new Set(n.map(e=>e.id)),i=new Set(e.nodes.map(e=>e.id)),a=new Set(n.map(e=>e.region)),o=new Set(n.map(e=>e.file));return{...e,nodes:n,edges:e.edges.filter(e=>r.has(e.src)&&(r.has(e.dst)||e.external||!i.has(e.dst))),entrypoint_candidates:e.entrypoint_candidates.filter(e=>r.has(e)),selected_entrypoint:r.has(e.selected_entrypoint)?e.selected_entrypoint:null,file_hashes:Object.fromEntries(Object.entries(e.file_hashes).filter(([e])=>o.has(e))),concept_annotations:e.concept_annotations.filter(e=>e.language===t&&r.has(e.node_id)),role_evidence:(e.role_evidence??[]).filter(e=>r.has(e.node_id)),regions:e.regions.filter(e=>a.has(e.id)),region_edges:e.region_edges.filter(e=>a.has(e.src)&&a.has(e.dst)),partial_files:e.partial_files.filter(e=>o.has(e))}}function NG(e,t){if(!e||!t||t===`all`)return e;let n=e.architecture,r=n.boxes.filter(e=>e.language===t),i=new Set(r.map(e=>e.id)),a=e.workflow;return{...e,architecture:{...n,boxes:r,edges:n.edges.filter(e=>i.has(e.src)&&i.has(e.dst)),unreachable:n.unreachable.filter(e=>i.has(e))},workflow:{...a,nodes:a.nodes.filter(e=>e.language===t),unreachable:a.unreachable.filter(e=>e.language===t)}}}function PG(e){let t=new Map;for(let n of e.regions)t.set(n.language,(t.get(n.language)??0)+1);let n=[...t].map(([e,t])=>({id:e,label:LG(e),shortLabel:RG(e),count:t})).sort((e,t)=>e.label.localeCompare(t.label));return[{id:`all`,label:`All languages`,shortLabel:`All`,count:e.regions.length},...n]}function FG(e,t){let n=new Map(e.nodes.map(e=>[e.id,e])),r=new Map;for(let i of e.concept_annotations??[]){let e=`${i.language}:${i.concept}`,a=r.get(e)??{language:i.language,concept:i.concept,occurrences:0,nodeIds:new Set,studiedNodeIds:new Set,understoodNodeIds:new Set};a.occurrences+=1,a.nodeIds.add(i.node_id),t.has(i.node_id)&&a.studiedNodeIds.add(i.node_id),n.get(i.node_id)?.understood&&a.understoodNodeIds.add(i.node_id),r.set(e,a)}return[...r.values()].map(e=>({language:e.language,concept:e.concept,occurrences:e.occurrences,nodes:e.nodeIds.size,studied_nodes:e.studiedNodeIds.size,understood_nodes:e.understoodNodeIds.size})).sort((e,t)=>e.language.localeCompare(t.language)||e.concept.localeCompare(t.concept))}function IG(e){let t={javascript:`JavaScript`,jsx:`JSX`,typescript:`TypeScript`,csharp:`C#`,"linq-query":`LINQ Query`};return t[e]?t[e]:e.split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}function LG(e){return IG(e)}function RG(e){return e===`javascript`?`JS`:e===`typescript`?`TS`:LG(e)}var zG=Object.freeze({python:`--cm-neb-python`,javascript:`--cm-neb-js`,typescript:`--cm-neb-ts`,go:`--cm-neb-go`,java:`--cm-neb-java`,rust:`--cm-neb-rust`,csharp:`--cm-neb-csharp`});function BG(e){let t=zG[e];return t?`var(${t})`:null}var VG=2;function HG(e,{showAll:t=!1,selectionId:n=null,visitedIds:r=null}={}){let i=()=>new Set(e.regions.map(e=>e.id));if(t||!e.regions.some(e=>e.home))return i();let a=new Set,o=new Set;for(let t of e.regions)typeof t.hops_from_home==`number`&&t.hops_from_home<=VG&&a.add(t.id),t.understood&&o.add(t.id),r&&r.has(t.id)&&o.add(t.id);n&&o.add(n);for(let e of o)a.add(e);for(let t of e.region_edges)(o.has(t.src)||o.has(t.dst))&&(a.add(t.src),a.add(t.dst));return a}function UG(e,t){if(!e||!e.length)return null;let n=e.reduce((e,t)=>e+t.count,0);return t===`easy`?`${WG(e.map(e=>`${e.count} ${e.language??e.extension}`))} ${n===1?`file`:`files`} not included`:`${e.map(e=>`${e.count} ${e.extension}`).join(` · `)} not charted`}function WG(e){return e.length<=1?e.join(``):`${e.slice(0,-1).join(`, `)} and ${e[e.length-1]}`}function GG(e){let t=new Map;for(let n of e.nodes)t.has(n.region)||t.set(n.region,n.file);return t}function KG(e){let t=e.split(`/`).filter(Boolean);return t.length>1?t.slice(-2).join(`/`):t[0]??e}function qG(e){let t=GG(e);return e.regions.map(e=>({id:e.id,file:t.get(e.id)??e.id,label:KG(t.get(e.id)??e.id),language:e.language,community:e.community,understood:e.understood,home:e.home,hops:typeof e.hops_from_home==`number`?e.hops_from_home:null,centrality:e.centrality,loc:e.loc})).sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id))}function JG(e){if(!e.length)return``;let t=e.map(e=>e.file.split(`/`).slice(0,-1)),n=t[0];for(let e of t.slice(1)){let t=0;for(;t{let n=JG(t),r=n.endsWith(`/`)?n:``;return{community:e,name:n,members:t.map(e=>({...e,display:r&&e.file.startsWith(r)?e.file.slice(r.length):e.file})).sort((e,t)=>e.display.localeCompare(t.display))}}).sort((e,t)=>t.members.length-e.members.length||e.community-t.community)}var XG=new Set([`tests`,`test`,`__tests__`,`spec`,`specs`]);function ZG(e){return String(e).split(`/`).slice(0,-1).some(e=>XG.has(e))}function QG(e){if(!e?.length)return null;let t=e=>String(e).split(`/`).filter(Boolean)[0]??null,n=t(e[0]);return n&&e.every(e=>t(e)===n)?n:null}var $G=5,eK=2;function tK(e){return Number.isInteger(e)&&e>=0&&e<8?e:null}var nK=Object.freeze({bright:1,mid:.72,dim:.5}),rK=.32;function iK(e){let t=/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(String(e));return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function aK(e,t,n){let r=iK(e),i=iK(t);if(!r||!i)return e;let a=e=>Math.round(r[e]*n+i[e]*(1-n));return`rgb(${a(0)}, ${a(1)}, ${a(2)})`}function oK(e,t,n,r){let i=tK(t),a=i===null?null:e.communities?.[i];if(!a)return EK(n,e,r);let o=n>=r?`bright`:n>=1?`mid`:`dim`;return aK(a,e.ground,nK[o])}function sK(e,t){let n=tK(t),r=n===null?null:e.communities?.[n];return r?aK(r,e.route,rK):e.route}var cK=e=>e.charted===!0,lK=e=>e.charted===!1;function uK(e,t,n){let{activeId:r,neighborIds:i}=t??{};return r?e.id===r?n.orbit:i?.has(e.id)?e.color:n.faded:e.color}function dK(e,t,n,r){if(e.focusDim)return n.faded;let{activeId:i,neighborIds:a}=t??{},o=e.certain?e.color??n.route:n.routePossible;if(!i)return o;let s=r(e.source),c=r(e.target);return s===i||c===i?e.certain?n.orbit:n.routePossible:a?.has(s)&&a?.has(c)?o:n.faded}function fK(e,t,n=null){let r=e=>n===null||n.has(e),i=GG(e),a=new Map(e.regions.map(e=>[e.id,e])),o=vK(e.region_edges);return{nodes:e.regions.map(n=>{let a=r(n.id);return{...n,usedBy:o.usedBy.get(n.id)??0,uses:o.uses.get(n.id)??0,kind:`region`,name:n.id,label:KG(i.get(n.id)??n.id),charted:a,fx:n.x,fy:n.y,fz:n.z,val:xK(n.loc,5,24),color:n.understood?t.star:e.nodes.some(e=>e.region===n.id&&e.partial)?t.routePossible:oK(t,n.community_family,n.centrality,$G),focusDim:!1}}),links:e.region_edges.filter(e=>r(e.src)&&r(e.dst)).map(e=>{let n=a.get(e.src),r=a.get(e.dst),i=n?.community===r?.community&&n?.community_family===r?.community_family?tK(n?.community_family):null;return{...e,source:e.src,target:e.dst,color:e.certain?sK(t,i):t.routePossible,community_family:i,focusDim:!1}})}}function pK(e,t,n,{selectedId:r=null}={}){let i=e.nodes.filter(e=>e.region===t),a=new Set(i.map(e=>e.id)),o=e.regions.find(e=>e.id===t)?.community_family,s=e.edges.filter(e=>e.kind===`call`&&!e.external&&a.has(e.src)&&a.has(e.dst)),c=vK(e.edges.filter(e=>!e.external)),l=new Set(r?[r]:[]);if(r)for(let e of s)e.src===r&&l.add(e.dst),e.dst===r&&l.add(e.src);return{nodes:i.map(e=>({...e,usedBy:c.usedBy.get(e.id)??0,uses:c.uses.get(e.id)??0,fx:e.system_x,fy:e.system_y,fz:e.system_z,val:xK(e.loc,2.8,11),color:e.understood?n.star:e.partial?n.routePossible:oK(n,o,e.centrality,eK),selected:e.id===r,focusDim:!!r&&!l.has(e.id)})),links:s.map(e=>({...e,source:e.src,target:e.dst,color:e.certain?n.route:n.routePossible,focusDim:!!r&&e.src!==r&&e.dst!==r}))}}function mK(e){return e.regions.find(e=>e.home)??e.regions[0]??null}function hK(e){let t=String(e?.project_root??``);return(t.split(`/`).filter(Boolean).at(-1)??t)||`Local project`}function gK(e,t){if(!e?.length)return null;let n=[...e].sort((e,t)=>t.length-e.length||e.join(`\0`).localeCompare(t.join(`\0`)))[0].join(`, `);return t===`easy`?`${e.length} ${e.length===1?`file circle`:`file circles`} found. Largest group: ${n}. Each file can reach every other through proven imports.`:`${e.length} proven import ${e.length===1?`cycle group`:`cycle groups`}. Largest strongly connected group: ${n}.`}function _K(e){let t=e?.regions??[],n=e?.nodes??[],r=e?.edges??[],i=new Map;for(let e of t)i.set(e.language,(i.get(e.language)??0)+1);let a=new Map;for(let e of n)a.set(e.language,(a.get(e.language)??0)+1);let o=[...i.entries()].sort((e,t)=>t[1]-e[1]||String(e[0]).localeCompare(String(t[0]))).map(([e,t])=>({language:e,label:LG(e),count:t,structures:a.get(e)??0})),s=e=>t.filter(t=>Number.isFinite(t[e])).sort((t,n)=>n[e]-t[e]||String(t.id).localeCompare(String(n.id))).slice(0,5).map(t=>({id:t.id,value:t[e]})),c=mK(e);return{projectName:hK(e),modules:t.length,structures:n.length,lines:t.reduce((e,t)=>e+(t.loc??0),0),languages:o,home:e?.selected_entrypoint&&c?{id:c.id,language:c.language}:null,biggest:s(`loc`),busiest:s(`centrality`),unreadable:(e?.partial_files??[]).length,unsupported:(e?.unsupported_sources??[]).reduce((e,t)=>e+(t.count??0),0),unsupportedSources:[...e?.unsupported_sources??[]].map(e=>({extension:e.extension,language:e.language,count:e.count??0})).sort((e,t)=>String(e.extension).localeCompare(String(t.extension))||String(e.language).localeCompare(String(t.language))),relationships:{proven:r.filter(e=>e.certain===!0).length,hedged:r.filter(e=>e.certain===!1).length},importCycles:(e?.import_cycles??[]).map(e=>[...e]),understood:t.filter(e=>e.understood).length}}function vK(e){let t=new Map,n=new Map;for(let r of e??[])n.set(r.src,(n.get(r.src)??0)+1),t.set(r.dst,(t.get(r.dst)??0)+1);return{usedBy:t,uses:n}}function yK(e){let t=e.kind===`region`?`star system`:e.kind,n=e.partial?` · unchartable · syntax error`:``,r=e.home?` · Home`:``,i=(e.usedBy?` · used by ${e.usedBy}`:``)+(e.uses?` · uses ${e.uses}`:``),a=e.system_orbit?.kind===`unreached`?` · no proven call path`:Number.isInteger(e.system_orbit?.call_depth)&&e.system_orbit.call_depth>0?` · call layer ${e.system_orbit.call_depth}`:``;return`${e.name} · ${t} · ${e.loc} LOC${r}${i}${n}${a}`}function bK(e){let t=e.kind===`import`?`import`:e.kind===`call`?`call`:`import route`,n=e.certain?`certain`:t===`call`?`possible call`:`possible import`,r=typeof e.weight==`number`?` · ${e.weight} ${e.weight===1?`import`:`imports`}`:``,i=typeof e.lineno==`number`?` · line ${e.lineno}`:``;return`${e.src} → ${e.dst} · ${t} · ${n}${r}${i}`}function xK(e,t,n){return Math.min(n,Math.max(t,Math.sqrt(Math.max(1,e))*1.15))}var SK=1.6,CK=6.5;function wK(e){return Math.cbrt(e?.val??1)*SK}function TK(e){return wK(e)*CK/2}function EK(e,t,n){return e>=n?t.nodeBright:e>=1?t.node:t.nodeDim}var DK={GALAXY:{min:120,max:640},SYSTEM:{min:55,max:320},STUDY:{min:22,max:170}},OK={GALAXY:{x:0,y:165,z:286},SYSTEM:{x:0,y:52,z:150}},kK=1.15,AK={x:20,y:15,z:42};function jK(e){if(!e)return null;let{width:t,height:n}=e;return!Number.isFinite(t)||!Number.isFinite(n)||t<=0||n<=0?null:t/n}var MK=e=>e.map(e=>({x:e.fx??e.x??0,y:e.fy??e.y??0,z:e.fz??e.z??0,radius:TK(e)})),NK=e=>{let t=[];for(let n of e??[])for(let e of n.radii??[])t.push({x:e,y:0,z:0},{x:-e,y:0,z:0},{x:0,y:0,z:e},{x:0,y:0,z:-e});return t};function PK({level:e,nodes:t,orbitPlan:n,fov:r,aspect:i,viewport:a,chrome:o}){let s=OK[e]??OK.GALAXY,c=DK[e]??DK.GALAXY,l=e===jG.GALAXY?[]:NK(n),u=Array.isArray(t)?t:[],d=e=>[...MK(e),...l],f=e=>AG({points:d(e),direction:s,fov:r,aspect:i}),p=u.filter(cK),m=(p.length?f(p):null)??f(u),h={target:m?.target??{x:0,y:0,z:0},distance:m?.distance??Math.hypot(s.x,s.y,s.z)},{target:g,distance:_}=m?IK({...h,points:d(p.length?p:u),direction:s,fov:r,viewport:a,chrome:o}):h,v=EG(s,_)??s,y=TG({points:d(u).map(e=>({x:e.x-g.x,y:e.y-g.y,z:e.z-g.z,radius:e.radius})),direction:s,fov:r,aspect:i});return{position:{x:g.x+v.x,y:g.y+v.y,z:g.z+v.z},target:g,distance:_,min:Math.min(c.min,_),max:Math.max(c.max,(y??_)*kK),fitted:m!==null}}function FK(e,t){let{width:n,height:r}=e??{};if(!Number.isFinite(n)||!Number.isFinite(r)||n<=0||r<=0)return null;let i={left:0,right:n,top:0,bottom:r},a=(t??[]).filter(e=>e&&Number.isFinite(e.left)&&Number.isFinite(e.right)&&Number.isFinite(e.top)&&Number.isFinite(e.bottom));for(let e=0;ee.lefti.left&&e.topi.top);if(!e)break;i=[{...i,right:Math.max(i.left,e.left)},{...i,left:Math.min(i.right,e.right)},{...i,bottom:Math.max(i.top,e.top)},{...i,top:Math.min(i.bottom,e.bottom)}].reduce((e,t)=>{let n=e=>Math.max(0,e.right-e.left)*Math.max(0,e.bottom-e.top);return n(t)>n(e)?t:e})}let o={left:i.left,right:i.right,top:i.top,bottom:i.bottom,width:Math.max(0,i.right-i.left),height:Math.max(0,i.bottom-i.top)};return o.width>0&&o.height>0?o:null}function IK({target:e,distance:t,points:n,direction:r,fov:i,viewport:a,chrome:o,margin:s=.1}){let c={target:e,distance:t},l=LK(r),u=FK(a,o);if(!l||!u||!Number.isFinite(t)||!Number.isFinite(i)||!Array.isArray(n)||!n.length)return c;let{width:d,height:f}=a;if(u.width>=d&&u.height>=f)return c;let p=LK(RK(Math.abs(l.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},l));if(!p)return c;let m=RK(l,p),h=i*Math.PI/360,g=t=>{let r=1/0,i=-1/0,a=1/0,o=-1/0;for(let s of n){let n={x:s.x-e.x,y:s.y-e.y,z:s.z-e.z},c=t-zK(n,l);if(c<=0)continue;let u=2*c*Math.tan(h)/f;if(!(u>0))continue;let d=Number.isFinite(s.radius)?Math.max(0,s.radius):0,g=zK(n,p)/u,_=zK(n,m)/u,v=d/u;r=Math.min(r,g-v),i=Math.max(i,g+v),a=Math.min(a,_-v),o=Math.max(o,_+v)}return!Number.isFinite(r)||!Number.isFinite(a)?null:{width:i-r,height:o-a,x:(r+i)/2,y:(a+o)/2}},_=g(t);if(!_)return c;let v=1-Math.min(.9,Math.max(0,s)),y=Math.max(1,_.width/Math.max(1,u.width*v),_.height/Math.max(1,u.height*v)),b=t*y,x=y===1?_:g(b);if(!x)return c;let S=2*b*Math.tan(h)/f,C=u.left+u.width/2-d/2,w=u.top+u.height/2-f/2,T=(x.x-C)*S,E=(x.y+w)*S;return{distance:b,target:{x:e.x+p.x*T+m.x*E,y:e.y+p.y*T+m.y*E,z:e.z+p.z*T+m.z*E}}}function LK(e){if(!e)return null;let{x:t,y:n,z:r}=e;if(![t,n,r].every(Number.isFinite))return null;let i=Math.hypot(t,n,r);return i>0?{x:t/i,y:n/i,z:r/i}:null}function RK(e,t){return{x:e.y*t.z-e.z*t.y,y:e.z*t.x-e.x*t.z,z:e.x*t.y-e.y*t.x}}function zK(e,t){return e.x*t.x+e.y*t.y+e.z*t.z}function BK(e){return DK[e]??DK.GALAXY}function VK(e){if(!e)return null;let{system_x:t,system_y:n,system_z:r}=e;return!Number.isFinite(t)||!Number.isFinite(n)||!Number.isFinite(r)?null:{position:{x:t+AK.x,y:n+AK.y,z:r+AK.z},target:{x:t,y:n,z:r}}}var HK=Object.freeze({width:132,height:30}),UK=Object.freeze({far:14,near:44}),WK=Object.freeze([0,-1,1,-2,2]),GK=4,KK=.034;function qK(e,{radius:t,aspect:n}){return e.scale.set(KK*n,KK,1),e.userData.baseOffsetY=t*2.2+1.2,e.position.set(0,e.userData.baseOffsetY,0),e.visible=!1,e.userData.codembleLabel=!0,e.userData.screenWidthFraction=KK*n,e.userData.screenHeightFraction=KK,e}function JK(e){let t=YK(e),n=new V,r=new V;function i({scene:e,camera:i,width:a,height:o,distance:s,distanceBounds:c,activeNodeId:l=null,neighborIds:u=null,chrome:d=[]}){let f=XK(e);for(let e of f)e.visible=!1;if(!f.length||!t.size)return Object.freeze({budget:0,shown:0,visibleIds:Object.freeze([])});let p=ZK(s,c),m=[];for(let e of f){let s=e.userData.nodeId,c=e.parent;if(!c)continue;c.getWorldPosition(r);let d=e.userData.baseOffsetY??0,f=QK(r,d,i,a,o,n);if(!f)continue;let p=QK(r,d+1,i,a,o,n),h=p?Math.abs(p.screenY-f.screenY):0;m.push({sprite:e,nodeId:s,origin:r.clone(),base:d,pixelsPerUnit:h,anchor:f,halfWidth:(e.userData.screenWidthFraction??.14)*o/2,halfHeight:(e.userData.screenHeightFraction??.034)*o/2,rank:s===l?-2:u?.has(s)?-1:t.get(s)??1/0})}m.sort((e,t)=>e.rank-t.rank||e.nodeId.localeCompare(t.nodeId));let h=new Set,g=[];for(let e of m){if(g.length>=p)break;let t=$K(e,{camera:i,width:a,height:o,projected:n,taken:h,chrome:d});if(t){for(let e of t.cells)h.add(e);e.sprite.position.y=e.base+t.offset,e.sprite.visible=!0,g.push(e.nodeId)}}return Object.freeze({budget:p,shown:g.length,visibleIds:Object.freeze(g)})}function a(e){for(let t of XK(e))t.visible=!1}return Object.freeze({hide:a,place:i})}function YK(e){let t=e.filter(e=>e.label).map(e=>({id:e.id,weight:(e.home?3e6:0)+(e.understood?1e6:0)+(e.centrality??0)*1e3})).sort((e,t)=>t.weight-e.weight||e.id.localeCompare(t.id));return new Map(t.map((e,t)=>[e.id,t]))}function XK(e){let t=[];return e.traverse(e=>{e.userData?.codembleLabel&&t.push(e)}),t}function ZK(e,t){let n=Math.max(1,t.max-t.min),r=1-Math.min(1,Math.max(0,(e-t.min)/n));return Math.round(UK.far+(UK.near-UK.far)*r)}function QK(e,t,n,r,i,a){if(a.set(e.x,e.y+t,e.z).project(n),a.z>1)return null;let o=(a.x*.5+.5)*r,s=(-a.y*.5+.5)*i;return o<0||o>r||s<0||s>i?null:{screenX:o,screenY:s}}function $K(e,{camera:t,width:n,height:r,projected:i,taken:a,chrome:o}){let s=e.pixelsPerUnit?(e.halfHeight*2+GK)/e.pixelsPerUnit:0;for(let c of WK){let l=c*s,u=c===0?e.anchor:QK(e.origin,e.base+l,t,n,r,i);if(!u||!eq(u,e,n,r)||!tq(u,e,o))continue;let d=nq(u,e);if(!d.some(e=>a.has(e)))return{cells:d,offset:l}}return null}function eq(e,{halfWidth:t,halfHeight:n},r,i){return e.screenX-t>=0&&e.screenX+t<=r&&e.screenY-n>=0&&e.screenY+n<=i}function tq(e,{halfWidth:t,halfHeight:n},r){if(!r?.length)return!0;let i=e.screenX-t,a=e.screenX+t,o=e.screenY-n,s=e.screenY+n;return!r.some(e=>ie.left&&oe.top)}function nq(e,{halfWidth:t,halfHeight:n}){let r=Math.floor((e.screenX-t)/HK.width),i=Math.floor((e.screenX+t)/HK.width),a=Math.floor((e.screenY-n)/HK.height),o=Math.floor((e.screenY+n)/HK.height),s=[];for(let e=r;e<=i;e+=1)for(let t=a;t<=o;t+=1)s.push(`${e}:${t}`);return s}var rq=128,iq=256,aq=.026;function oq(e,t){let n=document.createElement(`canvas`);n.width=e,n.height=e;let r=n.getContext(`2d`),i=r.createRadialGradient(e/2,e/2,0,e/2,e/2,e/2);for(let[e,n]of t)i.addColorStop(e,`rgba(255, 255, 255, ${n})`);r.fillStyle=i,r.fillRect(0,0,e,e);let a=new La(n);return a.colorSpace=ot,a}function sq(e){let t=document.createElement(`canvas`);t.width=e,t.height=e;let n=t.getContext(`2d`);n.strokeStyle=`rgba(255, 255, 255, 1)`,n.lineWidth=e*.05,n.beginPath(),n.arc(e/2,e/2,e*.4,0,Math.PI*2),n.stroke();let r=new La(t);return r.colorSpace=ot,r}function cq(e){let t=[];function n(e){let n=e.dispose.bind(e);return e.dispose=()=>{},t.push(n),e}let r=n(oq(rq,[[0,.85],[.25,.42],[.6,.1],[1,0]])),i=n(oq(iq,[[0,.32],[.45,.14],[.8,.03],[1,0]])),a=n(sq(rq)),o=new Map,s=new Map,c=null,l=new Map;function u(t){return l.has(t)||l.set(t,n(dq(t,e))),l.get(t)}function d(t){return o.has(t)||o.set(t,n(new bi({map:r,color:new H(t).multiply(new H(e.starHalo)),blending:2,transparent:!0,depthWrite:!1,opacity:.6}))),o.get(t)}return{halo(e,t){let n=new Pi(d(e.color));return n.scale.setScalar(t*6.5),n.renderOrder=-1,n},nebula(e,t){s.has(e)||s.set(e,n(new bi({map:i,color:new H(e),blending:2,transparent:!0,depthWrite:!1,opacity:.16})));let r=new Pi(s.get(e));return r.scale.setScalar(t),r.renderOrder=-2,r},spark(){c||=n(new bi({map:r,color:new H(e.star),blending:2,transparent:!0,depthWrite:!1,opacity:0}));let t=new Pi(c);return t.renderOrder=2,t},reticle(t){let n=new Pi(new bi({map:a,color:new H(e.orbit),transparent:!0,depthWrite:!1,depthTest:!1}));return n.scale.setScalar(t*5),n.renderOrder=3,n},label(e,t){let n=u(e),r=new Pi(n);return qK(r,{radius:t,aspect:n.userData.aspect??4}),r.renderOrder=4,r},guideLabel(e){let t=u(e),n=new Pi(t),r=t.userData.aspect??4;return n.scale.set(aq*r,aq,1),n.renderOrder=2,n},dispose(){for(let e of t)e();t.length=0,o.clear(),s.clear(),c=null,l.clear()}}}var lq=34,uq=12;function dq(e,t){let n=document.createElement(`canvas`),r=n.getContext(`2d`),i=`${lq}px "JetBrains Mono", ui-monospace, monospace`;r.font=i;let a=Math.ceil(r.measureText(e).width)+uq*2;n.width=a,n.height=58,r.font=i,r.textBaseline=`middle`,r.textAlign=`center`,r.fillStyle=t.labelPlate,fq(r,0,0,a,58,8),r.fill(),r.fillStyle=t.labelInk,r.fillText(e,a/2,30);let o=new La(n);o.colorSpace=ot;let s=new bi({map:o,transparent:!0,depthWrite:!1,depthTest:!1,sizeAttenuation:!1});s.userData.aspect=a/58;let c=s.dispose.bind(s);return s.dispose=()=>{o.dispose(),c()},s}function fq(e,t,n,r,i,a){e.beginPath(),e.moveTo(t+a,n),e.arcTo(t+r,n,t+r,n+i,a),e.arcTo(t+r,n+i,t,n+i,a),e.arcTo(t,n+i,t,n,a),e.arcTo(t,n,t+r,n,a),e.closePath()}function pq(e){return Object.entries(e??{}).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${e}:${t}`).join(`|`)}function mq(e){let t=e>>>0;return function(){t=t+1831565813>>>0;let e=Math.imul(t^t>>>15,1|t);return e=e+Math.imul(e^e>>>7,61|e)^e,((e^e>>>14)>>>0)/4294967296}}function hq(e){let t=2166136261;for(let n=0;n>>0}function gq(e,t,n=3200,r=1600){let i=mq(hq(e)),a=new Float32Array(n*3),o=new Float32Array(n*3),s=new Float32Array(n),c=new H(t.starCool??t.nodeDim),l=new H(t.starPale??t.nodeBright),u=new H;for(let e=0;evq(t,e),r=e=>vq(t,e);return e.addEventListener(`pointerdown`,n,{capture:!0}),e.addEventListener(`lostpointercapture`,r,{capture:!0}),()=>{e.removeEventListener(`pointerdown`,n,{capture:!0}),e.removeEventListener(`lostpointercapture`,r,{capture:!0})}}var bq=2.4,xq=1.8,Sq=.62;function Cq(e,t){let n=new Ho({color:new H(t),dashSize:bq,gapSize:xq,transparent:!0,opacity:Sq,depthWrite:!1}),r=new Ta(new mi,n);return r.userData.codembleRouteLink=e,r.renderOrder=-1,r}function wq(e,t,n,r){let i=t?t.getPoints(30):[new V(n.x,n.y??0,n.z??0),new V(r.x,r.y??0,r.z??0)],a=e.geometry.getAttribute(`position`);return(!a||a.count!==i.length)&&e.geometry.setAttribute(`position`,new Qr(new Float32Array(i.length*3),3)),e.geometry.setFromPoints(i),e.geometry.computeBoundingSphere(),e.computeLineDistances(),!0}function Tq(e,t){e.traverse(e=>{let n=e.userData?.codembleRouteLink;!n||!e.material||e.material.color.set(t(n))})}var Eq=128,Dq=-.72;function Oq(e){let t=new Map;for(let n of e??[]){let e=n.system_orbit;if(!e||!Number.isInteger(e.ring)||e.ring<=0||!Number.isFinite(e.radius)||e.radius<=0)continue;t.has(e.ring)||t.set(e.ring,{callDepths:new Set,kinds:new Set,radii:new Set});let r=t.get(e.ring);r.callDepths.add(e.call_depth??null),r.kinds.add(e.kind),r.radii.add(e.radius)}return[...t.entries()].sort(([e],[t])=>e-t).map(([e,t])=>{let n=[...t.callDepths],r=n.length===1?n[0]:null,i=[...t.kinds].sort(),a=i.every(e=>e===`unreached`);return{ring:e,callDepth:r,kinds:i,radii:[...t.radii].sort((e,t)=>e-t),label:a?`No proven path`:r===null?`Mixed call evidence`:`Layer ${r}`,unproven:a}})}function kq(e,t,n){let r=new sr;r.name=`codemble-system-orbits`;for(let i of e){let e=i.unproven?new Ho({color:t.faded,dashSize:3,gapSize:2,opacity:.72,transparent:!0,depthWrite:!1}):new _a({color:t.route,opacity:.52,transparent:!0,depthWrite:!1});for(let t of i.radii){let n=new Da(new mi().setFromPoints(Array.from({length:Eq},(e,n)=>{let r=n/Eq*Math.PI*2;return new V(Math.cos(r)*t,0,Math.sin(r)*t)})),e);n.userData.codembleOrbitGuide=!0,n.renderOrder=-1,i.unproven&&n.computeLineDistances(),r.add(n)}let a=Math.max(...i.radii),o=n.guideLabel(i.label);o.position.set(Math.cos(Dq)*a,2.5,Math.sin(Dq)*a),o.userData.codembleOrbitGuideLabel=!0,r.add(o)}return r}function Aq(e){let t=new Set;e.traverse(e=>{e.userData?.codembleOrbitGuide&&(e.geometry?.dispose(),e.material&&t.add(e.material))});for(let e of t)e.dispose()}var jq=.16,Mq=1.5,Nq=110,Pq=6,Fq=Object.freeze({requestFrame:e=>requestAnimationFrame(e),cancelFrame:e=>cancelAnimationFrame(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e),setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),now:()=>performance.now()}),Iq=Object.freeze({rendererFactory:e=>XW({controlType:`orbit`})(e),assertWebGL(){let e=document.createElement(`canvas`);if(!e.getContext(`webgl2`)&&!e.getContext(`webgl`))throw Error(`Codemble needs WebGL to draw your galaxy. Enable WebGL and reload.`)},ResizeObserver:globalThis.ResizeObserver,createDressing:cq,createBodyGeometry:nG,createBodySpin:aG,createSystemOrbitGuides:kq,disposeSystemOrbitGuides:Aq,createStarfield:gq,createGalacticGlow:_q,attachBloom:zd,guardOrbitPointerState:yq,runDawnSequence:yG,createNameAtlas:JK,documentElement:()=>document.documentElement,search:()=>window.location.search});function Lq({host:e,palette:t,reducedMotion:n,onHoverNode:r,onAdvance:i,onDawnConsumed:a,dependencies:o={},clock:s=Fq}){let c={...Iq,...o},l=!1,u=null,d=null,f=null,p=null,m=null,h=null,g=null,_=()=>{},v=()=>{},y=null,b=null,x=!1,S=null,C={activeId:null,neighborIds:new Set},w=null,T=null,E=()=>{},D=null,O=null,k=null,A=null,ee=null,te=e=>uK(e,C,t),j=e=>dK(e,C,t,zq),ne=e=>{if(e.focusDim)return .4;let t=Math.min(2.2,.45+(e.weight??1)*.25);if(!C.activeId)return t;let n=zq(e.source),r=zq(e.target);return n===C.activeId||r===C.activeId?t+.9:t};function re(){c.assertWebGL(),p=c.createDressing(t),m=c.createBodyGeometry(),d=c.rendererFactory(e).backgroundColor(t.ground).showNavInfo(!1).enableNavigationControls(!0).enableNodeDrag(!1).warmupTicks(0).cooldownTicks(0).nodeId(`id`).nodeLabel(yK).nodeVal(`val`).nodeColor(te).nodeRelSize(SK).nodeResolution(8).nodeOpacity(.82).nodeThreeObject(e=>Rq(e,t,p,S,{level:u?.level,bodyGeometry:m})).nodeThreeObjectExtend(!0).linkColor(j).linkLabel(bK).linkOpacity(.5).linkWidth(ne).linkCurvature(.12).linkThreeObject(e=>e.certain?null:Cq(e,j(e))).linkPositionUpdate((e,{start:t,end:n},r)=>!r.certain&&wq(e,r.__curve,t,n)).linkVisibility(e=>!(u?.mode===`easy`&&e.focusDim)).linkHoverPrecision(4).linkDirectionalArrowRelPos(1).linkDirectionalArrowColor(j).linkDirectionalParticles(e=>e.kind===`call`&&e.certain&&!e.focusDim&&!n?2:0).linkDirectionalParticleSpeed(.006).linkDirectionalParticleWidth(1.1).linkDirectionalParticleColor(()=>t.orbit).onNodeHover(t=>{l||(e.style.cursor=t?`pointer`:`default`,r(t?.id??null))}).onNodeClick(e=>{l||i(e)}),y=s.requestFrame(()=>{l||e.querySelector(`.scene-nav-info`)?.remove()}),f=d.controls(),f.enablePan=!1,f.enableDamping=!n,f.dampingFactor=.12,f.rotateSpeed=.55,f.zoomSpeed=.7,f.minPolarAngle=jq,f.maxPolarAngle=Mq;let a=()=>{l||(x=!0)};f.addEventListener(`start`,a),v=()=>f?.removeEventListener(`start`,a),_=c.guardOrbitPointerState(e,f),h=c.attachBloom(d,e.getBoundingClientRect()),g=new c.ResizeObserver(([e])=>{if(l)return;let{width:t,height:n}=e.contentRect;d.width(t).height(n);let r=jK(e.contentRect);!x&&r!==null&&b?.(r)}),g.observe(e)}function M(t,n){if(!u)return;let{data:r,level:i,orbitPlan:a}=u,o=cG({host:e,renderer:d}),s=PK({level:i,nodes:r.nodes,orbitPlan:a,fov:d.camera()?.fov,aspect:n??jK(e.getBoundingClientRect())??jK({width:d.width(),height:d.height()}),viewport:o.viewport,chrome:i===jG.SYSTEM?o.clickObstructions:[]});f.minDistance=s.min,f.maxDistance=s.max,d.cameraPosition(s.position,s.target,t)}function N(){E(),E=()=>{},T&&=(d.scene().remove(T),c.disposeSystemOrbitGuides(T),null),!(u.level===jG.GALAXY||!u.orbitPlan.length)&&(T=c.createSystemOrbitGuides(u.orbitPlan,t,p),d.scene().add(T),E=c.createBodySpin(d.scene(),{reducedMotion:n}))}function ie(){ae();let e=c.createStarfield(u.starfieldSeed,t),n=c.createGalacticGlow(t);d.scene().add(e),d.scene().add(n),w={starfield:e,glow:n}}function ae(){!w||!d||(d.scene().remove(w.starfield),w.starfield.geometry?.dispose(),w.starfield.material?.dispose(),d.scene().remove(w.glow),w.glow.material?.map?.dispose(),w.glow.material?.dispose(),w=null)}function oe(){D&&=(D.cancelled=!0,D.frame!==null&&s.cancelFrame(D.frame),D.stop(),null)}function P(e){if(O=e,a?.(e),n)return;oe();let r={cancelled:!1,frame:null,stop:()=>{}};D=r;let i=n=>{if(l||r.cancelled||D!==r)return;let a=d.scene();if(a.getObjectByName(`codemble-system-${e}`)||n>=Pq){r.frame=null,r.stop=c.runDawnSequence({scene:a,regionId:e,palette:t,dressing:p,routes:u?.graph?.region_edges??[],hopsById:new Map((u?.graph?.regions??[]).map(e=>[e.id,e.hops_from_home]))});return}r.frame=s.requestFrame(()=>i(n+1))};r.frame=s.requestFrame(()=>i(0))}function se(){A!==null&&s.clearInterval(A),k&&d&&k.hide(d.scene()),A=null,k=c.createNameAtlas(u.data.nodes);let t=d.scene(),n=()=>{if(!(l||!k))try{let n=d.camera(),r=BK(u.level),i=f?{min:f.minDistance??r.min,max:f.maxDistance??r.max}:r,a=f?n.position.distanceTo(f.target):n.position.length(),o=cG({host:e,renderer:d});k.place({scene:t,camera:n,width:d.width(),height:d.height(),distance:a,distanceBounds:i,activeNodeId:C.activeId,neighborIds:C.neighborIds,chrome:o.nameObstructions})}catch(e){A!==null&&s.clearInterval(A),A=null,k.hide(t),console.error(`Codemble: label declutter failed, names disabled`,e)}};n(),A=s.setInterval(n,Nq)}function F(){ee!==null&&s.clearTimeout(ee),ee=null,!(!new URLSearchParams(c.search()).has(`benchmark`)||u.data.nodes.length<900)&&(c.documentElement().removeAttribute(`data-codemble-fps`),ee=s.setTimeout(()=>{if(ee=null,l)return;let e=d.renderer(),t=d.postProcessingComposer(),n=s.now();for(let e=0;e<60;e+=1)t.render();e.getContext().finish();let r=s.now()-n;c.documentElement().dataset.codembleFps=(60*1e3/r).toFixed(1)},1e3))}function ce(e){if(l)return;let t=u;if(u=e,(!t||t.data!==e.data||t.level!==e.level||t.mode!==e.mode||t.orbitPlan!==e.orbitPlan)&&(d.nodeResolution(e.data.nodes.length>=900?4:8).nodeThreeObjectExtend(e.level===jG.GALAXY).linkVisibility(t=>!(e.mode===`easy`&&t.focusDim)).linkDirectionalArrowLength(e.level===jG.GALAXY?0:3.2).graphData(e.data),M(xG(420,{active:e.firstFlightActive,reducedMotion:n})),x=!1,b=e=>M(0,e)),(!t||t.level!==e.level||t.orbitPlan!==e.orbitPlan)&&N(),(!t||t.starfieldSeed!==e.starfieldSeed)&&ie(),e.level===jG.GALAXY?e.pendingDawnRegionId&&e.pendingDawnRegionId!==O&&P(e.pendingDawnRegionId):oe(),!t||t.data!==e.data||t.hoverNodeId!==e.hoverNodeId||t.focusedNodeId!==e.focusedNodeId||t.level!==e.level||t.selectedNode?.id!==e.selectedNode?.id){let t=e.hoverNodeId??e.focusedNodeId??(e.level===jG.STUDY?e.selectedNode?.id??null:null),n=new Set;if(t)for(let r of e.data.links){let e=zq(r.source),i=zq(r.target);e===t&&n.add(i),i===t&&n.add(e)}C={activeId:t,neighborIds:n},d.nodeColor(e=>te(e)).linkColor(e=>j(e)).linkWidth(e=>ne(e)).linkDirectionalArrowColor(e=>j(e)),Tq(d.scene(),j)}if((!t||t.focusedNodeId!==e.focusedNodeId)&&(S=e.focusedNodeId,d.refresh()),(!t||t.data.nodes!==e.data.nodes||t.hoverNodeId!==e.hoverNodeId||t.focusedNodeId!==e.focusedNodeId||t.level!==e.level)&&se(),(!t||t.data.nodes.length!==e.data.nodes.length)&&F(),e.level===jG.STUDY&&(!t||t.level!==e.level||t.selectedNode!==e.selectedNode)){let t=VK(e.selectedNode);t&&d.cameraPosition(t.position,t.target,420)}}function le(){l||(l=!0,g?.disconnect(),g=null,v(),v=()=>{},y!==null&&s.cancelFrame(y),y=null,_(),_=()=>{},E(),E=()=>{},T&&d&&(d.scene().remove(T),c.disposeSystemOrbitGuides(T),T=null),oe(),A!==null&&s.clearInterval(A),A=null,k&&d&&k.hide(d.scene()),k=null,ee!==null&&s.clearTimeout(ee),ee=null,ae(),d?.pauseAnimation(),h?.dispose(),h=null,d?._destructor(),p?.dispose(),p=null,m?.dispose(),m=null,f=null,d=null,e.replaceChildren(),b=null)}try{re()}catch(e){throw le(),e}return Object.freeze({update:ce,dispose:le})}function Rq(e,t,n,r,{level:i,bodyGeometry:a}={}){let o=new sr;o.name=e.kind===`region`?`codemble-system-${e.id}`:`codemble-node-${e.id}`;let s=wK(e),c=i&&i!==jG.GALAXY&&a;c&&o.add(iG({node:e,color:e.color,palette:t,radius:s,geometry:a}));let l=lK(e);if(!e.focusDim&&!l&&!c&&o.add(n.halo(e,s)),e.kind===`region`&&!l){let r=t.nebula[e.language];r&&o.add(n.nebula(r,s*14))}if(e.label){let t=n.label(e.label,s);t.userData.nodeId=e.id,o.add(t)}if(e.home){let e=new na(new bo(s*1.7,Math.max(.18,s*.07),8,36),new Wi({color:t.home}));e.rotation.x=Math.PI/2.8,o.add(e)}if(e.kind===`class`&&!e.focusDim){let e=new na(new bo(s*1.45,Math.max(.1,s*.045),6,28),new Wi({color:t.route}));e.rotation.x=Math.PI/2.4,o.add(e)}if(e.selected){let e=new na(new bo(s*2.1,Math.max(.16,s*.05),6,24),new Wi({color:t.orbit}));e.rotation.x=Math.PI/2.8,o.add(e)}return e.id===r&&o.add(n.reticle(s)),o}function zq(e){return typeof e==`object`&&e?e.id:e}var Bq=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),$=o(((e,t)=>{t.exports=Bq()}))();function Vq({graph:e,level:t,region:n,selectedNode:r,hoverNodeId:i,pendingDawnRegionId:a,revealedRegionIds:o,mode:s,firstFlightActive:c,onHoverNode:l,onAdvance:u,onRetreat:d,onDawnConsumed:f}){let p=(0,_.useRef)(null),m=(0,_.useRef)(null),h=(0,_.useRef)(u),g=(0,_.useRef)(d),v=(0,_.useRef)(l),y=(0,_.useRef)(f),[b,x]=(0,_.useState)(0),[S,C]=(0,_.useState)(!1),[w,T]=(0,_.useState)(``),E=(0,_.useMemo)(Uq,[]),D=(0,_.useMemo)(Bd,[]),O=pq(e.file_hashes),k=(0,_.useMemo)(()=>t===jG.GALAXY?fK(e,E,o):pK(e,n?.id,E,{selectedId:r?.id}),[e,t,E,n?.id,o,r?.id]),A=(0,_.useMemo)(()=>t===jG.GALAXY?[]:Oq(k.nodes),[k.nodes,t]);(0,_.useEffect)(()=>{h.current=u,g.current=d,v.current=l,y.current=f},[u,d,l,f]),(0,_.useEffect)(()=>{let e=p.current;if(e)try{let t=Lq({host:e,palette:E,reducedMotion:D,onHoverNode:e=>v.current(e),onAdvance:e=>h.current(e),onDawnConsumed:e=>y.current?.(e)});return m.current=t,T(``),()=>{t.dispose(),m.current===t&&(m.current=null)}}catch(e){let t=e instanceof Error?e.message:String(e);T(t.startsWith(`Codemble needs WebGL`)?t:`The galaxy could not start: ${t}`);return}},[E,D]),(0,_.useEffect)(()=>{m.current?.update({graph:e,data:k,level:t,mode:s,orbitPlan:A,selectedNode:r,hoverNodeId:i,pendingDawnRegionId:a,starfieldSeed:O,firstFlightActive:c,focusedNodeId:S?k.nodes[b]?.id??null:null})},[k,c,b,e,i,S,t,s,A,a,r,O]),(0,_.useEffect)(()=>{x(0),C(!1)},[k,t,s,A]);let ee=k.nodes[b]??null;function te(e){if(e.key===`ArrowRight`||e.key===`ArrowDown`)e.preventDefault(),x(e=>(e+1)%Math.max(1,k.nodes.length));else if(e.key===`ArrowLeft`||e.key===`ArrowUp`)e.preventDefault(),x(e=>(e-1+k.nodes.length)%Math.max(1,k.nodes.length));else if(e.key===`Enter`&&ee)e.preventDefault(),h.current(ee);else if(e.key===`Escape`||e.key===`Backspace`){if(e.key===`Escape`&&c)return;e.preventDefault(),g.current()}}return w?(0,$.jsxs)(`section`,{className:`webgl-error`,role:`alert`,children:[(0,$.jsx)(`h1`,{children:`The sky could not open.`}),(0,$.jsx)(`p`,{children:w}),(0,$.jsxs)(`p`,{children:[`The `,s===`easy`?`Diagram`:`Map`,` layer works without WebGL — switch to it at the top of the window to explore the same code.`]})]}):(0,$.jsxs)(`div`,{className:`galaxy-frame`,role:`application`,tabIndex:`0`,"aria-label":`Codemble ${t.toLowerCase()} view. Drag to orbit, scroll to zoom. Use arrow keys to choose a node and Enter to move closer.${t===jG.GALAXY?``:` Solid guides are parser-proven call layers; a dashed guide has no proven call path.`}`,onFocus:()=>C(!0),onBlur:()=>{v.current(null)},onKeyDown:te,children:[(0,$.jsx)(`div`,{ref:p,className:`galaxy-canvas`,"aria-hidden":`true`,onPointerLeave:()=>v.current(null)}),S&&ee?(0,$.jsx)(`output`,{className:`keyboard-focus`,"aria-live":`polite`,children:yK(ee)}):null]})}function Hq(e){let t=document.createElement(`canvas`).getContext(`2d`);t.fillStyle=`#000000`,t.fillStyle=e,t.fillRect(0,0,1,1);let[n,r,i]=t.getImageData(0,0,1,1).data;return`rgb(${n}, ${r}, ${i})`}function Uq(){let e=getComputedStyle(document.documentElement),t=t=>Hq(e.getPropertyValue(t).trim());return Object.freeze({ground:t(`--cm-sky`),skyGlow:t(`--cm-sky-glow`),home:t(`--cm-ink`),orbit:t(`--cm-orbit`),nodeBright:t(`--cm-node-bright`),node:t(`--cm-node-mid`),nodeDim:t(`--cm-node-unlit`),starCool:t(`--cm-star-cool`),starPale:t(`--cm-star-pale`),route:t(`--cm-route`),routePossible:t(`--cm-route-possible`),faded:t(`--cm-hairline-soft`),star:t(`--cm-star-high`),starHalo:t(`--cm-star-halo`),nebula:Object.freeze(Object.fromEntries(Object.entries(zG).map(([e,n])=>[e,t(n)]))),communities:Object.freeze(Array.from({length:8},(e,n)=>t(`--cm-com-${n}`))),labelPlate:e.getPropertyValue(`--cm-label-plate`).trim(),labelInk:e.getPropertyValue(`--cm-label-ink`).trim()})}function Wq({hint:e,onFollow:t,firstFlight:n}){let r=(0,_.useRef)(null),i=(0,_.useRef)(!1);if((0,_.useLayoutEffect)(()=>{n?.active&&!i.current&&r.current?.focus(),i.current=n?.active===!0},[n?.active]),n?.active){let{stop:e,index:t,total:i,mode:a}=n;return(0,$.jsxs)(`output`,{className:`hint-chip first-flight`,"aria-live":`polite`,"data-first-flight":`active`,children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`✦`}),(0,$.jsxs)(`strong`,{children:[`First Flight · `,t+1,` of `,i]}),(0,$.jsx)(`span`,{className:`first-flight__system`,children:e.id}),(0,$.jsxs)(`small`,{children:[a===`easy`?`${e.language} file`:`${e.language} system`,` · used by ${e.usedBy} · uses ${e.uses}`]}),(0,$.jsxs)(`span`,{className:`first-flight__actions`,children:[(0,$.jsx)(`button`,{type:`button`,disabled:t===0,onClick:n.onBack,children:`Back`}),t+1{let e=i.current;e&&!e.open&&e.showModal(),a.current?.focus()},[]);function c(){i.current?.close(),t()}return(0,$.jsxs)(`dialog`,{ref:i,className:`coach-marks`,"aria-labelledby":`coach-heading`,onCancel:e=>{e.preventDefault(),c()},children:[(0,$.jsxs)(`p`,{className:`coach-marks__progress`,children:[`Step `,n+1,` of `,o.length]}),(0,$.jsx)(`h1`,{id:`coach-heading`,children:s.title}),(0,$.jsx)(`p`,{children:s.body}),(0,$.jsxs)(`div`,{className:`coach-marks__actions`,children:[(0,$.jsx)(`button`,{type:`button`,className:`coach-skip`,onClick:c,children:`Skip`}),(0,$.jsx)(`button`,{ref:a,type:`button`,className:`check-primary`,onClick:()=>n+1=r&&o<=r+e&&s>=i&&s<=i+t}var eJ=.35;function tJ(e,t,n,r){if(!e||!t||!n||!r)return 1;let i=Zq(e,t,n,r);return i>=.35?i:Xq(Math.min(eJ,e/n))}function nJ(){let e=new Map;return Object.freeze({read(t){let n=e.get(t);return n?{...n}:null},write(t,n){e.set(t,{scale:Xq(n.scale),scrollLeft:Math.max(0,n.scrollLeft),scrollTop:Math.max(0,n.scrollTop)})},clear(){e.clear()}})}var rJ=1.25;function iJ({contentWidth:e,contentHeight:t,label:n,viewKey:r,viewportStore:i,focusPoint:a,children:o}){let s=(0,_.useRef)(null),c=(0,_.useRef)(i.read(r)),[l,u]=(0,_.useState)(()=>c.current?.scale??1),[d,f]=(0,_.useState)(!1),p=(0,_.useRef)(null),m=(0,_.useRef)(!1),h=Math.round(l*100),g=(0,_.useCallback)(()=>{let e=s.current;e&&i.write(r,{scale:l,scrollLeft:e.scrollLeft,scrollTop:e.scrollTop})},[l,r,i]),v=(0,_.useCallback)(()=>{let n=s.current?.getBoundingClientRect();!n||!e||!t||u(tJ(n.width,n.height,e,t))},[e,t]);(0,_.useLayoutEffect)(()=>{let e=s.current;if(!e||m.current)return;let t=0;return t=requestAnimationFrame(()=>{let t=e.getBoundingClientRect(),n=c.current,r=n&&(!a||$q({viewportWidth:t.width,viewportHeight:t.height,scale:n.scale,scrollLeft:n.scrollLeft,scrollTop:n.scrollTop,point:a}));if(r)e.scrollLeft=n.scrollLeft,e.scrollTop=n.scrollTop;else if(t.width<640&&a){let n=Qq({viewportWidth:t.width,viewportHeight:t.height,scale:l,point:a});e.scrollLeft=n.scrollLeft,e.scrollTop=n.scrollTop}else n&&!r&&(e.scrollLeft=0,e.scrollTop=0);m.current=!0,g()}),()=>cancelAnimationFrame(t)},[a,g,l]),(0,_.useEffect)(()=>{if(!m.current)return;let e=requestAnimationFrame(g);return()=>cancelAnimationFrame(e)},[g]),(0,_.useEffect)(()=>{let e=s.current;if(!e)return;let t=null,n=new ResizeObserver(([n])=>{let{width:r,height:i}=n.contentRect,o=t;if(t={width:r,height:i},!o||!a||!m.current||Math.abs(r-o.width)<1&&Math.abs(i-o.height)<1||$q({viewportWidth:r,viewportHeight:i,scale:l,scrollLeft:e.scrollLeft,scrollTop:e.scrollTop,point:a}))return;let s=Qq({viewportWidth:r,viewportHeight:i,scale:l,point:a});e.scrollLeft=s.scrollLeft,e.scrollTop=s.scrollTop,g()});return n.observe(e),()=>n.disconnect()},[a,g,l]);function y(e){if(e.button!==0||e.target.closest(`[role='button']`))return;let t=s.current;p.current={x:e.clientX,y:e.clientY,left:t.scrollLeft,top:t.scrollTop},f(!0),t.setPointerCapture(e.pointerId)}function b(e){if(!p.current)return;let t=s.current;t.scrollLeft=p.current.left-(e.clientX-p.current.x),t.scrollTop=p.current.top-(e.clientY-p.current.y)}function x(e){p.current&&(p.current=null,f(!1),s.current?.releasePointerCapture?.(e.pointerId))}return(0,$.jsxs)(`div`,{className:`map-canvas`,children:[(0,$.jsxs)(`div`,{className:`map-zoom`,role:`group`,"aria-label":`Zoom ${n}`,children:[(0,$.jsx)(`button`,{type:`button`,"aria-label":`Zoom out`,disabled:l<=.05,onClick:()=>u(e=>Xq(e/rJ)),children:`−`}),(0,$.jsx)(`button`,{type:`button`,onClick:v,children:`Fit`}),(0,$.jsxs)(`button`,{type:`button`,"aria-label":`Reset zoom to 100%. Current zoom ${h}%.`,title:`Reset zoom to 100%`,onClick:()=>u(1),children:[h,`%`]}),(0,$.jsx)(`button`,{type:`button`,"aria-label":`Zoom in`,disabled:l>=2.5,onClick:()=>u(e=>Xq(e*rJ)),children:`+`})]}),(0,$.jsx)(`div`,{ref:s,className:`map-scroll`,"data-panning":d||void 0,onPointerDown:y,onPointerMove:b,onPointerUp:x,onPointerCancel:x,onScroll:()=>{m.current&&g()},children:(0,$.jsx)(`div`,{className:`map-canvas__sized`,style:{width:e*l,height:t*l},children:o})})]})}function aJ(e){return BG(e)??`var(--cm-hairline)`}function oJ(e){return e.map(([e,t],n)=>`${n===0?`M`:`L`} ${e} ${t}`).join(` `)}function sJ(e){return 1+Math.min(2.5,(Math.max(1,e)-1)*.5)}var cJ=13,lJ=.62,uJ=14,dJ=6;function fJ(e,t){let n=t-uJ-dJ,r=Math.max(1,Math.floor(n/(cJ*lJ)));return e.length<=r?e:`${e.slice(0,Math.max(1,r-1))}…`}function pJ({data:e,mapTab:t,mode:n,communityIndexByRegion:r,selectedRegionId:i,hasEntrypointCandidates:a,unsupportedSources:o,importCycles:s,error:c,onSelectTab:l,onSelectRegion:u,onSelectNode:d,onRetry:f,viewportStore:p,languageFocusLabel:m=``,onClearLanguageFocus:h,children:g}){return(0,$.jsxs)(`section`,{className:`map-view`,"aria-label":`Two-dimensional project map`,children:[(0,$.jsxs)(`nav`,{className:`map-tabs`,"aria-label":`Map view`,children:[(0,$.jsx)(`button`,{type:`button`,"aria-pressed":t===`architecture`,onClick:()=>l(`architecture`),children:n===`easy`?`How it fits together`:`Architecture`}),(0,$.jsx)(`button`,{type:`button`,"aria-pressed":t===`workflow`,onClick:()=>l(`workflow`),children:n===`easy`?`What runs first`:`Workflow`})]}),g,c?(0,$.jsxs)(`div`,{className:`map-state`,role:`alert`,children:[(0,$.jsx)(`h2`,{children:`The map did not load.`}),(0,$.jsxs)(`p`,{children:[c,` The galaxy layer is unaffected.`]}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:f,children:`Try again`})]}):e?t===`architecture`?(0,$.jsx)(hJ,{architecture:e.architecture,mode:n,communityIndexByRegion:r,selectedRegionId:i,hasEntrypointCandidates:a,onSelectRegion:u,viewportStore:p,languageFocusLabel:m,onClearLanguageFocus:h}):(0,$.jsx)(gJ,{workflow:e.workflow,mode:n,selectedRegionId:i,hasEntrypointCandidates:a,onSelectNode:d,viewportStore:p,languageFocusLabel:m,onClearLanguageFocus:h}):(0,$.jsx)(`p`,{className:`map-loading`,role:`status`,children:`Laying out parser evidence…`}),UG(o,n)?(0,$.jsxs)(`p`,{className:`map-note`,children:[UG(o,n),n===`easy`?` — Codemble does not read that language yet, so none of it is drawn here.`:` — outside every registered adapter; no structure is inferred for them.`]}):null,gK(s,n)?(0,$.jsx)(`p`,{className:`map-note`,children:gK(s,n)}):null]})}var mJ=8;function hJ({architecture:e,mode:t,communityIndexByRegion:n,selectedRegionId:r,hasEntrypointCandidates:i,onSelectRegion:a,viewportStore:o,languageFocusLabel:s,onClearLanguageFocus:c}){let l=new Map(e.boxes.map(e=>[e.id,e])),[u,d]=(0,_.useState)(e.unreachable.length<=mJ),f=new Set(e.unreachable),p=e.boxes.length>0&&e.boxes.every(e=>f.has(e.id)),m=u||p,h=m?e.boxes:e.boxes.filter(e=>!f.has(e.id)),g=m?e.height:h.reduce((e,t)=>Math.max(e,t.y+t.height),0),v=l.get(r)??l.get(e.home)??e.boxes.find(e=>e.home)??e.boxes[0],y=v?{x:v.x+32+v.width/2,y:v.y+32+v.height/2}:null;return(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(iJ,{contentWidth:e.width+64,contentHeight:g+64,label:`the architecture map`,viewKey:`architecture:${e.home??`none`}`,viewportStore:o,focusPoint:y,children:(0,$.jsxs)(`svg`,{className:`architecture-map`,width:`100%`,height:`100%`,preserveAspectRatio:`xMidYMin meet`,viewBox:`-32 -32 ${e.width+64} ${g+64}`,role:`group`,"aria-label":e.home?`${e.boxes.length} modules in ${e.layer_count} import layers from Home`:`${e.boxes.length} modules in ${e.layer_count} import layers, measured from the modules nothing imports`,children:[(0,$.jsxs)(`defs`,{children:[(0,$.jsx)(`marker`,{id:`architecture-arrow`,className:`architecture-map__arrow`,markerWidth:`9`,markerHeight:`8`,refX:`9`,refY:`4`,orient:`auto`,markerUnits:`userSpaceOnUse`,children:(0,$.jsx)(`path`,{d:`M 0 0 L 9 4 L 0 8 Z`,fill:`currentColor`})}),(0,$.jsx)(`marker`,{id:`architecture-cycle-arrow`,className:`architecture-map__arrow is-cycle`,markerWidth:`9`,markerHeight:`8`,refX:`9`,refY:`4`,orient:`auto`,markerUnits:`userSpaceOnUse`,children:(0,$.jsx)(`path`,{d:`M 0 0 L 9 4 L 0 8 Z`,fill:`currentColor`})})]}),(0,$.jsx)(`g`,{className:`architecture-map__edges`,children:e.edges.map(e=>{let t=l.get(e.src),n=l.get(e.dst);return!t||!n||!m&&(f.has(e.src)||f.has(e.dst))?null:(0,$.jsx)(`path`,{d:oJ(e.points),strokeDasharray:e.certain?void 0:`5 4`,strokeWidth:sJ(e.weight),markerEnd:`url(#${e.cycle?`architecture-cycle-arrow`:`architecture-arrow`})`,className:`architecture-map__edge${e.cycle?` is-cycle`:``}${e.certain?``:` is-possible`}`},`${e.src}->${e.dst}`)})}),h.map(e=>(0,$.jsxs)(`g`,{className:`architecture-map__box`,"data-understood":e.understood,"data-home":e.home,"data-reachable":e.reachable,"data-partial":e.partial,style:n?.has(e.id)?{"--box-com":`var(--cm-com-${n.get(e.id)})`}:void 0,"data-selected":e.id===r,transform:`translate(${e.x} ${e.y})`,role:`button`,tabIndex:0,"aria-label":`${e.label}, ${e.node_count} ${e.node_count===1?`structure`:`structures`}, ${e.loc} ${e.loc===1?`line`:`lines`}${e.understood?`, understood`:`, not yet understood`}${e.home?`, Home`:``}${e.reachable?``:`, no import route from Home`}${e.partial?`, unchartable, syntax error`:``}`,onClick:()=>a(e.id),onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),a(e.id))},children:[(0,$.jsxs)(`title`,{children:[e.label,e.partial?` — unchartable, syntax error`:``]}),(0,$.jsx)(`rect`,{width:e.width,height:e.height,rx:`3`}),(0,$.jsx)(`rect`,{className:`box-tint`,width:`4`,height:e.height,style:{fill:aJ(e.language)}}),e.partial?(0,$.jsx)(`path`,{className:`box-partial`,d:`M ${e.width-16} 2 L ${e.width-2} 2 L ${e.width-2} 16 Z`}):null,(0,$.jsx)(`text`,{x:uJ,y:`24`,children:fJ(e.short_label??e.label,e.width)}),(0,$.jsx)(`text`,{className:`box-meta`,x:`14`,y:`42`,children:t===`easy`?`${e.node_count} ${e.node_count===1?`piece`:`pieces`}`:`${e.node_count} ${e.node_count===1?`node`:`nodes`} · ${e.loc} LOC`})]},e.id))]})}),e.home?null:(0,$.jsxs)(`p`,{className:`map-note`,children:[`No Home is selected, so these layers run from the modules nothing else imports rather than from your entrypoint. Both are read from your imports, not guessed.`,` `,i?`Pick your starting point with “Change Home” to see the same modules layered by what the project runs first.`:`This project has no parser-recognisable entrypoint, so there is no “runs first” order to layer by instead.`]}),p?(0,$.jsxs)(`p`,{className:`map-note`,children:[s?`Home is not written in ${s}, so none of these ${e.boxes.length} ${e.boxes.length===1?`module`:`modules`} has an import route to it. They are drawn in file order rather than layered by guesswork.`:`None of these ${e.boxes.length} ${e.boxes.length===1?`module`:`modules`} has an import route from Home, so they are drawn in file order rather than layered by guesswork.`,` `,s&&c?(0,$.jsx)(`button`,{type:`button`,className:`map-note__toggle`,onClick:c,children:`Show all languages`}):null]}):e.unreachable.length?(0,$.jsxs)(`p`,{className:`map-note`,children:[e.unreachable.length,` `,e.unreachable.length===1?`module has`:`modules have`,` no import route from Home`,u?`, so ${e.unreachable.length===1?`it sits`:`they sit`} in the bottom rows rather than being placed by guesswork.`:`. They are folded away so your connected code stays readable — nothing is hidden from the count.`,` `,(0,$.jsx)(`button`,{type:`button`,className:`map-note__toggle`,"aria-expanded":u,onClick:()=>d(e=>!e),children:u?`Fold them away`:`Show them`})]}):null]})}function gJ({workflow:e,mode:t,selectedRegionId:n,hasEntrypointCandidates:r,onSelectNode:i,viewportStore:a,languageFocusLabel:o,onClearLanguageFocus:s}){if(e.root&&e.nodes.length===0)return(0,$.jsxs)(`div`,{className:`map-state`,children:[(0,$.jsx)(`h2`,{children:o?`Nothing in ${o} runs from Home.`:`Nothing runs from Home yet.`}),(0,$.jsx)(`p`,{children:o?`This tab follows what Home calls, and Home is not written in ${o} — so no ${o} structure appears on a parser-proven call route. The other tab still maps how these modules import each other.`:`This tab follows what Home calls, and the parser proved no call route out of it yet.`}),o&&s?(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:s,children:`Show all languages`}):null]});if(!e.root)return r?(0,$.jsxs)(`div`,{className:`map-state`,children:[(0,$.jsx)(`h2`,{children:`No Home is selected.`}),(0,$.jsx)(`p`,{children:`The workflow tree starts at your entrypoint. Pick Home and this tab will show what runs first, then what that calls.`})]}):(0,$.jsxs)(`div`,{className:`map-state`,children:[(0,$.jsx)(`h2`,{children:`No “runs first” order to show.`}),(0,$.jsx)(`p`,{children:`This project has no parser-recognisable entrypoint — nothing here declares a startup structure Codemble recognises, and it will not guess one. The other tab still maps how your modules import each other.`})]});let c=new Map(e.nodes.map(e=>[e.order,e])),l=e.nodes.find(t=>t.id===e.root)??e.nodes[0],u=l?{x:l.x+24,y:l.y+32}:null;return(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(iJ,{contentWidth:e.width+32,contentHeight:e.height+32,label:`the workflow tree`,viewKey:`workflow:${e.root}`,viewportStore:a,focusPoint:u,children:(0,$.jsxs)(`svg`,{className:`workflow-tree`,width:`100%`,height:`100%`,preserveAspectRatio:`xMinYMin meet`,viewBox:`-16 -16 ${e.width+32} ${e.height+32}`,role:`group`,"aria-label":`Call tree from ${e.root}, ${e.nodes.length} steps deep to ${e.depth_count} levels`,children:[(0,$.jsx)(`g`,{className:`workflow-tree__edges`,children:e.nodes.map(e=>{if(e.parent===null)return null;let t=[...c.values()].filter(t=>t.id===e.parent&&t.order(0,$.jsxs)(`g`,{className:`workflow-tree__row`,"data-understood":e.understood,"data-cut":e.cut??void 0,"data-partial":e.partial,"data-relation":e.relation,"data-selected":e.region===n,transform:`translate(${e.x} ${e.y})`,role:`button`,tabIndex:0,"aria-label":`${e.label} at ${e.file}:${e.lineno}${e.certain?``:`, possible call`}${e.cut===`cycle`?`, repeats an earlier step`:``}${e.cut===`repeat`?`, already shown above`:``}${e.partial?`, unchartable, syntax error`:``}`,onClick:()=>i(e.id),onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),i(e.id))},children:[(0,$.jsx)(`circle`,{cx:`8`,cy:`16`,r:`4`}),(0,$.jsxs)(`text`,{x:`20`,y:`20`,children:[(0,$.jsx)(`tspan`,{children:e.label}),(0,$.jsxs)(`tspan`,{className:`row-meta`,children:[e.relation===`defines`?t===`easy`?` — lives here`:` — defined in this module`:e.certain?``:` — possible call`,e.cut===`cycle`?` — loops back`:``,e.cut===`repeat`?` — shown above`:``,e.partial?t===`easy`?` — could not be read`:` — unchartable`:``]})]})]},e.order))]})}),e.unreachable.length?(0,$.jsxs)(`p`,{className:`map-note`,children:[e.unreachable.length,` `,e.unreachable.length===1?`structure is`:`structures are`,` never reached from Home by a parser-proven call. They are listed as unreached rather than attached to the tree by guesswork.`]}):null]})}var _J=m(),vJ=[{mode:`easy`,label:`New to coding?`},{mode:`expert`,label:`I build software`}],yJ=[{mode:`easy`,label:`Easy`},{mode:`expert`,label:`Expert`}];function bJ({mode:e,modeChosen:t,onChoose:n}){let r=(0,_.useRef)(null);(0,_.useLayoutEffect)(()=>{let e=r.current;e&&t===!1&&!e.open&&e.showModal()},[t]);function i(e){r.current?.close(),n(e)}return t===null?null:t?(0,$.jsxs)(`fieldset`,{className:`mode-toggle`,children:[(0,$.jsx)(`legend`,{className:`mode-toggle__label`,children:`Audience`}),(0,$.jsx)(`div`,{className:`mode-toggle__options`,children:yJ.map(t=>(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`input`,{type:`radio`,name:`audience-mode`,value:t.mode,checked:e===t.mode,onChange:()=>i(t.mode)}),(0,$.jsx)(`span`,{children:t.label})]},t.mode))})]}):(0,_J.createPortal)((0,$.jsxs)(`dialog`,{ref:r,className:`mode-gate`,"aria-labelledby":`mode-gate-heading`,"aria-describedby":`mode-gate-detail`,onCancel:e=>e.preventDefault(),children:[(0,$.jsx)(`h1`,{id:`mode-gate-heading`,children:`New to coding, or do you build software already?`}),(0,$.jsx)(`p`,{id:`mode-gate-detail`,children:`This changes how much Codemble explains and how much it assumes you already know.`}),(0,$.jsx)(`div`,{className:`mode-gate__options`,children:vJ.map(e=>(0,$.jsx)(`button`,{type:`button`,onClick:()=>i(e.mode),children:e.label},e.mode))})]}),document.body)}function xJ(e,t){let n=e?.steps??[];return n.length?n.some(e=>e.id===t)?t:n[0].id:null}function SJ(e,t,n){let r=e?.steps??[];if(!r.length)return null;let i=xJ(e,t),a=Math.max(0,r.findIndex(e=>e.id===i));return r[Math.max(0,Math.min(r.length-1,a+n))].id}function CJ(e,t,n){let r=e?.steps??[],i=xJ(e,t),a=r.findIndex(e=>e.id===i);if(a<0)return null;let o=r[a];return{id:o.id,index:a,total:r.length,position:`${a+1} of ${r.length}`,heading:wJ(o),summary:TJ(o),citation:o.declaration?.citation??o.citation??null,observationCitation:o.observation?.citation??null,isFirst:a===0,isLast:a===r.length-1,isTarget:!!o.is_target,expertDetails:n===`expert`?{layer:String(o.layer??`unknown`).replaceAll(`-`,` `),relation:o.relation??`unknown`,ruleId:o.rule_id??null,nodeId:o.node_id??null}:null}}function wJ(e){return e.relation===`home`?`Start at Home: ${e.name}`:e.relation===`import`?`Open ${e.name}`:e.relation===`call`?`Follow the call to ${e.name}`:e.relation===`role`?e.role===`ui-renderer`?`Reach the screen in ${e.name}`:e.role===`test`?`Enter through test surface ${e.name}`:`Enter through ${e.name}`:`Read ${e.name}`}function TJ(e){return e.relation===`home`?`The parser ranked this as the project's starting point.`:e.relation===`import`?`A certain import connects the previous file to this declaration.`:e.relation===`call`?e.is_target?`A certain call reaches the structure you selected.`:`A certain call continues the runtime route through this structure.`:e.relation===`role`?e.role===`route-handler`?`A parser-proven route registration is where a request reaches the application.`:e.role===`ui-renderer`?`Parser-proven rendering syntax connects this function to the visible interface.`:e.role===`test`?`Only a parser-proven test surface reaches this route; it is not an application screen.`:`The parser proved this is an application entry before the runtime route continues.`:`This is the next parser-proven declaration on the route.`}function EJ({node:e,study:t,error:n,mode:r,explanation:i,explanationLoading:a,explanationError:o,llmStatus:s,onSelectNode:c,onRetryStudy:l,onRetryNarration:u,onClose:d,revealSource:f=!1}){let p=(0,_.useRef)(null),m=(0,_.useRef)(null),h=(0,_.useRef)(null),g=(0,_.useRef)(null),v=(0,_.useRef)(null),y=(0,_.useRef)(null),b=t?.learning_journey??null,[x,S]=(0,_.useState)(null),C=!!t?.source;(0,_.useEffect)(()=>{p.current?.scrollTo({top:0,behavior:`auto`}),m.current?.focus({preventScroll:!0})},[e.id,f]),(0,_.useEffect)(()=>{n&&(p.current?.scrollTo({top:0,behavior:`auto`}),h.current?.focus({preventScroll:!0}))},[n,e.id]),(0,_.useEffect)(()=>{!f||!C||y.current!==e.id&&(y.current=e.id,g.current?.scrollIntoView({block:`start`,behavior:`auto`}),v.current?.focus({preventScroll:!0}))},[f,C,e.id]),(0,_.useEffect)(()=>{S(e=>xJ(b,e))},[b]);function w(){p.current?.scrollTo({top:0,behavior:`auto`}),m.current?.focus({preventScroll:!0}),l()}function T(){m.current?.focus({preventScroll:!0}),u()}return(0,$.jsxs)(`aside`,{ref:p,className:`study-preview`,"aria-label":`Selected source structure`,"aria-busy":!t&&!n,children:[(0,$.jsxs)(`header`,{className:`study-preview__header`,children:[d?(0,$.jsx)(`button`,{className:`check-close study-preview__close`,type:`button`,onClick:d,children:`Close`}):null,(0,$.jsxs)(`p`,{className:`study-preview__path`,children:[e.file,`:`,e.lineno]}),(0,$.jsx)(`h1`,{ref:m,tabIndex:-1,children:e.name})]}),n?(0,$.jsxs)(`section`,{className:`study-notice`,role:`alert`,children:[(0,$.jsx)(`h2`,{ref:h,tabIndex:-1,children:`Study data did not load.`}),(0,$.jsxs)(`p`,{children:[n,` The parser map is still available.`]}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:w,children:`Try again`})]}):null,!t&&!n?(0,$.jsx)(`p`,{className:`study-loading`,role:`status`,children:`Reading parser evidence…`}):null,(0,$.jsxs)(`div`,{className:`study-content`,children:[t?(0,$.jsx)(OJ,{journey:b,activeStepId:x,onActiveStepChange:S,impact:t.impact,neighbors:t.neighbors,node:e,mode:r,onSelectNode:c}):null,t?(0,$.jsx)(DJ,{node:e,mode:r}):null,t?(0,$.jsx)(AJ,{structural:t.structural,mode:r}):null,(0,$.jsx)(UJ,{explanation:i,loading:a,error:o,llmStatus:s,mode:r,node:e,onSelectNode:c,onRetry:T}),t?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(VJ,{source:t.source,anchorRef:g,headingRef:v}),(0,$.jsx)(BJ,{lens:t.lens,language:e.language,mode:r})]}):null]})]})}function DJ({node:e,mode:t}){return(0,$.jsx)(`section`,{className:`study-facts`,"aria-label":`Selected structure facts`,children:(0,$.jsxs)(`dl`,{children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`What it is`:`Kind`}),(0,$.jsx)(`dd`,{children:e.kind})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`Length`:`Span`}),(0,$.jsxs)(`dd`,{children:[e.loc,` `,e.loc===1?`line`:`lines`]})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`Called by`:`Callers`}),(0,$.jsx)(`dd`,{children:e.centrality})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`Evidence`:`Resolution`}),(0,$.jsx)(`dd`,{children:e.partial?t===`easy`?`Could not be fully read`:`Partial parse`:t===`easy`?`Proven from your code`:`Parser-proven`})]})]})})}function OJ({journey:e,activeStepId:t,onActiveStepChange:n,impact:r,neighbors:i,node:a,mode:o,onSelectNode:s}){let[c,l]=(0,_.useState)(``),u=CJ(e,t,o),d=e?.steps??[],f=e?.possible_frontier??[],p=e?.verification_candidates??[];(0,_.useEffect)(()=>l(``),[e?.fingerprint]);function m(t){n(t);let r=CJ(e,t,o);r&&l(`Journey step ${r.position}: ${r.heading}`)}function h(n){let r=SJ(e,t,n);r&&m(r)}return(0,$.jsxs)(`section`,{className:`learning-journey`,"aria-labelledby":`learning-journey-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`learning-journey-heading`,children:`Feature journey`}),(0,$.jsx)(`span`,{children:`Parser evidence`})]}),d.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`ol`,{className:`journey-overview`,"aria-label":`Feature journey steps`,children:d.map((t,n)=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-current":t.id===u?.id?`step`:void 0,"aria-label":`Step ${n+1}: ${CJ(e,t.id,o)?.heading}`,onClick:()=>m(t.id),children:[(0,$.jsx)(`span`,{children:n+1}),(0,$.jsx)(`small`,{children:t.layer.replaceAll(`-`,` `)})]})},t.id))}),u?(0,$.jsxs)(`article`,{className:`journey-current`,"data-step-id":u.id,children:[(0,$.jsxs)(`p`,{className:`journey-position`,children:[`Step `,u.position]}),(0,$.jsx)(`h3`,{children:u.heading}),(0,$.jsx)(`p`,{children:u.summary}),(0,$.jsxs)(`div`,{className:`journey-citations`,children:[u.observationCitation?(0,$.jsxs)(`span`,{children:[`Observed at `,(0,$.jsx)(`code`,{children:u.observationCitation})]}):null,(0,$.jsxs)(`span`,{children:[`Declaration `,(0,$.jsx)(`code`,{children:u.citation})]})]}),(0,$.jsxs)(`div`,{className:`journey-controls`,"aria-label":`Journey controls`,children:[(0,$.jsx)(`button`,{type:`button`,onClick:()=>h(-1),disabled:u.isFirst,children:`Back`}),(0,$.jsx)(`button`,{type:`button`,onClick:()=>h(1),disabled:u.isLast,children:`Next`}),(0,$.jsx)(`button`,{type:`button`,onClick:()=>m(d[0].id),disabled:u.isFirst,children:`Replay`})]}),u.expertDetails?(0,$.jsxs)(`dl`,{className:`journey-expert-facts`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Layer`}),(0,$.jsx)(`dd`,{children:u.expertDetails.layer})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Relation`}),(0,$.jsx)(`dd`,{children:u.expertDetails.relation})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Node ID`}),(0,$.jsx)(`dd`,{children:u.expertDetails.nodeId})]}),u.expertDetails.ruleId?(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Parser rule`}),(0,$.jsx)(`dd`,{children:u.expertDetails.ruleId})]}):null]}):null]}):null]}):null,(0,$.jsx)(`span`,{className:`visually-hidden`,"aria-live":`polite`,"aria-atomic":`true`,children:c}),e?.break?(0,$.jsxs)(`div`,{className:`journey-break`,role:`note`,children:[(0,$.jsx)(`strong`,{children:`Proof stops here`}),(0,$.jsx)(`p`,{children:e.break.message}),e.break.citation?(0,$.jsx)(`code`,{children:e.break.citation}):null]}):null,f.length?(0,$.jsxs)(`div`,{className:`journey-possible`,children:[(0,$.jsx)(`h3`,{children:`Possible next evidence`}),(0,$.jsx)(`p`,{children:`These parser observations are not part of the proven route.`}),(0,$.jsx)(`ul`,{children:f.map(e=>(0,$.jsxs)(`li`,{children:[(0,$.jsxs)(`strong`,{children:[`Possible `,e.relation]}),(0,$.jsxs)(`span`,{children:[e.observation.citation,` → `,e.declaration.citation]})]},`${e.relation}-${e.source_node_id}-${e.target_node_id}-${e.observation.citation}`))})]}):null,(0,$.jsxs)(`details`,{className:`journey-support`,open:o===`expert`,children:[(0,$.jsx)(`summary`,{children:o===`easy`?`Explore selected feature impact and connections`:`Selected feature impact, connections, and verification`}),(0,$.jsxs)(`p`,{className:`journey-support-scope`,children:[`These facts belong to `,a.name,`, the selected feature. They stay fixed while you move between journey steps.`]}),(0,$.jsx)(jJ,{impact:r,mode:o,onSelectNode:s}),(0,$.jsx)(PJ,{neighbors:i,node:a,mode:o,onSelectNode:s}),o===`expert`?(0,$.jsx)(kJ,{items:p,onSelectNode:s}):null]})]})}function kJ({items:e,onSelectNode:t}){return(0,$.jsxs)(`section`,{className:`journey-verification`,"aria-labelledby":`journey-verification-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`journey-verification-heading`,children:`Selected feature verification candidates`}),(0,$.jsx)(`span`,{children:`Parser-linked`})]}),e.length?(0,$.jsx)(`ul`,{className:`impact-list`,children:e.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-label":`Open verification candidate ${e.name}; ${e.certain?`certain route`:`possible route`}; depth ${e.depth}; ${e.declaration.citation}`,onClick:()=>t(e.node_id),children:[(0,$.jsx)(`span`,{className:`impact-name`,children:e.name}),(0,$.jsxs)(`span`,{className:`impact-meta`,children:[`depth `,e.depth,e.certain?` · certain route`:` · possible route`]}),(0,$.jsx)(`span`,{className:`impact-citation`,children:e.declaration.citation})]})},e.node_id))}):(0,$.jsx)(`p`,{className:`study-loading`,children:`No connected test evidence was found.`}),(0,$.jsx)(`p`,{className:`journey-candidate-note`,children:`These are parser-linked candidate verification points, not proof that a test passes.`})]})}function AJ({structural:e,mode:t}){return e?(0,$.jsxs)(`section`,{className:`structural-summary`,"aria-labelledby":`structural-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`structural-heading`,children:t===`easy`?`What this is`:`Structural summary`}),(0,$.jsx)(`span`,{children:`No model needed`})]}),(0,$.jsx)(`p`,{children:e[t]??e.easy})]}):null}function jJ({impact:e,mode:t,onSelectNode:n}){if(!e)return null;let r=e.affects??[],i=e.depends_on??[];if(!r.length&&!i.length)return null;let a=t===`easy`;return(0,$.jsxs)(`section`,{className:`impact-widget`,"aria-labelledby":`impact-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`impact-heading`,children:a?`What the selected feature touches`:`Selected feature impact`}),(0,$.jsx)(`span`,{children:`No model needed`})]}),(0,$.jsxs)(`div`,{className:`impact-columns`,children:[(0,$.jsx)(MJ,{title:a?`Change this and these change too`:`Change this → affected`,empty:a?`Nothing else in your code would notice if you changed this.`:`No parser-proven dependents.`,items:r,mode:t,onSelectNode:n}),(0,$.jsx)(MJ,{title:a?`This needs these to work`:`Depends on → breaks if changed`,empty:a?`This does not rely on anything else in your code.`:`No parser-proven dependencies.`,items:i,mode:t,onSelectNode:n})]}),e.truncated?(0,$.jsx)(`p`,{className:`study-loading`,children:a?`Traced ${e.max_depth} steps out; the chain continues past that.`:`Traced to depth ${e.max_depth}; deeper reach is not shown.`}):null]})}function MJ({title:e,empty:t,items:n,mode:r,onSelectNode:i}){return(0,$.jsxs)(`div`,{className:`impact-column`,children:[(0,$.jsx)(`h3`,{children:e}),n.length===0?(0,$.jsx)(`p`,{className:`study-loading`,children:t}):(0,$.jsx)(`ul`,{className:`impact-list`,children:n.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-label":`${e.name} — ${e.depth===1?`direct`:`${e.depth} steps away`}${e.certain?``:`, possible`} — ${e.citation}`,onClick:()=>i(e.node_id),children:[(0,$.jsx)(`span`,{className:`impact-name`,children:e.name}),(0,$.jsxs)(`span`,{className:`impact-meta`,children:[e.depth===1?r===`easy`?`directly`:`direct`:r===`easy`?`${e.depth} steps away`:`depth ${e.depth}`,e.certain?``:r===`easy`?` · possible link`:` · possible`]}),(0,$.jsx)(`span`,{className:`impact-citation`,children:e.citation})]})},e.node_id))})]})}var NJ=8;function PJ({neighbors:e,node:t,mode:n,onSelectNode:r}){let i=e??[],a=i.filter(e=>e.direction===`inbound`),o=i.filter(e=>e.direction===`outbound`);return(0,$.jsxs)(`section`,{className:`connections`,"aria-labelledby":`connections-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`connections-heading`,children:n===`easy`?`What the selected feature connects to`:`Selected feature connections`}),(0,$.jsxs)(`span`,{children:[i.length,` parser `,i.length===1?`relationship`:`relationships`]})]}),i.length===0?(0,$.jsx)(`p`,{className:`study-loading`,children:n===`easy`?`Nothing in your code reaches this yet, and it does not reach anything else.`:`The parser observed no relationship into or out of this structure.`}):(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(zJ,{inbound:a,outbound:o,node:t}),(0,$.jsx)(FJ,{title:n===`easy`?`Uses this`:`Inbound`,items:a,mode:n,onSelectNode:r}),(0,$.jsx)(FJ,{title:n===`easy`?`This uses`:`Outbound`,items:o,mode:n,onSelectNode:r})]})]})}function FJ({title:e,items:t,mode:n,onSelectNode:r}){return t.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`h3`,{children:e}),(0,$.jsx)(`ul`,{className:`connection-list`,children:t.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-label":`${e.name} — ${IJ(e,n)}, ${LJ(e,n)} — ${e.citation}`,onClick:()=>r(e.node_id),children:[(0,$.jsx)(`span`,{className:`connection-name`,children:e.name}),(0,$.jsxs)(`span`,{className:`connection-meta`,children:[IJ(e,n),` · `,LJ(e,n)]}),(0,$.jsx)(`span`,{className:`source-citation`,children:e.citation})]})},`${e.direction}-${e.node_id}`))})]}):null}function IJ(e,t){return e.relationship===`import`?e.direction===`inbound`?t===`easy`?`brings this in`:`import · inbound`:t===`easy`?`this brings it in`:`import · outbound`:e.direction===`inbound`?t===`easy`?`calls this`:`call · inbound`:t===`easy`?`this calls it`:`call · outbound`}function LJ(e,t){return e.certain?`certain`:e.relationship===`import`?t===`easy`?`possible link, not certain`:`possible import`:t===`easy`?`possible link, not certain`:`possible call`}function RJ(e){let t=String(e??``);return t.length<=18?t:`…${t.slice(-17)}`}function zJ({inbound:e,outbound:t,node:n}){let r=e.slice(0,NJ),i=t.slice(0,NJ),a=Math.max(r.length,i.length,1)*22+16,o=a/2,s=(e,t)=>(e+1)*a/(t+1);return(0,$.jsxs)(`svg`,{className:`mini-constellation`,viewBox:`0 0 280 ${a}`,role:`img`,"aria-label":`${e.length} inbound and ${t.length} outbound parser relationships for ${n.name}`,children:[r.map((e,t)=>(0,$.jsx)(`line`,{x1:`26`,y1:s(t,r.length),x2:`132`,y2:o,strokeDasharray:e.certain?void 0:`3 3`},`in-line-${e.node_id}`)),r.map((e,t)=>(0,$.jsx)(`text`,{className:`mini-constellation__name`,x:`8`,y:s(t,r.length)-7,textAnchor:`start`,children:RJ(e.name)},`in-name-${e.node_id}`)),i.map((e,t)=>(0,$.jsx)(`text`,{className:`mini-constellation__name`,x:`272`,y:s(t,i.length)-7,textAnchor:`end`,children:RJ(e.name)},`out-name-${e.node_id}`)),i.map((e,t)=>(0,$.jsx)(`line`,{x1:`148`,y1:o,x2:`254`,y2:s(t,i.length),strokeDasharray:e.certain?void 0:`3 3`},`out-line-${e.node_id}`)),r.map((e,t)=>(0,$.jsx)(`circle`,{cx:`22`,cy:s(t,r.length),r:`4`},`in-dot-${e.node_id}`)),i.map((e,t)=>(0,$.jsx)(`circle`,{cx:`258`,cy:s(t,i.length),r:`4`},`out-dot-${e.node_id}`)),(0,$.jsx)(`circle`,{className:`mini-constellation__self`,cx:`140`,cy:o,r:`6`})]})}function BJ({lens:e,language:t,mode:n}){return e?.length?(0,$.jsxs)(`section`,{className:`lens-study`,"aria-labelledby":`lens-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsxs)(`h2`,{id:`lens-heading`,children:[IG(t),` lens`]}),(0,$.jsxs)(`span`,{children:[e.length,` detected`]})]}),(0,$.jsx)(`div`,{className:`lens-notes`,children:e.map(e=>(0,$.jsxs)(`article`,{className:`lens-note`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`h3`,{children:e.title}),(0,$.jsx)(GJ,{citation:e.citation,fallbackLine:e.line})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`p`,{children:e.note_voices[n]}),(0,$.jsx)(`code`,{children:e.snippet})]})]},`${e.concept}-${e.line}-${e.snippet}`))})]}):null}function VJ({source:e,anchorRef:t,headingRef:n}){return(0,$.jsxs)(`section`,{className:`source-study`,"aria-labelledby":`source-heading`,ref:t,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{ref:n,id:`source-heading`,tabIndex:-1,children:`Real source`}),(0,$.jsxs)(`span`,{title:`${e.file}:${e.start_line}–${e.end_line}`,children:[e.file,`:`,e.start_line,`–`,e.end_line]})]}),(0,$.jsx)(`ol`,{className:`source-code`,start:e.start_line,"aria-label":`Source excerpt from ${e.file}`,children:e.lines.map(e=>(0,$.jsx)(`li`,{id:`source-L${e.number}`,"data-line":e.number,children:(0,$.jsx)(`code`,{children:e.text||` `})},e.number))})]})}var HJ={grounding:`The explanation was withheld.`,unavailable:`Codemble could not reach the model.`,rejected:`The model refused the request.`,timeout:`The model is taking longer than expected.`,provider:`The model's reply could not be read.`};function UJ({explanation:e,loading:t,error:n,llmStatus:r,mode:i,node:a,onSelectNode:o,onRetry:s}){if(t)return(0,$.jsx)(`p`,{className:`study-loading`,role:`status`,children:i===`easy`?`Asking your model to explain this in plain language…`:`Requesting a grounded narration for this structure…`});if(n)return(0,$.jsxs)(`section`,{className:`study-notice`,role:`alert`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:`The explanation request failed.`}),(0,$.jsx)(`p`,{children:n}),(0,$.jsx)(`p`,{children:`Every fact above and below this block came from the parser and is unaffected.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:s,children:`Try again`})]});if(!e)return null;if(e.status===`no_key`)return(0,$.jsx)(WJ,{message:e.message,llmStatus:r,mode:i});if(e.status===`error`||e.status===`timeout`){let t=e.reason===`grounding`;return(0,$.jsxs)(`section`,{className:`study-notice`,role:`alert`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:HJ[e.reason]??HJ.provider}),(0,$.jsx)(`p`,{children:e.message}),t?(0,$.jsx)(`p`,{children:`Codemble will not display provider output that falls outside parser evidence.`}):null,(0,$.jsx)(`p`,{children:`Every fact from the parser on this panel is unaffected.`}),e.retryable===!1?null:(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:s,children:`Try again`})]})}return e.status===`partial`?(0,$.jsxs)(`section`,{className:`study-notice`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:`Narration stays off for partial source.`}),(0,$.jsx)(`p`,{children:e.message})]}):(0,$.jsxs)(`section`,{className:`grounded-explanation`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,role:`status`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:i===`easy`?`In plain language`:`Grounded explanation`}),(0,$.jsx)(`span`,{children:e.cached?`Local cache`:e.provider})]}),(0,$.jsxs)(`p`,{children:[e.summary.text,` `,(0,$.jsx)(GJ,{citation:e.summary.citation,fallbackLine:a.lineno})]}),e.excerpt?.truncated?(0,$.jsx)(`p`,{className:`study-loading`,children:i===`easy`?`This file is long, so the model was shown lines ${e.excerpt.first}–${e.excerpt.last} of ${e.excerpt.total}.`:`Narrated from an excerpt: lines ${e.excerpt.first}–${e.excerpt.last} of ${e.excerpt.total}.`}):null,e.withheld>0?(0,$.jsxs)(`p`,{className:`study-loading`,children:[e.withheld,` `,e.withheld===1?`part`:`parts`,` of the reply `,e.withheld===1?`was`:`were`,` malformed and left out.`]}):null,e.walkthrough.length?(0,$.jsxs)(`details`,{className:`walkthrough-disclosure`,children:[(0,$.jsx)(`summary`,{children:i===`easy`?`Walk me through it line by line (${e.walkthrough.length})`:`Line walkthrough (${e.walkthrough.length})`}),(0,$.jsx)(`ul`,{className:`evidence-list`,children:e.walkthrough.map(e=>(0,$.jsxs)(`li`,{children:[(0,$.jsx)(`p`,{children:e.text}),(0,$.jsx)(GJ,{citation:e.citation,fallbackLine:e.line})]},`${e.citation}-${e.text}`))})]}):null,e.relationships.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`h3`,{children:i===`easy`?`How it fits in`:`Parser relationships`}),(0,$.jsx)(`ul`,{className:`evidence-list`,children:e.relationships.map(e=>(0,$.jsxs)(`li`,{children:[(0,$.jsx)(`strong`,{children:e.certain?e.node_id:`Possible: ${e.node_id}`}),(0,$.jsx)(`p`,{children:e.text}),(0,$.jsxs)(`button`,{className:`source-citation source-citation--button`,type:`button`,onClick:()=>o(e.node_id),children:[`Study `,e.citation]})]},`${e.node_id}-${e.text}`))})]}):null]})}function WJ({message:e,llmStatus:t,mode:n}){let r=t?.ollama??null;return(0,$.jsxs)(`section`,{className:`study-notice`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:n===`easy`?`The plain-language write-up needs a model.`:`No narration provider is configured.`}),(0,$.jsx)(`p`,{children:e}),r?(0,$.jsx)(`p`,{children:r.running?`Ollama is already running on this machine. Set CODEMBLE_PROVIDER=ollama and CODEMBLE_OLLAMA_MODEL=${r.recommended}, then restart Codemble to narrate without sending code anywhere.`:`Want to stay fully local? Install Ollama, run "ollama pull ${r.recommended}" (or ${r.fallback} on a smaller machine), set CODEMBLE_PROVIDER=ollama, then restart Codemble.`}):null,(0,$.jsx)(`p`,{children:`Everything else on this panel is parser evidence and works without any model at all.`})]})}function GJ({citation:e,fallbackLine:t}){return(0,$.jsx)(`a`,{className:`source-citation`,href:`#source-L${Number(e.split(`:`).at(-1))||t}`,children:e})}var KJ=Object.freeze([]),qJ=new Set;function JJ(){let e=new WeakMap,t=new WeakMap,n=new WeakMap,r=new WeakMap,i=new WeakMap,a=new WeakMap,o=null,s=null,c=null,l=null;function u(e){let t=e.graph,n=t?d(t,e.languageFocus):null,r=n?p(n):null,i=e.level,a=r?.regionById.get(e.region?.id)??null,o=e.selectedNode;n&&(a?o&&(o=r.nodeById.get(o.id)??null,!o&&i===jG.STUDY&&(i=jG.SYSTEM)):(a=mK(n),o=null,i=jG.GALAXY));let s=e.studiedNodeIds;return{...e,focusedGraph:n,focusedMapData:f(e.mapData,e.languageFocus),entrypointOpen:!!t&&!e.entrypointDismissed,level:i,region:a,selectedNode:o,languageOptions:t?m(t):KJ,projectName:t?g(t):`Loading local project`,chart:n?_(n,s):KJ,focusedStudiedCount:n?v(n,s):0,hint:n?y(n,e.mode,i,a?.id??null,e.layer,t,e.languageFocus):null,revealedRegionIds:n?b(n,e.showAll,a?.id??null,e.visitedRegionIds):qJ,moduleIndex:n?h(n):KJ}}function d(t,n){let r=e.get(t);r||(r=new Map,e.set(t,r));let i=n||`all`;return r.has(i)||r.set(i,MG(t,n)),r.get(i)}function f(e,n){if(!e)return e;let r=t.get(e);r||(r=new Map,t.set(e,r));let i=n||`all`;return r.has(i)||r.set(i,NG(e,n)),r.get(i)}function p(e){return n.has(e)||n.set(e,{nodeById:new Map(e.nodes.map(e=>[e.id,e])),regionById:new Map(e.regions.map(e=>[e.id,e]))}),n.get(e)}function m(e){return r.has(e)||r.set(e,PG(e)),r.get(e)}function h(e){return i.has(e)||i.set(e,qG(e)),i.get(e)}function g(e){return a.has(e)||a.set(e,hK(e)),a.get(e)}function _(e,t){return(!o||o.graph!==e||o.studiedNodeIds!==t)&&(o={graph:e,studiedNodeIds:t,value:FG(e,t)}),o.value}function v(e,t){return(!s||s.graph!==e||s.studiedNodeIds!==t)&&(s={graph:e,studiedNodeIds:t,value:e.nodes.filter(e=>t.has(e.id)).length}),s.value}function y(e,t,n,r,i,a,o){return(!c||c.graph!==e||c.mode!==t||c.level!==n||c.regionId!==r||c.layer!==i||c.projectGraph!==a||c.languageFocus!==o)&&(c={graph:e,mode:t,level:n,regionId:r,layer:i,projectGraph:a,languageFocus:o,value:ZJ(e,{mode:t,level:n,regionId:r,layer:i,projectGraph:a,languageFocus:o})}),c.value}function b(e,t,n,r){return(!l||l.graph!==e||l.showAll!==t||l.selectionId!==n||l.visitedIds!==r)&&(l={graph:e,showAll:t,selectionId:n,visitedIds:r,value:HG(e,{showAll:t,selectionId:n,visitedIds:r})}),l.value}return Object.freeze({derive:u})}var YJ=1.5;function XJ({homeChosen:e,homeOutsideFocus:t,languageFocus:n,hops:r}){return e?t?`Home is not written in ${LG(n)}.`:r===0?`Home is not lit yet.`:Number.isFinite(r)?`${r} ${r===1?`route`:`routes`} from Home.`:`No import route reaches it from Home.`:`No Home is chosen, so there is no route to measure from.`}function ZJ(e,{mode:t,level:n,regionId:r,layer:i,projectGraph:a,languageFocus:o}){if(t!==`easy`)return null;let s=e.regions.filter(e=>!e.understood);if(!s.length)return null;let c=(a??e).regions.some(e=>e.home),l=c&&!e.regions.some(e=>e.home),u=GG(e),d=s.map(e=>{let t=typeof e.hops_from_home==`number`?e.hops_from_home:1/0;return{regionId:e.id,hops:t,biasedHops:ZG(u.get(e.id)??``)?t+YJ:t,structures:typeof e.node_count==`number`?e.node_count:0}}).sort((e,t)=>e.biasedHops-t.biasedHops||t.structures-e.structures||e.regionId.localeCompare(t.regionId))[0],{biasedHops:f,...p}=d,m={...p,message:`Study ${d.regionId} next`,reason:XJ({homeChosen:c,homeOutsideFocus:l,languageFocus:o,hops:d.hops})};return n===jG.STUDY?r===d.regionId?{...m,message:`Prove you understand ${d.regionId}`,reason:`Questions come from this file, not from a model.`,action:{type:`OPEN_CHECKS`},actionLabel:`Prove understanding`}:{...m,action:{type:`OPEN_REGION`,regionId:d.regionId},actionLabel:`Open ${d.regionId}`}:n!==jG.SYSTEM||r!==d.regionId?{...m,action:{type:`OPEN_REGION`,regionId:d.regionId},actionLabel:`Open ${d.regionId}`}:i===`map`?e.nodes.some(e=>e.id===d.regionId)?{...m,reason:`Read it before proving it.`,action:null,actionLabel:null}:{...m,reason:`Its parser-proven structures are visible in Galaxy.`,action:{type:`SET_LAYER`,layer:`galaxy`},actionLabel:`View structures`}:{...m,reason:`Choose one of its parser-proven structures.`,action:null,actionLabel:null}}var QJ=`Codemble's local server is not responding. Your source files and saved progress are unchanged. Start Codemble again, then try again.`;function $J(e){return Number.isInteger(e?.status)?eY(e):QJ}function eY(e){return e instanceof Error?e.message:String(e)}var tY=Object.freeze({setTimeout(e,t){return globalThis.setTimeout(e,t)},clearTimeout(e){globalThis.clearTimeout(e)}}),nY=300,rY=400,iY=4e3,aY=8,oY=Object.freeze([Object.freeze({id:`discovering`,copy:`Finding your source files`}),Object.freeze({id:`parsing`,copy:`Reading each file`}),Object.freeze({id:`resolving`,copy:`Connecting imports and calls`}),Object.freeze({id:`checks`,copy:`Building graph-only checks`}),Object.freeze({id:`layout`,copy:`Placing your galaxy`})]);function sY({adapter:e,clock:t=tY,onReady:n=async()=>{}}={}){if(!e)throw TypeError(`Project mapping requires a learner adapter.`);let r=new Set,i=cY({phase:`idle`,error:``,picker:null,progress:null,failure:null}),a=0,o=null,s=null,c=null,l=null;function u(){return i}function d(e){return r.add(e),()=>r.delete(e)}function f(e){i=cY({...i,...e});for(let e of r)e()}async function p(){a+=1;let t=a;b(),lY(o),o=new AbortController;let n=o;f({phase:`idle`,error:``,picker:null,progress:null,failure:null});try{let[r,o]=await Promise.all([e.loadRecents({signal:n.signal}),e.browsePicker(null,{signal:n.signal})]);if(t!==a||n.signal.aborted)return i;f({phase:`picking`,picker:{...o,recents:r.recents,error:``,retryPath:null,scale:null,busy:!1}})}catch(e){!uY(e)&&t===a&&f({phase:`error`,error:$J(e)})}return i}async function m(t){if(i.phase!==`picking`||i.picker?.busy)return;lY(o),o=new AbortController;let n=o,r=a;f({failure:null});try{let s=await e.browsePicker(t,{signal:n.signal});o===n&&r===a&&!n.signal.aborted&&i.phase===`picking`&&f({picker:{...i.picker,...s,error:``,retryPath:null,scale:null}})}catch(e){o===n&&r===a&&!uY(e)&&i.phase===`picking`&&f({picker:{...i.picker,error:$J(e),retryPath:t}})}}async function h(t){if(i.phase!==`picking`||i.picker?.busy)return;a+=1;let r=a;lY(o),b(),o=new AbortController;let s=o;f({failure:null,picker:{...i.picker,busy:!0,error:``,retryPath:null,scale:null}});try{let o=await e.selectProject(t,{signal:s.signal});if(r!==a||s.signal.aborted||i.phase!==`picking`)return o;if(o.state===`parsing`)return f({progress:{state:`parsing`,stage:`discovering`,detail:null,files_done:0,files_total:0,error:null,pollError:``,attempts:0,pollOutage:!1,path:t}}),_(0,r),o;if(o.state===`ready`)return y(),await n(),o;if(o.state===`scale`){let t=await e.browsePicker(o.root,{signal:s.signal});return r===a&&!s.signal.aborted&&i.phase===`picking`&&f({picker:{...i.picker,...t,busy:!1,scale:o}}),o}let c=o.detail??``;return f({picker:{...i.picker,busy:!1,error:c},failure:c?{path:t,detail:c}:null}),o}catch(e){if(r===a&&!uY(e)&&i.phase===`picking`){let n=fY(e);f({picker:{...i.picker,busy:!1,error:n},failure:{path:t,detail:n}})}return}}async function g(){lY(c),c=new AbortController;let t=c;try{await e.resetProject({signal:t.signal})}catch(e){if(uY(e))return!1;if(dY(e))throw e}return t.signal.aborted?!1:(a+=1,lY(o),o=null,b(),f({phase:`idle`,error:``,picker:null,progress:null,failure:null}),!0)}function _(e,n){l!==null&&t.clearTimeout(l),l=t.setTimeout(async()=>{l=null,await v(n)},e)}async function v(t){if(!i.progress||t!==a)return;lY(s),s=new AbortController;let r=s,o=i.progress,c;try{c=await e.fetchParseProgress({signal:r.signal})}catch(e){if(s!==r||t!==a||uY(e)||!i.progress)return;let n=o.attempts+1;f({progress:{...o,pollError:fY(e),attempts:n,pollOutage:n>=aY}}),_(Math.min(iY,rY*2**(n-1)),t);return}if(!(s!==r||t!==a||!i.progress)){if(c.state===`ready`){y(),await n();return}if(c.state===`error`||c.state===`idle`){b();let e=c.error??``;f({progress:null,picker:{...i.picker,busy:!1,error:e},failure:e?{path:o.path,detail:e}:null});return}f({progress:{...o,...c,pollError:``,attempts:0,pollOutage:!1}}),_(nY,t)}}function y(){a+=1,b(),f({phase:`idle`,error:``,picker:null,progress:null,failure:null})}function b(){lY(s),s=null,l!==null&&(t.clearTimeout(l),l=null)}function x(){a+=1;for(let e of[o,s,c])lY(e);o=null,c=null,b(),r.clear()}return Object.freeze({browse:m,dispose:x,getSnapshot:u,reset:g,select:h,start:p,subscribe:d})}function cY(e){return Object.freeze(e)}function lY(e){e&&!e.signal.aborted&&e.abort()}function uY(e){return e?.name===`AbortError`}function dY(e){return Number.isInteger(e?.status)}function fY(e){return e instanceof Error?e.message:String(e)}var pY=Object.freeze({setTimeout(e,t){return globalThis.setTimeout(e,t)},clearTimeout(e){globalThis.clearTimeout(e)}});function mY({adapter:e=_Y(),clock:t=pY}={}){let n=new Set,r=JJ(),i=r.derive({status:`idle`,error:``,graph:null,level:jG.GALAXY,region:null,selectedNode:null,studyData:null,studyError:``,explanation:null,explanationLoading:!1,explanationError:``,showChart:!1,studiedNodeIds:new Set,visitedRegionIds:new Set,showChecks:!1,checkData:null,checkError:``,entrypointDismissed:!1,entrypointError:``,litRegionId:null,pendingDawnRegionId:null,languageFocus:`all`,picker:null,parseProgress:null,projectFailure:null,layer:`galaxy`,layerChosen:!1,mapTab:`architecture`,mapData:null,mapError:``,coachmarksSeen:bY(),llmStatus:null,hoverNodeId:null,showAll:CY(),finderOpen:!1,sidebarOpen:!1,legendOpen:!1,mode:`easy`,modeChosen:null}),a=0,o=0,s=null,c=null,l=null,u=null,d=null,f=null,p=null,m=null,h=null;function g(){return i}function _(e){return n.add(e),()=>n.delete(e)}function v(e,{preserveChecks:t=!1}={}){let a=i,o=r.derive({...a,...e});if(o.level!==a.level||o.region?.id!==a.region?.id){let e={hoverNodeId:null};t||(TY(u),TY(d),u=null,d=null,e.showChecks=!1,e.checkData=null,e.checkError=``),o=r.derive({...o,...e})}i=Object.freeze(o);for(let e of n)e()}let y=sY({adapter:e,clock:t,async onReady(){a+=1;let e=a;return TY(s),s=new AbortController,S(s,e)}}),b=y.subscribe(()=>{let e=y.getSnapshot(),t={picker:e.picker,parseProgress:e.progress,projectFailure:e.failure};e.phase===`picking`&&(t.status=`picking`),e.phase===`error`&&(t.status=`error`,t.error=e.error),v(t)});async function x(){a+=1;let t=a;TY(s),s=new AbortController;let n=s;v({status:`loading`,error:``});let r;try{r=await e.loadPickerState({signal:n.signal})}catch(e){return!EY(e)&&t===a&&v({status:`error`,error:$J(e)}),i}return t!==a||n.signal.aborted?i:r.state===`unpicked`?(await y.start(),i):S(n,t)}async function S(t,n){v({status:`loading`,error:``,picker:null});try{let r=await e.loadGraph({signal:t.signal});if(n!==a||t.signal.aborted)return i;v({status:`ready`,graph:r,region:mK(r),error:``,entrypointDismissed:!!r.selected_entrypoint})}catch(e){!EY(e)&&n===a&&v({status:`error`,error:$J(e)})}return await C(t,n),i}async function C(t,n){let r=o,[s,c,l]=await Promise.allSettled([(async()=>e.loadMode({signal:t.signal}))(),(async()=>e.fetchLlmStatus({signal:t.signal}))(),(async()=>e.fetchVisited?.({signal:t.signal}))()]);if(!(n!==a||t.signal.aborted)){if(l.status===`fulfilled`&&Array.isArray(l.value?.visited)&&v({visitedRegionIds:new Set(l.value.visited)}),r===o){let e=s.status===`fulfilled`?s.value:null;e?.mode===`easy`||e?.mode===`expert`?ne(e.mode,e.chosen===!0):i.modeChosen===null&&v({modeChosen:!0})}c.status===`fulfilled`&&v({llmStatus:c.value})}}async function w(){let t=a;if(await e.clearProgress({}),v({visitedRegionIds:new Set}),t!==a)return i;a+=1;let n=a;TY(s),s=new AbortController,TY(p),p=null,v({mapData:null,mapError:``});let r=await S(s,n);return n===a&&i.layer===`map`&&await le(),r}async function T(){return await y.reset()?(N(),TY(f),f=null,TY(p),p=null,TY(m),m=null,v({graph:null,parseProgress:null,projectFailure:null,region:null,selectedNode:null,level:jG.GALAXY,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1,showChart:!1,studiedNodeIds:new Set,showChecks:!1,checkData:null,checkError:``,entrypointDismissed:!1,entrypointError:``,litRegionId:null,pendingDawnRegionId:null,languageFocus:`all`,llmStatus:null,picker:null,mapData:null,mapError:``}),x()):i}async function E(e){switch(e.type){case`ADVANCE`:return A(e.node);case`ADVANCE_REGION`:k(e.regionId);return;case`FOLLOW_HINT`:return D();case`RETREAT`:ee();return;case`SELECT_STUDY_NODE`:return te(e.nodeId);case`SET_LANGUAGE_FOCUS`:j(e.language);return;case`SET_MODE`:return re(e.mode);case`HOVER_NODE`:i.hoverNodeId!==(e.nodeId??null)&&v({hoverNodeId:e.nodeId??null});return;case`SHOW_CHART`:v({showChart:!0});return;case`HIDE_CHART`:v({showChart:!1});return;case`OPEN_CHECKS`:return ae();case`CLOSE_CHECKS`:P();return;case`SUBMIT_CHECK`:return se(e.checkId,e.selectedIds);case`CONSUME_DAWN`:fe(e.regionId);return;case`SELECT_ENTRYPOINT`:return F(e.nodeId);case`DISMISS_ENTRYPOINT`:v({entrypointDismissed:!0});return;case`CHANGE_HOME`:N(),v({entrypointDismissed:!1,entrypointError:``,level:jG.GALAXY,selectedNode:null,showChart:!1,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1});return;case`BROWSE_PICKER`:return y.browse(e.path);case`SELECT_PROJECT`:return y.select(e.path);case`RESET_PROJECT`:return T();case`CLEAR_PROGRESS`:return w();case`SET_LAYER`:return ce(e.layer);case`SET_MAP_TAB`:v({mapTab:e.tab});return;case`DISMISS_COACHMARKS`:xY(),v({coachmarksSeen:!0});return;case`SET_LEVEL_GALAXY`:N(),v({level:jG.GALAXY,selectedNode:null});return;case`TOGGLE_SHOW_ALL`:wY(!i.showAll),v({showAll:!i.showAll});return;case`SET_FINDER_OPEN`:v({finderOpen:e.open});return;case`TOGGLE_SIDEBAR`:v({sidebarOpen:!i.sidebarOpen});return;case`TOGGLE_LEGEND`:v({legendOpen:!i.legendOpen});return;case`GO_TO_REGION`:return v({finderOpen:!1}),k(e.regionId);default:throw Error(`Unknown learner-session event: ${e.type}`)}}function D(){let e=i.hint?.action;if(e){if(e.type===`OPEN_REGION`){k(e.regionId);return}if(e.type===`SET_LAYER`)return ce(e.layer);if(e.type===`OPEN_STUDY`)return te(e.nodeId);if(e.type===`OPEN_CHECKS`)return i.level===jG.STUDY&&ee(),ae()}}function O(t){!t||i.visitedRegionIds.has(t)||(v({visitedRegionIds:new Set(i.visitedRegionIds).add(t)}),Promise.resolve(e.markVisited?.(t)).catch(()=>{}))}function k(e){let t=i.graph?.regions.find(t=>t.id===e);t&&(N(),O(t.id),v({languageFocus:i.languageFocus!==`all`&&t.language!==i.languageFocus?t.language:i.languageFocus,region:t,selectedNode:null,level:jG.SYSTEM,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1}))}async function A(e){if(!(!i.focusedGraph||!e)){if(i.level===jG.GALAXY){let t=i.focusedGraph.regions.find(t=>t.id===e.id)??e;N(),O(t.id),v({region:t,selectedNode:null,level:jG.SYSTEM,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1});return}if(i.level===jG.SYSTEM){let t=i.focusedGraph.nodes.find(t=>t.id===e.id)??e;return v({selectedNode:t,level:jG.STUDY}),M(t.id)}if(i.level===jG.STUDY){let t=i.focusedGraph.nodes.find(t=>t.id===e.id);return!t||t.id===i.selectedNode?.id?void 0:(v({selectedNode:t}),M(t.id))}}}function ee(){i.level===jG.STUDY?(N(),v({selectedNode:null,level:jG.SYSTEM,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1})):i.level===jG.SYSTEM&&v({level:jG.GALAXY})}async function te(e){let t=i.graph?.nodes.find(t=>t.id===e);if(!t)return;let n=i.graph.regions.find(e=>e.id===t.region);return n&&O(n.id),v({languageFocus:i.languageFocus!==`all`&&t.language!==i.languageFocus?t.language:i.languageFocus,region:n??i.region,selectedNode:t,level:jG.STUDY}),M(t.id)}function j(e){let t=i.selectedNode?.id;v({languageFocus:e}),t&&i.selectedNode?.id!==t&&(N(),v({studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1}))}function ne(e,t){let n=i.layerChosen?i.layer:e===`easy`?`map`:`galaxy`;v({mode:e,layer:n,modeChosen:t}),n===`map`&&le()}async function re(t){if(t!==`easy`&&t!==`expert`)return;let n=i.mode,r=i.modeChosen;if(t===n&&i.modeChosen)return;o+=1,TY(m),m=new AbortController;let s=m,c=a;ne(t,!0);try{await e.saveMode(t,{signal:s.signal})}catch(e){m===s&&c===a&&!EY(e)&&ne(n,r);return}if(!(c!==a||s.signal.aborted)&&i.level===jG.STUDY&&i.selectedNode)return ie(i.selectedNode.id)}async function M(t){N(),c=new AbortController;let n=c;v({studyData:null,studyError:``}),ie(t);try{let r=await e.loadStudy(t,{signal:n.signal});if(n.signal.aborted||i.level!==jG.STUDY||i.selectedNode?.id!==t)return;v({studyData:r,studiedNodeIds:new Set(i.studiedNodeIds).add(r.node.id)})}catch(e){c===n&&!n.signal.aborted&&!EY(e)&&i.selectedNode?.id===t&&v({studyError:DY(e)})}}function N(){TY(c),c=null,TY(l),l=null}async function ie(t){TY(l),l=new AbortController;let n=l;v({explanation:null,explanationError:``,explanationLoading:!0});try{let r=await e.loadExplanation(t,i.mode,{signal:n.signal});if(n.signal.aborted||i.level!==jG.STUDY||i.selectedNode?.id!==t)return;v({explanation:r,explanationLoading:!1})}catch(e){l===n&&!n.signal.aborted&&!EY(e)&&i.selectedNode?.id===t&&v({explanationError:DY(e),explanationLoading:!1})}}async function ae(){if(!i.region)return;let e=i.region.id;return v({showChecks:!0,checkData:null,checkError:``}),oe(e)}async function oe(t){TY(u),u=new AbortController;let n=u;try{let r=await e.loadChecks(t,{signal:n.signal});return!n.signal.aborted&&i.showChecks&&i.region?.id===t&&v({checkData:r,checkError:``},{preserveChecks:!0}),r}catch(e){u===n&&!EY(e)&&!n.signal.aborted&&i.showChecks&&i.region?.id===t&&v({checkError:DY(e)},{preserveChecks:!0});return}}function P(){TY(u),u=null,v({showChecks:!1,checkData:null,checkError:``})}async function se(t,n){if(!i.region)return;let r=i.region.id;TY(d),d=new AbortController;let a=d,o=await e.submitCheck(r,t,n,{signal:a.signal});if(a.signal.aborted||i.region?.id!==r)return o;if(o.correct&&await oe(r),o.region_understood){let t=await e.loadGraph({signal:a.signal});!a.signal.aborted&&i.region?.id===r&&(TY(p),p=null,v({graph:t,region:t.regions.find(e=>e.id===r),mapData:null,mapError:``},{preserveChecks:!0}),de(r),i.layer===`map`&&await le())}return o}async function F(t){let n=a;TY(f),f=new AbortController;let r=f;v({entrypointError:``});try{let o=await e.selectEntrypoint(t,{signal:r.signal});return n!==a||r.signal.aborted?i:(TY(p),p=null,v({graph:o,region:mK(o),entrypointError:``,entrypointDismissed:!0,mapData:null,mapError:``}),i.layer===`map`&&await le(),o)}catch(e){f===r&&n===a&&!r.signal.aborted&&!EY(e)&&v({entrypointError:DY(e)});return}}async function ce(e){if(v({layer:e,layerChosen:!0}),e===`map`)return le()}function le(){if(!i.mapData)return p&&!p.signal.aborted&&!i.mapError?void 0:ue()}async function ue(){let t=a;TY(p),p=new AbortController;let n=p;v({mapError:``});try{let r=await e.fetchMap({signal:n.signal});return t===a&&!n.signal.aborted&&v({mapData:r,mapError:``}),r}catch(e){p===n&&t===a&&!n.signal.aborted&&!EY(e)&&v({mapError:DY(e),mapData:null});return}}function de(e){h!==null&&t.clearTimeout(h),v({litRegionId:e,pendingDawnRegionId:e}),h=t.setTimeout(()=>{h=null,i.litRegionId===e&&v({litRegionId:null})},520)}function fe(e){i.pendingDawnRegionId===e&&v({pendingDawnRegionId:null})}function pe(){a+=1;for(let e of[s,c,l,u,d,f,p,m])TY(e);s=null,c=null,l=null,u=null,d=null,f=null,p=null,m=null,b(),y.dispose(),h!==null&&(t.clearTimeout(h),h=null)}return Object.freeze({dispatch:E,dispose:pe,getSnapshot:g,start:x,subscribe:_})}var hY=6e4;function gY(e,t){if(typeof AbortSignal?.timeout!=`function`||typeof AbortSignal?.any!=`function`)return e;let n=AbortSignal.timeout(t);return{...e,signal:e.signal?AbortSignal.any([e.signal,n]):n}}function _Y(e=globalThis.fetch){if(typeof e!=`function`)throw TypeError(`Learner-session HTTP adapter requires fetch.`);async function t(t,n,r={}){let i;try{i=await e(t,r)}catch(e){throw e instanceof Error&&e.name===`TimeoutError`?Error(`${n} timed out. Codemble is still running — try again.`):e}if(!i.ok){let e=(await i.json().catch(()=>null))?.detail,t=Error(typeof e==`string`&&e?e:`${n} returned ${i.status}.`);throw t.status=i.status,t}return i.json()}return Object.freeze({loadGraph(e={}){return t(`/api/graph`,`Graph request`,e)},fetchMap(e={}){return t(`/api/map`,`Map request`,e)},loadStudy(e,n={}){return t(`/api/node/${encodeURIComponent(e)}/study`,`Study request`,n)},loadExplanation(e,n,r={}){return t(`/api/node/${encodeURIComponent(e)}/explanation?mode=${encodeURIComponent(n)}`,`Explanation request`,gY(r,hY))},loadChecks(e,n={}){return t(`/api/regions/${encodeURIComponent(e)}/checks`,`Checks request`,n)},submitCheck(e,n,r,i={}){return t(`/api/regions/${encodeURIComponent(e)}/checks/${encodeURIComponent(n)}`,`Check submission`,{...i,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({selected_ids:r})})},selectEntrypoint(e,n={}){return t(`/api/entrypoint`,`Home selection`,{...n,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({node_id:e})})},loadMode(e={}){return t(`/api/mode`,`Mode request`,e)},saveMode(e,n={}){return t(`/api/mode`,`Mode update`,{...n,method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({mode:e})})},fetchLlmStatus(e={}){return t(`/api/llm/status`,`Model status`,e)},resetProject(e={}){return t(`/api/picker/reset`,`Project reset`,{...e,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({confirmed:!0})})},loadPickerState(e={}){return t(`/api/picker/state`,`Picker state`,e)},browsePicker(e,n={}){return t(`/api/picker/browse${e?`?path=${encodeURIComponent(e)}`:``}`,`Folder listing`,n)},loadRecents(e={}){return t(`/api/picker/recents`,`Recent projects`,e)},fetchParseProgress(e={}){return t(`/api/picker/progress`,`Parse progress`,e)},clearProgress(e={}){return t(`/api/progress`,`Progress reset`,{...e,method:`DELETE`})},fetchVisited(e={}){return t(`/api/progress/visited`,`Explored regions`,e)},markVisited(e,n={}){return t(`/api/progress/visited`,`Explored regions`,{...n,method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({region_id:e})})},async selectProject(t,n={}){let r=await e(`/api/picker/select`,{...n,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:t})}),i=await r.json().catch(()=>null);if(r.ok)return{state:r.status===202?`parsing`:`ready`};let a=i?.detail;return a&&typeof a==`object`&&a.reason===`scale`?{state:`scale`,...a}:{state:`error`,detail:typeof a==`string`?a:`Project selection returned ${r.status}.`}}})}var vY=`codemble.coachmarks.seen`;function yY(){if(typeof document>`u`)return null;try{return globalThis.localStorage??null}catch{return null}}function bY(){try{return yY()?.getItem(vY)===`1`}catch{return!1}}function xY(){try{yY()?.setItem(vY,`1`)}catch{}}var SY=`codemble.galaxy.showAll`;function CY(){try{return yY()?.getItem(SY)===`1`}catch{return!1}}function wY(e){try{let t=yY();e?t?.setItem(SY,`1`):t?.removeItem(SY)}catch{}}function TY(e){e&&!e.signal.aborted&&e.abort()}function EY(e){return e instanceof Error&&e.name===`AbortError`}function DY(e){return e instanceof Error?e.message:String(e)}var OY=Object.freeze([{id:`editableField`,open:e=>e.editableFocus===!0,dismissible:!1},{id:`nativeDialog`,open:e=>e.nativeDialogOpen===!0,dismissible:!1},{id:`railDisclosure`,open:e=>e.railDisclosureOpen===!0,dismissible:!0},{id:`checks`,open:e=>e.showChecks===!0,dismissible:!0},{id:`sidebar`,open:e=>e.sidebarOpen===!0,dismissible:!0},{id:`finder`,open:e=>e.finderOpen===!0,dismissible:!1},{id:`entrypoint`,open:e=>e.entrypointOpen===!0,dismissible:!1},{id:`chart`,open:e=>e.showChart===!0,dismissible:!0},{id:`firstFlight`,open:e=>e.firstFlightOpen===!0,dismissible:!0}]);function kY(e){let t=OY.find(t=>t.open(e??{}));return t?{id:t.id,dismissible:t.dismissible}:null}function AY(e={}){if(e.ready!==!0)return null;let t=kY(e);return t?t.dismissible?{kind:`dismiss`,surface:t.id}:null:e.canRetreat?{kind:`retreat`}:null}function jY(e){return String(e??``).replace(/[\\`*_[\]<>#|]/g,`\\$&`)}function MY(e,t,n=`${t}s`){return`${e} ${e===1?t:n}`}function NY(e){return`${String(e?.projectName??`local-project`).normalize(`NFKD`).toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||`local-project`}-codemble-brief.md`}function PY(e,{charted:t=0}={}){let n=e?.languages??[],r=e?.busiest??[],i=e?.unsupportedSources??[],a=e?.relationships??{proven:0,hedged:0},o=e?.importCycles??[],s=e?.modules??0,c=e?.structures??0,l=e?.understood??0;return[`# ${jY(e?.projectName??`Local project`)} — Codemble project brief`,``,`> Generated locally from Codemble's parser graph. It contains no AI-generated narration.`,``,`## Project shape`,``,`- ${MY(s,`file`)} containing ${MY(c,`structure`)}.`,`- ${e?.lines??0} lines across parser-supported source files.`,``,`### Languages`,``,`| Language | Files | Structures |`,`| --- | ---: | ---: |`,...n.length?n.map(e=>`| ${jY(e.label)} | ${e.count} | ${e.structures} |`):[`| None reported | 0 | 0 |`],``,`## Home`,``,e?.home?`- Codemble resolved Home as ${jY(e.home.id)} (${jY(e.home.language)}).`:`- No Home entrypoint was resolved; this brief does not invent one.`,``,`## Called from the most places`,``,...r.length?r.map(e=>`- ${jY(e.id)} — called from ${MY(e.value,`place`)}.`):[`- No module has a reported caller count.`],``,`## Proven import cycles`,``,...o.length?o.map(e=>`- Strongly connected group: ${e.map(jY).join(`, `)}. Each file can reach every other through proven imports.`):[`- None reported.`],``,`## Parser evidence`,``,`- Proven relationships: ${a.proven}.`,`- Hedged relationships: ${a.hedged}. Hedged relationships are possible parser matches, not proven links.`,`- Files Codemble could not read: ${e?.unreadable??0}.`,...i.length?[`- Unsupported source files:`,...i.map(e=>` - ${jY(e.extension||`unknown extension`)} (${jY(e.language||`unknown language`)}): ${e.count}`)]:[`- Unsupported source files: 0.`],``,`## Learning progress`,``,`- Charted systems: ${t} of ${s}. Charted means visited, not understood.`,`- Understood systems: ${l} of ${s}. Understood means a graph-derived check passed.`,``].join(` -`)}function FY(){let e=(0,_.useRef)(null),t=(0,_.useRef)(null),n=(0,_.useRef)(null),r=(0,_.useRef)(null),i=(0,_.useRef)(!1),a=(0,_.useRef)(null),o=(0,_.useRef)(null),s=(0,_.useRef)(null),c=(0,_.useRef)(!1),l=(0,_.useRef)(null),u=(0,_.useRef)(null),[d,f]=(0,_.useState)(!1),[p,m]=(0,_.useState)(!1),[h,g]=(0,_.useState)(null),v=(0,_.useMemo)(()=>nJ(),[]),y=(0,_.useMemo)(()=>mY({adapter:_Y()}),[]),b=(0,_.useSyncExternalStore)(y.subscribe,y.getSnapshot,y.getSnapshot);(0,_.useEffect)(()=>(y.start(),()=>y.dispose()),[y]),(0,_.useEffect)(()=>{b.status!==`ready`&&(f(!1),g(null),v.clear())},[v,b.status]),(0,_.useEffect)(()=>{let e=window.matchMedia(`(min-width: 40rem)`),t=e=>{e.matches&&f(!1)};return e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]),(0,_.useEffect)(()=>{function e(e){(e.metaKey||e.ctrlKey)&&e.key.toLowerCase()===`k`&&(e.preventDefault(),r.current=document.activeElement===document.body?null:document.activeElement,y.dispatch({type:`SET_FINDER_OPEN`,open:!0}))}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[y]);let x={railDisclosure:()=>{f(!1),Pe(e)},checks:()=>Ie(),sidebar:()=>L(),chart:()=>{y.dispatch({type:`HIDE_CHART`}),Pe(a)},firstFlight:()=>He()};(0,_.useEffect)(()=>{function e(e){if(e.key!==`Escape`)return;let t=AY(RY(y.getSnapshot(),{firstFlightOpen:c.current}));if(t){if(e.preventDefault(),t.kind===`dismiss`){x[t.surface]();return}y.dispatch({type:`RETREAT`})}}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[y]);let{chart:S,checkData:C,checkError:w,coachmarksSeen:T,entrypointError:E,entrypointOpen:D,error:O,explanation:k,explanationError:A,explanationLoading:ee,finderOpen:te,focusedGraph:j,focusedMapData:ne,focusedStudiedCount:re,graph:M,hint:N,hoverNodeId:ie,languageFocus:ae,languageOptions:oe,layer:P,legendOpen:se,level:F,litRegionId:ce,llmStatus:le,mapError:ue,mapTab:de,mode:fe,modeChosen:pe,moduleIndex:me,parseProgress:he,pendingDawnRegionId:ge,picker:_e,projectName:ve,projectFailure:ye,region:be,revealedRegionIds:xe,visitedRegionIds:Se,selectedNode:Ce,showAll:we,showChart:Te,showChecks:Ee,sidebarOpen:De,status:Oe,studyData:ke,studyError:Ae}=b;(0,_.useLayoutEffect)(()=>{!i.current||te||F!==jG.SYSTEM||!u.current||(setTimeout(()=>u.current?.focus(),0),i.current=!1,r.current=null)},[te,F,be?.id]);let je=(0,_.useMemo)(()=>bG(M),[M]),Me=je[h]??null,I=h!==null&&Me!==null;c.current=I;let Ne=(0,_.useMemo)(()=>{if(!j)return null;let e=new Map;for(let t of j.regions){let n=tK(t.community_family);n!==null&&e.set(t.id,n)}return e},[j]);function Pe(t,n){setTimeout(()=>{for(let r of[t,n,e]){let e=r&&`current`in r?r.current:r;if(e?.isConnected&&e.getClientRects().length){e.focus();return}}},0)}function Fe(e){y.dispatch({type:`TOGGLE_SIDEBAR`}),De&&Pe(e.currentTarget,t)}function L(){y.dispatch({type:`TOGGLE_SIDEBAR`}),Pe(t)}function Ie(){y.dispatch({type:`CLOSE_CHECKS`}),Pe(o,u)}function Le(e){r.current=e.currentTarget,y.dispatch({type:`SET_FINDER_OPEN`,open:!0})}function Re(){i.current||(i.current=!1,y.dispatch({type:`SET_FINDER_OPEN`,open:!1}),Pe(r.current,n),r.current=null)}function ze(e){i.current=!0,y.dispatch({type:`GO_TO_REGION`,regionId:e})}function Be(){y.dispatch({type:`FOLLOW_HINT`}),y.getSnapshot().showChecks||Pe(u)}function Ve(e){let t=je[e];t&&(g(e),y.dispatch({type:`SET_LAYER`,layer:`galaxy`}),y.dispatch({type:`GO_TO_REGION`,regionId:t.id}))}function He(){g(null),Pe(s)}function Ue(){y.dispatch({type:`DISMISS_COACHMARKS`}),requestAnimationFrame(()=>l.current?.focus())}if(O)return(0,$.jsxs)(`main`,{className:`load-state`,role:`alert`,children:[(0,$.jsx)(`h1`,{children:`The graph did not load.`}),(0,$.jsx)(`p`,{children:O}),(0,$.jsx)(`p`,{children:`Your progress is stored on this machine and is not affected.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:()=>y.start(),children:`Try again`})]});if(he)return(0,$.jsx)(BY,{progress:he,onCancel:()=>y.dispatch({type:`RESET_PROJECT`})});if(Oe===`picking`&&_e)return(0,$.jsx)(VY,{picker:_e,failure:ye,onBrowse:e=>y.dispatch({type:`BROWSE_PICKER`,path:e}),onSelect:e=>y.dispatch({type:`SELECT_PROJECT`,path:e})});if(!M||!j||!be)return(0,$.jsx)(`main`,{className:`load-state`,"aria-busy":`true`,children:(0,$.jsx)(`p`,{children:`Mapping parser evidence…`})});let We=P===`map`?`map`:`galaxy`,Ge=j.nodes.some(e=>e.id===be.id)?be.id:null,Ke=F===jG.SYSTEM&&P===`galaxy`?Oq(j.nodes.filter(e=>e.region===be.id)):[],qe=F===jG.SYSTEM?(0,$.jsxs)(`section`,{ref:u,className:`orientation-copy orientation-copy--system${P===`map`?` orientation-copy--inline`:``}`,tabIndex:-1,children:[(0,$.jsx)(`p`,{children:j.nodes.some(e=>e.region===be.id&&e.partial)?`${be.node_count} source ${be.node_count===1?`file remains`:`files remain`} visible · ${be.loc} lines. The module is unchartable beyond raw source because it has a syntax error.`:P===`map`?`The ${be.node_count} parser-proven ${be.node_count===1?`structure`:`structures`} inside this module ${be.node_count===1?`is`:`are`} drawn as planets in the Galaxy layer. This map shows how modules connect, not what is inside them.`:`${be.node_count} parser-proven ${be.node_count===1?`structure`:`structures`} · ${be.loc} ${be.loc===1?`line`:`lines`} in this system.`}),Ke.length?(0,$.jsxs)(`p`,{className:`system-orbit-note`,children:[(0,$.jsx)(`span`,{children:`Call layers`}),Ke.map(e=>(0,$.jsx)(`i`,{"data-unproven":e.unproven||void 0,children:e.label},e.ring)),(0,$.jsxs)(`small`,{children:[`Solid guides use certain calls only`,Ke.some(e=>e.unproven)?`; dashed means no proven path.`:`.`]})]}):null,(0,$.jsxs)(`div`,{className:`orientation-copy__actions`,children:[P===`map`&&Ge?(0,$.jsx)(`button`,{className:`check-launch check-launch--read`,type:`button`,onClick:()=>{m(!0),y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:Ge})},children:`Read the source`}):null,(0,$.jsx)(`button`,{ref:o,className:`check-launch`,type:`button`,onClick:()=>y.dispatch({type:`OPEN_CHECKS`}),children:j.nodes.some(e=>e.region===be.id&&e.partial)?`Check availability`:be.understood?`Review understanding`:`Prove understanding`})]})]}):null;return(0,$.jsxs)(`main`,{className:`app-shell`,"data-level":Te?`chart`:F.toLowerCase(),"data-mode":fe,children:[(0,$.jsxs)(`header`,{className:`instrument-rail`,children:[(0,$.jsxs)(`div`,{className:`brand-lockup`,children:[(0,$.jsx)(`span`,{className:`brand-mark`,"aria-hidden":`true`}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`strong`,{children:`Codemble`}),(0,$.jsx)(`span`,{children:ve})]})]}),(0,$.jsx)(`nav`,{className:`location`,"aria-label":`Breadcrumb`,"aria-live":`polite`,children:Te?(0,$.jsx)(`span`,{"aria-current":`page`,children:`Star chart`}):(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`button`,{type:`button`,disabled:F===jG.GALAXY,"aria-current":F===jG.GALAXY?`page`:void 0,onClick:()=>y.dispatch({type:`SET_LEVEL_GALAXY`}),children:`All modules`}),F===jG.GALAXY?(0,$.jsxs)(`small`,{children:[` · Home `,M.selected_entrypoint&&mK(M)?(0,$.jsx)(`button`,{type:`button`,className:`breadcrumb-home`,onClick:()=>y.dispatch({type:`ADVANCE_REGION`,regionId:mK(M).id}),children:mK(M).id}):M.selected_entrypoint?`unresolved`:`unselected`]}):(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),(0,$.jsx)(`button`,{type:`button`,disabled:F===jG.SYSTEM,"aria-current":F===jG.SYSTEM?`page`:void 0,onClick:()=>y.dispatch({type:`RETREAT`}),children:be.id})]}),F===jG.STUDY&&Ce?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),(0,$.jsx)(`span`,{"aria-current":`page`,children:Ce.name})]}):null,ae===`all`?null:(0,$.jsxs)(`small`,{children:[` · `,LG(ae),` focus`]})]})}),(0,$.jsxs)(`div`,{className:`rail-overflow`,"data-open":d||void 0,children:[(0,$.jsxs)(`button`,{ref:e,className:`mobile-menu-trigger`,type:`button`,"aria-expanded":d,"aria-controls":`rail-overflow-panel`,onClick:()=>f(e=>!e),children:[(0,$.jsx)(`span`,{className:`rail-trigger__compact`,children:`Menu`}),(0,$.jsx)(`span`,{className:`rail-trigger__wide`,children:`More`})]}),(0,$.jsxs)(`div`,{className:`rail-overflow__panel`,id:`rail-overflow-panel`,onClickCapture:e=>{e.target.closest(`button`)&&!e.target.closest(`.switch-project`)&&f(!1)},children:[(0,$.jsxs)(`div`,{className:`rail-actions`,children:[(0,$.jsx)(`button`,{ref:t,className:`rail-action`,type:`button`,"aria-pressed":De,onClick:Fe,children:`Modules`}),(0,$.jsxs)(`button`,{ref:n,className:`rail-action`,type:`button`,onClick:Le,children:[`Find `,(0,$.jsx)(`kbd`,{children:`⌘K`})]}),Te?(0,$.jsxs)(`button`,{ref:a,className:`rail-action`,type:`button`,onClick:()=>{y.dispatch({type:`HIDE_CHART`}),Pe(a)},children:[`Back to `,We]}):(0,$.jsxs)($.Fragment,{children:[F===jG.GALAXY?null:(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,onClick:()=>y.dispatch({type:`RETREAT`}),children:F===jG.STUDY?P===`map`?`Back to the module`:`Back to system`:`Back to ${We}`}),(0,$.jsx)(`button`,{ref:a,className:`rail-action`,type:`button`,onClick:()=>y.dispatch({type:`SHOW_CHART`}),children:`Star chart`})]})]}),(0,$.jsxs)(`div`,{className:`rail-more`,children:[M.entrypoint_candidates.length?(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,onClick:()=>y.dispatch({type:`CHANGE_HOME`}),children:`Change Home`}):null,(0,$.jsx)(YY,{onConfirm:()=>y.dispatch({type:`RESET_PROJECT`})})]}),(0,$.jsxs)(`div`,{className:`rail-controls`,children:[P===`galaxy`?(0,$.jsx)(`button`,{className:`rail-action rail-action--toggle`,type:`button`,"aria-pressed":we,title:`Draw every module at once, including the ones no route from Home reaches`,onClick:()=>y.dispatch({type:`TOGGLE_SHOW_ALL`}),children:`Show all`}):null,(0,$.jsx)(qY,{layer:P,mode:fe,onChange:e=>y.dispatch({type:`SET_LAYER`,layer:e})}),(0,$.jsx)(JY,{options:oe,value:ae,onChange:e=>y.dispatch({type:`SET_LANGUAGE_FOCUS`,language:e})}),(0,$.jsx)(bJ,{mode:fe,modeChosen:pe,onChoose:e=>y.dispatch({type:`SET_MODE`,mode:e})})]})]})]})]}),Te?(0,$.jsxs)(`section`,{className:`chart-stage`,"aria-label":`Language concept progress`,children:[De?(0,$.jsx)(KY,{index:me,currentRegionId:null,onGo:e=>y.dispatch({type:`GO_TO_REGION`,regionId:e}),onClose:L}):null,(0,$.jsx)(eX,{chart:S,studiedCount:re,exploredCount:Se.size,projectName:ve,mode:fe,overview:_K(M),onClearProgress:()=>y.dispatch({type:`CLEAR_PROGRESS`})})]}):(0,$.jsxs)(`section`,{ref:l,className:`map-stage`,"aria-label":`Parser-proven project map`,tabIndex:-1,children:[De?(0,$.jsx)(KY,{index:me,currentRegionId:F===jG.GALAXY?null:be?.id,onGo:e=>y.dispatch({type:`GO_TO_REGION`,regionId:e}),onClose:L}):null,P===`map`?(0,$.jsx)(pJ,{data:ne,mapTab:de,mode:fe,communityIndexByRegion:Ne,selectedRegionId:F===jG.GALAXY?void 0:be?.id,hasEntrypointCandidates:M.entrypoint_candidates.length>0,unsupportedSources:j.unsupported_sources,importCycles:M.import_cycles,error:ue,languageFocus:ae,languageFocusLabel:ae===`all`?``:LG(ae),onClearLanguageFocus:()=>y.dispatch({type:`SET_LANGUAGE_FOCUS`,language:`all`}),onSelectTab:e=>y.dispatch({type:`SET_MAP_TAB`,tab:e}),onSelectRegion:e=>y.dispatch({type:`ADVANCE_REGION`,regionId:e}),onSelectNode:e=>{m(!1),y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:e})},onRetry:()=>y.dispatch({type:`SET_LAYER`,layer:`map`}),viewportStore:v,children:qe}):(0,$.jsx)(Vq,{graph:j,level:F,region:be,selectedNode:Ce,hoverNodeId:ie,pendingDawnRegionId:ge,revealedRegionIds:xe,mode:fe,firstFlightActive:I,onHoverNode:e=>y.dispatch({type:`HOVER_NODE`,nodeId:e}),onAdvance:e=>y.dispatch({type:`ADVANCE`,node:e}),onRetreat:()=>y.dispatch({type:`RETREAT`}),onDawnConsumed:e=>y.dispatch({type:`CONSUME_DAWN`,regionId:e})}),(0,$.jsx)(`button`,{className:`legend-toggle`,type:`button`,"aria-expanded":se,onPointerEnter:()=>y.dispatch({type:`HOVER_NODE`,nodeId:null}),onClick:()=>y.dispatch({type:`TOGGLE_LEGEND`}),children:`Key`}),(0,$.jsxs)(`aside`,{className:`map-legend`,hidden:!se,tabIndex:0,"aria-label":P===`map`?`Map legend`:`Galaxy legend`,onPointerEnter:()=>y.dispatch({type:`HOVER_NODE`,nodeId:null}),onKeyDown:e=>{let t=e.currentTarget,n=Math.max(48,t.clientHeight*.8),r={ArrowDown:32,ArrowUp:-32,PageDown:n,PageUp:-n}[e.key];r===void 0?(e.key===`Home`||e.key===`End`)&&(e.preventDefault(),t.scrollTop=e.key===`Home`?0:t.scrollHeight):(e.preventDefault(),t.scrollBy({top:r,behavior:`auto`}))},children:[P===`galaxy`?(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-size`}),`Size · `,fe===`easy`?`how much code`:`lines of code`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-brightness`}),`Brighter · `,fe===`easy`?`used in more places`:`more distinct callers`]})]}):null,(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-dot legend-dot--dim`}),`Dim · `,fe===`easy`?`not proven yet`:`not understood`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-dot legend-dot--lit`}),`Amber · `,fe===`easy`?`you proved you understand it`:`understood`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-dot legend-dot--partial`}),fe===`easy`?`Could not be read`:`Unchartable · syntax error`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-route`}),fe===`easy`?`Certain connection`:`Parser edge · certain`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-route legend-route--possible legend-route--dashed`}),fe===`easy`?`Possible connection`:`Possible relationship`]}),P===`galaxy`&&F===jG.GALAXY||P===`map`&&de===`architecture`?(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`span`,{children:[(0,$.jsxs)(`span`,{className:`legend-communities`,"aria-hidden":`true`,children:[(0,$.jsx)(`i`,{}),(0,$.jsx)(`i`,{}),(0,$.jsx)(`i`,{}),(0,$.jsx)(`i`,{})]}),fe===`easy`?`Colour family · related systems and their shared routes`:`Hue family · one import community; internal routes inherit a tint`]}),oe.filter(e=>e.id!==`all`).map(e=>(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-tint`,style:{background:BG(e.id)??void 0}}),` `,e.label]},e.id))]}):null]}),P===`galaxy`&&F===jG.GALAXY?(0,$.jsxs)(`p`,{className:`orientation-bar`,children:[(0,$.jsxs)(`span`,{children:[j.regions.length,` `,ae===`all`?j.regions.length===1?`system`:`systems`:`${LG(ae)} ${j.regions.length===1?`system`:`systems`}`]}),(0,$.jsx)(`span`,{className:`orientation-bar__charted`,children:we?`all charted`:`${xe.size} charted`}),j.partial_files.length?(0,$.jsxs)(`span`,{className:`partial-summary`,children:[j.partial_files.length,` `,fe===`easy`?`could not be read`:`unchartable · syntax error`,QG(j.partial_files)?` · ${j.partial_files.length===1?`in`:`all under`} ${QG(j.partial_files)}/`:``]}):null,UG(j.unsupported_sources,fe)?(0,$.jsx)(`span`,{className:`unsupported-summary`,children:UG(j.unsupported_sources,fe)}):null]}):null,P===`galaxy`?qe:null,F===jG.SYSTEM&&Ee?(0,$.jsx)(ZY,{suite:C,error:w,mode:fe,overviewNoun:We,onClose:Ie,onSubmit:(e,t)=>y.dispatch({type:`SUBMIT_CHECK`,checkId:e,selectedIds:t})}):null,pe===!0&&D&&F===jG.GALAXY?(0,$.jsx)(XY,{candidates:M.entrypoint_candidates,nodes:M.nodes,selectedEntrypoint:M.selected_entrypoint,mode:fe,error:E,onSelect:e=>y.dispatch({type:`SELECT_ENTRYPOINT`,nodeId:e}),onContinue:()=>y.dispatch({type:`DISMISS_ENTRYPOINT`})}):null,ce?(0,$.jsxs)(`output`,{className:`illumination-pulse`,"aria-live":`polite`,children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`✦`}),(0,$.jsxs)(`strong`,{children:[ce,` understood`]})]}):null,F===jG.STUDY&&Ce?(0,$.jsx)(EJ,{node:Ce,study:ke,error:Ae,mode:fe,explanation:k,explanationLoading:ee,explanationError:A,llmStatus:le,revealSource:p,onSelectNode:e=>{m(!1),y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:e})},onRetryStudy:()=>y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:Ce.id}),onRetryNarration:()=>y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:Ce.id}),onClose:()=>{m(!1),y.dispatch({type:`RETREAT`})}}):null,pe===!0&&!D&&!T?(0,$.jsx)(qq,{layer:P,onDismiss:Ue}):null]}),te?(0,$.jsx)(GY,{index:me,onGo:ze,onClose:Re}):null,!Te&&!Ee&&pe===!0&&!D&&T?(0,$.jsx)(Wq,{hint:N,onFollow:Be,firstFlight:{available:je.length>0,active:I,stop:Me,index:h??0,total:je.length,mode:fe,triggerRef:s,onStart:()=>Ve(0),onBack:()=>Ve(h-1),onNext:()=>Ve(h+1),onExit:He}}):null,(0,$.jsxs)(`footer`,{className:`status-line`,children:[(0,$.jsx)(`span`,{children:Te?`${S.length} concepts detected`:ae===`all`?`${M.nodes.length} nodes · ${M.edges.length} edges`:`${j.nodes.length}/${M.nodes.length} nodes · ${j.edges.length} focused edges`}),(0,$.jsx)(`span`,{children:Te?`${re} focused structures studied this session`:P===`map`?`Click a box or row to study · Switch tabs to change view`:`Drag to orbit · Scroll to zoom · Click to move closer · Escape to move back`}),(0,$.jsx)(`span`,{children:`Local only`})]})]})}var IY=Object.fromEntries(oY.map(({id:e,copy:t})=>[e,t])),LY=oY.map(({id:e})=>e);function RY(e,{firstFlightOpen:t=!1}={}){return{ready:e.status===`ready`,canRetreat:e.layer===`map`&&e.level!==jG.GALAXY,showChart:e.showChart,finderOpen:e.finderOpen,sidebarOpen:e.sidebarOpen,showChecks:e.showChecks,entrypointOpen:e.entrypointOpen,firstFlightOpen:t,nativeDialogOpen:document.querySelector(`dialog[open]`)!==null,railDisclosureOpen:document.querySelector(`.rail-overflow[data-open]`)!==null,editableFocus:zY(document.activeElement)}}function zY(e){if(!e)return!1;let t=e.tagName;return t===`INPUT`||t===`TEXTAREA`||t===`SELECT`||e.isContentEditable===!0}function BY({progress:e,onCancel:t}){let{stage:n,detail:r,files_done:i,files_total:a,pollError:o,pollOutage:s,attempts:c,path:l}=e,[u,d]=(0,_.useState)(!1),[f,p]=(0,_.useState)(``),m=(0,_.useRef)(null),h=LY.indexOf(n);(0,_.useEffect)(()=>{m.current?.focus()},[]);async function g(){d(!0),p(``);try{await t()}catch(e){p(e.message),d(!1)}}return(0,$.jsxs)(`main`,{className:`loading-screen`,children:[(0,$.jsxs)(`header`,{className:`loading-header`,children:[(0,$.jsx)(`p`,{className:`picker-wordmark`,children:`Codemble`}),(0,$.jsxs)(`h1`,{ref:m,tabIndex:-1,children:[`Mapping `,(0,$.jsx)(`span`,{className:`loading-path`,children:l})]}),(0,$.jsx)(`p`,{className:`loading-subtitle`,children:`Parsing runs on your machine. Nothing is sent anywhere.`})]}),(0,$.jsx)(`ol`,{className:`loading-stages`,"aria-label":`Parse stages`,children:LY.map((e,t)=>{let n=t{let e=m.current;l?p.current?.focus():e&&f.current?.focus(),m.current=!!l},[l]),(0,$.jsxs)(`main`,{className:`picker-screen`,"aria-busy":d,children:[(0,$.jsxs)(`header`,{className:`picker-header`,children:[(0,$.jsx)(`p`,{className:`picker-wordmark`,children:`Codemble`}),(0,$.jsx)(`h1`,{children:`Choose the project to chart`}),(0,$.jsx)(`p`,{className:`picker-subtitle`,children:`Codemble reads the folder locally and turns it into a galaxy. Nothing leaves this machine.`})]}),s.length?(0,$.jsxs)(`section`,{className:`picker-recents`,"aria-labelledby":`picker-recents-heading`,children:[(0,$.jsx)(`h2`,{id:`picker-recents-heading`,children:`Continue where you left off`}),(0,$.jsx)(`ul`,{children:s.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,disabled:d,onClick:()=>r(e.project_root),children:[(0,$.jsx)(`span`,{className:`picker-recent-path`,children:e.project_root}),(0,$.jsxs)(`span`,{className:`picker-recent-lit`,children:[e.understood_count,` `,e.understood_count===1?`system`:`systems`,` lit last visit`]})]})},e.project_root))})]}):null,u?(0,$.jsx)(HY,{scale:u,busy:d,onBrowse:n}):null,t?(0,$.jsx)(WY,{failure:t,busy:d,onSelect:r}):c?(0,$.jsxs)(`div`,{className:`picker-error`,children:[(0,$.jsx)(`p`,{role:`alert`,children:c}),l?(0,$.jsx)(`button`,{ref:p,className:`picker-retry`,type:`button`,disabled:d,onClick:()=>n(l),children:`Try this folder again`}):null]}):null,(0,$.jsxs)(`section`,{className:`picker-browser`,"aria-labelledby":`picker-browser-heading`,children:[(0,$.jsx)(`h2`,{ref:f,id:`picker-browser-heading`,tabIndex:-1,children:`Browse folders`}),(0,$.jsx)(`p`,{className:`picker-path`,children:i}),(0,$.jsxs)(`ul`,{children:[a?(0,$.jsx)(`li`,{children:(0,$.jsx)(`button`,{type:`button`,disabled:d,onClick:()=>n(a),children:`↑ Up`})}):null,o.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,disabled:d,onClick:()=>n(e.path),children:[e.name,`/`]})},e.path))]}),(0,$.jsx)(`button`,{className:`picker-select`,type:`button`,disabled:d,onClick:()=>r(i),children:d?`Mapping…`:`Map this folder`})]})]})}function HY({scale:e,busy:t,onBrowse:n}){let r=e.suggestions.filter(e=>e.path!==`.`);return(0,$.jsxs)(`section`,{className:`picker-scale`,role:`alert`,"aria-labelledby":`picker-scale-heading`,children:[(0,$.jsx)(`h2`,{id:`picker-scale-heading`,children:`That folder is too big to map at once.`}),(0,$.jsxs)(`p`,{children:[`It has `,e.file_count,` supported source files; Codemble maps up to`,` `,e.scale_cap,`. Choose a smaller scope — busiest first.`]}),r.length?(0,$.jsx)(`ul`,{className:`picker-scale-scopes`,children:r.map(r=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,disabled:t,onClick:()=>n(`${e.root}/${r.path}`),children:[(0,$.jsxs)(`span`,{children:[r.path,`/`]}),(0,$.jsxs)(`small`,{children:[r.file_count,` files`]})]})},r.path))}):null,(0,$.jsx)(UY,{busy:t,onBrowse:n})]})}function UY({busy:e,onBrowse:t}){let[n,r]=(0,_.useState)(``);return(0,$.jsxs)(`form`,{className:`picker-path-entry`,onSubmit:e=>{e.preventDefault();let r=n.trim();r&&t(r)},children:[(0,$.jsx)(`label`,{htmlFor:`picker-path-input`,children:`Or type a folder path`}),(0,$.jsx)(`input`,{id:`picker-path-input`,name:`project-path`,type:`text`,value:n,disabled:e,autoComplete:`off`,spellCheck:!1,placeholder:`/Users/you/project/src`,onChange:e=>r(e.target.value)}),(0,$.jsx)(`button`,{type:`submit`,disabled:e||!n.trim(),children:`Go`})]})}function WY({failure:e,busy:t,onSelect:n}){return(0,$.jsxs)(`section`,{className:`picker-failure`,role:`alert`,children:[(0,$.jsx)(`h2`,{children:`Codemble could not map that folder.`}),(0,$.jsx)(`p`,{children:`Nothing on disk changed, and no project's saved progress was touched. Try it again, or choose a different folder below.`}),(0,$.jsxs)(`p`,{className:`picker-failure__detail`,children:[(0,$.jsx)(`span`,{children:`What the parser reported`}),(0,$.jsx)(`code`,{children:e.detail})]}),(0,$.jsxs)(`button`,{className:`picker-select`,type:`button`,disabled:t,onClick:()=>n(e.path),children:[`Try `,(0,$.jsx)(`span`,{className:`picker-recent-path`,children:e.path}),` again`]})]})}function GY({index:e,onGo:t,onClose:n}){let[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(0),s=(0,_.useRef)(null),c=(0,_.useRef)(null),l=(0,_.useMemo)(()=>{let t=r.trim().toLowerCase();return t?e.filter(e=>e.label.toLowerCase().includes(t)||e.file.toLowerCase().includes(t)).slice(0,60):[...e].sort((e,t)=>Number(t.home)-Number(e.home)||Number(t.understood)-Number(e.understood)||t.centrality-e.centrality||e.label.localeCompare(t.label)||e.id.localeCompare(t.id)).slice(0,60)},[e,r]);(0,_.useLayoutEffect)(()=>{let e=s.current;e&&!e.open&&e.showModal(),c.current?.focus()},[]),(0,_.useEffect)(()=>o(0),[r]);let u=l[Math.min(a,l.length-1)]??null;function d(e){e.key===`ArrowDown`?(e.preventDefault(),o(e=>Math.min(e+1,l.length-1))):e.key===`ArrowUp`?(e.preventDefault(),o(e=>Math.max(e-1,0))):e.key===`Enter`&&u?(e.preventDefault(),t(u.id)):e.key===`Escape`&&(e.preventDefault(),e.stopPropagation(),n())}return(0,$.jsxs)(`dialog`,{ref:s,className:`module-finder`,"aria-label":`Find a module`,onCancel:e=>{e.preventDefault(),n()},onClose:n,children:[(0,$.jsx)(`input`,{ref:c,name:`module-search`,type:`search`,value:r,placeholder:`Find a module…`,"aria-label":`Find a module by name or path`,autoComplete:`off`,spellCheck:!1,onChange:e=>i(e.target.value),onKeyDown:d}),(0,$.jsx)(`ul`,{role:`listbox`,"aria-label":`Matching modules`,children:l.map((e,n)=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,role:`option`,"aria-selected":n===a,"data-active":n===a||void 0,onMouseEnter:()=>o(n),onClick:()=>t(e.id),children:[(0,$.jsx)(`strong`,{children:e.label}),e.file===e.label?e.hops===0?null:(0,$.jsx)(`small`,{children:typeof e.hops==`number`?`${e.hops} ${e.hops===1?`route`:`routes`} from Home`:`no route from Home`}):(0,$.jsx)(`small`,{children:e.file}),e.home?(0,$.jsx)(`em`,{children:`Home`}):e.understood?(0,$.jsx)(`em`,{children:`lit`}):null]})},e.id))}),l.length?null:(0,$.jsx)(`p`,{className:`module-finder__empty`,children:`No module matches that.`})]})}function KY({index:e,currentRegionId:t,onGo:n,onClose:r}){let i=(0,_.useMemo)(()=>YG(e),[e]),a=(0,_.useRef)(null);return(0,_.useLayoutEffect)(()=>{a.current?.focus()},[]),(0,$.jsxs)(`aside`,{className:`index-sidebar`,"aria-label":`Project index`,children:[(0,$.jsxs)(`header`,{children:[(0,$.jsx)(`h2`,{children:`Modules`}),(0,$.jsx)(`button`,{ref:a,type:`button`,onClick:r,"aria-label":`Close the project index`,children:`×`})]}),(0,$.jsx)(`div`,{className:`index-sidebar__scroll`,children:i.map(e=>(0,$.jsxs)(`section`,{children:[(0,$.jsx)(`h3`,{title:e.name,children:e.name}),(0,$.jsx)(`ul`,{children:e.members.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-current":e.id===t?`true`:void 0,"data-lit":e.understood||void 0,title:e.file,onClick:()=>n(e.id),children:[(0,$.jsx)(`span`,{children:e.display??e.label}),e.home?(0,$.jsx)(`em`,{children:`Home`}):null]})},e.id))})]},e.community))})]})}function qY({layer:e,mode:t,onChange:n}){return(0,$.jsx)(`nav`,{className:`layer-switcher`,"aria-label":`View layer`,children:[{id:`galaxy`,label:`Galaxy`},{id:`map`,label:t===`easy`?`Diagram`:`Map`}].map(t=>(0,$.jsx)(`button`,{type:`button`,"aria-pressed":e===t.id,onClick:()=>n(t.id),children:t.label},t.id))})}function JY({options:e,value:t,onChange:n}){return e.length<=2?null:(0,$.jsxs)(`nav`,{className:`language-focus`,"aria-label":`Language focus`,children:[(0,$.jsx)(`span`,{className:`language-focus__label`,children:`Focus`}),(0,$.jsx)(`div`,{children:e.map(e=>(0,$.jsxs)(`button`,{type:`button`,"aria-label":`Focus ${e.label}: ${e.count} ${e.count===1?`system`:`systems`}`,"aria-pressed":t===e.id,title:e.label,onClick:()=>n(e.id),children:[(0,$.jsx)(`span`,{children:e.shortLabel}),(0,$.jsx)(`small`,{children:e.count})]},e.id))})]})}function YY({onConfirm:e}){let[t,n]=(0,_.useState)(!1),[r,i]=(0,_.useState)(!1),[a,o]=(0,_.useState)(``),s=(0,_.useRef)(null),c=(0,_.useRef)(null),l=(0,_.useRef)(!1);(0,_.useEffect)(()=>{t?c.current?.focus():l.current&&s.current?.focus(),l.current=t},[t]);function u(){n(!1),o(``)}async function d(){i(!0),o(``);try{await e()}catch(e){o(e.message),i(!1)}}return(0,$.jsx)(`div`,{className:`switch-project`,"data-confirming":t||void 0,role:t?`group`:void 0,"aria-label":t?`Switch project`:void 0,tabIndex:t?-1:void 0,ref:t?c:void 0,onKeyDown:e=>{e.key===`Escape`&&t&&!r&&u()},children:t?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`p`,{children:`Progress is saved per project, so this galaxy comes back lit.`}),a?(0,$.jsx)(`p`,{className:`switch-project__error`,role:`alert`,children:a}):null,(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,disabled:r,onClick:d,children:r?`Releasing…`:`Switch`}),(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,disabled:r,onClick:u,children:`Cancel`})]})]}):(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,ref:s,onClick:()=>n(!0),children:`Switch project`})})}function XY({candidates:e,nodes:t,selectedEntrypoint:n,mode:r,error:i,onSelect:a,onContinue:o}){let s=new Map(t.map(e=>[e.id,e])),c=(0,_.useMemo)(()=>{let t=new Map;for(let n of e){let e=s.get(n),r=(e?.file??n).split(`/`).filter(Boolean),i=r.length>1?`${r[0]}/`:`project root`;t.has(i)||t.set(i,[]),t.get(i).push({candidate:n,node:e})}return[...t].map(([e,t])=>({name:e,rows:t,bestRank:Math.min(...t.map(e=>e.node?.entrypoint_rank??2**53-1))})).sort((e,t)=>e.bestRank-t.bestRank||e.name.localeCompare(t.name)).map((e,t)=>({...e,open:t===0||e.bestRank===0}))},[e,t]),l=c[0]?.rows[0]?.candidate??null,u=(0,_.useRef)(null),d=(0,_.useRef)(null);return(0,_.useLayoutEffect)(()=>{let e=d.current;e&&!e.open&&e.showModal(),u.current?.focus()},[]),(0,$.jsxs)(`dialog`,{ref:d,className:`entrypoint-picker`,"aria-labelledby":`entrypoint-heading`,onCancel:e=>e.preventDefault(),children:[(0,$.jsx)(`p`,{children:`Home calibration`}),(0,$.jsx)(`h1`,{id:`entrypoint-heading`,children:e.length?`Where does your project start?`:`No clear entrypoint found.`}),(0,$.jsx)(`p`,{children:e.length?r===`easy`?`Your project could start from more than one place, and Codemble will not guess. Pick the one you actually run — best guess first.`:`The parser found ranked candidates but cannot choose one honestly. Select the structure you run.`:`No file here declares a startup structure the parser recognises, and Codemble will not guess one. Explore the map without Home — every system, check, explanation, and lens note still works.`}),e.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`p`,{className:`entrypoint-count`,children:[e.length,` ranked`,` `,e.length===1?`candidate`:`candidates`,` in `,c.length,` `,c.length===1?`scope`:`scopes`]}),(0,$.jsx)(`div`,{className:`entrypoint-scroll`,children:c.map(e=>(0,$.jsxs)(`details`,{open:e.open,children:[(0,$.jsxs)(`summary`,{children:[(0,$.jsx)(`span`,{children:e.name}),(0,$.jsxs)(`small`,{children:[e.rows.length,` `,e.rows.length===1?`candidate`:`candidates`]})]}),(0,$.jsx)(`div`,{className:`entrypoint-candidates`,children:e.rows.map(({candidate:e,node:t})=>(0,$.jsxs)(`button`,{ref:e===l?u:void 0,type:`button`,onClick:()=>a(e),children:[(0,$.jsx)(`span`,{children:e}),(0,$.jsxs)(`small`,{children:[t?.file,`:`,t?.lineno,` ·`,` `,r===`easy`?`candidate ${(t?.entrypoint_rank??0)+1}`:`parser rank ${t?.entrypoint_rank}`]})]},e))})]},e.name))})]}):null,i?(0,$.jsx)(`p`,{className:`entrypoint-error`,role:`alert`,children:i}):null,(0,$.jsx)(`button`,{ref:e.length?void 0:u,className:`entrypoint-continue`,type:`button`,onClick:o,children:n?`Keep current Home`:`Explore without Home`})]})}function ZY({suite:e,error:t,mode:n,overviewNoun:r,onClose:i,onSubmit:a}){let o=e?.checks.find(e=>!e.passed)??null,s=e?.checks.filter(e=>e.passed).length??0,[c,l]=(0,_.useState)(()=>new Set),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(!1),[g,v]=(0,_.useState)(``),y=(0,_.useRef)(null),b=(0,_.useRef)(null),x=(0,_.useRef)(null),S=(0,_.useRef)(null),C=(0,_.useRef)(null),w=(0,_.useRef)(null),T=(0,_.useRef)(null);(0,_.useEffect)(()=>{l(new Set),d(null),v(``)},[o?.id]),(0,_.useLayoutEffect)(()=>{y.current?.focus()},[]),(0,_.useLayoutEffect)(()=>{e?.region_understood?S.current?.focus():u?b.current?.focus():f&&x.current?.focus()},[e?.region_understood,u,f]),(0,_.useLayoutEffect)(()=>{u&&C.current?.scrollIntoView({block:`nearest`})},[u]),(0,_.useLayoutEffect)(()=>{if(!o)return;let e=w.current,t=T.current;if(!e||!t)return;let n=!1,r=0,i=()=>{if(n||!window.matchMedia(`(max-width: 39.999rem)`).matches&&e.scrollHeight<=e.clientHeight+1)return;let r=t.getBoundingClientRect().top-e.getBoundingClientRect().top;e.scrollTop+=r};return i(),r=window.requestAnimationFrame(i),document.fonts?.ready.then(()=>{n||(window.cancelAnimationFrame(r),r=window.requestAnimationFrame(i))}),()=>{n=!0,window.cancelAnimationFrame(r)}},[o?.id]);function E(e,t){p(``),l(n=>{if(!t)return new Set([e]);let r=new Set(n);return r.has(e)?r.delete(e):r.add(e),r})}async function D(e){if(e.preventDefault(),!(!o||c.size===0)){h(!0),v(``);try{let e=await a(o.id,[...c]);e.correct?(p(e.message),d(null)):(p(``),d(e))}catch(e){v(e.message)}finally{h(!1)}}}return(0,$.jsxs)(`aside`,{ref:w,className:`check-panel`,"aria-label":`Graph-derived understanding checks`,children:[(0,$.jsxs)(`header`,{className:`check-panel__header`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`p`,{children:n===`easy`?`Quiz · answers come from your code, not AI`:`Active recall · graph only`}),(0,$.jsx)(`h1`,{ref:y,tabIndex:-1,children:e?.region_id??`Loading checks`})]}),(0,$.jsx)(`button`,{className:`check-close`,type:`button`,onClick:i,children:`Close`})]}),t||g?(0,$.jsxs)(`div`,{className:`check-state`,role:`alert`,children:[(0,$.jsx)(`h2`,{children:`The checks did not load.`}),(0,$.jsxs)(`p`,{children:[t||g,` The galaxy remains available.`]})]}):null,!e&&!t?(0,$.jsx)(`p`,{className:`check-loading`,role:`status`,children:`Deriving answers from parser edges…`}):null,e?.region_understood?(0,$.jsxs)(`div`,{className:`check-complete`,"aria-live":`polite`,children:[(0,$.jsx)(`span`,{className:`check-complete__star`,"aria-hidden":`true`,children:`✦`}),(0,$.jsx)(`h2`,{children:r===`map`?`Module lit.`:`System lit.`}),(0,$.jsx)(`p`,{children:n===`easy`?`Codemble remembers the exact code you proved. Edit this file and only this ${r===`map`?`module`:`system`} dims again.`:`This region's source hash matches the checks you passed. Edit its file and only this system will dim again.`}),(0,$.jsxs)(`button`,{ref:S,className:`check-primary`,type:`button`,onClick:i,children:[`Back to the `,r===`map`?`module`:`system`]})]}):null,e&&!e.region_understood&&e.checks.length===0?(0,$.jsxs)(`div`,{className:`check-state`,children:[(0,$.jsx)(`h2`,{children:`No safe check yet.`}),(0,$.jsx)(`p`,{children:`Every question here is answered by the parser graph, and this region has no certain relationship Codemble can build one from. It stays dim rather than lighting on a question that would prove nothing. Import this module somewhere, or call something inside it, and its checks appear.`})]}):null,e&&!e.region_understood&&e.checks.length>0&&!o?(0,$.jsxs)(`div`,{className:`check-state`,children:[(0,$.jsx)(`h2`,{children:`These checks are out of date.`}),(0,$.jsx)(`p`,{children:`This module is dim again, but this panel is still holding the answers from before. Reopen it to get the questions back — nothing you proved has been lost anywhere else.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:i,children:`Reopen the checks`})]}):null,o&&!e.region_understood?(0,$.jsxs)(`form`,{className:`active-check`,onSubmit:D,children:[(0,$.jsxs)(`div`,{className:`check-progress`,children:[(0,$.jsxs)(`span`,{children:[`Check `,s+1,` of `,e.checks.length]}),(0,$.jsx)(`progress`,{value:s,max:e.checks.length})]}),f?(0,$.jsxs)(`p`,{ref:x,className:`check-affirmation`,role:`status`,tabIndex:-1,children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`✦`}),` `,f]}):null,(0,$.jsxs)(`fieldset`,{ref:T,children:[(0,$.jsx)(`legend`,{children:o.prompt_voices[n]}),o.multiple?(0,$.jsx)(`p`,{children:`Select every answer supported by the graph.`}):null,(0,$.jsx)(`div`,{className:`check-options`,children:o.options.map(e=>(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`input`,{type:o.multiple?`checkbox`:`radio`,name:`answer-${o.id}`,value:e.id,checked:c.has(e.id),onChange:()=>E(e.id,o.multiple)}),(0,$.jsx)(`span`,{children:e.label})]},e.id))})]}),u&&!u.correct?(0,$.jsx)(`div`,{ref:b,className:`check-feedback`,role:`status`,tabIndex:-1,children:(0,$.jsx)(`strong`,{children:u.message})}):null,(0,$.jsx)(`div`,{className:`check-submit-bar`,children:(0,$.jsx)(`button`,{ref:C,className:`check-primary`,type:`submit`,disabled:!c.size||m,children:m?`Checking parser evidence…`:`Check answer`})})]}):null]})}function QY({overview:e,mode:t,charted:n}){let r=t===`easy`,i=e.languages.map(e=>`${e.label} ${e.count}`).join(` · `);return(0,$.jsxs)(`section`,{className:`project-summary`,"aria-label":`Project summary`,children:[(0,$.jsxs)(`p`,{className:`project-summary__lead`,children:[r?`${e.modules} files of code, holding ${e.structures} functions and classes between them.`:`${e.modules} modules · ${e.structures} structures · ${e.lines} lines.`,` `,e.home?r?`It starts at ${e.home.id}.`:`Entrypoint ${e.home.id}.`:r?`Codemble could not tell which file starts it.`:`No entrypoint resolved.`]}),(0,$.jsxs)(`dl`,{className:`project-summary__facts`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Written in`:`Languages`}),(0,$.jsx)(`dd`,{children:i||`—`})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Biggest files`:`Largest by LOC`}),(0,$.jsx)(`dd`,{children:e.biggest.length?e.biggest.map(e=>`${KG(e.id)} (${e.value})`).join(`, `):`—`})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Called from the most places`:`Highest centrality`}),(0,$.jsx)(`dd`,{children:e.busiest.length?e.busiest.map(e=>`${KG(e.id)} (${e.value})`).join(`, `):`—`})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`You have proved`:`Understood`}),(0,$.jsxs)(`dd`,{children:[e.understood,` of `,e.modules,` `,r?e.modules===1?`file`:`files`:`modules`]})]}),e.unreadable||e.unsupported?(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Not included`:`Outside the graph`}),(0,$.jsx)(`dd`,{children:[e.unreadable?`${e.unreadable} ${e.unreadable===1?`file`:`files`} could not be read`:``,e.unsupported?`${e.unsupported} in a language Codemble does not parse`:``].filter(Boolean).join(` · `)})]}):null,e.importCycles.length?(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Files that bring each other in`:`Proven import cycles`}),(0,$.jsxs)(`dd`,{children:[e.importCycles.length,` `,e.importCycles.length===1?`cycle group`:`cycle groups`,` · `,e.importCycles.slice().sort((e,t)=>t.length-e.length||e.join(`\0`).localeCompare(t.join(`\0`)))[0].join(`, `),` · each file can reach every other through proven imports`]})]}):null]}),(0,$.jsx)(`button`,{className:`project-summary__export`,type:`button`,onClick:()=>$Y(e,n),children:r?`Save a project guide`:`Export Markdown brief`})]})}function $Y(e,t){let n=PY(e,{charted:t}),r=URL.createObjectURL(new Blob([n],{type:`text/markdown;charset=utf-8`})),i=document.createElement(`a`);i.href=r,i.download=NY(e),document.body.append(i),i.click(),i.remove(),window.setTimeout(()=>URL.revokeObjectURL(r),0)}function eX({chart:e,studiedCount:t,exploredCount:n,projectName:r,mode:i,overview:a,onClearProgress:o}){let s=e.filter(e=>e.understood_nodes>0).length,c=(0,_.useRef)(null);return(0,_.useLayoutEffect)(()=>{c.current?.focus()},[]),(0,$.jsxs)(`section`,{className:`star-chart-screen`,"aria-labelledby":`star-chart-heading`,children:[(0,$.jsxs)(`header`,{className:`star-chart-intro`,children:[(0,$.jsx)(`p`,{children:r}),(0,$.jsx)(`h1`,{ref:c,id:`star-chart-heading`,tabIndex:-1,children:`What this project is.`}),a?(0,$.jsx)(QY,{overview:a,mode:i,charted:n}):null,(0,$.jsx)(`h2`,{className:`star-chart-section`,children:i===`easy`?`Ideas your code uses`:`Parser-detected concepts`}),(0,$.jsx)(`p`,{children:`Explored counts the systems you have flown to. Encountered comes from real syntax. Studied tracks this session. Understood lights only after graph-derived checks pass.`}),(0,$.jsxs)(`dl`,{children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Systems explored`}),(0,$.jsx)(`dd`,{children:n})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Concepts encountered`}),(0,$.jsx)(`dd`,{children:e.length})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Studied this session`}),(0,$.jsx)(`dd`,{children:t})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Concepts understood`}),(0,$.jsx)(`dd`,{children:s})]})]})]}),(0,$.jsx)(`div`,{className:`concept-ledger`,role:`list`,"aria-label":`Language concept progress`,children:e.map(e=>(0,$.jsxs)(`article`,{className:`concept-row`,role:`listitem`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`h2`,{children:IG(e.concept)}),(0,$.jsxs)(`span`,{children:[IG(e.language),` · `,e.occurrences,` parser `,e.occurrences===1?`occurrence`:`occurrences`]})]}),(0,$.jsx)(`div`,{className:`concept-meter`,"aria-label":`${e.understood_nodes} of ${e.nodes} ${e.nodes===1?`structure`:`structures`} understood`,children:(0,$.jsx)(`span`,{style:{width:`${e.nodes?e.understood_nodes/e.nodes*100:0}%`}})}),(0,$.jsxs)(`dl`,{children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Studied (session)`}),(0,$.jsxs)(`dd`,{children:[e.studied_nodes,`/`,e.nodes]})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Understood`}),(0,$.jsxs)(`dd`,{children:[e.understood_nodes,`/`,e.nodes]})]})]})]},`${e.language}:${e.concept}`))}),(0,$.jsx)(tX,{projectName:r,onConfirm:o})]})}function tX({projectName:e,onConfirm:t}){let[n,r]=(0,_.useState)(!1),[i,a]=(0,_.useState)(!1),[o,s]=(0,_.useState)(``),c=(0,_.useRef)(null),l=(0,_.useRef)(null),u=(0,_.useRef)(!1);(0,_.useEffect)(()=>{n?l.current?.focus():u.current&&c.current?.focus(),u.current=n},[n]);function d(){r(!1),s(``)}async function f(){a(!0),s(``);try{await t(),r(!1)}catch(e){s(e.message)}finally{a(!1)}}return n?(0,$.jsxs)(`section`,{className:`progress-reset progress-reset--confirming`,role:`group`,"aria-label":`Clear this project's progress`,tabIndex:-1,ref:l,onKeyDown:e=>{e.key===`Escape`&&!i&&d()},children:[(0,$.jsxs)(`p`,{role:`alert`,children:[`This dims every system you lit in `,e,` and cannot be undone. Your other projects keep their progress, and no source file is touched.`]}),o?(0,$.jsx)(`p`,{className:`progress-reset__error`,role:`alert`,children:o}):null,(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`button`,{className:`check-primary progress-reset__confirm`,type:`button`,disabled:i,onClick:f,children:i?`Clearing…`:`Yes, clear ${e}`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,disabled:i,onClick:d,children:`Keep it`})]})]}):(0,$.jsxs)(`section`,{className:`progress-reset`,children:[(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,ref:c,onClick:()=>r(!0),children:`Clear this project's progress`}),(0,$.jsxs)(`p`,{children:[`Start `,e,` over with every system dim again.`]})]})}var nX=class extends _.Component{constructor(e){super(e),this.state={message:``}}static getDerivedStateFromError(e){return{message:e instanceof Error?e.message:String(e)}}componentDidCatch(e,t){console.error(`Codemble stopped rendering:`,e,t?.componentStack)}render(){return this.state.message?(0,$.jsxs)(`main`,{className:`load-state`,role:`alert`,children:[(0,$.jsx)(`h1`,{children:`The galaxy stopped rendering.`}),(0,$.jsx)(`p`,{children:this.state.message}),(0,$.jsx)(`p`,{children:`Your progress is saved on this machine; reloading re-reads it.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:()=>window.location.reload(),children:`Reload Codemble`})]}):this.props.children}};(0,v.createRoot)(document.getElementById(`root`)).render((0,$.jsx)(_.StrictMode,{children:(0,$.jsx)(nX,{children:(0,$.jsx)(FY,{})})})); \ No newline at end of file +`;Object.freeze({vertex:$W,fragment:eG});function tG(e){let t=2166136261,n=String(e??``);for(let e=0;e>>0)/4294967296}function nG(e=32){return new yo(1,e,Math.max(8,e/2))}function rG({node:e,color:t,palette:n}){return new jo({vertexShader:$W,fragmentShader:eG,uniforms:{uBase:{value:new H(t)},uAmber:{value:new H(n.star)},uSeed:{value:tG(e.id)},uLit:{value:+!!e.understood},uPartial:{value:+!!e.partial},uClass:{value:+(e.kind===`class`)},uDim:{value:+!!e.focusDim}}})}function iG({node:e,color:t,palette:n,radius:r,geometry:i}){let a=new na(i,rG({node:e,color:t,palette:n}));return a.scale.setScalar(r),a.rotation.set(tG(`${e.id}:tilt`)*.9-.45,tG(e.id)*Math.PI*2,0),a.userData.codembleBody=!0,a}function aG(e,{reducedMotion:t=!1}={}){if(t)return()=>{};let n=0,r=performance.now(),i=()=>{let t=performance.now(),a=Math.min(.05,(t-r)/1e3);r=t,e.traverse(e=>{e.userData?.codembleBody&&(e.rotation.y+=a*.06)}),n=requestAnimationFrame(i)};return n=requestAnimationFrame(i),()=>cancelAnimationFrame(n)}var oG=`.orientation-copy button, .legend-toggle`,sG=`.orientation-bar, .keyboard-focus`;function cG({host:e,renderer:t,getStyle:n=e=>getComputedStyle(e)}){let r=e?.getBoundingClientRect?.(),i=lG(r,t),a=e?.closest?.(`.map-stage`);return Object.freeze(!a||!r?{viewport:i,clickObstructions:Object.freeze([]),nameObstructions:Object.freeze([])}:{viewport:i,clickObstructions:uG(a,oG,r,{accepts:e=>n(e).pointerEvents!==`none`}),nameObstructions:uG(a,sG,r)})}function lG(e,t){if(e?.width&&e?.height)return Object.freeze({width:e.width,height:e.height});let n=t?.width?.(),r=t?.height?.();return n&&r?Object.freeze({width:n,height:r}):null}function uG(e,t,n,{accepts:r=()=>!0}={}){let i=[];for(let a of e.querySelectorAll(t)){if(!r(a))continue;let e=a.getBoundingClientRect();!e.width||!e.height||i.push(Object.freeze({left:e.left-n.left,right:e.right-n.left,top:e.top-n.top,bottom:e.bottom-n.top}))}return Object.freeze(i)}var dG=1600,fG=.22,pG=.52,mG=.74,hG=3,gG=7;function _G(e){let t=Math.min(1,Math.max(0,e)),n=(e,n)=>Math.min(1,Math.max(0,(t-e)/(n-e)));return Object.freeze({ignite:n(0,fG),travel:n(fG,pG),flare:n(pG,mG),settle:n(mG,1)})}function vG(e,t,n=new Map){let r=[];for(let n of t??[]){if(!n?.certain)continue;let t=n.dst===e?n.src:n.src===e?n.dst:null;t===null||t===e||r.push(t)}let i=[...new Set(r)];return i.sort((e,t)=>{let r=n.get(e),i=n.get(t);return(typeof r==`number`?r:2**53-1)-(typeof i==`number`?i:2**53-1)||e.localeCompare(t)}),i.slice(0,hG)}function yG({scene:e,regionId:t,palette:n,dressing:r,routes:i=[],hopsById:a=new Map}){if(Bd())return()=>{};let o=e.getObjectByName(`codemble-system-${t}`);if(!o)return()=>{};let s=[];o.traverse(e=>{e.isSprite&&s.push([e,e.material.opacity,e.scale.clone()])});let c=s.map(([e])=>e.material.color.clone()),l=new H(n.star),u=new V;o.getWorldPosition(u);let d=[];if(r?.spark)for(let n of vG(t,i,a)){let t=e.getObjectByName(`codemble-system-${n}`);if(!t)continue;let i=new V;t.getWorldPosition(i);let a=r.spark();a.scale.setScalar(gG),a.position.copy(i),e.add(a),d.push({sprite:a,origin:i})}let f=()=>{s.forEach(([e,t,n],r)=>{e.material.color.copy(c[r]),e.material.opacity=t,e.scale.copy(n)});for(let{sprite:t}of d)e.remove(t),t.material.opacity=0;d.length=0},p=0,m=performance.now(),h=()=>{let e=Math.min(1,(performance.now()-m)/dG),t=_G(e),n=1-(1-t.travel)**3;for(let{sprite:e,origin:r}of d)e.position.lerpVectors(r,u,n),e.material.opacity=t.flare>0?0:Math.min(1,t.ignite)*.9;let r=Math.sin(Math.min(1,t.flare)*Math.PI*.5)*(1-t.settle);if(s.forEach(([e,t,n],i)=>{e.material.color.copy(c[i]).lerp(l,r*.85),e.material.opacity=t+r*.5,e.scale.copy(n).multiplyScalar(1+r*.45)}),e<1){p=requestAnimationFrame(h);return}f()};return p=requestAnimationFrame(h),()=>{cancelAnimationFrame(p),f()}}function bG(e){if(!e?.selected_entrypoint)return[];let t=new Map((e.nodes??[]).map(e=>[e.id,e])),n=new Map((e.regions??[]).map(e=>[e.id,e])),r=t.get(e.selected_entrypoint),i=r?n.get(r.region):null;if(!i?.home)return[];let a=new Map;for(let r of e.edges??[]){if(r.kind!==`import`||r.external||r.certain!==!0)continue;let e=t.get(r.src)?.region,i=t.get(r.dst)?.region;!e||!i||e===i||!n.has(e)||!n.has(i)||(a.has(e)||a.set(e,new Set),a.get(e).add(i))}let o=new Map,s=new Map;for(let t of e.region_edges??[])!n.has(t.src)||!n.has(t.dst)||t.src===t.dst||(o.has(t.src)||o.set(t.src,new Set),s.has(t.dst)||s.set(t.dst,new Set),o.get(t.src).add(t.dst),s.get(t.dst).add(t.src));return[i,...[...a.get(i.id)??[]].map(e=>n.get(e)).filter(Boolean).sort((e,t)=>(t.centrality??0)-(e.centrality??0)||String(e.id).localeCompare(String(t.id)))].slice(0,6).map(e=>({id:e.id,language:e.language,usedBy:s.get(e.id)?.size??0,uses:o.get(e.id)?.size??0,home:e.id===i.id}))}function xG(e,{active:t,reducedMotion:n}){return t&&n?0:e}var SG=(e,t)=>e.x*t.x+e.y*t.y+e.z*t.z,CG=(e,t)=>({x:e.y*t.z-e.z*t.y,y:e.z*t.x-e.x*t.z,z:e.x*t.y-e.y*t.x});function wG(e){if(!e)return null;let{x:t,y:n,z:r}=e;if(!Number.isFinite(t)||!Number.isFinite(n)||!Number.isFinite(r))return null;let i=Math.hypot(t,n,r);return i>0?{x:t/i,y:n/i,z:r/i}:null}function TG({points:e,direction:t,fov:n,aspect:r,margin:i=.1}){let a=wG(t);if(!a||!Array.isArray(e)||e.length===0||!Number.isFinite(n)||n<=0||n>=180||!Number.isFinite(r)||r<=0)return null;let o=1-Math.min(Math.max(i,0),.9),s=Math.tan(n*Math.PI/360)*o,c=s*r,l=wG(CG(Math.abs(a.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},a));if(!l)return null;let u=CG(a,l),d=0;for(let t of e){if(!t)continue;let{x:e,y:n,z:r}=t;if(!Number.isFinite(e)||!Number.isFinite(n)||!Number.isFinite(r))continue;let i=SG(t,a),o=Number.isFinite(t.radius)?Math.max(0,t.radius):0,f=(Math.abs(SG(t,l))+o)/c,p=(Math.abs(SG(t,u))+o)/s;d=Math.max(d,i+f,i+p)}return d>0?d:null}function EG(e,t){let n=wG(e);return!n||!Number.isFinite(t)?null:{x:n.x*t,y:n.y*t,z:n.z*t}}var DG=24,OG=30;function kG(e,t){if(!e.length)return 0;let n=n=>{let r=-1/0,i=1/0;for(let a=0;ar&&(r=o),o0?r=e:i=e}return(r+i)/2}function AG({points:e,direction:t,fov:n,aspect:r,margin:i=.1}){let a=wG(t);if(!a||!Array.isArray(e)||e.length===0)return null;let o=e.filter(e=>e&&Number.isFinite(e.x)&&Number.isFinite(e.y)&&Number.isFinite(e.z));if(!o.length)return null;let s=wG(CG(Math.abs(a.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},a));if(!s)return null;let c=CG(a,s),l=e=>{let t=1/0,n=-1/0;for(let r of o){let i=SG(r,e);in&&(n=i)}return(t+n)/2},u={x:s.x*l(s)+c.x*l(c)+a.x*l(a),y:s.y*l(s)+c.y*l(c)+a.y*l(a),z:s.z*l(s)+c.z*l(c)+a.z*l(a)},d=()=>o.map(e=>({x:e.x-u.x,y:e.y-u.y,z:e.z-u.z,radius:e.radius})),f=TG({points:d(),direction:t,fov:n,aspect:r,margin:i});if(f===null)return null;for(let e=0;ee.language===t),r=new Set(n.map(e=>e.id)),i=new Set(e.nodes.map(e=>e.id)),a=new Set(n.map(e=>e.region)),o=new Set(n.map(e=>e.file));return{...e,nodes:n,edges:e.edges.filter(e=>r.has(e.src)&&(r.has(e.dst)||e.external||!i.has(e.dst))),entrypoint_candidates:e.entrypoint_candidates.filter(e=>r.has(e)),selected_entrypoint:r.has(e.selected_entrypoint)?e.selected_entrypoint:null,file_hashes:Object.fromEntries(Object.entries(e.file_hashes).filter(([e])=>o.has(e))),concept_annotations:e.concept_annotations.filter(e=>e.language===t&&r.has(e.node_id)),role_evidence:(e.role_evidence??[]).filter(e=>r.has(e.node_id)),regions:e.regions.filter(e=>a.has(e.id)),region_edges:e.region_edges.filter(e=>a.has(e.src)&&a.has(e.dst)),partial_files:e.partial_files.filter(e=>o.has(e))}}function NG(e,t){if(!e||!t||t===`all`)return e;let n=e.architecture,r=n.boxes.filter(e=>e.language===t),i=new Set(r.map(e=>e.id)),a=e.workflow;return{...e,architecture:{...n,boxes:r,edges:n.edges.filter(e=>i.has(e.src)&&i.has(e.dst)),unreachable:n.unreachable.filter(e=>i.has(e))},workflow:{...a,nodes:a.nodes.filter(e=>e.language===t),unreachable:a.unreachable.filter(e=>e.language===t)}}}function PG(e){let t=new Map;for(let n of e.regions)t.set(n.language,(t.get(n.language)??0)+1);let n=[...t].map(([e,t])=>({id:e,label:LG(e),shortLabel:RG(e),count:t})).sort((e,t)=>e.label.localeCompare(t.label));return[{id:`all`,label:`All languages`,shortLabel:`All`,count:e.regions.length},...n]}function FG(e,t){let n=new Map(e.nodes.map(e=>[e.id,e])),r=new Map;for(let i of e.concept_annotations??[]){let e=`${i.language}:${i.concept}`,a=r.get(e)??{language:i.language,concept:i.concept,occurrences:0,nodeIds:new Set,studiedNodeIds:new Set,understoodNodeIds:new Set};a.occurrences+=1,a.nodeIds.add(i.node_id),t.has(i.node_id)&&a.studiedNodeIds.add(i.node_id),n.get(i.node_id)?.understood&&a.understoodNodeIds.add(i.node_id),r.set(e,a)}return[...r.values()].map(e=>({language:e.language,concept:e.concept,occurrences:e.occurrences,nodes:e.nodeIds.size,studied_nodes:e.studiedNodeIds.size,understood_nodes:e.understoodNodeIds.size})).sort((e,t)=>e.language.localeCompare(t.language)||e.concept.localeCompare(t.concept))}function IG(e){let t={javascript:`JavaScript`,jsx:`JSX`,typescript:`TypeScript`,csharp:`C#`,"linq-query":`LINQ Query`};return t[e]?t[e]:e.split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}function LG(e){return IG(e)}function RG(e){return e===`javascript`?`JS`:e===`typescript`?`TS`:LG(e)}var zG=Object.freeze({python:`--cm-neb-python`,javascript:`--cm-neb-js`,typescript:`--cm-neb-ts`,go:`--cm-neb-go`,java:`--cm-neb-java`,rust:`--cm-neb-rust`,csharp:`--cm-neb-csharp`});function BG(e){let t=zG[e];return t?`var(${t})`:null}var VG=2;function HG(e,{showAll:t=!1,selectionId:n=null,visitedIds:r=null}={}){let i=()=>new Set(e.regions.map(e=>e.id));if(t||!e.regions.some(e=>e.home))return i();let a=new Set,o=new Set;for(let t of e.regions)typeof t.hops_from_home==`number`&&t.hops_from_home<=VG&&a.add(t.id),t.understood&&o.add(t.id),r&&r.has(t.id)&&o.add(t.id);n&&o.add(n);for(let e of o)a.add(e);for(let t of e.region_edges)(o.has(t.src)||o.has(t.dst))&&(a.add(t.src),a.add(t.dst));return a}function UG(e,t){if(!e||!e.length)return null;let n=e.reduce((e,t)=>e+t.count,0);return t===`easy`?`${WG(e.map(e=>`${e.count} ${e.language??e.extension}`))} ${n===1?`file`:`files`} not included`:`${e.map(e=>`${e.count} ${e.extension}`).join(` · `)} not charted`}function WG(e){return e.length<=1?e.join(``):`${e.slice(0,-1).join(`, `)} and ${e[e.length-1]}`}function GG(e){let t=new Map;for(let n of e.nodes)t.has(n.region)||t.set(n.region,n.file);return t}function KG(e){let t=e.split(`/`).filter(Boolean);return t.length>1?t.slice(-2).join(`/`):t[0]??e}function qG(e){let t=GG(e);return e.regions.map(e=>({id:e.id,file:t.get(e.id)??e.id,label:KG(t.get(e.id)??e.id),language:e.language,community:e.community,understood:e.understood,home:e.home,hops:typeof e.hops_from_home==`number`?e.hops_from_home:null,centrality:e.centrality,loc:e.loc})).sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id))}function JG(e){if(!e.length)return``;let t=e.map(e=>e.file.split(`/`).slice(0,-1)),n=t[0];for(let e of t.slice(1)){let t=0;for(;t{let n=JG(t),r=n.endsWith(`/`)?n:``;return{community:e,name:n,members:t.map(e=>({...e,display:r&&e.file.startsWith(r)?e.file.slice(r.length):e.file})).sort((e,t)=>e.display.localeCompare(t.display))}}).sort((e,t)=>t.members.length-e.members.length||e.community-t.community)}var XG=new Set([`tests`,`test`,`__tests__`,`spec`,`specs`]);function ZG(e){return String(e).split(`/`).slice(0,-1).some(e=>XG.has(e))}function QG(e){if(!e?.length)return null;let t=e=>String(e).split(`/`).filter(Boolean)[0]??null,n=t(e[0]);return n&&e.every(e=>t(e)===n)?n:null}var $G=5,eK=2;function tK(e){return Number.isInteger(e)&&e>=0&&e<8?e:null}var nK=Object.freeze({bright:1,mid:.72,dim:.5}),rK=.32;function iK(e){let t=/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(String(e));return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function aK(e,t,n){let r=iK(e),i=iK(t);if(!r||!i)return e;let a=e=>Math.round(r[e]*n+i[e]*(1-n));return`rgb(${a(0)}, ${a(1)}, ${a(2)})`}function oK(e,t,n,r){let i=tK(t),a=i===null?null:e.communities?.[i];if(!a)return EK(n,e,r);let o=n>=r?`bright`:n>=1?`mid`:`dim`;return aK(a,e.ground,nK[o])}function sK(e,t){let n=tK(t),r=n===null?null:e.communities?.[n];return r?aK(r,e.route,rK):e.route}var cK=e=>e.charted===!0,lK=e=>e.charted===!1;function uK(e,t,n){let{activeId:r,neighborIds:i}=t??{};return r?e.id===r?n.orbit:i?.has(e.id)?e.color:n.faded:e.color}function dK(e,t,n,r){if(e.focusDim)return n.faded;let{activeId:i,neighborIds:a}=t??{},o=e.certain?e.color??n.route:n.routePossible;if(!i)return o;let s=r(e.source),c=r(e.target);return s===i||c===i?e.certain?n.orbit:n.routePossible:a?.has(s)&&a?.has(c)?o:n.faded}function fK(e,t,n=null){let r=e=>n===null||n.has(e),i=GG(e),a=new Map(e.regions.map(e=>[e.id,e])),o=vK(e.region_edges);return{nodes:e.regions.map(n=>{let a=r(n.id);return{...n,usedBy:o.usedBy.get(n.id)??0,uses:o.uses.get(n.id)??0,kind:`region`,name:n.id,label:KG(i.get(n.id)??n.id),charted:a,fx:n.x,fy:n.y,fz:n.z,val:xK(n.loc,5,24),color:n.understood?t.star:e.nodes.some(e=>e.region===n.id&&e.partial)?t.routePossible:oK(t,n.community_family,n.centrality,$G),focusDim:!1}}),links:e.region_edges.filter(e=>r(e.src)&&r(e.dst)).map(e=>{let n=a.get(e.src),r=a.get(e.dst),i=n?.community===r?.community&&n?.community_family===r?.community_family?tK(n?.community_family):null;return{...e,source:e.src,target:e.dst,color:e.certain?sK(t,i):t.routePossible,community_family:i,focusDim:!1}})}}function pK(e,t,n,{selectedId:r=null}={}){let i=e.nodes.filter(e=>e.region===t),a=new Set(i.map(e=>e.id)),o=e.regions.find(e=>e.id===t)?.community_family,s=e.edges.filter(e=>e.kind===`call`&&!e.external&&a.has(e.src)&&a.has(e.dst)),c=vK(e.edges.filter(e=>!e.external)),l=new Set(r?[r]:[]);if(r)for(let e of s)e.src===r&&l.add(e.dst),e.dst===r&&l.add(e.src);return{nodes:i.map(e=>({...e,usedBy:c.usedBy.get(e.id)??0,uses:c.uses.get(e.id)??0,fx:e.system_x,fy:e.system_y,fz:e.system_z,val:xK(e.loc,2.8,11),color:e.understood?n.star:e.partial?n.routePossible:oK(n,o,e.centrality,eK),selected:e.id===r,focusDim:!!r&&!l.has(e.id)})),links:s.map(e=>({...e,source:e.src,target:e.dst,color:e.certain?n.route:n.routePossible,focusDim:!!r&&e.src!==r&&e.dst!==r}))}}function mK(e){return e.regions.find(e=>e.home)??e.regions[0]??null}function hK(e){let t=String(e?.project_root??``);return(t.split(`/`).filter(Boolean).at(-1)??t)||`Local project`}function gK(e,t){if(!e?.length)return null;let n=[...e].sort((e,t)=>t.length-e.length||e.join(`\0`).localeCompare(t.join(`\0`)))[0].join(`, `);return t===`easy`?`${e.length} ${e.length===1?`file circle`:`file circles`} found. Largest group: ${n}. Each file can reach every other through proven imports.`:`${e.length} proven import ${e.length===1?`cycle group`:`cycle groups`}. Largest strongly connected group: ${n}.`}function _K(e){let t=e?.regions??[],n=e?.nodes??[],r=e?.edges??[],i=new Map;for(let e of t)i.set(e.language,(i.get(e.language)??0)+1);let a=new Map;for(let e of n)a.set(e.language,(a.get(e.language)??0)+1);let o=[...i.entries()].sort((e,t)=>t[1]-e[1]||String(e[0]).localeCompare(String(t[0]))).map(([e,t])=>({language:e,label:LG(e),count:t,structures:a.get(e)??0})),s=e=>t.filter(t=>Number.isFinite(t[e])).sort((t,n)=>n[e]-t[e]||String(t.id).localeCompare(String(n.id))).slice(0,5).map(t=>({id:t.id,value:t[e]})),c=mK(e);return{projectName:hK(e),modules:t.length,structures:n.length,lines:t.reduce((e,t)=>e+(t.loc??0),0),languages:o,home:e?.selected_entrypoint&&c?{id:c.id,language:c.language}:null,biggest:s(`loc`),busiest:s(`centrality`),unreadable:(e?.partial_files??[]).length,unsupported:(e?.unsupported_sources??[]).reduce((e,t)=>e+(t.count??0),0),unsupportedSources:[...e?.unsupported_sources??[]].map(e=>({extension:e.extension,language:e.language,count:e.count??0})).sort((e,t)=>String(e.extension).localeCompare(String(t.extension))||String(e.language).localeCompare(String(t.language))),relationships:{proven:r.filter(e=>e.certain===!0).length,hedged:r.filter(e=>e.certain===!1).length},importCycles:(e?.import_cycles??[]).map(e=>[...e]),understood:t.filter(e=>e.understood).length}}function vK(e){let t=new Map,n=new Map;for(let r of e??[])n.set(r.src,(n.get(r.src)??0)+1),t.set(r.dst,(t.get(r.dst)??0)+1);return{usedBy:t,uses:n}}function yK(e){let t=e.kind===`region`?`star system`:e.kind,n=e.partial?` · unchartable · syntax error`:``,r=e.home?` · Home`:``,i=(e.usedBy?` · used by ${e.usedBy}`:``)+(e.uses?` · uses ${e.uses}`:``),a=e.system_orbit?.kind===`unreached`?` · no proven call path`:Number.isInteger(e.system_orbit?.call_depth)&&e.system_orbit.call_depth>0?` · call layer ${e.system_orbit.call_depth}`:``;return`${e.name} · ${t} · ${e.loc} LOC${r}${i}${n}${a}`}function bK(e){let t=e.kind===`import`?`import`:e.kind===`call`?`call`:`import route`,n=e.certain?`certain`:t===`call`?`possible call`:`possible import`,r=typeof e.weight==`number`?` · ${e.weight} ${e.weight===1?`import`:`imports`}`:``,i=typeof e.lineno==`number`?` · line ${e.lineno}`:``;return`${e.src} → ${e.dst} · ${t} · ${n}${r}${i}`}function xK(e,t,n){return Math.min(n,Math.max(t,Math.sqrt(Math.max(1,e))*1.15))}var SK=1.6,CK=6.5;function wK(e){return Math.cbrt(e?.val??1)*SK}function TK(e){return wK(e)*CK/2}function EK(e,t,n){return e>=n?t.nodeBright:e>=1?t.node:t.nodeDim}var DK={GALAXY:{min:120,max:640},SYSTEM:{min:55,max:320},STUDY:{min:22,max:170}},OK={GALAXY:{x:0,y:165,z:286},SYSTEM:{x:0,y:52,z:150}},kK=1.15,AK={x:20,y:15,z:42};function jK(e){if(!e)return null;let{width:t,height:n}=e;return!Number.isFinite(t)||!Number.isFinite(n)||t<=0||n<=0?null:t/n}var MK=e=>e.map(e=>({x:e.fx??e.x??0,y:e.fy??e.y??0,z:e.fz??e.z??0,radius:TK(e)})),NK=e=>{let t=[];for(let n of e??[])for(let e of n.radii??[])t.push({x:e,y:0,z:0},{x:-e,y:0,z:0},{x:0,y:0,z:e},{x:0,y:0,z:-e});return t};function PK({level:e,nodes:t,orbitPlan:n,fov:r,aspect:i,viewport:a,chrome:o}){let s=OK[e]??OK.GALAXY,c=DK[e]??DK.GALAXY,l=e===jG.GALAXY?[]:NK(n),u=Array.isArray(t)?t:[],d=e=>[...MK(e),...l],f=e=>AG({points:d(e),direction:s,fov:r,aspect:i}),p=u.filter(cK),m=(p.length?f(p):null)??f(u),h={target:m?.target??{x:0,y:0,z:0},distance:m?.distance??Math.hypot(s.x,s.y,s.z)},{target:g,distance:_}=m?IK({...h,points:d(p.length?p:u),direction:s,fov:r,viewport:a,chrome:o}):h,v=EG(s,_)??s,y=TG({points:d(u).map(e=>({x:e.x-g.x,y:e.y-g.y,z:e.z-g.z,radius:e.radius})),direction:s,fov:r,aspect:i});return{position:{x:g.x+v.x,y:g.y+v.y,z:g.z+v.z},target:g,distance:_,min:Math.min(c.min,_),max:Math.max(c.max,(y??_)*kK),fitted:m!==null}}function FK(e,t){let{width:n,height:r}=e??{};if(!Number.isFinite(n)||!Number.isFinite(r)||n<=0||r<=0)return null;let i={left:0,right:n,top:0,bottom:r},a=(t??[]).filter(e=>e&&Number.isFinite(e.left)&&Number.isFinite(e.right)&&Number.isFinite(e.top)&&Number.isFinite(e.bottom));for(let e=0;ee.lefti.left&&e.topi.top);if(!e)break;i=[{...i,right:Math.max(i.left,e.left)},{...i,left:Math.min(i.right,e.right)},{...i,bottom:Math.max(i.top,e.top)},{...i,top:Math.min(i.bottom,e.bottom)}].reduce((e,t)=>{let n=e=>Math.max(0,e.right-e.left)*Math.max(0,e.bottom-e.top);return n(t)>n(e)?t:e})}let o={left:i.left,right:i.right,top:i.top,bottom:i.bottom,width:Math.max(0,i.right-i.left),height:Math.max(0,i.bottom-i.top)};return o.width>0&&o.height>0?o:null}function IK({target:e,distance:t,points:n,direction:r,fov:i,viewport:a,chrome:o,margin:s=.1}){let c={target:e,distance:t},l=LK(r),u=FK(a,o);if(!l||!u||!Number.isFinite(t)||!Number.isFinite(i)||!Array.isArray(n)||!n.length)return c;let{width:d,height:f}=a;if(u.width>=d&&u.height>=f)return c;let p=LK(RK(Math.abs(l.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},l));if(!p)return c;let m=RK(l,p),h=i*Math.PI/360,g=t=>{let r=1/0,i=-1/0,a=1/0,o=-1/0;for(let s of n){let n={x:s.x-e.x,y:s.y-e.y,z:s.z-e.z},c=t-zK(n,l);if(c<=0)continue;let u=2*c*Math.tan(h)/f;if(!(u>0))continue;let d=Number.isFinite(s.radius)?Math.max(0,s.radius):0,g=zK(n,p)/u,_=zK(n,m)/u,v=d/u;r=Math.min(r,g-v),i=Math.max(i,g+v),a=Math.min(a,_-v),o=Math.max(o,_+v)}return!Number.isFinite(r)||!Number.isFinite(a)?null:{width:i-r,height:o-a,x:(r+i)/2,y:(a+o)/2}},_=g(t);if(!_)return c;let v=1-Math.min(.9,Math.max(0,s)),y=Math.max(1,_.width/Math.max(1,u.width*v),_.height/Math.max(1,u.height*v)),b=t*y,x=y===1?_:g(b);if(!x)return c;let S=2*b*Math.tan(h)/f,C=u.left+u.width/2-d/2,w=u.top+u.height/2-f/2,T=(x.x-C)*S,E=(x.y+w)*S;return{distance:b,target:{x:e.x+p.x*T+m.x*E,y:e.y+p.y*T+m.y*E,z:e.z+p.z*T+m.z*E}}}function LK(e){if(!e)return null;let{x:t,y:n,z:r}=e;if(![t,n,r].every(Number.isFinite))return null;let i=Math.hypot(t,n,r);return i>0?{x:t/i,y:n/i,z:r/i}:null}function RK(e,t){return{x:e.y*t.z-e.z*t.y,y:e.z*t.x-e.x*t.z,z:e.x*t.y-e.y*t.x}}function zK(e,t){return e.x*t.x+e.y*t.y+e.z*t.z}function BK(e){return DK[e]??DK.GALAXY}function VK(e){if(!e)return null;let{system_x:t,system_y:n,system_z:r}=e;return!Number.isFinite(t)||!Number.isFinite(n)||!Number.isFinite(r)?null:{position:{x:t+AK.x,y:n+AK.y,z:r+AK.z},target:{x:t,y:n,z:r}}}var HK=Object.freeze({width:132,height:30}),UK=Object.freeze({far:14,near:44}),WK=Object.freeze([0,-1,1,-2,2]),GK=4,KK=.034;function qK(e,{radius:t,aspect:n}){return e.scale.set(KK*n,KK,1),e.userData.baseOffsetY=t*2.2+1.2,e.position.set(0,e.userData.baseOffsetY,0),e.visible=!1,e.userData.codembleLabel=!0,e.userData.screenWidthFraction=KK*n,e.userData.screenHeightFraction=KK,e}function JK(e){let t=YK(e),n=new V,r=new V;function i({scene:e,camera:i,width:a,height:o,distance:s,distanceBounds:c,activeNodeId:l=null,neighborIds:u=null,chrome:d=[]}){let f=XK(e);for(let e of f)e.visible=!1;if(!f.length||!t.size)return Object.freeze({budget:0,shown:0,visibleIds:Object.freeze([])});let p=ZK(s,c),m=[];for(let e of f){let s=e.userData.nodeId,c=e.parent;if(!c)continue;c.getWorldPosition(r);let d=e.userData.baseOffsetY??0,f=QK(r,d,i,a,o,n);if(!f)continue;let p=QK(r,d+1,i,a,o,n),h=p?Math.abs(p.screenY-f.screenY):0;m.push({sprite:e,nodeId:s,origin:r.clone(),base:d,pixelsPerUnit:h,anchor:f,halfWidth:(e.userData.screenWidthFraction??.14)*o/2,halfHeight:(e.userData.screenHeightFraction??.034)*o/2,rank:s===l?-2:u?.has(s)?-1:t.get(s)??1/0})}m.sort((e,t)=>e.rank-t.rank||e.nodeId.localeCompare(t.nodeId));let h=new Set,g=[];for(let e of m){if(g.length>=p)break;let t=$K(e,{camera:i,width:a,height:o,projected:n,taken:h,chrome:d});if(t){for(let e of t.cells)h.add(e);e.sprite.position.y=e.base+t.offset,e.sprite.visible=!0,g.push(e.nodeId)}}return Object.freeze({budget:p,shown:g.length,visibleIds:Object.freeze(g)})}function a(e){for(let t of XK(e))t.visible=!1}return Object.freeze({hide:a,place:i})}function YK(e){let t=e.filter(e=>e.label).map(e=>({id:e.id,weight:(e.home?3e6:0)+(e.understood?1e6:0)+(e.centrality??0)*1e3})).sort((e,t)=>t.weight-e.weight||e.id.localeCompare(t.id));return new Map(t.map((e,t)=>[e.id,t]))}function XK(e){let t=[];return e.traverse(e=>{e.userData?.codembleLabel&&t.push(e)}),t}function ZK(e,t){let n=Math.max(1,t.max-t.min),r=1-Math.min(1,Math.max(0,(e-t.min)/n));return Math.round(UK.far+(UK.near-UK.far)*r)}function QK(e,t,n,r,i,a){if(a.set(e.x,e.y+t,e.z).project(n),a.z>1)return null;let o=(a.x*.5+.5)*r,s=(-a.y*.5+.5)*i;return o<0||o>r||s<0||s>i?null:{screenX:o,screenY:s}}function $K(e,{camera:t,width:n,height:r,projected:i,taken:a,chrome:o}){let s=e.pixelsPerUnit?(e.halfHeight*2+GK)/e.pixelsPerUnit:0;for(let c of WK){let l=c*s,u=c===0?e.anchor:QK(e.origin,e.base+l,t,n,r,i);if(!u||!eq(u,e,n,r)||!tq(u,e,o))continue;let d=nq(u,e);if(!d.some(e=>a.has(e)))return{cells:d,offset:l}}return null}function eq(e,{halfWidth:t,halfHeight:n},r,i){return e.screenX-t>=0&&e.screenX+t<=r&&e.screenY-n>=0&&e.screenY+n<=i}function tq(e,{halfWidth:t,halfHeight:n},r){if(!r?.length)return!0;let i=e.screenX-t,a=e.screenX+t,o=e.screenY-n,s=e.screenY+n;return!r.some(e=>ie.left&&oe.top)}function nq(e,{halfWidth:t,halfHeight:n}){let r=Math.floor((e.screenX-t)/HK.width),i=Math.floor((e.screenX+t)/HK.width),a=Math.floor((e.screenY-n)/HK.height),o=Math.floor((e.screenY+n)/HK.height),s=[];for(let e=r;e<=i;e+=1)for(let t=a;t<=o;t+=1)s.push(`${e}:${t}`);return s}var rq=128,iq=256,aq=.026;function oq(e,t){let n=document.createElement(`canvas`);n.width=e,n.height=e;let r=n.getContext(`2d`),i=r.createRadialGradient(e/2,e/2,0,e/2,e/2,e/2);for(let[e,n]of t)i.addColorStop(e,`rgba(255, 255, 255, ${n})`);r.fillStyle=i,r.fillRect(0,0,e,e);let a=new La(n);return a.colorSpace=ot,a}function sq(e){let t=document.createElement(`canvas`);t.width=e,t.height=e;let n=t.getContext(`2d`);n.strokeStyle=`rgba(255, 255, 255, 1)`,n.lineWidth=e*.05,n.beginPath(),n.arc(e/2,e/2,e*.4,0,Math.PI*2),n.stroke();let r=new La(t);return r.colorSpace=ot,r}function cq(e){let t=[];function n(e){let n=e.dispose.bind(e);return e.dispose=()=>{},t.push(n),e}let r=n(oq(rq,[[0,.85],[.25,.42],[.6,.1],[1,0]])),i=n(oq(iq,[[0,.32],[.45,.14],[.8,.03],[1,0]])),a=n(sq(rq)),o=new Map,s=new Map,c=null,l=new Map;function u(t){return l.has(t)||l.set(t,n(dq(t,e))),l.get(t)}function d(t){return o.has(t)||o.set(t,n(new bi({map:r,color:new H(t).multiply(new H(e.starHalo)),blending:2,transparent:!0,depthWrite:!1,opacity:.6}))),o.get(t)}return{halo(e,t){let n=new Pi(d(e.color));return n.scale.setScalar(t*6.5),n.renderOrder=-1,n},nebula(e,t){s.has(e)||s.set(e,n(new bi({map:i,color:new H(e),blending:2,transparent:!0,depthWrite:!1,opacity:.16})));let r=new Pi(s.get(e));return r.scale.setScalar(t),r.renderOrder=-2,r},spark(){c||=n(new bi({map:r,color:new H(e.star),blending:2,transparent:!0,depthWrite:!1,opacity:0}));let t=new Pi(c);return t.renderOrder=2,t},reticle(t){let n=new Pi(new bi({map:a,color:new H(e.orbit),transparent:!0,depthWrite:!1,depthTest:!1}));return n.scale.setScalar(t*5),n.renderOrder=3,n},label(e,t){let n=u(e),r=new Pi(n);return qK(r,{radius:t,aspect:n.userData.aspect??4}),r.renderOrder=4,r},guideLabel(e){let t=u(e),n=new Pi(t),r=t.userData.aspect??4;return n.scale.set(aq*r,aq,1),n.renderOrder=2,n},dispose(){for(let e of t)e();t.length=0,o.clear(),s.clear(),c=null,l.clear()}}}var lq=34,uq=12;function dq(e,t){let n=document.createElement(`canvas`),r=n.getContext(`2d`),i=`${lq}px "JetBrains Mono", ui-monospace, monospace`;r.font=i;let a=Math.ceil(r.measureText(e).width)+uq*2;n.width=a,n.height=58,r.font=i,r.textBaseline=`middle`,r.textAlign=`center`,r.fillStyle=t.labelPlate,fq(r,0,0,a,58,8),r.fill(),r.fillStyle=t.labelInk,r.fillText(e,a/2,30);let o=new La(n);o.colorSpace=ot;let s=new bi({map:o,transparent:!0,depthWrite:!1,depthTest:!1,sizeAttenuation:!1});s.userData.aspect=a/58;let c=s.dispose.bind(s);return s.dispose=()=>{o.dispose(),c()},s}function fq(e,t,n,r,i,a){e.beginPath(),e.moveTo(t+a,n),e.arcTo(t+r,n,t+r,n+i,a),e.arcTo(t+r,n+i,t,n+i,a),e.arcTo(t,n+i,t,n,a),e.arcTo(t,n,t+r,n,a),e.closePath()}function pq(e){return Object.entries(e??{}).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${e}:${t}`).join(`|`)}function mq(e){let t=e>>>0;return function(){t=t+1831565813>>>0;let e=Math.imul(t^t>>>15,1|t);return e=e+Math.imul(e^e>>>7,61|e)^e,((e^e>>>14)>>>0)/4294967296}}function hq(e){let t=2166136261;for(let n=0;n>>0}function gq(e,t,n=3200,r=1600){let i=mq(hq(e)),a=new Float32Array(n*3),o=new Float32Array(n*3),s=new Float32Array(n),c=new H(t.starCool??t.nodeDim),l=new H(t.starPale??t.nodeBright),u=new H;for(let e=0;evq(t,e),r=e=>vq(t,e);return e.addEventListener(`pointerdown`,n,{capture:!0}),e.addEventListener(`lostpointercapture`,r,{capture:!0}),()=>{e.removeEventListener(`pointerdown`,n,{capture:!0}),e.removeEventListener(`lostpointercapture`,r,{capture:!0})}}var bq=2.4,xq=1.8,Sq=.62;function Cq(e,t){let n=new Ho({color:new H(t),dashSize:bq,gapSize:xq,transparent:!0,opacity:Sq,depthWrite:!1}),r=new Ta(new mi,n);return r.userData.codembleRouteLink=e,r.renderOrder=-1,r}function wq(e,t,n,r){let i=t?t.getPoints(30):[new V(n.x,n.y??0,n.z??0),new V(r.x,r.y??0,r.z??0)],a=e.geometry.getAttribute(`position`);return(!a||a.count!==i.length)&&e.geometry.setAttribute(`position`,new Qr(new Float32Array(i.length*3),3)),e.geometry.setFromPoints(i),e.geometry.computeBoundingSphere(),e.computeLineDistances(),!0}function Tq(e,t){e.traverse(e=>{let n=e.userData?.codembleRouteLink;!n||!e.material||e.material.color.set(t(n))})}var Eq=128,Dq=-.72;function Oq(e){let t=new Map;for(let n of e??[]){let e=n.system_orbit;if(!e||!Number.isInteger(e.ring)||e.ring<=0||!Number.isFinite(e.radius)||e.radius<=0)continue;t.has(e.ring)||t.set(e.ring,{callDepths:new Set,kinds:new Set,radii:new Set});let r=t.get(e.ring);r.callDepths.add(e.call_depth??null),r.kinds.add(e.kind),r.radii.add(e.radius)}return[...t.entries()].sort(([e],[t])=>e-t).map(([e,t])=>{let n=[...t.callDepths],r=n.length===1?n[0]:null,i=[...t.kinds].sort(),a=i.every(e=>e===`unreached`);return{ring:e,callDepth:r,kinds:i,radii:[...t.radii].sort((e,t)=>e-t),label:a?`No proven path`:r===null?`Mixed call evidence`:`Layer ${r}`,unproven:a}})}function kq(e,t,n){let r=new sr;r.name=`codemble-system-orbits`;for(let i of e){let e=i.unproven?new Ho({color:t.faded,dashSize:3,gapSize:2,opacity:.72,transparent:!0,depthWrite:!1}):new _a({color:t.route,opacity:.52,transparent:!0,depthWrite:!1});for(let t of i.radii){let n=new Da(new mi().setFromPoints(Array.from({length:Eq},(e,n)=>{let r=n/Eq*Math.PI*2;return new V(Math.cos(r)*t,0,Math.sin(r)*t)})),e);n.userData.codembleOrbitGuide=!0,n.renderOrder=-1,i.unproven&&n.computeLineDistances(),r.add(n)}let a=Math.max(...i.radii),o=n.guideLabel(i.label);o.position.set(Math.cos(Dq)*a,2.5,Math.sin(Dq)*a),o.userData.codembleOrbitGuideLabel=!0,r.add(o)}return r}function Aq(e){let t=new Set;e.traverse(e=>{e.userData?.codembleOrbitGuide&&(e.geometry?.dispose(),e.material&&t.add(e.material))});for(let e of t)e.dispose()}var jq=.16,Mq=1.5,Nq=110,Pq=6,Fq=Object.freeze({requestFrame:e=>requestAnimationFrame(e),cancelFrame:e=>cancelAnimationFrame(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e),setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),now:()=>performance.now()}),Iq=Object.freeze({rendererFactory:e=>XW({controlType:`orbit`})(e),assertWebGL(){let e=document.createElement(`canvas`);if(!e.getContext(`webgl2`)&&!e.getContext(`webgl`))throw Error(`Codemble needs WebGL to draw your galaxy. Enable WebGL and reload.`)},ResizeObserver:globalThis.ResizeObserver,createDressing:cq,createBodyGeometry:nG,createBodySpin:aG,createSystemOrbitGuides:kq,disposeSystemOrbitGuides:Aq,createStarfield:gq,createGalacticGlow:_q,attachBloom:zd,guardOrbitPointerState:yq,runDawnSequence:yG,createNameAtlas:JK,documentElement:()=>document.documentElement,search:()=>window.location.search});function Lq({host:e,palette:t,reducedMotion:n,onHoverNode:r,onAdvance:i,onDawnConsumed:a,dependencies:o={},clock:s=Fq}){let c={...Iq,...o},l=!1,u=null,d=null,f=null,p=null,m=null,h=null,g=null,_=()=>{},v=()=>{},y=null,b=null,x=!1,S=null,C={activeId:null,neighborIds:new Set},w=null,T=null,E=()=>{},D=null,O=null,k=null,A=null,ee=null,te=e=>uK(e,C,t),j=e=>dK(e,C,t,zq),ne=e=>{if(e.focusDim)return .4;let t=Math.min(2.2,.45+(e.weight??1)*.25);if(!C.activeId)return t;let n=zq(e.source),r=zq(e.target);return n===C.activeId||r===C.activeId?t+.9:t};function re(){c.assertWebGL(),p=c.createDressing(t),m=c.createBodyGeometry(),d=c.rendererFactory(e).backgroundColor(t.ground).showNavInfo(!1).enableNavigationControls(!0).enableNodeDrag(!1).warmupTicks(0).cooldownTicks(0).nodeId(`id`).nodeLabel(yK).nodeVal(`val`).nodeColor(te).nodeRelSize(SK).nodeResolution(8).nodeOpacity(.82).nodeThreeObject(e=>Rq(e,t,p,S,{level:u?.level,bodyGeometry:m})).nodeThreeObjectExtend(!0).linkColor(j).linkLabel(bK).linkOpacity(.5).linkWidth(ne).linkCurvature(.12).linkThreeObject(e=>e.certain?null:Cq(e,j(e))).linkPositionUpdate((e,{start:t,end:n},r)=>!r.certain&&wq(e,r.__curve,t,n)).linkVisibility(e=>!(u?.mode===`easy`&&e.focusDim)).linkHoverPrecision(4).linkDirectionalArrowRelPos(1).linkDirectionalArrowColor(j).linkDirectionalParticles(e=>e.kind===`call`&&e.certain&&!e.focusDim&&!n?2:0).linkDirectionalParticleSpeed(.006).linkDirectionalParticleWidth(1.1).linkDirectionalParticleColor(()=>t.orbit).onNodeHover(t=>{l||(e.style.cursor=t?`pointer`:`default`,r(t?.id??null))}).onNodeClick(e=>{l||i(e)}),y=s.requestFrame(()=>{l||e.querySelector(`.scene-nav-info`)?.remove()}),f=d.controls(),f.enablePan=!1,f.enableDamping=!n,f.dampingFactor=.12,f.rotateSpeed=.55,f.zoomSpeed=.7,f.minPolarAngle=jq,f.maxPolarAngle=Mq;let a=()=>{l||(x=!0)};f.addEventListener(`start`,a),v=()=>f?.removeEventListener(`start`,a),_=c.guardOrbitPointerState(e,f),h=c.attachBloom(d,e.getBoundingClientRect()),g=new c.ResizeObserver(([e])=>{if(l)return;let{width:t,height:n}=e.contentRect;d.width(t).height(n);let r=jK(e.contentRect);!x&&r!==null&&b?.(r)}),g.observe(e)}function M(t,n){if(!u)return;let{data:r,level:i,orbitPlan:a}=u,o=cG({host:e,renderer:d}),s=PK({level:i,nodes:r.nodes,orbitPlan:a,fov:d.camera()?.fov,aspect:n??jK(e.getBoundingClientRect())??jK({width:d.width(),height:d.height()}),viewport:o.viewport,chrome:i===jG.SYSTEM?o.clickObstructions:[]});f.minDistance=s.min,f.maxDistance=s.max,d.cameraPosition(s.position,s.target,t)}function N(){E(),E=()=>{},T&&=(d.scene().remove(T),c.disposeSystemOrbitGuides(T),null),!(u.level===jG.GALAXY||!u.orbitPlan.length)&&(T=c.createSystemOrbitGuides(u.orbitPlan,t,p),d.scene().add(T),E=c.createBodySpin(d.scene(),{reducedMotion:n}))}function ie(){ae();let e=c.createStarfield(u.starfieldSeed,t),n=c.createGalacticGlow(t);d.scene().add(e),d.scene().add(n),w={starfield:e,glow:n}}function ae(){!w||!d||(d.scene().remove(w.starfield),w.starfield.geometry?.dispose(),w.starfield.material?.dispose(),d.scene().remove(w.glow),w.glow.material?.map?.dispose(),w.glow.material?.dispose(),w=null)}function oe(){D&&=(D.cancelled=!0,D.frame!==null&&s.cancelFrame(D.frame),D.stop(),null)}function P(e){if(O=e,a?.(e),n)return;oe();let r={cancelled:!1,frame:null,stop:()=>{}};D=r;let i=n=>{if(l||r.cancelled||D!==r)return;let a=d.scene();if(a.getObjectByName(`codemble-system-${e}`)||n>=Pq){r.frame=null,r.stop=c.runDawnSequence({scene:a,regionId:e,palette:t,dressing:p,routes:u?.graph?.region_edges??[],hopsById:new Map((u?.graph?.regions??[]).map(e=>[e.id,e.hops_from_home]))});return}r.frame=s.requestFrame(()=>i(n+1))};r.frame=s.requestFrame(()=>i(0))}function se(){A!==null&&s.clearInterval(A),k&&d&&k.hide(d.scene()),A=null,k=c.createNameAtlas(u.data.nodes);let t=d.scene(),n=()=>{if(!(l||!k))try{let n=d.camera(),r=BK(u.level),i=f?{min:f.minDistance??r.min,max:f.maxDistance??r.max}:r,a=f?n.position.distanceTo(f.target):n.position.length(),o=cG({host:e,renderer:d});k.place({scene:t,camera:n,width:d.width(),height:d.height(),distance:a,distanceBounds:i,activeNodeId:C.activeId,neighborIds:C.neighborIds,chrome:o.nameObstructions})}catch(e){A!==null&&s.clearInterval(A),A=null,k.hide(t),console.error(`Codemble: label declutter failed, names disabled`,e)}};n(),A=s.setInterval(n,Nq)}function F(){ee!==null&&s.clearTimeout(ee),ee=null,!(!new URLSearchParams(c.search()).has(`benchmark`)||u.data.nodes.length<900)&&(c.documentElement().removeAttribute(`data-codemble-fps`),ee=s.setTimeout(()=>{if(ee=null,l)return;let e=d.renderer(),t=d.postProcessingComposer(),n=s.now();for(let e=0;e<60;e+=1)t.render();e.getContext().finish();let r=s.now()-n;c.documentElement().dataset.codembleFps=(60*1e3/r).toFixed(1)},1e3))}function ce(e){if(l)return;let t=u;if(u=e,(!t||t.data!==e.data||t.level!==e.level||t.mode!==e.mode||t.orbitPlan!==e.orbitPlan)&&(d.nodeResolution(e.data.nodes.length>=900?4:8).nodeThreeObjectExtend(e.level===jG.GALAXY).linkVisibility(t=>!(e.mode===`easy`&&t.focusDim)).linkDirectionalArrowLength(e.level===jG.GALAXY?0:3.2).graphData(e.data),M(xG(420,{active:e.firstFlightActive,reducedMotion:n})),x=!1,b=e=>M(0,e)),(!t||t.level!==e.level||t.orbitPlan!==e.orbitPlan)&&N(),(!t||t.starfieldSeed!==e.starfieldSeed)&&ie(),e.level===jG.GALAXY?e.pendingDawnRegionId&&e.pendingDawnRegionId!==O&&P(e.pendingDawnRegionId):oe(),!t||t.data!==e.data||t.hoverNodeId!==e.hoverNodeId||t.focusedNodeId!==e.focusedNodeId||t.level!==e.level||t.selectedNode?.id!==e.selectedNode?.id){let t=e.hoverNodeId??e.focusedNodeId??(e.level===jG.STUDY?e.selectedNode?.id??null:null),n=new Set;if(t)for(let r of e.data.links){let e=zq(r.source),i=zq(r.target);e===t&&n.add(i),i===t&&n.add(e)}C={activeId:t,neighborIds:n},d.nodeColor(e=>te(e)).linkColor(e=>j(e)).linkWidth(e=>ne(e)).linkDirectionalArrowColor(e=>j(e)),Tq(d.scene(),j)}if((!t||t.focusedNodeId!==e.focusedNodeId)&&(S=e.focusedNodeId,d.refresh()),(!t||t.data.nodes!==e.data.nodes||t.hoverNodeId!==e.hoverNodeId||t.focusedNodeId!==e.focusedNodeId||t.level!==e.level)&&se(),(!t||t.data.nodes.length!==e.data.nodes.length)&&F(),e.level===jG.STUDY&&(!t||t.level!==e.level||t.selectedNode!==e.selectedNode)){let t=VK(e.selectedNode);t&&d.cameraPosition(t.position,t.target,420)}}function le(){l||(l=!0,g?.disconnect(),g=null,v(),v=()=>{},y!==null&&s.cancelFrame(y),y=null,_(),_=()=>{},E(),E=()=>{},T&&d&&(d.scene().remove(T),c.disposeSystemOrbitGuides(T),T=null),oe(),A!==null&&s.clearInterval(A),A=null,k&&d&&k.hide(d.scene()),k=null,ee!==null&&s.clearTimeout(ee),ee=null,ae(),d?.pauseAnimation(),h?.dispose(),h=null,d?._destructor(),p?.dispose(),p=null,m?.dispose(),m=null,f=null,d=null,e.replaceChildren(),b=null)}try{re()}catch(e){throw le(),e}return Object.freeze({update:ce,dispose:le})}function Rq(e,t,n,r,{level:i,bodyGeometry:a}={}){let o=new sr;o.name=e.kind===`region`?`codemble-system-${e.id}`:`codemble-node-${e.id}`;let s=wK(e),c=i&&i!==jG.GALAXY&&a;c&&o.add(iG({node:e,color:e.color,palette:t,radius:s,geometry:a}));let l=lK(e);if(!e.focusDim&&!l&&!c&&o.add(n.halo(e,s)),e.kind===`region`&&!l){let r=t.nebula[e.language];r&&o.add(n.nebula(r,s*14))}if(e.label){let t=n.label(e.label,s);t.userData.nodeId=e.id,o.add(t)}if(e.home){let e=new na(new bo(s*1.7,Math.max(.18,s*.07),8,36),new Wi({color:t.home}));e.rotation.x=Math.PI/2.8,o.add(e)}if(e.kind===`class`&&!e.focusDim){let e=new na(new bo(s*1.45,Math.max(.1,s*.045),6,28),new Wi({color:t.route}));e.rotation.x=Math.PI/2.4,o.add(e)}if(e.selected){let e=new na(new bo(s*2.1,Math.max(.16,s*.05),6,24),new Wi({color:t.orbit}));e.rotation.x=Math.PI/2.8,o.add(e)}return e.id===r&&o.add(n.reticle(s)),o}function zq(e){return typeof e==`object`&&e?e.id:e}var Bq=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),$=o(((e,t)=>{t.exports=Bq()}))();function Vq({graph:e,level:t,region:n,selectedNode:r,hoverNodeId:i,pendingDawnRegionId:a,revealedRegionIds:o,mode:s,firstFlightActive:c,onHoverNode:l,onAdvance:u,onRetreat:d,onDawnConsumed:f}){let p=(0,_.useRef)(null),m=(0,_.useRef)(null),h=(0,_.useRef)(u),g=(0,_.useRef)(d),v=(0,_.useRef)(l),y=(0,_.useRef)(f),[b,x]=(0,_.useState)(0),[S,C]=(0,_.useState)(!1),[w,T]=(0,_.useState)(``),E=(0,_.useMemo)(Uq,[]),D=(0,_.useMemo)(Bd,[]),O=pq(e.file_hashes),k=(0,_.useMemo)(()=>t===jG.GALAXY?fK(e,E,o):pK(e,n?.id,E,{selectedId:r?.id}),[e,t,E,n?.id,o,r?.id]),A=(0,_.useMemo)(()=>t===jG.GALAXY?[]:Oq(k.nodes),[k.nodes,t]);(0,_.useEffect)(()=>{h.current=u,g.current=d,v.current=l,y.current=f},[u,d,l,f]),(0,_.useEffect)(()=>{let e=p.current;if(e)try{let t=Lq({host:e,palette:E,reducedMotion:D,onHoverNode:e=>v.current(e),onAdvance:e=>h.current(e),onDawnConsumed:e=>y.current?.(e)});return m.current=t,T(``),()=>{t.dispose(),m.current===t&&(m.current=null)}}catch(e){let t=e instanceof Error?e.message:String(e);T(t.startsWith(`Codemble needs WebGL`)?t:`The galaxy could not start: ${t}`);return}},[E,D]),(0,_.useEffect)(()=>{m.current?.update({graph:e,data:k,level:t,mode:s,orbitPlan:A,selectedNode:r,hoverNodeId:i,pendingDawnRegionId:a,starfieldSeed:O,firstFlightActive:c,focusedNodeId:S?k.nodes[b]?.id??null:null})},[k,c,b,e,i,S,t,s,A,a,r,O]),(0,_.useEffect)(()=>{x(0),C(!1)},[k,t,s,A]);let ee=k.nodes[b]??null;function te(e){if(e.key===`ArrowRight`||e.key===`ArrowDown`)e.preventDefault(),x(e=>(e+1)%Math.max(1,k.nodes.length));else if(e.key===`ArrowLeft`||e.key===`ArrowUp`)e.preventDefault(),x(e=>(e-1+k.nodes.length)%Math.max(1,k.nodes.length));else if(e.key===`Enter`&&ee)e.preventDefault(),h.current(ee);else if(e.key===`Escape`||e.key===`Backspace`){if(e.key===`Escape`&&c)return;e.preventDefault(),g.current()}}return w?(0,$.jsxs)(`section`,{className:`webgl-error`,role:`alert`,children:[(0,$.jsx)(`h1`,{children:`The sky could not open.`}),(0,$.jsx)(`p`,{children:w}),(0,$.jsxs)(`p`,{children:[`The `,s===`easy`?`Diagram`:`Map`,` layer works without WebGL — switch to it at the top of the window to explore the same code.`]})]}):(0,$.jsxs)(`div`,{className:`galaxy-frame`,role:`application`,tabIndex:`0`,"aria-label":`Codemble ${t.toLowerCase()} view. Drag to orbit, scroll to zoom. Use arrow keys to choose a node and Enter to move closer.${t===jG.GALAXY?``:` Solid guides are parser-proven call layers; a dashed guide has no proven call path.`}`,onFocus:()=>C(!0),onBlur:()=>{v.current(null)},onKeyDown:te,children:[(0,$.jsx)(`div`,{ref:p,className:`galaxy-canvas`,"aria-hidden":`true`,onPointerLeave:()=>v.current(null)}),S&&ee?(0,$.jsx)(`output`,{className:`keyboard-focus`,"aria-live":`polite`,children:yK(ee)}):null]})}function Hq(e){let t=document.createElement(`canvas`).getContext(`2d`);t.fillStyle=`#000000`,t.fillStyle=e,t.fillRect(0,0,1,1);let[n,r,i]=t.getImageData(0,0,1,1).data;return`rgb(${n}, ${r}, ${i})`}function Uq(){let e=getComputedStyle(document.documentElement),t=t=>Hq(e.getPropertyValue(t).trim());return Object.freeze({ground:t(`--cm-sky`),skyGlow:t(`--cm-sky-glow`),home:t(`--cm-ink`),orbit:t(`--cm-orbit`),nodeBright:t(`--cm-node-bright`),node:t(`--cm-node-mid`),nodeDim:t(`--cm-node-unlit`),starCool:t(`--cm-star-cool`),starPale:t(`--cm-star-pale`),route:t(`--cm-route`),routePossible:t(`--cm-route-possible`),faded:t(`--cm-hairline-soft`),star:t(`--cm-star-high`),starHalo:t(`--cm-star-halo`),nebula:Object.freeze(Object.fromEntries(Object.entries(zG).map(([e,n])=>[e,t(n)]))),communities:Object.freeze(Array.from({length:8},(e,n)=>t(`--cm-com-${n}`))),labelPlate:e.getPropertyValue(`--cm-label-plate`).trim(),labelInk:e.getPropertyValue(`--cm-label-ink`).trim()})}function Wq({hint:e,onFollow:t,firstFlight:n}){let r=(0,_.useRef)(null),i=(0,_.useRef)(!1);if((0,_.useLayoutEffect)(()=>{n?.active&&!i.current&&r.current?.focus(),i.current=n?.active===!0},[n?.active]),n?.active){let{stop:e,index:t,total:i,mode:a}=n;return(0,$.jsxs)(`output`,{className:`hint-chip first-flight`,"aria-live":`polite`,"data-first-flight":`active`,children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`✦`}),(0,$.jsxs)(`strong`,{children:[`First Flight · `,t+1,` of `,i]}),(0,$.jsx)(`span`,{className:`first-flight__system`,children:e.id}),(0,$.jsxs)(`small`,{children:[a===`easy`?`${e.language} file`:`${e.language} system`,` · used by ${e.usedBy} · uses ${e.uses}`]}),(0,$.jsxs)(`span`,{className:`first-flight__actions`,children:[(0,$.jsx)(`button`,{type:`button`,disabled:t===0,onClick:n.onBack,children:`Back`}),t+1{let e=i.current;e&&!e.open&&e.showModal(),a.current?.focus()},[]);function c(){i.current?.close(),t()}return(0,$.jsxs)(`dialog`,{ref:i,className:`coach-marks`,"aria-labelledby":`coach-heading`,onCancel:e=>{e.preventDefault(),c()},children:[(0,$.jsxs)(`p`,{className:`coach-marks__progress`,children:[`Step `,n+1,` of `,o.length]}),(0,$.jsx)(`h1`,{id:`coach-heading`,children:s.title}),(0,$.jsx)(`p`,{children:s.body}),(0,$.jsxs)(`div`,{className:`coach-marks__actions`,children:[(0,$.jsx)(`button`,{type:`button`,className:`coach-skip`,onClick:c,children:`Skip`}),(0,$.jsx)(`button`,{ref:a,type:`button`,className:`check-primary`,onClick:()=>n+1({left:Math.min(e.left,t),top:Math.min(e.top,n),right:Math.max(e.right,t),bottom:Math.max(e.bottom,n)}),{left:1/0,top:1/0,right:-1/0,bottom:-1/0})}function iJ(e,t){return!(e.rightt.right||e.bottomt.bottom)}function aJ(e,t){let n=new Map;for(let r of e){let e=t(r),i=Math.floor(e.top/Xq),a=Math.floor(e.bottom/Xq);for(let e=i;e<=a;e+=1){let t=n.get(e)??[];t.push(r),n.set(e,t)}}return n}function oJ(e){let t=Math.max(e.scale||1,1e-4);return{left:(e.scrollLeft-(e.offsetX||0))/t-nJ,top:e.scrollTop/t-nJ,right:(e.scrollLeft-(e.offsetX||0)+e.width)/t+nJ,bottom:(e.scrollTop+e.height)/t+nJ}}function sJ(e,t){let n=t-eJ-tJ,r=Math.max(1,Math.floor(n/(Qq*$q)));return e.length<=r?e:`${e.slice(0,Math.max(1,r-1))}…`}function cJ(e,{communityIndexByRegion:t=null}={}){let n=e.boxes.map(e=>({...e,drawX:e.x+Jq,drawY:e.y+Jq,communityFamily:t?.get(e.id)??null})).sort((e,t)=>e.y-t.y||e.x-t.x||e.id.localeCompare(t.id)),r=new Map(n.map(e=>[e.id,e])),i=aJ(n,e=>({left:e.drawX,top:e.drawY,right:e.drawX+e.width,bottom:e.drawY+e.height})),a=e.edges.filter(e=>r.has(e.src)&&r.has(e.dst)).map(e=>{let t=e.points.map(([e,t])=>[e+Jq,t+Jq]);return{...e,points:t,bounds:rJ(t)}});return Object.freeze({kind:`architecture`,boxes:n,boxById:r,boxesByBand:i,edges:a,padding:Jq})}function lJ(e,t){let n=oJ(t);return{boxes:e.boxes.filter(e=>iJ(n,{left:e.drawX,top:e.drawY,right:e.drawX+e.width,bottom:e.drawY+e.height})),edges:e.edges.filter(e=>iJ(n,e.bounds))}}function uJ(e,t){return e.boxesByBand.get(Math.floor(t.y/Xq))?.find(e=>t.x>=e.drawX&&t.x<=e.drawX+e.width&&t.y>=e.drawY&&t.y<=e.drawY+e.height)??null}function dJ(e,t,n,r,i){if(!e.length)return null;if(n===`Home`)return e[0];if(n===`End`)return e.at(-1);let a=Math.max(0,e.findIndex(e=>r(e)===t)),o=e[a]??e[0],s=i(o),c={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]}[n];if(!c)return o;let l=e.filter(e=>r(e)!==r(o)).map(e=>{let t=i(e),n=t.x-s.x,r=t.y-s.y;return{item:e,forward:n*c[0]+r*c[1],cross:Math.abs(n*c[1]-r*c[0])}}).filter(e=>e.forward>0).sort((e,t)=>e.cross/e.forward-t.cross/t.forward||Math.hypot(e.forward,e.cross)-Math.hypot(t.forward,t.cross)||r(e.item).localeCompare(r(t.item)));if(l.length)return l[0].item;let u=n===`ArrowLeft`||n===`ArrowUp`?a-1:a+1;return e[Math.max(0,Math.min(e.length-1,u))]}function fJ(e,t,n){return dJ(e.boxes,t,n,e=>e.id,e=>({x:e.drawX+e.width/2,y:e.drawY+e.height/2}))}function pJ(e){return`${e.label}, ${e.node_count} ${e.node_count===1?`structure`:`structures`}, ${e.loc} ${e.loc===1?`line`:`lines`}${e.understood?`, understood`:`, not yet understood`}${e.home?`, Home`:``}${e.reachable?``:`, no import route from Home`}${e.partial?`, unchartable, syntax error`:``}`}function mJ(e){let t=[...e.nodes].sort((e,t)=>e.order-t.order).map(e=>{let t=e.x+Yq,n=e.y+Yq,r=e.label.length*13*$q,i=Math.max(vJ(e,`easy`).length,vJ(e,`expert`).length)*11*$q;return{...e,drawX:t,drawY:n,bounds:{left:t,top:n,right:t+20+r+i+8,bottom:n+Zq}}}),n=new Map,r=[];for(let e of t){let t=e.parent===null?null:n.get(e.parent)??null;if(t){let n=[[t.drawX+8,t.drawY+20],[t.drawX+8,e.drawY+12],[e.drawX+8,e.drawY+12]];r.push({row:e,parent:t,points:n,bounds:rJ(n)})}n.set(e.id,e)}return Object.freeze({kind:`workflow`,rows:t,rowByOrder:new Map(t.map(e=>[e.order,e])),rowsByBand:aJ(t,e=>e.bounds),edges:r,padding:Yq})}function hJ(e,t){let n=oJ(t);return{rows:e.rows.filter(e=>iJ(n,e.bounds)),edges:e.edges.filter(e=>iJ(n,e.bounds))}}function gJ(e,t){return e.rowsByBand.get(Math.floor(t.y/Xq))?.find(e=>t.x>=e.bounds.left&&t.x<=e.bounds.right&&t.y>=e.bounds.top&&t.y<=e.bounds.bottom)??null}function _J(e,t,n){if(!e.rows.length)return null;if(n===`Home`)return e.rows[0];if(n===`End`)return e.rows.at(-1);let r=Math.max(0,e.rows.findIndex(e=>e.order===t)),i=e.rows[r]??e.rows[0];if(n===`ArrowLeft`&&i.parent!==null)return e.rows.slice(0,r).reverse().find(e=>e.id===i.parent)??i;if(n===`ArrowRight`)return e.rows.slice(r+1).find(e=>e.parent===i.id)??i;let a=n===`ArrowUp`?-1:+(n===`ArrowDown`);return e.rows[r+a]??i}function vJ(e,t){let n=[];return e.relation===`defines`?n.push(t===`easy`?`lives here`:`defined in this module`):e.certain||n.push(`possible call`),e.cut===`cycle`&&n.push(`loops back`),e.cut===`repeat`&&n.push(`shown above`),e.partial&&n.push(t===`easy`?`could not be read`:`unchartable`),n.map(e=>` — ${e}`).join(``)}function yJ(e){return`${e.label} at ${e.file}:${e.lineno}${e.certain?``:`, possible call`}${e.cut===`cycle`?`, repeats an earlier step`:``}${e.cut===`repeat`?`, already shown above`:``}${e.partial?`, unchartable, syntax error`:``}`}function bJ(e,t,n){return e?.getPropertyValue?.(t)?.trim()||n}function xJ(e){let t=e.match(/^#([0-9a-f]{6})$/i)?.[1];if(t)return[0,2,4].map(e=>Number.parseInt(t.slice(e,e+2),16));let n=e.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/i);return n?n.slice(1).map(Number):null}function SJ(e,t,n){let r=xJ(e),i=xJ(t);return!r||!i?t:`rgb(${r.map((e,t)=>Math.round(e*n+i[t]*(1-n))).join(` `)})`}function CJ(e){let t=bJ(e,`--cm-ground-2`,`#101a3e`),n=Array.from({length:8},(n,r)=>SJ(bJ(e,`--cm-com-${r}`,t),t,.16));return Object.freeze({ground2:t,hairline:bJ(e,`--cm-hairline`,`#24325c`),ink:bJ(e,`--cm-ink`,`#eef2fa`),ink3:bJ(e,`--cm-ink-3`,`#7d8aa8`),route:bJ(e,`--cm-route`,`rgb(96 113 152)`),possible:bJ(e,`--cm-route-possible`,`rgb(146 160 194)`),understood:bJ(e,`--cm-star-high`,`#f6d087`),interaction:bJ(e,`--cm-orbit`,`#82abec`),mono:bJ(e,`--cm-font-mono`,`monospace`),communities:n,language:{python:bJ(e,`--cm-neb-python`,`rgb(112 166 144)`),javascript:bJ(e,`--cm-neb-js`,`rgb(161 151 185)`),typescript:bJ(e,`--cm-neb-ts`,`rgb(119 161 186)`),go:bJ(e,`--cm-neb-go`,`rgb(132 178 161)`),java:bJ(e,`--cm-neb-java`,`rgb(177 154 137)`),rust:bJ(e,`--cm-neb-rust`,`rgb(183 145 132)`),csharp:bJ(e,`--cm-neb-csharp`,`rgb(157 146 184)`)}})}function wJ(e,t,n,r,i,a){e.beginPath(),e.moveTo(t+a,n),e.lineTo(t+r-a,n),e.quadraticCurveTo(t+r,n,t+r,n+a),e.lineTo(t+r,n+i-a),e.quadraticCurveTo(t+r,n+i,t+r-a,n+i),e.lineTo(t+a,n+i),e.quadraticCurveTo(t,n+i,t,n+i-a),e.lineTo(t,n+a),e.quadraticCurveTo(t,n,t+a,n),e.closePath()}function TJ(e,t){e.save(),e.translate((t.offsetX||0)-t.scrollLeft,-t.scrollTop),e.scale(t.scale,t.scale),e.lineCap=`round`,e.lineJoin=`round`}function EJ(e,t,n){if(t.length<2)return;let[r,i]=t.at(-2),[a,o]=t.at(-1),s=Math.atan2(o-i,a-r);e.save(),e.translate(a,o),e.rotate(s),e.fillStyle=n,e.beginPath(),e.moveTo(0,0),e.lineTo(-9,-4),e.lineTo(-9,4),e.closePath(),e.fill(),e.restore()}function DJ(e,t,n,r,i={}){let a=lJ(t,n);TJ(e,n);for(let t of a.edges){let n=t.certain&&!t.cycle?r.route:r.possible;e.strokeStyle=n,e.lineWidth=1+Math.min(2.5,(Math.max(1,t.weight)-1)*.5),e.setLineDash(t.certain?[]:[5,4]),e.beginPath(),t.points.forEach(([t,n],r)=>r===0?e.moveTo(t,n):e.lineTo(t,n)),e.stroke(),e.setLineDash([]),EJ(e,t.points,n)}for(let t of a.boxes){let n=t.drawX,a=t.drawY,o=t.communityFamily===null?r.ground2:r.communities[t.communityFamily]??r.ground2;wJ(e,n,a,t.width,t.height,3),e.fillStyle=o,e.fill(),e.strokeStyle=t.understood?r.understood:t.partial?r.possible:r.hairline,e.lineWidth=t.home?2:1,e.setLineDash(t.reachable?[]:[4,3]),e.stroke(),e.setLineDash([]),e.fillStyle=r.language[t.language]??r.hairline,e.fillRect(n,a,4,t.height),t.partial&&(e.fillStyle=r.possible,e.beginPath(),e.moveTo(n+t.width-16,a+2),e.lineTo(n+t.width-2,a+2),e.lineTo(n+t.width-2,a+16),e.closePath(),e.fill()),(t.id===i.selectedId||t.id===i.activeId||t.id===i.hoverId)&&(wJ(e,n-3,a-3,t.width+6,t.height+6,5),e.strokeStyle=r.interaction,e.lineWidth=t.id===i.activeId&&i.keyboardFocused?2.5:1.5,e.stroke()),e.fillStyle=r.ink,e.font=`13px ${r.mono}`,e.fillText(sJ(t.short_label??t.label,t.width),n+14,a+24),e.fillStyle=r.ink3,e.font=`11px ${r.mono}`;let s=i.mode===`easy`?`${t.node_count} ${t.node_count===1?`piece`:`pieces`}`:`${t.node_count} ${t.node_count===1?`node`:`nodes`} · ${t.loc} LOC`;e.fillText(s,n+14,a+42)}return e.restore(),{visibleBoxes:a.boxes.length,visibleEdges:a.edges.length}}function OJ(e,t,n,r,i={}){let a=hJ(t,n);TJ(e,n);for(let t of a.edges)e.strokeStyle=t.row.certain?r.route:r.possible,e.lineWidth=1.2,e.setLineDash(t.row.certain?[]:[5,4]),e.beginPath(),t.points.forEach(([t,n],r)=>r===0?e.moveTo(t,n):e.lineTo(t,n)),e.stroke();e.setLineDash([]);for(let t of a.rows){let n=t.drawX,a=t.drawY;e.fillStyle=t.understood?r.understood:t.partial||t.cut?r.possible:r.ink3,e.beginPath(),e.arc(n+8,a+16,4,0,Math.PI*2),e.fill(),(t.region===i.selectedRegionId||t.order===i.activeOrder||t.order===i.hoverOrder)&&(e.strokeStyle=r.interaction,e.lineWidth=t.order===i.activeOrder&&i.keyboardFocused?2.5:1.5,e.beginPath(),e.arc(n+8,a+16,7,0,Math.PI*2),e.stroke()),e.fillStyle=r.ink,e.font=`13px ${r.mono}`;let o=e.measureText(t.label).width;e.fillText(t.label,n+20,a+20),e.fillStyle=r.ink3,e.font=`11px ${r.mono}`,e.fillText(vJ(t,i.mode),n+20+o,a+20)}return e.restore(),{visibleRows:a.rows.length,visibleEdges:a.edges.length}}var kJ=Object.freeze({architecturePadding:Jq,workflowPadding:Yq}),AJ=.05,jJ=2.5;function MJ(e){return Math.min(jJ,Math.max(AJ,e))}function NJ(e,t,n,r){return!e||!t||!n||!r?1:MJ(Math.min(e/n,t/r))}function PJ({viewportWidth:e,viewportHeight:t,scale:n,point:r}){return{scrollLeft:Math.max(0,r.x*n-e/2),scrollTop:Math.max(0,r.y*n-t/2)}}function FJ({viewportWidth:e,viewportHeight:t,scale:n,scrollLeft:r,scrollTop:i,point:a}){if(!a||!e||!t)return!1;let o=a.x*n,s=a.y*n;return o>=r&&o<=r+e&&s>=i&&s<=i+t}var IJ=.35;function LJ(e,t,n,r){if(!e||!t||!n||!r)return 1;let i=NJ(e,t,n,r);return i>=.35?i:MJ(Math.min(IJ,e/n))}function RJ(){let e=new Map;return Object.freeze({read(t){let n=e.get(t);return n?{...n}:null},write(t,n){e.set(t,{scale:MJ(n.scale),scrollLeft:Math.max(0,n.scrollLeft),scrollTop:Math.max(0,n.scrollTop)})},clear(){e.clear()}})}var zJ=1.25;function BJ({contentWidth:e,contentHeight:t,label:n,viewKey:r,viewportStore:i,focusPoint:a,centerInline:o=!1,children:s}){let c=(0,_.useRef)(null),l=(0,_.useRef)(i.read(r)),[u,d]=(0,_.useState)(()=>l.current?.scale??1),[f,p]=(0,_.useState)(!1),m=(0,_.useRef)(null),h=(0,_.useRef)(!1),g=(0,_.useRef)(0),[v,y]=(0,_.useState)({width:0,height:0,scrollLeft:0,scrollTop:0}),b=Math.round(u*100),x=(0,_.useCallback)(()=>{let e=c.current;if(!e)return;let t={width:e.clientWidth,height:e.clientHeight,scrollLeft:e.scrollLeft,scrollTop:e.scrollTop};y(e=>e.width===t.width&&e.height===t.height&&e.scrollLeft===t.scrollLeft&&e.scrollTop===t.scrollTop?e:t)},[]),S=(0,_.useCallback)(()=>{cancelAnimationFrame(g.current),g.current=requestAnimationFrame(x)},[x]);(0,_.useEffect)(()=>()=>cancelAnimationFrame(g.current),[]);let C=(0,_.useCallback)(()=>{let e=c.current;e&&i.write(r,{scale:u,scrollLeft:e.scrollLeft,scrollTop:e.scrollTop})},[u,r,i]),w=(0,_.useCallback)(()=>{let n=c.current?.getBoundingClientRect();!n||!e||!t||d(LJ(n.width,n.height,e,t))},[e,t]);(0,_.useLayoutEffect)(()=>{let e=c.current;if(!e||h.current)return;let t=0;return t=requestAnimationFrame(()=>{let t=e.getBoundingClientRect(),n=l.current,r=n&&(!a||FJ({viewportWidth:t.width,viewportHeight:t.height,scale:n.scale,scrollLeft:n.scrollLeft,scrollTop:n.scrollTop,point:a}));if(r)e.scrollLeft=n.scrollLeft,e.scrollTop=n.scrollTop;else if(t.width<640&&a){let n=PJ({viewportWidth:t.width,viewportHeight:t.height,scale:u,point:a});e.scrollLeft=n.scrollLeft,e.scrollTop=n.scrollTop}else n&&!r&&(e.scrollLeft=0,e.scrollTop=0);h.current=!0,C(),x()}),()=>cancelAnimationFrame(t)},[a,x,C,u]),(0,_.useEffect)(()=>{if(!h.current)return;let e=requestAnimationFrame(()=>{C(),x()});return()=>cancelAnimationFrame(e)},[x,C]),(0,_.useEffect)(()=>{let e=c.current;if(!e)return;let t=null,n=new ResizeObserver(([n])=>{let{width:r,height:i}=n.contentRect,o=t;if(t={width:r,height:i},!o||!a||!h.current||Math.abs(r-o.width)<1&&Math.abs(i-o.height)<1||FJ({viewportWidth:r,viewportHeight:i,scale:u,scrollLeft:e.scrollLeft,scrollTop:e.scrollTop,point:a}))return;let s=PJ({viewportWidth:r,viewportHeight:i,scale:u,point:a});e.scrollLeft=s.scrollLeft,e.scrollTop=s.scrollTop,C(),S()});return n.observe(e),()=>n.disconnect()},[a,S,C,u]);let T=o?Math.max(0,(v.width-e*u)/2):0,E=(0,_.useCallback)((e,t=0)=>{let n=c.current;if(!n||!e)return;let r=e.x*u+t,i=e.y*u;n.scrollLeft=Math.max(0,r-n.clientWidth/2),n.scrollTop=Math.max(0,i-n.clientHeight/2),C(),S()},[S,C,u]);function D(e){if(e.button!==0||e.target.closest(`[role='button']`))return;let t=c.current;m.current={x:e.clientX,y:e.clientY,left:t.scrollLeft,top:t.scrollTop},p(!0),t.setPointerCapture(e.pointerId)}function O(e){if(!m.current)return;let t=c.current;t.scrollLeft=m.current.left-(e.clientX-m.current.x),t.scrollTop=m.current.top-(e.clientY-m.current.y)}function k(e){m.current&&(m.current=null,p(!1),c.current?.releasePointerCapture?.(e.pointerId))}return(0,$.jsxs)(`div`,{className:`map-canvas`,children:[(0,$.jsxs)(`div`,{className:`map-zoom`,role:`group`,"aria-label":`Zoom ${n}`,children:[(0,$.jsx)(`button`,{type:`button`,"aria-label":`Zoom out`,disabled:u<=.05,onClick:()=>d(e=>MJ(e/zJ)),children:`−`}),(0,$.jsx)(`button`,{type:`button`,onClick:w,children:`Fit`}),(0,$.jsxs)(`button`,{type:`button`,"aria-label":`Reset zoom to 100%. Current zoom ${b}%.`,title:`Reset zoom to 100%`,onClick:()=>d(1),children:[b,`%`]}),(0,$.jsx)(`button`,{type:`button`,"aria-label":`Zoom in`,disabled:u>=2.5,onClick:()=>d(e=>MJ(e*zJ)),children:`+`})]}),(0,$.jsx)(`div`,{ref:c,className:`map-scroll`,"data-panning":f||void 0,onPointerDown:D,onPointerMove:O,onPointerUp:k,onPointerCancel:k,onScroll:()=>{h.current&&(C(),S())},children:(0,$.jsx)(`div`,{className:`map-canvas__sized`,style:{width:e*u,height:t*u},children:typeof s==`function`?s({offsetX:T,revealPoint:E,scale:u,viewport:{...v,offsetX:T,scale:u}}):s})})]})}function VJ({ariaLabel:e,canvasClassName:t,drawScene:n,edgeCount:r,fallbackItem:i,hitTarget:a,itemForKey:o,itemIdentity:s,itemKey:c,itemLabel:l,itemPoint:u,itemReadout:d,keyboardTarget:f,onActivate:p,preferredKey:m,revealPoint:h,scene:g,viewport:v}){let y=(0,_.useRef)(null),b=(0,_.useRef)(null),x=(0,_.useId)(),S=(0,_.useRef)(m),[C,w]=(0,_.useState)(m??(i?c(i):null)),[T,E]=(0,_.useState)(null),[D,O]=(0,_.useState)(!1),[k,A]=(0,_.useState)(0),ee=(0,_.useMemo)(()=>CJ(getComputedStyle(document.documentElement)),[]);(0,_.useEffect)(()=>{let e=!1;return document.fonts?.ready?.then(()=>{e||A(e=>e+1)}),()=>{e=!0}},[]),(0,_.useEffect)(()=>{let e=S.current!==m;S.current=m,w(t=>e&&m!=null&&o(m)?m:o(t)?t:m!=null&&o(m)?m:i?c(i):null)},[i,o,c,m]);let te=C===null?null:o(C),j=T===null?null:o(T),ne=te?l(te):``,re=j??(D?te:null),M=g.kind===`architecture`?g.boxes:g.rows,N=te?M.findIndex(e=>c(e)===c(te))+1:0,ie=te?[...new TextEncoder().encode(String(s(te)))].map(e=>e.toString(16).padStart(2,`0`)).join(``):``,ae=N&&ie?`${x}-active-${N}-${ie}`:void 0;(0,_.useLayoutEffect)(()=>{let e=y.current;if(!e||v.width<1||v.height<1)return;let t=Math.min(2,Math.max(1,window.devicePixelRatio||1)),r=Math.max(1,Math.round(v.width*t)),i=Math.max(1,Math.round(v.height*t));e.width!==r&&(e.width=r),e.height!==i&&(e.height=i),e.style.width=`${v.width}px`,e.style.height=`${v.height}px`;let a=e.getContext(`2d`);a.setTransform(t,0,0,t,0,0),a.clearRect(0,0,v.width,v.height);let o=n(a,{activeKey:C,hoverKey:T,keyboardFocused:D,palette:ee,viewport:v});e.dataset.visibleItems=String(o.visibleItems),e.dataset.visibleEdges=String(o.visibleEdges)},[C,n,k,T,D,ee,v]);function oe(e){let t=y.current.getBoundingClientRect();return{x:(e.clientX-t.left+v.scrollLeft-(v.offsetX||0))/v.scale,y:(e.clientY-t.top+v.scrollTop)/v.scale}}function P(e){h(u(e),v.offsetX||0)}function se(e){if(e.key===`Enter`||e.key===` `){if(!te)return;e.preventDefault(),p(te);return}if(!/^(ArrowLeft|ArrowRight|ArrowUp|ArrowDown|Home|End)$/.test(e.key))return;e.preventDefault();let t=f(g,C,e.key);t&&(w(c(t)),P(t))}function F(e){return a(g,oe(e))}return(0,$.jsxs)(`div`,{className:`map-canvas-surface ${t}`,role:`listbox`,tabIndex:0,"aria-label":`${e}. Use arrow keys to move, Home or End to jump, and Enter or Space to open.`,"aria-activedescendant":ae,"data-item-count":M.length,"data-edge-count":r,onFocus:()=>O(!0),onBlur:()=>O(!1),onKeyDown:se,style:{width:v.width,height:v.height},children:[(0,$.jsx)(`canvas`,{ref:y,"aria-hidden":`true`,title:re?d(re):ne,onPointerDown:e=>{let t=F(e);b.current=t?c(t):null,t&&e.stopPropagation()},onPointerMove:e=>{if(e.buttons)return;let t=F(e);E(t?c(t):null)},onPointerLeave:()=>E(null),onClick:e=>{let t=F(e);!t||c(t)!==b.current||(w(c(t)),p(t))}}),re?(0,$.jsx)(`span`,{className:`map-canvas-readout`,"aria-hidden":`true`,children:d(re)}):null,te?(0,$.jsx)(`span`,{id:ae,role:`option`,"aria-selected":`true`,"aria-posinset":N,"aria-setsize":M.length,className:`map-canvas-active-option`,children:ne}):null]})}function HJ({data:e,mapTab:t,mode:n,communityIndexByRegion:r,selectedRegionId:i,hasEntrypointCandidates:a,unsupportedSources:o,importCycles:s,error:c,onSelectTab:l,onSelectRegion:u,onSelectNode:d,onRetry:f,viewportStore:p,languageFocusLabel:m=``,onClearLanguageFocus:h,children:g}){return(0,$.jsxs)(`section`,{className:`map-view`,"aria-label":`Two-dimensional project map`,children:[(0,$.jsxs)(`nav`,{className:`map-tabs`,"aria-label":`Map view`,children:[(0,$.jsx)(`button`,{type:`button`,"aria-pressed":t===`architecture`,onClick:()=>l(`architecture`),children:n===`easy`?`How it fits together`:`Architecture`}),(0,$.jsx)(`button`,{type:`button`,"aria-pressed":t===`workflow`,onClick:()=>l(`workflow`),children:n===`easy`?`What runs first`:`Workflow`})]}),g,c?(0,$.jsxs)(`div`,{className:`map-state`,role:`alert`,children:[(0,$.jsx)(`h2`,{children:`The map did not load.`}),(0,$.jsxs)(`p`,{children:[c,` The galaxy layer is unaffected.`]}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:f,children:`Try again`})]}):e?t===`architecture`?(0,$.jsx)(qJ,{architecture:e.architecture,mode:n,communityIndexByRegion:r,selectedRegionId:i,hasEntrypointCandidates:a,onSelectRegion:u,viewportStore:p,languageFocusLabel:m,onClearLanguageFocus:h}):(0,$.jsx)(JJ,{workflow:e.workflow,mode:n,selectedRegionId:i,hasEntrypointCandidates:a,onSelectNode:d,viewportStore:p,languageFocusLabel:m,onClearLanguageFocus:h}):(0,$.jsx)(`p`,{className:`map-loading`,role:`status`,children:`Laying out parser evidence…`}),UG(o,n)?(0,$.jsxs)(`p`,{className:`map-note`,children:[UG(o,n),n===`easy`?` — Codemble does not read that language yet, so none of it is drawn here.`:` — outside every registered adapter; no structure is inferred for them.`]}):null,gK(s,n)?(0,$.jsx)(`p`,{className:`map-note`,children:gK(s,n)}):null]})}var UJ=e=>e.id,WJ=e=>({x:e.drawX+e.width/2,y:e.drawY+e.height/2}),GJ=e=>e.order,KJ=e=>({x:e.drawX+8,y:e.drawY+16});function qJ({architecture:e,mode:t,communityIndexByRegion:n,selectedRegionId:r,hasEntrypointCandidates:i,onSelectRegion:a,viewportStore:o,languageFocusLabel:s,onClearLanguageFocus:c}){let l=(0,_.useMemo)(()=>new Set(e.unreachable),[e.unreachable]),u=e.boxes.length>0&&e.boxes.every(e=>l.has(e.id)),d=(0,_.useMemo)(()=>cJ(e,{communityIndexByRegion:n}),[e,n]),f=d.boxById.get(r)??d.boxById.get(e.home)??d.boxes.find(e=>e.home)??d.boxes[0]??null,p=f?WJ(f):null,m=(0,_.useCallback)(e=>d.boxById.get(e)??null,[d]),h=(0,_.useCallback)((e,n)=>{let i=DJ(e,d,n.viewport,n.palette,{activeId:n.activeKey,hoverId:n.hoverKey,keyboardFocused:n.keyboardFocused,mode:t,selectedId:r});return{visibleItems:i.visibleBoxes,visibleEdges:i.visibleEdges}},[t,d,r]),g=e.home?`${e.boxes.length} modules in ${e.layer_count} import layers from Home`:`${e.boxes.length} modules in ${e.layer_count} import layers, measured from the modules nothing imports`;return(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(BJ,{contentWidth:e.width+kJ.architecturePadding*2,contentHeight:e.height+kJ.architecturePadding*2,label:`the architecture map`,viewKey:`architecture:${e.home??`none`}`,viewportStore:o,focusPoint:p,centerInline:!0,children:({revealPoint:e,viewport:t})=>(0,$.jsx)(VJ,{ariaLabel:g,canvasClassName:`architecture-map-canvas`,drawScene:h,edgeCount:d.edges.length,fallbackItem:f,hitTarget:uJ,itemForKey:m,itemIdentity:e=>e.id,itemKey:UJ,itemLabel:pJ,itemPoint:WJ,itemReadout:e=>e.label,keyboardTarget:fJ,onActivate:e=>a(e.id),preferredKey:d.boxById.has(r)?r:f?.id,revealPoint:e,scene:d,viewport:t})}),e.home?null:(0,$.jsxs)(`p`,{className:`map-note`,children:[`No Home is selected, so these layers run from the modules nothing else imports rather than from your entrypoint. Both are read from your imports, not guessed.`,` `,i?`Pick your starting point with “Change Home” to see the same modules layered by what the project runs first.`:`This project has no parser-recognisable entrypoint, so there is no “runs first” order to layer by instead.`]}),u?(0,$.jsxs)(`p`,{className:`map-note`,children:[s?`Home is not written in ${s}, so none of these ${e.boxes.length} ${e.boxes.length===1?`module`:`modules`} has an import route to it. They are drawn in file order rather than layered by guesswork.`:`None of these ${e.boxes.length} ${e.boxes.length===1?`module`:`modules`} has an import route from Home, so they are drawn in file order rather than layered by guesswork.`,` `,s&&c?(0,$.jsx)(`button`,{type:`button`,className:`map-note__toggle`,onClick:c,children:`Show all languages`}):null]}):e.unreachable.length?(0,$.jsxs)(`p`,{className:`map-note`,children:[e.unreachable.length,` `,e.unreachable.length===1?`module has`:`modules have`,` no import route from Home, so `,e.unreachable.length===1?`it sits`:`they sit`,` `,`in the bottom rows rather than being placed by guesswork. Every module remains searchable, drawable, and keyboard reachable.`]}):null]})}function JJ({workflow:e,mode:t,selectedRegionId:n,hasEntrypointCandidates:r,onSelectNode:i,viewportStore:a,languageFocusLabel:o,onClearLanguageFocus:s}){return e.root&&e.nodes.length===0?(0,$.jsxs)(`div`,{className:`map-state`,children:[(0,$.jsx)(`h2`,{children:o?`Nothing in ${o} runs from Home.`:`Nothing runs from Home yet.`}),(0,$.jsx)(`p`,{children:o?`This tab follows what Home calls, and Home is not written in ${o} — so no ${o} structure appears on a parser-proven call route. The other tab still maps how these modules import each other.`:`This tab follows what Home calls, and the parser proved no call route out of it yet.`}),o&&s?(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:s,children:`Show all languages`}):null]}):e.root?(0,$.jsx)(YJ,{workflow:e,mode:t,selectedRegionId:n,onSelectNode:i,viewportStore:a}):r?(0,$.jsxs)(`div`,{className:`map-state`,children:[(0,$.jsx)(`h2`,{children:`No Home is selected.`}),(0,$.jsx)(`p`,{children:`The workflow tree starts at your entrypoint. Pick Home and this tab will show what runs first, then what that calls.`})]}):(0,$.jsxs)(`div`,{className:`map-state`,children:[(0,$.jsx)(`h2`,{children:`No “runs first” order to show.`}),(0,$.jsx)(`p`,{children:`This project has no parser-recognisable entrypoint — nothing here declares a startup structure Codemble recognises, and it will not guess one. The other tab still maps how your modules import each other.`})]})}function YJ({workflow:e,mode:t,selectedRegionId:n,onSelectNode:r,viewportStore:i}){let a=(0,_.useMemo)(()=>mJ(e),[e]),o=a.rows.find(e=>e.region===n)??null??a.rows.find(t=>t.id===e.root)??a.rows[0]??null,s=o?KJ(o):null,c=(0,_.useCallback)(e=>a.rowByOrder.get(Number(e))??null,[a]),l=(0,_.useCallback)((e,r)=>{let i=OJ(e,a,r.viewport,r.palette,{activeOrder:r.activeKey,hoverOrder:r.hoverKey,keyboardFocused:r.keyboardFocused,mode:t,selectedRegionId:n});return{visibleItems:i.visibleRows,visibleEdges:i.visibleEdges}},[t,a,n]);return(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(BJ,{contentWidth:e.width+kJ.workflowPadding*2,contentHeight:e.height+kJ.workflowPadding*2,label:`the workflow tree`,viewKey:`workflow:${e.root}`,viewportStore:i,focusPoint:s,children:({revealPoint:t,viewport:n})=>(0,$.jsx)(VJ,{ariaLabel:`Call tree from ${e.root}, ${e.nodes.length} steps deep to ${e.depth_count} levels`,canvasClassName:`workflow-map-canvas`,drawScene:l,edgeCount:a.edges.length,fallbackItem:o,hitTarget:gJ,itemForKey:c,itemIdentity:e=>`${e.order}:${e.id}:${e.file}:${e.lineno}`,itemKey:GJ,itemLabel:yJ,itemPoint:KJ,itemReadout:e=>`${e.label} · ${e.file}:${e.lineno}`,keyboardTarget:_J,onActivate:e=>r(e.id),preferredKey:o?.order,revealPoint:t,scene:a,viewport:n})}),e.unreachable.length?(0,$.jsxs)(`p`,{className:`map-note`,children:[e.unreachable.length,` `,e.unreachable.length===1?`structure is`:`structures are`,` never reached from Home by a parser-proven call. They are listed as unreached rather than attached to the tree by guesswork.`]}):null]})}var XJ=m(),ZJ=[{mode:`easy`,label:`New to coding?`},{mode:`expert`,label:`I build software`}],QJ=[{mode:`easy`,label:`Easy`},{mode:`expert`,label:`Expert`}];function $J({mode:e,modeChosen:t,onChoose:n}){let r=(0,_.useRef)(null);(0,_.useLayoutEffect)(()=>{let e=r.current;e&&t===!1&&!e.open&&e.showModal()},[t]);function i(e){r.current?.close(),n(e)}return t===null?null:t?(0,$.jsxs)(`fieldset`,{className:`mode-toggle`,children:[(0,$.jsx)(`legend`,{className:`mode-toggle__label`,children:`Audience`}),(0,$.jsx)(`div`,{className:`mode-toggle__options`,children:QJ.map(t=>(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`input`,{type:`radio`,name:`audience-mode`,value:t.mode,checked:e===t.mode,onChange:()=>i(t.mode)}),(0,$.jsx)(`span`,{children:t.label})]},t.mode))})]}):(0,XJ.createPortal)((0,$.jsxs)(`dialog`,{ref:r,className:`mode-gate`,"aria-labelledby":`mode-gate-heading`,"aria-describedby":`mode-gate-detail`,onCancel:e=>e.preventDefault(),children:[(0,$.jsx)(`h1`,{id:`mode-gate-heading`,children:`New to coding, or do you build software already?`}),(0,$.jsx)(`p`,{id:`mode-gate-detail`,children:`This changes how much Codemble explains and how much it assumes you already know.`}),(0,$.jsx)(`div`,{className:`mode-gate__options`,children:ZJ.map(e=>(0,$.jsx)(`button`,{type:`button`,onClick:()=>i(e.mode),children:e.label},e.mode))})]}),document.body)}function eY(e,t){let n=e?.steps??[];return n.length?n.some(e=>e.id===t)?t:n[0].id:null}function tY(e,t,n){let r=e?.steps??[];if(!r.length)return null;let i=eY(e,t),a=Math.max(0,r.findIndex(e=>e.id===i));return r[Math.max(0,Math.min(r.length-1,a+n))].id}function nY(e,t,n){let r=e?.steps??[],i=eY(e,t),a=r.findIndex(e=>e.id===i);if(a<0)return null;let o=r[a];return{id:o.id,index:a,total:r.length,position:`${a+1} of ${r.length}`,heading:rY(o),summary:iY(o),citation:o.declaration?.citation??o.citation??null,observationCitation:o.observation?.citation??null,isFirst:a===0,isLast:a===r.length-1,isTarget:!!o.is_target,expertDetails:n===`expert`?{layer:String(o.layer??`unknown`).replaceAll(`-`,` `),relation:o.relation??`unknown`,ruleId:o.rule_id??null,nodeId:o.node_id??null}:null}}function rY(e){return e.relation===`home`?`Start at Home: ${e.name}`:e.relation===`import`?`Open ${e.name}`:e.relation===`call`?`Follow the call to ${e.name}`:e.relation===`role`?e.role===`ui-renderer`?`Reach the screen in ${e.name}`:e.role===`test`?`Enter through test surface ${e.name}`:`Enter through ${e.name}`:`Read ${e.name}`}function iY(e){return e.relation===`home`?`The parser ranked this as the project's starting point.`:e.relation===`import`?`A certain import connects the previous file to this declaration.`:e.relation===`call`?e.is_target?`A certain call reaches the structure you selected.`:`A certain call continues the runtime route through this structure.`:e.relation===`role`?e.role===`route-handler`?`A parser-proven route registration is where a request reaches the application.`:e.role===`ui-renderer`?`Parser-proven rendering syntax connects this function to the visible interface.`:e.role===`test`?`Only a parser-proven test surface reaches this route; it is not an application screen.`:`The parser proved this is an application entry before the runtime route continues.`:`This is the next parser-proven declaration on the route.`}function aY({node:e,study:t,error:n,mode:r,explanation:i,explanationLoading:a,explanationError:o,llmStatus:s,onSelectNode:c,onRetryStudy:l,onRetryNarration:u,onClose:d,revealSource:f=!1}){let p=(0,_.useRef)(null),m=(0,_.useRef)(null),h=(0,_.useRef)(null),g=(0,_.useRef)(null),v=(0,_.useRef)(null),y=(0,_.useRef)(null),b=t?.learning_journey??null,[x,S]=(0,_.useState)(null),C=!!t?.source;(0,_.useEffect)(()=>{p.current?.scrollTo({top:0,behavior:`auto`}),m.current?.focus({preventScroll:!0})},[e.id,f]),(0,_.useEffect)(()=>{n&&(p.current?.scrollTo({top:0,behavior:`auto`}),h.current?.focus({preventScroll:!0}))},[n,e.id]),(0,_.useEffect)(()=>{!f||!C||y.current!==e.id&&(y.current=e.id,g.current?.scrollIntoView({block:`start`,behavior:`auto`}),v.current?.focus({preventScroll:!0}))},[f,C,e.id]),(0,_.useEffect)(()=>{S(e=>eY(b,e))},[b]);function w(){p.current?.scrollTo({top:0,behavior:`auto`}),m.current?.focus({preventScroll:!0}),l()}function T(){m.current?.focus({preventScroll:!0}),u()}return(0,$.jsxs)(`aside`,{ref:p,className:`study-preview`,"aria-label":`Selected source structure`,"aria-busy":!t&&!n,children:[(0,$.jsxs)(`header`,{className:`study-preview__header`,children:[d?(0,$.jsx)(`button`,{className:`check-close study-preview__close`,type:`button`,onClick:d,children:`Close`}):null,(0,$.jsxs)(`p`,{className:`study-preview__path`,children:[e.file,`:`,e.lineno]}),(0,$.jsx)(`h1`,{ref:m,tabIndex:-1,children:e.name})]}),n?(0,$.jsxs)(`section`,{className:`study-notice`,role:`alert`,children:[(0,$.jsx)(`h2`,{ref:h,tabIndex:-1,children:`Study data did not load.`}),(0,$.jsxs)(`p`,{children:[n,` The parser map is still available.`]}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:w,children:`Try again`})]}):null,!t&&!n?(0,$.jsx)(`p`,{className:`study-loading`,role:`status`,children:`Reading parser evidence…`}):null,(0,$.jsxs)(`div`,{className:`study-content`,children:[t?(0,$.jsx)(sY,{journey:b,activeStepId:x,onActiveStepChange:S,impact:t.impact,neighbors:t.neighbors,node:e,mode:r,onSelectNode:c}):null,t?(0,$.jsx)(oY,{node:e,mode:r}):null,t?(0,$.jsx)(lY,{structural:t.structural,mode:r}):null,(0,$.jsx)(SY,{explanation:i,loading:a,error:o,llmStatus:s,mode:r,node:e,onSelectNode:c,onRetry:T}),t?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(bY,{source:t.source,anchorRef:g,headingRef:v}),(0,$.jsx)(yY,{lens:t.lens,language:e.language,mode:r})]}):null]})]})}function oY({node:e,mode:t}){return(0,$.jsx)(`section`,{className:`study-facts`,"aria-label":`Selected structure facts`,children:(0,$.jsxs)(`dl`,{children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`What it is`:`Kind`}),(0,$.jsx)(`dd`,{children:e.kind})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`Length`:`Span`}),(0,$.jsxs)(`dd`,{children:[e.loc,` `,e.loc===1?`line`:`lines`]})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`Called by`:`Callers`}),(0,$.jsx)(`dd`,{children:e.centrality})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:t===`easy`?`Evidence`:`Resolution`}),(0,$.jsx)(`dd`,{children:e.partial?t===`easy`?`Could not be fully read`:`Partial parse`:t===`easy`?`Proven from your code`:`Parser-proven`})]})]})})}function sY({journey:e,activeStepId:t,onActiveStepChange:n,impact:r,neighbors:i,node:a,mode:o,onSelectNode:s}){let[c,l]=(0,_.useState)(``),u=nY(e,t,o),d=e?.steps??[],f=e?.possible_frontier??[],p=e?.verification_candidates??[];(0,_.useEffect)(()=>l(``),[e?.fingerprint]);function m(t){n(t);let r=nY(e,t,o);r&&l(`Journey step ${r.position}: ${r.heading}`)}function h(n){let r=tY(e,t,n);r&&m(r)}return(0,$.jsxs)(`section`,{className:`learning-journey`,"aria-labelledby":`learning-journey-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`learning-journey-heading`,children:`Feature journey`}),(0,$.jsx)(`span`,{children:`Parser evidence`})]}),d.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`ol`,{className:`journey-overview`,"aria-label":`Feature journey steps`,children:d.map((t,n)=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-current":t.id===u?.id?`step`:void 0,"aria-label":`Step ${n+1}: ${nY(e,t.id,o)?.heading}`,onClick:()=>m(t.id),children:[(0,$.jsx)(`span`,{children:n+1}),(0,$.jsx)(`small`,{children:t.layer.replaceAll(`-`,` `)})]})},t.id))}),u?(0,$.jsxs)(`article`,{className:`journey-current`,"data-step-id":u.id,children:[(0,$.jsxs)(`p`,{className:`journey-position`,children:[`Step `,u.position]}),(0,$.jsx)(`h3`,{children:u.heading}),(0,$.jsx)(`p`,{children:u.summary}),(0,$.jsxs)(`div`,{className:`journey-citations`,children:[u.observationCitation?(0,$.jsxs)(`span`,{children:[`Observed at `,(0,$.jsx)(`code`,{children:u.observationCitation})]}):null,(0,$.jsxs)(`span`,{children:[`Declaration `,(0,$.jsx)(`code`,{children:u.citation})]})]}),(0,$.jsxs)(`div`,{className:`journey-controls`,"aria-label":`Journey controls`,children:[(0,$.jsx)(`button`,{type:`button`,onClick:()=>h(-1),disabled:u.isFirst,children:`Back`}),(0,$.jsx)(`button`,{type:`button`,onClick:()=>h(1),disabled:u.isLast,children:`Next`}),(0,$.jsx)(`button`,{type:`button`,onClick:()=>m(d[0].id),disabled:u.isFirst,children:`Replay`})]}),u.expertDetails?(0,$.jsxs)(`dl`,{className:`journey-expert-facts`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Layer`}),(0,$.jsx)(`dd`,{children:u.expertDetails.layer})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Relation`}),(0,$.jsx)(`dd`,{children:u.expertDetails.relation})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Node ID`}),(0,$.jsx)(`dd`,{children:u.expertDetails.nodeId})]}),u.expertDetails.ruleId?(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Parser rule`}),(0,$.jsx)(`dd`,{children:u.expertDetails.ruleId})]}):null]}):null]}):null]}):null,(0,$.jsx)(`span`,{className:`visually-hidden`,"aria-live":`polite`,"aria-atomic":`true`,children:c}),e?.break?(0,$.jsxs)(`div`,{className:`journey-break`,role:`note`,children:[(0,$.jsx)(`strong`,{children:`Proof stops here`}),(0,$.jsx)(`p`,{children:e.break.message}),e.break.citation?(0,$.jsx)(`code`,{children:e.break.citation}):null]}):null,f.length?(0,$.jsxs)(`div`,{className:`journey-possible`,children:[(0,$.jsx)(`h3`,{children:`Possible next evidence`}),(0,$.jsx)(`p`,{children:`These parser observations are not part of the proven route.`}),(0,$.jsx)(`ul`,{children:f.map(e=>(0,$.jsxs)(`li`,{children:[(0,$.jsxs)(`strong`,{children:[`Possible `,e.relation]}),(0,$.jsxs)(`span`,{children:[e.observation.citation,` → `,e.declaration.citation]})]},`${e.relation}-${e.source_node_id}-${e.target_node_id}-${e.observation.citation}`))})]}):null,(0,$.jsxs)(`details`,{className:`journey-support`,open:o===`expert`,children:[(0,$.jsx)(`summary`,{children:o===`easy`?`Explore selected feature impact and connections`:`Selected feature impact, connections, and verification`}),(0,$.jsxs)(`p`,{className:`journey-support-scope`,children:[`These facts belong to `,a.name,`, the selected feature. They stay fixed while you move between journey steps.`]}),(0,$.jsx)(uY,{impact:r,mode:o,onSelectNode:s}),(0,$.jsx)(pY,{neighbors:i,node:a,mode:o,onSelectNode:s}),o===`expert`?(0,$.jsx)(cY,{items:p,onSelectNode:s}):null]})]})}function cY({items:e,onSelectNode:t}){return(0,$.jsxs)(`section`,{className:`journey-verification`,"aria-labelledby":`journey-verification-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`journey-verification-heading`,children:`Selected feature verification candidates`}),(0,$.jsx)(`span`,{children:`Parser-linked`})]}),e.length?(0,$.jsx)(`ul`,{className:`impact-list`,children:e.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-label":`Open verification candidate ${e.name}; ${e.certain?`certain route`:`possible route`}; depth ${e.depth}; ${e.declaration.citation}`,onClick:()=>t(e.node_id),children:[(0,$.jsx)(`span`,{className:`impact-name`,children:e.name}),(0,$.jsxs)(`span`,{className:`impact-meta`,children:[`depth `,e.depth,e.certain?` · certain route`:` · possible route`]}),(0,$.jsx)(`span`,{className:`impact-citation`,children:e.declaration.citation})]})},e.node_id))}):(0,$.jsx)(`p`,{className:`study-loading`,children:`No connected test evidence was found.`}),(0,$.jsx)(`p`,{className:`journey-candidate-note`,children:`These are parser-linked candidate verification points, not proof that a test passes.`})]})}function lY({structural:e,mode:t}){return e?(0,$.jsxs)(`section`,{className:`structural-summary`,"aria-labelledby":`structural-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`structural-heading`,children:t===`easy`?`What this is`:`Structural summary`}),(0,$.jsx)(`span`,{children:`No model needed`})]}),(0,$.jsx)(`p`,{children:e[t]??e.easy})]}):null}function uY({impact:e,mode:t,onSelectNode:n}){if(!e)return null;let r=e.affects??[],i=e.depends_on??[];if(!r.length&&!i.length)return null;let a=t===`easy`;return(0,$.jsxs)(`section`,{className:`impact-widget`,"aria-labelledby":`impact-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`impact-heading`,children:a?`What the selected feature touches`:`Selected feature impact`}),(0,$.jsx)(`span`,{children:`No model needed`})]}),(0,$.jsxs)(`div`,{className:`impact-columns`,children:[(0,$.jsx)(dY,{title:a?`Change this and these change too`:`Change this → affected`,empty:a?`Nothing else in your code would notice if you changed this.`:`No parser-proven dependents.`,items:r,mode:t,onSelectNode:n}),(0,$.jsx)(dY,{title:a?`This needs these to work`:`Depends on → breaks if changed`,empty:a?`This does not rely on anything else in your code.`:`No parser-proven dependencies.`,items:i,mode:t,onSelectNode:n})]}),e.truncated?(0,$.jsx)(`p`,{className:`study-loading`,children:a?`Traced ${e.max_depth} steps out; the chain continues past that.`:`Traced to depth ${e.max_depth}; deeper reach is not shown.`}):null]})}function dY({title:e,empty:t,items:n,mode:r,onSelectNode:i}){return(0,$.jsxs)(`div`,{className:`impact-column`,children:[(0,$.jsx)(`h3`,{children:e}),n.length===0?(0,$.jsx)(`p`,{className:`study-loading`,children:t}):(0,$.jsx)(`ul`,{className:`impact-list`,children:n.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-label":`${e.name} — ${e.depth===1?`direct`:`${e.depth} steps away`}${e.certain?``:`, possible`} — ${e.citation}`,onClick:()=>i(e.node_id),children:[(0,$.jsx)(`span`,{className:`impact-name`,children:e.name}),(0,$.jsxs)(`span`,{className:`impact-meta`,children:[e.depth===1?r===`easy`?`directly`:`direct`:r===`easy`?`${e.depth} steps away`:`depth ${e.depth}`,e.certain?``:r===`easy`?` · possible link`:` · possible`]}),(0,$.jsx)(`span`,{className:`impact-citation`,children:e.citation})]})},e.node_id))})]})}var fY=8;function pY({neighbors:e,node:t,mode:n,onSelectNode:r}){let i=e??[],a=i.filter(e=>e.direction===`inbound`),o=i.filter(e=>e.direction===`outbound`);return(0,$.jsxs)(`section`,{className:`connections`,"aria-labelledby":`connections-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{id:`connections-heading`,children:n===`easy`?`What the selected feature connects to`:`Selected feature connections`}),(0,$.jsxs)(`span`,{children:[i.length,` parser `,i.length===1?`relationship`:`relationships`]})]}),i.length===0?(0,$.jsx)(`p`,{className:`study-loading`,children:n===`easy`?`Nothing in your code reaches this yet, and it does not reach anything else.`:`The parser observed no relationship into or out of this structure.`}):(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(vY,{inbound:a,outbound:o,node:t}),(0,$.jsx)(mY,{title:n===`easy`?`Uses this`:`Inbound`,items:a,mode:n,onSelectNode:r}),(0,$.jsx)(mY,{title:n===`easy`?`This uses`:`Outbound`,items:o,mode:n,onSelectNode:r})]})]})}function mY({title:e,items:t,mode:n,onSelectNode:r}){return t.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`h3`,{children:e}),(0,$.jsx)(`ul`,{className:`connection-list`,children:t.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-label":`${e.name} — ${hY(e,n)}, ${gY(e,n)} — ${e.citation}`,onClick:()=>r(e.node_id),children:[(0,$.jsx)(`span`,{className:`connection-name`,children:e.name}),(0,$.jsxs)(`span`,{className:`connection-meta`,children:[hY(e,n),` · `,gY(e,n)]}),(0,$.jsx)(`span`,{className:`source-citation`,children:e.citation})]})},`${e.direction}-${e.node_id}`))})]}):null}function hY(e,t){return e.relationship===`import`?e.direction===`inbound`?t===`easy`?`brings this in`:`import · inbound`:t===`easy`?`this brings it in`:`import · outbound`:e.direction===`inbound`?t===`easy`?`calls this`:`call · inbound`:t===`easy`?`this calls it`:`call · outbound`}function gY(e,t){return e.certain?`certain`:e.relationship===`import`?t===`easy`?`possible link, not certain`:`possible import`:t===`easy`?`possible link, not certain`:`possible call`}function _Y(e){let t=String(e??``);return t.length<=18?t:`…${t.slice(-17)}`}function vY({inbound:e,outbound:t,node:n}){let r=e.slice(0,fY),i=t.slice(0,fY),a=Math.max(r.length,i.length,1)*22+16,o=a/2,s=(e,t)=>(e+1)*a/(t+1);return(0,$.jsxs)(`svg`,{className:`mini-constellation`,viewBox:`0 0 280 ${a}`,role:`img`,"aria-label":`${e.length} inbound and ${t.length} outbound parser relationships for ${n.name}`,children:[r.map((e,t)=>(0,$.jsx)(`line`,{x1:`26`,y1:s(t,r.length),x2:`132`,y2:o,strokeDasharray:e.certain?void 0:`3 3`},`in-line-${e.node_id}`)),r.map((e,t)=>(0,$.jsx)(`text`,{className:`mini-constellation__name`,x:`8`,y:s(t,r.length)-7,textAnchor:`start`,children:_Y(e.name)},`in-name-${e.node_id}`)),i.map((e,t)=>(0,$.jsx)(`text`,{className:`mini-constellation__name`,x:`272`,y:s(t,i.length)-7,textAnchor:`end`,children:_Y(e.name)},`out-name-${e.node_id}`)),i.map((e,t)=>(0,$.jsx)(`line`,{x1:`148`,y1:o,x2:`254`,y2:s(t,i.length),strokeDasharray:e.certain?void 0:`3 3`},`out-line-${e.node_id}`)),r.map((e,t)=>(0,$.jsx)(`circle`,{cx:`22`,cy:s(t,r.length),r:`4`},`in-dot-${e.node_id}`)),i.map((e,t)=>(0,$.jsx)(`circle`,{cx:`258`,cy:s(t,i.length),r:`4`},`out-dot-${e.node_id}`)),(0,$.jsx)(`circle`,{className:`mini-constellation__self`,cx:`140`,cy:o,r:`6`})]})}function yY({lens:e,language:t,mode:n}){return e?.length?(0,$.jsxs)(`section`,{className:`lens-study`,"aria-labelledby":`lens-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsxs)(`h2`,{id:`lens-heading`,children:[IG(t),` lens`]}),(0,$.jsxs)(`span`,{children:[e.length,` detected`]})]}),(0,$.jsx)(`div`,{className:`lens-notes`,children:e.map(e=>(0,$.jsxs)(`article`,{className:`lens-note`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`h3`,{children:e.title}),(0,$.jsx)(wY,{citation:e.citation,fallbackLine:e.line})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`p`,{children:e.note_voices[n]}),(0,$.jsx)(`code`,{children:e.snippet})]})]},`${e.concept}-${e.line}-${e.snippet}`))})]}):null}function bY({source:e,anchorRef:t,headingRef:n}){return(0,$.jsxs)(`section`,{className:`source-study`,"aria-labelledby":`source-heading`,ref:t,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,children:[(0,$.jsx)(`h2`,{ref:n,id:`source-heading`,tabIndex:-1,children:`Real source`}),(0,$.jsxs)(`span`,{title:`${e.file}:${e.start_line}–${e.end_line}`,children:[e.file,`:`,e.start_line,`–`,e.end_line]})]}),(0,$.jsx)(`ol`,{className:`source-code`,start:e.start_line,"aria-label":`Source excerpt from ${e.file}`,children:e.lines.map(e=>(0,$.jsx)(`li`,{id:`source-L${e.number}`,"data-line":e.number,children:(0,$.jsx)(`code`,{children:e.text||` `})},e.number))})]})}var xY={grounding:`The explanation was withheld.`,unavailable:`Codemble could not reach the model.`,rejected:`The model refused the request.`,timeout:`The model is taking longer than expected.`,provider:`The model's reply could not be read.`};function SY({explanation:e,loading:t,error:n,llmStatus:r,mode:i,node:a,onSelectNode:o,onRetry:s}){if(t)return(0,$.jsx)(`p`,{className:`study-loading`,role:`status`,children:i===`easy`?`Asking your model to explain this in plain language…`:`Requesting a grounded narration for this structure…`});if(n)return(0,$.jsxs)(`section`,{className:`study-notice`,role:`alert`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:`The explanation request failed.`}),(0,$.jsx)(`p`,{children:n}),(0,$.jsx)(`p`,{children:`Every fact above and below this block came from the parser and is unaffected.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:s,children:`Try again`})]});if(!e)return null;if(e.status===`no_key`)return(0,$.jsx)(CY,{message:e.message,llmStatus:r,mode:i});if(e.status===`error`||e.status===`timeout`){let t=e.reason===`grounding`;return(0,$.jsxs)(`section`,{className:`study-notice`,role:`alert`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:xY[e.reason]??xY.provider}),(0,$.jsx)(`p`,{children:e.message}),t?(0,$.jsx)(`p`,{children:`Codemble will not display provider output that falls outside parser evidence.`}):null,(0,$.jsx)(`p`,{children:`Every fact from the parser on this panel is unaffected.`}),e.retryable===!1?null:(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:s,children:`Try again`})]})}return e.status===`partial`?(0,$.jsxs)(`section`,{className:`study-notice`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:`Narration stays off for partial source.`}),(0,$.jsx)(`p`,{children:e.message})]}):(0,$.jsxs)(`section`,{className:`grounded-explanation`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsxs)(`div`,{className:`study-section-heading`,role:`status`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:i===`easy`?`In plain language`:`Grounded explanation`}),(0,$.jsx)(`span`,{children:e.cached?`Local cache`:e.provider})]}),(0,$.jsxs)(`p`,{children:[e.summary.text,` `,(0,$.jsx)(wY,{citation:e.summary.citation,fallbackLine:a.lineno})]}),e.excerpt?.truncated?(0,$.jsx)(`p`,{className:`study-loading`,children:i===`easy`?`This file is long, so the model was shown lines ${e.excerpt.first}–${e.excerpt.last} of ${e.excerpt.total}.`:`Narrated from an excerpt: lines ${e.excerpt.first}–${e.excerpt.last} of ${e.excerpt.total}.`}):null,e.withheld>0?(0,$.jsxs)(`p`,{className:`study-loading`,children:[e.withheld,` `,e.withheld===1?`part`:`parts`,` of the reply `,e.withheld===1?`was`:`were`,` malformed and left out.`]}):null,e.walkthrough.length?(0,$.jsxs)(`details`,{className:`walkthrough-disclosure`,children:[(0,$.jsx)(`summary`,{children:i===`easy`?`Walk me through it line by line (${e.walkthrough.length})`:`Line walkthrough (${e.walkthrough.length})`}),(0,$.jsx)(`ul`,{className:`evidence-list`,children:e.walkthrough.map(e=>(0,$.jsxs)(`li`,{children:[(0,$.jsx)(`p`,{children:e.text}),(0,$.jsx)(wY,{citation:e.citation,fallbackLine:e.line})]},`${e.citation}-${e.text}`))})]}):null,e.relationships.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`h3`,{children:i===`easy`?`How it fits in`:`Parser relationships`}),(0,$.jsx)(`ul`,{className:`evidence-list`,children:e.relationships.map(e=>(0,$.jsxs)(`li`,{children:[(0,$.jsx)(`strong`,{children:e.certain?e.node_id:`Possible: ${e.node_id}`}),(0,$.jsx)(`p`,{children:e.text}),(0,$.jsxs)(`button`,{className:`source-citation source-citation--button`,type:`button`,onClick:()=>o(e.node_id),children:[`Study `,e.citation]})]},`${e.node_id}-${e.text}`))})]}):null]})}function CY({message:e,llmStatus:t,mode:n}){let r=t?.ollama??null;return(0,$.jsxs)(`section`,{className:`study-notice`,"aria-labelledby":`explanation-heading`,children:[(0,$.jsx)(`h2`,{id:`explanation-heading`,children:n===`easy`?`The plain-language write-up needs a model.`:`No narration provider is configured.`}),(0,$.jsx)(`p`,{children:e}),r?(0,$.jsx)(`p`,{children:r.running?`Ollama is already running on this machine. Set CODEMBLE_PROVIDER=ollama and CODEMBLE_OLLAMA_MODEL=${r.recommended}, then restart Codemble to narrate without sending code anywhere.`:`Want to stay fully local? Install Ollama, run "ollama pull ${r.recommended}" (or ${r.fallback} on a smaller machine), set CODEMBLE_PROVIDER=ollama, then restart Codemble.`}):null,(0,$.jsx)(`p`,{children:`Everything else on this panel is parser evidence and works without any model at all.`})]})}function wY({citation:e,fallbackLine:t}){return(0,$.jsx)(`a`,{className:`source-citation`,href:`#source-L${Number(e.split(`:`).at(-1))||t}`,children:e})}var TY=Object.freeze([]),EY=new Set;function DY(){let e=new WeakMap,t=new WeakMap,n=new WeakMap,r=new WeakMap,i=new WeakMap,a=new WeakMap,o=null,s=null,c=null,l=null;function u(e){let t=e.graph,n=t?d(t,e.languageFocus):null,r=n?p(n):null,i=e.level,a=r?.regionById.get(e.region?.id)??null,o=e.selectedNode;n&&(a?o&&(o=r.nodeById.get(o.id)??null,!o&&i===jG.STUDY&&(i=jG.SYSTEM)):(a=mK(n),o=null,i=jG.GALAXY));let s=e.studiedNodeIds;return{...e,focusedGraph:n,focusedMapData:f(e.mapData,e.languageFocus),entrypointOpen:!!t&&!e.entrypointDismissed,level:i,region:a,selectedNode:o,languageOptions:t?m(t):TY,projectName:t?g(t):`Loading local project`,chart:n?_(n,s):TY,focusedStudiedCount:n?v(n,s):0,hint:n?y(n,e.mode,i,a?.id??null,e.layer,t,e.languageFocus):null,revealedRegionIds:n?b(n,e.showAll,a?.id??null,e.visitedRegionIds):EY,moduleIndex:n?h(n):TY}}function d(t,n){let r=e.get(t);r||(r=new Map,e.set(t,r));let i=n||`all`;return r.has(i)||r.set(i,MG(t,n)),r.get(i)}function f(e,n){if(!e)return e;let r=t.get(e);r||(r=new Map,t.set(e,r));let i=n||`all`;return r.has(i)||r.set(i,NG(e,n)),r.get(i)}function p(e){return n.has(e)||n.set(e,{nodeById:new Map(e.nodes.map(e=>[e.id,e])),regionById:new Map(e.regions.map(e=>[e.id,e]))}),n.get(e)}function m(e){return r.has(e)||r.set(e,PG(e)),r.get(e)}function h(e){return i.has(e)||i.set(e,qG(e)),i.get(e)}function g(e){return a.has(e)||a.set(e,hK(e)),a.get(e)}function _(e,t){return(!o||o.graph!==e||o.studiedNodeIds!==t)&&(o={graph:e,studiedNodeIds:t,value:FG(e,t)}),o.value}function v(e,t){return(!s||s.graph!==e||s.studiedNodeIds!==t)&&(s={graph:e,studiedNodeIds:t,value:e.nodes.filter(e=>t.has(e.id)).length}),s.value}function y(e,t,n,r,i,a,o){return(!c||c.graph!==e||c.mode!==t||c.level!==n||c.regionId!==r||c.layer!==i||c.projectGraph!==a||c.languageFocus!==o)&&(c={graph:e,mode:t,level:n,regionId:r,layer:i,projectGraph:a,languageFocus:o,value:AY(e,{mode:t,level:n,regionId:r,layer:i,projectGraph:a,languageFocus:o})}),c.value}function b(e,t,n,r){return(!l||l.graph!==e||l.showAll!==t||l.selectionId!==n||l.visitedIds!==r)&&(l={graph:e,showAll:t,selectionId:n,visitedIds:r,value:HG(e,{showAll:t,selectionId:n,visitedIds:r})}),l.value}return Object.freeze({derive:u})}var OY=1.5;function kY({homeChosen:e,homeOutsideFocus:t,languageFocus:n,hops:r}){return e?t?`Home is not written in ${LG(n)}.`:r===0?`Home is not lit yet.`:Number.isFinite(r)?`${r} ${r===1?`route`:`routes`} from Home.`:`No import route reaches it from Home.`:`No Home is chosen, so there is no route to measure from.`}function AY(e,{mode:t,level:n,regionId:r,layer:i,projectGraph:a,languageFocus:o}){if(t!==`easy`)return null;let s=e.regions.filter(e=>!e.understood);if(!s.length)return null;let c=(a??e).regions.some(e=>e.home),l=c&&!e.regions.some(e=>e.home),u=GG(e),d=s.map(e=>{let t=typeof e.hops_from_home==`number`?e.hops_from_home:1/0;return{regionId:e.id,hops:t,biasedHops:ZG(u.get(e.id)??``)?t+OY:t,structures:typeof e.node_count==`number`?e.node_count:0}}).sort((e,t)=>e.biasedHops-t.biasedHops||t.structures-e.structures||e.regionId.localeCompare(t.regionId))[0],{biasedHops:f,...p}=d,m={...p,message:`Study ${d.regionId} next`,reason:kY({homeChosen:c,homeOutsideFocus:l,languageFocus:o,hops:d.hops})};return n===jG.STUDY?r===d.regionId?{...m,message:`Prove you understand ${d.regionId}`,reason:`Questions come from this file, not from a model.`,action:{type:`OPEN_CHECKS`},actionLabel:`Prove understanding`}:{...m,action:{type:`OPEN_REGION`,regionId:d.regionId},actionLabel:`Open ${d.regionId}`}:n!==jG.SYSTEM||r!==d.regionId?{...m,action:{type:`OPEN_REGION`,regionId:d.regionId},actionLabel:`Open ${d.regionId}`}:i===`map`?e.nodes.some(e=>e.id===d.regionId)?{...m,reason:`Read it before proving it.`,action:null,actionLabel:null}:{...m,reason:`Its parser-proven structures are visible in Galaxy.`,action:{type:`SET_LAYER`,layer:`galaxy`},actionLabel:`View structures`}:{...m,reason:`Choose one of its parser-proven structures.`,action:null,actionLabel:null}}var jY=`Codemble's local server is not responding. Your source files and saved progress are unchanged. Start Codemble again, then try again.`;function MY(e){return Number.isInteger(e?.status)?NY(e):jY}function NY(e){return e instanceof Error?e.message:String(e)}var PY=Object.freeze({setTimeout(e,t){return globalThis.setTimeout(e,t)},clearTimeout(e){globalThis.clearTimeout(e)}}),FY=300,IY=400,LY=4e3,RY=8,zY=Object.freeze([Object.freeze({id:`discovering`,copy:`Finding your source files`}),Object.freeze({id:`parsing`,copy:`Reading each file`}),Object.freeze({id:`resolving`,copy:`Connecting imports and calls`}),Object.freeze({id:`checks`,copy:`Building graph-only checks`}),Object.freeze({id:`layout`,copy:`Placing your galaxy`})]);function BY({adapter:e,clock:t=PY,onReady:n=async()=>{}}={}){if(!e)throw TypeError(`Project mapping requires a learner adapter.`);let r=new Set,i=VY({phase:`idle`,error:``,picker:null,progress:null,failure:null}),a=0,o=null,s=null,c=null,l=null;function u(){return i}function d(e){return r.add(e),()=>r.delete(e)}function f(e){i=VY({...i,...e});for(let e of r)e()}async function p(){a+=1;let t=a;b(),HY(o),o=new AbortController;let n=o;f({phase:`idle`,error:``,picker:null,progress:null,failure:null});try{let[r,o]=await Promise.all([e.loadRecents({signal:n.signal}),e.browsePicker(null,{signal:n.signal})]);if(t!==a||n.signal.aborted)return i;f({phase:`picking`,picker:{...o,recents:r.recents,error:``,retryPath:null,scale:null,busy:!1}})}catch(e){!UY(e)&&t===a&&f({phase:`error`,error:MY(e)})}return i}async function m(t){if(i.phase!==`picking`||i.picker?.busy)return;HY(o),o=new AbortController;let n=o,r=a;f({failure:null});try{let s=await e.browsePicker(t,{signal:n.signal});o===n&&r===a&&!n.signal.aborted&&i.phase===`picking`&&f({picker:{...i.picker,...s,error:``,retryPath:null,scale:null}})}catch(e){o===n&&r===a&&!UY(e)&&i.phase===`picking`&&f({picker:{...i.picker,error:MY(e),retryPath:t}})}}async function h(t){if(i.phase!==`picking`||i.picker?.busy)return;a+=1;let r=a;HY(o),b(),o=new AbortController;let s=o;f({failure:null,picker:{...i.picker,busy:!0,error:``,retryPath:null,scale:null}});try{let o=await e.selectProject(t,{signal:s.signal});if(r!==a||s.signal.aborted||i.phase!==`picking`)return o;if(o.state===`parsing`)return f({progress:{state:`parsing`,stage:`discovering`,detail:null,files_done:0,files_total:0,error:null,pollError:``,attempts:0,pollOutage:!1,path:t}}),_(0,r),o;if(o.state===`ready`)return y(),await n(),o;if(o.state===`scale`){let t=await e.browsePicker(o.root,{signal:s.signal});return r===a&&!s.signal.aborted&&i.phase===`picking`&&f({picker:{...i.picker,...t,busy:!1,scale:o}}),o}let c=o.detail??``;return f({picker:{...i.picker,busy:!1,error:c},failure:c?{path:t,detail:c}:null}),o}catch(e){if(r===a&&!UY(e)&&i.phase===`picking`){let n=GY(e);f({picker:{...i.picker,busy:!1,error:n},failure:{path:t,detail:n}})}return}}async function g(){HY(c),c=new AbortController;let t=c;try{await e.resetProject({signal:t.signal})}catch(e){if(UY(e))return!1;if(WY(e))throw e}return t.signal.aborted?!1:(a+=1,HY(o),o=null,b(),f({phase:`idle`,error:``,picker:null,progress:null,failure:null}),!0)}function _(e,n){l!==null&&t.clearTimeout(l),l=t.setTimeout(async()=>{l=null,await v(n)},e)}async function v(t){if(!i.progress||t!==a)return;HY(s),s=new AbortController;let r=s,o=i.progress,c;try{c=await e.fetchParseProgress({signal:r.signal})}catch(e){if(s!==r||t!==a||UY(e)||!i.progress)return;let n=o.attempts+1;f({progress:{...o,pollError:GY(e),attempts:n,pollOutage:n>=RY}}),_(Math.min(LY,IY*2**(n-1)),t);return}if(!(s!==r||t!==a||!i.progress)){if(c.state===`ready`){y(),await n();return}if(c.state===`error`||c.state===`idle`){b();let e=c.error??``;f({progress:null,picker:{...i.picker,busy:!1,error:e},failure:e?{path:o.path,detail:e}:null});return}f({progress:{...o,...c,pollError:``,attempts:0,pollOutage:!1}}),_(FY,t)}}function y(){a+=1,b(),f({phase:`idle`,error:``,picker:null,progress:null,failure:null})}function b(){HY(s),s=null,l!==null&&(t.clearTimeout(l),l=null)}function x(){a+=1;for(let e of[o,s,c])HY(e);o=null,c=null,b(),r.clear()}return Object.freeze({browse:m,dispose:x,getSnapshot:u,reset:g,select:h,start:p,subscribe:d})}function VY(e){return Object.freeze(e)}function HY(e){e&&!e.signal.aborted&&e.abort()}function UY(e){return e?.name===`AbortError`}function WY(e){return Number.isInteger(e?.status)}function GY(e){return e instanceof Error?e.message:String(e)}var KY=Object.freeze({setTimeout(e,t){return globalThis.setTimeout(e,t)},clearTimeout(e){globalThis.clearTimeout(e)}});function qY({adapter:e=XY(),clock:t=KY}={}){let n=new Set,r=DY(),i=r.derive({status:`idle`,error:``,graph:null,level:jG.GALAXY,region:null,selectedNode:null,studyData:null,studyError:``,explanation:null,explanationLoading:!1,explanationError:``,showChart:!1,studiedNodeIds:new Set,visitedRegionIds:new Set,showChecks:!1,checkData:null,checkError:``,entrypointDismissed:!1,entrypointError:``,litRegionId:null,pendingDawnRegionId:null,languageFocus:`all`,picker:null,parseProgress:null,projectFailure:null,layer:`galaxy`,layerChosen:!1,mapTab:`architecture`,mapData:null,mapError:``,coachmarksSeen:$Y(),llmStatus:null,hoverNodeId:null,showAll:nX(),finderOpen:!1,sidebarOpen:!1,legendOpen:!1,mode:`easy`,modeChosen:null}),a=0,o=0,s=null,c=null,l=null,u=null,d=null,f=null,p=null,m=null,h=null;function g(){return i}function _(e){return n.add(e),()=>n.delete(e)}function v(e,{preserveChecks:t=!1}={}){let a=i,o=r.derive({...a,...e});if(o.level!==a.level||o.region?.id!==a.region?.id){let e={hoverNodeId:null};t||(iX(u),iX(d),u=null,d=null,e.showChecks=!1,e.checkData=null,e.checkError=``),o=r.derive({...o,...e})}i=Object.freeze(o);for(let e of n)e()}let y=BY({adapter:e,clock:t,async onReady(){a+=1;let e=a;return iX(s),s=new AbortController,S(s,e)}}),b=y.subscribe(()=>{let e=y.getSnapshot(),t={picker:e.picker,parseProgress:e.progress,projectFailure:e.failure};e.phase===`picking`&&(t.status=`picking`),e.phase===`error`&&(t.status=`error`,t.error=e.error),v(t)});async function x(){a+=1;let t=a;iX(s),s=new AbortController;let n=s;v({status:`loading`,error:``});let r;try{r=await e.loadPickerState({signal:n.signal})}catch(e){return!aX(e)&&t===a&&v({status:`error`,error:MY(e)}),i}return t!==a||n.signal.aborted?i:r.state===`unpicked`?(await y.start(),i):S(n,t)}async function S(t,n){v({status:`loading`,error:``,picker:null});try{let r=await e.loadGraph({signal:t.signal});if(n!==a||t.signal.aborted)return i;v({status:`ready`,graph:r,region:mK(r),error:``,entrypointDismissed:!!r.selected_entrypoint})}catch(e){!aX(e)&&n===a&&v({status:`error`,error:MY(e)})}return await C(t,n),i}async function C(t,n){let r=o,[s,c,l]=await Promise.allSettled([(async()=>e.loadMode({signal:t.signal}))(),(async()=>e.fetchLlmStatus({signal:t.signal}))(),(async()=>e.fetchVisited?.({signal:t.signal}))()]);if(!(n!==a||t.signal.aborted)){if(l.status===`fulfilled`&&Array.isArray(l.value?.visited)&&v({visitedRegionIds:new Set(l.value.visited)}),r===o){let e=s.status===`fulfilled`?s.value:null;e?.mode===`easy`||e?.mode===`expert`?ne(e.mode,e.chosen===!0):i.modeChosen===null&&v({modeChosen:!0})}c.status===`fulfilled`&&v({llmStatus:c.value})}}async function w(){let t=a;if(await e.clearProgress({}),v({visitedRegionIds:new Set}),t!==a)return i;a+=1;let n=a;iX(s),s=new AbortController,iX(p),p=null,v({mapData:null,mapError:``});let r=await S(s,n);return n===a&&i.layer===`map`&&await le(),r}async function T(){return await y.reset()?(N(),iX(f),f=null,iX(p),p=null,iX(m),m=null,v({graph:null,parseProgress:null,projectFailure:null,region:null,selectedNode:null,level:jG.GALAXY,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1,showChart:!1,studiedNodeIds:new Set,showChecks:!1,checkData:null,checkError:``,entrypointDismissed:!1,entrypointError:``,litRegionId:null,pendingDawnRegionId:null,languageFocus:`all`,llmStatus:null,picker:null,mapData:null,mapError:``}),x()):i}async function E(e){switch(e.type){case`ADVANCE`:return A(e.node);case`ADVANCE_REGION`:k(e.regionId);return;case`FOLLOW_HINT`:return D();case`RETREAT`:ee();return;case`SELECT_STUDY_NODE`:return te(e.nodeId);case`SET_LANGUAGE_FOCUS`:j(e.language);return;case`SET_MODE`:return re(e.mode);case`HOVER_NODE`:i.hoverNodeId!==(e.nodeId??null)&&v({hoverNodeId:e.nodeId??null});return;case`SHOW_CHART`:v({showChart:!0});return;case`HIDE_CHART`:v({showChart:!1});return;case`OPEN_CHECKS`:return ae();case`CLOSE_CHECKS`:P();return;case`SUBMIT_CHECK`:return se(e.checkId,e.selectedIds);case`CONSUME_DAWN`:fe(e.regionId);return;case`SELECT_ENTRYPOINT`:return F(e.nodeId);case`DISMISS_ENTRYPOINT`:v({entrypointDismissed:!0});return;case`CHANGE_HOME`:N(),v({entrypointDismissed:!1,entrypointError:``,level:jG.GALAXY,selectedNode:null,showChart:!1,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1});return;case`BROWSE_PICKER`:return y.browse(e.path);case`SELECT_PROJECT`:return y.select(e.path);case`RESET_PROJECT`:return T();case`CLEAR_PROGRESS`:return w();case`SET_LAYER`:return ce(e.layer);case`SET_MAP_TAB`:v({mapTab:e.tab});return;case`DISMISS_COACHMARKS`:eX(),v({coachmarksSeen:!0});return;case`SET_LEVEL_GALAXY`:N(),v({level:jG.GALAXY,selectedNode:null});return;case`TOGGLE_SHOW_ALL`:rX(!i.showAll),v({showAll:!i.showAll});return;case`SET_FINDER_OPEN`:v({finderOpen:e.open});return;case`TOGGLE_SIDEBAR`:v({sidebarOpen:!i.sidebarOpen});return;case`TOGGLE_LEGEND`:v({legendOpen:!i.legendOpen});return;case`GO_TO_REGION`:return v({finderOpen:!1}),k(e.regionId);default:throw Error(`Unknown learner-session event: ${e.type}`)}}function D(){let e=i.hint?.action;if(e){if(e.type===`OPEN_REGION`){k(e.regionId);return}if(e.type===`SET_LAYER`)return ce(e.layer);if(e.type===`OPEN_STUDY`)return te(e.nodeId);if(e.type===`OPEN_CHECKS`)return i.level===jG.STUDY&&ee(),ae()}}function O(t){!t||i.visitedRegionIds.has(t)||(v({visitedRegionIds:new Set(i.visitedRegionIds).add(t)}),Promise.resolve(e.markVisited?.(t)).catch(()=>{}))}function k(e){let t=i.graph?.regions.find(t=>t.id===e);t&&(N(),O(t.id),v({languageFocus:i.languageFocus!==`all`&&t.language!==i.languageFocus?t.language:i.languageFocus,region:t,selectedNode:null,level:jG.SYSTEM,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1}))}async function A(e){if(!(!i.focusedGraph||!e)){if(i.level===jG.GALAXY){let t=i.focusedGraph.regions.find(t=>t.id===e.id)??e;N(),O(t.id),v({region:t,selectedNode:null,level:jG.SYSTEM,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1});return}if(i.level===jG.SYSTEM){let t=i.focusedGraph.nodes.find(t=>t.id===e.id)??e;return v({selectedNode:t,level:jG.STUDY}),M(t.id)}if(i.level===jG.STUDY){let t=i.focusedGraph.nodes.find(t=>t.id===e.id);return!t||t.id===i.selectedNode?.id?void 0:(v({selectedNode:t}),M(t.id))}}}function ee(){i.level===jG.STUDY?(N(),v({selectedNode:null,level:jG.SYSTEM,studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1})):i.level===jG.SYSTEM&&v({level:jG.GALAXY})}async function te(e){let t=i.graph?.nodes.find(t=>t.id===e);if(!t)return;let n=i.graph.regions.find(e=>e.id===t.region);return n&&O(n.id),v({languageFocus:i.languageFocus!==`all`&&t.language!==i.languageFocus?t.language:i.languageFocus,region:n??i.region,selectedNode:t,level:jG.STUDY}),M(t.id)}function j(e){let t=i.selectedNode?.id;v({languageFocus:e}),t&&i.selectedNode?.id!==t&&(N(),v({studyData:null,studyError:``,explanation:null,explanationError:``,explanationLoading:!1}))}function ne(e,t){let n=i.layerChosen?i.layer:e===`easy`?`map`:`galaxy`;v({mode:e,layer:n,modeChosen:t}),n===`map`&&le()}async function re(t){if(t!==`easy`&&t!==`expert`)return;let n=i.mode,r=i.modeChosen;if(t===n&&i.modeChosen)return;o+=1,iX(m),m=new AbortController;let s=m,c=a;ne(t,!0);try{await e.saveMode(t,{signal:s.signal})}catch(e){m===s&&c===a&&!aX(e)&&ne(n,r);return}if(!(c!==a||s.signal.aborted)&&i.level===jG.STUDY&&i.selectedNode)return ie(i.selectedNode.id)}async function M(t){N(),c=new AbortController;let n=c;v({studyData:null,studyError:``}),ie(t);try{let r=await e.loadStudy(t,{signal:n.signal});if(n.signal.aborted||i.level!==jG.STUDY||i.selectedNode?.id!==t)return;v({studyData:r,studiedNodeIds:new Set(i.studiedNodeIds).add(r.node.id)})}catch(e){c===n&&!n.signal.aborted&&!aX(e)&&i.selectedNode?.id===t&&v({studyError:oX(e)})}}function N(){iX(c),c=null,iX(l),l=null}async function ie(t){iX(l),l=new AbortController;let n=l;v({explanation:null,explanationError:``,explanationLoading:!0});try{let r=await e.loadExplanation(t,i.mode,{signal:n.signal});if(n.signal.aborted||i.level!==jG.STUDY||i.selectedNode?.id!==t)return;v({explanation:r,explanationLoading:!1})}catch(e){l===n&&!n.signal.aborted&&!aX(e)&&i.selectedNode?.id===t&&v({explanationError:oX(e),explanationLoading:!1})}}async function ae(){if(!i.region)return;let e=i.region.id;return v({showChecks:!0,checkData:null,checkError:``}),oe(e)}async function oe(t){iX(u),u=new AbortController;let n=u;try{let r=await e.loadChecks(t,{signal:n.signal});return!n.signal.aborted&&i.showChecks&&i.region?.id===t&&v({checkData:r,checkError:``},{preserveChecks:!0}),r}catch(e){u===n&&!aX(e)&&!n.signal.aborted&&i.showChecks&&i.region?.id===t&&v({checkError:oX(e)},{preserveChecks:!0});return}}function P(){iX(u),u=null,v({showChecks:!1,checkData:null,checkError:``})}async function se(t,n){if(!i.region)return;let r=i.region.id;iX(d),d=new AbortController;let a=d,o=await e.submitCheck(r,t,n,{signal:a.signal});if(a.signal.aborted||i.region?.id!==r)return o;if(o.correct&&await oe(r),o.region_understood){let t=await e.loadGraph({signal:a.signal});!a.signal.aborted&&i.region?.id===r&&(iX(p),p=null,v({graph:t,region:t.regions.find(e=>e.id===r),mapData:null,mapError:``},{preserveChecks:!0}),de(r),i.layer===`map`&&await le())}return o}async function F(t){let n=a;iX(f),f=new AbortController;let r=f;v({entrypointError:``});try{let o=await e.selectEntrypoint(t,{signal:r.signal});return n!==a||r.signal.aborted?i:(iX(p),p=null,v({graph:o,region:mK(o),entrypointError:``,entrypointDismissed:!0,mapData:null,mapError:``}),i.layer===`map`&&await le(),o)}catch(e){f===r&&n===a&&!r.signal.aborted&&!aX(e)&&v({entrypointError:oX(e)});return}}async function ce(e){if(v({layer:e,layerChosen:!0}),e===`map`)return le()}function le(){if(!i.mapData)return p&&!p.signal.aborted&&!i.mapError?void 0:ue()}async function ue(){let t=a;iX(p),p=new AbortController;let n=p;v({mapError:``});try{let r=await e.fetchMap({signal:n.signal});return t===a&&!n.signal.aborted&&v({mapData:r,mapError:``}),r}catch(e){p===n&&t===a&&!n.signal.aborted&&!aX(e)&&v({mapError:oX(e),mapData:null});return}}function de(e){h!==null&&t.clearTimeout(h),v({litRegionId:e,pendingDawnRegionId:e}),h=t.setTimeout(()=>{h=null,i.litRegionId===e&&v({litRegionId:null})},520)}function fe(e){i.pendingDawnRegionId===e&&v({pendingDawnRegionId:null})}function pe(){a+=1;for(let e of[s,c,l,u,d,f,p,m])iX(e);s=null,c=null,l=null,u=null,d=null,f=null,p=null,m=null,b(),y.dispose(),h!==null&&(t.clearTimeout(h),h=null)}return Object.freeze({dispatch:E,dispose:pe,getSnapshot:g,start:x,subscribe:_})}var JY=6e4;function YY(e,t){if(typeof AbortSignal?.timeout!=`function`||typeof AbortSignal?.any!=`function`)return e;let n=AbortSignal.timeout(t);return{...e,signal:e.signal?AbortSignal.any([e.signal,n]):n}}function XY(e=globalThis.fetch){if(typeof e!=`function`)throw TypeError(`Learner-session HTTP adapter requires fetch.`);async function t(t,n,r={}){let i;try{i=await e(t,r)}catch(e){throw e instanceof Error&&e.name===`TimeoutError`?Error(`${n} timed out. Codemble is still running — try again.`):e}if(!i.ok){let e=(await i.json().catch(()=>null))?.detail,t=Error(typeof e==`string`&&e?e:`${n} returned ${i.status}.`);throw t.status=i.status,t}return i.json()}return Object.freeze({loadGraph(e={}){return t(`/api/graph`,`Graph request`,e)},fetchMap(e={}){return t(`/api/map`,`Map request`,e)},loadStudy(e,n={}){return t(`/api/node/${encodeURIComponent(e)}/study`,`Study request`,n)},loadExplanation(e,n,r={}){return t(`/api/node/${encodeURIComponent(e)}/explanation?mode=${encodeURIComponent(n)}`,`Explanation request`,YY(r,JY))},loadChecks(e,n={}){return t(`/api/regions/${encodeURIComponent(e)}/checks`,`Checks request`,n)},submitCheck(e,n,r,i={}){return t(`/api/regions/${encodeURIComponent(e)}/checks/${encodeURIComponent(n)}`,`Check submission`,{...i,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({selected_ids:r})})},selectEntrypoint(e,n={}){return t(`/api/entrypoint`,`Home selection`,{...n,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({node_id:e})})},loadMode(e={}){return t(`/api/mode`,`Mode request`,e)},saveMode(e,n={}){return t(`/api/mode`,`Mode update`,{...n,method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({mode:e})})},fetchLlmStatus(e={}){return t(`/api/llm/status`,`Model status`,e)},resetProject(e={}){return t(`/api/picker/reset`,`Project reset`,{...e,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({confirmed:!0})})},loadPickerState(e={}){return t(`/api/picker/state`,`Picker state`,e)},browsePicker(e,n={}){return t(`/api/picker/browse${e?`?path=${encodeURIComponent(e)}`:``}`,`Folder listing`,n)},loadRecents(e={}){return t(`/api/picker/recents`,`Recent projects`,e)},fetchParseProgress(e={}){return t(`/api/picker/progress`,`Parse progress`,e)},clearProgress(e={}){return t(`/api/progress`,`Progress reset`,{...e,method:`DELETE`})},fetchVisited(e={}){return t(`/api/progress/visited`,`Explored regions`,e)},markVisited(e,n={}){return t(`/api/progress/visited`,`Explored regions`,{...n,method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({region_id:e})})},async selectProject(t,n={}){let r=await e(`/api/picker/select`,{...n,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:t})}),i=await r.json().catch(()=>null);if(r.ok)return{state:r.status===202?`parsing`:`ready`};let a=i?.detail;return a&&typeof a==`object`&&a.reason===`scale`?{state:`scale`,...a}:{state:`error`,detail:typeof a==`string`?a:`Project selection returned ${r.status}.`}}})}var ZY=`codemble.coachmarks.seen`;function QY(){if(typeof document>`u`)return null;try{return globalThis.localStorage??null}catch{return null}}function $Y(){try{return QY()?.getItem(ZY)===`1`}catch{return!1}}function eX(){try{QY()?.setItem(ZY,`1`)}catch{}}var tX=`codemble.galaxy.showAll`;function nX(){try{return QY()?.getItem(tX)===`1`}catch{return!1}}function rX(e){try{let t=QY();e?t?.setItem(tX,`1`):t?.removeItem(tX)}catch{}}function iX(e){e&&!e.signal.aborted&&e.abort()}function aX(e){return e instanceof Error&&e.name===`AbortError`}function oX(e){return e instanceof Error?e.message:String(e)}var sX=Object.freeze([{id:`editableField`,open:e=>e.editableFocus===!0,dismissible:!1},{id:`nativeDialog`,open:e=>e.nativeDialogOpen===!0,dismissible:!1},{id:`railDisclosure`,open:e=>e.railDisclosureOpen===!0,dismissible:!0},{id:`checks`,open:e=>e.showChecks===!0,dismissible:!0},{id:`sidebar`,open:e=>e.sidebarOpen===!0,dismissible:!0},{id:`finder`,open:e=>e.finderOpen===!0,dismissible:!1},{id:`entrypoint`,open:e=>e.entrypointOpen===!0,dismissible:!1},{id:`chart`,open:e=>e.showChart===!0,dismissible:!0},{id:`firstFlight`,open:e=>e.firstFlightOpen===!0,dismissible:!0}]);function cX(e){let t=sX.find(t=>t.open(e??{}));return t?{id:t.id,dismissible:t.dismissible}:null}function lX(e={}){if(e.ready!==!0)return null;let t=cX(e);return t?t.dismissible?{kind:`dismiss`,surface:t.id}:null:e.canRetreat?{kind:`retreat`}:null}function uX(e){return String(e??``).replace(/[\\`*_[\]<>#|]/g,`\\$&`)}function dX(e,t,n=`${t}s`){return`${e} ${e===1?t:n}`}function fX(e){return`${String(e?.projectName??`local-project`).normalize(`NFKD`).toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||`local-project`}-codemble-brief.md`}function pX(e,{charted:t=0}={}){let n=e?.languages??[],r=e?.busiest??[],i=e?.unsupportedSources??[],a=e?.relationships??{proven:0,hedged:0},o=e?.importCycles??[],s=e?.modules??0,c=e?.structures??0,l=e?.understood??0;return[`# ${uX(e?.projectName??`Local project`)} — Codemble project brief`,``,`> Generated locally from Codemble's parser graph. It contains no AI-generated narration.`,``,`## Project shape`,``,`- ${dX(s,`file`)} containing ${dX(c,`structure`)}.`,`- ${e?.lines??0} lines across parser-supported source files.`,``,`### Languages`,``,`| Language | Files | Structures |`,`| --- | ---: | ---: |`,...n.length?n.map(e=>`| ${uX(e.label)} | ${e.count} | ${e.structures} |`):[`| None reported | 0 | 0 |`],``,`## Home`,``,e?.home?`- Codemble resolved Home as ${uX(e.home.id)} (${uX(e.home.language)}).`:`- No Home entrypoint was resolved; this brief does not invent one.`,``,`## Called from the most places`,``,...r.length?r.map(e=>`- ${uX(e.id)} — called from ${dX(e.value,`place`)}.`):[`- No module has a reported caller count.`],``,`## Proven import cycles`,``,...o.length?o.map(e=>`- Strongly connected group: ${e.map(uX).join(`, `)}. Each file can reach every other through proven imports.`):[`- None reported.`],``,`## Parser evidence`,``,`- Proven relationships: ${a.proven}.`,`- Hedged relationships: ${a.hedged}. Hedged relationships are possible parser matches, not proven links.`,`- Files Codemble could not read: ${e?.unreadable??0}.`,...i.length?[`- Unsupported source files:`,...i.map(e=>` - ${uX(e.extension||`unknown extension`)} (${uX(e.language||`unknown language`)}): ${e.count}`)]:[`- Unsupported source files: 0.`],``,`## Learning progress`,``,`- Charted systems: ${t} of ${s}. Charted means visited, not understood.`,`- Understood systems: ${l} of ${s}. Understood means a graph-derived check passed.`,``].join(` +`)}function mX(){let e=(0,_.useRef)(null),t=(0,_.useRef)(null),n=(0,_.useRef)(null),r=(0,_.useRef)(null),i=(0,_.useRef)(!1),a=(0,_.useRef)(null),o=(0,_.useRef)(null),s=(0,_.useRef)(null),c=(0,_.useRef)(!1),l=(0,_.useRef)(null),u=(0,_.useRef)(null),[d,f]=(0,_.useState)(!1),[p,m]=(0,_.useState)(!1),[h,g]=(0,_.useState)(null),v=(0,_.useMemo)(()=>RJ(),[]),y=(0,_.useMemo)(()=>qY({adapter:XY()}),[]),b=(0,_.useSyncExternalStore)(y.subscribe,y.getSnapshot,y.getSnapshot);(0,_.useEffect)(()=>(y.start(),()=>y.dispose()),[y]),(0,_.useEffect)(()=>{b.status!==`ready`&&(f(!1),g(null),v.clear())},[v,b.status]),(0,_.useEffect)(()=>{let e=window.matchMedia(`(min-width: 40rem)`),t=e=>{e.matches&&f(!1)};return e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]),(0,_.useEffect)(()=>{function e(e){(e.metaKey||e.ctrlKey)&&e.key.toLowerCase()===`k`&&(e.preventDefault(),r.current=document.activeElement===document.body?null:document.activeElement,y.dispatch({type:`SET_FINDER_OPEN`,open:!0}))}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[y]);let x={railDisclosure:()=>{f(!1),Pe(e)},checks:()=>Ie(),sidebar:()=>L(),chart:()=>{y.dispatch({type:`HIDE_CHART`}),Pe(a)},firstFlight:()=>He()};(0,_.useEffect)(()=>{function e(e){if(e.key!==`Escape`)return;let t=lX(_X(y.getSnapshot(),{firstFlightOpen:c.current}));if(t){if(e.preventDefault(),t.kind===`dismiss`){x[t.surface]();return}y.dispatch({type:`RETREAT`})}}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[y]);let{chart:S,checkData:C,checkError:w,coachmarksSeen:T,entrypointError:E,entrypointOpen:D,error:O,explanation:k,explanationError:A,explanationLoading:ee,finderOpen:te,focusedGraph:j,focusedMapData:ne,focusedStudiedCount:re,graph:M,hint:N,hoverNodeId:ie,languageFocus:ae,languageOptions:oe,layer:P,legendOpen:se,level:F,litRegionId:ce,llmStatus:le,mapError:ue,mapTab:de,mode:fe,modeChosen:pe,moduleIndex:me,parseProgress:he,pendingDawnRegionId:ge,picker:_e,projectName:ve,projectFailure:ye,region:be,revealedRegionIds:xe,visitedRegionIds:Se,selectedNode:Ce,showAll:we,showChart:Te,showChecks:Ee,sidebarOpen:De,status:Oe,studyData:ke,studyError:Ae}=b;(0,_.useLayoutEffect)(()=>{!i.current||te||F!==jG.SYSTEM||!u.current||(setTimeout(()=>u.current?.focus(),0),i.current=!1,r.current=null)},[te,F,be?.id]);let je=(0,_.useMemo)(()=>bG(M),[M]),Me=je[h]??null,I=h!==null&&Me!==null;c.current=I;let Ne=(0,_.useMemo)(()=>{if(!j)return null;let e=new Map;for(let t of j.regions){let n=tK(t.community_family);n!==null&&e.set(t.id,n)}return e},[j]);function Pe(t,n){setTimeout(()=>{for(let r of[t,n,e]){let e=r&&`current`in r?r.current:r;if(e?.isConnected&&e.getClientRects().length){e.focus();return}}},0)}function Fe(e){y.dispatch({type:`TOGGLE_SIDEBAR`}),De&&Pe(e.currentTarget,t)}function L(){y.dispatch({type:`TOGGLE_SIDEBAR`}),Pe(t)}function Ie(){y.dispatch({type:`CLOSE_CHECKS`}),Pe(o,u)}function Le(e){r.current=e.currentTarget,y.dispatch({type:`SET_FINDER_OPEN`,open:!0})}function Re(){i.current||(i.current=!1,y.dispatch({type:`SET_FINDER_OPEN`,open:!1}),Pe(r.current,n),r.current=null)}function ze(e){i.current=!0,y.dispatch({type:`GO_TO_REGION`,regionId:e})}function Be(){y.dispatch({type:`FOLLOW_HINT`}),y.getSnapshot().showChecks||Pe(u)}function Ve(e){let t=je[e];t&&(g(e),y.dispatch({type:`SET_LAYER`,layer:`galaxy`}),y.dispatch({type:`GO_TO_REGION`,regionId:t.id}))}function He(){g(null),Pe(s)}function Ue(){y.dispatch({type:`DISMISS_COACHMARKS`}),requestAnimationFrame(()=>l.current?.focus())}if(O)return(0,$.jsxs)(`main`,{className:`load-state`,role:`alert`,children:[(0,$.jsx)(`h1`,{children:`The graph did not load.`}),(0,$.jsx)(`p`,{children:O}),(0,$.jsx)(`p`,{children:`Your progress is stored on this machine and is not affected.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:()=>y.start(),children:`Try again`})]});if(he)return(0,$.jsx)(yX,{progress:he,onCancel:()=>y.dispatch({type:`RESET_PROJECT`})});if(Oe===`picking`&&_e)return(0,$.jsx)(bX,{picker:_e,failure:ye,onBrowse:e=>y.dispatch({type:`BROWSE_PICKER`,path:e}),onSelect:e=>y.dispatch({type:`SELECT_PROJECT`,path:e})});if(!M||!j||!be)return(0,$.jsx)(`main`,{className:`load-state`,"aria-busy":`true`,children:(0,$.jsx)(`p`,{children:`Mapping parser evidence…`})});let We=P===`map`?`map`:`galaxy`,Ge=j.nodes.some(e=>e.id===be.id)?be.id:null,Ke=F===jG.SYSTEM&&P===`galaxy`?Oq(j.nodes.filter(e=>e.region===be.id)):[],qe=F===jG.SYSTEM?(0,$.jsxs)(`section`,{ref:u,className:`orientation-copy orientation-copy--system${P===`map`?` orientation-copy--inline`:``}`,tabIndex:-1,children:[(0,$.jsx)(`p`,{children:j.nodes.some(e=>e.region===be.id&&e.partial)?`${be.node_count} source ${be.node_count===1?`file remains`:`files remain`} visible · ${be.loc} lines. The module is unchartable beyond raw source because it has a syntax error.`:P===`map`?`The ${be.node_count} parser-proven ${be.node_count===1?`structure`:`structures`} inside this module ${be.node_count===1?`is`:`are`} drawn as planets in the Galaxy layer. This map shows how modules connect, not what is inside them.`:`${be.node_count} parser-proven ${be.node_count===1?`structure`:`structures`} · ${be.loc} ${be.loc===1?`line`:`lines`} in this system.`}),Ke.length?(0,$.jsxs)(`p`,{className:`system-orbit-note`,children:[(0,$.jsx)(`span`,{children:`Call layers`}),Ke.map(e=>(0,$.jsx)(`i`,{"data-unproven":e.unproven||void 0,children:e.label},e.ring)),(0,$.jsxs)(`small`,{children:[`Solid guides use certain calls only`,Ke.some(e=>e.unproven)?`; dashed means no proven path.`:`.`]})]}):null,(0,$.jsxs)(`div`,{className:`orientation-copy__actions`,children:[P===`map`&&Ge?(0,$.jsx)(`button`,{className:`check-launch check-launch--read`,type:`button`,onClick:()=>{m(!0),y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:Ge})},children:`Read the source`}):null,(0,$.jsx)(`button`,{ref:o,className:`check-launch`,type:`button`,onClick:()=>y.dispatch({type:`OPEN_CHECKS`}),children:j.nodes.some(e=>e.region===be.id&&e.partial)?`Check availability`:be.understood?`Review understanding`:`Prove understanding`})]})]}):null;return(0,$.jsxs)(`main`,{className:`app-shell`,"data-level":Te?`chart`:F.toLowerCase(),"data-mode":fe,children:[(0,$.jsxs)(`header`,{className:`instrument-rail`,children:[(0,$.jsxs)(`div`,{className:`brand-lockup`,children:[(0,$.jsx)(`span`,{className:`brand-mark`,"aria-hidden":`true`}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`strong`,{children:`Codemble`}),(0,$.jsx)(`span`,{children:ve})]})]}),(0,$.jsx)(`nav`,{className:`location`,"aria-label":`Breadcrumb`,"aria-live":`polite`,children:Te?(0,$.jsx)(`span`,{"aria-current":`page`,children:`Star chart`}):(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`button`,{type:`button`,disabled:F===jG.GALAXY,"aria-current":F===jG.GALAXY?`page`:void 0,onClick:()=>y.dispatch({type:`SET_LEVEL_GALAXY`}),children:`All modules`}),F===jG.GALAXY?(0,$.jsxs)(`small`,{children:[` · Home `,M.selected_entrypoint&&mK(M)?(0,$.jsx)(`button`,{type:`button`,className:`breadcrumb-home`,onClick:()=>y.dispatch({type:`ADVANCE_REGION`,regionId:mK(M).id}),children:mK(M).id}):M.selected_entrypoint?`unresolved`:`unselected`]}):(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),(0,$.jsx)(`button`,{type:`button`,disabled:F===jG.SYSTEM,"aria-current":F===jG.SYSTEM?`page`:void 0,onClick:()=>y.dispatch({type:`RETREAT`}),children:be.id})]}),F===jG.STUDY&&Ce?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),(0,$.jsx)(`span`,{"aria-current":`page`,children:Ce.name})]}):null,ae===`all`?null:(0,$.jsxs)(`small`,{children:[` · `,LG(ae),` focus`]})]})}),(0,$.jsxs)(`div`,{className:`rail-overflow`,"data-open":d||void 0,children:[(0,$.jsxs)(`button`,{ref:e,className:`mobile-menu-trigger`,type:`button`,"aria-expanded":d,"aria-controls":`rail-overflow-panel`,onClick:()=>f(e=>!e),children:[(0,$.jsx)(`span`,{className:`rail-trigger__compact`,children:`Menu`}),(0,$.jsx)(`span`,{className:`rail-trigger__wide`,children:`More`})]}),(0,$.jsxs)(`div`,{className:`rail-overflow__panel`,id:`rail-overflow-panel`,onClickCapture:e=>{e.target.closest(`button`)&&!e.target.closest(`.switch-project`)&&f(!1)},children:[(0,$.jsxs)(`div`,{className:`rail-actions`,children:[(0,$.jsx)(`button`,{ref:t,className:`rail-action`,type:`button`,"aria-pressed":De,onClick:Fe,children:`Modules`}),(0,$.jsxs)(`button`,{ref:n,className:`rail-action`,type:`button`,onClick:Le,children:[`Find `,(0,$.jsx)(`kbd`,{children:`⌘K`})]}),Te?(0,$.jsxs)(`button`,{ref:a,className:`rail-action`,type:`button`,onClick:()=>{y.dispatch({type:`HIDE_CHART`}),Pe(a)},children:[`Back to `,We]}):(0,$.jsxs)($.Fragment,{children:[F===jG.GALAXY?null:(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,onClick:()=>y.dispatch({type:`RETREAT`}),children:F===jG.STUDY?P===`map`?`Back to the module`:`Back to system`:`Back to ${We}`}),(0,$.jsx)(`button`,{ref:a,className:`rail-action`,type:`button`,onClick:()=>y.dispatch({type:`SHOW_CHART`}),children:`Star chart`})]})]}),(0,$.jsxs)(`div`,{className:`rail-more`,children:[M.entrypoint_candidates.length?(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,onClick:()=>y.dispatch({type:`CHANGE_HOME`}),children:`Change Home`}):null,(0,$.jsx)(OX,{onConfirm:()=>y.dispatch({type:`RESET_PROJECT`})})]}),(0,$.jsxs)(`div`,{className:`rail-controls`,children:[P===`galaxy`?(0,$.jsx)(`button`,{className:`rail-action rail-action--toggle`,type:`button`,"aria-pressed":we,title:`Draw every module at once, including the ones no route from Home reaches`,onClick:()=>y.dispatch({type:`TOGGLE_SHOW_ALL`}),children:`Show all`}):null,(0,$.jsx)(EX,{layer:P,mode:fe,onChange:e=>y.dispatch({type:`SET_LAYER`,layer:e})}),(0,$.jsx)(DX,{options:oe,value:ae,onChange:e=>y.dispatch({type:`SET_LANGUAGE_FOCUS`,language:e})}),(0,$.jsx)($J,{mode:fe,modeChosen:pe,onChoose:e=>y.dispatch({type:`SET_MODE`,mode:e})})]})]})]})]}),Te?(0,$.jsxs)(`section`,{className:`chart-stage`,"aria-label":`Language concept progress`,children:[De?(0,$.jsx)(TX,{index:me,currentRegionId:null,onGo:e=>y.dispatch({type:`GO_TO_REGION`,regionId:e}),onClose:L}):null,(0,$.jsx)(NX,{chart:S,studiedCount:re,exploredCount:Se.size,projectName:ve,mode:fe,overview:_K(M),onClearProgress:()=>y.dispatch({type:`CLEAR_PROGRESS`})})]}):(0,$.jsxs)(`section`,{ref:l,className:`map-stage`,"aria-label":`Parser-proven project map`,tabIndex:-1,children:[De?(0,$.jsx)(TX,{index:me,currentRegionId:F===jG.GALAXY?null:be?.id,onGo:e=>y.dispatch({type:`GO_TO_REGION`,regionId:e}),onClose:L}):null,P===`map`?(0,$.jsx)(HJ,{data:ne,mapTab:de,mode:fe,communityIndexByRegion:Ne,selectedRegionId:F===jG.GALAXY?void 0:be?.id,hasEntrypointCandidates:M.entrypoint_candidates.length>0,unsupportedSources:j.unsupported_sources,importCycles:M.import_cycles,error:ue,languageFocus:ae,languageFocusLabel:ae===`all`?``:LG(ae),onClearLanguageFocus:()=>y.dispatch({type:`SET_LANGUAGE_FOCUS`,language:`all`}),onSelectTab:e=>y.dispatch({type:`SET_MAP_TAB`,tab:e}),onSelectRegion:e=>y.dispatch({type:`ADVANCE_REGION`,regionId:e}),onSelectNode:e=>{m(!1),y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:e})},onRetry:()=>y.dispatch({type:`SET_LAYER`,layer:`map`}),viewportStore:v,children:qe}):(0,$.jsx)(Vq,{graph:j,level:F,region:be,selectedNode:Ce,hoverNodeId:ie,pendingDawnRegionId:ge,revealedRegionIds:xe,mode:fe,firstFlightActive:I,onHoverNode:e=>y.dispatch({type:`HOVER_NODE`,nodeId:e}),onAdvance:e=>y.dispatch({type:`ADVANCE`,node:e}),onRetreat:()=>y.dispatch({type:`RETREAT`}),onDawnConsumed:e=>y.dispatch({type:`CONSUME_DAWN`,regionId:e})}),(0,$.jsx)(`button`,{className:`legend-toggle`,type:`button`,"aria-expanded":se,onPointerEnter:()=>y.dispatch({type:`HOVER_NODE`,nodeId:null}),onClick:()=>y.dispatch({type:`TOGGLE_LEGEND`}),children:`Key`}),(0,$.jsxs)(`aside`,{className:`map-legend`,hidden:!se,tabIndex:0,"aria-label":P===`map`?`Map legend`:`Galaxy legend`,onPointerEnter:()=>y.dispatch({type:`HOVER_NODE`,nodeId:null}),onKeyDown:e=>{let t=e.currentTarget,n=Math.max(48,t.clientHeight*.8),r={ArrowDown:32,ArrowUp:-32,PageDown:n,PageUp:-n}[e.key];r===void 0?(e.key===`Home`||e.key===`End`)&&(e.preventDefault(),t.scrollTop=e.key===`Home`?0:t.scrollHeight):(e.preventDefault(),t.scrollBy({top:r,behavior:`auto`}))},children:[P===`galaxy`?(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-size`}),`Size · `,fe===`easy`?`how much code`:`lines of code`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-brightness`}),`Brighter · `,fe===`easy`?`used in more places`:`more distinct callers`]})]}):null,(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-dot legend-dot--dim`}),`Dim · `,fe===`easy`?`not proven yet`:`not understood`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-dot legend-dot--lit`}),`Amber · `,fe===`easy`?`you proved you understand it`:`understood`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-dot legend-dot--partial`}),fe===`easy`?`Could not be read`:`Unchartable · syntax error`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-route`}),fe===`easy`?`Certain connection`:`Parser edge · certain`]}),(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-route legend-route--possible legend-route--dashed`}),fe===`easy`?`Possible connection`:`Possible relationship`]}),P===`galaxy`&&F===jG.GALAXY||P===`map`&&de===`architecture`?(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`span`,{children:[(0,$.jsxs)(`span`,{className:`legend-communities`,"aria-hidden":`true`,children:[(0,$.jsx)(`i`,{}),(0,$.jsx)(`i`,{}),(0,$.jsx)(`i`,{}),(0,$.jsx)(`i`,{})]}),fe===`easy`?`Colour family · related systems and their shared routes`:`Hue family · one import community; internal routes inherit a tint`]}),oe.filter(e=>e.id!==`all`).map(e=>(0,$.jsxs)(`span`,{children:[(0,$.jsx)(`i`,{className:`legend-tint`,style:{background:BG(e.id)??void 0}}),` `,e.label]},e.id))]}):null]}),P===`galaxy`&&F===jG.GALAXY?(0,$.jsxs)(`p`,{className:`orientation-bar`,children:[(0,$.jsxs)(`span`,{children:[j.regions.length,` `,ae===`all`?j.regions.length===1?`system`:`systems`:`${LG(ae)} ${j.regions.length===1?`system`:`systems`}`]}),(0,$.jsx)(`span`,{className:`orientation-bar__charted`,children:we?`all charted`:`${xe.size} charted`}),j.partial_files.length?(0,$.jsxs)(`span`,{className:`partial-summary`,children:[j.partial_files.length,` `,fe===`easy`?`could not be read`:`unchartable · syntax error`,QG(j.partial_files)?` · ${j.partial_files.length===1?`in`:`all under`} ${QG(j.partial_files)}/`:``]}):null,UG(j.unsupported_sources,fe)?(0,$.jsx)(`span`,{className:`unsupported-summary`,children:UG(j.unsupported_sources,fe)}):null]}):null,P===`galaxy`?qe:null,F===jG.SYSTEM&&Ee?(0,$.jsx)(AX,{suite:C,error:w,mode:fe,overviewNoun:We,onClose:Ie,onSubmit:(e,t)=>y.dispatch({type:`SUBMIT_CHECK`,checkId:e,selectedIds:t})}):null,pe===!0&&D&&F===jG.GALAXY?(0,$.jsx)(kX,{candidates:M.entrypoint_candidates,nodes:M.nodes,selectedEntrypoint:M.selected_entrypoint,mode:fe,error:E,onSelect:e=>y.dispatch({type:`SELECT_ENTRYPOINT`,nodeId:e}),onContinue:()=>y.dispatch({type:`DISMISS_ENTRYPOINT`})}):null,ce?(0,$.jsxs)(`output`,{className:`illumination-pulse`,"aria-live":`polite`,children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`✦`}),(0,$.jsxs)(`strong`,{children:[ce,` understood`]})]}):null,F===jG.STUDY&&Ce?(0,$.jsx)(aY,{node:Ce,study:ke,error:Ae,mode:fe,explanation:k,explanationLoading:ee,explanationError:A,llmStatus:le,revealSource:p,onSelectNode:e=>{m(!1),y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:e})},onRetryStudy:()=>y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:Ce.id}),onRetryNarration:()=>y.dispatch({type:`SELECT_STUDY_NODE`,nodeId:Ce.id}),onClose:()=>{m(!1),y.dispatch({type:`RETREAT`})}}):null,pe===!0&&!D&&!T?(0,$.jsx)(qq,{layer:P,onDismiss:Ue}):null]}),te?(0,$.jsx)(wX,{index:me,onGo:ze,onClose:Re}):null,!Te&&!Ee&&pe===!0&&!D&&T?(0,$.jsx)(Wq,{hint:N,onFollow:Be,firstFlight:{available:je.length>0,active:I,stop:Me,index:h??0,total:je.length,mode:fe,triggerRef:s,onStart:()=>Ve(0),onBack:()=>Ve(h-1),onNext:()=>Ve(h+1),onExit:He}}):null,(0,$.jsxs)(`footer`,{className:`status-line`,children:[(0,$.jsx)(`span`,{children:Te?`${S.length} concepts detected`:ae===`all`?`${M.nodes.length} nodes · ${M.edges.length} edges`:`${j.nodes.length}/${M.nodes.length} nodes · ${j.edges.length} focused edges`}),(0,$.jsx)(`span`,{children:Te?`${re} focused structures studied this session`:P===`map`?`Click a box or row to study · Switch tabs to change view`:`Drag to orbit · Scroll to zoom · Click to move closer · Escape to move back`}),(0,$.jsx)(`span`,{children:`Local only`})]})]})}var hX=Object.fromEntries(zY.map(({id:e,copy:t})=>[e,t])),gX=zY.map(({id:e})=>e);function _X(e,{firstFlightOpen:t=!1}={}){return{ready:e.status===`ready`,canRetreat:e.layer===`map`&&e.level!==jG.GALAXY,showChart:e.showChart,finderOpen:e.finderOpen,sidebarOpen:e.sidebarOpen,showChecks:e.showChecks,entrypointOpen:e.entrypointOpen,firstFlightOpen:t,nativeDialogOpen:document.querySelector(`dialog[open]`)!==null,railDisclosureOpen:document.querySelector(`.rail-overflow[data-open]`)!==null,editableFocus:vX(document.activeElement)}}function vX(e){if(!e)return!1;let t=e.tagName;return t===`INPUT`||t===`TEXTAREA`||t===`SELECT`||e.isContentEditable===!0}function yX({progress:e,onCancel:t}){let{stage:n,detail:r,files_done:i,files_total:a,pollError:o,pollOutage:s,attempts:c,path:l}=e,[u,d]=(0,_.useState)(!1),[f,p]=(0,_.useState)(``),m=(0,_.useRef)(null),h=gX.indexOf(n);(0,_.useEffect)(()=>{m.current?.focus()},[]);async function g(){d(!0),p(``);try{await t()}catch(e){p(e.message),d(!1)}}return(0,$.jsxs)(`main`,{className:`loading-screen`,children:[(0,$.jsxs)(`header`,{className:`loading-header`,children:[(0,$.jsx)(`p`,{className:`picker-wordmark`,children:`Codemble`}),(0,$.jsxs)(`h1`,{ref:m,tabIndex:-1,children:[`Mapping `,(0,$.jsx)(`span`,{className:`loading-path`,children:l})]}),(0,$.jsx)(`p`,{className:`loading-subtitle`,children:`Parsing runs on your machine. Nothing is sent anywhere.`})]}),(0,$.jsx)(`ol`,{className:`loading-stages`,"aria-label":`Parse stages`,children:gX.map((e,t)=>{let n=t{let e=m.current;l?p.current?.focus():e&&f.current?.focus(),m.current=!!l},[l]),(0,$.jsxs)(`main`,{className:`picker-screen`,"aria-busy":d,children:[(0,$.jsxs)(`header`,{className:`picker-header`,children:[(0,$.jsx)(`p`,{className:`picker-wordmark`,children:`Codemble`}),(0,$.jsx)(`h1`,{children:`Choose the project to chart`}),(0,$.jsx)(`p`,{className:`picker-subtitle`,children:`Codemble reads the folder locally and turns it into a galaxy. Nothing leaves this machine.`})]}),s.length?(0,$.jsxs)(`section`,{className:`picker-recents`,"aria-labelledby":`picker-recents-heading`,children:[(0,$.jsx)(`h2`,{id:`picker-recents-heading`,children:`Continue where you left off`}),(0,$.jsx)(`ul`,{children:s.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,disabled:d,onClick:()=>r(e.project_root),children:[(0,$.jsx)(`span`,{className:`picker-recent-path`,children:e.project_root}),(0,$.jsxs)(`span`,{className:`picker-recent-lit`,children:[e.understood_count,` `,e.understood_count===1?`system`:`systems`,` lit last visit`]})]})},e.project_root))})]}):null,u?(0,$.jsx)(xX,{scale:u,busy:d,onBrowse:n}):null,t?(0,$.jsx)(CX,{failure:t,busy:d,onSelect:r}):c?(0,$.jsxs)(`div`,{className:`picker-error`,children:[(0,$.jsx)(`p`,{role:`alert`,children:c}),l?(0,$.jsx)(`button`,{ref:p,className:`picker-retry`,type:`button`,disabled:d,onClick:()=>n(l),children:`Try this folder again`}):null]}):null,(0,$.jsxs)(`section`,{className:`picker-browser`,"aria-labelledby":`picker-browser-heading`,children:[(0,$.jsx)(`h2`,{ref:f,id:`picker-browser-heading`,tabIndex:-1,children:`Browse folders`}),(0,$.jsx)(`p`,{className:`picker-path`,children:i}),(0,$.jsxs)(`ul`,{children:[a?(0,$.jsx)(`li`,{children:(0,$.jsx)(`button`,{type:`button`,disabled:d,onClick:()=>n(a),children:`↑ Up`})}):null,o.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,disabled:d,onClick:()=>n(e.path),children:[e.name,`/`]})},e.path))]}),(0,$.jsx)(`button`,{className:`picker-select`,type:`button`,disabled:d,onClick:()=>r(i),children:d?`Mapping…`:`Map this folder`})]})]})}function xX({scale:e,busy:t,onBrowse:n}){let r=e.suggestions.filter(e=>e.path!==`.`);return(0,$.jsxs)(`section`,{className:`picker-scale`,role:`alert`,"aria-labelledby":`picker-scale-heading`,children:[(0,$.jsx)(`h2`,{id:`picker-scale-heading`,children:`That folder is too big to map at once.`}),(0,$.jsxs)(`p`,{children:[`It has `,e.file_count,` supported source files; Codemble maps up to`,` `,e.scale_cap,`. Choose a smaller scope — busiest first.`]}),r.length?(0,$.jsx)(`ul`,{className:`picker-scale-scopes`,children:r.map(r=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,disabled:t,onClick:()=>n(`${e.root}/${r.path}`),children:[(0,$.jsxs)(`span`,{children:[r.path,`/`]}),(0,$.jsxs)(`small`,{children:[r.file_count,` files`]})]})},r.path))}):null,(0,$.jsx)(SX,{busy:t,onBrowse:n})]})}function SX({busy:e,onBrowse:t}){let[n,r]=(0,_.useState)(``);return(0,$.jsxs)(`form`,{className:`picker-path-entry`,onSubmit:e=>{e.preventDefault();let r=n.trim();r&&t(r)},children:[(0,$.jsx)(`label`,{htmlFor:`picker-path-input`,children:`Or type a folder path`}),(0,$.jsx)(`input`,{id:`picker-path-input`,name:`project-path`,type:`text`,value:n,disabled:e,autoComplete:`off`,spellCheck:!1,placeholder:`/Users/you/project/src`,onChange:e=>r(e.target.value)}),(0,$.jsx)(`button`,{type:`submit`,disabled:e||!n.trim(),children:`Go`})]})}function CX({failure:e,busy:t,onSelect:n}){return(0,$.jsxs)(`section`,{className:`picker-failure`,role:`alert`,children:[(0,$.jsx)(`h2`,{children:`Codemble could not map that folder.`}),(0,$.jsx)(`p`,{children:`Nothing on disk changed, and no project's saved progress was touched. Try it again, or choose a different folder below.`}),(0,$.jsxs)(`p`,{className:`picker-failure__detail`,children:[(0,$.jsx)(`span`,{children:`What the parser reported`}),(0,$.jsx)(`code`,{children:e.detail})]}),(0,$.jsxs)(`button`,{className:`picker-select`,type:`button`,disabled:t,onClick:()=>n(e.path),children:[`Try `,(0,$.jsx)(`span`,{className:`picker-recent-path`,children:e.path}),` again`]})]})}function wX({index:e,onGo:t,onClose:n}){let[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(0),s=(0,_.useRef)(null),c=(0,_.useRef)(null),l=(0,_.useMemo)(()=>{let t=r.trim().toLowerCase();return t?e.filter(e=>e.label.toLowerCase().includes(t)||e.file.toLowerCase().includes(t)).slice(0,60):[...e].sort((e,t)=>Number(t.home)-Number(e.home)||Number(t.understood)-Number(e.understood)||t.centrality-e.centrality||e.label.localeCompare(t.label)||e.id.localeCompare(t.id)).slice(0,60)},[e,r]);(0,_.useLayoutEffect)(()=>{let e=s.current;e&&!e.open&&e.showModal(),c.current?.focus()},[]),(0,_.useEffect)(()=>o(0),[r]);let u=l[Math.min(a,l.length-1)]??null;function d(e){e.key===`ArrowDown`?(e.preventDefault(),o(e=>Math.min(e+1,l.length-1))):e.key===`ArrowUp`?(e.preventDefault(),o(e=>Math.max(e-1,0))):e.key===`Enter`&&u?(e.preventDefault(),t(u.id)):e.key===`Escape`&&(e.preventDefault(),e.stopPropagation(),n())}return(0,$.jsxs)(`dialog`,{ref:s,className:`module-finder`,"aria-label":`Find a module`,onCancel:e=>{e.preventDefault(),n()},onClose:n,children:[(0,$.jsx)(`input`,{ref:c,name:`module-search`,type:`search`,value:r,placeholder:`Find a module…`,"aria-label":`Find a module by name or path`,autoComplete:`off`,spellCheck:!1,onChange:e=>i(e.target.value),onKeyDown:d}),(0,$.jsx)(`ul`,{role:`listbox`,"aria-label":`Matching modules`,children:l.map((e,n)=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,role:`option`,"aria-selected":n===a,"data-active":n===a||void 0,onMouseEnter:()=>o(n),onClick:()=>t(e.id),children:[(0,$.jsx)(`strong`,{children:e.label}),e.file===e.label?e.hops===0?null:(0,$.jsx)(`small`,{children:typeof e.hops==`number`?`${e.hops} ${e.hops===1?`route`:`routes`} from Home`:`no route from Home`}):(0,$.jsx)(`small`,{children:e.file}),e.home?(0,$.jsx)(`em`,{children:`Home`}):e.understood?(0,$.jsx)(`em`,{children:`lit`}):null]})},e.id))}),l.length?null:(0,$.jsx)(`p`,{className:`module-finder__empty`,children:`No module matches that.`})]})}function TX({index:e,currentRegionId:t,onGo:n,onClose:r}){let i=(0,_.useMemo)(()=>YG(e),[e]),a=(0,_.useRef)(null);return(0,_.useLayoutEffect)(()=>{a.current?.focus()},[]),(0,$.jsxs)(`aside`,{className:`index-sidebar`,"aria-label":`Project index`,children:[(0,$.jsxs)(`header`,{children:[(0,$.jsx)(`h2`,{children:`Modules`}),(0,$.jsx)(`button`,{ref:a,type:`button`,onClick:r,"aria-label":`Close the project index`,children:`×`})]}),(0,$.jsx)(`div`,{className:`index-sidebar__scroll`,children:i.map(e=>(0,$.jsxs)(`section`,{children:[(0,$.jsx)(`h3`,{title:e.name,children:e.name}),(0,$.jsx)(`ul`,{children:e.members.map(e=>(0,$.jsx)(`li`,{children:(0,$.jsxs)(`button`,{type:`button`,"aria-current":e.id===t?`true`:void 0,"data-lit":e.understood||void 0,title:e.file,onClick:()=>n(e.id),children:[(0,$.jsx)(`span`,{children:e.display??e.label}),e.home?(0,$.jsx)(`em`,{children:`Home`}):null]})},e.id))})]},e.community))})]})}function EX({layer:e,mode:t,onChange:n}){return(0,$.jsx)(`nav`,{className:`layer-switcher`,"aria-label":`View layer`,children:[{id:`galaxy`,label:`Galaxy`},{id:`map`,label:t===`easy`?`Diagram`:`Map`}].map(t=>(0,$.jsx)(`button`,{type:`button`,"aria-pressed":e===t.id,onClick:()=>n(t.id),children:t.label},t.id))})}function DX({options:e,value:t,onChange:n}){return e.length<=2?null:(0,$.jsxs)(`nav`,{className:`language-focus`,"aria-label":`Language focus`,children:[(0,$.jsx)(`span`,{className:`language-focus__label`,children:`Focus`}),(0,$.jsx)(`div`,{children:e.map(e=>(0,$.jsxs)(`button`,{type:`button`,"aria-label":`Focus ${e.label}: ${e.count} ${e.count===1?`system`:`systems`}`,"aria-pressed":t===e.id,title:e.label,onClick:()=>n(e.id),children:[(0,$.jsx)(`span`,{children:e.shortLabel}),(0,$.jsx)(`small`,{children:e.count})]},e.id))})]})}function OX({onConfirm:e}){let[t,n]=(0,_.useState)(!1),[r,i]=(0,_.useState)(!1),[a,o]=(0,_.useState)(``),s=(0,_.useRef)(null),c=(0,_.useRef)(null),l=(0,_.useRef)(!1);(0,_.useEffect)(()=>{t?c.current?.focus():l.current&&s.current?.focus(),l.current=t},[t]);function u(){n(!1),o(``)}async function d(){i(!0),o(``);try{await e()}catch(e){o(e.message),i(!1)}}return(0,$.jsx)(`div`,{className:`switch-project`,"data-confirming":t||void 0,role:t?`group`:void 0,"aria-label":t?`Switch project`:void 0,tabIndex:t?-1:void 0,ref:t?c:void 0,onKeyDown:e=>{e.key===`Escape`&&t&&!r&&u()},children:t?(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(`p`,{children:`Progress is saved per project, so this galaxy comes back lit.`}),a?(0,$.jsx)(`p`,{className:`switch-project__error`,role:`alert`,children:a}):null,(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,disabled:r,onClick:d,children:r?`Releasing…`:`Switch`}),(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,disabled:r,onClick:u,children:`Cancel`})]})]}):(0,$.jsx)(`button`,{className:`rail-action`,type:`button`,ref:s,onClick:()=>n(!0),children:`Switch project`})})}function kX({candidates:e,nodes:t,selectedEntrypoint:n,mode:r,error:i,onSelect:a,onContinue:o}){let s=new Map(t.map(e=>[e.id,e])),c=(0,_.useMemo)(()=>{let t=new Map;for(let n of e){let e=s.get(n),r=(e?.file??n).split(`/`).filter(Boolean),i=r.length>1?`${r[0]}/`:`project root`;t.has(i)||t.set(i,[]),t.get(i).push({candidate:n,node:e})}return[...t].map(([e,t])=>({name:e,rows:t,bestRank:Math.min(...t.map(e=>e.node?.entrypoint_rank??2**53-1))})).sort((e,t)=>e.bestRank-t.bestRank||e.name.localeCompare(t.name)).map((e,t)=>({...e,open:t===0||e.bestRank===0}))},[e,t]),l=c[0]?.rows[0]?.candidate??null,u=(0,_.useRef)(null),d=(0,_.useRef)(null);return(0,_.useLayoutEffect)(()=>{let e=d.current;e&&!e.open&&e.showModal(),u.current?.focus()},[]),(0,$.jsxs)(`dialog`,{ref:d,className:`entrypoint-picker`,"aria-labelledby":`entrypoint-heading`,onCancel:e=>e.preventDefault(),children:[(0,$.jsx)(`p`,{children:`Home calibration`}),(0,$.jsx)(`h1`,{id:`entrypoint-heading`,children:e.length?`Where does your project start?`:`No clear entrypoint found.`}),(0,$.jsx)(`p`,{children:e.length?r===`easy`?`Your project could start from more than one place, and Codemble will not guess. Pick the one you actually run — best guess first.`:`The parser found ranked candidates but cannot choose one honestly. Select the structure you run.`:`No file here declares a startup structure the parser recognises, and Codemble will not guess one. Explore the map without Home — every system, check, explanation, and lens note still works.`}),e.length?(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`p`,{className:`entrypoint-count`,children:[e.length,` ranked`,` `,e.length===1?`candidate`:`candidates`,` in `,c.length,` `,c.length===1?`scope`:`scopes`]}),(0,$.jsx)(`div`,{className:`entrypoint-scroll`,children:c.map(e=>(0,$.jsxs)(`details`,{open:e.open,children:[(0,$.jsxs)(`summary`,{children:[(0,$.jsx)(`span`,{children:e.name}),(0,$.jsxs)(`small`,{children:[e.rows.length,` `,e.rows.length===1?`candidate`:`candidates`]})]}),(0,$.jsx)(`div`,{className:`entrypoint-candidates`,children:e.rows.map(({candidate:e,node:t})=>(0,$.jsxs)(`button`,{ref:e===l?u:void 0,type:`button`,onClick:()=>a(e),children:[(0,$.jsx)(`span`,{children:e}),(0,$.jsxs)(`small`,{children:[t?.file,`:`,t?.lineno,` ·`,` `,r===`easy`?`candidate ${(t?.entrypoint_rank??0)+1}`:`parser rank ${t?.entrypoint_rank}`]})]},e))})]},e.name))})]}):null,i?(0,$.jsx)(`p`,{className:`entrypoint-error`,role:`alert`,children:i}):null,(0,$.jsx)(`button`,{ref:e.length?void 0:u,className:`entrypoint-continue`,type:`button`,onClick:o,children:n?`Keep current Home`:`Explore without Home`})]})}function AX({suite:e,error:t,mode:n,overviewNoun:r,onClose:i,onSubmit:a}){let o=e?.checks.find(e=>!e.passed)??null,s=e?.checks.filter(e=>e.passed).length??0,[c,l]=(0,_.useState)(()=>new Set),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(!1),[g,v]=(0,_.useState)(``),y=(0,_.useRef)(null),b=(0,_.useRef)(null),x=(0,_.useRef)(null),S=(0,_.useRef)(null),C=(0,_.useRef)(null),w=(0,_.useRef)(null),T=(0,_.useRef)(null);(0,_.useEffect)(()=>{l(new Set),d(null),v(``)},[o?.id]),(0,_.useLayoutEffect)(()=>{y.current?.focus()},[]),(0,_.useLayoutEffect)(()=>{e?.region_understood?S.current?.focus():u?b.current?.focus():f&&x.current?.focus()},[e?.region_understood,u,f]),(0,_.useLayoutEffect)(()=>{u&&C.current?.scrollIntoView({block:`nearest`})},[u]),(0,_.useLayoutEffect)(()=>{if(!o)return;let e=w.current,t=T.current;if(!e||!t)return;let n=!1,r=0,i=()=>{if(n||!window.matchMedia(`(max-width: 39.999rem)`).matches&&e.scrollHeight<=e.clientHeight+1)return;let r=t.getBoundingClientRect().top-e.getBoundingClientRect().top;e.scrollTop+=r};return i(),r=window.requestAnimationFrame(i),document.fonts?.ready.then(()=>{n||(window.cancelAnimationFrame(r),r=window.requestAnimationFrame(i))}),()=>{n=!0,window.cancelAnimationFrame(r)}},[o?.id]);function E(e,t){p(``),l(n=>{if(!t)return new Set([e]);let r=new Set(n);return r.has(e)?r.delete(e):r.add(e),r})}async function D(e){if(e.preventDefault(),!(!o||c.size===0)){h(!0),v(``);try{let e=await a(o.id,[...c]);e.correct?(p(e.message),d(null)):(p(``),d(e))}catch(e){v(e.message)}finally{h(!1)}}}return(0,$.jsxs)(`aside`,{ref:w,className:`check-panel`,"aria-label":`Graph-derived understanding checks`,children:[(0,$.jsxs)(`header`,{className:`check-panel__header`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`p`,{children:n===`easy`?`Quiz · answers come from your code, not AI`:`Active recall · graph only`}),(0,$.jsx)(`h1`,{ref:y,tabIndex:-1,children:e?.region_id??`Loading checks`})]}),(0,$.jsx)(`button`,{className:`check-close`,type:`button`,onClick:i,children:`Close`})]}),t||g?(0,$.jsxs)(`div`,{className:`check-state`,role:`alert`,children:[(0,$.jsx)(`h2`,{children:`The checks did not load.`}),(0,$.jsxs)(`p`,{children:[t||g,` The galaxy remains available.`]})]}):null,!e&&!t?(0,$.jsx)(`p`,{className:`check-loading`,role:`status`,children:`Deriving answers from parser edges…`}):null,e?.region_understood?(0,$.jsxs)(`div`,{className:`check-complete`,"aria-live":`polite`,children:[(0,$.jsx)(`span`,{className:`check-complete__star`,"aria-hidden":`true`,children:`✦`}),(0,$.jsx)(`h2`,{children:r===`map`?`Module lit.`:`System lit.`}),(0,$.jsx)(`p`,{children:n===`easy`?`Codemble remembers the exact code you proved. Edit this file and only this ${r===`map`?`module`:`system`} dims again.`:`This region's source hash matches the checks you passed. Edit its file and only this system will dim again.`}),(0,$.jsxs)(`button`,{ref:S,className:`check-primary`,type:`button`,onClick:i,children:[`Back to the `,r===`map`?`module`:`system`]})]}):null,e&&!e.region_understood&&e.checks.length===0?(0,$.jsxs)(`div`,{className:`check-state`,children:[(0,$.jsx)(`h2`,{children:`No safe check yet.`}),(0,$.jsx)(`p`,{children:`Every question here is answered by the parser graph, and this region has no certain relationship Codemble can build one from. It stays dim rather than lighting on a question that would prove nothing. Import this module somewhere, or call something inside it, and its checks appear.`})]}):null,e&&!e.region_understood&&e.checks.length>0&&!o?(0,$.jsxs)(`div`,{className:`check-state`,children:[(0,$.jsx)(`h2`,{children:`These checks are out of date.`}),(0,$.jsx)(`p`,{children:`This module is dim again, but this panel is still holding the answers from before. Reopen it to get the questions back — nothing you proved has been lost anywhere else.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:i,children:`Reopen the checks`})]}):null,o&&!e.region_understood?(0,$.jsxs)(`form`,{className:`active-check`,onSubmit:D,children:[(0,$.jsxs)(`div`,{className:`check-progress`,children:[(0,$.jsxs)(`span`,{children:[`Check `,s+1,` of `,e.checks.length]}),(0,$.jsx)(`progress`,{value:s,max:e.checks.length})]}),f?(0,$.jsxs)(`p`,{ref:x,className:`check-affirmation`,role:`status`,tabIndex:-1,children:[(0,$.jsx)(`span`,{"aria-hidden":`true`,children:`✦`}),` `,f]}):null,(0,$.jsxs)(`fieldset`,{ref:T,children:[(0,$.jsx)(`legend`,{children:o.prompt_voices[n]}),o.multiple?(0,$.jsx)(`p`,{children:`Select every answer supported by the graph.`}):null,(0,$.jsx)(`div`,{className:`check-options`,children:o.options.map(e=>(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`input`,{type:o.multiple?`checkbox`:`radio`,name:`answer-${o.id}`,value:e.id,checked:c.has(e.id),onChange:()=>E(e.id,o.multiple)}),(0,$.jsx)(`span`,{children:e.label})]},e.id))})]}),u&&!u.correct?(0,$.jsx)(`div`,{ref:b,className:`check-feedback`,role:`status`,tabIndex:-1,children:(0,$.jsx)(`strong`,{children:u.message})}):null,(0,$.jsx)(`div`,{className:`check-submit-bar`,children:(0,$.jsx)(`button`,{ref:C,className:`check-primary`,type:`submit`,disabled:!c.size||m,children:m?`Checking parser evidence…`:`Check answer`})})]}):null]})}function jX({overview:e,mode:t,charted:n}){let r=t===`easy`,i=e.languages.map(e=>`${e.label} ${e.count}`).join(` · `);return(0,$.jsxs)(`section`,{className:`project-summary`,"aria-label":`Project summary`,children:[(0,$.jsxs)(`p`,{className:`project-summary__lead`,children:[r?`${e.modules} files of code, holding ${e.structures} functions and classes between them.`:`${e.modules} modules · ${e.structures} structures · ${e.lines} lines.`,` `,e.home?r?`It starts at ${e.home.id}.`:`Entrypoint ${e.home.id}.`:r?`Codemble could not tell which file starts it.`:`No entrypoint resolved.`]}),(0,$.jsxs)(`dl`,{className:`project-summary__facts`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Written in`:`Languages`}),(0,$.jsx)(`dd`,{children:i||`—`})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Biggest files`:`Largest by LOC`}),(0,$.jsx)(`dd`,{children:e.biggest.length?e.biggest.map(e=>`${KG(e.id)} (${e.value})`).join(`, `):`—`})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Called from the most places`:`Highest centrality`}),(0,$.jsx)(`dd`,{children:e.busiest.length?e.busiest.map(e=>`${KG(e.id)} (${e.value})`).join(`, `):`—`})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`You have proved`:`Understood`}),(0,$.jsxs)(`dd`,{children:[e.understood,` of `,e.modules,` `,r?e.modules===1?`file`:`files`:`modules`]})]}),e.unreadable||e.unsupported?(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Not included`:`Outside the graph`}),(0,$.jsx)(`dd`,{children:[e.unreadable?`${e.unreadable} ${e.unreadable===1?`file`:`files`} could not be read`:``,e.unsupported?`${e.unsupported} in a language Codemble does not parse`:``].filter(Boolean).join(` · `)})]}):null,e.importCycles.length?(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:r?`Files that bring each other in`:`Proven import cycles`}),(0,$.jsxs)(`dd`,{children:[e.importCycles.length,` `,e.importCycles.length===1?`cycle group`:`cycle groups`,` · `,e.importCycles.slice().sort((e,t)=>t.length-e.length||e.join(`\0`).localeCompare(t.join(`\0`)))[0].join(`, `),` · each file can reach every other through proven imports`]})]}):null]}),(0,$.jsx)(`button`,{className:`project-summary__export`,type:`button`,onClick:()=>MX(e,n),children:r?`Save a project guide`:`Export Markdown brief`})]})}function MX(e,t){let n=pX(e,{charted:t}),r=URL.createObjectURL(new Blob([n],{type:`text/markdown;charset=utf-8`})),i=document.createElement(`a`);i.href=r,i.download=fX(e),document.body.append(i),i.click(),i.remove(),window.setTimeout(()=>URL.revokeObjectURL(r),0)}function NX({chart:e,studiedCount:t,exploredCount:n,projectName:r,mode:i,overview:a,onClearProgress:o}){let s=e.filter(e=>e.understood_nodes>0).length,c=(0,_.useRef)(null);return(0,_.useLayoutEffect)(()=>{c.current?.focus()},[]),(0,$.jsxs)(`section`,{className:`star-chart-screen`,"aria-labelledby":`star-chart-heading`,children:[(0,$.jsxs)(`header`,{className:`star-chart-intro`,children:[(0,$.jsx)(`p`,{children:r}),(0,$.jsx)(`h1`,{ref:c,id:`star-chart-heading`,tabIndex:-1,children:`What this project is.`}),a?(0,$.jsx)(jX,{overview:a,mode:i,charted:n}):null,(0,$.jsx)(`h2`,{className:`star-chart-section`,children:i===`easy`?`Ideas your code uses`:`Parser-detected concepts`}),(0,$.jsx)(`p`,{children:`Explored counts the systems you have flown to. Encountered comes from real syntax. Studied tracks this session. Understood lights only after graph-derived checks pass.`}),(0,$.jsxs)(`dl`,{children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Systems explored`}),(0,$.jsx)(`dd`,{children:n})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Concepts encountered`}),(0,$.jsx)(`dd`,{children:e.length})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Studied this session`}),(0,$.jsx)(`dd`,{children:t})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Concepts understood`}),(0,$.jsx)(`dd`,{children:s})]})]})]}),(0,$.jsx)(`div`,{className:`concept-ledger`,role:`list`,"aria-label":`Language concept progress`,children:e.map(e=>(0,$.jsxs)(`article`,{className:`concept-row`,role:`listitem`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`h2`,{children:IG(e.concept)}),(0,$.jsxs)(`span`,{children:[IG(e.language),` · `,e.occurrences,` parser `,e.occurrences===1?`occurrence`:`occurrences`]})]}),(0,$.jsx)(`div`,{className:`concept-meter`,"aria-label":`${e.understood_nodes} of ${e.nodes} ${e.nodes===1?`structure`:`structures`} understood`,children:(0,$.jsx)(`span`,{style:{width:`${e.nodes?e.understood_nodes/e.nodes*100:0}%`}})}),(0,$.jsxs)(`dl`,{children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Studied (session)`}),(0,$.jsxs)(`dd`,{children:[e.studied_nodes,`/`,e.nodes]})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`dt`,{children:`Understood`}),(0,$.jsxs)(`dd`,{children:[e.understood_nodes,`/`,e.nodes]})]})]})]},`${e.language}:${e.concept}`))}),(0,$.jsx)(PX,{projectName:r,onConfirm:o})]})}function PX({projectName:e,onConfirm:t}){let[n,r]=(0,_.useState)(!1),[i,a]=(0,_.useState)(!1),[o,s]=(0,_.useState)(``),c=(0,_.useRef)(null),l=(0,_.useRef)(null),u=(0,_.useRef)(!1);(0,_.useEffect)(()=>{n?l.current?.focus():u.current&&c.current?.focus(),u.current=n},[n]);function d(){r(!1),s(``)}async function f(){a(!0),s(``);try{await t(),r(!1)}catch(e){s(e.message)}finally{a(!1)}}return n?(0,$.jsxs)(`section`,{className:`progress-reset progress-reset--confirming`,role:`group`,"aria-label":`Clear this project's progress`,tabIndex:-1,ref:l,onKeyDown:e=>{e.key===`Escape`&&!i&&d()},children:[(0,$.jsxs)(`p`,{role:`alert`,children:[`This dims every system you lit in `,e,` and cannot be undone. Your other projects keep their progress, and no source file is touched.`]}),o?(0,$.jsx)(`p`,{className:`progress-reset__error`,role:`alert`,children:o}):null,(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`button`,{className:`check-primary progress-reset__confirm`,type:`button`,disabled:i,onClick:f,children:i?`Clearing…`:`Yes, clear ${e}`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,disabled:i,onClick:d,children:`Keep it`})]})]}):(0,$.jsxs)(`section`,{className:`progress-reset`,children:[(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,ref:c,onClick:()=>r(!0),children:`Clear this project's progress`}),(0,$.jsxs)(`p`,{children:[`Start `,e,` over with every system dim again.`]})]})}var FX=class extends _.Component{constructor(e){super(e),this.state={message:``}}static getDerivedStateFromError(e){return{message:e instanceof Error?e.message:String(e)}}componentDidCatch(e,t){console.error(`Codemble stopped rendering:`,e,t?.componentStack)}render(){return this.state.message?(0,$.jsxs)(`main`,{className:`load-state`,role:`alert`,children:[(0,$.jsx)(`h1`,{children:`The galaxy stopped rendering.`}),(0,$.jsx)(`p`,{children:this.state.message}),(0,$.jsx)(`p`,{children:`Your progress is saved on this machine; reloading re-reads it.`}),(0,$.jsx)(`button`,{className:`check-primary`,type:`button`,onClick:()=>window.location.reload(),children:`Reload Codemble`})]}):this.props.children}};(0,v.createRoot)(document.getElementById(`root`)).render((0,$.jsx)(_.StrictMode,{children:(0,$.jsx)(FX,{children:(0,$.jsx)(mX,{})})})); \ No newline at end of file diff --git a/codemble/web_dist/assets/index-DgSklSeG.css b/codemble/web_dist/assets/index-R8cZVoqz.css similarity index 71% rename from codemble/web_dist/assets/index-DgSklSeG.css rename to codemble/web_dist/assets/index-R8cZVoqz.css index f6894a0..8f1e90d 100644 --- a/codemble/web_dist/assets/index-DgSklSeG.css +++ b/codemble/web_dist/assets/index-R8cZVoqz.css @@ -1 +1 @@ -@font-face{font-family:Zen Kaku Gothic New;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/zen-kaku-gothic-new-latin-400-normal-BEdayliK.woff2)format("woff2"),url(/assets/zen-kaku-gothic-new-latin-400-normal-CPSmNJAU.woff)format("woff")}@font-face{font-family:Shippori Mincho;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/shippori-mincho-latin-500-normal-XI1O8euf.woff2)format("woff2"),url(/assets/shippori-mincho-latin-500-normal-C-QwvIb3.woff)format("woff")}@font-face{font-family:Shippori Mincho;font-style:normal;font-display:swap;font-weight:700;src:url(/assets/shippori-mincho-latin-700-normal-DHcmzUO5.woff2)format("woff2"),url(/assets/shippori-mincho-latin-700-normal-CkoCYOiI.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff)format("woff")}:root{--cm-font-display:"Shippori Mincho", "Hiragino Mincho ProN", "Yu Mincho", serif;--cm-font-body:"Zen Kaku Gothic New", "Hiragino Sans", system-ui, sans-serif;--cm-font-mono:"JetBrains Mono", ui-monospace, monospace;--cm-text-xs:.75rem;--cm-text-sm:.875rem;--cm-text-base:1rem;--cm-text-lg:1.25rem;--cm-text-xl:clamp(1.25rem, .5vw + 1.1rem, 1.5rem);--cm-text-display:clamp(1.75rem, 3vw + 1rem, 3.25rem);--cm-space-2xs:.125rem;--cm-space-xs:.5rem;--cm-space-sm:.75rem;--cm-space-md:1rem;--cm-space-lg:2rem;--cm-space-xl:4rem;--cm-space-2xl:6rem;--cm-radius:.25rem;--cm-rule:1px;--cm-ease:cubic-bezier(.16, .7, .3, 1);--cm-dur-quick:.16s;--cm-dur-reveal:.42s;--cm-dur-warp:.9s;--cm-z-base:1;--cm-z-raised:10;--cm-z-sticky:200;--cm-z-panel:400;--cm-ground:#070b1c;--cm-ground-2:#101a3e;--cm-hairline:#24325c;--cm-hairline-soft:#17203f;--cm-ink:#eef2fa;--cm-ink-2:#9aa8c4;--cm-ink-3:#7d8aa8;--cm-star:#e89b2e;--cm-star-high:#f6d087;--cm-orbit:#82abec;--cm-orbit-deep:#2b4d96;--cm-orbit-low:#14264a;--cm-error:oklch(72% .16 28);--cm-scrim:color-mix(in srgb, var(--cm-ground) 82%, transparent);--cm-sidebar-width:min(19rem, 82vw);--cm-sky:#131f4b;--cm-sky-glow:#2a4b91;--cm-node-unlit:#7381a2;--cm-node-mid:#8e9ab4;--cm-node-bright:#a9b1c5;--cm-star-cool:#96b4ec;--cm-star-pale:#d6def0;--cm-route-possible:#92a0c2;--cm-route:#607198;--cm-neb-python:#70a690;--cm-neb-js:#a197b9;--cm-neb-ts:#77a1ba;--cm-neb-go:#62a7ae;--cm-neb-java:#c48c98;--cm-neb-rust:#c38ab2;--cm-neb-csharp:#78a957;--cm-star-halo:#f2efe9;--cm-com-0:#88c3ac;--cm-com-1:#c7acdb;--cm-com-2:#acbd8a;--cm-com-3:#89bfca;--cm-com-4:#dea9b7;--cm-com-5:#caafbd;--cm-com-6:#7cc69b;--cm-com-7:#b9b0e0;--cm-label-plate:#090e1cd1;--cm-label-ink:#d6dbe7}*{box-sizing:border-box}html,body,#root{width:100%;min-height:100%;margin:0;overflow-x:clip}html{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}body{background:var(--cm-ground);color:var(--cm-ink);font-family:var(--cm-font-body);font-size:var(--cm-text-base);line-height:1.55}button,output{font:inherit}button:focus-visible,[href]:focus-visible,[tabindex]:focus-visible,input:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:3px}.app-shell{background:var(--cm-ground);width:100%;height:100dvh;min-height:100dvh;color:var(--cm-ink);grid-template-rows:auto minmax(0,1fr) auto auto;grid-template-columns:minmax(0,1fr);display:grid;position:relative;overflow:hidden}.instrument-rail{z-index:calc(var(--cm-z-panel) + 2);align-items:center;gap:var(--cm-space-sm) var(--cm-space-md);min-width:0;max-width:100%;padding:var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-left));border-block-end:1px solid var(--cm-hairline);background:var(--cm-ground);grid-row:1;grid-template-columns:minmax(0,1fr) auto;display:grid;position:relative}.brand-lockup{align-items:center;gap:var(--cm-space-sm);min-width:0;max-width:18rem;display:flex}.brand-lockup div{min-width:0;display:grid}.brand-lockup strong{font-family:var(--cm-font-display);letter-spacing:-.02em;text-overflow:ellipsis;white-space:nowrap;font-weight:700;overflow:hidden}.brand-lockup span:not(.brand-mark),.home-readout,.location{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.brand-mark{width:var(--cm-space-md);height:var(--cm-space-md);border:1px solid var(--cm-ink-2);background:var(--cm-ink);border-radius:50%}.location{grid-area:2/1/auto/-1;margin:0}.rail-overflow{grid-area:1/2;justify-self:end}.mobile-menu-trigger{min-width:44px;min-height:44px;padding-inline:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-orbit);cursor:pointer;place-items:center;font-weight:600;display:grid}.rail-overflow__panel{z-index:var(--cm-z-panel);gap:var(--cm-space-md);max-height:calc(100dvh - 5rem);padding:var(--cm-space-md);border-block:1px solid var(--cm-hairline);background:var(--cm-ground-2);display:none;position:absolute;inset-block-start:100%;inset-inline:0;overflow-y:auto;box-shadow:0 1rem 2.5rem #00000061}.rail-overflow[data-open] .rail-overflow__panel{display:grid}.instrument-rail:has(.rail-overflow[data-open]){z-index:calc(var(--cm-z-panel) + 2)}@media not all and (width>=64rem){.rail-overflow__panel .rail-actions,.rail-overflow__panel .rail-controls{grid-area:auto;justify-content:start}}.rail-action{min-height:44px;padding-inline:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-orbit);cursor:pointer;white-space:nowrap;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease), transform .1s var(--cm-ease);font-weight:600}@media (hover:hover) and (pointer:fine){.rail-action:hover{background:var(--cm-orbit-low)}}.rail-action:active{transform:translateY(1px)}.rail-action:disabled,.rail-action[aria-disabled=true]{opacity:.5;cursor:not-allowed}.rail-action[data-state=loading]{color:var(--cm-ink-2)}.rail-action[data-state=error]{border-color:var(--cm-error)}.rail-action[data-state=success]{border-color:var(--cm-star);color:var(--cm-star)}.rail-actions{justify-content:end;align-items:center;gap:var(--cm-space-sm);flex-wrap:wrap;grid-area:2/1/auto/-1;min-width:0;display:flex}.rail-more{justify-content:end;align-items:center;gap:var(--cm-space-sm);flex-wrap:wrap;grid-area:3/1/auto/-1;min-width:0;display:flex}.rail-controls{align-items:center;gap:var(--cm-space-md);flex-wrap:wrap;grid-area:4/1/auto/-1;min-width:0;display:flex}.rail-trigger__wide{display:none}.switch-project{display:contents}.switch-project[data-confirming]{gap:var(--cm-space-xs);padding:var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);display:grid}.switch-project[data-confirming] p{max-width:26ch;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin:0}.switch-project__error{color:var(--cm-error)!important}.switch-project[data-confirming]>div{gap:var(--cm-space-xs);display:flex}.language-focus{align-items:center;gap:var(--cm-space-sm);min-width:0;color:var(--cm-ink-3);scrollbar-width:none;display:flex;overflow-x:auto}.language-focus::-webkit-scrollbar{display:none}.language-focus__label{font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;flex:none}.language-focus>div{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);min-width:max-content;display:flex}.language-focus button{justify-content:center;align-items:center;gap:var(--cm-space-xs);min-width:44px;min-height:44px;padding-inline:var(--cm-space-sm);border:0;border-inline-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:nowrap;background:0 0;display:inline-flex}.language-focus button:last-child{border-inline-end:0}.language-focus button[aria-pressed=true]{background:var(--cm-orbit-low);color:var(--cm-orbit)}.language-focus button small{color:var(--cm-ink-3);font-size:inherit;font-variant-numeric:tabular-nums}@media (hover:hover) and (pointer:fine){.language-focus button:hover{background:var(--cm-ground-2);color:var(--cm-ink)}.language-focus button[aria-pressed=true]:hover{background:var(--cm-orbit-low);color:var(--cm-orbit)}}.mode-toggle{align-items:center;gap:var(--cm-space-sm);border:0;grid-area:4/1/auto/-1;min-width:0;margin:0;padding:0;display:flex}.mode-toggle__label{float:inline-start;color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;flex:none;margin-inline-end:var(--cm-space-sm);padding:0}.mode-toggle__options{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);min-width:max-content;display:flex}.mode-toggle__options label{align-items:center;gap:var(--cm-space-2xs);min-width:44px;min-height:44px;padding-inline:var(--cm-space-sm);border-inline-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);display:inline-flex}.mode-toggle__options label:last-child{border-inline-end:0}.mode-toggle__options label:has(input:checked){background:var(--cm-orbit-low);color:var(--cm-orbit)}.mode-toggle__options input{width:1rem;height:1rem;accent-color:var(--cm-orbit);margin:0}@media (hover:hover) and (pointer:fine){.mode-toggle__options label:hover{background:var(--cm-ground-2);color:var(--cm-ink)}.mode-toggle__options label:has(input:checked):hover{background:var(--cm-orbit-low);color:var(--cm-orbit)}}.mode-gate{width:min(26rem, calc(100% - var(--cm-space-xl)));max-height:min(86%,30rem);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;margin:auto;overflow:auto}.mode-gate::backdrop{background:var(--cm-scrim)}.mode-gate h1{overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);margin:0;font-style:normal;line-height:1.15}.mode-gate p{max-width:42ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink-2)}.mode-gate__options{gap:var(--cm-space-sm);margin-block-start:var(--cm-space-lg);display:grid}.mode-gate__options button{min-height:52px;padding:var(--cm-space-sm) var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-orbit);cursor:pointer;font:inherit;text-align:start;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease);font-weight:600}@media (hover:hover) and (pointer:fine){.mode-gate__options button:hover{background:var(--cm-orbit-low);color:var(--cm-ink)}}.mode-gate__options button:active{transform:translateY(1px)}.map-stage{background:var(--cm-ground);grid-row:2;min-width:0;max-width:100%;min-height:0;position:relative;overflow:clip}.chart-stage{background:var(--cm-ground);grid-row:2;min-width:0;min-height:0;position:relative;overflow:hidden}.chart-stage .star-chart-screen{height:100%}.galaxy-frame,.galaxy-canvas{z-index:var(--cm-z-base);position:absolute;inset:0}.galaxy-frame{overflow:hidden}.galaxy-canvas canvas{display:block}.keyboard-focus{max-width:min(34rem, calc(100% - var(--cm-space-lg)));padding:var(--cm-space-sm) var(--cm-space-md);border-inline-start:2px solid var(--cm-orbit);background:var(--cm-ground-2);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);position:absolute;inset-block-end:var(--cm-space-md);inset-inline-start:var(--cm-space-md)}.orientation-bar{z-index:var(--cm-z-raised);gap:var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);pointer-events:none;flex-wrap:wrap;margin:0;display:flex;position:absolute;inset-block-start:var(--cm-space-md);inset-inline-start:max(var(--cm-space-md), env(safe-area-inset-left))}.orientation-bar>*+:before{content:"· ";color:var(--cm-hairline)}.orientation-bar__charted{color:var(--cm-ink-2)}.index-sidebar{z-index:var(--cm-z-panel);width:var(--cm-sidebar-width);border-inline-end:1px solid var(--cm-hairline);background:var(--cm-ground-2);flex-direction:column;display:flex;position:absolute;inset-block:0;inset-inline-start:0}@media (width>=60rem){:is(.map-stage:has(.index-sidebar) .galaxy-frame,.map-stage:has(.index-sidebar) .map-view){inset-inline-start:var(--cm-sidebar-width)}:is(.map-stage:has(.index-sidebar) .orientation-bar,.map-stage:has(.index-sidebar) .orientation-copy){inset-inline-start:calc(var(--cm-sidebar-width) + var(--cm-space-md))}}.index-sidebar>header{justify-content:space-between;align-items:center;gap:var(--cm-space-sm);padding:var(--cm-space-sm) var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);display:flex}.index-sidebar h2{color:var(--cm-ink);font-size:var(--cm-text-sm);letter-spacing:.08em;text-transform:uppercase;margin:0}.index-sidebar>header button{border-radius:var(--cm-radius);min-width:32px;min-height:32px;color:var(--cm-ink-2);font-size:var(--cm-text-lg);cursor:pointer;background:0 0;border:1px solid #0000;line-height:1}.index-sidebar>header button:hover,.index-sidebar>header button:focus-visible{border-color:var(--cm-orbit);color:var(--cm-ink)}.index-sidebar__scroll{flex:1;padding-block-end:var(--cm-space-lg);overflow-y:auto}.index-sidebar section{padding-block-start:var(--cm-space-sm)}.index-sidebar h3{padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-overflow:ellipsis;white-space:nowrap;margin:0;font-weight:400;overflow:hidden}.index-sidebar ul{margin:0;padding:0;list-style:none}.index-sidebar li button{align-items:baseline;gap:var(--cm-space-sm);width:100%;min-height:32px;padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-align:start;cursor:pointer;background:0 0;border:0;border-inline-start:2px solid #0000;display:flex}.index-sidebar li button span{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.index-sidebar li button:hover,.index-sidebar li button:focus-visible{border-inline-start-color:var(--cm-orbit);background:var(--cm-ground);color:var(--cm-ink)}.index-sidebar li button[aria-current=true]{border-inline-start-color:var(--cm-orbit);color:var(--cm-ink)}.index-sidebar li button[data-lit] span{color:var(--cm-star-high)}.index-sidebar li em{color:var(--cm-ink-3);font-size:var(--cm-text-xs);font-style:normal}.module-finder{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);width:min(34rem,92vw);max-height:70vh;color:var(--cm-ink);padding:0}.module-finder::backdrop{background:var(--cm-scrim)}.module-finder input{width:100%;padding:var(--cm-space-md);border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-base);background:0 0}.module-finder input:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:-2px}.module-finder ul{max-height:50vh;padding:var(--cm-space-2xs) 0;margin:0;list-style:none;overflow-y:auto}.module-finder li button{align-items:baseline;gap:var(--cm-space-sm);width:100%;min-height:36px;padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-align:start;cursor:pointer;background:0 0;border:0;border-inline-start:2px solid #0000;display:flex}.module-finder li button[data-active]{border-inline-start-color:var(--cm-orbit);background:var(--cm-ground);color:var(--cm-ink)}.module-finder li strong{font-weight:400}.module-finder li small{color:var(--cm-ink-3);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.module-finder li em{color:var(--cm-star-high);font-style:normal}.module-finder__empty{padding:var(--cm-space-md);color:var(--cm-ink-3);font-size:var(--cm-text-sm);margin:0}.rail-action kbd{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin-inline-start:var(--cm-space-2xs)}.rail-action--toggle[aria-pressed=true]{border-color:var(--cm-orbit);color:var(--cm-orbit)}.orientation-copy{z-index:var(--cm-z-raised);max-width:min(28rem, calc(100% - var(--cm-space-lg)));pointer-events:none;position:absolute;inset-block-start:clamp(var(--cm-space-lg), 8vh, var(--cm-space-2xl));inset-inline-start:max(var(--cm-space-md), env(safe-area-inset-left))}.orientation-copy--system{inset-block-start:var(--cm-space-lg)}.orientation-copy h1,.study-preview h1,.load-state h1,.webgl-error h1{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-display);letter-spacing:-.035em;margin:0;font-style:normal;font-weight:700;line-height:1.08}.orientation-copy p,.study-preview p{max-width:55ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink-2)}.orientation-copy .system-orbit-note{align-items:center;gap:var(--cm-space-2xs) var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);flex-wrap:wrap;display:flex}.system-orbit-note>span{color:var(--cm-ink-2);text-transform:uppercase;letter-spacing:.08em}.system-orbit-note i{border-inline-start:1.5rem solid var(--cm-route);color:var(--cm-ink-2);padding-inline-start:var(--cm-space-sm);font-style:normal;line-height:1}.system-orbit-note i[data-unproven]{border-inline-start-color:var(--cm-hairline-soft);text-underline-offset:.22em;-webkit-text-decoration:underline dashed;text-decoration:underline dashed}.system-orbit-note small{color:var(--cm-ink-3);font-size:inherit;flex-basis:100%}.orientation-copy--inline{z-index:auto;pointer-events:auto;flex-direction:column;flex:none;max-width:none;display:flex;position:static}.orientation-copy--inline h1{font-size:var(--cm-text-lg)}.orientation-copy--inline p{font-size:var(--cm-text-sm);margin-block-start:var(--cm-space-2xs)}.orientation-copy__actions{gap:var(--cm-space-sm);flex-wrap:wrap;align-items:center;margin-block-start:var(--cm-space-sm);display:flex}.orientation-copy__actions .check-launch{margin-block-start:0}.check-launch--read{border-color:var(--cm-hairline);color:var(--cm-ink)}.check-launch--read:hover,.check-launch--read:focus-visible{border-color:var(--cm-orbit);color:var(--cm-orbit)}.check-launch,.check-primary,.check-close{border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);min-height:44px;color:var(--cm-orbit);cursor:pointer;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease), transform .1s var(--cm-ease);font-weight:600}.check-launch{padding-inline:var(--cm-space-md);pointer-events:auto;margin-block-start:var(--cm-space-md);position:relative}.check-primary{padding-inline:var(--cm-space-lg);justify-self:start;margin-block-start:var(--cm-space-sm)}.check-submit-bar{z-index:1;padding-block:var(--cm-space-xs) var(--cm-space-md);background:var(--cm-ground-2);margin-block-end:calc(-1 * var(--cm-space-lg));display:grid;position:sticky;inset-block-end:calc(-1 * var(--cm-space-lg))}.check-submit-bar .check-primary{margin-block-start:0}.active-check :is(fieldset,.check-feedback){scroll-margin-block-end:5rem}.check-close{padding-inline:var(--cm-space-md);border-color:var(--cm-hairline);color:var(--cm-ink-2)}@media (hover:hover) and (pointer:fine){.check-launch:hover,.check-primary:hover,.check-close:hover{background:var(--cm-orbit-low);color:var(--cm-ink)}}.check-launch:active,.check-primary:active,.check-close:active{transform:translateY(1px)}.check-primary:disabled{opacity:.45;cursor:not-allowed;transform:none}.legend-toggle{z-index:var(--cm-z-sticky);min-height:32px;padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;position:absolute;inset-block-start:var(--cm-space-md);inset-inline-end:var(--cm-space-md)}.legend-toggle:hover,.legend-toggle:focus-visible{border-color:var(--cm-orbit);color:var(--cm-ink)}.legend-toggle[aria-expanded=true]{border-color:var(--cm-orbit);color:var(--cm-orbit)}.map-legend{--cm-legend-top:calc(var(--cm-space-md) + 40px);z-index:var(--cm-z-sticky);max-width:min(38rem, calc(100% - 2 * var(--cm-space-md)));max-height:min(60vh, 30rem, calc(100dvh - 124px - var(--cm-legend-top) - var(--cm-space-md)));gap:var(--cm-space-2xs) var(--cm-space-md);padding:var(--cm-space-sm) var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);--cm-scroll-cover:var(--cm-ground-2);background-color:var(--cm-ground-2);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);grid-template-columns:minmax(0,1fr);display:none;position:absolute;inset-block-start:var(--cm-legend-top);inset-inline-end:var(--cm-space-md);overflow-y:auto;box-shadow:0 16px 40px #04081452}.map-legend:not([hidden]){display:grid}.map-stage:has(.map-view) .map-legend{--cm-legend-top:calc(var(--cm-space-md) + 40px + 44px)}.map-legend span{align-items:center;gap:var(--cm-space-sm);white-space:normal;min-width:0;display:inline-flex}.legend-dot{width:var(--cm-space-sm);height:var(--cm-space-sm);border-radius:50%;display:inline-block}.legend-dot--dim{background:var(--cm-ink-3)}.legend-dot--lit{background:var(--cm-star-high)}.legend-dot--partial{background:var(--cm-route-possible);clip-path:polygon(0 0,100% 0,100% 100%);border-radius:0}.legend-route{width:var(--cm-space-md);border-block-start:2px solid var(--cm-route)}.legend-route--possible{border-block-start-color:var(--cm-route-possible)}.partial-summary,.unsupported-summary{border-inline-start:2px solid var(--cm-route-possible);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);padding-inline-start:var(--cm-space-sm);color:var(--cm-ink)!important}.entrypoint-picker{z-index:var(--cm-z-panel);width:min(36rem, calc(100vw - var(--cm-space-xl)));max-height:min(80dvh,42rem);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);background:var(--cm-ground-2);color:var(--cm-ink);animation:entrypoint-enter var(--cm-dur-reveal) var(--cm-ease) both}.entrypoint-picker[open]{flex-direction:column;display:flex;overflow:hidden}.entrypoint-picker::backdrop{background:color-mix(in srgb, var(--cm-ground) 88%, transparent)}.entrypoint-picker>p:first-child{color:var(--cm-orbit);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin:0}.entrypoint-picker h1{margin:var(--cm-space-sm) 0 0;overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);line-height:1.1}.entrypoint-picker>p:not(:first-child){margin:var(--cm-space-md) 0 0;color:var(--cm-ink-2)}.entrypoint-count{margin-block:var(--cm-space-md) 0;color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.entrypoint-scroll{min-height:0;border-block-start:1px solid var(--cm-hairline);flex:auto;margin-block-start:var(--cm-space-sm);overflow-y:auto}@media (width<=39.999rem),(height<=44rem){.entrypoint-picker{width:calc(100vw - (2 * var(--cm-space-sm)));height:calc(100dvh - (2 * var(--cm-space-sm)));max-height:none;padding:var(--cm-space-md);animation-name:entrypoint-enter-reduced}.entrypoint-scroll{flex-basis:min(10rem,28dvh);min-height:min(7.5rem,22dvh)}}@media (height<=32rem){.entrypoint-picker{padding:var(--cm-space-sm)}.entrypoint-picker h1{font-size:var(--cm-text-lg);margin-block-start:var(--cm-space-xs)}.entrypoint-picker>p:not(:first-child),.entrypoint-count,.entrypoint-continue{margin-block-start:var(--cm-space-xs)}.entrypoint-picker>p:not(:first-child){font-size:var(--cm-text-xs);line-height:1.3}.entrypoint-scroll{flex-basis:min(6rem,24dvh);min-height:min(4rem,18dvh)}}.entrypoint-scroll details{border-block-end:1px solid var(--cm-hairline)}.entrypoint-scroll summary{justify-content:space-between;align-items:baseline;gap:var(--cm-space-sm);padding:var(--cm-space-sm) 0;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;display:flex}.entrypoint-scroll summary small{color:var(--cm-ink-3)}.entrypoint-candidates{border-block-start:1px solid var(--cm-hairline)}.entrypoint-candidates button{gap:var(--cm-space-xs);width:100%;min-height:58px;padding:var(--cm-space-sm) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.entrypoint-candidates button span{overflow-wrap:anywhere;font-family:var(--cm-font-mono)}.entrypoint-candidates button small{color:var(--cm-ink-3);font-family:var(--cm-font-mono)}@media (hover:hover) and (pointer:fine){.entrypoint-candidates button:hover{color:var(--cm-orbit)}}.entrypoint-continue{width:100%;min-height:44px;padding:var(--cm-space-xs) 0 0;border:0;border-block-start:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;text-underline-offset:3px;background:0 0;flex:none;margin-block-start:var(--cm-space-md);text-decoration:underline}.entrypoint-error{color:var(--cm-error)!important}.illumination-pulse{z-index:var(--cm-z-raised);color:var(--cm-star);pointer-events:none;animation:illumination-pulse .42s var(--cm-ease) both;justify-items:center;display:grid;position:absolute;inset-block-start:50%;inset-inline-start:25%;transform:translate(-50%,-50%)}.illumination-pulse span{filter:drop-shadow(0 0 18px var(--cm-star));font-size:clamp(3rem,8vw,6rem);line-height:1}.illumination-pulse strong{color:var(--cm-ink);font-family:var(--cm-font-display)}.structural-summary p{max-width:62ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink)}.mini-constellation{width:100%;max-width:22rem;height:auto;margin-block-start:var(--cm-space-md)}.mini-constellation line{stroke:var(--cm-route);stroke-width:1px}.mini-constellation__name{fill:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:9px}.mini-constellation circle{fill:var(--cm-ink-3)}.mini-constellation__self{fill:var(--cm-orbit)}.connections h3{margin:var(--cm-space-lg) 0 0;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;font-weight:500}.connection-list{margin:var(--cm-space-xs) 0 0;border-block-start:1px solid var(--cm-hairline);padding:0;list-style:none}.connection-list button{gap:var(--cm-space-2xs);width:100%;min-height:52px;padding:var(--cm-space-sm) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.connection-name{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.connection-meta{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}@media (hover:hover) and (pointer:fine){.connection-list button:hover .connection-name{color:var(--cm-orbit)}}.load-state .check-primary,.study-notice .check-primary{justify-self:start;margin-block-start:var(--cm-space-md)}.study-preview{z-index:var(--cm-z-panel);width:100%;min-width:0;max-width:100%;max-height:72%;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-lg), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background-color:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;position:absolute;inset-block-end:0;inset-inline:0;overflow:auto}.check-panel{z-index:var(--cm-z-panel);width:100%;min-width:0;max-width:100%;max-height:76%;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-lg), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background-color:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;position:absolute;inset-block-end:0;inset-inline:0;overflow:auto}.study-preview,.check-panel,.star-chart-screen,.module-finder ul,.index-sidebar__scroll,.map-legend,.map-view{--cm-scroll-cover:var(--cm-ground-2);background-image:linear-gradient(var(--cm-scroll-cover) 30%, transparent), linear-gradient(transparent, var(--cm-scroll-cover) 70%), radial-gradient(farthest-side at 50% 0, #0000008c, transparent), radial-gradient(farthest-side at 50% 100%, #0000008c, transparent);background-position:0 0,0 100%,0 0,0 100%;background-repeat:no-repeat;background-size:100% 2.5rem,100% 2.5rem,100% .85rem,100% .85rem;background-attachment:local,local,scroll,scroll}.star-chart-screen{--cm-scroll-cover:var(--cm-ground)}.check-panel__header{justify-content:space-between;align-items:start;gap:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);padding-block-end:var(--cm-space-lg);display:flex}.check-panel__header p,.check-panel__header h1{margin:0}.check-panel__header p,.check-progress span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.check-panel__header h1{overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);margin-block-start:var(--cm-space-xs);line-height:1.1}.check-loading,.check-state,.check-complete,.active-check{margin-block-start:var(--cm-space-lg)}.check-loading,.check-state p,.check-complete p,.active-check fieldset>p{max-width:60ch;color:var(--cm-ink-2)}.check-state h2,.check-complete h2{font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0}.check-state p,.check-complete p{margin:var(--cm-space-sm) 0 0}.check-complete{gap:var(--cm-space-sm);display:grid}.check-complete__star{color:var(--cm-star);font-size:var(--cm-text-display);line-height:1}.check-progress{align-items:center;gap:var(--cm-space-md);grid-template-columns:auto minmax(5rem,1fr);margin-block-end:var(--cm-space-xl);display:grid}.check-progress progress{background:var(--cm-hairline);width:100%;height:2px;color:var(--cm-star);border:0}.check-progress progress::-webkit-progress-bar{background:var(--cm-hairline)}.check-progress progress::-webkit-progress-value{background:var(--cm-star)}.check-progress progress::-moz-progress-bar{background:var(--cm-star)}.active-check{gap:var(--cm-space-lg);display:grid}.active-check fieldset{border:0;min-width:0;margin:0;padding:0}.active-check legend{overflow-wrap:break-word;max-width:100%;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);padding:0;font-weight:700;line-height:1.15}.active-check fieldset>p{margin:var(--cm-space-sm) 0 0}.check-options{border-block-start:1px solid var(--cm-hairline);margin-block-start:var(--cm-space-lg)}.check-options label{align-items:center;gap:var(--cm-space-md);min-height:52px;padding-block:var(--cm-space-sm);border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;grid-template-columns:auto minmax(0,1fr);display:grid}.check-options label:has(input:checked){color:var(--cm-ink)}.check-options input{width:1rem;height:1rem;accent-color:var(--cm-star);margin:0}.check-options span{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.check-feedback{gap:var(--cm-space-xs);padding:var(--cm-space-md);border-inline-start:2px solid var(--cm-error);background:var(--cm-ground);color:var(--cm-ink-2);display:grid}.check-feedback strong{color:var(--cm-ink)}.check-feedback span{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.check-affirmation{align-items:baseline;gap:var(--cm-space-xs);margin:0 0 var(--cm-space-lg);border-inline-start:2px solid var(--cm-star);color:var(--cm-star-high);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);padding-inline-start:var(--cm-space-sm);display:flex}.study-preview__path{font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);color:var(--cm-orbit)!important}.study-preview__header{display:block}.study-preview dl{gap:var(--cm-space-md);margin:var(--cm-space-lg) 0 0;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.study-preview dl div{border-block-start:1px solid var(--cm-hairline);padding-block-start:var(--cm-space-sm)}.study-preview dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.study-preview dd{margin:var(--cm-space-2xs) 0 0;font-variant-numeric:tabular-nums}.study-content{gap:var(--cm-space-xl);min-width:0;margin-block-start:var(--cm-space-xl);display:grid}.study-content>*{min-width:0}.study-section-heading{justify-content:space-between;align-items:center;gap:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);padding-block-end:var(--cm-space-sm);display:flex}.study-section-heading h2,.study-notice h2{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal;line-height:1.15}.study-section-heading span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:nowrap}.source-code{width:100%;min-width:0;max-height:22rem;margin:var(--cm-space-md) 0 0;padding:var(--cm-space-sm) 0;border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-ink);counter-reset:source-line;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);tab-size:4;line-height:1.65;list-style:none;overflow:auto}.source-code li{min-width:max-content;padding-inline:var(--cm-space-sm) var(--cm-space-md);grid-template-columns:3.25rem minmax(0,1fr);display:grid}.source-code li:before{content:attr(data-line);color:var(--cm-ink-3);text-align:end;-webkit-user-select:none;user-select:none;padding-inline-end:var(--cm-space-md)}.source-code li:target{background:var(--cm-orbit-low)}.source-code code{font:inherit;white-space:pre}.grounded-explanation>p,.study-notice p,.study-loading,.evidence-list p{max-width:65ch;color:var(--cm-ink-2)}.grounded-explanation>p,.study-notice p,.study-loading{margin:var(--cm-space-md) 0 0}.grounded-explanation h3{margin:var(--cm-space-lg) 0 var(--cm-space-sm);font-family:var(--cm-font-display);font-size:var(--cm-text-base);font-style:normal}.source-citation{color:var(--cm-orbit);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-underline-offset:2px;text-decoration-thickness:1px}.source-citation:active{color:var(--cm-ink)}@media (hover:hover) and (pointer:fine){.source-citation:hover{color:var(--cm-ink)}}.source-citation--button{cursor:pointer;background:0 0;border:0;padding:0}.source-citation--button:disabled{color:var(--cm-ink-3);cursor:not-allowed}.evidence-list{gap:var(--cm-space-sm);margin:0;padding:0;list-style:none;display:grid}.evidence-list li,.study-notice{padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground)}.evidence-list p{margin:0 0 var(--cm-space-xs)}.evidence-list strong{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);margin-block-end:var(--cm-space-xs);display:block}.lens-notes{margin-block-start:var(--cm-space-sm);display:grid}.lens-note{gap:var(--cm-space-sm);min-width:0;padding-block:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);display:grid}.lens-note h3{margin:0 0 var(--cm-space-xs);font-family:var(--cm-font-display);font-size:var(--cm-text-base);font-style:normal}.lens-note p{max-width:65ch;margin:0 0 var(--cm-space-sm);color:var(--cm-ink-2)}.lens-note code{min-width:0;padding:var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:pre;display:block;overflow-x:auto}.star-chart-screen{min-width:0;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-xl) max(var(--cm-space-md), env(safe-area-inset-left));background-color:var(--cm-ground);overflow:auto}.star-chart-intro{max-width:72rem;margin-inline:auto;padding-block-end:var(--cm-space-xl);display:block}.star-chart-intro>p:first-child{margin:0 0 var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.star-chart-intro h1{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-display);letter-spacing:-.035em;margin:0;font-style:normal;line-height:1.08}.star-chart-intro>p:not(:first-child){max-width:65ch;margin:var(--cm-space-md) 0 0;color:var(--cm-ink-2)}.star-chart-intro dl{gap:var(--cm-space-md);margin:var(--cm-space-xl) 0 0;padding-block:var(--cm-space-md);border-block:1px solid var(--cm-hairline);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.star-chart-intro dt,.concept-row dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.star-chart-intro dd{margin:var(--cm-space-xs) 0 0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);font-variant-numeric:tabular-nums}.concept-ledger{border-block-start:1px solid var(--cm-hairline);max-width:72rem;margin-inline:auto}.concept-row{align-items:center;gap:var(--cm-space-lg);min-width:0;padding-block:var(--cm-space-lg);border-block-end:1px solid var(--cm-hairline);grid-template-columns:minmax(10rem,.7fr) minmax(8rem,1.2fr) auto;display:grid}.concept-row h2{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal}.concept-row>div:first-child>span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.concept-meter{background:var(--cm-hairline);height:2px;overflow:hidden}.concept-meter span{background:var(--cm-star);height:100%;display:block}.concept-row dl{align-items:center;gap:var(--cm-space-lg);margin:0;display:flex}.concept-row dl div{min-width:5rem}.concept-row dd{margin:var(--cm-space-xs) 0 0;font-variant-numeric:tabular-nums}.status-line{z-index:var(--cm-z-sticky);justify-content:space-between;gap:var(--cm-space-2xs) var(--cm-space-md);min-width:0;max-width:100%;padding:var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-sm), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background:var(--cm-ground);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);font-variant-numeric:tabular-nums;flex-wrap:wrap;grid-row:4;display:flex;position:relative}.status-line span{white-space:normal}.status-line span:nth-child(2){display:none}.load-state,.webgl-error{align-content:center;gap:var(--cm-space-md);min-height:100dvh;padding:var(--cm-space-lg);background:var(--cm-ground);color:var(--cm-ink);display:grid}.load-state p,.webgl-error p{max-width:65ch;color:var(--cm-ink-2);margin:0}@keyframes panel-enter{0%{opacity:0;transform:translateY(var(--cm-space-md))}to{opacity:1;transform:translateY(0)}}@keyframes entrypoint-enter{0%{opacity:0;transform:translateY(var(--cm-space-md))}to{opacity:1;transform:translateY(0)}}@keyframes illumination-pulse{0%{opacity:0;transform:translate(-50%,-50%)scale(.75)}45%{opacity:1;transform:translate(-50%,-50%)scale(1.08)}to{opacity:0;transform:translate(-50%,-50%)scale(1)}}@media (width>=40rem){.status-line span{white-space:nowrap;display:inline}.lens-note{gap:var(--cm-space-lg);grid-template-columns:minmax(8rem,.35fr) minmax(0,1fr)}.study-preview{border-block-start:0;border-inline-start:1px solid var(--cm-hairline);width:min(48rem,60%);max-height:none;inset-block:0;inset-inline-start:auto}.check-panel{border-block-start:0;border-inline-start:1px solid var(--cm-hairline);width:min(42rem,56%);max-height:none;inset-block:0;inset-inline-start:auto}}@media (width>=64rem){.instrument-rail{grid-template-columns:minmax(0,auto) minmax(9rem,1fr) auto auto}.rail-overflow{display:contents}.rail-overflow__panel,.rail-overflow[data-open] .rail-overflow__panel{display:contents;position:static}.location{grid-area:1/2}.rail-action,.home-readout{justify-self:end}.rail-actions{flex-wrap:nowrap;grid-area:1/3;justify-content:flex-end;min-width:max-content}.rail-more{z-index:var(--cm-z-panel);width:max-content;max-width:min(22rem,100%);padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);grid-area:auto;display:none;position:absolute;inset-block-start:100%;inset-inline-end:max(var(--cm-space-md), env(safe-area-inset-right));box-shadow:0 1rem 2.5rem #00000061}.rail-overflow[data-open] .rail-more{display:flex}.mobile-menu-trigger{min-width:0;padding-inline:var(--cm-space-sm);grid-area:1/4;place-self:center end}.rail-trigger__compact{display:none}.rail-trigger__wide{display:inline}.rail-controls{grid-area:2/1/auto/-1;justify-self:center}.language-focus{flex:12rem}}@media (width<=39.999rem){.study-preview{border-block-start:1px solid var(--cm-hairline);max-height:none;position:fixed;inset-block:7.75rem 0}.app-shell:has(.study-preview) .instrument-rail{z-index:calc(var(--cm-z-panel) + 2)}.check-panel{border-block-start:0;max-height:none;position:fixed;inset-block:7.75rem 0;inset-inline:0}.check-panel__header{padding-block-end:var(--cm-space-md)}.check-loading,.check-state,.check-complete,.active-check{margin-block-start:var(--cm-space-md)}.active-check{gap:var(--cm-space-md)}.check-options{margin-block-start:var(--cm-space-md)}}@media (width<=48rem){.illumination-pulse{inset-block-start:35%;inset-inline-start:50%}.star-chart-intro dl{grid-template-columns:1fr}.concept-row{gap:var(--cm-space-md);grid-template-columns:1fr}.concept-row dl{justify-content:space-between}}@media (width>=60rem){.map-stage:has(.map-view) .map-legend{grid-template-columns:repeat(2,auto)}.map-legend span{white-space:nowrap}.chart-stage:has(.index-sidebar) .star-chart-screen{margin-inline-start:var(--cm-sidebar-width)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.15s!important;animation-duration:.15s!important;animation-iteration-count:1!important}.study-preview,.check-panel,.mode-gate{animation-name:panel-enter-reduced}.entrypoint-picker{animation-name:entrypoint-enter-reduced}.illumination-pulse{animation-name:illumination-pulse-reduced}}@keyframes panel-enter-reduced{0%{opacity:0}to{opacity:1}}@keyframes entrypoint-enter-reduced{0%{opacity:0}to{opacity:1}}@keyframes illumination-pulse-reduced{0%{opacity:0}50%{opacity:1}to{opacity:0}}.picker-screen{gap:var(--cm-space-lg);min-height:100vh;padding:var(--cm-space-2xl) var(--cm-space-xl);flex-direction:column;max-width:44rem;margin:0 auto;display:flex}.picker-wordmark{font-family:var(--cm-font-display);font-size:var(--cm-text-sm);letter-spacing:.2em;text-transform:uppercase;color:var(--cm-ink-3)}.picker-subtitle{color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.picker-recents ul,.picker-browser ul{margin:var(--cm-space-sm) 0 0;gap:var(--cm-space-2xs);flex-direction:column;padding:0;list-style:none;display:flex}.picker-recents button,.picker-browser ul button{justify-content:space-between;gap:var(--cm-space-sm);width:100%;padding:var(--cm-space-xs) var(--cm-space-sm);background:var(--cm-ground-2);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);text-align:left;cursor:pointer;display:flex}.picker-recents button:hover,.picker-recents button:focus-visible,.picker-browser ul button:hover,.picker-browser ul button:focus-visible{border-color:var(--cm-orbit)}.picker-recents button{flex-wrap:wrap}.picker-recent-path{overflow-wrap:anywhere;min-width:0}.picker-recent-lit{color:var(--cm-star);white-space:nowrap}.picker-path{font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);color:var(--cm-ink-2);overflow-wrap:anywhere}.picker-scale,.picker-error{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);padding:var(--cm-space-sm);font-size:var(--cm-text-sm)}.picker-error{color:var(--cm-error)}.picker-error p{margin:0}.picker-retry{min-height:44px;padding:var(--cm-space-xs) var(--cm-space-md);border:1px solid var(--cm-error);border-radius:var(--cm-radius);color:var(--cm-ink);cursor:pointer;background:0 0;margin-block-start:var(--cm-space-sm)}.picker-retry:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:2px}.picker-select{padding:var(--cm-space-xs) var(--cm-space-lg);background:var(--cm-orbit-low);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-ink);font-size:var(--cm-text-base);cursor:pointer;align-self:flex-start}.picker-select:disabled{opacity:.6;cursor:progress}.layer-switcher{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);display:inline-flex;overflow:hidden}.layer-switcher button{padding:var(--cm-space-2xs) var(--cm-space-sm);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0;border:0}.layer-switcher button[aria-pressed=true]{background:var(--cm-orbit-low);color:var(--cm-ink)}.map-view{gap:var(--cm-space-sm);padding:var(--cm-space-md);--cm-scroll-cover:var(--cm-ground);background-color:var(--cm-ground);flex-direction:column;display:flex;position:absolute;inset:0;overflow:hidden auto}.map-tabs{gap:var(--cm-space-xs);flex:none;display:flex}.map-tabs button{padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0}.map-tabs button[aria-pressed=true]{border-color:var(--cm-orbit);color:var(--cm-ink)}.map-canvas{flex-direction:column;flex:auto;min-block-size:6rem;display:flex;position:relative}.map-zoom{z-index:var(--cm-z-raised);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);gap:1px;display:flex;position:absolute;inset-block-start:var(--cm-space-sm);inset-inline-end:var(--cm-space-sm)}.map-zoom button{min-width:34px;min-height:30px;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0;border:0}.map-zoom button:hover:not(:disabled),.map-zoom button:focus-visible{color:var(--cm-orbit)}.map-zoom button:disabled{color:var(--cm-ink-3);cursor:default}@media (width<=40rem){.map-tabs{flex-wrap:wrap;padding-inline-end:4.5rem}.map-zoom{align-self:flex-end;margin-block-end:var(--cm-space-xs);position:static}.map-canvas{min-block-size:8.5rem}}.map-scroll{overscroll-behavior:contain;cursor:grab;background:linear-gradient(var(--cm-ground) 30%, transparent) 0 0 / 100% 2.5rem local no-repeat, linear-gradient(transparent, var(--cm-ground) 70%) 0 100% / 100% 2.5rem local no-repeat, linear-gradient(to right, var(--cm-ground) 30%, transparent) 0 0 / 2.5rem 100% local no-repeat, linear-gradient(to left, var(--cm-ground) 30%, transparent) 100% 0 / 2.5rem 100% local no-repeat, radial-gradient(farthest-side at 50% 0, #0000008c, transparent) 0 0 / 100% .85rem scroll no-repeat, radial-gradient(farthest-side at 50% 100%, #0000008c, transparent) 0 100% / 100% .85rem scroll no-repeat, radial-gradient(farthest-side at 0 50%, #00000073, transparent) 0 0 / .85rem 100% scroll no-repeat, radial-gradient(farthest-side at 100% 50%, #00000073, transparent) 100% 0 / .85rem 100% scroll no-repeat;flex:auto;overflow:auto}.map-scroll[data-panning]{cursor:grabbing;-webkit-user-select:none;user-select:none}.map-canvas__sized{min-width:100%}.architecture-map,.workflow-tree{min-height:0;display:block}.architecture-map__edges path{color:var(--cm-route);stroke:currentColor;fill:none;stroke-linecap:round;stroke-linejoin:round}.architecture-map__edges path.is-possible,.architecture-map__edges path.is-cycle{color:var(--cm-route-possible)}.architecture-map__arrow{color:var(--cm-route)}.architecture-map__arrow.is-cycle{color:var(--cm-route-possible)}.workflow-tree__edges path{stroke:var(--cm-route);stroke-width:1.2px;fill:none}.workflow-tree__edges path.is-possible{stroke:var(--cm-route-possible)}.architecture-map__box rect{fill:color-mix(in srgb, var(--box-com,var(--cm-ground-2)) 16%, var(--cm-ground-2));stroke:var(--cm-hairline)}.architecture-map__box .box-tint{stroke:none}.architecture-map__box text{fill:var(--cm-ink);font-family:var(--cm-font-mono);font-size:13px}.architecture-map__box .box-meta{fill:var(--cm-ink-3);font-size:11px}.architecture-map__box[data-partial=true] rect:first-of-type{stroke:var(--cm-route-possible)}.architecture-map__box .box-partial{fill:var(--cm-route-possible);stroke:none}.architecture-map__box[data-understood=true] rect:first-of-type{stroke:var(--cm-star-high)}.architecture-map__box[data-home=true] rect:first-of-type{stroke-width:2px}.architecture-map__box[data-reachable=false] rect:first-of-type{stroke-dasharray:4 3}.architecture-map__box:focus-visible rect:first-of-type,.workflow-tree__row:focus-visible circle{outline:2px solid var(--cm-orbit);outline-offset:2px}.architecture-map__box,.workflow-tree__row{cursor:pointer}.workflow-tree__row circle{fill:var(--cm-ink-3)}.workflow-tree__row[data-partial=true] circle,.workflow-tree__row[data-cut] circle{fill:var(--cm-route-possible)}.workflow-tree__row[data-understood=true] circle{fill:var(--cm-star-high)}.workflow-tree__row text{fill:var(--cm-ink);font-family:var(--cm-font-mono);font-size:13px}.workflow-tree__row .row-meta{fill:var(--cm-ink-3);font-size:11px}.architecture-map__box[data-selected=true] rect:first-of-type{outline:2px solid var(--cm-orbit);outline-offset:3px}.workflow-tree__row[data-selected=true] circle{stroke:var(--cm-orbit);stroke-width:2px}.map-note,.map-loading{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);flex:none;margin:0}.map-state{text-align:center;max-width:46ch;color:var(--cm-ink-2);margin:auto}.hint-chip{z-index:calc(var(--cm-z-panel) + 1);align-items:center;gap:var(--cm-space-sm);min-width:0;padding:var(--cm-space-sm) var(--cm-space-md);border-block:1px solid var(--cm-orbit);background:var(--cm-ground-2);color:var(--cm-ink);font-size:var(--cm-text-sm);flex-wrap:wrap;grid-row:3;display:flex;position:static}@media (width<=40rem){.hint-chip{max-block-size:40vh;overflow-y:auto}.hint-chip small{text-overflow:ellipsis;white-space:nowrap;flex:100%;overflow:hidden}}.hint-chip small{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.hint-chip button{padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-orbit);font-size:var(--cm-text-xs);cursor:pointer;background:0 0}.first-flight__system{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-mono)}.first-flight__actions{gap:var(--cm-space-xs);flex-wrap:wrap;margin-inline-start:auto;display:flex}.first-flight__actions button[disabled]{border-color:var(--cm-hairline);color:var(--cm-ink-3);cursor:default}@media (width<=40rem){.first-flight__system,.first-flight__actions{flex:100%}.first-flight__actions{margin-inline-start:0}}.location button{color:var(--cm-orbit);font:inherit;cursor:pointer;background:0 0;border:0;padding:0}.location button[disabled]{color:var(--cm-ink-2);cursor:default}.location button:not([disabled]):hover{text-decoration:underline}.coach-marks{inset-block:auto var(--cm-space-lg);width:min(38ch, calc(100% - var(--cm-space-lg)));padding:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink);margin-inline:auto;inset-inline:0}.coach-marks::backdrop{background:var(--cm-scrim)}.coach-marks__progress{margin:0 0 var(--cm-space-2xs);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.coach-marks h1{margin:0 0 var(--cm-space-xs);font-size:var(--cm-text-lg)}.coach-marks__actions{justify-content:space-between;gap:var(--cm-space-sm);margin-block-start:var(--cm-space-md);display:flex}.coach-skip{color:var(--cm-ink-3);cursor:pointer;background:0 0;border:0}.legend-tint{width:var(--cm-space-sm);height:var(--cm-space-sm);border-radius:2px;display:inline-block}.legend-communities{gap:2px;display:inline-flex}.legend-communities i{width:calc(var(--cm-space-sm) / 1.6);height:var(--cm-space-sm);border-radius:2px;display:inline-block}.legend-communities i:first-child{background:var(--cm-com-0)}.legend-communities i:nth-child(2){background:var(--cm-com-1)}.legend-communities i:nth-child(3){background:var(--cm-com-2)}.legend-communities i:nth-child(4){background:var(--cm-com-3)}.map-note__toggle{border:var(--cm-rule) solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-orbit);font:inherit;font-size:var(--cm-text-xs);padding:var(--cm-space-2xs) var(--cm-space-sm);cursor:pointer;background:0 0}.map-note__toggle:hover,.map-note__toggle:focus-visible{border-color:var(--cm-orbit)}.legend-route--dashed{border-block-start-style:dashed}.legend-size{width:var(--cm-space-md);height:var(--cm-space-sm);background:var(--cm-ink-3);border-radius:50%;display:inline-block}.legend-brightness{width:var(--cm-space-md);height:var(--cm-space-sm);background:linear-gradient(90deg, var(--cm-node-unlit), var(--cm-ink-2));display:inline-block}.app-shell[data-mode=easy]{--cm-text-xs:.8125rem;--cm-text-sm:.9375rem;--cm-text-base:1.125rem;--cm-text-lg:1.375rem;--cm-space-sm:1rem;--cm-space-md:1.375rem;--cm-space-lg:2.5rem;font-size:var(--cm-text-base);line-height:1.7}.app-shell[data-mode=easy] .orientation-copy p,.app-shell[data-mode=easy] .study-preview p,.app-shell[data-mode=easy] .grounded-explanation>p,.app-shell[data-mode=easy] .study-notice p,.app-shell[data-mode=easy] .evidence-list p,.app-shell[data-mode=easy] .lens-note p,.app-shell[data-mode=easy] .active-check fieldset>p,.app-shell[data-mode=easy] .check-state p,.app-shell[data-mode=easy] .check-complete p,.app-shell[data-mode=easy] .check-loading{max-width:46ch}.app-shell[data-mode] .orientation-copy--inline>p{max-width:72ch}.app-shell[data-mode=easy] .language-focus__label,.app-shell[data-mode=easy] .mode-toggle__label{text-transform:none;letter-spacing:normal}.loading-screen{gap:var(--cm-space-md);max-width:44rem;min-height:100vh;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-left));align-content:center;justify-items:start;margin:0 auto;display:grid}.loading-header h1{min-width:0;margin:var(--cm-space-2xs) 0 0;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);overflow-wrap:anywhere;line-height:1.15}.loading-path{font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);color:var(--cm-ink-2)}.loading-subtitle{margin:var(--cm-space-xs) 0 0;color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.loading-stages{gap:var(--cm-space-2xs);width:100%;margin:0;padding:0;list-style:none;display:grid}.loading-stages li{justify-content:space-between;gap:var(--cm-space-sm);padding:var(--cm-space-xs) var(--cm-space-sm);border-left:2px solid var(--cm-hairline);color:var(--cm-ink-3);font-size:var(--cm-text-sm);flex-wrap:wrap;display:flex}.loading-stages li[data-state=active]{border-left-color:var(--cm-orbit);color:var(--cm-ink)}.loading-stages li[data-state=done]{border-left-color:var(--cm-orbit-deep);color:var(--cm-ink-2)}.loading-stages small{font-family:var(--cm-font-mono);color:var(--cm-ink-2)}.loading-meter{width:100%;height:var(--cm-space-xs);accent-color:var(--cm-orbit)}.loading-live{color:var(--cm-ink);font-size:var(--cm-text-sm)}.loading-error{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);width:100%;padding:var(--cm-space-sm);color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.loading-actions{gap:var(--cm-space-sm);flex-wrap:wrap;display:flex}.loading-actions button{padding:var(--cm-space-xs) var(--cm-space-md)}.picker-scale h2,.picker-failure h2{margin:0 0 var(--cm-space-xs);font-family:var(--cm-font-display);font-size:var(--cm-text-base)}.picker-scale-scopes{gap:var(--cm-space-xs);margin:var(--cm-space-sm) 0;flex-wrap:wrap;padding:0;list-style:none;display:flex}.picker-scale-scopes button{align-items:flex-start;gap:var(--cm-space-2xs);min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-sm);background:var(--cm-ground-2);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);cursor:pointer;flex-direction:column;display:flex}.picker-scale-scopes button:hover:not(:disabled),.picker-scale-scopes button:focus-visible{border-color:var(--cm-orbit)}.picker-scale-scopes button:disabled{opacity:.6;cursor:progress}.picker-scale-scopes small{color:var(--cm-ink-2)}.picker-path-entry{gap:var(--cm-space-xs);flex-wrap:wrap;align-items:center;display:flex}.picker-path-entry label{color:var(--cm-ink-2)}.picker-path-entry input{min-width:0;min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-xs);background:var(--cm-ground);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);flex:14rem}.picker-path-entry button{min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-md);background:var(--cm-orbit-low);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-ink);font-size:var(--cm-text-sm);cursor:pointer}.picker-path-entry button:disabled{opacity:.6;cursor:not-allowed}.picker-failure{border:1px solid var(--cm-error);border-radius:var(--cm-radius);padding:var(--cm-space-sm);font-size:var(--cm-text-sm)}.picker-failure>p{margin:0 0 var(--cm-space-sm);color:var(--cm-ink-2)}.picker-failure__detail{gap:var(--cm-space-2xs);display:grid}.picker-failure__detail span{color:var(--cm-ink-3);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em}.picker-failure__detail code{max-height:8rem;padding:var(--cm-space-xs);background:var(--cm-ground-2);border-radius:var(--cm-radius);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);overflow-wrap:anywhere;display:block;overflow:auto}.picker-failure .picker-select{min-height:44px;font-size:var(--cm-text-sm);align-self:flex-start}.progress-reset{align-items:center;gap:var(--cm-space-sm);max-width:72rem;margin:var(--cm-space-lg) auto 0;padding-top:var(--cm-space-md);border-top:1px solid var(--cm-hairline);flex-wrap:wrap;display:flex}.progress-reset p{color:var(--cm-ink-2);font-size:var(--cm-text-sm);margin:0}.progress-reset button{min-height:44px;padding-inline:var(--cm-space-md);font-size:var(--cm-text-sm)}.progress-reset--confirming{justify-items:start;gap:var(--cm-space-sm);display:grid}.progress-reset--confirming>p[role=alert]{max-width:60ch;color:var(--cm-ink)}.progress-reset--confirming>div{gap:var(--cm-space-sm);flex-wrap:wrap;display:flex}.progress-reset__confirm{border-color:var(--cm-error);color:var(--cm-error)}.progress-reset__error{color:var(--cm-error)}.learning-journey{min-width:0}.journey-overview{gap:var(--cm-space-2xs);margin:var(--cm-space-md) 0 0;padding:0 0 var(--cm-space-xs);scrollbar-width:thin;grid-auto-columns:minmax(4.5rem,1fr);grid-auto-flow:column;list-style:none;display:grid;overflow-x:auto}.journey-overview button{width:100%;min-height:44px;padding:var(--cm-space-xs);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink-2);cursor:pointer;text-align:start;background:0 0;align-content:center;justify-items:start;gap:1px;display:grid}.journey-overview button[aria-current=step]{border-color:var(--cm-orbit);background:var(--cm-orbit-low);color:var(--cm-ink)}.journey-overview button>span,.journey-overview button>small,.journey-position,.journey-citations,.journey-expert-facts,.journey-controls,.journey-break code,.journey-possible span,.journey-candidate-note,.journey-support-scope{font-family:var(--cm-font-mono)}.journey-overview button>span{color:var(--cm-orbit);font-size:var(--cm-text-sm)}.journey-overview button>small{text-transform:uppercase;font-size:.625rem;line-height:1.2}.journey-current{gap:var(--cm-space-sm);padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-inline-start:3px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);margin-block-start:var(--cm-space-sm);display:grid}.journey-position{font-size:var(--cm-text-xs);text-transform:uppercase;color:var(--cm-orbit)!important;margin:0!important}.journey-current h3{font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal}.journey-current>p:not(.journey-position){color:var(--cm-ink-2);margin:0}.journey-citations{gap:var(--cm-space-xs) var(--cm-space-md);color:var(--cm-ink-3);font-size:var(--cm-text-xs);flex-wrap:wrap;display:flex}.journey-citations code,.journey-break code{color:var(--cm-orbit);overflow-wrap:anywhere}.study-preview .journey-expert-facts{gap:var(--cm-space-sm);grid-template-columns:repeat(auto-fit,minmax(8rem,1fr));margin:0}.journey-expert-facts dd{overflow-wrap:anywhere;font-size:var(--cm-text-xs)}.journey-controls{gap:var(--cm-space-xs);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.journey-controls button{min-height:44px;padding:var(--cm-space-xs);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);cursor:pointer;background:0 0}.journey-controls button:nth-child(2){border-color:var(--cm-orbit);color:var(--cm-orbit)}.journey-controls button:disabled{color:var(--cm-ink-3);cursor:not-allowed;opacity:.55}.journey-break,.journey-possible{padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);margin-block-start:var(--cm-space-md)}.journey-break{border-inline-start:3px solid var(--cm-error)}.journey-break p,.journey-possible p{margin:var(--cm-space-xs) 0 0;color:var(--cm-ink-2)}.journey-possible h3{font-family:var(--cm-font-display);font-size:var(--cm-text-base);margin:0;font-style:normal}.journey-possible ul{gap:var(--cm-space-xs);margin:var(--cm-space-md) 0 0;padding:0;list-style:none;display:grid}.journey-possible li{gap:var(--cm-space-2xs);border-block-start:1px solid var(--cm-hairline);padding-block-start:var(--cm-space-xs);display:grid}.journey-possible span,.journey-candidate-note{color:var(--cm-ink-3);font-size:var(--cm-text-xs);overflow-wrap:anywhere}.journey-support{border-block-start:1px solid var(--cm-hairline);margin-block-start:var(--cm-space-lg)}.journey-support>summary{min-height:44px;padding-block:var(--cm-space-sm);color:var(--cm-orbit);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.journey-support>section{margin-block-start:var(--cm-space-lg)}.journey-support-scope{margin:0}.journey-candidate-note{margin-block-start:var(--cm-space-sm)}.visually-hidden{clip:rect(0 0 0 0)!important;clip-path:inset(50%)!important;white-space:nowrap!important;width:1px!important;height:1px!important;position:absolute!important;overflow:hidden!important}@media (width<=20rem){.study-preview{padding:var(--cm-space-sm) var(--cm-space-md) max(var(--cm-space-sm), env(safe-area-inset-bottom))}.study-content{gap:var(--cm-space-md);margin-block-start:var(--cm-space-xs)}.study-preview dl{gap:var(--cm-space-sm);margin-block-start:var(--cm-space-md)}.journey-current{gap:var(--cm-space-xs);padding:var(--cm-space-xs)}.journey-overview{margin-block-start:var(--cm-space-xs)}.journey-current>p:not(.journey-position){font-size:var(--cm-text-sm);line-height:1.45}.journey-controls{gap:var(--cm-space-2xs)}}@media (width<=22.5rem){.app-shell[data-level=study] .hint-chip{gap:var(--cm-space-xs)}.app-shell[data-level=study] .hint-chip button{padding-inline:var(--cm-space-xs)}.source-study .study-section-heading{gap:var(--cm-space-xs)}.source-study .study-section-heading h2{overflow-wrap:normal;flex:none}.source-study .study-section-heading span{text-overflow:ellipsis;min-width:0;overflow:hidden}}.impact-columns{gap:var(--cm-space-md);grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));display:grid}.impact-column h3{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);letter-spacing:.04em;text-transform:uppercase;margin:0;font-weight:500}.impact-list{margin:var(--cm-space-2xs) 0 0;border-block-start:1px solid var(--cm-hairline);padding:0;list-style:none}.impact-list button{gap:var(--cm-space-2xs);width:100%;min-height:44px;padding:var(--cm-space-xs) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.impact-name{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.impact-meta,.impact-citation{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.impact-citation{overflow-wrap:anywhere}@media (hover:hover) and (pointer:fine){.impact-list button:hover .impact-name{color:var(--cm-orbit)}}.walkthrough-disclosure>summary{min-height:44px;padding:var(--cm-space-xs) 0;color:var(--cm-orbit);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.walkthrough-disclosure>summary:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:2px}.project-summary{margin-block:var(--cm-space-lg);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2)}.project-summary__lead{max-width:60ch;margin:0 0 var(--cm-space-lg);color:var(--cm-ink);font-size:var(--cm-text-lg);line-height:1.4}.project-summary__facts{gap:var(--cm-space-md) var(--cm-space-xl);grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));margin:0;display:grid}.project-summary__facts>div{min-width:0}.project-summary__facts dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.project-summary__facts dd{margin:var(--cm-space-2xs) 0 0;overflow-wrap:break-word;color:var(--cm-ink)}.project-summary__export{min-height:44px;padding:var(--cm-space-xs) var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-orbit);cursor:pointer;font:500 var(--cm-text-sm) / 1.3 var(--cm-font-mono);background:0 0;margin-block-start:var(--cm-space-lg)}.project-summary__export:hover{background:color-mix(in srgb, var(--cm-orbit) 10%, transparent)}.project-summary__export:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:3px}.star-chart-section{margin-block:var(--cm-space-xl) 0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg)} +@font-face{font-family:Zen Kaku Gothic New;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/zen-kaku-gothic-new-latin-400-normal-BEdayliK.woff2)format("woff2"),url(/assets/zen-kaku-gothic-new-latin-400-normal-CPSmNJAU.woff)format("woff")}@font-face{font-family:Shippori Mincho;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/shippori-mincho-latin-500-normal-XI1O8euf.woff2)format("woff2"),url(/assets/shippori-mincho-latin-500-normal-C-QwvIb3.woff)format("woff")}@font-face{font-family:Shippori Mincho;font-style:normal;font-display:swap;font-weight:700;src:url(/assets/shippori-mincho-latin-700-normal-DHcmzUO5.woff2)format("woff2"),url(/assets/shippori-mincho-latin-700-normal-CkoCYOiI.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff)format("woff")}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff)format("woff")}:root{--cm-font-display:"Shippori Mincho", "Hiragino Mincho ProN", "Yu Mincho", serif;--cm-font-body:"Zen Kaku Gothic New", "Hiragino Sans", system-ui, sans-serif;--cm-font-mono:"JetBrains Mono", ui-monospace, monospace;--cm-text-xs:.75rem;--cm-text-sm:.875rem;--cm-text-base:1rem;--cm-text-lg:1.25rem;--cm-text-xl:clamp(1.25rem, .5vw + 1.1rem, 1.5rem);--cm-text-display:clamp(1.75rem, 3vw + 1rem, 3.25rem);--cm-space-2xs:.125rem;--cm-space-xs:.5rem;--cm-space-sm:.75rem;--cm-space-md:1rem;--cm-space-lg:2rem;--cm-space-xl:4rem;--cm-space-2xl:6rem;--cm-radius:.25rem;--cm-rule:1px;--cm-ease:cubic-bezier(.16, .7, .3, 1);--cm-dur-quick:.16s;--cm-dur-reveal:.42s;--cm-dur-warp:.9s;--cm-z-base:1;--cm-z-raised:10;--cm-z-sticky:200;--cm-z-panel:400;--cm-ground:#070b1c;--cm-ground-2:#101a3e;--cm-hairline:#24325c;--cm-hairline-soft:#17203f;--cm-ink:#eef2fa;--cm-ink-2:#9aa8c4;--cm-ink-3:#7d8aa8;--cm-star:#e89b2e;--cm-star-high:#f6d087;--cm-orbit:#82abec;--cm-orbit-deep:#2b4d96;--cm-orbit-low:#14264a;--cm-error:oklch(72% .16 28);--cm-scrim:color-mix(in srgb, var(--cm-ground) 82%, transparent);--cm-sidebar-width:min(19rem, 82vw);--cm-sky:#131f4b;--cm-sky-glow:#2a4b91;--cm-node-unlit:#7381a2;--cm-node-mid:#8e9ab4;--cm-node-bright:#a9b1c5;--cm-star-cool:#96b4ec;--cm-star-pale:#d6def0;--cm-route-possible:#92a0c2;--cm-route:#607198;--cm-neb-python:#70a690;--cm-neb-js:#a197b9;--cm-neb-ts:#77a1ba;--cm-neb-go:#62a7ae;--cm-neb-java:#c48c98;--cm-neb-rust:#c38ab2;--cm-neb-csharp:#78a957;--cm-star-halo:#f2efe9;--cm-com-0:#88c3ac;--cm-com-1:#c7acdb;--cm-com-2:#acbd8a;--cm-com-3:#89bfca;--cm-com-4:#dea9b7;--cm-com-5:#caafbd;--cm-com-6:#7cc69b;--cm-com-7:#b9b0e0;--cm-label-plate:#090e1cd1;--cm-label-ink:#d6dbe7}*{box-sizing:border-box}html,body,#root{width:100%;min-height:100%;margin:0;overflow-x:clip}html{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}body{background:var(--cm-ground);color:var(--cm-ink);font-family:var(--cm-font-body);font-size:var(--cm-text-base);line-height:1.55}button,output{font:inherit}button:focus-visible,[href]:focus-visible,[tabindex]:focus-visible,input:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:3px}.app-shell{background:var(--cm-ground);width:100%;height:100dvh;min-height:100dvh;color:var(--cm-ink);grid-template-rows:auto minmax(0,1fr) auto auto;grid-template-columns:minmax(0,1fr);display:grid;position:relative;overflow:hidden}.instrument-rail{z-index:calc(var(--cm-z-panel) + 2);align-items:center;gap:var(--cm-space-sm) var(--cm-space-md);min-width:0;max-width:100%;padding:var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-left));border-block-end:1px solid var(--cm-hairline);background:var(--cm-ground);grid-row:1;grid-template-columns:minmax(0,1fr) auto;display:grid;position:relative}.brand-lockup{align-items:center;gap:var(--cm-space-sm);min-width:0;max-width:18rem;display:flex}.brand-lockup div{min-width:0;display:grid}.brand-lockup strong{font-family:var(--cm-font-display);letter-spacing:-.02em;text-overflow:ellipsis;white-space:nowrap;font-weight:700;overflow:hidden}.brand-lockup span:not(.brand-mark),.home-readout,.location{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.brand-mark{width:var(--cm-space-md);height:var(--cm-space-md);border:1px solid var(--cm-ink-2);background:var(--cm-ink);border-radius:50%}.location{grid-area:2/1/auto/-1;margin:0}.rail-overflow{grid-area:1/2;justify-self:end}.mobile-menu-trigger{min-width:44px;min-height:44px;padding-inline:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-orbit);cursor:pointer;place-items:center;font-weight:600;display:grid}.rail-overflow__panel{z-index:var(--cm-z-panel);gap:var(--cm-space-md);max-height:calc(100dvh - 5rem);padding:var(--cm-space-md);border-block:1px solid var(--cm-hairline);background:var(--cm-ground-2);display:none;position:absolute;inset-block-start:100%;inset-inline:0;overflow-y:auto;box-shadow:0 1rem 2.5rem #00000061}.rail-overflow[data-open] .rail-overflow__panel{display:grid}.instrument-rail:has(.rail-overflow[data-open]){z-index:calc(var(--cm-z-panel) + 2)}@media not all and (width>=64rem){.rail-overflow__panel .rail-actions,.rail-overflow__panel .rail-controls{grid-area:auto;justify-content:start}}.rail-action{min-height:44px;padding-inline:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-orbit);cursor:pointer;white-space:nowrap;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease), transform .1s var(--cm-ease);font-weight:600}@media (hover:hover) and (pointer:fine){.rail-action:hover{background:var(--cm-orbit-low)}}.rail-action:active{transform:translateY(1px)}.rail-action:disabled,.rail-action[aria-disabled=true]{opacity:.5;cursor:not-allowed}.rail-action[data-state=loading]{color:var(--cm-ink-2)}.rail-action[data-state=error]{border-color:var(--cm-error)}.rail-action[data-state=success]{border-color:var(--cm-star);color:var(--cm-star)}.rail-actions{justify-content:end;align-items:center;gap:var(--cm-space-sm);flex-wrap:wrap;grid-area:2/1/auto/-1;min-width:0;display:flex}.rail-more{justify-content:end;align-items:center;gap:var(--cm-space-sm);flex-wrap:wrap;grid-area:3/1/auto/-1;min-width:0;display:flex}.rail-controls{align-items:center;gap:var(--cm-space-md);flex-wrap:wrap;grid-area:4/1/auto/-1;min-width:0;display:flex}.rail-trigger__wide{display:none}.switch-project{display:contents}.switch-project[data-confirming]{gap:var(--cm-space-xs);padding:var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);display:grid}.switch-project[data-confirming] p{max-width:26ch;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin:0}.switch-project__error{color:var(--cm-error)!important}.switch-project[data-confirming]>div{gap:var(--cm-space-xs);display:flex}.language-focus{align-items:center;gap:var(--cm-space-sm);min-width:0;color:var(--cm-ink-3);scrollbar-width:none;display:flex;overflow-x:auto}.language-focus::-webkit-scrollbar{display:none}.language-focus__label{font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;flex:none}.language-focus>div{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);min-width:max-content;display:flex}.language-focus button{justify-content:center;align-items:center;gap:var(--cm-space-xs);min-width:44px;min-height:44px;padding-inline:var(--cm-space-sm);border:0;border-inline-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:nowrap;background:0 0;display:inline-flex}.language-focus button:last-child{border-inline-end:0}.language-focus button[aria-pressed=true]{background:var(--cm-orbit-low);color:var(--cm-orbit)}.language-focus button small{color:var(--cm-ink-3);font-size:inherit;font-variant-numeric:tabular-nums}@media (hover:hover) and (pointer:fine){.language-focus button:hover{background:var(--cm-ground-2);color:var(--cm-ink)}.language-focus button[aria-pressed=true]:hover{background:var(--cm-orbit-low);color:var(--cm-orbit)}}.mode-toggle{align-items:center;gap:var(--cm-space-sm);border:0;grid-area:4/1/auto/-1;min-width:0;margin:0;padding:0;display:flex}.mode-toggle__label{float:inline-start;color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;flex:none;margin-inline-end:var(--cm-space-sm);padding:0}.mode-toggle__options{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);min-width:max-content;display:flex}.mode-toggle__options label{align-items:center;gap:var(--cm-space-2xs);min-width:44px;min-height:44px;padding-inline:var(--cm-space-sm);border-inline-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);display:inline-flex}.mode-toggle__options label:last-child{border-inline-end:0}.mode-toggle__options label:has(input:checked){background:var(--cm-orbit-low);color:var(--cm-orbit)}.mode-toggle__options input{width:1rem;height:1rem;accent-color:var(--cm-orbit);margin:0}@media (hover:hover) and (pointer:fine){.mode-toggle__options label:hover{background:var(--cm-ground-2);color:var(--cm-ink)}.mode-toggle__options label:has(input:checked):hover{background:var(--cm-orbit-low);color:var(--cm-orbit)}}.mode-gate{width:min(26rem, calc(100% - var(--cm-space-xl)));max-height:min(86%,30rem);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;margin:auto;overflow:auto}.mode-gate::backdrop{background:var(--cm-scrim)}.mode-gate h1{overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);margin:0;font-style:normal;line-height:1.15}.mode-gate p{max-width:42ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink-2)}.mode-gate__options{gap:var(--cm-space-sm);margin-block-start:var(--cm-space-lg);display:grid}.mode-gate__options button{min-height:52px;padding:var(--cm-space-sm) var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-orbit);cursor:pointer;font:inherit;text-align:start;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease);font-weight:600}@media (hover:hover) and (pointer:fine){.mode-gate__options button:hover{background:var(--cm-orbit-low);color:var(--cm-ink)}}.mode-gate__options button:active{transform:translateY(1px)}.map-stage{background:var(--cm-ground);grid-row:2;min-width:0;max-width:100%;min-height:0;position:relative;overflow:clip}.chart-stage{background:var(--cm-ground);grid-row:2;min-width:0;min-height:0;position:relative;overflow:hidden}.chart-stage .star-chart-screen{height:100%}.galaxy-frame,.galaxy-canvas{z-index:var(--cm-z-base);position:absolute;inset:0}.galaxy-frame{overflow:hidden}.galaxy-canvas canvas{display:block}.keyboard-focus{max-width:min(34rem, calc(100% - var(--cm-space-lg)));padding:var(--cm-space-sm) var(--cm-space-md);border-inline-start:2px solid var(--cm-orbit);background:var(--cm-ground-2);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);position:absolute;inset-block-end:var(--cm-space-md);inset-inline-start:var(--cm-space-md)}.orientation-bar{z-index:var(--cm-z-raised);gap:var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);pointer-events:none;flex-wrap:wrap;margin:0;display:flex;position:absolute;inset-block-start:var(--cm-space-md);inset-inline-start:max(var(--cm-space-md), env(safe-area-inset-left))}.orientation-bar>*+:before{content:"· ";color:var(--cm-hairline)}.orientation-bar__charted{color:var(--cm-ink-2)}.index-sidebar{z-index:var(--cm-z-panel);width:var(--cm-sidebar-width);border-inline-end:1px solid var(--cm-hairline);background:var(--cm-ground-2);flex-direction:column;display:flex;position:absolute;inset-block:0;inset-inline-start:0}@media (width>=60rem){:is(.map-stage:has(.index-sidebar) .galaxy-frame,.map-stage:has(.index-sidebar) .map-view){inset-inline-start:var(--cm-sidebar-width)}:is(.map-stage:has(.index-sidebar) .orientation-bar,.map-stage:has(.index-sidebar) .orientation-copy){inset-inline-start:calc(var(--cm-sidebar-width) + var(--cm-space-md))}}.index-sidebar>header{justify-content:space-between;align-items:center;gap:var(--cm-space-sm);padding:var(--cm-space-sm) var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);display:flex}.index-sidebar h2{color:var(--cm-ink);font-size:var(--cm-text-sm);letter-spacing:.08em;text-transform:uppercase;margin:0}.index-sidebar>header button{border-radius:var(--cm-radius);min-width:32px;min-height:32px;color:var(--cm-ink-2);font-size:var(--cm-text-lg);cursor:pointer;background:0 0;border:1px solid #0000;line-height:1}.index-sidebar>header button:hover,.index-sidebar>header button:focus-visible{border-color:var(--cm-orbit);color:var(--cm-ink)}.index-sidebar__scroll{flex:1;padding-block-end:var(--cm-space-lg);overflow-y:auto}.index-sidebar section{padding-block-start:var(--cm-space-sm)}.index-sidebar h3{padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-overflow:ellipsis;white-space:nowrap;margin:0;font-weight:400;overflow:hidden}.index-sidebar ul{margin:0;padding:0;list-style:none}.index-sidebar li button{align-items:baseline;gap:var(--cm-space-sm);width:100%;min-height:32px;padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-align:start;cursor:pointer;background:0 0;border:0;border-inline-start:2px solid #0000;display:flex}.index-sidebar li button span{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.index-sidebar li button:hover,.index-sidebar li button:focus-visible{border-inline-start-color:var(--cm-orbit);background:var(--cm-ground);color:var(--cm-ink)}.index-sidebar li button[aria-current=true]{border-inline-start-color:var(--cm-orbit);color:var(--cm-ink)}.index-sidebar li button[data-lit] span{color:var(--cm-star-high)}.index-sidebar li em{color:var(--cm-ink-3);font-size:var(--cm-text-xs);font-style:normal}.module-finder{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);width:min(34rem,92vw);max-height:70vh;color:var(--cm-ink);padding:0}.module-finder::backdrop{background:var(--cm-scrim)}.module-finder input{width:100%;padding:var(--cm-space-md);border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-base);background:0 0}.module-finder input:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:-2px}.module-finder ul{max-height:50vh;padding:var(--cm-space-2xs) 0;margin:0;list-style:none;overflow-y:auto}.module-finder li button{align-items:baseline;gap:var(--cm-space-sm);width:100%;min-height:36px;padding:var(--cm-space-2xs) var(--cm-space-md);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-align:start;cursor:pointer;background:0 0;border:0;border-inline-start:2px solid #0000;display:flex}.module-finder li button[data-active]{border-inline-start-color:var(--cm-orbit);background:var(--cm-ground);color:var(--cm-ink)}.module-finder li strong{font-weight:400}.module-finder li small{color:var(--cm-ink-3);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.module-finder li em{color:var(--cm-star-high);font-style:normal}.module-finder__empty{padding:var(--cm-space-md);color:var(--cm-ink-3);font-size:var(--cm-text-sm);margin:0}.rail-action kbd{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin-inline-start:var(--cm-space-2xs)}.rail-action--toggle[aria-pressed=true]{border-color:var(--cm-orbit);color:var(--cm-orbit)}.orientation-copy{z-index:var(--cm-z-raised);max-width:min(28rem, calc(100% - var(--cm-space-lg)));pointer-events:none;position:absolute;inset-block-start:clamp(var(--cm-space-lg), 8vh, var(--cm-space-2xl));inset-inline-start:max(var(--cm-space-md), env(safe-area-inset-left))}.orientation-copy--system{inset-block-start:var(--cm-space-lg)}.orientation-copy h1,.study-preview h1,.load-state h1,.webgl-error h1{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-display);letter-spacing:-.035em;margin:0;font-style:normal;font-weight:700;line-height:1.08}.orientation-copy p,.study-preview p{max-width:55ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink-2)}.orientation-copy .system-orbit-note{align-items:center;gap:var(--cm-space-2xs) var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);flex-wrap:wrap;display:flex}.system-orbit-note>span{color:var(--cm-ink-2);text-transform:uppercase;letter-spacing:.08em}.system-orbit-note i{border-inline-start:1.5rem solid var(--cm-route);color:var(--cm-ink-2);padding-inline-start:var(--cm-space-sm);font-style:normal;line-height:1}.system-orbit-note i[data-unproven]{border-inline-start-color:var(--cm-hairline-soft);text-underline-offset:.22em;-webkit-text-decoration:underline dashed;text-decoration:underline dashed}.system-orbit-note small{color:var(--cm-ink-3);font-size:inherit;flex-basis:100%}.orientation-copy--inline{z-index:auto;pointer-events:auto;flex-direction:column;flex:none;max-width:none;display:flex;position:static}.orientation-copy--inline h1{font-size:var(--cm-text-lg)}.orientation-copy--inline p{font-size:var(--cm-text-sm);margin-block-start:var(--cm-space-2xs)}.orientation-copy__actions{gap:var(--cm-space-sm);flex-wrap:wrap;align-items:center;margin-block-start:var(--cm-space-sm);display:flex}.orientation-copy__actions .check-launch{margin-block-start:0}.check-launch--read{border-color:var(--cm-hairline);color:var(--cm-ink)}.check-launch--read:hover,.check-launch--read:focus-visible{border-color:var(--cm-orbit);color:var(--cm-orbit)}.check-launch,.check-primary,.check-close{border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);min-height:44px;color:var(--cm-orbit);cursor:pointer;transition:background-color var(--cm-dur-quick) var(--cm-ease), color var(--cm-dur-quick) var(--cm-ease), transform .1s var(--cm-ease);font-weight:600}.check-launch{padding-inline:var(--cm-space-md);pointer-events:auto;margin-block-start:var(--cm-space-md);position:relative}.check-primary{padding-inline:var(--cm-space-lg);justify-self:start;margin-block-start:var(--cm-space-sm)}.check-submit-bar{z-index:1;padding-block:var(--cm-space-xs) var(--cm-space-md);background:var(--cm-ground-2);margin-block-end:calc(-1 * var(--cm-space-lg));display:grid;position:sticky;inset-block-end:calc(-1 * var(--cm-space-lg))}.check-submit-bar .check-primary{margin-block-start:0}.active-check :is(fieldset,.check-feedback){scroll-margin-block-end:5rem}.check-close{padding-inline:var(--cm-space-md);border-color:var(--cm-hairline);color:var(--cm-ink-2)}@media (hover:hover) and (pointer:fine){.check-launch:hover,.check-primary:hover,.check-close:hover{background:var(--cm-orbit-low);color:var(--cm-ink)}}.check-launch:active,.check-primary:active,.check-close:active{transform:translateY(1px)}.check-primary:disabled{opacity:.45;cursor:not-allowed;transform:none}.legend-toggle{z-index:var(--cm-z-sticky);min-height:32px;padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;position:absolute;inset-block-start:var(--cm-space-md);inset-inline-end:var(--cm-space-md)}.legend-toggle:hover,.legend-toggle:focus-visible{border-color:var(--cm-orbit);color:var(--cm-ink)}.legend-toggle[aria-expanded=true]{border-color:var(--cm-orbit);color:var(--cm-orbit)}.map-legend{--cm-legend-top:calc(var(--cm-space-md) + 40px);z-index:var(--cm-z-sticky);max-width:min(38rem, calc(100% - 2 * var(--cm-space-md)));max-height:min(60vh, 30rem, calc(100dvh - 124px - var(--cm-legend-top) - var(--cm-space-md)));gap:var(--cm-space-2xs) var(--cm-space-md);padding:var(--cm-space-sm) var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);--cm-scroll-cover:var(--cm-ground-2);background-color:var(--cm-ground-2);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);grid-template-columns:minmax(0,1fr);display:none;position:absolute;inset-block-start:var(--cm-legend-top);inset-inline-end:var(--cm-space-md);overflow-y:auto;box-shadow:0 16px 40px #04081452}.map-legend:not([hidden]){display:grid}.map-stage:has(.map-view) .map-legend{--cm-legend-top:calc(var(--cm-space-md) + 40px + 44px)}.map-legend span{align-items:center;gap:var(--cm-space-sm);white-space:normal;min-width:0;display:inline-flex}.legend-dot{width:var(--cm-space-sm);height:var(--cm-space-sm);border-radius:50%;display:inline-block}.legend-dot--dim{background:var(--cm-ink-3)}.legend-dot--lit{background:var(--cm-star-high)}.legend-dot--partial{background:var(--cm-route-possible);clip-path:polygon(0 0,100% 0,100% 100%);border-radius:0}.legend-route{width:var(--cm-space-md);border-block-start:2px solid var(--cm-route)}.legend-route--possible{border-block-start-color:var(--cm-route-possible)}.partial-summary,.unsupported-summary{border-inline-start:2px solid var(--cm-route-possible);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);padding-inline-start:var(--cm-space-sm);color:var(--cm-ink)!important}.entrypoint-picker{z-index:var(--cm-z-panel);width:min(36rem, calc(100vw - var(--cm-space-xl)));max-height:min(80dvh,42rem);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);background:var(--cm-ground-2);color:var(--cm-ink);animation:entrypoint-enter var(--cm-dur-reveal) var(--cm-ease) both}.entrypoint-picker[open]{flex-direction:column;display:flex;overflow:hidden}.entrypoint-picker::backdrop{background:color-mix(in srgb, var(--cm-ground) 88%, transparent)}.entrypoint-picker>p:first-child{color:var(--cm-orbit);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);margin:0}.entrypoint-picker h1{margin:var(--cm-space-sm) 0 0;overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);line-height:1.1}.entrypoint-picker>p:not(:first-child){margin:var(--cm-space-md) 0 0;color:var(--cm-ink-2)}.entrypoint-count{margin-block:var(--cm-space-md) 0;color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.entrypoint-scroll{min-height:0;border-block-start:1px solid var(--cm-hairline);flex:auto;margin-block-start:var(--cm-space-sm);overflow-y:auto}@media (width<=39.999rem),(height<=44rem){.entrypoint-picker{width:calc(100vw - (2 * var(--cm-space-sm)));height:calc(100dvh - (2 * var(--cm-space-sm)));max-height:none;padding:var(--cm-space-md);animation-name:entrypoint-enter-reduced}.entrypoint-scroll{flex-basis:min(10rem,28dvh);min-height:min(7.5rem,22dvh)}}@media (height<=32rem){.entrypoint-picker{padding:var(--cm-space-sm)}.entrypoint-picker h1{font-size:var(--cm-text-lg);margin-block-start:var(--cm-space-xs)}.entrypoint-picker>p:not(:first-child),.entrypoint-count,.entrypoint-continue{margin-block-start:var(--cm-space-xs)}.entrypoint-picker>p:not(:first-child){font-size:var(--cm-text-xs);line-height:1.3}.entrypoint-scroll{flex-basis:min(6rem,24dvh);min-height:min(4rem,18dvh)}}.entrypoint-scroll details{border-block-end:1px solid var(--cm-hairline)}.entrypoint-scroll summary{justify-content:space-between;align-items:baseline;gap:var(--cm-space-sm);padding:var(--cm-space-sm) 0;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;display:flex}.entrypoint-scroll summary small{color:var(--cm-ink-3)}.entrypoint-candidates{border-block-start:1px solid var(--cm-hairline)}.entrypoint-candidates button{gap:var(--cm-space-xs);width:100%;min-height:58px;padding:var(--cm-space-sm) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.entrypoint-candidates button span{overflow-wrap:anywhere;font-family:var(--cm-font-mono)}.entrypoint-candidates button small{color:var(--cm-ink-3);font-family:var(--cm-font-mono)}@media (hover:hover) and (pointer:fine){.entrypoint-candidates button:hover{color:var(--cm-orbit)}}.entrypoint-continue{width:100%;min-height:44px;padding:var(--cm-space-xs) 0 0;border:0;border-block-start:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;text-underline-offset:3px;background:0 0;flex:none;margin-block-start:var(--cm-space-md);text-decoration:underline}.entrypoint-error{color:var(--cm-error)!important}.illumination-pulse{z-index:var(--cm-z-raised);color:var(--cm-star);pointer-events:none;animation:illumination-pulse .42s var(--cm-ease) both;justify-items:center;display:grid;position:absolute;inset-block-start:50%;inset-inline-start:25%;transform:translate(-50%,-50%)}.illumination-pulse span{filter:drop-shadow(0 0 18px var(--cm-star));font-size:clamp(3rem,8vw,6rem);line-height:1}.illumination-pulse strong{color:var(--cm-ink);font-family:var(--cm-font-display)}.structural-summary p{max-width:62ch;margin:var(--cm-space-sm) 0 0;color:var(--cm-ink)}.mini-constellation{width:100%;max-width:22rem;height:auto;margin-block-start:var(--cm-space-md)}.mini-constellation line{stroke:var(--cm-route);stroke-width:1px}.mini-constellation__name{fill:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:9px}.mini-constellation circle{fill:var(--cm-ink-3)}.mini-constellation__self{fill:var(--cm-orbit)}.connections h3{margin:var(--cm-space-lg) 0 0;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em;font-weight:500}.connection-list{margin:var(--cm-space-xs) 0 0;border-block-start:1px solid var(--cm-hairline);padding:0;list-style:none}.connection-list button{gap:var(--cm-space-2xs);width:100%;min-height:52px;padding:var(--cm-space-sm) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.connection-name{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.connection-meta{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}@media (hover:hover) and (pointer:fine){.connection-list button:hover .connection-name{color:var(--cm-orbit)}}.load-state .check-primary,.study-notice .check-primary{justify-self:start;margin-block-start:var(--cm-space-md)}.study-preview{z-index:var(--cm-z-panel);width:100%;min-width:0;max-width:100%;max-height:72%;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-lg), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background-color:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;position:absolute;inset-block-end:0;inset-inline:0;overflow:auto}.check-panel{z-index:var(--cm-z-panel);width:100%;min-width:0;max-width:100%;max-height:76%;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-lg), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background-color:var(--cm-ground-2);color:var(--cm-ink);animation:panel-enter var(--cm-dur-reveal) var(--cm-ease) both;position:absolute;inset-block-end:0;inset-inline:0;overflow:auto}.study-preview,.check-panel,.star-chart-screen,.module-finder ul,.index-sidebar__scroll,.map-legend,.map-view{--cm-scroll-cover:var(--cm-ground-2);background-image:linear-gradient(var(--cm-scroll-cover) 30%, transparent), linear-gradient(transparent, var(--cm-scroll-cover) 70%), radial-gradient(farthest-side at 50% 0, #0000008c, transparent), radial-gradient(farthest-side at 50% 100%, #0000008c, transparent);background-position:0 0,0 100%,0 0,0 100%;background-repeat:no-repeat;background-size:100% 2.5rem,100% 2.5rem,100% .85rem,100% .85rem;background-attachment:local,local,scroll,scroll}.star-chart-screen{--cm-scroll-cover:var(--cm-ground)}.check-panel__header{justify-content:space-between;align-items:start;gap:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);padding-block-end:var(--cm-space-lg);display:flex}.check-panel__header p,.check-panel__header h1{margin:0}.check-panel__header p,.check-progress span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.check-panel__header h1{overflow-wrap:anywhere;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);margin-block-start:var(--cm-space-xs);line-height:1.1}.check-loading,.check-state,.check-complete,.active-check{margin-block-start:var(--cm-space-lg)}.check-loading,.check-state p,.check-complete p,.active-check fieldset>p{max-width:60ch;color:var(--cm-ink-2)}.check-state h2,.check-complete h2{font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0}.check-state p,.check-complete p{margin:var(--cm-space-sm) 0 0}.check-complete{gap:var(--cm-space-sm);display:grid}.check-complete__star{color:var(--cm-star);font-size:var(--cm-text-display);line-height:1}.check-progress{align-items:center;gap:var(--cm-space-md);grid-template-columns:auto minmax(5rem,1fr);margin-block-end:var(--cm-space-xl);display:grid}.check-progress progress{background:var(--cm-hairline);width:100%;height:2px;color:var(--cm-star);border:0}.check-progress progress::-webkit-progress-bar{background:var(--cm-hairline)}.check-progress progress::-webkit-progress-value{background:var(--cm-star)}.check-progress progress::-moz-progress-bar{background:var(--cm-star)}.active-check{gap:var(--cm-space-lg);display:grid}.active-check fieldset{border:0;min-width:0;margin:0;padding:0}.active-check legend{overflow-wrap:break-word;max-width:100%;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);padding:0;font-weight:700;line-height:1.15}.active-check fieldset>p{margin:var(--cm-space-sm) 0 0}.check-options{border-block-start:1px solid var(--cm-hairline);margin-block-start:var(--cm-space-lg)}.check-options label{align-items:center;gap:var(--cm-space-md);min-height:52px;padding-block:var(--cm-space-sm);border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink-2);cursor:pointer;grid-template-columns:auto minmax(0,1fr);display:grid}.check-options label:has(input:checked){color:var(--cm-ink)}.check-options input{width:1rem;height:1rem;accent-color:var(--cm-star);margin:0}.check-options span{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.check-feedback{gap:var(--cm-space-xs);padding:var(--cm-space-md);border-inline-start:2px solid var(--cm-error);background:var(--cm-ground);color:var(--cm-ink-2);display:grid}.check-feedback strong{color:var(--cm-ink)}.check-feedback span{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.check-affirmation{align-items:baseline;gap:var(--cm-space-xs);margin:0 0 var(--cm-space-lg);border-inline-start:2px solid var(--cm-star);color:var(--cm-star-high);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);padding-inline-start:var(--cm-space-sm);display:flex}.study-preview__path{font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);color:var(--cm-orbit)!important}.study-preview__header{display:block}.study-preview dl{gap:var(--cm-space-md);margin:var(--cm-space-lg) 0 0;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.study-preview dl div{border-block-start:1px solid var(--cm-hairline);padding-block-start:var(--cm-space-sm)}.study-preview dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.study-preview dd{margin:var(--cm-space-2xs) 0 0;font-variant-numeric:tabular-nums}.study-content{gap:var(--cm-space-xl);min-width:0;margin-block-start:var(--cm-space-xl);display:grid}.study-content>*{min-width:0}.study-section-heading{justify-content:space-between;align-items:center;gap:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);padding-block-end:var(--cm-space-sm);display:flex}.study-section-heading h2,.study-notice h2{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal;line-height:1.15}.study-section-heading span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:nowrap}.source-code{width:100%;min-width:0;max-height:22rem;margin:var(--cm-space-md) 0 0;padding:var(--cm-space-sm) 0;border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-ink);counter-reset:source-line;font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);tab-size:4;line-height:1.65;list-style:none;overflow:auto}.source-code li{min-width:max-content;padding-inline:var(--cm-space-sm) var(--cm-space-md);grid-template-columns:3.25rem minmax(0,1fr);display:grid}.source-code li:before{content:attr(data-line);color:var(--cm-ink-3);text-align:end;-webkit-user-select:none;user-select:none;padding-inline-end:var(--cm-space-md)}.source-code li:target{background:var(--cm-orbit-low)}.source-code code{font:inherit;white-space:pre}.grounded-explanation>p,.study-notice p,.study-loading,.evidence-list p{max-width:65ch;color:var(--cm-ink-2)}.grounded-explanation>p,.study-notice p,.study-loading{margin:var(--cm-space-md) 0 0}.grounded-explanation h3{margin:var(--cm-space-lg) 0 var(--cm-space-sm);font-family:var(--cm-font-display);font-size:var(--cm-text-base);font-style:normal}.source-citation{color:var(--cm-orbit);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);text-underline-offset:2px;text-decoration-thickness:1px}.source-citation:active{color:var(--cm-ink)}@media (hover:hover) and (pointer:fine){.source-citation:hover{color:var(--cm-ink)}}.source-citation--button{cursor:pointer;background:0 0;border:0;padding:0}.source-citation--button:disabled{color:var(--cm-ink-3);cursor:not-allowed}.evidence-list{gap:var(--cm-space-sm);margin:0;padding:0;list-style:none;display:grid}.evidence-list li,.study-notice{padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground)}.evidence-list p{margin:0 0 var(--cm-space-xs)}.evidence-list strong{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);margin-block-end:var(--cm-space-xs);display:block}.lens-notes{margin-block-start:var(--cm-space-sm);display:grid}.lens-note{gap:var(--cm-space-sm);min-width:0;padding-block:var(--cm-space-md);border-block-end:1px solid var(--cm-hairline);display:grid}.lens-note h3{margin:0 0 var(--cm-space-xs);font-family:var(--cm-font-display);font-size:var(--cm-text-base);font-style:normal}.lens-note p{max-width:65ch;margin:0 0 var(--cm-space-sm);color:var(--cm-ink-2)}.lens-note code{min-width:0;padding:var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);white-space:pre;display:block;overflow-x:auto}.star-chart-screen{min-width:0;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-xl) max(var(--cm-space-md), env(safe-area-inset-left));background-color:var(--cm-ground);overflow:auto}.star-chart-intro{max-width:72rem;margin-inline:auto;padding-block-end:var(--cm-space-xl);display:block}.star-chart-intro>p:first-child{margin:0 0 var(--cm-space-sm);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.star-chart-intro h1{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-display);letter-spacing:-.035em;margin:0;font-style:normal;line-height:1.08}.star-chart-intro>p:not(:first-child){max-width:65ch;margin:var(--cm-space-md) 0 0;color:var(--cm-ink-2)}.star-chart-intro dl{gap:var(--cm-space-md);margin:var(--cm-space-xl) 0 0;padding-block:var(--cm-space-md);border-block:1px solid var(--cm-hairline);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.star-chart-intro dt,.concept-row dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.star-chart-intro dd{margin:var(--cm-space-xs) 0 0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);font-variant-numeric:tabular-nums}.concept-ledger{border-block-start:1px solid var(--cm-hairline);max-width:72rem;margin-inline:auto}.concept-row{align-items:center;gap:var(--cm-space-lg);min-width:0;padding-block:var(--cm-space-lg);border-block-end:1px solid var(--cm-hairline);grid-template-columns:minmax(10rem,.7fr) minmax(8rem,1.2fr) auto;display:grid}.concept-row h2{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal}.concept-row>div:first-child>span{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.concept-meter{background:var(--cm-hairline);height:2px;overflow:hidden}.concept-meter span{background:var(--cm-star);height:100%;display:block}.concept-row dl{align-items:center;gap:var(--cm-space-lg);margin:0;display:flex}.concept-row dl div{min-width:5rem}.concept-row dd{margin:var(--cm-space-xs) 0 0;font-variant-numeric:tabular-nums}.status-line{z-index:var(--cm-z-sticky);justify-content:space-between;gap:var(--cm-space-2xs) var(--cm-space-md);min-width:0;max-width:100%;padding:var(--cm-space-sm) max(var(--cm-space-md), env(safe-area-inset-right)) max(var(--cm-space-sm), env(safe-area-inset-bottom)) max(var(--cm-space-md), env(safe-area-inset-left));border-block-start:1px solid var(--cm-hairline);background:var(--cm-ground);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);font-variant-numeric:tabular-nums;flex-wrap:wrap;grid-row:4;display:flex;position:relative}.status-line span{white-space:normal}.status-line span:nth-child(2){display:none}.load-state,.webgl-error{align-content:center;gap:var(--cm-space-md);min-height:100dvh;padding:var(--cm-space-lg);background:var(--cm-ground);color:var(--cm-ink);display:grid}.load-state p,.webgl-error p{max-width:65ch;color:var(--cm-ink-2);margin:0}@keyframes panel-enter{0%{opacity:0;transform:translateY(var(--cm-space-md))}to{opacity:1;transform:translateY(0)}}@keyframes entrypoint-enter{0%{opacity:0;transform:translateY(var(--cm-space-md))}to{opacity:1;transform:translateY(0)}}@keyframes illumination-pulse{0%{opacity:0;transform:translate(-50%,-50%)scale(.75)}45%{opacity:1;transform:translate(-50%,-50%)scale(1.08)}to{opacity:0;transform:translate(-50%,-50%)scale(1)}}@media (width>=40rem){.status-line span{white-space:nowrap;display:inline}.lens-note{gap:var(--cm-space-lg);grid-template-columns:minmax(8rem,.35fr) minmax(0,1fr)}.study-preview{border-block-start:0;border-inline-start:1px solid var(--cm-hairline);width:min(48rem,60%);max-height:none;inset-block:0;inset-inline-start:auto}.check-panel{border-block-start:0;border-inline-start:1px solid var(--cm-hairline);width:min(42rem,56%);max-height:none;inset-block:0;inset-inline-start:auto}}@media (width>=64rem){.instrument-rail{grid-template-columns:minmax(0,auto) minmax(9rem,1fr) auto auto}.rail-overflow{display:contents}.rail-overflow__panel,.rail-overflow[data-open] .rail-overflow__panel{display:contents;position:static}.location{grid-area:1/2}.rail-action,.home-readout{justify-self:end}.rail-actions{flex-wrap:nowrap;grid-area:1/3;justify-content:flex-end;min-width:max-content}.rail-more{z-index:var(--cm-z-panel);width:max-content;max-width:min(22rem,100%);padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);grid-area:auto;display:none;position:absolute;inset-block-start:100%;inset-inline-end:max(var(--cm-space-md), env(safe-area-inset-right));box-shadow:0 1rem 2.5rem #00000061}.rail-overflow[data-open] .rail-more{display:flex}.mobile-menu-trigger{min-width:0;padding-inline:var(--cm-space-sm);grid-area:1/4;place-self:center end}.rail-trigger__compact{display:none}.rail-trigger__wide{display:inline}.rail-controls{grid-area:2/1/auto/-1;justify-self:center}.language-focus{flex:12rem}}@media (width<=39.999rem){.study-preview{border-block-start:1px solid var(--cm-hairline);max-height:none;position:fixed;inset-block:7.75rem 0}.app-shell:has(.study-preview) .instrument-rail{z-index:calc(var(--cm-z-panel) + 2)}.check-panel{border-block-start:0;max-height:none;position:fixed;inset-block:7.75rem 0;inset-inline:0}.check-panel__header{padding-block-end:var(--cm-space-md)}.check-loading,.check-state,.check-complete,.active-check{margin-block-start:var(--cm-space-md)}.active-check{gap:var(--cm-space-md)}.check-options{margin-block-start:var(--cm-space-md)}}@media (width<=48rem){.illumination-pulse{inset-block-start:35%;inset-inline-start:50%}.star-chart-intro dl{grid-template-columns:1fr}.concept-row{gap:var(--cm-space-md);grid-template-columns:1fr}.concept-row dl{justify-content:space-between}}@media (width>=60rem){.map-stage:has(.map-view) .map-legend{grid-template-columns:repeat(2,auto)}.map-legend span{white-space:nowrap}.chart-stage:has(.index-sidebar) .star-chart-screen{margin-inline-start:var(--cm-sidebar-width)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.15s!important;animation-duration:.15s!important;animation-iteration-count:1!important}.study-preview,.check-panel,.mode-gate{animation-name:panel-enter-reduced}.entrypoint-picker{animation-name:entrypoint-enter-reduced}.illumination-pulse{animation-name:illumination-pulse-reduced}}@keyframes panel-enter-reduced{0%{opacity:0}to{opacity:1}}@keyframes entrypoint-enter-reduced{0%{opacity:0}to{opacity:1}}@keyframes illumination-pulse-reduced{0%{opacity:0}50%{opacity:1}to{opacity:0}}.picker-screen{gap:var(--cm-space-lg);min-height:100vh;padding:var(--cm-space-2xl) var(--cm-space-xl);flex-direction:column;max-width:44rem;margin:0 auto;display:flex}.picker-wordmark{font-family:var(--cm-font-display);font-size:var(--cm-text-sm);letter-spacing:.2em;text-transform:uppercase;color:var(--cm-ink-3)}.picker-subtitle{color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.picker-recents ul,.picker-browser ul{margin:var(--cm-space-sm) 0 0;gap:var(--cm-space-2xs);flex-direction:column;padding:0;list-style:none;display:flex}.picker-recents button,.picker-browser ul button{justify-content:space-between;gap:var(--cm-space-sm);width:100%;padding:var(--cm-space-xs) var(--cm-space-sm);background:var(--cm-ground-2);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);text-align:left;cursor:pointer;display:flex}.picker-recents button:hover,.picker-recents button:focus-visible,.picker-browser ul button:hover,.picker-browser ul button:focus-visible{border-color:var(--cm-orbit)}.picker-recents button{flex-wrap:wrap}.picker-recent-path{overflow-wrap:anywhere;min-width:0}.picker-recent-lit{color:var(--cm-star);white-space:nowrap}.picker-path{font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);color:var(--cm-ink-2);overflow-wrap:anywhere}.picker-scale,.picker-error{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);padding:var(--cm-space-sm);font-size:var(--cm-text-sm)}.picker-error{color:var(--cm-error)}.picker-error p{margin:0}.picker-retry{min-height:44px;padding:var(--cm-space-xs) var(--cm-space-md);border:1px solid var(--cm-error);border-radius:var(--cm-radius);color:var(--cm-ink);cursor:pointer;background:0 0;margin-block-start:var(--cm-space-sm)}.picker-retry:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:2px}.picker-select{padding:var(--cm-space-xs) var(--cm-space-lg);background:var(--cm-orbit-low);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-ink);font-size:var(--cm-text-base);cursor:pointer;align-self:flex-start}.picker-select:disabled{opacity:.6;cursor:progress}.layer-switcher{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);display:inline-flex;overflow:hidden}.layer-switcher button{padding:var(--cm-space-2xs) var(--cm-space-sm);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0;border:0}.layer-switcher button[aria-pressed=true]{background:var(--cm-orbit-low);color:var(--cm-ink)}.map-view{gap:var(--cm-space-sm);padding:var(--cm-space-md);--cm-scroll-cover:var(--cm-ground);background-color:var(--cm-ground);flex-direction:column;display:flex;position:absolute;inset:0;overflow:hidden auto}.map-tabs{gap:var(--cm-space-xs);flex:none;display:flex}.map-tabs button{padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0}.map-tabs button[aria-pressed=true]{border-color:var(--cm-orbit);color:var(--cm-ink)}.map-canvas{flex-direction:column;flex:auto;min-block-size:6rem;display:flex;position:relative}.map-zoom{z-index:var(--cm-z-raised);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2);gap:1px;display:flex;position:absolute;inset-block-start:var(--cm-space-sm);inset-inline-end:var(--cm-space-sm)}.map-zoom button{min-width:34px;min-height:30px;color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);cursor:pointer;background:0 0;border:0}.map-zoom button:hover:not(:disabled),.map-zoom button:focus-visible{color:var(--cm-orbit)}.map-zoom button:disabled{color:var(--cm-ink-3);cursor:default}@media (width<=40rem){.map-tabs{flex-wrap:wrap;padding-inline-end:4.5rem}.map-zoom{align-self:flex-end;margin-block-end:var(--cm-space-xs);position:static}.map-canvas{min-block-size:8.5rem}}.map-scroll{overscroll-behavior:contain;cursor:grab;background:linear-gradient(var(--cm-ground) 30%, transparent) 0 0 / 100% 2.5rem local no-repeat, linear-gradient(transparent, var(--cm-ground) 70%) 0 100% / 100% 2.5rem local no-repeat, linear-gradient(to right, var(--cm-ground) 30%, transparent) 0 0 / 2.5rem 100% local no-repeat, linear-gradient(to left, var(--cm-ground) 30%, transparent) 100% 0 / 2.5rem 100% local no-repeat, radial-gradient(farthest-side at 50% 0, #0000008c, transparent) 0 0 / 100% .85rem scroll no-repeat, radial-gradient(farthest-side at 50% 100%, #0000008c, transparent) 0 100% / 100% .85rem scroll no-repeat, radial-gradient(farthest-side at 0 50%, #00000073, transparent) 0 0 / .85rem 100% scroll no-repeat, radial-gradient(farthest-side at 100% 50%, #00000073, transparent) 100% 0 / .85rem 100% scroll no-repeat;flex:auto;overflow:auto}.map-scroll[data-panning]{cursor:grabbing;-webkit-user-select:none;user-select:none}.map-canvas__sized{min-width:100%}.map-canvas-surface{contain:strict;max-width:100%;max-height:100%;display:block;position:sticky;inset-block-start:0;inset-inline-start:0;overflow:hidden}.map-canvas-surface:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:-3px}.map-canvas-surface canvas{display:block}.map-canvas-readout{max-width:calc(100% - var(--cm-space-sm) * 2);padding:var(--cm-space-2xs) var(--cm-space-xs);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);overflow-wrap:anywhere;white-space:normal;pointer-events:none;background:#101a3eeb;position:absolute;inset-block-end:var(--cm-space-sm);inset-inline-start:var(--cm-space-sm)}.map-canvas-active-option{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.map-note,.map-loading{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);flex:none;margin:0}.map-state{text-align:center;max-width:46ch;color:var(--cm-ink-2);margin:auto}.hint-chip{z-index:calc(var(--cm-z-panel) + 1);align-items:center;gap:var(--cm-space-sm);min-width:0;padding:var(--cm-space-sm) var(--cm-space-md);border-block:1px solid var(--cm-orbit);background:var(--cm-ground-2);color:var(--cm-ink);font-size:var(--cm-text-sm);flex-wrap:wrap;grid-row:3;display:flex;position:static}@media (width<=40rem){.hint-chip{max-block-size:40vh;overflow-y:auto}.hint-chip small{text-overflow:ellipsis;white-space:nowrap;flex:100%;overflow:hidden}}.hint-chip small{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.hint-chip button{padding:var(--cm-space-2xs) var(--cm-space-sm);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-orbit);font-size:var(--cm-text-xs);cursor:pointer;background:0 0}.first-flight__system{overflow-wrap:anywhere;min-width:0;font-family:var(--cm-font-mono)}.first-flight__actions{gap:var(--cm-space-xs);flex-wrap:wrap;margin-inline-start:auto;display:flex}.first-flight__actions button[disabled]{border-color:var(--cm-hairline);color:var(--cm-ink-3);cursor:default}@media (width<=40rem){.first-flight__system,.first-flight__actions{flex:100%}.first-flight__actions{margin-inline-start:0}}.location button{color:var(--cm-orbit);font:inherit;cursor:pointer;background:0 0;border:0;padding:0}.location button[disabled]{color:var(--cm-ink-2);cursor:default}.location button:not([disabled]):hover{text-decoration:underline}.coach-marks{inset-block:auto var(--cm-space-lg);width:min(38ch, calc(100% - var(--cm-space-lg)));padding:var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground-2);color:var(--cm-ink);margin-inline:auto;inset-inline:0}.coach-marks::backdrop{background:var(--cm-scrim)}.coach-marks__progress{margin:0 0 var(--cm-space-2xs);color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.coach-marks h1{margin:0 0 var(--cm-space-xs);font-size:var(--cm-text-lg)}.coach-marks__actions{justify-content:space-between;gap:var(--cm-space-sm);margin-block-start:var(--cm-space-md);display:flex}.coach-skip{color:var(--cm-ink-3);cursor:pointer;background:0 0;border:0}.legend-tint{width:var(--cm-space-sm);height:var(--cm-space-sm);border-radius:2px;display:inline-block}.legend-communities{gap:2px;display:inline-flex}.legend-communities i{width:calc(var(--cm-space-sm) / 1.6);height:var(--cm-space-sm);border-radius:2px;display:inline-block}.legend-communities i:first-child{background:var(--cm-com-0)}.legend-communities i:nth-child(2){background:var(--cm-com-1)}.legend-communities i:nth-child(3){background:var(--cm-com-2)}.legend-communities i:nth-child(4){background:var(--cm-com-3)}.map-note__toggle{border:var(--cm-rule) solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-orbit);font:inherit;font-size:var(--cm-text-xs);padding:var(--cm-space-2xs) var(--cm-space-sm);cursor:pointer;background:0 0}.map-note__toggle:hover,.map-note__toggle:focus-visible{border-color:var(--cm-orbit)}.legend-route--dashed{border-block-start-style:dashed}.legend-size{width:var(--cm-space-md);height:var(--cm-space-sm);background:var(--cm-ink-3);border-radius:50%;display:inline-block}.legend-brightness{width:var(--cm-space-md);height:var(--cm-space-sm);background:linear-gradient(90deg, var(--cm-node-unlit), var(--cm-ink-2));display:inline-block}.app-shell[data-mode=easy]{--cm-text-xs:.8125rem;--cm-text-sm:.9375rem;--cm-text-base:1.125rem;--cm-text-lg:1.375rem;--cm-space-sm:1rem;--cm-space-md:1.375rem;--cm-space-lg:2.5rem;font-size:var(--cm-text-base);line-height:1.7}.app-shell[data-mode=easy] .orientation-copy p,.app-shell[data-mode=easy] .study-preview p,.app-shell[data-mode=easy] .grounded-explanation>p,.app-shell[data-mode=easy] .study-notice p,.app-shell[data-mode=easy] .evidence-list p,.app-shell[data-mode=easy] .lens-note p,.app-shell[data-mode=easy] .active-check fieldset>p,.app-shell[data-mode=easy] .check-state p,.app-shell[data-mode=easy] .check-complete p,.app-shell[data-mode=easy] .check-loading{max-width:46ch}.app-shell[data-mode] .orientation-copy--inline>p{max-width:72ch}.app-shell[data-mode=easy] .language-focus__label,.app-shell[data-mode=easy] .mode-toggle__label{text-transform:none;letter-spacing:normal}.loading-screen{gap:var(--cm-space-md);max-width:44rem;min-height:100vh;padding:var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-right)) var(--cm-space-lg) max(var(--cm-space-md), env(safe-area-inset-left));align-content:center;justify-items:start;margin:0 auto;display:grid}.loading-header h1{min-width:0;margin:var(--cm-space-2xs) 0 0;font-family:var(--cm-font-display);font-size:var(--cm-text-xl);overflow-wrap:anywhere;line-height:1.15}.loading-path{font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);color:var(--cm-ink-2)}.loading-subtitle{margin:var(--cm-space-xs) 0 0;color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.loading-stages{gap:var(--cm-space-2xs);width:100%;margin:0;padding:0;list-style:none;display:grid}.loading-stages li{justify-content:space-between;gap:var(--cm-space-sm);padding:var(--cm-space-xs) var(--cm-space-sm);border-left:2px solid var(--cm-hairline);color:var(--cm-ink-3);font-size:var(--cm-text-sm);flex-wrap:wrap;display:flex}.loading-stages li[data-state=active]{border-left-color:var(--cm-orbit);color:var(--cm-ink)}.loading-stages li[data-state=done]{border-left-color:var(--cm-orbit-deep);color:var(--cm-ink-2)}.loading-stages small{font-family:var(--cm-font-mono);color:var(--cm-ink-2)}.loading-meter{width:100%;height:var(--cm-space-xs);accent-color:var(--cm-orbit)}.loading-live{color:var(--cm-ink);font-size:var(--cm-text-sm)}.loading-error{border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);width:100%;padding:var(--cm-space-sm);color:var(--cm-ink-2);font-size:var(--cm-text-sm)}.loading-actions{gap:var(--cm-space-sm);flex-wrap:wrap;display:flex}.loading-actions button{padding:var(--cm-space-xs) var(--cm-space-md)}.picker-scale h2,.picker-failure h2{margin:0 0 var(--cm-space-xs);font-family:var(--cm-font-display);font-size:var(--cm-text-base)}.picker-scale-scopes{gap:var(--cm-space-xs);margin:var(--cm-space-sm) 0;flex-wrap:wrap;padding:0;list-style:none;display:flex}.picker-scale-scopes button{align-items:flex-start;gap:var(--cm-space-2xs);min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-sm);background:var(--cm-ground-2);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);cursor:pointer;flex-direction:column;display:flex}.picker-scale-scopes button:hover:not(:disabled),.picker-scale-scopes button:focus-visible{border-color:var(--cm-orbit)}.picker-scale-scopes button:disabled{opacity:.6;cursor:progress}.picker-scale-scopes small{color:var(--cm-ink-2)}.picker-path-entry{gap:var(--cm-space-xs);flex-wrap:wrap;align-items:center;display:flex}.picker-path-entry label{color:var(--cm-ink-2)}.picker-path-entry input{min-width:0;min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-xs);background:var(--cm-ground);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);font-family:var(--cm-font-mono);font-size:var(--cm-text-sm);flex:14rem}.picker-path-entry button{min-height:44px;padding:var(--cm-space-2xs) var(--cm-space-md);background:var(--cm-orbit-low);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-ink);font-size:var(--cm-text-sm);cursor:pointer}.picker-path-entry button:disabled{opacity:.6;cursor:not-allowed}.picker-failure{border:1px solid var(--cm-error);border-radius:var(--cm-radius);padding:var(--cm-space-sm);font-size:var(--cm-text-sm)}.picker-failure>p{margin:0 0 var(--cm-space-sm);color:var(--cm-ink-2)}.picker-failure__detail{gap:var(--cm-space-2xs);display:grid}.picker-failure__detail span{color:var(--cm-ink-3);font-size:var(--cm-text-xs);text-transform:uppercase;letter-spacing:.08em}.picker-failure__detail code{max-height:8rem;padding:var(--cm-space-xs);background:var(--cm-ground-2);border-radius:var(--cm-radius);color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);overflow-wrap:anywhere;display:block;overflow:auto}.picker-failure .picker-select{min-height:44px;font-size:var(--cm-text-sm);align-self:flex-start}.progress-reset{align-items:center;gap:var(--cm-space-sm);max-width:72rem;margin:var(--cm-space-lg) auto 0;padding-top:var(--cm-space-md);border-top:1px solid var(--cm-hairline);flex-wrap:wrap;display:flex}.progress-reset p{color:var(--cm-ink-2);font-size:var(--cm-text-sm);margin:0}.progress-reset button{min-height:44px;padding-inline:var(--cm-space-md);font-size:var(--cm-text-sm)}.progress-reset--confirming{justify-items:start;gap:var(--cm-space-sm);display:grid}.progress-reset--confirming>p[role=alert]{max-width:60ch;color:var(--cm-ink)}.progress-reset--confirming>div{gap:var(--cm-space-sm);flex-wrap:wrap;display:flex}.progress-reset__confirm{border-color:var(--cm-error);color:var(--cm-error)}.progress-reset__error{color:var(--cm-error)}.learning-journey{min-width:0}.journey-overview{gap:var(--cm-space-2xs);margin:var(--cm-space-md) 0 0;padding:0 0 var(--cm-space-xs);scrollbar-width:thin;grid-auto-columns:minmax(4.5rem,1fr);grid-auto-flow:column;list-style:none;display:grid;overflow-x:auto}.journey-overview button{width:100%;min-height:44px;padding:var(--cm-space-xs);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink-2);cursor:pointer;text-align:start;background:0 0;align-content:center;justify-items:start;gap:1px;display:grid}.journey-overview button[aria-current=step]{border-color:var(--cm-orbit);background:var(--cm-orbit-low);color:var(--cm-ink)}.journey-overview button>span,.journey-overview button>small,.journey-position,.journey-citations,.journey-expert-facts,.journey-controls,.journey-break code,.journey-possible span,.journey-candidate-note,.journey-support-scope{font-family:var(--cm-font-mono)}.journey-overview button>span{color:var(--cm-orbit);font-size:var(--cm-text-sm)}.journey-overview button>small{text-transform:uppercase;font-size:.625rem;line-height:1.2}.journey-current{gap:var(--cm-space-sm);padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-inline-start:3px solid var(--cm-orbit);border-radius:var(--cm-radius);background:var(--cm-ground);margin-block-start:var(--cm-space-sm);display:grid}.journey-position{font-size:var(--cm-text-xs);text-transform:uppercase;color:var(--cm-orbit)!important;margin:0!important}.journey-current h3{font-family:var(--cm-font-display);font-size:var(--cm-text-lg);margin:0;font-style:normal}.journey-current>p:not(.journey-position){color:var(--cm-ink-2);margin:0}.journey-citations{gap:var(--cm-space-xs) var(--cm-space-md);color:var(--cm-ink-3);font-size:var(--cm-text-xs);flex-wrap:wrap;display:flex}.journey-citations code,.journey-break code{color:var(--cm-orbit);overflow-wrap:anywhere}.study-preview .journey-expert-facts{gap:var(--cm-space-sm);grid-template-columns:repeat(auto-fit,minmax(8rem,1fr));margin:0}.journey-expert-facts dd{overflow-wrap:anywhere;font-size:var(--cm-text-xs)}.journey-controls{gap:var(--cm-space-xs);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.journey-controls button{min-height:44px;padding:var(--cm-space-xs);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);color:var(--cm-ink);cursor:pointer;background:0 0}.journey-controls button:nth-child(2){border-color:var(--cm-orbit);color:var(--cm-orbit)}.journey-controls button:disabled{color:var(--cm-ink-3);cursor:not-allowed;opacity:.55}.journey-break,.journey-possible{padding:var(--cm-space-md);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);margin-block-start:var(--cm-space-md)}.journey-break{border-inline-start:3px solid var(--cm-error)}.journey-break p,.journey-possible p{margin:var(--cm-space-xs) 0 0;color:var(--cm-ink-2)}.journey-possible h3{font-family:var(--cm-font-display);font-size:var(--cm-text-base);margin:0;font-style:normal}.journey-possible ul{gap:var(--cm-space-xs);margin:var(--cm-space-md) 0 0;padding:0;list-style:none;display:grid}.journey-possible li{gap:var(--cm-space-2xs);border-block-start:1px solid var(--cm-hairline);padding-block-start:var(--cm-space-xs);display:grid}.journey-possible span,.journey-candidate-note{color:var(--cm-ink-3);font-size:var(--cm-text-xs);overflow-wrap:anywhere}.journey-support{border-block-start:1px solid var(--cm-hairline);margin-block-start:var(--cm-space-lg)}.journey-support>summary{min-height:44px;padding-block:var(--cm-space-sm);color:var(--cm-orbit);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.journey-support>section{margin-block-start:var(--cm-space-lg)}.journey-support-scope{margin:0}.journey-candidate-note{margin-block-start:var(--cm-space-sm)}.visually-hidden{clip:rect(0 0 0 0)!important;clip-path:inset(50%)!important;white-space:nowrap!important;width:1px!important;height:1px!important;position:absolute!important;overflow:hidden!important}@media (width<=20rem){.study-preview{padding:var(--cm-space-sm) var(--cm-space-md) max(var(--cm-space-sm), env(safe-area-inset-bottom))}.study-content{gap:var(--cm-space-md);margin-block-start:var(--cm-space-xs)}.study-preview dl{gap:var(--cm-space-sm);margin-block-start:var(--cm-space-md)}.journey-current{gap:var(--cm-space-xs);padding:var(--cm-space-xs)}.journey-overview{margin-block-start:var(--cm-space-xs)}.journey-current>p:not(.journey-position){font-size:var(--cm-text-sm);line-height:1.45}.journey-controls{gap:var(--cm-space-2xs)}}@media (width<=22.5rem){.app-shell[data-level=study] .hint-chip{gap:var(--cm-space-xs)}.app-shell[data-level=study] .hint-chip button{padding-inline:var(--cm-space-xs)}.source-study .study-section-heading{gap:var(--cm-space-xs)}.source-study .study-section-heading h2{overflow-wrap:normal;flex:none}.source-study .study-section-heading span{text-overflow:ellipsis;min-width:0;overflow:hidden}}.impact-columns{gap:var(--cm-space-md);grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));display:grid}.impact-column h3{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs);letter-spacing:.04em;text-transform:uppercase;margin:0;font-weight:500}.impact-list{margin:var(--cm-space-2xs) 0 0;border-block-start:1px solid var(--cm-hairline);padding:0;list-style:none}.impact-list button{gap:var(--cm-space-2xs);width:100%;min-height:44px;padding:var(--cm-space-xs) 0;border:0;border-block-end:1px solid var(--cm-hairline);color:var(--cm-ink);cursor:pointer;text-align:start;background:0 0;display:grid}.impact-name{overflow-wrap:anywhere;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.impact-meta,.impact-citation{color:var(--cm-ink-2);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.impact-citation{overflow-wrap:anywhere}@media (hover:hover) and (pointer:fine){.impact-list button:hover .impact-name{color:var(--cm-orbit)}}.walkthrough-disclosure>summary{min-height:44px;padding:var(--cm-space-xs) 0;color:var(--cm-orbit);cursor:pointer;font-family:var(--cm-font-mono);font-size:var(--cm-text-sm)}.walkthrough-disclosure>summary:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:2px}.project-summary{margin-block:var(--cm-space-lg);padding:var(--cm-space-lg);border:1px solid var(--cm-hairline);border-radius:var(--cm-radius);background:var(--cm-ground-2)}.project-summary__lead{max-width:60ch;margin:0 0 var(--cm-space-lg);color:var(--cm-ink);font-size:var(--cm-text-lg);line-height:1.4}.project-summary__facts{gap:var(--cm-space-md) var(--cm-space-xl);grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));margin:0;display:grid}.project-summary__facts>div{min-width:0}.project-summary__facts dt{color:var(--cm-ink-3);font-family:var(--cm-font-mono);font-size:var(--cm-text-xs)}.project-summary__facts dd{margin:var(--cm-space-2xs) 0 0;overflow-wrap:break-word;color:var(--cm-ink)}.project-summary__export{min-height:44px;padding:var(--cm-space-xs) var(--cm-space-md);border:1px solid var(--cm-orbit);border-radius:var(--cm-radius);color:var(--cm-orbit);cursor:pointer;font:500 var(--cm-text-sm) / 1.3 var(--cm-font-mono);background:0 0;margin-block-start:var(--cm-space-lg)}.project-summary__export:hover{background:color-mix(in srgb, var(--cm-orbit) 10%, transparent)}.project-summary__export:focus-visible{outline:2px solid var(--cm-orbit);outline-offset:3px}.star-chart-section{margin-block:var(--cm-space-xl) 0;font-family:var(--cm-font-display);font-size:var(--cm-text-lg)} diff --git a/codemble/web_dist/index.html b/codemble/web_dist/index.html index 15f6051..e74d252 100644 --- a/codemble/web_dist/index.html +++ b/codemble/web_dist/index.html @@ -9,8 +9,8 @@ content="Explore the parser-proven structure of your local project with Codemble." /> Codemble · Your code, mapped - - + +
diff --git a/docs-site/package.json b/docs-site/package.json index 325843b..7562db1 100644 --- a/docs-site/package.json +++ b/docs-site/package.json @@ -1,7 +1,7 @@ { "name": "codemble-docs", "type": "module", - "version": "0.19.2", + "version": "0.20.0", "private": true, "scripts": { "dev": "astro dev", diff --git a/docs-site/public/shots/easy-mode.png b/docs-site/public/shots/easy-mode.png index 89be080..60e14e7 100644 Binary files a/docs-site/public/shots/easy-mode.png and b/docs-site/public/shots/easy-mode.png differ diff --git a/docs-site/public/shots/galaxy-lit.png b/docs-site/public/shots/galaxy-lit.png index 5d2b1a9..6787119 100644 Binary files a/docs-site/public/shots/galaxy-lit.png and b/docs-site/public/shots/galaxy-lit.png differ diff --git a/docs-site/public/shots/galaxy.png b/docs-site/public/shots/galaxy.png index 1a24ee1..841468c 100644 Binary files a/docs-site/public/shots/galaxy.png and b/docs-site/public/shots/galaxy.png differ diff --git a/docs-site/public/shots/home-proved.png b/docs-site/public/shots/home-proved.png index de3ff9c..5685344 100644 Binary files a/docs-site/public/shots/home-proved.png and b/docs-site/public/shots/home-proved.png differ diff --git a/docs-site/public/shots/map-architecture.png b/docs-site/public/shots/map-architecture.png index ed14c71..709ccd7 100644 Binary files a/docs-site/public/shots/map-architecture.png and b/docs-site/public/shots/map-architecture.png differ diff --git a/docs-site/public/shots/map-workflow.png b/docs-site/public/shots/map-workflow.png index 1a00ca1..4071108 100644 Binary files a/docs-site/public/shots/map-workflow.png and b/docs-site/public/shots/map-workflow.png differ diff --git a/docs-site/public/shots/study-impact.png b/docs-site/public/shots/study-impact.png index a2252a1..70745d2 100644 Binary files a/docs-site/public/shots/study-impact.png and b/docs-site/public/shots/study-impact.png differ diff --git a/docs-site/public/shots/study-panel.png b/docs-site/public/shots/study-panel.png index 4062360..759b9dd 100644 Binary files a/docs-site/public/shots/study-panel.png and b/docs-site/public/shots/study-panel.png differ diff --git a/docs-site/public/shots/system.png b/docs-site/public/shots/system.png index 00e9b4e..e6ffc50 100644 Binary files a/docs-site/public/shots/system.png and b/docs-site/public/shots/system.png differ diff --git a/docs-site/release.json b/docs-site/release.json index e5927f4..67f78f8 100644 --- a/docs-site/release.json +++ b/docs-site/release.json @@ -1,21 +1,21 @@ { - "version": "0.19.2", - "tag": "v0.19.2", - "sourceDateEpoch": 1787356800, - "releaseUrl": "https://github.com/udhawan97/Codemble/releases/tag/v0.19.2", - "pypiFilesUrl": "https://pypi.org/project/codemble/0.19.2/#files", + "version": "0.20.0", + "tag": "v0.20.0", + "sourceDateEpoch": 1787529600, + "releaseUrl": "https://github.com/udhawan97/Codemble/releases/tag/v0.20.0", + "pypiFilesUrl": "https://pypi.org/project/codemble/0.20.0/#files", "checksums": { "filename": "SHA256SUMS.txt", - "url": "https://github.com/udhawan97/Codemble/releases/download/v0.19.2/SHA256SUMS.txt" + "url": "https://github.com/udhawan97/Codemble/releases/download/v0.20.0/SHA256SUMS.txt" }, "wheel": { - "filename": "codemble-0.19.2-py3-none-any.whl", - "url": "https://github.com/udhawan97/Codemble/releases/download/v0.19.2/codemble-0.19.2-py3-none-any.whl", - "sha256": "8d6183d2d7166bb6f84490026f9057e12acbbe6361ebbcc43727bc8f5e7492a6" + "filename": "codemble-0.20.0-py3-none-any.whl", + "url": "https://github.com/udhawan97/Codemble/releases/download/v0.20.0/codemble-0.20.0-py3-none-any.whl", + "sha256": "15e42dee6b06d4569a969767cfae7556b2b642a15092e6801c924fcda692cf61" }, "sdist": { - "filename": "codemble-0.19.2.tar.gz", - "url": "https://github.com/udhawan97/Codemble/releases/download/v0.19.2/codemble-0.19.2.tar.gz", - "sha256": "b570ecc329e4aca79cbf34bca60674130b71470e02c049320e5021f533e50ad7" + "filename": "codemble-0.20.0.tar.gz", + "url": "https://github.com/udhawan97/Codemble/releases/download/v0.20.0/codemble-0.20.0.tar.gz", + "sha256": "466e5f5bdad022730abd4b0c4228e1079bf88645ad3700af6cdc2e5ebc78b008" } } diff --git a/docs-site/scripts/check-release-facts.mjs b/docs-site/scripts/check-release-facts.mjs index ef0b186..fe31ba8 100644 --- a/docs-site/scripts/check-release-facts.mjs +++ b/docs-site/scripts/check-release-facts.mjs @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; +import { execFileSync } from "node:child_process"; import { readFile } from "node:fs/promises"; import { basename, dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -86,6 +87,18 @@ if (distDirectory) { const path = resolve(repositoryRoot, distDirectory, artifact.filename); assert.equal(await digestFile(path), artifact.sha256, `${artifact.filename} build drifted`); } + const sdistPath = resolve(repositoryRoot, distDirectory, release.sdist.filename); + const archiveEntries = execFileSync("tar", ["-tzf", sdistPath], { + encoding: "utf8", + }).trim().split("\n"); + const forbiddenArchiveEntry = archiveEntries.find((entry) => + /(?:^|\/)(?:\.git(?:\/|$)|\.git-backup-remote$|\.last-git-backup-ts$|\.playwright-cli(?:\/|$)|\.env(?:\.(?!example$)[^/]+)?$|\.DS_Store$|tests\/fixtures\/sampleproj\/(?:generated(?:\/|$)|ignored\.py$))/.test(entry), + ); + assert.equal( + forbiddenArchiveEntry, + undefined, + `source archive contains developer, ignored fixture, or sensitive state: ${forbiddenArchiveEntry}`, + ); process.stdout.write(`release artifact build passed (${release.tag}; local dist)\n`); process.exit(0); } diff --git a/docs-site/src/components/AtlasJourney.astro b/docs-site/src/components/AtlasJourney.astro index f35ca6e..0206c32 100644 --- a/docs-site/src/components/AtlasJourney.astro +++ b/docs-site/src/components/AtlasJourney.astro @@ -14,19 +14,19 @@ const scenes = [ id: "explore", label: "Explore", title: "Begin with a sky that is already readable.", - body: "All 199 systems are present on the first frame. Related systems and proven routes share a restrained colour corridor; bridges stay neutral, possible routes stay dashed, and First Flight offers a short path from Home.", + body: "All 203 systems are present on the first frame. Related systems and proven routes share a restrained colour corridor; bridges stay neutral, possible routes stay dashed, and First Flight offers a short path from Home.", image: "galaxy.png", focus: "center", - alt: "Codemble v0.19.2 galaxy with 199 represented systems across seven languages, community-tinted route corridors, ranked labels, 35 charted systems, and an unlit Home resolved to codemble.cli.", + alt: "Codemble v0.20.0 galaxy with 203 represented systems across seven languages, community-tinted route corridors, ranked labels, 35 charted systems, and an unlit Home resolved to codemble.cli.", }, { id: "map", label: "Map", title: "Flatten the same evidence into a reading path.", - body: "Architecture lays modules out by proven imports from Home. Workflow follows certain calls from the entrypoint. Modules with no proven route are counted and folded—not erased.", + body: "Architecture lays modules out by proven imports from Home. Workflow follows certain calls from the entrypoint. Modules with no proven route remain in complete bottom rows rather than being guessed into the graph.", image: "map-architecture.png", focus: "center", - alt: "Codemble’s Expert Architecture map showing Home, connected server modules, real lines of code, and a counted shelf for 159 modules with no proven import route.", + alt: "Codemble’s Expert canvas Architecture map showing Home, connected server modules, real lines of code, and complete bottom rows for 163 modules with no proven import route.", }, { id: "system", @@ -80,7 +80,7 @@ const scenes = [ )) } -

Codemble v0.19.2same project · same Home

+

Codemble v0.20.0same project · same Home

    diff --git a/docs-site/src/content/docs/architecture.md b/docs-site/src/content/docs/architecture.md index 6aee822..d840274 100644 --- a/docs-site/src/content/docs/architecture.md +++ b/docs-site/src/content/docs/architecture.md @@ -3,7 +3,7 @@ title: Architecture description: The adapter seam, the render-ready graph, and why the LLM only narrates. --- -:::note[v0.19.2 architecture] +:::note[v0.20.0 architecture] This page describes the seven-language packaged app and current source tree. ::: @@ -126,7 +126,7 @@ regions when graph JSON is requested; stale signatures simply remain dim. ## Stack Python 3.11+ · FastAPI · tree-sitter · Vite + React · `3d-force-graph` (three.js) · -plain SVG for the 2D map · Anthropic / OpenAI (bring your own key) · local JSON +Canvas 2D for the complete flat map · Anthropic / OpenAI (bring your own key) · local JSON persistence. Both the 3D galaxy coordinates and the 2D map layouts are computed in diff --git a/docs-site/src/content/docs/checks-and-lighting.md b/docs-site/src/content/docs/checks-and-lighting.md index 7641916..9fd509e 100644 --- a/docs-site/src/content/docs/checks-and-lighting.md +++ b/docs-site/src/content/docs/checks-and-lighting.md @@ -4,7 +4,7 @@ description: The game loop — prove you understand a region, light it up foreve --- :::note[Version] -The check-and-light loop, explorer trail, and capture below match v0.19.2. +The check-and-light loop, explorer trail, and capture below match v0.20.0. ::: ## Why checks exist diff --git a/docs-site/src/content/docs/download.md b/docs-site/src/content/docs/download.md index 52cdcad..3299aba 100644 --- a/docs-site/src/content/docs/download.md +++ b/docs-site/src/content/docs/download.md @@ -1,10 +1,10 @@ --- title: Download -description: Run Codemble, download its verified release artifacts, or build v0.19.2 from source. +description: Run Codemble, download its verified release artifacts, or build v0.20.0 from source. --- The one-command route, direct downloads, current source, and screenshots all -match **Codemble v0.19.2**. +match **Codemble v0.20.0**.
    -

    uv fetches the published v0.19.2 wheel in an isolated environment, opens Codemble, and leaves your system Python alone.

    -
    uvx --from codemble==0.19.2 codemble
    -

    Pass a folder to skip the picker: uvx --from codemble==0.19.2 codemble ./your-project. Use uvx codemble when you intentionally want the newest PyPI release.

    +

    uv fetches the published v0.20.0 wheel in an isolated environment, opens Codemble, and leaves your system Python alone.

    +
    uvx --from codemble==0.20.0 codemble
    +

    Pass a folder to skip the picker: uvx --from codemble==0.20.0 codemble ./your-project. Use uvx codemble when you intentionally want the newest PyPI release.

    -

    Direct

    Download v0.19.2

    +

    Direct

    Download v0.20.0

    GitHub mirrors the exact wheel and source archive published to PyPI, beside one plain-text SHA256 ledger.

    @@ -37,11 +37,11 @@ match **Codemble v0.19.2**.
    Try this exact release
    -
    uvx --from codemble==0.19.2 codembleOne isolated command, pinned to the screens on this site.
    +
    uvx --from codemble==0.20.0 codembleOne isolated command, pinned to the screens on this site.
    Keep the command
    -
    pipx install codemble==0.19.2An isolated app environment with codemble on your path.
    +
    pipx install codemble==0.20.0An isolated app environment with codemble on your path.
    Install from a local file
    @@ -59,31 +59,31 @@ The same SHA256 digests are published in `SHA256SUMS.txt`, the GitHub asset ledger, and PyPI metadata: ```text -8d6183d2d7166bb6f84490026f9057e12acbbe6361ebbcc43727bc8f5e7492a6 codemble-0.19.2-py3-none-any.whl -b570ecc329e4aca79cbf34bca60674130b71470e02c049320e5021f533e50ad7 codemble-0.19.2.tar.gz +15e42dee6b06d4569a969767cfae7556b2b642a15092e6801c924fcda692cf61 codemble-0.20.0-py3-none-any.whl +466e5f5bdad022730abd4b0c4228e1079bf88645ad3700af6cdc2e5ebc78b008 codemble-0.20.0.tar.gz ``` To check the wheel on macOS or Linux: ```bash printf '%s %s\n' \ - 8d6183d2d7166bb6f84490026f9057e12acbbe6361ebbcc43727bc8f5e7492a6 \ - codemble-0.19.2-py3-none-any.whl | shasum -a 256 -c - + 15e42dee6b06d4569a969767cfae7556b2b642a15092e6801c924fcda692cf61 \ + codemble-0.20.0-py3-none-any.whl | shasum -a 256 -c - ``` Then install it into an isolated app environment: ```bash -pipx install ./codemble-0.19.2-py3-none-any.whl +pipx install ./codemble-0.20.0-py3-none-any.whl codemble --version ``` -The expected version is `0.19.2`. +The expected version is `0.20.0`. ## Build from source ```bash -git clone --branch v0.19.2 --depth 1 https://github.com/udhawan97/Codemble.git +git clone --branch v0.20.0 --depth 1 https://github.com/udhawan97/Codemble.git cd Codemble python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate @@ -91,7 +91,7 @@ pip install -e . codemble ``` -This checks out and runs the same v0.19.2 app with an editable Python environment. Continue +This checks out and runs the same v0.20.0 app with an editable Python environment. Continue with [Installation](/Codemble/installation/) for provider setup and failure boundaries, or [Build from source](/Codemble/build-from-source/) for the full contributor gates. diff --git a/docs-site/src/content/docs/early-testing.md b/docs-site/src/content/docs/early-testing.md index 47ff7dd..e5a9d06 100644 --- a/docs-site/src/content/docs/early-testing.md +++ b/docs-site/src/content/docs/early-testing.md @@ -4,29 +4,29 @@ description: A ten-minute, privacy-safe first-run test for Codemble. --- Codemble needs learners who built a small project with AI and want to understand -it better. v0.19.2 reads Python, JavaScript, TypeScript, Go, Java, Rust, C#, or +it better. v0.20.0 reads Python, JavaScript, TypeScript, Go, Java, Rust, C#, or any mix. Success is simple: without maintainer help, light one system and confirm it stays lit after restart. ## Install -The command below runs packaged v0.19.2. An editable checkout produces the same +The command below runs packaged v0.20.0. An editable checkout produces the same app; state which [run or download route](/Codemble/download/) you used. Install [uv](https://docs.astral.sh/uv/) once, then run Codemble through it: ```bash brew install uv # once per machine; or the installer at docs.astral.sh/uv -uvx --from codemble==0.19.2 codemble +uvx --from codemble==0.20.0 codemble ``` -Prefer a permanent install? `pipx install codemble==0.19.2`, then run `codemble` — no +Prefer a permanent install? `pipx install codemble==0.20.0`, then run `codemble` — no uv needed. Codemble opens your browser to an in-app picker — pick your project folder there, or pass one directly: -`uvx --from codemble==0.19.2 codemble ./path-to-your-project`. +`uvx --from codemble==0.20.0 codemble ./path-to-your-project`. An API key is optional. Do not read the rest of the docs before the first run; the product should teach the loop itself. diff --git a/docs-site/src/content/docs/installation.md b/docs-site/src/content/docs/installation.md index 4c7f3ea..fbe38bb 100644 --- a/docs-site/src/content/docs/installation.md +++ b/docs-site/src/content/docs/installation.md @@ -1,18 +1,18 @@ --- title: Installation -description: Run Codemble v0.19.2 and configure optional narration. +description: Run Codemble v0.20.0 and configure optional narration. --- :::note[One current version] The packaged app, current source, screenshots, and these instructions all match -**v0.19.2**. [Download the wheel or source archive](/Codemble/download/) when +**v0.20.0**. [Download the wheel or source archive](/Codemble/download/) when you do not want the one-command route. ::: ## Requirements - **Python 3.11+** -- A modern browser. WebGL draws the 3D galaxy; the plain-SVG Map still works +- A modern browser. WebGL draws the 3D galaxy; the canvas Map still works without it - **[uv](https://docs.astral.sh/uv/)** for the recommended no-install run, or `pipx` for a permanent command @@ -36,14 +36,14 @@ local Ollama can narrate instead, with nothing leaving your machine.

    Open Codemble

    Pick a project in the browser. Nothing is added to your system Python.

    -
    uvx --from codemble==0.19.2 codemble
    +
    uvx --from codemble==0.20.0 codemble
Use uv's [official installation guide](https://docs.astral.sh/uv/getting-started/installation/) if you do not use Homebrew. Prefer a permanent install? Run `pipx install -codemble==0.19.2`, then `codemble`. Plain `pip install codemble==0.19.2` also works inside a +codemble==0.20.0`, then `codemble`. Plain `pip install codemble==0.20.0` also works inside a virtual environment. The shorter `uvx codemble` intentionally follows the newest PyPI release. The @@ -52,7 +52,7 @@ version-pinned command above stays aligned with this guide and its screenshots. Codemble opens an in-app picker. To skip it, pass a folder: ```bash -uvx --from codemble==0.19.2 codemble ./your-project +uvx --from codemble==0.20.0 codemble ./your-project ``` The package contains the production web app, so Node.js is not required. @@ -60,7 +60,7 @@ The package contains the production web app, so Node.js is not required. ## Build an editable checkout ```bash -git clone --branch v0.19.2 --depth 1 https://github.com/udhawan97/Codemble.git +git clone --branch v0.20.0 --depth 1 https://github.com/udhawan97/Codemble.git cd Codemble python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate @@ -68,7 +68,7 @@ pip install -e . codemble ``` -Run `codemble --version` to confirm v0.19.2. Contributors should follow the +Run `codemble --version` to confirm v0.20.0. Contributors should follow the [full source build and verification guide](/Codemble/build-from-source/). ## Bring your own key—or do not @@ -115,7 +115,7 @@ parser-owned. ## Limits that fail honestly -- Above roughly 1,000 supported source files, choose a subdirectory in the +- Above roughly 5,000 supported source files, choose a subdirectory in the picker or pass `--path ./project/src`. - When several entrypoints genuinely tie, choose Home in the app or pass a parser-ranked `--entrypoint NODE_ID`. diff --git a/docs-site/src/content/docs/introduction.md b/docs-site/src/content/docs/introduction.md index b892b90..02fd5e5 100644 --- a/docs-site/src/content/docs/introduction.md +++ b/docs-site/src/content/docs/introduction.md @@ -42,7 +42,7 @@ optional extra: bring your own Claude or OpenAI key, or run a local model through Ollama and send nothing anywhere. :::note[Status] -Codemble **v0.19.2** is published on PyPI and maps Python, JavaScript, +Codemble **v0.20.0** is published on PyPI and maps Python, JavaScript, TypeScript, Go, Java, Rust, C#, and mixed projects. Easy guides one cited journey step at a time; Expert enriches that same step with parser evidence. Impact, dependencies, and verification candidates are selected-feature facts, diff --git a/docs-site/src/content/docs/parser-scale.md b/docs-site/src/content/docs/parser-scale.md index 138acce..f9c551d 100644 --- a/docs-site/src/content/docs/parser-scale.md +++ b/docs-site/src/content/docs/parser-scale.md @@ -1,6 +1,6 @@ --- title: Parser evidence and scale -description: What Codemble caches, how semantic quality is measured, and why the public limit remains 1,000 files. +description: What Codemble caches, how semantic quality is measured, and how the complete Map proved the 5,000-file limit. --- Codemble now keeps one `ProjectParser` alive across explicit project release and @@ -79,9 +79,9 @@ certainty first, false roles/Home/journeys second, missing certain journey structure third, and bounded precision or coverage improvements last. No narrator or provider decides those facts. -## Why the public limit is still 1,000 +## Why the public limit is now 5,000 -The public cap can move to 5,000 only when the complete Map passes the same +The public cap moved to 5,000 only after the complete Map passed the same predeclared gate in Chromium and WebKit: ```bash @@ -90,23 +90,29 @@ CODEMBLE_PYTHON=python3.12 npm run check:large-project -- \ --output /tmp/codemble-browser-scale.json ``` -The 5,000-module backend passed a 4.956 s cold activation, a 1.666 s no-change -activation, and a 166,789,120-byte process RSS high-water mark reported by the -operating system. Chromium passed 5,000 boxes and 4,999 routes, 35,102 DOM -elements, 11.48 MB of resources, 4.161 s to usable, 29.1 ms Finder input p95, -0.960 s maximum event-loop lag, successful Map retry, zero page overflow at -320 px, and keyboard arrival at the final module. - -WebKit did not pass. While the complete 5,000-box Map committed, the first-run -**Skip** control did not become stable within the 5 s interaction budget. The -gate therefore exits non-zero and the ordinary picker remains capped at 1,000 -supported files. `--path` still allows an explicit larger scope, but the -cross-engine result is why that is not the default. - -The next scale milestone is a canvas-backed **complete Map** renderer followed -by the same two-engine gate. It may change delivery, not truth: every module -must remain represented, searchable, keyboard reachable, and recoverable. A -logical LOD that hides modules is not an acceptable shortcut. +The v0.20.0 renderer changes delivery, not truth: the source scene retains all +5,000 modules and 4,999 routes while the viewport draws only the intersecting +slice. Finder and End-key navigation both reach the final module directly; +recovery restores a visible complete Map; and the compact page keeps zero +horizontal overflow. The DOM no longer grows with the number of boxes, so the +same gate now passes in both Chromium and WebKit. The exact schema-4 receipt is +recorded with the release candidate and checks backend cold/no-change +activation, process RSS high-water mark, usable time, DOM and resource budgets, +event-loop lag, Finder input latency, canvas keyboard arrival, recovery, and +320 px geometry. + +The final macOS candidate receipt measured 8.476 s cold activation, 2.475 s +no-change activation, and a 170,541,056-byte process RSS high-water mark. +Chromium reached usable in 3.748 s with 99 DOM elements, six visible boxes, +38.1 ms canvas End-key arrival, 32.7 ms Finder input p95, 55.7 ms recovery, and +zero compact overflow. WebKit reached usable in 2.433 s with the same 99 DOM +elements, five visible boxes, 24.7 ms canvas arrival, 34.0 ms Finder input p95, +55.9 ms recovery, and zero compact overflow. Both retained all 5,000 boxes and +4,999 routes in the complete source scene. + +Above 5,000 supported files, the picker still asks for a smaller scope. That is +an explicit verified limit, not a claim of unbounded rendering. Logical LOD +that hides modules remains outside the correctness contract. ## Open-source inspiration and credit diff --git a/docs-site/src/content/docs/quickstart.md b/docs-site/src/content/docs/quickstart.md index 2c38f1f..d165ad3 100644 --- a/docs-site/src/content/docs/quickstart.md +++ b/docs-site/src/content/docs/quickstart.md @@ -3,7 +3,7 @@ title: Quickstart description: From one command to your first lit star system. --- -:::note[This quickstart matches v0.19.2] +:::note[This quickstart matches v0.20.0] The one-command package, current source, and screenshots all cover the same seven-language app. [Direct downloads are available too](/Codemble/download/#direct-artifacts). ::: @@ -22,12 +22,12 @@ No Homebrew? Use the official installer — `curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS and Linux, or `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` on Windows. If you would rather install Codemble permanently, `pipx install -codemble==0.19.2` needs no uv at all. +codemble==0.20.0` needs no uv at all. ## 2. Point it at your project ```bash -uvx --from codemble==0.19.2 codemble +uvx --from codemble==0.20.0 codemble ``` Codemble opens your browser to an in-app picker: browse your home folders or @@ -36,7 +36,7 @@ TypeScript, Go, Java, Rust, C#, and mixtures of those languages. To skip the picker, pass a path directly: ```bash -uvx --from codemble==0.19.2 codemble ./my-project +uvx --from codemble==0.20.0 codemble ./my-project ``` Codemble parses locally, chooses a free localhost port, and opens the galaxy. @@ -44,12 +44,12 @@ It keeps syntax-error files visible and labels unresolved calls instead of guessing. Use `--no-open` when you want to copy the printed URL yourself. Codemble reads supported source; it never runs your project or package scripts. -For a project above 1,000 supported source files, the picker offers the +For a project above 5,000 supported source files, the picker offers the busiest-first subdirectories as buttons and accepts a typed path, right in the UI. From the CLI, select the scope yourself: ```bash -uvx --from codemble==0.19.2 codemble --path ./my-project/src +uvx --from codemble==0.20.0 codemble --path ./my-project/src ``` ## 3. Find Home diff --git a/docs-site/src/content/docs/roadmap.md b/docs-site/src/content/docs/roadmap.md index 20eed91..efc9b8f 100644 --- a/docs-site/src/content/docs/roadmap.md +++ b/docs-site/src/content/docs/roadmap.md @@ -7,21 +7,21 @@ The authoritative roadmap lives in the repo's [CLAUDE.md](https://github.com/udhawan97/Codemble/blob/main/CLAUDE.md) and moves only when milestones actually complete. The shape: -## NOW — learner evidence and the next packaged stable +## NOW — learner evidence and the 5,000-file stable -Packaged v0.19.2 and current source map Python, JavaScript, TypeScript, Go, +Packaged v0.20.0 and current source map Python, JavaScript, TypeScript, Go, Java, Rust, C#, and mixed projects through one local parser-proven learning -loop. The current work is learner evidence and correctness reports. The +loop. v0.20.0 adds a complete canvas Map and moves the ordinary supported-file +cap to 5,000 after the full backend, Chromium, and WebKit gate passed. The +current work is learner evidence and correctness reports. The original learner-acceptance issue remains open and is not treated as passed by technical completion. -## NEXT — scale +## NEXT — read-only sharing design -A canvas-backed complete Map that keeps every module represented, searchable, -keyboard reachable, and recoverable. The public 1,000-file cap moves only after -the complete 5,000-module view passes the same backend, Chromium, and WebKit -budgets. The current evidence and failed WebKit gate are recorded in -[Parser evidence and scale](../parser-scale/). +Define the privacy, provenance, expiry, and deletion boundary for the planned +shareable read-only galaxy link before building its only cloud touch. The +completed scale evidence is recorded in [Parser evidence and scale](../parser-scale/). ## LATER — reach diff --git a/docs-site/src/content/docs/star-chart.md b/docs-site/src/content/docs/star-chart.md index 7df609f..41137b6 100644 --- a/docs-site/src/content/docs/star-chart.md +++ b/docs-site/src/content/docs/star-chart.md @@ -4,7 +4,7 @@ description: Language concepts you've met and mastered, tracked across your gala --- :::note[Version] -The star chart and its separate **Systems explored** row match v0.19.2. +The star chart and its separate **Systems explored** row match v0.20.0. ::: ## Concepts, not just code diff --git a/docs-site/src/content/docs/study-panel.md b/docs-site/src/content/docs/study-panel.md index cb2830e..cb80d72 100644 --- a/docs-site/src/content/docs/study-panel.md +++ b/docs-site/src/content/docs/study-panel.md @@ -3,7 +3,7 @@ title: Study a feature description: Follow one parser-proven journey in Easy or Expert mode, then inspect source, impact, connections, and checks. --- -:::note[v0.19.2 product guide] +:::note[v0.20.0 product guide] Study now begins with one shared, parser-owned feature journey. Easy and Expert change the teaching depth, never the evidence or your place in the route. ::: @@ -68,7 +68,7 @@ record. Charting, checks, and understood state retain their existing meanings.
The current Expert Study panel scrolled to its integrated Impact lists over the Architecture map.
-
Codemble v0.19.2 · one journey, with Impact and Connections integrated once.
+
Codemble v0.20.0 · one journey, with Impact and Connections integrated once.
The journey's details disclosure contains graph facts about the **selected diff --git a/docs-site/src/content/docs/the-galaxy.md b/docs-site/src/content/docs/the-galaxy.md index d1d0864..7d0006d 100644 --- a/docs-site/src/content/docs/the-galaxy.md +++ b/docs-site/src/content/docs/the-galaxy.md @@ -3,7 +3,7 @@ title: The galaxy description: How your code becomes a sky — and why the camera stays on rails. --- -:::note[v0.19.2 product guide] +:::note[v0.20.0 product guide] This page matches the packaged app, current source, and fresh product captures. [Choose a run or download route](/Codemble/download/). ::: @@ -12,7 +12,7 @@ This page matches the packaged app, current source, and fresh product captures.
- Codemble v0.19.2 at galaxy level: 199 star systems across seven languages, with parser-owned names, community colour shared subtly by related proven routes, ranked labels, 35 charted systems, an unlit Home resolved to codemble.cli, and six unreadable test fixtures called out. + Codemble v0.20.0 at galaxy level: 203 star systems across seven languages, with parser-owned names, community colour shared subtly by related proven routes, ranked labels, 35 charted systems, an unlit Home resolved to codemble.cli, and six unreadable test fixtures called out.
Full-size product screen · drag, swipe, or use arrow keys to inspect the interface.
@@ -209,11 +209,10 @@ two tabs: *Architecture* lays your modules out by folder and by how far they sit from Home along import routes, and *Workflow* walks the call tree from your entrypoint. Both layouts are computed by the same parser-backed graph the galaxy draws — the map cannot show you a relationship the galaxy does not have. Modules -with no import route from Home are never guessed into position: when more than a -handful exist they fold into a counted shelf — the note says exactly how many -and **Show them** draws every one — so a project whose test fixtures outnumber -its source keeps a readable connected core without hiding a single module from -the count. Clicking anything in either layer opens the same study panel, and a +with no import route from Home are never guessed into position: they remain in +complete bottom rows, and the note says exactly how many. Every module stays in +the canvas scene, Finder, and keyboard order even when test fixtures outnumber +the connected source. Clicking anything in either layer opens the same study panel, and a lit system is amber in both. Architecture boxes carry their community's colour family and a language stripe, and routes are drawn in their own ink so a connection never disappears beside a box border. diff --git a/docs-site/src/pages/index.astro b/docs-site/src/pages/index.astro index b7a5a0a..792f73e 100644 --- a/docs-site/src/pages/index.astro +++ b/docs-site/src/pages/index.astro @@ -4,7 +4,7 @@ * * Macrostructure: Hallmark Workbench. Real product captures lead; the Formal * Edo system supplies material and typography without turning the site into a - * decorative atlas. The current product and packaged release are both v0.19.2. + * decorative atlas. The current product and packaged release are both v0.20.0. */ import AtlasJourney from "../components/AtlasJourney.astro"; import Search from "../components/Search.astro"; @@ -184,7 +184,7 @@ const contract = [ > Codemble v0.19.2 showing 199 represented systems across Python, JavaScript, TypeScript, Go, Java, Rust, and C#, with community-tinted route corridors, ranked labels, 35 systems charted, six unreadable test fixtures called out, Home resolved to codemble.cli and still unlit.
Current app · Easy galaxy - 199 systems · seven languages · local only + 203 systems · seven languages · local only
diff --git a/docs/releases/v0.20.0.md b/docs/releases/v0.20.0.md new file mode 100644 index 0000000..069fbe6 --- /dev/null +++ b/docs/releases/v0.20.0.md @@ -0,0 +1,50 @@ +# Codemble v0.20.0 — a complete Map at 5,000 files + +v0.20.0 replaces the Map's per-item SVG DOM with a complete +viewport-rendered canvas and raises the ordinary supported-project cap from +1,000 to 5,000 files after the same predeclared backend, Chromium, and WebKit +gate passed. + +## Highlights + +- The default all-language Architecture and Workflow maps preserve every + parser-owned module, route, coordinate, certainty label, language stripe, + and progress state while drawing only the visible viewport slice. An + explicit language focus preserves every item in that named projection. +- One focusable Map surface provides directional navigation, Home/End jumps, + Enter/Space activation, and a full-label readout without creating thousands + of tab stops. +- Empty space still drags to pan; pointer activation remains limited to boxes + and rows defined by backend-owned coordinates and renderer-owned label bounds. +- Finder still reaches the final module directly, recovery returns the complete + Map, and the 320 px layout has no horizontal overflow. + +## Correctness boundaries + +- Canvas changes delivery only. Parser evidence still decides structure, Home, + routes, coordinates, roles, citations, impact, and check answers. +- Every module in the default all-language map remains represented, searchable, + keyboard reachable, and recoverable; an explicit language focus retains the + complete named projection. No logical level of detail hides graph truth. +- Possible routes remain visibly possible; amber still means passed checks and + nothing else. +- Above 5,000 supported source files, Codemble still asks the learner to choose + a smaller subdirectory. This release does not claim unbounded scale. +- No account, cloud ingestion, telemetry, watcher, disk parser cache, new + provider, or progression system was added. + +## Verification before tagging + +- 532 Python tests, Ruff, and the seven-language plus mixed semantic oracle +- complete frontend contract/build, shell-space, Escape, user-flow, and panel + reach gates +- schema-4 complete 5,000-module backend, Chromium, and WebKit gate, including + direct canvas keyboard arrival, recovery, and 320 px geometry +- docs typecheck/build plus the public-site browser matrix +- reproducible wheel and source archive matched against the committed release + manifest + +The separate human learner-acceptance issue remains open. This release does not +claim that unaided external testing has completed, and native Safari visual +acceptance was unavailable while the test Mac was locked; WebKit engine +acceptance passed independently. diff --git a/pyproject.toml b/pyproject.toml index 9ad0317..ab5fc9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "codemble" -version = "0.19.2" +version = "0.20.0" description = "A learning game that turns the code AI wrote for you into a galaxy you light up by understanding it." readme = "README.md" license = "Apache-2.0" @@ -93,6 +93,16 @@ exclude = [ ".hallmark", ".claude", ".superpowers", + # Developer-owned backup/runtime markers can exist in a working tree but + # must never become published source-archive payload. + ".git-backup-remote", + ".last-git-backup-ts", + ".playwright-cli", + # These files are deliberately generated/ignored by the parser fixture. Hatch + # otherwise includes them when a developer builds from an exercised worktree, + # making that sdist differ from the same commit built in a clean checkout. + "tests/fixtures/sampleproj/generated", + "tests/fixtures/sampleproj/ignored.py", # The public site has its own Pages artifact. Keeping it out of the Python # source archive also lets release.json record the archive's own digest # without creating a self-referential build. diff --git a/scripts/serve_large_project_gate.py b/scripts/serve_large_project_gate.py index c717329..9299c63 100644 --- a/scripts/serve_large_project_gate.py +++ b/scripts/serve_large_project_gate.py @@ -21,8 +21,9 @@ def main() -> int: arguments = _arguments() parser = ProjectParser() - # This process exists only for the disposable cross-engine gate. The - # product default remains 1,000 unless that gate passes every budget. + # Keep the disposable fixture size explicit even when the product default + # moves: a later gate must never silently inherit a larger public cap and + # claim it measured a size the command did not request. parser.scale_cap = arguments.max_files codemble_app = create_app( picker=PickerConfig(browse_root=arguments.browse_root.resolve()), diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 1b26fb9..45674db 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -67,10 +67,10 @@ def test_production_web_app_is_part_of_the_python_package() -> None: assert any((distribution / "assets").iterdir()) -def test_the_v1_scale_cap_is_one_thousand_supported_files() -> None: +def test_the_public_scale_cap_matches_the_complete_cross_engine_receipt() -> None: from codemble.adapters.project import ProjectParser - assert ProjectParser.scale_cap == 1000 + assert ProjectParser.scale_cap == 5000 def test_large_project_requires_an_explicit_or_interactive_scope( diff --git a/web/package-lock.json b/web/package-lock.json index b7e508d..a8164cf 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "codemble-web", - "version": "0.19.2", + "version": "0.20.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codemble-web", - "version": "0.19.2", + "version": "0.20.0", "dependencies": { "@fontsource/jetbrains-mono": "5.2.8", "@fontsource/shippori-mincho": "^5.3.0", diff --git a/web/package.json b/web/package.json index 3545746..3080c9d 100644 --- a/web/package.json +++ b/web/package.json @@ -1,14 +1,14 @@ { "name": "codemble-web", "private": true, - "version": "0.19.2", + "version": "0.20.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "capture:docs": "node scripts/capture_product_shots.mjs", "check:site": "node scripts/check_public_site.mjs", - "check": "node scripts/check_capture_safety.mjs && node scripts/check_learning_journey.mjs && node scripts/check_graph_data.mjs && node scripts/check_project_brief.mjs && node scripts/check_first_flight.mjs && node scripts/check_sky_palette.mjs && node scripts/check_system_orbits.mjs && node scripts/check_name_atlas.mjs && node scripts/check_project_mapping.mjs && node scripts/check_learner_projection.mjs && node scripts/check_learner_session.mjs && node scripts/check_map_viewport.mjs && node scripts/check_orbit_pointer_guard.mjs && node scripts/check_canvas_occlusion.mjs && node scripts/check_galaxy_runtime.mjs && node scripts/check_mode_gate_portal.mjs && node scripts/check_focus_handoffs.mjs && node scripts/check_interface_semantics.mjs && node scripts/check_galaxy_effects.mjs && node scripts/check_celestial_bodies.mjs && node scripts/check_possible_routes.mjs && node scripts/check_camera_framing.mjs && node scripts/check_galaxy_view.mjs && node scripts/check_escape_arbiter.mjs && vite build", + "check": "node scripts/check_capture_safety.mjs && node scripts/check_learning_journey.mjs && node scripts/check_graph_data.mjs && node scripts/check_project_brief.mjs && node scripts/check_first_flight.mjs && node scripts/check_sky_palette.mjs && node scripts/check_system_orbits.mjs && node scripts/check_name_atlas.mjs && node scripts/check_project_mapping.mjs && node scripts/check_learner_projection.mjs && node scripts/check_learner_session.mjs && node scripts/check_map_viewport.mjs && node scripts/check_map_canvas_renderer.mjs && node scripts/check_orbit_pointer_guard.mjs && node scripts/check_canvas_occlusion.mjs && node scripts/check_galaxy_runtime.mjs && node scripts/check_mode_gate_portal.mjs && node scripts/check_focus_handoffs.mjs && node scripts/check_interface_semantics.mjs && node scripts/check_galaxy_effects.mjs && node scripts/check_celestial_bodies.mjs && node scripts/check_possible_routes.mjs && node scripts/check_camera_framing.mjs && node scripts/check_galaxy_view.mjs && node scripts/check_escape_arbiter.mjs && vite build", "check:space": "node scripts/check_space_budget.mjs", "check:escape": "node scripts/check_escape_surfaces.mjs", "check:userflows": "node scripts/check_userflow_repairs.mjs", diff --git a/web/scripts/capture_product_shots.mjs b/web/scripts/capture_product_shots.mjs index 2bccd74..5eec313 100644 --- a/web/scripts/capture_product_shots.mjs +++ b/web/scripts/capture_product_shots.mjs @@ -110,8 +110,18 @@ async function capture(name) { } async function selectAppModule() { - const moduleButton = page.getByRole("button").filter({ hasText: appModulePath }); - await moduleButton.first().click(); + await page.keyboard.press("Meta+K"); + const finder = page.locator(".module-finder[open]"); + await finder.waitFor(); + const input = finder.getByRole("searchbox", { + name: "Find a module by name or path", + }); + await input.fill(appModulePath); + const option = finder.getByRole("option").first(); + await option.waitFor(); + await page.keyboard.press("Enter"); + await finder.waitFor({ state: "detached" }); + await page.locator(".orientation-copy--system").waitFor(); await page.waitForTimeout(500); } diff --git a/web/scripts/check_large_project.mjs b/web/scripts/check_large_project.mjs index 52cddac..104a939 100644 --- a/web/scripts/check_large_project.mjs +++ b/web/scripts/check_large_project.mjs @@ -28,10 +28,11 @@ const BUDGETS = Object.freeze({ coldActivationMs: 12_000, noChangeActivationMs: 2_500, serverPeakRssBytes: 512 * 1024 * 1024, - domElements: 40_000, + domElements: 2_000, resourceBytes: 16 * 1024 * 1024, usableMs: 5_000, inputP95Ms: 200, + canvasKeyboardMs: 200, eventLoopLagMs: 2_000, pageOverflowPx: 1, }); @@ -120,7 +121,7 @@ try { } const receipt = { - schema_version: 3, + schema_version: 4, status: gateError ? "fail" : "pass", fixture_kind: "deterministic-sparse-python-import-chain", files: FILES, @@ -202,29 +203,37 @@ async function checkCompleteMap(browser, engine, url) { const started = performance.now(); await page.goto(url, { waitUntil: "domcontentloaded" }); await settleAppOnMap(page); - await page.locator(".architecture-map").waitFor(); + await page.locator(".architecture-map-canvas canvas").waitFor(); await page.evaluate(() => document.fonts.ready); const usableMs = performance.now() - started; await page.waitForTimeout(250); - const measurements = await page.evaluate(() => ({ - domElements: document.getElementsByTagName("*").length, - boxes: document.querySelectorAll(".architecture-map__box").length, - edges: document.querySelectorAll(".architecture-map__edge").length, - maxEventLoopLagMs: Math.max(0, ...(window.__codembleLoopLag || [0])), - maxLongTaskMs: Math.max(0, ...(window.__codembleLongTasks || [0])), - pageOverflowPx: Math.max( - document.documentElement.scrollWidth - document.documentElement.clientWidth, - document.body.scrollWidth - document.body.clientWidth, - ), - performanceResourceBytes: [ - ...performance.getEntriesByType("navigation"), - ...performance.getEntriesByType("resource"), - ].reduce( - (total, entry) => total + (entry.encodedBodySize || entry.transferSize || 0), - 0, - ), - })); + const measurements = await page.evaluate(() => { + const surface = document.querySelector(".architecture-map-canvas"); + const canvas = surface?.querySelector("canvas"); + const scroller = surface?.closest(".map-scroll"); + return { + domElements: document.getElementsByTagName("*").length, + boxes: Number(surface?.dataset.itemCount || 0), + edges: Number(surface?.dataset.edgeCount || 0), + visibleBoxes: Number(canvas?.dataset.visibleItems || 0), + visibleEdges: Number(canvas?.dataset.visibleEdges || 0), + scrollExtentHeight: scroller?.scrollHeight || 0, + maxEventLoopLagMs: Math.max(0, ...(window.__codembleLoopLag || [0])), + maxLongTaskMs: Math.max(0, ...(window.__codembleLongTasks || [0])), + pageOverflowPx: Math.max( + document.documentElement.scrollWidth - document.documentElement.clientWidth, + document.body.scrollWidth - document.body.clientWidth, + ), + performanceResourceBytes: [ + ...performance.getEntriesByType("navigation"), + ...performance.getEntriesByType("resource"), + ].reduce( + (total, entry) => total + (entry.encodedBodySize || entry.transferSize || 0), + 0, + ), + }; + }); const resourceBytes = Math.max( responseBytes.reduce((total, value) => total + value, 0), measurements.performanceResourceBytes, @@ -232,6 +241,15 @@ async function checkCompleteMap(browser, engine, url) { assert.equal(problems.length, 0, `${engine}: ${problems.join("; ")}`); assert.equal(measurements.boxes, FILES, `${engine}: incomplete Map`); + assert.equal(measurements.edges, FILES - 1, `${engine}: incomplete route mesh`); + assert.ok( + measurements.visibleBoxes > 0 && measurements.visibleBoxes < FILES, + `${engine}: canvas delivery did not virtualize the complete Map`, + ); + assert.ok( + measurements.scrollExtentHeight > 100_000, + `${engine}: complete backend geometry did not reach the native scroll surface`, + ); assert.ok( measurements.domElements <= BUDGETS.domElements, `${engine}: ${measurements.domElements} DOM elements exceed ${BUDGETS.domElements}`, @@ -257,6 +275,11 @@ async function checkCompleteMap(browser, engine, url) { `${engine}: desktop page overflow ${measurements.pageOverflowPx}px`, ); + const canvasKeyboardMs = await reachFinalCanvasModule(page, engine); + assert.ok( + canvasKeyboardMs <= BUDGETS.canvasKeyboardMs, + `${engine}: canvas End-key arrival ${canvasKeyboardMs.toFixed(1)}ms exceeds ${BUDGETS.canvasKeyboardMs}ms`, + ); const inputP95Ms = await findFinalModuleByKeyboard(page, engine); assert.ok( inputP95Ms <= BUDGETS.inputP95Ms, @@ -268,6 +291,10 @@ async function checkCompleteMap(browser, engine, url) { dom_elements: measurements.domElements, architecture_boxes: measurements.boxes, architecture_edges: measurements.edges, + visible_architecture_boxes: measurements.visibleBoxes, + visible_architecture_edges: measurements.visibleEdges, + map_scroll_extent_height: measurements.scrollExtentHeight, + canvas_keyboard_ms: round(canvasKeyboardMs), resource_bytes: resourceBytes, input_p95_ms: round(inputP95Ms), max_event_loop_lag_ms: round(measurements.maxEventLoopLagMs), @@ -293,16 +320,27 @@ async function checkRecoveryAndNarrowLayout(browser, engine, url) { await page.unroute("**/api/map"); const started = performance.now(); await page.getByRole("button", { name: "Try again", exact: true }).click(); - await page.locator(".architecture-map").waitFor(); + await page.locator(".architecture-map-canvas canvas").waitFor(); + await page.waitForFunction( + () => + Number( + document.querySelector(".architecture-map-canvas canvas")?.dataset + .visibleItems || 0, + ) > 0, + undefined, + { timeout: BUDGETS.usableMs }, + ); const recoveryMs = performance.now() - started; const narrow = await page.evaluate(() => ({ - boxes: document.querySelectorAll(".architecture-map__box").length, + boxes: Number(document.querySelector(".architecture-map-canvas")?.dataset.itemCount || 0), + visibleBoxes: Number(document.querySelector(".architecture-map-canvas canvas")?.dataset.visibleItems || 0), pageOverflowPx: Math.max( document.documentElement.scrollWidth - document.documentElement.clientWidth, document.body.scrollWidth - document.body.clientWidth, ), })); assert.equal(narrow.boxes, FILES, `${engine}: recovery returned an incomplete Map`); + assert.ok(narrow.visibleBoxes > 0, `${engine}: recovery left the canvas blank`); assert.ok( recoveryMs <= BUDGETS.usableMs, `${engine}: Map recovery took ${recoveryMs.toFixed(1)}ms`, @@ -320,6 +358,52 @@ async function checkRecoveryAndNarrowLayout(browser, engine, url) { } } +async function reachFinalCanvasModule(page, engine) { + const surface = page.locator(".architecture-map-canvas"); + await surface.focus(); + await surface.evaluate((node) => { + window.__codembleCanvasKeys = []; + node.addEventListener("keydown", (event) => { + window.__codembleCanvasKeys.push(event.key); + }); + }); + const initialDescendant = await surface.getAttribute("aria-activedescendant"); + const initialLabel = await surface.locator(".map-canvas-active-option").innerText(); + const started = performance.now(); + await surface.press("End"); + await page.evaluate( + () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))), + ); + const active = surface.locator(".map-canvas-active-option"); + const diagnostic = await surface.evaluate((node) => ({ + activeDescendant: node.getAttribute("aria-activedescendant"), + activeElementClass: document.activeElement?.className, + keys: window.__codembleCanvasKeys, + scrollTop: node.closest(".map-scroll")?.scrollTop, + })); + assert.notEqual( + await active.innerText(), + initialLabel, + `${engine}: End did not reach the final canvas module (${JSON.stringify(diagnostic)})`, + ); + const accessibility = await active.evaluate((option) => ({ + id: option.id, + position: option.getAttribute("aria-posinset"), + size: option.getAttribute("aria-setsize"), + })); + assert.notEqual( + accessibility.id, + initialDescendant, + `${engine}: active-descendant identity did not move with the final module`, + ); + assert.deepEqual( + { position: accessibility.position, size: accessibility.size }, + { position: String(FILES), size: String(FILES) }, + `${engine}: active canvas option does not announce its position in the complete set`, + ); + return performance.now() - started; +} + async function findFinalModuleByKeyboard(page, engine) { await page.keyboard.press("Meta+K"); const finder = page.locator(".module-finder[open]"); diff --git a/web/scripts/check_map_canvas_renderer.mjs b/web/scripts/check_map_canvas_renderer.mjs new file mode 100644 index 0000000..50be630 --- /dev/null +++ b/web/scripts/check_map_canvas_renderer.mjs @@ -0,0 +1,220 @@ +import assert from "node:assert/strict"; + +import { + architectureKeyboardTarget, + fitBoxLabel, + hitArchitectureCanvas, + hitWorkflowCanvas, + prepareArchitectureCanvas, + prepareWorkflowCanvas, + visibleArchitectureCanvas, + visibleWorkflowCanvas, + workflowKeyboardTarget, + workflowRowMeta, +} from "../src/mapCanvasRenderer.js"; + +const architecture = { + boxes: Array.from({ length: 5_000 }, (_, index) => ({ + id: `module_${String(index).padStart(5, "0")}`, + label: `module_${String(index).padStart(5, "0")}`, + short_label: `module_${String(index).padStart(5, "0")}.py`, + language: "python", + x: 24, + y: index * 120, + width: 160, + height: 56, + loc: 4, + node_count: 2, + understood: false, + home: index === 0, + reachable: index < 4_988, + partial: false, + })), + edges: Array.from({ length: 4_999 }, (_, index) => ({ + src: `module_${String(index).padStart(5, "0")}`, + dst: `module_${String(index + 1).padStart(5, "0")}`, + certain: true, + weight: 1, + cycle: false, + points: [ + [104, index * 120 + 56], + [104, (index + 1) * 120], + ], + })), +}; + +const scene = prepareArchitectureCanvas(architecture); +assert.equal(scene.boxes.length, 5_000, "the canvas scene retains every module"); +assert.equal(scene.edges.length, 4_999, "the canvas scene retains every import route"); + +const viewport = { + width: 960, + height: 640, + scrollLeft: 0, + scrollTop: 0, + offsetX: 0, + scale: 1, +}; +const firstFrame = visibleArchitectureCanvas(scene, viewport); +assert.ok( + firstFrame.boxes.length < 10, + `viewport delivery draws a bounded slice, got ${firstFrame.boxes.length}`, +); +assert.equal( + scene.boxes.length, + 5_000, + "culling changes delivery only; it cannot remove parser-owned modules", +); + +assert.equal( + architectureKeyboardTarget(scene, scene.boxes[0].id, "End")?.id, + "module_04999", + "End reaches the final module without 4,999 Tab presses", +); +assert.equal( + architectureKeyboardTarget(scene, "module_02499", "ArrowDown")?.id, + "module_02500", + "arrow navigation follows backend row order", +); +assert.equal( + hitArchitectureCanvas(scene, { x: 56, y: 32 })?.id, + "module_00000", + "pointer hit-testing uses the backend box geometry plus the declared view padding", +); +assert.equal( + hitArchitectureCanvas(scene, { x: 400, y: 32 }), + null, + "empty drawing space remains available to drag-to-pan", +); + +assert.equal( + scene.boxes.filter((box) => !box.reachable).length, + 12, + "more than eight modules without a Home route remain in the complete scene", +); + +assert.equal( + fitBoxLabel("server/runtime.py", 160), + "server/runtime.py", + "a path tail that fits stays whole", +); +assert.match( + fitBoxLabel("a-very-long-module-name-that-cannot-fit.py", 160), + /…$/, + "a shortened identifier remains visibly shortened", +); + +const workflow = { + nodes: [ + { + id: "app", + label: "app", + parent: null, + relation: "root", + certain: true, + cut: null, + order: 0, + x: 0, + y: 0, + region: "app", + file: "app.py", + lineno: 1, + understood: false, + partial: false, + }, + { + id: "app.main", + label: "main", + parent: "app", + relation: "defines", + certain: true, + cut: null, + order: 1, + x: 28, + y: 34, + region: "app", + file: "app.py", + lineno: 3, + understood: true, + partial: false, + }, + { + id: "app.helper", + label: "helper", + parent: "app.main", + relation: "calls", + certain: false, + cut: "repeat", + order: 2, + x: 56, + y: 68, + region: "app", + file: "app.py", + lineno: 8, + understood: false, + partial: true, + }, + ], +}; +const workflowScene = prepareWorkflowCanvas(workflow); +assert.equal(workflowScene.edges.length, 2, "the renderer indexes parents in one pass"); +assert.equal( + workflowKeyboardTarget(workflowScene, 1, "ArrowRight")?.order, + 2, + "Right enters the first child", +); +assert.equal( + workflowKeyboardTarget(workflowScene, 2, "ArrowLeft")?.order, + 1, + "Left returns to the nearest earlier parent occurrence", +); +assert.equal( + workflowKeyboardTarget(workflowScene, 0, "End")?.order, + 2, + "End reaches the final workflow row", +); +assert.equal( + hitWorkflowCanvas(workflowScene, { x: 80, y: 90 })?.order, + 2, + "workflow pointer hit-testing uses backend row coordinates", +); +assert.equal( + hitWorkflowCanvas(workflowScene, { x: 900, y: 90 }), + null, + "blank space to the right of a workflow label remains available to drag-to-pan", +); +assert.match( + workflowRowMeta(workflow.nodes[2], "easy"), + /possible call — shown above — could not be read/, + "uncertainty, repeat cuts, and partial source remain explicit", +); +assert.equal( + visibleWorkflowCanvas(workflowScene, viewport).rows.length, + 3, + "small workflow views remain complete", +); + +const sparseWorkflow = { + nodes: [ + { ...workflow.nodes[0], order: 100, y: 3_400 }, + { ...workflow.nodes[1], order: 102, y: 3_468 }, + ], +}; +const sparseScene = prepareWorkflowCanvas(sparseWorkflow); +const sparseViewport = { + ...viewport, + height: 120, + scrollTop: 3_380, +}; +assert.equal( + visibleWorkflowCanvas(sparseScene, sparseViewport).rows.length, + 2, + "focused workflow culling follows emitted y coordinates rather than sparse order values", +); +assert.equal( + workflowKeyboardTarget(sparseScene, 100, "ArrowDown")?.order, + 102, + "focused workflow keyboard navigation follows scene position across sparse orders", +); + +console.log("canvas Map renderer contracts passed"); diff --git a/web/scripts/check_panel_reach.mjs b/web/scripts/check_panel_reach.mjs index f89290d..5b91575 100644 --- a/web/scripts/check_panel_reach.mjs +++ b/web/scripts/check_panel_reach.mjs @@ -425,9 +425,10 @@ async function settleFirstRun(page, register) { } async function descendToRegion(page) { - const box = page.locator("[role='button'][aria-label*='structure']").first(); - if ((await box.count()) === 0) return false; - await box.click({ timeout: 8000 }).catch(() => {}); + const map = page.locator(".architecture-map-canvas").first(); + if ((await map.count()) === 0) return false; + await map.focus(); + await page.keyboard.press("Enter"); await page.waitForTimeout(700); // Confirm the level from the breadcrumb, not from the "Back to map" action. // At compact widths that action lives inside the Menu disclosure, which is diff --git a/web/scripts/check_space_budget.mjs b/web/scripts/check_space_budget.mjs index 1c548fa..cc4a767 100644 --- a/web/scripts/check_space_budget.mjs +++ b/web/scripts/check_space_budget.mjs @@ -524,9 +524,10 @@ async function settleFirstRun(page, register, { home = false } = {}) { */ async function descend(page, level) { if (level === "region") { - const box = page.locator("[role='button'][aria-label*='structure']").first(); - if ((await box.count()) === 0) return false; - await box.click({ timeout: 8000 }).catch(() => {}); + const map = page.locator(".architecture-map-canvas").first(); + if ((await map.count()) === 0) return false; + await map.focus(); + await page.keyboard.press("Enter"); } else { const read = page.getByRole("button", { name: /read the source/i }).first(); if ((await read.count()) === 0) return false; diff --git a/web/scripts/check_userflow_repairs.mjs b/web/scripts/check_userflow_repairs.mjs index 58f21e9..8dab841 100644 --- a/web/scripts/check_userflow_repairs.mjs +++ b/web/scripts/check_userflow_repairs.mjs @@ -43,6 +43,7 @@ try { await checkHomeGeometry(browser, engine, project.url); await checkCompactRailEscape(browser, engine, project.url); await checkCompactQuizVisibility(browser, engine, project.url); + await checkCanvasMapInteraction(browser, engine, project.url); await checkGuidanceFocus(browser, engine, project.url); await checkPickerRecovery(browser, engine, picker.url); } finally { @@ -54,6 +55,173 @@ try { for (const dataRoot of dataRoots) rmSync(dataRoot, { force: true, recursive: true }); } +async function checkCanvasMapInteraction(browser, engine, url) { + const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); + page.setDefaultTimeout(15_000); + try { + await page.goto(url, { waitUntil: "networkidle" }); + await settleApp(page); + const mapTruth = await page.evaluate(async () => { + const [mapResponse, graphResponse] = await Promise.all([ + fetch("/api/map"), + fetch("/api/graph"), + ]); + if (!mapResponse.ok) throw new Error(`map truth returned ${mapResponse.status}`); + if (!graphResponse.ok) throw new Error(`graph truth returned ${graphResponse.status}`); + const map = await mapResponse.json(); + const graph = await graphResponse.json(); + const unreachableTarget = map.architecture.unreachable.at(-1); + return { + boxCount: map.architecture.boxes.length, + unreachableCount: map.architecture.unreachable.length, + unreachableTarget, + unreachableFile: + graph.nodes.find((node) => node.region === unreachableTarget)?.file ?? unreachableTarget, + }; + }); + const architecture = page.locator(".architecture-map-canvas"); + assert.ok( + mapTruth.unreachableCount > 8, + `${engine}: complete-default fixture no longer has more than eight unreachable modules`, + ); + assert.equal( + Number(await architecture.getAttribute("data-item-count")), + mapTruth.boxCount, + `${engine}: default Architecture scene folded parser-owned modules`, + ); + await architecture.focus(); + await architecture.press("Home"); + const allLanguageActive = await architecture.locator(".map-canvas-active-option").evaluate( + (option) => ({ id: option.id, position: option.getAttribute("aria-posinset"), text: option.textContent }), + ); + await architecture.press("Meta+K"); + const finder = page.locator(".module-finder[open]"); + await finder.waitFor(); + const search = finder.getByRole("searchbox", { name: "Find a module by name or path" }); + await search.fill(mapTruth.unreachableFile); + await finder.getByRole("option").first().waitFor(); + await search.press("Enter"); + await finder.waitFor({ state: "detached" }); + assert.ok( + (await page.locator(".location [aria-current='page']").innerText()).includes(mapTruth.unreachableTarget), + `${engine}: Finder did not reach an unfolded-default unreachable module`, + ); + const javascriptFocus = page.getByRole("button", { name: /^Focus JavaScript:/ }); + await javascriptFocus.click(); + await page.waitForFunction( + () => document.querySelector(".language-focus button[aria-pressed='true']")?.getAttribute("aria-label")?.startsWith("Focus JavaScript:"), + ); + const focusedArchitecture = page.locator(".architecture-map-canvas"); + await focusedArchitecture.focus(); + await focusedArchitecture.press("Home"); + const focusedLanguageActive = await focusedArchitecture.locator(".map-canvas-active-option").evaluate( + (option) => ({ id: option.id, position: option.getAttribute("aria-posinset"), text: option.textContent }), + ); + assert.equal(allLanguageActive.position, "1", `${engine}: all-language Home did not select position 1`); + assert.equal(focusedLanguageActive.position, "1", `${engine}: focused language Home did not select position 1`); + assert.notEqual( + focusedLanguageActive.text, + allLanguageActive.text, + `${engine}: language projection did not replace the first active item fixture`, + ); + assert.notEqual( + focusedLanguageActive.id, + allLanguageActive.id, + `${engine}: active-descendant identity did not change for a same-position projection swap`, + ); + const pythonFocus = page.getByRole("button", { name: /^Focus Python:/ }); + await pythonFocus.click(); + await page.waitForFunction( + () => document.querySelector(".language-focus button[aria-pressed='true']")?.getAttribute("aria-label")?.startsWith("Focus Python:"), + ); + await page.getByRole("button", { name: "What runs first", exact: true }).click(); + const surface = page.locator(".workflow-map-canvas"); + await surface.waitFor(); + await surface.focus(); + await surface.press("End"); + await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(resolve))); + const focusedPosition = await surface.locator(".map-canvas-active-option").evaluate((option) => ({ + position: option.getAttribute("aria-posinset"), + size: option.getAttribute("aria-setsize"), + })); + assert.equal( + focusedPosition.position, + focusedPosition.size, + `${engine}: focused Workflow keyboard did not reach its final retained row`, + ); + assert.ok( + Number(await surface.locator("canvas").getAttribute("data-visible-items")) > 0, + `${engine}: focused Workflow culled every visible row`, + ); + + await surface.press("Home"); + await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(resolve))); + const canvas = surface.locator("canvas"); + await canvas.click({ position: { x: 44, y: 32 } }); + await page.locator(".study-preview").waitFor(); + await closeStudy(page); + + const scroller = surface.locator("xpath=ancestor::*[contains(@class,'map-scroll')]"); + await scroller.evaluate((node) => { node.scrollTop = 0; }); + const box = await canvas.boundingBox(); + assert.ok(box, `${engine}: Workflow canvas has no pointer box`); + await page.mouse.move(box.x + box.width - 8, box.y + Math.min(320, box.height - 24)); + await page.mouse.down(); + await page.mouse.move(box.x + box.width - 8, box.y + 80, { steps: 6 }); + await page.mouse.up(); + assert.ok( + (await scroller.evaluate((node) => node.scrollTop)) > 0, + `${engine}: blank far-right canvas space did not drag-to-pan`, + ); + assert.equal( + await page.locator(".study-preview").count(), + 0, + `${engine}: blank far-right canvas space activated a workflow row`, + ); + results.push(`${engine} focused Workflow keyboard, pointer, and blank-space pan`); + } finally { + await page.close(); + } + + const narrow = await browser.newPage({ viewport: { width: 320, height: 640 } }); + narrow.setDefaultTimeout(15_000); + try { + await narrow.goto(url, { waitUntil: "networkidle" }); + await settleApp(narrow); + const surface = narrow.locator(".architecture-map-canvas"); + await surface.focus(); + const longLabel = "a/complete/module/identifier/that/remains/readable/at/two-hundred-percent.py"; + const measured = await surface.locator(".map-canvas-readout").evaluate((readout, text) => { + readout.textContent = text; + const style = getComputedStyle(readout); + const rect = readout.getBoundingClientRect(); + return { + text: readout.textContent, + whiteSpace: style.whiteSpace, + overflowX: style.overflowX, + completeHeight: readout.scrollHeight <= readout.clientHeight + 1, + completeWidth: readout.scrollWidth <= readout.clientWidth + 1, + inViewport: rect.left >= -1 && rect.right <= innerWidth + 1, + }; + }, longLabel); + assert.deepEqual( + measured, + { + text: longLabel, + whiteSpace: "normal", + overflowX: "visible", + completeHeight: true, + completeWidth: true, + inViewport: true, + }, + `${engine}: full canvas label is clipped at 320px`, + ); + results.push(`${engine} full canvas label at 320px`); + } finally { + await narrow.close(); + } +} + for (const result of results) console.log(`PASS ${result}`); console.log(`user-flow repair contracts passed (${results.length} assertions)`); @@ -98,8 +266,9 @@ async function checkCompactQuizVisibility(browser, engine, url) { try { await page.goto(url, { waitUntil: "networkidle" }); await settleApp(page); - const box = page.locator("[role='button'][aria-label*='structure']").first(); - await box.click({ force: true }); + const map = page.locator(".architecture-map-canvas").first(); + await map.focus(); + await page.keyboard.press("Enter"); const prove = page.getByRole("button", { name: /^(Prove understanding|Review understanding|Check availability)$/, }).first(); @@ -436,8 +605,9 @@ async function settleApp(page) { async function openStudy(page) { const read = page.getByRole("button", { name: "Read the source", exact: true }); if (!(await read.count()) || !(await read.first().isVisible())) { - const box = page.locator("[role='button'][aria-label*='structure']").first(); - await box.click({ force: true }); + const map = page.locator(".architecture-map-canvas").first(); + await map.focus(); + await page.keyboard.press("Enter"); await read.first().waitFor(); } await read.first().click(); @@ -447,9 +617,10 @@ async function openStudy(page) { async function openStudyNormally(page) { const workflow = page.getByRole("button", { name: "What runs first", exact: true }); await workflow.click(); - const row = page.locator(".workflow-tree__row").first(); - await row.waitFor(); - await row.click({ force: true }); + const tree = page.locator(".workflow-map-canvas"); + await tree.waitFor(); + await tree.focus(); + await page.keyboard.press("Enter"); await page.locator(".study-preview").waitFor(); } diff --git a/web/src/App.jsx b/web/src/App.jsx index 2974def..716e734 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -374,7 +374,7 @@ export function App() { } // One element, two placements. Over the galaxy it floats: the 3D canvas is - // deep space at this corner, so nothing is behind it. The Map draws its SVG + // deep space at this corner, so nothing is behind it. The Map draws its canvas // in normal flow from that same corner, so there it is handed to MapView and // takes its own row above the drawing instead of sitting on the first rows // of the tree. @@ -900,7 +900,7 @@ export function App() { {/* Possible routes use the custom dashed path in the galaxy and a - dashed SVG path on the Map, so the semantic key is the same on + dashed canvas stroke on the Map, so the semantic key is the same on both layers. */} {mode === "easy" ? "Possible connection" : "Possible relationship"} diff --git a/web/src/MapView.jsx b/web/src/MapView.jsx index 865f55c..f8f2e14 100644 --- a/web/src/MapView.jsx +++ b/web/src/MapView.jsx @@ -1,10 +1,31 @@ -import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; +import { + useCallback, + useEffect, + useId, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; import { importCycleSummary, - nebulaTintPaint, unsupportedSummary, } from "./graphData.js"; +import { + MAP_CANVAS_GEOMETRY, + architectureBoxLabel, + architectureKeyboardTarget, + canvasMapPalette, + drawArchitectureCanvas, + drawWorkflowCanvas, + hitArchitectureCanvas, + hitWorkflowCanvas, + prepareArchitectureCanvas, + prepareWorkflowCanvas, + workflowKeyboardTarget, + workflowRowLabel, +} from "./mapCanvasRenderer.js"; import { centerMapPoint, clampMapZoom, @@ -29,8 +50,8 @@ const ZOOM_STEP = 1.25; * Panning rides the container's own scroll rather than a transform, so native * scrollbars, wheel scrolling, keyboard scrolling and screen-reader behaviour * all keep working. Zoom only scales the rendered size -- every coordinate - * inside the SVG is still the backend's, untouched, so this stays a pure - * renderer of graph-owned geometry. + * painted into the viewport canvas is still the backend's, untouched, so this + * stays a pure renderer of graph-owned geometry. */ function MapCanvas({ contentWidth, @@ -39,6 +60,7 @@ function MapCanvas({ viewKey, viewportStore, focusPoint, + centerInline = false, children, }) { const scrollRef = useRef(null); @@ -47,8 +69,44 @@ function MapCanvas({ const [panning, setPanning] = useState(false); const drag = useRef(null); const initialized = useRef(false); + const viewportFrame = useRef(0); + const [viewport, setViewport] = useState({ + width: 0, + height: 0, + scrollLeft: 0, + scrollTop: 0, + }); const zoomPercent = Math.round(scale * 100); + const publishViewport = useCallback(() => { + const scroller = scrollRef.current; + if (!scroller) return; + const next = { + width: scroller.clientWidth, + height: scroller.clientHeight, + scrollLeft: scroller.scrollLeft, + scrollTop: scroller.scrollTop, + }; + setViewport((current) => + current.width === next.width && + current.height === next.height && + current.scrollLeft === next.scrollLeft && + current.scrollTop === next.scrollTop + ? current + : next, + ); + }, []); + + const queueViewport = useCallback(() => { + cancelAnimationFrame(viewportFrame.current); + viewportFrame.current = requestAnimationFrame(publishViewport); + }, [publishViewport]); + + useEffect( + () => () => cancelAnimationFrame(viewportFrame.current), + [], + ); + const rememberViewport = useCallback(() => { const scroller = scrollRef.current; if (!scroller) return; @@ -119,15 +177,19 @@ function MapCanvas({ } initialized.current = true; rememberViewport(); + publishViewport(); }); return () => cancelAnimationFrame(frame); - }, [focusPoint, rememberViewport, scale]); + }, [focusPoint, publishViewport, rememberViewport, scale]); useEffect(() => { if (!initialized.current) return undefined; - const frame = requestAnimationFrame(rememberViewport); + const frame = requestAnimationFrame(() => { + rememberViewport(); + publishViewport(); + }); return () => cancelAnimationFrame(frame); - }, [rememberViewport]); + }, [publishViewport, rememberViewport]); // The mount-time honesty check has a live twin: a window RESIZE never // remounts this component, so shrinking a desktop window to phone width @@ -162,14 +224,34 @@ function MapCanvas({ scroller.scrollLeft = position.scrollLeft; scroller.scrollTop = position.scrollTop; rememberViewport(); + queueViewport(); }); observer.observe(scroller); return () => observer.disconnect(); - }, [focusPoint, rememberViewport, scale]); + }, [focusPoint, queueViewport, rememberViewport, scale]); + + const offsetX = centerInline + ? Math.max(0, (viewport.width - contentWidth * scale) / 2) + : 0; + + const revealPoint = useCallback( + (point, inlineOffset = 0) => { + const scroller = scrollRef.current; + if (!scroller || !point) return; + const x = point.x * scale + inlineOffset; + const y = point.y * scale; + scroller.scrollLeft = Math.max(0, x - scroller.clientWidth / 2); + scroller.scrollTop = Math.max(0, y - scroller.clientHeight / 2); + rememberViewport(); + queueViewport(); + }, + [queueViewport, rememberViewport, scale], + ); function onPointerDown(event) { - // Left button on empty diagram space only: boxes and rows are buttons, and - // stealing their pointer would make the map look interactive but inert. + // Left button on empty diagram space only. The canvas surface stops this + // event when its backend-geometry hit test finds a box or row; empty pixels + // bubble here and retain the native scroll-backed drag-to-pan path. if (event.button !== 0 || event.target.closest("[role='button']")) return; const scroller = scrollRef.current; drag.current = { @@ -234,60 +316,241 @@ function MapCanvas({ onPointerUp={endPan} onPointerCancel={endPan} onScroll={() => { - if (initialized.current) rememberViewport(); + if (initialized.current) { + rememberViewport(); + queueViewport(); + } }} >
- {children} + {typeof children === "function" + ? children({ + offsetX, + revealPoint, + scale, + viewport: { ...viewport, offsetX, scale }, + }) + : children}
); } -// Every coordinate here comes from GET /api/map. This file draws numbers and -// decides nothing: no layout, no ordering, no layering happens client-side. +function CanvasMapSurface({ + ariaLabel, + canvasClassName, + drawScene, + edgeCount, + fallbackItem, + hitTarget, + itemForKey, + itemIdentity, + itemKey, + itemLabel, + itemPoint, + itemReadout, + keyboardTarget, + onActivate, + preferredKey, + revealPoint, + scene, + viewport, +}) { + const canvasRef = useRef(null); + const pressedKeyRef = useRef(null); + const optionBaseId = useId(); + const previousPreferredKeyRef = useRef(preferredKey); + const [activeKey, setActiveKey] = useState( + preferredKey ?? (fallbackItem ? itemKey(fallbackItem) : null), + ); + const [hoverKey, setHoverKey] = useState(null); + const [keyboardFocused, setKeyboardFocused] = useState(false); + const [fontRevision, setFontRevision] = useState(0); + const palette = useMemo( + () => canvasMapPalette(getComputedStyle(document.documentElement)), + [], + ); -function tintFor(language) { - return nebulaTintPaint(language) ?? "var(--cm-hairline)"; -} + useEffect(() => { + let cancelled = false; + document.fonts?.ready?.then(() => { + if (!cancelled) setFontRevision((value) => value + 1); + }); + return () => { + cancelled = true; + }; + }, []); -function architectureEdgePath(points) { - return points - .map(([x, y], index) => `${index === 0 ? "M" : "L"} ${x} ${y}`) - .join(" "); -} + useEffect(() => { + const preferredChanged = previousPreferredKeyRef.current !== preferredKey; + previousPreferredKeyRef.current = preferredKey; + setActiveKey((current) => { + if ( + preferredChanged && + preferredKey !== null && + preferredKey !== undefined && + itemForKey(preferredKey) + ) { + return preferredKey; + } + if (itemForKey(current)) return current; + if ( + preferredKey !== null && + preferredKey !== undefined && + itemForKey(preferredKey) + ) { + return preferredKey; + } + return fallbackItem ? itemKey(fallbackItem) : null; + }); + }, [fallbackItem, itemForKey, itemKey, preferredKey]); -function architectureEdgeWidth(weight) { - return 1 + Math.min(2.5, (Math.max(1, weight) - 1) * 0.5); -} + const activeItem = activeKey === null ? null : itemForKey(activeKey); + const hoverItem = hoverKey === null ? null : itemForKey(hoverKey); + const activeLabel = activeItem ? itemLabel(activeItem) : ""; + const readoutItem = hoverItem ?? (keyboardFocused ? activeItem : null); + const items = scene.kind === "architecture" ? scene.boxes : scene.rows; + const activePosition = activeItem + ? items.findIndex((item) => itemKey(item) === itemKey(activeItem)) + 1 + : 0; + const activeIdentity = activeItem + ? [...new TextEncoder().encode(String(itemIdentity(activeItem)))] + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("") + : ""; + const optionId = activePosition && activeIdentity + ? `${optionBaseId}-active-${activePosition}-${activeIdentity}` + : undefined; + + useLayoutEffect(() => { + const canvas = canvasRef.current; + if (!canvas || viewport.width < 1 || viewport.height < 1) return; + const ratio = Math.min(2, Math.max(1, window.devicePixelRatio || 1)); + const pixelWidth = Math.max(1, Math.round(viewport.width * ratio)); + const pixelHeight = Math.max(1, Math.round(viewport.height * ratio)); + if (canvas.width !== pixelWidth) canvas.width = pixelWidth; + if (canvas.height !== pixelHeight) canvas.height = pixelHeight; + canvas.style.width = `${viewport.width}px`; + canvas.style.height = `${viewport.height}px`; + const context = canvas.getContext("2d"); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + context.clearRect(0, 0, viewport.width, viewport.height); + const stats = drawScene(context, { + activeKey, + hoverKey, + keyboardFocused, + palette, + viewport, + }); + canvas.dataset.visibleItems = String(stats.visibleItems); + canvas.dataset.visibleEdges = String(stats.visibleEdges); + }, [ + activeKey, + drawScene, + fontRevision, + hoverKey, + keyboardFocused, + palette, + viewport, + ]); + + function logicalPoint(event) { + const rect = canvasRef.current.getBoundingClientRect(); + return { + x: + (event.clientX - rect.left + viewport.scrollLeft - (viewport.offsetX || 0)) / + viewport.scale, + y: (event.clientY - rect.top + viewport.scrollTop) / viewport.scale, + }; + } + + function reveal(item) { + revealPoint(itemPoint(item), viewport.offsetX || 0); + } + + function onKeyDown(event) { + if (event.key === "Enter" || event.key === " ") { + if (!activeItem) return; + event.preventDefault(); + onActivate(activeItem); + return; + } + if (!/^(ArrowLeft|ArrowRight|ArrowUp|ArrowDown|Home|End)$/.test(event.key)) return; + event.preventDefault(); + const target = keyboardTarget(scene, activeKey, event.key); + if (!target) return; + setActiveKey(itemKey(target)); + reveal(target); + } -// Box geometry (box.width) is backend-computed and fixed regardless of label -// length (codemble/graph/mapview.py: _BOX_WIDTH is a constant) -- this only -// decides how much of the label fits inside that width, the way CSS -// text-overflow would if SVG supported it. 0.62em matches the -// monospace advance width WorkflowTree already assumes for row.label below. -const BOX_LABEL_FONT_PX = 13; -const BOX_LABEL_CHAR_EM = 0.62; -const BOX_LABEL_X = 14; -// 6, not 10: at 13px monospace those four pixels are half a character, and the -// difference decides whether `server/runtime.py` fits whole or loses its -// extension to an ellipsis. The box outline is still clear of the glyphs. -const BOX_LABEL_RIGHT_PAD = 6; - -function fitBoxLabel(label, boxWidth) { - const available = boxWidth - BOX_LABEL_X - BOX_LABEL_RIGHT_PAD; - const maxChars = Math.max(1, Math.floor(available / (BOX_LABEL_FONT_PX * BOX_LABEL_CHAR_EM))); - if (label.length <= maxChars) return label; - // An honest truncation, never a silent clip: a shortened identifier with - // an ellipsis tells the learner it is shortened; a bare clip (the bug this - // fixes) looked like a real, different identifier. - return `${label.slice(0, Math.max(1, maxChars - 1))}…`; + function pointerTarget(event) { + return hitTarget(scene, logicalPoint(event)); + } + + return ( +
setKeyboardFocused(true)} + onBlur={() => setKeyboardFocused(false)} + onKeyDown={onKeyDown} + style={{ width: viewport.width, height: viewport.height }} + > +
+ ); } +// Every coordinate here comes from GET /api/map. This file draws numbers and +// decides nothing: no layout, no ordering, no layering happens client-side. + export function MapView({ data, mapTab, @@ -343,7 +606,7 @@ export function MapView({ ) : !data ? (

Laying out parser evidence…

) : mapTab === "architecture" ? ( - ) : ( - [box.id, box])); - // View state, like zoom -- never graph truth. Collapsed, the unrouted block - // is REPRESENTED by its count in the note below (with the control to expand), - // so every module stays accounted for even while its box is folded away. - const [showUnreached, setShowUnreached] = useState( - architecture.unreachable.length <= SHELF_AUTO_COLLAPSE, +const boxKey = (box) => box.id; +const boxPoint = (box) => ({ + x: box.drawX + box.width / 2, + y: box.drawY + box.height / 2, +}); +const workflowKey = (row) => row.order; +const workflowPoint = (row) => ({ x: row.drawX + 8, y: row.drawY + 16 }); + +function CanvasArchitectureMap({ + architecture, + mode, + communityIndexByRegion, + selectedRegionId, + hasEntrypointCandidates, + onSelectRegion, + viewportStore, + languageFocusLabel, + onClearLanguageFocus, +}) { + const unreachableSet = useMemo( + () => new Set(architecture.unreachable), + [architecture.unreachable], ); - const unreachableSet = new Set(architecture.unreachable); - // Folding is only ever a readability trade made FOR the connected core. When - // every box in view is unreachable there is no core to protect and folding - // empties the drawing outright -- which is exactly what a language focus - // produces on a polyglot project, because Home is written in one language and - // nothing in another language has an import route to it. Measured: focusing - // Rust here drew 0 of 5 boxes on a 1396x509 canvas. - // - // The state above cannot catch it on its own, because `useState` runs its - // initialiser once: the component mounts unfocused (132 unreachable, so - // collapsed) and the focus then changes the set to 5 while the collapse - // sticks. This is a derived guard rather than a second piece of state, so it - // cannot go stale the same way. const everyBoxUnreachable = architecture.boxes.length > 0 && architecture.boxes.every((box) => unreachableSet.has(box.id)); - const unfolded = showUnreached || everyBoxUnreachable; - const visibleBoxes = unfolded - ? architecture.boxes - : architecture.boxes.filter((box) => !unreachableSet.has(box.id)); - // Cropping the empty band the folded shelf leaves behind is presentation of - // backend geometry, not layout: every visible coordinate is untouched. - const contentHeight = unfolded - ? architecture.height - : visibleBoxes.reduce((max, box) => Math.max(max, box.y + box.height), 0); - const padding = 32; + const scene = useMemo( + () => + prepareArchitectureCanvas(architecture, { + communityIndexByRegion, + }), + [architecture, communityIndexByRegion], + ); const focusBox = - boxes.get(selectedRegionId) ?? - boxes.get(architecture.home) ?? - architecture.boxes.find((box) => box.home) ?? - architecture.boxes[0]; - const focusPoint = focusBox - ? { - x: focusBox.x + padding + focusBox.width / 2, - y: focusBox.y + padding + focusBox.height / 2, - } - : null; + scene.boxById.get(selectedRegionId) ?? + scene.boxById.get(architecture.home) ?? + scene.boxes.find((box) => box.home) ?? + scene.boxes[0] ?? + null; + const focusPoint = focusBox ? boxPoint(focusBox) : null; + const itemForKey = useCallback((key) => scene.boxById.get(key) ?? null, [scene]); + const drawScene = useCallback( + (context, state) => { + const stats = drawArchitectureCanvas(context, scene, state.viewport, state.palette, { + activeId: state.activeKey, + hoverId: state.hoverKey, + keyboardFocused: state.keyboardFocused, + mode, + selectedId: selectedRegionId, + }); + return { visibleItems: stats.visibleBoxes, visibleEdges: stats.visibleEdges }; + }, + [mode, scene, selectedRegionId], + ); + const mapLabel = architecture.home + ? `${architecture.boxes.length} modules in ${architecture.layer_count} import layers from Home` + : `${architecture.boxes.length} modules in ${architecture.layer_count} import layers, measured from the modules nothing imports`; + return ( <> - - - - {/* userSpaceOnUse, not strokeWidth: a route's stroke scales with its - weight, so a strokeWidth-relative head gave the thinnest -- and - most numerous -- routes the smallest arrows on the map. The head - carries direction, which every route needs equally. */} - - - - - - - - - {architecture.edges.map((edge) => { - const from = boxes.get(edge.src); - const to = boxes.get(edge.dst); - if (!from || !to) return null; - // A folded box takes its edges with it (fixtures import each - // other); an edge to a hidden anchor would point at nothing. - if (!unfolded && (unreachableSet.has(edge.src) || unreachableSet.has(edge.dst))) { - return null; - } - return ( - ${edge.dst}`} - d={architectureEdgePath(edge.points)} - // Uncertainty stays visible in 2D exactly as it does in 3D. - strokeDasharray={edge.certain ? undefined : "5 4"} - strokeWidth={architectureEdgeWidth(edge.weight)} - markerEnd={`url(#${edge.cycle ? "architecture-cycle-arrow" : "architecture-arrow"})`} - className={`architecture-map__edge${edge.cycle ? " is-cycle" : ""}${edge.certain ? "" : " is-possible"}`} - /> - ); - })} - - {visibleBoxes.map((box) => ( - onSelectRegion(box.id)} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - onSelectRegion(box.id); - } - }} - > - {/* Native hover tooltip; the full label is also always in - aria-label above regardless of what the glyphs below fit. */} - {box.label}{box.partial ? " — unchartable, syntax error" : ""} - - {/* style, never the fill ATTRIBUTE: var() is invalid in an SVG - presentation attribute, so the attribute form silently fell - back to the box navy and the legend advertised language - colours the map never drew. The style property is CSS, where - var() resolves. */} - - {/* A corner flag, not just a colour: the box outline already - carries understood (colour), Home (width), and reachability - (dash), so a fourth signal on the same property would collide - -- and a syntax error must stay perceivable without colour - vision. The words are in and aria-label above; the - meta line below is already full at this fixed box width. */} - {box.partial ? ( - - ) : null} - {/* short_label is the tail of the module's real path; `label` is - the full identifier and stays in and aria-label above. - Truncating the dotted id instead used to render two different - modules as the same glyphs (codemble.server… twice). */} - <text x={BOX_LABEL_X} y="24"> - {fitBoxLabel(box.short_label ?? box.label, box.width)} - </text> - <text className="box-meta" x="14" y="42"> - {mode === "easy" - ? `${box.node_count} ${box.node_count === 1 ? "piece" : "pieces"}` - : `${box.node_count} ${box.node_count === 1 ? "node" : "nodes"} · ${box.loc} LOC`} - </text> - </g> - ))} - </svg> + {({ revealPoint, viewport }) => ( + <CanvasMapSurface + ariaLabel={mapLabel} + canvasClassName="architecture-map-canvas" + drawScene={drawScene} + edgeCount={scene.edges.length} + fallbackItem={focusBox} + hitTarget={hitArchitectureCanvas} + itemForKey={itemForKey} + itemIdentity={(box) => box.id} + itemKey={boxKey} + itemLabel={architectureBoxLabel} + itemPoint={boxPoint} + itemReadout={(box) => box.label} + keyboardTarget={architectureKeyboardTarget} + onActivate={(box) => onSelectRegion(box.id)} + preferredKey={scene.boxById.has(selectedRegionId) ? selectedRegionId : focusBox?.id} + revealPoint={revealPoint} + scene={scene} + viewport={viewport} + /> + )} </MapCanvas> - {/* Notes sit outside the zoom canvas: they are prose about the drawing, - so scaling them with it would shrink the explanation exactly when a - zoomed-out learner most needs to read it. */} {architecture.home ? null : ( <p className="map-note"> No Home is selected, so these layers run from the modules nothing else imports rather than from your entrypoint. Both are read from your imports, not guessed.{" "} {hasEntrypointCandidates - ? // Candidates exist, so the "Change Home" control is rendered. - "Pick your starting point with “Change Home” to see the same modules layered by what the project runs first." - : // No candidates, so no "Change Home" button is rendered -- point at - // the real reason instead of a control that isn't there. - "This project has no parser-recognisable entrypoint, so there is no “runs first” order to layer by instead."} + ? "Pick your starting point with “Change Home” to see the same modules layered by what the project runs first." + : "This project has no parser-recognisable entrypoint, so there is no “runs first” order to layer by instead."} </p> )} {everyBoxUnreachable ? ( - // Nothing here connects to Home, so there is no folded/unfolded choice - // to offer -- the shelf IS the drawing. Say why rather than showing a - // note about hiding boxes that are all on screen. <p className="map-note"> {languageFocusLabel ? `Home is not written in ${languageFocusLabel}, so none of these ${architecture.boxes.length} ${architecture.boxes.length === 1 ? "module" : "modules"} has an import route to it. They are drawn in file order rather than layered by guesswork.` @@ -628,30 +764,25 @@ function ArchitectureMap({ architecture, mode, communityIndexByRegion, selectedR <p className="map-note"> {architecture.unreachable.length}{" "} {architecture.unreachable.length === 1 ? "module has" : "modules have"} no import - route from Home - {showUnreached - ? `, so ${architecture.unreachable.length === 1 ? "it sits" : "they sit"} in the bottom rows rather than being placed by guesswork.` - : ". They are folded away so your connected code stays readable — nothing is hidden from the count."}{" "} - <button - type="button" - className="map-note__toggle" - aria-expanded={showUnreached} - onClick={() => setShowUnreached((value) => !value)} - > - {showUnreached ? "Fold them away" : "Show them"} - </button> + route from Home, so {architecture.unreachable.length === 1 ? "it sits" : "they sit"} + {" "}in the bottom rows rather than being placed by guesswork. Every module remains + searchable, drawable, and keyboard reachable. </p> ) : null} </> ); } -function WorkflowTree({ workflow, mode, selectedRegionId, hasEntrypointCandidates, onSelectNode, viewportStore, languageFocusLabel, onClearLanguageFocus }) { - // A root with no rows. The guard below only catches a MISSING root, so a - // language focus that filters out every row of a tree whose root still exists - // fell straight through it and drew an empty canvas with no message and no - // way back -- measured on this project: focusing Rust left a 1396x509 void - // whose only text was a note about structures it was not showing. +function CanvasWorkflowTree({ + workflow, + mode, + selectedRegionId, + hasEntrypointCandidates, + onSelectNode, + viewportStore, + languageFocusLabel, + onClearLanguageFocus, +}) { if (workflow.root && workflow.nodes.length === 0) { return ( <div className="map-state"> @@ -674,11 +805,6 @@ function WorkflowTree({ workflow, mode, selectedRegionId, hasEntrypointCandidate ); } if (!workflow.root) { - // Two ways to reach an empty workflow. With candidates, a Home just hasn't - // been chosen and the "Change Home" control exists to fix it -- keep the - // original instruction. Without candidates, the parser found no entrypoint - // at all, the button isn't rendered, and there is no "runs first" order to - // show, so say that instead of pointing at a button that isn't there. return hasEntrypointCandidates ? ( <div className="map-state"> <h2>No Home is selected.</h2> @@ -698,104 +824,78 @@ function WorkflowTree({ workflow, mode, selectedRegionId, hasEntrypointCandidate </div> ); } - const rows = new Map(workflow.nodes.map((row) => [row.order, row])); + return ( + <CanvasWorkflowDiagram + workflow={workflow} + mode={mode} + selectedRegionId={selectedRegionId} + onSelectNode={onSelectNode} + viewportStore={viewportStore} + /> + ); +} + +function CanvasWorkflowDiagram({ + workflow, + mode, + selectedRegionId, + onSelectNode, + viewportStore, +}) { + const scene = useMemo(() => prepareWorkflowCanvas(workflow), [workflow]); + const selectedRow = scene.rows.find((row) => row.region === selectedRegionId) ?? null; const focusRow = - workflow.nodes.find((row) => row.id === workflow.root) ?? workflow.nodes[0]; - const focusPoint = focusRow - ? { x: focusRow.x + 24, y: focusRow.y + 32 } - : null; + selectedRow ?? scene.rows.find((row) => row.id === workflow.root) ?? scene.rows[0] ?? null; + const focusPoint = focusRow ? workflowPoint(focusRow) : null; + const itemForKey = useCallback( + (key) => scene.rowByOrder.get(Number(key)) ?? null, + [scene], + ); + const drawScene = useCallback( + (context, state) => { + const stats = drawWorkflowCanvas(context, scene, state.viewport, state.palette, { + activeOrder: state.activeKey, + hoverOrder: state.hoverKey, + keyboardFocused: state.keyboardFocused, + mode, + selectedRegionId, + }); + return { visibleItems: stats.visibleRows, visibleEdges: stats.visibleEdges }; + }, + [mode, scene, selectedRegionId], + ); return ( <> - <MapCanvas - contentWidth={workflow.width + 32} - contentHeight={workflow.height + 32} - label="the workflow tree" - viewKey={`workflow:${workflow.root}`} - viewportStore={viewportStore} - focusPoint={focusPoint} - > - <svg - className="workflow-tree" - width="100%" - height="100%" - preserveAspectRatio="xMinYMin meet" - viewBox={`-16 -16 ${workflow.width + 32} ${workflow.height + 32}`} - // group, not img -- see ArchitectureMap above: these rows are buttons. - role="group" - aria-label={`Call tree from ${workflow.root}, ${workflow.nodes.length} steps deep to ${workflow.depth_count} levels`} + <MapCanvas + contentWidth={workflow.width + MAP_CANVAS_GEOMETRY.workflowPadding * 2} + contentHeight={workflow.height + MAP_CANVAS_GEOMETRY.workflowPadding * 2} + label="the workflow tree" + viewKey={`workflow:${workflow.root}`} + viewportStore={viewportStore} + focusPoint={focusPoint} > - <g className="workflow-tree__edges"> - {workflow.nodes.map((row) => { - if (row.parent === null) return null; - const parent = [...rows.values()] - .filter((candidate) => candidate.id === row.parent && candidate.order < row.order) - .at(-1); - if (!parent) return null; - return ( - <path - key={`${row.order}`} - d={`M ${parent.x + 8} ${parent.y + 20} V ${row.y + 12} H ${row.x + 8}`} - strokeDasharray={row.certain ? undefined : "5 4"} - className={row.certain ? undefined : "is-possible"} - /> - ); - })} - </g> - {workflow.nodes.map((row) => ( - <g - key={row.order} - className="workflow-tree__row" - data-understood={row.understood} - data-cut={row.cut ?? undefined} - data-partial={row.partial} - data-relation={row.relation} - // Every row whose structure lives in the selected module rings in - // the interaction accent, so clicking a box shows what it contains - // on this tab too. row.region is parser truth (mapview.py), matched - // by id -- no client-side region lookup. - data-selected={row.region === selectedRegionId} - transform={`translate(${row.x} ${row.y})`} - role="button" - tabIndex={0} - aria-label={`${row.label} at ${row.file}:${row.lineno}${row.certain ? "" : ", possible call"}${row.cut === "cycle" ? ", repeats an earlier step" : ""}${row.cut === "repeat" ? ", already shown above" : ""}${row.partial ? ", unchartable, syntax error" : ""}`} - onClick={() => onSelectNode(row.id)} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - onSelectNode(row.id); - } - }} - > - <circle cx="8" cy="16" r="4" /> - {/* One <text> with two <tspan>s, so the meta flows after the label - at whatever width the label actually renders. - - It used to be a SECOND <text> pinned to the same x/y and pushed - clear with `dx={row.label.length * 0.62}em` -- a guess at the - label's width from its character count. That is only ever right - for a monospace face at one size: every proportional label, and - every label of unusual letter widths, printed "— possible call" - or "— shown above" straight through its own name. Reported from - the served build as text merging into text on this tab. */} - <text x="20" y="20"> - <tspan>{row.label}</tspan> - <tspan className="row-meta"> - {row.relation === "defines" - ? mode === "easy" ? " — lives here" : " — defined in this module" - : row.certain - ? "" - : " — possible call"} - {row.cut === "cycle" ? " — loops back" : ""} - {row.cut === "repeat" ? " — shown above" : ""} - {/* Unlike the fixed-width architecture box, a tree row has room - for the word, so partial says so in the same slot that - already carries "possible call". */} - {row.partial ? (mode === "easy" ? " — could not be read" : " — unchartable") : ""} - </tspan> - </text> - </g> - ))} - </svg> + {({ revealPoint, viewport }) => ( + <CanvasMapSurface + ariaLabel={`Call tree from ${workflow.root}, ${workflow.nodes.length} steps deep to ${workflow.depth_count} levels`} + canvasClassName="workflow-map-canvas" + drawScene={drawScene} + edgeCount={scene.edges.length} + fallbackItem={focusRow} + hitTarget={hitWorkflowCanvas} + itemForKey={itemForKey} + itemIdentity={(row) => `${row.order}:${row.id}:${row.file}:${row.lineno}`} + itemKey={workflowKey} + itemLabel={workflowRowLabel} + itemPoint={workflowPoint} + itemReadout={(row) => `${row.label} · ${row.file}:${row.lineno}`} + keyboardTarget={workflowKeyboardTarget} + onActivate={(row) => onSelectNode(row.id)} + preferredKey={focusRow?.order} + revealPoint={revealPoint} + scene={scene} + viewport={viewport} + /> + )} </MapCanvas> {workflow.unreachable.length ? ( <p className="map-note"> diff --git a/web/src/celestialBodies.js b/web/src/celestialBodies.js index 4c624d4..ce0c223 100644 --- a/web/src/celestialBodies.js +++ b/web/src/celestialBodies.js @@ -12,7 +12,7 @@ import * as THREE from "three"; * parser truth and is passed in here as a finished value. * * Level-of-detail is the reason this module exists at all rather than being - * folded into `galaxyMaterials`. A galaxy draws up to ~1,000 systems and cannot + * folded into `galaxyMaterials`. A galaxy draws up to ~5,000 systems and cannot * afford a four-octave noise loop per fragment, so it keeps the cheap halo * sprites. A system draws a few dozen members at close range, which is both * where the cost is affordable and where the learner is actually looking. @@ -166,7 +166,7 @@ export function createBodyGeometry(segments = 32) { * * Deliberately per-node rather than shared: the surface varies by seed, and a * System level holds a few dozen members, so a material each is affordable - * where it would not be for a 1,000-system galaxy. These are NOT registered as + * where it would not be for a 5,000-system galaxy. These are NOT registered as * shared resources, so three-forcegraph's deallocator freeing them with their * node object is exactly right. */ diff --git a/web/src/mapCanvasRenderer.js b/web/src/mapCanvasRenderer.js new file mode 100644 index 0000000..16bf30f --- /dev/null +++ b/web/src/mapCanvasRenderer.js @@ -0,0 +1,500 @@ +const ARCHITECTURE_PADDING = 32; +const WORKFLOW_PADDING = 16; +const SPATIAL_BAND_HEIGHT = 64; +const WORKFLOW_HIT_HEIGHT = 32; +const BOX_LABEL_FONT_PX = 13; +const BOX_LABEL_CHAR_EM = 0.62; +const BOX_LABEL_X = 14; +const BOX_LABEL_RIGHT_PAD = 6; +const VIEWPORT_OVERSCAN = 48; + +function boundsFromPoints(points) { + return points.reduce( + (bounds, [x, y]) => ({ + left: Math.min(bounds.left, x), + top: Math.min(bounds.top, y), + right: Math.max(bounds.right, x), + bottom: Math.max(bounds.bottom, y), + }), + { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity }, + ); +} + +function intersects(first, second) { + return !( + first.right < second.left || + first.left > second.right || + first.bottom < second.top || + first.top > second.bottom + ); +} + +function indexByVerticalBand(items, boundsOf) { + const bands = new Map(); + for (const item of items) { + const bounds = boundsOf(item); + const first = Math.floor(bounds.top / SPATIAL_BAND_HEIGHT); + const last = Math.floor(bounds.bottom / SPATIAL_BAND_HEIGHT); + for (let band = first; band <= last; band += 1) { + const members = bands.get(band) ?? []; + members.push(item); + bands.set(band, members); + } + } + return bands; +} + +function logicalViewport(viewport) { + const scale = Math.max(viewport.scale || 1, 0.0001); + return { + left: (viewport.scrollLeft - (viewport.offsetX || 0)) / scale - VIEWPORT_OVERSCAN, + top: viewport.scrollTop / scale - VIEWPORT_OVERSCAN, + right: + (viewport.scrollLeft - (viewport.offsetX || 0) + viewport.width) / scale + + VIEWPORT_OVERSCAN, + bottom: (viewport.scrollTop + viewport.height) / scale + VIEWPORT_OVERSCAN, + }; +} + +export function fitBoxLabel(label, boxWidth) { + const available = boxWidth - BOX_LABEL_X - BOX_LABEL_RIGHT_PAD; + const maxChars = Math.max( + 1, + Math.floor(available / (BOX_LABEL_FONT_PX * BOX_LABEL_CHAR_EM)), + ); + if (label.length <= maxChars) return label; + return `${label.slice(0, Math.max(1, maxChars - 1))}…`; +} + +export function prepareArchitectureCanvas( + architecture, + { communityIndexByRegion = null } = {}, +) { + const boxes = architecture.boxes + .map((box) => ({ + ...box, + drawX: box.x + ARCHITECTURE_PADDING, + drawY: box.y + ARCHITECTURE_PADDING, + communityFamily: communityIndexByRegion?.get(box.id) ?? null, + })) + .sort((left, right) => left.y - right.y || left.x - right.x || left.id.localeCompare(right.id)); + const boxById = new Map(boxes.map((box) => [box.id, box])); + const boxBounds = (box) => ({ + left: box.drawX, + top: box.drawY, + right: box.drawX + box.width, + bottom: box.drawY + box.height, + }); + const boxesByBand = indexByVerticalBand(boxes, boxBounds); + const edges = architecture.edges + .filter((edge) => boxById.has(edge.src) && boxById.has(edge.dst)) + .map((edge) => { + const points = edge.points.map(([x, y]) => [ + x + ARCHITECTURE_PADDING, + y + ARCHITECTURE_PADDING, + ]); + return { ...edge, points, bounds: boundsFromPoints(points) }; + }); + return Object.freeze({ + kind: "architecture", + boxes, + boxById, + boxesByBand, + edges, + padding: ARCHITECTURE_PADDING, + }); +} + +export function visibleArchitectureCanvas(scene, viewport) { + const bounds = logicalViewport(viewport); + return { + boxes: scene.boxes.filter((box) => + intersects(bounds, { + left: box.drawX, + top: box.drawY, + right: box.drawX + box.width, + bottom: box.drawY + box.height, + }), + ), + edges: scene.edges.filter((edge) => intersects(bounds, edge.bounds)), + }; +} + +export function hitArchitectureCanvas(scene, point) { + return ( + scene.boxesByBand + .get(Math.floor(point.y / SPATIAL_BAND_HEIGHT)) + ?.find( + (box) => + point.x >= box.drawX && + point.x <= box.drawX + box.width && + point.y >= box.drawY && + point.y <= box.drawY + box.height, + ) ?? null + ); +} + +function directionalTarget(items, activeId, key, idOf, centerOf) { + if (!items.length) return null; + if (key === "Home") return items[0]; + if (key === "End") return items.at(-1); + const activeIndex = Math.max(0, items.findIndex((item) => idOf(item) === activeId)); + const active = items[activeIndex] ?? items[0]; + const origin = centerOf(active); + const direction = { + ArrowLeft: [-1, 0], + ArrowRight: [1, 0], + ArrowUp: [0, -1], + ArrowDown: [0, 1], + }[key]; + if (!direction) return active; + const candidates = items + .filter((item) => idOf(item) !== idOf(active)) + .map((item) => { + const point = centerOf(item); + const dx = point.x - origin.x; + const dy = point.y - origin.y; + const forward = dx * direction[0] + dy * direction[1]; + const cross = Math.abs(dx * direction[1] - dy * direction[0]); + return { item, forward, cross }; + }) + .filter((candidate) => candidate.forward > 0) + .sort( + (left, right) => + left.cross / left.forward - right.cross / right.forward || + Math.hypot(left.forward, left.cross) - + Math.hypot(right.forward, right.cross) || + idOf(left.item).localeCompare(idOf(right.item)), + ); + if (candidates.length) return candidates[0].item; + const fallback = key === "ArrowLeft" || key === "ArrowUp" ? activeIndex - 1 : activeIndex + 1; + return items[Math.max(0, Math.min(items.length - 1, fallback))]; +} + +export function architectureKeyboardTarget(scene, activeId, key) { + return directionalTarget( + scene.boxes, + activeId, + key, + (box) => box.id, + (box) => ({ x: box.drawX + box.width / 2, y: box.drawY + box.height / 2 }), + ); +} + +export function architectureBoxLabel(box) { + return `${box.label}, ${box.node_count} ${box.node_count === 1 ? "structure" : "structures"}, ${box.loc} ${box.loc === 1 ? "line" : "lines"}${box.understood ? ", understood" : ", not yet understood"}${box.home ? ", Home" : ""}${box.reachable ? "" : ", no import route from Home"}${box.partial ? ", unchartable, syntax error" : ""}`; +} + +export function prepareWorkflowCanvas(workflow) { + const rows = [...workflow.nodes] + .sort((left, right) => left.order - right.order) + .map((row) => { + const drawX = row.x + WORKFLOW_PADDING; + const drawY = row.y + WORKFLOW_PADDING; + const labelWidth = row.label.length * 13 * BOX_LABEL_CHAR_EM; + const metaWidth = Math.max( + workflowRowMeta(row, "easy").length, + workflowRowMeta(row, "expert").length, + ) * 11 * BOX_LABEL_CHAR_EM; + return { + ...row, + drawX, + drawY, + bounds: { + left: drawX, + top: drawY, + right: drawX + 20 + labelWidth + metaWidth + 8, + bottom: drawY + WORKFLOW_HIT_HEIGHT, + }, + }; + }); + const lastById = new Map(); + const edges = []; + for (const row of rows) { + const parent = row.parent === null ? null : lastById.get(row.parent) ?? null; + if (parent) { + const points = [ + [parent.drawX + 8, parent.drawY + 20], + [parent.drawX + 8, row.drawY + 12], + [row.drawX + 8, row.drawY + 12], + ]; + edges.push({ row, parent, points, bounds: boundsFromPoints(points) }); + } + lastById.set(row.id, row); + } + return Object.freeze({ + kind: "workflow", + rows, + rowByOrder: new Map(rows.map((row) => [row.order, row])), + rowsByBand: indexByVerticalBand(rows, (row) => row.bounds), + edges, + padding: WORKFLOW_PADDING, + }); +} + +export function visibleWorkflowCanvas(scene, viewport) { + const bounds = logicalViewport(viewport); + return { + rows: scene.rows.filter((row) => intersects(bounds, row.bounds)), + edges: scene.edges.filter((edge) => intersects(bounds, edge.bounds)), + }; +} + +export function hitWorkflowCanvas(scene, point) { + return ( + scene.rowsByBand + .get(Math.floor(point.y / SPATIAL_BAND_HEIGHT)) + ?.find( + (row) => + point.x >= row.bounds.left && + point.x <= row.bounds.right && + point.y >= row.bounds.top && + point.y <= row.bounds.bottom, + ) ?? null + ); +} + +export function workflowKeyboardTarget(scene, activeOrder, key) { + if (!scene.rows.length) return null; + if (key === "Home") return scene.rows[0]; + if (key === "End") return scene.rows.at(-1); + const currentIndex = Math.max( + 0, + scene.rows.findIndex((row) => row.order === activeOrder), + ); + const current = scene.rows[currentIndex] ?? scene.rows[0]; + if (key === "ArrowLeft" && current.parent !== null) { + return scene.rows + .slice(0, currentIndex) + .reverse() + .find((row) => row.id === current.parent) ?? current; + } + if (key === "ArrowRight") { + return scene.rows.slice(currentIndex + 1).find((row) => row.parent === current.id) ?? current; + } + const delta = key === "ArrowUp" ? -1 : key === "ArrowDown" ? 1 : 0; + return scene.rows[currentIndex + delta] ?? current; +} + +export function workflowRowMeta(row, mode) { + const parts = []; + if (row.relation === "defines") { + parts.push(mode === "easy" ? "lives here" : "defined in this module"); + } else if (!row.certain) { + parts.push("possible call"); + } + if (row.cut === "cycle") parts.push("loops back"); + if (row.cut === "repeat") parts.push("shown above"); + if (row.partial) parts.push(mode === "easy" ? "could not be read" : "unchartable"); + return parts.map((part) => ` — ${part}`).join(""); +} + +export function workflowRowLabel(row) { + return `${row.label} at ${row.file}:${row.lineno}${row.certain ? "" : ", possible call"}${row.cut === "cycle" ? ", repeats an earlier step" : ""}${row.cut === "repeat" ? ", already shown above" : ""}${row.partial ? ", unchartable, syntax error" : ""}`; +} + +function cssValue(style, property, fallback) { + return style?.getPropertyValue?.(property)?.trim() || fallback; +} + +function rgbChannels(value) { + const hex = value.match(/^#([0-9a-f]{6})$/i)?.[1]; + if (hex) return [0, 2, 4].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16)); + const rgb = value.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/i); + return rgb ? rgb.slice(1).map(Number) : null; +} + +function mixPaint(foreground, background, weight) { + const front = rgbChannels(foreground); + const back = rgbChannels(background); + if (!front || !back) return background; + return `rgb(${front.map((value, index) => Math.round(value * weight + back[index] * (1 - weight))).join(" ")})`; +} + +export function canvasMapPalette(style) { + const ground2 = cssValue(style, "--cm-ground-2", "#101a3e"); + const communities = Array.from({ length: 8 }, (_, index) => + mixPaint(cssValue(style, `--cm-com-${index}`, ground2), ground2, 0.16), + ); + return Object.freeze({ + ground2, + hairline: cssValue(style, "--cm-hairline", "#24325c"), + ink: cssValue(style, "--cm-ink", "#eef2fa"), + ink3: cssValue(style, "--cm-ink-3", "#7d8aa8"), + route: cssValue(style, "--cm-route", "rgb(96 113 152)"), + possible: cssValue(style, "--cm-route-possible", "rgb(146 160 194)"), + understood: cssValue(style, "--cm-star-high", "#f6d087"), + interaction: cssValue(style, "--cm-orbit", "#82abec"), + mono: cssValue(style, "--cm-font-mono", "monospace"), + communities, + language: { + python: cssValue(style, "--cm-neb-python", "rgb(112 166 144)"), + javascript: cssValue(style, "--cm-neb-js", "rgb(161 151 185)"), + typescript: cssValue(style, "--cm-neb-ts", "rgb(119 161 186)"), + go: cssValue(style, "--cm-neb-go", "rgb(132 178 161)"), + java: cssValue(style, "--cm-neb-java", "rgb(177 154 137)"), + rust: cssValue(style, "--cm-neb-rust", "rgb(183 145 132)"), + csharp: cssValue(style, "--cm-neb-csharp", "rgb(157 146 184)"), + }, + }); +} + +function roundedRect(context, x, y, width, height, radius) { + context.beginPath(); + context.moveTo(x + radius, y); + context.lineTo(x + width - radius, y); + context.quadraticCurveTo(x + width, y, x + width, y + radius); + context.lineTo(x + width, y + height - radius); + context.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + context.lineTo(x + radius, y + height); + context.quadraticCurveTo(x, y + height, x, y + height - radius); + context.lineTo(x, y + radius); + context.quadraticCurveTo(x, y, x + radius, y); + context.closePath(); +} + +function beginViewport(context, viewport) { + context.save(); + context.translate((viewport.offsetX || 0) - viewport.scrollLeft, -viewport.scrollTop); + context.scale(viewport.scale, viewport.scale); + context.lineCap = "round"; + context.lineJoin = "round"; +} + +function drawArrow(context, points, paint) { + if (points.length < 2) return; + const [fromX, fromY] = points.at(-2); + const [toX, toY] = points.at(-1); + const angle = Math.atan2(toY - fromY, toX - fromX); + context.save(); + context.translate(toX, toY); + context.rotate(angle); + context.fillStyle = paint; + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(-9, -4); + context.lineTo(-9, 4); + context.closePath(); + context.fill(); + context.restore(); +} + +export function drawArchitectureCanvas(context, scene, viewport, palette, options = {}) { + const visible = visibleArchitectureCanvas(scene, viewport); + beginViewport(context, viewport); + for (const edge of visible.edges) { + const paint = edge.certain && !edge.cycle ? palette.route : palette.possible; + context.strokeStyle = paint; + context.lineWidth = 1 + Math.min(2.5, (Math.max(1, edge.weight) - 1) * 0.5); + context.setLineDash(edge.certain ? [] : [5, 4]); + context.beginPath(); + edge.points.forEach(([x, y], index) => + index === 0 ? context.moveTo(x, y) : context.lineTo(x, y), + ); + context.stroke(); + context.setLineDash([]); + drawArrow(context, edge.points, paint); + } + for (const box of visible.boxes) { + const x = box.drawX; + const y = box.drawY; + const fill = + box.communityFamily === null + ? palette.ground2 + : palette.communities[box.communityFamily] ?? palette.ground2; + roundedRect(context, x, y, box.width, box.height, 3); + context.fillStyle = fill; + context.fill(); + context.strokeStyle = box.understood + ? palette.understood + : box.partial + ? palette.possible + : palette.hairline; + context.lineWidth = box.home ? 2 : 1; + context.setLineDash(box.reachable ? [] : [4, 3]); + context.stroke(); + context.setLineDash([]); + context.fillStyle = palette.language[box.language] ?? palette.hairline; + context.fillRect(x, y, 4, box.height); + if (box.partial) { + context.fillStyle = palette.possible; + context.beginPath(); + context.moveTo(x + box.width - 16, y + 2); + context.lineTo(x + box.width - 2, y + 2); + context.lineTo(x + box.width - 2, y + 16); + context.closePath(); + context.fill(); + } + if (box.id === options.selectedId || box.id === options.activeId || box.id === options.hoverId) { + roundedRect(context, x - 3, y - 3, box.width + 6, box.height + 6, 5); + context.strokeStyle = palette.interaction; + context.lineWidth = box.id === options.activeId && options.keyboardFocused ? 2.5 : 1.5; + context.stroke(); + } + context.fillStyle = palette.ink; + context.font = `13px ${palette.mono}`; + context.fillText(fitBoxLabel(box.short_label ?? box.label, box.width), x + 14, y + 24); + context.fillStyle = palette.ink3; + context.font = `11px ${palette.mono}`; + const meta = + options.mode === "easy" + ? `${box.node_count} ${box.node_count === 1 ? "piece" : "pieces"}` + : `${box.node_count} ${box.node_count === 1 ? "node" : "nodes"} · ${box.loc} LOC`; + context.fillText(meta, x + 14, y + 42); + } + context.restore(); + return { visibleBoxes: visible.boxes.length, visibleEdges: visible.edges.length }; +} + +export function drawWorkflowCanvas(context, scene, viewport, palette, options = {}) { + const visible = visibleWorkflowCanvas(scene, viewport); + beginViewport(context, viewport); + for (const edge of visible.edges) { + context.strokeStyle = edge.row.certain ? palette.route : palette.possible; + context.lineWidth = 1.2; + context.setLineDash(edge.row.certain ? [] : [5, 4]); + context.beginPath(); + edge.points.forEach(([x, y], index) => + index === 0 ? context.moveTo(x, y) : context.lineTo(x, y), + ); + context.stroke(); + } + context.setLineDash([]); + for (const row of visible.rows) { + const x = row.drawX; + const y = row.drawY; + context.fillStyle = row.understood + ? palette.understood + : row.partial || row.cut + ? palette.possible + : palette.ink3; + context.beginPath(); + context.arc(x + 8, y + 16, 4, 0, Math.PI * 2); + context.fill(); + if ( + row.region === options.selectedRegionId || + row.order === options.activeOrder || + row.order === options.hoverOrder + ) { + context.strokeStyle = palette.interaction; + context.lineWidth = row.order === options.activeOrder && options.keyboardFocused ? 2.5 : 1.5; + context.beginPath(); + context.arc(x + 8, y + 16, 7, 0, Math.PI * 2); + context.stroke(); + } + context.fillStyle = palette.ink; + context.font = `13px ${palette.mono}`; + const labelWidth = context.measureText(row.label).width; + context.fillText(row.label, x + 20, y + 20); + context.fillStyle = palette.ink3; + context.font = `11px ${palette.mono}`; + context.fillText(workflowRowMeta(row, options.mode), x + 20 + labelWidth, y + 20); + } + context.restore(); + return { visibleRows: visible.rows.length, visibleEdges: visible.edges.length }; +} + +export const MAP_CANVAS_GEOMETRY = Object.freeze({ + architecturePadding: ARCHITECTURE_PADDING, + workflowPadding: WORKFLOW_PADDING, +}); diff --git a/web/src/styles.css b/web/src/styles.css index fbc7e0b..aa97cdd 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -885,7 +885,7 @@ input:focus-visible { } /* The Map layer's variant: a row in the map column, not an overlay on it. The - 3D canvas has nothing behind this corner, but the SVG below draws its first + 3D canvas has nothing behind this corner, but the Map canvas below draws its first box and its root tree row exactly here, so floating meant display-size serif on top of the learner's own identifiers. Being a row rather than an overlay, it has nothing to keep clear of horizontally, so it does not inherit the @@ -2901,159 +2901,59 @@ input:focus-visible { } .map-canvas__sized { - /* The zoomed drawing's box. Its size is the only thing zoom changes -- every - coordinate inside the SVG stays exactly as the backend computed it. */ + /* The zoomed drawing's scroll extent. Its size is the only thing zoom + changes; the viewport-sized canvas stays sticky inside it and draws the + backend coordinates that intersect the current native scroll window. */ min-width: 100%; } -.architecture-map, -.workflow-tree { +.map-canvas-surface { + position: sticky; + inset-block-start: 0; + inset-inline-start: 0; display: block; - min-height: 0; -} - -.architecture-map__edges path { - /* Route ink, not --cm-hairline: sharing the border ink made every edge - invisible against the chrome it ran beside (audit gap 3). */ - color: var(--cm-route); - stroke: currentColor; - fill: none; - stroke-linecap: round; - stroke-linejoin: round; -} - -.architecture-map__edges path.is-possible, -.architecture-map__edges path.is-cycle { - color: var(--cm-route-possible); -} - -.architecture-map__arrow { - color: var(--cm-route); -} - -.architecture-map__arrow.is-cycle { - color: var(--cm-route-possible); -} - -.workflow-tree__edges path { - stroke: var(--cm-route); - stroke-width: 1.2; - fill: none; -} - -.workflow-tree__edges path.is-possible { - stroke: var(--cm-route-possible); -} - -.architecture-map__box rect { - /* A few percent of the box's community family hue (D1), set per box as - --box-com. Zones of related modules form at a glance while the stroke - channels stay free for understood (colour), Home (width) and - reachability (dash). Without a community fact the mix collapses to the - plain panel ground. */ - fill: color-mix(in srgb, var(--box-com, var(--cm-ground-2)) 16%, var(--cm-ground-2)); - stroke: var(--cm-hairline); -} - -.architecture-map__box .box-tint { - stroke: none; -} - -.architecture-map__box text { - fill: var(--cm-ink); - font-family: var(--cm-font-mono); - font-size: 13px; -} - -.architecture-map__box .box-meta { - fill: var(--cm-ink-3); - font-size: 11px; -} - -/* Declared before the understood rule on purpose: equal specificity means - source order decides, and a region that is both partial and understood - should still read as understood on the outline. The corner flag drawn by - MapView carries "partial" on its own channel either way, so the two facts - never compete for one property. */ -.architecture-map__box[data-partial="true"] rect:first-of-type { - stroke: var(--cm-route-possible); -} - -.architecture-map__box .box-partial { - fill: var(--cm-route-possible); - stroke: none; -} - -.architecture-map__box[data-understood="true"] rect:first-of-type { - stroke: var(--cm-star-high); -} - -.architecture-map__box[data-home="true"] rect:first-of-type { - stroke-width: 2; -} - -.architecture-map__box[data-reachable="false"] rect:first-of-type { - stroke-dasharray: 4 3; + overflow: hidden; + max-width: 100%; + max-height: 100%; + contain: strict; } -.architecture-map__box:focus-visible rect:first-of-type, -.workflow-tree__row:focus-visible circle { +.map-canvas-surface:focus-visible { outline: 2px solid var(--cm-orbit); - outline-offset: 2px; + outline-offset: -3px; } -.architecture-map__box, -.workflow-tree__row { - cursor: pointer; -} - -.workflow-tree__row circle { - fill: var(--cm-ink-3); -} - -.workflow-tree__row[data-partial="true"] circle { - fill: var(--cm-route-possible); -} - -.workflow-tree__row[data-cut] circle { - fill: var(--cm-route-possible); -} - -/* Last of the three at equal specificity, so understanding wins the cascade: - a repeated or cycle-cut row that the learner HAS proven must keep its amber. - The cut itself is never colour-only -- the row says "shown above" or - "loops back" in its own text and aria-label. */ -.workflow-tree__row[data-understood="true"] circle { - fill: var(--cm-star-high); +.map-canvas-surface canvas { + display: block; } -.workflow-tree__row text { - fill: var(--cm-ink); +.map-canvas-readout { + position: absolute; + inset-inline-start: var(--cm-space-sm); + inset-block-end: var(--cm-space-sm); + max-width: calc(100% - var(--cm-space-sm) * 2); + padding: var(--cm-space-2xs) var(--cm-space-xs); + border: 1px solid var(--cm-hairline); + border-radius: var(--cm-radius); + background: rgb(16 26 62 / 92%); + color: var(--cm-ink-2); font-family: var(--cm-font-mono); - font-size: 13px; -} - -.workflow-tree__row .row-meta { - fill: var(--cm-ink-3); - font-size: 11px; -} - -/* Selection wears the interaction accent (--cm-orbit), never the amber - understanding accent -- amber means understood only. Both signals compose - with the understood ones rather than replacing them: the box outline sits - outside its (possibly amber) stroke, and the row ring uses the circle's - otherwise-unused stroke channel, so an understood region that is also - selected still reads as understood. Driven by a persistent data-selected - attribute, not :focus-visible, because a pointer click must show it and - focus-visible stays dark for the mouse -- the exact bug this fixes. */ -.architecture-map__box[data-selected="true"] rect:first-of-type { - outline: 2px solid var(--cm-orbit); - outline-offset: 3px; + font-size: var(--cm-text-xs); + overflow-wrap: anywhere; + white-space: normal; + pointer-events: none; } -.workflow-tree__row[data-selected="true"] circle { - stroke: var(--cm-orbit); - stroke-width: 2; +.map-canvas-active-option { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; } .map-note, @@ -3276,7 +3176,7 @@ input:focus-visible { border-color: var(--cm-orbit); } -/* Possible routes are dashed on both layers: SVG draws the Map version and +/* Possible routes are dashed on both layers: canvas draws the Map version and possibleRoutes.js owns the custom WebGL path. */ .legend-route--dashed { border-block-start-style: dashed;