@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,601 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { TestTecladoUI } from './ui';
4
+ import { Icon } from 'astro-icon/components';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ ui?: Record<string, unknown>;
9
+ }
10
+
11
+ const { ui } = Astro.props;
12
+ const t = (ui ?? {}) as TestTecladoUI;
13
+
14
+ const keyClass = 'tt-key';
15
+ const activeClass = 'tt-key-active';
16
+ const testedClass = 'tt-key-tested';
17
+ ---
18
+
19
+ <div id="keyboard-tester-root" class="tt-wrapper" data-active-class={activeClass} data-tested-class={testedClass}>
20
+ <style is:global>
21
+ .tt-wrapper {
22
+ --tt-accent: #4f46e5;
23
+ --tt-accent-hover: #4338ca;
24
+ --tt-surface: #fff;
25
+ --tt-surface-alt: #f1f5f9;
26
+ --tt-border: #e2e8f0;
27
+ --tt-text: #0f172a;
28
+ --tt-text-muted: #64748b;
29
+ --tt-key-bg: #fff;
30
+ --tt-key-text: #64748b;
31
+ --tt-key-border: #e2e8f0;
32
+ --tt-key-shadow: rgba(0, 0, 0, 0.2);
33
+ --tt-key-active-bg: #4f46e5;
34
+ --tt-key-tested-bg: #d1fae5;
35
+ --tt-key-tested-text: #059669;
36
+ --tt-kb-bg: #e2e8f0;
37
+ }
38
+
39
+ .theme-dark .tt-wrapper {
40
+ --tt-surface: #0f172a;
41
+ --tt-surface-alt: #1e293b;
42
+ --tt-border: #334155;
43
+ --tt-text: #f1f5f9;
44
+ --tt-text-muted: #94a3b8;
45
+ --tt-key-bg: #334155;
46
+ --tt-key-text: #cbd5e1;
47
+ --tt-key-border: #475569;
48
+ --tt-key-shadow: rgba(0, 0, 0, 0.5);
49
+ --tt-key-active-bg: #6366f1;
50
+ --tt-key-tested-bg: rgba(16, 185, 129, 0.3);
51
+ --tt-key-tested-text: #6ee7b7;
52
+ --tt-kb-bg: #334155;
53
+ }
54
+
55
+ body.is-widget .tt-wrapper {
56
+ padding: 0;
57
+ }
58
+
59
+ body.is-widget .tt-wrapper > div {
60
+ background: transparent;
61
+ box-shadow: none;
62
+ border: none;
63
+ padding: 1rem;
64
+ }
65
+ </style>
66
+
67
+ <div class="tt-container">
68
+ <div class="tt-card">
69
+ <div class="tt-header">
70
+ <div class="tt-header-left">
71
+ <div class="tt-badge">
72
+ <Icon name="mdi:keyboard-outline" class="tt-badge-icon" />
73
+ {t.badge}
74
+ </div>
75
+ <h2 class="tt-title">{t.title}</h2>
76
+ <p class="tt-description">{t.description}</p>
77
+ </div>
78
+
79
+ <div class="tt-stats">
80
+ <div class="tt-stat-box">
81
+ <div class="tt-stat-label">{t.simultaneousLabel}</div>
82
+ <div class="tt-stat-value" id="max-kro">0</div>
83
+ </div>
84
+ <button id="reset-btn" class="tt-reset-btn" title={t.resetBtn}>
85
+ <Icon name="mdi:refresh" class="tt-reset-icon" />
86
+ </button>
87
+ </div>
88
+ </div>
89
+
90
+ <div class="tt-keyboard-wrapper">
91
+ <div class="tt-keyboard" id="keyboard-base">
92
+ <div class="tt-row">
93
+ <div class={`${keyClass} tt-key-esc`} data-code="Escape">ESC</div>
94
+ <div class="tt-spacer-8"></div>
95
+ <div class={`${keyClass} tt-key-f`} data-code="F1">F1</div>
96
+ <div class={`${keyClass} tt-key-f`} data-code="F2">F2</div>
97
+ <div class={`${keyClass} tt-key-f`} data-code="F3">F3</div>
98
+ <div class={`${keyClass} tt-key-f`} data-code="F4">F4</div>
99
+ <div class="tt-spacer-4"></div>
100
+ <div class={`${keyClass} tt-key-f`} data-code="F5">F5</div>
101
+ <div class={`${keyClass} tt-key-f`} data-code="F6">F6</div>
102
+ <div class={`${keyClass} tt-key-f`} data-code="F7">F7</div>
103
+ <div class={`${keyClass} tt-key-f`} data-code="F8">F8</div>
104
+ <div class="tt-spacer-4"></div>
105
+ <div class={`${keyClass} tt-key-f`} data-code="F9">F9</div>
106
+ <div class={`${keyClass} tt-key-f`} data-code="F10">F10</div>
107
+ <div class={`${keyClass} tt-key-f`} data-code="F11">F11</div>
108
+ <div class={`${keyClass} tt-key-f`} data-code="F12">F12</div>
109
+ <div class="tt-spacer-4"></div>
110
+ <div class={`${keyClass} tt-key-sm`} data-code="PrintScreen">PRT</div>
111
+ <div class={`${keyClass} tt-key-sm`} data-code="ScrollLock">SCR</div>
112
+ <div class={`${keyClass} tt-key-sm`} data-code="Pause">PAU</div>
113
+ </div>
114
+
115
+ <div class="tt-row">
116
+ <div class={`${keyClass} tt-key-sm`} data-code="Backquote">`</div>
117
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit1">1</div>
118
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit2">2</div>
119
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit3">3</div>
120
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit4">4</div>
121
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit5">5</div>
122
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit6">6</div>
123
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit7">7</div>
124
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit8">8</div>
125
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit9">9</div>
126
+ <div class={`${keyClass} tt-key-sm`} data-code="Digit0">0</div>
127
+ <div class={`${keyClass} tt-key-sm`} data-code="Minus">-</div>
128
+ <div class={`${keyClass} tt-key-sm`} data-code="Equal">=</div>
129
+ <div class={`${keyClass} tt-key-md`} data-code="Backspace">BACK</div>
130
+ <div class="tt-spacer-4"></div>
131
+ <div class={`${keyClass} tt-key-sm`} data-code="Insert">INS</div>
132
+ <div class={`${keyClass} tt-key-sm`} data-code="Home">HOM</div>
133
+ <div class={`${keyClass} tt-key-sm`} data-code="PageUp">PGU</div>
134
+ </div>
135
+
136
+ <div class="tt-row">
137
+ <div class={`${keyClass} tt-key-md`} data-code="Tab">TAB</div>
138
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyQ">Q</div>
139
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyW">W</div>
140
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyE">E</div>
141
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyR">R</div>
142
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyT">T</div>
143
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyY">Y</div>
144
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyU">U</div>
145
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyI">I</div>
146
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyO">O</div>
147
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyP">P</div>
148
+ <div class={`${keyClass} tt-key-sm`} data-code="BracketLeft">[</div>
149
+ <div class={`${keyClass} tt-key-sm`} data-code="BracketRight">]</div>
150
+ <div class={`${keyClass} tt-key-md`} data-code="Backslash">\</div>
151
+ <div class="tt-spacer-4"></div>
152
+ <div class={`${keyClass} tt-key-sm`} data-code="Delete">DEL</div>
153
+ <div class={`${keyClass} tt-key-sm`} data-code="End">END</div>
154
+ <div class={`${keyClass} tt-key-sm`} data-code="PageDown">PGD</div>
155
+ </div>
156
+
157
+ <div class="tt-row">
158
+ <div class={`${keyClass} tt-key-lg`} data-code="CapsLock">CAPS</div>
159
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyA">A</div>
160
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyS">S</div>
161
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyD">D</div>
162
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyF">F</div>
163
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyG">G</div>
164
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyH">H</div>
165
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyJ">J</div>
166
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyK">K</div>
167
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyL">L</div>
168
+ <div class={`${keyClass} tt-key-sm`} data-code="Semicolon">;</div>
169
+ <div class={`${keyClass} tt-key-sm`} data-code="Quote">'</div>
170
+ <div class={`${keyClass} tt-key-lg`} data-code="Enter">ENTER</div>
171
+ <div class="tt-spacer-108"></div>
172
+ </div>
173
+
174
+ <div class="tt-row">
175
+ <div class={`${keyClass} tt-key-lg`} data-code="ShiftLeft">SHIFT</div>
176
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyZ">Z</div>
177
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyX">X</div>
178
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyC">C</div>
179
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyV">V</div>
180
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyB">B</div>
181
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyN">N</div>
182
+ <div class={`${keyClass} tt-key-sm`} data-code="KeyM">M</div>
183
+ <div class={`${keyClass} tt-key-sm`} data-code="Comma">,</div>
184
+ <div class={`${keyClass} tt-key-sm`} data-code="Period">.</div>
185
+ <div class={`${keyClass} tt-key-sm`} data-code="Slash">/</div>
186
+ <div class={`${keyClass} tt-key-xl`} data-code="ShiftRight">SHIFT</div>
187
+ <div class="tt-spacer-4"></div>
188
+ <div class="tt-spacer-12"></div>
189
+ <div class={`${keyClass} tt-key-sm`} data-code="ArrowUp">↑</div>
190
+ <div class="tt-spacer-12"></div>
191
+ </div>
192
+
193
+ <div class="tt-row">
194
+ <div class={`${keyClass} tt-key-md`} data-code="ControlLeft">CTRL</div>
195
+ <div class={`${keyClass} tt-key-sm`} data-code="MetaLeft">WIN</div>
196
+ <div class={`${keyClass} tt-key-md`} data-code="AltLeft">ALT</div>
197
+ <div class={`${keyClass} tt-key-xxl`} data-code="Space">SPACE</div>
198
+ <div class={`${keyClass} tt-key-md`} data-code="AltRight">ALT</div>
199
+ <div class={`${keyClass} tt-key-sm`} data-code="MetaRight">WIN</div>
200
+ <div class={`${keyClass} tt-key-sm`} data-code="ContextMenu">CTX</div>
201
+ <div class={`${keyClass} tt-key-md`} data-code="ControlRight">CTRL</div>
202
+ <div class="tt-spacer-4"></div>
203
+ <div class={`${keyClass} tt-key-sm`} data-code="ArrowLeft">←</div>
204
+ <div class={`${keyClass} tt-key-sm`} data-code="ArrowDown">↓</div>
205
+ <div class={`${keyClass} tt-key-sm`} data-code="ArrowRight">→</div>
206
+ </div>
207
+ </div>
208
+ </div>
209
+
210
+ <div class="tt-event-log-container">
211
+ <div class="tt-event-log-header">
212
+ <Icon name="mdi:history" class="tt-event-log-icon" />
213
+ <h3 class="tt-event-log-label">{t.eventLogLabel}</h3>
214
+ </div>
215
+ <div id="event-log" class="tt-event-log"></div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+
220
+ <style>
221
+ .tt-wrapper {
222
+ width: 100%;
223
+ color: var(--tt-text);
224
+ }
225
+
226
+ .tt-container {
227
+ max-width: 80rem;
228
+ margin: 0 auto;
229
+ padding: 1rem;
230
+ }
231
+
232
+ .tt-card {
233
+ background: var(--tt-surface);
234
+ border-radius: 1.5rem;
235
+ padding: 2rem;
236
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
237
+ border: 1px solid var(--tt-border);
238
+ }
239
+
240
+ .tt-header {
241
+ display: flex;
242
+ flex-direction: column;
243
+ gap: 1rem;
244
+ margin-bottom: 2rem;
245
+ width: 100%;
246
+ }
247
+
248
+ @media (min-width: 768px) {
249
+ .tt-header {
250
+ flex-direction: row;
251
+ justify-content: space-between;
252
+ align-items: flex-start;
253
+ }
254
+ }
255
+
256
+ .tt-header-left {
257
+ flex: 1;
258
+ }
259
+
260
+ .tt-badge {
261
+ display: inline-flex;
262
+ align-items: center;
263
+ gap: 0.5rem;
264
+ padding: 0.25rem 0.75rem;
265
+ border-radius: 9999px;
266
+ background-color: rgba(79, 70, 229, 0.1);
267
+ color: var(--tt-accent);
268
+ font-size: 0.75rem;
269
+ font-weight: 700;
270
+ text-transform: uppercase;
271
+ letter-spacing: 0.05em;
272
+ margin-bottom: 0.5rem;
273
+ }
274
+
275
+ .tt-badge-icon {
276
+ width: 1rem;
277
+ height: 1rem;
278
+ }
279
+
280
+ .tt-title {
281
+ font-size: 1.875rem;
282
+ font-weight: 900;
283
+ letter-spacing: -0.02em;
284
+ margin: 0 0 0.25rem;
285
+ }
286
+
287
+ .tt-description {
288
+ font-size: 0.9375rem;
289
+ color: var(--tt-text-muted);
290
+ margin: 0;
291
+ }
292
+
293
+ .tt-stats {
294
+ display: flex;
295
+ gap: 1rem;
296
+ flex-wrap: wrap;
297
+ }
298
+
299
+ .tt-stat-box {
300
+ background: var(--tt-surface-alt);
301
+ padding: 1rem;
302
+ border-radius: 1rem;
303
+ text-align: center;
304
+ min-width: 120px;
305
+ }
306
+
307
+ .tt-stat-label {
308
+ font-size: 0.75rem;
309
+ font-weight: 700;
310
+ text-transform: uppercase;
311
+ color: var(--tt-text-muted);
312
+ letter-spacing: 0.05em;
313
+ }
314
+
315
+ .tt-stat-value {
316
+ font-size: 2.25rem;
317
+ font-weight: 900;
318
+ color: var(--tt-accent);
319
+ margin-top: 0.5rem;
320
+ }
321
+
322
+ .tt-reset-btn {
323
+ padding: 1rem;
324
+ background: var(--tt-surface-alt);
325
+ border: none;
326
+ border-radius: 1rem;
327
+ cursor: pointer;
328
+ color: var(--tt-text-muted);
329
+ transition: all 0.2s ease;
330
+ display: flex;
331
+ align-items: center;
332
+ justify-content: center;
333
+ }
334
+
335
+ .tt-reset-btn:hover {
336
+ background: var(--tt-border);
337
+ }
338
+
339
+ .tt-reset-icon {
340
+ width: 1.5rem;
341
+ height: 1.5rem;
342
+ }
343
+
344
+ .tt-keyboard-wrapper {
345
+ position: relative;
346
+ width: 100%;
347
+ overflow-x: auto;
348
+ padding-bottom: 1rem;
349
+ display: flex;
350
+ justify-content: center;
351
+ }
352
+
353
+ .tt-keyboard {
354
+ background: var(--tt-kb-bg);
355
+ padding: 0.5rem;
356
+ border-radius: 0.75rem;
357
+ display: flex;
358
+ flex-direction: column;
359
+ gap: 0.25rem;
360
+ user-select: none;
361
+ min-width: 900px;
362
+ }
363
+
364
+ .tt-row {
365
+ display: flex;
366
+ gap: 0.25rem;
367
+ }
368
+
369
+ .tt-key {
370
+ height: 3rem;
371
+ background: var(--tt-key-bg);
372
+ border-radius: 0.5rem;
373
+ display: flex;
374
+ align-items: center;
375
+ justify-content: center;
376
+ font-weight: 700;
377
+ font-size: 0.75rem;
378
+ color: var(--tt-key-text);
379
+ border-top: 1px solid rgba(255, 255, 255, 0.3);
380
+ box-shadow: 0 2px 0 var(--tt-key-shadow);
381
+ transition: all 300ms ease-out;
382
+ border: 1px solid var(--tt-key-border);
383
+ }
384
+
385
+ .tt-key-esc,
386
+ .tt-key-f,
387
+ .tt-key-sm {
388
+ width: 3rem;
389
+ }
390
+
391
+ .tt-key-md {
392
+ width: 4rem;
393
+ }
394
+
395
+ .tt-key-lg {
396
+ width: 5rem;
397
+ }
398
+
399
+ .tt-key-xl {
400
+ width: 8rem;
401
+ }
402
+
403
+ .tt-key-xxl {
404
+ width: 18rem;
405
+ }
406
+
407
+ .tt-key.tt-key-active {
408
+ background: var(--tt-key-active-bg);
409
+ color: white;
410
+ box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.2);
411
+ transform: translateY(1px);
412
+ border-color: var(--tt-accent);
413
+ }
414
+
415
+ .tt-key.tt-key-tested {
416
+ background: var(--tt-key-tested-bg);
417
+ color: var(--tt-key-tested-text);
418
+ border-color: var(--tt-key-tested-text);
419
+ }
420
+
421
+ .tt-key.tt-key-releasing {
422
+ background: var(--tt-key-bg);
423
+ color: var(--tt-key-text);
424
+ box-shadow: 0 2px 0 var(--tt-key-shadow);
425
+ transform: translateY(0);
426
+ border-color: var(--tt-key-border);
427
+ }
428
+
429
+ .tt-spacer-4 {
430
+ width: 0.25rem;
431
+ }
432
+
433
+ .tt-spacer-8 {
434
+ width: 0.5rem;
435
+ }
436
+
437
+ .tt-spacer-12 {
438
+ width: 0.75rem;
439
+ }
440
+
441
+ .tt-spacer-108 {
442
+ width: 6.75rem;
443
+ }
444
+
445
+ .tt-event-log-container {
446
+ margin-top: 0.75rem;
447
+ padding: 0;
448
+ background: transparent;
449
+ border-radius: 0;
450
+ border: none;
451
+ width: 100%;
452
+ }
453
+
454
+ .tt-event-log-header {
455
+ display: none;
456
+ }
457
+
458
+ .tt-event-log-icon {
459
+ display: none;
460
+ }
461
+
462
+ .tt-event-log-label {
463
+ display: none;
464
+ }
465
+
466
+ .tt-event-log {
467
+ font-size: 0.65rem;
468
+ color: var(--tt-text);
469
+ height: 2rem;
470
+ overflow-x: auto;
471
+ overflow-y: hidden;
472
+ background: var(--tt-surface-alt);
473
+ padding: 0.375rem;
474
+ border-radius: 0.5rem;
475
+ border: 1px solid var(--tt-border);
476
+ display: flex;
477
+ align-items: center;
478
+ justify-content: flex-start;
479
+ gap: 0.5rem;
480
+ white-space: nowrap;
481
+ width: 100%;
482
+ scrollbar-width: none;
483
+ }
484
+
485
+ .tt-event-log::-webkit-scrollbar {
486
+ display: none;
487
+ }
488
+
489
+ .tt-event-log div {
490
+ display: inline-flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ line-height: 1;
494
+ font-weight: 600;
495
+ padding: 0.25rem 0.5rem;
496
+ min-width: 1.5rem;
497
+ height: 1.5rem;
498
+ background: var(--tt-key-bg);
499
+ border: 1px solid var(--tt-key-border);
500
+ border-radius: 0.375rem;
501
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
502
+ flex-shrink: 0;
503
+ }
504
+ </style>
505
+
506
+ <script>
507
+ const activeClass = 'tt-key-active';
508
+ const testedClass = 'tt-key-tested';
509
+
510
+ const activeClasses = [activeClass];
511
+ const testedClasses = [testedClass];
512
+
513
+ const keys = document.querySelectorAll('[data-code]');
514
+ const maxKroDisplay = document.getElementById('max-kro');
515
+ const eventLog = document.getElementById('event-log');
516
+ const resetBtn = document.getElementById('reset-btn');
517
+
518
+ const activeKeys = new Set<string>();
519
+ let maxSimultaneous = 0;
520
+
521
+ const keyMap = new Map<string, Element>();
522
+ keys.forEach((k) => {
523
+ const code = k.getAttribute('data-code');
524
+ if (code) keyMap.set(code, k);
525
+ });
526
+
527
+ function log(msg: string) {
528
+ if (!eventLog) return;
529
+ const div = document.createElement('div');
530
+ div.textContent = `${msg}`;
531
+ eventLog.appendChild(div);
532
+ if (eventLog.children.length > 50) eventLog.firstElementChild?.remove();
533
+ eventLog.scrollLeft = eventLog.scrollWidth;
534
+ }
535
+
536
+ function handleDown(e: KeyboardEvent) {
537
+ e.preventDefault();
538
+ const code = e.code;
539
+
540
+ if (!activeKeys.has(code)) {
541
+ activeKeys.add(code);
542
+
543
+ const el = keyMap.get(code) as HTMLElement | undefined;
544
+ if (el) {
545
+ el.classList.add(...activeClasses);
546
+ el.classList.add('is-tested');
547
+
548
+ if (!el.dataset.tested) {
549
+ el.classList.add(...testedClasses);
550
+ el.dataset.tested = 'true';
551
+ }
552
+
553
+ const keyText = el.textContent || code;
554
+ log(`${keyText}`);
555
+ }
556
+
557
+ if (activeKeys.size > maxSimultaneous) {
558
+ maxSimultaneous = activeKeys.size;
559
+ if (maxKroDisplay) maxKroDisplay.textContent = maxSimultaneous.toString();
560
+ }
561
+ }
562
+ }
563
+
564
+ function handleUp(e: KeyboardEvent) {
565
+ e.preventDefault();
566
+ const code = e.code;
567
+ activeKeys.delete(code);
568
+
569
+ const el = keyMap.get(code) as HTMLElement | undefined;
570
+ if (el) {
571
+ el.classList.add('tt-key-releasing');
572
+ el.classList.remove('tt-key-active');
573
+ el.classList.remove('tt-key-tested');
574
+ setTimeout(() => {
575
+ el.classList.remove('tt-key-releasing');
576
+ }, 300);
577
+ }
578
+ }
579
+
580
+ function reset() {
581
+ activeKeys.clear();
582
+ maxSimultaneous = 0;
583
+ if (maxKroDisplay) maxKroDisplay.textContent = '0';
584
+ if (eventLog) eventLog.innerHTML = '';
585
+
586
+ keys.forEach((k) => {
587
+ k.classList.remove(...activeClasses);
588
+ k.classList.remove(...testedClasses);
589
+ (k as HTMLElement).removeAttribute('data-tested');
590
+ k.classList.remove('is-tested');
591
+ });
592
+ log('Test reiniciado.');
593
+ }
594
+
595
+ window.addEventListener('keydown', handleDown);
596
+ window.addEventListener('keyup', handleUp);
597
+ resetBtn?.addEventListener('click', reset);
598
+
599
+ window.focus();
600
+ </script>
601
+ </div>