@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,101 @@
1
+ @use 'sass:map';
2
+
3
+ // Canonical palette maps — values are CSS variables with compile-time fallbacks.
4
+ // Runtime: set the same keys on <sf-leaderboard> via applySfLeaderboardTheme().
5
+
6
+ $color-base: (
7
+ 4: var(--color-base-4, #00061a),
8
+ 3: var(--color-base-3, #000c35),
9
+ 2: var(--color-base-2, #00114f),
10
+ 1: var(--color-base-1, #0a0c14),
11
+ );
12
+
13
+ $color-primary: (
14
+ 3: var(--color-primary-3, linear-gradient(256deg, #ffeec2 0%, #ffc431 100%)),
15
+ 2: var(--color-primary-2, #00e5ff),
16
+ 1: var(--color-primary-1, #00d4ff),
17
+ );
18
+
19
+ $color-semantic: (
20
+ success: var(--color-success, #00d26a),
21
+ warning: var(--color-warning, #ffa502),
22
+ error: var(--color-error, #ff4757),
23
+ informative: var(--color-informative, #2388fb),
24
+ );
25
+
26
+ $color-neutral: var(--color-neutral, #f0f0f5);
27
+
28
+ $color-surface: (
29
+ divider: var(--color-surface-divider, rgba(42, 47, 66, 0.55)),
30
+ muted: var(--color-surface-muted, #808090),
31
+ success-bright: var(--color-surface-success-bright, #15bf81),
32
+ );
33
+
34
+ // Placement (1st / 2nd / 3rd) — overridable per slot role
35
+ $place-first-icon: var(--place-first-table-icon, #ffd700);
36
+ $place-first-podium-slot: var(--place-first-podium-slot, #ffd700);
37
+ $place-first-podium-number: var(--place-first-podium-number, #ffd700);
38
+
39
+ $place-second-icon: var(--place-second-table-icon, #c0c0c0);
40
+ $place-second-podium-slot: var(--place-second-podium-slot, #c0c0c0);
41
+ $place-second-podium-number: var(--place-second-podium-number, #c0c0c0);
42
+
43
+ $place-third-icon: var(--place-third-table-icon, #cd7f32);
44
+ $place-third-podium-slot: var(--place-third-podium-slot, #cd7f32);
45
+ $place-third-podium-number: var(--place-third-podium-number, #cd7f32);
46
+
47
+ @function base($step) {
48
+ @return map.get($color-base, $step);
49
+ }
50
+
51
+ @function primary($step) {
52
+ @return map.get($color-primary, $step);
53
+ }
54
+
55
+ @function semantic($name) {
56
+ @return map.get($color-semantic, $name);
57
+ }
58
+
59
+ @function neutral() {
60
+ @return $color-neutral;
61
+ }
62
+
63
+ @function surface($name) {
64
+ @return map.get($color-surface, $name);
65
+ }
66
+
67
+ @function place-first-icon() {
68
+ @return $place-first-icon;
69
+ }
70
+
71
+ @function place-first-podium-slot() {
72
+ @return $place-first-podium-slot;
73
+ }
74
+
75
+ @function place-first-podium-number() {
76
+ @return $place-first-podium-number;
77
+ }
78
+
79
+ @function place-second-icon() {
80
+ @return $place-second-icon;
81
+ }
82
+
83
+ @function place-second-podium-slot() {
84
+ @return $place-second-podium-slot;
85
+ }
86
+
87
+ @function place-second-podium-number() {
88
+ @return $place-second-podium-number;
89
+ }
90
+
91
+ @function place-third-icon() {
92
+ @return $place-third-icon;
93
+ }
94
+
95
+ @function place-third-podium-slot() {
96
+ @return $place-third-podium-slot;
97
+ }
98
+
99
+ @function place-third-podium-number() {
100
+ @return $place-third-podium-number;
101
+ }
@@ -0,0 +1,123 @@
1
+ @use 'colors' as c;
2
+
3
+ :host {
4
+ /* ── Semantic aliases (derived from canonical --color-* tokens) ── */
5
+ --background: #{c.base(1)};
6
+ --foreground: #{c.neutral()};
7
+ --card: #{c.base(2)};
8
+ --card-foreground: #{c.neutral()};
9
+ --primary: #{c.primary(1)};
10
+ --primary-rgb: var(--color-primary-1-rgb, 0, 212, 255);
11
+ --primary-foreground: #{c.base(3)};
12
+ --secondary: #{c.base(2)};
13
+ --secondary-foreground: #{c.neutral()};
14
+ --muted: #{c.base(2)};
15
+ --muted-foreground: #{c.surface(muted)};
16
+ --accent: #{c.primary(2)};
17
+ --accent-foreground: #{c.base(3)};
18
+ --destructive: #{c.semantic(error)};
19
+ --destructive-rgb: var(--color-error-rgb, 255, 71, 87);
20
+ --border: #{c.base(2)};
21
+ --border-subtle: #{c.surface(divider)};
22
+ --success: #{c.semantic(success)};
23
+ --success-rgb: var(--color-success-rgb, 0, 210, 106);
24
+ --warning: #{c.semantic(warning)};
25
+ --warning-rgb: var(--color-warning-rgb, 255, 165, 2);
26
+ --informative: #{c.semantic(informative)};
27
+
28
+ /* Placement → legacy medal aliases */
29
+ --gold: #{c.place-first-icon()};
30
+ --gold-rgb: var(--place-first-table-icon-rgb, 255, 215, 0);
31
+ --silver: #{c.place-second-icon()};
32
+ --silver-rgb: var(--place-second-table-icon-rgb, 192, 192, 192);
33
+ --bronze: #{c.place-third-icon()};
34
+ --bronze-rgb: var(--place-third-table-icon-rgb, 205, 127, 50);
35
+
36
+ --radius: 12px;
37
+ --radius-sm: 8px;
38
+ --radius-lg: 16px;
39
+ --font-sans: var(--font-primary, 'Inter', system-ui, sans-serif);
40
+ --transition-fast: 0.2s ease;
41
+ --transition-base: 0.3s ease;
42
+
43
+ --surface-gradient: linear-gradient(145deg, #{c.base(2)} 0%, #{c.base(1)} 100%);
44
+ --surface-border-color: color-mix(in srgb, #{c.primary(1)} 18%, transparent);
45
+ --surface-border-hover: color-mix(in srgb, #{c.primary(1)} 35%, transparent);
46
+ --surface-radius: var(--radius-lg);
47
+ --surface-padding: clamp(12px, 2vw, 18px);
48
+ --surface-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
49
+ --surface-shadow-hover: 0 0 24px color-mix(in srgb, #{c.primary(1)} 12%, transparent);
50
+
51
+ --inset-bg: color-mix(in srgb, #{c.neutral()} 3%, transparent);
52
+ --inset-border: color-mix(in srgb, #{c.neutral()} 8%, transparent);
53
+ --inset-radius: var(--radius-sm);
54
+ --inset-hover-bg: color-mix(in srgb, #{c.primary(1)} 6%, transparent);
55
+ --inset-hover-border: color-mix(in srgb, #{c.primary(1)} 22%, transparent);
56
+
57
+ --accent-soft: color-mix(in srgb, #{c.primary(1)} 10%, transparent);
58
+ --accent-soft-strong: color-mix(in srgb, #{c.primary(1)} 18%, transparent);
59
+ --accent-border: color-mix(in srgb, #{c.primary(1)} 28%, transparent);
60
+ --accent-glow: color-mix(in srgb, #{c.primary(1)} 45%, transparent);
61
+ --accent-gradient: #{c.primary(3)};
62
+ --accent-text-glow: 0 0 16px color-mix(in srgb, #{c.primary(1)} 55%, transparent),
63
+ 0 0 32px color-mix(in srgb, #{c.primary(1)} 28%, transparent);
64
+
65
+ --medal-gold-bg: color-mix(in srgb, #{c.place-first-icon()} 22%, transparent);
66
+ --medal-gold-border: color-mix(in srgb, #{c.place-first-icon()} 50%, transparent);
67
+ --medal-silver-bg: color-mix(in srgb, #{c.place-second-icon()} 18%, transparent);
68
+ --medal-silver-border: color-mix(in srgb, #{c.place-second-icon()} 40%, transparent);
69
+ --medal-bronze-bg: color-mix(in srgb, #{c.place-third-icon()} 18%, transparent);
70
+ --medal-bronze-border: color-mix(in srgb, #{c.place-third-icon()} 40%, transparent);
71
+ --medal-gold-gradient: linear-gradient(135deg, #{c.place-first-icon()} 0%, #{c.primary(1)} 100%);
72
+ --medal-silver-gradient: linear-gradient(135deg, #{c.place-second-icon()} 0%, #{c.base(2)} 100%);
73
+ --medal-bronze-gradient: linear-gradient(135deg, #{c.place-third-icon()} 0%, #{c.base(3)} 100%);
74
+ --medal-avatar-fg: #{c.base(3)};
75
+ --medal-gold-glow: color-mix(in srgb, #{c.place-first-icon()} 60%, transparent);
76
+ --medal-silver-glow: color-mix(in srgb, #{c.place-second-icon()} 50%, transparent);
77
+ --medal-bronze-glow: color-mix(in srgb, #{c.place-third-icon()} 50%, transparent);
78
+
79
+ --badge-success-bg: color-mix(in srgb, #{c.semantic(success)} 20%, transparent);
80
+ --badge-success-border: color-mix(in srgb, #{c.semantic(success)} 35%, transparent);
81
+ --badge-warning-bg: color-mix(in srgb, #{c.semantic(warning)} 20%, transparent);
82
+ --badge-warning-border: color-mix(in srgb, #{c.semantic(warning)} 35%, transparent);
83
+ --badge-accent-bg: var(--accent-soft);
84
+ --badge-accent-border: var(--accent-border);
85
+
86
+ font-family: var(--font-sans);
87
+ color: var(--foreground);
88
+ }
89
+
90
+ * {
91
+ box-sizing: border-box;
92
+ }
93
+
94
+ .card {
95
+ position: relative;
96
+ overflow: hidden;
97
+ box-sizing: border-box;
98
+ background: var(--surface-gradient);
99
+ border: 1px solid var(--surface-border-color);
100
+ border-radius: var(--surface-radius);
101
+ box-shadow: var(--surface-shadow);
102
+ transition:
103
+ border-color var(--transition-fast),
104
+ box-shadow var(--transition-fast);
105
+ }
106
+
107
+ .card:hover {
108
+ border-color: var(--surface-border-hover);
109
+ box-shadow: var(--surface-shadow-hover);
110
+ }
111
+
112
+ .stat-item,
113
+ .avatar-section,
114
+ .percentile-badge {
115
+ background: var(--inset-bg);
116
+ border: 1px solid var(--inset-border);
117
+ border-radius: var(--inset-radius);
118
+ }
119
+
120
+ .stat-item:hover {
121
+ background: var(--inset-hover-bg);
122
+ border-color: var(--inset-hover-border);
123
+ }
@@ -0,0 +1,6 @@
1
+ import { scss } from '../../../../lib/lit/scss.js';
2
+ import type { CSSResult } from 'lit';
3
+ import sharedStyles from './scss/shared.scss?inline';
4
+
5
+ /** Shared stylesheet for leaderboard components (semantic aliases + .card). */
6
+ export const sfLeaderboardTheme: CSSResult = scss(sharedStyles);
@@ -0,0 +1,99 @@
1
+ import { hexToRgbChannels } from './hex-to-rgb.js';
2
+ import type {
3
+ SfLeaderboardTheme,
4
+ TableColumnId,
5
+ } from './types.js';
6
+
7
+ const TABLE_COLUMN_IDS: TableColumnId[] = [
8
+ 'rank',
9
+ 'player',
10
+ 'games',
11
+ 'winRate',
12
+ 'streak',
13
+ 'winnings',
14
+ ];
15
+
16
+ function setRgbFromHex(
17
+ vars: Record<string, string>,
18
+ cssKey: string,
19
+ hex: string | undefined,
20
+ ): void {
21
+ if (!hex || hex.includes('gradient')) {
22
+ return;
23
+ }
24
+ const channels = hexToRgbChannels(hex);
25
+ if (channels) {
26
+ vars[`${cssKey}-rgb`] = channels;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Flattens a theme object into CSS custom properties for the leaderboard host.
32
+ * Keys match `styles/_colors.scss` (`--color-base-1`, `--place-first-table-icon`, …).
33
+ */
34
+ export function themeToCssVars(theme: SfLeaderboardTheme): Record<string, string> {
35
+ const { palette, typography, placement, table } = theme;
36
+ const vars: Record<string, string> = {};
37
+
38
+ for (const step of ['1', '2', '3', '4'] as const) {
39
+ vars[`--color-base-${step}`] = palette.base[step];
40
+ }
41
+
42
+ for (const step of ['1', '2', '3'] as const) {
43
+ vars[`--color-primary-${step}`] = palette.primary[step];
44
+ }
45
+
46
+ vars['--color-success'] = palette.semantic.success;
47
+ vars['--color-warning'] = palette.semantic.warning;
48
+ vars['--color-error'] = palette.semantic.error;
49
+ if (palette.semantic.informative) {
50
+ vars['--color-informative'] = palette.semantic.informative;
51
+ }
52
+
53
+ vars['--color-neutral'] = palette.neutral;
54
+ vars['--color-surface-divider'] = palette.surface.divider;
55
+ vars['--color-surface-muted'] = palette.surface.muted;
56
+ if (palette.surface.successBright) {
57
+ vars['--color-surface-success-bright'] = palette.surface.successBright;
58
+ }
59
+
60
+ vars['--font-primary'] = typography.primary;
61
+ vars['--font-secondary'] = typography.secondary;
62
+ if (typography.mono) {
63
+ vars['--font-mono'] = typography.mono;
64
+ }
65
+
66
+ const placements = [
67
+ ['first', placement.first],
68
+ ['second', placement.second],
69
+ ['third', placement.third],
70
+ ] as const;
71
+
72
+ for (const [key, p] of placements) {
73
+ vars[`--place-${key}-table-icon`] = p.tableIcon;
74
+ vars[`--place-${key}-podium-slot`] = p.podiumSlot;
75
+ vars[`--place-${key}-podium-number`] = p.podiumNumber;
76
+ setRgbFromHex(vars, `--place-${key}-table-icon`, p.tableIcon);
77
+ }
78
+
79
+ setRgbFromHex(vars, '--color-primary-1', palette.primary['1']);
80
+ setRgbFromHex(vars, '--color-success', palette.semantic.success);
81
+ setRgbFromHex(vars, '--color-warning', palette.semantic.warning);
82
+ setRgbFromHex(vars, '--color-error', palette.semantic.error);
83
+
84
+ for (const colId of TABLE_COLUMN_IDS) {
85
+ const col = table.columns[colId];
86
+ if (!col?.enabled) {
87
+ continue;
88
+ }
89
+ const kebab = colId.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
90
+ if (col.color) {
91
+ vars[`--table-col-${kebab}-color`] = col.color;
92
+ }
93
+ if (col.fontSize) {
94
+ vars[`--table-col-${kebab}-font-size`] = col.fontSize;
95
+ }
96
+ }
97
+
98
+ return vars;
99
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "id": "sf-leaderboard-default",
3
+ "name": "SF Leaderboard Default",
4
+ "palette": {
5
+ "base": {
6
+ "4": "#060810",
7
+ "3": "#0a0c14",
8
+ "2": "#12151f",
9
+ "1": "#161a26"
10
+ },
11
+ "primary": {
12
+ "1": "#00d4ff",
13
+ "2": "#00e5ff",
14
+ "3": "linear-gradient(135deg, #00d4ff 0%, #00e5ff 100%)"
15
+ },
16
+ "semantic": {
17
+ "success": "#00d26a",
18
+ "warning": "#ffa502",
19
+ "error": "#ff4757",
20
+ "informative": "#2388fb"
21
+ },
22
+ "neutral": "#f0f0f5",
23
+ "surface": {
24
+ "divider": "rgba(42, 47, 66, 0.55)",
25
+ "muted": "#808090",
26
+ "successBright": "#15bf81"
27
+ }
28
+ },
29
+ "typography": {
30
+ "primary": "'Inter', system-ui, -apple-system, sans-serif",
31
+ "secondary": "'Inter', system-ui, -apple-system, sans-serif",
32
+ "mono": "'JetBrains Mono', monospace"
33
+ },
34
+ "placement": {
35
+ "first": {
36
+ "tableIcon": "#ffd700",
37
+ "podiumSlot": "#ffd700",
38
+ "podiumNumber": "#ffd700"
39
+ },
40
+ "second": {
41
+ "tableIcon": "#c0c0c0",
42
+ "podiumSlot": "#c0c0c0",
43
+ "podiumNumber": "#c0c0c0"
44
+ },
45
+ "third": {
46
+ "tableIcon": "#cd7f32",
47
+ "podiumSlot": "#cd7f32",
48
+ "podiumNumber": "#cd7f32"
49
+ }
50
+ },
51
+ "table": {
52
+ "columns": {
53
+ "rank": { "enabled": false },
54
+ "player": { "enabled": false },
55
+ "games": { "enabled": false },
56
+ "winRate": { "enabled": false },
57
+ "streak": { "enabled": false },
58
+ "winnings": { "enabled": false }
59
+ }
60
+ },
61
+ "labels": {}
62
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "id": "sf-leaderboard-neon-sunset",
3
+ "name": "SF Leaderboard Neon Sunset",
4
+ "palette": {
5
+ "base": {
6
+ "4": "#140B1A",
7
+ "3": "#1E1227",
8
+ "2": "#2A1835",
9
+ "1": "#342044"
10
+ },
11
+ "primary": {
12
+ "1": "#FF7A18",
13
+ "2": "#FF3D81",
14
+ "3": "linear-gradient(135deg, #FF7A18 0%, #FF3D81 55%, #9B5CFF 100%)"
15
+ },
16
+ "semantic": {
17
+ "success": "#3DFFB5",
18
+ "warning": "#FFD166",
19
+ "error": "#FF5A7A",
20
+ "informative": "#6EC5FF"
21
+ },
22
+ "neutral": "#F8F4FF",
23
+ "surface": {
24
+ "divider": "rgba(255, 255, 255, 0.08)",
25
+ "muted": "#B39BC8",
26
+ "successBright": "#7BFFCB"
27
+ }
28
+ },
29
+ "typography": {
30
+ "primary": "'Space Grotesk', system-ui, sans-serif",
31
+ "secondary": "'Inter', system-ui, sans-serif",
32
+ "mono": "'JetBrains Mono', monospace"
33
+ },
34
+ "placement": {
35
+ "first": {
36
+ "tableIcon": "#FFB703",
37
+ "podiumSlot": "#FFB703",
38
+ "podiumNumber": "#FFF3B0"
39
+ },
40
+ "second": {
41
+ "tableIcon": "#8ECAE6",
42
+ "podiumSlot": "#8ECAE6",
43
+ "podiumNumber": "#D9F3FF"
44
+ },
45
+ "third": {
46
+ "tableIcon": "#FB8500",
47
+ "podiumSlot": "#FB8500",
48
+ "podiumNumber": "#FFD6A5"
49
+ }
50
+ },
51
+ "table": {
52
+ "columns": {
53
+ "rank": { "enabled": true },
54
+ "player": { "enabled": true },
55
+ "games": { "enabled": true },
56
+ "winRate": { "enabled": true },
57
+ "streak": { "enabled": true },
58
+ "winnings": { "enabled": true }
59
+ }
60
+ },
61
+ "labels": {}
62
+ }
@@ -0,0 +1,71 @@
1
+ /** Base scale: 4 = deepest, 1 = primary surface */
2
+ export type ColorBaseStep = '1' | '2' | '3' | '4';
3
+
4
+ /** Primary scale: 1 = main brand, 3 = gradient / highlight */
5
+ export type ColorPrimaryStep = '1' | '2' | '3';
6
+
7
+ export type TableColumnId =
8
+ | 'rank'
9
+ | 'player'
10
+ | 'games'
11
+ | 'winRate'
12
+ | 'streak'
13
+ | 'winnings';
14
+
15
+ /** Per-column table styling (header + body cells). */
16
+ export interface SfLeaderboardTableColumnTheme {
17
+ enabled: boolean;
18
+ color?: string;
19
+ fontSize?: string;
20
+ }
21
+
22
+ /** Colors for a podium / table rank tier. */
23
+ export interface SfLeaderboardPlacementTheme {
24
+ tableIcon: string;
25
+ podiumSlot: string;
26
+ podiumNumber: string;
27
+ }
28
+
29
+ export interface SfLeaderboardPaletteTheme {
30
+ base: Record<ColorBaseStep, string>;
31
+ primary: Record<ColorPrimaryStep, string>;
32
+ semantic: {
33
+ success: string;
34
+ warning: string;
35
+ error: string;
36
+ informative?: string;
37
+ };
38
+ neutral: string;
39
+ surface: {
40
+ divider: string;
41
+ muted: string;
42
+ successBright?: string;
43
+ };
44
+ }
45
+
46
+ export interface SfLeaderboardTypographyTheme {
47
+ primary: string;
48
+ secondary: string;
49
+ mono?: string;
50
+ }
51
+
52
+ /**
53
+ * Full theme contract for `<sf-leaderboard>`.
54
+ * Clients load JSON (or build this object) and pass it to the `theme` property.
55
+ */
56
+ export interface SfLeaderboardTheme {
57
+ id: string;
58
+ name: string;
59
+ palette: SfLeaderboardPaletteTheme;
60
+ typography: SfLeaderboardTypographyTheme;
61
+ placement: {
62
+ first: SfLeaderboardPlacementTheme;
63
+ second: SfLeaderboardPlacementTheme;
64
+ third: SfLeaderboardPlacementTheme;
65
+ };
66
+ table: {
67
+ columns: Partial<Record<TableColumnId, SfLeaderboardTableColumnTheme>>;
68
+ };
69
+ /** Optional copy overrides (not applied to CSS) */
70
+ labels?: Record<string, string>;
71
+ }
@@ -0,0 +1,5 @@
1
+ import type { TemplateResult } from 'lit';
2
+
3
+ export interface AvatarHost {
4
+ renderAvatarContent(): TemplateResult;
5
+ }
@@ -0,0 +1,3 @@
1
+ <div class="avatar">
2
+ ${host.renderAvatarContent()}
3
+ </div>
@@ -0,0 +1,24 @@
1
+ :host {
2
+ display: inline-block;
3
+ }
4
+
5
+ .avatar {
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ width: 32px;
10
+ height: 32px;
11
+ border-radius: 50%;
12
+ border: 2px solid var(--border);
13
+ background: var(--card);
14
+ color: var(--foreground);
15
+ font-size: 0.7rem;
16
+ font-weight: 700;
17
+ overflow: hidden;
18
+
19
+ img {
20
+ width: 100%;
21
+ height: 100%;
22
+ object-fit: cover;
23
+ }
24
+ }
@@ -0,0 +1,27 @@
1
+ import { html, LitElement } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { SF_LIVE_FEED_AVATAR_TAG } from '../../constants.js';
4
+ import { sfLiveFeedTheme } from '../../styles/theme.js';
5
+ import { getInitials } from '../../utils.js';
6
+ import type { AvatarHost } from './avatar.host.js';
7
+ import renderTemplate from './avatar.html?lit-html';
8
+ import styles from './avatar.scss?inline';
9
+ import { scss } from '../../../../lib/lit/scss.js';
10
+
11
+ @customElement(SF_LIVE_FEED_AVATAR_TAG)
12
+ export class SfLiveFeedAvatar extends LitElement implements AvatarHost {
13
+ static styles = [sfLiveFeedTheme, scss(styles)];
14
+
15
+ @property({ type: String }) username = '';
16
+ @property({ type: String }) avatar = '';
17
+
18
+ renderAvatarContent() {
19
+ return this.avatar
20
+ ? html`<img src="${this.avatar}" alt="${this.username}" />`
21
+ : html`<span>${getInitials(this.username)}</span>`;
22
+ }
23
+
24
+ render() {
25
+ return renderTemplate(this);
26
+ }
27
+ }
@@ -0,0 +1,8 @@
1
+ import type { TemplateResult } from 'lit';
2
+ import type { LiveFeedActivity } from '../../types.js';
3
+
4
+ export interface SfLiveFeedHost {
5
+ feedTitle: string;
6
+ activities: LiveFeedActivity[];
7
+ renderActivities(): TemplateResult;
8
+ }
@@ -0,0 +1,10 @@
1
+ <div class="feed-container">
2
+ <div class="feed-header">
3
+ <div class="header-left">
4
+ <div class="live-dot"></div>
5
+ <span class="header-title">${host.feedTitle}</span>
6
+ </div>
7
+ <span class="event-count">${host.activities.length} events</span>
8
+ </div>
9
+ <div class="feed-body">${host.renderActivities()}</div>
10
+ </div>