@jjlmoya/utils-science 1.1.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 (73) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +97 -0
  3. package/src/category/i18n/es.ts +97 -0
  4. package/src/category/i18n/fr.ts +96 -0
  5. package/src/category/index.ts +17 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +24 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/seo_length.test.ts +22 -0
  21. package/src/tests/tool_validation.test.ts +17 -0
  22. package/src/tool/asteroid-impact/AsteroidImpact.css +799 -0
  23. package/src/tool/asteroid-impact/bibliography.astro +14 -0
  24. package/src/tool/asteroid-impact/component.astro +436 -0
  25. package/src/tool/asteroid-impact/constants.ts +67 -0
  26. package/src/tool/asteroid-impact/helpers.ts +17 -0
  27. package/src/tool/asteroid-impact/i18n/en.ts +153 -0
  28. package/src/tool/asteroid-impact/i18n/es.ts +153 -0
  29. package/src/tool/asteroid-impact/i18n/fr.ts +153 -0
  30. package/src/tool/asteroid-impact/index.ts +24 -0
  31. package/src/tool/asteroid-impact/logic/impactPhysics.ts +86 -0
  32. package/src/tool/asteroid-impact/script.ts +256 -0
  33. package/src/tool/asteroid-impact/seo.astro +15 -0
  34. package/src/tool/asteroid-impact/ui-helpers.ts +56 -0
  35. package/src/tool/asteroid-impact/ui.ts +69 -0
  36. package/src/tool/asteroid-impact/utils.ts +17 -0
  37. package/src/tool/cellular-renewal/CellularRenewal.css +1 -0
  38. package/src/tool/cellular-renewal/bibliography.astro +14 -0
  39. package/src/tool/cellular-renewal/component.astro +387 -0
  40. package/src/tool/cellular-renewal/i18n/en.ts +170 -0
  41. package/src/tool/cellular-renewal/i18n/es.ts +170 -0
  42. package/src/tool/cellular-renewal/i18n/fr.ts +170 -0
  43. package/src/tool/cellular-renewal/index.ts +24 -0
  44. package/src/tool/cellular-renewal/logic/CellularRenewalEngine.ts +50 -0
  45. package/src/tool/cellular-renewal/seo.astro +14 -0
  46. package/src/tool/colony-counter/ColonyCounter.css +473 -0
  47. package/src/tool/colony-counter/bibliography.astro +14 -0
  48. package/src/tool/colony-counter/component.astro +358 -0
  49. package/src/tool/colony-counter/i18n/en.ts +151 -0
  50. package/src/tool/colony-counter/i18n/es.ts +151 -0
  51. package/src/tool/colony-counter/i18n/fr.ts +151 -0
  52. package/src/tool/colony-counter/index.ts +27 -0
  53. package/src/tool/colony-counter/seo.astro +15 -0
  54. package/src/tool/microwave-detector/MicrowaveDetector.css +122 -0
  55. package/src/tool/microwave-detector/bibliography.astro +14 -0
  56. package/src/tool/microwave-detector/component.astro +650 -0
  57. package/src/tool/microwave-detector/i18n/en.ts +155 -0
  58. package/src/tool/microwave-detector/i18n/es.ts +155 -0
  59. package/src/tool/microwave-detector/i18n/fr.ts +155 -0
  60. package/src/tool/microwave-detector/index.ts +24 -0
  61. package/src/tool/microwave-detector/logic/MicrowaveEngine.ts +89 -0
  62. package/src/tool/microwave-detector/seo.astro +14 -0
  63. package/src/tool/simulation-probability/SimulationProbability.css +1 -0
  64. package/src/tool/simulation-probability/bibliography.astro +14 -0
  65. package/src/tool/simulation-probability/component.astro +348 -0
  66. package/src/tool/simulation-probability/i18n/en.ts +184 -0
  67. package/src/tool/simulation-probability/i18n/es.ts +184 -0
  68. package/src/tool/simulation-probability/i18n/fr.ts +184 -0
  69. package/src/tool/simulation-probability/index.ts +24 -0
  70. package/src/tool/simulation-probability/logic/SimulationEngine.ts +42 -0
  71. package/src/tool/simulation-probability/seo.astro +14 -0
  72. package/src/tools.ts +15 -0
  73. package/src/types.ts +72 -0
@@ -0,0 +1,650 @@
1
+ ---
2
+ import "./MicrowaveDetector.css";
3
+ import { Icon } from "astro-icon/components";
4
+ ---
5
+
6
+ <div class="microwave-detector-container" id="microwave-detector-root">
7
+ <div class="microwave-modal" id="initial-modal">
8
+ <div class="microwave-modal-content">
9
+ <div class="microwave-modal-icon">
10
+ <Icon name="mdi:wifi-alert" />
11
+ </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>
20
+ <button id="start-btn" class="microwave-btn-start">
21
+ Entendido, Iniciar Escaneo
22
+ </button>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="microwave-detector-panel">
27
+ <div class="microwave-header">
28
+ <div>
29
+ <span class="microwave-label">RF Interference Monitor</span>
30
+ <h3 class="microwave-title">MW-LEAK DETECTOR v2.0</h3>
31
+ </div>
32
+ <div class="microwave-status-badge">
33
+ <div id="status-dot" class="microwave-status-dot"></div>
34
+ <span id="status-text" class="microwave-status-text">Estático</span>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="microwave-canvas-container">
39
+ <canvas id="interference-canvas"></canvas>
40
+ <div class="microwave-canvas-overlay">
41
+ <div id="big-value-bg" class="microwave-big-value">00</div>
42
+ <div class="microwave-jitter-display">
43
+ <span id="jitter-value" class="microwave-jitter-value">0.0</span>
44
+ <span class="microwave-jitter-unit">ms / jitter</span>
45
+ </div>
46
+ </div>
47
+ <div class="microwave-canvas-grid"></div>
48
+ </div>
49
+
50
+ <div class="microwave-content-grid">
51
+ <div>
52
+ <div id="verdict-container" class="microwave-verdict">
53
+ <h4 id="verdict-label" class="microwave-verdict-label">Sistema Listado</h4>
54
+ <p id="verdict-description" class="microwave-verdict-desc">
55
+ Conecta para iniciar el análisis térmico de interferencia.
56
+ </p>
57
+ </div>
58
+ <div class="microwave-latency-info">
59
+ <div class="microwave-latency-badge">
60
+ <Icon name="mdi:pulse" />
61
+ LAT: <span id="latency-val">0ms</span>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <div class="microwave-audio-section">
67
+ <div class="microwave-audio-label">
68
+ <span>Audio Feedback</span>
69
+ <span id="audio-status">ON</span>
70
+ </div>
71
+ <button id="toggle-audio-btn" class="microwave-btn-audio">
72
+ <Icon name="mdi:volume-high" />
73
+ MUTE / UNMUTE
74
+ </button>
75
+ </div>
76
+ </div>
77
+
78
+ <div class="microwave-scanline"></div>
79
+ </div>
80
+ </div>
81
+
82
+ <style is:inline define:vars={{}}>
83
+ :root {
84
+ --microwave-primary: #e11d48;
85
+ --microwave-text: #0f172a;
86
+ --microwave-text-muted: #64748b;
87
+ --microwave-bg: #fff;
88
+ --microwave-bg-secondary: #fafbfc;
89
+ --microwave-bg-tertiary: #f1f5f9;
90
+ --microwave-border: #e2e8f0;
91
+ --microwave-border-light: #f1f5f9;
92
+ --microwave-badge-bg: rgba(255, 255, 255, 0.9);
93
+ --microwave-panel-shadow: rgba(0, 0, 0, 0.05);
94
+ --microwave-modal-backdrop: rgba(255, 255, 255, 0.98);
95
+ --microwave-modal-icon-bg: var(--microwave-bg-tertiary);
96
+ --microwave-emerald: #10b981;
97
+ --microwave-yellow: #eab308;
98
+ --microwave-orange: #f97316;
99
+ --microwave-red: #dc2626;
100
+ --microwave-indigo: #4f46e5;
101
+ }
102
+
103
+ .theme-dark {
104
+ --microwave-text: #fff;
105
+ --microwave-text-muted: #94a3b8;
106
+ --microwave-bg: #000;
107
+ --microwave-bg-secondary: #0f172a;
108
+ --microwave-bg-tertiary: #1e293b;
109
+ --microwave-border: #1e293b;
110
+ --microwave-border-light: #334155;
111
+ --microwave-badge-bg: rgba(15, 23, 42, 0.5);
112
+ --microwave-panel-shadow: rgba(0, 0, 0, 0.3);
113
+ --microwave-modal-backdrop: rgba(15, 23, 42, 0.95);
114
+ --microwave-modal-icon-bg: var(--microwave-bg-tertiary);
115
+ }
116
+
117
+ .microwave-detector-container {
118
+ max-width: 56rem;
119
+ margin: 0 auto;
120
+ padding: 1rem;
121
+
122
+ @media (min-width: 640px) {
123
+ padding: 2rem;
124
+ }
125
+
126
+ position: relative;
127
+ }
128
+
129
+ .microwave-modal {
130
+ position: fixed;
131
+ inset: 0;
132
+ z-index: 50;
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ padding: 1rem;
137
+ background-color: var(--microwave-modal-backdrop);
138
+ backdrop-filter: blur(12px);
139
+ }
140
+
141
+ .microwave-modal-content {
142
+ background-color: var(--microwave-bg);
143
+ border: 1px solid var(--microwave-border);
144
+ border-radius: 1.5rem;
145
+ padding: 2rem;
146
+ max-width: 28rem;
147
+ width: 100%;
148
+ text-align: center;
149
+ animation: zoomIn 0.3s ease-out;
150
+ box-shadow: 0 20px 25px -5px var(--microwave-panel-shadow);
151
+ }
152
+
153
+ .microwave-modal-icon {
154
+ background-color: var(--microwave-modal-icon-bg);
155
+ width: 5rem;
156
+ height: 5rem;
157
+ border-radius: 9999px;
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ margin: 0 auto 1.5rem;
162
+ }
163
+
164
+ .microwave-modal-icon svg {
165
+ width: 3rem;
166
+ height: 3rem;
167
+ color: var(--microwave-primary);
168
+ }
169
+
170
+ .microwave-modal-content h2 {
171
+ font-size: 1.5rem;
172
+ font-weight: 700;
173
+ color: var(--microwave-text);
174
+ margin-bottom: 1rem;
175
+ }
176
+
177
+ .microwave-modal-content p {
178
+ color: var(--microwave-text-muted);
179
+ margin-bottom: 2rem;
180
+ line-height: 1.6;
181
+ }
182
+
183
+ .microwave-btn-start {
184
+ width: 100%;
185
+ padding: 1rem;
186
+ background-color: var(--microwave-indigo);
187
+ color: white;
188
+ border: none;
189
+ border-radius: 0.75rem;
190
+ font-weight: 700;
191
+ font-size: 1rem;
192
+ cursor: pointer;
193
+ transition: background-color 0.2s, box-shadow 0.2s;
194
+ box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.2);
195
+ }
196
+
197
+ .microwave-btn-start:hover {
198
+ background-color: #4338ca;
199
+ opacity: 0.9;
200
+ }
201
+
202
+ .microwave-btn-start:active {
203
+ transform: scale(0.95);
204
+ }
205
+
206
+ .microwave-modal.hidden {
207
+ display: none;
208
+ }
209
+
210
+ .microwave-detector-panel {
211
+ background-color: var(--microwave-bg);
212
+ border: 1px solid var(--microwave-border);
213
+ border-radius: 2.5rem;
214
+ box-shadow: 0 20px 25px -5px var(--microwave-panel-shadow);
215
+ overflow: hidden;
216
+ padding: 1.5rem;
217
+ position: relative;
218
+
219
+ @media (min-width: 640px) {
220
+ padding: 2.5rem;
221
+ }
222
+ }
223
+
224
+ .microwave-header {
225
+ display: flex;
226
+ justify-content: space-between;
227
+ align-items: flex-start;
228
+ margin-bottom: 2rem;
229
+ }
230
+
231
+ .microwave-label {
232
+ color: var(--microwave-primary);
233
+ font-size: 0.75rem;
234
+ text-transform: uppercase;
235
+ letter-spacing: 0.3em;
236
+ font-weight: 700;
237
+ display: block;
238
+ margin-bottom: 0.25rem;
239
+ }
240
+
241
+ .microwave-title {
242
+ color: var(--microwave-text);
243
+ font-size: 1.875rem;
244
+ font-weight: 900;
245
+ font-style: italic;
246
+ letter-spacing: -0.02em;
247
+ }
248
+
249
+ .microwave-status-badge {
250
+ display: flex;
251
+ align-items: center;
252
+ gap: 0.5rem;
253
+ background-color: var(--microwave-badge-bg);
254
+ padding: 0.5rem 1rem;
255
+ border-radius: 9999px;
256
+ border: 1px solid var(--microwave-border);
257
+ }
258
+
259
+ .microwave-status-dot {
260
+ width: 0.75rem;
261
+ height: 0.75rem;
262
+ border-radius: 50%;
263
+ background-color: var(--microwave-secondary);
264
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
265
+ }
266
+
267
+ .microwave-status-text {
268
+ color: var(--microwave-text-muted);
269
+ font-size: 0.625rem;
270
+ text-transform: uppercase;
271
+ }
272
+
273
+ @keyframes pulse {
274
+ 0%, 100% {
275
+ opacity: 1;
276
+ }
277
+ 50% {
278
+ opacity: 0.5;
279
+ }
280
+ }
281
+
282
+ .microwave-canvas-container {
283
+ position: relative;
284
+ aspect-ratio: 2.3333;
285
+ background-color: var(--microwave-bg-secondary);
286
+ border: 1px solid var(--microwave-border);
287
+ border-radius: 1rem;
288
+ overflow: hidden;
289
+ margin-bottom: 2rem;
290
+ }
291
+
292
+ #interference-canvas {
293
+ width: 100%;
294
+ height: 100%;
295
+ opacity: 0.6;
296
+ display: block;
297
+ }
298
+
299
+ .microwave-canvas-overlay {
300
+ position: absolute;
301
+ inset: 0;
302
+ display: flex;
303
+ flex-direction: column;
304
+ align-items: center;
305
+ justify-content: center;
306
+ pointer-events: none;
307
+ }
308
+
309
+ .microwave-big-value {
310
+ font-size: 120px;
311
+
312
+ @media (min-width: 640px) {
313
+ font-size: 180px;
314
+ }
315
+
316
+ font-weight: 900;
317
+ font-style: italic;
318
+ letter-spacing: -0.02em;
319
+ color: white;
320
+ opacity: 0.05;
321
+ user-select: none;
322
+ line-height: 1;
323
+ }
324
+
325
+ .microwave-jitter-display {
326
+ position: absolute;
327
+ inset: 0;
328
+ display: flex;
329
+ flex-direction: column;
330
+ align-items: center;
331
+ justify-content: center;
332
+ }
333
+
334
+ .microwave-jitter-value {
335
+ color: var(--microwave-primary);
336
+ font-size: 40px;
337
+
338
+ @media (min-width: 640px) {
339
+ font-size: 60px;
340
+ }
341
+
342
+ font-weight: 900;
343
+ }
344
+
345
+ .microwave-jitter-unit {
346
+ color: var(--microwave-text-muted);
347
+ font-size: 0.75rem;
348
+ text-transform: uppercase;
349
+ letter-spacing: 0.05em;
350
+ margin-top: -0.625rem;
351
+ }
352
+
353
+ .microwave-canvas-grid {
354
+ position: absolute;
355
+ inset: 0;
356
+ pointer-events: none;
357
+ z-index: 10;
358
+ opacity: 0.2;
359
+ background-image: linear-gradient(var(--microwave-border-light) 1px, transparent 1px), linear-gradient(90deg, var(--microwave-border-light) 1px, transparent 1px);
360
+ background-size: 40px 40px;
361
+ }
362
+
363
+ .microwave-content-grid {
364
+ display: grid;
365
+ grid-template-columns: 1fr;
366
+
367
+ @media (min-width: 768px) {
368
+ grid-template-columns: 1fr 1fr;
369
+ }
370
+
371
+ gap: 2rem;
372
+ align-items: flex-end;
373
+ }
374
+
375
+ .microwave-verdict {
376
+ padding: 1.5rem;
377
+ border-radius: 1rem;
378
+ border: 1px solid var(--microwave-border);
379
+ background-color: var(--microwave-bg-secondary);
380
+ transition: all 0.3s;
381
+ }
382
+
383
+ .microwave-verdict-label {
384
+ font-size: 0.75rem;
385
+ font-weight: 700;
386
+ color: var(--microwave-text-muted);
387
+ text-transform: uppercase;
388
+ letter-spacing: 0.05em;
389
+ margin-bottom: 0.5rem;
390
+ }
391
+
392
+ .microwave-verdict-desc {
393
+ color: var(--microwave-text);
394
+ font-weight: 500;
395
+ }
396
+
397
+ .microwave-latency-info {
398
+ margin-top: 1rem;
399
+ display: flex;
400
+ align-items: center;
401
+ gap: 1rem;
402
+ color: var(--microwave-text-muted);
403
+ }
404
+
405
+ .microwave-latency-badge {
406
+ display: flex;
407
+ align-items: center;
408
+ gap: 0.5rem;
409
+ font-size: 0.75rem;
410
+ }
411
+
412
+ .microwave-latency-badge svg {
413
+ width: 1rem;
414
+ height: 1rem;
415
+ }
416
+
417
+ .microwave-audio-section {
418
+ display: flex;
419
+ flex-direction: column;
420
+ gap: 1rem;
421
+ }
422
+
423
+ .microwave-audio-label {
424
+ display: flex;
425
+ justify-content: space-between;
426
+ align-items: center;
427
+ font-size: 0.75rem;
428
+ color: var(--microwave-text-muted);
429
+ text-transform: uppercase;
430
+ letter-spacing: 0.05em;
431
+ padding: 0 0.5rem;
432
+ }
433
+
434
+ .microwave-btn-audio {
435
+ height: 3rem;
436
+ border-radius: 0.75rem;
437
+ background-color: var(--microwave-bg-tertiary);
438
+ color: var(--microwave-text-muted);
439
+ border: 1px solid var(--microwave-border);
440
+ transition: all 0.2s;
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: center;
444
+ gap: 0.5rem;
445
+ cursor: pointer;
446
+ font-weight: 600;
447
+ font-size: 0.875rem;
448
+ text-transform: uppercase;
449
+ }
450
+
451
+ .microwave-btn-audio:hover {
452
+ background-color: var(--microwave-border-light);
453
+ }
454
+
455
+ .microwave-btn-audio svg {
456
+ width: 1.25rem;
457
+ height: 1.25rem;
458
+ }
459
+
460
+ .microwave-scanline {
461
+ position: absolute;
462
+ inset: 0;
463
+ pointer-events: none;
464
+ z-index: 20;
465
+ opacity: 0.03;
466
+ overflow: hidden;
467
+ border-radius: 2.5rem;
468
+ }
469
+
470
+ .microwave-scanline::before {
471
+ content: '';
472
+ position: absolute;
473
+ height: 4px;
474
+ background-color: white;
475
+ width: 100%;
476
+ animation: scanline 4s linear infinite;
477
+ }
478
+
479
+ @keyframes scanline {
480
+ 0% {
481
+ transform: translateY(-100%);
482
+ }
483
+ 100% {
484
+ transform: translateY(1000%);
485
+ }
486
+ }
487
+
488
+ @keyframes zoomIn {
489
+ from {
490
+ opacity: 0;
491
+ transform: scale(0.95);
492
+ }
493
+ to {
494
+ opacity: 1;
495
+ transform: scale(1);
496
+ }
497
+ }
498
+ </style>
499
+
500
+ <script>
501
+ import { MicrowaveEngine } from './logic/MicrowaveEngine';
502
+
503
+ const root = document.getElementById("microwave-detector-root");
504
+ if (root) {
505
+ const startBtn = document.getElementById("start-btn");
506
+ const modal = document.getElementById("initial-modal");
507
+ const canvas = document.getElementById("interference-canvas");
508
+ const ctx = canvas?.getContext("2d");
509
+
510
+ const jitterDisplay = document.getElementById("jitter-value");
511
+ const bigValueBg = document.getElementById("big-value-bg");
512
+ const latencyVal = document.getElementById("latency-val");
513
+ const verdictLabel = document.getElementById("verdict-label");
514
+ const verdictDesc = document.getElementById("verdict-description");
515
+ const verdictContainer = document.getElementById("verdict-container");
516
+ const statusDot = document.getElementById("status-dot");
517
+ const statusText = document.getElementById("status-text");
518
+ const toggleAudioBtn = document.getElementById("toggle-audio-btn");
519
+
520
+ const engine = new MicrowaveEngine();
521
+ let running = false;
522
+ let audioEnabled = true;
523
+ let audioCtx = null;
524
+
525
+ const history = new Array(60).fill(0);
526
+
527
+ function resizeCanvas() {
528
+ if (!canvas) return;
529
+ canvas.width = canvas.clientWidth;
530
+ canvas.height = canvas.clientHeight;
531
+ }
532
+
533
+ function drawGraph() {
534
+ if (!ctx || !canvas) return;
535
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
536
+
537
+ ctx.beginPath();
538
+ ctx.strokeStyle = "#4f46e5";
539
+ ctx.lineWidth = 3;
540
+ ctx.lineJoin = "round";
541
+
542
+ const step = canvas.width / (history.length - 1);
543
+ const maxHistory = Math.max(...history, 50);
544
+
545
+ history.forEach((val, i) => {
546
+ const x = i * step;
547
+ const y = canvas.height - (val / maxHistory) * canvas.height * 0.8 - 20;
548
+ if (i === 0) ctx.moveTo(x, y);
549
+ else ctx.lineTo(x, y);
550
+ });
551
+ ctx.stroke();
552
+
553
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
554
+ gradient.addColorStop(0, "rgba(79, 70, 229, 0.2)");
555
+ gradient.addColorStop(1, "transparent");
556
+ ctx.lineTo(canvas.width, canvas.height);
557
+ ctx.lineTo(0, canvas.height);
558
+ ctx.fillStyle = gradient;
559
+ ctx.fill();
560
+ }
561
+
562
+ async function playClick(_intensity) {
563
+ if (!audioEnabled) return;
564
+ if (!audioCtx)
565
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
566
+
567
+ const osc = audioCtx.createOscillator();
568
+ const gain = audioCtx.createGain();
569
+
570
+ osc.type = "square";
571
+ osc.frequency.setValueAtTime(40 + Math.random() * 20, audioCtx.currentTime);
572
+
573
+ gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
574
+ gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 0.05);
575
+
576
+ osc.connect(gain);
577
+ gain.connect(audioCtx.destination);
578
+
579
+ osc.start();
580
+ osc.stop(audioCtx.currentTime + 0.05);
581
+ }
582
+
583
+ function updateVerdictDisplay(level: { label: string; description: string; color: string }) {
584
+ if (verdictLabel) verdictLabel.textContent = level.label;
585
+ if (verdictDesc) verdictDesc.textContent = level.description;
586
+
587
+ const colorMap = {
588
+ emerald: { bg: "rgba(16, 185, 129, 0.1)", border: "rgba(16, 185, 129, 0.3)", text: "#10b981" },
589
+ yellow: { bg: "rgba(234, 179, 8, 0.1)", border: "rgba(234, 179, 8, 0.3)", text: "#eab308" },
590
+ orange: { bg: "rgba(249, 115, 22, 0.1)", border: "rgba(249, 115, 22, 0.3)", text: "#f97316" },
591
+ red: { bg: "rgba(220, 38, 38, 0.2)", border: "rgba(220, 38, 38, 0.5)", text: "#dc2626" },
592
+ };
593
+
594
+ if (verdictContainer) {
595
+ const colors = colorMap[level.color] || colorMap.emerald;
596
+ verdictContainer.style.backgroundColor = colors.bg;
597
+ verdictContainer.style.borderColor = colors.border;
598
+ verdictContainer.style.color = colors.text;
599
+ }
600
+
601
+ if (statusDot) {
602
+ const colorValues = {
603
+ emerald: "#10b981",
604
+ yellow: "#eab308",
605
+ orange: "#f97316",
606
+ red: "#dc2626",
607
+ };
608
+ statusDot.style.backgroundColor = colorValues[level.color] || colorValues.emerald;
609
+ }
610
+ }
611
+
612
+ async function scan() {
613
+ if (!running) return;
614
+
615
+ const result = await engine.measurePing();
616
+ history.push(result.jitter);
617
+ history.shift();
618
+
619
+ if (jitterDisplay) jitterDisplay.textContent = result.jitter.toFixed(1);
620
+ if (bigValueBg) bigValueBg.textContent = Math.floor(result.jitter).toString().padStart(2, "0");
621
+ if (latencyVal) latencyVal.textContent = `${Math.floor(result.latency)}ms`;
622
+
623
+ const level = MicrowaveEngine.getInterferenceLevel(result.jitter);
624
+ updateVerdictDisplay(level);
625
+
626
+ if (result.jitter > 1) {
627
+ playClick(result.jitter);
628
+ }
629
+
630
+ drawGraph();
631
+ setTimeout(scan, 200 + Math.random() * 100);
632
+ }
633
+
634
+ startBtn?.addEventListener("click", () => {
635
+ if (modal) modal.classList.add("hidden");
636
+ running = true;
637
+ resizeCanvas();
638
+ if (statusText) statusText.textContent = "Scanning...";
639
+ scan();
640
+ });
641
+
642
+ toggleAudioBtn?.addEventListener("click", () => {
643
+ audioEnabled = !audioEnabled;
644
+ const statusLabel = document.getElementById("audio-status");
645
+ if (statusLabel) statusLabel.textContent = audioEnabled ? "ON" : "OFF";
646
+ });
647
+
648
+ window.addEventListener("resize", resizeCanvas);
649
+ }
650
+ </script>