@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,351 @@
1
+ :host {
2
+ display: block;
3
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
4
+ }
5
+
6
+ * {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ .container {
11
+ position: relative;
12
+ padding: 32px;
13
+ border-radius: 24px;
14
+ background: linear-gradient(135deg, #0a0a1a 0%, #1a1a3a 50%, #0a0a1a 100%);
15
+ border: 1px solid rgba(99, 102, 241, 0.3);
16
+ overflow: hidden;
17
+ max-width: 600px;
18
+ }
19
+
20
+ /* Animated background glow */
21
+ .container::before {
22
+ content: '';
23
+ position: absolute;
24
+ top: -50%;
25
+ left: -50%;
26
+ width: 200%;
27
+ height: 200%;
28
+ background: radial-gradient(
29
+ circle at 30% 30%,
30
+ rgba(99, 102, 241, 0.15) 0%,
31
+ transparent 50%
32
+ );
33
+ animation: pulse 8s ease-in-out infinite;
34
+ pointer-events: none;
35
+ }
36
+
37
+ .container::after {
38
+ content: '';
39
+ position: absolute;
40
+ bottom: -30%;
41
+ right: -30%;
42
+ width: 60%;
43
+ height: 60%;
44
+ background: radial-gradient(
45
+ circle,
46
+ rgba(236, 72, 153, 0.1) 0%,
47
+ transparent 60%
48
+ );
49
+ animation: pulse 6s ease-in-out infinite reverse;
50
+ pointer-events: none;
51
+ }
52
+
53
+ @keyframes pulse {
54
+ 0%, 100% { opacity: 0.5; transform: scale(1); }
55
+ 50% { opacity: 1; transform: scale(1.1); }
56
+ }
57
+
58
+ .header {
59
+ display: flex;
60
+ justify-content: space-between;
61
+ align-items: flex-start;
62
+ margin-bottom: 24px;
63
+ position: relative;
64
+ z-index: 1;
65
+ }
66
+
67
+ .title-section {
68
+ flex: 1;
69
+ }
70
+
71
+ .title {
72
+ font-size: 1.75rem;
73
+ font-weight: 700;
74
+ margin: 0 0 8px 0;
75
+ background: linear-gradient(135deg, #fff 0%, #a5b4fc 50%, #ec4899 100%);
76
+ -webkit-background-clip: text;
77
+ -webkit-text-fill-color: transparent;
78
+ background-clip: text;
79
+ letter-spacing: -0.02em;
80
+ }
81
+
82
+ .subtitle {
83
+ font-size: 0.9rem;
84
+ color: rgba(255, 255, 255, 0.7);
85
+ margin: 0;
86
+ line-height: 1.5;
87
+ max-width: 320px;
88
+ }
89
+
90
+ .wheel-container {
91
+ position: relative;
92
+ width: 100px;
93
+ height: 100px;
94
+ flex-shrink: 0;
95
+ }
96
+
97
+ .wheel {
98
+ width: 100%;
99
+ height: 100%;
100
+ animation: float 4s ease-in-out infinite;
101
+ }
102
+
103
+ .wheel-ring {
104
+ position: absolute;
105
+ inset: -8px;
106
+ border-radius: 50%;
107
+ border: 2px solid transparent;
108
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.5), rgba(236, 72, 153, 0.5)) border-box;
109
+ -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
110
+ mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
111
+ -webkit-mask-composite: xor;
112
+ mask-composite: exclude;
113
+ animation: spin 20s linear infinite;
114
+ }
115
+
116
+ @keyframes float {
117
+ 0%, 100% { transform: translateY(0); }
118
+ 50% { transform: translateY(-8px); }
119
+ }
120
+
121
+ @keyframes spin {
122
+ from { transform: rotate(0deg); }
123
+ to { transform: rotate(360deg); }
124
+ }
125
+
126
+ .steps-container {
127
+ display: flex;
128
+ gap: 12px;
129
+ position: relative;
130
+ z-index: 1;
131
+ }
132
+
133
+ /* Connection line */
134
+ .steps-container::before {
135
+ content: '';
136
+ position: absolute;
137
+ top: 50%;
138
+ left: 24px;
139
+ right: 24px;
140
+ height: 2px;
141
+ background: linear-gradient(90deg,
142
+ rgba(99, 102, 241, 0.5) 0%,
143
+ rgba(168, 85, 247, 0.5) 50%,
144
+ rgba(236, 72, 153, 0.5) 100%
145
+ );
146
+ transform: translateY(-50%);
147
+ z-index: 0;
148
+ }
149
+
150
+ .step {
151
+ flex: 1;
152
+ display: flex;
153
+ flex-direction: column;
154
+ align-items: center;
155
+ gap: 8px;
156
+ position: relative;
157
+ z-index: 1;
158
+ }
159
+
160
+ .step-box {
161
+ width: 100%;
162
+ aspect-ratio: 1;
163
+ border-radius: 16px;
164
+ display: flex;
165
+ flex-direction: column;
166
+ align-items: center;
167
+ justify-content: center;
168
+ gap: 8px;
169
+ background: rgba(255, 255, 255, 0.03);
170
+ backdrop-filter: blur(12px);
171
+ border: 1px solid rgba(255, 255, 255, 0.1);
172
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
173
+ cursor: pointer;
174
+ position: relative;
175
+ overflow: hidden;
176
+ }
177
+
178
+ .step-box::before {
179
+ content: '';
180
+ position: absolute;
181
+ inset: 0;
182
+ background: linear-gradient(135deg,
183
+ transparent 0%,
184
+ rgba(99, 102, 241, 0.1) 50%,
185
+ transparent 100%
186
+ );
187
+ opacity: 0;
188
+ transition: opacity 0.4s ease;
189
+ }
190
+
191
+ .step-box:hover::before {
192
+ opacity: 1;
193
+ }
194
+
195
+ .step-box:hover {
196
+ transform: translateY(-4px);
197
+ border-color: rgba(99, 102, 241, 0.4);
198
+ box-shadow:
199
+ 0 8px 32px rgba(99, 102, 241, 0.2),
200
+ 0 0 0 1px rgba(99, 102, 241, 0.2);
201
+ }
202
+
203
+ .step-box.completed {
204
+ background: linear-gradient(135deg,
205
+ rgba(34, 197, 94, 0.15) 0%,
206
+ rgba(34, 197, 94, 0.05) 100%
207
+ );
208
+ border-color: rgba(34, 197, 94, 0.4);
209
+ }
210
+
211
+ .step-box.completed:hover {
212
+ border-color: rgba(34, 197, 94, 0.6);
213
+ box-shadow:
214
+ 0 8px 32px rgba(34, 197, 94, 0.2),
215
+ 0 0 0 1px rgba(34, 197, 94, 0.3);
216
+ }
217
+
218
+ .step-box.reward {
219
+ background: linear-gradient(135deg,
220
+ rgba(236, 72, 153, 0.15) 0%,
221
+ rgba(168, 85, 247, 0.15) 100%
222
+ );
223
+ border-color: rgba(236, 72, 153, 0.4);
224
+ animation: glow 2s ease-in-out infinite;
225
+ }
226
+
227
+ .step-box.reward:hover {
228
+ border-color: rgba(236, 72, 153, 0.6);
229
+ box-shadow:
230
+ 0 8px 32px rgba(236, 72, 153, 0.3),
231
+ 0 0 0 1px rgba(236, 72, 153, 0.3);
232
+ }
233
+
234
+ @keyframes glow {
235
+ 0%, 100% {
236
+ box-shadow:
237
+ 0 0 20px rgba(236, 72, 153, 0.2),
238
+ inset 0 0 20px rgba(236, 72, 153, 0.05);
239
+ }
240
+ 50% {
241
+ box-shadow:
242
+ 0 0 30px rgba(236, 72, 153, 0.4),
243
+ inset 0 0 30px rgba(236, 72, 153, 0.1);
244
+ }
245
+ }
246
+
247
+ .step-label {
248
+ font-size: 0.85rem;
249
+ font-weight: 600;
250
+ color: rgba(255, 255, 255, 0.9);
251
+ letter-spacing: 0.05em;
252
+ }
253
+
254
+ .step-icon {
255
+ width: 28px;
256
+ height: 28px;
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ }
261
+
262
+ .check-icon {
263
+ width: 100%;
264
+ height: 100%;
265
+ color: #22c55e;
266
+ animation: checkIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
267
+ }
268
+
269
+ @keyframes checkIn {
270
+ 0% {
271
+ transform: scale(0) rotate(-45deg);
272
+ opacity: 0;
273
+ }
274
+ 50% { transform: scale(1.2) rotate(0deg); }
275
+ 100% { transform: scale(1) rotate(0deg); opacity: 1; }
276
+ }
277
+
278
+ .gift-icon {
279
+ width: 100%;
280
+ height: 100%;
281
+ color: #ec4899;
282
+ animation: bounce 1s ease-in-out infinite;
283
+ }
284
+
285
+ @keyframes bounce {
286
+ 0%, 100% { transform: translateY(0); }
287
+ 50% { transform: translateY(-4px); }
288
+ }
289
+
290
+ .pending-icon {
291
+ width: 12px;
292
+ height: 12px;
293
+ border-radius: 50%;
294
+ background: rgba(255, 255, 255, 0.2);
295
+ border: 2px solid rgba(255, 255, 255, 0.3);
296
+ }
297
+
298
+ .progress-bar {
299
+ margin-top: 20px;
300
+ position: relative;
301
+ z-index: 1;
302
+ }
303
+
304
+ .progress-track {
305
+ height: 6px;
306
+ background: rgba(255, 255, 255, 0.1);
307
+ border-radius: 3px;
308
+ overflow: hidden;
309
+ }
310
+
311
+ .progress-fill {
312
+ height: 100%;
313
+ background: linear-gradient(90deg, #22c55e 0%, #10b981 100%);
314
+ border-radius: 3px;
315
+ transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
316
+ position: relative;
317
+ }
318
+
319
+ .progress-fill::after {
320
+ content: '';
321
+ position: absolute;
322
+ top: 0;
323
+ left: 0;
324
+ right: 0;
325
+ bottom: 0;
326
+ background: linear-gradient(
327
+ 90deg,
328
+ transparent 0%,
329
+ rgba(255, 255, 255, 0.3) 50%,
330
+ transparent 100%
331
+ );
332
+ animation: shimmer 2s infinite;
333
+ }
334
+
335
+ @keyframes shimmer {
336
+ 0% { transform: translateX(-100%); }
337
+ 100% { transform: translateX(100%); }
338
+ }
339
+
340
+ .progress-text {
341
+ display: flex;
342
+ justify-content: space-between;
343
+ margin-top: 8px;
344
+ font-size: 0.75rem;
345
+ color: rgba(255, 255, 255, 0.5);
346
+ }
347
+
348
+ .progress-percentage {
349
+ color: #22c55e;
350
+ font-weight: 600;
351
+ }
@@ -0,0 +1,235 @@
1
+ import { LitElement, html, PropertyValues } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import { scss } from '../../lib/lit/scss.js';
5
+ import {
6
+ SF_STREAKS_TAG,
7
+ DEFAULT_STREAKS_TITLE,
8
+ DEFAULT_STREAKS_SUBTITLE,
9
+ DEFAULT_STREAKS_STEPS,
10
+ } from './constants.js';
11
+ import { STREAKS_STEP_CLICK } from './events.js';
12
+ import type { StreaksStep } from './types.js';
13
+ import renderTemplate from './sf-streaks.html?lit-html';
14
+ import styles from './sf-streaks.scss?inline';
15
+
16
+ @customElement(SF_STREAKS_TAG)
17
+ export class SfStreaks extends LitElement {
18
+ static override styles = scss(styles);
19
+
20
+ static override properties = {
21
+ streaksTitle: { type: String, attribute: 'streak-title' },
22
+ streaksSubtitle: { type: String, attribute: 'streak-subtitle' },
23
+ streaksSteps: { type: Array, attribute: false },
24
+ _animatedProgress: { type: Number, state: true },
25
+ };
26
+
27
+ private _streaksTitle = DEFAULT_STREAKS_TITLE;
28
+ private _streaksSubtitle = DEFAULT_STREAKS_SUBTITLE;
29
+ private _streaksSteps: StreaksStep[] = [...DEFAULT_STREAKS_STEPS];
30
+ private _progress = 0;
31
+
32
+ get streaksTitle() {
33
+ return this._streaksTitle;
34
+ }
35
+ set streaksTitle(value: string) {
36
+ const oldVal = this._streaksTitle;
37
+ this._streaksTitle = value;
38
+ this.requestUpdate('streaksTitle', oldVal);
39
+ }
40
+
41
+ get streaksSubtitle() {
42
+ return this._streaksSubtitle;
43
+ }
44
+ set streaksSubtitle(value: string) {
45
+ const oldVal = this._streaksSubtitle;
46
+ this._streaksSubtitle = value;
47
+ this.requestUpdate('streaksSubtitle', oldVal);
48
+ }
49
+
50
+ get streaksSteps() {
51
+ return this._streaksSteps;
52
+ }
53
+ set streaksSteps(value: StreaksStep[]) {
54
+ const oldVal = this._streaksSteps;
55
+ this._streaksSteps = value;
56
+ this.requestUpdate('streaksSteps', oldVal);
57
+ }
58
+
59
+ get _animatedProgress() {
60
+ return this._progress;
61
+ }
62
+ set _animatedProgress(value: number) {
63
+ const oldVal = this._progress;
64
+ this._progress = value;
65
+ this.requestUpdate('_animatedProgress', oldVal);
66
+ }
67
+
68
+ override firstUpdated() {
69
+ requestAnimationFrame(() => {
70
+ this._animatedProgress = this.progress;
71
+ });
72
+ }
73
+
74
+ override updated(changedProperties: PropertyValues) {
75
+ if (changedProperties.has('streaksSteps')) {
76
+ this._animatedProgress = this.progress;
77
+ }
78
+ }
79
+
80
+ get completedCount() {
81
+ return this._streaksSteps.filter((s) => s.completed).length;
82
+ }
83
+
84
+ get progress() {
85
+ return (this.completedCount / this._streaksSteps.length) * 100;
86
+ }
87
+
88
+ private _handleStepClick(step: StreaksStep) {
89
+ this.dispatchEvent(
90
+ new CustomEvent(STREAKS_STEP_CLICK, {
91
+ detail: { step },
92
+ bubbles: true,
93
+ composed: true,
94
+ }),
95
+ );
96
+ }
97
+
98
+ private _renderCheckIcon() {
99
+ return html`
100
+ <svg
101
+ class="check-icon"
102
+ viewBox="0 0 24 24"
103
+ fill="none"
104
+ stroke="currentColor"
105
+ stroke-width="3"
106
+ stroke-linecap="round"
107
+ stroke-linejoin="round"
108
+ >
109
+ <polyline points="20 6 9 17 4 12"></polyline>
110
+ </svg>
111
+ `;
112
+ }
113
+
114
+ private _renderGiftIcon() {
115
+ return html`
116
+ <svg
117
+ class="gift-icon"
118
+ viewBox="0 0 24 24"
119
+ fill="none"
120
+ stroke="currentColor"
121
+ stroke-width="2"
122
+ stroke-linecap="round"
123
+ stroke-linejoin="round"
124
+ >
125
+ <polyline points="20 12 20 22 4 22 4 12"></polyline>
126
+ <rect x="2" y="7" width="20" height="5"></rect>
127
+ <line x1="12" y1="22" x2="12" y2="7"></line>
128
+ <path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
129
+ <path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
130
+ </svg>
131
+ `;
132
+ }
133
+
134
+ _renderSteps() {
135
+ return this._streaksSteps.map(
136
+ (step) => html`
137
+ <div class="step">
138
+ <div
139
+ class=${classMap({
140
+ 'step-box': true,
141
+ completed: step.completed,
142
+ reward: !!step.isReward && !step.completed,
143
+ })}
144
+ @click=${() => this._handleStepClick(step)}
145
+ >
146
+ <span class="step-label">${step.label}</span>
147
+ <div class="step-icon">
148
+ ${step.completed
149
+ ? this._renderCheckIcon()
150
+ : step.isReward
151
+ ? this._renderGiftIcon()
152
+ : html`<div class="pending-icon"></div>`}
153
+ </div>
154
+ </div>
155
+ </div>
156
+ `,
157
+ );
158
+ }
159
+
160
+ _renderWheel() {
161
+ return html`
162
+ <svg class="wheel" viewBox="0 0 100 100" fill="none">
163
+ <defs>
164
+ <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
165
+ <feGaussianBlur stdDeviation="2" result="coloredBlur" />
166
+ <feMerge>
167
+ <feMergeNode in="coloredBlur" />
168
+ <feMergeNode in="SourceGraphic" />
169
+ </feMerge>
170
+ </filter>
171
+ <linearGradient id="segment1" x1="0%" y1="0%" x2="100%" y2="100%">
172
+ <stop offset="0%" style="stop-color:#ef4444" />
173
+ <stop offset="100%" style="stop-color:#dc2626" />
174
+ </linearGradient>
175
+ <linearGradient id="segment2" x1="0%" y1="0%" x2="100%" y2="100%">
176
+ <stop offset="0%" style="stop-color:#f97316" />
177
+ <stop offset="100%" style="stop-color:#ea580c" />
178
+ </linearGradient>
179
+ <linearGradient id="segment3" x1="0%" y1="0%" x2="100%" y2="100%">
180
+ <stop offset="0%" style="stop-color:#eab308" />
181
+ <stop offset="100%" style="stop-color:#ca8a04" />
182
+ </linearGradient>
183
+ <linearGradient id="segment4" x1="0%" y1="0%" x2="100%" y2="100%">
184
+ <stop offset="0%" style="stop-color:#22c55e" />
185
+ <stop offset="100%" style="stop-color:#16a34a" />
186
+ </linearGradient>
187
+ <linearGradient id="segment5" x1="0%" y1="0%" x2="100%" y2="100%">
188
+ <stop offset="0%" style="stop-color:#3b82f6" />
189
+ <stop offset="100%" style="stop-color:#2563eb" />
190
+ </linearGradient>
191
+ <linearGradient id="segment6" x1="0%" y1="0%" x2="100%" y2="100%">
192
+ <stop offset="0%" style="stop-color:#a855f7" />
193
+ <stop offset="100%" style="stop-color:#9333ea" />
194
+ </linearGradient>
195
+ </defs>
196
+ <g filter="url(#glow)">
197
+ <path d="M50 50 L50 10 A40 40 0 0 1 84.64 30 Z" fill="url(#segment1)" />
198
+ <path d="M50 50 L84.64 30 A40 40 0 0 1 84.64 70 Z" fill="url(#segment2)" />
199
+ <path d="M50 50 L84.64 70 A40 40 0 0 1 50 90 Z" fill="url(#segment3)" />
200
+ <path d="M50 50 L50 90 A40 40 0 0 1 15.36 70 Z" fill="url(#segment4)" />
201
+ <path d="M50 50 L15.36 70 A40 40 0 0 1 15.36 30 Z" fill="url(#segment5)" />
202
+ <path d="M50 50 L15.36 30 A40 40 0 0 1 50 10 Z" fill="url(#segment6)" />
203
+ </g>
204
+ <circle
205
+ cx="50"
206
+ cy="50"
207
+ r="12"
208
+ fill="#1a1a3a"
209
+ stroke="rgba(255,255,255,0.2)"
210
+ stroke-width="2"
211
+ />
212
+ <circle cx="50" cy="50" r="6" fill="#6366f1" />
213
+ <polygon points="50,2 46,12 54,12" fill="#fff" filter="url(#glow)" />
214
+ <circle
215
+ cx="50"
216
+ cy="50"
217
+ r="44"
218
+ fill="none"
219
+ stroke="rgba(255,255,255,0.1)"
220
+ stroke-width="2"
221
+ />
222
+ </svg>
223
+ `;
224
+ }
225
+
226
+ override render() {
227
+ return renderTemplate(this);
228
+ }
229
+ }
230
+
231
+ declare global {
232
+ interface HTMLElementTagNameMap {
233
+ 'sf-streaks': SfStreaks;
234
+ }
235
+ }
@@ -0,0 +1,7 @@
1
+ /** A single step in the streaks progress track. */
2
+ export interface StreaksStep {
3
+ id: string;
4
+ label: string;
5
+ completed: boolean;
6
+ isReward?: boolean;
7
+ }
@@ -0,0 +1,10 @@
1
+ import { customElement } from './safe-custom-element.js';
2
+
3
+ export interface ComponentOptions {
4
+ selector: string;
5
+ }
6
+
7
+ /** Angular-style `@Component` — registers a Lit class as a custom element. */
8
+ export function Component(options: ComponentOptions) {
9
+ return customElement(options.selector);
10
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Registers a custom element only if the tag is not already in the registry.
3
+ * Use instead of Lit's `@customElement` when HMR or duplicate bundles are possible.
4
+ */
5
+ export function customElement(tag: string) {
6
+ return <T extends CustomElementConstructor>(cls: T): T => {
7
+ if (!customElements.get(tag)) {
8
+ customElements.define(tag, cls);
9
+ }
10
+ return cls;
11
+ };
12
+ }
@@ -0,0 +1,6 @@
1
+ import { unsafeCSS, type CSSResult } from 'lit';
2
+
3
+ /** Compiled SCSS string → adoptable shadow-root stylesheet. */
4
+ export function scss(stylesheet: string): CSSResult {
5
+ return unsafeCSS(stylesheet);
6
+ }
@@ -0,0 +1,18 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.scss?inline' {
4
+ const css: string;
5
+ export default css;
6
+ }
7
+
8
+ declare module '*.theme.json' {
9
+ import type { SfLeaderboardTheme } from './theme/types.js';
10
+ const theme: SfLeaderboardTheme;
11
+ export default theme;
12
+ }
13
+
14
+ declare module '*.html?lit-html' {
15
+ import type { TemplateResult } from 'lit';
16
+
17
+ export default function render(host: unknown): TemplateResult;
18
+ }