@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.
- package/LICENSE +1 -1
- package/README.md +5 -1
- package/dist/_virtual/_rolldown/runtime.mjs +18 -0
- package/dist/assets/index.mjs +0 -1
- package/dist/atomic/aufbau.d.mts +144 -0
- package/dist/atomic/aufbau.mjs +384 -0
- package/dist/atomic/elements.d.mts +67 -0
- package/dist/atomic/elements.mjs +785 -0
- package/dist/atomic/emission.d.mts +112 -0
- package/dist/atomic/emission.mjs +179 -0
- package/dist/atomic/index.d.mts +8 -0
- package/dist/atomic/index.mjs +9 -0
- package/dist/atomic/periodic.d.mts +82 -0
- package/dist/atomic/periodic.mjs +231 -0
- package/dist/atomic/properties.d.mts +100 -0
- package/dist/atomic/properties.mjs +574 -0
- package/dist/atomic/shells.d.mts +137 -0
- package/dist/atomic/shells.mjs +170 -0
- package/dist/atomic/trends.d.mts +148 -0
- package/dist/atomic/trends.mjs +225 -0
- package/dist/builder/Palette.mjs +50 -89
- package/dist/builder/SceneBuilder.mjs +15 -73
- package/dist/chem/chromatography.d.mts +66 -0
- package/dist/chem/chromatography.mjs +63 -0
- package/dist/chem/complex.d.mts +114 -0
- package/dist/chem/complex.mjs +234 -0
- package/dist/chem/cycle.d.mts +75 -0
- package/dist/chem/cycle.mjs +145 -0
- package/dist/chem/equation.d.mts +86 -0
- package/dist/chem/equation.mjs +168 -0
- package/dist/chem/formula.d.mts +90 -0
- package/dist/chem/formula.mjs +201 -0
- package/dist/chem/functional-groups.d.mts +31 -0
- package/dist/chem/functional-groups.mjs +187 -0
- package/dist/chem/hazard.d.mts +94 -0
- package/dist/chem/hazard.mjs +329 -0
- package/dist/chem/index.d.mts +34 -33
- package/dist/chem/index.mjs +34 -28
- package/dist/chem/ionic.d.mts +98 -0
- package/dist/chem/ionic.mjs +156 -0
- package/dist/chem/layout.d.mts +46 -0
- package/dist/chem/layout.mjs +342 -0
- package/dist/chem/lewis.d.mts +79 -0
- package/dist/chem/lewis.mjs +163 -0
- package/dist/chem/molecular-orbital.d.mts +114 -0
- package/dist/chem/molecular-orbital.mjs +201 -0
- package/dist/chem/molecule.d.mts +154 -0
- package/dist/chem/molecule.mjs +282 -0
- package/dist/chem/nmr.d.mts +153 -0
- package/dist/chem/nmr.mjs +165 -0
- package/dist/chem/polymer.d.mts +65 -0
- package/dist/chem/polymer.mjs +179 -0
- package/dist/chem/redox.d.mts +138 -0
- package/dist/chem/redox.mjs +328 -0
- package/dist/chem/relations.d.mts +183 -0
- package/dist/chem/relations.mjs +299 -0
- package/dist/chem/skeleton.d.mts +51 -0
- package/dist/chem/skeleton.mjs +186 -0
- package/dist/chem/solution.d.mts +116 -0
- package/dist/chem/solution.mjs +186 -0
- package/dist/chem/species.d.mts +47 -0
- package/dist/chem/species.mjs +128 -0
- package/dist/chem/stereo.d.mts +108 -0
- package/dist/chem/stereo.mjs +286 -0
- package/dist/chem/stoichiometry.d.mts +95 -0
- package/dist/chem/stoichiometry.mjs +145 -0
- package/dist/chem/synthesis.d.mts +113 -0
- package/dist/chem/synthesis.mjs +281 -0
- package/dist/chem/vsepr.d.mts +80 -0
- package/dist/chem/vsepr.mjs +247 -0
- package/dist/circuit/ac.d.mts +163 -0
- package/dist/circuit/ac.mjs +452 -0
- package/dist/circuit/dc.d.mts +92 -0
- package/dist/circuit/dc.mjs +333 -0
- package/dist/circuit/index.d.mts +5 -92
- package/dist/circuit/index.mjs +5 -332
- package/dist/circuit/network.d.mts +99 -0
- package/dist/circuit/network.mjs +440 -0
- package/dist/circuit/topology.d.mts +102 -0
- package/dist/circuit/topology.mjs +143 -0
- package/dist/colour/absorption.d.mts +79 -0
- package/dist/colour/absorption.mjs +137 -0
- package/dist/colour/index.d.mts +6 -0
- package/dist/colour/index.mjs +7 -0
- package/dist/colour/name.d.mts +14 -0
- package/dist/colour/name.mjs +76 -0
- package/dist/colour/observer.d.mts +31 -0
- package/dist/colour/observer.mjs +46 -0
- package/dist/colour/spectrum.d.mts +61 -0
- package/dist/colour/spectrum.mjs +108 -0
- package/dist/colour/srgb.d.mts +49 -0
- package/dist/colour/srgb.mjs +108 -0
- package/dist/core/constants.d.mts +138 -0
- package/dist/core/constants.mjs +139 -0
- package/dist/core/index.d.mts +4 -1
- package/dist/core/index.mjs +4 -1
- package/dist/core/math.d.mts +26 -0
- package/dist/core/math.mjs +37 -0
- package/dist/core/vec3.d.mts +94 -0
- package/dist/core/vec3.mjs +123 -0
- package/dist/core/worked.d.mts +35 -0
- package/dist/core/worked.mjs +44 -0
- package/dist/electro/cell.d.mts +102 -0
- package/dist/electro/cell.mjs +170 -0
- package/dist/electro/conductivity.d.mts +157 -0
- package/dist/electro/conductivity.mjs +156 -0
- package/dist/electro/couples.d.mts +114 -0
- package/dist/electro/couples.mjs +334 -0
- package/dist/electro/electrolysis.d.mts +96 -0
- package/dist/electro/electrolysis.mjs +274 -0
- package/dist/electro/faraday.d.mts +150 -0
- package/dist/electro/faraday.mjs +118 -0
- package/dist/electro/index.d.mts +6 -0
- package/dist/electro/index.mjs +7 -0
- package/dist/finance/accounting/adjustments.d.mts +17 -0
- package/dist/finance/accounting/adjustments.mjs +202 -0
- package/dist/finance/accounting/analysis.d.mts +8 -0
- package/dist/finance/accounting/analysis.mjs +205 -0
- package/dist/finance/accounting/clubs.d.mts +21 -0
- package/dist/finance/accounting/clubs.mjs +217 -0
- package/dist/finance/accounting/company-statements.d.mts +7 -0
- package/dist/finance/accounting/company-statements.mjs +150 -0
- package/dist/finance/accounting/harness.d.mts +7 -0
- package/dist/finance/accounting/harness.mjs +45 -0
- package/dist/finance/accounting/index.d.mts +18 -0
- package/dist/finance/accounting/index.mjs +18 -0
- package/dist/finance/accounting/journalize.d.mts +16 -0
- package/dist/finance/accounting/journalize.mjs +128 -0
- package/dist/finance/accounting/manufacturing.d.mts +8 -0
- package/dist/finance/accounting/manufacturing.mjs +236 -0
- package/dist/finance/accounting/partnership.d.mts +7 -0
- package/dist/finance/accounting/partnership.mjs +152 -0
- package/dist/finance/accounting/period-case.d.mts +6 -0
- package/dist/finance/accounting/period-case.mjs +71 -0
- package/dist/finance/accounting/post.d.mts +9 -0
- package/dist/finance/accounting/post.mjs +84 -0
- package/dist/finance/accounting/precision.d.mts +9 -0
- package/dist/finance/accounting/precision.mjs +15 -0
- package/dist/finance/accounting/rules.d.mts +21 -0
- package/dist/finance/accounting/rules.mjs +37 -0
- package/dist/finance/accounting/solve.d.mts +28 -0
- package/dist/finance/accounting/solve.mjs +92 -0
- package/dist/finance/accounting/statements.d.mts +7 -0
- package/dist/finance/accounting/statements.mjs +142 -0
- package/dist/finance/accounting/types.d.mts +649 -0
- package/dist/finance/accounting/validate.d.mts +7 -0
- package/dist/finance/accounting/validate.mjs +67 -0
- package/dist/finance/accounting/verification.d.mts +24 -0
- package/dist/finance/accounting/verification.mjs +386 -0
- package/dist/finance/bizsim.d.mts +75 -0
- package/dist/finance/bizsim.mjs +296 -0
- package/dist/finance/index.d.mts +95 -0
- package/dist/finance/index.mjs +145 -0
- package/dist/finance/investment-math.d.mts +7 -0
- package/dist/finance/investment-math.mjs +18 -0
- package/dist/finance/management-accounting/budgeting.d.mts +7 -0
- package/dist/finance/management-accounting/budgeting.mjs +189 -0
- package/dist/finance/management-accounting/cash-budget.d.mts +7 -0
- package/dist/finance/management-accounting/cash-budget.mjs +154 -0
- package/dist/finance/management-accounting/costing.d.mts +8 -0
- package/dist/finance/management-accounting/costing.mjs +175 -0
- package/dist/finance/management-accounting/decisions.d.mts +8 -0
- package/dist/finance/management-accounting/decisions.mjs +164 -0
- package/dist/finance/management-accounting/index.d.mts +13 -0
- package/dist/finance/management-accounting/index.mjs +13 -0
- package/dist/finance/management-accounting/integrated-budget.d.mts +8 -0
- package/dist/finance/management-accounting/integrated-budget.mjs +67 -0
- package/dist/finance/management-accounting/inventory-costing.d.mts +7 -0
- package/dist/finance/management-accounting/inventory-costing.mjs +173 -0
- package/dist/finance/management-accounting/investment-appraisal.d.mts +7 -0
- package/dist/finance/management-accounting/investment-appraisal.mjs +148 -0
- package/dist/finance/management-accounting/management-case.d.mts +8 -0
- package/dist/finance/management-accounting/management-case.mjs +91 -0
- package/dist/finance/management-accounting/master-budget.d.mts +7 -0
- package/dist/finance/management-accounting/master-budget.mjs +137 -0
- package/dist/finance/management-accounting/production-costing.d.mts +8 -0
- package/dist/finance/management-accounting/production-costing.mjs +214 -0
- package/dist/finance/management-accounting/types.d.mts +644 -0
- package/dist/finance/management-accounting/variances.d.mts +7 -0
- package/dist/finance/management-accounting/variances.mjs +150 -0
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +5 -5
- package/dist/interaction/MovableDot.mjs +19 -0
- package/dist/interaction/useDraggable.mjs +24 -4
- package/dist/math/analysis.d.mts +61 -0
- package/dist/math/analysis.mjs +187 -0
- package/dist/math/answer-check.d.mts +50 -0
- package/dist/math/answer-check.mjs +106 -0
- package/dist/math/calculus.d.mts +17 -1
- package/dist/math/calculus.mjs +365 -2
- package/dist/math/complex.d.mts +93 -0
- package/dist/math/complex.mjs +239 -0
- package/dist/math/expr.d.mts +23 -0
- package/dist/math/expr.mjs +36 -0
- package/dist/math/index.d.mts +9 -23
- package/dist/math/index.mjs +9 -38
- package/dist/math/latex.mjs +10 -1
- package/dist/math/parse.mjs +1 -1
- package/dist/math/poly.d.mts +55 -0
- package/dist/math/poly.mjs +273 -0
- package/dist/math/rearrange.d.mts +14 -0
- package/dist/math/rearrange.mjs +125 -0
- package/dist/math/solve.d.mts +21 -0
- package/dist/math/solve.mjs +75 -0
- package/dist/networking/addressing.d.mts +26 -0
- package/dist/networking/addressing.mjs +33 -0
- package/dist/networking/cloud/access.d.mts +36 -0
- package/dist/networking/cloud/access.mjs +95 -0
- package/dist/networking/cloud/containers.d.mts +43 -0
- package/dist/networking/cloud/containers.mjs +54 -0
- package/dist/networking/cloud/edge.d.mts +17 -0
- package/dist/networking/cloud/edge.mjs +31 -0
- package/dist/networking/cloud/failure-domains.d.mts +19 -0
- package/dist/networking/cloud/failure-domains.mjs +22 -0
- package/dist/networking/cloud/geo-routing.d.mts +22 -0
- package/dist/networking/cloud/geo-routing.mjs +64 -0
- package/dist/networking/cloud/index.d.mts +18 -0
- package/dist/networking/cloud/index.mjs +19 -0
- package/dist/networking/cloud/inference-reliability.d.mts +30 -0
- package/dist/networking/cloud/inference-reliability.mjs +76 -0
- package/dist/networking/cloud/inference.d.mts +118 -0
- package/dist/networking/cloud/inference.mjs +179 -0
- package/dist/networking/cloud/infrastructure.d.mts +45 -0
- package/dist/networking/cloud/infrastructure.mjs +96 -0
- package/dist/networking/cloud/observability.d.mts +24 -0
- package/dist/networking/cloud/observability.mjs +34 -0
- package/dist/networking/cloud/orchestration.d.mts +32 -0
- package/dist/networking/cloud/orchestration.mjs +38 -0
- package/dist/networking/cloud/recovery.d.mts +21 -0
- package/dist/networking/cloud/recovery.mjs +27 -0
- package/dist/networking/cloud/releases.d.mts +42 -0
- package/dist/networking/cloud/releases.mjs +135 -0
- package/dist/networking/cloud/scaling.d.mts +25 -0
- package/dist/networking/cloud/scaling.mjs +65 -0
- package/dist/networking/cloud/secrets.d.mts +39 -0
- package/dist/networking/cloud/secrets.mjs +68 -0
- package/dist/networking/cloud/server-deployment.d.mts +29 -0
- package/dist/networking/cloud/server-deployment.mjs +72 -0
- package/dist/networking/cloud/storage.d.mts +16 -0
- package/dist/networking/cloud/storage.mjs +58 -0
- package/dist/networking/cloud/vpc.d.mts +18 -0
- package/dist/networking/cloud/vpc.mjs +100 -0
- package/dist/networking/delivery.d.mts +34 -0
- package/dist/networking/delivery.mjs +52 -0
- package/dist/networking/index.d.mts +47 -0
- package/dist/networking/index.mjs +47 -0
- package/dist/networking/ipv6.d.mts +28 -0
- package/dist/networking/ipv6.mjs +17 -0
- package/dist/networking/journeys.d.mts +83 -0
- package/dist/networking/journeys.mjs +161 -0
- package/dist/networking/models/airtime.d.mts +87 -0
- package/dist/networking/models/airtime.mjs +130 -0
- package/dist/networking/models/cellular.d.mts +157 -0
- package/dist/networking/models/cellular.mjs +252 -0
- package/dist/networking/models/encapsulation.d.mts +98 -0
- package/dist/networking/models/encapsulation.mjs +132 -0
- package/dist/networking/models/handover.d.mts +116 -0
- package/dist/networking/models/handover.mjs +209 -0
- package/dist/networking/models/handshake.d.mts +90 -0
- package/dist/networking/models/handshake.mjs +122 -0
- package/dist/networking/models/index.d.mts +14 -0
- package/dist/networking/models/index.mjs +15 -0
- package/dist/networking/models/media.d.mts +82 -0
- package/dist/networking/models/media.mjs +188 -0
- package/dist/networking/models/satellite.d.mts +86 -0
- package/dist/networking/models/satellite.mjs +121 -0
- package/dist/networking/models/spectrum.d.mts +83 -0
- package/dist/networking/models/spectrum.mjs +235 -0
- package/dist/networking/models/switch.d.mts +80 -0
- package/dist/networking/models/switch.mjs +155 -0
- package/dist/networking/models/vlan.d.mts +62 -0
- package/dist/networking/models/vlan.mjs +100 -0
- package/dist/networking/models/vpn.d.mts +66 -0
- package/dist/networking/models/vpn.mjs +125 -0
- package/dist/networking/models/wifi-privacy.d.mts +82 -0
- package/dist/networking/models/wifi-privacy.mjs +130 -0
- package/dist/networking/models/wifi.d.mts +240 -0
- package/dist/networking/models/wifi.mjs +562 -0
- package/dist/networking/nat-traversal.d.mts +24 -0
- package/dist/networking/nat-traversal.mjs +38 -0
- package/dist/networking/protocols.d.mts +17 -0
- package/dist/networking/protocols.mjs +57 -0
- package/dist/networking/queue.d.mts +12 -0
- package/dist/networking/queue.mjs +18 -0
- package/dist/networking/route-learning.d.mts +23 -0
- package/dist/networking/route-learning.mjs +43 -0
- package/dist/networking/routing.d.mts +11 -0
- package/dist/networking/routing.mjs +25 -0
- package/dist/networking/signals.d.mts +26 -0
- package/dist/networking/signals.mjs +35 -0
- package/dist/networking/subnet.d.mts +49 -0
- package/dist/networking/subnet.mjs +89 -0
- package/dist/networking/tcp-reliability.d.mts +24 -0
- package/dist/networking/tcp-reliability.mjs +38 -0
- package/dist/networking/topology/contract.d.mts +123 -0
- package/dist/networking/topology/index.d.mts +4 -0
- package/dist/networking/topology/index.mjs +4 -0
- package/dist/networking/topology/presets.d.mts +7 -0
- package/dist/networking/topology/presets.mjs +302 -0
- package/dist/networking/topology/simulation.d.mts +8 -0
- package/dist/networking/topology/simulation.mjs +179 -0
- package/dist/networking/wireless.d.mts +11 -0
- package/dist/networking/wireless.mjs +17 -0
- package/dist/optics/imaging.d.mts +218 -0
- package/dist/optics/imaging.mjs +276 -0
- package/dist/optics/index.d.mts +7 -0
- package/dist/optics/index.mjs +8 -0
- package/dist/optics/laws.d.mts +70 -0
- package/dist/optics/laws.mjs +195 -0
- package/dist/optics/prism.d.mts +148 -0
- package/dist/optics/prism.mjs +197 -0
- package/dist/optics/refraction.d.mts +201 -0
- package/dist/optics/refraction.mjs +270 -0
- package/dist/optics/wave-optics.d.mts +171 -0
- package/dist/optics/wave-optics.mjs +259 -0
- package/dist/physics/ac/index.d.mts +183 -0
- package/dist/physics/ac/index.mjs +203 -0
- package/dist/physics/dynamics/forces.d.mts +98 -0
- package/dist/physics/dynamics/forces.mjs +153 -0
- package/dist/physics/dynamics/friction.d.mts +76 -0
- package/dist/physics/dynamics/friction.mjs +119 -0
- package/dist/physics/dynamics/system.d.mts +68 -0
- package/dist/physics/dynamics/system.mjs +125 -0
- package/dist/physics/electrostatics/index.d.mts +169 -0
- package/dist/physics/electrostatics/index.mjs +249 -0
- package/dist/physics/energy/centre-of-mass.d.mts +115 -0
- package/dist/physics/energy/centre-of-mass.mjs +180 -0
- package/dist/physics/energy/conservation.d.mts +81 -0
- package/dist/physics/energy/conservation.mjs +129 -0
- package/dist/physics/energy/power.d.mts +90 -0
- package/dist/physics/energy/power.mjs +167 -0
- package/dist/physics/energy/spring.d.mts +73 -0
- package/dist/physics/energy/spring.mjs +129 -0
- package/dist/physics/energy/store.d.mts +63 -0
- package/dist/physics/energy/store.mjs +108 -0
- package/dist/physics/energy/theorem.d.mts +65 -0
- package/dist/physics/energy/theorem.mjs +113 -0
- package/dist/physics/energy/work.d.mts +114 -0
- package/dist/physics/energy/work.mjs +219 -0
- package/dist/physics/gravitation/body.d.mts +85 -0
- package/dist/physics/gravitation/body.mjs +168 -0
- package/dist/physics/gravitation/kepler.d.mts +127 -0
- package/dist/physics/gravitation/kepler.mjs +216 -0
- package/dist/physics/gravitation/law.d.mts +117 -0
- package/dist/physics/gravitation/law.mjs +175 -0
- package/dist/physics/gravitation/orbit.d.mts +119 -0
- package/dist/physics/gravitation/orbit.mjs +202 -0
- package/dist/physics/gravitation/potential.d.mts +114 -0
- package/dist/physics/gravitation/potential.mjs +202 -0
- package/dist/physics/gravitation/variation.d.mts +97 -0
- package/dist/physics/gravitation/variation.mjs +175 -0
- package/dist/physics/index.d.mts +35 -0
- package/dist/physics/index.mjs +36 -0
- package/dist/physics/integral-sweep.d.mts +63 -0
- package/dist/physics/integral-sweep.mjs +126 -0
- package/dist/physics/kinematics/equations.d.mts +47 -0
- package/dist/physics/kinematics/equations.mjs +126 -0
- package/dist/physics/kinematics/free-fall.d.mts +53 -0
- package/dist/physics/kinematics/free-fall.mjs +71 -0
- package/dist/physics/kinematics/given.d.mts +16 -0
- package/dist/physics/kinematics/given.mjs +55 -0
- package/dist/physics/kinematics/roots.d.mts +41 -0
- package/dist/physics/kinematics/roots.mjs +72 -0
- package/dist/physics/kinematics/solve.d.mts +52 -0
- package/dist/physics/kinematics/solve.mjs +172 -0
- package/dist/physics/magnetism/biot-savart.d.mts +161 -0
- package/dist/physics/magnetism/biot-savart.mjs +353 -0
- package/dist/physics/magnetism/flux.d.mts +94 -0
- package/dist/physics/magnetism/flux.mjs +313 -0
- package/dist/physics/magnetism/geomagnetism.d.mts +151 -0
- package/dist/physics/magnetism/geomagnetism.mjs +184 -0
- package/dist/physics/magnetism/index.d.mts +12 -0
- package/dist/physics/magnetism/index.mjs +74 -0
- package/dist/physics/magnetism/lorentz.d.mts +28 -0
- package/dist/physics/magnetism/lorentz.mjs +88 -0
- package/dist/physics/magnetism/magnetic-force.d.mts +102 -0
- package/dist/physics/magnetism/magnetic-force.mjs +169 -0
- package/dist/physics/momentum/collide.d.mts +77 -0
- package/dist/physics/momentum/collide.mjs +115 -0
- package/dist/physics/momentum/impulse.d.mts +89 -0
- package/dist/physics/momentum/impulse.mjs +159 -0
- package/dist/physics/momentum/planar.d.mts +60 -0
- package/dist/physics/momentum/planar.mjs +110 -0
- package/dist/physics/projectile.d.mts +107 -0
- package/dist/physics/projectile.mjs +185 -0
- package/dist/physics/rotation/angular.d.mts +85 -0
- package/dist/physics/rotation/angular.mjs +130 -0
- package/dist/physics/rotation/circular.d.mts +79 -0
- package/dist/physics/rotation/circular.mjs +131 -0
- package/dist/physics/rotation/inertia.d.mts +164 -0
- package/dist/physics/rotation/inertia.mjs +332 -0
- package/dist/physics/rotation/torque.d.mts +65 -0
- package/dist/physics/rotation/torque.mjs +134 -0
- package/dist/physics/vectors.d.mts +64 -0
- package/dist/physics/vectors.mjs +114 -0
- package/dist/primitives/Dot.d.mts +2 -15
- package/dist/primitives/Dot.mjs +6 -4
- package/dist/primitives/Grid.d.mts +33 -17
- package/dist/primitives/Grid.mjs +89 -17
- package/dist/primitives/Label.d.mts +1 -14
- package/dist/primitives/Label.mjs +3 -2
- package/dist/primitives/Lines.d.mts +4 -32
- package/dist/primitives/Lines.mjs +10 -8
- package/dist/primitives/Shapes.d.mts +5 -43
- package/dist/primitives/Shapes.mjs +12 -10
- package/dist/primitives/index.d.mts +2 -2
- package/dist/primitives/index.mjs +2 -2
- package/dist/primitives/props.mjs +31 -0
- package/dist/quantity/index.d.mts +119 -0
- package/dist/quantity/index.mjs +417 -0
- package/dist/relation/derive.d.mts +74 -0
- package/dist/relation/derive.mjs +73 -0
- package/dist/relation/index.d.mts +56 -0
- package/dist/relation/index.mjs +234 -0
- package/dist/scene/Scene.d.mts +6 -1
- package/dist/scene/Scene.mjs +15 -40
- package/dist/scene/evaluators.mjs +1 -1
- package/dist/sim/thermal.mjs +45 -20
- package/dist/thermo/calorimetry.d.mts +207 -0
- package/dist/thermo/calorimetry.mjs +345 -0
- package/dist/thermo/cycles.d.mts +142 -0
- package/dist/thermo/cycles.mjs +256 -0
- package/dist/thermo/entropy.d.mts +87 -0
- package/dist/thermo/entropy.mjs +140 -0
- package/dist/thermo/expansion.d.mts +130 -0
- package/dist/thermo/expansion.mjs +178 -0
- package/dist/thermo/gas.d.mts +197 -0
- package/dist/thermo/gas.mjs +279 -0
- package/dist/thermo/heat-transfer.d.mts +164 -0
- package/dist/thermo/heat-transfer.mjs +217 -0
- package/dist/thermo/index.d.mts +9 -82
- package/dist/thermo/index.mjs +9 -132
- package/dist/thermo/processes.d.mts +95 -0
- package/dist/thermo/processes.mjs +154 -0
- package/dist/thermo/temperature.d.mts +174 -0
- package/dist/thermo/temperature.mjs +236 -0
- package/dist/view/Stage.mjs +4 -11
- package/dist/waves/damping.d.mts +224 -0
- package/dist/waves/damping.mjs +317 -0
- package/dist/waves/doppler.d.mts +72 -0
- package/dist/waves/doppler.mjs +125 -0
- package/dist/waves/index.d.mts +10 -0
- package/dist/waves/index.mjs +10 -0
- package/dist/waves/pendulum.d.mts +130 -0
- package/dist/waves/pendulum.mjs +211 -0
- package/dist/waves/pitch.d.mts +125 -0
- package/dist/waves/pitch.mjs +158 -0
- package/dist/waves/shm.d.mts +170 -0
- package/dist/waves/shm.mjs +279 -0
- package/dist/waves/springs.d.mts +103 -0
- package/dist/waves/springs.mjs +176 -0
- package/dist/waves/superposition.d.mts +124 -0
- package/dist/waves/superposition.mjs +196 -0
- package/dist/waves/wave.d.mts +154 -0
- package/dist/waves/wave.mjs +178 -0
- package/package.json +103 -23
- package/styles.css +125 -8
- 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 };
|