@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,79 @@
1
+ import { Spectrum, XYZ } from "./spectrum.mjs";
2
+ import { SRGB } from "./srgb.mjs";
3
+
4
+ //#region src/colour/absorption.d.ts
5
+ /** One absorption band, as a spectrum is quoted: where it peaks, how wide, how strong. */
6
+ interface AbsorptionBand {
7
+ /** Absorption maximum, λmax, in nm. */
8
+ readonly peakNm: number;
9
+ /** Full width at half maximum, in nm. A d-d band in water is typically 80 to 150. */
10
+ readonly widthNm: number;
11
+ /** Peak absorbance at `amount` = 1. Around 1 gives a clearly coloured solution. */
12
+ readonly strength: number;
13
+ }
14
+ /** Total absorbance at one wavelength, summed over every band and scaled by c·l. */
15
+ declare function absorbance(bands: readonly AbsorptionBand[], nm: number, amount?: number): number;
16
+ /** The fraction of light getting through at each wavelength: Beer-Lambert, `10^-A`. */
17
+ declare const transmittance: (bands: readonly AbsorptionBand[], amount?: number) => Spectrum;
18
+ /** A colour, in every form a caller here needs. */
19
+ interface Colour {
20
+ readonly xyz: XYZ;
21
+ readonly srgb: SRGB;
22
+ /** `#rrggbb`, ready to draw with. */
23
+ readonly hex: string;
24
+ /** Hue in degrees, saturation and lightness 0 to 1. */
25
+ readonly hsl: {
26
+ readonly h: number;
27
+ readonly s: number;
28
+ readonly l: number;
29
+ };
30
+ /**
31
+ * The single wavelength the eye would call this colour, if there is one.
32
+ *
33
+ * `undefined` for a purple, which is not a wavelength at all. That is the honest answer for a
34
+ * solution absorbing green: what is left is red plus blue, and no lamp emits it.
35
+ */
36
+ readonly dominantNm: number | undefined;
37
+ }
38
+ /**
39
+ * The colour of any spectrum: the general entry point into this engine.
40
+ *
41
+ * Until this existed the only way in was through {@link solutionColour}, which asks what colour is
42
+ * LEFT when a sample absorbs. That is the right question for a solution in a beaker and the wrong
43
+ * one for anything that emits its own light, where the spectrum is what arrives rather than what
44
+ * survives. A flame, a discharge tube and a star all needed a door that was not there.
45
+ *
46
+ * The white point is what the result is balanced against, and it defaults to equal energy. Pass the
47
+ * illuminant instead when the sample is being viewed under a particular light.
48
+ */
49
+ declare const colourFromSpectrum: (spectrum: Spectrum, white?: Spectrum) => Colour;
50
+ /**
51
+ * The colour of one wavelength, at the brightness a spectrum is DRAWN at.
52
+ *
53
+ * The luminance is normalised, and that is a deliberate departure from the raw physics. One 5 nm
54
+ * slice of an equal-energy spectrum carries very little light, so unnormalised every wavelength
55
+ * came out near black: the bar was ten shades of very dark. What a spectrum illustration shows is
56
+ * the HUE at each wavelength, which is what this returns.
57
+ *
58
+ * The ends still fade, because they have to. Below about 410 nm and above about 690 the eye's own
59
+ * response has nearly gone, and a bar that stayed vivid to 780 would claim a visibility that is not
60
+ * there. The taper is that response, not a decoration.
61
+ */
62
+ declare function spectralColour(nm: number): Colour;
63
+ /**
64
+ * The colour of a solution, from its absorption bands.
65
+ *
66
+ * `white` defaults to equal energy, so the result is a transmittance colour: absorb nothing and it
67
+ * comes out white, which is the correct answer for a colourless solution.
68
+ */
69
+ declare function solutionColour(bands: readonly AbsorptionBand[], amount?: number, white?: Spectrum): Colour;
70
+ /**
71
+ * The colour left when ONE band is taken out of white light: the complementary colour, computed.
72
+ *
73
+ * This is the whole of "a solution is the colour it does not absorb", and it is derived rather than
74
+ * tabulated. Absorbing 575 nm gives a violet, and the violet has no dominant wavelength of its own,
75
+ * which is the reason the rule cannot be stated as "the opposite wavelength".
76
+ */
77
+ declare const complementaryOf: (absorbedNm: number, widthNm?: number, strength?: number) => Colour;
78
+ //#endregion
79
+ export { AbsorptionBand, Colour, absorbance, colourFromSpectrum, complementaryOf, solutionColour, spectralColour, transmittance };
@@ -0,0 +1,137 @@
1
+ import { yBar } from "./observer.mjs";
2
+ import { EQUAL_ENERGY, dominantWavelength, spectrumToXYZ } from "./spectrum.mjs";
3
+ import { fitGamut, hexOf, hslOf, linearToSRGB, whiteBalance, xyzToLinear } from "./srgb.mjs";
4
+
5
+ //#region src/colour/absorption.ts
6
+ /**
7
+ * What a solution looks like, worked out from what it absorbs.
8
+ *
9
+ * This is the file a chemistry lab actually wants. Declare the absorption band a species has,
10
+ * say how much of it is in the way, and the colour that reaches the eye comes out. Nothing is
11
+ * looked up: the complementary colour is produced by removing the band from white light and
12
+ * integrating what is left against the eye's own response, which is what the eye does.
13
+ *
14
+ * ## Why the answer changes with concentration, for free
15
+ *
16
+ * Beer-Lambert says absorbance is proportional to concentration and path length, and transmittance
17
+ * is `10^-A`. So `amount` here is the product c·l in whatever units make the band's `strength` an
18
+ * absorbance, and the consequences arrive on their own: a dilute copper solution is a pale blue that
19
+ * a concentrated one deepens without changing hue, until the band saturates and the colour stops
20
+ * responding. A lab that wants to teach colorimetry has that behaviour already, rather than needing
21
+ * a second table of colours for "dilute".
22
+ *
23
+ * ## Why the band is a Gaussian
24
+ *
25
+ * A d-d transition in solution is broadened by every solvent molecule moving, and the observed
26
+ * envelope is close to Gaussian. `widthNm` is the FULL WIDTH AT HALF MAXIMUM, which is the number a
27
+ * spectrum is quoted with, not a standard deviation.
28
+ */
29
+ /** Converts FWHM to the coefficient of a Gaussian exponent. */
30
+ const FWHM = 4 * Math.LN2;
31
+ /** Total absorbance at one wavelength, summed over every band and scaled by c·l. */
32
+ function absorbance(bands, nm, amount = 1) {
33
+ let a = 0;
34
+ for (const band of bands) {
35
+ const t = (nm - band.peakNm) / band.widthNm;
36
+ a += band.strength * Math.exp(-FWHM * t * t);
37
+ }
38
+ return a * amount;
39
+ }
40
+ /** The fraction of light getting through at each wavelength: Beer-Lambert, `10^-A`. */
41
+ const transmittance = (bands, amount = 1) => (nm) => Math.pow(10, -absorbance(bands, nm, amount));
42
+ /**
43
+ * Every colour here is measured against a lamp, so the lamp is divided out.
44
+ *
45
+ * The default illuminant's own linear RGB is computed once, LAZILY. A module-level `new Map()` was
46
+ * the first version and it is a top-level side effect: a bundler cannot prove it inert, so it
47
+ * pins this module into any build that merely imports the package. A `let` filled on first call
48
+ * costs nothing and leaves the module free of top-level work.
49
+ */
50
+ let equalEnergyWhite;
51
+ const whiteLinear = (white) => {
52
+ if (white !== EQUAL_ENERGY) return xyzToLinear(spectrumToXYZ(white, white));
53
+ equalEnergyWhite ??= xyzToLinear(spectrumToXYZ(white, white));
54
+ return equalEnergyWhite;
55
+ };
56
+ const colourOf = (xyz, white) => {
57
+ const srgb = linearToSRGB(whiteBalance(xyzToLinear(xyz), whiteLinear(white)));
58
+ return {
59
+ xyz,
60
+ srgb,
61
+ hex: hexOf(srgb),
62
+ hsl: hslOf(srgb),
63
+ dominantNm: dominantWavelength(xyz)
64
+ };
65
+ };
66
+ /**
67
+ * The colour of any spectrum: the general entry point into this engine.
68
+ *
69
+ * Until this existed the only way in was through {@link solutionColour}, which asks what colour is
70
+ * LEFT when a sample absorbs. That is the right question for a solution in a beaker and the wrong
71
+ * one for anything that emits its own light, where the spectrum is what arrives rather than what
72
+ * survives. A flame, a discharge tube and a star all needed a door that was not there.
73
+ *
74
+ * The white point is what the result is balanced against, and it defaults to equal energy. Pass the
75
+ * illuminant instead when the sample is being viewed under a particular light.
76
+ */
77
+ const colourFromSpectrum = (spectrum, white = EQUAL_ENERGY) => colourOf(spectrumToXYZ(spectrum, white), white);
78
+ /**
79
+ * The colour of one wavelength, at the brightness a spectrum is DRAWN at.
80
+ *
81
+ * The luminance is normalised, and that is a deliberate departure from the raw physics. One 5 nm
82
+ * slice of an equal-energy spectrum carries very little light, so unnormalised every wavelength
83
+ * came out near black: the bar was ten shades of very dark. What a spectrum illustration shows is
84
+ * the HUE at each wavelength, which is what this returns.
85
+ *
86
+ * The ends still fade, because they have to. Below about 410 nm and above about 690 the eye's own
87
+ * response has nearly gone, and a bar that stayed vivid to 780 would claim a visibility that is not
88
+ * there. The taper is that response, not a decoration.
89
+ */
90
+ function spectralColour(nm) {
91
+ const hueNm = Math.max(400, Math.min(660, nm));
92
+ const raw = spectrumToXYZ((l) => Math.abs(l - hueNm) <= 2.5 ? 1 : 0);
93
+ if (raw.Y <= 0) return colourOf(raw, EQUAL_ENERGY);
94
+ const k = 1.4 / raw.Y;
95
+ const full = fitGamut(whiteBalance(xyzToLinear({
96
+ X: raw.X * k,
97
+ Y: raw.Y * k,
98
+ Z: raw.Z * k
99
+ }), whiteLinear(EQUAL_ENERGY)));
100
+ const visible = Math.sqrt(Math.min(1, yBar(nm) / .05));
101
+ const srgb = linearToSRGB({
102
+ r: full.r * visible,
103
+ g: full.g * visible,
104
+ b: full.b * visible
105
+ });
106
+ return {
107
+ xyz: raw,
108
+ srgb,
109
+ hex: hexOf(srgb),
110
+ hsl: hslOf(srgb),
111
+ dominantNm: dominantWavelength(raw)
112
+ };
113
+ }
114
+ /**
115
+ * The colour of a solution, from its absorption bands.
116
+ *
117
+ * `white` defaults to equal energy, so the result is a transmittance colour: absorb nothing and it
118
+ * comes out white, which is the correct answer for a colourless solution.
119
+ */
120
+ function solutionColour(bands, amount = 1, white = EQUAL_ENERGY) {
121
+ return colourOf(spectrumToXYZ(transmittance(bands, amount), white), white);
122
+ }
123
+ /**
124
+ * The colour left when ONE band is taken out of white light: the complementary colour, computed.
125
+ *
126
+ * This is the whole of "a solution is the colour it does not absorb", and it is derived rather than
127
+ * tabulated. Absorbing 575 nm gives a violet, and the violet has no dominant wavelength of its own,
128
+ * which is the reason the rule cannot be stated as "the opposite wavelength".
129
+ */
130
+ const complementaryOf = (absorbedNm, widthNm = 110, strength = 1.6) => solutionColour([{
131
+ peakNm: absorbedNm,
132
+ widthNm,
133
+ strength
134
+ }]);
135
+
136
+ //#endregion
137
+ export { absorbance, colourFromSpectrum, complementaryOf, solutionColour, spectralColour, transmittance };
@@ -0,0 +1,6 @@
1
+ import { VISIBLE_FROM, VISIBLE_TO, observer, xBar, yBar, zBar } from "./observer.mjs";
2
+ import { EQUAL_ENERGY, Spectrum, XYZ, chromaticity, dominantWavelength, monochromatic, spectrumToXYZ } from "./spectrum.mjs";
3
+ import { LinearRGB, SRGB, fitGamut, hexOf, hslOf, linearToSRGB, xyzToLinear, xyzToSRGB } from "./srgb.mjs";
4
+ import { AbsorptionBand, Colour, absorbance, colourFromSpectrum, complementaryOf, solutionColour, spectralColour, transmittance } from "./absorption.mjs";
5
+ import { ColourName, nameOf } from "./name.mjs";
6
+ export { type AbsorptionBand, type Colour, type ColourName, EQUAL_ENERGY, type LinearRGB, type SRGB, type Spectrum, VISIBLE_FROM, VISIBLE_TO, type XYZ, absorbance, chromaticity, colourFromSpectrum, complementaryOf, dominantWavelength, fitGamut, hexOf, hslOf, linearToSRGB, monochromatic, nameOf, observer, solutionColour, spectralColour, spectrumToXYZ, transmittance, xBar, xyzToLinear, xyzToSRGB, yBar, zBar };
@@ -0,0 +1,7 @@
1
+ import { VISIBLE_FROM, VISIBLE_TO, observer, xBar, yBar, zBar } from "./observer.mjs";
2
+ import { EQUAL_ENERGY, chromaticity, dominantWavelength, monochromatic, spectrumToXYZ } from "./spectrum.mjs";
3
+ import { fitGamut, hexOf, hslOf, linearToSRGB, xyzToLinear, xyzToSRGB } from "./srgb.mjs";
4
+ import { absorbance, colourFromSpectrum, complementaryOf, solutionColour, spectralColour, transmittance } from "./absorption.mjs";
5
+ import { nameOf } from "./name.mjs";
6
+
7
+ export { EQUAL_ENERGY, VISIBLE_FROM, VISIBLE_TO, absorbance, chromaticity, colourFromSpectrum, complementaryOf, dominantWavelength, fitGamut, hexOf, hslOf, linearToSRGB, monochromatic, nameOf, observer, solutionColour, spectralColour, spectrumToXYZ, transmittance, xBar, xyzToLinear, xyzToSRGB, yBar, zBar };
@@ -0,0 +1,14 @@
1
+ import { Colour } from "./absorption.mjs";
2
+
3
+ //#region src/colour/name.d.ts
4
+ type ColourName = 'red' | 'orange' | 'yellow' | 'yellow-green' | 'green' | 'blue-green' | 'green-blue' | 'blue' | 'violet' | 'purple' | 'pink' | 'brown' | 'grey' | 'colourless';
5
+ /**
6
+ * Name a colour.
7
+ *
8
+ * Saturation and lightness are checked BEFORE hue, because a pale unsaturated thing has a hue angle
9
+ * and is not that colour. A copper(II) solution and a drop of it in a swimming pool have nearly the
10
+ * same hue; only one of them is blue.
11
+ */
12
+ declare function nameOf(colour: Colour): ColourName;
13
+ //#endregion
14
+ export { ColourName, nameOf };
@@ -0,0 +1,76 @@
1
+ //#region src/colour/name.ts
2
+ /**
3
+ * Hue bands, in degrees, each holding the upper bound of the name before it.
4
+ *
5
+ * The boundaries are calibrated against complexes whose colour a mark scheme names, not spaced
6
+ * evenly. Two of them were moved after measurement:
7
+ *
8
+ * YELLOW starts at 34, not 42. [Cr(NH₃)₆]³⁺ computes to hue 38 and every syllabus calls it
9
+ * yellow. Chemistry's "yellow" reaches further into the orange than a designer's does.
10
+ *
11
+ * BLUE ends at 246, not 250. [Cr(H₂O)₆]³⁺ computes to hue 250 and is violet, which put it exactly
12
+ * on the old boundary and therefore one rounding away from being named wrong.
13
+ */
14
+ const HUES = [
15
+ {
16
+ upTo: 14,
17
+ name: "red"
18
+ },
19
+ {
20
+ upTo: 34,
21
+ name: "orange"
22
+ },
23
+ {
24
+ upTo: 66,
25
+ name: "yellow"
26
+ },
27
+ {
28
+ upTo: 90,
29
+ name: "yellow-green"
30
+ },
31
+ {
32
+ upTo: 152,
33
+ name: "green"
34
+ },
35
+ {
36
+ upTo: 176,
37
+ name: "blue-green"
38
+ },
39
+ {
40
+ upTo: 196,
41
+ name: "green-blue"
42
+ },
43
+ {
44
+ upTo: 246,
45
+ name: "blue"
46
+ },
47
+ {
48
+ upTo: 290,
49
+ name: "violet"
50
+ },
51
+ {
52
+ upTo: 330,
53
+ name: "purple"
54
+ },
55
+ {
56
+ upTo: 360,
57
+ name: "red"
58
+ }
59
+ ];
60
+ /**
61
+ * Name a colour.
62
+ *
63
+ * Saturation and lightness are checked BEFORE hue, because a pale unsaturated thing has a hue angle
64
+ * and is not that colour. A copper(II) solution and a drop of it in a swimming pool have nearly the
65
+ * same hue; only one of them is blue.
66
+ */
67
+ function nameOf(colour) {
68
+ const { h, s, l } = colour.hsl;
69
+ if (l > .93 || s < .06) return l > .93 ? "colourless" : "grey";
70
+ if (l < .42 && s < .7 && h >= 14 && h < 50) return "brown";
71
+ if (l > .72 && (h < 20 || h >= 330)) return "pink";
72
+ return HUES.find((band) => h < band.upTo)?.name ?? "red";
73
+ }
74
+
75
+ //#endregion
76
+ export { nameOf };
@@ -0,0 +1,31 @@
1
+ //#region src/colour/observer.d.ts
2
+ /**
3
+ * The CIE 1931 2° standard observer: how a human eye responds to each wavelength.
4
+ *
5
+ * This is the measured data the whole engine rests on. Three response curves, one per cone type as
6
+ * reconciled by the CIE in 1931, and everything else here is arithmetic on top of them.
7
+ *
8
+ * ## Why an analytic fit rather than a 81-row table
9
+ *
10
+ * The published data is tabulated at 5 nm and would be 243 numbers typed by hand, every one of them
11
+ * a chance to transpose a digit that no test would notice. Wyman, Sloan and Shirley (2013),
12
+ * "Simple Analytic Approximations to the CIE XYZ Color Matching Functions", JCGT 2(2), fit each
13
+ * curve with a sum of piecewise-Gaussian lobes to under about 1% error. Their constants are the
14
+ * whole dataset in twenty numbers, and the fit is checked the only way that matters: the white
15
+ * points it produces. `tests/colour.test.ts` asserts that an equal-energy spectrum lands on
16
+ * (1/3, 1/3) in chromaticity, which is the definition of illuminant E and which a mistyped constant
17
+ * would miss.
18
+ */
19
+ /** Visible range, in nanometres. Outside it the response is zero and a colour is not defined. */
20
+ declare const VISIBLE_FROM = 380;
21
+ declare const VISIBLE_TO = 780;
22
+ /** The x̄ response: two red-orange lobes, with a small negative correction near cyan. */
23
+ declare const xBar: (nm: number) => number;
24
+ /** The ȳ response, which is also the luminous efficiency curve: what "bright" means. */
25
+ declare const yBar: (nm: number) => number;
26
+ /** The z̄ response: entirely in the blue and violet. */
27
+ declare const zBar: (nm: number) => number;
28
+ /** All three at one wavelength. */
29
+ declare const observer: (nm: number) => readonly [number, number, number];
30
+ //#endregion
31
+ export { VISIBLE_FROM, VISIBLE_TO, observer, xBar, yBar, zBar };
@@ -0,0 +1,46 @@
1
+ //#region src/colour/observer.ts
2
+ /**
3
+ * The CIE 1931 2° standard observer: how a human eye responds to each wavelength.
4
+ *
5
+ * This is the measured data the whole engine rests on. Three response curves, one per cone type as
6
+ * reconciled by the CIE in 1931, and everything else here is arithmetic on top of them.
7
+ *
8
+ * ## Why an analytic fit rather than a 81-row table
9
+ *
10
+ * The published data is tabulated at 5 nm and would be 243 numbers typed by hand, every one of them
11
+ * a chance to transpose a digit that no test would notice. Wyman, Sloan and Shirley (2013),
12
+ * "Simple Analytic Approximations to the CIE XYZ Color Matching Functions", JCGT 2(2), fit each
13
+ * curve with a sum of piecewise-Gaussian lobes to under about 1% error. Their constants are the
14
+ * whole dataset in twenty numbers, and the fit is checked the only way that matters: the white
15
+ * points it produces. `tests/colour.test.ts` asserts that an equal-energy spectrum lands on
16
+ * (1/3, 1/3) in chromaticity, which is the definition of illuminant E and which a mistyped constant
17
+ * would miss.
18
+ */
19
+ /** Visible range, in nanometres. Outside it the response is zero and a colour is not defined. */
20
+ const VISIBLE_FROM = 380;
21
+ const VISIBLE_TO = 780;
22
+ /**
23
+ * A Gaussian that is allowed a different width on each side of its peak.
24
+ *
25
+ * `inv1` and `inv2` are RECIPROCAL widths in 1/nm, which is the form the paper publishes and the
26
+ * form that keeps the constants below readable.
27
+ */
28
+ const lobe = (nm, peak, inv1, inv2) => {
29
+ const t = (nm - peak) * (nm < peak ? inv1 : inv2);
30
+ return Math.exp(-.5 * t * t);
31
+ };
32
+ /** The x̄ response: two red-orange lobes, with a small negative correction near cyan. */
33
+ const xBar = (nm) => .362 * lobe(nm, 442, .0624, .0374) + 1.056 * lobe(nm, 599.8, .0264, .0323) - .065 * lobe(nm, 501.1, .049, .0382);
34
+ /** The ȳ response, which is also the luminous efficiency curve: what "bright" means. */
35
+ const yBar = (nm) => .821 * lobe(nm, 568.8, .0213, .0247) + .286 * lobe(nm, 530.9, .0613, .0322);
36
+ /** The z̄ response: entirely in the blue and violet. */
37
+ const zBar = (nm) => 1.217 * lobe(nm, 437, .0845, .0278) + .681 * lobe(nm, 459, .0385, .0725);
38
+ /** All three at one wavelength. */
39
+ const observer = (nm) => [
40
+ xBar(nm),
41
+ yBar(nm),
42
+ zBar(nm)
43
+ ];
44
+
45
+ //#endregion
46
+ export { VISIBLE_FROM, VISIBLE_TO, observer, xBar, yBar, zBar };
@@ -0,0 +1,61 @@
1
+ //#region src/colour/spectrum.d.ts
2
+ /**
3
+ * A spectrum, and the tristimulus values it produces in a human eye.
4
+ *
5
+ * A spectrum here is a FUNCTION of wavelength, not an array. That is what lets an absorption band
6
+ * be declared by its peak and width rather than sampled into a table by every caller, and it is
7
+ * what lets the integration step choose its own resolution.
8
+ *
9
+ * ## White light is equal energy, and that is a choice worth stating
10
+ *
11
+ * The engine's default white is CIE illuminant E: the same power at every visible wavelength. Real
12
+ * daylight is D65, which needs its own measured table and is slightly blue. E is used because a
13
+ * chemistry lesson says "white light goes in, one band is absorbed, the rest reaches your eye", and
14
+ * in that sentence white light means equal at every wavelength. Using D65 would tint every result
15
+ * by the lamp rather than by the chemistry, which is the one thing a colour here must not do.
16
+ *
17
+ * Pass any other illuminant explicitly if a lab ever needs one.
18
+ */
19
+ /** Spectral power at a wavelength in nm. Any non-negative scale: only ratios matter. */
20
+ type Spectrum = (nm: number) => number;
21
+ /** CIE XYZ tristimulus values, normalised so that a white spectrum gives Y = 1. */
22
+ interface XYZ {
23
+ readonly X: number;
24
+ readonly Y: number;
25
+ readonly Z: number;
26
+ }
27
+ /** Equal power at every visible wavelength: CIE illuminant E, and what "white light" means here. */
28
+ declare const EQUAL_ENERGY: Spectrum;
29
+ /** A single wavelength, as a spectrum. Width 0 is not representable, so it is one nanometre wide. */
30
+ declare const monochromatic: (peakNm: number) => Spectrum;
31
+ /**
32
+ * Integrate a spectrum against the observer, normalised so the reference white gives Y = 1.
33
+ *
34
+ * The normalisation is what makes the result a REFLECTANCE or transmittance colour rather than a
35
+ * brightness: a solution that absorbs nothing comes out white rather than some arbitrary Y.
36
+ *
37
+ * `step` is 1 nm, which is finer than the 5 nm the observer data is published at, so the
38
+ * integration error is far below the error in the fit itself.
39
+ */
40
+ declare function spectrumToXYZ(spectrum: Spectrum, white?: Spectrum): XYZ;
41
+ /**
42
+ * Chromaticity: the colour with its brightness divided out.
43
+ *
44
+ * Useful for one thing above all, which is checking this engine against a published number. An
45
+ * equal-energy spectrum must land on (1/3, 1/3), and that is a test the fitted observer curves
46
+ * cannot pass by accident.
47
+ */
48
+ declare function chromaticity(xyz: XYZ): {
49
+ readonly x: number;
50
+ readonly y: number;
51
+ };
52
+ /**
53
+ * The dominant wavelength: which single wavelength the eye would call this colour.
54
+ *
55
+ * Returned as the wavelength whose chromaticity is closest in direction from the white point, and
56
+ * `undefined` when the colour is a purple, because purples are not on the spectral locus at all.
57
+ * That is not a limitation to work around: it is why a magenta cannot be written as a wavelength.
58
+ */
59
+ declare function dominantWavelength(xyz: XYZ): number | undefined;
60
+ //#endregion
61
+ export { EQUAL_ENERGY, Spectrum, XYZ, chromaticity, dominantWavelength, monochromatic, spectrumToXYZ };
@@ -0,0 +1,108 @@
1
+ import { VISIBLE_FROM, VISIBLE_TO, observer } from "./observer.mjs";
2
+
3
+ //#region src/colour/spectrum.ts
4
+ /**
5
+ * A spectrum, and the tristimulus values it produces in a human eye.
6
+ *
7
+ * A spectrum here is a FUNCTION of wavelength, not an array. That is what lets an absorption band
8
+ * be declared by its peak and width rather than sampled into a table by every caller, and it is
9
+ * what lets the integration step choose its own resolution.
10
+ *
11
+ * ## White light is equal energy, and that is a choice worth stating
12
+ *
13
+ * The engine's default white is CIE illuminant E: the same power at every visible wavelength. Real
14
+ * daylight is D65, which needs its own measured table and is slightly blue. E is used because a
15
+ * chemistry lesson says "white light goes in, one band is absorbed, the rest reaches your eye", and
16
+ * in that sentence white light means equal at every wavelength. Using D65 would tint every result
17
+ * by the lamp rather than by the chemistry, which is the one thing a colour here must not do.
18
+ *
19
+ * Pass any other illuminant explicitly if a lab ever needs one.
20
+ */
21
+ /** Equal power at every visible wavelength: CIE illuminant E, and what "white light" means here. */
22
+ const EQUAL_ENERGY = () => 1;
23
+ /** A single wavelength, as a spectrum. Width 0 is not representable, so it is one nanometre wide. */
24
+ const monochromatic = (peakNm) => (nm) => Math.abs(nm - peakNm) <= .5 ? 1 : 0;
25
+ /**
26
+ * Integrate a spectrum against the observer, normalised so the reference white gives Y = 1.
27
+ *
28
+ * The normalisation is what makes the result a REFLECTANCE or transmittance colour rather than a
29
+ * brightness: a solution that absorbs nothing comes out white rather than some arbitrary Y.
30
+ *
31
+ * `step` is 1 nm, which is finer than the 5 nm the observer data is published at, so the
32
+ * integration error is far below the error in the fit itself.
33
+ */
34
+ function spectrumToXYZ(spectrum, white = EQUAL_ENERGY) {
35
+ let X = 0;
36
+ let Y = 0;
37
+ let Z = 0;
38
+ let norm = 0;
39
+ for (let nm = 380; nm <= 780; nm += 1) {
40
+ const [x, y, z] = observer(nm);
41
+ const w = white(nm);
42
+ const p = spectrum(nm) * w;
43
+ X += p * x;
44
+ Y += p * y;
45
+ Z += p * z;
46
+ norm += w * y;
47
+ }
48
+ if (norm === 0) return {
49
+ X: 0,
50
+ Y: 0,
51
+ Z: 0
52
+ };
53
+ return {
54
+ X: X / norm,
55
+ Y: Y / norm,
56
+ Z: Z / norm
57
+ };
58
+ }
59
+ /**
60
+ * Chromaticity: the colour with its brightness divided out.
61
+ *
62
+ * Useful for one thing above all, which is checking this engine against a published number. An
63
+ * equal-energy spectrum must land on (1/3, 1/3), and that is a test the fitted observer curves
64
+ * cannot pass by accident.
65
+ */
66
+ function chromaticity(xyz) {
67
+ const sum = xyz.X + xyz.Y + xyz.Z;
68
+ if (sum === 0) return {
69
+ x: 0,
70
+ y: 0
71
+ };
72
+ return {
73
+ x: xyz.X / sum,
74
+ y: xyz.Y / sum
75
+ };
76
+ }
77
+ /**
78
+ * The dominant wavelength: which single wavelength the eye would call this colour.
79
+ *
80
+ * Returned as the wavelength whose chromaticity is closest in direction from the white point, and
81
+ * `undefined` when the colour is a purple, because purples are not on the spectral locus at all.
82
+ * That is not a limitation to work around: it is why a magenta cannot be written as a wavelength.
83
+ */
84
+ function dominantWavelength(xyz) {
85
+ const c = chromaticity(xyz);
86
+ const dx = c.x - 1 / 3;
87
+ const dy = c.y - 1 / 3;
88
+ const len = Math.hypot(dx, dy);
89
+ if (len < 1e-4) return void 0;
90
+ let best;
91
+ let bestDot = -1;
92
+ for (let nm = 380; nm <= 780; nm += 1) {
93
+ const s = chromaticity(spectrumToXYZ(monochromatic(nm)));
94
+ const ex = s.x - 1 / 3;
95
+ const ey = s.y - 1 / 3;
96
+ const elen = Math.hypot(ex, ey);
97
+ if (elen < 1e-6) continue;
98
+ const dot = (dx * ex + dy * ey) / (len * elen);
99
+ if (dot > bestDot) {
100
+ bestDot = dot;
101
+ best = nm;
102
+ }
103
+ }
104
+ return bestDot > .999 ? best : void 0;
105
+ }
106
+
107
+ //#endregion
108
+ export { EQUAL_ENERGY, chromaticity, dominantWavelength, monochromatic, spectrumToXYZ };
@@ -0,0 +1,49 @@
1
+ import { XYZ } from "./spectrum.mjs";
2
+
3
+ //#region src/colour/srgb.d.ts
4
+ /** Linear RGB in the sRGB primaries. Components may be negative or above one before fitting. */
5
+ interface LinearRGB {
6
+ readonly r: number;
7
+ readonly g: number;
8
+ readonly b: number;
9
+ }
10
+ /** sRGB with the transfer function applied, each channel 0 to 255. */
11
+ interface SRGB {
12
+ readonly r: number;
13
+ readonly g: number;
14
+ readonly b: number;
15
+ }
16
+ /** The sRGB / Rec. 709 matrix, for a D65 white point. */
17
+ declare function xyzToLinear(xyz: XYZ): LinearRGB;
18
+ /**
19
+ * Bring a colour into gamut: clamp the negative channels, then scale to fit.
20
+ *
21
+ * A pure wavelength is more saturated than three phosphors can make, so its linear RGB has a
22
+ * negative component and something has to give. The alternative tried here first was to desaturate,
23
+ * mixing toward grey of the same luminance until the negative channel reached zero, on the argument
24
+ * that desaturation preserves hue and clamping does not.
25
+ *
26
+ * Measured across the spectrum at 20 nm, that argument is simply wrong for this data, and in the
27
+ * red it is badly wrong. Desaturating 650 nm gives `#ff004e`, a pink, because the green channel is
28
+ * far more negative than the blue: mixing far enough to lift green to zero carries blue up past it.
29
+ * Clamping gives `#ff0000`. The same holds at 530 nm, where desaturating produces a mint green and
30
+ * clamping produces green. Published spectral colour tables agree with the clamped values.
31
+ *
32
+ * The lesson is that the hue-preservation argument applies to a colour with ONE offending channel,
33
+ * and a spectral colour usually has two of unequal size.
34
+ */
35
+ declare function fitGamut(rgb: LinearRGB): LinearRGB;
36
+ /** Linear RGB to 8-bit sRGB, fitting the gamut first. */
37
+ declare function linearToSRGB(rgb: LinearRGB): SRGB;
38
+ /** Tristimulus values straight to a displayable colour. */
39
+ declare const xyzToSRGB: (xyz: XYZ) => SRGB;
40
+ /** `#rrggbb`, for anything that draws. */
41
+ declare const hexOf: (c: SRGB) => string;
42
+ /** Hue in degrees, saturation and lightness each 0 to 1. The form a colour NAME is decided from. */
43
+ declare function hslOf(c: SRGB): {
44
+ readonly h: number;
45
+ readonly s: number;
46
+ readonly l: number;
47
+ };
48
+ //#endregion
49
+ export { LinearRGB, SRGB, fitGamut, hexOf, hslOf, linearToSRGB, xyzToLinear, xyzToSRGB };