@jjlmoya/utils-hardware 1.11.0 → 1.13.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 (32) hide show
  1. package/package.json +7 -4
  2. package/scripts/postinstall.mjs +27 -0
  3. package/src/entries.ts +23 -0
  4. package/src/tool/batteryHealthEstimator/component.astro +2 -369
  5. package/src/tool/batteryHealthEstimator/entry.ts +30 -0
  6. package/src/tool/batteryHealthEstimator/index.ts +2 -32
  7. package/src/tool/batteryHealthEstimator/lithium-battery-health-calculator.css +275 -0
  8. package/src/tool/deadPixelTest/component.astro +2 -445
  9. package/src/tool/deadPixelTest/dead-pixel-tester.css +400 -0
  10. package/src/tool/deadPixelTest/entry.ts +30 -0
  11. package/src/tool/deadPixelTest/index.ts +2 -32
  12. package/src/tool/gamepadTest/component.astro +2 -973
  13. package/src/tool/gamepadTest/entry.ts +30 -0
  14. package/src/tool/gamepadTest/gamepad-test.css +882 -0
  15. package/src/tool/gamepadTest/index.ts +2 -32
  16. package/src/tool/gamepadVibrationTester/component.astro +2 -404
  17. package/src/tool/gamepadVibrationTester/entry.ts +30 -0
  18. package/src/tool/gamepadVibrationTester/gamepad-vibration-tester.css +280 -0
  19. package/src/tool/gamepadVibrationTester/index.ts +2 -32
  20. package/src/tool/keyboardTest/component.astro +2 -333
  21. package/src/tool/keyboardTest/entry.ts +30 -0
  22. package/src/tool/keyboardTest/index.ts +2 -32
  23. package/src/tool/keyboardTest/keyboard-test.css +283 -0
  24. package/src/tool/mousePollingTest/component.astro +2 -172
  25. package/src/tool/mousePollingTest/entry.ts +30 -0
  26. package/src/tool/mousePollingTest/index.ts +2 -32
  27. package/src/tool/mousePollingTest/mouse-polling-rate-test.css +143 -0
  28. package/src/tool/toneGenerator/component.astro +2 -330
  29. package/src/tool/toneGenerator/entry.ts +30 -0
  30. package/src/tool/toneGenerator/index.ts +2 -32
  31. package/src/tool/toneGenerator/tone-frequency-generator.css +265 -0
  32. package/src/tools.ts +1 -1
@@ -1,35 +1,5 @@
1
- import type { HardwareToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
2
-
3
-
4
- import type { TestTecladoUI } from './ui';
5
- export type TestTecladoLocaleContent = ToolLocaleContent<TestTecladoUI>;
6
-
7
- export const testTeclado: HardwareToolEntry<TestTecladoUI> = {
8
- id: 'test-teclado',
9
- icons: {
10
- bg: 'mdi:keyboard-variant',
11
- fg: 'mdi:keyboard-settings',
12
- },
13
- i18n: {
14
- de: () => import('./i18n/de').then((m) => m.content),
15
- en: () => import('./i18n/en').then((m) => m.content),
16
- es: () => import('./i18n/es').then((m) => m.content),
17
- fr: () => import('./i18n/fr').then((m) => m.content),
18
- id: () => import('./i18n/id').then((m) => m.content),
19
- it: () => import('./i18n/it').then((m) => m.content),
20
- ja: () => import('./i18n/ja').then((m) => m.content),
21
- ko: () => import('./i18n/ko').then((m) => m.content),
22
- nl: () => import('./i18n/nl').then((m) => m.content),
23
- pl: () => import('./i18n/pl').then((m) => m.content),
24
- pt: () => import('./i18n/pt').then((m) => m.content),
25
- ru: () => import('./i18n/ru').then((m) => m.content),
26
- sv: () => import('./i18n/sv').then((m) => m.content),
27
- tr: () => import('./i18n/tr').then((m) => m.content),
28
- zh: () => import('./i18n/zh').then((m) => m.content),
29
- },
30
- };
31
-
32
-
1
+ import { testTeclado } from './entry';
2
+ export * from './entry';
33
3
  export const TEST_TECLADO_TOOL: ToolDefinition = {
34
4
  entry: testTeclado,
35
5
  Component: () => import('./component.astro'),
@@ -0,0 +1,283 @@
1
+ .tt-wrapper {
2
+ width: 100%;
3
+ color: var(--tt-text);
4
+ }
5
+
6
+ .tt-container {
7
+ max-width: 80rem;
8
+ margin: 0 auto;
9
+ padding: 1rem;
10
+ }
11
+
12
+ .tt-card {
13
+ background: var(--tt-surface);
14
+ border-radius: 1.5rem;
15
+ padding: 2rem;
16
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
17
+ border: 1px solid var(--tt-border);
18
+ }
19
+
20
+ .tt-header {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: 1rem;
24
+ margin-bottom: 2rem;
25
+ width: 100%;
26
+ }
27
+
28
+ @media (min-width: 768px) {
29
+ .tt-header {
30
+ flex-direction: row;
31
+ justify-content: space-between;
32
+ align-items: flex-start;
33
+ }
34
+ }
35
+
36
+ .tt-header-left {
37
+ flex: 1;
38
+ }
39
+
40
+ .tt-badge {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: 0.5rem;
44
+ padding: 0.25rem 0.75rem;
45
+ border-radius: 9999px;
46
+ background-color: rgba(79, 70, 229, 0.1);
47
+ color: var(--tt-accent);
48
+ font-size: 0.75rem;
49
+ font-weight: 700;
50
+ text-transform: uppercase;
51
+ letter-spacing: 0.05em;
52
+ margin-bottom: 0.5rem;
53
+ }
54
+
55
+ .tt-badge-icon {
56
+ width: 1rem;
57
+ height: 1rem;
58
+ }
59
+
60
+ .tt-title {
61
+ font-size: 1.875rem;
62
+ font-weight: 900;
63
+ letter-spacing: -0.02em;
64
+ margin: 0 0 0.25rem;
65
+ }
66
+
67
+ .tt-description {
68
+ font-size: 0.9375rem;
69
+ color: var(--tt-text-muted);
70
+ margin: 0;
71
+ }
72
+
73
+ .tt-stats {
74
+ display: flex;
75
+ gap: 1rem;
76
+ flex-wrap: wrap;
77
+ }
78
+
79
+ .tt-stat-box {
80
+ background: var(--tt-surface-alt);
81
+ padding: 1rem;
82
+ border-radius: 1rem;
83
+ text-align: center;
84
+ min-width: 120px;
85
+ }
86
+
87
+ .tt-stat-label {
88
+ font-size: 0.75rem;
89
+ font-weight: 700;
90
+ text-transform: uppercase;
91
+ color: var(--tt-text-muted);
92
+ letter-spacing: 0.05em;
93
+ }
94
+
95
+ .tt-stat-value {
96
+ font-size: 2.25rem;
97
+ font-weight: 900;
98
+ color: var(--tt-accent);
99
+ margin-top: 0.5rem;
100
+ }
101
+
102
+ .tt-reset-btn {
103
+ padding: 1rem;
104
+ background: var(--tt-surface-alt);
105
+ border: none;
106
+ border-radius: 1rem;
107
+ cursor: pointer;
108
+ color: var(--tt-text-muted);
109
+ transition: all 0.2s ease;
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: center;
113
+ }
114
+
115
+ .tt-reset-btn:hover {
116
+ background: var(--tt-border);
117
+ }
118
+
119
+ .tt-reset-icon {
120
+ width: 1.5rem;
121
+ height: 1.5rem;
122
+ }
123
+
124
+ .tt-keyboard-wrapper {
125
+ position: relative;
126
+ width: 100%;
127
+ overflow-x: auto;
128
+ padding-bottom: 1rem;
129
+ display: flex;
130
+ justify-content: center;
131
+ }
132
+
133
+ .tt-keyboard {
134
+ background: var(--tt-kb-bg);
135
+ padding: 0.5rem;
136
+ border-radius: 0.75rem;
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 0.25rem;
140
+ user-select: none;
141
+ min-width: 900px;
142
+ }
143
+
144
+ .tt-row {
145
+ display: flex;
146
+ gap: 0.25rem;
147
+ }
148
+
149
+ .tt-key {
150
+ height: 3rem;
151
+ background: var(--tt-key-bg);
152
+ border-radius: 0.5rem;
153
+ display: flex;
154
+ align-items: center;
155
+ justify-content: center;
156
+ font-weight: 700;
157
+ font-size: 0.75rem;
158
+ color: var(--tt-key-text);
159
+ border-top: 1px solid rgba(255, 255, 255, 0.3);
160
+ box-shadow: 0 2px 0 var(--tt-key-shadow);
161
+ transition: all 300ms ease-out;
162
+ border: 1px solid var(--tt-key-border);
163
+ }
164
+
165
+ .tt-key-esc,
166
+ .tt-key-f,
167
+ .tt-key-sm {
168
+ width: 3rem;
169
+ }
170
+
171
+ .tt-key-md {
172
+ width: 4rem;
173
+ }
174
+
175
+ .tt-key-lg {
176
+ width: 5rem;
177
+ }
178
+
179
+ .tt-key-xl {
180
+ width: 8rem;
181
+ }
182
+
183
+ .tt-key-xxl {
184
+ width: 18rem;
185
+ }
186
+
187
+ .tt-key.tt-key-active {
188
+ background: var(--tt-key-active-bg);
189
+ color: white;
190
+ box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.2);
191
+ transform: translateY(1px);
192
+ border-color: var(--tt-accent);
193
+ }
194
+
195
+ .tt-key.tt-key-tested {
196
+ background: var(--tt-key-tested-bg);
197
+ color: var(--tt-key-tested-text);
198
+ border-color: var(--tt-key-tested-text);
199
+ }
200
+
201
+ .tt-key.tt-key-releasing {
202
+ background: var(--tt-key-bg);
203
+ color: var(--tt-key-text);
204
+ box-shadow: 0 2px 0 var(--tt-key-shadow);
205
+ transform: translateY(0);
206
+ border-color: var(--tt-key-border);
207
+ }
208
+
209
+ .tt-spacer-4 {
210
+ width: 0.25rem;
211
+ }
212
+
213
+ .tt-spacer-8 {
214
+ width: 0.5rem;
215
+ }
216
+
217
+ .tt-spacer-12 {
218
+ width: 0.75rem;
219
+ }
220
+
221
+ .tt-spacer-108 {
222
+ width: 6.75rem;
223
+ }
224
+
225
+ .tt-event-log-container {
226
+ margin-top: 0.75rem;
227
+ padding: 0;
228
+ background: transparent;
229
+ border-radius: 0;
230
+ border: none;
231
+ width: 100%;
232
+ }
233
+
234
+ .tt-event-log-header {
235
+ display: none;
236
+ }
237
+
238
+ .tt-event-log-icon {
239
+ display: none;
240
+ }
241
+
242
+ .tt-event-log-label {
243
+ display: none;
244
+ }
245
+
246
+ .tt-event-log {
247
+ font-size: 0.65rem;
248
+ color: var(--tt-text);
249
+ height: 2rem;
250
+ overflow-x: auto;
251
+ overflow-y: hidden;
252
+ background: var(--tt-surface-alt);
253
+ padding: 0.375rem;
254
+ border-radius: 0.5rem;
255
+ border: 1px solid var(--tt-border);
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: flex-start;
259
+ gap: 0.5rem;
260
+ white-space: nowrap;
261
+ width: 100%;
262
+ scrollbar-width: none;
263
+ }
264
+
265
+ .tt-event-log::-webkit-scrollbar {
266
+ display: none;
267
+ }
268
+
269
+ .tt-event-log div {
270
+ display: inline-flex;
271
+ align-items: center;
272
+ justify-content: center;
273
+ line-height: 1;
274
+ font-weight: 600;
275
+ padding: 0.25rem 0.5rem;
276
+ min-width: 1.5rem;
277
+ height: 1.5rem;
278
+ background: var(--tt-key-bg);
279
+ border: 1px solid var(--tt-key-border);
280
+ border-radius: 0.375rem;
281
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
282
+ flex-shrink: 0;
283
+ }
@@ -14,32 +14,7 @@ const t = (ui ?? {}) as TestRatonUI;
14
14
 
15
15
  <div id="tr-root" class="tr-wrapper">
16
16
 
17
- <style is:global>
18
- .tr-wrapper {
19
- --tr-accent: #10b981;
20
- --tr-accent-glow: rgba(16, 185, 129, 0.4);
21
- --tr-surface: #fff;
22
- --tr-surface-alt: #f1f5f9;
23
- --tr-surface-dark: #020617;
24
- --tr-border: #e2e8f0;
25
- --tr-text: #0f172a;
26
- --tr-text-muted: #64748b;
27
- }
28
-
29
- .theme-dark .tr-wrapper {
30
- --tr-surface: #0f172a;
31
- --tr-surface-alt: #1e293b;
32
- --tr-surface-dark: #020617;
33
- --tr-border: #334155;
34
- --tr-text: #f1f5f9;
35
- --tr-text-muted: #94a3b8;
36
- }
37
-
38
- body.is-widget .tr-wrapper {
39
- padding: 0;
40
- }
41
- </style>
42
-
17
+
43
18
  <div class="tr-card">
44
19
  <div class="tr-header">
45
20
  <div class="tr-header-info">
@@ -76,152 +51,7 @@ const t = (ui ?? {}) as TestRatonUI;
76
51
  </div>
77
52
  </div>
78
53
 
79
- <style>
80
- .tr-wrapper {
81
- width: 100%;
82
- max-width: 56rem;
83
- margin: 0 auto;
84
- color: var(--tr-text);
85
- }
86
-
87
- .tr-card {
88
- background: var(--tr-surface);
89
- border: 1px solid var(--tr-border);
90
- border-radius: 1.5rem;
91
- padding: 2rem;
92
- box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.1);
93
- display: flex;
94
- flex-direction: column;
95
- gap: 1.5rem;
96
- }
97
-
98
- .tr-header {
99
- display: flex;
100
- justify-content: space-between;
101
- align-items: center;
102
- gap: 1rem;
103
- flex-wrap: wrap;
104
- }
105
-
106
- .tr-header-info {
107
- display: flex;
108
- flex-direction: column;
109
- gap: 0.25rem;
110
- }
111
-
112
- .tr-badge {
113
- display: inline-flex;
114
- align-items: center;
115
- gap: 0.5rem;
116
- padding: 0.25rem 0.75rem;
117
- border-radius: 9999px;
118
- background: rgba(16, 185, 129, 0.1);
119
- color: var(--tr-accent);
120
- font-size: 0.75rem;
121
- font-weight: 700;
122
- text-transform: uppercase;
123
- letter-spacing: 0.05em;
124
- }
125
-
126
- .tr-badge-icon {
127
- width: 1rem;
128
- height: 1rem;
129
- }
130
-
131
- .tr-title {
132
- margin: 0;
133
- font-size: 1.875rem;
134
- font-weight: 900;
135
- letter-spacing: -0.02em;
136
- }
137
-
138
- .tr-description {
139
- margin: 0;
140
- color: var(--tr-text-muted);
141
- }
142
-
143
- .tr-stats {
144
- display: flex;
145
- gap: 1rem;
146
- }
147
-
148
- .tr-stat {
149
- background: var(--tr-surface-alt);
150
- border-radius: 1rem;
151
- padding: 1rem;
152
- min-width: 7.5rem;
153
- text-align: center;
154
- }
155
-
156
- .tr-stat-label {
157
- font-size: 0.75rem;
158
- font-weight: 700;
159
- color: var(--tr-text-muted);
160
- text-transform: uppercase;
161
- letter-spacing: 0.05em;
162
- }
163
-
164
- .tr-stat-value {
165
- display: flex;
166
- align-items: baseline;
167
- justify-content: center;
168
- gap: 0.25rem;
169
- font-size: 2.25rem;
170
- font-weight: 900;
171
- color: var(--tr-accent);
172
- }
173
-
174
- .tr-stat-unit {
175
- font-size: 1rem;
176
- color: var(--tr-text-muted);
177
- }
178
-
179
- .tr-area {
180
- position: relative;
181
- width: 100%;
182
- height: 400px;
183
- background: var(--tr-surface-alt);
184
- border-radius: 1rem;
185
- border: 2px dashed var(--tr-border);
186
- overflow: hidden;
187
- cursor: crosshair;
188
- display: flex;
189
- align-items: center;
190
- justify-content: center;
191
- }
192
-
193
- .tr-placeholder {
194
- color: var(--tr-border);
195
- font-weight: 700;
196
- font-size: 1.25rem;
197
- pointer-events: none;
198
- transition: opacity 200ms;
199
- }
200
-
201
- .tr-canvas {
202
- position: absolute;
203
- inset: 0;
204
- width: 100%;
205
- height: 100%;
206
- opacity: 0.6;
207
- }
208
-
209
- .tr-follower {
210
- position: absolute;
211
- pointer-events: none;
212
- background: var(--tr-accent);
213
- color: #fff;
214
- font-size: 0.75rem;
215
- font-weight: 700;
216
- padding: 0.25rem 0.5rem;
217
- border-radius: 9999px;
218
- box-shadow: 0 4px 10px var(--tr-accent-glow);
219
- opacity: 0;
220
- transition: opacity 75ms;
221
- transform: translate(0, 0);
222
- }
223
- </style>
224
-
54
+
225
55
  <script>
226
56
  import { RatonManager } from './logic/RatonManager';
227
57
 
@@ -0,0 +1,30 @@
1
+ import type { HardwareToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+
4
+ import type { TestRatonUI } from './ui';
5
+ export type TestRatonLocaleContent = ToolLocaleContent<TestRatonUI>;
6
+
7
+ export const testRaton: HardwareToolEntry<TestRatonUI> = {
8
+ id: 'test-raton',
9
+ icons: {
10
+ bg: 'mdi:mouse-variant',
11
+ fg: 'mdi:mouse',
12
+ },
13
+ i18n: {
14
+ de: () => import('./i18n/de').then((m) => m.content),
15
+ en: () => import('./i18n/en').then((m) => m.content),
16
+ es: () => import('./i18n/es').then((m) => m.content),
17
+ fr: () => import('./i18n/fr').then((m) => m.content),
18
+ id: () => import('./i18n/id').then((m) => m.content),
19
+ it: () => import('./i18n/it').then((m) => m.content),
20
+ ja: () => import('./i18n/ja').then((m) => m.content),
21
+ ko: () => import('./i18n/ko').then((m) => m.content),
22
+ nl: () => import('./i18n/nl').then((m) => m.content),
23
+ pl: () => import('./i18n/pl').then((m) => m.content),
24
+ pt: () => import('./i18n/pt').then((m) => m.content),
25
+ ru: () => import('./i18n/ru').then((m) => m.content),
26
+ sv: () => import('./i18n/sv').then((m) => m.content),
27
+ tr: () => import('./i18n/tr').then((m) => m.content),
28
+ zh: () => import('./i18n/zh').then((m) => m.content),
29
+ },
30
+ };
@@ -1,35 +1,5 @@
1
- import type { HardwareToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
2
-
3
-
4
- import type { TestRatonUI } from './ui';
5
- export type TestRatonLocaleContent = ToolLocaleContent<TestRatonUI>;
6
-
7
- export const testRaton: HardwareToolEntry<TestRatonUI> = {
8
- id: 'test-raton',
9
- icons: {
10
- bg: 'mdi:mouse-variant',
11
- fg: 'mdi:mouse',
12
- },
13
- i18n: {
14
- de: () => import('./i18n/de').then((m) => m.content),
15
- en: () => import('./i18n/en').then((m) => m.content),
16
- es: () => import('./i18n/es').then((m) => m.content),
17
- fr: () => import('./i18n/fr').then((m) => m.content),
18
- id: () => import('./i18n/id').then((m) => m.content),
19
- it: () => import('./i18n/it').then((m) => m.content),
20
- ja: () => import('./i18n/ja').then((m) => m.content),
21
- ko: () => import('./i18n/ko').then((m) => m.content),
22
- nl: () => import('./i18n/nl').then((m) => m.content),
23
- pl: () => import('./i18n/pl').then((m) => m.content),
24
- pt: () => import('./i18n/pt').then((m) => m.content),
25
- ru: () => import('./i18n/ru').then((m) => m.content),
26
- sv: () => import('./i18n/sv').then((m) => m.content),
27
- tr: () => import('./i18n/tr').then((m) => m.content),
28
- zh: () => import('./i18n/zh').then((m) => m.content),
29
- },
30
- };
31
-
32
-
1
+ import { testRaton } from './entry';
2
+ export * from './entry';
33
3
  export const TEST_RATON_TOOL: ToolDefinition = {
34
4
  entry: testRaton,
35
5
  Component: () => import('./component.astro'),
@@ -0,0 +1,143 @@
1
+ .tr-wrapper {
2
+ width: 100%;
3
+ max-width: 56rem;
4
+ margin: 0 auto;
5
+ color: var(--tr-text);
6
+ }
7
+
8
+ .tr-card {
9
+ background: var(--tr-surface);
10
+ border: 1px solid var(--tr-border);
11
+ border-radius: 1.5rem;
12
+ padding: 2rem;
13
+ box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.1);
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 1.5rem;
17
+ }
18
+
19
+ .tr-header {
20
+ display: flex;
21
+ justify-content: space-between;
22
+ align-items: center;
23
+ gap: 1rem;
24
+ flex-wrap: wrap;
25
+ }
26
+
27
+ .tr-header-info {
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: 0.25rem;
31
+ }
32
+
33
+ .tr-badge {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ gap: 0.5rem;
37
+ padding: 0.25rem 0.75rem;
38
+ border-radius: 9999px;
39
+ background: rgba(16, 185, 129, 0.1);
40
+ color: var(--tr-accent);
41
+ font-size: 0.75rem;
42
+ font-weight: 700;
43
+ text-transform: uppercase;
44
+ letter-spacing: 0.05em;
45
+ }
46
+
47
+ .tr-badge-icon {
48
+ width: 1rem;
49
+ height: 1rem;
50
+ }
51
+
52
+ .tr-title {
53
+ margin: 0;
54
+ font-size: 1.875rem;
55
+ font-weight: 900;
56
+ letter-spacing: -0.02em;
57
+ }
58
+
59
+ .tr-description {
60
+ margin: 0;
61
+ color: var(--tr-text-muted);
62
+ }
63
+
64
+ .tr-stats {
65
+ display: flex;
66
+ gap: 1rem;
67
+ }
68
+
69
+ .tr-stat {
70
+ background: var(--tr-surface-alt);
71
+ border-radius: 1rem;
72
+ padding: 1rem;
73
+ min-width: 7.5rem;
74
+ text-align: center;
75
+ }
76
+
77
+ .tr-stat-label {
78
+ font-size: 0.75rem;
79
+ font-weight: 700;
80
+ color: var(--tr-text-muted);
81
+ text-transform: uppercase;
82
+ letter-spacing: 0.05em;
83
+ }
84
+
85
+ .tr-stat-value {
86
+ display: flex;
87
+ align-items: baseline;
88
+ justify-content: center;
89
+ gap: 0.25rem;
90
+ font-size: 2.25rem;
91
+ font-weight: 900;
92
+ color: var(--tr-accent);
93
+ }
94
+
95
+ .tr-stat-unit {
96
+ font-size: 1rem;
97
+ color: var(--tr-text-muted);
98
+ }
99
+
100
+ .tr-area {
101
+ position: relative;
102
+ width: 100%;
103
+ height: 400px;
104
+ background: var(--tr-surface-alt);
105
+ border-radius: 1rem;
106
+ border: 2px dashed var(--tr-border);
107
+ overflow: hidden;
108
+ cursor: crosshair;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ }
113
+
114
+ .tr-placeholder {
115
+ color: var(--tr-border);
116
+ font-weight: 700;
117
+ font-size: 1.25rem;
118
+ pointer-events: none;
119
+ transition: opacity 200ms;
120
+ }
121
+
122
+ .tr-canvas {
123
+ position: absolute;
124
+ inset: 0;
125
+ width: 100%;
126
+ height: 100%;
127
+ opacity: 0.6;
128
+ }
129
+
130
+ .tr-follower {
131
+ position: absolute;
132
+ pointer-events: none;
133
+ background: var(--tr-accent);
134
+ color: #fff;
135
+ font-size: 0.75rem;
136
+ font-weight: 700;
137
+ padding: 0.25rem 0.5rem;
138
+ border-radius: 9999px;
139
+ box-shadow: 0 4px 10px var(--tr-accent-glow);
140
+ opacity: 0;
141
+ transition: opacity 75ms;
142
+ transform: translate(0, 0);
143
+ }