Siddheart commited on
Commit
f5ae0fe
·
verified ·
1 Parent(s): b281d9f

undefined - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +695 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Dota 2
3
- emoji: 📊
4
- colorFrom: yellow
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: dota-2
3
+ emoji: 🐳
4
+ colorFrom: pink
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,695 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Dota 2 Match Stats Viewer</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .hero-gradient {
11
+ background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
12
+ }
13
+ .radiant-gradient {
14
+ background: linear-gradient(135deg, rgba(26, 160, 93, 0.1) 0%, rgba(26, 160, 93, 0.2) 100%);
15
+ }
16
+ .dire-gradient {
17
+ background: linear-gradient(135deg, rgba(160, 26, 26, 0.1) 0%, rgba(160, 26, 26, 0.2) 100%);
18
+ }
19
+ .hero-icon {
20
+ width: 24px;
21
+ height: 24px;
22
+ border-radius: 50%;
23
+ background-size: cover;
24
+ background-position: center;
25
+ }
26
+ .player-item:hover {
27
+ transform: translateY(-2px);
28
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
29
+ }
30
+ .loading-spinner {
31
+ border: 4px solid rgba(255, 255, 255, 0.3);
32
+ border-radius: 50%;
33
+ border-top: 4px solid #ffffff;
34
+ width: 40px;
35
+ height: 40px;
36
+ animation: spin 1s linear infinite;
37
+ }
38
+ @keyframes spin {
39
+ 0% { transform: rotate(0deg); }
40
+ 100% { transform: rotate(360deg); }
41
+ }
42
+ </style>
43
+ </head>
44
+ <body class="bg-gray-900 text-gray-100 min-h-screen">
45
+ <div class="container mx-auto px-4 py-8">
46
+ <!-- Header -->
47
+ <header class="hero-gradient rounded-xl p-6 mb-8 shadow-lg">
48
+ <div class="flex flex-col md:flex-row justify-between items-center">
49
+ <div class="mb-4 md:mb-0">
50
+ <h1 class="text-3xl font-bold text-green-400">Dota 2 Match Stats</h1>
51
+ <p class="text-gray-300">Analyze match details in real-time</p>
52
+ </div>
53
+ <div class="flex items-center space-x-4">
54
+ <input
55
+ type="number"
56
+ id="matchIdInput"
57
+ placeholder="Enter Match ID"
58
+ class="px-4 py-2 rounded-lg bg-gray-800 text-white focus:outline-none focus:ring-2 focus:ring-green-500"
59
+ >
60
+ <button
61
+ id="fetchBtn"
62
+ class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg transition duration-200 flex items-center"
63
+ >
64
+ <i class="fas fa-search mr-2"></i> Fetch
65
+ </button>
66
+ </div>
67
+ </div>
68
+ </header>
69
+
70
+ <!-- Loading State -->
71
+ <div id="loading" class="hidden flex flex-col items-center justify-center py-16">
72
+ <div class="loading-spinner mb-4"></div>
73
+ <p class="text-xl">Fetching match data...</p>
74
+ </div>
75
+
76
+ <!-- Error State -->
77
+ <div id="error" class="hidden bg-red-900 text-white p-6 rounded-xl mb-8">
78
+ <div class="flex items-center">
79
+ <i class="fas fa-exclamation-triangle text-2xl mr-4"></i>
80
+ <div>
81
+ <h3 class="font-bold text-xl">Error</h3>
82
+ <p id="errorMessage">Failed to fetch match data. Please try again.</p>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- Match Info Section -->
88
+ <div id="matchInfo" class="hidden bg-gray-800 rounded-xl p-6 mb-8 shadow-lg">
89
+ <div class="flex flex-col md:flex-row justify-between items-center mb-6">
90
+ <div>
91
+ <h2 class="text-2xl font-bold">Match <span id="matchIdDisplay" class="text-green-400"></span></h2>
92
+ <div class="flex items-center mt-2">
93
+ <span id="gameMode" class="bg-gray-700 px-3 py-1 rounded-full text-sm"></span>
94
+ <span id="lobbyType" class="bg-gray-700 px-3 py-1 rounded-full text-sm ml-2"></span>
95
+ <span id="duration" class="bg-gray-700 px-3 py-1 rounded-full text-sm ml-2"></span>
96
+ </div>
97
+ </div>
98
+ <div class="mt-4 md:mt-0 flex items-center">
99
+ <div class="text-center px-4">
100
+ <div class="text-green-400 font-bold text-xl" id="radiantScore">0</div>
101
+ <div class="text-sm text-gray-300">Radiant</div>
102
+ </div>
103
+ <div class="text-2xl mx-2">vs</div>
104
+ <div class="text-center px-4">
105
+ <div class="text-red-400 font-bold text-xl" id="direScore">0</div>
106
+ <div class="text-sm text-gray-300">Dire</div>
107
+ </div>
108
+ </div>
109
+ <div class="mt-4 md:mt-0">
110
+ <div id="resultBadge" class="px-4 py-2 rounded-full font-bold"></div>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
115
+ <!-- Radiant Team -->
116
+ <div class="radiant-gradient rounded-xl p-4">
117
+ <div class="flex justify-between items-center mb-4">
118
+ <h3 class="text-xl font-bold text-green-400">Radiant</h3>
119
+ <div class="flex items-center">
120
+ <span class="text-sm mr-2">Advantage:</span>
121
+ <span id="radiantAdvantage" class="font-bold">0</span>
122
+ </div>
123
+ </div>
124
+ <div id="radiantPlayers" class="space-y-3">
125
+ <!-- Players will be inserted here -->
126
+ </div>
127
+ </div>
128
+
129
+ <!-- Dire Team -->
130
+ <div class="dire-gradient rounded-xl p-4">
131
+ <div class="flex justify-between items-center mb-4">
132
+ <h3 class="text-xl font-bold text-red-400">Dire</h3>
133
+ <div class="flex items-center">
134
+ <span class="text-sm mr-2">Advantage:</span>
135
+ <span id="direAdvantage" class="font-bold">0</span>
136
+ </div>
137
+ </div>
138
+ <div id="direPlayers" class="space-y-3">
139
+ <!-- Players will be inserted here -->
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Match Details Section -->
146
+ <div id="matchDetails" class="hidden bg-gray-800 rounded-xl p-6 shadow-lg mt-8">
147
+ <h3 class="text-xl font-bold mb-4">Match Details</h3>
148
+
149
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
150
+ <!-- Kills/Damage -->
151
+ <div class="bg-gray-700 rounded-lg p-4">
152
+ <h4 class="font-bold mb-3 text-green-400">Combat Stats</h4>
153
+ <div class="space-y-4">
154
+ <div>
155
+ <div class="flex justify-between mb-1">
156
+ <span>Total Kills</span>
157
+ <span id="totalKills" class="font-bold">0</span>
158
+ </div>
159
+ <div class="w-full bg-gray-600 rounded-full h-2">
160
+ <div id="killsBar" class="bg-gradient-to-r from-green-400 to-red-500 h-2 rounded-full" style="width: 50%"></div>
161
+ </div>
162
+ </div>
163
+ <div>
164
+ <div class="flex justify-between mb-1">
165
+ <span>Hero Damage</span>
166
+ <span id="heroDamage" class="font-bold">0</span>
167
+ </div>
168
+ <div class="w-full bg-gray-600 rounded-full h-2">
169
+ <div id="damageBar" class="bg-gradient-to-r from-green-400 to-red-500 h-2 rounded-full" style="width: 50%"></div>
170
+ </div>
171
+ </div>
172
+ <div>
173
+ <div class="flex justify-between mb-1">
174
+ <span>Tower Damage</span>
175
+ <span id="towerDamage" class="font-bold">0</span>
176
+ </div>
177
+ <div class="w-full bg-gray-600 rounded-full h-2">
178
+ <div id="towerDamageBar" class="bg-gradient-to-r from-green-400 to-red-500 h-2 rounded-full" style="width: 50%"></div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Economy -->
185
+ <div class="bg-gray-700 rounded-lg p-4">
186
+ <h4 class="font-bold mb-3 text-yellow-400">Economy</h4>
187
+ <div class="space-y-4">
188
+ <div>
189
+ <div class="flex justify-between mb-1">
190
+ <span>Gold Earned</span>
191
+ <span id="goldEarned" class="font-bold">0</span>
192
+ </div>
193
+ <div class="w-full bg-gray-600 rounded-full h-2">
194
+ <div id="goldBar" class="bg-gradient-to-r from-green-400 to-red-500 h-2 rounded-full" style="width: 50%"></div>
195
+ </div>
196
+ </div>
197
+ <div>
198
+ <div class="flex justify-between mb-1">
199
+ <span>XP Earned</span>
200
+ <span id="xpEarned" class="font-bold">0</span>
201
+ </div>
202
+ <div class="w-full bg-gray-600 rounded-full h-2">
203
+ <div id="xpBar" class="bg-gradient-to-r from-green-400 to-red-500 h-2 rounded-full" style="width: 50%"></div>
204
+ </div>
205
+ </div>
206
+ <div>
207
+ <div class="flex justify-between mb-1">
208
+ <span>Last Hits</span>
209
+ <span id="lastHits" class="font-bold">0</span>
210
+ </div>
211
+ <div class="w-full bg-gray-600 rounded-full h-2">
212
+ <div id="lastHitsBar" class="bg-gradient-to-r from-green-400 to-red-500 h-2 rounded-full" style="width: 50%"></div>
213
+ </div>
214
+ </div>
215
+ </div>
216
+ </div>
217
+
218
+ <!-- Objectives -->
219
+ <div class="bg-gray-700 rounded-lg p-4">
220
+ <h4 class="font-bold mb-3 text-blue-400">Objectives</h4>
221
+ <div class="grid grid-cols-2 gap-4">
222
+ <div class="text-center">
223
+ <div class="text-3xl font-bold text-green-400" id="radiantTowers">0</div>
224
+ <div class="text-sm">Towers</div>
225
+ </div>
226
+ <div class="text-center">
227
+ <div class="text-3xl font-bold text-green-400" id="radiantBarracks">0</div>
228
+ <div class="text-sm">Barracks</div>
229
+ </div>
230
+ <div class="text-center">
231
+ <div class="text-3xl font-bold text-red-400" id="direTowers">0</div>
232
+ <div class="text-sm">Towers</div>
233
+ </div>
234
+ <div class="text-center">
235
+ <div class="text-3xl font-bold text-red-400" id="direBarracks">0</div>
236
+ <div class="text-sm">Barracks</div>
237
+ </div>
238
+ </div>
239
+ <div class="mt-4 pt-4 border-t border-gray-600">
240
+ <div class="flex justify-between items-center">
241
+ <span>Roshan Kills</span>
242
+ <div class="flex items-center">
243
+ <span class="text-green-400 mr-2" id="radiantRoshan">0</span>
244
+ <span class="text-gray-400">-</span>
245
+ <span class="text-red-400 ml-2" id="direRoshan">0</span>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </div>
252
+
253
+ <!-- Charts Section -->
254
+ <div id="chartsSection" class="hidden bg-gray-800 rounded-xl p-6 shadow-lg mt-8">
255
+ <h3 class="text-xl font-bold mb-4">Gold & XP Advantage</h3>
256
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
257
+ <div class="bg-gray-700 rounded-lg p-4">
258
+ <h4 class="font-bold mb-3 text-yellow-400">Gold Advantage</h4>
259
+ <canvas id="goldChart" height="250"></canvas>
260
+ </div>
261
+ <div class="bg-gray-700 rounded-lg p-4">
262
+ <h4 class="font-bold mb-3 text-blue-400">XP Advantage</h4>
263
+ <canvas id="xpChart" height="250"></canvas>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
270
+ <script>
271
+ // DOM Elements
272
+ const matchIdInput = document.getElementById('matchIdInput');
273
+ const fetchBtn = document.getElementById('fetchBtn');
274
+ const loading = document.getElementById('loading');
275
+ const error = document.getElementById('error');
276
+ const matchInfo = document.getElementById('matchInfo');
277
+ const matchDetails = document.getElementById('matchDetails');
278
+ const chartsSection = document.getElementById('chartsSection');
279
+ const errorMessage = document.getElementById('errorMessage');
280
+
281
+ // Event Listeners
282
+ fetchBtn.addEventListener('click', fetchMatchData);
283
+ matchIdInput.addEventListener('keypress', (e) => {
284
+ if (e.key === 'Enter') fetchMatchData();
285
+ });
286
+
287
+ // Chart instances
288
+ let goldChart = null;
289
+ let xpChart = null;
290
+
291
+ // Fetch match data from OpenDota API
292
+ async function fetchMatchData() {
293
+ const matchId = matchIdInput.value.trim();
294
+
295
+ if (!matchId) {
296
+ showError('Please enter a valid Match ID');
297
+ return;
298
+ }
299
+
300
+ // Show loading state
301
+ loading.classList.remove('hidden');
302
+ error.classList.add('hidden');
303
+ matchInfo.classList.add('hidden');
304
+ matchDetails.classList.add('hidden');
305
+ chartsSection.classList.add('hidden');
306
+
307
+ try {
308
+ // Clear previous charts if they exist
309
+ if (goldChart) goldChart.destroy();
310
+ if (xpChart) xpChart.destroy();
311
+
312
+ // Fetch match data
313
+ const matchResponse = await fetch(`https://api.opendota.com/api/matches/${matchId}`);
314
+ if (!matchResponse.ok) throw new Error('Match not found or API error');
315
+
316
+ const matchData = await matchResponse.json();
317
+
318
+ // Fetch additional details if needed
319
+ let playersData = [];
320
+ try {
321
+ const playersResponse = await fetch(`https://api.opendota.com/api/matches/${matchId}/players`);
322
+ if (!playersResponse.ok) {
323
+ console.warn('Failed to fetch player details, using basic match data only');
324
+ } else {
325
+ playersData = await playersResponse.json();
326
+ }
327
+ } catch (err) {
328
+ console.warn('Error fetching player details:', err);
329
+ }
330
+
331
+ // Process and display data
332
+ displayMatchData(matchData, playersData);
333
+
334
+ // Hide loading and show content
335
+ loading.classList.add('hidden');
336
+ matchInfo.classList.remove('hidden');
337
+ matchDetails.classList.remove('hidden');
338
+ chartsSection.classList.remove('hidden');
339
+
340
+ } catch (err) {
341
+ console.error('Error fetching match data:', err);
342
+ let errorMsg = err.message || 'Failed to fetch match data.';
343
+ if (errorMsg.includes('player details')) {
344
+ errorMsg += ' Some player details may be missing.';
345
+ }
346
+ showError(errorMsg + ' Please try again.');
347
+ loading.classList.add('hidden');
348
+ }
349
+ }
350
+
351
+ function showError(message) {
352
+ errorMessage.textContent = message;
353
+ error.classList.remove('hidden');
354
+ }
355
+
356
+ function displayMatchData(match, players) {
357
+ // Display basic match info
358
+ document.getElementById('matchIdDisplay').textContent = match.match_id;
359
+ document.getElementById('gameMode').textContent = getGameMode(match.game_mode);
360
+ document.getElementById('lobbyType').textContent = getLobbyType(match.lobby_type);
361
+ document.getElementById('duration').textContent = formatDuration(match.duration);
362
+
363
+ // Display scores and result
364
+ document.getElementById('radiantScore').textContent = match.radiant_score;
365
+ document.getElementById('direScore').textContent = match.dire_score;
366
+
367
+ const resultBadge = document.getElementById('resultBadge');
368
+ if (match.radiant_win) {
369
+ resultBadge.textContent = 'Radiant Victory';
370
+ resultBadge.className = 'px-4 py-2 rounded-full font-bold bg-green-600 text-white';
371
+ } else {
372
+ resultBadge.textContent = 'Dire Victory';
373
+ resultBadge.className = 'px-4 py-2 rounded-full font-bold bg-red-600 text-white';
374
+ }
375
+
376
+ // Process players data
377
+ const radiantPlayers = players.filter(p => p.isRadiant);
378
+ const direPlayers = players.filter(p => !p.isRadiant);
379
+
380
+ // Calculate team stats
381
+ const radiantStats = calculateTeamStats(radiantPlayers);
382
+ const direStats = calculateTeamStats(direPlayers);
383
+
384
+ // Display team advantages
385
+ document.getElementById('radiantAdvantage').textContent = radiantStats.networth.toLocaleString();
386
+ document.getElementById('direAdvantage').textContent = direStats.networth.toLocaleString();
387
+
388
+ // Display players
389
+ displayPlayers(radiantPlayers, 'radiantPlayers');
390
+ displayPlayers(direPlayers, 'direPlayers');
391
+
392
+ // Display match details
393
+ displayMatchDetails(radiantStats, direStats, match);
394
+
395
+ // Prepare and display charts
396
+ prepareCharts(match);
397
+ }
398
+
399
+ function calculateTeamStats(players) {
400
+ return players.reduce((stats, player) => {
401
+ stats.kills += player.kills || 0;
402
+ stats.deaths += player.deaths || 0;
403
+ stats.assists += player.assists || 0;
404
+ stats.heroDamage += player.hero_damage || 0;
405
+ stats.towerDamage += player.tower_damage || 0;
406
+ stats.lastHits += player.last_hits || 0;
407
+ stats.goldPerMin += player.gold_per_min || 0;
408
+ stats.xpPerMin += player.xp_per_min || 0;
409
+ stats.networth += player.net_worth || 0;
410
+ stats.totalGold += player.total_gold || 0;
411
+ stats.totalXp += player.total_xp || 0;
412
+ return stats;
413
+ }, {
414
+ kills: 0,
415
+ deaths: 0,
416
+ assists: 0,
417
+ heroDamage: 0,
418
+ towerDamage: 0,
419
+ lastHits: 0,
420
+ goldPerMin: 0,
421
+ xpPerMin: 0,
422
+ networth: 0,
423
+ totalGold: 0,
424
+ totalXp: 0
425
+ });
426
+ }
427
+
428
+ function displayPlayers(players, containerId) {
429
+ const container = document.getElementById(containerId);
430
+ container.innerHTML = '';
431
+
432
+ players.forEach(player => {
433
+ const playerElement = document.createElement('div');
434
+ playerElement.className = 'player-item bg-gray-700 rounded-lg p-3 transition duration-200';
435
+ playerElement.innerHTML = `
436
+ <div class="flex items-center justify-between">
437
+ <div class="flex items-center">
438
+ <div class="hero-icon mr-3" style="background-image: url('https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/heroes/${getHeroShortName(player.hero_id)}.png')"></div>
439
+ <div>
440
+ <div class="font-bold">${player.personaname || 'Anonymous'}</div>
441
+ <div class="text-sm text-gray-400">${getHeroName(player.hero_id)}</div>
442
+ </div>
443
+ </div>
444
+ <div class="text-right">
445
+ <div class="font-bold">${player.kills || 0}/${player.deaths || 0}/${player.assists || 0}</div>
446
+ <div class="text-sm text-gray-400">K/D/A</div>
447
+ </div>
448
+ </div>
449
+ <div class="grid grid-cols-4 gap-2 mt-3 text-center text-xs">
450
+ <div>
451
+ <div class="font-bold">${player.last_hits || 0}</div>
452
+ <div class="text-gray-400">LH</div>
453
+ </div>
454
+ <div>
455
+ <div class="font-bold">${player.denies || 0}</div>
456
+ <div class="text-gray-400">DN</div>
457
+ </div>
458
+ <div>
459
+ <div class="font-bold">${player.gold_per_min || 0}</div>
460
+ <div class="text-gray-400">GPM</div>
461
+ </div>
462
+ <div>
463
+ <div class="font-bold">${player.xp_per_min || 0}</div>
464
+ <div class="text-gray-400">XPM</div>
465
+ </div>
466
+ </div>
467
+ `;
468
+ container.appendChild(playerElement);
469
+ });
470
+ }
471
+
472
+ function displayMatchDetails(radiantStats, direStats, match) {
473
+ // Combat Stats
474
+ document.getElementById('totalKills').textContent = `${radiantStats.kills} - ${direStats.kills}`;
475
+ document.getElementById('heroDamage').textContent = `${radiantStats.heroDamage.toLocaleString()} - ${direStats.heroDamage.toLocaleString()}`;
476
+ document.getElementById('towerDamage').textContent = `${radiantStats.towerDamage.toLocaleString()} - ${direStats.towerDamage.toLocaleString()}`;
477
+
478
+ // Update progress bars
479
+ const totalKills = radiantStats.kills + direStats.kills;
480
+ const killsPercentage = totalKills > 0 ? (radiantStats.kills / totalKills * 100) : 50;
481
+ document.getElementById('killsBar').style.width = `${killsPercentage}%`;
482
+
483
+ const totalDamage = radiantStats.heroDamage + direStats.heroDamage;
484
+ const damagePercentage = totalDamage > 0 ? (radiantStats.heroDamage / totalDamage * 100) : 50;
485
+ document.getElementById('damageBar').style.width = `${damagePercentage}%`;
486
+
487
+ const totalTowerDamage = radiantStats.towerDamage + direStats.towerDamage;
488
+ const towerDamagePercentage = totalTowerDamage > 0 ? (radiantStats.towerDamage / totalTowerDamage * 100) : 50;
489
+ document.getElementById('towerDamageBar').style.width = `${towerDamagePercentage}%`;
490
+
491
+ // Economy Stats
492
+ document.getElementById('goldEarned').textContent = `${radiantStats.totalGold.toLocaleString()} - ${direStats.totalGold.toLocaleString()}`;
493
+ document.getElementById('xpEarned').textContent = `${radiantStats.totalXp.toLocaleString()} - ${direStats.totalXp.toLocaleString()}`;
494
+ document.getElementById('lastHits').textContent = `${radiantStats.lastHits} - ${direStats.lastHits}`;
495
+
496
+ const totalGold = radiantStats.totalGold + direStats.totalGold;
497
+ const goldPercentage = totalGold > 0 ? (radiantStats.totalGold / totalGold * 100) : 50;
498
+ document.getElementById('goldBar').style.width = `${goldPercentage}%`;
499
+
500
+ const totalXp = radiantStats.totalXp + direStats.totalXp;
501
+ const xpPercentage = totalXp > 0 ? (radiantStats.totalXp / totalXp * 100) : 50;
502
+ document.getElementById('xpBar').style.width = `${xpPercentage}%`;
503
+
504
+ const totalLastHits = radiantStats.lastHits + direStats.lastHits;
505
+ const lastHitsPercentage = totalLastHits > 0 ? (radiantStats.lastHits / totalLastHits * 100) : 50;
506
+ document.getElementById('lastHitsBar').style.width = `${lastHitsPercentage}%`;
507
+
508
+ // Objectives
509
+ document.getElementById('radiantTowers').textContent = match.tower_status_radiant ? countBits(match.tower_status_radiant) : 0;
510
+ document.getElementById('direTowers').textContent = match.tower_status_dire ? countBits(match.tower_status_dire) : 0;
511
+ document.getElementById('radiantBarracks').textContent = match.barracks_status_radiant ? countBits(match.barracks_status_radiant) : 0;
512
+ document.getElementById('direBarracks').textContent = match.barracks_status_dire ? countBits(match.barracks_status_dire) : 0;
513
+
514
+ // Roshan kills
515
+ document.getElementById('radiantRoshan').textContent = match.roshan_respawn_timer ? 'Unknown' : 0;
516
+ document.getElementById('direRoshan').textContent = match.roshan_respawn_timer ? 'Unknown' : 0;
517
+ }
518
+
519
+ function prepareCharts(match) {
520
+ // Prepare gold advantage data
521
+ const goldAdvantage = match.radiant_gold_adv || [];
522
+ const xpAdvantage = match.radiant_xp_adv || [];
523
+
524
+ // Create time labels (every minute)
525
+ const durationMinutes = Math.ceil(match.duration / 60);
526
+ const timeLabels = Array.from({length: durationMinutes}, (_, i) => `${i+1}'`);
527
+
528
+ // Gold Advantage Chart
529
+ const goldCtx = document.getElementById('goldChart').getContext('2d');
530
+ goldChart = new Chart(goldCtx, {
531
+ type: 'line',
532
+ data: {
533
+ labels: timeLabels,
534
+ datasets: [{
535
+ label: 'Gold Advantage',
536
+ data: goldAdvantage,
537
+ borderColor: '#f6e05e',
538
+ backgroundColor: 'rgba(246, 224, 94, 0.1)',
539
+ borderWidth: 2,
540
+ tension: 0.1,
541
+ fill: true
542
+ }]
543
+ },
544
+ options: {
545
+ responsive: true,
546
+ maintainAspectRatio: false,
547
+ plugins: {
548
+ legend: {
549
+ display: false
550
+ }
551
+ },
552
+ scales: {
553
+ x: {
554
+ grid: {
555
+ color: 'rgba(255, 255, 255, 0.1)'
556
+ },
557
+ ticks: {
558
+ color: 'rgba(255, 255, 255, 0.7)'
559
+ }
560
+ },
561
+ y: {
562
+ grid: {
563
+ color: 'rgba(255, 255, 255, 0.1)'
564
+ },
565
+ ticks: {
566
+ color: 'rgba(255, 255, 255, 0.7)'
567
+ }
568
+ }
569
+ }
570
+ }
571
+ });
572
+
573
+ // XP Advantage Chart
574
+ const xpCtx = document.getElementById('xpChart').getContext('2d');
575
+ xpChart = new Chart(xpCtx, {
576
+ type: 'line',
577
+ data: {
578
+ labels: timeLabels,
579
+ datasets: [{
580
+ label: 'XP Advantage',
581
+ data: xpAdvantage,
582
+ borderColor: '#63b3ed',
583
+ backgroundColor: 'rgba(99, 179, 237, 0.1)',
584
+ borderWidth: 2,
585
+ tension: 0.1,
586
+ fill: true
587
+ }]
588
+ },
589
+ options: {
590
+ responsive: true,
591
+ maintainAspectRatio: false,
592
+ plugins: {
593
+ legend: {
594
+ display: false
595
+ }
596
+ },
597
+ scales: {
598
+ x: {
599
+ grid: {
600
+ color: 'rgba(255, 255, 255, 0.1)'
601
+ },
602
+ ticks: {
603
+ color: 'rgba(255, 255, 255, 0.7)'
604
+ }
605
+ },
606
+ y: {
607
+ grid: {
608
+ color: 'rgba(255, 255, 255, 0.1)'
609
+ },
610
+ ticks: {
611
+ color: 'rgba(255, 255, 255, 0.7)'
612
+ }
613
+ }
614
+ }
615
+ }
616
+ });
617
+ }
618
+
619
+ // Helper functions
620
+ function getGameMode(modeId) {
621
+ const modes = {
622
+ 0: 'Unknown',
623
+ 1: 'All Pick',
624
+ 2: 'Captains Mode',
625
+ 3: 'Random Draft',
626
+ 4: 'Single Draft',
627
+ 5: 'All Random',
628
+ 6: 'Intro',
629
+ 7: 'Diretide',
630
+ 8: 'Reverse Captains Mode',
631
+ 9: 'Greeviling',
632
+ 10: 'Tutorial',
633
+ 11: 'Mid Only',
634
+ 12: 'Least Played',
635
+ 13: 'Limited Heroes',
636
+ 14: 'Compendium Matchmaking',
637
+ 15: 'Custom',
638
+ 16: 'Captains Draft',
639
+ 17: 'Balanced Draft',
640
+ 18: 'Ability Draft',
641
+ 19: 'Event',
642
+ 20: 'All Random Deathmatch',
643
+ 21: '1v1 Mid',
644
+ 22: 'All Draft',
645
+ 23: 'Turbo',
646
+ 24: 'Mutation'
647
+ };
648
+ return modes[modeId] || `Mode ${modeId}`;
649
+ }
650
+
651
+ function getLobbyType(lobbyId) {
652
+ const lobbies = {
653
+ 0: 'Normal',
654
+ 1: 'Practice',
655
+ 2: 'Tournament',
656
+ 3: 'Tutorial',
657
+ 4: 'Co-op Bots',
658
+ 5: 'Ranked',
659
+ 6: 'Solo Ranked',
660
+ 7: 'Ranked Roles',
661
+ 8: 'Battle Cup'
662
+ };
663
+ return lobbies[lobbyId] || `Lobby ${lobbyId}`;
664
+ }
665
+
666
+ function formatDuration(seconds) {
667
+ const mins = Math.floor(seconds / 60);
668
+ const secs = seconds % 60;
669
+ return `${mins}m ${secs}s`;
670
+ }
671
+
672
+ function getHeroName(heroId) {
673
+ // This would normally come from a heroes API endpoint
674
+ // For simplicity, we'll use a placeholder
675
+ return `Hero ${heroId}`;
676
+ }
677
+
678
+ function getHeroShortName(heroId) {
679
+ // This would normally come from a heroes API endpoint
680
+ // For simplicity, we'll use a placeholder
681
+ return `hero_placeholder`;
682
+ }
683
+
684
+ function countBits(n) {
685
+ // Count number of set bits (1s) in a number
686
+ let count = 0;
687
+ while (n) {
688
+ count += n & 1;
689
+ n >>= 1;
690
+ }
691
+ return count;
692
+ }
693
+ </script>
694
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Siddheart/dota-2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
695
+ </html>