@mindexec/cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +275 -0
  2. package/codex-runtime.js +960 -0
  3. package/launch-bridge.cjs +162 -0
  4. package/package.json +61 -0
  5. package/port-guard.cjs +232 -0
  6. package/scripts/setup-tree-sitter-grammars.mjs +59 -0
  7. package/server.js +8422 -0
  8. package/start-bridge.bat +32 -0
  9. package/start-bridge.sh +81 -0
  10. package/tree-sitter-grammars/README.md +18 -0
  11. package/tree-sitter-grammars/tree-sitter-c_sharp.wasm +0 -0
  12. package/tree-sitter-grammars/tree-sitter-go.wasm +0 -0
  13. package/tree-sitter-grammars/tree-sitter-java.wasm +0 -0
  14. package/tree-sitter-grammars/tree-sitter-javascript.wasm +0 -0
  15. package/tree-sitter-grammars/tree-sitter-python.wasm +0 -0
  16. package/tree-sitter-grammars/tree-sitter-rust.wasm +0 -0
  17. package/tree-sitter-grammars/tree-sitter-tsx.wasm +0 -0
  18. package/tree-sitter-grammars/tree-sitter-typescript.wasm +0 -0
  19. package/wwwroot/MindExecution.Web.styles.css +3 -0
  20. package/wwwroot/_content/MindExecution.Plugins.Admin/css/admin-dashboard.css +546 -0
  21. package/wwwroot/_content/MindExecution.Plugins.Directory/MindExecution.Plugins.Directory.u7utcng611.bundle.scp.css +7 -0
  22. package/wwwroot/_content/MindExecution.Plugins.Directory/background.png +0 -0
  23. package/wwwroot/_content/MindExecution.Plugins.Directory/directory-manager.js +202 -0
  24. package/wwwroot/_content/MindExecution.Plugins.Directory/exampleJsInterop.js +6 -0
  25. package/wwwroot/_content/MindExecution.Plugins.YouTube/css/youtube-search.css +251 -0
  26. package/wwwroot/_content/MindExecution.Shared/MindExecution.Shared.wsano1j4wp.bundle.scp.css +4 -0
  27. package/wwwroot/_content/MindExecution.Shared/css/admin-dashboard.css +559 -0
  28. package/wwwroot/_content/MindExecution.Shared/css/app.css +1 -0
  29. package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +2936 -0
  30. package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Bold.ttf +0 -0
  31. package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Regular.ttf +0 -0
  32. package/wwwroot/_content/MindExecution.Shared/js/agent-visualization.js +359 -0
  33. package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1721 -0
  34. package/wwwroot/_content/MindExecution.Shared/js/code-master.js +8316 -0
  35. package/wwwroot/_content/MindExecution.Shared/js/file-system-helper.js +639 -0
  36. package/wwwroot/_content/MindExecution.Shared/js/helpers/InfiniteGridHelper.js +109 -0
  37. package/wwwroot/_content/MindExecution.Shared/js/marked.min.js +69 -0
  38. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +7982 -0
  39. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js.backup +1059 -0
  40. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +15803 -0
  41. package/wwwroot/_content/MindExecution.Shared/js/mind-map-dev-guards.js +325 -0
  42. package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js +1430 -0
  43. package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js.bak +434 -0
  44. package/wwwroot/_content/MindExecution.Shared/js/mind-map-glow-shader.js +260 -0
  45. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +7640 -0
  46. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +160 -0
  47. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +9923 -0
  48. package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +977 -0
  49. package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +1431 -0
  50. package/wwwroot/_content/MindExecution.Shared/js/mind-map-multi-select.js +1716 -0
  51. package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +553 -0
  52. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +4541 -0
  53. package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js +489 -0
  54. package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js.backup +372 -0
  55. package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +2075 -0
  56. package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-lod-system.js +646 -0
  57. package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-overlay-v2.js +4323 -0
  58. package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +2260 -0
  59. package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js.backup +1258 -0
  60. package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +890 -0
  61. package/wwwroot/_content/MindExecution.Shared/js/mindmap-toolbar.js +594 -0
  62. package/wwwroot/_content/MindExecution.Shared/js/native-drop-handler.js +170 -0
  63. package/wwwroot/_content/MindExecution.Shared/js/plan-master.js +788 -0
  64. package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +50 -0
  65. package/wwwroot/_content/MindExecution.Shared/js/texture-worker-manager.js +188 -0
  66. package/wwwroot/_content/MindExecution.Shared/js/texture-worker.js +208 -0
  67. package/wwwroot/_content/MindExecution.Shared/js/three.min.js +6 -0
  68. package/wwwroot/_content/MindExecution.Shared/js/titlebar-handler.js +191 -0
  69. package/wwwroot/_content/MindExecution.Shared/js/token-manager.js +37 -0
  70. package/wwwroot/_content/MindExecution.Shared/js/token-worker.js +28 -0
  71. package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js +5626 -0
  72. package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js.map +7 -0
  73. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/css/all.min.css +9 -0
  74. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-brands-400.ttf +0 -0
  75. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-brands-400.woff2 +0 -0
  76. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-regular-400.ttf +0 -0
  77. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-regular-400.woff2 +0 -0
  78. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-solid-900.ttf +0 -0
  79. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-solid-900.woff2 +0 -0
  80. package/wwwroot/_content/MindExecution.Shared/models/all-MiniLM-L6-v2-quantized.onnx +0 -0
  81. package/wwwroot/_content/MindExecution.Shared/models/vocab.txt +30522 -0
  82. package/wwwroot/_framework/Google.Protobuf.9h59ukbel7.dll +0 -0
  83. package/wwwroot/_framework/Markdig.d1j7v41cl1.dll +0 -0
  84. package/wwwroot/_framework/MessagePack.Annotations.l6qv48kgpt.dll +0 -0
  85. package/wwwroot/_framework/MessagePack.eqoptzx9d5.dll +0 -0
  86. package/wwwroot/_framework/Microsoft.AspNetCore.Authorization.k7dsih5y5g.dll +0 -0
  87. package/wwwroot/_framework/Microsoft.AspNetCore.Components.6nyje9sa0g.dll +0 -0
  88. package/wwwroot/_framework/Microsoft.AspNetCore.Components.Authorization.iycd6unprw.dll +0 -0
  89. package/wwwroot/_framework/Microsoft.AspNetCore.Components.Web.487u3twia4.dll +0 -0
  90. package/wwwroot/_framework/Microsoft.AspNetCore.Components.WebAssembly.d0gcnmlxxz.dll +0 -0
  91. package/wwwroot/_framework/Microsoft.AspNetCore.Metadata.h4yevl9adi.dll +0 -0
  92. package/wwwroot/_framework/Microsoft.CSharp.qrvp77qmhs.dll +0 -0
  93. package/wwwroot/_framework/Microsoft.Data.Sqlite.jdlxgv2jtg.dll +0 -0
  94. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.4gjazp7kjf.dll +0 -0
  95. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Abstractions.gocudnvz7b.dll +0 -0
  96. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Relational.lt4rsvinuo.dll +0 -0
  97. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Sqlite.69luj0fa9r.dll +0 -0
  98. package/wwwroot/_framework/Microsoft.Extensions.Caching.Abstractions.364t4jh3zz.dll +0 -0
  99. package/wwwroot/_framework/Microsoft.Extensions.Caching.Memory.izlxhpzosu.dll +0 -0
  100. package/wwwroot/_framework/Microsoft.Extensions.Configuration.8zq7hh41o7.dll +0 -0
  101. package/wwwroot/_framework/Microsoft.Extensions.Configuration.Abstractions.8if74zs6ea.dll +0 -0
  102. package/wwwroot/_framework/Microsoft.Extensions.Configuration.Json.duvlngw8i0.dll +0 -0
  103. package/wwwroot/_framework/Microsoft.Extensions.DependencyInjection.Abstractions.t2hh9kvx0o.dll +0 -0
  104. package/wwwroot/_framework/Microsoft.Extensions.DependencyInjection.n4tg99oy8l.dll +0 -0
  105. package/wwwroot/_framework/Microsoft.Extensions.DependencyModel.h0d06ixk3e.dll +0 -0
  106. package/wwwroot/_framework/Microsoft.Extensions.Logging.Abstractions.rl32bkx2sd.dll +0 -0
  107. package/wwwroot/_framework/Microsoft.Extensions.Logging.dlht1xei0t.dll +0 -0
  108. package/wwwroot/_framework/Microsoft.Extensions.Options.qeunebioml.dll +0 -0
  109. package/wwwroot/_framework/Microsoft.Extensions.Primitives.18cr6vnuuz.dll +0 -0
  110. package/wwwroot/_framework/Microsoft.IO.RecyclableMemoryStream.r915vovvw4.dll +0 -0
  111. package/wwwroot/_framework/Microsoft.IdentityModel.Abstractions.1ejljk3erv.dll +0 -0
  112. package/wwwroot/_framework/Microsoft.IdentityModel.JsonWebTokens.1596zr8gne.dll +0 -0
  113. package/wwwroot/_framework/Microsoft.IdentityModel.Logging.229uyvpgio.dll +0 -0
  114. package/wwwroot/_framework/Microsoft.IdentityModel.Tokens.9sibtajc9f.dll +0 -0
  115. package/wwwroot/_framework/Microsoft.JSInterop.17lq4j1j7g.dll +0 -0
  116. package/wwwroot/_framework/Microsoft.JSInterop.WebAssembly.ryia5gxiad.dll +0 -0
  117. package/wwwroot/_framework/Microsoft.ML.OnnxRuntime.w9deo1m5ss.dll +0 -0
  118. package/wwwroot/_framework/Microsoft.ML.Tokenizers.cm2vuv2z61.dll +0 -0
  119. package/wwwroot/_framework/Microsoft.NET.StringTools.3qbrf4v2ki.dll +0 -0
  120. package/wwwroot/_framework/MimeMapping.og9ys58ylm.dll +0 -0
  121. package/wwwroot/_framework/MindExecution.Core.1q1trifbuu.dll +0 -0
  122. package/wwwroot/_framework/MindExecution.Kernel.gwwc40sc45.dll +0 -0
  123. package/wwwroot/_framework/MindExecution.Plugins.Admin.0jgrn1sckv.dll +0 -0
  124. package/wwwroot/_framework/MindExecution.Plugins.Business.13mme2qcag.dll +0 -0
  125. package/wwwroot/_framework/MindExecution.Plugins.Concept.dfp2mdt45q.dll +0 -0
  126. package/wwwroot/_framework/MindExecution.Plugins.Directory.3w4t6n3se0.dll +0 -0
  127. package/wwwroot/_framework/MindExecution.Plugins.PlanMaster.s0qpntz420.dll +0 -0
  128. package/wwwroot/_framework/MindExecution.Plugins.YouTube.iu11fq8d16.dll +0 -0
  129. package/wwwroot/_framework/MindExecution.Shared.7j27dcqnrc.dll +0 -0
  130. package/wwwroot/_framework/MindExecution.Web.pq1ty8ov2v.dll +0 -0
  131. package/wwwroot/_framework/Newtonsoft.Json.a56zs13vug.dll +0 -0
  132. package/wwwroot/_framework/SQLitePCLRaw.batteries_v2.rrd1nzawpp.dll +0 -0
  133. package/wwwroot/_framework/SQLitePCLRaw.core.1dxloztpfz.dll +0 -0
  134. package/wwwroot/_framework/SQLitePCLRaw.provider.e_sqlite3.oekyzl53i1.dll +0 -0
  135. package/wwwroot/_framework/Supabase.Core.s1pkj4aj0l.dll +0 -0
  136. package/wwwroot/_framework/Supabase.Functions.qz4nu782sg.dll +0 -0
  137. package/wwwroot/_framework/Supabase.Gotrue.twah27pkik.dll +0 -0
  138. package/wwwroot/_framework/Supabase.Postgrest.gmuuv369ih.dll +0 -0
  139. package/wwwroot/_framework/Supabase.Realtime.ox3kchdy3w.dll +0 -0
  140. package/wwwroot/_framework/Supabase.Storage.fnjnepaowr.dll +0 -0
  141. package/wwwroot/_framework/Supabase.azmaw5pgcz.dll +0 -0
  142. package/wwwroot/_framework/System.Collections.Concurrent.y1zmvuyipi.dll +0 -0
  143. package/wwwroot/_framework/System.Collections.Immutable.ug3j698qms.dll +0 -0
  144. package/wwwroot/_framework/System.Collections.NonGeneric.h66hj3863h.dll +0 -0
  145. package/wwwroot/_framework/System.Collections.Specialized.umr3y27ntj.dll +0 -0
  146. package/wwwroot/_framework/System.Collections.x53e19vfsj.dll +0 -0
  147. package/wwwroot/_framework/System.ComponentModel.Annotations.tz6gnt4ebt.dll +0 -0
  148. package/wwwroot/_framework/System.ComponentModel.Primitives.j7tiphu4rg.dll +0 -0
  149. package/wwwroot/_framework/System.ComponentModel.TypeConverter.ujlztox1gx.dll +0 -0
  150. package/wwwroot/_framework/System.ComponentModel.x9xz0ojfb6.dll +0 -0
  151. package/wwwroot/_framework/System.Console.ijzpqmj7ne.dll +0 -0
  152. package/wwwroot/_framework/System.Data.Common.1r0sqffq1p.dll +0 -0
  153. package/wwwroot/_framework/System.Diagnostics.DiagnosticSource.9upoqwq09o.dll +0 -0
  154. package/wwwroot/_framework/System.Diagnostics.Process.m99azzntjm.dll +0 -0
  155. package/wwwroot/_framework/System.Diagnostics.TraceSource.pl7wv26myr.dll +0 -0
  156. package/wwwroot/_framework/System.Diagnostics.Tracing.crlhfx6tut.dll +0 -0
  157. package/wwwroot/_framework/System.Drawing.Primitives.22e4y9ikq9.dll +0 -0
  158. package/wwwroot/_framework/System.Drawing.mi7d8hwowb.dll +0 -0
  159. package/wwwroot/_framework/System.Formats.Asn1.jx23sjiqnn.dll +0 -0
  160. package/wwwroot/_framework/System.IO.Compression.6fyoii3uej.dll +0 -0
  161. package/wwwroot/_framework/System.IO.Pipelines.vg77t4cd4d.dll +0 -0
  162. package/wwwroot/_framework/System.IdentityModel.Tokens.Jwt.t67es60z5b.dll +0 -0
  163. package/wwwroot/_framework/System.Linq.1bkoxlqgmq.dll +0 -0
  164. package/wwwroot/_framework/System.Linq.Expressions.24xqiypwdt.dll +0 -0
  165. package/wwwroot/_framework/System.Linq.Queryable.hvd01d6rsa.dll +0 -0
  166. package/wwwroot/_framework/System.Memory.8dx3lwgym4.dll +0 -0
  167. package/wwwroot/_framework/System.Net.Http.Json.3mhdm9l1rf.dll +0 -0
  168. package/wwwroot/_framework/System.Net.Http.eitrz660my.dll +0 -0
  169. package/wwwroot/_framework/System.Net.NetworkInformation.3pkuofcv9r.dll +0 -0
  170. package/wwwroot/_framework/System.Net.Ping.8clj5pklrp.dll +0 -0
  171. package/wwwroot/_framework/System.Net.Primitives.qrp4wcjz1p.dll +0 -0
  172. package/wwwroot/_framework/System.Net.WebSockets.Client.2u6pv01g69.dll +0 -0
  173. package/wwwroot/_framework/System.Net.WebSockets.qp6u31zvm5.dll +0 -0
  174. package/wwwroot/_framework/System.Numerics.Tensors.0c7z4mt3on.dll +0 -0
  175. package/wwwroot/_framework/System.Numerics.Vectors.kc7ufp2j4l.dll +0 -0
  176. package/wwwroot/_framework/System.ObjectModel.qv82fot1ib.dll +0 -0
  177. package/wwwroot/_framework/System.Private.CoreLib.rkafq04oma.dll +0 -0
  178. package/wwwroot/_framework/System.Private.Uri.t9542hmr6j.dll +0 -0
  179. package/wwwroot/_framework/System.Private.Xml.Linq.n8n3ptrbwu.dll +0 -0
  180. package/wwwroot/_framework/System.Private.Xml.rxd3tytisn.dll +0 -0
  181. package/wwwroot/_framework/System.Reactive.t3fuon548l.dll +0 -0
  182. package/wwwroot/_framework/System.Reflection.Emit.9tjhp6y0j3.dll +0 -0
  183. package/wwwroot/_framework/System.Reflection.Emit.ILGeneration.stxyk8zoo1.dll +0 -0
  184. package/wwwroot/_framework/System.Reflection.Emit.Lightweight.6xrd5v8vg0.dll +0 -0
  185. package/wwwroot/_framework/System.Reflection.Primitives.wgn8fpwwvv.dll +0 -0
  186. package/wwwroot/_framework/System.Runtime.InteropServices.JavaScript.sliym526xh.dll +0 -0
  187. package/wwwroot/_framework/System.Runtime.InteropServices.RuntimeInformation.oji7zut14z.dll +0 -0
  188. package/wwwroot/_framework/System.Runtime.InteropServices.te07xr2we9.dll +0 -0
  189. package/wwwroot/_framework/System.Runtime.Intrinsics.507y4h8nzq.dll +0 -0
  190. package/wwwroot/_framework/System.Runtime.Loader.v7gk4bse0k.dll +0 -0
  191. package/wwwroot/_framework/System.Runtime.Numerics.eqy5xjv3nd.dll +0 -0
  192. package/wwwroot/_framework/System.Runtime.Serialization.Formatters.zpkrub8lab.dll +0 -0
  193. package/wwwroot/_framework/System.Runtime.Serialization.Primitives.vhkpnbxjip.dll +0 -0
  194. package/wwwroot/_framework/System.Runtime.jn319d5nyg.dll +0 -0
  195. package/wwwroot/_framework/System.Security.Claims.0ztig1q9vo.dll +0 -0
  196. package/wwwroot/_framework/System.Security.Cryptography.vttizqc9ho.dll +0 -0
  197. package/wwwroot/_framework/System.Text.Encoding.Extensions.utdd47ny8f.dll +0 -0
  198. package/wwwroot/_framework/System.Text.Encodings.Web.wah8r1zoe0.dll +0 -0
  199. package/wwwroot/_framework/System.Text.Json.kxlfxj0wrs.dll +0 -0
  200. package/wwwroot/_framework/System.Text.RegularExpressions.dbqn58klox.dll +0 -0
  201. package/wwwroot/_framework/System.Threading.42ao9vi047.dll +0 -0
  202. package/wwwroot/_framework/System.Threading.Channels.hfa7j0uv2w.dll +0 -0
  203. package/wwwroot/_framework/System.Threading.Thread.caul0pdqul.dll +0 -0
  204. package/wwwroot/_framework/System.Transactions.Local.fimi2hamzo.dll +0 -0
  205. package/wwwroot/_framework/System.Web.HttpUtility.gq8yz50p2e.dll +0 -0
  206. package/wwwroot/_framework/System.Xml.Linq.kitin4zjoj.dll +0 -0
  207. package/wwwroot/_framework/System.Xml.ReaderWriter.kzvw3qgxb0.dll +0 -0
  208. package/wwwroot/_framework/System.Xml.XDocument.c539ki6cuq.dll +0 -0
  209. package/wwwroot/_framework/System.m05i39uvk9.dll +0 -0
  210. package/wwwroot/_framework/Websocket.Client.vapounvmnl.dll +0 -0
  211. package/wwwroot/_framework/blazor.boot.json +305 -0
  212. package/wwwroot/_framework/blazor.webassembly.js +1 -0
  213. package/wwwroot/_framework/dotnet.js +4 -0
  214. package/wwwroot/_framework/dotnet.native.vz0adxojrz.wasm +0 -0
  215. package/wwwroot/_framework/dotnet.native.xsn1d6x2kd.js +16 -0
  216. package/wwwroot/_framework/dotnet.runtime.dstopyvqzi.js +4 -0
  217. package/wwwroot/_framework/icudt_CJK.tjcz0u77k5.dat +0 -0
  218. package/wwwroot/_framework/icudt_EFIGS.tptq2av103.dat +0 -0
  219. package/wwwroot/_framework/icudt_no_CJK.lfu7j35m59.dat +0 -0
  220. package/wwwroot/_framework/netstandard.0xet7jg7ky.dll +0 -0
  221. package/wwwroot/_headers +40 -0
  222. package/wwwroot/_redirects +1 -0
  223. package/wwwroot/appsettings.json +71 -0
  224. package/wwwroot/icon-192.png +0 -0
  225. package/wwwroot/icon-512.png +0 -0
  226. package/wwwroot/index.html +710 -0
  227. package/wwwroot/js/marketing-tool.js +180 -0
  228. package/wwwroot/manifest.webmanifest +22 -0
  229. package/wwwroot/robots.txt +4 -0
  230. package/wwwroot/service-worker-assets.js +857 -0
  231. package/wwwroot/service-worker.js +33 -0
  232. package/wwwroot/sitemap.xml +27 -0
@@ -0,0 +1,1059 @@
1
+ // File: mind-map-core.js
2
+ // MindMap3D and component interface - core logic (simplified: style queue removed)
3
+ (function () {
4
+ if (typeof MindMapInteractions === 'undefined' ||
5
+ typeof MindMapNodes === 'undefined' ||
6
+ typeof MindMapDnD === 'undefined' ||
7
+ typeof globalThis.THREE === 'undefined' ||
8
+ typeof window.MindMapThemes === 'undefined') {
9
+ console.error('[mind-map-core.js] Failed: dependencies not loaded.');
10
+ return;
11
+ }
12
+
13
+ let moduleInstance = null;
14
+
15
+ class MindMapModule {
16
+ constructor() {
17
+ this.scene = null;
18
+ this.camera = null;
19
+ this.renderer = null;
20
+ this.cssRenderer = null; // ▼▼▼ [Added] CSS3DRenderer reference ▼▼▼
21
+ this.dotNetHelper = null;
22
+ this.container = null;
23
+ this.animationFrameId = null;
24
+ this.nodeObjectsById = new Map();
25
+ this.lodUpdateQueue = []; // Initialize lodUpdateQueue
26
+ this.isPanning = false;
27
+ this.lastMousePos = { x: 0, y: 0 };
28
+ this.targetZ = null;
29
+ this.targetX = null;
30
+ this.targetY = null;
31
+ this.isDraggingNode = false;
32
+ this.draggedNodeObject = null;
33
+ this.startMousePos = { x: 0, y: 0 };
34
+ this.wasDragged = false;
35
+ this.clickStartTime = 0;
36
+ this.selectedNodeIdJs = null;
37
+ this.raycaster = null;
38
+ this.pointer = null;
39
+ this.plane = null;
40
+ this.intersectPoint = new THREE.Vector3();
41
+ this.dragOffset = new THREE.Vector3();
42
+ this.isSelecting = false;
43
+ this.isShiftSelecting = false;
44
+ this.selectionRectStartPos = { x: 0, y: 0 };
45
+ this.selectionRectElement = null;
46
+ this.isDraggingMultipleNodes = false;
47
+ this.multiSelectBox = null;
48
+ // ▼▼▼ [Changed] Initialize cursor position ▼▼▼
49
+ this.cursorPosition = new THREE.Vector3(0, 0, 0);
50
+ // ▲▲▲ [Changed] ▲▲▲
51
+ this.cursorMesh = null;
52
+ this.cursorMaterial = null;
53
+ this.cursorLineMaterial = null;
54
+
55
+ // ▼▼▼ [Changed] Initialize multi-drag related fields ▼▼▼
56
+ this.multiDragInitialPositions = new Map(); // Naming aligned (previous: initialMultiSelectPositions)
57
+ this.clickedNodeInitialPos = new THREE.Vector3(); // Fix: missing initialization
58
+ // ▲▲▲ [Changed] ▲▲▲
59
+
60
+ this.nodeZCounter = 1; // renderOrder counter
61
+
62
+ // ▼▼▼ [Core addition] Flag to track Note node editing state ▼▼▼
63
+ this.isEditingNote = false;
64
+ // ▲▲▲ [Core addition] ▲▲▲
65
+
66
+ // ▼▼▼ [Core addition] Multi-selection and spatial grid initialization ▼▼▼
67
+ this.multiSelectedNodeIds = new Set();
68
+ this.spatialGrid = new Map();
69
+ this.GRID_CELL_SIZE = 500;
70
+ // ▼▼▼ [Changed] 25 -> 10 (match background-themes.js 'XYGrid' size1) ▼▼▼
71
+ this.GRID_SIZE = 10;
72
+ // ▲▲▲ [Changed] ▲▲▲
73
+
74
+ // ▼▼▼ [Optimization] Add render-loop optimization fields ▼▼▼
75
+ this.frameCount = 0;
76
+ this.lastCameraPos = new THREE.Vector3();
77
+ this.lastCameraRot = new THREE.Euler();
78
+ // ▲▲▲ [Optimization] ▲▲▲
79
+
80
+ // ▼▼▼ [New] Texture Atlas Manager for batched rendering ▼▼▼
81
+ this.atlasManager = null; // Lazy init when needed
82
+ this.useAtlasRendering = false; // Feature flag - enable when stable
83
+ // ▲▲▲ [New] ▲▲▲
84
+ }
85
+
86
+ // Cursor snap function (keep legacy behavior: floor/ceil)
87
+ snapToGrid(v) { return Math.floor(v / this.GRID_SIZE) * this.GRID_SIZE; }
88
+ snapToGridY(v) { return Math.ceil(v / this.GRID_SIZE) * this.GRID_SIZE; }
89
+
90
+ // ▼▼▼ [New] Node snap function - use Math.round to snap to nearest grid ▼▼▼
91
+ snapNodeToGrid(v) { return Math.round(v / this.GRID_SIZE) * this.GRID_SIZE; }
92
+ // ▲▲▲ [New] ▲▲▲
93
+
94
+ init(containerId, helper) {
95
+ // Re-activate
96
+ if (this.container) {
97
+ const target = document.getElementById(containerId);
98
+ if (target && this.renderer) {
99
+ try {
100
+ if (this.renderer.domElement.parentElement) {
101
+ this.renderer.domElement.parentElement.removeChild(this.renderer.domElement);
102
+ }
103
+ // ▼▼▼ [Added] Also remove the CSS3D canvas from the DOM ▼▼▼
104
+ if (this.cssRenderer && this.cssRenderer.domElement.parentElement) {
105
+ this.cssRenderer.domElement.parentElement.removeChild(this.cssRenderer.domElement);
106
+ }
107
+ // ▲▲▲ [Added] ▲▲▲
108
+ } catch { }
109
+ target.appendChild(this.renderer.domElement);
110
+ // ▼▼▼ [Critical fix] Re-add missing CSS3D canvas ▼▼▼
111
+ if (this.cssRenderer) {
112
+ target.appendChild(this.cssRenderer.domElement);
113
+ }
114
+ // ▲▲▲ [Critical fix] ▲▲▲
115
+ this.container = target;
116
+ this.dotNetHelper = helper;
117
+ window.dotNetHelper = helper; // Expose globally for NativeDropHandler access
118
+ target.style.display = 'block';
119
+ MindMapInteractions.onResize(this);
120
+ if (!this.animationFrameId) this.animate();
121
+ console.log(`[mind-map-core.js] Re-activated for #${containerId}.`);
122
+ return;
123
+ }
124
+ }
125
+
126
+ // Initialize
127
+ this.dotNetHelper = helper;
128
+ window.dotNetHelper = helper; // Expose globally for NativeDropHandler access
129
+ this.container = document.getElementById(containerId);
130
+ if (!this.container) {
131
+ console.error('[mind-map-core.js] Container not found!');
132
+ return;
133
+ }
134
+ this.container.style.display = 'block';
135
+ const THREE = globalThis.THREE;
136
+ this.scene = new THREE.Scene();
137
+ this.debugGroup = new THREE.Group();
138
+ this.scene.add(this.debugGroup);
139
+
140
+ // ▼▼▼ [Cursor creation] Mesh + Line hybrid -----
141
+ this.cursorMesh = new THREE.Group();
142
+ const cursorSize = 20; // Cursor length
143
+ const thickness = 4; // Cursor thickness
144
+
145
+ // Mesh material (background / thickness)
146
+ this.cursorMaterial = new THREE.MeshBasicMaterial({
147
+ color: 0xffffff,
148
+ transparent: true,
149
+ opacity: 1.0,
150
+ depthTest: false
151
+ });
152
+
153
+ // Line material (centerline)
154
+ this.cursorLineMaterial = new THREE.LineBasicMaterial({
155
+ color: 0xffffff,
156
+ transparent: false,
157
+ opacity: 1,
158
+ depthTest: false
159
+ });
160
+
161
+ // Mesh geometry (vertical/horizontal bars)
162
+ const vGeo = new THREE.BoxGeometry(thickness, cursorSize, 1);
163
+ const vMesh = new THREE.Mesh(vGeo, this.cursorMaterial);
164
+ vMesh.position.set(thickness / 2, -cursorSize / 2, 0);
165
+ vMesh.renderOrder = 9998;
166
+
167
+ const hGeo = new THREE.BoxGeometry(cursorSize, thickness, 1);
168
+ const hMesh = new THREE.Mesh(hGeo, this.cursorMaterial);
169
+ hMesh.position.set(cursorSize / 2, -thickness / 2, 0);
170
+ hMesh.renderOrder = 9998;
171
+
172
+ this.cursorMesh.add(vMesh);
173
+ this.cursorMesh.add(hMesh);
174
+
175
+ // Line geometry (position adjusted)
176
+ const lineGeo = new THREE.BufferGeometry().setFromPoints([
177
+ new THREE.Vector3(0, -cursorSize, 0),
178
+ new THREE.Vector3(0, 0, 0),
179
+ new THREE.Vector3(cursorSize, 0, 0)
180
+ ]);
181
+ const line = new THREE.Line(lineGeo, this.cursorLineMaterial);
182
+ line.position.z = 0.6;
183
+ line.renderOrder = 9999;
184
+ this.cursorMesh.add(line);
185
+
186
+ this.cursorMesh.position.set(0, 0, 1);
187
+ this.cursorMesh.renderOrder = 9999;
188
+ this.scene.add(this.cursorMesh);
189
+
190
+ if (this.updateCursorPosition) {
191
+ this.updateCursorPosition(0, 0);
192
+ }
193
+ // ▲▲▲ [Cursor creation] ▲▲▲
194
+
195
+ const NEAR = 0.1, FAR = 1e7;
196
+ this.camera = new THREE.PerspectiveCamera(50, this.container.clientWidth / this.container.clientHeight, NEAR, FAR);
197
+ const initialZ = 1200;
198
+ this.camera.position.z = initialZ;
199
+ this.targetZ = initialZ;
200
+ this.targetX = this.camera.position.x;
201
+ this.targetY = this.camera.position.y;
202
+
203
+ // ▼▼▼ [Changed] Add fallback logic if WebGLRenderer init fails (context limits, etc.) ▼▼▼
204
+ try {
205
+ this.renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true, alpha: true });
206
+ } catch (e) {
207
+ console.warn('[MindMap] WebGL context creation failed with advanced settings. Retrying with defaults.', e);
208
+ try {
209
+ this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
210
+ } catch (e2) {
211
+ console.warn('[MindMap] WebGL context creation failed with antialias. Retrying plain.', e2);
212
+ this.renderer = new THREE.WebGLRenderer({ alpha: true });
213
+ }
214
+ }
215
+ // ▲▲▲ [Changed] ▲▲▲
216
+
217
+ this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
218
+ this.renderer.setPixelRatio(window.devicePixelRatio || 1);
219
+ this.renderer.setClearColor(0x000000, 0); // Transparent background for Space3D theme
220
+ // [Changed] Z-Index: 1, pointerEvents: 'none' (same as CodeMaster)
221
+ Object.assign(this.renderer.domElement.style, { position: 'absolute', top: 0, left: 0, zIndex: 1, pointerEvents: 'none' });
222
+ this.container.appendChild(this.renderer.domElement);
223
+
224
+ // ▼▼▼ [Changed] CSS3DRenderer initialization (same as CodeMaster) ▼▼▼
225
+ if (globalThis.CSS3DRenderer) {
226
+ this.cssRenderer = new globalThis.CSS3DRenderer();
227
+ this.cssRenderer.setSize(this.container.clientWidth, this.container.clientHeight);
228
+ // [Changed] Z-Index: 2 (top-most), pointerEvents: 'auto' (receive all events)
229
+ Object.assign(this.cssRenderer.domElement.style, {
230
+ position: 'absolute',
231
+ top: 0,
232
+ left: 0,
233
+ zIndex: 2,
234
+ pointerEvents: 'auto'
235
+ });
236
+ this.container.appendChild(this.cssRenderer.domElement);
237
+ console.log('[mind-map-core.js] CSS3DRenderer initialized (Z-Index: 2, pointerEvents: auto)');
238
+ } else {
239
+ console.warn('[mind-map-core.js] CSS3DRenderer not available');
240
+ }
241
+ // ▲▲▲ [Changed] ▲▲▲
242
+
243
+ this.raycaster = new THREE.Raycaster();
244
+ this.pointer = new THREE.Vector2();
245
+ this.plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
246
+ this.selectionRectElement = document.getElementById('selection-rectangle');
247
+
248
+ this.setBackgroundTheme('Space3D');
249
+
250
+ MindMapInteractions.addEventListeners(this);
251
+ MindMapDnD.addEventListeners(this);
252
+
253
+ this.animate();
254
+ // ▼▼▼ [Added] Initialize MenuManager ▼▼▼
255
+ if (window.MindMapMenuManager) {
256
+ window.MindMapMenuManager.init(this);
257
+ }
258
+ // ▲▲▲ [Added] ▲▲▲
259
+ // ▼▼▼ [Added] Initialize MultiSelect ▼▼▼
260
+ if (window.MindMapMultiSelect) {
261
+ window.MindMapMultiSelect.init(this);
262
+ }
263
+ // ▲▲▲ [Added] ▲▲▲
264
+
265
+ // ▼▼▼ [New] Initialize Atlas Manager (can be enabled later via enableAtlasRendering) ▼▼▼
266
+ if (window.TextureAtlasManager) {
267
+ this.initAtlasManager({ atlasSize: 4096, maxAtlasPages: 16 });
268
+ console.log('[mind-map-core.js] TextureAtlasManager ready (enable with mindMap.enableAtlasRendering(true))');
269
+ }
270
+ // ▲▲▲ [New] ▲▲▲
271
+
272
+ // ▼▼▼ [New] Initialize LOD Renderer for massive node counts ▼▼▼
273
+ // ▼▼▼ [TEMP DISABLED] LOD Renderer initialization to test if it causes rendering issues ▼▼▼
274
+ /*
275
+ if (window.LODRenderer) {
276
+ this.lodRenderer = new window.LODRenderer(this.renderer, this.scene);
277
+ console.log('[mind-map-core.js] LODRenderer ready (enable with mindMap.enableLOD(true))');
278
+ }
279
+ */
280
+ this.lodRenderer = null;
281
+ this.useLODRendering = false; // Disabled for testing - enable with mindMap.enableLOD(true)
282
+ // ▲▲▲ [TEMP DISABLED] ▲▲▲
283
+
284
+ console.log(`[mind-map-core.js] Initialized for #${containerId}.`);
285
+ }
286
+
287
+ setBackgroundTheme(themeName) {
288
+ if (themeName === this.currentThemeName || !this.scene || !window.MindMapThemes) return;
289
+ const newTheme = window.MindMapThemes.getTheme(themeName);
290
+ if (!newTheme) { console.warn(`[mind-map-core.js] Theme '${themeName}' not found.`); return; }
291
+ if (this.currentThemeObject) {
292
+ const oldTheme = window.MindMapThemes.getTheme(this.currentThemeName);
293
+ try { oldTheme?.dispose(this.currentThemeObject); } catch { }
294
+ if (this.scene) this.scene.remove(this.currentThemeObject);
295
+ }
296
+ this.currentThemeObject = newTheme.create(this.scene);
297
+ if (this.currentThemeObject) this.scene.add(this.currentThemeObject);
298
+ this.currentThemeName = themeName;
299
+ }
300
+
301
+ cullAndLOD() {
302
+ // ▼▼▼ [Optimization] Frustum culling ▼▼▼
303
+ // Nodes outside the camera frustum have visible=false, reducing draw calls
304
+ if (!this.camera || !this.scene) return;
305
+
306
+ // 1. Create a frustum and update it from the current camera projection matrix.
307
+ const frustum = new THREE.Frustum();
308
+ const projScreenMatrix = new THREE.Matrix4();
309
+ projScreenMatrix.multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse);
310
+ frustum.setFromProjectionMatrix(projScreenMatrix);
311
+
312
+ // Check if LOD is controlling visibility
313
+ const lodControlsVisibility = this.useLODRendering && this.lodRenderer?.isInLODMode;
314
+
315
+ // 2. Iterate all nodes and check whether they are inside the frustum.
316
+ this.nodeObjectsById.forEach((nodeEntry, nodeId) => {
317
+ const glObject = nodeEntry.glObject;
318
+ if (!glObject || !glObject.userData) return;
319
+
320
+ // Compute a node bounding box.
321
+ const width = glObject.userData.worldWidth || 300;
322
+ const height = glObject.userData.worldHeight || 200;
323
+ const nodeBox = new THREE.Box3(
324
+ new THREE.Vector3(glObject.position.x, glObject.position.y - height, glObject.position.z),
325
+ new THREE.Vector3(glObject.position.x + width, glObject.position.y, glObject.position.z)
326
+ );
327
+
328
+ // 3. Determine visibility by checking frustum-box intersection.
329
+ const isVisible = frustum.intersectsBox(nodeBox);
330
+
331
+ // 4. Control visibility for both WebGL and CSS3D objects.
332
+ // Skip glObject visibility if LOD is controlling it
333
+ if (!lodControlsVisibility && glObject.visible !== isVisible) {
334
+ glObject.visible = isVisible;
335
+ }
336
+ if (nodeEntry.cssObject && nodeEntry.cssObject.visible !== (isVisible && nodeEntry.currentType === 'CSS')) {
337
+ nodeEntry.cssObject.visible = (isVisible && nodeEntry.currentType === 'CSS');
338
+ }
339
+ });
340
+ // ▲▲▲ [Optimization] ▲▲▲
341
+ }
342
+
343
+ animate() {
344
+ const loop = async () => {
345
+ this.animationFrameId = requestAnimationFrame(loop);
346
+
347
+ // Rendering always runs (camera interpolation, etc.)
348
+ // During resize the canvas is hidden and not visible to the user
349
+
350
+ if (this.currentThemeObject && typeof this.currentThemeObject.animate === 'function') {
351
+ this.currentThemeObject.animate(this.camera);
352
+ }
353
+
354
+ const s = 0.25;
355
+ const oldPos = this.camera.position.clone(); // Position before movement
356
+
357
+ this.camera.position.x += (this.targetX - this.camera.position.x) * s;
358
+ this.camera.position.y += (this.targetY - this.camera.position.y) * s;
359
+ this.camera.position.z += (this.targetZ - this.camera.position.z) * s;
360
+
361
+ // ▼▼▼ [Optimization] If the camera barely moved, skip movement work (sleep mode) ▼▼▼
362
+ // (avoid unnecessary matrix updates)
363
+ const deltaMove = this.camera.position.distanceToSquared(oldPos);
364
+ const isCameraMoving = deltaMove > 0.0001;
365
+
366
+ // ▼▼▼ [Changed] Do not sync node positions during camera movement (Zoom/Pan) ▼▼▼
367
+ // Reason: PageUp/Down or wheel zoom moves only the camera.
368
+ // Forcing WebGL/CSS coordinate sync here can introduce numeric drift and cause nodes to "jump".
369
+
370
+ // [Changed] Only sync during drag operations (when nodes actually move)
371
+ if (this.isDraggingNode || this.isDraggingMultipleNodes) {
372
+ this.nodeObjectsById.forEach((nodeEntry) => {
373
+ if (nodeEntry.currentType === 'CSS' && nodeEntry.cssObject && nodeEntry.glObject) {
374
+ if (!this.isEditingNote) {
375
+ nodeEntry.glObject.position.copy(nodeEntry.cssObject.position);
376
+ }
377
+ }
378
+ });
379
+ }
380
+ // ▲▲▲ [Changed] ▲▲▲
381
+
382
+ // ▼▼▼ [New] LOD Rendering for massive node counts ▼▼▼
383
+ if (this.useLODRendering && this.lodRenderer) {
384
+ this.lodRenderer.updateLOD(this.camera, this.nodeObjectsById);
385
+ }
386
+ // ▲▲▲ [New] ▲▲▲
387
+
388
+ // ▼▼▼ [Optimization] Throttle frustum culling & LOD frequency ▼▼▼
389
+ // Run only when the camera moves, or once every 10 frames
390
+ this.frameCount = (this.frameCount || 0) + 1;
391
+ if (isCameraMoving || this.frameCount % 10 === 0) {
392
+ this.cullAndLOD();
393
+ }
394
+ // ▲▲▲ [Optimization] ▲▲▲
395
+
396
+ if (this.cursorMaterial) {
397
+ const time = Date.now() * 0.005;
398
+ const val = (Math.sin(time) * 0.5) + 0.5;
399
+ this.cursorMaterial.color.setRGB(val, val, val);
400
+ if (this.cursorLineMaterial) {
401
+ this.cursorLineMaterial.color.setRGB(val, val, val);
402
+ }
403
+ }
404
+ this.renderer.render(this.scene, this.camera);
405
+
406
+ if (this.cssRenderer) {
407
+ this.cssRenderer.render(this.scene, this.camera);
408
+ }
409
+
410
+ // ▼▼▼ [Added] Sync menu position ▼▼▼
411
+ if (window.MindMapMenuManager) {
412
+ window.MindMapMenuManager.update();
413
+ }
414
+ // ▲▲▲ [Added] ▲▲▲
415
+ // ▼▼▼ [Added] Sync multi-select overlay position ▼▼▼
416
+ if (window.MindMapMultiSelect) {
417
+ window.MindMapMultiSelect.update();
418
+ }
419
+ // ▲▲▲ [Added] ▲▲▲
420
+ };
421
+ loop();
422
+ }
423
+
424
+ updateCursorPosition(x, y, moveCamera = false, cameraTarget = null) {
425
+ const snappedX = this.snapToGrid(x);
426
+ const snappedY = this.snapToGridY(y);
427
+
428
+ this.cursorPosition.set(snappedX, snappedY, 0);
429
+ if (this.cursorMesh) {
430
+ this.cursorMesh.position.set(snappedX, snappedY, 1);
431
+ }
432
+
433
+ if (moveCamera) {
434
+ let targetCenterX = snappedX;
435
+ let targetCenterY = snappedY;
436
+
437
+ if (cameraTarget) {
438
+ targetCenterX = cameraTarget.x;
439
+ targetCenterY = cameraTarget.y;
440
+ }
441
+
442
+ if (this.camera) {
443
+ const vfov = (this.camera.fov * Math.PI) / 180;
444
+ const viewHeight = 2 * Math.tan(vfov / 2) * this.camera.position.z;
445
+ const yOffset = viewHeight * 0.3;
446
+ this.targetX = targetCenterX;
447
+ this.targetY = targetCenterY - yOffset;
448
+ } else {
449
+ this.targetX = targetCenterX;
450
+ this.targetY = targetCenterY;
451
+ }
452
+ }
453
+ }
454
+
455
+ hide() {
456
+ if (this.container) {
457
+ this.container.style.display = 'none';
458
+ if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; }
459
+ }
460
+ }
461
+
462
+ cleanupAndDispose() {
463
+ if (!this.container) return;
464
+ if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; }
465
+
466
+ MindMapInteractions.removeEventListeners(this);
467
+ MindMapDnD.removeEventListeners(this);
468
+
469
+ if (this.scene && this.debugGroup) { this.scene.remove(this.debugGroup); }
470
+ this.debugGroup = null;
471
+
472
+ if (this.currentThemeObject && window.MindMapThemes) {
473
+ const theme = window.MindMapThemes.getTheme(this.currentThemeName);
474
+ try { theme?.dispose(this.currentThemeObject); } catch { }
475
+ if (this.scene) this.scene.remove(this.currentThemeObject);
476
+ }
477
+ this.currentThemeObject = null;
478
+ this.currentThemeName = null;
479
+
480
+ MindMapNodes.resetState(this);
481
+
482
+ if (this.scene) {
483
+ this.scene.traverse(o => {
484
+ if (o.geometry) o.geometry.dispose();
485
+ if (o.material) {
486
+ if (Array.isArray(o.material)) o.material.forEach(m => m.dispose());
487
+ else o.material.dispose();
488
+ }
489
+ });
490
+ }
491
+
492
+ if (this.renderer) {
493
+ this.renderer.dispose();
494
+ // ▼▼▼ [Changed] Force context loss to prevent context leaks ▼▼▼
495
+ try {
496
+ if (typeof this.renderer.forceContextLoss === 'function') {
497
+ this.renderer.forceContextLoss();
498
+ } else {
499
+ const gl = this.renderer.getContext();
500
+ if (gl && gl.getExtension) {
501
+ const ext = gl.getExtension('WEBGL_lose_context');
502
+ if (ext) ext.loseContext();
503
+ }
504
+ }
505
+ } catch (e) { console.warn('[MindMap] Failed to lose context:', e); }
506
+ this.renderer.domElement = null;
507
+ this.renderer = null; // Release reference
508
+ // ▲▲▲ [Changed] ▲▲▲
509
+ }
510
+
511
+ // ▼▼▼ [Added] Cleanup CSS3DRenderer ▼▼▼
512
+ if (this.cssRenderer && this.cssRenderer.domElement) {
513
+ if (this.cssRenderer.domElement.parentElement) {
514
+ this.cssRenderer.domElement.parentElement.removeChild(this.cssRenderer.domElement);
515
+ }
516
+ }
517
+ this.cssRenderer = null;
518
+ // ▲▲▲ [Added] ▲▲▲
519
+
520
+ this.scene = null;
521
+ this.camera = null;
522
+ this.renderer = null;
523
+ this.container = null;
524
+ this.dotNetHelper = null;
525
+ window.dotNetHelper = null; // Cleanup global reference too
526
+ this.lodUpdateQueue = [];
527
+ }
528
+
529
+ toggleDebugLines() {
530
+ if (!this.scene) return;
531
+ const DEBUG_GROUP_NAME = 'MindMapDebugLines';
532
+ let debugGroup = this.scene.getObjectByName(DEBUG_GROUP_NAME);
533
+ if (debugGroup) {
534
+ this.scene.remove(debugGroup);
535
+ debugGroup.traverse(child => { if (child.geometry) child.geometry.dispose(); if (child.material) child.material.dispose(); });
536
+ console.log('[MindMap] Debug lines hidden.');
537
+ } else {
538
+ debugGroup = new THREE.Group();
539
+ debugGroup.name = DEBUG_GROUP_NAME;
540
+ this.nodeObjectsById.forEach(nodeEntry => {
541
+ const glObject = nodeEntry.glObject;
542
+ if (glObject) {
543
+ glObject.traverse(child => {
544
+ if (child.isMesh) {
545
+ const edges = new THREE.EdgesGeometry(child.geometry);
546
+ const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xff00ff }));
547
+ const childWorldPos = new THREE.Vector3();
548
+ child.getWorldPosition(childWorldPos);
549
+ line.position.copy(childWorldPos);
550
+ line.rotation.copy(child.rotation);
551
+ line.scale.copy(child.scale);
552
+ debugGroup.add(line);
553
+ }
554
+ });
555
+ }
556
+ });
557
+ this.scene.add(debugGroup);
558
+ console.log('[MindMap] Debug lines shown.');
559
+ }
560
+ }
561
+
562
+ // ▼▼▼ [Changed] Expand camera & cursor state save/restore API ▼▼▼
563
+ getCameraState() {
564
+ if (!this.camera) return { x: 0, y: 0, z: 1200, cursorX: 0, cursorY: 0 };
565
+ return {
566
+ x: this.camera.position.x,
567
+ y: this.camera.position.y,
568
+ z: this.camera.position.z,
569
+ cursorX: this.cursorPosition?.x ?? 0,
570
+ cursorY: this.cursorPosition?.y ?? 0
571
+ };
572
+ }
573
+
574
+ setCameraState(x, y, z, cursorX, cursorY) {
575
+ if (!this.camera) return;
576
+
577
+ this.targetX = x;
578
+ this.targetY = y;
579
+ this.targetZ = z;
580
+ this.camera.position.set(x, y, z);
581
+
582
+ if (cursorX !== undefined && cursorY !== undefined) {
583
+ this.updateCursorPosition(cursorX, cursorY, false);
584
+ }
585
+ }
586
+
587
+ getNodePositions() {
588
+ if (!this.nodeObjectsById) return [];
589
+
590
+ const positions = [];
591
+ this.nodeObjectsById.forEach((entry, nodeId) => {
592
+ const obj = entry?.glObject || entry?.cssObject;
593
+ if (!obj || !obj.position) return;
594
+ positions.push({
595
+ nodeId: String(nodeId),
596
+ x: obj.position.x,
597
+ y: obj.position.y
598
+ });
599
+ });
600
+ return positions;
601
+ }
602
+ // ▲▲▲ [Modified] ▲▲▲
603
+
604
+ // ▼▼▼ [New] Texture Atlas Manager methods ▼▼▼
605
+ initAtlasManager(options = {}) {
606
+ if (!window.TextureAtlasManager) {
607
+ console.warn('[MindMapModule] TextureAtlasManager not loaded');
608
+ return null;
609
+ }
610
+ if (!this.atlasManager) {
611
+ this.atlasManager = new window.TextureAtlasManager({
612
+ atlasSize: options.atlasSize || 4096,
613
+ maxAtlasPages: options.maxAtlasPages || 16
614
+ });
615
+ console.log('[MindMapModule] TextureAtlasManager initialized');
616
+ }
617
+ return this.atlasManager;
618
+ }
619
+
620
+ getAtlasManager() {
621
+ return this.atlasManager;
622
+ }
623
+
624
+ getAtlasStats() {
625
+ if (!this.atlasManager) return null;
626
+ return this.atlasManager.getStats();
627
+ }
628
+
629
+ enableAtlasRendering(enabled = true) {
630
+ this.useAtlasRendering = enabled;
631
+ console.log(`[MindMapModule] Atlas rendering ${enabled ? 'enabled' : 'disabled'}`);
632
+ }
633
+ // ▲▲▲ [New] ▲▲▲
634
+
635
+ // ▼▼▼ [New] LOD Renderer methods ▼▼▼
636
+ enableLOD(enabled = true) {
637
+ this.useLODRendering = enabled;
638
+ if (this.lodRenderer) {
639
+ if (enabled) {
640
+ // When enabling, generate thumbnails for existing nodes SYNCHRONOUSLY
641
+ const textureCache = window.MindMapNodes?.textureCache;
642
+ let generatedCount = 0;
643
+ this.nodeObjectsById.forEach((entry, nodeId) => {
644
+ // Try to get the source canvas from texture cache
645
+ const cached = textureCache?.get(nodeId);
646
+ if (cached?.default?.body?.image) {
647
+ const sourceCanvas = cached.default.body.image;
648
+ const model = entry.model;
649
+ const w = model?.width || 400;
650
+ const h = model?.height || 200;
651
+ // Use generateThumbnail directly (sync) instead of addNode (async queue)
652
+ this.lodRenderer.generateThumbnail(nodeId, sourceCanvas, w, h);
653
+ generatedCount++;
654
+ }
655
+ });
656
+ console.log(`[MindMapModule] LOD enabled, generated ${generatedCount} thumbnails for existing nodes`);
657
+ } else {
658
+ // When disabling, make sure all individual nodes are visible
659
+ this.nodeObjectsById.forEach((entry) => {
660
+ if (entry.glObject) entry.glObject.visible = true;
661
+ });
662
+ if (this.lodRenderer.instancedMesh) {
663
+ this.lodRenderer.instancedMesh.visible = false;
664
+ }
665
+ this.lodRenderer.isInLODMode = false;
666
+ console.log(`[MindMapModule] LOD rendering disabled`);
667
+ }
668
+ } else {
669
+ console.warn('[MindMapModule] LODRenderer not initialized');
670
+ }
671
+ }
672
+ getLODStats() {
673
+ if (!this.lodRenderer) return null;
674
+ return this.lodRenderer.getStats();
675
+ }
676
+
677
+ addNodeToLOD(nodeId, canvas, width, height) {
678
+ if (this.lodRenderer && canvas) {
679
+ this.lodRenderer.addNode(nodeId, canvas, width, height);
680
+ }
681
+ }
682
+
683
+ removeNodeFromLOD(nodeId) {
684
+ if (this.lodRenderer) {
685
+ this.lodRenderer.removeNode(nodeId);
686
+ }
687
+ }
688
+ // ▲▲▲ [New] ▲▲▲
689
+ } // End MindMapModule class
690
+
691
+ window.mindMap = {
692
+ init: (id, h) => { if (!moduleInstance) moduleInstance = new MindMapModule(); moduleInstance.init(id, h); },
693
+ addNode: async (m) => { if (moduleInstance) await MindMapNodes.addNode(moduleInstance, m); },
694
+ // ▼▼▼ [Optimization] Batch processing to avoid blocking the UI thread ▼▼▼
695
+ addNodes: async (models) => {
696
+ if (!moduleInstance || !models || models.length === 0) return;
697
+
698
+ console.log(`[mindMap.addNodes] Starting batch add for ${models.length} nodes`);
699
+ const CHUNK_SIZE = 5; // Nodes per chunk
700
+
701
+ for (let i = 0; i < models.length; i += CHUNK_SIZE) {
702
+ const chunk = models.slice(i, i + CHUNK_SIZE);
703
+ console.log(`[mindMap.addNodes] Processing chunk ${Math.floor(i / CHUNK_SIZE) + 1}/${Math.ceil(models.length / CHUNK_SIZE)} (${chunk.length} nodes)`);
704
+
705
+ // Process nodes sequentially within the chunk (each node may do heavy work like texture creation)
706
+ for (const m of chunk) {
707
+ await MindMapNodes.addNode(moduleInstance, m);
708
+ }
709
+
710
+ // Yield to the UI thread (allow one render cycle)
711
+ if (i + CHUNK_SIZE < models.length) {
712
+ await new Promise(r => requestAnimationFrame(r));
713
+ }
714
+ }
715
+
716
+ console.log(`[mindMap.addNodes] Batch add completed for ${models.length} nodes`);
717
+ },
718
+ // ▲▲▲ [Optimization] ▲▲▲
719
+ removeNode: (id) => { if (moduleInstance) MindMapNodes.removeNode(moduleInstance, id); },
720
+ updateNodeContent: async (id, content, w = null, h = null, isLoading = null) => { if (moduleInstance) await MindMapNodes.updateNodeContent(moduleInstance, id, content, w, h, isLoading); },
721
+ // ▼▼▼ [Changed] Apply chunked batch processing to reduce browser load during bulk updates ▼▼▼
722
+ updateMultipleNodesContent: async (details) => {
723
+ if (!moduleInstance || !details || details.length === 0) return;
724
+
725
+ console.log(`[mindMap.updateMultipleNodesContent] Starting batch update for ${details.length} nodes`);
726
+
727
+ // Nodes per chunk (to reduce browser load)
728
+ // 5-10 is typically stable.
729
+ const CHUNK_SIZE = 5;
730
+
731
+ // Split the array into chunks
732
+ for (let i = 0; i < details.length; i += CHUNK_SIZE) {
733
+ const chunk = details.slice(i, i + CHUNK_SIZE);
734
+
735
+ // Run only the current chunk in parallel
736
+ const updatePromises = chunk.map(detail => {
737
+ return new Promise(async (resolve, reject) => {
738
+ const maxWaitTime = 2000; // Increased wait time: 1s -> 2s
739
+ const startTime = performance.now();
740
+
741
+ // Wait until the node object is created (polling)
742
+ while (!moduleInstance.nodeObjectsById.has(detail.nodeId) && (performance.now() - startTime < maxWaitTime)) {
743
+ await new Promise(r => setTimeout(r, 50));
744
+ }
745
+
746
+ if (!moduleInstance.nodeObjectsById.has(detail.nodeId)) {
747
+ console.warn(`[mindMap] Timeout waiting for node: ${detail.nodeId}`);
748
+ // Resolve so the overall process continues even on failure
749
+ return resolve();
750
+ }
751
+
752
+ // Perform the actual content (image) update
753
+ MindMapNodes.updateNodeContent(moduleInstance, detail.nodeId, detail.fileUrl, detail.width, detail.height)
754
+ .then(resolve)
755
+ .catch(err => {
756
+ console.warn(`[mindMap] Update failed for ${detail.nodeId}:`, err);
757
+ resolve(); // Resolve to keep going even if an error occurs
758
+ });
759
+ });
760
+ });
761
+
762
+ // Wait for all work in the current chunk
763
+ await Promise.all(updatePromises);
764
+
765
+ // Yield to the UI thread (allow a render cycle)
766
+ if (i + CHUNK_SIZE < details.length) {
767
+ await new Promise(r => requestAnimationFrame(r));
768
+ }
769
+ }
770
+
771
+ console.log(`[mindMap.updateMultipleNodesContent] Batch update completed for ${details.length} nodes`);
772
+ },
773
+ // ▲▲▲ [Changed] ▲▲▲
774
+ // ▼▼▼ [New] Add a function to sync selection state from C# to JS ▼▼▼
775
+ updateNodeSelection: (oldNodeId, newNodeId) => {
776
+ if (!moduleInstance) return;
777
+
778
+ // Convert empty strings to null
779
+ const actualOldId = oldNodeId || null;
780
+ const actualNewId = newNodeId || null;
781
+
782
+ console.log(`[mindMap.updateNodeSelection] old=${actualOldId}, new=${actualNewId}`);
783
+
784
+ // ▼▼▼ [Changed] Remove automatic WebGL switching logic ▼▼▼
785
+ // (This logic was moved to interactions.js blurActiveEditable() and
786
+ // the blur event handler in css3d-manager.js.)
787
+ /*
788
+ if (actualOldId && actualOldId !== actualNewId) {
789
+ const oldEntry = moduleInstance.nodeObjectsById.get(actualOldId);
790
+ if (oldEntry && oldEntry.model.contentType === 'note' && oldEntry.currentType === 'CSS') {
791
+ console.log(`[mindMap.updateNodeSelection] Switching old note ${actualOldId} back to WebGL`);
792
+ MindMapNodes.switchToWebGL(moduleInstance, actualOldId);
793
+ }
794
+ }
795
+ */
796
+ // ▲▲▲ [Changed] ▲▲▲
797
+
798
+ // 2. Update JS internal state
799
+ if (actualNewId) {
800
+ console.log(`[mindMap.updateNodeSelection] Setting selectedNodeIdJs to ${actualNewId}`);
801
+ MindMapNodes.clearMultiSelection(moduleInstance, actualNewId);
802
+ moduleInstance.selectedNodeIdJs = actualNewId;
803
+ MindMapNodes.updateNodeSelectionStyle(moduleInstance, actualNewId, true, true);
804
+ } else {
805
+ console.log(`[mindMap.updateNodeSelection] Clearing selection (null)`);
806
+ MindMapNodes.clearMultiSelection(moduleInstance, null);
807
+ moduleInstance.selectedNodeIdJs = null;
808
+ }
809
+ },
810
+ // ▲▲▲ [New] ▲▲▲
811
+ // ▼▼▼ [New] Add hybrid rendering helpers ▼▼▼
812
+ switchToCss3D: async (id) => { if (moduleInstance) await MindMapNodes.switchToCss3D(moduleInstance, id); },
813
+ switchToWebGL: async (id) => { if (moduleInstance) await MindMapNodes.switchToWebGL(moduleInstance, id); },
814
+ // ▲▲▲ [New] ▲▲▲
815
+ // ▼▼▼ [New] Convert node content type (e.g., text to note for editing) ▼▼▼
816
+ updateNodeContentType: async (id, newType) => { if (moduleInstance) await MindMapNodes.updateNodeContentType(moduleInstance, id, newType); },
817
+ // ▲▲▲ [New] ▲▲▲
818
+
819
+ // ▼▼▼ [Added] Pause/resume rendering ▼▼▼
820
+ pause: () => {
821
+ if (moduleInstance && moduleInstance.animationFrameId) {
822
+ cancelAnimationFrame(moduleInstance.animationFrameId);
823
+ moduleInstance.animationFrameId = null;
824
+ console.log('[MindMap] Animation paused.');
825
+ }
826
+ },
827
+ resume: () => {
828
+ if (moduleInstance && !moduleInstance.animationFrameId) {
829
+ moduleInstance.animate();
830
+ console.log('[MindMap] Animation resumed.');
831
+ }
832
+ },
833
+ // ▲▲▲ [Added] ▲▲▲
834
+
835
+ // ▼▼▼ [Changed] Expand camera & cursor state save/restore API ▼▼▼
836
+ getCameraState: () => {
837
+ if (moduleInstance) return moduleInstance.getCameraState();
838
+ return { x: 0, y: 0, z: 1200, cursorX: 0, cursorY: 0 };
839
+ },
840
+ setCameraState: (x, y, z, cx, cy) => {
841
+ if (moduleInstance) moduleInstance.setCameraState(x, y, z, cx, cy);
842
+ },
843
+
844
+ getNodePositions: () => {
845
+ if (moduleInstance) return moduleInstance.getNodePositions();
846
+ return [];
847
+ },
848
+ // ▲▲▲ [Changed] ▲▲▲
849
+
850
+ setBackgroundTheme: (name) => { if (moduleInstance) moduleInstance.setBackgroundTheme(name); },
851
+ hide: () => { if (moduleInstance) moduleInstance.hide(); },
852
+ toggleDebug: () => { if (moduleInstance) moduleInstance.toggleDebugLines(); },
853
+ cleanupAndDispose: () => { if (moduleInstance) { moduleInstance.cleanupAndDispose(); moduleInstance = null; } },
854
+
855
+ // ▼▼▼ [New] LOD Rendering API ▼▼▼
856
+ enableLOD: (enabled = true) => { if (moduleInstance) moduleInstance.enableLOD(enabled); },
857
+ getLODStats: () => { if (moduleInstance) return moduleInstance.getLODStats(); return null; },
858
+ // ▲▲▲ [New] ▲▲▲
859
+
860
+ // ▼▼▼ [New] Reset nodes on board switch (keep WebGL context) ▼▼▼
861
+ resetBoard: () => {
862
+ if (!moduleInstance) return;
863
+ console.log('[MindMap] resetBoard: Clearing all nodes for board switch...');
864
+
865
+ // 1. Remove all node objects from the scene
866
+ if (moduleInstance.nodeObjectsById) {
867
+ moduleInstance.nodeObjectsById.forEach((nodeEntry, nodeId) => {
868
+ try {
869
+ // Remove WebGL object
870
+ if (nodeEntry.glObject && moduleInstance.scene) {
871
+ moduleInstance.scene.remove(nodeEntry.glObject);
872
+ nodeEntry.glObject.traverse(child => {
873
+ if (child.geometry) child.geometry.dispose();
874
+ if (child.material) {
875
+ if (Array.isArray(child.material)) {
876
+ child.material.forEach(m => m.dispose());
877
+ } else {
878
+ child.material.dispose();
879
+ }
880
+ }
881
+ });
882
+ }
883
+ // Remove CSS3D object
884
+ if (nodeEntry.cssObject && moduleInstance.scene) {
885
+ moduleInstance.scene.remove(nodeEntry.cssObject);
886
+ }
887
+ } catch (e) {
888
+ console.warn(`[MindMap] Failed to remove node ${nodeId}:`, e);
889
+ }
890
+ });
891
+ }
892
+
893
+ // 2. Reset MindMapNodes state (texture cache, etc.)
894
+ MindMapNodes.resetState(moduleInstance);
895
+
896
+ console.log('[MindMap] resetBoard: All nodes cleared.');
897
+ },
898
+ // ▲▲▲ [New] ▲▲▲
899
+
900
+ // ▼▼▼ [New] Hide canvases immediately before window resize ▼▼▼
901
+ hideForResize: () => {
902
+ if (!moduleInstance) return;
903
+ moduleInstance.isWindowResizing = true;
904
+
905
+ // Fully hide canvases
906
+ if (moduleInstance.renderer && moduleInstance.renderer.domElement) {
907
+ moduleInstance.renderer.domElement.style.visibility = 'hidden';
908
+ }
909
+ if (moduleInstance.cssRenderer && moduleInstance.cssRenderer.domElement) {
910
+ moduleInstance.cssRenderer.domElement.style.visibility = 'hidden';
911
+ }
912
+ // Set a white background on the container
913
+ if (moduleInstance.container) {
914
+ moduleInstance.container.style.backgroundColor = 'white';
915
+ }
916
+ },
917
+ // ▲▲▲ [New] ▲▲▲
918
+
919
+ // ▼▼▼ [New] Settings application API ▼▼▼
920
+ applySettings: (settings) => {
921
+ if (!moduleInstance) return;
922
+ console.log('[MindMap] Applying settings:', settings);
923
+
924
+ // Enable/disable grid snapping
925
+ if (settings.gridSnapEnabled !== undefined) {
926
+ moduleInstance.gridSnapEnabled = settings.gridSnapEnabled;
927
+ }
928
+
929
+ // Change grid type
930
+ if (settings.gridType) {
931
+ const themeMap = {
932
+ 'space3d': 'Space3D',
933
+ 'default': 'XYGrid',
934
+ 'dot': 'SpatialGrid2D'
935
+ };
936
+ const themeName = themeMap[settings.gridType] || 'Space3D';
937
+ moduleInstance.setBackgroundTheme(themeName);
938
+ }
939
+
940
+ // Camera trace mode
941
+ if (settings.cameraTraceMode !== undefined) {
942
+ moduleInstance.cameraTraceMode = settings.cameraTraceMode;
943
+ }
944
+
945
+ // ▼▼▼ [New] Default AI node size settings ▼▼▼
946
+ if (settings.aiNodeDefaultWidth !== undefined) {
947
+ moduleInstance.aiNodeDefaultWidth = settings.aiNodeDefaultWidth;
948
+ }
949
+ if (settings.aiNodeDefaultHeight !== undefined) {
950
+ moduleInstance.aiNodeDefaultHeight = settings.aiNodeDefaultHeight;
951
+ }
952
+ // Show full AI response (no height limit)
953
+ if (settings.showFullAiResponse !== undefined) {
954
+ moduleInstance.showFullAiResponse = settings.showFullAiResponse;
955
+ }
956
+
957
+ // Cursor direction (horizontal / vertical)
958
+ if (settings.cursorDirection !== undefined) {
959
+ moduleInstance.cursorDirection = settings.cursorDirection;
960
+ }
961
+ // ▲▲▲ [New] ▲▲▲
962
+ },
963
+
964
+ // Get current settings
965
+ getSettings: () => {
966
+ if (!moduleInstance) return {};
967
+ return {
968
+ gridSnapEnabled: moduleInstance.gridSnapEnabled !== false, // Default: true
969
+ gridType: moduleInstance.currentThemeName === 'SpatialGrid2D' ? 'dot' : 'default',
970
+ cameraTraceMode: moduleInstance.cameraTraceMode || 'cursor',
971
+ aiNodeDefaultWidth: moduleInstance.aiNodeDefaultWidth || 400,
972
+ aiNodeDefaultHeight: moduleInstance.aiNodeDefaultHeight || 200,
973
+ showFullAiResponse: moduleInstance.showFullAiResponse || false,
974
+ cursorDirection: moduleInstance.cursorDirection || 'horizontal'
975
+ };
976
+ },
977
+ // ▲▲▲ [New] ▲▲▲
978
+
979
+ // ▼▼▼ [New] Helper functions for native drop ▼▼▼
980
+ // Screen coordinates -> world coordinates
981
+ screenToWorld: (screenX, screenY) => {
982
+ if (!moduleInstance || !moduleInstance.camera || !moduleInstance.container) {
983
+ return [0, 0];
984
+ }
985
+
986
+ const container = moduleInstance.container;
987
+ const camera = moduleInstance.camera;
988
+
989
+ // Compute NDC coordinates
990
+ const ndcX = (screenX / container.clientWidth) * 2 - 1;
991
+ const ndcY = -(screenY / container.clientHeight) * 2 + 1;
992
+
993
+ // Compute view extents
994
+ const vfov = (camera.fov * Math.PI) / 180;
995
+ const viewHeight = 2 * Math.tan(vfov / 2) * camera.position.z;
996
+ const viewWidth = viewHeight * camera.aspect;
997
+
998
+ // Compute world coordinates
999
+ const worldX = camera.position.x + (ndcX * viewWidth) / 2;
1000
+ const worldY = camera.position.y + (ndcY * viewHeight) / 2;
1001
+
1002
+ return [worldX, worldY];
1003
+ },
1004
+
1005
+ // Measure image size (for C# native drop)
1006
+ measureImageSize: (src) => {
1007
+ return new Promise((resolve) => {
1008
+ const img = new Image();
1009
+ img.onload = () => {
1010
+ resolve([img.naturalWidth, img.naturalHeight]);
1011
+ };
1012
+ img.onerror = () => {
1013
+ resolve([400, 300]); // Default
1014
+ };
1015
+ img.src = src;
1016
+ });
1017
+ },
1018
+
1019
+ // Create Blob URL from byte array (much faster than base64)
1020
+ createBlobUrl: (bytes, mimeType) => {
1021
+ try {
1022
+ const blob = new Blob([bytes], { type: mimeType });
1023
+ const url = URL.createObjectURL(blob);
1024
+ console.log(`[mindMap.createBlobUrl] Created Blob URL (${Math.round(bytes.length / 1024)}KB, ${mimeType})`);
1025
+ return url;
1026
+ } catch (ex) {
1027
+ console.error(`[mindMap.createBlobUrl] Failed: ${ex}`);
1028
+ return null;
1029
+ }
1030
+ },
1031
+
1032
+ // Revoke Blob URL to free memory
1033
+ revokeBlobUrl: (url) => {
1034
+ if (url && url.startsWith('blob:')) {
1035
+ URL.revokeObjectURL(url);
1036
+ }
1037
+ },
1038
+ // ▲▲▲ [New] ▲▲▲
1039
+
1040
+ // ▼▼▼ [New] Atlas rendering API ▼▼▼
1041
+ initAtlas: (options) => {
1042
+ if (moduleInstance) return moduleInstance.initAtlasManager(options);
1043
+ return null;
1044
+ },
1045
+ getAtlasStats: () => {
1046
+ if (moduleInstance) return moduleInstance.getAtlasStats();
1047
+ return null;
1048
+ },
1049
+ enableAtlasRendering: (enabled) => {
1050
+ if (moduleInstance) moduleInstance.enableAtlasRendering(enabled);
1051
+ }
1052
+ // ▲▲▲ [New] ▲▲▲
1053
+ };
1054
+
1055
+ // Backward-compatible alias: MindCanvas is the new page/internal name, but the underlying surface is the same.
1056
+ window.mindCanvas = window.mindMap;
1057
+
1058
+ console.log('? mind-map-core.js loaded (simplified, style queue removed).');
1059
+ })();