Barkmaster commited on
Commit
acb6fce
Β·
verified Β·
1 Parent(s): b907f49

Add website cos sigma

Browse files
Files changed (1) hide show
  1. index.html +1311 -19
index.html CHANGED
@@ -1,19 +1,1311 @@
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>WARZONE STATS β€” COMMAND CENTER</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1" integrity="sha384-jb8JQMbMoBUzgWatfe6COACi2ljcDdZQ2OxczGA3bGNeWe+6DChMTBJemed7ZnvJ" crossorigin="anonymous"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --bg: #060a0f;
12
+ --bg2: #0c1219;
13
+ --bg3: #111b25;
14
+ --border: #1e3048;
15
+ --accent: #00d4ff;
16
+ --accent2: #ff4444;
17
+ --accent3: #ffd700;
18
+ --accent4: #39ff14;
19
+ --text: #cee8ff;
20
+ --text-dim: #4a7a9b;
21
+ --glow: 0 0 18px rgba(0,212,255,0.35);
22
+ --glow-red: 0 0 18px rgba(255,68,68,0.35);
23
+ --glow-gold: 0 0 18px rgba(255,215,0,0.35);
24
+ }
25
+
26
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
27
+
28
+ body {
29
+ background: var(--bg);
30
+ color: var(--text);
31
+ font-family: 'Share Tech Mono', monospace;
32
+ min-height: 100vh;
33
+ overflow-x: hidden;
34
+ }
35
+
36
+ body::after {
37
+ content: '';
38
+ position: fixed;
39
+ inset: 0;
40
+ background: repeating-linear-gradient(
41
+ 0deg,
42
+ rgba(0,0,0,0.03) 0px,
43
+ rgba(0,0,0,0.03) 1px,
44
+ transparent 1px,
45
+ transparent 2px
46
+ );
47
+ pointer-events: none;
48
+ z-index: 9999;
49
+ }
50
+
51
+ /* ── HEADER ── */
52
+ .header {
53
+ background: linear-gradient(135deg, #060f1a 0%, #0d1f33 100%);
54
+ border-bottom: 2px solid var(--accent);
55
+ padding: 18px 28px;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: space-between;
59
+ box-shadow: 0 4px 30px rgba(0,212,255,0.2);
60
+ position: sticky;
61
+ top: 0;
62
+ z-index: 100;
63
+ }
64
+
65
+ .header-left { display: flex; align-items: center; gap: 18px; }
66
+
67
+ .logo-mark {
68
+ width: 44px; height: 44px;
69
+ background: var(--accent);
70
+ clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
71
+ display: flex; align-items: center; justify-content: center;
72
+ font-size: 20px;
73
+ box-shadow: var(--glow);
74
+ animation: pulse 2.5s ease-in-out infinite;
75
+ }
76
+
77
+ @keyframes pulse {
78
+ 0%,100% { box-shadow: var(--glow); }
79
+ 50% { box-shadow: 0 0 35px rgba(0,212,255,0.7); }
80
+ }
81
+
82
+ .header h1 {
83
+ font-family: 'Orbitron', sans-serif;
84
+ font-size: 22px;
85
+ font-weight: 900;
86
+ letter-spacing: 4px;
87
+ color: var(--accent);
88
+ text-shadow: var(--glow);
89
+ }
90
+
91
+ .header-sub {
92
+ font-size: 11px;
93
+ color: var(--text-dim);
94
+ letter-spacing: 2px;
95
+ text-transform: uppercase;
96
+ }
97
+
98
+ .header-right { display: flex; align-items: center; gap: 16px; }
99
+
100
+ .live-badge {
101
+ display: flex; align-items: center; gap: 6px;
102
+ font-size: 11px; letter-spacing: 1px; color: var(--accent4);
103
+ }
104
+ .live-dot {
105
+ width: 8px; height: 8px; border-radius: 50%;
106
+ background: var(--accent4);
107
+ animation: blink 1.2s ease-in-out infinite;
108
+ box-shadow: 0 0 8px var(--accent4);
109
+ }
110
+ @keyframes blink { 0%,100% { opacity:1; } 50% { opacity:0.2; } }
111
+
112
+ .last-refresh { font-size: 11px; color: var(--text-dim); }
113
+
114
+ .refresh-btn, .login-btn, .logout-btn {
115
+ background: transparent;
116
+ border: 1px solid var(--accent);
117
+ color: var(--accent);
118
+ font-family: 'Share Tech Mono', monospace;
119
+ font-size: 12px;
120
+ padding: 6px 16px;
121
+ cursor: pointer;
122
+ letter-spacing: 2px;
123
+ transition: all .2s;
124
+ }
125
+ .refresh-btn:hover, .login-btn:hover {
126
+ background: var(--accent);
127
+ color: var(--bg);
128
+ box-shadow: var(--glow);
129
+ }
130
+ .logout-btn {
131
+ border-color: var(--accent2);
132
+ color: var(--accent2);
133
+ }
134
+ .logout-btn:hover {
135
+ background: var(--accent2);
136
+ color: var(--bg);
137
+ box-shadow: var(--glow-red);
138
+ }
139
+
140
+ .user-info {
141
+ display: flex; align-items: center; gap: 10px;
142
+ font-size: 12px; color: var(--text);
143
+ }
144
+ .user-info .username {
145
+ color: var(--accent);
146
+ font-family: 'Orbitron', sans-serif;
147
+ font-size: 11px;
148
+ letter-spacing: 2px;
149
+ }
150
+
151
+ /* ── LOGIN MODAL ── */
152
+ .modal-overlay {
153
+ position: fixed; inset: 0;
154
+ background: rgba(6,10,15,0.92);
155
+ display: flex; align-items: center; justify-content: center;
156
+ z-index: 1000;
157
+ backdrop-filter: blur(4px);
158
+ opacity: 0;
159
+ pointer-events: none;
160
+ transition: opacity .3s;
161
+ }
162
+ .modal-overlay.active {
163
+ opacity: 1;
164
+ pointer-events: all;
165
+ }
166
+
167
+ .modal {
168
+ background: var(--bg2);
169
+ border: 1px solid var(--accent);
170
+ box-shadow: var(--glow), inset 0 0 60px rgba(0,212,255,0.03);
171
+ padding: 40px;
172
+ width: 100%;
173
+ max-width: 420px;
174
+ position: relative;
175
+ transform: translateY(20px);
176
+ transition: transform .3s;
177
+ }
178
+ .modal-overlay.active .modal { transform: translateY(0); }
179
+
180
+ .modal-corner {
181
+ position: absolute;
182
+ width: 14px; height: 14px;
183
+ border-color: var(--accent);
184
+ border-style: solid;
185
+ }
186
+ .modal-corner.tl { top: -1px; left: -1px; border-width: 2px 0 0 2px; }
187
+ .modal-corner.tr { top: -1px; right: -1px; border-width: 2px 2px 0 0; }
188
+ .modal-corner.bl { bottom: -1px; left: -1px; border-width: 0 0 2px 2px; }
189
+ .modal-corner.br { bottom: -1px; right: -1px; border-width: 0 2px 2px 0; }
190
+
191
+ .modal-close {
192
+ position: absolute;
193
+ top: 15px; right: 15px;
194
+ background: transparent;
195
+ border: none;
196
+ color: var(--text-dim);
197
+ font-size: 16px;
198
+ cursor: pointer;
199
+ transition: color .2s;
200
+ }
201
+ .modal-close:hover { color: var(--accent2); }
202
+
203
+ .modal-title {
204
+ font-family: 'Orbitron', sans-serif;
205
+ font-size: 18px;
206
+ font-weight: 700;
207
+ letter-spacing: 4px;
208
+ color: var(--accent);
209
+ text-shadow: var(--glow);
210
+ margin-bottom: 6px;
211
+ }
212
+ .modal-sub {
213
+ font-size: 11px;
214
+ color: var(--text-dim);
215
+ letter-spacing: 2px;
216
+ margin-bottom: 30px;
217
+ }
218
+
219
+ .form-group { margin-bottom: 20px; }
220
+ .form-group label {
221
+ display: block;
222
+ font-size: 10px;
223
+ letter-spacing: 3px;
224
+ color: var(--text-dim);
225
+ text-transform: uppercase;
226
+ margin-bottom: 8px;
227
+ }
228
+ .form-group input {
229
+ width: 100%;
230
+ background: var(--bg3);
231
+ border: 1px solid var(--border);
232
+ color: var(--text);
233
+ font-family: 'Share Tech Mono', monospace;
234
+ font-size: 14px;
235
+ padding: 10px 14px;
236
+ outline: none;
237
+ transition: border-color .2s;
238
+ }
239
+ .form-group input:focus {
240
+ border-color: var(--accent);
241
+ box-shadow: 0 0 0 1px rgba(0,212,255,0.2);
242
+ }
243
+
244
+ .modal-submit {
245
+ width: 100%;
246
+ background: var(--accent);
247
+ color: var(--bg);
248
+ border: none;
249
+ font-family: 'Orbitron', sans-serif;
250
+ font-size: 13px;
251
+ font-weight: 700;
252
+ letter-spacing: 4px;
253
+ padding: 12px;
254
+ cursor: pointer;
255
+ transition: all .2s;
256
+ margin-top: 8px;
257
+ }
258
+ .modal-submit:hover { box-shadow: var(--glow); filter: brightness(1.1); }
259
+ .modal-submit:disabled { opacity: 0.5; cursor: not-allowed; }
260
+
261
+ .modal-error {
262
+ background: rgba(255,68,68,0.1);
263
+ border: 1px solid var(--accent2);
264
+ color: var(--accent2);
265
+ padding: 10px 14px;
266
+ font-size: 12px;
267
+ letter-spacing: 1px;
268
+ margin-top: 16px;
269
+ display: none;
270
+ }
271
+
272
+ .auth-toggle {
273
+ text-align: center;
274
+ margin-top: 20px;
275
+ font-size: 11px;
276
+ color: var(--text-dim);
277
+ }
278
+ .auth-toggle button {
279
+ background: none; border: none;
280
+ color: var(--accent);
281
+ cursor: pointer;
282
+ font-family: 'Share Tech Mono', monospace;
283
+ letter-spacing: 1px;
284
+ text-decoration: underline;
285
+ margin-left: 5px;
286
+ }
287
+ .auth-toggle button:hover { color: #fff; }
288
+
289
+ /* ── ANON BANNER ── */
290
+ .anon-banner {
291
+ background: linear-gradient(90deg, rgba(0,212,255,0.08), rgba(0,212,255,0.02));
292
+ border: 1px solid rgba(0,212,255,0.25);
293
+ border-left: 3px solid var(--accent);
294
+ padding: 14px 20px;
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: space-between;
298
+ gap: 16px;
299
+ margin-bottom: 24px;
300
+ flex-wrap: wrap;
301
+ }
302
+ .anon-banner-text {
303
+ font-size: 12px;
304
+ color: var(--text-dim);
305
+ letter-spacing: 1px;
306
+ }
307
+ .anon-banner-text span { color: var(--accent); font-weight: 600; }
308
+ .anon-banner-btn {
309
+ background: transparent;
310
+ border: 1px solid var(--accent);
311
+ color: var(--accent);
312
+ font-family: 'Share Tech Mono', monospace;
313
+ font-size: 11px;
314
+ padding: 5px 14px;
315
+ cursor: pointer;
316
+ letter-spacing: 2px;
317
+ transition: all .2s;
318
+ white-space: nowrap;
319
+ }
320
+ .anon-banner-btn:hover { background: var(--accent); color: var(--bg); }
321
+
322
+ /* ── LOCKED SECTION ── */
323
+ .locked-section {
324
+ position: relative;
325
+ overflow: hidden;
326
+ }
327
+ .locked-overlay {
328
+ position: absolute;
329
+ inset: 0;
330
+ background: rgba(6,10,15,0.85);
331
+ backdrop-filter: blur(3px);
332
+ display: flex;
333
+ flex-direction: column;
334
+ align-items: center;
335
+ justify-content: center;
336
+ gap: 14px;
337
+ z-index: 10;
338
+ }
339
+ .locked-icon { font-size: 32px; }
340
+ .locked-text {
341
+ font-family: 'Orbitron', sans-serif;
342
+ font-size: 12px;
343
+ letter-spacing: 3px;
344
+ color: var(--text-dim);
345
+ text-align: center;
346
+ }
347
+ .locked-btn {
348
+ background: transparent;
349
+ border: 1px solid var(--accent);
350
+ color: var(--accent);
351
+ font-family: 'Share Tech Mono', monospace;
352
+ font-size: 11px;
353
+ padding: 6px 18px;
354
+ cursor: pointer;
355
+ letter-spacing: 2px;
356
+ transition: all .2s;
357
+ }
358
+ .locked-btn:hover { background: var(--accent); color: var(--bg); }
359
+
360
+ /* ── MAIN LAYOUT ── */
361
+ .dashboard { padding: 24px 28px; max-width: 1600px; margin: 0 auto; }
362
+
363
+ .section-label {
364
+ font-family: 'Orbitron', sans-serif;
365
+ font-size: 11px;
366
+ font-weight: 600;
367
+ letter-spacing: 4px;
368
+ color: var(--text-dim);
369
+ text-transform: uppercase;
370
+ margin-bottom: 14px;
371
+ display: flex;
372
+ align-items: center;
373
+ gap: 10px;
374
+ }
375
+ .section-label::after {
376
+ content: '';
377
+ flex: 1;
378
+ height: 1px;
379
+ background: linear-gradient(90deg, var(--border), transparent);
380
+ }
381
+
382
+ /* ── KPI CARDS ── */
383
+ .kpi-row {
384
+ display: grid;
385
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
386
+ gap: 14px;
387
+ margin-bottom: 28px;
388
+ }
389
+
390
+ .kpi-card {
391
+ background: var(--bg2);
392
+ border: 1px solid var(--border);
393
+ padding: 18px 20px;
394
+ position: relative;
395
+ overflow: hidden;
396
+ transition: border-color .3s, box-shadow .3s;
397
+ }
398
+ .kpi-card::before {
399
+ content: '';
400
+ position: absolute;
401
+ top: 0; left: 0; right: 0;
402
+ height: 2px;
403
+ }
404
+ .kpi-card.cyan::before { background: var(--accent); box-shadow: var(--glow); }
405
+ .kpi-card.red::before { background: var(--accent2); box-shadow: var(--glow-red); }
406
+ .kpi-card.gold::before { background: var(--accent3); box-shadow: var(--glow-gold); }
407
+ .kpi-card.green::before { background: var(--accent4); }
408
+ .kpi-card:hover { border-color: var(--accent); box-shadow: var(--glow); }
409
+
410
+ .kpi-icon { font-size: 22px; margin-bottom: 8px; }
411
+ .kpi-label { font-size: 10px; letter-spacing: 2px; color: var(--text-dim); text-transform: uppercase; margin-bottom: 6px; }
412
+ .kpi-value { font-family: 'Orbitron', sans-serif; font-size: 26px; font-weight: 700; line-height: 1; }
413
+ .kpi-card.cyan .kpi-value { color: var(--accent); }
414
+ .kpi-card.red .kpi-value { color: var(--accent2); }
415
+ .kpi-card.gold .kpi-value { color: var(--accent3); }
416
+ .kpi-card.green .kpi-value { color: var(--accent4); }
417
+ .kpi-sub { font-size: 11px; color: var(--text-dim); margin-top: 4px; }
418
+
419
+ /* ── CHARTS ROW ── */
420
+ .charts-row {
421
+ display: grid;
422
+ grid-template-columns: 1fr 1fr 1fr;
423
+ gap: 14px;
424
+ margin-bottom: 28px;
425
+ }
426
+ @media (max-width: 1100px) { .charts-row { grid-template-columns: 1fr 1fr; } }
427
+ @media (max-width: 700px) { .charts-row { grid-template-columns: 1fr; } }
428
+
429
+ .chart-card {
430
+ background: var(--bg2);
431
+ border: 1px solid var(--border);
432
+ padding: 20px;
433
+ }
434
+ .chart-card h3 {
435
+ font-family: 'Orbitron', sans-serif;
436
+ font-size: 11px;
437
+ font-weight: 600;
438
+ letter-spacing: 3px;
439
+ color: var(--text-dim);
440
+ text-transform: uppercase;
441
+ margin-bottom: 18px;
442
+ }
443
+ .chart-card canvas { max-height: 220px; }
444
+
445
+ /* ── TABLE ── */
446
+ .table-card {
447
+ background: var(--bg2);
448
+ border: 1px solid var(--border);
449
+ padding: 20px;
450
+ margin-bottom: 28px;
451
+ overflow-x: auto;
452
+ }
453
+ .table-card h3 {
454
+ font-family: 'Orbitron', sans-serif;
455
+ font-size: 11px;
456
+ font-weight: 600;
457
+ letter-spacing: 3px;
458
+ color: var(--text-dim);
459
+ text-transform: uppercase;
460
+ margin-bottom: 16px;
461
+ }
462
+
463
+ .data-table {
464
+ width: 100%;
465
+ border-collapse: collapse;
466
+ font-size: 13px;
467
+ }
468
+ .data-table thead th {
469
+ text-align: left;
470
+ padding: 10px 14px;
471
+ border-bottom: 1px solid var(--border);
472
+ color: var(--text-dim);
473
+ font-size: 10px;
474
+ letter-spacing: 2px;
475
+ text-transform: uppercase;
476
+ white-space: nowrap;
477
+ cursor: pointer;
478
+ user-select: none;
479
+ }
480
+ .data-table thead th:hover { color: var(--accent); }
481
+ .data-table tbody td {
482
+ padding: 10px 14px;
483
+ border-bottom: 1px solid rgba(30,48,72,0.5);
484
+ white-space: nowrap;
485
+ }
486
+ .data-table tbody tr:hover { background: rgba(0,212,255,0.04); }
487
+ .data-table tbody tr:last-child td { border-bottom: none; }
488
+
489
+ .data-table tbody tr.own-row {
490
+ background: rgba(0,212,255,0.06);
491
+ border-left: 3px solid var(--accent);
492
+ }
493
+ .data-table tbody tr.own-row td:first-child { padding-left: 11px; }
494
+
495
+ .rank-badge {
496
+ display: inline-flex; align-items: center; justify-content: center;
497
+ width: 26px; height: 26px;
498
+ font-family: 'Orbitron', sans-serif;
499
+ font-size: 12px; font-weight: 700;
500
+ }
501
+ .rank-1 { background: var(--accent3); color: #000; }
502
+ .rank-2 { background: #aaa; color: #000; }
503
+ .rank-3 { background: #cd7f32; color: #000; }
504
+ .rank-other { background: var(--bg3); color: var(--text-dim); }
505
+
506
+ .weapon-tag {
507
+ display: inline-block; padding: 2px 8px;
508
+ border: 1px solid var(--border);
509
+ font-size: 11px; color: var(--accent);
510
+ }
511
+ .armor-tag {
512
+ display: inline-block; padding: 2px 8px;
513
+ border: 1px solid var(--border);
514
+ font-size: 11px; color: var(--accent3);
515
+ }
516
+ .armor-none { color: var(--text-dim); border-color: transparent; }
517
+ .gold-val { color: var(--accent3); font-family: 'Orbitron', sans-serif; font-size: 12px; }
518
+ .kd-good { color: var(--accent4); }
519
+ .kd-bad { color: var(--accent2); }
520
+ .kd-ok { color: var(--accent); }
521
+
522
+ .tier { display: inline-block; padding: 2px 10px; font-size: 11px; font-family: 'Orbitron', sans-serif; letter-spacing: 1px; }
523
+ .tier-diamond { background: rgba(0,212,255,0.15); color: var(--accent); border: 1px solid var(--accent); }
524
+ .tier-platinum{ background: rgba(200,200,255,0.1); color: #b0c4de; border: 1px solid #b0c4de; }
525
+ .tier-gold { background: rgba(255,215,0,0.1); color: var(--accent3); border: 1px solid var(--accent3); }
526
+ .tier-silver { background: rgba(170,170,170,0.1); color: #aaa; border: 1px solid #aaa; }
527
+ .tier-bronze { background: rgba(205,127,50,0.1); color: #cd7f32; border: 1px solid #cd7f32; }
528
+
529
+ /* ── LOADING ── */
530
+ #loading-overlay {
531
+ position: fixed; inset: 0;
532
+ background: var(--bg);
533
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
534
+ gap: 20px;
535
+ z-index: 500;
536
+ transition: opacity .4s;
537
+ }
538
+ #loading-overlay.hidden { opacity: 0; pointer-events: none; }
539
+
540
+ .loading-hex {
541
+ width: 60px; height: 60px;
542
+ background: var(--accent);
543
+ clip-path: polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);
544
+ animation: spin 1s linear infinite;
545
+ }
546
+ @keyframes spin { to { transform: rotate(360deg); } }
547
+
548
+ .loading-text {
549
+ font-family: 'Orbitron', sans-serif;
550
+ font-size: 13px; letter-spacing: 4px; color: var(--accent);
551
+ }
552
+
553
+ .error-msg {
554
+ background: rgba(255,68,68,0.1);
555
+ border: 1px solid var(--accent2);
556
+ color: var(--accent2);
557
+ padding: 14px 20px;
558
+ font-size: 13px;
559
+ letter-spacing: 1px;
560
+ display: none;
561
+ margin-bottom: 20px;
562
+ }
563
+
564
+ .corner-deco {
565
+ position: absolute;
566
+ width: 12px; height: 12px;
567
+ border-color: var(--accent);
568
+ border-style: solid;
569
+ }
570
+ .corner-deco.tl { top: 0; left: 0; border-width: 2px 0 0 2px; }
571
+ .corner-deco.tr { top: 0; right: 0; border-width: 2px 2px 0 0; }
572
+ .corner-deco.bl { bottom: 0; left: 0; border-width: 0 0 2px 2px; }
573
+ .corner-deco.br { bottom: 0; right: 0; border-width: 0 2px 2px 0; }
574
+ .chart-card, .table-card, .kpi-card { position: relative; }
575
+
576
+ .filter-row {
577
+ display: flex; gap: 12px; align-items: center; margin-bottom: 20px; flex-wrap: wrap;
578
+ }
579
+ .filter-row label { font-size: 11px; color: var(--text-dim); letter-spacing: 1px; }
580
+ .filter-row select {
581
+ background: var(--bg3); border: 1px solid var(--border);
582
+ color: var(--text); font-family: 'Share Tech Mono', monospace;
583
+ font-size: 12px; padding: 6px 12px; outline: none; cursor: pointer;
584
+ }
585
+ .filter-row select:focus { border-color: var(--accent); }
586
+ .filter-row select option { background: var(--bg3); }
587
+
588
+ /* ── MY STATS CARD ── */
589
+ .my-stats-banner {
590
+ background: linear-gradient(90deg, rgba(0,212,255,0.08), rgba(0,212,255,0.02));
591
+ border: 1px solid rgba(0,212,255,0.3);
592
+ border-left: 3px solid var(--accent);
593
+ padding: 20px 24px;
594
+ margin-bottom: 28px;
595
+ display: none;
596
+ }
597
+ .my-stats-banner.visible { display: block; }
598
+ .my-stats-title {
599
+ font-family: 'Orbitron', sans-serif;
600
+ font-size: 11px; letter-spacing: 3px; color: var(--accent);
601
+ margin-bottom: 14px;
602
+ }
603
+ .my-stats-grid {
604
+ display: grid;
605
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
606
+ gap: 16px;
607
+ }
608
+ .my-stat { text-align: center; }
609
+ .my-stat-val {
610
+ font-family: 'Orbitron', sans-serif;
611
+ font-size: 20px; font-weight: 700; color: var(--accent);
612
+ }
613
+ .my-stat-lbl { font-size: 10px; color: var(--text-dim); letter-spacing: 2px; margin-top: 4px; }
614
+
615
+ footer {
616
+ text-align: center;
617
+ padding: 18px;
618
+ font-size: 11px;
619
+ color: var(--text-dim);
620
+ letter-spacing: 2px;
621
+ border-top: 1px solid var(--border);
622
+ }
623
+ </style>
624
+ </head>
625
+ <body>
626
+
627
+ <div id="loading-overlay">
628
+ <div class="loading-hex"></div>
629
+ <div class="loading-text">CONNECTING TO WARZONE DATABASE...</div>
630
+ </div>
631
+
632
+ <div class="modal-overlay" id="login-modal">
633
+ <div class="modal">
634
+ <div class="modal-corner tl"></div>
635
+ <div class="modal-corner tr"></div>
636
+ <div class="modal-corner bl"></div>
637
+ <div class="modal-corner br"></div>
638
+ <button class="modal-close" onclick="closeLoginModal()">βœ•</button>
639
+
640
+ <div class="modal-title" id="modal-title-text">SIGN IN</div>
641
+ <div class="modal-sub" id="modal-sub-text">ACCESS FULL COMBAT ANALYTICS</div>
642
+
643
+ <div class="form-group" id="group-username" style="display:none;">
644
+ <label>Operator Handle (Username)</label>
645
+ <input type="text" id="login-username" placeholder="ShadowSniper99" />
646
+ </div>
647
+
648
+ <div class="form-group">
649
+ <label>Email Address</label>
650
+ <input type="email" id="login-email" placeholder="operator@warzone.com" />
651
+ </div>
652
+
653
+ <div class="form-group">
654
+ <label>Password</label>
655
+ <input type="password" id="login-password" placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" />
656
+ </div>
657
+
658
+ <button class="modal-submit" id="login-submit" onclick="submitAuth()">AUTHENTICATE</button>
659
+ <div class="modal-error" id="login-error"></div>
660
+
661
+ <div class="auth-toggle">
662
+ <span id="auth-toggle-text">Don't have clearance?</span>
663
+ <button id="auth-toggle-btn" onclick="toggleAuthMode()">REGISTER NEW OPERATOR</button>
664
+ </div>
665
+ </div>
666
+ </div>
667
+
668
+ <header class="header">
669
+ <div class="header-left">
670
+ <div class="logo-mark">⬑</div>
671
+ <div>
672
+ <h1>WARZONE STATS</h1>
673
+ <div class="header-sub">Command Center &nbsp;Β·&nbsp; Live Analytics</div>
674
+ </div>
675
+ </div>
676
+ <div class="header-right">
677
+ <div class="live-badge"><div class="live-dot"></div>LIVE</div>
678
+ <div class="last-refresh" id="last-refresh">–</div>
679
+ <button class="refresh-btn" onclick="loadData()">⟳ REFRESH</button>
680
+ <button class="login-btn" id="header-login-btn" onclick="openLoginModal()">⚿ LOGIN</button>
681
+ <div class="user-info" id="header-user-info" style="display:none">
682
+ <span>β–Έ</span>
683
+ <span class="username" id="header-username">β€”</span>
684
+ <button class="logout-btn" onclick="doLogout()">LOGOUT</button>
685
+ </div>
686
+ </div>
687
+ </header>
688
+
689
+ <div class="dashboard">
690
+
691
+ <div class="error-msg" id="error-msg"></div>
692
+
693
+ <div class="anon-banner" id="anon-banner">
694
+ <div class="anon-banner-text">
695
+ πŸ‘ Viewing <span>anonymous public data</span> β€” Login to unlock full stats, K/D ratios, headshots, weapons and your personal dashboard.
696
+ </div>
697
+ <button class="anon-banner-btn" onclick="openLoginModal()">⚿ LOGIN FOR FULL ACCESS</button>
698
+ </div>
699
+
700
+ <div class="my-stats-banner" id="my-stats-banner">
701
+ <div class="my-stats-title">βš” MY STATS</div>
702
+ <div class="my-stats-grid" id="my-stats-grid"></div>
703
+ </div>
704
+
705
+ <div class="section-label">GLOBAL STATS</div>
706
+ <div class="kpi-row">
707
+ <div class="kpi-card cyan">
708
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
709
+ <div class="kpi-icon">🎯</div>
710
+ <div class="kpi-label">Total Players</div>
711
+ <div class="kpi-value" id="kpi-players">β€”</div>
712
+ <div class="kpi-sub">Registered combatants</div>
713
+ </div>
714
+ <div class="kpi-card gold">
715
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
716
+ <div class="kpi-icon">πŸͺ™</div>
717
+ <div class="kpi-label">Gold in Circulation</div>
718
+ <div class="kpi-value" id="kpi-gold">β€”</div>
719
+ <div class="kpi-sub">Combined balance</div>
720
+ </div>
721
+ <div class="kpi-card cyan">
722
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
723
+ <div class="kpi-icon">πŸ†</div>
724
+ <div class="kpi-label">Top Rank Points</div>
725
+ <div class="kpi-value" id="kpi-top-rank">β€”</div>
726
+ <div class="kpi-sub" id="kpi-top-player">β€”</div>
727
+ </div>
728
+ <div class="kpi-card green">
729
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
730
+ <div class="kpi-icon">πŸ“ˆ</div>
731
+ <div class="kpi-label">Best Win Rate</div>
732
+ <div class="kpi-value" id="kpi-top-winrate">β€”</div>
733
+ <div class="kpi-sub" id="kpi-top-winrate-player">β€”</div>
734
+ </div>
735
+ <div class="kpi-card red" id="kpi-kills-card" style="display:none">
736
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
737
+ <div class="kpi-icon">πŸ’€</div>
738
+ <div class="kpi-label">Total Kills</div>
739
+ <div class="kpi-value" id="kpi-kills">β€”</div>
740
+ <div class="kpi-sub">Across all players</div>
741
+ </div>
742
+ <div class="kpi-card green" id="kpi-headshots-card" style="display:none">
743
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
744
+ <div class="kpi-icon">🎯</div>
745
+ <div class="kpi-label">Total Headshots</div>
746
+ <div class="kpi-value" id="kpi-headshots">β€”</div>
747
+ <div class="kpi-sub">Precision kills</div>
748
+ </div>
749
+ <div class="kpi-card red" id="kpi-playtime-card" style="display:none">
750
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
751
+ <div class="kpi-icon">⏱</div>
752
+ <div class="kpi-label">Playtime (hrs)</div>
753
+ <div class="kpi-value" id="kpi-playtime">β€”</div>
754
+ <div class="kpi-sub">Combined hours</div>
755
+ </div>
756
+ </div>
757
+
758
+ <div class="section-label">ANALYTICS</div>
759
+ <div class="locked-section" id="charts-section">
760
+ <div class="charts-row">
761
+ <div class="chart-card">
762
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
763
+ <h3>Top 5 β€” Rank Points</h3>
764
+ <canvas id="rankChart"></canvas>
765
+ </div>
766
+ <div class="chart-card">
767
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
768
+ <h3>Weapon Loadouts</h3>
769
+ <canvas id="weaponChart"></canvas>
770
+ </div>
771
+ <div class="chart-card">
772
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
773
+ <h3>K/D Ratio β€” Top 8</h3>
774
+ <canvas id="kdChart"></canvas>
775
+ </div>
776
+ </div>
777
+ <div class="charts-row" style="grid-template-columns: 1fr 1fr;">
778
+ <div class="chart-card">
779
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
780
+ <h3>Gold Holdings β€” Top 5</h3>
781
+ <canvas id="goldChart"></canvas>
782
+ </div>
783
+ <div class="chart-card">
784
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
785
+ <h3>Win Rate Distribution</h3>
786
+ <canvas id="winChart"></canvas>
787
+ </div>
788
+ </div>
789
+ <div class="locked-overlay" id="charts-lock">
790
+ <div class="locked-icon">πŸ”’</div>
791
+ <div class="locked-text">FULL ANALYTICS β€” AUTHORISED PERSONNEL ONLY</div>
792
+ <button class="locked-btn" onclick="openLoginModal()">⚿ LOGIN TO UNLOCK</button>
793
+ </div>
794
+ </div>
795
+
796
+ <div class="section-label" style="margin-top:28px">PLAYER LEADERBOARD</div>
797
+
798
+ <div class="filter-row" id="filter-row" style="display:none">
799
+ <label>FILTER BY WEAPON</label>
800
+ <select id="filter-weapon" onchange="renderTable()">
801
+ <option value="">ALL WEAPONS</option>
802
+ </select>
803
+ <label>SORT BY</label>
804
+ <select id="sort-by" onchange="renderTable()">
805
+ <option value="rank_points">Rank Points</option>
806
+ <option value="kills">Kills</option>
807
+ <option value="gold">Gold</option>
808
+ <option value="kd">K/D Ratio</option>
809
+ <option value="headshots">Headshots</option>
810
+ </select>
811
+ </div>
812
+
813
+ <div class="table-card">
814
+ <div class="corner-deco tl"></div><div class="corner-deco br"></div>
815
+ <h3>βš”οΈ &nbsp; ACTIVE ROSTER</h3>
816
+ <table class="data-table" id="leaderboard">
817
+ <thead id="leaderboard-head">
818
+ </thead>
819
+ <tbody id="leaderboard-body"></tbody>
820
+ </table>
821
+ </div>
822
+
823
+ </div>
824
+
825
+ <footer>WARZONE STATS &nbsp;Β·&nbsp; POWERED BY SUPABASE &nbsp;Β·&nbsp; BUILT WITH GODOT</footer>
826
+
827
+ <script>
828
+ const SUPABASE_URL = 'https://kkezjquxsrpifsbvggzs.supabase.co';
829
+ const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtrZXpqcXV4c3JwaWZzYnZnZ3pzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODA2NjI4MDksImV4cCI6MjA5NjIzODgwOX0.LkuUHbFIKswz7QEYrm9-7O6WnjvFn9WWo6UtE5o0bAQ';
830
+
831
+ let players = [];
832
+ let charts = {};
833
+ let authUser = null;
834
+ let isLoginMode = true;
835
+
836
+ // ── AUTH STATE ─────────────────────────────────────────────────────
837
+ function isLoggedIn() { return authUser !== null; }
838
+
839
+ function applyAuthState() {
840
+ const loggedIn = isLoggedIn();
841
+
842
+ // Header
843
+ document.getElementById('header-login-btn').style.display = loggedIn ? 'none' : '';
844
+ document.getElementById('header-user-info').style.display = loggedIn ? 'flex' : 'none';
845
+ if (loggedIn) document.getElementById('header-username').textContent = authUser.username || authUser.email;
846
+
847
+ // Anon banner / my stats
848
+ document.getElementById('anon-banner').style.display = loggedIn ? 'none' : '';
849
+ document.getElementById('my-stats-banner').classList.toggle('visible', loggedIn);
850
+
851
+ // Extra KPI cards
852
+ ['kpi-kills-card','kpi-headshots-card','kpi-playtime-card'].forEach(id => {
853
+ document.getElementById(id).style.display = loggedIn ? '' : 'none';
854
+ });
855
+
856
+ // Charts lock
857
+ document.getElementById('charts-lock').style.display = loggedIn ? 'none' : 'flex';
858
+
859
+ // Filter row
860
+ document.getElementById('filter-row').style.display = loggedIn ? 'flex' : 'none';
861
+
862
+ renderTable();
863
+ if (loggedIn) renderMyStats();
864
+ }
865
+
866
+ // ── LOGIN / REGISTER MODAL ─────────────────────────────────────────
867
+ function openLoginModal() {
868
+ document.getElementById('login-modal').classList.add('active');
869
+ setTimeout(() => {
870
+ if(isLoginMode) document.getElementById('login-email').focus();
871
+ else document.getElementById('login-username').focus();
872
+ }, 300);
873
+ }
874
+
875
+ function closeLoginModal() {
876
+ document.getElementById('login-modal').classList.remove('active');
877
+ }
878
+
879
+ document.getElementById('login-modal').addEventListener('click', e => {
880
+ if (e.target === e.currentTarget) closeLoginModal();
881
+ });
882
+ document.addEventListener('keydown', e => {
883
+ if (e.key === 'Escape') closeLoginModal();
884
+ if (e.key === 'Enter' && document.getElementById('login-modal').classList.contains('active')) submitAuth();
885
+ });
886
+
887
+ function toggleAuthMode() {
888
+ isLoginMode = !isLoginMode;
889
+ const title = document.getElementById('modal-title-text');
890
+ const sub = document.getElementById('modal-sub-text');
891
+ const userGroup = document.getElementById('group-username');
892
+ const submitBtn = document.getElementById('login-submit');
893
+ const toggleText = document.getElementById('auth-toggle-text');
894
+ const toggleBtn = document.getElementById('auth-toggle-btn');
895
+ const errEl = document.getElementById('login-error');
896
+
897
+ errEl.style.display = 'none';
898
+
899
+ if (isLoginMode) {
900
+ title.textContent = 'SIGN IN';
901
+ sub.textContent = 'ACCESS FULL COMBAT ANALYTICS';
902
+ userGroup.style.display = 'none';
903
+ submitBtn.textContent = 'AUTHENTICATE';
904
+ toggleText.textContent = "Don't have clearance?";
905
+ toggleBtn.textContent = "REGISTER NEW OPERATOR";
906
+ } else {
907
+ title.textContent = 'ENLIST';
908
+ sub.textContent = 'CREATE YOUR OPERATOR PROFILE';
909
+ userGroup.style.display = 'block';
910
+ submitBtn.textContent = 'INITIALIZE ACCOUNT';
911
+ toggleText.textContent = "Already enlisted?";
912
+ toggleBtn.textContent = "SIGN IN HERE";
913
+ }
914
+ }
915
+
916
+ async function submitAuth() {
917
+ const email = document.getElementById('login-email').value.trim();
918
+ const password = document.getElementById('login-password').value;
919
+ const username = document.getElementById('login-username').value.trim();
920
+ const errEl = document.getElementById('login-error');
921
+ const btn = document.getElementById('login-submit');
922
+
923
+ errEl.style.display = 'none';
924
+
925
+ if (!email || !password) {
926
+ errEl.textContent = '⚠ EMAIL AND PASSWORD REQUIRED';
927
+ errEl.style.display = 'block';
928
+ return;
929
+ }
930
+ if (!isLoginMode && !username) {
931
+ errEl.textContent = '⚠ OPERATOR HANDLE REQUIRED';
932
+ errEl.style.display = 'block';
933
+ return;
934
+ }
935
+
936
+ btn.disabled = true;
937
+ btn.textContent = 'PROCESSING...';
938
+
939
+ try {
940
+ let res, data;
941
+
942
+ if (isLoginMode) {
943
+ res = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=password`, {
944
+ method: 'POST',
945
+ headers: { 'apikey': SUPABASE_KEY, 'Content-Type': 'application/json' },
946
+ body: JSON.stringify({ email, password })
947
+ });
948
+ data = await res.json();
949
+ if (!res.ok) throw new Error(data.error_description || data.msg || 'Login failed');
950
+
951
+ const userId = data.user?.id;
952
+ let fetchedUsername = data.user?.email;
953
+ try {
954
+ const statsRes = await fetch(
955
+ `${SUPABASE_URL}/rest/v1/player_stats?id=eq.${userId}&select=username`,
956
+ { headers: { 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${data.access_token}` } }
957
+ );
958
+ const statsData = await statsRes.json();
959
+ if (statsData?.[0]?.username) fetchedUsername = statsData[0].username;
960
+ } catch(_) {}
961
+
962
+ authUser = { id: userId, email: data.user?.email, username: fetchedUsername, token: data.access_token };
963
+
964
+ } else {
965
+ res = await fetch(`${SUPABASE_URL}/auth/v1/signup`, {
966
+ method: 'POST',
967
+ headers: { 'apikey': SUPABASE_KEY, 'Content-Type': 'application/json' },
968
+ body: JSON.stringify({ email, password })
969
+ });
970
+ data = await res.json();
971
+ if (!res.ok) throw new Error(data.error_description || data.msg || 'Registration failed');
972
+
973
+ const userId = data.user?.id;
974
+ const token = data.session?.access_token || SUPABASE_KEY;
975
+
976
+ const createRes = await fetch(`${SUPABASE_URL}/rest/v1/player_stats`, {
977
+ method: 'POST',
978
+ headers: {
979
+ 'apikey': SUPABASE_KEY,
980
+ 'Authorization': `Bearer ${token}`,
981
+ 'Content-Type': 'application/json',
982
+ 'Prefer': 'return=minimal'
983
+ },
984
+ body: JSON.stringify({
985
+ id: userId,
986
+ username: username,
987
+ rank_points: 1000,
988
+ kills: 0,
989
+ deaths: 0,
990
+ gold: 0,
991
+ headshots: 0,
992
+ level: 1,
993
+ equipped_weapon: 'Pistol',
994
+ equipped_armor: 'None'
995
+ })
996
+ });
997
+
998
+ if (!createRes.ok) {
999
+ console.warn("Failed to create player_stats row. Check RLS policies.");
1000
+ }
1001
+
1002
+ authUser = { id: userId, email: data.user?.email, username: username, token: token };
1003
+ toggleAuthMode();
1004
+ }
1005
+
1006
+ closeLoginModal();
1007
+ applyAuthState();
1008
+ loadData();
1009
+
1010
+ } catch (e) {
1011
+ errEl.textContent = `⚠ ${e.message.toUpperCase()}`;
1012
+ errEl.style.display = 'block';
1013
+ } finally {
1014
+ btn.disabled = false;
1015
+ btn.textContent = isLoginMode ? 'AUTHENTICATE' : 'INITIALIZE ACCOUNT';
1016
+ }
1017
+ }
1018
+
1019
+ function doLogout() {
1020
+ authUser = null;
1021
+ applyAuthState();
1022
+ renderCharts();
1023
+ document.getElementById('login-email').value = '';
1024
+ document.getElementById('login-password').value = '';
1025
+ document.getElementById('login-username').value = '';
1026
+ }
1027
+
1028
+ // ── FETCH DATA ─────────────────────────────────────────────────────
1029
+ async function loadData() {
1030
+ document.getElementById('last-refresh').textContent = 'LOADING...';
1031
+ try {
1032
+ const res = await fetch(
1033
+ `${SUPABASE_URL}/rest/v1/player_stats?select=*&order=rank_points.desc`,
1034
+ { headers: { 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${SUPABASE_KEY}` } }
1035
+ );
1036
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1037
+ players = await res.json();
1038
+ players = players.map(p => ({
1039
+ ...p,
1040
+ kd: p.deaths > 0 ? (p.kills / p.deaths) : p.kills,
1041
+ win_rate: (p.wins + p.losses) > 0 ? (p.wins / (p.wins + p.losses) * 100) : 0
1042
+ }));
1043
+
1044
+ renderKPIs();
1045
+ renderCharts();
1046
+ populateFilters();
1047
+ renderTable();
1048
+ if (isLoggedIn()) renderMyStats();
1049
+
1050
+ const now = new Date();
1051
+ document.getElementById('last-refresh').textContent = `LAST UPDATE: ${now.toLocaleTimeString()}`;
1052
+ document.getElementById('error-msg').style.display = 'none';
1053
+ } catch (e) {
1054
+ const em = document.getElementById('error-msg');
1055
+ em.style.display = 'block';
1056
+ em.textContent = `⚠ DATABASE CONNECTION ERROR: ${e.message}`;
1057
+ document.getElementById('last-refresh').textContent = 'ERROR';
1058
+ } finally {
1059
+ const ol = document.getElementById('loading-overlay');
1060
+ ol.classList.add('hidden');
1061
+ setTimeout(() => ol.style.display = 'none', 500);
1062
+ }
1063
+ }
1064
+
1065
+ function fmt(n) {
1066
+ if (n >= 1000000) return (n/1000000).toFixed(1)+'M';
1067
+ if (n >= 1000) return (n/1000).toFixed(1)+'K';
1068
+ return n.toLocaleString();
1069
+ }
1070
+
1071
+ // ── KPIs ───────────────────────────────────────────────────────────
1072
+ function renderKPIs() {
1073
+ const totalKills = players.reduce((a,p) => a + (p.kills||0), 0);
1074
+ const totalGold = players.reduce((a,p) => a + (p.gold||0), 0);
1075
+ const totalHS = players.reduce((a,p) => a + (p.headshots||0), 0);
1076
+ const totalPlay = players.reduce((a,p) => a + (parseFloat(p.playtime_hours)||0), 0);
1077
+ const top = players[0];
1078
+ const topWR = [...players].sort((a,b) => b.win_rate - a.win_rate)[0];
1079
+
1080
+ document.getElementById('kpi-players').textContent = players.length;
1081
+ document.getElementById('kpi-gold').textContent = fmt(totalGold);
1082
+ document.getElementById('kpi-kills').textContent = fmt(totalKills);
1083
+ document.getElementById('kpi-headshots').textContent = fmt(totalHS);
1084
+ document.getElementById('kpi-playtime').textContent = Math.round(totalPlay).toLocaleString();
1085
+
1086
+ if (top) {
1087
+ document.getElementById('kpi-top-rank').textContent = fmt(top.rank_points);
1088
+ document.getElementById('kpi-top-player').textContent = top.username || 'β€”';
1089
+ }
1090
+ if (topWR) {
1091
+ document.getElementById('kpi-top-winrate').textContent = topWR.win_rate.toFixed(0) + '%';
1092
+ document.getElementById('kpi-top-winrate-player').textContent = topWR.username || 'β€”';
1093
+ }
1094
+ }
1095
+
1096
+ // ── MY STATS ───────────────────────────────────────────────────────
1097
+ function renderMyStats() {
1098
+ if (!isLoggedIn()) return;
1099
+ const me = players.find(p => p.id === authUser.id);
1100
+ if (!me) return;
1101
+
1102
+ const [tier] = getTier(me.rank_points);
1103
+ const grid = document.getElementById('my-stats-grid');
1104
+ grid.innerHTML = [
1105
+ { val: (me.rank_points||0).toLocaleString(), lbl: 'RANK PTS' },
1106
+ { val: tier, lbl: 'TIER' },
1107
+ { val: (me.kills||0).toLocaleString(), lbl: 'KILLS' },
1108
+ { val: me.kd.toFixed(2), lbl: 'K/D' },
1109
+ { val: (me.headshots||0).toLocaleString(), lbl: 'HEADSHOTS' },
1110
+ { val: (me.gold||0).toLocaleString(), lbl: 'GOLD' },
1111
+ { val: `LVL ${me.level||1}`, lbl: 'LEVEL' },
1112
+ { val: me.win_rate.toFixed(0) + '%', lbl: 'WIN RATE' },
1113
+ ].map(s => `
1114
+ <div class="my-stat">
1115
+ <div class="my-stat-val">${s.val}</div>
1116
+ <div class="my-stat-lbl">${s.lbl}</div>
1117
+ </div>
1118
+ `).join('');
1119
+ }
1120
+
1121
+ // ── TIER ───────────────────────────────────────────────────────────
1122
+ function getTier(pts) {
1123
+ if (pts >= 3000) return ['DIAMOND', 'tier-diamond'];
1124
+ if (pts >= 2500) return ['PLATINUM', 'tier-platinum'];
1125
+ if (pts >= 1800) return ['GOLD', 'tier-gold'];
1126
+ if (pts >= 1200) return ['SILVER', 'tier-silver'];
1127
+ return ['BRONZE', 'tier-bronze'];
1128
+ }
1129
+
1130
+ // ── CHARTS ─────────────────────────────────────────────────────────
1131
+ const C = {
1132
+ cyan:'#00d4ff', red:'#ff4444', gold:'#ffd700',
1133
+ green:'#39ff14', blue:'#4488ff', purple:'#b044ff', orange:'#ff8800',
1134
+ };
1135
+
1136
+ function destroyAll() { Object.values(charts).forEach(c => c.destroy()); charts = {}; }
1137
+
1138
+ function renderCharts() {
1139
+ destroyAll();
1140
+ if (!isLoggedIn()) return;
1141
+
1142
+ Chart.defaults.color = '#4a7a9b';
1143
+ Chart.defaults.font.family = "'Share Tech Mono', monospace";
1144
+
1145
+ const top5 = players.slice(0, 5);
1146
+ const top8 = players.slice(0, 8);
1147
+
1148
+ charts.rank = new Chart(document.getElementById('rankChart').getContext('2d'), {
1149
+ type: 'bar',
1150
+ data: {
1151
+ labels: top5.map(p => p.username),
1152
+ datasets: [{ label: 'Rank Points', data: top5.map(p => p.rank_points),
1153
+ backgroundColor: top5.map((_,i) => i===0?C.gold:i===1?C.cyan:C.blue+'99'),
1154
+ borderWidth: 0, borderRadius: 2 }]
1155
+ },
1156
+ options: { responsive:true, maintainAspectRatio:true, plugins:{legend:{display:false}},
1157
+ scales:{ x:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}},
1158
+ y:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}} } }
1159
+ });
1160
+
1161
+ const weaponMap = {};
1162
+ players.forEach(p => { const w = p.equipped_weapon||'Unknown'; weaponMap[w]=(weaponMap[w]||0)+1; });
1163
+ const wLabels = Object.keys(weaponMap), wData = Object.values(weaponMap);
1164
+ const wColors = [C.cyan,C.red,C.gold,C.green,C.blue,C.purple,C.orange];
1165
+
1166
+ charts.weapon = new Chart(document.getElementById('weaponChart').getContext('2d'), {
1167
+ type: 'doughnut',
1168
+ data: { labels: wLabels, datasets:[{ data:wData, backgroundColor:wColors.slice(0,wLabels.length),
1169
+ borderColor:'#060a0f', borderWidth:3 }] },
1170
+ options: { responsive:true, maintainAspectRatio:true, cutout:'65%',
1171
+ plugins:{ legend:{ position:'right', labels:{color:'#cee8ff',font:{size:10},padding:10,boxWidth:12} } } }
1172
+ });
1173
+
1174
+ charts.kd = new Chart(document.getElementById('kdChart').getContext('2d'), {
1175
+ type: 'bar',
1176
+ data: { labels: top8.map(p=>p.username), datasets:[{ label:'K/D Ratio',
1177
+ data:top8.map(p=>+p.kd.toFixed(2)),
1178
+ backgroundColor:top8.map(p=>p.kd>=3?C.gold+'cc':p.kd>=2?C.green+'cc':p.kd>=1?C.cyan+'cc':C.red+'cc'),
1179
+ borderWidth:0, borderRadius:2 }] },
1180
+ options: { responsive:true, maintainAspectRatio:true, plugins:{legend:{display:false}},
1181
+ scales:{ x:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}},
1182
+ y:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}} } }
1183
+ });
1184
+
1185
+ const top5gold = [...players].sort((a,b)=>b.gold-a.gold).slice(0,5);
1186
+ charts.gold = new Chart(document.getElementById('goldChart').getContext('2d'), {
1187
+ type: 'bar',
1188
+ data: { labels:top5gold.map(p=>p.username), datasets:[{ label:'Gold',
1189
+ data:top5gold.map(p=>p.gold), backgroundColor:C.gold+'bb', borderWidth:0, borderRadius:2 }] },
1190
+ options: { indexAxis:'y', responsive:true, maintainAspectRatio:true, plugins:{legend:{display:false}},
1191
+ scales:{ x:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}},
1192
+ y:{grid:{display:false},ticks:{color:'#cee8ff',font:{size:11}}} } }
1193
+ });
1194
+
1195
+ const winData = [...players].sort((a,b)=>b.win_rate-a.win_rate);
1196
+ charts.win = new Chart(document.getElementById('winChart').getContext('2d'), {
1197
+ type: 'bar',
1198
+ data: { labels:winData.map(p=>p.username), datasets:[
1199
+ { label:'Wins', data:winData.map(p=>p.wins), backgroundColor:C.green+'bb', borderWidth:0, borderRadius:2 },
1200
+ { label:'Losses', data:winData.map(p=>p.losses), backgroundColor:C.red+'bb', borderWidth:0, borderRadius:2 }
1201
+ ]},
1202
+ options: { responsive:true, maintainAspectRatio:true,
1203
+ plugins:{ legend:{labels:{color:'#cee8ff',font:{size:10},boxWidth:10,padding:8}} },
1204
+ scales:{
1205
+ x:{stacked:true,grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:9},maxRotation:35}},
1206
+ y:{stacked:true,grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}}
1207
+ }
1208
+ }
1209
+ });
1210
+ }
1211
+
1212
+ // ── FILTERS ────────────────────────────────────────────────────────
1213
+ function populateFilters() {
1214
+ const weapons = [...new Set(players.map(p=>p.equipped_weapon).filter(Boolean))];
1215
+ const sel = document.getElementById('filter-weapon');
1216
+ sel.innerHTML = '<option value="">ALL WEAPONS</option>';
1217
+ weapons.forEach(w => { const o=document.createElement('option'); o.value=w; o.textContent=w; sel.appendChild(o); });
1218
+ }
1219
+
1220
+ // ── TABLE ──────────────────────────────────────────────────────────
1221
+ function renderTable() {
1222
+ const loggedIn = isLoggedIn();
1223
+ const head = document.getElementById('leaderboard-head');
1224
+
1225
+ if (loggedIn) {
1226
+ head.innerHTML = `<tr>
1227
+ <th>#</th><th>PLAYER</th><th>TIER</th><th>RANK PTS</th>
1228
+ <th>K / D</th><th>HEADSHOTS</th><th>WEAPON</th><th>ARMOR</th>
1229
+ <th>GOLD πŸͺ™</th><th>LEVEL</th><th>WIN RATE</th><th>PLAYTIME</th>
1230
+ </tr>`;
1231
+ } else {
1232
+ head.innerHTML = `<tr>
1233
+ <th>#</th><th>PLAYER</th><th>TIER</th><th>RANK PTS</th>
1234
+ <th>GOLD πŸͺ™</th><th>WIN RATE</th>
1235
+ </tr>`;
1236
+ }
1237
+
1238
+ const weaponFilter = loggedIn ? document.getElementById('filter-weapon').value : '';
1239
+ const sortKey = loggedIn ? document.getElementById('sort-by').value : 'rank_points';
1240
+
1241
+ let data = [...players];
1242
+ if (weaponFilter) data = data.filter(p => p.equipped_weapon === weaponFilter);
1243
+ data.sort((a,b) => sortKey==='kd' ? b.kd-a.kd : (b[sortKey]||0)-(a[sortKey]||0));
1244
+
1245
+ if (!loggedIn) data = data.slice(0, 10);
1246
+
1247
+ const tbody = document.getElementById('leaderboard-body');
1248
+ tbody.innerHTML = '';
1249
+
1250
+ data.forEach((p, i) => {
1251
+ const rank = i + 1;
1252
+ const [tier, tierClass] = getTier(p.rank_points);
1253
+ const rankBadgeCls = rank===1?'rank-1':rank===2?'rank-2':rank===3?'rank-3':'rank-other';
1254
+ const isOwn = loggedIn && authUser && p.id === authUser.id;
1255
+
1256
+ const tr = document.createElement('tr');
1257
+ if (isOwn) tr.classList.add('own-row');
1258
+
1259
+ if (loggedIn) {
1260
+ const kd = p.kd.toFixed(2);
1261
+ const kdCls = p.kd>=3?'kd-good':p.kd>=1?'kd-ok':'kd-bad';
1262
+ const armorCls = (!p.equipped_armor||p.equipped_armor==='None')?'armor-none':'';
1263
+ tr.innerHTML = `
1264
+ <td><span class="rank-badge ${rankBadgeCls}">${rank}</span></td>
1265
+ <td><strong style="color:${isOwn?'var(--accent)':'#cee8ff'}">${p.username||'β€”'}${isOwn?' β—€ YOU':''}</strong></td>
1266
+ <td><span class="tier ${tierClass}">${tier}</span></td>
1267
+ <td style="color:#00d4ff;font-family:'Orbitron',sans-serif;font-size:13px">${(p.rank_points||0).toLocaleString()}</td>
1268
+ <td class="${kdCls}">${kd}</td>
1269
+ <td style="color:#39ff14">${(p.headshots||0).toLocaleString()}</td>
1270
+ <td><span class="weapon-tag">${p.equipped_weapon||'β€”'}</span></td>
1271
+ <td><span class="armor-tag ${armorCls}">${p.equipped_armor||'None'}</span></td>
1272
+ <td><span class="gold-val">⬑ ${(p.gold||0).toLocaleString()}</span></td>
1273
+ <td style="color:#b044ff;font-family:'Orbitron',sans-serif;font-size:12px">LVL ${p.level||1}</td>
1274
+ <td>${p.win_rate.toFixed(0)}%</td>
1275
+ <td style="color:#4a7a9b">${parseFloat(p.playtime_hours||0).toFixed(1)}h</td>
1276
+ `;
1277
+ } else {
1278
+ tr.innerHTML = `
1279
+ <td><span class="rank-badge ${rankBadgeCls}">${rank}</span></td>
1280
+ <td><strong style="color:#cee8ff">${p.username||'β€”'}</strong></td>
1281
+ <td><span class="tier ${tierClass}">${tier}</span></td>
1282
+ <td style="color:#00d4ff;font-family:'Orbitron',sans-serif;font-size:13px">${(p.rank_points||0).toLocaleString()}</td>
1283
+ <td><span class="gold-val">⬑ ${(p.gold||0).toLocaleString()}</span></td>
1284
+ <td>${p.win_rate.toFixed(0)}%</td>
1285
+ `;
1286
+ }
1287
+ tbody.appendChild(tr);
1288
+ });
1289
+
1290
+ if (!loggedIn && players.length > 10) {
1291
+ const tr = document.createElement('tr');
1292
+ tr.innerHTML = `<td colspan="6" style="text-align:center;color:var(--text-dim);padding:16px;border-top:1px solid var(--border)">
1293
+ <span style="color:var(--accent)">+${players.length - 10} more players</span> β€”
1294
+ <button onclick="openLoginModal()" style="background:none;border:none;color:var(--accent);cursor:pointer;font-family:'Share Tech Mono',monospace;font-size:12px;letter-spacing:1px;text-decoration:underline">Login to see full leaderboard</button>
1295
+ </td>`;
1296
+ tbody.appendChild(tr);
1297
+ }
1298
+
1299
+ if (data.length === 0) {
1300
+ const tr = document.createElement('tr');
1301
+ tr.innerHTML = `<td colspan="${loggedIn?12:6}" style="text-align:center;color:var(--text-dim);padding:24px">NO PLAYERS FOUND</td>`;
1302
+ tbody.appendChild(tr);
1303
+ }
1304
+ }
1305
+
1306
+ // ── INIT ───────────────────────────────────────────────────────────
1307
+ applyAuthState();
1308
+ loadData();
1309
+ </script>
1310
+ </body>
1311
+ </html>