@jjlmoya/utils-science 1.36.0 → 1.38.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 (76) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +4 -1
  3. package/src/entries.ts +7 -1
  4. package/src/index.ts +3 -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/double-slit-decoherence/bibliography.astro +14 -0
  8. package/src/tool/double-slit-decoherence/bibliography.ts +12 -0
  9. package/src/tool/double-slit-decoherence/component.astro +235 -0
  10. package/src/tool/double-slit-decoherence/double-slit-decoherence-simulator.css +344 -0
  11. package/src/tool/double-slit-decoherence/entry.ts +26 -0
  12. package/src/tool/double-slit-decoherence/i18n/de.ts +181 -0
  13. package/src/tool/double-slit-decoherence/i18n/en.ts +181 -0
  14. package/src/tool/double-slit-decoherence/i18n/es.ts +181 -0
  15. package/src/tool/double-slit-decoherence/i18n/fr.ts +181 -0
  16. package/src/tool/double-slit-decoherence/i18n/id.ts +181 -0
  17. package/src/tool/double-slit-decoherence/i18n/it.ts +181 -0
  18. package/src/tool/double-slit-decoherence/i18n/ja.ts +181 -0
  19. package/src/tool/double-slit-decoherence/i18n/ko.ts +181 -0
  20. package/src/tool/double-slit-decoherence/i18n/nl.ts +181 -0
  21. package/src/tool/double-slit-decoherence/i18n/pl.ts +181 -0
  22. package/src/tool/double-slit-decoherence/i18n/pt.ts +181 -0
  23. package/src/tool/double-slit-decoherence/i18n/ru.ts +181 -0
  24. package/src/tool/double-slit-decoherence/i18n/sv.ts +181 -0
  25. package/src/tool/double-slit-decoherence/i18n/tr.ts +181 -0
  26. package/src/tool/double-slit-decoherence/i18n/zh.ts +181 -0
  27. package/src/tool/double-slit-decoherence/index.ts +11 -0
  28. package/src/tool/double-slit-decoherence/logic.ts +77 -0
  29. package/src/tool/double-slit-decoherence/seo.astro +15 -0
  30. package/src/tool/dyson-sphere-energy-capture/bibliography.astro +14 -0
  31. package/src/tool/dyson-sphere-energy-capture/bibliography.ts +16 -0
  32. package/src/tool/dyson-sphere-energy-capture/component.astro +253 -0
  33. package/src/tool/dyson-sphere-energy-capture/dyson-sphere-energy-capture.css +502 -0
  34. package/src/tool/dyson-sphere-energy-capture/entry.ts +26 -0
  35. package/src/tool/dyson-sphere-energy-capture/i18n/de.ts +195 -0
  36. package/src/tool/dyson-sphere-energy-capture/i18n/en.ts +195 -0
  37. package/src/tool/dyson-sphere-energy-capture/i18n/es.ts +195 -0
  38. package/src/tool/dyson-sphere-energy-capture/i18n/fr.ts +195 -0
  39. package/src/tool/dyson-sphere-energy-capture/i18n/id.ts +195 -0
  40. package/src/tool/dyson-sphere-energy-capture/i18n/it.ts +195 -0
  41. package/src/tool/dyson-sphere-energy-capture/i18n/ja.ts +71 -0
  42. package/src/tool/dyson-sphere-energy-capture/i18n/ko.ts +71 -0
  43. package/src/tool/dyson-sphere-energy-capture/i18n/nl.ts +197 -0
  44. package/src/tool/dyson-sphere-energy-capture/i18n/pl.ts +197 -0
  45. package/src/tool/dyson-sphere-energy-capture/i18n/pt.ts +195 -0
  46. package/src/tool/dyson-sphere-energy-capture/i18n/ru.ts +195 -0
  47. package/src/tool/dyson-sphere-energy-capture/i18n/sv.ts +195 -0
  48. package/src/tool/dyson-sphere-energy-capture/i18n/tr.ts +195 -0
  49. package/src/tool/dyson-sphere-energy-capture/i18n/zh.ts +71 -0
  50. package/src/tool/dyson-sphere-energy-capture/index.ts +11 -0
  51. package/src/tool/dyson-sphere-energy-capture/logic.ts +120 -0
  52. package/src/tool/dyson-sphere-energy-capture/seo.astro +15 -0
  53. package/src/tool/global-albedo-snowball-simulator/bibliography.astro +14 -0
  54. package/src/tool/global-albedo-snowball-simulator/bibliography.ts +16 -0
  55. package/src/tool/global-albedo-snowball-simulator/component.astro +278 -0
  56. package/src/tool/global-albedo-snowball-simulator/entry.ts +26 -0
  57. package/src/tool/global-albedo-snowball-simulator/global-albedo-snowball-simulator.css +530 -0
  58. package/src/tool/global-albedo-snowball-simulator/i18n/de.ts +169 -0
  59. package/src/tool/global-albedo-snowball-simulator/i18n/en.ts +169 -0
  60. package/src/tool/global-albedo-snowball-simulator/i18n/es.ts +169 -0
  61. package/src/tool/global-albedo-snowball-simulator/i18n/fr.ts +169 -0
  62. package/src/tool/global-albedo-snowball-simulator/i18n/id.ts +169 -0
  63. package/src/tool/global-albedo-snowball-simulator/i18n/it.ts +87 -0
  64. package/src/tool/global-albedo-snowball-simulator/i18n/ja.ts +87 -0
  65. package/src/tool/global-albedo-snowball-simulator/i18n/ko.ts +169 -0
  66. package/src/tool/global-albedo-snowball-simulator/i18n/nl.ts +169 -0
  67. package/src/tool/global-albedo-snowball-simulator/i18n/pl.ts +169 -0
  68. package/src/tool/global-albedo-snowball-simulator/i18n/pt.ts +169 -0
  69. package/src/tool/global-albedo-snowball-simulator/i18n/ru.ts +169 -0
  70. package/src/tool/global-albedo-snowball-simulator/i18n/sv.ts +169 -0
  71. package/src/tool/global-albedo-snowball-simulator/i18n/tr.ts +169 -0
  72. package/src/tool/global-albedo-snowball-simulator/i18n/zh.ts +169 -0
  73. package/src/tool/global-albedo-snowball-simulator/index.ts +11 -0
  74. package/src/tool/global-albedo-snowball-simulator/logic.ts +88 -0
  75. package/src/tool/global-albedo-snowball-simulator/seo.astro +15 -0
  76. package/src/tools.ts +6 -0
@@ -0,0 +1,253 @@
1
+ ---
2
+ import './dyson-sphere-energy-capture.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div
12
+ class="dyson-root"
13
+ id="dyson-root"
14
+ data-status-underbuilt={ui.statusUnderbuilt}
15
+ data-status-balanced={ui.statusBalanced}
16
+ data-status-extreme={ui.statusExtreme}
17
+ data-mercury-masses={ui.mercuryMasses}
18
+ data-kilograms={ui.kilograms}
19
+ data-days-unit={ui.daysUnit}
20
+ >
21
+ <section class="dyson-stage" aria-label={ui.visualization}>
22
+ <div class="dyson-starfield">
23
+ <div class="dyson-orbit dyson-orbit-outer"></div>
24
+ <div class="dyson-orbit dyson-orbit-inner"></div>
25
+ <div class="dyson-star"></div>
26
+ <div class="dyson-collectors" id="dyson-collectors"></div>
27
+ <div class="dyson-power-beam"></div>
28
+ </div>
29
+ <div class="dyson-stage-readout">
30
+ <span>{ui.kardashevRating}</span>
31
+ <strong id="dyson-kardashev">---</strong>
32
+ </div>
33
+ </section>
34
+
35
+ <section class="dyson-panel">
36
+ <div class="dyson-selector-grid">
37
+ <label>
38
+ <span>{ui.starType}</span>
39
+ <select id="dyson-star-select">
40
+ <option value="m-dwarf">{ui.starMDwarf}</option>
41
+ <option value="sun" selected>{ui.starSun}</option>
42
+ <option value="a-star">{ui.starA}</option>
43
+ <option value="red-giant">{ui.starRedGiant}</option>
44
+ <option value="blue-giant">{ui.starBlueGiant}</option>
45
+ </select>
46
+ </label>
47
+ <label>
48
+ <span>{ui.structureType}</span>
49
+ <select id="dyson-structure-select">
50
+ <option value="swarm" selected>{ui.structureSwarm}</option>
51
+ <option value="ring">{ui.structureRing}</option>
52
+ <option value="shell">{ui.structureShell}</option>
53
+ <option value="statite-cloud">{ui.structureStatite}</option>
54
+ </select>
55
+ </label>
56
+ </div>
57
+
58
+ <div class="dyson-slider-stack">
59
+ <label class="dyson-slider-row" for="dyson-coverage">
60
+ <span>{ui.coverage}</span>
61
+ <strong id="dyson-coverage-value">35%</strong>
62
+ <input id="dyson-coverage" type="range" min="1" max="100" value="35" step="1" />
63
+ </label>
64
+ <label class="dyson-slider-row" for="dyson-temperature">
65
+ <span>{ui.operatingTemp}</span>
66
+ <strong id="dyson-temperature-value">600 K</strong>
67
+ <input id="dyson-temperature" type="range" min="250" max="1200" value="600" step="10" />
68
+ </label>
69
+ <label class="dyson-slider-row" for="dyson-target">
70
+ <span>{ui.kardashevTarget}</span>
71
+ <strong id="dyson-target-value">1.7</strong>
72
+ <input id="dyson-target" type="range" min="1" max="2" value="1.7" step="0.01" />
73
+ </label>
74
+ </div>
75
+ </section>
76
+
77
+ <section class="dyson-results">
78
+ <article>
79
+ <span>{ui.capturedPower}</span>
80
+ <strong id="dyson-captured">---</strong>
81
+ </article>
82
+ <article>
83
+ <span>{ui.optimalRadius}</span>
84
+ <strong id="dyson-radius">---</strong>
85
+ </article>
86
+ <article>
87
+ <span>{ui.targetCoverage}</span>
88
+ <strong id="dyson-target-coverage">---</strong>
89
+ </article>
90
+ <article>
91
+ <span>{ui.materialMass}</span>
92
+ <strong id="dyson-mass">---</strong>
93
+ </article>
94
+ </section>
95
+
96
+ <section class="dyson-diagnostics">
97
+ <div class="dyson-meter">
98
+ <span>{ui.captureMeter}</span>
99
+ <div><i id="dyson-meter-fill"></i></div>
100
+ </div>
101
+ <p id="dyson-status">{ui.statusReady}</p>
102
+ <dl>
103
+ <div>
104
+ <dt>{ui.orbitalPeriod}</dt>
105
+ <dd id="dyson-period">---</dd>
106
+ </div>
107
+ <div>
108
+ <dt>{ui.collectorArea}</dt>
109
+ <dd id="dyson-area">---</dd>
110
+ </div>
111
+ </dl>
112
+ </section>
113
+ </div>
114
+
115
+ <script>
116
+ import { calculateDyson } from './logic';
117
+ import type { StarType, StructureType } from './logic';
118
+
119
+ const root = document.getElementById('dyson-root');
120
+ if (root) {
121
+ const storageKey = 'dyson-sphere-energy-capture:v1';
122
+ const starSelect = document.getElementById('dyson-star-select') as HTMLSelectElement;
123
+ const structureSelect = document.getElementById('dyson-structure-select') as HTMLSelectElement;
124
+ const coverageInput = document.getElementById('dyson-coverage') as HTMLInputElement;
125
+ const temperatureInput = document.getElementById('dyson-temperature') as HTMLInputElement;
126
+ const targetInput = document.getElementById('dyson-target') as HTMLInputElement;
127
+ const collectors = document.getElementById('dyson-collectors');
128
+ const statusText: Record<string, string> = {
129
+ underbuilt: root.dataset.statusUnderbuilt || '',
130
+ balanced: root.dataset.statusBalanced || '',
131
+ extreme: root.dataset.statusExtreme || '',
132
+ };
133
+
134
+ const setText = (id: string, text: string) => {
135
+ const node = document.getElementById(id);
136
+ if (node) node.textContent = text;
137
+ };
138
+
139
+ const formatPower = (value: number) => `${value.toExponential(2).replace('e+', 'e')} W`;
140
+
141
+ function formatMass(kg: number, mercury: number) {
142
+ if (mercury > 0.01) return (root.dataset.mercuryMasses || '{value}').replace('{value}', mercury.toFixed(2));
143
+ return (root.dataset.kilograms || '{value} kg').replace('{value}', kg.toExponential(2));
144
+ }
145
+
146
+ function getCoverageBand(coveragePercent: number) {
147
+ if (coveragePercent > 92) return 'full';
148
+ if (coveragePercent > 62) return 'dense';
149
+ return 'open';
150
+ }
151
+
152
+ function getCollectorCount(coveragePercent: number) {
153
+ const baseCount = Math.max(6, Math.round(coveragePercent * 1.2));
154
+ if (structureSelect.value === 'ring') return Math.max(10, Math.round(coveragePercent * 0.7));
155
+ if (structureSelect.value === 'shell') return Math.max(18, Math.round(coveragePercent * 1.7));
156
+ if (structureSelect.value === 'statite-cloud') return Math.max(14, Math.round(coveragePercent * 1.45));
157
+ return baseCount;
158
+ }
159
+
160
+ function buildCollectors(count: number) {
161
+ if (!collectors) return;
162
+ collectors.innerHTML = '';
163
+ for (let i = 0; i < count; i++) {
164
+ const dot = document.createElement('span');
165
+ const band = i % 4;
166
+ dot.style.setProperty('--angle', `${(360 / count) * i}deg`);
167
+ dot.style.setProperty('--depth', `${86 + band * 17}px`);
168
+ dot.style.setProperty('--delay', `${i * -0.18}s`);
169
+ dot.style.setProperty('--tilt', `${band * 9 - 13}deg`);
170
+ collectors.append(dot);
171
+ }
172
+ }
173
+
174
+ function renderReadouts(result: ReturnType<typeof calculateDyson>, coveragePercent: number, operatingTempK: number, kardashevTarget: number) {
175
+ setText('dyson-coverage-value', `${coveragePercent}%`);
176
+ setText('dyson-temperature-value', `${operatingTempK} K`);
177
+ setText('dyson-target-value', kardashevTarget.toFixed(2));
178
+ setText('dyson-kardashev', `K${result.kardashevRating.toFixed(2)}`);
179
+ setText('dyson-captured', formatPower(result.capturedWatts));
180
+ setText('dyson-radius', `${result.optimalRadiusAu.toFixed(2)} AU`);
181
+ setText('dyson-target-coverage', `${result.targetCoveragePercent.toFixed(1)}%`);
182
+ setText('dyson-mass', formatMass(result.materialMassKg, result.mercuryMasses));
183
+ setText('dyson-period', (root.dataset.daysUnit || '{value} days').replace('{value}', result.orbitalPeriodDays.toLocaleString(undefined, { maximumFractionDigits: 0 })));
184
+ setText('dyson-area', `${result.collectorAreaM2.toExponential(2)} m2`);
185
+ setText('dyson-status', statusText[result.status]);
186
+ }
187
+
188
+ function renderVisuals(result: ReturnType<typeof calculateDyson>, coveragePercent: number) {
189
+ const fill = document.getElementById('dyson-meter-fill');
190
+ if (fill) fill.style.width = `${Math.min(100, (coveragePercent / Math.max(1, result.targetCoveragePercent)) * 100)}%`;
191
+
192
+ const radiusScale = Math.min(1.34, Math.max(0.68, 0.78 + Math.log10(Math.max(0.08, result.optimalRadiusAu)) * 0.18));
193
+ const orbitSpeedSeconds = Math.min(34, Math.max(5, Math.log10(Math.max(2, result.orbitalPeriodDays)) * 8));
194
+ root.style.setProperty('--dyson-coverage', `${coveragePercent}%`);
195
+ root.style.setProperty('--dyson-orbit-scale', radiusScale.toFixed(3));
196
+ root.style.setProperty('--dyson-orbit-speed', `${orbitSpeedSeconds.toFixed(2)}s`);
197
+ root.style.setProperty('--dyson-glow-strength', `${Math.min(0.82, 0.28 + coveragePercent / 160)}`);
198
+ root.dataset.star = starSelect.value;
199
+ root.dataset.structure = structureSelect.value;
200
+ root.dataset.status = result.status;
201
+ root.dataset.coverage = getCoverageBand(coveragePercent);
202
+ buildCollectors(getCollectorCount(coveragePercent));
203
+ }
204
+
205
+ function saveState() {
206
+ const payload = {
207
+ star: starSelect.value,
208
+ structure: structureSelect.value,
209
+ coverage: coverageInput.value,
210
+ temperature: temperatureInput.value,
211
+ target: targetInput.value,
212
+ };
213
+ localStorage.setItem(storageKey, JSON.stringify(payload));
214
+ }
215
+
216
+ function restoreState() {
217
+ const raw = localStorage.getItem(storageKey);
218
+ if (!raw) return;
219
+
220
+ const payload = JSON.parse(raw) as Record<string, string>;
221
+ if (payload.star) starSelect.value = payload.star;
222
+ if (payload.structure) structureSelect.value = payload.structure;
223
+ if (payload.coverage) coverageInput.value = payload.coverage;
224
+ if (payload.temperature) temperatureInput.value = payload.temperature;
225
+ if (payload.target) targetInput.value = payload.target;
226
+ }
227
+
228
+ function update() {
229
+ const coveragePercent = Number(coverageInput.value);
230
+ const operatingTempK = Number(temperatureInput.value);
231
+ const kardashevTarget = Number(targetInput.value);
232
+ const result = calculateDyson({
233
+ star: starSelect.value as StarType,
234
+ structure: structureSelect.value as StructureType,
235
+ coveragePercent,
236
+ operatingTempK,
237
+ kardashevTarget,
238
+ });
239
+
240
+ renderReadouts(result, coveragePercent, operatingTempK, kardashevTarget);
241
+ renderVisuals(result, coveragePercent);
242
+ saveState();
243
+ }
244
+
245
+ [starSelect, structureSelect, coverageInput, temperatureInput, targetInput].forEach((control) => {
246
+ control.addEventListener('input', update);
247
+ control.addEventListener('change', update);
248
+ });
249
+
250
+ restoreState();
251
+ update();
252
+ }
253
+ </script>