@jjlmoya/utils-science 1.1.0 → 1.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-science",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -29,11 +29,18 @@
29
29
  }
30
30
 
31
31
  .theme-dark {
32
+ --asteroid-primary: #ff9f43;
33
+ --asteroid-secondary: #ff6b5b;
34
+ --asteroid-accent: #94a3b8;
35
+ --asteroid-success: #2ecc71;
36
+ --asteroid-warning: #ffa502;
37
+ --asteroid-danger: #ff4757;
32
38
  --asteroid-bg-light: #0f172a;
33
- --asteroid-bg-card: #1e293b;
34
- --asteroid-bg-modal: rgba(30, 41, 59, 0.9);
35
- --asteroid-border-light: #334155;
36
- --asteroid-text-primary: #f1f5f9;
39
+ --asteroid-bg-card: #1a2332;
40
+ --asteroid-bg-modal: rgba(26, 35, 50, 0.95);
41
+ --asteroid-border-light: #2d3748;
42
+ --asteroid-border-dark: #0f172a;
43
+ --asteroid-text-primary: #f8f9fa;
37
44
  --asteroid-text-secondary: #cbd5e1;
38
45
  }
39
46
 
@@ -133,6 +140,11 @@
133
140
  transition: var(--asteroid-transition);
134
141
  }
135
142
 
143
+ .theme-dark .asteroid-gps-btn {
144
+ background: rgba(30, 41, 59, 0.95);
145
+ border: 1px solid rgba(100, 116, 139, 0.4);
146
+ }
147
+
136
148
  .asteroid-gps-btn:hover {
137
149
  transform: scale(1.05);
138
150
  }
@@ -151,8 +163,15 @@
151
163
  }
152
164
 
153
165
  @keyframes pulse {
154
- 0%, 100% { opacity: 1; }
155
- 50% { opacity: 0.5; }
166
+
167
+ 0%,
168
+ 100% {
169
+ opacity: 1;
170
+ }
171
+
172
+ 50% {
173
+ opacity: 0.5;
174
+ }
156
175
  }
157
176
 
158
177
  /* Píldora de Veredicto */
@@ -172,7 +191,7 @@
172
191
 
173
192
  .asteroid-verdict-container {
174
193
  background: var(--asteroid-bg-dark);
175
- color: white;
194
+ color: var(--asteroid-text-primary);
176
195
  padding: 0.75rem 1.25rem;
177
196
  border-radius: var(--asteroid-radius-lg);
178
197
  box-shadow: var(--asteroid-shadow-xl);
@@ -251,9 +270,9 @@
251
270
  }
252
271
 
253
272
  .asteroid-lab-panel {
254
- background: rgba(255, 255, 255, 0.9);
273
+ background: var(--asteroid-bg-modal);
255
274
  backdrop-filter: blur(32px);
256
- border: 1px solid rgba(255, 255, 255, 0.4);
275
+ border: 1px solid var(--asteroid-border-light);
257
276
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
258
277
  border-radius: var(--asteroid-radius-lg);
259
278
  padding: 1.25rem;
@@ -263,6 +282,7 @@
263
282
  pointer-events: auto;
264
283
  height: 100%;
265
284
  overflow: hidden;
285
+ color: var(--asteroid-text-primary);
266
286
  }
267
287
 
268
288
  .asteroid-lab-header {
@@ -270,7 +290,7 @@
270
290
  align-items: center;
271
291
  justify-content: space-between;
272
292
  padding-bottom: 0.5rem;
273
- border-bottom: 1px solid rgba(226, 232, 240, 0.6);
293
+ border-bottom: 1px solid var(--asteroid-border-light);
274
294
  flex-shrink: 0;
275
295
  }
276
296
 
@@ -288,7 +308,7 @@
288
308
  display: flex;
289
309
  align-items: center;
290
310
  justify-content: center;
291
- color: white;
311
+ color: var(--asteroid-text-primary);
292
312
  box-shadow: 0 4px 8px rgba(var(--asteroid-primary-rgb), 0.3);
293
313
  }
294
314
 
@@ -375,7 +395,7 @@
375
395
 
376
396
  .asteroid-drag-tooltip-text {
377
397
  background: rgba(0, 0, 0, 0.85);
378
- color: white;
398
+ color: var(--asteroid-text-primary);
379
399
  font-size: 0.65rem;
380
400
  font-weight: 700;
381
401
  padding: 0.4rem 0.75rem;
@@ -432,7 +452,7 @@
432
452
  display: flex;
433
453
  align-items: center;
434
454
  justify-content: center;
435
- color: white;
455
+ color: var(--asteroid-text-primary);
436
456
  z-index: 20;
437
457
  opacity: 1;
438
458
  transition: var(--asteroid-transition);
@@ -462,8 +482,13 @@
462
482
  }
463
483
 
464
484
  @keyframes asteroid-spin {
465
- from { transform: rotate(0deg); }
466
- to { transform: rotate(360deg); }
485
+ from {
486
+ transform: rotate(0deg);
487
+ }
488
+
489
+ to {
490
+ transform: rotate(360deg);
491
+ }
467
492
  }
468
493
 
469
494
  .asteroid-params-badge {
@@ -480,7 +505,7 @@
480
505
  z-index: 20;
481
506
  font-size: 0.7rem;
482
507
  font-weight: 700;
483
- color: white;
508
+ color: var(--asteroid-text-primary);
484
509
  white-space: nowrap;
485
510
  opacity: 1;
486
511
  transition: opacity var(--asteroid-transition-slow);
@@ -514,7 +539,7 @@
514
539
  .asteroid-preset-btn {
515
540
  position: relative;
516
541
  overflow: hidden;
517
- background: white;
542
+ background: var(--asteroid-bg-card);
518
543
  border: 1px solid var(--asteroid-border-light);
519
544
  border-radius: var(--asteroid-radius-md);
520
545
  padding: 0.75rem;
@@ -673,22 +698,23 @@
673
698
 
674
699
  /* Botón Limpiar */
675
700
  .asteroid-clear-btn {
676
- width: 100%;
677
- padding: 0.75rem;
678
- border-radius: var(--asteroid-radius-md);
701
+ padding: 0.25rem 0.35rem;
702
+ border-radius: 0.35rem;
679
703
  background: var(--asteroid-bg-light);
680
704
  color: var(--asteroid-text-secondary);
681
- font-weight: 700;
682
- font-size: 0.75rem;
705
+ font-weight: 600;
706
+ font-size: 0.5rem;
683
707
  text-transform: uppercase;
684
- letter-spacing: 0.05em;
708
+ letter-spacing: 0.04em;
685
709
  border: none;
686
710
  cursor: pointer;
687
711
  transition: var(--asteroid-transition);
688
712
  display: flex;
689
713
  align-items: center;
690
714
  justify-content: center;
691
- gap: 0.5rem;
715
+ gap: 0.2rem;
716
+ height: auto;
717
+ min-height: 1.5rem;
692
718
  flex-shrink: 0;
693
719
  }
694
720
 
@@ -753,7 +779,7 @@
753
779
  position: absolute;
754
780
  top: -0.75rem;
755
781
  background: var(--asteroid-bg-dark);
756
- color: white;
782
+ color: var(--asteroid-text-primary);
757
783
  font-size: 0.5625rem;
758
784
  font-weight: 900;
759
785
  letter-spacing: 0.125em;
@@ -796,4 +822,4 @@
796
822
  width: 1px;
797
823
  background: var(--asteroid-border-light);
798
824
  margin: 0 0.5rem;
799
- }
825
+ }
@@ -2,6 +2,12 @@
2
2
  import "./AsteroidImpact.css";
3
3
  import "leaflet/dist/leaflet.css";
4
4
  import { Icon } from "astro-icon/components";
5
+
6
+ interface Props {
7
+ ui: Record<string, string>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
5
11
  ---
6
12
 
7
13
  <div class="asteroid-app" id="asteroid-app">
@@ -17,7 +23,7 @@ import { Icon } from "astro-icon/components";
17
23
  <div>
18
24
  <button id="asteroid-gps-btn" class="asteroid-gps-btn">
19
25
  <div id="asteroid-gps-dot" class="asteroid-gps-dot"></div>
20
- <span id="asteroid-gps-text">Activar GPS</span>
26
+ <span id="asteroid-gps-text">{ui.activateGPS}</span>
21
27
  </button>
22
28
  </div>
23
29
 
@@ -25,7 +31,7 @@ import { Icon } from "astro-icon/components";
25
31
  <div id="asteroid-verdict-container" class="asteroid-verdict-container">
26
32
  <div id="asteroid-verdict-icon" class="asteroid-verdict-icon"></div>
27
33
  <div class="asteroid-verdict-text">
28
- <div id="asteroid-verdict-label" class="asteroid-verdict-label">Análisis</div>
34
+ <div id="asteroid-verdict-label" class="asteroid-verdict-label">{ui.analysisLabel}</div>
29
35
  <div id="asteroid-verdict-value" class="asteroid-verdict-value">--</div>
30
36
  </div>
31
37
  </div>
@@ -52,12 +58,12 @@ import { Icon } from "astro-icon/components";
52
58
  <div class="asteroid-drag-source" id="drag-source-desktop">
53
59
  <div class="asteroid-drag-bg"></div>
54
60
  <div class="asteroid-drag-tooltip">
55
- <span class="asteroid-drag-tooltip-text">ARRASTRA AL MAPA</span>
61
+ <span class="asteroid-drag-tooltip-text">{ui.dragToMap}</span>
56
62
  </div>
57
63
  <div class="asteroid-params-badge">
58
- <div id="asteroid-param-size" class="asteroid-param-item">Diametro: <span id="asteroid-param-size-val">50m</span></div>
59
- <div id="asteroid-param-vel" class="asteroid-param-item">Velocidad: <span id="asteroid-param-vel-val">15 km/s</span></div>
60
- <div id="asteroid-param-type" class="asteroid-param-item">Tipo: <span id="asteroid-param-type-val">Roca</span></div>
64
+ <div id="asteroid-param-size" class="asteroid-param-item">{ui.diameterLabel}: <span id="asteroid-param-size-val">50m</span></div>
65
+ <div id="asteroid-param-vel" class="asteroid-param-item">{ui.velocityLabel}: <span id="asteroid-param-vel-val">15 km/s</span></div>
66
+ <div id="asteroid-param-type" class="asteroid-param-item">Tipo: <span id="asteroid-param-type-val">{ui.rock}</span></div>
61
67
  </div>
62
68
  <div class="asteroid-drag-icon-parent">
63
69
  <Icon name="mdi:drag" class="w-6 h-6" />
@@ -74,20 +80,20 @@ import { Icon } from "astro-icon/components";
74
80
 
75
81
  <div>
76
82
  <div class="asteroid-control-label" style="margin-bottom: 1rem;">
77
- <span class="asteroid-control-text">Datos Históricos</span>
83
+ <span class="asteroid-control-text">{ui.historicalData}</span>
78
84
  </div>
79
85
  <div class="asteroid-presets">
80
86
  <button class="asteroid-preset-btn" data-dia="20" data-vel="19" data-comp="rock">
81
87
  <div class="asteroid-preset-title">CHELYABINSK</div>
82
- <div class="asteroid-preset-subtitle">20m · Aéreo</div>
88
+ <div class="asteroid-preset-subtitle">20m · {ui.presetAerial}</div>
83
89
  </button>
84
90
  <button class="asteroid-preset-btn" data-dia="50" data-vel="15" data-comp="rock">
85
91
  <div class="asteroid-preset-title">TUNGUSKA</div>
86
- <div class="asteroid-preset-subtitle">50m · Bosque</div>
92
+ <div class="asteroid-preset-subtitle">50m · {ui.presetForest}</div>
87
93
  </button>
88
94
  <button class="asteroid-preset-btn" data-dia="200" data-vel="25" data-comp="ice">
89
95
  <div class="asteroid-preset-title">3I ATLAS</div>
90
- <div class="asteroid-preset-subtitle">200m · Cometa</div>
96
+ <div class="asteroid-preset-subtitle">200m · {ui.presetComet}</div>
91
97
  </button>
92
98
  <button class="asteroid-preset-btn" data-dia="10000" data-vel="20" data-comp="rock">
93
99
  <div class="asteroid-preset-title">CHICXULUB</div>
@@ -98,7 +104,7 @@ import { Icon } from "astro-icon/components";
98
104
 
99
105
  <div class="asteroid-control-group">
100
106
  <div class="asteroid-control-label">
101
- <span class="asteroid-control-text">Diámetro</span>
107
+ <span class="asteroid-control-text">{ui.diameterLabel}</span>
102
108
  <span id="display-size" class="asteroid-control-value">100m</span>
103
109
  </div>
104
110
  <input type="range" id="input-size" min="10" max="5000" step="10" value="5000" class="asteroid-slider" />
@@ -106,35 +112,33 @@ import { Icon } from "astro-icon/components";
106
112
 
107
113
  <div class="asteroid-control-group">
108
114
  <div class="asteroid-control-label">
109
- <span class="asteroid-control-text">Velocidad</span>
115
+ <span class="asteroid-control-text">{ui.velocityLabel}</span>
110
116
  <span id="display-velocity" class="asteroid-control-value">20 km/s</span>
111
117
  </div>
112
118
  <input type="range" id="input-velocity" min="10" max="70" step="1" value="20" class="asteroid-slider" />
113
119
  </div>
114
120
 
115
121
  <div>
116
- <div class="asteroid-control-text" style="margin-bottom: 0.5rem; display: block;">Composición</div>
122
+ <div class="asteroid-control-text" style="margin-bottom: 0.5rem; display: block;">{ui.composition}</div>
117
123
  <div class="asteroid-material-buttons">
118
124
  <button class="asteroid-material-btn active" data-type="rock">
119
125
  <div class="asteroid-material-dot" style="background: #64748b;"></div>
120
- <span class="asteroid-material-name">Roca</span>
126
+ <span class="asteroid-material-name">{ui.rock}</span>
121
127
  </button>
122
128
  <button class="asteroid-material-btn" data-type="iron">
123
129
  <div class="asteroid-material-dot" style="background: #1e293b;"></div>
124
- <span class="asteroid-material-name">Hierro</span>
130
+ <span class="asteroid-material-name">{ui.iron}</span>
125
131
  </button>
126
132
  <button class="asteroid-material-btn" data-type="ice">
127
133
  <div class="asteroid-material-dot" style="background: #67e8f9;"></div>
128
- <span class="asteroid-material-name">Hielo</span>
134
+ <span class="asteroid-material-name">{ui.ice}</span>
129
135
  </button>
130
136
  </div>
131
137
  </div>
132
138
 
133
139
  <button id="clear-btn" class="asteroid-clear-btn">
134
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
135
- <path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"></path>
136
- </svg>
137
- Limpiar todo
140
+ <Icon name="mdi:trash-can-outline" class="w-4 h-4" />
141
+ {ui.clearAll}
138
142
  </button>
139
143
  </div>
140
144
  </div>
@@ -224,18 +228,18 @@ import { Icon } from "astro-icon/components";
224
228
 
225
229
  sizeInput?.addEventListener("input", (e) => {
226
230
  config.diameter = parseInt((e.target as HTMLInputElement).value);
227
- updateControlUI();
231
+ updateControlUI(config);
228
232
  });
229
233
 
230
234
  velInput?.addEventListener("input", (e) => {
231
235
  config.velocity = parseInt((e.target as HTMLInputElement).value);
232
- updateControlUI();
236
+ updateControlUI(config);
233
237
  });
234
238
 
235
239
  matBtns.forEach((btn) => {
236
240
  btn.addEventListener("click", () => {
237
241
  config.composition = btn.getAttribute("data-type") as Composition;
238
- updateControlUI();
242
+ updateControlUI(config);
239
243
  });
240
244
  });
241
245
 
@@ -244,7 +248,7 @@ import { Icon } from "astro-icon/components";
244
248
  config.diameter = parseInt(btn.getAttribute("data-dia")!);
245
249
  config.velocity = parseInt(btn.getAttribute("data-vel")!);
246
250
  config.composition = btn.getAttribute("data-comp") as Composition;
247
- updateControlUI();
251
+ updateControlUI(config);
248
252
  });
249
253
  });
250
254
 
@@ -34,6 +34,10 @@ export const content: ToolLocaleContent = {
34
34
  verdictBurnedSub: 'Extreme danger',
35
35
  verdictVaporized: 'GROUND ZERO',
36
36
  verdictVaporizedSub: 'Direct impact',
37
+ presetAerial: 'Aerial',
38
+ presetForest: 'Forest',
39
+ presetComet: 'Comet',
40
+ presetELE: 'E.L.E.',
37
41
  },
38
42
  seo: [
39
43
  {
@@ -34,6 +34,10 @@ export const content: ToolLocaleContent = {
34
34
  verdictBurnedSub: 'Peligro extremo',
35
35
  verdictVaporized: 'ZONA CERO',
36
36
  verdictVaporizedSub: 'Impacto directo',
37
+ presetAerial: 'Aéreo',
38
+ presetForest: 'Bosque',
39
+ presetComet: 'Cometa',
40
+ presetELE: 'E.L.E.',
37
41
  },
38
42
  seo: [
39
43
  {
@@ -34,6 +34,10 @@ export const content: ToolLocaleContent = {
34
34
  verdictBurnedSub: 'Danger extrême',
35
35
  verdictVaporized: 'ZONE ZÉRO',
36
36
  verdictVaporizedSub: 'Impact direct',
37
+ presetAerial: 'Aérien',
38
+ presetForest: 'Forêt',
39
+ presetComet: 'Comète',
40
+ presetELE: 'E.L.E.',
37
41
  },
38
42
  seo: [
39
43
  {
@@ -1,14 +1,19 @@
1
1
  ---
2
+ interface Props {
3
+ ui: Record<string, string>;
4
+ }
5
+
6
+ const { ui } = Astro.props;
2
7
  ---
3
8
 
4
9
  <div class="cellular-app" id="cellular-app">
5
10
  <div class="cellular-card">
6
11
  <div class="input-section">
7
12
  <div class="input-group">
8
- <div class="input-label">Cronología Biológica</div>
13
+ <div class="input-label">{ui.biologicalTimeline}</div>
9
14
  <div class="age-display">
10
15
  <span id="ageDisplay">25</span>
11
- <span class="age-unit">años de evolución</span>
16
+ <span class="age-unit">{ui.ageUnit}</span>
12
17
  </div>
13
18
  <input
14
19
  type="range"
@@ -24,18 +29,18 @@
24
29
 
25
30
  <div class="results-grid">
26
31
  <div class="main-stat">
27
- <span class="main-stat-label">Tu materia es nueva en un</span>
32
+ <span class="main-stat-label">{ui.matterNewPercent}</span>
28
33
  <div class="main-stat-value">
29
34
  <span id="newStatVal">93</span>
30
35
  <span class="percent-sign">%</span>
31
36
  </div>
32
- <div class="main-stat-sublabel">Renovación Atómica</div>
37
+ <div class="main-stat-sublabel">{ui.atomicRenewal}</div>
33
38
  </div>
34
39
 
35
40
  <div class="tissue-list">
36
41
  <div class="tissue-item">
37
42
  <div class="tissue-header">
38
- <span class="tissue-name">Epidermis y Sangre</span>
43
+ <span class="tissue-name">{ui.skinAndBlood}</span>
39
44
  <span class="tissue-percentage" id="skinVal">100%</span>
40
45
  </div>
41
46
  <div class="bar-container">
@@ -45,7 +50,7 @@
45
50
 
46
51
  <div class="tissue-item">
47
52
  <div class="tissue-header">
48
- <span class="tissue-name">Remodelación Ósea</span>
53
+ <span class="tissue-name">{ui.boneRemodeling}</span>
49
54
  <span class="tissue-percentage" id="boneVal">85%</span>
50
55
  </div>
51
56
  <div class="bar-container">
@@ -55,7 +60,7 @@
55
60
 
56
61
  <div class="tissue-item">
57
62
  <div class="tissue-header">
58
- <span class="tissue-name">Matriz Orgánica</span>
63
+ <span class="tissue-name">{ui.organicMatrix}</span>
59
64
  <span class="tissue-percentage" id="organVal">60%</span>
60
65
  </div>
61
66
  <div class="bar-container">
@@ -65,7 +70,7 @@
65
70
 
66
71
  <div class="tissue-item">
67
72
  <div class="tissue-header">
68
- <span class="tissue-name">Células Perennes</span>
73
+ <span class="tissue-name">{ui.perennialCells}</span>
69
74
  <span class="tissue-percentage" id="brainVal">12%</span>
70
75
  </div>
71
76
  <div class="bar-container">
@@ -76,7 +81,7 @@
76
81
  </div>
77
82
 
78
83
  <p class="disclaimer">
79
- Los cálculos se basan en la vida media celular según estudios isotópicos. Mientras que la sangre y la piel se renuevan en semanas, las proteínas del cristalino y gran parte de tu corteza cerebral permanecen desde el desarrollo embrionario. Físicamente, eres una estructura dinámica en flujo constante.
84
+ {ui.disclaimerText}
80
85
  </p>
81
86
  </div>
82
87
  </div>
@@ -1,6 +1,12 @@
1
1
  ---
2
2
  import "./ColonyCounter.css";
3
3
  import { Icon } from "astro-icon/components";
4
+
5
+ interface Props {
6
+ ui: Record<string, string>;
7
+ }
8
+
9
+ const { ui } = Astro.props;
4
10
  ---
5
11
 
6
12
  <div class="colony-container">
@@ -14,10 +20,10 @@ import { Icon } from "astro-icon/components";
14
20
  <Icon name="mdi:upload" class="colony-upload-icon" />
15
21
  <div class="colony-upload-text">
16
22
  <p class="colony-upload-title">
17
- Haz clic para subir tu placa de Petri
23
+ {ui.uploadTitle}
18
24
  </p>
19
25
  <p class="colony-upload-subtitle">
20
- Sube una foto de tu placa y empieza a contar colonias
26
+ {ui.uploadSubtitle}
21
27
  </p>
22
28
  </div>
23
29
  </div>
@@ -29,10 +35,10 @@ import { Icon } from "astro-icon/components";
29
35
 
30
36
  <div id="mode-indicator" class="colony-hidden colony-mode-indicator">
31
37
  <p class="colony-mode-label">
32
- Modo Actual
38
+ {ui.currentModeLabel}
33
39
  </p>
34
40
  <p id="current-mode" class="colony-mode-value">
35
- Tipo A
41
+ {ui.typeA}
36
42
  </p>
37
43
  </div>
38
44
  </div>
@@ -41,7 +47,7 @@ import { Icon } from "astro-icon/components";
41
47
  <div class="colony-control-panel">
42
48
  <div class="colony-control-section">
43
49
  <h3 class="colony-control-title">
44
- Tipo de Colonia
50
+ {ui.colonyType}
45
51
  </h3>
46
52
 
47
53
  <div class="colony-button-grid">
@@ -50,14 +56,14 @@ import { Icon } from "astro-icon/components";
50
56
  class="colony-mode-btn colony-mode-btn-active"
51
57
  >
52
58
  <span class="colony-color-dot colony-color-a"></span>
53
- Tipo A
59
+ {ui.typeA}
54
60
  </button>
55
61
  <button
56
62
  id="mode-b"
57
63
  class="colony-mode-btn"
58
64
  >
59
65
  <span class="colony-color-dot colony-color-b"></span>
60
- Tipo B
66
+ {ui.typeB}
61
67
  </button>
62
68
  </div>
63
69
  </div>
@@ -66,13 +72,13 @@ import { Icon } from "astro-icon/components";
66
72
 
67
73
  <div class="colony-control-section">
68
74
  <h3 class="colony-control-title">
69
- Conteo
75
+ {ui.counting}
70
76
  </h3>
71
77
 
72
78
  <div class="colony-count-grid">
73
79
  <div class="colony-count-box colony-count-box-a">
74
80
  <p class="colony-count-label colony-count-label-a">
75
- Tipo A
81
+ {ui.typeA}
76
82
  </p>
77
83
  <p id="count-a" class="colony-count-value">
78
84
  0
@@ -80,7 +86,7 @@ import { Icon } from "astro-icon/components";
80
86
  </div>
81
87
  <div class="colony-count-box colony-count-box-b">
82
88
  <p class="colony-count-label colony-count-label-b">
83
- Tipo B
89
+ {ui.typeB}
84
90
  </p>
85
91
  <p id="count-b" class="colony-count-value">
86
92
  0
@@ -90,7 +96,7 @@ import { Icon } from "astro-icon/components";
90
96
 
91
97
  <div class="colony-count-total">
92
98
  <p class="colony-count-label-total">
93
- Total UFC
99
+ {ui.totalCFU}
94
100
  </p>
95
101
  <p id="count-total" class="colony-count-value-total">
96
102
  0
@@ -106,7 +112,7 @@ import { Icon } from "astro-icon/components";
106
112
  class="colony-action-btn colony-undo-btn"
107
113
  >
108
114
  <Icon name="mdi:undo" class="colony-btn-icon" />
109
- Deshacer Último
115
+ {ui.undo}
110
116
  </button>
111
117
 
112
118
  <button
@@ -114,17 +120,16 @@ import { Icon } from "astro-icon/components";
114
120
  class="colony-action-btn colony-clear-btn"
115
121
  >
116
122
  <Icon name="mdi:delete-sweep" class="colony-btn-icon" />
117
- Limpiar Todo
123
+ {ui.clearAll}
118
124
  </button>
119
125
  </div>
120
126
 
121
127
  <div class="colony-info-text">
122
128
  <p>
123
- <Icon name="mdi:information" class="colony-info-icon" /> Haz clic en la placa para
124
- marcar colonias
129
+ <Icon name="mdi:information" class="colony-info-icon" /> {ui.infoClick}
125
130
  </p>
126
131
  <p>
127
- <Icon name="mdi:palette" class="colony-info-icon" /> Cambia el tipo antes de marcar
132
+ <Icon name="mdi:palette" class="colony-info-icon" /> {ui.infoChange}
128
133
  </p>
129
134
  </div>
130
135
  </div>
@@ -1,24 +1,24 @@
1
1
  ---
2
2
  import "./MicrowaveDetector.css";
3
3
  import { Icon } from "astro-icon/components";
4
+
5
+ interface Props {
6
+ ui: Record<string, string>;
7
+ }
8
+
9
+ const { ui } = Astro.props;
4
10
  ---
5
11
 
6
- <div class="microwave-detector-container" id="microwave-detector-root">
12
+ <div class="microwave-detector-container" id="microwave-detector-root" data-detecting-interference={ui.detectingInterference}>
7
13
  <div class="microwave-modal" id="initial-modal">
8
14
  <div class="microwave-modal-content">
9
15
  <div class="microwave-modal-icon">
10
16
  <Icon name="mdi:wifi-alert" />
11
17
  </div>
12
- <h2>Requisito de Física</h2>
13
- <p>
14
- Este detector utiliza la interferencia en la banda de <strong>2.4GHz</strong> (la frecuencia de
15
- los microondas).
16
- <br /><br />
17
- Para que funcione, asegúrate de estar conectado a una red <strong>WiFi 2.4GHz</strong> (no 5GHz/6GHz)
18
- o usa tu teléfono cerca del aparato.
19
- </p>
18
+ <h2>{ui.physicalRequirement}</h2>
19
+ <p set:html={ui.physicalDesc}></p>
20
20
  <button id="start-btn" class="microwave-btn-start">
21
- Entendido, Iniciar Escaneo
21
+ {ui.understandStart}
22
22
  </button>
23
23
  </div>
24
24
  </div>
@@ -26,12 +26,12 @@ import { Icon } from "astro-icon/components";
26
26
  <div class="microwave-detector-panel">
27
27
  <div class="microwave-header">
28
28
  <div>
29
- <span class="microwave-label">RF Interference Monitor</span>
30
- <h3 class="microwave-title">MW-LEAK DETECTOR v2.0</h3>
29
+ <span class="microwave-label">{ui.rfInterferenceMonitor}</span>
30
+ <h3 class="microwave-title">{ui.mwLeakDetector}</h3>
31
31
  </div>
32
32
  <div class="microwave-status-badge">
33
33
  <div id="status-dot" class="microwave-status-dot"></div>
34
- <span id="status-text" class="microwave-status-text">Estático</span>
34
+ <span id="status-text" class="microwave-status-text">{ui.static}</span>
35
35
  </div>
36
36
  </div>
37
37
 
@@ -41,7 +41,7 @@ import { Icon } from "astro-icon/components";
41
41
  <div id="big-value-bg" class="microwave-big-value">00</div>
42
42
  <div class="microwave-jitter-display">
43
43
  <span id="jitter-value" class="microwave-jitter-value">0.0</span>
44
- <span class="microwave-jitter-unit">ms / jitter</span>
44
+ <span class="microwave-jitter-unit">{ui.jitterUnit}</span>
45
45
  </div>
46
46
  </div>
47
47
  <div class="microwave-canvas-grid"></div>
@@ -50,9 +50,9 @@ import { Icon } from "astro-icon/components";
50
50
  <div class="microwave-content-grid">
51
51
  <div>
52
52
  <div id="verdict-container" class="microwave-verdict">
53
- <h4 id="verdict-label" class="microwave-verdict-label">Sistema Listado</h4>
53
+ <h4 id="verdict-label" class="microwave-verdict-label">{ui.systemReady}</h4>
54
54
  <p id="verdict-description" class="microwave-verdict-desc">
55
- Conecta para iniciar el análisis térmico de interferencia.
55
+ {ui.connectToAnalyze}
56
56
  </p>
57
57
  </div>
58
58
  <div class="microwave-latency-info">
@@ -65,12 +65,12 @@ import { Icon } from "astro-icon/components";
65
65
 
66
66
  <div class="microwave-audio-section">
67
67
  <div class="microwave-audio-label">
68
- <span>Audio Feedback</span>
68
+ <span>{ui.audioFeedback}</span>
69
69
  <span id="audio-status">ON</span>
70
70
  </div>
71
71
  <button id="toggle-audio-btn" class="microwave-btn-audio">
72
72
  <Icon name="mdi:volume-high" />
73
- MUTE / UNMUTE
73
+ {ui.muteUnmute}
74
74
  </button>
75
75
  </div>
76
76
  </div>
@@ -80,6 +80,7 @@ import { Icon } from "astro-icon/components";
80
80
  </div>
81
81
 
82
82
  <style is:inline define:vars={{}}>
83
+
83
84
  :root {
84
85
  --microwave-primary: #e11d48;
85
86
  --microwave-text: #0f172a;
@@ -517,7 +518,8 @@ import { Icon } from "astro-icon/components";
517
518
  const statusText = document.getElementById("status-text");
518
519
  const toggleAudioBtn = document.getElementById("toggle-audio-btn");
519
520
 
520
- const engine = new MicrowaveEngine();
521
+ const currentLang = document.documentElement.lang || 'es';
522
+ const engine = new MicrowaveEngine(currentLang);
521
523
  let running = false;
522
524
  let audioEnabled = true;
523
525
  let audioCtx = null;
@@ -620,7 +622,7 @@ import { Icon } from "astro-icon/components";
620
622
  if (bigValueBg) bigValueBg.textContent = Math.floor(result.jitter).toString().padStart(2, "0");
621
623
  if (latencyVal) latencyVal.textContent = `${Math.floor(result.latency)}ms`;
622
624
 
623
- const level = MicrowaveEngine.getInterferenceLevel(result.jitter);
625
+ const level = engine.getInterferenceLevel(result.jitter);
624
626
  updateVerdictDisplay(level);
625
627
 
626
628
  if (result.jitter > 1) {
@@ -631,11 +633,13 @@ import { Icon } from "astro-icon/components";
631
633
  setTimeout(scan, 200 + Math.random() * 100);
632
634
  }
633
635
 
636
+ const detectingInterferenceText = root?.getAttribute('data-detecting-interference') || 'Detectando interferencias...';
637
+
634
638
  startBtn?.addEventListener("click", () => {
635
639
  if (modal) modal.classList.add("hidden");
636
640
  running = true;
637
641
  resizeCanvas();
638
- if (statusText) statusText.textContent = "Scanning...";
642
+ if (statusText) statusText.textContent = detectingInterferenceText;
639
643
  scan();
640
644
  });
641
645
 
@@ -0,0 +1,87 @@
1
+ export interface InterferenceLevel {
2
+ threshold: number;
3
+ label: string;
4
+ color: string;
5
+ description: string;
6
+ }
7
+
8
+ export const INTERFERENCE_LEVELS_ES: InterferenceLevel[] = [
9
+ {
10
+ threshold: 2,
11
+ label: "Señal Limpia",
12
+ color: "emerald",
13
+ description: "Tu conexión es estable. No hay interferencias electromagnéticas significativas detectadas.",
14
+ },
15
+ {
16
+ threshold: 10,
17
+ label: "Interferencia Leve",
18
+ color: "yellow",
19
+ description: "Se detecta algo de ruido en la señal. Podría ser actividad normal o dispositivos Bluetooth cercanos.",
20
+ },
21
+ {
22
+ threshold: 30,
23
+ label: "Interferencia Alta",
24
+ color: "orange",
25
+ description: "Ruido electromagnético considerable detectado. Si el microondas está encendido, es posible que tenga fugas leves.",
26
+ },
27
+ ];
28
+
29
+ export const INTERFERENCE_LEVELS_EN: InterferenceLevel[] = [
30
+ {
31
+ threshold: 2,
32
+ label: "Clean Signal",
33
+ color: "emerald",
34
+ description: "Your connection is stable. No significant electromagnetic interference detected.",
35
+ },
36
+ {
37
+ threshold: 10,
38
+ label: "Light Interference",
39
+ color: "yellow",
40
+ description: "Some noise detected in the signal. Could be normal activity or nearby Bluetooth devices.",
41
+ },
42
+ {
43
+ threshold: 30,
44
+ label: "High Interference",
45
+ color: "orange",
46
+ description: "Considerable electromagnetic noise detected. If your microwave is on, it may have minor leaks.",
47
+ },
48
+ ];
49
+
50
+ export const INTERFERENCE_LEVELS_FR: InterferenceLevel[] = [
51
+ {
52
+ threshold: 2,
53
+ label: "Signal Propre",
54
+ color: "emerald",
55
+ description: "Votre connexion est stable. Aucune interférence électromagnétique significative détectée.",
56
+ },
57
+ {
58
+ threshold: 10,
59
+ label: "Interférence Légère",
60
+ color: "yellow",
61
+ description: "Du bruit détecté dans le signal. Pourrait être une activité normale ou des appareils Bluetooth à proximité.",
62
+ },
63
+ {
64
+ threshold: 30,
65
+ label: "Interférence Élevée",
66
+ color: "orange",
67
+ description: "Bruit électromagnétique considérable détecté. Si votre micro-ondes est allumé, il peut y avoir des fuites mineures.",
68
+ },
69
+ ];
70
+
71
+ export const CRITICAL_INTERFERENCE_ES = {
72
+ label: "FUGA CRÍTICA / RUIDO EXTREMO",
73
+ color: "red",
74
+ description: "Inestabilidad masiva en la señal. Si estás junto al microondas, apágalo: la protección RF podría estar degradada.",
75
+ };
76
+
77
+ export const CRITICAL_INTERFERENCE_EN = {
78
+ label: "CRITICAL LEAK / EXTREME NOISE",
79
+ color: "red",
80
+ description: "Massive signal instability. If you're near the microwave, turn it off: RF protection may be degraded.",
81
+ };
82
+
83
+ export const CRITICAL_INTERFERENCE_FR = {
84
+ label: "FUITE CRITIQUE / BRUIT EXTRÊME",
85
+ color: "red",
86
+ description: "Instabilité massive du signal. Si vous êtes près du micro-ondes, éteignez-le : la protection RF pourrait être dégradée.",
87
+ };
@@ -25,6 +25,18 @@ export const content: ToolLocaleContent = {
25
25
  highLeak: 'High',
26
26
  pingLabel: 'Latency (ms)',
27
27
  packetLossLabel: 'Packet Loss (%)',
28
+ rfInterferenceMonitor: 'RF Interference Monitor',
29
+ mwLeakDetector: 'MW LEAK DETECTOR v2.0',
30
+ jitterUnit: 'ms / jitter',
31
+ systemReady: 'System Ready',
32
+ physicalRequirement: 'Physical Requirement',
33
+ physicalDesc: 'This detector uses interference in the 2.4GHz band (the microwave frequency). To work properly, make sure you are connected to a 2.4GHz WiFi network (not 5GHz/6GHz) or use your phone near the device.',
34
+ understandStart: 'Understood, Start Scan',
35
+ rfInterferenceTitle: 'Interference Monitor',
36
+ connectToAnalyze: 'Connect to start thermal interference analysis.',
37
+ audioFeedback: 'Audio Feedback',
38
+ muteUnmute: 'MUTE / UNMUTE',
39
+ static: 'Static',
28
40
  },
29
41
  seo: [
30
42
  {
@@ -25,6 +25,18 @@ export const content: ToolLocaleContent = {
25
25
  highLeak: 'Alta',
26
26
  pingLabel: 'Latencia (ms)',
27
27
  packetLossLabel: 'Pérdida de Paquetes (%)',
28
+ rfInterferenceMonitor: 'Monitor de Interferencias RF',
29
+ mwLeakDetector: 'DETECTOR DE FUGAS MW v2.0',
30
+ jitterUnit: 'ms / interferencia',
31
+ systemReady: 'Sistema Listo',
32
+ physicalRequirement: 'Requisito de Física',
33
+ physicalDesc: 'Este detector utiliza la interferencia en la banda de 2.4GHz (la frecuencia de los microondas). Para que funcione, asegúrate de estar conectado a una red WiFi 2.4GHz (no 5GHz/6GHz) o usa tu teléfono cerca del aparato.',
34
+ understandStart: 'Entendido, Iniciar Escaneo',
35
+ rfInterferenceTitle: 'Monitor de Interferencias',
36
+ connectToAnalyze: 'Conecta para iniciar el análisis térmico de interferencia.',
37
+ audioFeedback: 'Audio Feedback',
38
+ muteUnmute: 'MUTEADO / ACTIVADO',
39
+ static: 'Estático',
28
40
  },
29
41
  seo: [
30
42
  {
@@ -25,6 +25,18 @@ export const content: ToolLocaleContent = {
25
25
  highLeak: 'Haute',
26
26
  pingLabel: 'Latence (ms)',
27
27
  packetLossLabel: 'Perte de paquets (%)',
28
+ rfInterferenceMonitor: 'Moniteur d\'Interférences RF',
29
+ mwLeakDetector: 'DÉTECTEUR DE FUITES MW v2.0',
30
+ jitterUnit: 'ms / gigue',
31
+ systemReady: 'Système Prêt',
32
+ physicalRequirement: 'Exigence Physique',
33
+ physicalDesc: 'Ce détecteur utilise l\'interférence dans la bande 2.4GHz (la fréquence des micro-ondes). Pour fonctionner correctement, assurez-vous que vous êtes connecté à un réseau WiFi 2.4GHz (pas 5GHz/6GHz) ou utilisez votre téléphone près de l\'appareil.',
34
+ understandStart: 'D\'accord, Démarrer l\'Analyse',
35
+ rfInterferenceTitle: 'Moniteur d\'Interférences',
36
+ connectToAnalyze: 'Connectez-vous pour démarrer l\'analyse thermique des interférences.',
37
+ audioFeedback: 'Retour Audio',
38
+ muteUnmute: 'ACTIVER / DÉSACTIVER',
39
+ static: 'Statique',
28
40
  },
29
41
  seo: [
30
42
  {
@@ -1,40 +1,55 @@
1
+ import {
2
+ INTERFERENCE_LEVELS_ES,
3
+ INTERFERENCE_LEVELS_EN,
4
+ INTERFERENCE_LEVELS_FR,
5
+ CRITICAL_INTERFERENCE_ES,
6
+ CRITICAL_INTERFERENCE_EN,
7
+ CRITICAL_INTERFERENCE_FR,
8
+ type InterferenceLevel,
9
+ } from '../constants';
10
+
1
11
  export interface PingResult {
2
12
  latency: number;
3
13
  jitter: number;
4
14
  timestamp: number;
5
15
  }
6
16
 
7
- interface InterferenceLevel {
8
- threshold: number;
9
- label: string;
10
- color: string;
11
- description: string;
12
- }
13
-
14
17
  export class MicrowaveEngine {
15
18
  private lastPings: number[] = [];
16
19
  private maxHistory = 100;
20
+ private lang: string = 'es';
21
+
22
+ constructor(lang: string = 'es') {
23
+ this.lang = lang;
24
+ }
25
+
26
+ setLanguage(lang: string) {
27
+ this.lang = lang;
28
+ }
29
+
30
+ private getInterferenceLevels(): InterferenceLevel[] {
31
+ switch (this.lang) {
32
+ case 'en':
33
+ return INTERFERENCE_LEVELS_EN;
34
+ case 'fr':
35
+ return INTERFERENCE_LEVELS_FR;
36
+ case 'es':
37
+ default:
38
+ return INTERFERENCE_LEVELS_ES;
39
+ }
40
+ }
17
41
 
18
- private static readonly INTERFERENCE_LEVELS: InterferenceLevel[] = [
19
- {
20
- threshold: 2,
21
- label: "Señal Limpia",
22
- color: "emerald",
23
- description: "Tu conexión es estable. No hay interferencias electromagnéticas significativas detectadas.",
24
- },
25
- {
26
- threshold: 10,
27
- label: "Interferencia Leve",
28
- color: "yellow",
29
- description: "Se detecta algo de ruido en la señal. Podría ser actividad normal o dispositivos Bluetooth cercanos.",
30
- },
31
- {
32
- threshold: 30,
33
- label: "Interferencia Alta",
34
- color: "orange",
35
- description: "Ruido electromagnético considerable detectado. Si el microondas está encendido, es posible que tenga fugas leves.",
36
- },
37
- ];
42
+ private getCriticalInterference() {
43
+ switch (this.lang) {
44
+ case 'en':
45
+ return CRITICAL_INTERFERENCE_EN;
46
+ case 'fr':
47
+ return CRITICAL_INTERFERENCE_FR;
48
+ case 'es':
49
+ default:
50
+ return CRITICAL_INTERFERENCE_ES;
51
+ }
52
+ }
38
53
 
39
54
  async measurePing(): Promise<PingResult> {
40
55
  const start = performance.now();
@@ -71,19 +86,16 @@ export class MicrowaveEngine {
71
86
  return diffSum / (this.lastPings.length - 1);
72
87
  }
73
88
 
74
- static getInterferenceLevel(jitter: number): {
89
+ getInterferenceLevel(jitter: number): {
75
90
  label: string;
76
91
  color: string;
77
92
  description: string;
78
93
  } {
79
- const level = this.INTERFERENCE_LEVELS.find((l) => jitter < l.threshold);
94
+ const levels = this.getInterferenceLevels();
95
+ const level = levels.find((l) => jitter < l.threshold);
80
96
  if (level) {
81
97
  return { label: level.label, color: level.color, description: level.description };
82
98
  }
83
- return {
84
- label: "FUGA CRÍTICA / RUIDO EXTREMO",
85
- color: "red",
86
- description: "Inestabilidad masiva en la señal. Si estás junto al microondas, apágalo: la protección RF podría estar degradada.",
87
- };
99
+ return this.getCriticalInterference();
88
100
  }
89
101
  }
@@ -1,20 +1,25 @@
1
1
  ---
2
+ interface Props {
3
+ ui: Record<string, string>;
4
+ }
5
+
6
+ const { ui } = Astro.props;
2
7
  ---
3
8
 
4
9
  <div class="simulation-app" id="simulation-app">
5
10
  <div class="simulation-header">
6
- <h2>Analizador de Realidad Cuántica</h2>
11
+ <h2>{ui.probabilityTitle}</h2>
7
12
  </div>
8
13
 
9
14
  <div class="simulation-main">
10
15
  <div class="parameter-card">
11
16
  <div class="label-row">
12
17
  <div class="label-info">
13
- <label for="technological-progress">Progreso Tecnológico (fp)</label>
18
+ <label for="technological-progress">{ui.fpSub} ({ui.fpLabel})</label>
14
19
  </div>
15
20
  <div class="value-display" id="fp-value">50%</div>
16
21
  </div>
17
- <small class="helper-text">Probabilidad de que la humanidad alcance la capacidad técnica para simular universos con conciencia.</small>
22
+ <small class="helper-text">{ui.fpDescription}</small>
18
23
  <div class="control-row">
19
24
  <div class="slider-container">
20
25
  <input type="range" id="technological-progress" min="1" max="100" value="50" />
@@ -25,11 +30,11 @@
25
30
  <div class="parameter-card">
26
31
  <div class="label-row">
27
32
  <div class="label-info">
28
- <label for="societal-survival">Tasa de Supervivencia (fl)</label>
33
+ <label for="societal-survival">{ui.flSub} ({ui.flLabel})</label>
29
34
  </div>
30
35
  <div class="value-display" id="fl-value">50%</div>
31
36
  </div>
32
- <small class="helper-text">Probabilidad de evitar el colapso (extinción, guerras) antes de alcanzar el nivel post-humano.</small>
37
+ <small class="helper-text">{ui.flDescription}</small>
33
38
  <div class="control-row">
34
39
  <div class="slider-container">
35
40
  <input type="range" id="societal-survival" min="1" max="100" value="50" />
@@ -40,11 +45,11 @@
40
45
  <div class="parameter-card">
41
46
  <div class="label-row">
42
47
  <div class="label-info">
43
- <label for="simulation-interest">Interés en Simular (fi)</label>
48
+ <label for="simulation-interest">{ui.fiSub} ({ui.fiLabel})</label>
44
49
  </div>
45
50
  <div class="value-display" id="fi-value">20%</div>
46
51
  </div>
47
- <small class="helper-text">Porcentaje de sociedades avanzadas que deciden crear simulaciones de sus antepasados.</small>
52
+ <small class="helper-text">{ui.fiDescription}</small>
48
53
  <div class="control-row">
49
54
  <div class="slider-container">
50
55
  <input type="range" id="simulation-interest" min="1" max="100" value="20" />
@@ -55,11 +60,11 @@
55
60
  <div class="parameter-card">
56
61
  <div class="label-row">
57
62
  <div class="label-info">
58
- <label for="ancestral-sims">Escala de Simulación (N)</label>
63
+ <label for="ancestral-sims">{ui.nSub} ({ui.nLabel})</label>
59
64
  </div>
60
65
  <div class="value-display" id="n-value">1,000</div>
61
66
  </div>
62
- <small class="helper-text">Número de mundos simulados que cada civilización avanzada suele ejecutar simultáneamente.</small>
67
+ <small class="helper-text">{ui.nDescription}</small>
63
68
  <div class="control-row">
64
69
  <div class="slider-container">
65
70
  <input type="range" id="ancestral-sims" min="1" max="10000" value="1000" />
@@ -74,9 +79,9 @@
74
79
  </div>
75
80
 
76
81
  <div class="verdict-box">
77
- <div class="verdict-title" id="verdict">Probabilidad de Simulación</div>
82
+ <div class="verdict-title" id="verdict">{ui.probabilityTitle}</div>
78
83
  <div class="verdict-detail" id="verdict-detail">
79
- Los datos sugieren que es extremadamente probable que tu conciencia sea un proceso de software.
84
+ {ui.verdictDetail}
80
85
  </div>
81
86
  </div>
82
87
  </div>
@@ -323,6 +328,9 @@
323
328
  throw new Error("Required simulation elements not found");
324
329
  }
325
330
 
331
+ const currentLang = document.documentElement.lang || 'es';
332
+ const engine = new SimulationEngine(currentLang);
333
+
326
334
  const calculateProbability = () => {
327
335
  const fp = parseFloat(fpInput!.value) / 100;
328
336
  const fl = parseFloat(flInput!.value) / 100;
@@ -334,7 +342,7 @@
334
342
  fiText!.textContent = `${Math.round(fi * 100)}%`;
335
343
  nText!.textContent = n.toLocaleString();
336
344
 
337
- const result = SimulationEngine.calculate(fp, fl, fi, n);
345
+ const result = engine.calculate(fp, fl, fi, n);
338
346
  probDisplay!.textContent = `${result.probability.toFixed(2)}%`;
339
347
  verdictLabel!.textContent = result.verdict;
340
348
  verdictDetail!.textContent = result.detail;
@@ -0,0 +1,89 @@
1
+ export interface VerdictLevel {
2
+ percentage: number;
3
+ verdict: string;
4
+ detail: string;
5
+ }
6
+
7
+ export const VERDICTS_ES: VerdictLevel[] = [
8
+ {
9
+ percentage: Infinity,
10
+ verdict: "Casi Ciertamente Simulado",
11
+ detail: "Estás casi ciertamente en una simulación. Los errores en el sistema son solo cuestión de tiempo.",
12
+ },
13
+ {
14
+ percentage: 50,
15
+ verdict: "Probablemente Simulado",
16
+ detail: "Es más probable que seas un código que un ser biológico original.",
17
+ },
18
+ {
19
+ percentage: 10,
20
+ verdict: "Incierto",
21
+ detail: "La realidad física parece resistir, pero la duda sistemática es razonable.",
22
+ },
23
+ {
24
+ percentage: 0,
25
+ verdict: "Probablemente Original",
26
+ detail: "Probablemente seas un original biológico. Disfruta de la realidad 'real' mientras dure.",
27
+ },
28
+ ];
29
+
30
+ export const VERDICTS_EN: VerdictLevel[] = [
31
+ {
32
+ percentage: Infinity,
33
+ verdict: "Almost Certainly Simulated",
34
+ detail: "You are almost certainly in a simulation. System errors are just a matter of time.",
35
+ },
36
+ {
37
+ percentage: 50,
38
+ verdict: "Probably Simulated",
39
+ detail: "It's more likely you're code than an original biological being.",
40
+ },
41
+ {
42
+ percentage: 10,
43
+ verdict: "Uncertain",
44
+ detail: "Physical reality seems to resist, but systematic doubt is reasonable.",
45
+ },
46
+ {
47
+ percentage: 0,
48
+ verdict: "Probably Original",
49
+ detail: "You're probably an original biological being. Enjoy the 'real' reality while it lasts.",
50
+ },
51
+ ];
52
+
53
+ export const VERDICTS_FR: VerdictLevel[] = [
54
+ {
55
+ percentage: Infinity,
56
+ verdict: "Presque Certainement Simulé",
57
+ detail: "Vous êtes presque certainement dans une simulation. Les erreurs du système ne sont qu'une question de temps.",
58
+ },
59
+ {
60
+ percentage: 50,
61
+ verdict: "Probablement Simulé",
62
+ detail: "Il est plus probable que vous soyez du code qu'un être biologique original.",
63
+ },
64
+ {
65
+ percentage: 10,
66
+ verdict: "Incertain",
67
+ detail: "La réalité physique semble résister, mais le doute systématique est raisonnable.",
68
+ },
69
+ {
70
+ percentage: 0,
71
+ verdict: "Probablement Original",
72
+ detail: "Vous êtes probablement un être biologique original. Profitez de la réalité 'réelle' tant qu'elle dure.",
73
+ },
74
+ ];
75
+
76
+ export const DEFAULT_VERDICT_ES = {
77
+ verdict: "Probabilidad de Simulación",
78
+ detail: "Analiza los parámetros para ver si vives en una simulación.",
79
+ };
80
+
81
+ export const DEFAULT_VERDICT_EN = {
82
+ verdict: "Simulation Probability",
83
+ detail: "Analyze the parameters to see if you live in a simulation.",
84
+ };
85
+
86
+ export const DEFAULT_VERDICT_FR = {
87
+ verdict: "Probabilité de Simulation",
88
+ detail: "Analysez les paramètres pour voir si vous vivez dans une simulation.",
89
+ };
@@ -25,6 +25,11 @@ export const content: ToolLocaleContent = {
25
25
  scenario1: 'Scenario 1: Extinction',
26
26
  scenario2: 'Scenario 2: Disinterest',
27
27
  scenario3: 'Scenario 3: We Are Simulated',
28
+ fpDescription: 'Probability that humanity achieves the technical capability to simulate universes with consciousness.',
29
+ flDescription: 'Probability of avoiding collapse (extinction, war) before reaching post-human level.',
30
+ fiDescription: 'Percentage of advanced civilizations that decide to create simulations of their ancestors.',
31
+ nDescription: 'Number of simulated worlds that each advanced civilization typically runs simultaneously.',
32
+ verdictDetail: 'The data suggests it is extremely probable that your consciousness is a software process.',
28
33
  },
29
34
  seo: [
30
35
  {
@@ -25,6 +25,11 @@ export const content: ToolLocaleContent = {
25
25
  scenario1: 'Escenario 1: Extinción',
26
26
  scenario2: 'Escenario 2: Desinterés',
27
27
  scenario3: 'Escenario 3: Estamos Simulados',
28
+ fpDescription: 'Probabilidad de que la humanidad alcance la capacidad técnica para simular universos con conciencia.',
29
+ flDescription: 'Probabilidad de evitar el colapso (extinción, guerras) antes de alcanzar el nivel post-humano.',
30
+ fiDescription: 'Porcentaje de sociedades avanzadas que deciden crear simulaciones de sus antepasados.',
31
+ nDescription: 'Número de mundos simulados que cada civilización avanzada suele ejecutar simultáneamente.',
32
+ verdictDetail: 'Los datos sugieren que es extremadamente probable que tu conciencia sea un proceso de software.',
28
33
  },
29
34
  seo: [
30
35
  {
@@ -25,6 +25,11 @@ export const content: ToolLocaleContent = {
25
25
  scenario1: 'Scénario 1 : Extinction',
26
26
  scenario2: 'Scénario 2 : Désintérêt',
27
27
  scenario3: 'Scénario 3 : Nous sommes simulés',
28
+ fpDescription: 'Probabilité que l\'humanité atteigne la capacité technique de simuler des univers avec conscience.',
29
+ flDescription: 'Probabilité d\'éviter l\'effondrement (extinction, guerre) avant d\'atteindre le niveau post-humain.',
30
+ fiDescription: 'Pourcentage de civilisations avancées qui décident de créer des simulations de leurs ancêtres.',
31
+ nDescription: 'Nombre de mondes simulés que chaque civilisation avancée exécute généralement simultanément.',
32
+ verdictDetail: 'Les données suggèrent qu\'il est extrêmement probable que votre conscience soit un processus logiciel.',
28
33
  },
29
34
  seo: [
30
35
  {
@@ -1,3 +1,13 @@
1
+ import {
2
+ VERDICTS_ES,
3
+ VERDICTS_EN,
4
+ VERDICTS_FR,
5
+ DEFAULT_VERDICT_ES,
6
+ DEFAULT_VERDICT_EN,
7
+ DEFAULT_VERDICT_FR,
8
+ type VerdictLevel,
9
+ } from '../constants';
10
+
1
11
  export interface SimulationResult {
2
12
  probability: number;
3
13
  verdict: string;
@@ -5,32 +15,63 @@ export interface SimulationResult {
5
15
  }
6
16
 
7
17
  export class SimulationEngine {
8
- static calculate(
9
- fp: number,
10
- fl: number,
11
- fi: number,
12
- n: number
18
+ private lang: string = 'es';
19
+
20
+ constructor(lang: string = 'es') {
21
+ this.lang = lang;
22
+ }
23
+
24
+ setLanguage(lang: string) {
25
+ this.lang = lang;
26
+ }
27
+
28
+ private getVerdicts(): VerdictLevel[] {
29
+ switch (this.lang) {
30
+ case 'en':
31
+ return VERDICTS_EN;
32
+ case 'fr':
33
+ return VERDICTS_FR;
34
+ case 'es':
35
+ default:
36
+ return VERDICTS_ES;
37
+ }
38
+ }
39
+
40
+ private getDefaultVerdict() {
41
+ switch (this.lang) {
42
+ case 'en':
43
+ return DEFAULT_VERDICT_EN;
44
+ case 'fr':
45
+ return DEFAULT_VERDICT_FR;
46
+ case 'es':
47
+ default:
48
+ return DEFAULT_VERDICT_ES;
49
+ }
50
+ }
51
+
52
+ calculate(
53
+ fp: number,
54
+ fl: number,
55
+ fi: number,
56
+ n: number
13
57
  ): SimulationResult {
14
-
58
+
15
59
  const numerator = fp * fl * fi * n;
16
60
  const pSim = numerator / (1 + numerator);
17
61
  const percentage = pSim * 100;
18
62
 
19
- let verdict = "Probabilidad de Simulación";
20
- let detail = "Analiza los parámetros para ver si vives en una simulación.";
21
-
22
- if (percentage > 90) {
23
- verdict = "Casi Ciertamente Simulado";
24
- detail = "Estás casi ciertamente en una simulación. Los errores en el sistema son solo cuestión de tiempo.";
25
- } else if (percentage > 50) {
26
- verdict = "Probablemente Simulado";
27
- detail = "Es más probable que seas un código que un ser biológico original.";
28
- } else if (percentage > 10) {
29
- verdict = "Incierto";
30
- detail = "La realidad física parece resistir, pero la duda sistemática es razonable.";
31
- } else {
32
- verdict = "Probablemente Original";
33
- detail = "Probablemente seas un original biológico. Disfruta de la realidad 'real' mientras dure.";
63
+ const verdicts = this.getVerdicts();
64
+ const defaultVerdict = this.getDefaultVerdict();
65
+
66
+ let verdict = defaultVerdict.verdict;
67
+ let detail = defaultVerdict.detail;
68
+
69
+ for (const v of verdicts) {
70
+ if (percentage > v.percentage) {
71
+ verdict = v.verdict;
72
+ detail = v.detail;
73
+ break;
74
+ }
34
75
  }
35
76
 
36
77
  return {