@classytic/stage 0.2.0 → 0.4.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 (458) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +5 -1
  3. package/dist/_virtual/_rolldown/runtime.mjs +18 -0
  4. package/dist/assets/index.mjs +0 -1
  5. package/dist/atomic/aufbau.d.mts +144 -0
  6. package/dist/atomic/aufbau.mjs +384 -0
  7. package/dist/atomic/elements.d.mts +67 -0
  8. package/dist/atomic/elements.mjs +785 -0
  9. package/dist/atomic/emission.d.mts +112 -0
  10. package/dist/atomic/emission.mjs +179 -0
  11. package/dist/atomic/index.d.mts +8 -0
  12. package/dist/atomic/index.mjs +9 -0
  13. package/dist/atomic/periodic.d.mts +82 -0
  14. package/dist/atomic/periodic.mjs +231 -0
  15. package/dist/atomic/properties.d.mts +100 -0
  16. package/dist/atomic/properties.mjs +574 -0
  17. package/dist/atomic/shells.d.mts +137 -0
  18. package/dist/atomic/shells.mjs +170 -0
  19. package/dist/atomic/trends.d.mts +148 -0
  20. package/dist/atomic/trends.mjs +225 -0
  21. package/dist/builder/Palette.mjs +50 -89
  22. package/dist/builder/SceneBuilder.mjs +15 -73
  23. package/dist/chem/chromatography.d.mts +66 -0
  24. package/dist/chem/chromatography.mjs +63 -0
  25. package/dist/chem/complex.d.mts +114 -0
  26. package/dist/chem/complex.mjs +234 -0
  27. package/dist/chem/cycle.d.mts +75 -0
  28. package/dist/chem/cycle.mjs +145 -0
  29. package/dist/chem/equation.d.mts +86 -0
  30. package/dist/chem/equation.mjs +168 -0
  31. package/dist/chem/formula.d.mts +90 -0
  32. package/dist/chem/formula.mjs +201 -0
  33. package/dist/chem/functional-groups.d.mts +31 -0
  34. package/dist/chem/functional-groups.mjs +187 -0
  35. package/dist/chem/hazard.d.mts +94 -0
  36. package/dist/chem/hazard.mjs +329 -0
  37. package/dist/chem/index.d.mts +34 -33
  38. package/dist/chem/index.mjs +34 -28
  39. package/dist/chem/ionic.d.mts +98 -0
  40. package/dist/chem/ionic.mjs +156 -0
  41. package/dist/chem/layout.d.mts +46 -0
  42. package/dist/chem/layout.mjs +342 -0
  43. package/dist/chem/lewis.d.mts +79 -0
  44. package/dist/chem/lewis.mjs +163 -0
  45. package/dist/chem/molecular-orbital.d.mts +114 -0
  46. package/dist/chem/molecular-orbital.mjs +201 -0
  47. package/dist/chem/molecule.d.mts +154 -0
  48. package/dist/chem/molecule.mjs +282 -0
  49. package/dist/chem/nmr.d.mts +153 -0
  50. package/dist/chem/nmr.mjs +165 -0
  51. package/dist/chem/polymer.d.mts +65 -0
  52. package/dist/chem/polymer.mjs +179 -0
  53. package/dist/chem/redox.d.mts +138 -0
  54. package/dist/chem/redox.mjs +328 -0
  55. package/dist/chem/relations.d.mts +183 -0
  56. package/dist/chem/relations.mjs +299 -0
  57. package/dist/chem/skeleton.d.mts +51 -0
  58. package/dist/chem/skeleton.mjs +186 -0
  59. package/dist/chem/solution.d.mts +116 -0
  60. package/dist/chem/solution.mjs +186 -0
  61. package/dist/chem/species.d.mts +47 -0
  62. package/dist/chem/species.mjs +128 -0
  63. package/dist/chem/stereo.d.mts +108 -0
  64. package/dist/chem/stereo.mjs +286 -0
  65. package/dist/chem/stoichiometry.d.mts +95 -0
  66. package/dist/chem/stoichiometry.mjs +145 -0
  67. package/dist/chem/synthesis.d.mts +113 -0
  68. package/dist/chem/synthesis.mjs +281 -0
  69. package/dist/chem/vsepr.d.mts +80 -0
  70. package/dist/chem/vsepr.mjs +247 -0
  71. package/dist/circuit/ac.d.mts +163 -0
  72. package/dist/circuit/ac.mjs +452 -0
  73. package/dist/circuit/dc.d.mts +92 -0
  74. package/dist/circuit/dc.mjs +333 -0
  75. package/dist/circuit/index.d.mts +5 -92
  76. package/dist/circuit/index.mjs +5 -332
  77. package/dist/circuit/network.d.mts +99 -0
  78. package/dist/circuit/network.mjs +440 -0
  79. package/dist/circuit/topology.d.mts +102 -0
  80. package/dist/circuit/topology.mjs +143 -0
  81. package/dist/colour/absorption.d.mts +79 -0
  82. package/dist/colour/absorption.mjs +137 -0
  83. package/dist/colour/index.d.mts +6 -0
  84. package/dist/colour/index.mjs +7 -0
  85. package/dist/colour/name.d.mts +14 -0
  86. package/dist/colour/name.mjs +76 -0
  87. package/dist/colour/observer.d.mts +31 -0
  88. package/dist/colour/observer.mjs +46 -0
  89. package/dist/colour/spectrum.d.mts +61 -0
  90. package/dist/colour/spectrum.mjs +108 -0
  91. package/dist/colour/srgb.d.mts +49 -0
  92. package/dist/colour/srgb.mjs +108 -0
  93. package/dist/core/constants.d.mts +138 -0
  94. package/dist/core/constants.mjs +139 -0
  95. package/dist/core/index.d.mts +4 -1
  96. package/dist/core/index.mjs +4 -1
  97. package/dist/core/math.d.mts +26 -0
  98. package/dist/core/math.mjs +37 -0
  99. package/dist/core/vec3.d.mts +94 -0
  100. package/dist/core/vec3.mjs +123 -0
  101. package/dist/core/worked.d.mts +35 -0
  102. package/dist/core/worked.mjs +44 -0
  103. package/dist/electro/cell.d.mts +102 -0
  104. package/dist/electro/cell.mjs +170 -0
  105. package/dist/electro/conductivity.d.mts +157 -0
  106. package/dist/electro/conductivity.mjs +156 -0
  107. package/dist/electro/couples.d.mts +114 -0
  108. package/dist/electro/couples.mjs +334 -0
  109. package/dist/electro/electrolysis.d.mts +96 -0
  110. package/dist/electro/electrolysis.mjs +274 -0
  111. package/dist/electro/faraday.d.mts +150 -0
  112. package/dist/electro/faraday.mjs +118 -0
  113. package/dist/electro/index.d.mts +6 -0
  114. package/dist/electro/index.mjs +7 -0
  115. package/dist/finance/accounting/adjustments.d.mts +17 -0
  116. package/dist/finance/accounting/adjustments.mjs +202 -0
  117. package/dist/finance/accounting/analysis.d.mts +8 -0
  118. package/dist/finance/accounting/analysis.mjs +205 -0
  119. package/dist/finance/accounting/clubs.d.mts +21 -0
  120. package/dist/finance/accounting/clubs.mjs +217 -0
  121. package/dist/finance/accounting/company-statements.d.mts +7 -0
  122. package/dist/finance/accounting/company-statements.mjs +150 -0
  123. package/dist/finance/accounting/harness.d.mts +7 -0
  124. package/dist/finance/accounting/harness.mjs +45 -0
  125. package/dist/finance/accounting/index.d.mts +18 -0
  126. package/dist/finance/accounting/index.mjs +18 -0
  127. package/dist/finance/accounting/journalize.d.mts +16 -0
  128. package/dist/finance/accounting/journalize.mjs +128 -0
  129. package/dist/finance/accounting/manufacturing.d.mts +8 -0
  130. package/dist/finance/accounting/manufacturing.mjs +236 -0
  131. package/dist/finance/accounting/partnership.d.mts +7 -0
  132. package/dist/finance/accounting/partnership.mjs +152 -0
  133. package/dist/finance/accounting/period-case.d.mts +6 -0
  134. package/dist/finance/accounting/period-case.mjs +71 -0
  135. package/dist/finance/accounting/post.d.mts +9 -0
  136. package/dist/finance/accounting/post.mjs +84 -0
  137. package/dist/finance/accounting/precision.d.mts +9 -0
  138. package/dist/finance/accounting/precision.mjs +15 -0
  139. package/dist/finance/accounting/rules.d.mts +21 -0
  140. package/dist/finance/accounting/rules.mjs +37 -0
  141. package/dist/finance/accounting/solve.d.mts +28 -0
  142. package/dist/finance/accounting/solve.mjs +92 -0
  143. package/dist/finance/accounting/statements.d.mts +7 -0
  144. package/dist/finance/accounting/statements.mjs +142 -0
  145. package/dist/finance/accounting/types.d.mts +649 -0
  146. package/dist/finance/accounting/validate.d.mts +7 -0
  147. package/dist/finance/accounting/validate.mjs +67 -0
  148. package/dist/finance/accounting/verification.d.mts +24 -0
  149. package/dist/finance/accounting/verification.mjs +386 -0
  150. package/dist/finance/bizsim.d.mts +75 -0
  151. package/dist/finance/bizsim.mjs +296 -0
  152. package/dist/finance/index.d.mts +95 -0
  153. package/dist/finance/index.mjs +145 -0
  154. package/dist/finance/investment-math.d.mts +7 -0
  155. package/dist/finance/investment-math.mjs +18 -0
  156. package/dist/finance/management-accounting/budgeting.d.mts +7 -0
  157. package/dist/finance/management-accounting/budgeting.mjs +189 -0
  158. package/dist/finance/management-accounting/cash-budget.d.mts +7 -0
  159. package/dist/finance/management-accounting/cash-budget.mjs +154 -0
  160. package/dist/finance/management-accounting/costing.d.mts +8 -0
  161. package/dist/finance/management-accounting/costing.mjs +175 -0
  162. package/dist/finance/management-accounting/decisions.d.mts +8 -0
  163. package/dist/finance/management-accounting/decisions.mjs +164 -0
  164. package/dist/finance/management-accounting/index.d.mts +13 -0
  165. package/dist/finance/management-accounting/index.mjs +13 -0
  166. package/dist/finance/management-accounting/integrated-budget.d.mts +8 -0
  167. package/dist/finance/management-accounting/integrated-budget.mjs +67 -0
  168. package/dist/finance/management-accounting/inventory-costing.d.mts +7 -0
  169. package/dist/finance/management-accounting/inventory-costing.mjs +173 -0
  170. package/dist/finance/management-accounting/investment-appraisal.d.mts +7 -0
  171. package/dist/finance/management-accounting/investment-appraisal.mjs +148 -0
  172. package/dist/finance/management-accounting/management-case.d.mts +8 -0
  173. package/dist/finance/management-accounting/management-case.mjs +91 -0
  174. package/dist/finance/management-accounting/master-budget.d.mts +7 -0
  175. package/dist/finance/management-accounting/master-budget.mjs +137 -0
  176. package/dist/finance/management-accounting/production-costing.d.mts +8 -0
  177. package/dist/finance/management-accounting/production-costing.mjs +214 -0
  178. package/dist/finance/management-accounting/types.d.mts +644 -0
  179. package/dist/finance/management-accounting/variances.d.mts +7 -0
  180. package/dist/finance/management-accounting/variances.mjs +150 -0
  181. package/dist/index.d.mts +9 -8
  182. package/dist/index.mjs +5 -5
  183. package/dist/interaction/MovableDot.mjs +19 -0
  184. package/dist/interaction/useDraggable.mjs +24 -4
  185. package/dist/math/analysis.d.mts +61 -0
  186. package/dist/math/analysis.mjs +187 -0
  187. package/dist/math/answer-check.d.mts +50 -0
  188. package/dist/math/answer-check.mjs +106 -0
  189. package/dist/math/calculus.d.mts +17 -1
  190. package/dist/math/calculus.mjs +365 -2
  191. package/dist/math/complex.d.mts +93 -0
  192. package/dist/math/complex.mjs +239 -0
  193. package/dist/math/expr.d.mts +23 -0
  194. package/dist/math/expr.mjs +36 -0
  195. package/dist/math/index.d.mts +9 -23
  196. package/dist/math/index.mjs +9 -38
  197. package/dist/math/latex.mjs +10 -1
  198. package/dist/math/parse.mjs +1 -1
  199. package/dist/math/poly.d.mts +55 -0
  200. package/dist/math/poly.mjs +273 -0
  201. package/dist/math/rearrange.d.mts +14 -0
  202. package/dist/math/rearrange.mjs +125 -0
  203. package/dist/math/solve.d.mts +21 -0
  204. package/dist/math/solve.mjs +75 -0
  205. package/dist/networking/addressing.d.mts +26 -0
  206. package/dist/networking/addressing.mjs +33 -0
  207. package/dist/networking/cloud/access.d.mts +36 -0
  208. package/dist/networking/cloud/access.mjs +95 -0
  209. package/dist/networking/cloud/containers.d.mts +43 -0
  210. package/dist/networking/cloud/containers.mjs +54 -0
  211. package/dist/networking/cloud/edge.d.mts +17 -0
  212. package/dist/networking/cloud/edge.mjs +31 -0
  213. package/dist/networking/cloud/failure-domains.d.mts +19 -0
  214. package/dist/networking/cloud/failure-domains.mjs +22 -0
  215. package/dist/networking/cloud/geo-routing.d.mts +22 -0
  216. package/dist/networking/cloud/geo-routing.mjs +64 -0
  217. package/dist/networking/cloud/index.d.mts +18 -0
  218. package/dist/networking/cloud/index.mjs +19 -0
  219. package/dist/networking/cloud/inference-reliability.d.mts +30 -0
  220. package/dist/networking/cloud/inference-reliability.mjs +76 -0
  221. package/dist/networking/cloud/inference.d.mts +118 -0
  222. package/dist/networking/cloud/inference.mjs +179 -0
  223. package/dist/networking/cloud/infrastructure.d.mts +45 -0
  224. package/dist/networking/cloud/infrastructure.mjs +96 -0
  225. package/dist/networking/cloud/observability.d.mts +24 -0
  226. package/dist/networking/cloud/observability.mjs +34 -0
  227. package/dist/networking/cloud/orchestration.d.mts +32 -0
  228. package/dist/networking/cloud/orchestration.mjs +38 -0
  229. package/dist/networking/cloud/recovery.d.mts +21 -0
  230. package/dist/networking/cloud/recovery.mjs +27 -0
  231. package/dist/networking/cloud/releases.d.mts +42 -0
  232. package/dist/networking/cloud/releases.mjs +135 -0
  233. package/dist/networking/cloud/scaling.d.mts +25 -0
  234. package/dist/networking/cloud/scaling.mjs +65 -0
  235. package/dist/networking/cloud/secrets.d.mts +39 -0
  236. package/dist/networking/cloud/secrets.mjs +68 -0
  237. package/dist/networking/cloud/server-deployment.d.mts +29 -0
  238. package/dist/networking/cloud/server-deployment.mjs +72 -0
  239. package/dist/networking/cloud/storage.d.mts +16 -0
  240. package/dist/networking/cloud/storage.mjs +58 -0
  241. package/dist/networking/cloud/vpc.d.mts +18 -0
  242. package/dist/networking/cloud/vpc.mjs +100 -0
  243. package/dist/networking/delivery.d.mts +34 -0
  244. package/dist/networking/delivery.mjs +52 -0
  245. package/dist/networking/index.d.mts +47 -0
  246. package/dist/networking/index.mjs +47 -0
  247. package/dist/networking/ipv6.d.mts +28 -0
  248. package/dist/networking/ipv6.mjs +17 -0
  249. package/dist/networking/journeys.d.mts +83 -0
  250. package/dist/networking/journeys.mjs +161 -0
  251. package/dist/networking/models/airtime.d.mts +87 -0
  252. package/dist/networking/models/airtime.mjs +130 -0
  253. package/dist/networking/models/cellular.d.mts +157 -0
  254. package/dist/networking/models/cellular.mjs +252 -0
  255. package/dist/networking/models/encapsulation.d.mts +98 -0
  256. package/dist/networking/models/encapsulation.mjs +132 -0
  257. package/dist/networking/models/handover.d.mts +116 -0
  258. package/dist/networking/models/handover.mjs +209 -0
  259. package/dist/networking/models/handshake.d.mts +90 -0
  260. package/dist/networking/models/handshake.mjs +122 -0
  261. package/dist/networking/models/index.d.mts +14 -0
  262. package/dist/networking/models/index.mjs +15 -0
  263. package/dist/networking/models/media.d.mts +82 -0
  264. package/dist/networking/models/media.mjs +188 -0
  265. package/dist/networking/models/satellite.d.mts +86 -0
  266. package/dist/networking/models/satellite.mjs +121 -0
  267. package/dist/networking/models/spectrum.d.mts +83 -0
  268. package/dist/networking/models/spectrum.mjs +235 -0
  269. package/dist/networking/models/switch.d.mts +80 -0
  270. package/dist/networking/models/switch.mjs +155 -0
  271. package/dist/networking/models/vlan.d.mts +62 -0
  272. package/dist/networking/models/vlan.mjs +100 -0
  273. package/dist/networking/models/vpn.d.mts +66 -0
  274. package/dist/networking/models/vpn.mjs +125 -0
  275. package/dist/networking/models/wifi-privacy.d.mts +82 -0
  276. package/dist/networking/models/wifi-privacy.mjs +130 -0
  277. package/dist/networking/models/wifi.d.mts +240 -0
  278. package/dist/networking/models/wifi.mjs +562 -0
  279. package/dist/networking/nat-traversal.d.mts +24 -0
  280. package/dist/networking/nat-traversal.mjs +38 -0
  281. package/dist/networking/protocols.d.mts +17 -0
  282. package/dist/networking/protocols.mjs +57 -0
  283. package/dist/networking/queue.d.mts +12 -0
  284. package/dist/networking/queue.mjs +18 -0
  285. package/dist/networking/route-learning.d.mts +23 -0
  286. package/dist/networking/route-learning.mjs +43 -0
  287. package/dist/networking/routing.d.mts +11 -0
  288. package/dist/networking/routing.mjs +25 -0
  289. package/dist/networking/signals.d.mts +26 -0
  290. package/dist/networking/signals.mjs +35 -0
  291. package/dist/networking/subnet.d.mts +49 -0
  292. package/dist/networking/subnet.mjs +89 -0
  293. package/dist/networking/tcp-reliability.d.mts +24 -0
  294. package/dist/networking/tcp-reliability.mjs +38 -0
  295. package/dist/networking/topology/contract.d.mts +123 -0
  296. package/dist/networking/topology/index.d.mts +4 -0
  297. package/dist/networking/topology/index.mjs +4 -0
  298. package/dist/networking/topology/presets.d.mts +7 -0
  299. package/dist/networking/topology/presets.mjs +302 -0
  300. package/dist/networking/topology/simulation.d.mts +8 -0
  301. package/dist/networking/topology/simulation.mjs +179 -0
  302. package/dist/networking/wireless.d.mts +11 -0
  303. package/dist/networking/wireless.mjs +17 -0
  304. package/dist/optics/imaging.d.mts +218 -0
  305. package/dist/optics/imaging.mjs +276 -0
  306. package/dist/optics/index.d.mts +7 -0
  307. package/dist/optics/index.mjs +8 -0
  308. package/dist/optics/laws.d.mts +70 -0
  309. package/dist/optics/laws.mjs +195 -0
  310. package/dist/optics/prism.d.mts +148 -0
  311. package/dist/optics/prism.mjs +197 -0
  312. package/dist/optics/refraction.d.mts +201 -0
  313. package/dist/optics/refraction.mjs +270 -0
  314. package/dist/optics/wave-optics.d.mts +171 -0
  315. package/dist/optics/wave-optics.mjs +259 -0
  316. package/dist/physics/ac/index.d.mts +183 -0
  317. package/dist/physics/ac/index.mjs +203 -0
  318. package/dist/physics/dynamics/forces.d.mts +98 -0
  319. package/dist/physics/dynamics/forces.mjs +153 -0
  320. package/dist/physics/dynamics/friction.d.mts +76 -0
  321. package/dist/physics/dynamics/friction.mjs +119 -0
  322. package/dist/physics/dynamics/system.d.mts +68 -0
  323. package/dist/physics/dynamics/system.mjs +125 -0
  324. package/dist/physics/electrostatics/index.d.mts +169 -0
  325. package/dist/physics/electrostatics/index.mjs +249 -0
  326. package/dist/physics/energy/centre-of-mass.d.mts +115 -0
  327. package/dist/physics/energy/centre-of-mass.mjs +180 -0
  328. package/dist/physics/energy/conservation.d.mts +81 -0
  329. package/dist/physics/energy/conservation.mjs +129 -0
  330. package/dist/physics/energy/power.d.mts +90 -0
  331. package/dist/physics/energy/power.mjs +167 -0
  332. package/dist/physics/energy/spring.d.mts +73 -0
  333. package/dist/physics/energy/spring.mjs +129 -0
  334. package/dist/physics/energy/store.d.mts +63 -0
  335. package/dist/physics/energy/store.mjs +108 -0
  336. package/dist/physics/energy/theorem.d.mts +65 -0
  337. package/dist/physics/energy/theorem.mjs +113 -0
  338. package/dist/physics/energy/work.d.mts +114 -0
  339. package/dist/physics/energy/work.mjs +219 -0
  340. package/dist/physics/gravitation/body.d.mts +85 -0
  341. package/dist/physics/gravitation/body.mjs +168 -0
  342. package/dist/physics/gravitation/kepler.d.mts +127 -0
  343. package/dist/physics/gravitation/kepler.mjs +216 -0
  344. package/dist/physics/gravitation/law.d.mts +117 -0
  345. package/dist/physics/gravitation/law.mjs +175 -0
  346. package/dist/physics/gravitation/orbit.d.mts +119 -0
  347. package/dist/physics/gravitation/orbit.mjs +202 -0
  348. package/dist/physics/gravitation/potential.d.mts +114 -0
  349. package/dist/physics/gravitation/potential.mjs +202 -0
  350. package/dist/physics/gravitation/variation.d.mts +97 -0
  351. package/dist/physics/gravitation/variation.mjs +175 -0
  352. package/dist/physics/index.d.mts +35 -0
  353. package/dist/physics/index.mjs +36 -0
  354. package/dist/physics/integral-sweep.d.mts +63 -0
  355. package/dist/physics/integral-sweep.mjs +126 -0
  356. package/dist/physics/kinematics/equations.d.mts +47 -0
  357. package/dist/physics/kinematics/equations.mjs +126 -0
  358. package/dist/physics/kinematics/free-fall.d.mts +53 -0
  359. package/dist/physics/kinematics/free-fall.mjs +71 -0
  360. package/dist/physics/kinematics/given.d.mts +16 -0
  361. package/dist/physics/kinematics/given.mjs +55 -0
  362. package/dist/physics/kinematics/roots.d.mts +41 -0
  363. package/dist/physics/kinematics/roots.mjs +72 -0
  364. package/dist/physics/kinematics/solve.d.mts +52 -0
  365. package/dist/physics/kinematics/solve.mjs +172 -0
  366. package/dist/physics/magnetism/biot-savart.d.mts +161 -0
  367. package/dist/physics/magnetism/biot-savart.mjs +353 -0
  368. package/dist/physics/magnetism/flux.d.mts +94 -0
  369. package/dist/physics/magnetism/flux.mjs +313 -0
  370. package/dist/physics/magnetism/geomagnetism.d.mts +151 -0
  371. package/dist/physics/magnetism/geomagnetism.mjs +184 -0
  372. package/dist/physics/magnetism/index.d.mts +12 -0
  373. package/dist/physics/magnetism/index.mjs +74 -0
  374. package/dist/physics/magnetism/lorentz.d.mts +28 -0
  375. package/dist/physics/magnetism/lorentz.mjs +88 -0
  376. package/dist/physics/magnetism/magnetic-force.d.mts +102 -0
  377. package/dist/physics/magnetism/magnetic-force.mjs +169 -0
  378. package/dist/physics/momentum/collide.d.mts +77 -0
  379. package/dist/physics/momentum/collide.mjs +115 -0
  380. package/dist/physics/momentum/impulse.d.mts +89 -0
  381. package/dist/physics/momentum/impulse.mjs +159 -0
  382. package/dist/physics/momentum/planar.d.mts +60 -0
  383. package/dist/physics/momentum/planar.mjs +110 -0
  384. package/dist/physics/projectile.d.mts +107 -0
  385. package/dist/physics/projectile.mjs +185 -0
  386. package/dist/physics/rotation/angular.d.mts +85 -0
  387. package/dist/physics/rotation/angular.mjs +130 -0
  388. package/dist/physics/rotation/circular.d.mts +79 -0
  389. package/dist/physics/rotation/circular.mjs +131 -0
  390. package/dist/physics/rotation/inertia.d.mts +164 -0
  391. package/dist/physics/rotation/inertia.mjs +332 -0
  392. package/dist/physics/rotation/torque.d.mts +65 -0
  393. package/dist/physics/rotation/torque.mjs +134 -0
  394. package/dist/physics/vectors.d.mts +64 -0
  395. package/dist/physics/vectors.mjs +114 -0
  396. package/dist/primitives/Dot.d.mts +2 -15
  397. package/dist/primitives/Dot.mjs +6 -4
  398. package/dist/primitives/Grid.d.mts +33 -17
  399. package/dist/primitives/Grid.mjs +89 -17
  400. package/dist/primitives/Label.d.mts +1 -14
  401. package/dist/primitives/Label.mjs +3 -2
  402. package/dist/primitives/Lines.d.mts +4 -32
  403. package/dist/primitives/Lines.mjs +10 -8
  404. package/dist/primitives/Shapes.d.mts +5 -43
  405. package/dist/primitives/Shapes.mjs +12 -10
  406. package/dist/primitives/index.d.mts +2 -2
  407. package/dist/primitives/index.mjs +2 -2
  408. package/dist/primitives/props.mjs +31 -0
  409. package/dist/quantity/index.d.mts +119 -0
  410. package/dist/quantity/index.mjs +417 -0
  411. package/dist/relation/derive.d.mts +74 -0
  412. package/dist/relation/derive.mjs +73 -0
  413. package/dist/relation/index.d.mts +56 -0
  414. package/dist/relation/index.mjs +234 -0
  415. package/dist/scene/Scene.d.mts +6 -1
  416. package/dist/scene/Scene.mjs +15 -40
  417. package/dist/scene/evaluators.mjs +1 -1
  418. package/dist/sim/thermal.mjs +45 -20
  419. package/dist/thermo/calorimetry.d.mts +207 -0
  420. package/dist/thermo/calorimetry.mjs +345 -0
  421. package/dist/thermo/cycles.d.mts +142 -0
  422. package/dist/thermo/cycles.mjs +256 -0
  423. package/dist/thermo/entropy.d.mts +87 -0
  424. package/dist/thermo/entropy.mjs +140 -0
  425. package/dist/thermo/expansion.d.mts +130 -0
  426. package/dist/thermo/expansion.mjs +178 -0
  427. package/dist/thermo/gas.d.mts +197 -0
  428. package/dist/thermo/gas.mjs +279 -0
  429. package/dist/thermo/heat-transfer.d.mts +164 -0
  430. package/dist/thermo/heat-transfer.mjs +217 -0
  431. package/dist/thermo/index.d.mts +9 -82
  432. package/dist/thermo/index.mjs +9 -132
  433. package/dist/thermo/processes.d.mts +95 -0
  434. package/dist/thermo/processes.mjs +154 -0
  435. package/dist/thermo/temperature.d.mts +174 -0
  436. package/dist/thermo/temperature.mjs +236 -0
  437. package/dist/view/Stage.mjs +4 -11
  438. package/dist/waves/damping.d.mts +224 -0
  439. package/dist/waves/damping.mjs +317 -0
  440. package/dist/waves/doppler.d.mts +72 -0
  441. package/dist/waves/doppler.mjs +125 -0
  442. package/dist/waves/index.d.mts +10 -0
  443. package/dist/waves/index.mjs +10 -0
  444. package/dist/waves/pendulum.d.mts +130 -0
  445. package/dist/waves/pendulum.mjs +211 -0
  446. package/dist/waves/pitch.d.mts +125 -0
  447. package/dist/waves/pitch.mjs +158 -0
  448. package/dist/waves/shm.d.mts +170 -0
  449. package/dist/waves/shm.mjs +279 -0
  450. package/dist/waves/springs.d.mts +103 -0
  451. package/dist/waves/springs.mjs +176 -0
  452. package/dist/waves/superposition.d.mts +124 -0
  453. package/dist/waves/superposition.mjs +196 -0
  454. package/dist/waves/wave.d.mts +154 -0
  455. package/dist/waves/wave.mjs +178 -0
  456. package/package.json +103 -23
  457. package/styles.css +125 -8
  458. package/dist/assets/kit/index.mjs +0 -4
@@ -0,0 +1,98 @@
1
+ //#region src/chem/ionic.d.ts
2
+ /**
3
+ * Ions, and the formula two of them make.
4
+ *
5
+ * ## The one rule, which is usually taught as a trick
6
+ *
7
+ * "Write the formula of aluminium oxide" is answered in most classrooms by the crossover trick:
8
+ * write the charges, swap them over, drop the signs. It gives Al2O3, and it gives the wrong answer
9
+ * for magnesium oxide, where crossing over 2 and 2 gives Mg2O2 and the compound is MgO. The trick
10
+ * then acquires a patch: "and cancel down if you can".
11
+ *
12
+ * There is no trick. A compound has no overall charge, so the positive and negative charges in it
13
+ * must cancel exactly, and the formula is the SMALLEST whole number of each ion that makes them.
14
+ * That is a lowest common multiple, and it gets magnesium oxide right without a patch because the
15
+ * smallest multiple of 2 that is also a multiple of 2 is 2, not 4.
16
+ *
17
+ * {@link neutralRatio} computes it that way, so the reasoning a learner is shown is the reasoning
18
+ * the engine uses.
19
+ *
20
+ * ## Charges are data, and the ones worth knowing are here
21
+ *
22
+ * Which charge an ion carries is not derivable for a transition metal, and only roughly derivable
23
+ * from the group for everything else. So the common ones are listed, with their names, because
24
+ * "what is the formula of iron(III) sulfate" needs both the charge and the fact that sulfate is one
25
+ * unit that keeps its brackets.
26
+ *
27
+ * Pure: no React, no pixels.
28
+ */
29
+ interface Ion {
30
+ /** As written in a formula, without the charge: "SO4", "Al", "NH4". */
31
+ readonly formula: string;
32
+ /** Signed: +3 for aluminium, -2 for sulfate. */
33
+ readonly charge: number;
34
+ /** The name it takes in a compound: "aluminium", "sulfate". */
35
+ readonly name: string;
36
+ /**
37
+ * Whether it is more than one atom, and so needs brackets when there is more than one of it.
38
+ *
39
+ * Ca(NO3)2 needs them and CaCl2 does not, and the difference is exactly this. Without brackets
40
+ * the formula reads as one nitrogen and thirty-two oxygens.
41
+ */
42
+ readonly polyatomic: boolean;
43
+ }
44
+ /** The cations a syllabus expects to be known. */
45
+ declare const CATIONS: readonly Ion[];
46
+ /** The anions a syllabus expects to be known. */
47
+ declare const ANIONS: readonly Ion[];
48
+ /** Every ion listed here, cations and anions together. */
49
+ declare const IONS: readonly Ion[];
50
+ /**
51
+ * Find an ion by the name it takes in a compound.
52
+ *
53
+ * Matched case-insensitively on BOTH sides. Lowercasing only the input looked right and failed for
54
+ * every ion whose name carries a Roman numeral, since "iron(iii)" never matches "iron(III)".
55
+ */
56
+ declare const ionByName: (name: string) => Ion | undefined;
57
+ /**
58
+ * How many of each ion make a compound with no overall charge, in the smallest whole numbers.
59
+ *
60
+ * The lowest common multiple of the two charge sizes, divided by each. Magnesium and oxide both
61
+ * carry two, their lowest common multiple is two, and one of each is the answer: MgO. The crossover
62
+ * trick would have given Mg2O2 and needed a separate rule to cancel it down.
63
+ */
64
+ declare function neutralRatio(cationCharge: number, anionCharge: number): [number, number];
65
+ /** One ion written out with its count, bracketed when it needs to be. */
66
+ declare function writeIon(part: Ion, count: number): string;
67
+ interface IonicCompound {
68
+ readonly formula: string;
69
+ readonly name: string;
70
+ readonly cation: Ion;
71
+ readonly anion: Ion;
72
+ readonly cationCount: number;
73
+ readonly anionCount: number;
74
+ }
75
+ /**
76
+ * The compound two ions form.
77
+ *
78
+ * `ionicFormula(aluminium, oxide)` gives Al2O3; `ionicFormula(magnesium, oxide)` gives MgO, not
79
+ * Mg2O2; `ionicFormula(calcium, nitrate)` gives Ca(NO3)2 with the brackets that stop it being read
80
+ * as thirty-two oxygens.
81
+ */
82
+ declare function ionicCompound(cation: Ion, anion: Ion): IonicCompound;
83
+ /** The same, by name: `compoundOf('calcium', 'carbonate')`. */
84
+ declare function compoundOf(cationName: string, anionName: string): IonicCompound;
85
+ /**
86
+ * Whether a proposed pair of counts makes a neutral compound, and whether it is the simplest one.
87
+ *
88
+ * Both answers matter and they are different. Mg2O2 IS neutral and is not a formula anyone writes,
89
+ * so a learner offering it has understood the charge balance and not the "smallest" part, and
90
+ * telling them only that it is wrong would hide which half they have.
91
+ */
92
+ declare function assessCounts(cation: Ion, anion: Ion, cationCount: number, anionCount: number): {
93
+ totalCharge: number;
94
+ neutral: boolean;
95
+ simplest: boolean;
96
+ };
97
+ //#endregion
98
+ export { ANIONS, CATIONS, IONS, Ion, IonicCompound, assessCounts, compoundOf, ionByName, ionicCompound, neutralRatio, writeIon };
@@ -0,0 +1,156 @@
1
+ import { parseFormula } from "./formula.mjs";
2
+
3
+ //#region src/chem/ionic.ts
4
+ /**
5
+ * Ions, and the formula two of them make.
6
+ *
7
+ * ## The one rule, which is usually taught as a trick
8
+ *
9
+ * "Write the formula of aluminium oxide" is answered in most classrooms by the crossover trick:
10
+ * write the charges, swap them over, drop the signs. It gives Al2O3, and it gives the wrong answer
11
+ * for magnesium oxide, where crossing over 2 and 2 gives Mg2O2 and the compound is MgO. The trick
12
+ * then acquires a patch: "and cancel down if you can".
13
+ *
14
+ * There is no trick. A compound has no overall charge, so the positive and negative charges in it
15
+ * must cancel exactly, and the formula is the SMALLEST whole number of each ion that makes them.
16
+ * That is a lowest common multiple, and it gets magnesium oxide right without a patch because the
17
+ * smallest multiple of 2 that is also a multiple of 2 is 2, not 4.
18
+ *
19
+ * {@link neutralRatio} computes it that way, so the reasoning a learner is shown is the reasoning
20
+ * the engine uses.
21
+ *
22
+ * ## Charges are data, and the ones worth knowing are here
23
+ *
24
+ * Which charge an ion carries is not derivable for a transition metal, and only roughly derivable
25
+ * from the group for everything else. So the common ones are listed, with their names, because
26
+ * "what is the formula of iron(III) sulfate" needs both the charge and the fact that sulfate is one
27
+ * unit that keeps its brackets.
28
+ *
29
+ * Pure: no React, no pixels.
30
+ */
31
+ const ion = (formula, charge, name) => ({
32
+ formula,
33
+ charge,
34
+ name,
35
+ polyatomic: parseFormula(formula).reduce((total, e) => total + e.count, 0) > 1
36
+ });
37
+ /** The cations a syllabus expects to be known. */
38
+ const CATIONS = [
39
+ ion("H", 1, "hydrogen"),
40
+ ion("Li", 1, "lithium"),
41
+ ion("Na", 1, "sodium"),
42
+ ion("K", 1, "potassium"),
43
+ ion("Ag", 1, "silver"),
44
+ ion("NH4", 1, "ammonium"),
45
+ ion("Mg", 2, "magnesium"),
46
+ ion("Ca", 2, "calcium"),
47
+ ion("Ba", 2, "barium"),
48
+ ion("Zn", 2, "zinc"),
49
+ ion("Cu", 2, "copper(II)"),
50
+ ion("Fe", 2, "iron(II)"),
51
+ ion("Pb", 2, "lead(II)"),
52
+ ion("Al", 3, "aluminium"),
53
+ ion("Cr", 3, "chromium(III)")
54
+ ];
55
+ /** Iron and copper each appear twice, so the ion has to be named rather than the element. */
56
+ const IRON_III = ion("Fe", 3, "iron(III)");
57
+ const COPPER_I = ion("Cu", 1, "copper(I)");
58
+ /** The anions a syllabus expects to be known. */
59
+ const ANIONS = [
60
+ ion("F", -1, "fluoride"),
61
+ ion("Cl", -1, "chloride"),
62
+ ion("Br", -1, "bromide"),
63
+ ion("I", -1, "iodide"),
64
+ ion("OH", -1, "hydroxide"),
65
+ ion("NO3", -1, "nitrate"),
66
+ ion("HCO3", -1, "hydrogencarbonate"),
67
+ ion("O", -2, "oxide"),
68
+ ion("S", -2, "sulfide"),
69
+ ion("SO4", -2, "sulfate"),
70
+ ion("CO3", -2, "carbonate"),
71
+ ion("PO4", -3, "phosphate")
72
+ ];
73
+ /** Every ion listed here, cations and anions together. */
74
+ const IONS = [
75
+ ...CATIONS,
76
+ IRON_III,
77
+ COPPER_I,
78
+ ...ANIONS
79
+ ];
80
+ /**
81
+ * Find an ion by the name it takes in a compound.
82
+ *
83
+ * Matched case-insensitively on BOTH sides. Lowercasing only the input looked right and failed for
84
+ * every ion whose name carries a Roman numeral, since "iron(iii)" never matches "iron(III)".
85
+ */
86
+ const ionByName = (name) => {
87
+ const wanted = name.trim().toLowerCase();
88
+ return IONS.find((i) => i.name.toLowerCase() === wanted);
89
+ };
90
+ const gcd = (a, b) => b === 0 ? Math.abs(a) : gcd(b, a % b);
91
+ /**
92
+ * How many of each ion make a compound with no overall charge, in the smallest whole numbers.
93
+ *
94
+ * The lowest common multiple of the two charge sizes, divided by each. Magnesium and oxide both
95
+ * carry two, their lowest common multiple is two, and one of each is the answer: MgO. The crossover
96
+ * trick would have given Mg2O2 and needed a separate rule to cancel it down.
97
+ */
98
+ function neutralRatio(cationCharge, anionCharge) {
99
+ if (cationCharge <= 0) throw new Error(`a cation carries a positive charge, not ${cationCharge}`);
100
+ if (anionCharge >= 0) throw new Error(`an anion carries a negative charge, not ${anionCharge}`);
101
+ const size = Math.abs(anionCharge);
102
+ const lcm = cationCharge * size / gcd(cationCharge, size);
103
+ return [lcm / cationCharge, lcm / size];
104
+ }
105
+ /** One ion written out with its count, bracketed when it needs to be. */
106
+ function writeIon(part, count) {
107
+ if (count === 1) return part.formula;
108
+ return part.polyatomic ? `(${part.formula})${count}` : `${part.formula}${count}`;
109
+ }
110
+ /**
111
+ * The compound two ions form.
112
+ *
113
+ * `ionicFormula(aluminium, oxide)` gives Al2O3; `ionicFormula(magnesium, oxide)` gives MgO, not
114
+ * Mg2O2; `ionicFormula(calcium, nitrate)` gives Ca(NO3)2 with the brackets that stop it being read
115
+ * as thirty-two oxygens.
116
+ */
117
+ function ionicCompound(cation, anion) {
118
+ const [cationCount, anionCount] = neutralRatio(cation.charge, anion.charge);
119
+ return {
120
+ formula: `${writeIon(cation, cationCount)}${writeIon(anion, anionCount)}`,
121
+ name: `${cation.name} ${anion.name}`,
122
+ cation,
123
+ anion,
124
+ cationCount,
125
+ anionCount
126
+ };
127
+ }
128
+ /** The same, by name: `compoundOf('calcium', 'carbonate')`. */
129
+ function compoundOf(cationName, anionName) {
130
+ const cation = ionByName(cationName);
131
+ const anion = ionByName(anionName);
132
+ if (!cation) throw new Error(`no ion named "${cationName}" in this table`);
133
+ if (!anion) throw new Error(`no ion named "${anionName}" in this table`);
134
+ if (cation.charge <= 0) throw new Error(`"${cationName}" is not a cation`);
135
+ if (anion.charge >= 0) throw new Error(`"${anionName}" is not an anion`);
136
+ return ionicCompound(cation, anion);
137
+ }
138
+ /**
139
+ * Whether a proposed pair of counts makes a neutral compound, and whether it is the simplest one.
140
+ *
141
+ * Both answers matter and they are different. Mg2O2 IS neutral and is not a formula anyone writes,
142
+ * so a learner offering it has understood the charge balance and not the "smallest" part, and
143
+ * telling them only that it is wrong would hide which half they have.
144
+ */
145
+ function assessCounts(cation, anion, cationCount, anionCount) {
146
+ const totalCharge = cation.charge * cationCount + anion.charge * anionCount;
147
+ const [wantCation, wantAnion] = neutralRatio(cation.charge, anion.charge);
148
+ return {
149
+ totalCharge,
150
+ neutral: totalCharge === 0,
151
+ simplest: cationCount === wantCation && anionCount === wantAnion
152
+ };
153
+ }
154
+
155
+ //#endregion
156
+ export { ANIONS, CATIONS, IONS, assessCounts, compoundOf, ionByName, ionicCompound, neutralRatio, writeIon };
@@ -0,0 +1,46 @@
1
+ import { Structure } from "./molecule.mjs";
2
+
3
+ //#region src/chem/layout.d.ts
4
+ interface Point {
5
+ readonly x: number;
6
+ readonly y: number;
7
+ }
8
+ /**
9
+ * Where every atom goes, in bond lengths, with y downward.
10
+ *
11
+ * A disconnected structure lays each piece out and offsets it, so nothing overlaps: that happens
12
+ * when a reaction has produced a small molecule alongside the main one.
13
+ */
14
+ declare function layout(structure: Structure): Map<number, Point>;
15
+ interface LaidOut {
16
+ readonly positions: ReadonlyMap<number, Point>;
17
+ /** Corners of the drawing, in bond lengths, so a caller can centre and scale it. */
18
+ readonly bounds: {
19
+ readonly minX: number;
20
+ readonly minY: number;
21
+ readonly maxX: number;
22
+ readonly maxY: number;
23
+ };
24
+ /** Ring systems, in bonded order, so an aromatic circle can be drawn in the right place. */
25
+ readonly rings: readonly (readonly number[])[];
26
+ }
27
+ /** The layout plus everything a figure needs to place it on a page. */
28
+ declare function layoutOf(structure: Structure): LaidOut;
29
+ /** The centre of a ring, for the delocalisation circle. */
30
+ declare function ringCentre(ring: readonly number[], positions: ReadonlyMap<number, Point>): Point;
31
+ /**
32
+ * Several free directions at an atom, for drawing its hydrogens in a displayed formula.
33
+ *
34
+ * Returned widest-gap first and spread apart, so a CH2 puts its two hydrogens on opposite sides
35
+ * rather than side by side where they would touch.
36
+ */
37
+ declare function freeDirections(structure: Structure, id: number, positions: ReadonlyMap<number, Point>, count: number): Point[];
38
+ /**
39
+ * A free direction at an atom, for drawing an open valence or an implicit-hydrogen label.
40
+ *
41
+ * Returns the lattice direction furthest from every bond the atom already has, which is where a
42
+ * chemist would put it.
43
+ */
44
+ declare function freeDirection(structure: Structure, id: number, positions: ReadonlyMap<number, Point>): Point;
45
+ //#endregion
46
+ export { LaidOut, Point, freeDirection, freeDirections, layout, layoutOf, ringCentre };
@@ -0,0 +1,342 @@
1
+ import { bondsAt, otherEnd } from "./molecule.mjs";
2
+ import { ringSystems } from "./skeleton.mjs";
3
+
4
+ //#region src/chem/layout.ts
5
+ /**
6
+ * layout, where every atom of a structure goes on the page.
7
+ *
8
+ * WHAT THIS IS FOR. Drawing a molecule from a graph needs coordinates, and until now the corpus got
9
+ * them by having an author place a chain by hand. That works for a hand-written structure and cannot
10
+ * work for a computed one: a polymer repeat unit is built by the kernel, and nobody is there to
11
+ * place it. It also cannot express the shape that broke the polymer lab, a ring with the chain
12
+ * running THROUGH it, because a hand-placed chain is a chain or a ring and never both.
13
+ *
14
+ * So positions are computed. Coordinates come back in BOND LENGTHS, with y increasing downward to
15
+ * match SVG, and the caller scales and centres them.
16
+ *
17
+ * ## The lattice, and why 30 degrees
18
+ *
19
+ * Every bond points along one of twelve directions, 30 degrees apart. That single choice gives both
20
+ * shapes chemistry needs: a zig-zag chain is bonds alternating at plus and minus 30 degrees from
21
+ * horizontal, which is a 120 degree bond angle, and a hexagon's edges are at 30 plus multiples of
22
+ * 60. A finer lattice would let bonds land at angles no structure drawing uses; a coarser one cannot
23
+ * draw both shapes at once.
24
+ *
25
+ * ## Rings are placed first, and everything else grows off them
26
+ *
27
+ * A ring's geometry is fixed and a chain's is not, so the rings are laid down as regular polygons
28
+ * and the acyclic atoms are then walked outward from whatever is already placed. Doing it the other
29
+ * way round means discovering halfway along a chain that it has to close into a hexagon, and moving
30
+ * everything already placed.
31
+ *
32
+ * ## How a direction is chosen
33
+ *
34
+ * From the direction the walk arrived, turn 60 degrees, alternating which way so a chain zig-zags
35
+ * instead of curling into a circle. If that position is occupied, try the other way, then wider
36
+ * angles. The occupancy test is what stops a branch being drawn on top of the chain it hangs from,
37
+ * and it is why this is a search rather than a formula.
38
+ *
39
+ * Pure: no React, no pixels.
40
+ */
41
+ /** Twelve directions, 30 degrees apart, as unit vectors. */
42
+ const STEP = Math.PI / 6;
43
+ const DIRECTIONS = Array.from({ length: 12 }, (_, k) => ({
44
+ x: Math.cos(k * STEP),
45
+ y: Math.sin(k * STEP)
46
+ }));
47
+ /** The lattice direction closest to an angle, as an index into {@link DIRECTIONS}. */
48
+ const snap = (angle) => (Math.round(angle / STEP) % 12 + 12) % 12;
49
+ const angleOf = (from, to) => Math.atan2(to.y - from.y, to.x - from.x);
50
+ /** Two atoms are on top of each other if they are closer than this, in bond lengths. */
51
+ const TOO_CLOSE = .55;
52
+ const linked = (structure, id) => bondsAt(structure, id).map((b) => otherEnd(b, id));
53
+ /**
54
+ * How many atoms lie beyond a neighbour, without coming back through this one.
55
+ *
56
+ * Used to decide which way the CHAIN goes. At a branching atom the neighbour leading to the most
57
+ * atoms is the backbone and takes the zig-zag; the small ones hanging off it are substituents and
58
+ * are placed wherever there is room. Without this the order the bonds happen to be declared in
59
+ * decides, so ethyl ethanoate gave its chain direction to the carbonyl oxygen and drew itself
60
+ * vertically with the chain hanging off the side.
61
+ */
62
+ function reachBeyond(structure, from, through) {
63
+ const seen = new Set([from, through]);
64
+ const stack = [through];
65
+ let count = 0;
66
+ while (stack.length) {
67
+ const at = stack.pop();
68
+ count += 1;
69
+ for (const n of linked(structure, at)) {
70
+ if (seen.has(n)) continue;
71
+ seen.add(n);
72
+ stack.push(n);
73
+ }
74
+ }
75
+ return count;
76
+ }
77
+ /**
78
+ * Where every atom goes, in bond lengths, with y downward.
79
+ *
80
+ * A disconnected structure lays each piece out and offsets it, so nothing overlaps: that happens
81
+ * when a reaction has produced a small molecule alongside the main one.
82
+ */
83
+ function layout(structure) {
84
+ const placed = /* @__PURE__ */ new Map();
85
+ const rings = ringSystems(structure);
86
+ const occupied = (p, ignore) => [...placed].some(([id, q]) => id !== ignore && Math.hypot(p.x - q.x, p.y - q.y) < TOO_CLOSE);
87
+ /**
88
+ * Put a ring down as a regular polygon, turned so its substituents run across the page.
89
+ *
90
+ * A bare ring gets a vertex at the top, which is how benzene is printed. A SUBSTITUTED ring is
91
+ * turned so the first substituted vertex sits on the left, which for a para-disubstituted ring
92
+ * puts the other one on the right and lets the chain run horizontally through it. Without that,
93
+ * terephthalic acid came out six bond lengths tall and two wide, with its two carboxyls stacked.
94
+ */
95
+ const placeRing = (ring, centre) => {
96
+ const n = ring.length;
97
+ const radius = 1 / (2 * Math.sin(Math.PI / n));
98
+ const ordered = orderRing(structure, ring);
99
+ const inRing = new Set(ordered);
100
+ const substituted = ordered.findIndex((id) => linked(structure, id).some((nb) => !inRing.has(nb)));
101
+ const turn = substituted >= 0 ? Math.PI - substituted * 2 * Math.PI / n : -Math.PI / 2;
102
+ ordered.forEach((id, i) => {
103
+ const a = turn + i * 2 * Math.PI / n;
104
+ placed.set(id, {
105
+ x: centre.x + radius * Math.cos(a),
106
+ y: centre.y + radius * Math.sin(a)
107
+ });
108
+ });
109
+ };
110
+ if (rings[0]?.length) placeRing(rings[0], {
111
+ x: 0,
112
+ y: 0
113
+ });
114
+ else if (structure.atoms[0]) placed.set(structure.atoms[0].id, {
115
+ x: 0,
116
+ y: 0
117
+ });
118
+ /** Directions already used by bonds at an atom, as angles. */
119
+ const usedAngles = (id) => linked(structure, id).filter((n) => placed.has(n)).map((n) => angleOf(placed.get(id), placed.get(n)));
120
+ /**
121
+ * The direction a bond leaves an atom in.
122
+ *
123
+ * CONTINUING A CHAIN is a reflection, not a rotation. A zig-zag is bonds alternating between plus
124
+ * and minus 30 degrees, which is exactly "mirror the direction you arrived on in the horizontal",
125
+ * so the chain travels along the page instead of turning. Rotating by a fixed 60 degrees each
126
+ * step was the first version and it curls: six carbons came out as a hexagon the molecule does not
127
+ * have, and hexanedioic acid was drawn taller than it was wide.
128
+ *
129
+ * A BRANCH has no direction to reflect, so it takes the lattice direction furthest from every bond
130
+ * the atom already has, which is where a chemist puts it and which is also what keeps a
131
+ * substituent off a ring's own edges.
132
+ */
133
+ const directionFrom = (id, incoming) => {
134
+ const used = usedAngles(id);
135
+ if (incoming !== void 0 && used.length <= 1) {
136
+ const candidate = DIRECTIONS[snap(snap(-incoming) === snap(incoming) ? incoming + STEP : -incoming)];
137
+ if (!occupied({
138
+ x: placed.get(id).x + candidate.x,
139
+ y: placed.get(id).y + candidate.y
140
+ })) return candidate;
141
+ }
142
+ let best;
143
+ let bestGap = -1;
144
+ for (const d of DIRECTIONS) {
145
+ const here = placed.get(id);
146
+ if (occupied({
147
+ x: here.x + d.x,
148
+ y: here.y + d.y
149
+ })) continue;
150
+ const a = Math.atan2(d.y, d.x);
151
+ const gap = used.length ? Math.min(...used.map((u) => Math.abs(Math.atan2(Math.sin(a - u), Math.cos(a - u))))) : Math.PI;
152
+ if (gap > bestGap) {
153
+ bestGap = gap;
154
+ best = d;
155
+ }
156
+ }
157
+ return best ?? DIRECTIONS[0];
158
+ };
159
+ /**
160
+ * Walk outward from everything placed, breadth first, carrying the direction each step arrived on.
161
+ *
162
+ * Breadth first rather than depth first so a long chain is laid down before its branches, which
163
+ * means the branches see the chain already in place and step around it.
164
+ */
165
+ const grow = () => {
166
+ const queue = [...placed.keys()].map((id) => ({
167
+ id,
168
+ incoming: void 0
169
+ }));
170
+ let progress = false;
171
+ const seen = new Set(queue.map((q) => q.id));
172
+ const flat = structure.atoms.length <= 2;
173
+ let seeded = placed.size > 1;
174
+ while (queue.length) {
175
+ const { id, incoming } = queue.shift();
176
+ let continued = false;
177
+ const outward = [...linked(structure, id)].sort((a, b) => reachBeyond(structure, id, b) - reachBeyond(structure, id, a));
178
+ for (const next of outward) {
179
+ if (placed.has(next)) {
180
+ if (!seen.has(next)) {
181
+ seen.add(next);
182
+ queue.push({
183
+ id: next,
184
+ incoming: angleOf(placed.get(id), placed.get(next))
185
+ });
186
+ }
187
+ continue;
188
+ }
189
+ const direction = !seeded ? DIRECTIONS[flat ? 0 : snap(STEP)] : directionFrom(id, continued ? void 0 : incoming);
190
+ seeded = true;
191
+ continued = true;
192
+ const here = placed.get(id);
193
+ placed.set(next, {
194
+ x: here.x + direction.x,
195
+ y: here.y + direction.y
196
+ });
197
+ seen.add(next);
198
+ queue.push({
199
+ id: next,
200
+ incoming: angleOf(here, placed.get(next))
201
+ });
202
+ progress = true;
203
+ }
204
+ }
205
+ return progress;
206
+ };
207
+ let changed = true;
208
+ while (changed) {
209
+ changed = grow();
210
+ for (const ring of rings.slice(1)) {
211
+ if (ring.every((id) => placed.has(id))) continue;
212
+ const anchor = ring.find((id) => placed.has(id));
213
+ if (anchor === void 0) continue;
214
+ const here = placed.get(anchor);
215
+ const away = usedAngles(anchor);
216
+ const outward = away.length ? away.reduce((a, b) => a + b, 0) / away.length + Math.PI : 0;
217
+ const radius = 1 / (2 * Math.sin(Math.PI / ring.length));
218
+ placeRing(ring, {
219
+ x: here.x + radius * Math.cos(outward),
220
+ y: here.y + radius * Math.sin(outward)
221
+ });
222
+ changed = true;
223
+ }
224
+ }
225
+ const missing = structure.atoms.filter((a) => !placed.has(a.id));
226
+ if (missing.length) {
227
+ const right = Math.max(...[...placed.values()].map((p) => p.x), 0) + 2;
228
+ missing.forEach((atom, i) => placed.set(atom.id, {
229
+ x: right + i,
230
+ y: 0
231
+ }));
232
+ }
233
+ return placed;
234
+ }
235
+ /** Put a ring's atoms in bonded order, so a polygon can be walked round. */
236
+ function orderRing(structure, ring) {
237
+ const inRing = new Set(ring);
238
+ const ordered = [ring[0]];
239
+ const seen = new Set(ordered);
240
+ while (ordered.length < ring.length) {
241
+ const at = ordered[ordered.length - 1];
242
+ const next = linked(structure, at).find((n) => inRing.has(n) && !seen.has(n));
243
+ if (next === void 0) break;
244
+ seen.add(next);
245
+ ordered.push(next);
246
+ }
247
+ return ordered;
248
+ }
249
+ /** The layout plus everything a figure needs to place it on a page. */
250
+ function layoutOf(structure) {
251
+ const positions = layout(structure);
252
+ const xs = [...positions.values()].map((p) => p.x);
253
+ const ys = [...positions.values()].map((p) => p.y);
254
+ return {
255
+ positions,
256
+ bounds: {
257
+ minX: Math.min(...xs, 0),
258
+ minY: Math.min(...ys, 0),
259
+ maxX: Math.max(...xs, 0),
260
+ maxY: Math.max(...ys, 0)
261
+ },
262
+ rings: ringSystems(structure).map((ring) => orderRing(structure, ring))
263
+ };
264
+ }
265
+ /** The centre of a ring, for the delocalisation circle. */
266
+ function ringCentre(ring, positions) {
267
+ const pts = ring.map((id) => positions.get(id)).filter(Boolean);
268
+ return {
269
+ x: pts.reduce((s, p) => s + p.x, 0) / pts.length,
270
+ y: pts.reduce((s, p) => s + p.y, 0) / pts.length
271
+ };
272
+ }
273
+ /**
274
+ * Several free directions at an atom, for drawing its hydrogens in a displayed formula.
275
+ *
276
+ * Returned widest-gap first and spread apart, so a CH2 puts its two hydrogens on opposite sides
277
+ * rather than side by side where they would touch.
278
+ */
279
+ function freeDirections(structure, id, positions, count) {
280
+ const here = positions.get(id);
281
+ if (!here || count <= 0) return [];
282
+ const taken = [...linked(structure, id).map((n) => positions.get(n)).filter((p) => Boolean(p)).map((p) => angleOf(here, p))];
283
+ const out = [];
284
+ for (let k = 0; k < count; k += 1) {
285
+ let best = DIRECTIONS[0];
286
+ let bestGap = -1;
287
+ for (const d of DIRECTIONS) {
288
+ const a = Math.atan2(d.y, d.x);
289
+ const gap = taken.length ? Math.min(...taken.map((u) => Math.abs(Math.atan2(Math.sin(a - u), Math.cos(a - u))))) : Math.PI;
290
+ if (gap > bestGap) {
291
+ bestGap = gap;
292
+ best = d;
293
+ }
294
+ }
295
+ out.push(best);
296
+ taken.push(Math.atan2(best.y, best.x));
297
+ }
298
+ return out;
299
+ }
300
+ /**
301
+ * A free direction at an atom, for drawing an open valence or an implicit-hydrogen label.
302
+ *
303
+ * Returns the lattice direction furthest from every bond the atom already has, which is where a
304
+ * chemist would put it.
305
+ */
306
+ function freeDirection(structure, id, positions) {
307
+ const here = positions.get(id);
308
+ if (!here) return {
309
+ x: 1,
310
+ y: 0
311
+ };
312
+ const used = linked(structure, id).map((n) => positions.get(n)).filter((p) => Boolean(p)).map((p) => angleOf(here, p));
313
+ if (used.length === 0) return {
314
+ x: 1,
315
+ y: 0
316
+ };
317
+ const all = [...positions.values()];
318
+ const centroidX = all.reduce((sum, p) => sum + p.x, 0) / all.length;
319
+ const outward = here.x >= centroidX ? {
320
+ x: 1,
321
+ y: 0
322
+ } : {
323
+ x: -1,
324
+ y: 0
325
+ };
326
+ const outwardAngle = Math.atan2(outward.y, outward.x);
327
+ if (!used.some((u) => Math.abs(Math.atan2(Math.sin(outwardAngle - u), Math.cos(outwardAngle - u))) < STEP - 1e-6)) return outward;
328
+ let best = DIRECTIONS[0];
329
+ let bestGap = -1;
330
+ for (const d of DIRECTIONS) {
331
+ const a = Math.atan2(d.y, d.x);
332
+ const gap = Math.min(...used.map((u) => Math.abs(Math.atan2(Math.sin(a - u), Math.cos(a - u)))));
333
+ if (gap > bestGap) {
334
+ bestGap = gap;
335
+ best = d;
336
+ }
337
+ }
338
+ return best;
339
+ }
340
+
341
+ //#endregion
342
+ export { freeDirection, freeDirections, layout, layoutOf, ringCentre };