@jjlmoya/utils-science 1.23.0 → 1.25.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 (37) 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 -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/cosmic-inflation/component.astro +6 -4
  8. package/src/tool/lorenz-attractor/i18n/es.ts +12 -4
  9. package/src/tool/lorenz-attractor/lorenz-attractor.css +56 -25
  10. package/src/tool/stellar-habitability-zone/bibliography.astro +14 -0
  11. package/src/tool/stellar-habitability-zone/bibliography.ts +12 -0
  12. package/src/tool/stellar-habitability-zone/component.astro +123 -0
  13. package/src/tool/stellar-habitability-zone/dom-updater.ts +94 -0
  14. package/src/tool/stellar-habitability-zone/entry.ts +26 -0
  15. package/src/tool/stellar-habitability-zone/i18n/de.ts +189 -0
  16. package/src/tool/stellar-habitability-zone/i18n/en.ts +189 -0
  17. package/src/tool/stellar-habitability-zone/i18n/es.ts +189 -0
  18. package/src/tool/stellar-habitability-zone/i18n/fr.ts +189 -0
  19. package/src/tool/stellar-habitability-zone/i18n/id.ts +189 -0
  20. package/src/tool/stellar-habitability-zone/i18n/it.ts +189 -0
  21. package/src/tool/stellar-habitability-zone/i18n/ja.ts +189 -0
  22. package/src/tool/stellar-habitability-zone/i18n/ko.ts +189 -0
  23. package/src/tool/stellar-habitability-zone/i18n/nl.ts +189 -0
  24. package/src/tool/stellar-habitability-zone/i18n/pl.ts +189 -0
  25. package/src/tool/stellar-habitability-zone/i18n/pt.ts +189 -0
  26. package/src/tool/stellar-habitability-zone/i18n/ru.ts +189 -0
  27. package/src/tool/stellar-habitability-zone/i18n/sv.ts +189 -0
  28. package/src/tool/stellar-habitability-zone/i18n/tr.ts +189 -0
  29. package/src/tool/stellar-habitability-zone/i18n/zh.ts +189 -0
  30. package/src/tool/stellar-habitability-zone/index.ts +11 -0
  31. package/src/tool/stellar-habitability-zone/interaction.ts +45 -0
  32. package/src/tool/stellar-habitability-zone/logic/StellarHabitabilityEngine.ts +158 -0
  33. package/src/tool/stellar-habitability-zone/renderer.ts +241 -0
  34. package/src/tool/stellar-habitability-zone/script.ts +273 -0
  35. package/src/tool/stellar-habitability-zone/seo.astro +15 -0
  36. package/src/tool/stellar-habitability-zone/stellar-habitability-zone.css +375 -0
  37. package/src/tools.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-science",
3
- "version": "1.23.0",
3
+ "version": "1.25.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -7,10 +7,11 @@ import { cellularRenewal } from '../tool/cellular-renewal/index';
7
7
  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
+ import { stellarHabitabilityZone } from '../tool/stellar-habitability-zone/index';
10
11
 
11
12
  export const scienceCategory: ScienceCategoryEntry = {
12
13
  icon: 'mdi:flask',
13
- tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal, cosmicInflation, temperatureTimeline, lorenzAttractor],
14
+ tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone],
14
15
  i18n: {
15
16
  es: () => import('./i18n/es').then((m) => m.content),
16
17
  en: () => import('./i18n/en').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -7,6 +7,7 @@ export { simulationProbability } from './tool/simulation-probability/entry';
7
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
+ export { stellarHabitabilityZone } from './tool/stellar-habitability-zone/entry';
10
11
  export { scienceCategory } from './category';
11
12
  import { asteroidImpact } from './tool/asteroid-impact/entry';
12
13
  import { cellularRenewal } from './tool/cellular-renewal/entry';
@@ -16,4 +17,5 @@ import { simulationProbability } from './tool/simulation-probability/entry';
16
17
  import { cosmicInflation } from './tool/cosmic-inflation/entry';
17
18
  import { temperatureTimeline } from './tool/temperature-timeline/entry';
18
19
  import { lorenzAttractor } from './tool/lorenz-attractor/entry';
19
- export const ALL_ENTRIES = [asteroidImpact, cellularRenewal, colonyCounter, microwaveDetector, simulationProbability, cosmicInflation, temperatureTimeline, lorenzAttractor];
20
+ import { stellarHabitabilityZone } from './tool/stellar-habitability-zone/entry';
21
+ export const ALL_ENTRIES = [asteroidImpact, cellularRenewal, colonyCounter, microwaveDetector, simulationProbability, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone];
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export { CELLULAR_RENEWAL_TOOL } from './tool/cellular-renewal/index';
8
8
  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
+ export { STELLAR_HABITABILITY_ZONE_TOOL } from './tool/stellar-habitability-zone/index';
11
12
 
12
13
  export type {
13
14
  KnownLocale,
@@ -18,8 +18,8 @@ describe('Locale Completeness Validation', () => {
18
18
  });
19
19
  });
20
20
 
21
- it('all 8 tools registered', () => {
22
- expect(ALL_TOOLS.length).toBe(8);
21
+ it('all 9 tools registered', () => {
22
+ expect(ALL_TOOLS.length).toBe(9);
23
23
  });
24
24
  });
25
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 8 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(8);
7
+ it('should have 9 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(9);
9
9
  });
10
10
 
11
11
  it('scienceCategory should be defined', () => {
@@ -140,6 +140,7 @@ const { ui } = Astro.props;
140
140
  isExtreme: boolean;
141
141
  efolds: number;
142
142
  gridColor: string;
143
+ isDark: boolean;
143
144
  }
144
145
 
145
146
  function drawVerticalGridLines(c: DrawingContext) {
@@ -205,7 +206,7 @@ const { ui } = Astro.props;
205
206
  for (let p = 1; p <= pulseCount; p++) {
206
207
  const radius = ((p * 40 + (c.efolds * 2)) % 150) + 10;
207
208
  c.ctx.beginPath();
208
- c.ctx.strokeStyle = c.gridColor + (1 - radius / 160) * 0.3 + ')';
209
+ c.ctx.strokeStyle = c.gridColor + (1 - radius / 160) * (c.isDark ? 0.3 : 0.7) + ')';
209
210
  c.ctx.lineWidth = 1.5;
210
211
  c.ctx.arc(c.centerX, c.centerY, radius, 0, Math.PI * 2);
211
212
  c.ctx.stroke();
@@ -224,15 +225,16 @@ const { ui } = Astro.props;
224
225
  const h = canvas.height / dpr;
225
226
  ctx.clearRect(0, 0, w, h);
226
227
  const isDark = document.body.classList.contains('theme-dark') || document.documentElement.classList.contains('theme-dark');
227
- ctx.strokeStyle = isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.05)';
228
- ctx.lineWidth = 0.5;
228
+ ctx.strokeStyle = isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(15, 23, 42, 0.45)';
229
+ ctx.lineWidth = 1.0;
229
230
  const drawingCtx: DrawingContext = {
230
231
  ctx, w, h,
231
232
  centerX: w / 2, centerY: h / 2,
232
233
  scale: 1 + (efolds - 10) / 10,
233
234
  isExtreme: efolds > 80,
234
235
  efolds,
235
- gridColor: isDark ? 'rgba(244, 114, 182, ' : 'rgba(236, 72, 153, '
236
+ gridColor: isDark ? 'rgba(244, 114, 182, ' : 'rgba(236, 72, 153, ',
237
+ isDark
236
238
  };
237
239
  drawVerticalGridLines(drawingCtx);
238
240
  drawHorizontalGridLines(drawingCtx);
@@ -33,6 +33,14 @@ const faq = [
33
33
  {
34
34
  "question": "Que representan Sigma, Rho y Beta?",
35
35
  "answer": "Sigma es el numero de Prandtl, Rho es el numero de Rayleigh y Beta es la relacion de aspecto geometrico del sistema."
36
+ },
37
+ {
38
+ "question": "Por que la forma del atractor parece una mariposa?",
39
+ "answer": "La iconica forma de alas dobles de mariposa surge porque el sistema tiene dos puntos de equilibrio inestables. La trayectoria orbita alrededor de un ala y luego transita de forma impredecible para orbitar la otra, creando la estructura distintiva en el espacio de fases tridimensional."
40
+ },
41
+ {
42
+ "question": "Es el Atractor de Lorenz verdaderamente aleatorio?",
43
+ "answer": "No. El sistema de Lorenz es completamente deterministico, lo que significa que su estado futuro esta totalmente definido por su estado actual y sus ecuaciones. Sin embargo, como es caotico, es completamente impredecible a largo plazo sin una precision infinita de las condiciones iniciales."
36
44
  }
37
45
  ];
38
46
 
@@ -56,10 +64,10 @@ export const content: ToolLocaleContent = {
56
64
  "trajectories": "Trayectorias",
57
65
  "distance": "Distancia de Divergencia",
58
66
  "exponentialGrowth": "Divergencia Exponencial",
59
- "resetDefault": "Reset",
60
- "clearPath": "Clear",
61
- "play": "Resume",
62
- "pause": "Pause",
67
+ "resetDefault": "Reiniciar",
68
+ "clearPath": "Limpiar",
69
+ "play": "Reanudar",
70
+ "pause": "Pausar",
63
71
  "coords": "Coordenadas",
64
72
  "divergenceExplanation": "El grafico de divergencia muestra la distancia euclidiana entre las dos trayectorias a lo largo del tiempo. Observa como aumenta exponencialmente."
65
73
  },
@@ -11,7 +11,6 @@
11
11
  --lorenz-text-muted: #64748b;
12
12
  --lorenz-slider-track: #cbd5e1;
13
13
  --lorenz-shadow-color: rgba(0, 0, 0, 0.04);
14
- --lorenz-font-mono: consolas, monaco, 'Courier New', monospace;
15
14
  }
16
15
 
17
16
  .theme-dark {
@@ -33,7 +32,7 @@
33
32
  width: 100%;
34
33
  max-width: 1200px;
35
34
  margin: 0 auto;
36
- font: 14px Outfit, Inter, sans-serif;
35
+ font-size: 14px;
37
36
  color: var(--lorenz-text);
38
37
  box-sizing: border-box;
39
38
  }
@@ -60,13 +59,12 @@
60
59
  @media (min-width: 992px) {
61
60
  .lorenz-grid {
62
61
  grid-template-columns: 1.5fr 1fr;
63
- align-items: stretch;
62
+ align-items: start;
64
63
  }
65
64
 
66
65
  .lorenz-canvas-container {
67
- aspect-ratio: auto;
66
+ aspect-ratio: 4 / 3;
68
67
  min-height: 520px;
69
- height: 100%;
70
68
  }
71
69
 
72
70
  .lorenz-controls-section {
@@ -160,12 +158,13 @@
160
158
  }
161
159
 
162
160
  .lorenz-slider-label {
163
- font: 700 12px Outfit, Inter, sans-serif;
161
+ font-weight: 700;
162
+ font-size: 12px;
164
163
  }
165
164
 
166
165
  .lorenz-slider-val {
167
166
  color: var(--lorenz-primary);
168
- font: 13px consolas, monaco, monospace;
167
+ font-size: 13px;
169
168
  font-variant-numeric: tabular-nums;
170
169
  }
171
170
 
@@ -261,12 +260,13 @@
261
260
  }
262
261
 
263
262
  .lorenz-coord-item {
264
- font: 12px Outfit, Inter, sans-serif;
263
+ font-size: 12px;
265
264
  color: var(--lorenz-text-muted);
266
265
  }
267
266
 
268
267
  .lorenz-coord-val {
269
- font: 600 14px consolas, monaco, monospace;
268
+ font-weight: 600;
269
+ font-size: 14px;
270
270
  font-variant-numeric: tabular-nums;
271
271
  }
272
272
 
@@ -279,7 +279,8 @@
279
279
  }
280
280
 
281
281
  .lorenz-divergence-title {
282
- font: 700 12px Outfit, Inter, sans-serif;
282
+ font-weight: 700;
283
+ font-size: 12px;
283
284
  text-transform: uppercase;
284
285
  letter-spacing: 0.05em;
285
286
  color: var(--lorenz-text-muted);
@@ -292,7 +293,8 @@
292
293
  }
293
294
 
294
295
  .lorenz-divergence-num {
295
- font: 750 2.8rem consolas, monaco, monospace;
296
+ font-weight: 750;
297
+ font-size: 2.8rem;
296
298
  line-height: 1;
297
299
  color: var(--lorenz-accent);
298
300
  font-variant-numeric: tabular-nums;
@@ -300,7 +302,8 @@
300
302
  }
301
303
 
302
304
  .lorenz-divergence-delta {
303
- font: 700 14px consolas, monaco, monospace;
305
+ font-weight: 700;
306
+ font-size: 14px;
304
307
  color: var(--lorenz-text-muted);
305
308
  font-variant-numeric: tabular-nums;
306
309
  }
@@ -316,7 +319,8 @@
316
319
  .lorenz-chart-header {
317
320
  display: flex;
318
321
  align-items: center;
319
- font: 700 12px Outfit, Inter, sans-serif;
322
+ font-weight: 700;
323
+ font-size: 12px;
320
324
  text-transform: uppercase;
321
325
  letter-spacing: 0.05em;
322
326
  color: var(--lorenz-text-muted);
@@ -337,7 +341,8 @@
337
341
  height: 14px;
338
342
  border-radius: 50%;
339
343
  border: 1px solid var(--lorenz-text-muted);
340
- font: bold 9px consolas, monaco, monospace;
344
+ font-weight: bold;
345
+ font-size: 9px;
341
346
  color: var(--lorenz-text-muted);
342
347
  }
343
348
 
@@ -352,7 +357,7 @@
352
357
  text-align: center;
353
358
  padding: 8px 12px;
354
359
  border-radius: 8px;
355
- font: 12px Outfit, Inter, sans-serif;
360
+ font-size: 12px;
356
361
  line-height: 1.3;
357
362
  width: 200px;
358
363
  z-index: 10;
@@ -400,32 +405,39 @@
400
405
  display: flex;
401
406
  flex-direction: column;
402
407
  height: 100%;
403
- gap: 16px;
408
+ gap: 12px;
409
+ overflow: hidden;
404
410
  }
405
411
 
406
412
  .lorenz-canvas-container {
407
- height: 43%;
413
+ height: 28%;
408
414
  flex-shrink: 0;
409
415
  aspect-ratio: auto;
416
+ min-height: 0;
410
417
  }
411
418
 
412
419
  .lorenz-controls-section {
413
- height: 57%;
414
- overflow-y: auto;
420
+ flex: 1;
415
421
  display: flex;
416
422
  flex-direction: column;
417
- gap: 20px;
423
+ gap: 6px;
424
+ overflow: hidden;
418
425
  padding-right: 4px;
426
+ min-height: 0;
419
427
  }
420
428
 
421
429
  .lorenz-slider-group {
422
- padding-left: 24px;
423
- padding-right: 24px;
430
+ padding-left: 12px;
431
+ padding-right: 12px;
432
+ }
433
+
434
+ .lorenz-slider {
435
+ height: 28px;
424
436
  }
425
437
 
426
438
  .lorenz-button-row {
427
439
  grid-template-columns: 2fr 1fr 1fr;
428
- gap: 12px;
440
+ gap: 6px;
429
441
  order: 4;
430
442
  }
431
443
 
@@ -433,21 +445,40 @@
433
445
  order: 1;
434
446
  border-top: none;
435
447
  padding-top: 0;
448
+ gap: 4px;
436
449
  }
437
450
 
438
451
  .lorenz-chart-block {
439
452
  order: 2;
440
453
  border-top: none;
441
454
  padding-top: 0;
455
+ gap: 4px;
442
456
  }
443
457
 
444
458
  .lorenz-chart-canvas-wrapper {
445
- height: 56px;
459
+ height: 40px;
446
460
  }
447
461
 
448
462
  .lorenz-sliders-block {
449
463
  order: 3;
464
+ display: grid;
465
+ grid-template-columns: 1fr 1fr;
466
+ gap: 4px;
450
467
  border-top: 1px solid var(--lorenz-border);
451
- padding-top: 16px;
468
+ padding-top: 8px;
469
+ }
470
+
471
+ .lorenz-slider-group:nth-child(5) {
472
+ grid-column: 1 / -1;
473
+ }
474
+
475
+ .lorenz-divergence-display {
476
+ margin-top: 6px;
477
+ margin-bottom: 6px;
478
+ gap: 4px;
479
+ }
480
+
481
+ .lorenz-divergence-num {
482
+ font-size: 1.5rem;
452
483
  }
453
484
  }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { stellarHabitabilityZone } 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 stellarHabitabilityZone.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Habitable Zones Around Main-Sequence Stars: New Estimates',
6
+ url: 'https://arxiv.org/abs/1301.6674',
7
+ },
8
+ {
9
+ name: 'Habitable Zones Around Main-Sequence Stars: Dependence on Planetary Mass',
10
+ url: 'https://iopscience.iop.org/article/10.1088/2041-8205/787/2/L29',
11
+ },
12
+ ];
@@ -0,0 +1,123 @@
1
+ ---
2
+ import './stellar-habitability-zone.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="hz-calculator-root" id="hz-simulator-root">
12
+ <div class="hz-presets-container">
13
+ <div class="hz-presets">
14
+ <button class="hz-preset-btn active" data-type="G">G-Type (Sun-like)</button>
15
+ <button class="hz-preset-btn" data-type="M">M-Type (Red Dwarf)</button>
16
+ <button class="hz-preset-btn" data-type="K">K-Type (Orange Dwarf)</button>
17
+ <button class="hz-preset-btn" data-type="F">F-Type (Procyon-like)</button>
18
+ <button class="hz-preset-btn" data-type="A">A-Type (Sirius-like)</button>
19
+ <button class="hz-preset-btn" data-type="B">B-Type (Blue Giant)</button>
20
+ <button class="hz-preset-btn" data-type="O">O-Type (Blue Hypergiant)</button>
21
+ <div style="flex-grow: 1;"></div>
22
+ <button class="hz-preset-btn" id="hz-unit-toggle" style="opacity: 0.8; border-bottom: none;"></button>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="hz-controls-section">
27
+ <div class="hz-input-group">
28
+ <label for="hz-temperature">{ui.starTemperature}</label>
29
+ <input type="range" id="hz-temperature" class="hz-slider" min="2000" max="50000" value="5778" step="100" />
30
+ <span id="hz-temperature-val" class="hz-input-val">5,778</span>
31
+ </div>
32
+
33
+ <div class="hz-input-group">
34
+ <label for="hz-luminosity">{ui.starLuminosity}</label>
35
+ <input type="range" id="hz-luminosity" class="hz-slider" min="-4" max="6" value="0" step="0.1" />
36
+ <span id="hz-luminosity-val" class="hz-input-val">1.0</span>
37
+ </div>
38
+
39
+ <div class="hz-input-group">
40
+ <label for="hz-mass">{ui.starMass}</label>
41
+ <input type="range" id="hz-mass" class="hz-slider" min="0.05" max="100" value="1.0" step="0.05" />
42
+ <span id="hz-mass-val" class="hz-input-val">1.0</span>
43
+ </div>
44
+
45
+ <div class="hz-input-group">
46
+ <label for="hz-radius">{ui.starRadius}</label>
47
+ <input type="range" id="hz-radius" class="hz-slider" min="0.05" max="30" value="1.0" step="0.05" />
48
+ <span id="hz-radius-val" class="hz-input-val">1.0</span>
49
+ </div>
50
+
51
+ <div class="hz-input-group">
52
+ <label for="hz-distance">{ui.planetDistance}</label>
53
+ <input type="range" id="hz-distance" class="hz-slider" min="-2" max="3" value="0" step="0.01" />
54
+ <span id="hz-distance-val" class="hz-input-val">1.00</span>
55
+ </div>
56
+
57
+ <div class="hz-input-group">
58
+ <label for="hz-albedo">{ui.planetAlbedo}</label>
59
+ <input type="range" id="hz-albedo" class="hz-slider" min="0.00" max="0.95" value="0.30" step="0.01" />
60
+ <span id="hz-albedo-val" class="hz-input-val">0.30</span>
61
+ </div>
62
+
63
+ <div class="hz-input-group">
64
+ <label for="hz-greenhouse">{ui.greenhouseDelta}</label>
65
+ <input type="range" id="hz-greenhouse" class="hz-slider" min="0" max="500" value="33" step="1" />
66
+ <span id="hz-greenhouse-val" class="hz-input-val">33</span>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="hz-viewport-container">
71
+ <canvas id="hz-orbit-canvas"></canvas>
72
+ <div class="hz-canvas-section">
73
+ <h3>{ui.orbitCanvasTitle}</h3>
74
+ <div class="hz-canvas-container" id="hz-canvas-placeholder">
75
+ </div>
76
+ </div>
77
+
78
+ <div class="hz-results-section">
79
+ <div class="hz-result-grid">
80
+ <div class="hz-result-block">
81
+ <span class="hz-result-label">{ui.equilibriumTemperature}</span>
82
+ <span id="hz-eq-temp" class="hz-result-number">---</span>
83
+ </div>
84
+ <div class="hz-result-block">
85
+ <span class="hz-result-label">{ui.estimatedSurfaceTemp || ui.surfTempResult}</span>
86
+ <span id="hz-surf-temp" class="hz-result-number">---</span>
87
+ </div>
88
+ <div class="hz-result-block">
89
+ <span class="hz-result-label">{ui.stellarFluxResult}</span>
90
+ <span id="hz-stellar-flux" class="hz-result-number">---</span>
91
+ </div>
92
+ <div class="hz-result-block">
93
+ <span class="hz-result-label">{ui.orbitPeriodResult}</span>
94
+ <span id="hz-orbit-period" class="hz-result-number">---</span>
95
+ </div>
96
+ <div class="hz-result-block">
97
+ <span class="hz-result-label">{ui.orbitVelocityResult}</span>
98
+ <span id="hz-orbit-velocity" class="hz-result-number">---</span>
99
+ </div>
100
+ <div class="hz-result-block">
101
+ <span class="hz-result-label">{ui.innerLimit}</span>
102
+ <span id="hz-inner-limit" class="hz-result-number">---</span>
103
+ </div>
104
+ <div class="hz-result-block">
105
+ <span class="hz-result-label">{ui.outerLimit}</span>
106
+ <span id="hz-outer-limit" class="hz-result-number">---</span>
107
+ </div>
108
+ </div>
109
+
110
+ <div>
111
+ <div class="hz-status-box habitable" id="hz-status-box">
112
+ <div class="hz-status-title" id="hz-status-title">---</div>
113
+ <div class="hz-status-desc">{ui.statusExplanation}</div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <script>
121
+ import { initStellarSimulator } from './script';
122
+ initStellarSimulator();
123
+ </script>
@@ -0,0 +1,94 @@
1
+ import type { SimulationResult } from './logic/StellarHabitabilityEngine';
2
+
3
+ export interface UpdateValTextsParams {
4
+ temp: number;
5
+ luminosity: number;
6
+ mass: number;
7
+ radius: number;
8
+ distanceAu: number;
9
+ albedo: number;
10
+ greenhouse: number;
11
+ isImperial: boolean;
12
+ tempVal: HTMLElement | null;
13
+ lumVal: HTMLElement | null;
14
+ massVal: HTMLElement | null;
15
+ radVal: HTMLElement | null;
16
+ distVal: HTMLElement | null;
17
+ albedoVal: HTMLElement | null;
18
+ greenVal: HTMLElement | null;
19
+ }
20
+
21
+ export interface UpdateResultsParams {
22
+ result: SimulationResult;
23
+ isImperial: boolean;
24
+ eqTempResult: HTMLElement | null;
25
+ surfTempResult: HTMLElement | null;
26
+ fluxResult: HTMLElement | null;
27
+ periodResult: HTMLElement | null;
28
+ velocityResult: HTMLElement | null;
29
+ innerResult: HTMLElement | null;
30
+ outerResult: HTMLElement | null;
31
+ }
32
+
33
+ function setText(el: HTMLElement | null, text: string) {
34
+ if (el) {
35
+ el.textContent = text;
36
+ }
37
+ }
38
+
39
+ export function updateSliderFill(slider: HTMLInputElement | null) {
40
+ if (!slider) return;
41
+ const min = parseFloat(slider.min) || 0;
42
+ const max = parseFloat(slider.max) || 100;
43
+ const val = parseFloat(slider.value) || 0;
44
+ const percentage = ((val - min) / (max - min)) * 100;
45
+ slider.style.setProperty('--percent', `${percentage}%`);
46
+ slider.style.background = `linear-gradient(to right, var(--hz-slider-active) 0%, var(--hz-slider-active) ${percentage}%, var(--hz-slider-track) ${percentage}%, var(--hz-slider-track) 100%)`;
47
+ }
48
+
49
+ export function updateValTexts(p: UpdateValTextsParams) {
50
+ setText(p.tempVal, p.temp.toLocaleString());
51
+ setText(p.lumVal, p.luminosity >= 100 || p.luminosity <= 0.01 ? p.luminosity.toExponential(2) : p.luminosity.toFixed(2));
52
+ setText(p.massVal, p.mass.toFixed(2));
53
+ setText(p.radVal, p.radius.toFixed(2));
54
+ const dist = p.isImperial ? `${(p.distanceAu * 92.9558).toFixed(1)} Mmi` : `${p.distanceAu.toFixed(2)} AU`;
55
+ setText(p.distVal, dist);
56
+ setText(p.albedoVal, p.albedo.toFixed(2));
57
+ setText(p.greenVal, p.greenhouse.toString());
58
+ }
59
+
60
+ export function updateResults(p: UpdateResultsParams) {
61
+ const isImp = p.isImperial;
62
+ const res = p.result;
63
+ const eq = isImp
64
+ ? `${Math.round((res.equilibriumTemperature - 273.15) * 1.8 + 32)}°F`
65
+ : `${Math.round(res.equilibriumTemperature - 273.15)}°C (${Math.round(res.equilibriumTemperature)}K)`;
66
+ setText(p.eqTempResult, eq);
67
+
68
+ const surf = isImp
69
+ ? `${Math.round((res.surfaceTemperature - 273.15) * 1.8 + 32)}°F`
70
+ : `${Math.round(res.surfaceTemperature - 273.15)}°C (${Math.round(res.surfaceTemperature)}K)`;
71
+ setText(p.surfTempResult, surf);
72
+
73
+ setText(p.fluxResult, `${res.stellarFlux.toFixed(2)} S⊕`);
74
+
75
+ const period = res.orbitalPeriod >= 365
76
+ ? `${(res.orbitalPeriod / 365.255).toFixed(1)} yr`
77
+ : `${Math.round(res.orbitalPeriod)} d`;
78
+ setText(p.periodResult, period);
79
+
80
+ const vel = isImp
81
+ ? `${Math.round(res.orbitalVelocity * 2236.936).toLocaleString()} mph`
82
+ : `${res.orbitalVelocity.toFixed(1)} km/s`;
83
+ setText(p.velocityResult, vel);
84
+
85
+ const inner = isImp
86
+ ? `${(res.hzLimits.runawayGreenhouse * 92.9558).toFixed(1)} Mmi`
87
+ : `${res.hzLimits.runawayGreenhouse.toFixed(2)} AU`;
88
+ setText(p.innerResult, inner);
89
+
90
+ const outer = isImp
91
+ ? `${(res.hzLimits.maximumGreenhouse * 92.9558).toFixed(1)} Mmi`
92
+ : `${res.hzLimits.maximumGreenhouse.toFixed(2)} AU`;
93
+ setText(p.outerResult, outer);
94
+ }
@@ -0,0 +1,26 @@
1
+ import type { ScienceToolEntry } from '../../types';
2
+
3
+ export const stellarHabitabilityZone: ScienceToolEntry = {
4
+ id: 'stellar-habitability-zone',
5
+ icons: {
6
+ bg: 'mdi:star-circle-outline',
7
+ fg: 'mdi:orbit',
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
+ };