@jjlmoya/utils-hardware 1.24.0 → 1.26.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 (79) hide show
  1. package/package.json +2 -1
  2. package/src/category/index.ts +4 -1
  3. package/src/entries.ts +10 -1
  4. package/src/index.ts +3 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/keyboardChatterTest/bibliography.astro +15 -0
  8. package/src/tool/keyboardChatterTest/bibliography.ts +20 -0
  9. package/src/tool/keyboardChatterTest/component.astro +353 -0
  10. package/src/tool/keyboardChatterTest/entry.ts +30 -0
  11. package/src/tool/keyboardChatterTest/i18n/de.ts +232 -0
  12. package/src/tool/keyboardChatterTest/i18n/en.ts +232 -0
  13. package/src/tool/keyboardChatterTest/i18n/es.ts +232 -0
  14. package/src/tool/keyboardChatterTest/i18n/fr.ts +232 -0
  15. package/src/tool/keyboardChatterTest/i18n/id.ts +232 -0
  16. package/src/tool/keyboardChatterTest/i18n/it.ts +232 -0
  17. package/src/tool/keyboardChatterTest/i18n/ja.ts +232 -0
  18. package/src/tool/keyboardChatterTest/i18n/ko.ts +232 -0
  19. package/src/tool/keyboardChatterTest/i18n/nl.ts +232 -0
  20. package/src/tool/keyboardChatterTest/i18n/pl.ts +232 -0
  21. package/src/tool/keyboardChatterTest/i18n/pt.ts +232 -0
  22. package/src/tool/keyboardChatterTest/i18n/ru.ts +232 -0
  23. package/src/tool/keyboardChatterTest/i18n/sv.ts +232 -0
  24. package/src/tool/keyboardChatterTest/i18n/tr.ts +232 -0
  25. package/src/tool/keyboardChatterTest/i18n/zh.ts +232 -0
  26. package/src/tool/keyboardChatterTest/index.ts +12 -0
  27. package/src/tool/keyboardChatterTest/keyboard-chatter-test.css +512 -0
  28. package/src/tool/keyboardChatterTest/logic.ts +23 -0
  29. package/src/tool/keyboardChatterTest/seo.astro +16 -0
  30. package/src/tool/keyboardChatterTest/ui.ts +34 -0
  31. package/src/tool/webBluetoothBleScanner/bibliography.astro +14 -0
  32. package/src/tool/webBluetoothBleScanner/bibliography.ts +16 -0
  33. package/src/tool/webBluetoothBleScanner/component.astro +339 -0
  34. package/src/tool/webBluetoothBleScanner/entry.ts +29 -0
  35. package/src/tool/webBluetoothBleScanner/i18n/de.ts +233 -0
  36. package/src/tool/webBluetoothBleScanner/i18n/en.ts +233 -0
  37. package/src/tool/webBluetoothBleScanner/i18n/es.ts +233 -0
  38. package/src/tool/webBluetoothBleScanner/i18n/fr.ts +233 -0
  39. package/src/tool/webBluetoothBleScanner/i18n/id.ts +233 -0
  40. package/src/tool/webBluetoothBleScanner/i18n/it.ts +233 -0
  41. package/src/tool/webBluetoothBleScanner/i18n/ja.ts +233 -0
  42. package/src/tool/webBluetoothBleScanner/i18n/ko.ts +233 -0
  43. package/src/tool/webBluetoothBleScanner/i18n/nl.ts +233 -0
  44. package/src/tool/webBluetoothBleScanner/i18n/pl.ts +233 -0
  45. package/src/tool/webBluetoothBleScanner/i18n/pt.ts +233 -0
  46. package/src/tool/webBluetoothBleScanner/i18n/ru.ts +233 -0
  47. package/src/tool/webBluetoothBleScanner/i18n/sv.ts +233 -0
  48. package/src/tool/webBluetoothBleScanner/i18n/tr.ts +233 -0
  49. package/src/tool/webBluetoothBleScanner/i18n/zh.ts +233 -0
  50. package/src/tool/webBluetoothBleScanner/index.ts +11 -0
  51. package/src/tool/webBluetoothBleScanner/logic.ts +79 -0
  52. package/src/tool/webBluetoothBleScanner/seo.astro +15 -0
  53. package/src/tool/webBluetoothBleScanner/ui.ts +41 -0
  54. package/src/tool/webBluetoothBleScanner/web-bluetooth-ble-scanner.css +406 -0
  55. package/src/tool/webUsbSerialMonitor/bibliography.astro +15 -0
  56. package/src/tool/webUsbSerialMonitor/bibliography.ts +18 -0
  57. package/src/tool/webUsbSerialMonitor/component.astro +356 -0
  58. package/src/tool/webUsbSerialMonitor/entry.ts +30 -0
  59. package/src/tool/webUsbSerialMonitor/i18n/de.ts +241 -0
  60. package/src/tool/webUsbSerialMonitor/i18n/en.ts +241 -0
  61. package/src/tool/webUsbSerialMonitor/i18n/es.ts +241 -0
  62. package/src/tool/webUsbSerialMonitor/i18n/fr.ts +241 -0
  63. package/src/tool/webUsbSerialMonitor/i18n/id.ts +241 -0
  64. package/src/tool/webUsbSerialMonitor/i18n/it.ts +241 -0
  65. package/src/tool/webUsbSerialMonitor/i18n/ja.ts +241 -0
  66. package/src/tool/webUsbSerialMonitor/i18n/ko.ts +241 -0
  67. package/src/tool/webUsbSerialMonitor/i18n/nl.ts +241 -0
  68. package/src/tool/webUsbSerialMonitor/i18n/pl.ts +241 -0
  69. package/src/tool/webUsbSerialMonitor/i18n/pt.ts +241 -0
  70. package/src/tool/webUsbSerialMonitor/i18n/ru.ts +241 -0
  71. package/src/tool/webUsbSerialMonitor/i18n/sv.ts +241 -0
  72. package/src/tool/webUsbSerialMonitor/i18n/tr.ts +241 -0
  73. package/src/tool/webUsbSerialMonitor/i18n/zh.ts +241 -0
  74. package/src/tool/webUsbSerialMonitor/index.ts +12 -0
  75. package/src/tool/webUsbSerialMonitor/logic.ts +44 -0
  76. package/src/tool/webUsbSerialMonitor/seo.astro +16 -0
  77. package/src/tool/webUsbSerialMonitor/ui.ts +51 -0
  78. package/src/tool/webUsbSerialMonitor/web-usb-serial-monitor.css +415 -0
  79. package/src/tools.ts +4 -1
@@ -0,0 +1,512 @@
1
+ .kct-root {
2
+ --kct-ink: #101624;
3
+ --kct-muted: #647085;
4
+ --kct-panel: #f8fafc;
5
+ --kct-console: #e9f4ff;
6
+ --kct-console-2: #f8fcff;
7
+ --kct-console-ink: #102033;
8
+ --kct-console-muted: #53657d;
9
+ --kct-console-line: rgb(40, 96, 150, 0.18);
10
+ --kct-console-grid: rgb(36, 83, 135, 0.07);
11
+ --kct-console-chip: rgb(255, 255, 255, 0.72);
12
+ --kct-console-chip-border: rgb(31, 81, 130, 0.18);
13
+ --kct-console-button: rgb(255, 255, 255, 0.74);
14
+ --kct-console-button-hover: rgb(255, 255, 255, 0.95);
15
+ --kct-gauge-core: #f8fcff;
16
+ --kct-gauge-shadow: rgb(31, 81, 130, 0.16);
17
+ --kct-gauge-text: #294866;
18
+ --kct-strip-idle: rgb(32, 74, 118, 0.18);
19
+ --kct-surface: #fff;
20
+ --kct-soft: #edf2f7;
21
+ --kct-line: rgb(48, 63, 90, 0.16);
22
+ --kct-normal: #14b87a;
23
+ --kct-suspect: #f0a928;
24
+ --kct-chatter: #ef476f;
25
+ --kct-cyan: #22d3ee;
26
+ --kct-blue: #4f7cff;
27
+ --kct-shadow: rgb(16, 22, 36, 0.15);
28
+
29
+ color: var(--kct-ink);
30
+ }
31
+
32
+ .theme-dark .kct-root {
33
+ --kct-ink: #f8fafc;
34
+ --kct-muted: #9aa8bc;
35
+ --kct-panel: #0d1422;
36
+ --kct-console: #090f1c;
37
+ --kct-console-2: #142033;
38
+ --kct-console-ink: #f8fbff;
39
+ --kct-console-muted: #b8c6dc;
40
+ --kct-console-line: rgb(255, 255, 255, 0.16);
41
+ --kct-console-grid: rgb(255, 255, 255, 0.045);
42
+ --kct-console-chip: rgb(255, 255, 255, 0.08);
43
+ --kct-console-chip-border: rgb(255, 255, 255, 0.16);
44
+ --kct-console-button: rgb(255, 255, 255, 0.08);
45
+ --kct-console-button-hover: rgb(255, 255, 255, 0.15);
46
+ --kct-gauge-core: #090f1c;
47
+ --kct-gauge-shadow: rgb(0, 0, 0, 0.28);
48
+ --kct-gauge-text: #dce9ff;
49
+ --kct-strip-idle: rgb(255, 255, 255, 0.16);
50
+ --kct-surface: #121b2b;
51
+ --kct-soft: #172235;
52
+ --kct-line: rgb(184, 202, 232, 0.16);
53
+ --kct-shadow: rgb(0, 0, 0, 0.36);
54
+ }
55
+
56
+ .kct-panel {
57
+ display: grid;
58
+ gap: 0.75rem;
59
+ width: 100%;
60
+ padding: 0.75rem;
61
+ border: 1px solid var(--kct-line);
62
+ border-radius: 8px;
63
+ background:
64
+ linear-gradient(135deg, color-mix(in srgb, var(--kct-cyan) 8%, transparent), transparent 32%),
65
+ linear-gradient(315deg, color-mix(in srgb, var(--kct-chatter) 7%, transparent), transparent 36%),
66
+ var(--kct-panel);
67
+ box-shadow: 0 1rem 2.5rem var(--kct-shadow);
68
+ outline: none;
69
+ }
70
+
71
+ .kct-panel > * {
72
+ min-width: 0;
73
+ min-height: 0;
74
+ }
75
+
76
+ .kct-console {
77
+ position: relative;
78
+ display: grid;
79
+ grid-template-columns: minmax(0, 1fr) auto;
80
+ gap: 0.8rem;
81
+ min-height: 12rem;
82
+ padding: 0.8rem;
83
+ border: 1px solid color-mix(in srgb, var(--kct-cyan) 20%, var(--kct-console-line));
84
+ border-radius: 8px;
85
+ background:
86
+ linear-gradient(135deg, color-mix(in srgb, var(--kct-blue) 26%, transparent), transparent 42%),
87
+ linear-gradient(315deg, color-mix(in srgb, var(--kct-normal) 16%, transparent), transparent 38%),
88
+ linear-gradient(180deg, var(--kct-console-2), var(--kct-console));
89
+ color: var(--kct-console-ink);
90
+ overflow: hidden;
91
+ }
92
+
93
+ .kct-console::before {
94
+ position: absolute;
95
+ inset: 0;
96
+ background-image:
97
+ linear-gradient(var(--kct-console-grid) 1px, transparent 1px),
98
+ linear-gradient(90deg, var(--kct-console-grid) 1px, transparent 1px);
99
+ background-size: 18px 18px;
100
+ content: "";
101
+ mask-image: linear-gradient(90deg, #000, transparent 68%);
102
+ }
103
+
104
+ .kct-readout,
105
+ .kct-actions,
106
+ .kct-gauge,
107
+ .kct-strip {
108
+ position: relative;
109
+ z-index: 1;
110
+ }
111
+
112
+ .kct-readout {
113
+ display: grid;
114
+ align-content: end;
115
+ gap: 0.45rem;
116
+ min-width: 0;
117
+ padding: 0.4rem;
118
+ }
119
+
120
+ .kct-readout span {
121
+ width: fit-content;
122
+ padding: 0.32rem 0.55rem;
123
+ border: 1px solid var(--kct-console-chip-border);
124
+ border-radius: 999px;
125
+ background: var(--kct-console-chip);
126
+ color: color-mix(in srgb, var(--kct-cyan) 45%, var(--kct-console-ink));
127
+ font-size: 0.68rem;
128
+ font-weight: 900;
129
+ letter-spacing: 0.04em;
130
+ text-transform: uppercase;
131
+ }
132
+
133
+ .kct-readout strong {
134
+ max-width: 100%;
135
+ color: var(--kct-console-ink);
136
+ font-size: clamp(2rem, 10vw, 4.9rem);
137
+ font-weight: 950;
138
+ line-height: 1.08;
139
+ overflow: hidden;
140
+ text-overflow: ellipsis;
141
+ white-space: nowrap;
142
+ }
143
+
144
+ .kct-readout em,
145
+ .kct-hint {
146
+ color: var(--kct-muted);
147
+ font-size: 0.82rem;
148
+ font-style: normal;
149
+ font-weight: 750;
150
+ line-height: 1.35;
151
+ }
152
+
153
+ .kct-readout em {
154
+ max-width: 34rem;
155
+ color: var(--kct-console-muted);
156
+ }
157
+
158
+ .kct-actions {
159
+ display: flex;
160
+ gap: 0.45rem;
161
+ justify-self: end;
162
+ align-self: start;
163
+ }
164
+
165
+ .kct-actions button {
166
+ display: grid;
167
+ place-items: center;
168
+ width: 2.45rem;
169
+ aspect-ratio: 1;
170
+ border: 1px solid var(--kct-console-chip-border);
171
+ border-radius: 8px;
172
+ background: var(--kct-console-button);
173
+ color: var(--kct-console-ink);
174
+ cursor: pointer;
175
+ transition: background 160ms ease, transform 160ms ease;
176
+ }
177
+
178
+ .kct-actions button:hover {
179
+ background: var(--kct-console-button-hover);
180
+ transform: translateY(-1px);
181
+ }
182
+
183
+ .kct-actions svg {
184
+ width: 1.08rem;
185
+ height: 1.08rem;
186
+ }
187
+
188
+ .kct-gauge {
189
+ grid-column: 2;
190
+ grid-row: 1 / span 2;
191
+ display: grid;
192
+ place-items: center;
193
+ align-self: end;
194
+ width: min(10rem, 28vw);
195
+ aspect-ratio: 1;
196
+ margin: 0.3rem;
197
+ border-radius: 50%;
198
+ background:
199
+ radial-gradient(circle at 50% 50%, var(--kct-gauge-core) 0 36%, transparent 37%),
200
+ conic-gradient(var(--kct-chatter) 0 18%, var(--kct-suspect) 18% 42%, var(--kct-normal) 42% 100%);
201
+ box-shadow: inset 0 0 0 1px var(--kct-console-chip-border), 0 1rem 2rem var(--kct-gauge-shadow);
202
+ }
203
+
204
+ .kct-gauge span {
205
+ width: 42%;
206
+ aspect-ratio: 1;
207
+ border-radius: 50%;
208
+ background: #f8fbff;
209
+ box-shadow: 0 0 2rem color-mix(in srgb, var(--kct-cyan) 45%, transparent);
210
+ animation: kct-hit 360ms ease-out;
211
+ }
212
+
213
+ .kct-root[data-severity="normal"] .kct-gauge span {
214
+ background: var(--kct-normal);
215
+ }
216
+
217
+ .kct-root[data-severity="suspect"] .kct-gauge span {
218
+ background: var(--kct-suspect);
219
+ }
220
+
221
+ .kct-root[data-severity="chatter"] .kct-gauge span {
222
+ background: var(--kct-chatter);
223
+ }
224
+
225
+ .kct-gauge i {
226
+ position: absolute;
227
+ color: var(--kct-gauge-text);
228
+ font-size: 0.65rem;
229
+ font-style: normal;
230
+ font-weight: 950;
231
+ }
232
+
233
+ .kct-gauge i:nth-of-type(1) {
234
+ right: 18%;
235
+ bottom: 31%;
236
+ }
237
+
238
+ .kct-gauge i:nth-of-type(2) {
239
+ left: 24%;
240
+ bottom: 18%;
241
+ }
242
+
243
+ .kct-strip {
244
+ grid-column: 1 / -1;
245
+ display: grid;
246
+ grid-template-columns: repeat(6, minmax(0, 1fr));
247
+ gap: 0.35rem;
248
+ align-self: end;
249
+ }
250
+
251
+ .kct-strip span {
252
+ height: 0.38rem;
253
+ border-radius: 999px;
254
+ background: var(--kct-strip-idle);
255
+ }
256
+
257
+ .kct-root[data-severity="normal"] .kct-strip span:nth-child(-n + 4) {
258
+ background: var(--kct-normal);
259
+ }
260
+
261
+ .kct-root[data-severity="suspect"] .kct-strip span:nth-child(-n + 5) {
262
+ background: var(--kct-suspect);
263
+ }
264
+
265
+ .kct-root[data-severity="chatter"] .kct-strip span {
266
+ background: var(--kct-chatter);
267
+ }
268
+
269
+ .kct-keyboard {
270
+ display: grid;
271
+ grid-template-columns: repeat(12, minmax(0, 1fr));
272
+ gap: 0.35rem;
273
+ padding: 0.65rem;
274
+ border: 1px solid var(--kct-line);
275
+ border-radius: 8px;
276
+ background: var(--kct-surface);
277
+ }
278
+
279
+ .kct-keyboard kbd {
280
+ display: grid;
281
+ place-items: center;
282
+ min-width: 0;
283
+ min-height: 2.2rem;
284
+ border: 1px solid var(--kct-line);
285
+ border-bottom-width: 3px;
286
+ border-radius: 6px;
287
+ background: var(--kct-soft);
288
+ color: var(--kct-ink);
289
+ font: inherit;
290
+ font-size: 0.72rem;
291
+ font-weight: 900;
292
+ transition: background 150ms ease, border-color 150ms ease, box-shadow 150ms ease, transform 150ms ease;
293
+ }
294
+
295
+ .kct-keyboard .wide {
296
+ grid-column: span 2;
297
+ }
298
+
299
+ .kct-keyboard .space {
300
+ grid-column: span 4;
301
+ }
302
+
303
+ .kct-keyboard kbd.is-hit {
304
+ border-color: var(--row-color, var(--kct-normal));
305
+ background: color-mix(in srgb, var(--row-color, var(--kct-normal)) 18%, var(--kct-soft));
306
+ box-shadow: 0 0.55rem 1.3rem color-mix(in srgb, var(--row-color, var(--kct-normal)) 20%, transparent);
307
+ transform: translateY(1px);
308
+ }
309
+
310
+ .kct-metrics {
311
+ display: grid;
312
+ grid-template-columns: repeat(4, minmax(0, 1fr));
313
+ gap: 0.55rem;
314
+ }
315
+
316
+ .kct-metrics article {
317
+ display: grid;
318
+ gap: 0.35rem;
319
+ min-height: 4.6rem;
320
+ padding: 0.7rem;
321
+ border: 1px solid var(--kct-line);
322
+ border-radius: 8px;
323
+ background: var(--kct-surface);
324
+ }
325
+
326
+ .kct-metrics span {
327
+ color: var(--kct-muted);
328
+ font-size: 0.7rem;
329
+ font-weight: 900;
330
+ }
331
+
332
+ .kct-metrics strong {
333
+ color: var(--kct-ink);
334
+ font-size: clamp(1.35rem, 4vw, 2rem);
335
+ font-weight: 950;
336
+ line-height: 1.12;
337
+ }
338
+
339
+ .kct-log {
340
+ display: grid;
341
+ align-content: start;
342
+ max-height: 22rem;
343
+ border: 1px solid var(--kct-line);
344
+ border-radius: 8px;
345
+ background: var(--kct-surface);
346
+ overflow: auto;
347
+ overscroll-behavior: contain;
348
+ }
349
+
350
+ .kct-row {
351
+ display: grid;
352
+ grid-template-columns: minmax(6.5rem, 1fr) minmax(5rem, 0.58fr) minmax(5.6rem, 0.64fr) minmax(4.8rem, 0.54fr);
353
+ gap: 0.45rem;
354
+ align-items: center;
355
+ min-height: 2.7rem;
356
+ padding: 0.45rem 0.6rem;
357
+ border-bottom: 1px solid var(--kct-line);
358
+ color: var(--kct-ink);
359
+ font-size: 0.78rem;
360
+ font-weight: 850;
361
+ }
362
+
363
+ .kct-row span {
364
+ min-width: 0;
365
+ }
366
+
367
+ .kct-row b,
368
+ .kct-row small {
369
+ display: block;
370
+ overflow: hidden;
371
+ text-overflow: ellipsis;
372
+ white-space: nowrap;
373
+ }
374
+
375
+ .kct-row small {
376
+ color: var(--kct-muted);
377
+ font-size: 0.66rem;
378
+ }
379
+
380
+ .kct-row i {
381
+ display: inline-grid;
382
+ min-width: 4.7rem;
383
+ padding: 0.24rem 0.42rem;
384
+ border-radius: 999px;
385
+ background: color-mix(in srgb, var(--row-color, var(--kct-normal)) 13%, transparent);
386
+ color: var(--row-color, var(--kct-normal));
387
+ font-style: normal;
388
+ font-weight: 950;
389
+ text-align: center;
390
+ }
391
+
392
+ .kct-head {
393
+ position: sticky;
394
+ z-index: 1;
395
+ top: 0;
396
+ min-height: 2.4rem;
397
+ background: var(--kct-soft);
398
+ color: var(--kct-muted);
399
+ font-size: 0.68rem;
400
+ text-transform: uppercase;
401
+ }
402
+
403
+ .is-normal {
404
+ --row-color: var(--kct-normal);
405
+ }
406
+
407
+ .is-suspect {
408
+ --row-color: var(--kct-suspect);
409
+ }
410
+
411
+ .is-chatter {
412
+ --row-color: var(--kct-chatter);
413
+ }
414
+
415
+ .kct-hint {
416
+ margin: 0;
417
+ padding: 0.85rem 0.95rem;
418
+ border: 1px solid var(--kct-line);
419
+ border-radius: 8px;
420
+ background: var(--kct-surface);
421
+ line-height: 1.5;
422
+ overflow: visible;
423
+ overflow-wrap: anywhere;
424
+ }
425
+
426
+ @keyframes kct-hit {
427
+ 0% {
428
+ transform: scale(0.78);
429
+ filter: brightness(1.6);
430
+ }
431
+ 100% {
432
+ transform: scale(1);
433
+ filter: brightness(1);
434
+ }
435
+ }
436
+
437
+ @media (min-width: 900px) {
438
+ .kct-panel {
439
+ grid-template-columns: minmax(0, 1.15fr) minmax(22rem, 0.85fr);
440
+ grid-template-rows: auto auto auto minmax(0, 1fr);
441
+ }
442
+
443
+ .kct-console,
444
+ .kct-keyboard,
445
+ .kct-metrics,
446
+ .kct-hint {
447
+ grid-column: 1;
448
+ }
449
+
450
+ .kct-log {
451
+ grid-column: 2;
452
+ grid-row: 1 / span 4;
453
+ align-self: start;
454
+ width: 100%;
455
+ max-height: 34rem;
456
+ }
457
+ }
458
+
459
+ @media (max-width: 640px) {
460
+ .kct-panel {
461
+ padding: 0.55rem;
462
+ }
463
+
464
+ .kct-console {
465
+ grid-template-columns: minmax(0, 1fr);
466
+ min-height: 14rem;
467
+ }
468
+
469
+ .kct-actions {
470
+ position: absolute;
471
+ top: 0.75rem;
472
+ right: 0.75rem;
473
+ }
474
+
475
+ .kct-gauge {
476
+ grid-column: 1;
477
+ grid-row: auto;
478
+ width: 7rem;
479
+ justify-self: end;
480
+ align-self: center;
481
+ }
482
+
483
+ .kct-readout {
484
+ padding-top: 3rem;
485
+ }
486
+
487
+ .kct-keyboard {
488
+ grid-template-columns: repeat(6, minmax(0, 1fr));
489
+ }
490
+
491
+ .kct-keyboard .wide,
492
+ .kct-keyboard .space {
493
+ grid-column: span 2;
494
+ }
495
+
496
+ .kct-metrics,
497
+ .kct-row {
498
+ grid-template-columns: 1fr 1fr;
499
+ }
500
+
501
+ .kct-head {
502
+ display: none;
503
+ }
504
+ }
505
+
506
+ @media (prefers-reduced-motion: reduce) {
507
+ .kct-gauge span,
508
+ .kct-keyboard kbd {
509
+ animation: none;
510
+ transition: none;
511
+ }
512
+ }
@@ -0,0 +1,23 @@
1
+ export type ChatterSeverity = 'normal' | 'suspect' | 'chatter';
2
+
3
+ export interface ChatterThresholds {
4
+ suspectMs: number;
5
+ chatterMs: number;
6
+ }
7
+
8
+ export const defaultChatterThresholds: ChatterThresholds = {
9
+ suspectMs: 50,
10
+ chatterMs: 30,
11
+ };
12
+
13
+ export function classifyKeyDelta(deltaMs: number, thresholds = defaultChatterThresholds): ChatterSeverity {
14
+ if (deltaMs < thresholds.chatterMs) return 'chatter';
15
+ if (deltaMs <= thresholds.suspectMs) return 'suspect';
16
+ return 'normal';
17
+ }
18
+
19
+ export function formatDelta(deltaMs: number | null): string {
20
+ if (deltaMs === null || !Number.isFinite(deltaMs)) return '--';
21
+ return `${Math.max(0, deltaMs).toFixed(1)} ms`;
22
+ }
23
+
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { keyboardChatterTest } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await keyboardChatterTest.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
16
+
@@ -0,0 +1,34 @@
1
+ export interface KeyboardChatterTestUI extends Record<string, string> {
2
+ statusIdle: string;
3
+ statusListening: string;
4
+ statusChatter: string;
5
+ totalPresses: string;
6
+ chatterEvents: string;
7
+ worstDelta: string;
8
+ watchWindow: string;
9
+ keyColumn: string;
10
+ deltaColumn: string;
11
+ verdictColumn: string;
12
+ timeColumn: string;
13
+ normal: string;
14
+ suspect: string;
15
+ chatter: string;
16
+ waiting: string;
17
+ clear: string;
18
+ exportLog: string;
19
+ hint: string;
20
+ captureNotice: string;
21
+ keyboardAriaLabel: string;
22
+ logAriaLabel: string;
23
+ escapeKey: string;
24
+ backspaceKey: string;
25
+ tabKey: string;
26
+ enterKey: string;
27
+ capsLockKey: string;
28
+ shiftKey: string;
29
+ controlKey: string;
30
+ metaKey: string;
31
+ altKey: string;
32
+ spaceKey: string;
33
+ csvHeader: string;
34
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import type { KnownLocale } from '../../types';
4
+ import { webBluetoothBleScanner } from './index';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await webBluetoothBleScanner.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && content.bibliography.length > 0 && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,16 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Bluetooth SIG - Bluetooth Low Energy overview',
6
+ url: 'https://www.bluetooth.com/learn-about-bluetooth/tech-overview/',
7
+ },
8
+ {
9
+ name: 'Bluetooth SIG - GATT assigned services',
10
+ url: 'https://www.bluetooth.com/specifications/assigned-numbers/',
11
+ },
12
+ {
13
+ name: 'Nordic Semiconductor - Bluetooth LE fundamentals',
14
+ url: 'https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/',
15
+ }
16
+ ];