@jjlmoya/utils-science 1.25.0 → 1.26.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 (31) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +3 -1
  4. package/src/index.ts +1 -1
  5. package/src/tests/locale_completeness.test.ts +2 -3
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/radioactive-decay/bibliography.astro +15 -0
  8. package/src/tool/radioactive-decay/bibliography.ts +17 -0
  9. package/src/tool/radioactive-decay/component.astro +346 -0
  10. package/src/tool/radioactive-decay/entry.ts +26 -0
  11. package/src/tool/radioactive-decay/i18n/de.ts +78 -0
  12. package/src/tool/radioactive-decay/i18n/en.ts +223 -0
  13. package/src/tool/radioactive-decay/i18n/es.ts +106 -0
  14. package/src/tool/radioactive-decay/i18n/fr.ts +78 -0
  15. package/src/tool/radioactive-decay/i18n/id.ts +66 -0
  16. package/src/tool/radioactive-decay/i18n/it.ts +79 -0
  17. package/src/tool/radioactive-decay/i18n/ja.ts +65 -0
  18. package/src/tool/radioactive-decay/i18n/ko.ts +65 -0
  19. package/src/tool/radioactive-decay/i18n/nl.ts +72 -0
  20. package/src/tool/radioactive-decay/i18n/pl.ts +65 -0
  21. package/src/tool/radioactive-decay/i18n/pt.ts +78 -0
  22. package/src/tool/radioactive-decay/i18n/ru.ts +66 -0
  23. package/src/tool/radioactive-decay/i18n/sv.ts +66 -0
  24. package/src/tool/radioactive-decay/i18n/tr.ts +66 -0
  25. package/src/tool/radioactive-decay/i18n/zh.ts +65 -0
  26. package/src/tool/radioactive-decay/index.ts +12 -0
  27. package/src/tool/radioactive-decay/logic.test.ts +20 -0
  28. package/src/tool/radioactive-decay/logic.ts +120 -0
  29. package/src/tool/radioactive-decay/radioactive-decay-half-life-calculator.css +435 -0
  30. package/src/tool/radioactive-decay/seo.astro +16 -0
  31. package/src/tools.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-science",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -8,10 +8,11 @@ import { cosmicInflation } from '../tool/cosmic-inflation/index';
8
8
  import { temperatureTimeline } from '../tool/temperature-timeline/index';
9
9
  import { lorenzAttractor } from '../tool/lorenz-attractor/index';
10
10
  import { stellarHabitabilityZone } from '../tool/stellar-habitability-zone/index';
11
+ import { radioactiveDecay } from '../tool/radioactive-decay/index';
11
12
 
12
13
  export const scienceCategory: ScienceCategoryEntry = {
13
14
  icon: 'mdi:flask',
14
- tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone],
15
+ tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone, radioactiveDecay],
15
16
  i18n: {
16
17
  es: () => import('./i18n/es').then((m) => m.content),
17
18
  en: () => import('./i18n/en').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -8,6 +8,7 @@ export { cosmicInflation } from './tool/cosmic-inflation/entry';
8
8
  export { temperatureTimeline } from './tool/temperature-timeline/entry';
9
9
  export { lorenzAttractor } from './tool/lorenz-attractor/entry';
10
10
  export { stellarHabitabilityZone } from './tool/stellar-habitability-zone/entry';
11
+ export { radioactiveDecay } from './tool/radioactive-decay/entry';
11
12
  export { scienceCategory } from './category';
12
13
  import { asteroidImpact } from './tool/asteroid-impact/entry';
13
14
  import { cellularRenewal } from './tool/cellular-renewal/entry';
@@ -18,4 +19,5 @@ import { cosmicInflation } from './tool/cosmic-inflation/entry';
18
19
  import { temperatureTimeline } from './tool/temperature-timeline/entry';
19
20
  import { lorenzAttractor } from './tool/lorenz-attractor/entry';
20
21
  import { stellarHabitabilityZone } from './tool/stellar-habitability-zone/entry';
21
- export const ALL_ENTRIES = [asteroidImpact, cellularRenewal, colonyCounter, microwaveDetector, simulationProbability, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone];
22
+ import { radioactiveDecay } from './tool/radioactive-decay/entry';
23
+ export const ALL_ENTRIES = [asteroidImpact, cellularRenewal, colonyCounter, microwaveDetector, simulationProbability, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone, radioactiveDecay];
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export { COSMIC_INFLATION_TOOL } from './tool/cosmic-inflation/index';
9
9
  export { TEMPERATURE_TIMELINE_TOOL } from './tool/temperature-timeline/index';
10
10
  export { LORENZ_ATTRACTOR_TOOL } from './tool/lorenz-attractor/index';
11
11
  export { STELLAR_HABITABILITY_ZONE_TOOL } from './tool/stellar-habitability-zone/index';
12
+ export { RADIOACTIVE_DECAY_TOOL } from './tool/radioactive-decay/index';
12
13
 
13
14
  export type {
14
15
  KnownLocale,
@@ -25,4 +26,3 @@ export type {
25
26
  } from './types';
26
27
 
27
28
  export { ALL_ENTRIES, ALL_TOOLS } from './tools';
28
-
@@ -18,8 +18,7 @@ describe('Locale Completeness Validation', () => {
18
18
  });
19
19
  });
20
20
 
21
- it('all 9 tools registered', () => {
22
- expect(ALL_TOOLS.length).toBe(9);
21
+ it('all 10 tools registered', () => {
22
+ expect(ALL_TOOLS.length).toBe(10);
23
23
  });
24
24
  });
25
-
@@ -4,8 +4,8 @@ import { scienceCategory } from '../data';
4
4
 
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
- it('should have 9 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(9);
7
+ it('should have 10 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(10);
9
9
  });
10
10
 
11
11
  it('scienceCategory should be defined', () => {
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { radioactiveDecay } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await radioactiveDecay.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} />}
15
+
@@ -0,0 +1,17 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Lawrence Berkeley National Laboratory, The ABCs of Nuclear Science: Radioactive Decay',
6
+ url: 'https://www2.lbl.gov/abc/wallchart/chapters/03/4.html',
7
+ },
8
+ {
9
+ name: 'NIST, Radionuclide Half-Life Measurements',
10
+ url: 'https://www.nist.gov/pml/radionuclide-half-life-measurements',
11
+ },
12
+ {
13
+ name: 'International Atomic Energy Agency, Radioisotopes in Medicine',
14
+ url: 'https://www.iaea.org/topics/nuclear-science/isotopes/radioisotopes',
15
+ },
16
+ ];
17
+
@@ -0,0 +1,346 @@
1
+ ---
2
+ import './radioactive-decay-half-life-calculator.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="decay-lab" id="decay-lab">
12
+ <section class="decay-stage" aria-label={ui.atomField}>
13
+ <div class="decay-stage-header">
14
+ <div>
15
+ <p class="decay-kicker">{ui.expectedCurve}</p>
16
+ <h2>{ui.atomField}</h2>
17
+ </div>
18
+ <button class="decay-seed-button" id="decay-seed-button" type="button">{ui.resetSeed}</button>
19
+ </div>
20
+ <svg class="decay-curve" viewBox="0 0 640 150" aria-hidden="true">
21
+ <path class="decay-curve-ghost" id="decay-curve-ghost" d="M18 26 C 155 32, 236 78, 326 104 S 520 129, 622 137"></path>
22
+ <path class="decay-curve-line" id="decay-curve-line" d="M18 26 C 155 32, 236 78, 326 104 S 520 129, 622 137"></path>
23
+ <circle class="decay-curve-marker" id="decay-curve-marker" cx="169" cy="61" r="8"></circle>
24
+ </svg>
25
+ <svg class="decay-field-art" id="decay-field-art" viewBox="0 0 640 360" role="img" aria-label={ui.atomField}></svg>
26
+ <div class="decay-legend">
27
+ <span><i class="decay-dot decay-dot-live"></i>{ui.liveAtoms}</span>
28
+ <span><i class="decay-dot decay-dot-spent"></i>{ui.decayedAtoms}</span>
29
+ </div>
30
+ </section>
31
+
32
+ <section class="decay-console" aria-label="Radioactive decay controls">
33
+ <div class="decay-select-block">
34
+ <label class="decay-field decay-field--select" for="decay-isotope">
35
+ <span>{ui.isotope}</span>
36
+ <span class="decay-select-shell">
37
+ <select id="decay-isotope"></select>
38
+ </span>
39
+ </label>
40
+
41
+ <div class="decay-use" id="decay-use"></div>
42
+ </div>
43
+
44
+ <label class="decay-field" for="decay-atoms">
45
+ <span>{ui.sampleAtoms}</span>
46
+ <output id="decay-atoms-output">160</output>
47
+ <input id="decay-atoms" type="range" min="40" max="400" step="20" value="160" />
48
+ </label>
49
+
50
+ <label class="decay-field" for="decay-time">
51
+ <span>{ui.elapsedTime}</span>
52
+ <output id="decay-time-output">0</output>
53
+ <input id="decay-time" type="range" min="0" max="4" step="0.01" value="1" />
54
+ </label>
55
+
56
+ <div class="decay-jumps" aria-label="Half-life shortcuts">
57
+ <button type="button" data-half-lives="1">{ui.oneHalfLife}</button>
58
+ <button type="button" data-half-lives="2">{ui.twoHalfLives}</button>
59
+ <button type="button" data-half-lives="4">{ui.fourHalfLives}</button>
60
+ </div>
61
+
62
+ <div class="decay-results">
63
+ <div class="decay-metric">
64
+ <span>{ui.halfLife}</span>
65
+ <strong id="decay-half-life">-</strong>
66
+ </div>
67
+ <div class="decay-metric">
68
+ <span>{ui.remaining}</span>
69
+ <strong id="decay-remaining">-</strong>
70
+ </div>
71
+ <div class="decay-metric">
72
+ <span>{ui.decayed}</span>
73
+ <strong id="decay-decayed">-</strong>
74
+ </div>
75
+ <div class="decay-metric">
76
+ <span>{ui.activity}</span>
77
+ <strong id="decay-activity">-</strong>
78
+ </div>
79
+ </div>
80
+ </section>
81
+ </div>
82
+
83
+ <script>
84
+ import { ISOTOPE_PRESETS, simulateDecay } from './logic';
85
+ import type { IsotopePreset } from './logic';
86
+
87
+ const root = document.getElementById('decay-lab');
88
+ const isotopeSelect = document.getElementById('decay-isotope') as HTMLSelectElement | null;
89
+ const useText = document.getElementById('decay-use');
90
+ const atomsInput = document.getElementById('decay-atoms') as HTMLInputElement | null;
91
+ const atomsOutput = document.getElementById('decay-atoms-output');
92
+ const timeInput = document.getElementById('decay-time') as HTMLInputElement | null;
93
+ const timeOutput = document.getElementById('decay-time-output');
94
+ const atomField = document.getElementById('decay-field-art');
95
+ const halfLifeText = document.getElementById('decay-half-life');
96
+ const remainingText = document.getElementById('decay-remaining');
97
+ const decayedText = document.getElementById('decay-decayed');
98
+ const activityText = document.getElementById('decay-activity');
99
+ const marker = document.getElementById('decay-curve-marker') as SVGCircleElement | null;
100
+ const curveLine = document.getElementById('decay-curve-line') as SVGPathElement | null;
101
+ const curveGhost = document.getElementById('decay-curve-ghost') as SVGPathElement | null;
102
+ const seedButton = document.getElementById('decay-seed-button');
103
+
104
+ let seed = 1487;
105
+ let currentDecayed = new Set<number>();
106
+ const atomNodes = new Map<number, SVGCircleElement>();
107
+ const atomBase = new Map<number, ReturnType<typeof atomPosition>>();
108
+ const decayTimers = new Map<number, number>();
109
+ const svgNamespace = 'http://www.w3.org/2000/svg';
110
+ const curveState = {
111
+ progress: 0.25,
112
+ remainingFraction: 0.5,
113
+ targetProgress: 0.25,
114
+ targetRemainingFraction: 0.5,
115
+ frame: 0,
116
+ };
117
+
118
+ function getPreset(): IsotopePreset {
119
+ return ISOTOPE_PRESETS.find((preset) => preset.id === isotopeSelect?.value) ?? ISOTOPE_PRESETS[0];
120
+ }
121
+
122
+ function formatNumber(value: number): string {
123
+ if (Math.abs(value) >= 1_000_000) {
124
+ return new Intl.NumberFormat('en', {
125
+ notation: 'compact',
126
+ maximumFractionDigits: 2,
127
+ }).format(value);
128
+ }
129
+
130
+ return new Intl.NumberFormat('en', { maximumFractionDigits: 2 }).format(value);
131
+ }
132
+
133
+ function atomPosition(atomId: number) {
134
+ const columnCount = 20;
135
+ const row = Math.floor(atomId / columnCount);
136
+ const column = atomId % columnCount;
137
+ const jitterX = Math.sin((atomId + seed) * 12.9898) * 7;
138
+ const jitterY = Math.cos((atomId + seed) * 78.233) * 7;
139
+
140
+ return {
141
+ x: 30 + column * 30.6 + jitterX,
142
+ y: 30 + row * 25.5 + jitterY,
143
+ radius: 4.8 + Math.abs(Math.sin(atomId * 1.7)) * 3.2,
144
+ };
145
+ }
146
+
147
+ function setAtomBase(circle: SVGCircleElement, atomId: number) {
148
+ const position = atomPosition(atomId);
149
+ atomBase.set(atomId, position);
150
+ circle.setAttribute('cx', position.x.toFixed(2));
151
+ circle.setAttribute('cy', position.y.toFixed(2));
152
+ circle.setAttribute('r', position.radius.toFixed(2));
153
+ }
154
+
155
+ function createAtom(atomId: number) {
156
+ const circle = document.createElementNS(svgNamespace, 'circle');
157
+ circle.classList.add('decay-atom');
158
+ circle.style.setProperty('--delay', `${atomId % 37}ms`);
159
+ setAtomBase(circle, atomId);
160
+ atomField?.append(circle);
161
+ atomNodes.set(atomId, circle);
162
+
163
+ return circle;
164
+ }
165
+
166
+ function setAtomLive(circle: SVGCircleElement, atomId: number) {
167
+ const timer = decayTimers.get(atomId);
168
+ if (timer) {
169
+ window.clearTimeout(timer);
170
+ decayTimers.delete(atomId);
171
+ }
172
+
173
+ circle.classList.remove('decay-atom-decaying', 'decay-atom-spent');
174
+ }
175
+
176
+ function setAtomDecayed(circle: SVGCircleElement, atomId: number, justDecayed: boolean) {
177
+ if (!justDecayed) {
178
+ circle.classList.add('decay-atom-spent');
179
+ circle.classList.remove('decay-atom-decaying');
180
+ return;
181
+ }
182
+
183
+ circle.classList.remove('decay-atom-spent');
184
+ circle.classList.add('decay-atom-decaying');
185
+
186
+ const existingTimer = decayTimers.get(atomId);
187
+ if (existingTimer) window.clearTimeout(existingTimer);
188
+
189
+ const timer = window.setTimeout(() => {
190
+ if (!currentDecayed.has(atomId)) return;
191
+ circle.classList.remove('decay-atom-decaying');
192
+ circle.classList.add('decay-atom-spent');
193
+ decayTimers.delete(atomId);
194
+ }, 600);
195
+ decayTimers.set(atomId, timer);
196
+ }
197
+
198
+ function renderAtoms(result: ReturnType<typeof simulateDecay>) {
199
+ if (!atomField) return;
200
+
201
+ const nextDecayed = new Set(result.atoms.filter((atom) => atom.decayed).map((atom) => atom.id));
202
+ const liveIds = new Set(result.atoms.map((atom) => atom.id));
203
+
204
+ atomNodes.forEach((circle, atomId) => {
205
+ if (liveIds.has(atomId)) return;
206
+ circle.remove();
207
+ atomNodes.delete(atomId);
208
+ atomBase.delete(atomId);
209
+ currentDecayed.delete(atomId);
210
+ });
211
+
212
+ result.atoms.forEach((atom) => {
213
+ const circle = atomNodes.get(atom.id) ?? createAtom(atom.id);
214
+ if (!atomBase.has(atom.id)) setAtomBase(circle, atom.id);
215
+
216
+ if (atom.decayed) {
217
+ setAtomDecayed(circle, atom.id, !currentDecayed.has(atom.id));
218
+ } else {
219
+ setAtomLive(circle, atom.id);
220
+ }
221
+ });
222
+
223
+ currentDecayed = nextDecayed;
224
+ }
225
+
226
+ function curvePath(remainingFraction: number): string {
227
+ const lift = remainingFraction * 18;
228
+ const sag = (1 - remainingFraction) * 16;
229
+
230
+ return `M18 ${26 + sag} C 154 ${30 + lift}, 238 ${77 - lift}, 326 ${104 - lift * 0.35} S 520 ${128 + sag * 0.22}, 622 ${137 - lift * 0.12}`;
231
+ }
232
+
233
+ function drawCurve(progress: number, remainingFraction: number) {
234
+ if (!curveLine || !marker) return;
235
+
236
+ const path = curvePath(remainingFraction);
237
+ curveLine.setAttribute('d', path);
238
+ curveGhost?.setAttribute('d', path);
239
+
240
+ const totalLength = curveLine.getTotalLength();
241
+ const point = curveLine.getPointAtLength(totalLength * progress);
242
+
243
+ curveLine.style.strokeDasharray = `${totalLength}`;
244
+ curveLine.style.strokeDashoffset = `${totalLength * (1 - progress)}`;
245
+ marker.setAttribute('cx', point.x.toFixed(2));
246
+ marker.setAttribute('cy', point.y.toFixed(2));
247
+ marker.style.opacity = `${Math.max(0.42, remainingFraction)}`;
248
+ }
249
+
250
+ function animateCurve() {
251
+ const deltaProgress = curveState.targetProgress - curveState.progress;
252
+ const deltaRemaining = curveState.targetRemainingFraction - curveState.remainingFraction;
253
+ curveState.progress += deltaProgress * 0.18;
254
+ curveState.remainingFraction += deltaRemaining * 0.18;
255
+ drawCurve(curveState.progress, curveState.remainingFraction);
256
+
257
+ if (Math.abs(deltaProgress) > 0.001 || Math.abs(deltaRemaining) > 0.001) {
258
+ curveState.frame = window.requestAnimationFrame(animateCurve);
259
+ return;
260
+ }
261
+
262
+ curveState.progress = curveState.targetProgress;
263
+ curveState.remainingFraction = curveState.targetRemainingFraction;
264
+ drawCurve(curveState.progress, curveState.remainingFraction);
265
+ curveState.frame = 0;
266
+ }
267
+
268
+ function updateCurve(halfLifeCount: number, remainingFraction: number) {
269
+ curveState.targetProgress = Math.min(1, halfLifeCount / 4);
270
+ curveState.targetRemainingFraction = remainingFraction;
271
+
272
+ if (!curveState.frame) {
273
+ curveState.frame = window.requestAnimationFrame(animateCurve);
274
+ }
275
+ }
276
+
277
+ function floatAtoms(time: number) {
278
+ atomNodes.forEach((circle, atomId) => {
279
+ if (circle.classList.contains('decay-atom-spent')) return;
280
+
281
+ const base = atomBase.get(atomId);
282
+ if (!base) return;
283
+
284
+ const driftX = Math.sin(time * 0.0012 + atomId * 1.91) * 1.5;
285
+ const driftY = Math.cos(time * 0.0015 + atomId * 2.37) * 1.25;
286
+ circle.setAttribute('cx', (base.x + driftX).toFixed(2));
287
+ circle.setAttribute('cy', (base.y + driftY).toFixed(2));
288
+ });
289
+
290
+ window.requestAnimationFrame(floatAtoms);
291
+ }
292
+
293
+ function update() {
294
+ if (!root || !isotopeSelect || !atomsInput || !timeInput) return;
295
+
296
+ const preset = getPreset();
297
+ const halfLifeCount = Number(timeInput.value);
298
+ const elapsedTime = halfLifeCount * preset.halfLife;
299
+ const result = simulateDecay({
300
+ initialAtoms: Number(atomsInput.value),
301
+ halfLife: preset.halfLife,
302
+ elapsedTime,
303
+ seed,
304
+ });
305
+
306
+ atomsOutput!.textContent = atomsInput.value;
307
+ timeOutput!.textContent = `${formatNumber(elapsedTime)} ${preset.unit}`;
308
+ useText!.textContent = preset.useCase;
309
+ halfLifeText!.textContent = `${formatNumber(preset.halfLife)} ${preset.unit}`;
310
+ remainingText!.textContent = `${result.remainingAtoms} (${(result.remainingFraction * 100).toFixed(1)}%)`;
311
+ decayedText!.textContent = `${result.decayedAtoms} (${(result.decayedFraction * 100).toFixed(1)}%)`;
312
+ activityText!.textContent = `${(result.activityFraction * 100).toFixed(1)}%`;
313
+
314
+ updateCurve(halfLifeCount, result.remainingFraction);
315
+ renderAtoms(result);
316
+ }
317
+
318
+ ISOTOPE_PRESETS.forEach((preset) => {
319
+ const option = document.createElement('option');
320
+ option.value = preset.id;
321
+ option.textContent = preset.name;
322
+ isotopeSelect?.append(option);
323
+ });
324
+
325
+ isotopeSelect?.addEventListener('change', update);
326
+ atomsInput?.addEventListener('input', update);
327
+ timeInput?.addEventListener('input', update);
328
+ seedButton?.addEventListener('click', () => {
329
+ seed += 9973;
330
+ atomNodes.forEach((circle, atomId) => {
331
+ setAtomBase(circle, atomId);
332
+ });
333
+ update();
334
+ });
335
+
336
+ document.querySelectorAll<HTMLButtonElement>('[data-half-lives]').forEach((button) => {
337
+ button.addEventListener('click', () => {
338
+ if (!timeInput) return;
339
+ timeInput.value = button.dataset.halfLives ?? '1';
340
+ update();
341
+ });
342
+ });
343
+
344
+ update();
345
+ window.requestAnimationFrame(floatAtoms);
346
+ </script>
@@ -0,0 +1,26 @@
1
+ import type { ScienceToolEntry } from '../../types';
2
+
3
+ export const radioactiveDecay: ScienceToolEntry = {
4
+ id: 'radioactive-decay',
5
+ icons: {
6
+ bg: 'mdi:radioactive-circle-outline',
7
+ fg: 'mdi:chart-bell-curve-cumulative',
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,78 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+
4
+ const slug = 'halbwertszeit-rechner-radioaktiver-zerfall';
5
+ const title = 'Rechner fur Halbwertszeit und radioaktiven Zerfall';
6
+ const description = 'Simulieren Sie radioaktiven Zerfall mit realen Isotopen, Halbwertszeit-Formel, stochastischem Atomfeld, Restmenge und relativer Aktivitat.';
7
+
8
+ const howTo = [
9
+ { name: 'Isotop auswahlen', text: 'Beginnen Sie mit Kohlenstoff-14, Iod-131, Uran-238, Technetium-99m oder Radon-222. Jedes Preset ladt eine realistische Halbwertszeit und den typischen wissenschaftlichen Kontext.' },
10
+ { name: 'Probe und Zeit einstellen', text: 'Passen Sie die dargestellte Atomzahl an und verschieben Sie die Zeit, um zu sehen, wie der verbleibende Anteil dem exponentiellen Gesetz der Halbwertszeit folgt.' },
11
+ { name: 'Exakte Rechnung und atomaren Zufall vergleichen', text: 'Nutzen Sie das deterministische Ergebnis als Erwartungswert und betrachten Sie das Atomfeld, um Schwankungen kleiner Proben um die theoretische Kurve zu verstehen.' },
12
+ { name: 'Aktivitat interpretieren', text: 'Die Aktivitat fallt im gleichen Verhaltnis wie die nicht zerfallenen Kerne. Die Anzeige zeigt also, welcher Anteil der ursprunglichen Strahlungsrate noch bleibt.' },
13
+ ];
14
+
15
+ const faq = [
16
+ { question: 'Was bedeutet Halbwertszeit?', answer: 'Die Halbwertszeit ist die Zeit, nach der im Mittel die Halfte der instabilen Kerne einer Probe zerfallen ist. Nach einer Halbwertszeit bleiben 50%, nach zwei 25% und nach drei 12,5%.' },
17
+ { question: 'Warum stimmt das Atomfeld nicht immer exakt mit dem Prozentwert uberein?', answer: 'Radioaktiver Zerfall ist probabilistisch. Die Formel liefert den Erwartungswert fur eine sehr grosse Probe, wahrend das Feld einzelne Atome mit zufalligen Schwellen simuliert. Kleine Proben zeigen naturliche statistische Schwankungen.' },
18
+ { question: 'Entspricht die Aktivitat den verbleibenden Atomen?', answer: 'Bei einem einzelnen Isotop ist die Aktivitat proportional zur Zahl der nicht zerfallenen Kerne. Wenn 30% der Atome ubrig sind, liegt auch die momentane Aktivitat ungefahr bei 30% der Anfangsaktivitat.' },
19
+ { question: 'Kann der Rechner fur Radiokarbondatierung genutzt werden?', answer: 'Ja, als konzeptionelle Rechnung. Die Kohlenstoff-14-Datierung vergleicht die verbleibende Aktivitat mit lebendem Material; echte Laboranalysen korrigieren zusatzlich Kalibrationskurven, Kontamination und Probenaufbereitung.' },
20
+ { question: 'Zerfallt jedes Isotop in genau ein stabiles Produkt?', answer: 'Nicht immer. Manche Isotope zerfallen uber Ketten mit mehreren Tochterprodukten. Dieses Werkzeug modelliert die Halbwertszeit des Mutterisotops, die wichtigste Grosse fur verbleibende Mutterkerne und Aktivitat.' },
21
+ ];
22
+
23
+ export const content: ToolLocaleContent = {
24
+ slug,
25
+ title,
26
+ description,
27
+ ui: {
28
+ isotope: 'Isotop',
29
+ sampleAtoms: 'Atome in der Probe',
30
+ elapsedTime: 'Verstrichene Zeit',
31
+ halfLife: 'Halbwertszeit',
32
+ remaining: 'Verbleibend',
33
+ decayed: 'Zerfallen',
34
+ activity: 'Relative Aktivitat',
35
+ timeUnit: 'Zeiteinheit',
36
+ expectedCurve: 'Erwartete Kurve',
37
+ atomField: 'Atomfeld',
38
+ presetUse: 'Typische Nutzung',
39
+ oneHalfLife: '1 Halbwertszeit',
40
+ twoHalfLives: '2 Halbwertszeiten',
41
+ fourHalfLives: '4 Halbwertszeiten',
42
+ custom: 'Benutzerdefiniert',
43
+ liveAtoms: 'Aktive Atome',
44
+ decayedAtoms: 'Zerfallene Atome',
45
+ resetSeed: 'Neues Atommuster',
46
+ },
47
+ seo: [
48
+ { type: 'title', text: 'Halbwertszeit-Rechner: verbleibende Atome, Aktivitat und Isotop-Beispiele', level: 2 },
49
+ { type: 'paragraph', html: 'Mit diesem Rechner fur radioaktiven Zerfall schatzen Sie, wie viel eines instabilen Isotops nach einer bestimmten Zeit noch vorhanden ist. Er deckt die wichtigsten Suchabsichten ab: die Halbwertszeit-Formel finden, sie auf reale Isotope anwenden, verbleibende Mutterkerne mit zerfallenen Kernen vergleichen und verstehen, warum Aktivitat mit der Zeit abnimmt.' },
50
+ { type: 'paragraph', html: 'Das Werkzeug verbindet zwei Perspektiven. Die Zahlen nutzen die exponentielle Zerfallsgleichung, das Atomfeld simuliert einzelne Kerne mit stochastischen Schwellen. So eignet es sich als schneller Rechner und als visuelle Erklarung dafur, warum Zerfall in grossen Mengen vorhersagbar, beim einzelnen Atom aber zufallig ist.' },
51
+ { type: 'title', text: 'Verwendete Formel fur radioaktiven Zerfall', level: 3 },
52
+ { type: 'paragraph', html: 'Die Formel lautet <strong>N(t) = N0 x (1/2)^(t / T1/2)</strong>. <strong>N0</strong> ist die Anfangszahl der Mutterkerne, <strong>N(t)</strong> die erwartete Restzahl nach der Zeit <strong>t</strong>, und <strong>T1/2</strong> die Halbwertszeit. Der Exponent <strong>t / T1/2</strong> zahlt die vergangenen Halbwertszeiten.' },
53
+ { type: 'paragraph', html: 'Beginnt eine Probe mit 1.000 Mutterkernen und zwei Halbwertszeiten verstreichen, bleiben erwartet 1.000 x (1/2)^2 = 250 Kerne. Zerfallen sind 750 Kerne. Dieselbe Rechnung gilt fur Sekunden, Stunden, Tage, Jahre oder Milliarden Jahre.' },
54
+ { type: 'table', headers: ['Verstrichene Zeit', 'Formelfaktor', 'Verbleibende Mutterkerne', 'Relative Aktivitat'], rows: [['0 Halbwertszeiten', '(1/2)^0', '100%', '100%'], ['1 Halbwertszeit', '(1/2)^1', '50%', '50%'], ['2 Halbwertszeiten', '(1/2)^2', '25%', '25%'], ['3 Halbwertszeiten', '(1/2)^3', '12,5%', '12,5%'], ['5 Halbwertszeiten', '(1/2)^5', '3,125%', '3,125%'], ['10 Halbwertszeiten', '(1/2)^10', '0,098%', '0,098%']] },
55
+ { type: 'title', text: 'Restaktivitat berechnen', level: 3 },
56
+ { type: 'paragraph', html: 'Bei einem einzelnen Mutterisotop ist die Aktivitat proportional zur Zahl der nicht zerfallenen Kerne. Bleiben 25% des Mutterisotops, liegt auch die Aktivitat dieses Isotops bei etwa 25% der Anfangsaktivitat.' },
57
+ { type: 'paragraph', html: 'Das ist wichtig in Nuklearmedizin und Strahlenschutz. Technetium-99m verliert Aktivitat innerhalb von Stunden, Iod-131 bleibt uber Tage relevant und beeinflusst Therapieplanung, Kontaminationsuberwachung und Expositionshinweise.' },
58
+ { type: 'title', text: 'Beispiele: Kohlenstoff-14, Iod-131, Technetium-99m, Uran-238 und Radon-222', level: 3 },
59
+ { type: 'table', headers: ['Isotop', 'Ungefaire Halbwertszeit', 'Typischer Suchkontext', 'Was das Ergebnis zeigt'], rows: [['Kohlenstoff-14', '5.730 Jahre', 'Radiokarbondatierung', 'Wie viel Aktivitat des Mutterisotops Kohlenstoff-14 in ehemals lebendem Material verbleibt.'], ['Iod-131', '8,02 Tage', 'Medizinische Therapie und nukleare Ereignisse', 'Wie schnell Aktivitat uber Tage nach Freisetzung oder Behandlung sinkt.'], ['Technetium-99m', '6,01 Stunden', 'Diagnostische Bildgebung', 'Warum nutzbare medizinische Aktivitat innerhalb eines klinischen Tages abnimmt.'], ['Uran-238', '4,47 Milliarden Jahre', 'Geologische Datierung', 'Warum langlebige Isotope uber Erdgeschichte messbar bleiben.'], ['Radon-222', '3,82 Tage', 'Innenraumstrahlung und Zerfallsketten', 'Wie sich eine gasformige Expositionsquelle uber Tage verandert.']] },
60
+ { type: 'paragraph', html: 'Diese Beispiele decken verschiedene Zeitskalen und Suchintentionen ab: Archaologie fur Kohlenstoff-14, medizinische Aktivitat fur Iod-131 und Technetium-99m, Umweltbelastung fur Radon-222 und geologische Zeit fur Uran-238.' },
61
+ { type: 'title', text: 'Die stochastische Atomsimulation lesen', level: 3 },
62
+ { type: 'paragraph', html: 'Das animierte Atomfeld ist bewusst stochastisch. Die Gleichung liefert den Erwartungswert fur grosse Proben, aber einzelne Kerne zerfallen zufallig. Kleine Proben konnen nach einer Halbwertszeit etwas uber oder unter 50% liegen; grossere Proben nahern sich der theoretischen Kurve.' },
63
+ { type: 'paragraph', html: 'Halbwertszeit bedeutet nicht, dass jedes Atom auf einen Timer wartet und dann genau die Halfte verschwindet. Jeder instabile Kern hat pro Zeiteinheit eine konstante Zerfallswahrscheinlichkeit. Die glatte Kurve entsteht erst aus vielen unabhangigen Zufallsereignissen.' },
64
+ { type: 'title', text: 'Anwendungsfalle des Rechners', level: 3 },
65
+ { type: 'list', items: ['<strong>Physikunterricht:</strong> verbleibende Mutterkerne nach einer Anzahl von Halbwertszeiten berechnen.', '<strong>Chemie und Kernwissenschaft:</strong> Stabilitat, Zerfallsgeschwindigkeit und relative Aktivitat vergleichen.', '<strong>Radiokarbon-Verstandnis:</strong> verstehen, warum alte Proben weniger Kohlenstoff-14 enthalten.', '<strong>Planung medizinischer Isotope:</strong> sehen, warum kurze Halbwertszeiten fur Bildgebung nutzlich sind.', '<strong>Strahlenschutzbildung:</strong> Aktivitatsschatzungen ohne Verwechslung von Halbwertszeit und sofortigem Verschwinden.'] },
66
+ { type: 'title', text: 'Wichtige Grenzen', level: 3 },
67
+ { type: 'paragraph', html: 'Dieser Rechner modelliert das Mutterisotop mit einer einzigen Halbwertszeit. Reale Messungen konnen Korrekturen fur Detektoreffizienz, Hintergrundstrahlung, Verzweigungsverhaltnisse, Tochterprodukte, biologische Ausscheidung, chemische Form, Abschirmung und Kalibrationskurven erfordern.' },
68
+ { type: 'paragraph', html: 'Nutzen Sie das Ergebnis als klare wissenschaftliche Schatzung und Lernmodell, nicht als Ersatz fur Strahlenschutzberatung, medizinische Anweisungen, Dosimetrie oder Labor-Datierung.' },
69
+ ],
70
+ faq,
71
+ bibliography,
72
+ howTo,
73
+ schemas: [
74
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'ScientificApplication', operatingSystem: 'Any' },
75
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) },
76
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: title, step: howTo.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })) },
77
+ ],
78
+ };