@classytic/stage 0.1.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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/assets/index.d.mts +3 -0
  4. package/dist/assets/index.mjs +5 -0
  5. package/dist/assets/kit/glyphs.d.mts +61 -0
  6. package/dist/assets/kit/glyphs.mjs +256 -0
  7. package/dist/assets/kit/index.d.mts +2 -0
  8. package/dist/assets/kit/index.mjs +4 -0
  9. package/dist/assets/kit/svg-defs.d.mts +6 -0
  10. package/dist/assets/kit/svg-defs.mjs +137 -0
  11. package/dist/builder/Palette.d.mts +30 -0
  12. package/dist/builder/Palette.mjs +100 -0
  13. package/dist/builder/SceneBuilder.d.mts +26 -0
  14. package/dist/builder/SceneBuilder.mjs +340 -0
  15. package/dist/builder/controls.d.mts +18 -0
  16. package/dist/builder/controls.mjs +29 -0
  17. package/dist/builder/editor.d.mts +46 -0
  18. package/dist/builder/editor.mjs +130 -0
  19. package/dist/builder/icons.d.mts +14 -0
  20. package/dist/builder/icons.mjs +143 -0
  21. package/dist/builder/index.d.mts +6 -0
  22. package/dist/builder/index.mjs +7 -0
  23. package/dist/builder/tools.d.mts +47 -0
  24. package/dist/builder/tools.mjs +303 -0
  25. package/dist/chem/index.d.mts +118 -0
  26. package/dist/chem/index.mjs +131 -0
  27. package/dist/core/clock.d.mts +41 -0
  28. package/dist/core/clock.mjs +62 -0
  29. package/dist/core/context.d.mts +13 -0
  30. package/dist/core/context.mjs +29 -0
  31. package/dist/core/control.d.mts +58 -0
  32. package/dist/core/control.mjs +113 -0
  33. package/dist/core/coords.d.mts +51 -0
  34. package/dist/core/coords.mjs +44 -0
  35. package/dist/core/index.d.mts +8 -0
  36. package/dist/core/index.mjs +9 -0
  37. package/dist/core/learner.d.mts +44 -0
  38. package/dist/core/learner.mjs +23 -0
  39. package/dist/core/motion.d.mts +73 -0
  40. package/dist/core/motion.mjs +103 -0
  41. package/dist/core/richText.d.mts +28 -0
  42. package/dist/core/richText.mjs +52 -0
  43. package/dist/core/vec.d.mts +47 -0
  44. package/dist/core/vec.mjs +144 -0
  45. package/dist/field/index.d.mts +58 -0
  46. package/dist/field/index.mjs +156 -0
  47. package/dist/index.d.mts +54 -0
  48. package/dist/index.mjs +55 -0
  49. package/dist/interaction/MovableDot.d.mts +33 -0
  50. package/dist/interaction/MovableDot.mjs +66 -0
  51. package/dist/interaction/hitTest.d.mts +16 -0
  52. package/dist/interaction/hitTest.mjs +39 -0
  53. package/dist/interaction/index.d.mts +5 -0
  54. package/dist/interaction/index.mjs +6 -0
  55. package/dist/interaction/useDraggable.d.mts +51 -0
  56. package/dist/interaction/useDraggable.mjs +116 -0
  57. package/dist/interaction/useScreenToMath.d.mts +12 -0
  58. package/dist/interaction/useScreenToMath.mjs +25 -0
  59. package/dist/logic/ast.d.mts +29 -0
  60. package/dist/logic/ast.mjs +37 -0
  61. package/dist/logic/index.d.mts +25 -0
  62. package/dist/logic/index.mjs +36 -0
  63. package/dist/logic/latex.d.mts +6 -0
  64. package/dist/logic/latex.mjs +33 -0
  65. package/dist/logic/minimize.d.mts +37 -0
  66. package/dist/logic/minimize.mjs +175 -0
  67. package/dist/logic/parse.d.mts +6 -0
  68. package/dist/logic/parse.mjs +68 -0
  69. package/dist/logic/table.d.mts +24 -0
  70. package/dist/logic/table.mjs +65 -0
  71. package/dist/logic/tokenize.mjs +258 -0
  72. package/dist/math/ast.d.mts +35 -0
  73. package/dist/math/ast.mjs +94 -0
  74. package/dist/math/calculus.d.mts +9 -0
  75. package/dist/math/calculus.mjs +188 -0
  76. package/dist/math/defs.mjs +51 -0
  77. package/dist/math/index.d.mts +26 -0
  78. package/dist/math/index.mjs +42 -0
  79. package/dist/math/latex.d.mts +7 -0
  80. package/dist/math/latex.mjs +115 -0
  81. package/dist/math/parse.d.mts +6 -0
  82. package/dist/math/parse.mjs +91 -0
  83. package/dist/math/tokenize.mjs +112 -0
  84. package/dist/primitives/CanvasLayer.d.mts +26 -0
  85. package/dist/primitives/CanvasLayer.mjs +87 -0
  86. package/dist/primitives/Dot.d.mts +27 -0
  87. package/dist/primitives/Dot.mjs +40 -0
  88. package/dist/primitives/Grid.d.mts +30 -0
  89. package/dist/primitives/Grid.mjs +122 -0
  90. package/dist/primitives/Label.d.mts +33 -0
  91. package/dist/primitives/Label.mjs +45 -0
  92. package/dist/primitives/Lines.d.mts +50 -0
  93. package/dist/primitives/Lines.mjs +82 -0
  94. package/dist/primitives/Plot.d.mts +50 -0
  95. package/dist/primitives/Plot.mjs +87 -0
  96. package/dist/primitives/Shapes.d.mts +76 -0
  97. package/dist/primitives/Shapes.mjs +79 -0
  98. package/dist/primitives/Tex.d.mts +27 -0
  99. package/dist/primitives/Tex.mjs +54 -0
  100. package/dist/primitives/index.d.mts +10 -0
  101. package/dist/primitives/index.mjs +10 -0
  102. package/dist/primitives/props.d.mts +15 -0
  103. package/dist/scene/Scene.d.mts +31 -0
  104. package/dist/scene/Scene.mjs +168 -0
  105. package/dist/scene/assets.d.mts +37 -0
  106. package/dist/scene/assets.mjs +31 -0
  107. package/dist/scene/commands.d.mts +47 -0
  108. package/dist/scene/commands.mjs +162 -0
  109. package/dist/scene/evaluators.d.mts +17 -0
  110. package/dist/scene/evaluators.mjs +210 -0
  111. package/dist/scene/index.d.mts +13 -0
  112. package/dist/scene/index.mjs +13 -0
  113. package/dist/scene/migrate.d.mts +11 -0
  114. package/dist/scene/migrate.mjs +35 -0
  115. package/dist/scene/render.d.mts +14 -0
  116. package/dist/scene/render.mjs +126 -0
  117. package/dist/scene/resolve.d.mts +12 -0
  118. package/dist/scene/resolve.mjs +95 -0
  119. package/dist/scene/schema.d.mts +9 -0
  120. package/dist/scene/schema.mjs +51 -0
  121. package/dist/scene/sims.d.mts +18 -0
  122. package/dist/scene/sims.mjs +54 -0
  123. package/dist/scene/store.d.mts +22 -0
  124. package/dist/scene/store.mjs +37 -0
  125. package/dist/scene/types.d.mts +228 -0
  126. package/dist/scene/types.mjs +27 -0
  127. package/dist/sim/equilibrium.d.mts +36 -0
  128. package/dist/sim/equilibrium.mjs +54 -0
  129. package/dist/sim/index.d.mts +9 -0
  130. package/dist/sim/index.mjs +9 -0
  131. package/dist/sim/particles.d.mts +43 -0
  132. package/dist/sim/particles.mjs +136 -0
  133. package/dist/sim/rate.d.mts +36 -0
  134. package/dist/sim/rate.mjs +39 -0
  135. package/dist/sim/registry.d.mts +10 -0
  136. package/dist/sim/registry.mjs +27 -0
  137. package/dist/sim/sampler.d.mts +47 -0
  138. package/dist/sim/sampler.mjs +79 -0
  139. package/dist/sim/thermal.d.mts +78 -0
  140. package/dist/sim/thermal.mjs +172 -0
  141. package/dist/sim/types.d.mts +24 -0
  142. package/dist/sim/wave.d.mts +34 -0
  143. package/dist/sim/wave.mjs +61 -0
  144. package/dist/steps/index.d.mts +62 -0
  145. package/dist/steps/index.mjs +133 -0
  146. package/dist/thermo/index.d.mts +82 -0
  147. package/dist/thermo/index.mjs +133 -0
  148. package/dist/view/Stage.d.mts +39 -0
  149. package/dist/view/Stage.mjs +91 -0
  150. package/dist/view/index.d.mts +4 -0
  151. package/dist/view/index.mjs +5 -0
  152. package/dist/view/useElementSize.d.mts +13 -0
  153. package/dist/view/useElementSize.mjs +40 -0
  154. package/dist/view/useInView.d.mts +9 -0
  155. package/dist/view/useInView.mjs +33 -0
  156. package/package.json +141 -0
  157. package/styles.css +70 -0
@@ -0,0 +1,143 @@
1
+ 'use client';
2
+
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+
5
+ //#region src/builder/icons.tsx
6
+ const PATHS = {
7
+ select: /* @__PURE__ */ jsx("path", { d: "M5 3l6 15 2-6 6-2z" }),
8
+ point: /* @__PURE__ */ jsx("circle", {
9
+ cx: "12",
10
+ cy: "12",
11
+ r: "3.5",
12
+ fill: "currentColor",
13
+ stroke: "none"
14
+ }),
15
+ segment: /* @__PURE__ */ jsxs(Fragment, { children: [
16
+ /* @__PURE__ */ jsx("line", {
17
+ x1: "6",
18
+ y1: "18",
19
+ x2: "18",
20
+ y2: "6"
21
+ }),
22
+ /* @__PURE__ */ jsx("circle", {
23
+ cx: "6",
24
+ cy: "18",
25
+ r: "2.2",
26
+ fill: "currentColor",
27
+ stroke: "none"
28
+ }),
29
+ /* @__PURE__ */ jsx("circle", {
30
+ cx: "18",
31
+ cy: "6",
32
+ r: "2.2",
33
+ fill: "currentColor",
34
+ stroke: "none"
35
+ })
36
+ ] }),
37
+ line: /* @__PURE__ */ jsxs(Fragment, { children: [
38
+ /* @__PURE__ */ jsx("line", {
39
+ x1: "3",
40
+ y1: "20",
41
+ x2: "21",
42
+ y2: "4"
43
+ }),
44
+ /* @__PURE__ */ jsx("circle", {
45
+ cx: "9",
46
+ cy: "14.7",
47
+ r: "1.8",
48
+ fill: "currentColor",
49
+ stroke: "none"
50
+ }),
51
+ /* @__PURE__ */ jsx("circle", {
52
+ cx: "15",
53
+ cy: "9.3",
54
+ r: "1.8",
55
+ fill: "currentColor",
56
+ stroke: "none"
57
+ })
58
+ ] }),
59
+ circle: /* @__PURE__ */ jsx("circle", {
60
+ cx: "12",
61
+ cy: "12",
62
+ r: "8"
63
+ }),
64
+ midpoint: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("line", {
65
+ x1: "5",
66
+ y1: "12",
67
+ x2: "19",
68
+ y2: "12"
69
+ }), /* @__PURE__ */ jsx("circle", {
70
+ cx: "12",
71
+ cy: "12",
72
+ r: "2.6",
73
+ fill: "currentColor",
74
+ stroke: "none"
75
+ })] }),
76
+ intersect: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("circle", {
77
+ cx: "9",
78
+ cy: "12",
79
+ r: "6"
80
+ }), /* @__PURE__ */ jsx("circle", {
81
+ cx: "15",
82
+ cy: "12",
83
+ r: "6"
84
+ })] }),
85
+ measure: /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ /* @__PURE__ */ jsx("line", {
87
+ x1: "4",
88
+ y1: "12",
89
+ x2: "20",
90
+ y2: "12"
91
+ }),
92
+ /* @__PURE__ */ jsx("path", { d: "M7 9l-3 3 3 3" }),
93
+ /* @__PURE__ */ jsx("path", { d: "M17 9l3 3-3 3" })
94
+ ] }),
95
+ scalar: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("line", {
96
+ x1: "4",
97
+ y1: "12",
98
+ x2: "20",
99
+ y2: "12"
100
+ }), /* @__PURE__ */ jsx("circle", {
101
+ cx: "14",
102
+ cy: "12",
103
+ r: "3.2",
104
+ fill: "currentColor",
105
+ stroke: "none"
106
+ })] }),
107
+ note: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M6 6h12" }), /* @__PURE__ */ jsx("line", {
108
+ x1: "12",
109
+ y1: "6",
110
+ x2: "12",
111
+ y2: "18"
112
+ })] }),
113
+ delete: /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ /* @__PURE__ */ jsx("path", { d: "M5 7h14" }),
115
+ /* @__PURE__ */ jsx("path", { d: "M9 7V5h6v2" }),
116
+ /* @__PURE__ */ jsx("path", { d: "M7 7l1 12h8l1-12" })
117
+ ] }),
118
+ undo: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M9 7L4 12l5 5" }), /* @__PURE__ */ jsx("path", { d: "M4 12h10a6 6 0 0 1 0 12" })] }),
119
+ redo: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M15 7l5 5-5 5" }), /* @__PURE__ */ jsx("path", { d: "M20 12H10a6 6 0 0 0 0 12" })] })
120
+ };
121
+ /** A single builder icon. `size` in px; inherits `currentColor`. Decorative. */
122
+ function ToolIcon({ name, size = 16 }) {
123
+ return /* @__PURE__ */ jsx("svg", {
124
+ width: size,
125
+ height: size,
126
+ viewBox: "0 0 24 24",
127
+ fill: "none",
128
+ stroke: "currentColor",
129
+ strokeWidth: 2,
130
+ strokeLinecap: "round",
131
+ strokeLinejoin: "round",
132
+ "aria-hidden": true,
133
+ focusable: "false",
134
+ style: {
135
+ display: "block",
136
+ flex: "none"
137
+ },
138
+ children: PATHS[name]
139
+ });
140
+ }
141
+
142
+ //#endregion
143
+ export { ToolIcon };
@@ -0,0 +1,6 @@
1
+ import { DispatchOpts, Editor, useEditor } from "./editor.mjs";
2
+ import { ControlPick, controlsFromScene } from "./controls.mjs";
3
+ import { AUTHORING_TOOLS, CIRCLE, DELETE, GEOMETRY_TOOLS, INTERSECT, LINE, MEASURE, MIDPOINT, NOTE, POINT, SCALAR, SEGMENT, SELECT, Tool, ToolCtx } from "./tools.mjs";
4
+ import { SceneBuilder, SceneBuilderProps } from "./SceneBuilder.mjs";
5
+ import { Palette, PaletteProps } from "./Palette.mjs";
6
+ export { AUTHORING_TOOLS, CIRCLE, type ControlPick, DELETE, type DispatchOpts, Editor, GEOMETRY_TOOLS, INTERSECT, LINE, MEASURE, MIDPOINT, NOTE, POINT, Palette, type PaletteProps, SCALAR, SEGMENT, SELECT, SceneBuilder, type SceneBuilderProps, type Tool, type ToolCtx, controlsFromScene, useEditor };
@@ -0,0 +1,7 @@
1
+ import { Editor, useEditor } from "./editor.mjs";
2
+ import { controlsFromScene } from "./controls.mjs";
3
+ import { Palette } from "./Palette.mjs";
4
+ import { AUTHORING_TOOLS, CIRCLE, DELETE, GEOMETRY_TOOLS, INTERSECT, LINE, MEASURE, MIDPOINT, NOTE, POINT, SCALAR, SEGMENT, SELECT } from "./tools.mjs";
5
+ import { SceneBuilder } from "./SceneBuilder.mjs";
6
+
7
+ export { AUTHORING_TOOLS, CIRCLE, DELETE, Editor, GEOMETRY_TOOLS, INTERSECT, LINE, MEASURE, MIDPOINT, NOTE, POINT, Palette, SCALAR, SEGMENT, SELECT, SceneBuilder, controlsFromScene, useEditor };
@@ -0,0 +1,47 @@
1
+ import { Vec2 } from "../core/vec.mjs";
2
+ import { Id } from "../scene/types.mjs";
3
+ import { Editor } from "./editor.mjs";
4
+ import { IconName } from "./icons.mjs";
5
+
6
+ //#region src/builder/tools.d.ts
7
+ interface ToolCtx {
8
+ editor: Editor;
9
+ /** nearest existing point id within tolerance, or null. */
10
+ hitPoint: (m: Vec2) => Id | null;
11
+ /** nearest circle/line id within tolerance, or null. */
12
+ hitElement: (m: Vec2) => Id | null;
13
+ /** ids selected so far in a multi-click construct. */
14
+ pending: Id[];
15
+ setPending: (ids: Id[]) => void;
16
+ /** snap a math point to the working grid. */
17
+ snap: (m: Vec2) => Vec2;
18
+ /** fresh id with a prefix (A,B,C… for points). */
19
+ nextId: (prefix: string) => Id;
20
+ }
21
+ interface Tool {
22
+ id: string;
23
+ /** Internal builder-icon name (rendered by the palette; no React-icon peer). */
24
+ icon: IconName;
25
+ /** Plain text label (also the button's a11y name). */
26
+ label: string;
27
+ hint: string;
28
+ onPoint(ctx: ToolCtx, m: Vec2): void;
29
+ }
30
+ declare const SELECT: Tool;
31
+ declare const POINT: Tool;
32
+ declare const SEGMENT: Tool;
33
+ declare const LINE: Tool;
34
+ declare const CIRCLE: Tool;
35
+ declare const MIDPOINT: Tool;
36
+ declare const INTERSECT: Tool;
37
+ declare const MEASURE: Tool;
38
+ declare const DELETE: Tool;
39
+ /** A free scalar parameter — appears as a live slider in the authoring strip. */
40
+ declare const SCALAR: Tool;
41
+ /** A free text note placed in the scene. */
42
+ declare const NOTE: Tool;
43
+ declare const GEOMETRY_TOOLS: Tool[];
44
+ /** Geometry construction + the authoring primitives (sliders, labels). */
45
+ declare const AUTHORING_TOOLS: Tool[];
46
+ //#endregion
47
+ export { AUTHORING_TOOLS, CIRCLE, DELETE, GEOMETRY_TOOLS, INTERSECT, LINE, MEASURE, MIDPOINT, NOTE, POINT, SCALAR, SEGMENT, SELECT, Tool, ToolCtx };
@@ -0,0 +1,303 @@
1
+ 'use client';
2
+
3
+ //#region src/builder/tools.ts
4
+ const colors = {
5
+ point: "var(--stage-accent)",
6
+ seg: "var(--stage-good)",
7
+ line: "var(--stage-muted)",
8
+ circle: "var(--stage-accent)",
9
+ derived: "var(--stage-warn)"
10
+ };
11
+ /** Collect `n` distinct points by click; when full, run `done(ids)` and reset. */
12
+ function collectPoints(ctx, m, n, done) {
13
+ const pid = ctx.hitPoint(m);
14
+ if (!pid) return;
15
+ const next = [...ctx.pending, pid];
16
+ if (next.length < n) {
17
+ ctx.setPending(next);
18
+ return;
19
+ }
20
+ done(next);
21
+ ctx.setPending([]);
22
+ }
23
+ const SELECT = {
24
+ id: "select",
25
+ icon: "select",
26
+ label: "Move",
27
+ hint: "Drag a labelled point to move it.",
28
+ onPoint: () => {}
29
+ };
30
+ const POINT = {
31
+ id: "point",
32
+ icon: "point",
33
+ label: "Point",
34
+ hint: "Click empty space to place a point.",
35
+ onPoint: (ctx, m) => {
36
+ const id = ctx.nextId("P");
37
+ ctx.editor.dispatch({
38
+ op: "create",
39
+ id,
40
+ element: {
41
+ id,
42
+ kind: "point",
43
+ label: id,
44
+ style: { color: colors.point },
45
+ free: {
46
+ at: ctx.snap(m),
47
+ draggable: true
48
+ }
49
+ }
50
+ });
51
+ }
52
+ };
53
+ const SEGMENT = {
54
+ id: "segment",
55
+ icon: "segment",
56
+ label: "Segment",
57
+ hint: "Click two points.",
58
+ onPoint: (ctx, m) => collectPoints(ctx, m, 2, ([a, b]) => {
59
+ const id = ctx.nextId("s");
60
+ ctx.editor.dispatch({
61
+ op: "create",
62
+ id,
63
+ element: {
64
+ id,
65
+ kind: "segment",
66
+ style: { color: colors.seg },
67
+ def: {
68
+ op: "segment",
69
+ from: { ref: a },
70
+ to: { ref: b }
71
+ }
72
+ }
73
+ });
74
+ })
75
+ };
76
+ const LINE = {
77
+ id: "line",
78
+ icon: "line",
79
+ label: "Line",
80
+ hint: "Click two points.",
81
+ onPoint: (ctx, m) => collectPoints(ctx, m, 2, ([a, b]) => {
82
+ const id = ctx.nextId("l");
83
+ ctx.editor.dispatch({
84
+ op: "create",
85
+ id,
86
+ element: {
87
+ id,
88
+ kind: "line",
89
+ style: { color: colors.line },
90
+ def: {
91
+ op: "line",
92
+ through: [{ ref: a }, { ref: b }]
93
+ }
94
+ }
95
+ });
96
+ })
97
+ };
98
+ const CIRCLE = {
99
+ id: "circle",
100
+ icon: "circle",
101
+ label: "Circle",
102
+ hint: "Click centre, then a point on it.",
103
+ onPoint: (ctx, m) => collectPoints(ctx, m, 2, ([c, t]) => {
104
+ const id = ctx.nextId("c");
105
+ ctx.editor.dispatch({
106
+ op: "create",
107
+ id,
108
+ element: {
109
+ id,
110
+ kind: "circle",
111
+ style: { color: colors.circle },
112
+ def: {
113
+ op: "circle",
114
+ center: { ref: c },
115
+ through: { ref: t }
116
+ }
117
+ }
118
+ });
119
+ })
120
+ };
121
+ const MIDPOINT = {
122
+ id: "midpoint",
123
+ icon: "midpoint",
124
+ label: "Midpoint",
125
+ hint: "Click two points.",
126
+ onPoint: (ctx, m) => collectPoints(ctx, m, 2, ([a, b]) => {
127
+ const id = ctx.nextId("M");
128
+ ctx.editor.dispatch({
129
+ op: "create",
130
+ id,
131
+ element: {
132
+ id,
133
+ kind: "point",
134
+ label: id,
135
+ style: { color: colors.derived },
136
+ def: {
137
+ op: "midpoint",
138
+ of: [{ ref: a }, { ref: b }]
139
+ }
140
+ }
141
+ });
142
+ })
143
+ };
144
+ const INTERSECT = {
145
+ id: "intersect",
146
+ icon: "intersect",
147
+ label: "Intersect",
148
+ hint: "Click two circles/lines.",
149
+ onPoint: (ctx, m) => {
150
+ const eid = ctx.hitElement(m);
151
+ if (!eid) return;
152
+ const next = [...ctx.pending, eid];
153
+ if (next.length < 2) {
154
+ ctx.setPending(next);
155
+ return;
156
+ }
157
+ const id1 = ctx.nextId("X");
158
+ ctx.editor.dispatch({
159
+ op: "create",
160
+ id: id1,
161
+ element: {
162
+ id: id1,
163
+ kind: "point",
164
+ label: id1,
165
+ style: { color: colors.derived },
166
+ def: {
167
+ op: "intersect",
168
+ of: [{ ref: next[0] }, { ref: next[1] }],
169
+ pick: 0
170
+ }
171
+ }
172
+ });
173
+ const id2 = ctx.nextId("X");
174
+ ctx.editor.dispatch({
175
+ op: "create",
176
+ id: id2,
177
+ element: {
178
+ id: id2,
179
+ kind: "point",
180
+ label: id2,
181
+ style: { color: colors.derived },
182
+ def: {
183
+ op: "intersect",
184
+ of: [{ ref: next[0] }, { ref: next[1] }],
185
+ pick: 1
186
+ }
187
+ }
188
+ });
189
+ ctx.setPending([]);
190
+ }
191
+ };
192
+ const MEASURE = {
193
+ id: "measure",
194
+ icon: "measure",
195
+ label: "Measure",
196
+ hint: "Click two points to measure the distance.",
197
+ onPoint: (ctx, m) => collectPoints(ctx, m, 2, ([a, b]) => {
198
+ const id = ctx.nextId("d");
199
+ ctx.editor.dispatch({
200
+ op: "create",
201
+ id,
202
+ element: {
203
+ id,
204
+ kind: "measure",
205
+ label: `|${a}${b}|`,
206
+ def: {
207
+ op: "distance",
208
+ of: [{ ref: a }, { ref: b }]
209
+ }
210
+ }
211
+ });
212
+ })
213
+ };
214
+ const DELETE = {
215
+ id: "delete",
216
+ icon: "delete",
217
+ label: "Delete",
218
+ hint: "Click a point or curve to remove it (and anything built on it).",
219
+ onPoint: (ctx, m) => {
220
+ const id = ctx.hitPoint(m) ?? ctx.hitElement(m);
221
+ if (id) ctx.editor.dispatch({
222
+ op: "remove",
223
+ id
224
+ });
225
+ }
226
+ };
227
+ /** A free scalar parameter — appears as a live slider in the authoring strip. */
228
+ const SCALAR = {
229
+ id: "scalar",
230
+ icon: "scalar",
231
+ label: "Slider",
232
+ hint: "Click to add a value/slider parameter (drives equations + assets).",
233
+ onPoint: (ctx) => {
234
+ const id = ctx.nextId("k");
235
+ ctx.editor.dispatch({
236
+ op: "create",
237
+ id,
238
+ element: {
239
+ id,
240
+ kind: "scalar",
241
+ label: id,
242
+ a11y: { label: `parameter ${id}` },
243
+ free: {
244
+ value: 1,
245
+ min: 0,
246
+ max: 10,
247
+ step: 1
248
+ }
249
+ }
250
+ });
251
+ }
252
+ };
253
+ /** A free text note placed in the scene. */
254
+ const NOTE = {
255
+ id: "note",
256
+ icon: "note",
257
+ label: "Label",
258
+ hint: "Click to drop a text label (edit it in the inspector).",
259
+ onPoint: (ctx, m) => {
260
+ const id = ctx.nextId("t");
261
+ ctx.editor.dispatch({
262
+ op: "create",
263
+ id,
264
+ element: {
265
+ id,
266
+ kind: "note",
267
+ style: { color: "var(--stage-fg)" },
268
+ free: {
269
+ at: ctx.snap(m),
270
+ text: "label"
271
+ }
272
+ }
273
+ });
274
+ }
275
+ };
276
+ const GEOMETRY_TOOLS = [
277
+ SELECT,
278
+ POINT,
279
+ SEGMENT,
280
+ LINE,
281
+ CIRCLE,
282
+ MIDPOINT,
283
+ INTERSECT,
284
+ MEASURE,
285
+ DELETE
286
+ ];
287
+ /** Geometry construction + the authoring primitives (sliders, labels). */
288
+ const AUTHORING_TOOLS = [
289
+ SELECT,
290
+ POINT,
291
+ SEGMENT,
292
+ LINE,
293
+ CIRCLE,
294
+ MIDPOINT,
295
+ INTERSECT,
296
+ MEASURE,
297
+ SCALAR,
298
+ NOTE,
299
+ DELETE
300
+ ];
301
+
302
+ //#endregion
303
+ export { AUTHORING_TOOLS, CIRCLE, DELETE, GEOMETRY_TOOLS, INTERSECT, LINE, MEASURE, MIDPOINT, NOTE, POINT, SCALAR, SEGMENT, SELECT };
@@ -0,0 +1,118 @@
1
+ //#region src/chem/index.d.ts
2
+ /**
3
+ * `chem` — acid–base / pH AND electrochemistry / Nernst, as a PURE kernel (no
4
+ * React, no pixels). The engine behind titration curves, pH problems, and galvanic
5
+ * cells.
6
+ *
7
+ * pH at any point is found by solving the exact charge balance for [H⁺] = h:
8
+ *
9
+ * [titrant cation] + h = [OH⁻] + [A⁻]
10
+ * n_b/V + h = K_w/h + (n_a/V)·K_a/(K_a + h)
11
+ *
12
+ * where n_a is the acid initially present, n_b the strong base added, V the total
13
+ * volume. A strong acid is just the K_a → ∞ limit (fully dissociated). Solving the
14
+ * balance (monotonic in h → bisection) is robust everywhere — initial point, buffer
15
+ * region, equivalence, and past it — with no piecewise approximations to get wrong.
16
+ *
17
+ * Concentrations in mol/L, volumes in litres. Currently models an ACID analyte
18
+ * titrated by a STRONG BASE (covers strong-acid and weak-acid curves).
19
+ */
20
+ declare const Kw = 1e-14;
21
+ type Analyte = 'strong-acid' | 'weak-acid';
22
+ interface TitrationSpec {
23
+ analyte: Analyte;
24
+ /** Analyte concentration, mol/L. */
25
+ Ca: number;
26
+ /** Analyte volume, L. */
27
+ Va: number;
28
+ /** Strong-base titrant concentration, mol/L. */
29
+ Cb: number;
30
+ /** Acid pKa (weak acid only; ignored for a strong acid). */
31
+ pKa?: number;
32
+ }
33
+ /** pH after adding `Vb` litres of titrant. */
34
+ declare function pHAt(spec: TitrationSpec, Vb: number): number;
35
+ interface TitrationCurve {
36
+ points: {
37
+ v: number;
38
+ pH: number;
39
+ }[];
40
+ /** Equivalence volume, L (moles base = moles acid). */
41
+ vEq: number;
42
+ /** pH at the equivalence point. */
43
+ pHEq: number;
44
+ /** Half-equivalence pH (= pKa for a weak acid). */
45
+ pHHalf: number;
46
+ pKa: number;
47
+ }
48
+ /** Sample the full titration curve from 0 to `span`·vEq (default 2×). */
49
+ declare function titrationCurve(spec: TitrationSpec, n?: number, span?: number): TitrationCurve;
50
+ /** Universal gas constant, J/(mol·K). */
51
+ declare const R = 8.314462618;
52
+ /** Faraday constant, C/mol. */
53
+ declare const F = 96485.332;
54
+ /** Standard temperature, K (25 °C). */
55
+ declare const T_STD = 298.15;
56
+ interface HalfCell {
57
+ /** Electrode metal symbol (e.g. 'Zn'). */
58
+ metal: string;
59
+ /** Ion charge z in Mᶻ⁺ (e.g. 2 for Zn²⁺). */
60
+ z: number;
61
+ /** Standard reduction potential E°, volts. */
62
+ E0: number;
63
+ /** Ion concentration [Mᶻ⁺], mol/L. */
64
+ conc: number;
65
+ }
66
+ interface CellResult {
67
+ cathode: HalfCell;
68
+ anode: HalfCell;
69
+ /** Standard cell potential E°cell = E°cathode − E°anode, V. */
70
+ E0cell: number;
71
+ /** Electrons transferred (balances the half-reactions). */
72
+ n: number;
73
+ /** Reaction quotient Q = [anode ion]^(n/zₐ) / [cathode ion]^(n/z_c). */
74
+ Q: number;
75
+ /** Actual cell potential from the Nernst equation, V. */
76
+ E: number;
77
+ /** Does the cell drive current as written (E > 0)? */
78
+ spontaneous: boolean;
79
+ }
80
+ /** Galvanic cell from two half-cells; cathode/anode assigned by standard potential. */
81
+ declare function galvanicCell(x: HalfCell, y: HalfCell, T?: number): CellResult;
82
+ /** Arrhenius rate constant k = A·e^(−Ea/RT). */
83
+ declare function arrhenius(A: number, Ea: number, T: number): number;
84
+ /** Ratio k(T)/k(Tref) at fixed Ea — the stable way to scale a rate without a raw A. */
85
+ declare function arrheniusRatio(Ea: number, T: number, Tref?: number): number;
86
+ /** Fraction of molecules with energy ≥ Ea (Boltzmann) ≈ e^(−Ea/RT). */
87
+ declare function fractionAboveEa(Ea: number, T: number): number;
88
+ type RateOrder = 0 | 1 | 2;
89
+ /** Concentration [A] at time t for a reaction of the given order, rate = k·[A]^order. */
90
+ declare function concAt(order: RateOrder, A0: number, k: number, t: number): number;
91
+ /** Half-life for the given order (constant only for first order). */
92
+ declare function halfLife(order: RateOrder, A0: number, k: number): number;
93
+ interface StoichSpecies {
94
+ name: string;
95
+ /** Stoichiometric coefficient in the balanced equation. */
96
+ coeff: number;
97
+ /** Molar mass, g/mol (optional — enables grams in the result). */
98
+ molarMass?: number;
99
+ }
100
+ interface StoichResult {
101
+ /** Reaction extent ξ = min(moles/coeff) over the reactants. */
102
+ extent: number;
103
+ /** Name(s) of the limiting reactant(s) (more than one on an exact-ratio tie). */
104
+ limiting: string[];
105
+ /** Moles of each reactant consumed (aligned with the reactants array). */
106
+ consumed: number[];
107
+ /** Moles of each reactant left over. */
108
+ leftover: number[];
109
+ products: {
110
+ name: string;
111
+ moles: number;
112
+ grams?: number;
113
+ }[];
114
+ }
115
+ /** Solve a balanced reaction for the limiting reagent, extent, products and leftovers. */
116
+ declare function solveStoichiometry(reactants: StoichSpecies[], products: StoichSpecies[], amountsMol: number[]): StoichResult;
117
+ //#endregion
118
+ export { Analyte, CellResult, F, HalfCell, Kw, R, RateOrder, StoichResult, StoichSpecies, T_STD, TitrationCurve, TitrationSpec, arrhenius, arrheniusRatio, concAt, fractionAboveEa, galvanicCell, halfLife, pHAt, solveStoichiometry, titrationCurve };