@jjlmoya/utils-sports 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 (79) hide show
  1. package/package.json +62 -0
  2. package/src/category/i18n/en.ts +108 -0
  3. package/src/category/i18n/es.ts +108 -0
  4. package/src/category/i18n/fr.ts +95 -0
  5. package/src/category/index.ts +21 -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 +55 -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 +22 -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/gymTracker/bibliography.astro +15 -0
  25. package/src/tool/gymTracker/component.astro +835 -0
  26. package/src/tool/gymTracker/exercises.ts +28 -0
  27. package/src/tool/gymTracker/i18n/en.ts +225 -0
  28. package/src/tool/gymTracker/i18n/es.ts +225 -0
  29. package/src/tool/gymTracker/i18n/fr.ts +225 -0
  30. package/src/tool/gymTracker/index.ts +34 -0
  31. package/src/tool/gymTracker/logic.ts +169 -0
  32. package/src/tool/gymTracker/seo.astro +15 -0
  33. package/src/tool/gymTracker/storage.ts +43 -0
  34. package/src/tool/gymTracker/timer.ts +126 -0
  35. package/src/tool/gymTracker/types.ts +11 -0
  36. package/src/tool/gymTracker/ui-utils.ts +59 -0
  37. package/src/tool/gymTracker/ui.ts +27 -0
  38. package/src/tool/reactionTester/bibliography.astro +2 -0
  39. package/src/tool/reactionTester/component.astro +1074 -0
  40. package/src/tool/reactionTester/i18n/en.ts +144 -0
  41. package/src/tool/reactionTester/i18n/es.ts +144 -0
  42. package/src/tool/reactionTester/i18n/fr.ts +144 -0
  43. package/src/tool/reactionTester/index.ts +34 -0
  44. package/src/tool/reactionTester/seo.astro +12 -0
  45. package/src/tool/reactionTester/ui.ts +43 -0
  46. package/src/tool/scoreKeeper/bibliography.astro +14 -0
  47. package/src/tool/scoreKeeper/component.astro +858 -0
  48. package/src/tool/scoreKeeper/i18n/en.ts +207 -0
  49. package/src/tool/scoreKeeper/i18n/es.ts +207 -0
  50. package/src/tool/scoreKeeper/i18n/fr.ts +207 -0
  51. package/src/tool/scoreKeeper/index.ts +35 -0
  52. package/src/tool/scoreKeeper/logic.ts +275 -0
  53. package/src/tool/scoreKeeper/seo.astro +15 -0
  54. package/src/tool/scoreKeeper/sports.ts +70 -0
  55. package/src/tool/scoreKeeper/ui.ts +19 -0
  56. package/src/tool/tournamentBracket/bibliography.astro +10 -0
  57. package/src/tool/tournamentBracket/component.astro +1092 -0
  58. package/src/tool/tournamentBracket/i18n/en.ts +160 -0
  59. package/src/tool/tournamentBracket/i18n/es.ts +178 -0
  60. package/src/tool/tournamentBracket/i18n/fr.ts +160 -0
  61. package/src/tool/tournamentBracket/index.ts +34 -0
  62. package/src/tool/tournamentBracket/logic/active.controller.ts +106 -0
  63. package/src/tool/tournamentBracket/logic/generator.ts +71 -0
  64. package/src/tool/tournamentBracket/logic/manager.ts +165 -0
  65. package/src/tool/tournamentBracket/logic/setup.controller.ts +84 -0
  66. package/src/tool/tournamentBracket/logic/sharing.ts +81 -0
  67. package/src/tool/tournamentBracket/logic/storage.ts +56 -0
  68. package/src/tool/tournamentBracket/models.ts +34 -0
  69. package/src/tool/tournamentBracket/seo.astro +12 -0
  70. package/src/tool/tournamentBracket/tournament.controller.ts +65 -0
  71. package/src/tool/tournamentBracket/tournament.renderer.ts +45 -0
  72. package/src/tool/tournamentBracket/ui/bracket-desktop.ts +143 -0
  73. package/src/tool/tournamentBracket/ui/bracket-mobile.ts +82 -0
  74. package/src/tool/tournamentBracket/ui/mediator.ts +96 -0
  75. package/src/tool/tournamentBracket/ui/navigator.ts +84 -0
  76. package/src/tool/tournamentBracket/ui/setup.ts +120 -0
  77. package/src/tool/tournamentBracket/ui.ts +42 -0
  78. package/src/tools.ts +13 -0
  79. package/src/types.ts +72 -0
@@ -0,0 +1,858 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { ScoreKeeperUI } 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 ScoreKeeperUI;
12
+ ---
13
+
14
+ <div class="sk-root" id="scoreboard-app" data-sk-ui={JSON.stringify(t)}>
15
+ <div class="sk-header">
16
+ <div class="sk-select-wrapper">
17
+ <div class="sk-select-icon">
18
+ <svg xmlns="http://www.w3.org/2000/svg" class="sk-icon-sm sk-icon-accent" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
19
+ <circle cx="12" cy="12" r="10"></circle>
20
+ <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"></path>
21
+ <path d="M2 12h20"></path>
22
+ </svg>
23
+ </div>
24
+ <select id="sport-select" class="sk-select"></select>
25
+ <div class="sk-select-arrow">
26
+ <svg class="sk-icon-sm sk-icon-muted" fill="none" stroke="currentcolor" viewBox="0 0 24 24">
27
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
28
+ </svg>
29
+ </div>
30
+ </div>
31
+ <div class="sk-controls">
32
+ <button id="btn-swap" class="sk-btn-secondary">
33
+ <svg xmlns="http://www.w3.org/2000/svg" class="sk-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
34
+ <path d="M7 16V4M7 4L3 8M7 4L11 8"></path>
35
+ <path d="M17 8V20M17 20L21 16M17 20L13 16"></path>
36
+ </svg>
37
+ <span class="sk-btn-label">{t.swapSides}</span>
38
+ </button>
39
+ <button id="btn-reset" class="sk-btn-icon" title={t.reset}>
40
+ <svg xmlns="http://www.w3.org/2000/svg" class="sk-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41
+ <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 12"></path>
42
+ </svg>
43
+ </button>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="sk-grid">
48
+ <div class="sk-widget sk-widget-a">
49
+ <div class="sk-widget-header">
50
+ <input type="text" value={t.playerA} class="sk-player-name sk-player-name-a" />
51
+ <div id="serve-a" class="sk-serve-indicator sk-serve-hidden"></div>
52
+ </div>
53
+ <div class="sk-score-area">
54
+ <div id="score-a" class="sk-score sk-score-a">0</div>
55
+ <div id="touch-layer-a" class="sk-touch-layer"></div>
56
+ </div>
57
+ <div id="stats-a" class="sk-stats sk-hidden">
58
+ <div class="sk-stat-group">
59
+ <span class="sk-stat-label sk-stat-label-a">{t.sets}</span>
60
+ <div class="sk-stat-control">
61
+ <button class="sk-stat-btn sk-stat-btn-a" onclick="window.game.adjSet('A', -1)">−</button>
62
+ <span id="sets-a" class="sk-stat-value">0</span>
63
+ <button class="sk-stat-btn sk-stat-btn-a" onclick="window.game.adjSet('A', 1)">+</button>
64
+ </div>
65
+ </div>
66
+ <div class="sk-stat-group">
67
+ <span class="sk-stat-label sk-stat-label-a">{t.games}</span>
68
+ <div class="sk-stat-control">
69
+ <button class="sk-stat-btn sk-stat-btn-a" onclick="window.game.adjGame('A', -1)">−</button>
70
+ <span id="games-a" class="sk-stat-value">0</span>
71
+ <button class="sk-stat-btn sk-stat-btn-a" onclick="window.game.adjGame('A', 1)">+</button>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ <div class="sk-widget-footer">
76
+ <button class="sk-serve-btn sk-serve-btn-a" onclick="window.game.setServe('A')" title={t.serve}>
77
+ <div class="sk-serve-dot"></div>
78
+ <span class="sk-serve-text">{t.serve}</span>
79
+ </button>
80
+ <button class="sk-minus-btn sk-minus-btn-a" onclick="window.game.adjScore('A', -1)">−</button>
81
+ </div>
82
+ </div>
83
+
84
+ <div class="sk-widget sk-widget-b">
85
+ <div class="sk-widget-header">
86
+ <div class="sk-flex-fill"></div>
87
+ <input type="text" value={t.playerB} class="sk-player-name sk-player-name-b" />
88
+ <div id="serve-b" class="sk-serve-indicator sk-serve-indicator-b sk-serve-hidden"></div>
89
+ </div>
90
+ <div class="sk-score-area">
91
+ <div id="score-b" class="sk-score sk-score-b">0</div>
92
+ <div id="touch-layer-b" class="sk-touch-layer"></div>
93
+ </div>
94
+ <div id="stats-b" class="sk-stats sk-hidden">
95
+ <div class="sk-stat-group">
96
+ <span class="sk-stat-label sk-stat-label-b">{t.sets}</span>
97
+ <div class="sk-stat-control">
98
+ <button class="sk-stat-btn sk-stat-btn-b" onclick="window.game.adjSet('B', -1)">−</button>
99
+ <span id="sets-b" class="sk-stat-value">0</span>
100
+ <button class="sk-stat-btn sk-stat-btn-b" onclick="window.game.adjSet('B', 1)">+</button>
101
+ </div>
102
+ </div>
103
+ <div class="sk-stat-group">
104
+ <span class="sk-stat-label sk-stat-label-b">{t.games}</span>
105
+ <div class="sk-stat-control">
106
+ <button class="sk-stat-btn sk-stat-btn-b" onclick="window.game.adjGame('B', -1)">−</button>
107
+ <span id="games-b" class="sk-stat-value">0</span>
108
+ <button class="sk-stat-btn sk-stat-btn-b" onclick="window.game.adjGame('B', 1)">+</button>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ <div class="sk-widget-footer sk-widget-footer-b">
113
+ <button class="sk-minus-btn sk-minus-btn-b" onclick="window.game.adjScore('B', -1)">−</button>
114
+ <button class="sk-serve-btn sk-serve-btn-b" onclick="window.game.setServe('B')" title={t.serve}>
115
+ <div class="sk-serve-dot"></div>
116
+ <span class="sk-serve-text">{t.serve}</span>
117
+ </button>
118
+ </div>
119
+ </div>
120
+ </div>
121
+
122
+ <div id="winner-modal" class="sk-modal sk-hidden">
123
+ <div class="sk-modal-box">
124
+ <div class="sk-modal-trophy">
125
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentcolor" width="64" height="64">
126
+ <path d="M11 15.93V18H9v2h6v-2h-2v-2.07A8.001 8.001 0 0 0 12 2a8 8 0 0 0-1 15.93zM5 4H3a2 2 0 0 0-2 2v1a4 4 0 0 0 4 4h.1A8.01 8.01 0 0 1 4 8V4zm16 0h-2v4a8.01 8.01 0 0 1-1.1 3H18a4 4 0 0 0 4-4V6a2 2 0 0 0-2-2z"/>
127
+ </svg>
128
+ </div>
129
+ <h2 class="sk-modal-title">{t.victory}</h2>
130
+ <p id="winner-name" class="sk-modal-winner">{t.playerA}</p>
131
+ <div class="sk-modal-actions">
132
+ <button id="btn-modal-reset" class="sk-modal-btn-new">{t.newGame}</button>
133
+ <button id="btn-modal-continue" class="sk-modal-btn-continue">{t.continueGame}</button>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <script>
140
+ import { initScoreKeeper } from './logic';
141
+ initScoreKeeper();
142
+ </script>
143
+
144
+ <style>
145
+ .sk-root {
146
+ width: 100%;
147
+ max-width: 80rem;
148
+ margin: 0 auto;
149
+ user-select: none;
150
+ padding: 0.5rem;
151
+ }
152
+
153
+ @media (min-width: 768px) {
154
+ .sk-root {
155
+ padding: 1.5rem;
156
+ }
157
+ }
158
+
159
+ .sk-header {
160
+ display: flex;
161
+ flex-direction: column;
162
+ justify-content: space-between;
163
+ align-items: center;
164
+ margin-bottom: 1.5rem;
165
+ gap: 1rem;
166
+ }
167
+
168
+ @media (min-width: 640px) {
169
+ .sk-header {
170
+ flex-direction: row;
171
+ }
172
+ }
173
+
174
+ .sk-select-wrapper {
175
+ position: relative;
176
+ width: 100%;
177
+ max-width: 28rem;
178
+ }
179
+
180
+ .sk-select-icon {
181
+ position: absolute;
182
+ inset-block: 0;
183
+ left: 0;
184
+ display: flex;
185
+ align-items: center;
186
+ padding-left: 0.75rem;
187
+ pointer-events: none;
188
+ }
189
+
190
+ .sk-select {
191
+ width: 100%;
192
+ appearance: none;
193
+ background-color: var(--bg-surface);
194
+ color: var(--text-base);
195
+ font-weight: 700;
196
+ font-size: 1.125rem;
197
+ padding: 0.75rem 3rem 0.75rem 2.5rem;
198
+ border-radius: 0.75rem;
199
+ border: 1px solid var(--border-color);
200
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
201
+ cursor: pointer;
202
+ transition: border-color 0.15s, box-shadow 0.15s;
203
+ }
204
+
205
+ .sk-select:focus {
206
+ outline: none;
207
+ box-shadow: 0 0 0 2px var(--accent);
208
+ border-color: var(--accent);
209
+ }
210
+
211
+ .sk-select-arrow {
212
+ position: absolute;
213
+ inset-block: 0;
214
+ right: 0;
215
+ display: flex;
216
+ align-items: center;
217
+ padding-right: 1rem;
218
+ pointer-events: none;
219
+ }
220
+
221
+ .sk-icon-sm {
222
+ width: 1.25rem;
223
+ height: 1.25rem;
224
+ }
225
+
226
+ .sk-icon-accent {
227
+ color: var(--accent);
228
+ }
229
+
230
+ .sk-icon-muted {
231
+ color: var(--text-muted);
232
+ }
233
+
234
+ .sk-controls {
235
+ display: flex;
236
+ gap: 0.5rem;
237
+ flex-shrink: 0;
238
+ }
239
+
240
+ .sk-btn-secondary {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 0.5rem;
244
+ padding: 0.5rem 1rem;
245
+ background-color: var(--bg-surface);
246
+ color: var(--text-muted);
247
+ border: 1px solid var(--border-color);
248
+ border-radius: 0.5rem;
249
+ font-weight: 700;
250
+ font-size: 0.875rem;
251
+ cursor: pointer;
252
+ transition: background-color 0.15s, color 0.15s;
253
+ }
254
+
255
+ .sk-btn-secondary:hover {
256
+ background-color: var(--bg-page);
257
+ color: var(--text-base);
258
+ }
259
+
260
+ .sk-btn-label {
261
+ display: none;
262
+ }
263
+
264
+ @media (min-width: 640px) {
265
+ .sk-btn-label {
266
+ display: inline;
267
+ }
268
+ }
269
+
270
+ .sk-btn-icon {
271
+ padding: 0.5rem 1rem;
272
+ background-color: var(--bg-surface);
273
+ color: var(--text-muted);
274
+ border: 1px solid var(--border-color);
275
+ border-radius: 0.5rem;
276
+ font-weight: 700;
277
+ font-size: 0.875rem;
278
+ cursor: pointer;
279
+ transition: background-color 0.15s, color 0.15s;
280
+ }
281
+
282
+ .sk-btn-icon:hover {
283
+ color: #ef4444;
284
+ background-color: #fef2f2;
285
+ }
286
+
287
+ .sk-grid {
288
+ display: grid;
289
+ grid-template-columns: 1fr;
290
+ gap: 1rem;
291
+ height: calc(100vh - 12rem);
292
+ min-height: 500px;
293
+ }
294
+
295
+ @media (min-width: 768px) {
296
+ .sk-grid {
297
+ grid-template-columns: 1fr 1fr;
298
+ height: 600px;
299
+ }
300
+ }
301
+
302
+ .sk-widget {
303
+ position: relative;
304
+ display: flex;
305
+ flex-direction: column;
306
+ border-radius: 2rem;
307
+ border-width: 4px;
308
+ border-style: solid;
309
+ overflow: hidden;
310
+ }
311
+
312
+ .sk-widget-a {
313
+ background-color: rgba(59, 130, 246, 0.05);
314
+ border-color: rgb(219, 234, 254);
315
+ }
316
+
317
+ .sk-widget-b {
318
+ background-color: rgba(244, 63, 94, 0.05);
319
+ border-color: rgb(254, 205, 211);
320
+ }
321
+
322
+ .sk-widget-header {
323
+ position: relative;
324
+ z-index: 20;
325
+ display: flex;
326
+ justify-content: space-between;
327
+ align-items: flex-start;
328
+ padding: 1rem;
329
+ pointer-events: auto;
330
+ }
331
+
332
+ @media (min-width: 768px) {
333
+ .sk-widget-header {
334
+ padding: 1.5rem;
335
+ }
336
+ }
337
+
338
+ .sk-flex-fill {
339
+ flex: 1;
340
+ }
341
+
342
+ .sk-player-name {
343
+ width: 66%;
344
+ background-color: transparent;
345
+ font-size: 1.25rem;
346
+ font-weight: 900;
347
+ letter-spacing: 0.05em;
348
+ text-transform: uppercase;
349
+ border: none;
350
+ outline: none;
351
+ transition: color 0.15s;
352
+ }
353
+
354
+ @media (min-width: 768px) {
355
+ .sk-player-name {
356
+ font-size: 1.5rem;
357
+ }
358
+ }
359
+
360
+ .sk-player-name-a {
361
+ color: rgba(30, 58, 138, 0.5);
362
+ }
363
+
364
+ .sk-player-name-a:focus {
365
+ color: #2563eb;
366
+ }
367
+
368
+ .sk-player-name-b {
369
+ color: rgba(136, 19, 55, 0.5);
370
+ text-align: right;
371
+ }
372
+
373
+ .sk-player-name-b:focus {
374
+ color: #e11d48;
375
+ }
376
+
377
+ .sk-serve-indicator {
378
+ width: 1.5rem;
379
+ height: 1.5rem;
380
+ border-radius: 9999px;
381
+ background-color: #facc15;
382
+ border: 2px solid var(--bg-surface);
383
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
384
+ transition: opacity 0.3s, transform 0.3s;
385
+ flex-shrink: 0;
386
+ }
387
+
388
+ .sk-serve-indicator-b {
389
+ position: absolute;
390
+ left: 1.5rem;
391
+ top: 1.5rem;
392
+ }
393
+
394
+ .sk-score-area {
395
+ position: relative;
396
+ flex: 1;
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ margin-top: -2rem;
401
+ }
402
+
403
+ @media (min-width: 768px) {
404
+ .sk-score-area {
405
+ margin-top: 0;
406
+ }
407
+ }
408
+
409
+ .sk-score {
410
+ font-size: clamp(8rem, 20vw, 13rem);
411
+ font-weight: 900;
412
+ line-height: 1;
413
+ letter-spacing: -0.05em;
414
+ font-variant-numeric: tabular-nums;
415
+ transition: transform 0.1s;
416
+ z-index: 10;
417
+ }
418
+
419
+ .sk-score-a {
420
+ color: #2563eb;
421
+ }
422
+
423
+ .sk-score-b {
424
+ color: #e11d48;
425
+ }
426
+
427
+ .sk-touch-layer {
428
+ position: absolute;
429
+ inset: 0;
430
+ z-index: 20;
431
+ display: grid;
432
+ grid-template-columns: 1fr;
433
+ gap: 0.5rem;
434
+ padding: 1rem;
435
+ padding-top: 6rem;
436
+ opacity: 0;
437
+ transition: opacity 0.15s;
438
+ }
439
+
440
+ .sk-touch-layer:hover {
441
+ opacity: 0.1;
442
+ }
443
+
444
+ .sk-touch-btn {
445
+ width: 100%;
446
+ height: 100%;
447
+ cursor: pointer;
448
+ background: transparent;
449
+ border: none;
450
+ }
451
+
452
+ .sk-stats {
453
+ position: relative;
454
+ z-index: 30;
455
+ display: flex;
456
+ justify-content: center;
457
+ gap: 1rem;
458
+ margin-bottom: 0.5rem;
459
+ pointer-events: auto;
460
+ }
461
+
462
+ .sk-stat-group {
463
+ display: flex;
464
+ flex-direction: column;
465
+ align-items: center;
466
+ }
467
+
468
+ .sk-stat-label {
469
+ font-size: 0.65rem;
470
+ font-weight: 700;
471
+ text-transform: uppercase;
472
+ letter-spacing: 0.1em;
473
+ margin-bottom: 0.25rem;
474
+ }
475
+
476
+ .sk-stat-label-a {
477
+ color: #60a5fa;
478
+ }
479
+
480
+ .sk-stat-label-b {
481
+ color: #fb7185;
482
+ }
483
+
484
+ .sk-stat-control {
485
+ display: flex;
486
+ align-items: center;
487
+ gap: 0.5rem;
488
+ background-color: var(--bg-surface);
489
+ padding: 0.25rem 0.5rem;
490
+ border-radius: 9999px;
491
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
492
+ border: 1px solid var(--border-color);
493
+ }
494
+
495
+ .sk-stat-value {
496
+ font-size: 1.125rem;
497
+ font-weight: 700;
498
+ width: 1rem;
499
+ text-align: center;
500
+ color: var(--text-base);
501
+ }
502
+
503
+ .sk-stat-btn {
504
+ width: 2rem;
505
+ height: 2rem;
506
+ display: flex;
507
+ align-items: center;
508
+ justify-content: center;
509
+ border-radius: 9999px;
510
+ border: none;
511
+ background: transparent;
512
+ cursor: pointer;
513
+ font-size: 1rem;
514
+ transition: background-color 0.15s;
515
+ }
516
+
517
+ .sk-stat-btn-a {
518
+ color: #2563eb;
519
+ }
520
+
521
+ .sk-stat-btn-a:hover {
522
+ background-color: var(--bg-page);
523
+ }
524
+
525
+ .sk-stat-btn-b {
526
+ color: #e11d48;
527
+ }
528
+
529
+ .sk-stat-btn-b:hover {
530
+ background-color: var(--bg-page);
531
+ }
532
+
533
+ .sk-widget-footer {
534
+ position: relative;
535
+ z-index: 30;
536
+ display: flex;
537
+ justify-content: space-between;
538
+ align-items: flex-end;
539
+ padding: 1rem;
540
+ pointer-events: auto;
541
+ }
542
+
543
+ @media (min-width: 768px) {
544
+ .sk-widget-footer {
545
+ padding: 1.5rem;
546
+ }
547
+ }
548
+
549
+ .sk-serve-btn {
550
+ width: 3rem;
551
+ height: 3rem;
552
+ border-radius: 1rem;
553
+ border: none;
554
+ display: flex;
555
+ flex-direction: column;
556
+ align-items: center;
557
+ justify-content: center;
558
+ gap: 0.25rem;
559
+ cursor: pointer;
560
+ transition: background-color 0.15s;
561
+ }
562
+
563
+ .sk-serve-btn-a {
564
+ background-color: rgb(219, 234, 254);
565
+ color: #2563eb;
566
+ }
567
+
568
+ .sk-serve-btn-a:hover {
569
+ background-color: rgb(191, 219, 254);
570
+ }
571
+
572
+ .sk-serve-btn-b {
573
+ background-color: rgb(254, 205, 211);
574
+ color: #e11d48;
575
+ }
576
+
577
+ .sk-serve-btn-b:hover {
578
+ background-color: rgb(253, 164, 175);
579
+ }
580
+
581
+ .sk-serve-dot {
582
+ width: 0.5rem;
583
+ height: 0.5rem;
584
+ border-radius: 9999px;
585
+ background-color: currentcolor;
586
+ }
587
+
588
+ .sk-serve-text {
589
+ font-size: 0.5rem;
590
+ font-weight: 700;
591
+ text-transform: uppercase;
592
+ }
593
+
594
+ .sk-minus-btn {
595
+ width: 4rem;
596
+ height: 4rem;
597
+ border-radius: 9999px;
598
+ background-color: var(--bg-surface);
599
+ border: 4px solid var(--bg-page);
600
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
601
+ font-size: 1.875rem;
602
+ font-weight: 700;
603
+ display: flex;
604
+ align-items: center;
605
+ justify-content: center;
606
+ cursor: pointer;
607
+ transition: transform 0.1s;
608
+ }
609
+
610
+ .sk-minus-btn:active {
611
+ transform: scale(0.95);
612
+ }
613
+
614
+ .sk-minus-btn-a {
615
+ color: #3b82f6;
616
+ }
617
+
618
+ .sk-minus-btn-b {
619
+ color: #f43f5e;
620
+ }
621
+
622
+ .sk-modal {
623
+ position: fixed;
624
+ inset: 0;
625
+ z-index: 100;
626
+ background-color: rgba(0, 0, 0, 0.8);
627
+ backdrop-filter: blur(4px);
628
+ display: flex;
629
+ align-items: center;
630
+ justify-content: center;
631
+ padding: 1rem;
632
+ }
633
+
634
+ .sk-modal-box {
635
+ background-color: var(--bg-surface);
636
+ border-radius: 1.5rem;
637
+ padding: 2rem;
638
+ max-width: 24rem;
639
+ width: 100%;
640
+ text-align: center;
641
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
642
+ border: 2px solid var(--border-color);
643
+ }
644
+
645
+ .sk-modal-trophy {
646
+ margin-bottom: 1rem;
647
+ color: #facc15;
648
+ display: flex;
649
+ justify-content: center;
650
+ animation: sk-bounce 1s infinite;
651
+ }
652
+
653
+ @keyframes sk-bounce {
654
+ 0%,
655
+ 100% {
656
+ transform: translateY(0);
657
+ animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
658
+ }
659
+ 50% {
660
+ transform: translateY(-25%);
661
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
662
+ }
663
+ }
664
+
665
+ .sk-modal-title {
666
+ font-size: 1.875rem;
667
+ font-weight: 900;
668
+ color: var(--text-base);
669
+ margin-bottom: 0.5rem;
670
+ }
671
+
672
+ .sk-modal-winner {
673
+ font-size: 1.25rem;
674
+ font-weight: 700;
675
+ color: var(--accent);
676
+ margin-bottom: 2rem;
677
+ text-transform: uppercase;
678
+ letter-spacing: 0.1em;
679
+ overflow-wrap: break-word;
680
+ }
681
+
682
+ .sk-modal-actions {
683
+ display: flex;
684
+ flex-direction: column;
685
+ gap: 0.75rem;
686
+ }
687
+
688
+ .sk-modal-btn-new {
689
+ width: 100%;
690
+ padding: 1rem;
691
+ background-color: var(--accent);
692
+ color: white;
693
+ border: none;
694
+ border-radius: 0.75rem;
695
+ font-weight: 700;
696
+ font-size: 1.125rem;
697
+ cursor: pointer;
698
+ transition: opacity 0.15s, transform 0.1s;
699
+ }
700
+
701
+ .sk-modal-btn-new:hover {
702
+ opacity: 0.9;
703
+ }
704
+
705
+ .sk-modal-btn-new:active {
706
+ transform: scale(0.95);
707
+ }
708
+
709
+ .sk-modal-btn-continue {
710
+ width: 100%;
711
+ padding: 1rem;
712
+ background-color: var(--bg-page);
713
+ color: var(--text-muted);
714
+ border: 1px solid var(--border-color);
715
+ border-radius: 0.75rem;
716
+ font-weight: 700;
717
+ cursor: pointer;
718
+ transition: background-color 0.15s, transform 0.1s;
719
+ }
720
+
721
+ .sk-modal-btn-continue:hover {
722
+ background-color: var(--bg-surface);
723
+ }
724
+
725
+ .sk-modal-btn-continue:active {
726
+ transform: scale(0.95);
727
+ }
728
+
729
+ :global(.theme-dark) .sk-widget-a {
730
+ background-color: rgba(59, 130, 246, 0.1);
731
+ border-color: rgba(30, 58, 138, 0.3);
732
+ }
733
+
734
+ :global(.theme-dark) .sk-widget-b {
735
+ background-color: rgba(244, 63, 94, 0.1);
736
+ border-color: rgba(136, 19, 55, 0.3);
737
+ }
738
+
739
+ :global(.theme-dark) .sk-player-name-a {
740
+ color: rgba(191, 219, 254, 0.5);
741
+ }
742
+
743
+ :global(.theme-dark) .sk-player-name-a:focus {
744
+ color: #60a5fa;
745
+ }
746
+
747
+ :global(.theme-dark) .sk-player-name-b {
748
+ color: rgba(253, 164, 175, 0.5);
749
+ }
750
+
751
+ :global(.theme-dark) .sk-player-name-b:focus {
752
+ color: #fb7185;
753
+ }
754
+
755
+ :global(.theme-dark) .sk-score-a {
756
+ color: #3b82f6;
757
+ }
758
+
759
+ :global(.theme-dark) .sk-score-b {
760
+ color: #f43f5e;
761
+ }
762
+
763
+ :global(.theme-dark) .sk-serve-btn-a {
764
+ background-color: rgba(30, 58, 138, 0.3);
765
+ }
766
+
767
+ :global(.theme-dark) .sk-serve-btn-a:hover {
768
+ background-color: rgba(30, 58, 138, 0.5);
769
+ }
770
+
771
+ :global(.theme-dark) .sk-serve-btn-b {
772
+ background-color: rgba(136, 19, 55, 0.3);
773
+ }
774
+
775
+ :global(.theme-dark) .sk-serve-btn-b:hover {
776
+ background-color: rgba(136, 19, 55, 0.5);
777
+ }
778
+
779
+ :global(.theme-dark) .sk-btn-icon:hover {
780
+ background-color: rgba(127, 29, 29, 0.2);
781
+ }
782
+
783
+ :global(#scoreboard-app .sk-hidden) {
784
+ display: none;
785
+ }
786
+
787
+ :global(#scoreboard-app .sk-serve-hidden) {
788
+ opacity: 0;
789
+ transform: scale(0);
790
+ }
791
+
792
+ :global(#scoreboard-app .sk-score-pop) {
793
+ transform: scale(1.1);
794
+ }
795
+
796
+ :global(#scoreboard-app .sk-score-tiebreak) {
797
+ color: #f97316;
798
+ }
799
+
800
+ :global(.sk-touch-layer-multi) {
801
+ position: absolute;
802
+ inset: 0;
803
+ z-index: 20;
804
+ display: flex;
805
+ flex-direction: column;
806
+ justify-content: flex-end;
807
+ padding: 1rem;
808
+ gap: 0.5rem;
809
+ opacity: 1;
810
+ pointer-events: none;
811
+ }
812
+
813
+ @media (min-width: 768px) {
814
+ :global(.sk-touch-layer-multi) {
815
+ opacity: 0;
816
+ pointer-events: auto;
817
+ }
818
+
819
+ :global(.sk-touch-layer-multi:hover) {
820
+ opacity: 1;
821
+ }
822
+ }
823
+
824
+ :global(.sk-plus-btn) {
825
+ flex: none;
826
+ height: 3.5rem;
827
+ width: 100%;
828
+ display: flex;
829
+ align-items: center;
830
+ justify-content: center;
831
+ font-size: 1.5rem;
832
+ font-weight: 900;
833
+ background-color: rgba(255, 255, 255, 0.2);
834
+ color: var(--text-base);
835
+ border: 1px solid rgba(255, 255, 255, 0.2);
836
+ border-radius: 0.75rem;
837
+ cursor: pointer;
838
+ transition: background-color 0.15s, transform 0.1s;
839
+ pointer-events: auto;
840
+ }
841
+
842
+ :global(.sk-plus-btn:hover) {
843
+ background-color: rgba(255, 255, 255, 0.4);
844
+ }
845
+
846
+ :global(.sk-plus-btn:active) {
847
+ transform: scale(0.95);
848
+ }
849
+
850
+ :global(.theme-dark .sk-plus-btn) {
851
+ background-color: rgba(15, 23, 42, 0.3);
852
+ border-color: rgba(255, 255, 255, 0.1);
853
+ }
854
+
855
+ :global(.theme-dark .sk-plus-btn:hover) {
856
+ background-color: rgba(30, 41, 59, 0.5);
857
+ }
858
+ </style>