@jjlmoya/utils-hardware 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 (85) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +107 -0
  3. package/src/category/i18n/es.ts +107 -0
  4. package/src/category/i18n/fr.ts +107 -0
  5. package/src/category/index.ts +18 -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 +26 -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/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +23 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/batteryHealthEstimator/bibliography.astro +14 -0
  25. package/src/tool/batteryHealthEstimator/component.astro +606 -0
  26. package/src/tool/batteryHealthEstimator/i18n/en.ts +158 -0
  27. package/src/tool/batteryHealthEstimator/i18n/es.ts +158 -0
  28. package/src/tool/batteryHealthEstimator/i18n/fr.ts +158 -0
  29. package/src/tool/batteryHealthEstimator/index.ts +34 -0
  30. package/src/tool/batteryHealthEstimator/seo.astro +14 -0
  31. package/src/tool/batteryHealthEstimator/ui.ts +31 -0
  32. package/src/tool/deadPixelTest/bibliography.astro +31 -0
  33. package/src/tool/deadPixelTest/component.astro +717 -0
  34. package/src/tool/deadPixelTest/i18n/en.ts +218 -0
  35. package/src/tool/deadPixelTest/i18n/es.ts +218 -0
  36. package/src/tool/deadPixelTest/i18n/fr.ts +218 -0
  37. package/src/tool/deadPixelTest/index.ts +34 -0
  38. package/src/tool/deadPixelTest/seo.astro +14 -0
  39. package/src/tool/deadPixelTest/ui.ts +20 -0
  40. package/src/tool/gamepadTest/bibliography.astro +30 -0
  41. package/src/tool/gamepadTest/component.astro +1419 -0
  42. package/src/tool/gamepadTest/i18n/en.ts +186 -0
  43. package/src/tool/gamepadTest/i18n/es.ts +186 -0
  44. package/src/tool/gamepadTest/i18n/fr.ts +186 -0
  45. package/src/tool/gamepadTest/index.ts +34 -0
  46. package/src/tool/gamepadTest/seo.astro +14 -0
  47. package/src/tool/gamepadTest/ui.ts +57 -0
  48. package/src/tool/gamepadVibrationTester/bibliography.astro +14 -0
  49. package/src/tool/gamepadVibrationTester/component.astro +694 -0
  50. package/src/tool/gamepadVibrationTester/i18n/en.ts +155 -0
  51. package/src/tool/gamepadVibrationTester/i18n/es.ts +155 -0
  52. package/src/tool/gamepadVibrationTester/i18n/fr.ts +150 -0
  53. package/src/tool/gamepadVibrationTester/index.ts +34 -0
  54. package/src/tool/gamepadVibrationTester/seo.astro +14 -0
  55. package/src/tool/gamepadVibrationTester/ui.ts +28 -0
  56. package/src/tool/keyboardTest/bibliography.astro +30 -0
  57. package/src/tool/keyboardTest/component.astro +601 -0
  58. package/src/tool/keyboardTest/i18n/en.ts +146 -0
  59. package/src/tool/keyboardTest/i18n/es.ts +146 -0
  60. package/src/tool/keyboardTest/i18n/fr.ts +146 -0
  61. package/src/tool/keyboardTest/index.ts +34 -0
  62. package/src/tool/keyboardTest/seo.astro +14 -0
  63. package/src/tool/keyboardTest/ui.ts +8 -0
  64. package/src/tool/mousePollingTest/bibliography.astro +14 -0
  65. package/src/tool/mousePollingTest/component.astro +237 -0
  66. package/src/tool/mousePollingTest/i18n/en.ts +135 -0
  67. package/src/tool/mousePollingTest/i18n/es.ts +135 -0
  68. package/src/tool/mousePollingTest/i18n/fr.ts +135 -0
  69. package/src/tool/mousePollingTest/index.ts +34 -0
  70. package/src/tool/mousePollingTest/logic/MovingAverage.ts +20 -0
  71. package/src/tool/mousePollingTest/logic/RatonManager.ts +97 -0
  72. package/src/tool/mousePollingTest/logic/StatisticalFilter.ts +48 -0
  73. package/src/tool/mousePollingTest/seo.astro +14 -0
  74. package/src/tool/mousePollingTest/ui.ts +8 -0
  75. package/src/tool/mousePollingTest/worker/mouseWorker.ts +50 -0
  76. package/src/tool/toneGenerator/bibliography.astro +14 -0
  77. package/src/tool/toneGenerator/component.astro +571 -0
  78. package/src/tool/toneGenerator/i18n/en.ts +146 -0
  79. package/src/tool/toneGenerator/i18n/es.ts +146 -0
  80. package/src/tool/toneGenerator/i18n/fr.ts +146 -0
  81. package/src/tool/toneGenerator/index.ts +34 -0
  82. package/src/tool/toneGenerator/seo.astro +14 -0
  83. package/src/tool/toneGenerator/ui.ts +19 -0
  84. package/src/tools.ts +10 -0
  85. package/src/types.ts +72 -0
@@ -0,0 +1,717 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { PixelesPantallaUI } from './ui';
4
+
5
+ interface Props {
6
+ locale?: KnownLocale;
7
+ ui?: Record<string, unknown>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
11
+ const t = (ui ?? {}) as PixelesPantallaUI;
12
+ ---
13
+
14
+ <div class="pt-root" data-badge={t.badge} data-title={t.title} data-description={t.description} data-btn-start-test={t.btnStartTest} data-btn-start-fix={t.btnStartFix} data-kbd-fullscreen={t.kbdFullscreen} data-kbd-fullscreen-label={t.kbdFullscreenLabel} data-kbd-space={t.kbdSpace} data-kbd-space-label={t.kbdSpaceLabel} data-kbd-esc={t.kbdEsc} data-kbd-esc-label={t.kbdEscLabel} data-color-black={t.colorBlack} data-color-white={t.colorWhite} data-color-red={t.colorRed} data-color-green={t.colorGreen} data-color-blue={t.colorBlue} data-btn-toggle-fixer={t.btnToggleFixer} data-btn-exit={t.btnExit}>
15
+ <style is:global>
16
+ .pt-root {
17
+ --pt-accent: #4f46e5;
18
+ --pt-accent-hover: #4338ca;
19
+ --pt-accent-bg: #e0e7ff;
20
+ --pt-accent-text: #4338ca;
21
+ --pt-surface: #fff;
22
+ --pt-surface-alt: #f8fafc;
23
+ --pt-border: #e2e8f0;
24
+ --pt-text: #0f172a;
25
+ --pt-text-muted: #64748b;
26
+ --pt-kbd-bg: #f1f5f9;
27
+ --pt-monitor-body: #1e293b;
28
+ --pt-monitor-screen: #020617;
29
+ --pt-shadow: rgba(0, 0, 0, 0.1);
30
+ }
31
+
32
+ .theme-dark .pt-root {
33
+ --pt-surface: #0f172a;
34
+ --pt-surface-alt: #1e293b;
35
+ --pt-border: #334155;
36
+ --pt-text: #f1f5f9;
37
+ --pt-text-muted: #94a3b8;
38
+ --pt-kbd-bg: #1e293b;
39
+ --pt-accent-bg: rgba(79, 70, 229, 0.3);
40
+ --pt-accent-text: #a5b4fc;
41
+ --pt-monitor-body: #334155;
42
+ --pt-monitor-screen: #1e293b;
43
+ }
44
+
45
+ body.is-widget .pt-root {
46
+ padding: 0;
47
+ }
48
+
49
+ body.is-widget .pt-dashboard {
50
+ background: transparent;
51
+ box-shadow: none;
52
+ border: none;
53
+ padding: 1rem;
54
+ }
55
+ </style>
56
+
57
+ <div class="pt-wrapper">
58
+ <div class="pt-dashboard" id="dashboard">
59
+ <div class="pt-grid">
60
+ <div class="pt-content">
61
+ <div class="pt-badge-container">
62
+ <span class="pt-badge">
63
+ <svg class="pt-badge-icon" viewBox="0 0 24 24" fill="currentColor">
64
+ <path d="M17 2H7c-1.1 0-1.99.9-1.99 2L5 21c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V4c0-1.1-.9-2-2-2zm-5 18c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/>
65
+ </svg>
66
+ {t.badge}
67
+ </span>
68
+ </div>
69
+
70
+ <h2 class="pt-title">{t.title}</h2>
71
+ <p class="pt-description">{t.description}</p>
72
+
73
+ <div class="pt-buttons">
74
+ <button id="btn-start-test" class="pt-btn pt-btn-primary">
75
+ <svg class="pt-btn-icon" viewBox="0 0 24 24" fill="currentColor">
76
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
77
+ </svg>
78
+ {t.btnStartTest}
79
+ </button>
80
+ <button id="btn-start-fix" class="pt-btn pt-btn-secondary">
81
+ <svg class="pt-btn-icon" viewBox="0 0 24 24" fill="currentColor">
82
+ <path d="M3 13h2v8H3zM17 13h2v8h-2zM5.73 10.04c2.96-2.96 7.78-2.96 10.74 0 .39.39.39 1.02 0 1.41l-1.41 1.41c-.39.39-1.02.39-1.41 0-1.95-1.95-5.12-1.95-7.07 0-.39.39-1.02.39-1.41 0L5.73 11.45c-.39-.39-.39-1.02 0-1.41zM10.59 5.59c1.77-1.77 4.13-2.29 6.36-1.64l2.12-2.12c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-2.12 2.12c.65 2.23.13 4.59-1.64 6.36-1.95 1.95-4.74 2.35-7.07 1.29-.39-.19-.52-.65-.33-1.04.19-.39.65-.52 1.04-.33 1.81.92 4.08.68 5.59-.83 1.27-1.27 1.64-3.1.98-4.8.21.01.42-.06.59-.23 1.41-1.41 1.41-3.71 0-5.12-1.41-1.41-3.71-1.41-5.12 0-.16.16-.24.38-.23.59l-1.04-1.04zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"/>
83
+ </svg>
84
+ {t.btnStartFix}
85
+ </button>
86
+ </div>
87
+
88
+ <div class="pt-shortcuts">
89
+ <div class="pt-shortcut">
90
+ <kbd class="pt-kbd">{t.kbdFullscreen}</kbd>
91
+ <span>{t.kbdFullscreenLabel}</span>
92
+ </div>
93
+ <span>•</span>
94
+ <div class="pt-shortcut">
95
+ <kbd class="pt-kbd">{t.kbdSpace}</kbd>
96
+ <span>{t.kbdSpaceLabel}</span>
97
+ </div>
98
+ <span>•</span>
99
+ <div class="pt-shortcut">
100
+ <kbd class="pt-kbd">{t.kbdEsc}</kbd>
101
+ <span>{t.kbdEscLabel}</span>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <div class="pt-monitor-wrapper">
107
+ <div class="pt-monitor" id="monitor-preview">
108
+ <div class="pt-monitor-screen-wrapper">
109
+ <div class="pt-monitor-shine"></div>
110
+ <div class="pt-monitor-screen" id="preview-screen">
111
+ <div class="pt-monitor-section" style="background-color: #dc2626;"></div>
112
+ <div class="pt-monitor-section" style="background-color: #16a34a;"></div>
113
+ <div class="pt-monitor-section" style="background-color: #2563eb;"></div>
114
+ </div>
115
+ <div class="pt-fixer-preview" id="preview-fixer"></div>
116
+ </div>
117
+ <div class="pt-monitor-stand"></div>
118
+ <div class="pt-monitor-base"></div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ <div id="test-overlay" class="pt-overlay-hidden">
125
+ <div id="test-bg" class="pt-overlay-bg"></div>
126
+
127
+ <div id="fixer-box" class="pt-fixer-box pt-fixer-hidden">
128
+ <div class="pt-noise-canvas" id="noise-canvas"></div>
129
+ <button id="close-fixer" class="pt-close-fixer">
130
+ <svg viewBox="0 0 24 24" fill="currentColor">
131
+ <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
132
+ </svg>
133
+ </button>
134
+ </div>
135
+
136
+ <div id="test-controls" class="pt-controls">
137
+ <div class="pt-color-buttons">
138
+ <button class="pt-color-btn" data-color="#000" style="background-color: #000;" title={t.colorBlack}></button>
139
+ <button class="pt-color-btn" data-color="#fff" style="background-color: #fff;" title={t.colorWhite}></button>
140
+ <button class="pt-color-btn" data-color="#dc2626" style="background-color: #dc2626;" title={t.colorRed}></button>
141
+ <button class="pt-color-btn" data-color="#16a34a" style="background-color: #16a34a;" title={t.colorGreen}></button>
142
+ <button class="pt-color-btn" data-color="#2563eb" style="background-color: #2563eb;" title={t.colorBlue}></button>
143
+ </div>
144
+ <div class="pt-divider"></div>
145
+ <button id="toggle-fixer" class="pt-control-btn" title={t.btnToggleFixer}>
146
+ <svg viewBox="0 0 24 24" fill="currentColor">
147
+ <path d="M14 6l-3.732 6 3.732 6h4v-12h-4zm2 2h.952v8H16v-8zM4 6l3.732 6L4 18h4v-12H4zm2 2h.952v8H6v-8zm6-2l3.732 6-3.732 6h4v-12h-4zm2 2h.952v8h-.952v-8z"/>
148
+ </svg>
149
+ </button>
150
+ <div class="pt-divider"></div>
151
+ <button id="btn-exit" class="pt-control-btn pt-control-btn-exit" title={t.btnExit}>
152
+ <svg viewBox="0 0 24 24" fill="currentColor">
153
+ <path d="M17 12h-5v5h5v-5zM16 1v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2h-1V1h-2zm3 19H5V8h14v12z"/>
154
+ </svg>
155
+ </button>
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <style>
161
+ .pt-wrapper {
162
+ width: 100%;
163
+ max-width: 80rem;
164
+ margin: 0 auto;
165
+ padding: 1rem;
166
+ color: var(--pt-text);
167
+ }
168
+
169
+ .pt-dashboard {
170
+ background: var(--pt-surface);
171
+ border-radius: 1.5rem;
172
+ padding: 2rem;
173
+ box-shadow: 0 20px 25px -5px var(--pt-shadow);
174
+ border: 1px solid var(--pt-border);
175
+ transition: all 0.5s ease;
176
+ }
177
+
178
+ .pt-grid {
179
+ display: grid;
180
+ grid-template-columns: 1fr 1fr;
181
+ gap: 3rem;
182
+ align-items: center;
183
+ }
184
+
185
+ @media (max-width: 640px) {
186
+ .pt-grid {
187
+ grid-template-columns: 1fr;
188
+ }
189
+ }
190
+
191
+ .pt-badge-container {
192
+ margin-bottom: 1.5rem;
193
+ }
194
+
195
+ .pt-badge {
196
+ display: inline-flex;
197
+ align-items: center;
198
+ gap: 0.5rem;
199
+ padding: 0.25rem 0.75rem;
200
+ border-radius: 9999px;
201
+ background-color: var(--pt-accent-bg);
202
+ color: var(--pt-accent-text);
203
+ font-size: 0.75rem;
204
+ font-weight: 700;
205
+ letter-spacing: 0.05em;
206
+ text-transform: uppercase;
207
+ }
208
+
209
+ .pt-badge-icon {
210
+ width: 1rem;
211
+ height: 1rem;
212
+ }
213
+
214
+ .pt-title {
215
+ font-size: 2.25rem;
216
+ font-weight: 900;
217
+ letter-spacing: -0.02em;
218
+ margin-bottom: 1rem;
219
+ line-height: 1.2;
220
+ }
221
+
222
+ @media (min-width: 768px) {
223
+ .pt-title {
224
+ font-size: 3rem;
225
+ }
226
+ }
227
+
228
+ .pt-description {
229
+ font-size: 1.125rem;
230
+ color: var(--pt-text-muted);
231
+ line-height: 1.625;
232
+ margin-bottom: 1.5rem;
233
+ }
234
+
235
+ .pt-buttons {
236
+ display: flex;
237
+ gap: 1rem;
238
+ margin-bottom: 1.5rem;
239
+ flex-wrap: wrap;
240
+ }
241
+
242
+ @media (max-width: 640px) {
243
+ .pt-buttons {
244
+ flex-direction: column;
245
+ }
246
+ }
247
+
248
+ .pt-btn {
249
+ flex: 1;
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ gap: 0.75rem;
254
+ padding: 1rem 2rem;
255
+ border-radius: 0.75rem;
256
+ font-weight: 700;
257
+ border: none;
258
+ cursor: pointer;
259
+ transition: all 0.3s ease;
260
+ min-width: auto;
261
+ }
262
+
263
+ .pt-btn-primary {
264
+ background-color: var(--pt-accent);
265
+ color: white;
266
+ }
267
+
268
+ .pt-btn-primary:hover {
269
+ background-color: var(--pt-accent-hover);
270
+ box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.3);
271
+ }
272
+
273
+ .pt-btn-secondary {
274
+ background-color: var(--pt-surface-alt);
275
+ color: var(--pt-text);
276
+ border: 2px solid var(--pt-border);
277
+ }
278
+
279
+ .pt-btn-secondary:hover {
280
+ border-color: #10b981;
281
+ color: #059669;
282
+ }
283
+
284
+ .pt-btn-icon {
285
+ width: 1.5rem;
286
+ height: 1.5rem;
287
+ }
288
+
289
+ .pt-shortcuts {
290
+ display: flex;
291
+ align-items: center;
292
+ gap: 1rem;
293
+ font-size: 0.75rem;
294
+ color: var(--pt-text-muted);
295
+ flex-wrap: wrap;
296
+ }
297
+
298
+ .pt-shortcut {
299
+ display: flex;
300
+ align-items: center;
301
+ gap: 0.25rem;
302
+ }
303
+
304
+ .pt-kbd {
305
+ background-color: var(--pt-kbd-bg);
306
+ padding: 0.125rem 0.375rem;
307
+ border-radius: 0.25rem;
308
+ border: 1px solid var(--pt-border);
309
+ font-size: 0.75rem;
310
+ }
311
+
312
+ .pt-monitor-wrapper {
313
+ position: relative;
314
+ display: flex;
315
+ justify-content: center;
316
+ align-items: center;
317
+ cursor: pointer;
318
+ min-height: 250px;
319
+ }
320
+
321
+ .pt-monitor {
322
+ position: relative;
323
+ aspect-ratio: 16 / 9;
324
+ width: 100%;
325
+ max-width: 500px;
326
+ background: var(--pt-monitor-body);
327
+ border-radius: 1rem;
328
+ border: 4px solid var(--pt-monitor-body);
329
+ box-shadow: 0 25px 50px -12px var(--pt-shadow);
330
+ display: flex;
331
+ align-items: center;
332
+ justify-content: center;
333
+ overflow: hidden;
334
+ }
335
+
336
+ .pt-monitor-screen-wrapper {
337
+ position: relative;
338
+ width: 100%;
339
+ height: 100%;
340
+ display: flex;
341
+ border-radius: 0.5rem;
342
+ overflow: hidden;
343
+ }
344
+
345
+ .pt-monitor-shine {
346
+ position: absolute;
347
+ inset: 0;
348
+ background: linear-gradient(to top right, rgba(255, 255, 255, 0.05), transparent);
349
+ pointer-events: none;
350
+ z-index: 10;
351
+ border-radius: 0.5rem;
352
+ }
353
+
354
+ .pt-monitor-screen {
355
+ width: 100%;
356
+ height: 100%;
357
+ display: flex;
358
+ transition: background-color 0.5s ease;
359
+ background-color: #000;
360
+ }
361
+
362
+ .pt-monitor-section {
363
+ flex: 1;
364
+ opacity: 0.9;
365
+ }
366
+
367
+ .pt-fixer-preview {
368
+ position: absolute;
369
+ width: 3rem;
370
+ height: 3rem;
371
+ background: white;
372
+ animation: pulse-glow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
373
+ box-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
374
+ z-index: 20;
375
+ display: none;
376
+ }
377
+
378
+ @keyframes pulse-glow {
379
+ 0%,
380
+ 100% {
381
+ opacity: 1;
382
+ }
383
+ 50% {
384
+ opacity: 0.7;
385
+ }
386
+ }
387
+
388
+ .pt-monitor-stand {
389
+ position: absolute;
390
+ bottom: -1rem;
391
+ left: 50%;
392
+ transform: translateX(-50%);
393
+ width: 6rem;
394
+ height: 3rem;
395
+ background: var(--pt-monitor-body);
396
+ border-radius: 0 0 0.75rem 0.75rem;
397
+ z-index: -1;
398
+ }
399
+
400
+ .pt-monitor-base {
401
+ position: absolute;
402
+ bottom: -1.25rem;
403
+ left: 50%;
404
+ transform: translateX(-50%);
405
+ width: 12rem;
406
+ height: 1rem;
407
+ background: var(--pt-monitor-screen);
408
+ border-radius: 9999px;
409
+ margin-top: -1.25rem;
410
+ }
411
+
412
+ :global(.pt-overlay-hidden) {
413
+ position: fixed;
414
+ inset: 0;
415
+ z-index: 50;
416
+ background-color: black;
417
+ cursor: none;
418
+ display: none;
419
+ }
420
+
421
+ :global(.pt-overlay-active) {
422
+ display: flex;
423
+ }
424
+
425
+ .pt-overlay-bg {
426
+ position: absolute;
427
+ inset: 0;
428
+ background-color: black;
429
+ transition: background-color 0s;
430
+ }
431
+
432
+ .pt-fixer-box {
433
+ position: fixed;
434
+ inset: 0;
435
+ z-index: 55;
436
+ background-color: white;
437
+ cursor: none;
438
+ overflow: hidden;
439
+ display: none;
440
+ }
441
+
442
+ :global(.theme-dark) .pt-fixer-box {
443
+ background-color: var(--pt-surface);
444
+ }
445
+
446
+ .pt-fixer-box:not(.pt-fixer-hidden) {
447
+ display: flex;
448
+ }
449
+
450
+ .pt-fixer-hidden {
451
+ display: none;
452
+ }
453
+
454
+ .pt-noise-canvas {
455
+ position: absolute;
456
+ inset: 0;
457
+ width: 100%;
458
+ height: 100%;
459
+ }
460
+
461
+ .pt-close-fixer {
462
+ position: absolute;
463
+ top: 0.5rem;
464
+ right: 0.5rem;
465
+ padding: 0.5rem;
466
+ opacity: 0;
467
+ transition: opacity 0.3s ease;
468
+ background: rgba(0, 0, 0, 0.5);
469
+ color: white;
470
+ border: none;
471
+ cursor: pointer;
472
+ z-index: 60;
473
+ display: flex;
474
+ align-items: center;
475
+ justify-content: center;
476
+ }
477
+
478
+ .pt-fixer-box:hover .pt-close-fixer {
479
+ opacity: 1;
480
+ }
481
+
482
+ .pt-close-fixer svg {
483
+ width: 1rem;
484
+ height: 1rem;
485
+ }
486
+
487
+ .pt-controls {
488
+ position: absolute;
489
+ bottom: 3rem;
490
+ left: 50%;
491
+ transform: translateX(-50%);
492
+ background: rgba(15, 23, 42, 0.8);
493
+ backdrop-filter: blur(0.5rem);
494
+ border: 1px solid rgba(255, 255, 255, 0.1);
495
+ padding: 1rem;
496
+ border-radius: 1rem;
497
+ display: flex;
498
+ align-items: center;
499
+ gap: 1rem;
500
+ transition: opacity 0.3s ease;
501
+ opacity: 0;
502
+ pointer-events: none;
503
+ }
504
+
505
+ :global(.pt-controls-visible) {
506
+ opacity: 1;
507
+ pointer-events: auto;
508
+ }
509
+
510
+ .pt-color-buttons {
511
+ display: flex;
512
+ gap: 0.5rem;
513
+ }
514
+
515
+ .pt-color-btn {
516
+ width: 2.5rem;
517
+ height: 2.5rem;
518
+ border-radius: 9999px;
519
+ border: 2px solid rgba(255, 255, 255, 0.2);
520
+ cursor: pointer;
521
+ transition: all 0.3s ease;
522
+ }
523
+
524
+ .pt-color-btn:hover {
525
+ transform: scale(1.1);
526
+ box-shadow: 0 0 15px rgba(255, 255, 255, 0.3);
527
+ }
528
+
529
+ .pt-divider {
530
+ width: 1px;
531
+ height: 2rem;
532
+ background-color: rgba(255, 255, 255, 0.2);
533
+ }
534
+
535
+ .pt-control-btn {
536
+ padding: 0.5rem;
537
+ border-radius: 0.5rem;
538
+ background: transparent;
539
+ color: white;
540
+ border: none;
541
+ cursor: pointer;
542
+ transition: all 0.3s ease;
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: center;
546
+ }
547
+
548
+ .pt-control-btn:hover {
549
+ color: #4ade80;
550
+ background: rgba(255, 255, 255, 0.1);
551
+ }
552
+
553
+ .pt-control-btn-exit:hover {
554
+ color: #ef4444;
555
+ }
556
+
557
+ .pt-control-btn svg {
558
+ width: 1.5rem;
559
+ height: 1.5rem;
560
+ }
561
+ </style>
562
+
563
+ <script>
564
+ const els = {
565
+ btnStart: document.getElementById('btn-start-test'),
566
+ btnFix: document.getElementById('btn-start-fix'),
567
+ overlay: document.getElementById('test-overlay'),
568
+ testBg: document.getElementById('test-bg'),
569
+ controls: document.getElementById('test-controls'),
570
+ btnExit: document.getElementById('btn-exit'),
571
+ toggleFixer: document.getElementById('toggle-fixer'),
572
+ fixerBox: document.getElementById('fixer-box'),
573
+ closeFixer: document.getElementById('close-fixer'),
574
+ noiseCanvas: document.getElementById('noise-canvas'),
575
+ };
576
+
577
+ let idleTimer: ReturnType<typeof setTimeout>;
578
+ let isStrobing = false;
579
+ let animationFrame: number;
580
+ let currentColorIndex = 0;
581
+
582
+ const colors = ['#000000', '#ffffff', '#dc2626', '#16a34a', '#2563eb'];
583
+
584
+ function startTest(initialMode = 'test') {
585
+ els.overlay?.classList.add('pt-overlay-active');
586
+ document.documentElement.requestFullscreen().catch(() => {});
587
+
588
+ if (initialMode === 'fix') {
589
+ toggleFixerBox(true);
590
+ } else {
591
+ const firstColor = colors[0];
592
+ if (firstColor) setColor(firstColor);
593
+ }
594
+
595
+ document.addEventListener('mousemove', handleMouseMove);
596
+ document.addEventListener('keydown', handleKeyDown);
597
+ resetIdleTimer();
598
+ }
599
+
600
+ function exitTest() {
601
+ if (document.fullscreenElement) {
602
+ document.exitFullscreen().catch(() => {});
603
+ }
604
+ els.overlay?.classList.remove('pt-overlay-active');
605
+ document.removeEventListener('mousemove', handleMouseMove);
606
+ document.removeEventListener('keydown', handleKeyDown);
607
+ toggleFixerBox(false);
608
+ }
609
+
610
+ function setColor(hex: string) {
611
+ if (!els.testBg) return;
612
+ els.testBg.style.backgroundColor = hex;
613
+ }
614
+
615
+ function rotateColor(direction = 1) {
616
+ currentColorIndex = (currentColorIndex + direction + colors.length) % colors.length;
617
+ const color = colors[currentColorIndex];
618
+ if (color) setColor(color);
619
+ }
620
+
621
+ function generateNoise(element: HTMLElement) {
622
+ const canvas = document.createElement('canvas');
623
+ const ctx = canvas.getContext('2d');
624
+ if (!ctx) return;
625
+
626
+ const baseSize = 64;
627
+ canvas.height = baseSize;
628
+ canvas.width = baseSize;
629
+
630
+ canvas.style.width = '100%';
631
+ canvas.style.height = '100%';
632
+ canvas.style.imageRendering = 'pixelated';
633
+
634
+ element.innerHTML = '';
635
+ element.appendChild(canvas);
636
+
637
+ const w = canvas.width;
638
+ const h = canvas.height;
639
+ const idata = ctx.createImageData(w, h);
640
+ const buffer32 = new Uint32Array(idata.data.buffer);
641
+
642
+ const step = () => {
643
+ if (!isStrobing) {
644
+ return;
645
+ }
646
+
647
+ for (let i = 0; i < buffer32.length; i++) {
648
+ buffer32[i] = (255 << 24) | ((Math.random() * 255) << 16) | ((Math.random() * 255) << 8) | ((Math.random() * 255) << 0);
649
+ }
650
+
651
+ ctx.putImageData(idata, 0, 0);
652
+ animationFrame = requestAnimationFrame(step);
653
+ };
654
+ step();
655
+ }
656
+
657
+ function toggleFixerBox(show: boolean) {
658
+ if (!els.fixerBox || !els.noiseCanvas) return;
659
+
660
+ if (show) {
661
+ els.fixerBox.classList.remove('pt-fixer-hidden');
662
+ isStrobing = true;
663
+ generateNoise(els.noiseCanvas);
664
+ } else {
665
+ els.fixerBox.classList.add('pt-fixer-hidden');
666
+ isStrobing = false;
667
+ if (animationFrame) cancelAnimationFrame(animationFrame);
668
+ }
669
+ }
670
+
671
+ function handleMouseMove() {
672
+ if (!els.controls || !els.overlay) return;
673
+
674
+ els.controls.classList.add('pt-controls-visible');
675
+ els.overlay.style.cursor = 'default';
676
+
677
+ resetIdleTimer();
678
+ }
679
+
680
+ function resetIdleTimer() {
681
+ if (idleTimer) clearTimeout(idleTimer);
682
+ idleTimer = setTimeout(() => {
683
+ if (els.controls && els.overlay) {
684
+ els.controls.classList.remove('pt-controls-visible');
685
+ els.overlay.style.cursor = 'none';
686
+ }
687
+ }, 3000);
688
+ }
689
+
690
+ function handleKeyDown(e: KeyboardEvent) {
691
+ if (e.key === 'Escape') exitTest();
692
+ if (e.key === ' ' || e.key === 'ArrowRight') rotateColor(1);
693
+ if (e.key === 'ArrowLeft') rotateColor(-1);
694
+ }
695
+
696
+ document.querySelectorAll('.pt-color-btn').forEach((btn) => {
697
+ btn.addEventListener('click', (e) => {
698
+ const color = (e.target as HTMLElement).getAttribute('data-color');
699
+ if (color) setColor(color);
700
+ });
701
+ });
702
+
703
+ els.btnStart?.addEventListener('click', () => startTest('test'));
704
+ els.btnFix?.addEventListener('click', () => startTest('fix'));
705
+ els.btnExit?.addEventListener('click', exitTest);
706
+
707
+ els.toggleFixer?.addEventListener('click', () => {
708
+ const isHidden = els.fixerBox?.classList.contains('pt-fixer-hidden');
709
+ toggleFixerBox(!!isHidden);
710
+ });
711
+
712
+ els.closeFixer?.addEventListener('click', (e) => {
713
+ e.stopPropagation();
714
+ toggleFixerBox(false);
715
+ });
716
+ </script>
717
+ </div>