@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
package/main.css ADDED
@@ -0,0 +1,210 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ font-family: system-ui, sans-serif;
8
+ background: #0f1117;
9
+ color: #e8eaed;
10
+ min-height: 100vh;
11
+ }
12
+
13
+ .core-dev {
14
+ display: flex;
15
+ min-height: 100vh;
16
+ }
17
+
18
+ .core-dev__sidebar {
19
+ width: 14rem;
20
+ flex-shrink: 0;
21
+ padding: 1rem;
22
+ background: #1a1d26;
23
+ border-right: 1px solid #2a2f3a;
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: 1rem;
27
+ }
28
+
29
+ .core-dev__brand strong {
30
+ display: block;
31
+ font-size: 0.95rem;
32
+ }
33
+
34
+ .core-dev__brand span {
35
+ font-size: 0.75rem;
36
+ color: #9aa0a6;
37
+ }
38
+
39
+ .core-dev__nav {
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: 0.25rem;
43
+ }
44
+
45
+ .core-dev__nav-link {
46
+ display: block;
47
+ padding: 0.5rem 0.65rem;
48
+ border-radius: 6px;
49
+ color: #c4c7cc;
50
+ text-decoration: none;
51
+ font-size: 0.875rem;
52
+ }
53
+
54
+ .core-dev__nav-link:hover {
55
+ background: #252830;
56
+ color: #fff;
57
+ }
58
+
59
+ .core-dev__nav-link.is-active {
60
+ background: #2d3340;
61
+ color: #fff;
62
+ }
63
+
64
+ .core-dev__main {
65
+ flex: 1;
66
+ display: flex;
67
+ flex-direction: column;
68
+ min-width: 0;
69
+ }
70
+
71
+ .core-dev__header {
72
+ padding: 1rem 1.25rem;
73
+ border-bottom: 1px solid #2a2f3a;
74
+ }
75
+
76
+ .core-dev__header h1 {
77
+ margin: 0 0 0.35rem;
78
+ font-size: 1.25rem;
79
+ }
80
+
81
+ .core-dev__header p {
82
+ margin: 0;
83
+ font-size: 0.875rem;
84
+ color: #9aa0a6;
85
+ }
86
+
87
+ .core-dev__stage {
88
+ flex: 1;
89
+ padding: 1.5rem;
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: flex-start;
93
+ }
94
+
95
+ .core-dev__stage:has(> sf-leaderboard:only-child),
96
+ .core-dev__stage:has(.sf-leaderboard-dev > sf-leaderboard) {
97
+ padding: 0;
98
+ display: block;
99
+ }
100
+
101
+ .core-dev__stage > sf-leaderboard,
102
+ .core-dev__stage .sf-leaderboard-dev > sf-leaderboard {
103
+ width: 100%;
104
+ display: block;
105
+ }
106
+
107
+ .core-dev__stage:empty::after {
108
+ content: 'Select a component';
109
+ color: #6b7280;
110
+ font-size: 0.875rem;
111
+ }
112
+
113
+ .core-dev__error {
114
+ color: #f87171;
115
+ font-size: 0.875rem;
116
+ }
117
+
118
+ /* SF Leaderboard preset bar (dev preview) */
119
+ .sf-leaderboard-dev {
120
+ display: flex;
121
+ flex-direction: column;
122
+ width: 100%;
123
+ }
124
+
125
+ .playground-preset-bar {
126
+ display: flex;
127
+ flex-wrap: wrap;
128
+ gap: 1rem 1.5rem;
129
+ padding: 0.75rem 1rem;
130
+ background: #1a1d26;
131
+ border-bottom: 1px solid #2a2f3a;
132
+ align-items: flex-start;
133
+ }
134
+
135
+ .playground-preset-bar__group {
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: 0.35rem;
139
+ }
140
+
141
+ .playground-preset-bar__label {
142
+ font-size: 0.75rem;
143
+ color: #9aa0a6;
144
+ }
145
+
146
+ .playground-preset-bar__select {
147
+ font: inherit;
148
+ padding: 0.4rem 0.6rem;
149
+ border-radius: 6px;
150
+ border: 1px solid #3c4048;
151
+ background: #0f1117;
152
+ color: inherit;
153
+ min-width: 12rem;
154
+ }
155
+
156
+ .playground-preset-bar__hint {
157
+ margin: 0;
158
+ font-size: 0.75rem;
159
+ color: #6b7280;
160
+ max-width: 20rem;
161
+ }
162
+
163
+ .playground-preset-bar__meta {
164
+ flex-direction: row;
165
+ flex-wrap: wrap;
166
+ align-items: center;
167
+ gap: 0.5rem;
168
+ }
169
+
170
+ .playground-preset-bar__badge {
171
+ font-size: 0.75rem;
172
+ padding: 0.25rem 0.5rem;
173
+ border-radius: 4px;
174
+ background: #2d3340;
175
+ }
176
+
177
+ .playground-preset-bar__badge--muted {
178
+ background: #252830;
179
+ color: #9aa0a6;
180
+ }
181
+
182
+ .playground-preset-bar__labels {
183
+ border: 1px solid #2a2f3a;
184
+ border-radius: 6px;
185
+ padding: 0.5rem 0.75rem;
186
+ margin: 0;
187
+ }
188
+
189
+ .playground-preset-bar__labels legend {
190
+ font-size: 0.75rem;
191
+ color: #9aa0a6;
192
+ padding: 0 0.25rem;
193
+ }
194
+
195
+ .playground-preset-bar__labels label {
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 0.2rem;
199
+ font-size: 0.75rem;
200
+ margin-top: 0.35rem;
201
+ }
202
+
203
+ .playground-preset-bar__labels input {
204
+ font: inherit;
205
+ padding: 0.3rem 0.5rem;
206
+ border-radius: 4px;
207
+ border: 1px solid #3c4048;
208
+ background: #0f1117;
209
+ color: inherit;
210
+ }
package/main.ts ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Core dev shell — runs before any component module loads.
3
+ * Start: npm run dev → http://localhost:5173/?component=sf-leaderboard
4
+ */
5
+ import {
6
+ COMPONENT_CATALOG,
7
+ getCatalogEntry,
8
+ navigateToComponent,
9
+ persistComponentId,
10
+ readInitialComponentId,
11
+ switchingRequiresReload,
12
+ } from './previews/catalog.js';
13
+
14
+ const app = document.getElementById('app');
15
+ if (!app) {
16
+ throw new Error('#app not found — index.html must define the mount point before main.ts');
17
+ }
18
+
19
+ let activeId = readInitialComponentId();
20
+ let teardown: (() => void) | undefined;
21
+
22
+ const shell = document.createElement('div');
23
+ shell.className = 'core-dev';
24
+
25
+ const sidebar = document.createElement('aside');
26
+ sidebar.className = 'core-dev__sidebar';
27
+
28
+ const brand = document.createElement('div');
29
+ brand.className = 'core-dev__brand';
30
+ brand.innerHTML = '<strong>Oddsmith</strong><span>Core dev</span>';
31
+
32
+ const nav = document.createElement('nav');
33
+ nav.className = 'core-dev__nav';
34
+ nav.setAttribute('aria-label', 'Components');
35
+
36
+ const main = document.createElement('main');
37
+ main.className = 'core-dev__main';
38
+
39
+ const header = document.createElement('header');
40
+ header.className = 'core-dev__header';
41
+
42
+ const titleEl = document.createElement('h1');
43
+ const descEl = document.createElement('p');
44
+ header.append(titleEl, descEl);
45
+
46
+ const stage = document.createElement('div');
47
+ stage.className = 'core-dev__stage';
48
+ main.append(header, stage);
49
+
50
+ shell.append(sidebar, main);
51
+ sidebar.append(brand, nav);
52
+ app.appendChild(shell);
53
+
54
+ function renderNav(): void {
55
+ nav.replaceChildren();
56
+ for (const entry of COMPONENT_CATALOG) {
57
+ if (entry.id === 'none') continue;
58
+ const link = document.createElement('a');
59
+ link.className = 'core-dev__nav-link';
60
+ link.href = `?component=${entry.id}`;
61
+ link.textContent = entry.title;
62
+ link.dataset.component = entry.id;
63
+ if (entry.id === activeId) link.classList.add('is-active');
64
+ nav.appendChild(link);
65
+ }
66
+ }
67
+
68
+ function updateHeader(): void {
69
+ const entry = getCatalogEntry(activeId);
70
+ titleEl.textContent = entry?.title ?? 'Preview';
71
+ descEl.textContent = entry?.description ?? '';
72
+ }
73
+
74
+ async function showComponent(id: string): Promise<void> {
75
+ teardown?.();
76
+ teardown = undefined;
77
+ stage.replaceChildren();
78
+
79
+ const entry = getCatalogEntry(id);
80
+ if (!entry || entry.id === 'none' || !entry.tag) {
81
+ updateHeader();
82
+ return;
83
+ }
84
+
85
+ try {
86
+ const mod = await entry.load();
87
+ teardown = await mod.mount(stage);
88
+ } catch (err) {
89
+ console.error(err);
90
+ const msg = document.createElement('p');
91
+ msg.className = 'core-dev__error';
92
+ msg.textContent = `Failed to load "${id}": ${err instanceof Error ? err.message : String(err)}`;
93
+ stage.appendChild(msg);
94
+ }
95
+
96
+ updateHeader();
97
+ }
98
+
99
+ nav.addEventListener('click', (e) => {
100
+ const link = (e.target as HTMLElement).closest<HTMLAnchorElement>(
101
+ 'a[data-component]',
102
+ );
103
+ if (!link) return;
104
+ e.preventDefault();
105
+ const nextId = link.dataset.component;
106
+ if (!nextId || nextId === activeId) return;
107
+
108
+ if (switchingRequiresReload(activeId, nextId)) {
109
+ navigateToComponent(nextId);
110
+ return;
111
+ }
112
+
113
+ activeId = nextId;
114
+ persistComponentId(nextId);
115
+ const url = new URL(location.href);
116
+ url.searchParams.set('component', nextId);
117
+ history.replaceState(null, '', url);
118
+ renderNav();
119
+ void showComponent(activeId);
120
+ });
121
+
122
+ renderNav();
123
+ updateHeader();
124
+ void showComponent(activeId);
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@oddsmith/ui",
3
+ "version": "0.0.0",
4
+ "description": "Brandable web components",
5
+ "keywords": [
6
+ "web-components",
7
+ "lit-element",
8
+ "typescript"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git@github-skinforge:skinforge-io/core.git"
13
+ },
14
+ "license": "BSD-3-Clause",
15
+ "author": "Oddsmith",
16
+ "type": "module",
17
+ "exports": {
18
+ "./leaderboard": "./dist/leaderboard/main.js",
19
+ "./leaderboard-v2": "./dist/leaderboard-v2/index.js",
20
+ "./sf-leaderboard": "./dist/sf-leaderboard/index.js",
21
+ "./casino-leaderboard": "./dist/casino-leaderboard/component.js",
22
+ "./streaks": "./dist/streaks/index.js",
23
+ "./sf-live-feed": "./dist/sf-live-feed/index.js"
24
+ },
25
+ "main": "./dist/sf-leaderboard/index.js",
26
+ "directories": {
27
+ "doc": "docs",
28
+ "test": "test"
29
+ },
30
+ "scripts": {
31
+ "build": "vite build",
32
+ "build:watch": "vite build --watch",
33
+ "typecheck": "tsc --noEmit",
34
+ "clean": "rm -rf node_modules package-lock.json",
35
+ "lint": "npm run lint:lit-analyzer && npm run lint:eslint",
36
+ "lint:eslint": "eslint 'src/**/*.ts'",
37
+ "lint:lit-analyzer": "lit-analyzer",
38
+ "format": "prettier src/* --write",
39
+ "docs": "npm run docs:clean && npm run build && npm run analyze && npm run docs:bundle && npm run docs:gen && npm run docs:assets",
40
+ "docs:clean": "rimraf docs",
41
+ "docs:bundle": "vite build --config vite.docs.config.ts",
42
+ "docs:gen": "eleventy --config=.eleventy.cjs",
43
+ "docs:gen:watch": "eleventy --config=.eleventy.cjs --watch",
44
+ "docs:assets": "cp node_modules/prismjs/themes/prism-okaidia.css docs/",
45
+ "docs:serve": "npx --yes serve docs",
46
+ "analyze": "wca analyze \"src/**/*.ts\" --outFile custom-elements.json",
47
+ "dev": "vite",
48
+ "serve": "vite",
49
+ "test": "karma start karma.conf.cjs",
50
+ "test:watch": "karma start karma.conf.cjs --auto-watch=true --single-run=false",
51
+ "test:update-snapshots": "karma start karma.conf.cjs --update-snapshots",
52
+ "test:prune-snapshots": "karma start karma.conf.cjs --prune-snapshots"
53
+ },
54
+ "dependencies": {
55
+ "immer": "^11.1.8",
56
+ "lit": "^3.3.3",
57
+ "lit-element": "^2.3.1",
58
+ "rxjs": "~7.8.0"
59
+ },
60
+ "devDependencies": {
61
+ "@11ty/eleventy": "^0.10.0",
62
+ "@11ty/eleventy-plugin-syntaxhighlight": "^3.0.1",
63
+ "@open-wc/testing": "^2.5.10",
64
+ "@open-wc/testing-karma": "^3.3.11",
65
+ "@types/chai": "^4.2.11",
66
+ "@types/mocha": "^7.0.2",
67
+ "@typescript-eslint/eslint-plugin": "^2.27.0",
68
+ "@typescript-eslint/parser": "^2.27.0",
69
+ "chai": "^4.2.0",
70
+ "deepmerge": "^4.2.2",
71
+ "eslint": "^6.8.0",
72
+ "karma": "^4.4.1",
73
+ "karma-chai": "^0.1.0",
74
+ "karma-mocha": "^1.3.0",
75
+ "lit-analyzer": "^1.1.10",
76
+ "mocha": "^7.1.1",
77
+ "prettier": "^2.0.4",
78
+ "prismjs": "^1.30.0",
79
+ "rimraf": "^3.0.2",
80
+ "sass": "^1.97.3",
81
+ "typescript": "^6.0.3",
82
+ "vite": "^7.3.1",
83
+ "web-component-analyzer": "^1.0.3"
84
+ },
85
+ "module": "./dist/sf-leaderboard/index.js"
86
+ }
@@ -0,0 +1,12 @@
1
+ import type { ComponentPreviewModule } from './types.js';
2
+
3
+ export const mount: ComponentPreviewModule['mount'] = async (root) => {
4
+ await import('../src/components/casino-leaderboard/index.ts');
5
+
6
+ const el = document.createElement('casino-leaderboard');
7
+ root.appendChild(el);
8
+
9
+ return () => {
10
+ root.replaceChildren();
11
+ };
12
+ };
@@ -0,0 +1,94 @@
1
+ import type { ComponentCatalogEntry } from './types.js';
2
+
3
+ export const COMPONENT_CATALOG: readonly ComponentCatalogEntry[] = [
4
+ {
5
+ id: 'none',
6
+ title: '— None —',
7
+ description: 'Empty preview stage.',
8
+ tag: null,
9
+ load: async () => ({
10
+ mount: async () => () => {},
11
+ }),
12
+ },
13
+ {
14
+ id: 'sf-leaderboard',
15
+ title: 'SF Leaderboard',
16
+ description:
17
+ 'Config-driven leaderboard with presets, theme, and interactive demo data.',
18
+ tag: 'sf-leaderboard',
19
+ sharedTag: 'sf-leaderboard',
20
+ load: () => import('./sf-leaderboard.js'),
21
+ },
22
+ {
23
+ id: 'sf-live-feed',
24
+ title: 'Live Feed',
25
+ description: 'Activity stream with avatars and event types.',
26
+ tag: 'sf-live-feed',
27
+ load: () => import('./sf-live-feed.js'),
28
+ },
29
+ {
30
+ id: 'streaks',
31
+ title: 'Streaks',
32
+ description: 'Step tracker with click-to-complete interaction.',
33
+ tag: 'sf-streaks',
34
+ load: () => import('./streaks.js'),
35
+ },
36
+ {
37
+ id: 'leaderboard-v2',
38
+ title: 'Leaderboard v2 (legacy)',
39
+ description: 'Earlier leaderboard — same tag as SF Leaderboard; switch via reload.',
40
+ tag: 'sf-leaderboard',
41
+ sharedTag: 'sf-leaderboard',
42
+ load: () => import('./leaderboard-v2.js'),
43
+ },
44
+ {
45
+ id: 'leaderboard',
46
+ title: 'Leaderboard (v1)',
47
+ description: 'Original game leaderboard shell.',
48
+ tag: 'leaderboard-app',
49
+ load: () => import('./leaderboard-v1.js'),
50
+ },
51
+ {
52
+ id: 'casino',
53
+ title: 'Casino Leaderboard',
54
+ description: 'Casino-style leaderboard with tabs and prize pool.',
55
+ tag: 'casino-leaderboard',
56
+ load: () => import('./casino.js'),
57
+ },
58
+ ];
59
+
60
+ const STORAGE_KEY = 'oddsmith-core-dev-component';
61
+
62
+ export function getCatalogEntry(id: string): ComponentCatalogEntry | undefined {
63
+ return COMPONENT_CATALOG.find((e) => e.id === id);
64
+ }
65
+
66
+ export function readInitialComponentId(): string {
67
+ const fromQuery = new URLSearchParams(location.search).get('component');
68
+ if (fromQuery && getCatalogEntry(fromQuery)) return fromQuery;
69
+ const fromStorage = localStorage.getItem(STORAGE_KEY);
70
+ if (fromStorage && getCatalogEntry(fromStorage)) return fromStorage;
71
+ return 'sf-leaderboard';
72
+ }
73
+
74
+ export function persistComponentId(id: string): void {
75
+ localStorage.setItem(STORAGE_KEY, id);
76
+ }
77
+
78
+ export function navigateToComponent(id: string): void {
79
+ persistComponentId(id);
80
+ const url = new URL(location.href);
81
+ if (id === 'none') url.searchParams.delete('component');
82
+ else url.searchParams.set('component', id);
83
+ location.assign(url);
84
+ }
85
+
86
+ /** Tags that cannot coexist without reload (duplicate custom element names). */
87
+ export function switchingRequiresReload(fromId: string, toId: string): boolean {
88
+ if (fromId === toId) return false;
89
+ const from = getCatalogEntry(fromId);
90
+ const to = getCatalogEntry(toId);
91
+ const fromTag = from?.sharedTag ?? from?.tag;
92
+ const toTag = to?.sharedTag ?? to?.tag;
93
+ return Boolean(fromTag && toTag && fromTag === toTag);
94
+ }
@@ -0,0 +1,12 @@
1
+ import type { ComponentPreviewModule } from './types.js';
2
+
3
+ export const mount: ComponentPreviewModule['mount'] = async (root) => {
4
+ await import('../src/components/leaderboard/index.ts');
5
+
6
+ const el = document.createElement('leaderboard-app');
7
+ root.appendChild(el);
8
+
9
+ return () => {
10
+ root.replaceChildren();
11
+ };
12
+ };
@@ -0,0 +1,17 @@
1
+ import type { SfLeaderboard } from '../src/components/leaderboard-v2/components/sf-leaderboard.js';
2
+ import { lbV2CurrentUser, lbV2Players, lbV2Stats } from './sample-data.js';
3
+ import type { ComponentPreviewModule } from './types.js';
4
+
5
+ export const mount: ComponentPreviewModule['mount'] = async (root) => {
6
+ await import('../src/components/leaderboard-v2/index.ts');
7
+
8
+ const el = document.createElement('sf-leaderboard') as SfLeaderboard;
9
+ el.players = lbV2Players;
10
+ el.stats = lbV2Stats;
11
+ el.currentUser = lbV2CurrentUser;
12
+ root.appendChild(el);
13
+
14
+ return () => {
15
+ root.replaceChildren();
16
+ };
17
+ };
@@ -0,0 +1,101 @@
1
+ import type { LiveFeedActivity } from '../src/components/sf-live-feed/types.js';
2
+ import type { StreaksStep } from '../src/components/streaks/types.js';
3
+ import type {
4
+ LbBadge,
5
+ LbPlayer,
6
+ LbStats,
7
+ } from '../src/components/leaderboard-v2/types.js';
8
+
9
+ export const mockLiveFeedActivities: LiveFeedActivity[] = [
10
+ {
11
+ id: '1',
12
+ type: 'jackpot',
13
+ player: { username: 'CryptoKing', avatar: '' },
14
+ message: 'Hit the MEGA JACKPOT on Lightning Slots!',
15
+ amount: 125000,
16
+ timestamp: new Date(Date.now() - 30000),
17
+ },
18
+ {
19
+ id: '2',
20
+ type: 'win',
21
+ player: { username: 'AceHunter', avatar: '' },
22
+ message: "Won a massive pot in Texas Hold'em",
23
+ amount: 45000,
24
+ timestamp: new Date(Date.now() - 120000),
25
+ },
26
+ {
27
+ id: '3',
28
+ type: 'streak',
29
+ player: { username: 'LuckyDragon', avatar: '' },
30
+ message: 'Reached a 8-game winning streak!',
31
+ timestamp: new Date(Date.now() - 300000),
32
+ },
33
+ ];
34
+
35
+ export const demoStreaksSteps: StreaksStep[] = [
36
+ { id: '1', label: 'G1', completed: true },
37
+ { id: '2', label: 'G2', completed: true },
38
+ { id: '3', label: 'G3', completed: true },
39
+ { id: '4', label: 'G4', completed: false },
40
+ { id: '5', label: 'G5', completed: false, isReward: true },
41
+ ];
42
+
43
+ export const lbV2Badges: LbBadge[] = [
44
+ { id: '1', name: 'First Win', icon: 'trophy', rarity: 'common' },
45
+ { id: '2', name: 'Streak Master', icon: 'flame', rarity: 'rare' },
46
+ { id: '3', name: 'Top 10', icon: 'star', rarity: 'epic' },
47
+ ];
48
+
49
+ export const lbV2Players: LbPlayer[] = [
50
+ {
51
+ id: '1',
52
+ rank: 1,
53
+ username: 'ShadowNinja',
54
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=shadow',
55
+ level: 99,
56
+ xp: 45000,
57
+ xpToNextLevel: 50000,
58
+ score: 2847500,
59
+ wins: 342,
60
+ streak: 15,
61
+ badges: [lbV2Badges[2], lbV2Badges[1]],
62
+ trend: 'same',
63
+ },
64
+ {
65
+ id: '2',
66
+ rank: 2,
67
+ username: 'CyberPhoenix',
68
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=phoenix',
69
+ level: 95,
70
+ xp: 38000,
71
+ xpToNextLevel: 45000,
72
+ score: 2654200,
73
+ wins: 298,
74
+ streak: 8,
75
+ badges: [lbV2Badges[1], lbV2Badges[0]],
76
+ trend: 'up',
77
+ rankChange: 2,
78
+ },
79
+ ];
80
+
81
+ export const lbV2Stats: LbStats = {
82
+ totalPlayers: 12847,
83
+ yourRank: 156,
84
+ topScore: 2847500,
85
+ };
86
+
87
+ export const lbV2CurrentUser: LbPlayer = {
88
+ id: 'current',
89
+ rank: 156,
90
+ username: 'YourLbPlayer',
91
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=current',
92
+ level: 45,
93
+ xp: 12500,
94
+ xpToNextLevel: 20000,
95
+ score: 456780,
96
+ wins: 42,
97
+ streak: 3,
98
+ badges: [lbV2Badges[0], lbV2Badges[1]],
99
+ trend: 'up',
100
+ rankChange: 12,
101
+ };