@oddsmith/ui 0.0.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 (185) hide show
  1. package/.eleventy.cjs +14 -0
  2. package/LICENSE +28 -0
  3. package/README.md +118 -0
  4. package/custom-elements.json +1539 -0
  5. package/docs/_README/index.html +4 -0
  6. package/docs/api/index.html +2100 -0
  7. package/docs/components.bundle.js +1669 -0
  8. package/docs/components.bundle.js.map +1 -0
  9. package/docs/docs.css +162 -0
  10. package/docs/examples/index.html +56 -0
  11. package/docs/index.html +53 -0
  12. package/docs/install/index.html +45 -0
  13. package/docs/prism-okaidia.css +123 -0
  14. package/docs-src/.nojekyll +0 -0
  15. package/docs-src/_README.md +7 -0
  16. package/docs-src/_data/api.11tydata.js +8 -0
  17. package/docs-src/_includes/example.11ty.js +35 -0
  18. package/docs-src/_includes/footer.11ty.js +6 -0
  19. package/docs-src/_includes/header.11ty.js +7 -0
  20. package/docs-src/_includes/nav.11ty.js +11 -0
  21. package/docs-src/_includes/page.11ty.js +32 -0
  22. package/docs-src/_includes/relative-path.cjs +9 -0
  23. package/docs-src/api.11ty.js +85 -0
  24. package/docs-src/bundle.ts +9 -0
  25. package/docs-src/docs.css +162 -0
  26. package/docs-src/examples/index.md +15 -0
  27. package/docs-src/index.md +39 -0
  28. package/docs-src/install.md +28 -0
  29. package/docs-src/package.json +3 -0
  30. package/index.html +19 -0
  31. package/karma.conf.cjs +24 -0
  32. package/main.css +210 -0
  33. package/main.ts +124 -0
  34. package/package.json +86 -0
  35. package/previews/casino.ts +12 -0
  36. package/previews/catalog.ts +94 -0
  37. package/previews/leaderboard-v1.ts +12 -0
  38. package/previews/leaderboard-v2.ts +17 -0
  39. package/previews/sample-data.ts +101 -0
  40. package/previews/sf-leaderboard.ts +100 -0
  41. package/previews/sf-live-feed.ts +15 -0
  42. package/previews/streaks.ts +40 -0
  43. package/previews/types.ts +18 -0
  44. package/src/components/README.md +16 -0
  45. package/src/components/casino-leaderboard/casino-leaderboard.html +80 -0
  46. package/src/components/casino-leaderboard/casino-leaderboard.scss +585 -0
  47. package/src/components/casino-leaderboard/casino-leaderboard.ts +136 -0
  48. package/src/components/casino-leaderboard/data.ts +111 -0
  49. package/src/components/casino-leaderboard/index.ts +5 -0
  50. package/src/components/casino-leaderboard/todo.txt +2 -0
  51. package/src/components/casino-leaderboard/types.ts +19 -0
  52. package/src/components/leaderboard/components/leaderboard.ts +373 -0
  53. package/src/components/leaderboard/components/player-card.ts +342 -0
  54. package/src/components/leaderboard/components/ui.ts +452 -0
  55. package/src/components/leaderboard/data.ts +152 -0
  56. package/src/components/leaderboard/index.ts +2 -0
  57. package/src/components/leaderboard/main.ts +42 -0
  58. package/src/components/leaderboard/styles.ts +67 -0
  59. package/src/components/leaderboard/types.ts +28 -0
  60. package/src/components/leaderboard-v2/components/sf-leaderboard-player.ts +451 -0
  61. package/src/components/leaderboard-v2/components/sf-leaderboard-ui.ts +512 -0
  62. package/src/components/leaderboard-v2/components/sf-leaderboard.ts +205 -0
  63. package/src/components/leaderboard-v2/constants.ts +16 -0
  64. package/src/components/leaderboard-v2/demo/sample-data.ts +152 -0
  65. package/src/components/leaderboard-v2/events.ts +13 -0
  66. package/src/components/leaderboard-v2/icons.ts +22 -0
  67. package/src/components/leaderboard-v2/index.ts +23 -0
  68. package/src/components/leaderboard-v2/sf-leaderboard.html +1 -0
  69. package/src/components/leaderboard-v2/sf-leaderboard.scss +382 -0
  70. package/src/components/leaderboard-v2/tokens.ts +35 -0
  71. package/src/components/leaderboard-v2/types.ts +30 -0
  72. package/src/components/sf-leaderboard/index.ts +77 -0
  73. package/src/components/sf-leaderboard/sections/footer-section/footer-section.host.ts +3 -0
  74. package/src/components/sf-leaderboard/sections/footer-section/footer-section.html +3 -0
  75. package/src/components/sf-leaderboard/sections/footer-section/footer-section.scss +18 -0
  76. package/src/components/sf-leaderboard/sections/footer-section/footer-section.ts +22 -0
  77. package/src/components/sf-leaderboard/sections/header-section/header-section.host.ts +14 -0
  78. package/src/components/sf-leaderboard/sections/header-section/header-section.html +27 -0
  79. package/src/components/sf-leaderboard/sections/header-section/header-section.scss +189 -0
  80. package/src/components/sf-leaderboard/sections/header-section/header-section.ts +70 -0
  81. package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.host.ts +22 -0
  82. package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.html +38 -0
  83. package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.scss +99 -0
  84. package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.ts +121 -0
  85. package/src/components/sf-leaderboard/sections/stats-section/stats-section.host.ts +8 -0
  86. package/src/components/sf-leaderboard/sections/stats-section/stats-section.html +6 -0
  87. package/src/components/sf-leaderboard/sections/stats-section/stats-section.scss +44 -0
  88. package/src/components/sf-leaderboard/sections/stats-section/stats-section.ts +41 -0
  89. package/src/components/sf-leaderboard/sections/table-section/table-section.host.ts +17 -0
  90. package/src/components/sf-leaderboard/sections/table-section/table-section.html +19 -0
  91. package/src/components/sf-leaderboard/sections/table-section/table-section.scss +37 -0
  92. package/src/components/sf-leaderboard/sections/table-section/table-section.ts +108 -0
  93. package/src/components/sf-leaderboard/services/index.ts +22 -0
  94. package/src/components/sf-leaderboard/services/sf-leaderboard-data.service.ts +54 -0
  95. package/src/components/sf-leaderboard/services/sf-leaderboard.state.ts +160 -0
  96. package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.host.ts +7 -0
  97. package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.html +10 -0
  98. package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.scss +180 -0
  99. package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.ts +88 -0
  100. package/src/components/sf-leaderboard/shared/components/filters/filters.host.ts +12 -0
  101. package/src/components/sf-leaderboard/shared/components/filters/filters.html +22 -0
  102. package/src/components/sf-leaderboard/shared/components/filters/filters.scss +122 -0
  103. package/src/components/sf-leaderboard/shared/components/filters/filters.ts +75 -0
  104. package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.host.ts +9 -0
  105. package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.html +5 -0
  106. package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.scss +81 -0
  107. package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.ts +34 -0
  108. package/src/components/sf-leaderboard/shared/components/podium/map-players.ts +24 -0
  109. package/src/components/sf-leaderboard/shared/components/podium/podium.host.ts +10 -0
  110. package/src/components/sf-leaderboard/shared/components/podium/podium.html +53 -0
  111. package/src/components/sf-leaderboard/shared/components/podium/podium.scss +580 -0
  112. package/src/components/sf-leaderboard/shared/components/podium/podium.ts +49 -0
  113. package/src/components/sf-leaderboard/shared/components/podium/podium.types.ts +9 -0
  114. package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.host.ts +11 -0
  115. package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.html +9 -0
  116. package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.scss +98 -0
  117. package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.ts +63 -0
  118. package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.host.ts +9 -0
  119. package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.html +15 -0
  120. package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.scss +210 -0
  121. package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.ts +36 -0
  122. package/src/components/sf-leaderboard/shared/components/table/table.host.ts +5 -0
  123. package/src/components/sf-leaderboard/shared/components/table/table.html +11 -0
  124. package/src/components/sf-leaderboard/shared/components/table/table.scss +212 -0
  125. package/src/components/sf-leaderboard/shared/components/table/table.ts +111 -0
  126. package/src/components/sf-leaderboard/shared/constants/defaults.ts +7 -0
  127. package/src/components/sf-leaderboard/shared/constants/filters.ts +16 -0
  128. package/src/components/sf-leaderboard/shared/constants/index.ts +5 -0
  129. package/src/components/sf-leaderboard/shared/constants/player-stats.ts +3 -0
  130. package/src/components/sf-leaderboard/shared/constants/stats-overview.ts +38 -0
  131. package/src/components/sf-leaderboard/shared/constants/tags.ts +16 -0
  132. package/src/components/sf-leaderboard/shared/styles/_section.scss +35 -0
  133. package/src/components/sf-leaderboard/shared/types/data.ts +29 -0
  134. package/src/components/sf-leaderboard/shared/types/events.ts +30 -0
  135. package/src/components/sf-leaderboard/shared/types/player-stats.ts +3 -0
  136. package/src/components/sf-leaderboard/shared/types/sections.ts +100 -0
  137. package/src/components/sf-leaderboard/shared/utils/utils.ts +17 -0
  138. package/src/components/sf-leaderboard/theme/THEMING.md +54 -0
  139. package/src/components/sf-leaderboard/theme/context.ts +16 -0
  140. package/src/components/sf-leaderboard/theme/default-theme.ts +4 -0
  141. package/src/components/sf-leaderboard/theme/hex-to-rgb.ts +25 -0
  142. package/src/components/sf-leaderboard/theme/index.ts +18 -0
  143. package/src/components/sf-leaderboard/theme/inject-theme.ts +39 -0
  144. package/src/components/sf-leaderboard/theme/load-theme.ts +26 -0
  145. package/src/components/sf-leaderboard/theme/merge-theme.ts +59 -0
  146. package/src/components/sf-leaderboard/theme/scss/_colors.scss +101 -0
  147. package/src/components/sf-leaderboard/theme/scss/shared.scss +123 -0
  148. package/src/components/sf-leaderboard/theme/styles.ts +6 -0
  149. package/src/components/sf-leaderboard/theme/theme-to-css-vars.ts +99 -0
  150. package/src/components/sf-leaderboard/theme/themes/fallback.json +62 -0
  151. package/src/components/sf-leaderboard/theme/themes/red.json +62 -0
  152. package/src/components/sf-leaderboard/theme/types.ts +71 -0
  153. package/src/components/sf-live-feed/components/avatar/avatar.host.ts +5 -0
  154. package/src/components/sf-live-feed/components/avatar/avatar.html +3 -0
  155. package/src/components/sf-live-feed/components/avatar/avatar.scss +24 -0
  156. package/src/components/sf-live-feed/components/avatar/avatar.ts +27 -0
  157. package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.host.ts +8 -0
  158. package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.html +10 -0
  159. package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.scss +177 -0
  160. package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.ts +65 -0
  161. package/src/components/sf-live-feed/constants.ts +4 -0
  162. package/src/components/sf-live-feed/demo/sample-data.ts +34 -0
  163. package/src/components/sf-live-feed/index.ts +19 -0
  164. package/src/components/sf-live-feed/styles/theme.scss +19 -0
  165. package/src/components/sf-live-feed/styles/theme.ts +5 -0
  166. package/src/components/sf-live-feed/types.ts +19 -0
  167. package/src/components/sf-live-feed/utils.ts +17 -0
  168. package/src/components/streaks/constants.ts +17 -0
  169. package/src/components/streaks/demo/sample-steps.ts +10 -0
  170. package/src/components/streaks/events.ts +8 -0
  171. package/src/components/streaks/index.ts +16 -0
  172. package/src/components/streaks/sf-streaks.html +26 -0
  173. package/src/components/streaks/sf-streaks.scss +351 -0
  174. package/src/components/streaks/sf-streaks.ts +235 -0
  175. package/src/components/streaks/types.ts +7 -0
  176. package/src/lib/lit/component.ts +10 -0
  177. package/src/lib/lit/safe-custom-element.ts +12 -0
  178. package/src/lib/lit/scss.ts +6 -0
  179. package/src/vite-env.d.ts +18 -0
  180. package/styles/global.css +125 -0
  181. package/todo.txt +54 -0
  182. package/tsconfig.json +31 -0
  183. package/vite.config.ts +56 -0
  184. package/vite.docs.config.ts +33 -0
  185. package/vite.lit-html-plugin.ts +43 -0
@@ -0,0 +1,53 @@
1
+ <div class="card">
2
+ <div class="ambient-glow glow-gold"></div>
3
+ <div class="card-content">
4
+ <div class="podium-container">
5
+ <div class="player-column second">
6
+ <div class="avatar-wrapper">
7
+ <div class="avatar-ring silver"></div>
8
+ <div class="avatar silver">${host.second.initials}</div>
9
+ <span class="badge-emoji">${host.second.badgeEmoji}</span>
10
+ </div>
11
+ <div class="player-name">${host.second.username}</div>
12
+ <div class="player-earnings silver">${host.second.earnings}</div>
13
+ <div class="player-winrate">${host.second.winRate}</div>
14
+ <div class="podium-block second">
15
+ <span class="podium-number silver">2</span>
16
+ </div>
17
+ </div>
18
+
19
+ <div class="player-column first">
20
+ <div class="sparkles">
21
+ <span class="sparkle">✦</span>
22
+ <span class="sparkle">✦</span>
23
+ <span class="sparkle">✦</span>
24
+ </div>
25
+ <div class="avatar-wrapper">
26
+ <div class="avatar-ring gold"></div>
27
+ <div class="avatar gold">${host.first.initials}</div>
28
+ <span class="badge-emoji fire">${host.first.badgeEmoji}</span>
29
+ </div>
30
+ <div class="player-name">${host.first.username}</div>
31
+ <div class="player-earnings gold">${host.first.earnings}</div>
32
+ <div class="player-winrate">${host.first.winRate}</div>
33
+ <div class="podium-block first">
34
+ <span class="podium-number gold">1</span>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="player-column third">
39
+ <div class="avatar-wrapper">
40
+ <div class="avatar-ring bronze"></div>
41
+ <div class="avatar bronze">${host.third.initials}</div>
42
+ <span class="badge-emoji">${host.third.badgeEmoji}</span>
43
+ </div>
44
+ <div class="player-name">${host.third.username}</div>
45
+ <div class="player-earnings bronze">${host.third.earnings}</div>
46
+ <div class="player-winrate">${host.third.winRate}</div>
47
+ <div class="podium-block third">
48
+ <span class="podium-number bronze">3</span>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
@@ -0,0 +1,580 @@
1
+ @use '../../../theme/scss/colors' as color;
2
+
3
+ :host {
4
+ display: flex;
5
+ width: 100%;
6
+ height: 100%;
7
+ min-height: 0;
8
+ }
9
+
10
+ .card {
11
+ padding: var(--surface-padding) clamp(14px, 2vw, 24px) var(--surface-padding);
12
+ width: 100%;
13
+ max-width: 100%;
14
+ min-width: 0;
15
+ height: 100%;
16
+ flex: 1;
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ .card::before {
22
+ content: '';
23
+ position: absolute;
24
+ top: -50%;
25
+ left: -50%;
26
+ width: 200%;
27
+ height: 200%;
28
+ background: conic-gradient(
29
+ from 0deg,
30
+ transparent,
31
+ rgba(var(--primary-rgb), 0.04),
32
+ transparent,
33
+ rgba(var(--gold-rgb), 0.04),
34
+ transparent
35
+ );
36
+ animation: rotate 12s linear infinite;
37
+ }
38
+
39
+ @keyframes rotate {
40
+ 100% {
41
+ transform: rotate(360deg);
42
+ }
43
+ }
44
+
45
+ .card-content {
46
+ position: relative;
47
+ z-index: 1;
48
+ flex: 1;
49
+ min-height: 0;
50
+ display: flex;
51
+ flex-direction: column;
52
+ padding-top: clamp(28px, 12%, 52px);
53
+ }
54
+
55
+ .podium-container {
56
+ flex: 1;
57
+ min-height: 0;
58
+ display: flex;
59
+ align-items: stretch;
60
+ justify-content: center;
61
+ gap: clamp(6px, 1.5vw, 14px);
62
+ }
63
+
64
+ .player-column {
65
+ flex: 1;
66
+ max-width: clamp(88px, 14vw, 160px);
67
+ min-width: 0;
68
+ display: flex;
69
+ flex-direction: column;
70
+ align-items: center;
71
+ justify-content: flex-end;
72
+ min-height: 0;
73
+ }
74
+
75
+ .player-column.first {
76
+ order: 2;
77
+ max-width: clamp(96px, 16vw, 180px);
78
+ }
79
+
80
+ .player-column.second {
81
+ order: 1;
82
+ }
83
+
84
+ .player-column.third {
85
+ order: 3;
86
+ }
87
+
88
+ .avatar-wrapper,
89
+ .player-name,
90
+ .player-earnings,
91
+ .player-winrate,
92
+ .sparkles {
93
+ flex-shrink: 0;
94
+ }
95
+
96
+ .sparkles {
97
+ position: relative;
98
+ height: clamp(14px, 3vh, 22px);
99
+ width: clamp(40px, 8vw, 56px);
100
+ margin-bottom: clamp(2px, 0.5vh, 4px);
101
+ flex-shrink: 0;
102
+ }
103
+
104
+ .sparkle {
105
+ position: absolute;
106
+ font-size: clamp(10px, 1.8vw, 14px);
107
+ animation: sparkle 1.5s ease-in-out infinite;
108
+ }
109
+
110
+ .sparkle:nth-child(1) {
111
+ left: 10px;
112
+ top: 5px;
113
+ animation-delay: 0s;
114
+ }
115
+
116
+ .sparkle:nth-child(2) {
117
+ left: 25px;
118
+ top: 0;
119
+ animation-delay: 0.3s;
120
+ }
121
+
122
+ .sparkle:nth-child(3) {
123
+ left: 40px;
124
+ top: 8px;
125
+ animation-delay: 0.6s;
126
+ }
127
+
128
+ @keyframes sparkle {
129
+ 0%,
130
+ 100% {
131
+ opacity: 1;
132
+ transform: scale(1) rotate(0deg);
133
+ }
134
+ 50% {
135
+ opacity: 0.4;
136
+ transform: scale(1.3) rotate(180deg);
137
+ }
138
+ }
139
+
140
+ .avatar-wrapper {
141
+ position: relative;
142
+ margin-bottom: clamp(4px, 0.8vh, 8px);
143
+ }
144
+
145
+ .avatar {
146
+ width: clamp(40px, 7vw, 56px);
147
+ height: clamp(40px, 7vw, 56px);
148
+ border-radius: 50%;
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ font-size: clamp(14px, 2.5vw, 18px);
153
+ font-weight: 700;
154
+ position: relative;
155
+ z-index: 2;
156
+ transition: transform 0.3s ease;
157
+ }
158
+
159
+ .player-column.first .avatar {
160
+ width: clamp(44px, 8vw, 64px);
161
+ height: clamp(44px, 8vw, 64px);
162
+ font-size: clamp(15px, 2.8vw, 22px);
163
+ }
164
+
165
+ .avatar:hover {
166
+ transform: scale(1.08);
167
+ }
168
+
169
+ .avatar.gold {
170
+ background: var(--medal-gold-gradient);
171
+ color: var(--medal-avatar-fg);
172
+ box-shadow: 0 0 30px var(--medal-gold-glow);
173
+ }
174
+
175
+ .avatar.silver {
176
+ background: var(--medal-silver-gradient);
177
+ color: var(--medal-avatar-fg);
178
+ box-shadow: 0 0 20px var(--medal-silver-glow);
179
+ }
180
+
181
+ .avatar.bronze {
182
+ background: var(--medal-bronze-gradient);
183
+ color: var(--medal-avatar-fg);
184
+ box-shadow: 0 0 20px var(--medal-bronze-glow);
185
+ }
186
+
187
+ .avatar-ring {
188
+ position: absolute;
189
+ inset: -5px;
190
+ border-radius: 50%;
191
+ border: 2px solid;
192
+ animation: ring-pulse 2.5s ease-in-out infinite;
193
+ }
194
+
195
+ .avatar-ring.gold {
196
+ border-color: var(--medal-gold-border);
197
+ }
198
+
199
+ .avatar-ring.silver {
200
+ border-color: var(--medal-silver-border);
201
+ }
202
+
203
+ .avatar-ring.bronze {
204
+ border-color: var(--medal-bronze-border);
205
+ }
206
+
207
+ @keyframes ring-pulse {
208
+ 0%,
209
+ 100% {
210
+ transform: scale(1);
211
+ opacity: 0.6;
212
+ }
213
+ 50% {
214
+ transform: scale(1.15);
215
+ opacity: 0.2;
216
+ }
217
+ }
218
+
219
+ .badge-emoji {
220
+ position: absolute;
221
+ bottom: -3px;
222
+ right: -3px;
223
+ font-size: clamp(12px, 2vw, 16px);
224
+ z-index: 3;
225
+ animation: badge-bounce 2s ease-in-out infinite;
226
+ }
227
+
228
+ .badge-emoji.fire {
229
+ animation: fire-pulse 0.6s ease-in-out infinite alternate;
230
+ }
231
+
232
+ @keyframes badge-bounce {
233
+ 0%,
234
+ 100% {
235
+ transform: translateY(0);
236
+ }
237
+ 50% {
238
+ transform: translateY(-3px);
239
+ }
240
+ }
241
+
242
+ @keyframes fire-pulse {
243
+ from {
244
+ transform: scale(1);
245
+ }
246
+ to {
247
+ transform: scale(1.2);
248
+ }
249
+ }
250
+
251
+ .player-name {
252
+ font-size: clamp(10px, 1.6vw, 13px);
253
+ font-weight: 700;
254
+ color: var(--foreground);
255
+ margin-bottom: 2px;
256
+ text-align: center;
257
+ line-height: 1.2;
258
+ max-width: 100%;
259
+ overflow: hidden;
260
+ text-overflow: ellipsis;
261
+ white-space: nowrap;
262
+ }
263
+
264
+ .player-earnings {
265
+ font-size: clamp(11px, 1.8vw, 15px);
266
+ font-weight: 700;
267
+ margin-bottom: 2px;
268
+ text-align: center;
269
+ line-height: 1.2;
270
+ }
271
+
272
+ .player-earnings.gold {
273
+ color: var(--gold);
274
+ text-shadow: 0 0 15px rgba(var(--gold-rgb), 0.5);
275
+ }
276
+
277
+ .player-earnings.silver {
278
+ color: var(--silver);
279
+ }
280
+
281
+ .player-earnings.bronze {
282
+ color: var(--bronze);
283
+ }
284
+
285
+ .player-winrate {
286
+ font-size: clamp(9px, 1.4vw, 11px);
287
+ color: var(--muted-foreground);
288
+ margin-bottom: clamp(4px, 0.8vh, 8px);
289
+ text-align: center;
290
+ line-height: 1.2;
291
+ }
292
+
293
+ .podium-block {
294
+ flex: 0 0 auto;
295
+ width: 100%;
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ border-radius: 12px 12px 0 0;
300
+ position: relative;
301
+ overflow: hidden;
302
+ transition: transform 0.3s ease;
303
+ }
304
+
305
+ /* % of column height: 1st tallest, then 2nd, then 3rd (aligned at baseline) */
306
+ .podium-block.first {
307
+ height: 50%;
308
+ min-height: clamp(80px, 12vh, 190px);
309
+ }
310
+
311
+ .podium-block.second {
312
+ height: 36%;
313
+ min-height: clamp(58px, 9vh, 140px);
314
+ }
315
+
316
+ .podium-block.third {
317
+ height: 26%;
318
+ min-height: clamp(42px, 7vh, 105px);
319
+ }
320
+
321
+ .podium-block:hover {
322
+ transform: translateY(-3px);
323
+ }
324
+
325
+ .podium-block::before {
326
+ content: '';
327
+ position: absolute;
328
+ inset: 0;
329
+ opacity: 0.1;
330
+ background: linear-gradient(
331
+ 180deg,
332
+ rgba(255, 255, 255, 0.2) 0%,
333
+ transparent 100%
334
+ );
335
+ }
336
+
337
+ .podium-block.first {
338
+ background: #{color.place-first-podium-slot()};
339
+ border: 2px solid color-mix(in srgb, #{color.place-first-podium-slot()} 55%, transparent);
340
+ border-bottom: none;
341
+ box-shadow: 0 0 32px color-mix(in srgb, #{color.place-first-podium-slot()} 15%, transparent);
342
+ }
343
+
344
+ .podium-block.second {
345
+ background: #{color.place-second-podium-slot()};
346
+ border: 2px solid color-mix(in srgb, #{color.place-second-podium-slot()} 45%, transparent);
347
+ border-bottom: none;
348
+ box-shadow: 0 0 24px color-mix(in srgb, #{color.place-second-podium-slot()} 12%, transparent);
349
+ }
350
+
351
+ .podium-block.third {
352
+ background: #{color.place-third-podium-slot()};
353
+ border: 2px solid color-mix(in srgb, #{color.place-third-podium-slot()} 45%, transparent);
354
+ border-bottom: none;
355
+ box-shadow: 0 0 24px color-mix(in srgb, #{color.place-third-podium-slot()} 12%, transparent);
356
+ }
357
+
358
+ .podium-number {
359
+ font-size: clamp(28px, 6vw, 48px);
360
+ font-weight: 800;
361
+ opacity: 0.4;
362
+ letter-spacing: -2px;
363
+ line-height: 1;
364
+ }
365
+
366
+ .player-column.first .podium-number {
367
+ font-size: clamp(32px, 7vw, 56px);
368
+ }
369
+
370
+ .podium-number.gold {
371
+ color: #{color.place-first-podium-number()};
372
+ }
373
+
374
+ .podium-number.silver {
375
+ color: #{color.place-second-podium-number()};
376
+ }
377
+
378
+ .podium-number.bronze {
379
+ color: #{color.place-third-podium-number()};
380
+ }
381
+
382
+ .ambient-glow {
383
+ position: absolute;
384
+ width: clamp(120px, 40%, 200px);
385
+ height: clamp(120px, 40%, 200px);
386
+ border-radius: 50%;
387
+ filter: blur(60px);
388
+ opacity: 0.15;
389
+ pointer-events: none;
390
+ z-index: 0;
391
+ }
392
+
393
+ .glow-gold {
394
+ background: var(--gold);
395
+ top: 8%;
396
+ left: 50%;
397
+ transform: translateX(-50%);
398
+ animation: glow-pulse 4s ease-in-out infinite;
399
+ }
400
+
401
+ @keyframes glow-pulse {
402
+ 0%,
403
+ 100% {
404
+ opacity: 0.15;
405
+ }
406
+ 50% {
407
+ opacity: 0.25;
408
+ }
409
+ }
410
+
411
+ @media (min-width: 1024px) {
412
+ .card-content {
413
+ padding-top: clamp(32px, 14%, 56px);
414
+ }
415
+
416
+ .podium-block.first {
417
+ height: 54%;
418
+ min-height: clamp(92px, 13vh, 210px);
419
+ }
420
+
421
+ .podium-block.second {
422
+ height: 38%;
423
+ min-height: clamp(66px, 10vh, 155px);
424
+ }
425
+
426
+ .podium-block.third {
427
+ height: 28%;
428
+ min-height: clamp(48px, 8vh, 118px);
429
+ }
430
+
431
+ .podium-number {
432
+ font-size: clamp(36px, 5vw, 52px);
433
+ }
434
+
435
+ .player-column.first .podium-number {
436
+ font-size: clamp(42px, 6vw, 64px);
437
+ }
438
+ }
439
+
440
+ @media (min-width: 1280px) {
441
+ .podium-container {
442
+ gap: clamp(10px, 1.5vw, 20px);
443
+ }
444
+
445
+ .player-column {
446
+ max-width: clamp(100px, 12vw, 180px);
447
+ }
448
+
449
+ .player-column.first {
450
+ max-width: clamp(110px, 14vw, 200px);
451
+ }
452
+
453
+ .podium-block.first {
454
+ height: 56%;
455
+ min-height: clamp(100px, 14vh, 230px);
456
+ }
457
+
458
+ .podium-block.second {
459
+ height: 40%;
460
+ min-height: clamp(72px, 11vh, 168px);
461
+ }
462
+
463
+ .podium-block.third {
464
+ height: 30%;
465
+ min-height: clamp(52px, 8vh, 128px);
466
+ }
467
+
468
+ .podium-number {
469
+ font-size: clamp(40px, 4.5vw, 60px);
470
+ }
471
+
472
+ .player-column.first .podium-number {
473
+ font-size: clamp(48px, 5.5vw, 72px);
474
+ }
475
+ }
476
+
477
+ @media (max-width: 1023px) {
478
+ .podium-block.first {
479
+ min-height: clamp(64px, 10vh, 120px);
480
+ }
481
+
482
+ .podium-block.second {
483
+ min-height: clamp(48px, 8vh, 95px);
484
+ }
485
+
486
+ .podium-block.third {
487
+ min-height: clamp(36px, 6vh, 75px);
488
+ }
489
+
490
+ .player-column {
491
+ max-width: clamp(72px, 22vw, 120px);
492
+ }
493
+
494
+ .player-column.first {
495
+ max-width: clamp(80px, 24vw, 140px);
496
+ }
497
+ }
498
+
499
+ @media (max-width: 639px) {
500
+ .card {
501
+ border-radius: 18px;
502
+ padding: clamp(8px, 2vw, 12px) clamp(10px, 3vw, 16px);
503
+ }
504
+
505
+ .card-content {
506
+ padding-top: clamp(18px, 8%, 32px);
507
+ }
508
+
509
+ .podium-container {
510
+ gap: clamp(4px, 1.5vw, 8px);
511
+ }
512
+
513
+ .player-column {
514
+ max-width: clamp(64px, 28vw, 100px);
515
+ }
516
+
517
+ .player-column.first {
518
+ max-width: clamp(72px, 30vw, 110px);
519
+ }
520
+
521
+ .podium-block.first {
522
+ height: 48%;
523
+ min-height: clamp(56px, 12dvh, 100px);
524
+ }
525
+
526
+ .podium-block.second {
527
+ height: 34%;
528
+ min-height: clamp(42px, 9dvh, 80px);
529
+ }
530
+
531
+ .podium-block.third {
532
+ height: 24%;
533
+ min-height: clamp(32px, 7dvh, 65px);
534
+ }
535
+
536
+ .podium-number {
537
+ font-size: clamp(22px, 6vw, 36px);
538
+ }
539
+
540
+ .player-column.first .podium-number {
541
+ font-size: clamp(26px, 7vw, 42px);
542
+ }
543
+
544
+ .avatar {
545
+ width: clamp(34px, 9vw, 44px);
546
+ height: clamp(34px, 9vw, 44px);
547
+ font-size: clamp(12px, 3vw, 14px);
548
+ }
549
+
550
+ .player-column.first .avatar {
551
+ width: clamp(38px, 10vw, 48px);
552
+ height: clamp(38px, 10vw, 48px);
553
+ }
554
+
555
+ .player-name {
556
+ font-size: clamp(9px, 2.4vw, 11px);
557
+ }
558
+
559
+ .player-earnings {
560
+ font-size: clamp(10px, 2.6vw, 12px);
561
+ }
562
+ }
563
+
564
+ @media (max-width: 380px) {
565
+ .player-column {
566
+ max-width: 30vw;
567
+ }
568
+
569
+ .player-column.first {
570
+ max-width: 34vw;
571
+ }
572
+
573
+ .sparkle:nth-child(2) {
574
+ left: 18px;
575
+ }
576
+
577
+ .sparkle:nth-child(3) {
578
+ left: 30px;
579
+ }
580
+ }
@@ -0,0 +1,49 @@
1
+ import { LitElement, nothing } from 'lit';
2
+ import { property } from 'lit/decorators.js';
3
+ import { Component } from '../../../../../../lib/lit/component.js';
4
+ import { scss } from '../../../../../../lib/lit/scss.js';
5
+ import { SF_LEADERBOARD_PODIUM } from '../../constants/tags.js';
6
+ import { sfLeaderboardTheme } from '../../../theme/styles.js';
7
+ import type { Player } from '../../types/data.js';
8
+ import { mapPlayersToPodiumEntries } from './map-players.js';
9
+ import type { PodiumEntry } from './podium.types.js';
10
+ import type { PodiumHost } from './podium.host.js';
11
+ import renderTemplate from './podium.html?lit-html';
12
+ import styles from './podium.scss?inline';
13
+
14
+ @Component({ selector: SF_LEADERBOARD_PODIUM })
15
+ export class SfLeaderboardPodium extends LitElement implements PodiumHost {
16
+ static styles = [sfLeaderboardTheme, scss(styles)];
17
+
18
+ @property({ type: Array }) players: Player[] = [];
19
+
20
+ private entryByRank(rank: number): PodiumEntry | undefined {
21
+ return mapPlayersToPodiumEntries(this.players).find((p) => p.rank === rank);
22
+ }
23
+
24
+ get first(): PodiumEntry {
25
+ return this.entryByRank(1)!;
26
+ }
27
+
28
+ get second(): PodiumEntry {
29
+ return this.entryByRank(2)!;
30
+ }
31
+
32
+ get third(): PodiumEntry {
33
+ return this.entryByRank(3)!;
34
+ }
35
+
36
+ get hasPodium(): boolean {
37
+ return (
38
+ this.players.length >= 3 &&
39
+ this.entryByRank(1) !== undefined &&
40
+ this.entryByRank(2) !== undefined &&
41
+ this.entryByRank(3) !== undefined
42
+ );
43
+ }
44
+
45
+ render() {
46
+ if (!this.hasPodium) return nothing;
47
+ return renderTemplate(this);
48
+ }
49
+ }
@@ -0,0 +1,9 @@
1
+ /** View model for one podium column (mapped from leaderboard `Player`). */
2
+ export interface PodiumEntry {
3
+ rank: number;
4
+ initials: string;
5
+ username: string;
6
+ earnings: string;
7
+ winRate: string;
8
+ badgeEmoji: string;
9
+ }
@@ -0,0 +1,11 @@
1
+ export interface RankBadgeHost {
2
+ size: 'sm' | 'md' | 'lg';
3
+ rank: number;
4
+ rankClass: string;
5
+ isTop3: boolean;
6
+ rankIcon: string;
7
+ hasTrend: boolean;
8
+ trendClass: string;
9
+ trendIcon: string;
10
+ trendDiff: string;
11
+ }
@@ -0,0 +1,9 @@
1
+ <div class="badge ${host.size} ${host.rankClass}">
2
+ <span class="icon" ?hidden="${!host.isTop3}">${host.rankIcon}</span>
3
+ <span class="rank-number" ?hidden="${!host.isTop3}">${host.rank}</span>
4
+ <span ?hidden="${host.isTop3}">#${host.rank}</span>
5
+ </div>
6
+ <div class="trend ${host.trendClass}" ?hidden="${!host.hasTrend}">
7
+ <span class="trend-icon">${host.trendIcon}</span>
8
+ <span>${host.trendDiff}</span>
9
+ </div>