22< html lang ="en ">
33< head >
44 < meta charset ="UTF-8 " />
5- < title > 7TV Emote Leaderboard </ title >
5+ < title > 7TV Live Emote Tracker </ title >
66 < style >
7- * { box-sizing : border-box; }
8-
7+ * {
8+ box-sizing : border-box;
9+ }
910 body {
11+ margin : 0 ;
1012 font-family : 'Inter' , sans-serif;
11- color : white;
13+ background : # 0a0013 ;
14+ color : # fff ;
1215 text-align : center;
13- margin : 0 ;
14- padding : 0 ;
15- background : # 0d0b12 ;
16+ min-height : 100vh ;
17+ background-image : url ("peepo.png" );
18+ background-size : cover;
19+ background-position : center;
20+ background-attachment : fixed;
21+ background-repeat : no-repeat;
1622 overflow-x : hidden;
1723 }
1824
1925 body ::before {
2026 content : "" ;
2127 position : fixed;
22- top : 0 ; left : 0 ;
23- width : 100% ; height : 100% ;
24- background : url ("8ccf8c6b-762b-4688-9be3-de591ca256b8-profile_image-300x300.png" ) center/cover no-repeat;
25- filter : blur (20px ) brightness (0.3 );
28+ inset : 0 ;
29+ background : radial-gradient (circle at top left, rgba (90 , 0 , 255 , 0.4 ), transparent),
30+ radial-gradient (circle at bottom right, rgba (180 , 0 , 255 , 0.25 ), transparent),
31+ rgba (0 , 0 , 0 , 0.85 );
32+ backdrop-filter : blur (12px );
2633 z-index : -1 ;
2734 }
2835
2936 h1 {
30- font-weight : 800 ;
37+ margin-top : 1.2 rem ;
3138 font-size : 2rem ;
32- color : # a770ff ;
33- margin-top : 2rem ;
39+ color : # c8a9ff ;
40+ text-shadow : 0 0 10px # a76eff, 0 0 25px # 6b2cff ;
41+ animation : glowPulse 2s infinite alternate;
3442 }
3543
36- .controls {
37- margin : 1.5rem auto;
44+ @keyframes glowPulse {
45+ from { text-shadow : 0 0 8px # 9b59ff, 0 0 16px # 823bff ; }
46+ to { text-shadow : 0 0 18px # bb8cff, 0 0 32px # b57bff ; }
3847 }
3948
40- input {
41- padding : 0.5rem 1rem ;
49+ input , button {
50+ padding : 0.6rem 1rem ;
51+ border : 2px solid # 9146FF ;
4252 border-radius : 10px ;
43- border : none;
44- background : # 1e1b24 ;
53+ background : # 161024 ;
4554 color : white;
55+ margin : 0.5rem ;
4656 font-size : 1rem ;
47- margin-right : 0.5rem ;
57+ transition : 0.3s ;
58+ box-shadow : 0 0 10px rgba (145 , 70 , 255 , 0.3 );
59+ }
60+
61+ input : focus {
62+ border-color : # b68cff ;
4863 outline : none;
49- border : 2 px solid # 7b4fff ;
64+ box-shadow : 0 0 12 px # b68cff ;
5065 }
5166
5267 button {
53- padding : 0.5rem 1rem ;
54- border-radius : 10px ;
55- border : none;
56- font-size : 1rem ;
68+ background : linear-gradient (90deg , # 9146FF, # b88cff );
5769 cursor : pointer;
58- color : white;
59- background : linear-gradient (90deg , # 7b4fff, # a770ff );
60- transition : 0.2s ;
70+ font-weight : bold;
71+ box-shadow : 0 0 12px # 9146ff7a ;
72+ }
73+
74+ button : hover {
75+ box-shadow : 0 0 18px # b88cff ;
76+ transform : scale (1.03 );
6177 }
6278
63- button : hover { opacity : 0.8 ; }
79+ # title {
80+ margin-top : 0.8rem ;
81+ font-size : 1.2rem ;
82+ color : # c7b6ff ;
83+ text-shadow : 0 0 8px # 8f6cff ;
84+ }
85+
86+ # tables {
87+ display : flex;
88+ justify-content : space-around;
89+ flex-wrap : wrap;
90+ margin-top : 2rem ;
91+ }
6492
65- .leaderboard {
66- width : 80% ;
67- margin : 3rem auto;
93+ .table-container {
94+ width : 45% ;
95+ min-width : 320px ;
96+ background : rgba (10 , 0 , 25 , 0.6 );
97+ border : 2px solid rgba (130 , 60 , 255 , 0.4 );
6898 border-radius : 15px ;
69- overflow : hidden;
70- box-shadow : 0 0 25px rgba (0 , 0 , 0 , 0.5 );
99+ box-shadow : 0 0 20px rgba (145 , 70 , 255 , 0.15 );
100+ padding : 1rem ;
101+ transition : 0.3s ;
102+ }
103+
104+ .table-container : hover {
105+ box-shadow : 0 0 30px rgba (160 , 90 , 255 , 0.3 );
106+ }
107+
108+ h2 {
109+ color : # d2b6ff ;
110+ text-shadow : 0 0 8px # a66eff ;
111+ font-weight : 600 ;
71112 }
72113
73114 table {
74115 width : 100% ;
75116 border-collapse : collapse;
76- background : # 15121a ;
117+ margin-top : 1rem ;
118+ background : rgba (25 , 10 , 40 , 0.6 );
119+ border-radius : 10px ;
120+ overflow : hidden;
121+ }
122+
123+ th , td {
124+ padding : 10px 16px ;
125+ border-bottom : 1px solid rgba (90 , 50 , 160 , 0.3 );
77126 }
78127
79128 th {
80- background : # 1c1625 ;
129+ background : rgba (90 , 40 , 160 , 0.3 );
130+ color : # c6a3ff ;
81131 text-transform : uppercase;
82- color : # b9aaff ;
83- padding : 0.75 rem ;
132+ font-size : 0.85 rem ;
133+ letter-spacing : 1 px ;
84134 }
85135
86136 td {
87- padding : 0.75rem ;
88- border-top : 1px solid # 2c2238 ;
89- }
90-
91- tr : nth-child (even) { background : # 1a1723 ; }
92-
93- img .avatar {
94- width : 40px ;
95- height : 40px ;
96- border-radius : 50% ;
97- margin-right : 8px ;
98- vertical-align : middle;
99- }
100-
101- .tabs {
102- display : flex;
103- justify-content : center;
104- margin-top : 2rem ;
105- gap : 1rem ;
137+ color : # fff ;
138+ font-size : 1rem ;
106139 }
107140
108- .tab {
109- background : # 1f1a28 ;
110- padding : 0.6rem 1.2rem ;
111- border-radius : 10px ;
112- cursor : pointer;
113- font-weight : bold;
114- border : 2px solid transparent;
141+ tr : nth-child (even) {
142+ background : rgba (50 , 30 , 90 , 0.4 );
115143 }
116144
117- . tab . active {
118- border-color : # a770ff ;
119- background : # 2b2139 ;
145+ tr : hover {
146+ background : rgba ( 100 , 50 , 200 , 0.4 ) ;
147+ transition : 0.2 s ;
120148 }
121149 </ style >
122150</ head >
123151< body >
124- < h1 > 7TV Emote Leaderboard (All-Time) </ h1 >
152+ < h1 > 💫 7TV Live Tracker </ h1 >
125153
126- < div class =" controls " >
154+ < div >
127155 < input id ="channelInput " placeholder ="Enter Twitch channel... " />
128- < button onclick ="loadChannel() "> Start Tracking </ button >
156+ < button onclick ="loadChannel() "> Track Channel </ button >
129157 </ div >
130-
131- < div class ="tabs ">
132- < div class ="tab active " id ="userTab " onclick ="showTab('users') "> User Leaderboard</ div >
133- < div class ="tab " id ="emoteTab " onclick ="showTab('emotes') "> Emote Leaderboard</ div >
134- </ div >
135-
136- < div id ="userLeaderboard " class ="leaderboard ">
137- < table >
138- < thead >
139- < tr > < th > #</ th > < th > User</ th > < th > Emotes Used (All Time)</ th > </ tr >
140- </ thead >
141- < tbody id ="userTable "> </ tbody >
142- </ table >
143- </ div >
144-
145- < div id ="emoteLeaderboard " class ="leaderboard " style ="display:none; ">
146- < table >
147- < thead >
148- < tr > < th > #</ th > < th > Emote</ th > < th > Uses</ th > </ tr >
149- </ thead >
150- < tbody id ="emoteTable "> </ tbody >
151- </ table >
158+ < div id ="title "> </ div >
159+
160+ < div id ="tables ">
161+ < div class ="table-container ">
162+ < h2 > 🧍 User Leaderboard</ h2 >
163+ < table >
164+ < thead > < tr > < th > #</ th > < th > User</ th > < th > Count</ th > </ tr > </ thead >
165+ < tbody id ="userboard "> </ tbody >
166+ </ table >
167+ </ div >
168+
169+ < div class ="table-container ">
170+ < h2 > 🎭 Emote Leaderboard</ h2 >
171+ < table >
172+ < thead > < tr > < th > #</ th > < th > Emote</ th > < th > Count</ th > </ tr > </ thead >
173+ < tbody id ="emoteboard "> </ tbody >
174+ </ table >
175+ </ div >
152176 </ div >
153177
154178 < script >
@@ -159,9 +183,7 @@ <h1>7TV Emote Leaderboard (All-Time)</h1>
159183
160184 ws . onmessage = ( e ) => {
161185 const data = JSON . parse ( e . data ) ;
162- if ( data . channel !== channel ) return ;
163-
164- if ( data . type === "update" ) {
186+ if ( data . channel === channel && data . type === "update" ) {
165187 users [ data . user . id ] = data . user ;
166188 emoteUsage = data . emoteUsage ;
167189 render ( ) ;
@@ -174,40 +196,25 @@ <h1>7TV Emote Leaderboard (All-Time)</h1>
174196 const res = await fetch ( `/api/channel/${ channel } ` ) ;
175197 const data = await res . json ( ) ;
176198 users = { } ;
177- data . users . forEach ( u => users [ u . id ] = u ) ;
178- emoteUsage = data . emoteUsage || { } ;
199+ data . users . forEach ( ( u ) => ( users [ u . id ] = u ) ) ;
200+ emoteUsage = data . emoteUsage ;
201+ document . getElementById ( "title" ) . innerText = `Tracking: ${ channel } ` ;
179202 render ( ) ;
180203 }
181204
182205 function render ( ) {
183- // user leaderboard
184- const userSorted = Object . values ( users ) . sort ( ( a , b ) => b . count - a . count ) ;
185- const userTable = document . getElementById ( "userTable" ) ;
186- userTable . innerHTML = userSorted . map ( ( u , i ) => `
187- <tr>
188- <td>${ i + 1 } </td>
189- <td><img class="avatar" src="${ u . avatar } "/>${ u . username } </td>
190- <td>${ u . count } </td>
191- </tr>
192- ` ) . join ( '' ) ;
193-
194- // emote leaderboard
195- const emoteSorted = Object . entries ( emoteUsage ) . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) ;
196- const emoteTable = document . getElementById ( "emoteTable" ) ;
197- emoteTable . innerHTML = emoteSorted . map ( ( [ name , count ] , i ) => `
198- <tr>
199- <td>${ i + 1 } </td>
200- <td>${ name } </td>
201- <td>${ count } </td>
202- </tr>
203- ` ) . join ( '' ) ;
204- }
205-
206- function showTab ( tab ) {
207- document . getElementById ( "userLeaderboard" ) . style . display = tab === "users" ? "" : "none" ;
208- document . getElementById ( "emoteLeaderboard" ) . style . display = tab === "emotes" ? "" : "none" ;
209- document . getElementById ( "userTab" ) . classList . toggle ( "active" , tab === "users" ) ;
210- document . getElementById ( "emoteTab" ) . classList . toggle ( "active" , tab === "emotes" ) ;
206+ const sortedUsers = Object . values ( users ) . sort ( ( a , b ) => b . count - a . count ) ;
207+ const sortedEmotes = Object . entries ( emoteUsage )
208+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
209+ . map ( ( [ name , count ] ) => ( { name, count } ) ) ;
210+
211+ document . getElementById ( "userboard" ) . innerHTML = sortedUsers
212+ . map ( ( u , i ) => `<tr><td>${ i + 1 } </td><td>${ u . username } </td><td>${ u . count } </td></tr>` )
213+ . join ( "" ) ;
214+
215+ document . getElementById ( "emoteboard" ) . innerHTML = sortedEmotes
216+ . map ( ( e , i ) => `<tr><td>${ i + 1 } </td><td>${ e . name } </td><td>${ e . count } </td></tr>` )
217+ . join ( "" ) ;
211218 }
212219 </ script >
213220</ body >
0 commit comments