@jjlmoya/utils-tabletop 1.3.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 (121) hide show
  1. package/package.json +65 -0
  2. package/scripts/postinstall.mjs +27 -0
  3. package/src/category/TabletopCategorySEO.astro +14 -0
  4. package/src/category/i18n/de.ts +27 -0
  5. package/src/category/i18n/en.ts +27 -0
  6. package/src/category/i18n/es.ts +27 -0
  7. package/src/category/i18n/fr.ts +27 -0
  8. package/src/category/i18n/id.ts +27 -0
  9. package/src/category/i18n/it.ts +27 -0
  10. package/src/category/i18n/ja.ts +27 -0
  11. package/src/category/i18n/ko.ts +27 -0
  12. package/src/category/i18n/nl.ts +27 -0
  13. package/src/category/i18n/pl.ts +27 -0
  14. package/src/category/i18n/pt.ts +27 -0
  15. package/src/category/i18n/ru.ts +27 -0
  16. package/src/category/i18n/sv.ts +27 -0
  17. package/src/category/i18n/tr.ts +27 -0
  18. package/src/category/i18n/zh.ts +27 -0
  19. package/src/category/index.ts +27 -0
  20. package/src/components/PreviewNavSidebar.astro +116 -0
  21. package/src/components/PreviewToolbar.astro +143 -0
  22. package/src/data.ts +10 -0
  23. package/src/entries.ts +10 -0
  24. package/src/env.d.ts +5 -0
  25. package/src/index.ts +21 -0
  26. package/src/layouts/PreviewLayout.astro +117 -0
  27. package/src/pages/[locale]/[slug].astro +161 -0
  28. package/src/pages/[locale].astro +251 -0
  29. package/src/pages/index.astro +4 -0
  30. package/src/tests/faq_count.test.ts +18 -0
  31. package/src/tests/i18n_coverage.test.ts +36 -0
  32. package/src/tests/locale_completeness.test.ts +29 -0
  33. package/src/tests/mocks/astro_mock.js +2 -0
  34. package/src/tests/no_en_dash.test.ts +41 -0
  35. package/src/tests/no_h1_in_components.test.ts +48 -0
  36. package/src/tests/schemas_fulfillment.test.ts +23 -0
  37. package/src/tests/seo_length.test.ts +22 -0
  38. package/src/tests/shared-test-helpers.ts +56 -0
  39. package/src/tests/slug_language_code_format.test.ts +23 -0
  40. package/src/tests/slug_uniqueness.test.ts +81 -0
  41. package/src/tests/title_quality.test.ts +55 -0
  42. package/src/tests/tool_exports.test.ts +34 -0
  43. package/src/tests/tool_validation.test.ts +16 -0
  44. package/src/tool/board-game-timer/bibliography.astro +16 -0
  45. package/src/tool/board-game-timer/bibliography.ts +12 -0
  46. package/src/tool/board-game-timer/board-game-timer.css +1615 -0
  47. package/src/tool/board-game-timer/client.ts +17 -0
  48. package/src/tool/board-game-timer/component.astro +22 -0
  49. package/src/tool/board-game-timer/components/DuelTimer.astro +75 -0
  50. package/src/tool/board-game-timer/components/MultiplayerTimer.astro +58 -0
  51. package/src/tool/board-game-timer/components/SetupPanel.astro +155 -0
  52. package/src/tool/board-game-timer/components/StatsModal.astro +52 -0
  53. package/src/tool/board-game-timer/entry.ts +76 -0
  54. package/src/tool/board-game-timer/i18n/de.ts +303 -0
  55. package/src/tool/board-game-timer/i18n/en.ts +304 -0
  56. package/src/tool/board-game-timer/i18n/es.ts +303 -0
  57. package/src/tool/board-game-timer/i18n/fr.ts +303 -0
  58. package/src/tool/board-game-timer/i18n/id.ts +303 -0
  59. package/src/tool/board-game-timer/i18n/it.ts +303 -0
  60. package/src/tool/board-game-timer/i18n/ja.ts +306 -0
  61. package/src/tool/board-game-timer/i18n/ko.ts +303 -0
  62. package/src/tool/board-game-timer/i18n/nl.ts +303 -0
  63. package/src/tool/board-game-timer/i18n/pl.ts +303 -0
  64. package/src/tool/board-game-timer/i18n/pt.ts +303 -0
  65. package/src/tool/board-game-timer/i18n/ru.ts +303 -0
  66. package/src/tool/board-game-timer/i18n/sv.ts +303 -0
  67. package/src/tool/board-game-timer/i18n/tr.ts +303 -0
  68. package/src/tool/board-game-timer/i18n/zh.ts +303 -0
  69. package/src/tool/board-game-timer/index.ts +9 -0
  70. package/src/tool/board-game-timer/logic.test.ts +282 -0
  71. package/src/tool/board-game-timer/logic.ts +6 -0
  72. package/src/tool/board-game-timer/modules/ColorHelper.ts +26 -0
  73. package/src/tool/board-game-timer/modules/DuelViewManager.ts +120 -0
  74. package/src/tool/board-game-timer/modules/FormatHelper.ts +12 -0
  75. package/src/tool/board-game-timer/modules/GameRunner.ts +136 -0
  76. package/src/tool/board-game-timer/modules/GameState.ts +48 -0
  77. package/src/tool/board-game-timer/modules/MultiplayerViewManager.ts +163 -0
  78. package/src/tool/board-game-timer/modules/SetupPanelManager.ts +209 -0
  79. package/src/tool/board-game-timer/modules/SoundManager.ts +109 -0
  80. package/src/tool/board-game-timer/modules/SpeechManager.ts +1 -0
  81. package/src/tool/board-game-timer/modules/StatsManager.ts +87 -0
  82. package/src/tool/board-game-timer/modules/StatsModalManager.ts +74 -0
  83. package/src/tool/board-game-timer/modules/TimerEngine.ts +175 -0
  84. package/src/tool/board-game-timer/modules/TimerEngineBase.ts +49 -0
  85. package/src/tool/board-game-timer/modules/TimerEngineClock.ts +127 -0
  86. package/src/tool/board-game-timer/modules/TimerStateHelper.ts +55 -0
  87. package/src/tool/board-game-timer/modules/particles.ts +108 -0
  88. package/src/tool/board-game-timer/seo.astro +16 -0
  89. package/src/tool/board-game-timer/types.ts +97 -0
  90. package/src/tool/dice-roller-simulator/bibliography.astro +16 -0
  91. package/src/tool/dice-roller-simulator/bibliography.ts +12 -0
  92. package/src/tool/dice-roller-simulator/client.ts +273 -0
  93. package/src/tool/dice-roller-simulator/component.astro +75 -0
  94. package/src/tool/dice-roller-simulator/components/DiceGrid.astro +55 -0
  95. package/src/tool/dice-roller-simulator/components/RollHistorySection.astro +26 -0
  96. package/src/tool/dice-roller-simulator/components/RollResultBanner.astro +10 -0
  97. package/src/tool/dice-roller-simulator/components/StatsPanel.astro +36 -0
  98. package/src/tool/dice-roller-simulator/dice-roller-simulator.css +1319 -0
  99. package/src/tool/dice-roller-simulator/entry.ts +39 -0
  100. package/src/tool/dice-roller-simulator/i18n/de.ts +305 -0
  101. package/src/tool/dice-roller-simulator/i18n/en.ts +305 -0
  102. package/src/tool/dice-roller-simulator/i18n/es.ts +305 -0
  103. package/src/tool/dice-roller-simulator/i18n/fr.ts +305 -0
  104. package/src/tool/dice-roller-simulator/i18n/id.ts +305 -0
  105. package/src/tool/dice-roller-simulator/i18n/it.ts +305 -0
  106. package/src/tool/dice-roller-simulator/i18n/ja.ts +305 -0
  107. package/src/tool/dice-roller-simulator/i18n/ko.ts +305 -0
  108. package/src/tool/dice-roller-simulator/i18n/nl.ts +305 -0
  109. package/src/tool/dice-roller-simulator/i18n/pl.ts +305 -0
  110. package/src/tool/dice-roller-simulator/i18n/pt.ts +305 -0
  111. package/src/tool/dice-roller-simulator/i18n/ru.ts +305 -0
  112. package/src/tool/dice-roller-simulator/i18n/sv.ts +305 -0
  113. package/src/tool/dice-roller-simulator/i18n/tr.ts +305 -0
  114. package/src/tool/dice-roller-simulator/i18n/zh.ts +305 -0
  115. package/src/tool/dice-roller-simulator/index.ts +9 -0
  116. package/src/tool/dice-roller-simulator/modules/chart.ts +32 -0
  117. package/src/tool/dice-roller-simulator/modules/history.ts +87 -0
  118. package/src/tool/dice-roller-simulator/modules/particles.ts +22 -0
  119. package/src/tool/dice-roller-simulator/seo.astro +16 -0
  120. package/src/tools.ts +9 -0
  121. package/src/types.ts +70 -0
@@ -0,0 +1,1615 @@
1
+ :root {
2
+ --color-timer-accent: #3b82f6;
3
+ --color-timer-warning: #f59e0b;
4
+ --color-timer-danger: #ef4444;
5
+ --color-timer-success: #10b981;
6
+ --color-blue: #3b82f6;
7
+ --color-red: #ef4444;
8
+ --color-green: #22c55e;
9
+ --color-yellow: #eab308;
10
+ --color-purple: #a855f7;
11
+ --color-orange: #f97316;
12
+ --color-teal: #14b8a6;
13
+ --color-pink: #ec4899;
14
+ --color-white: #fff;
15
+ --color-text-blue: #60a5fa;
16
+ --color-text-red: #f87171;
17
+ --color-text-green: #4ade80;
18
+ --color-text-yellow: #fde047;
19
+ --color-text-purple: #c084fc;
20
+ --color-text-orange: #fb923c;
21
+ --color-text-teal: #2dd4bf;
22
+ --color-text-pink: #f472b6;
23
+ }
24
+
25
+ .theme-light {
26
+ --color-text-blue: #1d4ed8;
27
+ --color-text-red: #b91c1c;
28
+ --color-text-green: #15803d;
29
+ --color-text-yellow: #a16207;
30
+ --color-text-purple: #7e22ce;
31
+ --color-text-orange: #c2410c;
32
+ --color-text-teal: #0f766e;
33
+ --color-text-pink: #be185d;
34
+ }
35
+
36
+ .timer-main-wrapper {
37
+ background: var(--bg-surface);
38
+ border: 1px solid var(--border-color);
39
+ border-radius: 1.5rem;
40
+ padding: 2rem;
41
+ color: var(--text-base);
42
+ box-shadow: 0 15px 40px rgba(0, 0, 0, 0.12);
43
+ position: relative;
44
+ overflow: hidden;
45
+ display: flex;
46
+ flex-direction: column;
47
+ gap: 2rem;
48
+ min-height: 550px;
49
+ background-size: 200% 200%;
50
+ animation: bg-shift 8s ease-in-out infinite alternate;
51
+ }
52
+
53
+ .timer-setup-panel {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 2rem;
57
+ animation: timer-fade-in 0.4s ease;
58
+ }
59
+
60
+ .setup-grid {
61
+ display: grid;
62
+ grid-template-columns: 1fr 1fr;
63
+ gap: 2rem;
64
+ }
65
+
66
+ @media (max-width: 768px) {
67
+ .setup-grid {
68
+ grid-template-columns: 1fr;
69
+ }
70
+ }
71
+
72
+ .setup-card {
73
+ background: var(--bg-page);
74
+ border: 1px solid var(--border-color);
75
+ border-radius: 1rem;
76
+ padding: 1.5rem;
77
+ display: flex;
78
+ flex-direction: column;
79
+ gap: 1.5rem;
80
+ }
81
+
82
+ .settings-group {
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: 1rem;
86
+ }
87
+
88
+ .settings-group h4 {
89
+ font-size: 0.95rem;
90
+ font-weight: 800;
91
+ text-transform: uppercase;
92
+ letter-spacing: 0.05em;
93
+ color: var(--text-muted);
94
+ margin: 0;
95
+ border-bottom: 1px solid var(--border-color);
96
+ padding-bottom: 0.5rem;
97
+ }
98
+
99
+ .presets-grid {
100
+ display: grid;
101
+ grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
102
+ gap: 0.75rem;
103
+ }
104
+
105
+ .preset-card {
106
+ background: var(--bg-surface);
107
+ border: 1px solid var(--border-color);
108
+ border-radius: 0.75rem;
109
+ padding: 0.75rem;
110
+ display: flex;
111
+ flex-direction: column;
112
+ align-items: center;
113
+ gap: 0.25rem;
114
+ cursor: pointer;
115
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
116
+ }
117
+
118
+ .preset-card:hover {
119
+ border-color: var(--accent);
120
+ background: var(--accent);
121
+ transform: translateY(-2px);
122
+ box-shadow: 0 4px 12px rgba(244, 63, 94, 0.25);
123
+ }
124
+
125
+ .preset-card:hover .preset-name {
126
+ color: var(--color-white);
127
+ }
128
+
129
+ .preset-card:hover .preset-meta {
130
+ color: rgba(255, 255, 255, 0.85);
131
+ }
132
+
133
+ .preset-card.active {
134
+ background: var(--accent);
135
+ border-color: var(--accent);
136
+ }
137
+
138
+ .preset-card.active .preset-name {
139
+ color: var(--color-white);
140
+ }
141
+
142
+ .preset-card.active .preset-meta {
143
+ color: rgba(255, 255, 255, 0.8);
144
+ }
145
+
146
+ .preset-name {
147
+ font-size: 0.85rem;
148
+ font-weight: 700;
149
+ color: var(--text-base);
150
+ }
151
+
152
+ .preset-meta {
153
+ font-size: 0.75rem;
154
+ color: var(--text-muted);
155
+ }
156
+
157
+ .settings-grid {
158
+ display: grid;
159
+ grid-template-columns: 1fr 1fr;
160
+ gap: 1rem;
161
+ }
162
+
163
+ .control-box {
164
+ display: flex;
165
+ flex-direction: column;
166
+ gap: 0.5rem;
167
+ }
168
+
169
+ .config-label {
170
+ font-size: 0.75rem;
171
+ font-weight: 700;
172
+ color: var(--text-muted);
173
+ text-transform: uppercase;
174
+ }
175
+
176
+ .select-input {
177
+ background: var(--bg-surface);
178
+ border: 1px solid var(--border-color);
179
+ border-radius: 0.5rem;
180
+ padding: 0.6rem;
181
+ color: var(--text-base);
182
+ font-weight: 600;
183
+ outline: none;
184
+ cursor: pointer;
185
+ }
186
+
187
+ .time-inputs-container {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 0.5rem;
191
+ }
192
+
193
+ .time-adjuster-group {
194
+ display: flex;
195
+ align-items: center;
196
+ background: var(--bg-surface);
197
+ border: 1px solid var(--border-color);
198
+ border-radius: 0.5rem;
199
+ padding: 0.15rem 0.35rem;
200
+ }
201
+
202
+ .time-adj-btn {
203
+ background: none;
204
+ border: none;
205
+ color: var(--text-muted);
206
+ font-size: 1.1rem;
207
+ font-weight: 700;
208
+ width: 1.5rem;
209
+ height: 1.5rem;
210
+ cursor: pointer;
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: center;
214
+ transition: all 0.2s;
215
+ padding: 0;
216
+ user-select: none;
217
+ }
218
+
219
+ .time-adj-btn:hover {
220
+ color: var(--accent);
221
+ }
222
+
223
+ .time-unit-lbl {
224
+ font-size: 0.7rem;
225
+ font-weight: 700;
226
+ color: var(--text-muted);
227
+ margin-left: 0.15rem;
228
+ text-transform: uppercase;
229
+ }
230
+
231
+ .num-input {
232
+ width: 1.8rem;
233
+ background: none;
234
+ border: none;
235
+ color: var(--text-base);
236
+ font-size: 1.1rem;
237
+ font-weight: 700;
238
+ text-align: center;
239
+ outline: none;
240
+ -moz-appearance: textfield;
241
+ }
242
+
243
+ .num-input::-webkit-outer-spin-button,
244
+ .num-input::-webkit-inner-spin-button {
245
+ -webkit-appearance: none;
246
+ margin: 0;
247
+ }
248
+
249
+ .time-sep {
250
+ font-weight: 700;
251
+ color: var(--text-muted);
252
+ }
253
+
254
+ .number-input-wrapper {
255
+ display: flex;
256
+ align-items: center;
257
+ background: var(--bg-surface);
258
+ border: 1px solid var(--border-color);
259
+ border-radius: 0.5rem;
260
+ padding: 0.4rem 0.75rem;
261
+ }
262
+
263
+ .num-input-full {
264
+ width: 100%;
265
+ background: none;
266
+ border: none;
267
+ color: var(--text-base);
268
+ font-size: 1rem;
269
+ font-weight: 700;
270
+ outline: none;
271
+ }
272
+
273
+ .unit-label {
274
+ font-size: 0.8rem;
275
+ font-weight: 700;
276
+ color: var(--text-muted);
277
+ }
278
+
279
+ .audio-toggles {
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 0.75rem;
283
+ }
284
+
285
+ .toggle-control {
286
+ display: flex;
287
+ align-items: center;
288
+ gap: 0.75rem;
289
+ cursor: pointer;
290
+ user-select: none;
291
+ }
292
+
293
+ .toggle-control input {
294
+ display: none;
295
+ }
296
+
297
+ .toggle-slider {
298
+ position: relative;
299
+ width: 2.6rem;
300
+ height: 1.4rem;
301
+ background: var(--border-color);
302
+ border-radius: 99px;
303
+ transition: background 0.3s;
304
+ }
305
+
306
+ .toggle-slider::before {
307
+ content: '';
308
+ position: absolute;
309
+ top: 0.15rem;
310
+ left: 0.15rem;
311
+ width: 1.1rem;
312
+ height: 1.1rem;
313
+ background: var(--bg-surface);
314
+ border-radius: 50%;
315
+ transition: transform 0.3s;
316
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
317
+ }
318
+
319
+ .toggle-control input:checked + .toggle-slider {
320
+ background: var(--accent);
321
+ }
322
+
323
+ .toggle-control input:checked + .toggle-slider::before {
324
+ transform: translateX(1.2rem);
325
+ }
326
+
327
+ .toggle-label {
328
+ font-size: 0.85rem;
329
+ font-weight: 600;
330
+ color: var(--text-base);
331
+ }
332
+
333
+ .player-input-block {
334
+ display: flex;
335
+ flex-direction: column;
336
+ gap: 0.75rem;
337
+ width: 100%;
338
+ }
339
+
340
+ .player-input-row-nowrap {
341
+ display: flex;
342
+ gap: 0.5rem;
343
+ width: 100%;
344
+ }
345
+
346
+ .color-picker-row {
347
+ display: flex;
348
+ align-items: center;
349
+ width: 100%;
350
+ }
351
+
352
+ .text-input {
353
+ flex: 1;
354
+ min-width: 120px;
355
+ background: var(--bg-surface);
356
+ border: 1px solid var(--border-color);
357
+ border-radius: 0.5rem;
358
+ padding: 0.6rem;
359
+ color: var(--text-base);
360
+ font-weight: 600;
361
+ outline: none;
362
+ }
363
+
364
+ .text-input:focus {
365
+ border-color: var(--accent);
366
+ }
367
+
368
+ .color-picker-grid {
369
+ display: flex;
370
+ gap: 0.35rem;
371
+ }
372
+
373
+ .color-dot {
374
+ width: 1.4rem;
375
+ height: 1.4rem;
376
+ border-radius: 50%;
377
+ border: 2px solid transparent;
378
+ cursor: pointer;
379
+ transition: transform 0.2s, border-color 0.2s;
380
+ padding: 0;
381
+ }
382
+
383
+ .color-dot:hover {
384
+ transform: scale(1.15);
385
+ }
386
+
387
+ .color-dot.active {
388
+ border-color: var(--text-base);
389
+ transform: scale(1.2);
390
+ box-shadow: 0 0 0 3px var(--text-base), 0 0 12px rgba(59, 130, 246, 0.4);
391
+ }
392
+
393
+ .color-blue { background: #3b82f6; }
394
+ .color-red { background: #ef4444; }
395
+ .color-green { background: #22c55e; }
396
+ .color-yellow { background: #eab308; }
397
+ .color-purple { background: #a855f7; }
398
+ .color-orange { background: #f97316; }
399
+ .color-teal { background: #14b8a6; }
400
+ .color-pink { background: #ec4899; }
401
+
402
+ .btn-action {
403
+ background: var(--bg-surface);
404
+ border: 1px solid var(--border-color);
405
+ color: var(--text-base);
406
+ border-radius: 0.5rem;
407
+ width: 2.4rem;
408
+ height: 2.4rem;
409
+ display: flex;
410
+ align-items: center;
411
+ justify-content: center;
412
+ cursor: pointer;
413
+ transition: all 0.2s;
414
+ }
415
+
416
+ .btn-action:hover {
417
+ border-color: var(--accent);
418
+ background: var(--accent);
419
+ color: var(--color-white);
420
+ box-shadow: 0 4px 10px rgba(244, 63, 94, 0.2);
421
+ }
422
+
423
+ .btn-action.danger:hover {
424
+ border-color: var(--color-timer-danger);
425
+ background: var(--color-timer-danger);
426
+ color: var(--color-white);
427
+ box-shadow: 0 4px 10px rgba(239, 68, 68, 0.2);
428
+ }
429
+
430
+ .btn-action.primary {
431
+ background: var(--accent);
432
+ border-color: var(--accent);
433
+ color: var(--color-white);
434
+ }
435
+
436
+ .btn-action.primary:hover {
437
+ filter: brightness(1.1);
438
+ }
439
+
440
+ .list-group {
441
+ flex: 1;
442
+ min-height: 160px;
443
+ }
444
+
445
+ .player-list-header {
446
+ display: flex;
447
+ justify-content: space-between;
448
+ align-items: center;
449
+ font-size: 0.85rem;
450
+ font-weight: 700;
451
+ color: var(--text-muted);
452
+ }
453
+
454
+ .count-badge {
455
+ background: var(--border-color);
456
+ padding: 0.15rem 0.5rem;
457
+ border-radius: 99px;
458
+ font-size: 0.75rem;
459
+ }
460
+
461
+ .player-list-scroll {
462
+ display: flex;
463
+ flex-direction: column;
464
+ gap: 0.5rem;
465
+ max-height: 180px;
466
+ overflow-y: auto;
467
+ }
468
+
469
+ .player-row {
470
+ display: flex;
471
+ align-items: center;
472
+ justify-content: space-between;
473
+ background: var(--bg-surface);
474
+ border: 1px solid var(--border-color);
475
+ border-radius: 0.5rem;
476
+ padding: 0.5rem 0.75rem;
477
+ animation: timer-slide-up 0.3s ease forwards;
478
+ opacity: 0;
479
+ }
480
+
481
+ .player-info-meta {
482
+ display: flex;
483
+ align-items: center;
484
+ gap: 0.75rem;
485
+ }
486
+
487
+ .player-badge-color {
488
+ width: 0.75rem;
489
+ height: 0.75rem;
490
+ border-radius: 50%;
491
+ }
492
+
493
+ .player-row-name {
494
+ font-size: 0.9rem;
495
+ font-weight: 700;
496
+ }
497
+
498
+ .player-row-actions {
499
+ display: flex;
500
+ gap: 0.25rem;
501
+ }
502
+
503
+ .players-hint {
504
+ display: flex;
505
+ flex-direction: column;
506
+ align-items: center;
507
+ justify-content: center;
508
+ gap: 0.5rem;
509
+ color: var(--text-muted);
510
+ flex: 1;
511
+ padding: 1.5rem 0;
512
+ text-align: center;
513
+ }
514
+
515
+ .players-hint p {
516
+ margin: 0;
517
+ font-size: 0.85rem;
518
+ }
519
+
520
+ .setup-actions {
521
+ display: flex;
522
+ justify-content: center;
523
+ margin-top: 1rem;
524
+ }
525
+
526
+ .start-hero-btn {
527
+ background: linear-gradient(135deg, var(--accent), #e11d48);
528
+ border: none;
529
+ color: var(--color-white);
530
+ border-radius: 0.75rem;
531
+ padding: 0.8rem 2.5rem;
532
+ font-size: 1.1rem;
533
+ font-weight: 800;
534
+ display: flex;
535
+ align-items: center;
536
+ gap: 0.5rem;
537
+ cursor: pointer;
538
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
539
+ box-shadow: 0 4px 15px rgba(244, 63, 94, 0.3);
540
+ }
541
+
542
+ .start-hero-btn:hover:not(:disabled) {
543
+ transform: translateY(-2px) scale(1.03);
544
+ box-shadow: 0 8px 25px rgba(244, 63, 94, 0.4);
545
+ }
546
+
547
+ .start-hero-btn:active:not(:disabled) {
548
+ transform: scale(0.97);
549
+ }
550
+
551
+ .start-hero-btn:disabled {
552
+ opacity: 0.5;
553
+ cursor: not-allowed;
554
+ box-shadow: none;
555
+ }
556
+
557
+ .theme-light .start-hero-btn {
558
+ background: linear-gradient(135deg, #ef4444, #dc2626);
559
+ color: var(--color-white);
560
+ box-shadow: 0 4px 15px rgba(220, 38, 38, 0.25);
561
+ }
562
+
563
+ .theme-light .start-hero-btn:hover:not(:disabled) {
564
+ box-shadow: 0 8px 25px rgba(220, 38, 38, 0.4);
565
+ }
566
+
567
+ .theme-light .btn-action.primary {
568
+ background: linear-gradient(135deg, #ef4444, #dc2626);
569
+ border-color: #dc2626;
570
+ color: var(--color-white);
571
+ box-shadow: 0 2px 6px rgba(220, 38, 38, 0.2);
572
+ }
573
+
574
+ .theme-light .btn-action.primary:hover {
575
+ background: linear-gradient(135deg, #f87171, #ef4444);
576
+ border-color: #ef4444;
577
+ box-shadow: 0 4px 12px rgba(220, 38, 38, 0.35);
578
+ }
579
+
580
+ .theme-light .modal-action-btn.primary,
581
+ .theme-light .resume-hero-btn {
582
+ background: linear-gradient(135deg, #ef4444, #dc2626);
583
+ color: var(--color-white);
584
+ box-shadow: 0 4px 10px rgba(220, 38, 38, 0.2);
585
+ }
586
+
587
+ .theme-light .modal-action-btn.primary:hover,
588
+ .theme-light .resume-hero-btn:hover {
589
+ filter: brightness(1.1);
590
+ box-shadow: 0 6px 15px rgba(220, 38, 38, 0.3);
591
+ }
592
+
593
+ .timer-duel-panel {
594
+ display: grid;
595
+ grid-template-rows: 1fr auto 1fr;
596
+ height: 520px;
597
+ border-radius: 1rem;
598
+ overflow: hidden;
599
+ border: 1px solid var(--border-color);
600
+ animation: timer-fade-in 0.4s ease;
601
+ }
602
+
603
+ .duel-player-side {
604
+ position: relative;
605
+ display: flex;
606
+ align-items: center;
607
+ justify-content: center;
608
+ cursor: pointer;
609
+ user-select: none;
610
+ transition: background 0.3s;
611
+ }
612
+
613
+ .duel-player-side::before {
614
+ content: '';
615
+ position: absolute;
616
+ inset: 0;
617
+ opacity: 0.08;
618
+ transition: opacity 0.3s;
619
+ }
620
+
621
+ .duel-player-side:hover::before {
622
+ opacity: 0.14;
623
+ }
624
+
625
+ .duel-player-side.active {
626
+ box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.2);
627
+ animation: pulse-glow 2s ease-in-out infinite;
628
+ }
629
+
630
+ .duel-player-side.active::before {
631
+ opacity: 0.22;
632
+ }
633
+
634
+ .duel-player-side.expired {
635
+ animation: flash 0.6s ease-in-out 3;
636
+ background: rgba(239, 68, 68, 0.25);
637
+ }
638
+
639
+ .duel-player-container {
640
+ display: flex;
641
+ flex-direction: column;
642
+ align-items: center;
643
+ gap: 0.5rem;
644
+ position: relative;
645
+ z-index: 2;
646
+ transition: transform 0.3s;
647
+ pointer-events: none;
648
+ }
649
+
650
+ .duel-player-side.rotated .duel-player-container {
651
+ transform: rotate(180deg);
652
+ }
653
+
654
+ .side-header {
655
+ display: flex;
656
+ align-items: center;
657
+ gap: 0.5rem;
658
+ pointer-events: auto;
659
+ }
660
+
661
+ .player-name-lbl {
662
+ font-size: 1.1rem;
663
+ font-weight: 800;
664
+ text-transform: uppercase;
665
+ letter-spacing: 0.05em;
666
+ color: inherit;
667
+ }
668
+
669
+ .side-rotate-btn {
670
+ background: rgba(255, 255, 255, 0.15);
671
+ border: none;
672
+ border-radius: 50%;
673
+ width: 1.8rem;
674
+ height: 1.8rem;
675
+ display: flex;
676
+ align-items: center;
677
+ justify-content: center;
678
+ cursor: pointer;
679
+ color: inherit;
680
+ transition: transform 0.2s;
681
+ }
682
+
683
+ .side-rotate-btn:hover {
684
+ transform: scale(1.1);
685
+ }
686
+
687
+ .side-timer {
688
+ font-size: 4.5rem;
689
+ font-weight: 900;
690
+ font-variant-numeric: tabular-nums;
691
+ line-height: 1;
692
+ transition: color 0.3s ease;
693
+ }
694
+
695
+ .side-meta {
696
+ display: flex;
697
+ align-items: center;
698
+ gap: 1rem;
699
+ }
700
+
701
+ .rounds-count-lbl {
702
+ font-size: 0.8rem;
703
+ font-weight: 600;
704
+ color: inherit;
705
+ opacity: 0.85;
706
+ }
707
+
708
+ .side-status-badge {
709
+ font-size: 0.7rem;
710
+ font-weight: 800;
711
+ padding: 0.15rem 0.5rem;
712
+ border-radius: 99px;
713
+ background: rgba(0, 0, 0, 0.2);
714
+ }
715
+
716
+ .duel-controls-bar {
717
+ display: flex;
718
+ align-items: center;
719
+ justify-content: space-between;
720
+ padding: 0.5rem 1.5rem;
721
+ background: var(--bg-page);
722
+ border-top: 1px solid var(--border-color);
723
+ border-bottom: 1px solid var(--border-color);
724
+ z-index: 10;
725
+ }
726
+
727
+ .ctrl-bar-btn {
728
+ background: var(--bg-surface);
729
+ border: 1px solid var(--border-color);
730
+ border-radius: 50%;
731
+ width: 2.6rem;
732
+ height: 2.6rem;
733
+ display: flex;
734
+ align-items: center;
735
+ justify-content: center;
736
+ color: var(--text-base);
737
+ cursor: pointer;
738
+ transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
739
+ }
740
+
741
+ .ctrl-bar-btn:hover {
742
+ border-color: var(--accent);
743
+ background: var(--accent);
744
+ color: var(--color-white);
745
+ transform: scale(1.08);
746
+ box-shadow: 0 4px 10px rgba(244, 63, 94, 0.2);
747
+ }
748
+
749
+ .ctrl-bar-btn.primary-ctrl {
750
+ background: var(--accent);
751
+ border-color: var(--accent);
752
+ color: var(--color-white);
753
+ width: 3.2rem;
754
+ height: 3.2rem;
755
+ }
756
+
757
+ .ctrl-bar-btn.primary-ctrl:hover {
758
+ filter: brightness(1.1);
759
+ transform: scale(1.1);
760
+ }
761
+
762
+ .round-counter-display {
763
+ display: flex;
764
+ flex-direction: column;
765
+ align-items: center;
766
+ }
767
+
768
+ .round-lbl {
769
+ font-size: 0.65rem;
770
+ font-weight: 700;
771
+ text-transform: uppercase;
772
+ color: var(--text-muted);
773
+ }
774
+
775
+ .round-num {
776
+ font-size: 1.2rem;
777
+ font-weight: 900;
778
+ }
779
+
780
+ .timer-multiplayer-panel {
781
+ display: flex;
782
+ flex-direction: column;
783
+ gap: 1.5rem;
784
+ animation: timer-fade-in 0.4s ease;
785
+ }
786
+
787
+ .multiplayer-header-bar {
788
+ display: flex;
789
+ justify-content: space-between;
790
+ align-items: center;
791
+ background: var(--bg-page);
792
+ border: 1px solid var(--border-color);
793
+ border-radius: 0.75rem;
794
+ padding: 0.6rem 1rem;
795
+ }
796
+
797
+ .game-meta-indicators {
798
+ display: flex;
799
+ gap: 1.5rem;
800
+ }
801
+
802
+ .meta-indicator {
803
+ display: flex;
804
+ flex-direction: column;
805
+ align-items: center;
806
+ }
807
+
808
+ .meta-lbl {
809
+ font-size: 0.6rem;
810
+ font-weight: 700;
811
+ text-transform: uppercase;
812
+ color: var(--text-muted);
813
+ }
814
+
815
+ .meta-val {
816
+ font-size: 0.95rem;
817
+ font-weight: 800;
818
+ }
819
+
820
+ .header-action-group {
821
+ display: flex;
822
+ gap: 0.5rem;
823
+ }
824
+
825
+ .multiplayer-grid {
826
+ display: grid;
827
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
828
+ gap: 1rem;
829
+ }
830
+
831
+ .multi-player-card {
832
+ background: var(--bg-page);
833
+ border: 1px solid var(--border-color);
834
+ border-radius: 0.75rem;
835
+ padding: 1rem;
836
+ display: flex;
837
+ flex-direction: column;
838
+ gap: 0.75rem;
839
+ position: relative;
840
+ overflow: hidden;
841
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
842
+ }
843
+
844
+ .multi-player-card::before {
845
+ content: '';
846
+ position: absolute;
847
+ top: 0;
848
+ left: 0;
849
+ right: 0;
850
+ height: 4px;
851
+ }
852
+
853
+ .multi-player-card.active {
854
+ transform: translateY(-4px) scale(1.02);
855
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
856
+ border-color: var(--accent);
857
+ }
858
+
859
+ .multi-card-header {
860
+ display: flex;
861
+ justify-content: space-between;
862
+ align-items: center;
863
+ }
864
+
865
+ .multi-card-name {
866
+ font-size: 0.95rem;
867
+ font-weight: 800;
868
+ }
869
+
870
+ .multi-card-rounds {
871
+ font-size: 0.7rem;
872
+ color: var(--text-muted);
873
+ font-weight: 600;
874
+ }
875
+
876
+ .multi-card-timer {
877
+ font-size: 2.2rem;
878
+ font-weight: 800;
879
+ font-variant-numeric: tabular-nums;
880
+ text-align: center;
881
+ }
882
+
883
+ .multi-card-progress {
884
+ height: 4px;
885
+ background: rgba(0, 0, 0, 0.1);
886
+ border-radius: 2px;
887
+ overflow: hidden;
888
+ }
889
+
890
+ .multi-card-fill {
891
+ height: 100%;
892
+ border-radius: 2px;
893
+ transition: width 0.3s linear;
894
+ background-size: 200% auto;
895
+ animation: shimmer-slide 3s linear infinite;
896
+ }
897
+
898
+ .multi-card-actions {
899
+ display: flex;
900
+ gap: 0.35rem;
901
+ }
902
+
903
+ .card-btn {
904
+ flex: 1;
905
+ background: var(--bg-surface);
906
+ border: 1px solid var(--border-color);
907
+ color: var(--text-base);
908
+ font-size: 0.7rem;
909
+ font-weight: 800;
910
+ padding: 0.35rem 0;
911
+ border-radius: 0.4rem;
912
+ cursor: pointer;
913
+ transition: all 0.2s;
914
+ }
915
+
916
+ .card-btn:hover {
917
+ border-color: var(--accent);
918
+ background: var(--accent);
919
+ color: var(--color-white);
920
+ box-shadow: 0 4px 10px rgba(244, 63, 94, 0.2);
921
+ }
922
+
923
+ .giant-turn-button-container {
924
+ display: flex;
925
+ justify-content: center;
926
+ margin-top: 1rem;
927
+ }
928
+
929
+ .giant-next-btn {
930
+ background-color: var(--accent);
931
+ background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.15));
932
+ border: none;
933
+ color: var(--color-white);
934
+ border-radius: 1rem;
935
+ padding: 1.2rem 4rem;
936
+ display: flex;
937
+ flex-direction: column;
938
+ align-items: center;
939
+ gap: 0.25rem;
940
+ cursor: pointer;
941
+ transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
942
+ box-shadow: 0 6px 20px rgba(59, 130, 246, 0.35);
943
+ min-width: 280px;
944
+ animation: heartbeat 2s ease-in-out infinite;
945
+ }
946
+
947
+ .giant-next-btn:hover {
948
+ transform: translateY(-3px) scale(1.02);
949
+ box-shadow: 0 10px 30px rgba(59, 130, 246, 0.45);
950
+ }
951
+
952
+ .giant-next-btn:active {
953
+ transform: translateY(1px) scale(0.98);
954
+ }
955
+
956
+ .giant-btn-lbl {
957
+ font-size: 1.2rem;
958
+ font-weight: 800;
959
+ text-transform: uppercase;
960
+ letter-spacing: 0.05em;
961
+ }
962
+
963
+ .giant-btn-sub {
964
+ font-size: 0.8rem;
965
+ opacity: 0.85;
966
+ font-weight: 700;
967
+ }
968
+
969
+ .paused-overlay {
970
+ position: absolute;
971
+ inset: 0;
972
+ background: rgba(0, 0, 0, 0.6);
973
+ backdrop-filter: blur(8px);
974
+ display: flex;
975
+ align-items: center;
976
+ justify-content: center;
977
+ z-index: 100;
978
+ }
979
+
980
+ .overlay-card {
981
+ background: var(--bg-surface);
982
+ border: 1px solid var(--border-color);
983
+ border-radius: 1rem;
984
+ padding: 2.5rem;
985
+ text-align: center;
986
+ display: flex;
987
+ flex-direction: column;
988
+ align-items: center;
989
+ gap: 1.5rem;
990
+ max-width: 320px;
991
+ animation: timer-bounce-in 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
992
+ }
993
+
994
+ .overlay-card h3 {
995
+ margin: 0;
996
+ font-size: 1.3rem;
997
+ font-weight: 800;
998
+ }
999
+
1000
+ .resume-hero-btn {
1001
+ background: var(--accent);
1002
+ border: none;
1003
+ color: var(--color-white);
1004
+ padding: 0.75rem 2rem;
1005
+ border-radius: 0.5rem;
1006
+ font-size: 1rem;
1007
+ font-weight: 700;
1008
+ display: flex;
1009
+ align-items: center;
1010
+ gap: 0.5rem;
1011
+ cursor: pointer;
1012
+ transition: all 0.2s;
1013
+ }
1014
+
1015
+ .resume-hero-btn:hover {
1016
+ filter: brightness(1.15);
1017
+ transform: translateY(-2px);
1018
+ }
1019
+
1020
+ .timer-modal-backdrop {
1021
+ position: absolute;
1022
+ inset: 0;
1023
+ background: rgba(0, 0, 0, 0.55);
1024
+ backdrop-filter: blur(6px);
1025
+ display: flex;
1026
+ align-items: center;
1027
+ justify-content: center;
1028
+ z-index: 1000;
1029
+ }
1030
+
1031
+ .stats-modal-card {
1032
+ background: var(--bg-surface);
1033
+ border: 1px solid var(--border-color);
1034
+ border-radius: 1.25rem;
1035
+ padding: 2rem;
1036
+ width: 90%;
1037
+ max-width: 500px;
1038
+ display: flex;
1039
+ flex-direction: column;
1040
+ gap: 1.5rem;
1041
+ animation: timer-bounce-in 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
1042
+ }
1043
+
1044
+ .modal-header {
1045
+ display: flex;
1046
+ justify-content: space-between;
1047
+ align-items: center;
1048
+ }
1049
+
1050
+ .header-title-group {
1051
+ display: flex;
1052
+ align-items: center;
1053
+ gap: 0.5rem;
1054
+ color: var(--accent);
1055
+ }
1056
+
1057
+ .header-title-group h3 {
1058
+ margin: 0;
1059
+ font-size: 1.25rem;
1060
+ font-weight: 800;
1061
+ }
1062
+
1063
+ .modal-close-btn {
1064
+ background: none;
1065
+ border: none;
1066
+ color: var(--text-muted);
1067
+ cursor: pointer;
1068
+ transition: color 0.2s;
1069
+ padding: 0.25rem;
1070
+ border-radius: 50%;
1071
+ }
1072
+
1073
+ .modal-close-btn:hover {
1074
+ color: var(--text-base);
1075
+ background: var(--bg-page);
1076
+ }
1077
+
1078
+ .modal-body {
1079
+ display: flex;
1080
+ flex-direction: column;
1081
+ gap: 1.5rem;
1082
+ }
1083
+
1084
+ .general-stats-grid {
1085
+ display: grid;
1086
+ grid-template-columns: repeat(3, 1fr);
1087
+ gap: 0.75rem;
1088
+ }
1089
+
1090
+ .stat-card-mini {
1091
+ background: var(--bg-page);
1092
+ border: 1px solid var(--border-color);
1093
+ border-radius: 0.75rem;
1094
+ padding: 0.75rem;
1095
+ display: flex;
1096
+ flex-direction: column;
1097
+ align-items: center;
1098
+ gap: 0.25rem;
1099
+ }
1100
+
1101
+ .mini-lbl {
1102
+ font-size: 0.65rem;
1103
+ font-weight: 700;
1104
+ text-transform: uppercase;
1105
+ color: var(--text-muted);
1106
+ text-align: center;
1107
+ }
1108
+
1109
+ .mini-val {
1110
+ font-size: 0.95rem;
1111
+ font-weight: 800;
1112
+ text-align: center;
1113
+ }
1114
+
1115
+ .highlighted-stat {
1116
+ border-color: rgba(245, 158, 11, 0.4);
1117
+ background: rgba(245, 158, 11, 0.05);
1118
+ }
1119
+
1120
+ .highlighted-stat .mini-val {
1121
+ color: var(--color-timer-warning);
1122
+ }
1123
+
1124
+ .charts-section h4 {
1125
+ font-size: 0.9rem;
1126
+ font-weight: 700;
1127
+ color: var(--text-muted);
1128
+ text-transform: uppercase;
1129
+ margin: 0 0 0.75rem;
1130
+ }
1131
+
1132
+ .players-ranking-list {
1133
+ display: flex;
1134
+ flex-direction: column;
1135
+ gap: 0.85rem;
1136
+ }
1137
+
1138
+ .ranking-row {
1139
+ display: flex;
1140
+ flex-direction: column;
1141
+ gap: 0.25rem;
1142
+ }
1143
+
1144
+ .ranking-info {
1145
+ display: flex;
1146
+ justify-content: space-between;
1147
+ font-size: 0.85rem;
1148
+ font-weight: 700;
1149
+ }
1150
+
1151
+ .ranking-bar-outer {
1152
+ height: 0.6rem;
1153
+ background: var(--border-color);
1154
+ border-radius: 99px;
1155
+ overflow: hidden;
1156
+ }
1157
+
1158
+ .ranking-bar-inner {
1159
+ height: 100%;
1160
+ border-radius: 99px;
1161
+ transition: width 0.8s cubic-bezier(0.1, 0.8, 0.2, 1);
1162
+ background-size: 200% auto;
1163
+ animation: shimmer-slide 3s linear infinite;
1164
+ }
1165
+
1166
+ .modal-footer {
1167
+ display: flex;
1168
+ justify-content: flex-end;
1169
+ }
1170
+
1171
+ .modal-action-btn {
1172
+ background: var(--accent);
1173
+ border: none;
1174
+ color: var(--color-white);
1175
+ padding: 0.65rem 1.5rem;
1176
+ border-radius: 0.5rem;
1177
+ font-size: 0.9rem;
1178
+ font-weight: 700;
1179
+ display: flex;
1180
+ align-items: center;
1181
+ gap: 0.5rem;
1182
+ cursor: pointer;
1183
+ transition: all 0.2s;
1184
+ }
1185
+
1186
+ .modal-action-btn:hover {
1187
+ filter: brightness(1.1);
1188
+ transform: translateY(-1px);
1189
+ }
1190
+
1191
+ @keyframes timer-fade-in {
1192
+ from {
1193
+ opacity: 0;
1194
+ transform: translateY(8px);
1195
+ }
1196
+ to {
1197
+ opacity: 1;
1198
+ transform: translateY(0);
1199
+ }
1200
+ }
1201
+
1202
+ @keyframes modal-pop {
1203
+ from {
1204
+ opacity: 0;
1205
+ transform: scale(0.95);
1206
+ }
1207
+ to {
1208
+ opacity: 1;
1209
+ transform: scale(1);
1210
+ }
1211
+ }
1212
+
1213
+ @keyframes slide-in {
1214
+ from {
1215
+ opacity: 0;
1216
+ transform: translateY(4px);
1217
+ }
1218
+ to {
1219
+ opacity: 1;
1220
+ transform: translateY(0);
1221
+ }
1222
+ }
1223
+
1224
+ .multi-card-name-wrapper {
1225
+ display: flex;
1226
+ align-items: center;
1227
+ gap: 0.4rem;
1228
+ }
1229
+
1230
+ .player-color-indicator {
1231
+ width: 0.6rem;
1232
+ height: 0.6rem;
1233
+ border-radius: 50%;
1234
+ display: inline-block;
1235
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
1236
+ }
1237
+
1238
+ /* =============================================================== */
1239
+ /* PARTICLE EFFECTS — styling for particles.ts DOM elements */
1240
+ /* =============================================================== */
1241
+
1242
+ .timer-particle {
1243
+ position: absolute;
1244
+ font-size: 1.3rem;
1245
+ font-weight: 900;
1246
+ pointer-events: none;
1247
+ z-index: 999;
1248
+ animation: particle-float 0.9s ease-out forwards;
1249
+ }
1250
+ .timer-particle-accent { color: var(--accent); }
1251
+ .timer-particle-warning { color: var(--color-timer-warning); }
1252
+ .timer-particle-danger { color: var(--color-timer-danger); }
1253
+ .timer-particle-success { color: var(--color-timer-success); }
1254
+
1255
+ @keyframes particle-float {
1256
+ 0% {
1257
+ opacity: 1;
1258
+ transform: translateY(0) scale(1);
1259
+ }
1260
+ 100% {
1261
+ opacity: 0;
1262
+ transform: translateY(-60px) scale(0.4);
1263
+ }
1264
+ }
1265
+
1266
+ .timer-pop-burst {
1267
+ position: absolute;
1268
+ font-size: 2rem;
1269
+ font-weight: 900;
1270
+ pointer-events: none;
1271
+ z-index: 999;
1272
+ left: 50%;
1273
+ top: 50%;
1274
+ animation: pop-burst 1.2s ease-out forwards;
1275
+ }
1276
+
1277
+ @keyframes pop-burst {
1278
+ 0% {
1279
+ opacity: 1;
1280
+ transform: translate(-50%, -50%) scale(0.5);
1281
+ }
1282
+ 50% {
1283
+ opacity: 1;
1284
+ transform: translate(-50%, calc(-50% - 20px)) scale(1.2);
1285
+ }
1286
+ 100% {
1287
+ opacity: 0;
1288
+ transform: translate(-50%, calc(-50% - 50px)) scale(1.5);
1289
+ }
1290
+ }
1291
+
1292
+ .timer-expired-particle {
1293
+ position: absolute;
1294
+ font-weight: 900;
1295
+ font-size: 1.5rem;
1296
+ pointer-events: none;
1297
+ z-index: 1000;
1298
+ left: 50%;
1299
+ top: 50%;
1300
+ animation: expired-burst 1s ease-out forwards;
1301
+ color: var(--color);
1302
+ }
1303
+
1304
+ @keyframes expired-burst {
1305
+ 0% {
1306
+ opacity: 1;
1307
+ transform: translate(-50%, -50%) scale(1);
1308
+ }
1309
+ 100% {
1310
+ opacity: 0;
1311
+ transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.3);
1312
+ }
1313
+ }
1314
+
1315
+ .timer-turn-particle {
1316
+ position: absolute;
1317
+ font-weight: 900;
1318
+ font-size: 1.2rem;
1319
+ pointer-events: none;
1320
+ z-index: 999;
1321
+ left: 50%;
1322
+ top: 50%;
1323
+ animation: turn-burst 0.8s ease-out forwards;
1324
+ color: var(--accent);
1325
+ }
1326
+
1327
+ @keyframes turn-burst {
1328
+ 0% {
1329
+ opacity: 1;
1330
+ transform: translate(-50%, -50%) scale(1);
1331
+ }
1332
+ 100% {
1333
+ opacity: 0;
1334
+ transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.5);
1335
+ }
1336
+ }
1337
+
1338
+ /* Confetti celebration */
1339
+ .timer-confetti-piece {
1340
+ position: absolute;
1341
+ width: 8px;
1342
+ height: 8px;
1343
+ pointer-events: none;
1344
+ z-index: 9999;
1345
+ animation: confetti-fall 2.5s ease-in forwards;
1346
+ border-radius: 2px;
1347
+ }
1348
+ .timer-confetti-round {
1349
+ border-radius: 50%;
1350
+ }
1351
+
1352
+ @keyframes confetti-fall {
1353
+ 0% {
1354
+ transform: translateY(-10px) rotate(0deg) scale(1);
1355
+ opacity: 1;
1356
+ }
1357
+ 80% {
1358
+ opacity: 0.8;
1359
+ }
1360
+ 100% {
1361
+ transform: translateY(500px) rotate(720deg) scale(0.3);
1362
+ opacity: 0;
1363
+ }
1364
+ }
1365
+
1366
+ /* =============================================================== */
1367
+ /* NEW KEYFRAME ANIMATIONS */
1368
+ /* =============================================================== */
1369
+
1370
+ @keyframes pulse-glow {
1371
+ 0%, 100% { box-shadow: 0 0 8px rgba(59, 130, 246, 0.2); }
1372
+ 50% { box-shadow: 0 0 25px rgba(59, 130, 246, 0.5), 0 0 50px rgba(59, 130, 246, 0.15); }
1373
+ }
1374
+
1375
+ @keyframes pulse-glow-warning {
1376
+ 0%, 100% { box-shadow: 0 0 8px rgba(245, 158, 11, 0.3); }
1377
+ 50% { box-shadow: 0 0 25px rgba(245, 158, 11, 0.6), 0 0 50px rgba(245, 158, 11, 0.2); }
1378
+ }
1379
+
1380
+ @keyframes pulse-glow-danger {
1381
+ 0%, 100% { box-shadow: 0 0 8px rgba(239, 68, 68, 0.3); }
1382
+ 50% { box-shadow: 0 0 25px rgba(239, 68, 68, 0.6), 0 0 50px rgba(239, 68, 68, 0.2); }
1383
+ }
1384
+
1385
+ @keyframes shake {
1386
+ 0%, 100% { transform: translateX(0); }
1387
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
1388
+ 20%, 40%, 60%, 80% { transform: translateX(4px); }
1389
+ }
1390
+
1391
+ @keyframes float {
1392
+ 0%, 100% { transform: translateY(0); }
1393
+ 50% { transform: translateY(-6px); }
1394
+ }
1395
+
1396
+ @keyframes breathe {
1397
+ 0%, 100% { transform: scale(1); }
1398
+ 50% { transform: scale(1.04); }
1399
+ }
1400
+
1401
+ @keyframes timer-bounce-in {
1402
+ 0% {
1403
+ transform: scale(0);
1404
+ opacity: 0;
1405
+ }
1406
+ 60% {
1407
+ transform: scale(1.1);
1408
+ }
1409
+ 100% {
1410
+ transform: scale(1);
1411
+ opacity: 1;
1412
+ }
1413
+ }
1414
+
1415
+ @keyframes timer-slide-up {
1416
+ 0% {
1417
+ opacity: 0;
1418
+ transform: translateY(20px);
1419
+ }
1420
+ 100% {
1421
+ opacity: 1;
1422
+ transform: translateY(0);
1423
+ }
1424
+ }
1425
+
1426
+ @keyframes timer-digit-pop {
1427
+ 0% {
1428
+ transform: scale(0.85);
1429
+ opacity: 0.5;
1430
+ }
1431
+ 50% {
1432
+ transform: scale(1.06);
1433
+ }
1434
+ 100% {
1435
+ transform: scale(1);
1436
+ opacity: 1;
1437
+ }
1438
+ }
1439
+
1440
+ @keyframes shimmer-slide {
1441
+ 0% { background-position: -200% center; }
1442
+ 100% { background-position: 200% center; }
1443
+ }
1444
+
1445
+ @keyframes ripple-expand {
1446
+ 0% {
1447
+ width: 0;
1448
+ height: 0;
1449
+ opacity: 0.5;
1450
+ }
1451
+ 100% {
1452
+ width: 300px;
1453
+ height: 300px;
1454
+ opacity: 0;
1455
+ }
1456
+ }
1457
+
1458
+ @keyframes flash {
1459
+ 0%, 100% { opacity: 1; }
1460
+ 50% { opacity: 0.3; }
1461
+ }
1462
+
1463
+ @keyframes heartbeat {
1464
+ 0%, 100% { transform: scale(1); }
1465
+ 15% { transform: scale(1.12); }
1466
+ 30% { transform: scale(1); }
1467
+ 45% { transform: scale(1.06); }
1468
+ 60% { transform: scale(1); }
1469
+ }
1470
+
1471
+ @keyframes spin-slow {
1472
+ 0% { transform: rotate(0deg); }
1473
+ 100% { transform: rotate(360deg); }
1474
+ }
1475
+
1476
+ @keyframes wobble {
1477
+ 0%, 100% { transform: rotate(0deg); }
1478
+ 25% { transform: rotate(-3deg); }
1479
+ 75% { transform: rotate(3deg); }
1480
+ }
1481
+
1482
+ @keyframes glow-expand {
1483
+ 0% {
1484
+ opacity: 0;
1485
+ transform: scale(0.8);
1486
+ }
1487
+ 50% {
1488
+ opacity: 0.4;
1489
+ transform: scale(1.2);
1490
+ }
1491
+ 100% {
1492
+ opacity: 0;
1493
+ transform: scale(1.5);
1494
+ }
1495
+ }
1496
+
1497
+ /* =============================================================== */
1498
+ /* ANIMATION UTILITY CLASSES */
1499
+ /* =============================================================== */
1500
+
1501
+ .active-glow {
1502
+ animation: pulse-glow 2s ease-in-out infinite;
1503
+ }
1504
+ .warning-glow {
1505
+ animation: pulse-glow-warning 1.2s ease-in-out infinite;
1506
+ }
1507
+ .danger-glow {
1508
+ animation: pulse-glow-danger 0.8s ease-in-out infinite;
1509
+ }
1510
+ .danger-shake {
1511
+ animation: shake 0.5s ease-in-out;
1512
+ }
1513
+ .float-anim {
1514
+ animation: float 3s ease-in-out infinite;
1515
+ }
1516
+ .breathe-anim {
1517
+ animation: breathe 2s ease-in-out infinite;
1518
+ }
1519
+ .timer-digit-update {
1520
+ animation: timer-digit-pop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
1521
+ }
1522
+ .bounce-in {
1523
+ animation: timer-bounce-in 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
1524
+ }
1525
+ .slide-up {
1526
+ animation: timer-slide-up 0.3s ease forwards;
1527
+ }
1528
+ .heartbeat {
1529
+ animation: heartbeat 1s ease-in-out infinite;
1530
+ }
1531
+
1532
+ /* Duel: overtime shake + danger glow */
1533
+ .duel-player-side.overtime {
1534
+ animation: shake 0.5s ease-in-out, pulse-glow-danger 1.2s ease-in-out infinite;
1535
+ background: rgba(239, 68, 68, 0.15);
1536
+ }
1537
+
1538
+ /* Staggered entrance for player rows in setup */
1539
+ .player-row:nth-child(1) { animation-delay: 0s; }
1540
+ .player-row:nth-child(2) { animation-delay: 0.04s; }
1541
+ .player-row:nth-child(3) { animation-delay: 0.08s; }
1542
+ .player-row:nth-child(4) { animation-delay: 0.12s; }
1543
+ .player-row:nth-child(5) { animation-delay: 0.16s; }
1544
+ .player-row:nth-child(6) { animation-delay: 0.20s; }
1545
+ .player-row:nth-child(7) { animation-delay: 0.24s; }
1546
+ .player-row:nth-child(8) { animation-delay: 0.28s; }
1547
+
1548
+ /* Start button gentle pulse */
1549
+ .start-hero-btn:not(:disabled) {
1550
+ animation: pulse-glow 2s ease-in-out infinite;
1551
+ }
1552
+
1553
+ /* Warning state badge */
1554
+ .side-status-badge.warning {
1555
+ animation: flash 0.8s ease-in-out infinite;
1556
+ background: var(--color-timer-warning);
1557
+ color: var(--text-base);
1558
+ }
1559
+
1560
+ /* Ripple layer inside duel sides */
1561
+ .duel-ripple-effect {
1562
+ position: absolute;
1563
+ inset: 0;
1564
+ pointer-events: none;
1565
+ overflow: hidden;
1566
+ z-index: 1;
1567
+ }
1568
+ .duel-ripple-effect .ripple {
1569
+ position: absolute;
1570
+ border-radius: 50%;
1571
+ background: rgba(255, 255, 255, 0.35);
1572
+ animation: ripple-expand 0.7s ease-out forwards;
1573
+ pointer-events: none;
1574
+ }
1575
+
1576
+ /* =============================================================== */
1577
+ /* AMBIENT BACKGROUND EFFECTS */
1578
+ /* =============================================================== */
1579
+
1580
+ @keyframes bg-shift {
1581
+ 0% { background-position: 0% 0%; }
1582
+ 100% { background-position: 100% 100%; }
1583
+ }
1584
+
1585
+ .theme-light .timer-main-wrapper {
1586
+ background: linear-gradient(135deg, var(--bg-surface), #f8fafc, var(--bg-surface));
1587
+ background-size: 200% 200%;
1588
+ }
1589
+
1590
+ .theme-dark .timer-main-wrapper {
1591
+ background: linear-gradient(135deg, var(--bg-surface), #1e1e2e, var(--bg-surface));
1592
+ background-size: 200% 200%;
1593
+ }
1594
+
1595
+ /* Subtle floating sparkle dots in background (shown on hover of wrapper) */
1596
+ .timer-bg-sparkle {
1597
+ position: absolute;
1598
+ border-radius: 50%;
1599
+ background: var(--accent);
1600
+ pointer-events: none;
1601
+ opacity: 0;
1602
+ z-index: 0;
1603
+ animation: sparkle-float 4s ease-in-out infinite;
1604
+ }
1605
+
1606
+ @keyframes sparkle-float {
1607
+ 0%, 100% {
1608
+ opacity: 0;
1609
+ transform: translateY(0) scale(0);
1610
+ }
1611
+ 50% {
1612
+ opacity: 0.15;
1613
+ transform: translateY(-20px) scale(1);
1614
+ }
1615
+ }