@jjlmoya/utils-science 1.27.0 → 1.29.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 (53) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +5 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/entropy-second-law/bibliography.astro +14 -0
  8. package/src/tool/entropy-second-law/bibliography.ts +12 -0
  9. package/src/tool/entropy-second-law/component.astro +366 -0
  10. package/src/tool/entropy-second-law/entropy-second-law-simulator.css +445 -0
  11. package/src/tool/entropy-second-law/entry.ts +26 -0
  12. package/src/tool/entropy-second-law/i18n/de.ts +210 -0
  13. package/src/tool/entropy-second-law/i18n/en.ts +210 -0
  14. package/src/tool/entropy-second-law/i18n/es.ts +210 -0
  15. package/src/tool/entropy-second-law/i18n/fr.ts +210 -0
  16. package/src/tool/entropy-second-law/i18n/id.ts +210 -0
  17. package/src/tool/entropy-second-law/i18n/it.ts +210 -0
  18. package/src/tool/entropy-second-law/i18n/ja.ts +210 -0
  19. package/src/tool/entropy-second-law/i18n/ko.ts +210 -0
  20. package/src/tool/entropy-second-law/i18n/nl.ts +210 -0
  21. package/src/tool/entropy-second-law/i18n/pl.ts +210 -0
  22. package/src/tool/entropy-second-law/i18n/pt.ts +210 -0
  23. package/src/tool/entropy-second-law/i18n/ru.ts +210 -0
  24. package/src/tool/entropy-second-law/i18n/sv.ts +210 -0
  25. package/src/tool/entropy-second-law/i18n/tr.ts +210 -0
  26. package/src/tool/entropy-second-law/i18n/zh.ts +210 -0
  27. package/src/tool/entropy-second-law/index.ts +11 -0
  28. package/src/tool/entropy-second-law/logic.ts +208 -0
  29. package/src/tool/entropy-second-law/seo.astro +15 -0
  30. package/src/tool/phase-diagram-critical-points/bibliography.astro +14 -0
  31. package/src/tool/phase-diagram-critical-points/bibliography.ts +16 -0
  32. package/src/tool/phase-diagram-critical-points/component.astro +397 -0
  33. package/src/tool/phase-diagram-critical-points/entry.ts +26 -0
  34. package/src/tool/phase-diagram-critical-points/i18n/de.ts +179 -0
  35. package/src/tool/phase-diagram-critical-points/i18n/en.ts +181 -0
  36. package/src/tool/phase-diagram-critical-points/i18n/es.ts +179 -0
  37. package/src/tool/phase-diagram-critical-points/i18n/fr.ts +179 -0
  38. package/src/tool/phase-diagram-critical-points/i18n/id.ts +179 -0
  39. package/src/tool/phase-diagram-critical-points/i18n/it.ts +179 -0
  40. package/src/tool/phase-diagram-critical-points/i18n/ja.ts +179 -0
  41. package/src/tool/phase-diagram-critical-points/i18n/ko.ts +179 -0
  42. package/src/tool/phase-diagram-critical-points/i18n/nl.ts +179 -0
  43. package/src/tool/phase-diagram-critical-points/i18n/pl.ts +179 -0
  44. package/src/tool/phase-diagram-critical-points/i18n/pt.ts +179 -0
  45. package/src/tool/phase-diagram-critical-points/i18n/ru.ts +179 -0
  46. package/src/tool/phase-diagram-critical-points/i18n/sv.ts +179 -0
  47. package/src/tool/phase-diagram-critical-points/i18n/tr.ts +179 -0
  48. package/src/tool/phase-diagram-critical-points/i18n/zh.ts +179 -0
  49. package/src/tool/phase-diagram-critical-points/index.ts +11 -0
  50. package/src/tool/phase-diagram-critical-points/logic.ts +179 -0
  51. package/src/tool/phase-diagram-critical-points/phase-diagram-critical-points-visualizer.css +542 -0
  52. package/src/tool/phase-diagram-critical-points/seo.astro +15 -0
  53. package/src/tools.ts +4 -0
@@ -0,0 +1,16 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'NIST Chemistry WebBook',
6
+ url: 'https://webbook.nist.gov/chemistry/',
7
+ },
8
+ {
9
+ name: 'OpenStax Chemistry 2e, Phase Diagrams',
10
+ url: 'https://openstax.org/books/chemistry-2e/pages/10-4-phase-diagrams',
11
+ },
12
+ {
13
+ name: 'IUPAC Gold Book, Critical Point',
14
+ url: 'https://goldbook.iupac.org/terms/view/C01396',
15
+ },
16
+ ];
@@ -0,0 +1,397 @@
1
+ ---
2
+ import './phase-diagram-critical-points-visualizer.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="phase-lab" id="phase-lab">
12
+ <section class="phase-panel phase-controls" aria-label={ui.controls}>
13
+ <label class="phase-field" for="phase-substance">
14
+ <span>{ui.substance}</span>
15
+ <select id="phase-substance">
16
+ <option value="water">Water</option>
17
+ <option value="carbon-dioxide">Carbon dioxide</option>
18
+ <option value="nitrogen">Nitrogen</option>
19
+ </select>
20
+ </label>
21
+
22
+ <label class="phase-field" for="phase-units">
23
+ <span>{ui.units}</span>
24
+ <select id="phase-units">
25
+ <option value="scientific">{ui.scientificUnits}</option>
26
+ <option value="metric">{ui.metricUnits}</option>
27
+ <option value="imperial">{ui.imperialUnits}</option>
28
+ </select>
29
+ </label>
30
+
31
+ <label class="phase-field" for="phase-temperature">
32
+ <span>{ui.temperature}</span>
33
+ <output id="phase-temperature-output">373 K</output>
34
+ <input id="phase-temperature" type="range" min="40" max="720" step="1" value="373" />
35
+ </label>
36
+
37
+ <label class="phase-field" for="phase-pressure">
38
+ <span>{ui.pressure}</span>
39
+ <output id="phase-pressure-output">0.10 MPa</output>
40
+ <input id="phase-pressure" type="range" min="-3" max="1.45" step="0.01" value="-1" />
41
+ </label>
42
+
43
+ <button type="button" id="phase-reset">{ui.reset}</button>
44
+ </section>
45
+
46
+ <section class="phase-map-shell" aria-label={ui.diagram}>
47
+ <div class="phase-map-frame">
48
+ <span class="phase-axis-label phase-axis-label-y" aria-hidden="true">{ui.pressure}</span>
49
+ <svg class="phase-map" id="phase-map" viewBox="0 0 820 560" role="img" aria-label={ui.diagram}>
50
+ <defs>
51
+ <linearGradient id="phase-supercritical-glow" x1="0%" y1="0%" x2="100%" y2="100%">
52
+ <stop offset="0%" stop-color="#ffcf5a" stop-opacity="0.18"></stop>
53
+ <stop offset="100%" stop-color="#4fd6b5" stop-opacity="0.3"></stop>
54
+ </linearGradient>
55
+ <filter id="phase-soft-glow" x="-30%" y="-30%" width="160%" height="160%">
56
+ <feGaussianBlur stdDeviation="8" result="glow"></feGaussianBlur>
57
+ <feMerge>
58
+ <feMergeNode in="glow"></feMergeNode>
59
+ <feMergeNode in="SourceGraphic"></feMergeNode>
60
+ </feMerge>
61
+ </filter>
62
+ </defs>
63
+ <rect class="phase-region phase-region-gas" x="46" y="72" width="730" height="410" rx="14"></rect>
64
+ <path class="phase-region phase-region-solid" id="phase-solid-region"></path>
65
+ <path class="phase-region phase-region-liquid" id="phase-liquid-region"></path>
66
+ <path class="phase-region phase-region-supercritical" id="phase-super-region"></path>
67
+ <path class="phase-grid" d="M46 482 H776 M46 379.5 H776 M46 277 H776 M46 174.5 H776 M46 72 H776 M46 72 V482 M228.5 72 V482 M411 72 V482 M593.5 72 V482 M776 72 V482"></path>
68
+ <path class="phase-boundary phase-vapor" id="phase-vapor-curve"></path>
69
+ <path class="phase-boundary phase-melt" id="phase-melt-line"></path>
70
+ <g class="phase-point phase-triple" id="phase-triple-point">
71
+ <circle r="7"></circle>
72
+ <text x="15" y="24">{ui.triplePoint}</text>
73
+ </g>
74
+ <g class="phase-point phase-critical" id="phase-critical-point">
75
+ <circle r="9"></circle>
76
+ <text x="-106" y="-24">{ui.criticalPoint}</text>
77
+ </g>
78
+ <g class="phase-sample-marker" id="phase-sample-marker">
79
+ <circle class="phase-sample-trail phase-sample-trail-outer" r="38"></circle>
80
+ <circle class="phase-sample-trail phase-sample-trail-inner" r="22"></circle>
81
+ <circle class="phase-sample-dot" r="7"></circle>
82
+ </g>
83
+ <text class="phase-region-label phase-label-solid" id="phase-label-solid">{ui.solid}</text>
84
+ <text class="phase-region-label phase-label-liquid" id="phase-label-liquid">{ui.liquid}</text>
85
+ <text class="phase-region-label phase-label-gas" id="phase-label-gas">{ui.gas}</text>
86
+ <text class="phase-region-label phase-label-super" id="phase-label-super">{ui.supercritical}</text>
87
+ </svg>
88
+ <span class="phase-axis-label phase-axis-label-x" aria-hidden="true">{ui.temperature}</span>
89
+ </div>
90
+ </section>
91
+
92
+ <section class="phase-panel phase-readout" aria-label={ui.sample}>
93
+ <span>{ui.phase}</span>
94
+ <strong id="phase-state">{ui.liquid}</strong>
95
+ <p id="phase-coordinates">{ui.coordinates}: 373 K / 0.10 MPa</p>
96
+
97
+ <div class="phase-meter">
98
+ <div>
99
+ <span>{ui.latentHeat}</span>
100
+ <output id="phase-latent-output">0.82</output>
101
+ </div>
102
+ <i id="phase-latent-bar"></i>
103
+ </div>
104
+
105
+ <div class="phase-meter">
106
+ <div>
107
+ <span>{ui.criticalProximity}</span>
108
+ <output id="phase-critical-output">0.18</output>
109
+ </div>
110
+ <i id="phase-critical-bar"></i>
111
+ </div>
112
+
113
+ <div class="phase-coexistence" aria-label={ui.interpretation}>
114
+ <div class="phase-coexistence-head">
115
+ <span>{ui.purePhase}</span>
116
+ <output id="phase-coexistence-output">12%</output>
117
+ <span>{ui.coexistence}</span>
118
+ </div>
119
+ <div class="phase-coexistence-axis">
120
+ <i id="phase-coexistence-marker"></i>
121
+ </div>
122
+ </div>
123
+ </section>
124
+ </div>
125
+
126
+ <script>
127
+ import {
128
+ buildPhaseCurve,
129
+ classifyPhase,
130
+ formatPressureForUnits,
131
+ formatTemperatureForUnits,
132
+ getSubstance,
133
+ meltingPressureAt,
134
+ vaporPressureAt,
135
+ } from './logic';
136
+
137
+ const substanceSelect = document.getElementById('phase-substance') as HTMLSelectElement | null;
138
+ const unitsSelect = document.getElementById('phase-units') as HTMLSelectElement | null;
139
+ const temperatureInput = document.getElementById('phase-temperature') as HTMLInputElement | null;
140
+ const pressureInput = document.getElementById('phase-pressure') as HTMLInputElement | null;
141
+ const temperatureOutput = document.getElementById('phase-temperature-output');
142
+ const pressureOutput = document.getElementById('phase-pressure-output');
143
+ const resetButton = document.getElementById('phase-reset');
144
+ const vaporCurve = document.getElementById('phase-vapor-curve');
145
+ const meltLine = document.getElementById('phase-melt-line');
146
+ const solidRegion = document.getElementById('phase-solid-region');
147
+ const liquidRegion = document.getElementById('phase-liquid-region');
148
+ const superRegion = document.getElementById('phase-super-region');
149
+ const triplePoint = document.getElementById('phase-triple-point');
150
+ const criticalPoint = document.getElementById('phase-critical-point');
151
+ const sampleMarker = document.getElementById('phase-sample-marker');
152
+ const stateOutput = document.getElementById('phase-state');
153
+ const coordinatesOutput = document.getElementById('phase-coordinates');
154
+ const latentOutput = document.getElementById('phase-latent-output');
155
+ const criticalOutput = document.getElementById('phase-critical-output');
156
+ const latentBar = document.getElementById('phase-latent-bar');
157
+ const criticalBar = document.getElementById('phase-critical-bar');
158
+ const coexistenceOutput = document.getElementById('phase-coexistence-output');
159
+ const coexistenceMarker = document.getElementById('phase-coexistence-marker');
160
+ const coexistenceAxis = document.querySelector('.phase-coexistence-axis') as HTMLElement | null;
161
+ const storageKey = 'phase-diagram-critical-points:last-state';
162
+
163
+ const plot = {
164
+ left: 46,
165
+ top: 72,
166
+ width: 730,
167
+ height: 410,
168
+ };
169
+ const phaseLabelNodes = {
170
+ solid: document.getElementById('phase-label-solid'),
171
+ liquid: document.getElementById('phase-label-liquid'),
172
+ gas: document.getElementById('phase-label-gas'),
173
+ supercritical: document.getElementById('phase-label-super'),
174
+ };
175
+
176
+ const labels = {
177
+ solid: document.getElementById('phase-label-solid')?.textContent ?? 'Solid',
178
+ liquid: document.getElementById('phase-label-liquid')?.textContent ?? 'Liquid',
179
+ gas: document.getElementById('phase-label-gas')?.textContent ?? 'Gas',
180
+ supercritical: document.getElementById('phase-label-super')?.textContent ?? 'Supercritical',
181
+ };
182
+
183
+ function pressureFromSlider() {
184
+ return 10 ** Number(pressureInput?.value ?? -1);
185
+ }
186
+
187
+ function currentSubstance() {
188
+ return getSubstance(substanceSelect?.value ?? 'water');
189
+ }
190
+
191
+ function currentUnits() {
192
+ return (unitsSelect?.value ?? 'scientific') as 'scientific' | 'metric' | 'imperial';
193
+ }
194
+
195
+ function xForTemperature(temperature: number, minTemperature: number, maxTemperature: number) {
196
+ return plot.left + ((temperature - minTemperature) / (maxTemperature - minTemperature)) * plot.width;
197
+ }
198
+
199
+ function yForPressure(pressure: number, minPressure: number, maxPressure: number) {
200
+ const logMin = Math.log10(minPressure);
201
+ const logMax = Math.log10(maxPressure);
202
+ const progress = (Math.log10(Math.max(minPressure, pressure)) - logMin) / (logMax - logMin);
203
+
204
+ return plot.top + plot.height - progress * plot.height;
205
+ }
206
+
207
+ function pathFromPoints(points: { x: number; y: number }[]) {
208
+ return points.map((point, index) => `${index === 0 ? 'M' : 'L'}${point.x.toFixed(1)} ${point.y.toFixed(1)}`).join(' ');
209
+ }
210
+
211
+ function setAttribute(node: Element | null, name: string, value: string) {
212
+ node?.setAttribute(name, value);
213
+ }
214
+
215
+ function setText(node: Element | null, value: string) {
216
+ if (node) node.textContent = value;
217
+ }
218
+
219
+ function setCssValue(node: HTMLElement | null, name: string, value: string) {
220
+ node?.style.setProperty(name, value);
221
+ }
222
+
223
+ function updateSliderFill(input: HTMLInputElement | null) {
224
+ if (!input) return;
225
+ const min = Number(input.min);
226
+ const max = Number(input.max);
227
+ const value = Number(input.value);
228
+ input.style.setProperty('--fill', `${((value - min) / (max - min)) * 100}%`);
229
+ }
230
+
231
+ function boundaryCoexistence(substance: ReturnType<typeof getSubstance>, temperature: number, pressure: number) {
232
+ const vaporPressure = vaporPressureAt(substance, temperature);
233
+ const vaporDistance = Math.abs(Math.log10(Math.max(0.001, pressure)) - Math.log10(Math.max(0.001, vaporPressure)));
234
+ const meltPressure = meltingPressureAt(substance, temperature);
235
+ const meltDistance = Math.abs(Math.log10(Math.max(0.001, pressure)) - Math.log10(Math.max(0.001, meltPressure)));
236
+ const temperatureWindow = Math.abs(temperature - substance.tripleTemperature) < substance.criticalTemperature * 0.5;
237
+ const nearestDistance = Math.min(vaporDistance, temperatureWindow ? meltDistance : 99);
238
+
239
+ return Math.max(0, Math.min(1, 1 - nearestDistance / 0.22));
240
+ }
241
+
242
+ function readSavedState() {
243
+ try {
244
+ const rawState = window.localStorage.getItem(storageKey);
245
+ return rawState ? JSON.parse(rawState) : null;
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+
251
+ function saveState() {
252
+ try {
253
+ window.localStorage.setItem(storageKey, JSON.stringify({
254
+ substance: substanceSelect?.value,
255
+ units: unitsSelect?.value,
256
+ temperature: temperatureInput?.value,
257
+ pressure: pressureInput?.value,
258
+ }));
259
+ } catch {
260
+ }
261
+ }
262
+
263
+ function getPlotBounds(substance: ReturnType<typeof getSubstance>) {
264
+ return {
265
+ minTemperature: Math.max(35, substance.tripleTemperature * 0.55),
266
+ maxTemperature: substance.criticalTemperature * 1.18,
267
+ minPressure: Math.max(0.001, substance.triplePressure * 0.2),
268
+ maxPressure: substance.criticalPressure * 1.45,
269
+ };
270
+ }
271
+
272
+ function pointForValues(temperature: number, pressure: number, bounds: ReturnType<typeof getPlotBounds>) {
273
+ return {
274
+ x: xForTemperature(temperature, bounds.minTemperature, bounds.maxTemperature),
275
+ y: yForPressure(pressure, bounds.minPressure, bounds.maxPressure),
276
+ };
277
+ }
278
+
279
+ function buildGeometry(substance: ReturnType<typeof getSubstance>) {
280
+ const bounds = getPlotBounds(substance);
281
+ const minTemperature = Math.max(35, substance.tripleTemperature * 0.55);
282
+ const maxTemperature = substance.criticalTemperature * 1.18;
283
+ const curve = buildPhaseCurve(substance).map((point) => ({
284
+ x: xForTemperature(point.temperature, bounds.minTemperature, bounds.maxTemperature),
285
+ y: yForPressure(point.pressure, bounds.minPressure, bounds.maxPressure),
286
+ }));
287
+ const triple = pointForValues(substance.tripleTemperature, substance.triplePressure, bounds);
288
+ const critical = pointForValues(substance.criticalTemperature, substance.criticalPressure, bounds);
289
+ const meltTopTemperature = substance.tripleTemperature
290
+ + Math.sign(substance.fusionSlope) * (maxTemperature - minTemperature) * 0.24;
291
+ const meltTop = pointForValues(meltTopTemperature, bounds.maxPressure, bounds);
292
+ const meltLower = pointForValues(substance.tripleTemperature - 46, bounds.minPressure, bounds);
293
+
294
+ return { bounds, curve, triple, critical, meltTop, meltLower };
295
+ }
296
+
297
+ function drawPhaseGeometry(geometry: ReturnType<typeof buildGeometry>) {
298
+ const { curve, triple, critical, meltTop, meltLower } = geometry;
299
+ vaporCurve?.setAttribute('d', pathFromPoints(curve));
300
+ meltLine?.setAttribute('d', pathFromPoints([meltLower, triple, meltTop]));
301
+ solidRegion?.setAttribute('d', `M${plot.left} ${plot.top} H${meltTop.x.toFixed(1)} L${triple.x.toFixed(1)} ${triple.y.toFixed(1)} L${meltLower.x.toFixed(1)} ${plot.top + plot.height} H${plot.left} Z`);
302
+ liquidRegion?.setAttribute('d', `M${triple.x.toFixed(1)} ${triple.y.toFixed(1)} L${meltTop.x.toFixed(1)} ${plot.top} H${plot.left + plot.width} V${critical.y.toFixed(1)} L${critical.x.toFixed(1)} ${critical.y.toFixed(1)} ${pathFromPoints(curve.slice().reverse()).replace('M', 'L')} Z`);
303
+ superRegion?.setAttribute('d', `M${critical.x.toFixed(1)} ${plot.top} H${plot.left + plot.width} V${critical.y.toFixed(1)} H${critical.x.toFixed(1)} Z`);
304
+ triplePoint?.setAttribute('transform', `translate(${triple.x.toFixed(1)} ${triple.y.toFixed(1)})`);
305
+ criticalPoint?.setAttribute('transform', `translate(${critical.x.toFixed(1)} ${critical.y.toFixed(1)})`);
306
+ }
307
+
308
+ function positionRegionLabels(critical: { x: number; y: number }) {
309
+ setAttribute(phaseLabelNodes.solid, 'x', `${plot.left + 64}`);
310
+ setAttribute(phaseLabelNodes.solid, 'y', `${plot.top + 80}`);
311
+ setAttribute(phaseLabelNodes.liquid, 'x', `${Math.min(plot.left + plot.width - 162, critical.x - 118)}`);
312
+ setAttribute(phaseLabelNodes.liquid, 'y', `${plot.top + 126}`);
313
+ setAttribute(phaseLabelNodes.gas, 'x', `${plot.left + plot.width - 124}`);
314
+ setAttribute(phaseLabelNodes.gas, 'y', `${plot.top + plot.height - 54}`);
315
+ setAttribute(phaseLabelNodes.supercritical, 'x', `${Math.min(critical.x + 14, plot.left + plot.width - 168)}`);
316
+ setAttribute(phaseLabelNodes.supercritical, 'y', `${Math.max(plot.top + 42, critical.y + 38)}`);
317
+ }
318
+
319
+ function updateSampleReadout(substance: ReturnType<typeof getSubstance>, geometry: ReturnType<typeof buildGeometry>) {
320
+ const temperature = Number(temperatureInput?.value ?? substance.normalBoilingPoint);
321
+ const pressure = pressureFromSlider();
322
+ const sample = classifyPhase(substance, { temperature, pressure });
323
+ const samplePoint = pointForValues(temperature, pressure, geometry.bounds);
324
+ const meltPressure = meltingPressureAt(substance, temperature);
325
+ const coexistence = boundaryCoexistence(substance, temperature, pressure);
326
+
327
+ setAttribute(sampleMarker, 'transform', `translate(${samplePoint.x.toFixed(1)} ${samplePoint.y.toFixed(1)})`);
328
+ setAttribute(sampleMarker, 'data-phase', sample.phase);
329
+ setText(stateOutput, labels[sample.phase]);
330
+ setText(coordinatesOutput, `${formatTemperatureForUnits(temperature, currentUnits())} / ${formatPressureForUnits(pressure, currentUnits())}`);
331
+ setText(temperatureOutput, formatTemperatureForUnits(temperature, currentUnits()));
332
+ setText(pressureOutput, formatPressureForUnits(pressure, currentUnits()));
333
+ setText(latentOutput, sample.latentHeatIndex.toFixed(2));
334
+ setText(criticalOutput, sample.proximityToCritical.toFixed(2));
335
+ setText(coexistenceOutput, `${Math.round(coexistence * 100)}%`);
336
+ setCssValue(latentBar, '--value', `${sample.latentHeatIndex}`);
337
+ setCssValue(criticalBar, '--value', `${sample.proximityToCritical}`);
338
+ setCssValue(coexistenceMarker, '--value', `${coexistence}`);
339
+ setCssValue(coexistenceAxis, '--coexistence', `${coexistence}`);
340
+ document.getElementById('phase-lab')?.style.setProperty('--melt-pressure', `${meltPressure}`);
341
+ updateSliderFill(temperatureInput);
342
+ updateSliderFill(pressureInput);
343
+ saveState();
344
+ }
345
+
346
+ function drawDiagram() {
347
+ const substance = currentSubstance();
348
+ const geometry = buildGeometry(substance);
349
+
350
+ drawPhaseGeometry(geometry);
351
+ positionRegionLabels(geometry.critical);
352
+ updateSampleReadout(substance, geometry);
353
+ }
354
+
355
+ function resetForSubstance(savedState?: { temperature?: string; pressure?: string }) {
356
+ const substance = currentSubstance();
357
+ if (temperatureInput) {
358
+ temperatureInput.min = `${Math.max(40, Math.floor(substance.tripleTemperature * 0.55))}`;
359
+ temperatureInput.max = `${Math.ceil(substance.criticalTemperature * 1.18)}`;
360
+ temperatureInput.value = savedState?.temperature ?? `${Math.round(substance.normalBoilingPoint)}`;
361
+ }
362
+ if (pressureInput) {
363
+ pressureInput.min = `${Math.log10(Math.max(0.001, substance.triplePressure * 0.2)).toFixed(2)}`;
364
+ pressureInput.max = `${Math.log10(substance.criticalPressure * 1.45).toFixed(2)}`;
365
+ pressureInput.value = savedState?.pressure ?? `${Math.log10(Math.min(0.101, substance.criticalPressure * 0.72)).toFixed(2)}`;
366
+ }
367
+ drawDiagram();
368
+ }
369
+
370
+ function hydrateSavedState() {
371
+ const savedState = readSavedState();
372
+ if (!savedState) {
373
+ resetForSubstance();
374
+ return;
375
+ }
376
+
377
+ if (savedState.substance && substanceSelect) {
378
+ substanceSelect.value = savedState.substance;
379
+ }
380
+
381
+ if (savedState.units && unitsSelect) {
382
+ unitsSelect.value = savedState.units;
383
+ }
384
+
385
+ resetForSubstance({
386
+ temperature: savedState.temperature,
387
+ pressure: savedState.pressure,
388
+ });
389
+ }
390
+
391
+ substanceSelect?.addEventListener('change', () => resetForSubstance());
392
+ unitsSelect?.addEventListener('change', drawDiagram);
393
+ temperatureInput?.addEventListener('input', drawDiagram);
394
+ pressureInput?.addEventListener('input', drawDiagram);
395
+ resetButton?.addEventListener('click', () => resetForSubstance());
396
+ hydrateSavedState();
397
+ </script>
@@ -0,0 +1,26 @@
1
+ import type { ScienceToolEntry } from '../../types';
2
+
3
+ export const phaseDiagramCriticalPoints: ScienceToolEntry = {
4
+ id: 'phase-diagram-critical-points',
5
+ icons: {
6
+ bg: 'mdi:chart-bell-curve-cumulative',
7
+ fg: 'mdi:state-machine',
8
+ },
9
+ i18n: {
10
+ de: () => import('./i18n/de').then((m) => m.content),
11
+ en: () => import('./i18n/en').then((m) => m.content),
12
+ es: () => import('./i18n/es').then((m) => m.content),
13
+ fr: () => import('./i18n/fr').then((m) => m.content),
14
+ id: () => import('./i18n/id').then((m) => m.content),
15
+ it: () => import('./i18n/it').then((m) => m.content),
16
+ ja: () => import('./i18n/ja').then((m) => m.content),
17
+ ko: () => import('./i18n/ko').then((m) => m.content),
18
+ nl: () => import('./i18n/nl').then((m) => m.content),
19
+ pl: () => import('./i18n/pl').then((m) => m.content),
20
+ pt: () => import('./i18n/pt').then((m) => m.content),
21
+ ru: () => import('./i18n/ru').then((m) => m.content),
22
+ sv: () => import('./i18n/sv').then((m) => m.content),
23
+ tr: () => import('./i18n/tr').then((m) => m.content),
24
+ zh: () => import('./i18n/zh').then((m) => m.content),
25
+ },
26
+ };
@@ -0,0 +1,179 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+
4
+ const slug = 'phasendiagramm-kritischer-punkt-visualisierer';
5
+ const title = 'Phasendiagramm und Kritischer Punkt Visualisierer';
6
+ const description = 'Erkunden Sie feste, flussige, gasformige und uberkritische Bereiche in einem interaktiven Druck-Temperatur-Phasendiagramm mit Tripelpunkt- und Kritischer-Punkt-Markierungen.';
7
+
8
+ const howTo = [
9
+ {
10
+ name: 'Substanz auswahlen',
11
+ text: 'Wechseln Sie zwischen Wasser, Kohlendioxid und Stickstoff, um zu sehen, wie reale Tripelpunkte und kritische Punkte das Phasenbild verandern.',
12
+ },
13
+ {
14
+ name: 'Temperatur und Druck andern',
15
+ text: 'Nutzen Sie die Schieberegler, um die Probe auf der Druck-Temperatur-Ebene zu positionieren. Das Diagramm aktualisiert den aktiven Phasenbereich und die Live-Probenmarkierung.',
16
+ },
17
+ {
18
+ name: 'Den kritischen Hof beobachten',
19
+ text: 'Bewegen Sie sich zum Ende der Dampf-Flussig-Grenze, um zu sehen, wie die latente Warme verschwindet und der Flussig-Gas-Unterscheid in einer uberkritischen Flussigkeit aufgeht.',
20
+ },
21
+ {
22
+ name: 'Das Lehrpanel lesen',
23
+ text: 'Nutzen Sie die Phasenbezeichnung, den Latenzwarme-Indikator und die Punktanzeigen, um das visuelle Diagramm mit thermodynamischen Fachbegriffen zu verknupfen.',
24
+ },
25
+ ];
26
+
27
+ const faq = [
28
+ {
29
+ question: 'Was ist ein Phasendiagramm?',
30
+ answer: 'Ein Phasendiagramm zeigt, welcher Aggregatzustand bei verschiedenen Kombinationen von Temperatur und Druck stabil ist. Die Grenzlinien markieren Bedingungen, unter denen zwei Phasen im Gleichgewicht nebeneinander existieren konnen.',
31
+ },
32
+ {
33
+ question: 'Was passiert am kritischen Punkt?',
34
+ answer: 'Am kritischen Punkt endet die Dampf-Flussig-Grenze. Oberhalb der kritischen Temperatur und des kritischen Drucks wird das Material zu einer uberkritischen Flussigkeit, und es gibt keine scharfe Trennung zwischen Flussigkeit und Gas.',
35
+ },
36
+ {
37
+ question: 'Warum hat Wasser eine andere Schmelzlinie?',
38
+ answer: 'Wasser ist ungewohnlich, weil Eis in der Nahe des Schmelzpunkts eine geringere Dichte als flussiges Wasser hat. Steigender Druck kann die dichtere flussige Phase begunstigen, weshalb die Fest-Flussig-Grenze einen anderen Verlauf hat als bei vielen anderen Substanzen.',
39
+ },
40
+ {
41
+ question: 'Sind die dargestellten Kurven laborgenau?',
42
+ answer: 'Nein. Das Werkzeug verwendet vereinfachte Kurven, die an publizierte Tripelpunkt- und Kritischer-Punkt-Werte angelehnt sind. Es ist fur das konzeptionelle Lernen konzipiert, nicht fur Verfahrenstechnik oder Sicherheitsberechnungen.',
43
+ },
44
+ ];
45
+
46
+ export const content: ToolLocaleContent = {
47
+ slug,
48
+ title,
49
+ description,
50
+ ui: {
51
+ controls: 'Phasendiagramm Steuerung',
52
+ substance: 'Substanz',
53
+ units: 'Einheiten',
54
+ scientificUnits: 'Wissenschaftlich (K, MPa)',
55
+ metricUnits: 'Metrisch (Celsius, kPa)',
56
+ imperialUnits: 'Imperial (Fahrenheit, psi)',
57
+ temperature: 'Temperatur',
58
+ pressure: 'Druck',
59
+ diagram: 'Druck-Temperatur-Phasendiagramm',
60
+ sample: 'Probenzustand',
61
+ phase: 'Stabile Phase',
62
+ triplePoint: 'Tripelpunkt',
63
+ criticalPoint: 'Kritischer Punkt',
64
+ vaporCurve: 'Dampf-Flussig-Grenze',
65
+ meltingLine: 'Fest-Flussig-Grenze',
66
+ latentHeat: 'Latentwarme Kontrast',
67
+ criticalProximity: 'Kritische Nahe',
68
+ coordinates: 'Koordinaten',
69
+ solid: 'Fest',
70
+ liquid: 'Flussig',
71
+ gas: 'Gasformig',
72
+ supercritical: 'Uberkritisch',
73
+ low: 'niedrig',
74
+ high: 'hoch',
75
+ reset: 'Zurucksetzen',
76
+ interpretation: 'Interpretation',
77
+ note: 'Grenzlinien markieren Koexistenz; Bereiche markieren die stabilste Phase fur die gewahlten Bedingungen.',
78
+ },
79
+ seo: [
80
+ {
81
+ type: 'title',
82
+ text: 'Interaktives Phasendiagramm Visualisierer fur Tripelpunkte, Siedekurven und kritische Punkte',
83
+ level: 2,
84
+ },
85
+ {
86
+ type: 'paragraph',
87
+ html: 'Dieser Phasendiagramm-Visualisierer verwandelt ein abstraktes Druck-Temperatur-Diagramm in eine interaktive Karte. Wahlen Sie eine Substanz, andern Sie Temperatur und Druck, und sehen Sie, ob die Probe als Feststoff, Flussigkeit, Gas oder uberkritische Flussigkeit vorhergesagt wird. Das Ziel ist es, Phasengrenzen raumlich erfahrbar zu machen: Das Uberschreiten einer Linie andert den stabilen Zustand, wahrend die Annaherung an den kritischen Punkt die Bedeutung einer Phasengrenze selbst verandert.',
88
+ },
89
+ {
90
+ type: 'paragraph',
91
+ html: 'Das Werkzeug richtet sich an Studenten, Lehrer, Wissenschaftsautoren und alle, die eine klare Erklarung von Phasendiagrammen suchen. Es betont die Merkmale, die in der einfuhrenden Thermodynamik am wichtigsten sind: den Tripelpunkt, an dem drei Phasen koexistieren, die Dampf-Flussig-Kurve, die Fest-Flussig-Grenze und den kritischen Endpunkt, an dem der Flussig-Gas-Unterschied verschwindet.',
92
+ },
93
+ {
94
+ type: 'title',
95
+ text: 'Wie man das Druck-Temperatur-Diagramm liest',
96
+ level: 3,
97
+ },
98
+ {
99
+ type: 'paragraph',
100
+ html: 'Ein Phasendiagramm tragt die Temperatur auf der einen und den Druck auf der anderen Achse auf. Jeder Bereich zeigt die Phase, die unter diesen Bedingungen stabil ist. Die Linien zwischen den Bereichen sind Koexistenzkurven: Entlang dieser Linien konnen zwei Phasen im Gleichgewicht bleiben, anstatt dass eine Phase die andere vollstandig ersetzt.',
101
+ },
102
+ {
103
+ type: 'table',
104
+ headers: ['Diagrammmerkmal', 'Bedeutung', 'Was im Werkzeug zu beachten ist'],
105
+ rows: [
106
+ ['Tripelpunkt', 'Fest, Flussig und Gas koexistieren', 'Der Tieftemperatur-Knotenpunkt, an dem die Grenzen aufeinandertreffen.'],
107
+ ['Dampf-Flussig-Kurve', 'Siede- oder Kondensationsgleichgewicht', 'Die gekrummte Linie vom Tripelpunkt zum kritischen Punkt.'],
108
+ ['Fest-Flussig-Grenze', 'Schmelz- oder Gefriergleichgewicht', 'Die steile Linie, die feste und flussige Bereiche trennt.'],
109
+ ['Kritischer Punkt', 'Ende der Dampf-Flussig-Grenze', 'Der hervorgehobene Endpunkt, an dem die latente Warme verschwindet.'],
110
+ ['Uberkritischer Bereich', 'Keine scharfe Flussig-Gas-Unterscheidung', 'Der Hochtemperatur- und Hochdruckbereich jenseits des kritischen Punkts.'],
111
+ ],
112
+ },
113
+ {
114
+ type: 'title',
115
+ text: 'Warum der kritische Punkt wichtig ist',
116
+ level: 3,
117
+ },
118
+ {
119
+ type: 'paragraph',
120
+ html: 'Unterhalb des kritischen Punkts ist Sieden ein Phasenubergang: Flussigkeit und Dampf konnen koexistieren, und Energie kann als latente Warme aufgenommen werden, wahrend die Temperatur an die Randbedingung gebunden bleibt. Am kritischen Punkt endet diese Grenze. Daruber andert sich die Dichte kontinuierlich, und die Substanz wird als uberkritische Flussigkeit und nicht als normale Flussigkeit oder Gas beschrieben.',
121
+ },
122
+ {
123
+ type: 'paragraph',
124
+ html: 'Dies ist in der Chemie, der Planetenwissenschaft, der industriellen Extraktion, der Kalte- und Hochdruckphysik von Bedeutung. Kohlendioxid wird beispielsweise unter relativ zuganglichen Bedingungen uberkritisch im Vergleich zu Wasser, weshalb uberkritisches CO2 in der Extraktion und Materialverarbeitung eingesetzt wird. Wasser erfordert viel hohere Temperaturen und Drucke, was seinen kritischen Punkt fur Energiesysteme und Geophysik wichtig macht.',
125
+ },
126
+ {
127
+ type: 'title',
128
+ text: 'Was dieser Visualisierer vereinfacht',
129
+ level: 3,
130
+ },
131
+ {
132
+ type: 'paragraph',
133
+ html: 'Reale Phasendiagramme konnen Polymorphe, metastabile Zustande, nichtideale Mischungen, mehrere feste Phasen und experimentell angepasste Zustandsgleichungen umfassen. Dieses Lehrwerkzeug halt das Modell bewusst kompakt. Es verankert jede Substanz an erkennbaren Referenzwerten und zeichnet glatte Lehrkurven, damit die Hauptideen leicht zu erfassen sind, ohne dass eine Thermodynamik-Tabelle benotigt wird.',
134
+ },
135
+ {
136
+ type: 'list',
137
+ items: [
138
+ '<strong>Nutzen Sie es fur die Intuition:</strong> Es hilft zu erklaren, warum sich Schnellkochtopfe, Trockeneis, Sieden und uberkritische Flussigkeiten unterschiedlich verhalten.',
139
+ '<strong>Nicht fur technische Grenzwerte verwenden:</strong> Vereinfachte Kurven sind kein Ersatz fur zertifizierte Stoffdaten.',
140
+ '<strong>Fokus auf Topologie:</strong> Das wichtigste Lernergebnis ist, wie Phasenbereiche zusammenhangen und wo Grenzen enden.',
141
+ ],
142
+ },
143
+ ],
144
+ faq,
145
+ bibliography,
146
+ howTo,
147
+ schemas: [
148
+ {
149
+ '@context': 'https://schema.org',
150
+ '@type': 'SoftwareApplication',
151
+ name: title,
152
+ description,
153
+ applicationCategory: 'ScientificApplication',
154
+ operatingSystem: 'Any',
155
+ },
156
+ {
157
+ '@context': 'https://schema.org',
158
+ '@type': 'FAQPage',
159
+ mainEntity: faq.map((item) => ({
160
+ '@type': 'Question',
161
+ name: item.question,
162
+ acceptedAnswer: {
163
+ '@type': 'Answer',
164
+ text: item.answer,
165
+ },
166
+ })),
167
+ },
168
+ {
169
+ '@context': 'https://schema.org',
170
+ '@type': 'HowTo',
171
+ name: title,
172
+ step: howTo.map((step) => ({
173
+ '@type': 'HowToStep',
174
+ name: step.name,
175
+ text: step.text,
176
+ })),
177
+ },
178
+ ],
179
+ };