Barkmaster commited on
Commit
272727a
Β·
verified Β·
1 Parent(s): e64d122

Create index.md

Browse files
Files changed (1) hide show
  1. index.md +1113 -0
index.md ADDED
@@ -0,0 +1,1113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 β€” COMMAND CENTER</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></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
+ --red: #ff4444;
17
+ --gold: #ffd700;
18
+ --green: #39ff14;
19
+ --purple: #b044ff;
20
+ --text: #cee8ff;
21
+ --dim: #4a7a9b;
22
+ --glow: 0 0 20px rgba(0,212,255,.35);
23
+ --glow-red: 0 0 20px rgba(255,68,68,.35);
24
+ }
25
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
26
+ body{background:var(--bg);color:var(--text);font-family:'Share Tech Mono',monospace;min-height:100vh;overflow-x:hidden}
27
+ body::after{content:'';position:fixed;inset:0;background:repeating-linear-gradient(0deg,rgba(0,0,0,.03) 0,rgba(0,0,0,.03) 1px,transparent 1px,transparent 2px);pointer-events:none;z-index:9999}
28
+
29
+ /* ── GLOBAL ── */
30
+ .page{display:none;min-height:100vh}
31
+ .page.active{display:block}
32
+ .btn{font-family:'Share Tech Mono',monospace;font-size:12px;letter-spacing:2px;padding:8px 20px;cursor:pointer;transition:all .2s;border:0}
33
+ .btn-primary{background:var(--accent);color:var(--bg);border:none}
34
+ .btn-primary:hover{box-shadow:var(--glow);filter:brightness(1.1)}
35
+ .btn-primary:disabled{opacity:.45;cursor:not-allowed}
36
+ .btn-outline{background:transparent;border:1px solid var(--accent);color:var(--accent)}
37
+ .btn-outline:hover{background:var(--accent);color:var(--bg)}
38
+ .btn-danger{background:transparent;border:1px solid var(--red);color:var(--red)}
39
+ .btn-danger:hover{background:var(--red);color:#fff}
40
+ .btn-ghost{background:transparent;border:1px solid var(--border);color:var(--dim)}
41
+ .btn-ghost:hover{border-color:var(--accent);color:var(--accent)}
42
+ .form-group{margin-bottom:20px}
43
+ .form-group label{display:block;font-size:10px;letter-spacing:3px;color:var(--dim);text-transform:uppercase;margin-bottom:7px}
44
+ .form-group input,.form-group select{width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--text);font-family:'Share Tech Mono',monospace;font-size:14px;padding:10px 14px;outline:none;transition:border-color .2s}
45
+ .form-group input:focus,.form-group select:focus{border-color:var(--accent);box-shadow:0 0 0 1px rgba(0,212,255,.15)}
46
+ .form-group input.error{border-color:var(--red)}
47
+ .alert{padding:11px 16px;font-size:12px;letter-spacing:1px;margin-bottom:18px;display:none}
48
+ .alert.error{background:rgba(255,68,68,.1);border:1px solid var(--red);color:var(--red);display:block}
49
+ .alert.success{background:rgba(57,255,20,.1);border:1px solid var(--green);color:var(--green);display:block}
50
+ .alert.info{background:rgba(0,212,255,.08);border:1px solid rgba(0,212,255,.3);color:var(--accent);display:block}
51
+ .card{background:var(--bg2);border:1px solid var(--border);padding:24px;position:relative}
52
+ .card::before,.card::after{content:'';position:absolute;width:12px;height:12px;border-color:var(--accent);border-style:solid}
53
+ .card::before{top:0;left:0;border-width:2px 0 0 2px}
54
+ .card::after{bottom:0;right:0;border-width:0 2px 2px 0}
55
+ .section-title{font-family:'Orbitron',sans-serif;font-size:11px;font-weight:600;letter-spacing:4px;color:var(--dim);text-transform:uppercase;margin-bottom:16px;display:flex;align-items:center;gap:10px}
56
+ .section-title::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--border),transparent)}
57
+ .live-dot{width:7px;height:7px;border-radius:50%;background:var(--green);animation:blink 1.2s ease-in-out infinite;box-shadow:0 0 6px var(--green);display:inline-block}
58
+ @media(max-width:900px){.auth-body{grid-template-columns:1fr}}
59
+ @keyframes blink{0%,100%{opacity:1}50%{opacity:.2}}
60
+ @keyframes pulse{0%,100%{box-shadow:var(--glow)}50%{box-shadow:0 0 35px rgba(0,212,255,.7)}}
61
+ @keyframes spin{to{transform:rotate(360deg)}}
62
+ @keyframes fadeIn{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
63
+ .fade-in{animation:fadeIn .4s ease forwards}
64
+ .spinner{width:40px;height:40px;background:var(--accent);clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);animation:spin .9s linear infinite}
65
+
66
+ /* ── NAV ── */
67
+ .nav{background:linear-gradient(135deg,#060f1a,#0d1f33);border-bottom:2px solid var(--accent);padding:14px 28px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:200;box-shadow:0 4px 30px rgba(0,212,255,.15)}
68
+ .nav-brand{display:flex;align-items:center;gap:14px;cursor:pointer}
69
+ .nav-hex{width:38px;height:38px;background:var(--accent);clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);display:flex;align-items:center;justify-content:center;font-size:17px;animation:pulse 2.5s ease-in-out infinite}
70
+ .nav-title{font-family:'Orbitron',sans-serif;font-size:18px;font-weight:900;letter-spacing:4px;color:var(--accent);text-shadow:var(--glow)}
71
+ .nav-sub{font-size:10px;color:var(--dim);letter-spacing:2px}
72
+ .nav-right{display:flex;align-items:center;gap:14px}
73
+ .nav-badge{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--green)}
74
+
75
+ /* ── LOGIN PAGE ── */
76
+ .auth-page{min-height:100vh;display:flex;flex-direction:column}
77
+ .auth-body{flex:1;display:grid;grid-template-columns:1fr 1fr;min-height:calc(100vh - 70px)}
78
+ @media(max-width:900px){.auth-body{grid-template-columns:1fr}}
79
+ .auth-hero{background:linear-gradient(135deg,#060f1a 0%,#0d1f33 60%,#091828 100%);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 40px;position:relative;overflow:hidden}
80
+ .auth-hero::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at 50% 0%,rgba(0,212,255,.12),transparent 70%)}
81
+ .hero-icon{font-size:80px;margin-bottom:28px;filter:drop-shadow(0 0 20px rgba(0,212,255,.5))}
82
+ .hero-title{font-family:'Orbitron',sans-serif;font-size:32px;font-weight:900;letter-spacing:5px;color:var(--accent);text-shadow:var(--glow);text-align:center;margin-bottom:12px}
83
+ .hero-sub|{font-size:13px;color:var(--dim);letter-spacing:2px;text-align:center;max-width:320px;line-height:1.8;margin-bottom:36px}
84
+ .hero-stats{display:grid;grid-template-columns:1fr 1fr;gap:12px;width:100%;max-width:280px}
85
+ .hero-stat{background:rgba(0,212,255,.05);border:1px solid rgba(0,212,255,.15);padding:12px 16px;text-align:center}
86
+ .hero-stat-val{font-family:'Orbitron',sans-serif;font-size:22px;font-weight:700;color:var(--accent)}
87
+ .hero-stat-lbl{font-size:10px;color:var(--dim);letter-spacing:2px;margin-top:3px}
88
+ .auth-form-panel{background:var(--bg);display:flex;align-items:center;justify-content:center;padding:60px 40px}
89
+ .auth-form-box{width:100%;max-width:420px}
90
+ .auth-form-title{font-family:'Orbitron',sans-serif;font-size:22px;font-weight:700;letter-spacing:4px;color:var(--accent);text-shadow:var(--glow);margin-bottom:6px}
91
+ .auth-form-sub{font-size:12px;color:var(--dim);letter-spacing:2px;margin-bottom:30px}
92
+ .auth-link{color:var(--accent);cursor:pointer;text-decoration:underline;font-size:12px;letter-spacing:1px}
93
+ .auth-link:hover{color:#fff}
94
+ .auth-divider{text-align:center;color:var(--dim);font-size:11px;letter-spacing:2px;margin:20px 0;position:relative}
95
+ .auth-divider::before,.auth-divider::after{content:'';position:absolute;top:50%;width:42%;height:1px;background:var(--border)}
96
+ .auth-divider::before{left:0}
97
+ .auth-divider::after{right:0}
98
+
99
+ /* ── TEST ACCOUNTS PANEL ── */
100
+ .test-panel{background:rgba(0,212,255,.04);border:1px solid rgba(0,212,255,.2);border-left:3px solid var(--accent);padding:16px 18px;margin-top:22px}
101
+ .test-panel-title{font-size:10px;letter-spacing:3px;color:var(--accent);margin-bottom:12px;text-transform:uppercase}
102
+ .test-account{display:flex;align-items:center;justify-content:space-between;padding:7px 0;border-bottom:1px solid rgba(30,48,72,.5);cursor:pointer;transition:background .15s}
103
+ .test-account:last-child{border-bottom:none}
104
+ .test-account:hover .ta-email{color:var(--accent)}
105
+ .ta-user{font-size:12px;color:var(--text)}
106
+ .ta-email{font-size:11px;color:var(--dim);transition:color .15s}
107
+ .ta-use{font-size:10px;color:var(--accent);letter-spacing:1px;opacity:0;transition:opacity .15s}
108
+ .test-account:hover .ta-use{opacity:1}
109
+ .test-note{font-size:10px;color:var(--dim);margin-top:10px;line-height:1.7;border-top:1px solid var(--border);padding-top:10px}
110
+ .test-note .warn{color:var(--gold)}
111
+
112
+ /* ── DASHBOARD PAGE ── */
113
+ .dash-body{padding:24px 28px;max-width:1600px;margin:0 auto}
114
+ .kpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(175px,1fr));gap:14px;margin-bottom:28px}
115
+ .kpi-card{background:var(--bg2);border:1px solid var(--border);padding:18px 20px;position:relative;overflow:hidden;transition:border-color .3s,box-shadow .3s}
116
+ .kpi-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px}
117
+ .kpi-card.c::before{background:var(--accent);box-shadow:var(--glow)}
118
+ .kpi-card.r::before{background:var(--red);box-shadow:var(--glow-red)}
119
+ .kpi-card.g::before{background:var(--gold);box-shadow:0 0 18px rgba(255,215,0,.35)}
120
+ .kpi-card.e::before{background:var(--green)}
121
+ .kpi-card:hover{border-color:var(--accent);box-shadow:var(--glow)}
122
+ .kpi-icon{font-size:20px;margin-bottom:8px}
123
+ .kpi-label{font-size:10px;letter-spacing:2px;color:var(--dim);text-transform:uppercase;margin-bottom:5px}
124
+ .kpi-value{font-family:'Orbitron',sans-serif;font-size:24px;font-weight:700;line-height:1}
125
+ .kpi-card.c .kpi-value{color:var(--accent)}
126
+ .kpi-card.r .kpi-value{color:var(--red)}
127
+ .kpi-card.g .kpi-value{color:var(--gold)}
128
+ .kpi-card.e .kpi-value{color:var(--green)}
129
+ .kpi-sub{font-size:11px;color:var(--dim);margin-top:4px}
130
+ .charts-row{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;margin-bottom:28px}
131
+ @media(max-width:1100px){.charts-row{grid-template-columns:1fr 1fr}}
132
+ @media(max-width:700px){.charts-row{grid-template-columns:1fr}}
133
+ .chart-card{background:var(--bg2);border:1px solid var(--border);padding:20px;position:relative}
134
+ .chart-card::before,.chart-card::after{content:'';position:absolute;width:10px;height:10px;border-color:var(--accent);border-style:solid}
135
+ .chart-card::before{top:0;left:0;border-width:2px 0 0 2px}
136
+ .chart-card::after{bottom:0;right:0;border-width:0 2px 2px 0}
137
+ .chart-card h3{font-family:'Orbitron',sans-serif;font-size:10px;font-weight:600;letter-spacing:3px;color:var(--dim);text-transform:uppercase;margin-bottom:16px}
138
+ .chart-card canvas{max-height:200px}
139
+ .table-card{background:var(--bg2);border:1px solid var(--border);padding:20px;margin-bottom:28px;overflow-x:auto;position:relative}
140
+ .table-card::before,.table-card::after{content:'';position:absolute;width:10px;height:10px;border-color:var(--accent);border-style:solid}
141
+ .table-card::before{top:0;left:0;border-width:2px 0 0 2px}
142
+ .table-card::after{bottom:0;right:0;border-width:0 2px 2px 0}
143
+ .table-card h3{font-family:'Orbitron',sans-serif;font-size:10px;font-weight:600;letter-spacing:3px;color:var(--dim);text-transform:uppercase;margin-bottom:16px}
144
+ .data-table{width:100%;border-collapse:collapse;font-size:13px}
145
+ .data-table thead th{text-align:left;padding:9px 13px;border-bottom:1px solid var(--border);color:var(--dim);font-size:10px;letter-spacing:2px;text-transform:uppercase;white-space:nowrap;cursor:pointer;user-select:none}
146
+ .data-table thead th:hover{color:var(--accent)}
147
+ .data-table tbody td{padding:9px 13px;border-bottom:1px solid rgba(30,48,72,.5);white-space:nowrap}
148
+ .data-table tbody tr:hover{background:rgba(0,212,255,.04)}
149
+ .data-table tbody tr:last-child td{border-bottom:none}
150
+ .data-table tbody tr.own-row{background:rgba(0,212,255,.06);border-left:3px solid var(--accent)}
151
+ .rank-badge{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700}
152
+ .r1{background:var(--gold);color:#000}
153
+ .r2{background:#aaa;color:#000}
154
+ .r3{background:#cd7f32;color:#000}
155
+ .rn{background:var(--bg3);color:var(--dim)}
156
+ .tier{display:inline-block;padding:2px 9px;font-size:10px;font-family:'Orbitron',sans-serif;letter-spacing:1px}
157
+ .td{background:rgba(0,212,255,.15);color:var(--accent);border:1px solid var(--accent)}
158
+ .tp{background:rgba(200,200,255,.1);color:#b0c4de;border:1px solid #b0c4de}
159
+ .tg{background:rgba(255,215,0,.1);color:var(--gold);border:1px solid var(--gold)}
160
+ .ts{background:rgba(170,170,170,.1);color:#aaa;border:1px solid #aaa}
161
+ .tb{background:rgba(205,127,50,.1);color:#cd7f32;border:1px solid #cd7f32}
162
+ .weapon-tag{display:inline-block;padding:2px 8px;border:1px solid var(--border);font-size:11px;color:var(--accent)}
163
+ .armor-tag{display:inline-block;padding:2px 8px;border:1px solid var(--border);font-size:11px;color:var(--gold)}
164
+ .kd-g{color:var(--green)}.kd-o{color:var(--accent)}.kd-b{color:var(--red)}
165
+ .gold-val{color:var(--gold);font-family:'Orbitron',sans-serif;font-size:12px}
166
+ .filter-row{display:flex;gap:12px;align-items:center;margin-bottom:18px;flex-wrap:wrap}
167
+ .filter-row label{font-size:11px;color:var(--dim);letter-spacing:1px}
168
+ .filter-row select{background:var(--bg3);border:1px solid var(--border);color:var(--text);font-family:'Share Tech Mono',monospace;font-size:12px;padding:6px 12px;outline:none;cursor:pointer}
169
+ .filter-row select:focus{border-color:var(--accent)}
170
+ .anon-banner{background:rgba(0,212,255,.05);border:1px solid rgba(0,212,255,.2);border-left:3px solid var(--accent);padding:13px 18px;display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:22px;flex-wrap:wrap}
171
+ .anon-text{font-size:12px;color:var(--dim);letter-spacing:1px}
172
+ .anon-text span{color:var(--accent)}
173
+ .locked-section{position:relative;overflow:hidden}
174
+ .locked-overlay{position:absolute;inset:0;background:rgba(6,10,15,.87);backdrop-filter:blur(3px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:13px;z-index:10}
175
+ .locked-icon{font-size:30px}
176
+ .locked-text{font-family:'Orbitron',sans-serif;font-size:11px;letter-spacing:3px;color:var(--dim);text-align:center}
177
+
178
+ /* ── PROFILE PAGE ── */
179
+ .profile-body{padding:28px;max-width:1200px;margin:0 auto}
180
+ .profile-header{background:linear-gradient(135deg,#060f1a,#0d1f33);border:1px solid var(--border);border-top:3px solid var(--accent);padding:30px 32px;margin-bottom:28px;display:flex;align-items:center;gap:28px;flex-wrap:wrap;position:relative;overflow:hidden}
181
+ .profile-header::after{content:'';position:absolute;top:0;right:0;width:300px;height:100%;background:radial-gradient(ellipse at right,rgba(0,212,255,.07),transparent 70%)}
182
+ .profile-avatar{width:72px;height:72px;background:var(--accent);clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);display:flex;align-items:center;justify-content:center;font-family:'Orbitron',sans-serif;font-size:24px;font-weight:900;color:var(--bg);flex-shrink:0}
183
+ .profile-info{flex:1}
184
+ .profile-username{font-family:'Orbitron',sans-serif;font-size:24px;font-weight:700;color:var(--accent);letter-spacing:3px;text-shadow:var(--glow);margin-bottom:4px}
185
+ .profile-email{font-size:12px;color:var(--dim);letter-spacing:1px;margin-bottom:8px}
186
+ .profile-badges{display:flex;gap:8px;flex-wrap:wrap}
187
+ .profile-grid{display:grid;grid-template-columns:2fr 1fr;gap:24px}
188
+ @media(max-width:900px){.profile-grid{grid-template-columns:1fr}}
189
+ .profile-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:14px;margin-bottom:24px}
190
+ .pstat{background:var(--bg3);border:1px solid var(--border);padding:14px 16px;text-align:center;transition:border-color .2s}
191
+ .pstat:hover{border-color:var(--accent)}
192
+ .pstat-val{font-family:'Orbitron',sans-serif;font-size:20px;font-weight:700;color:var(--accent)}
193
+ .pstat-lbl{font-size:10px;color:var(--dim);letter-spacing:2px;margin-top:3px}
194
+ .edit-section{margin-bottom:24px}
195
+ .edit-section-title{font-family:'Orbitron',sans-serif;font-size:11px;letter-spacing:3px;color:var(--dim);text-transform:uppercase;margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
196
+ .edit-row{display:flex;gap:10px;align-items:flex-end}
197
+ .edit-row .form-group{flex:1;margin-bottom:0}
198
+ .edit-row .btn{white-space:nowrap;padding:10px 16px}
199
+ .danger-zone{background:rgba(255,68,68,.04);border:1px solid rgba(255,68,68,.25);border-left:3px solid var(--red);padding:20px}
200
+ .danger-title{font-family:'Orbitron',sans-serif;font-size:11px;letter-spacing:3px;color:var(--red);margin-bottom:14px}
201
+ .sidebar-card{background:var(--bg2);border:1px solid var(--border);padding:20px;margin-bottom:20px;position:relative}
202
+ .sidebar-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--accent)}
203
+ .sidebar-title{font-family:'Orbitron',sans-serif;font-size:10px;font-weight:600;letter-spacing:3px;color:var(--dim);margin-bottom:16px;text-transform:uppercase}
204
+ .stat-row{display:flex;justify-content:space-between;align-items:center;padding:7px 0;border-bottom:1px solid rgba(30,48,72,.4)}
205
+ .stat-row:last-child{border-bottom:none}
206
+ .stat-row-label{font-size:12px;color:var(--dim);letter-spacing:1px}
207
+ .stat-row-val{font-size:13px;color:var(--text);font-family:'Orbitron',sans-serif;font-size:12px}
208
+ .rank-bar{height:6px;background:var(--bg3);margin-top:8px;overflow:hidden}
209
+ .rank-bar-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--purple));transition:width 1s ease;border-radius:0}
210
+ .progress-label{display:flex;justify-content:space-between;font-size:10px;color:var(--dim);margin-top:5px}
211
+
212
+ /* loading */
213
+ #loading-screen{position:fixed;inset:0;background:var(--bg);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px;z-index:9998;transition:opacity .4s}
214
+ #loading-screen.hidden{opacity:0;pointer-events:none}
215
+ .loading-title{font-family:'Orbitron',sans-serif;font-size:13px;letter-spacing:4px;color:var(--accent)}
216
+
217
+ /* error banner */
218
+ .error-banner{background:rgba(255,68,68,.1);border:1px solid var(--red);color:var(--red);padding:12px 18px;font-size:12px;letter-spacing:1px;margin-bottom:18px;display:none}
219
+ </style>
220
+ </head>
221
+ <body>
222
+
223
+ <div id="loading-screen">
224
+ <div class="spinner"></div>
225
+ <div class="loading-title">CONNECTING TO WARZONE DATABASE...</div>
226
+ </div>
227
+
228
+ <div class="page" id="page-login">
229
+ <nav class="nav">
230
+ <div class="nav-brand" onclick="goto('login')">
231
+ <div class="nav-hex">⬑</div>
232
+ <div><div class="nav-title">WARZONE</div><div class="nav-sub">COMMAND CENTER</div></div>
233
+ </div>
234
+ <div class="nav-right">
235
+ <div class="nav-badge"><div class="live-dot"></div>&nbsp;LIVE</div>
236
+ </div>
237
+ </nav>
238
+
239
+ <div class="auth-body">
240
+ <div class="auth-hero">
241
+ <div class="hero-icon">🎯</div>
242
+ <div class="hero-title">OPERATOR ACCESS</div>
243
+ <div class="hero-sub">Sign in to access full combat analytics, personal stats, and your operator profile.</div>
244
+ <div class="hero-stats" id="login-hero-stats">
245
+ <div class="hero-stat"><div class="hero-stat-val" id="hs-players">β€”</div><div class="hero-stat-lbl">PLAYERS</div></div>
246
+ <div class="hero-stat"><div class="hero-stat-val" id="hs-kills">β€”</div><div class="hero-stat-lbl">KILLS</div></div>
247
+ <div class="hero-stat"><div class="hero-stat-val" id="hs-gold">β€”</div><div class="hero-stat-lbl">GOLD</div></div>
248
+ <div class="hero-stat"><div class="hero-stat-val" id="hs-top">β€”</div><div class="hero-stat-lbl">TOP RANK</div></div>
249
+ </div>
250
+ </div>
251
+
252
+ <div class="auth-form-panel">
253
+ <div class="auth-form-box fade-in">
254
+ <div class="auth-form-title">SIGN IN</div>
255
+ <div class="auth-form-sub">ENTER YOUR CREDENTIALS TO PROCEED</div>
256
+
257
+ <div id="login-alert" class="alert"></div>
258
+
259
+ <div class="form-group">
260
+ <label>Email Address</label>
261
+ <input type="email" id="login-email" placeholder="operator@warzone.gg" />
262
+ </div>
263
+ <div class="form-group">
264
+ <label>Password</label>
265
+ <input type="password" id="login-password" placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" onkeydown="if(event.key==='Enter')doLogin()" />
266
+ </div>
267
+ <button class="btn btn-primary" id="login-btn" onclick="doLogin()" style="width:100%;font-size:13px;padding:12px;font-family:'Orbitron',sans-serif;font-weight:700;letter-spacing:4px">AUTHENTICATE</button>
268
+
269
+ <div style="text-align:center;margin-top:18px;font-size:12px;color:var(--dim)">
270
+ No account? <span class="auth-link" onclick="goto('register')">Register here</span>
271
+ &nbsp;Β·&nbsp;
272
+ <span class="auth-link" onclick="goto('dashboard')">Browse as guest</span>
273
+ </div>
274
+
275
+ <div class="test-panel">
276
+ <div class="test-panel-title">βš— TEST ACCOUNTS β€” CLICK TO AUTO-FILL</div>
277
+ <div id="test-accounts-list"></div>
278
+ <div class="test-note">
279
+ <span class="warn">⚠ IMPORTANT:</span> These bot accounts need auth entries created in Supabase Auth dashboard first. Go to
280
+ <code style="color:var(--accent)">supabase.com β†’ Authentication β†’ Users β†’ Invite</code>
281
+ using the emails shown. Password for all bots: <code style="color:var(--green)">Warzone2024!</code>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+
289
+ <div class="page" id="page-register">
290
+ <nav class="nav">
291
+ <div class="nav-brand" onclick="goto('login')">
292
+ <div class="nav-hex">⬑</div>
293
+ <div><div class="nav-title">WARZONE</div><div class="nav-sub">COMMAND CENTER</div></div>
294
+ </div>
295
+ <div class="nav-right">
296
+ <button class="btn btn-outline" onclick="goto('login')" style="font-size:11px;padding:6px 16px">← BACK TO LOGIN</button>
297
+ </div>
298
+ </nav>
299
+
300
+ <div class="auth-body">
301
+ <div class="auth-hero">
302
+ <div class="hero-icon">⚑</div>
303
+ <div class="hero-title">ENLIST NOW</div>
304
+ <div class="hero-sub">Create your operator profile and join the combat leaderboard. Track your kills, rank up, and dominate.</div>
305
+ <div style="margin-top:24px;text-align:center">
306
+ <div style="font-size:11px;color:var(--dim);letter-spacing:2px;margin-bottom:16px">WHAT YOU GET</div>
307
+ <div style="display:flex;flex-direction:column;gap:10px;width:100%;max-width:280px">
308
+ <div style="background:rgba(0,212,255,.06);border:1px solid rgba(0,212,255,.15);padding:10px 16px;font-size:12px;color:var(--text);letter-spacing:1px">πŸ“Š Full analytics dashboard</div>
309
+ <div style="background:rgba(0,212,255,.06);border:1px solid rgba(0,212,255,.15);padding:10px 16px;font-size:12px;color:var(--text);letter-spacing:1px">πŸ† Global leaderboard ranking</div>
310
+ <div style="background:rgba(0,212,255,.06);border:1px solid rgba(0,212,255,.15);padding:10px 16px;font-size:12px;color:var(--text);letter-spacing:1px">βš” Personal stat tracking</div>
311
+ <div style="background:rgba(0,212,255,.06);border:1px solid rgba(0,212,255,.15);padding:10px 16px;font-size:12px;color:var(--text);letter-spacing:1px">πŸͺ™ Gold & reward system</div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+
316
+ <div class="auth-form-panel">
317
+ <div class="auth-form-box fade-in">
318
+ <div class="auth-form-title">CREATE ACCOUNT</div>
319
+ <div class="auth-form-sub">REGISTER YOUR OPERATOR PROFILE</div>
320
+
321
+ <div id="reg-alert" class="alert"></div>
322
+
323
+ <div class="form-group">
324
+ <label>Username (your callsign)</label>
325
+ <input type="text" id="reg-username" placeholder="e.g. ShadowSniper99" maxlength="24" />
326
+ </div>
327
+ <div class="form-group">
328
+ <label>Email Address</label>
329
+ <input type="email" id="reg-email" placeholder="operator@warzone.gg" />
330
+ </div>
331
+ <div class="form-group">
332
+ <label>Password</label>
333
+ <input type="password" id="reg-password" placeholder="Min 6 characters" />
334
+ </div>
335
+ <div class="form-group">
336
+ <label>Confirm Password</label>
337
+ <input type="password" id="reg-confirm" placeholder="Repeat password" onkeydown="if(event.key==='Enter')doRegister()" />
338
+ </div>
339
+ <button class="btn btn-primary" id="reg-btn" onclick="doRegister()" style="width:100%;font-size:13px;padding:12px;font-family:'Orbitron',sans-serif;font-weight:700;letter-spacing:4px">ENLIST</button>
340
+
341
+ <div style="text-align:center;margin-top:18px;font-size:12px;color:var(--dim)">
342
+ Already enlisted? <span class="auth-link" onclick="goto('login')">Sign in</span>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ </div>
347
+ </div>
348
+
349
+ <div class="page" id="page-dashboard">
350
+ <nav class="nav">
351
+ <div class="nav-brand" onclick="goto('dashboard')">
352
+ <div class="nav-hex">⬑</div>
353
+ <div><div class="nav-title">WARZONE</div><div class="nav-sub">COMMAND CENTER</div></div>
354
+ </div>
355
+ <div class="nav-right">
356
+ <div class="nav-badge"><div class="live-dot"></div>&nbsp;LIVE</div>
357
+ <span id="dash-last-refresh" style="font-size:11px;color:var(--dim)">β€”</span>
358
+ <button class="btn btn-ghost" onclick="loadData()" style="font-size:11px;padding:6px 14px">⟳ REFRESH</button>
359
+ <button class="btn btn-outline" id="dash-login-btn" onclick="goto('login')" style="font-size:11px;padding:6px 14px">⚿ LOGIN</button>
360
+ <div id="dash-user-panel" style="display:none;align-items:center;gap:10px">
361
+ <span style="font-size:11px;color:var(--dim)">β–Έ</span>
362
+ <span id="dash-username" style="font-family:'Orbitron',sans-serif;font-size:11px;letter-spacing:2px;color:var(--accent)">β€”</span>
363
+ <button class="btn btn-ghost" onclick="goto('profile')" style="font-size:11px;padding:5px 12px">πŸ‘€ PROFILE</button>
364
+ <button class="btn btn-danger" onclick="doLogout(false)" style="font-size:11px;padding:5px 12px">LOGOUT</button>
365
+ </div>
366
+ </div>
367
+ </nav>
368
+
369
+ <div class="dash-body">
370
+ <div class="error-banner" id="dash-error"></div>
371
+
372
+ <div class="anon-banner" id="dash-anon-banner">
373
+ <div class="anon-text">πŸ‘ Viewing <span>public data only</span> β€” Login to unlock full stats, charts, K/D ratios and your personal profile.</div>
374
+ <div style="display:flex;gap:10px">
375
+ <button class="btn btn-outline" onclick="goto('login')" style="font-size:11px;padding:5px 14px">⚿ LOGIN</button>
376
+ <button class="btn btn-ghost" onclick="goto('register')" style="font-size:11px;padding:5px 14px">+ REGISTER</button>
377
+ </div>
378
+ </div>
379
+
380
+ <div class="section-title">GLOBAL STATS</div>
381
+ <div class="kpi-row">
382
+ <div class="kpi-card c"><div class="kpi-icon">🎯</div><div class="kpi-label">Total Players</div><div class="kpi-value" id="dk-players">β€”</div><div class="kpi-sub">Registered combatants</div></div>
383
+ <div class="kpi-card g"><div class="kpi-icon">πŸͺ™</div><div class="kpi-label">Gold in Circulation</div><div class="kpi-value" id="dk-gold">β€”</div><div class="kpi-sub">Combined balance</div></div>
384
+ <div class="kpi-card c"><div class="kpi-icon">πŸ†</div><div class="kpi-label">Top Rank</div><div class="kpi-value" id="dk-toprank">β€”</div><div class="kpi-sub" id="dk-topplayer">β€”</div></div>
385
+ <div class="kpi-card e"><div class="kpi-icon">πŸ“ˆ</div><div class="kpi-label">Best Win Rate</div><div class="kpi-value" id="dk-topwr">β€”</div><div class="kpi-sub" id="dk-topwrplayer">β€”</div></div>
386
+ <div class="kpi-card r" id="dk-kills-card" style="display:none"><div class="kpi-icon">πŸ’€</div><div class="kpi-label">Total Kills</div><div class="kpi-value" id="dk-kills">β€”</div><div class="kpi-sub">All players combined</div></div>
387
+ <div class="kpi-card e" id="dk-hs-card" style="display:none"><div class="kpi-icon">🎯</div><div class="kpi-label">Headshots</div><div class="kpi-value" id="dk-hs">β€”</div><div class="kpi-sub">Precision kills</div></div>
388
+ <div class="kpi-card r" id="dk-play-card" style="display:none"><div class="kpi-icon">⏱</div><div class="kpi-label">Playtime hrs</div><div class="kpi-value" id="dk-play">β€”</div><div class="kpi-sub">Combined hours</div></div>
389
+ </div>
390
+
391
+ <div class="section-title">ANALYTICS</div>
392
+ <div class="locked-section" id="dash-charts-section">
393
+ <div class="charts-row">
394
+ <div class="chart-card"><h3>Top 5 Rank Points</h3><canvas id="rankChart"></canvas></div>
395
+ <div class="chart-card"><h3>Weapon Loadouts</h3><canvas id="weaponChart"></canvas></div>
396
+ <div class="chart-card"><h3>K/D Ratio β€” Top 8</h3><canvas id="kdChart"></canvas></div>
397
+ </div>
398
+ <div class="charts-row" style="grid-template-columns:1fr 1fr">
399
+ <div class="chart-card"><h3>Gold Holdings β€” Top 5</h3><canvas id="goldChart"></canvas></div>
400
+ <div class="chart-card"><h3>Wins vs Losses</h3><canvas id="winChart"></canvas></div>
401
+ </div>
402
+ <div class="locked-overlay" id="charts-lock">
403
+ <div class="locked-icon">πŸ”’</div>
404
+ <div class="locked-text">FULL ANALYTICS β€” AUTHORISED PERSONNEL ONLY</div>
405
+ <button class="btn btn-outline" onclick="goto('login')" style="font-size:11px;padding:6px 18px;margin-top:4px">⚿ LOGIN TO UNLOCK</button>
406
+ </div>
407
+ </div>
408
+
409
+ <div class="section-title" style="margin-top:28px">PLAYER LEADERBOARD</div>
410
+ <div class="filter-row" id="dash-filters" style="display:none">
411
+ <label>WEAPON</label>
412
+ <select id="filter-weapon" onchange="renderTable()"><option value="">ALL</option></select>
413
+ <label>SORT BY</label>
414
+ <select id="sort-by" onchange="renderTable()">
415
+ <option value="rank_points">Rank Points</option>
416
+ <option value="kills">Kills</option>
417
+ <option value="gold">Gold</option>
418
+ <option value="kd">K/D</option>
419
+ <option value="headshots">Headshots</option>
420
+ </select>
421
+ </div>
422
+ <div class="table-card">
423
+ <h3>βš” ACTIVE ROSTER</h3>
424
+ <table class="data-table">
425
+ <thead id="lb-head"></thead>
426
+ <tbody id="lb-body"></tbody>
427
+ </table>
428
+ </div>
429
+ </div>
430
+ </div>
431
+
432
+ <div class="page" id="page-profile">
433
+ <nav class="nav">
434
+ <div class="nav-brand" onclick="goto('dashboard')">
435
+ <div class="nav-hex">⬑</div>
436
+ <div><div class="nav-title">WARZONE</div><div class="nav-sub">COMMAND CENTER</div></div>
437
+ </div>
438
+ <div class="nav-right">
439
+ <button class="btn btn-ghost" onclick="goto('dashboard')" style="font-size:11px;padding:6px 14px">← DASHBOARD</button>
440
+ <span id="prof-username-nav" style="font-family:'Orbitron',sans-serif;font-size:11px;letter-spacing:2px;color:var(--accent)">β€”</span>
441
+ <button class="btn btn-danger" onclick="doLogout(false)" style="font-size:11px;padding:5px 12px">LOGOUT</button>
442
+ </div>
443
+ </nav>
444
+
445
+ <div class="profile-body">
446
+
447
+ <div class="profile-header">
448
+ <div class="profile-avatar" id="prof-avatar">?</div>
449
+ <div class="profile-info">
450
+ <div class="profile-username" id="prof-display-name">β€”</div>
451
+ <div class="profile-email" id="prof-email">β€”</div>
452
+ <div class="profile-badges">
453
+ <span class="tier" id="prof-tier-badge">β€”</span>
454
+ <span style="background:rgba(176,68,255,.15);border:1px solid var(--purple);padding:2px 10px;font-size:10px;font-family:'Orbitron',sans-serif;color:var(--purple)" id="prof-level-badge">β€”</span>
455
+ <span style="background:rgba(255,215,0,.1);border:1px solid var(--gold);padding:2px 10px;font-size:10px;font-family:'Share Tech Mono',monospace;color:var(--gold)" id="prof-gold-badge">β€”</span>
456
+ </div>
457
+ </div>
458
+ <div style="text-align:right;margin-left:auto">
459
+ <div style="font-size:10px;color:var(--dim);letter-spacing:2px;margin-bottom:4px">RANK POINTS</div>
460
+ <div style="font-family:'Orbitron',sans-serif;font-size:36px;font-weight:900;color:var(--accent);text-shadow:var(--glow)" id="prof-rank-pts">β€”</div>
461
+ <div class="rank-bar" style="width:180px;margin-top:8px"><div class="rank-bar-fill" id="prof-rank-bar" style="width:0%"></div></div>
462
+ <div class="progress-label" style="width:180px"><span id="prof-tier-cur">β€”</span><span id="prof-tier-next">β€”</span></div>
463
+ </div>
464
+ </div>
465
+
466
+ <div class="profile-grid">
467
+ <div>
468
+ <div class="section-title">COMBAT STATS</div>
469
+ <div class="profile-stats-grid" id="prof-stats-grid"></div>
470
+
471
+ <div class="section-title">ACCOUNT SETTINGS</div>
472
+ <div class="card" style="margin-bottom:20px">
473
+ <div class="edit-section-title">CHANGE CALLSIGN (USERNAME)</div>
474
+ <div id="prof-username-alert" class="alert"></div>
475
+ <div class="edit-row">
476
+ <div class="form-group"><label>New Username</label><input type="text" id="prof-new-username" placeholder="Enter new callsign" maxlength="24" /></div>
477
+ <button class="btn btn-primary" onclick="updateUsername()" style="font-size:11px">UPDATE</button>
478
+ </div>
479
+ </div>
480
+
481
+ <div class="card" style="margin-bottom:20px">
482
+ <div class="edit-section-title">CHANGE EMAIL ADDRESS</div>
483
+ <div id="prof-email-alert" class="alert"></div>
484
+ <div class="edit-row">
485
+ <div class="form-group"><label>New Email</label><input type="email" id="prof-new-email" placeholder="new@email.com" /></div>
486
+ <button class="btn btn-primary" onclick="updateEmail()" style="font-size:11px">UPDATE</button>
487
+ </div>
488
+ </div>
489
+
490
+ <div class="card" style="margin-bottom:20px">
491
+ <div class="edit-section-title">CHANGE PASSWORD</div>
492
+ <div id="prof-pw-alert" class="alert"></div>
493
+ <div class="form-group"><label>New Password</label><input type="password" id="prof-new-pw" placeholder="Min 6 characters" /></div>
494
+ <div class="edit-row">
495
+ <div class="form-group"><label>Confirm New Password</label><input type="password" id="prof-confirm-pw" placeholder="Repeat password" /></div>
496
+ <button class="btn btn-primary" onclick="updatePassword()" style="font-size:11px">UPDATE</button>
497
+ </div>
498
+ </div>
499
+
500
+ <div class="danger-zone">
501
+ <div class="danger-title">⚠ DANGER ZONE</div>
502
+ <p style="font-size:12px;color:var(--dim);line-height:1.7;margin-bottom:16px">Logging out will end your session on all devices. Your stats and progress are always saved automatically.</p>
503
+ <button class="btn btn-danger" onclick="doLogout(true)" style="font-size:12px;padding:9px 22px">⏻ LOG OUT OF ALL SESSIONS</button>
504
+ </div>
505
+ </div>
506
+
507
+ <div>
508
+ <div class="sidebar-card">
509
+ <div class="sidebar-title">RANK BREAKDOWN</div>
510
+ <div class="stat-row"><span class="stat-row-label">Current Tier</span><span class="stat-row-val" id="sb-tier">β€”</span></div>
511
+ <div class="stat-row"><span class="stat-row-label">Rank Points</span><span class="stat-row-val" id="sb-rp">β€”</span></div>
512
+ <div class="stat-row"><span class="stat-row-label">Next Tier At</span><span class="stat-row-val" id="sb-next">β€”</span></div>
513
+ <div class="stat-row"><span class="stat-row-label">Points Needed</span><span class="stat-row-val" id="sb-needed" style="color:var(--gold)">β€”</span></div>
514
+ </div>
515
+
516
+ <div class="sidebar-card">
517
+ <div class="sidebar-title">CURRENT LOADOUT</div>
518
+ <div class="stat-row"><span class="stat-row-label">Weapon</span><span class="weapon-tag" id="sb-weapon">β€”</span></div>
519
+ <div class="stat-row"><span class="stat-row-label">Armor</span><span class="armor-tag" id="sb-armor">β€”</span></div>
520
+ <div class="stat-row"><span class="stat-row-label">Level</span><span class="stat-row-val" id="sb-level" style="color:var(--purple)">β€”</span></div>
521
+ </div>
522
+
523
+ <div class="sidebar-card">
524
+ <div class="sidebar-title">MATCH RECORD</div>
525
+ <div class="stat-row"><span class="stat-row-label">Wins</span><span class="stat-row-val" id="sb-wins" style="color:var(--green)">β€”</span></div>
526
+ <div class="stat-row"><span class="stat-row-label">Losses</span><span class="stat-row-val" id="sb-losses" style="color:var(--red)">β€”</span></div>
527
+ <div class="stat-row"><span class="stat-row-label">Win Rate</span><span class="stat-row-val" id="sb-wr" style="color:var(--accent)">β€”</span></div>
528
+ <div class="stat-row"><span class="stat-row-label">Playtime</span><span class="stat-row-val" id="sb-play" style="color:var(--dim)">β€”</span></div>
529
+ <div class="stat-row"><span class="stat-row-label">Last Seen</span><span class="stat-row-val" id="sb-lastseen" style="color:var(--dim);font-size:11px">β€”</span></div>
530
+ </div>
531
+
532
+ <div class="sidebar-card">
533
+ <div class="sidebar-title">LEADERBOARD POSITION</div>
534
+ <div style="text-align:center;padding:14px 0">
535
+ <div style="font-family:'Orbitron',sans-serif;font-size:42px;font-weight:900;color:var(--accent);text-shadow:var(--glow)" id="sb-lbpos">β€”</div>
536
+ <div style="font-size:11px;color:var(--dim);letter-spacing:2px;margin-top:4px">GLOBAL RANKING</div>
537
+ </div>
538
+ <button class="btn btn-outline" onclick="goto('dashboard')" style="width:100%;font-size:11px;padding:8px">VIEW FULL LEADERBOARD</button>
539
+ </div>
540
+ </div>
541
+ </div>
542
+ </div>
543
+ </div>
544
+
545
+ <script>
546
+ // ── CONFIG ──
547
+ const SB_URL = 'https://kkezjquxsrpifsbvggzs.supabase.co';
548
+ const SB_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtrZXpqcXV4c3JwaWZzYnZnZ3pzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODA2NjI4MDksImV4cCI6MjA5NjIzODgwOX0.LkuUHbFIKswz7QEYrm9-7O6WnjvFn9WWo6UtE5o0bAQ';
549
+
550
+ const TEST_ACCOUNTS = [
551
+ { username:'ShadowSniper99', email:'shadowsniper99@warzone.gg' },
552
+ { username:'NightHawkX', email:'nighthawkx@warzone.gg' },
553
+ { username:'BlazeFury', email:'blazefury@warzone.gg' },
554
+ { username:'PixelDestroyer', email:'pixeldestroyer@warzone.gg' },
555
+ { username:'IronGhost', email:'ironghost@warzone.gg' },
556
+ { username:'StormBringer', email:'stormbringer@warzone.gg' },
557
+ { username:'VoidWalker', email:'voidwalker@warzone.gg' },
558
+ { username:'ChaosRocket', email:'chaosrocket@warzone.gg' },
559
+ { username:'PhantomAce', email:'phantomace@warzone.gg' },
560
+ { username:'NoobSlayer', email:'noobslayer@warzone.gg' },
561
+ ];
562
+ const TEST_PASSWORD = 'Warzone2024!';
563
+
564
+ // ── STATE ──
565
+ let players = [];
566
+ let charts = {};
567
+ let authUser = null;
568
+ let currentPage = '';
569
+
570
+ // ── HELPERS ──
571
+ const fmt = n => n >= 1e6 ? (n/1e6).toFixed(1)+'M' : n >= 1e3 ? (n/1e3).toFixed(1)+'K' : (n||0).toLocaleString();
572
+ const $ = id => document.getElementById(id);
573
+ function setAlert(elId, type, msg) {
574
+ const el = $(elId); if(el) { el.className = `alert ${type}`; el.textContent = msg; el.style.display = 'block'; }
575
+ }
576
+ function clearAlert(elId) { const el=$(elId); if(el) el.style.display='none'; }
577
+
578
+ function getTier(pts) {
579
+ if (pts >= 3000) return { name:'DIAMOND', cls:'td', next: null, nextPts: null };
580
+ if (pts >= 2500) return { name:'PLATINUM', cls:'tp', next:'DIAMOND', nextPts: 3000 };
581
+ if (pts >= 1800) return { name:'GOLD', cls:'tg', next:'PLATINUM',nextPts: 2500 };
582
+ if (pts >= 1200) return { name:'SILVER', cls:'ts', next:'GOLD', nextPts: 1800 };
583
+ return { name:'BRONZE', cls:'tb', next:'SILVER', nextPts: 1200 };
584
+ }
585
+
586
+ // ── ROUTING ──
587
+ function goto(page) {
588
+ document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
589
+ const target = $(`page-${page}`);
590
+ if(target) target.classList.add('active');
591
+ currentPage = page;
592
+ window.scrollTo(0,0);
593
+ if (page === 'dashboard') refreshDashNav();
594
+ if (page === 'profile') { if (!authUser) { goto('login'); return; } loadProfile(); }
595
+ if (page === 'login') buildTestAccounts();
596
+ }
597
+
598
+ function refreshDashNav() {
599
+ const li = isLoggedIn();
600
+ $('dash-login-btn').style.display = li ? 'none' : '';
601
+ $('dash-user-panel').style.display = li ? 'flex' : 'none';
602
+ if (li && authUser) $('dash-username').textContent = authUser.username || authUser.email;
603
+ $('dash-anon-banner').style.display = li ? 'none' : '';
604
+ ['dk-kills-card','dk-hs-card','dk-play-card'].forEach(id => { const el=$(id); if(el) el.style.display = li ? '' : 'none'; });
605
+ $('charts-lock').style.display = li ? 'none' : 'flex';
606
+ $('dash-filters').style.display = li ? 'flex' : 'none';
607
+ }
608
+
609
+ // ── AUTH & SESSIONS ──
610
+ function isLoggedIn() { return authUser !== null; }
611
+ function isBotAccount(email) { return email && email.endsWith('@warzone.gg'); }
612
+
613
+ // FIXED: Parsing logic specifically built to handle confirmation link hashes seamlessly
614
+ function parseUrlHash() {
615
+ const hash = window.location.hash.substring(1);
616
+ if (!hash) return null;
617
+ const params = new URLSearchParams(hash);
618
+ const access_token = params.get('access_token');
619
+ if (access_token) {
620
+ return {
621
+ access_token,
622
+ email: params.get('email') || '',
623
+ uid: params.get('user_id') || ''
624
+ };
625
+ }
626
+ return null;
627
+ }
628
+
629
+ async function doLogin() {
630
+ const email = $('login-email').value.trim();
631
+ const pass = $('login-password').value;
632
+ clearAlert('login-alert');
633
+ if (!email || !pass) { setAlert('login-alert','error','⚠ EMAIL AND PASSWORD REQUIRED'); return; }
634
+
635
+ const btn = $('login-btn');
636
+ btn.disabled = true; btn.textContent = 'AUTHENTICATING...';
637
+
638
+ try {
639
+ const res = await fetch(`${SB_URL}/auth/v1/token?grant_type=password`, {
640
+ method:'POST',
641
+ headers:{'apikey':SB_KEY,'Content-Type':'application/json'},
642
+ body:JSON.stringify({email,password:pass})
643
+ });
644
+ const data = await res.json();
645
+ if (!res.ok) throw new Error(data.error_description || data.message || 'Login failed');
646
+ await completeLoginSession(data);
647
+ } catch(e) {
648
+ setAlert('login-alert','error',`⚠ ${e.message.toUpperCase()}`);
649
+ } finally {
650
+ btn.disabled=false; btn.textContent='AUTHENTICATE';
651
+ }
652
+ }
653
+
654
+ async function completeLoginSession(authData) {
655
+ const userId = authData.user?.id;
656
+ let username = "Operator";
657
+ if (authData.user && authData.user.email) {
658
+ username = authData.user.email.split('@')[0];
659
+ }
660
+
661
+ const sr = await fetch(`${SB_URL}/rest/v1/player_stats?id=eq.${userId}&select=*`, {
662
+ headers:{'apikey':SB_KEY,'Authorization':`Bearer ${authData.access_token}`}
663
+ });
664
+ const sd = await sr.json();
665
+
666
+ if (!sd || sd.length === 0) {
667
+ await fetch(`${SB_URL}/rest/v1/player_stats`, {
668
+ method:'POST',
669
+ headers:{ 'apikey':SB_KEY, 'Authorization':`Bearer ${authData.access_token}`, 'Content-Type':'application/json', 'Prefer':'return=minimal' },
670
+ body:JSON.stringify({
671
+ id: userId, username: username, kills: 0, deaths: 0, rank_points: 1000,
672
+ gold: 0, level: 1, equipped_weapon: 'Pistol', equipped_armor: 'None',
673
+ headshots: 0, wins: 0, losses: 0, playtime_hours: 0
674
+ })
675
+ });
676
+ } else if (sd[0].username) {
677
+ username = sd[0].username;
678
+ }
679
+
680
+ authUser = { id:userId, email:authData.user?.email, username, token:authData.access_token };
681
+
682
+ if (!isBotAccount(authUser.email)) {
683
+ localStorage.setItem('warzone_session', JSON.stringify(authUser));
684
+ }
685
+
686
+ $('login-email').value=''; $('login-password').value='';
687
+ window.location.hash = ""; // Clear hash clean
688
+ await loadData();
689
+ goto('dashboard');
690
+ }
691
+
692
+ async function doRegister() {
693
+ const username = $('reg-username').value.trim();
694
+ const email = $('reg-email').value.trim();
695
+ const pass = $('reg-password').value;
696
+ const confirm = $('reg-confirm').value;
697
+ clearAlert('reg-alert');
698
+
699
+ if (!username || !email || !pass) { setAlert('reg-alert','error','⚠ ALL FIELDS REQUIRED'); return; }
700
+ if (pass !== confirm) { setAlert('reg-alert','error','⚠ PASSWORDS DO NOT MATCH'); return; }
701
+ if (pass.length < 6) { setAlert('reg-alert','error','⚠ PASSWORD MUST BE AT LEAST 6 CHARACTERS'); return; }
702
+ if (!/^[a-zA-Z0-9_]+$/.test(username)) { setAlert('reg-alert','error','⚠ USERNAME: LETTERS, NUMBERS AND UNDERSCORES ONLY'); return; }
703
+
704
+ const btn = $('reg-btn');
705
+ btn.disabled=true; btn.textContent='ENLIST...';
706
+ try {
707
+ const authRes = await fetch(`${SB_URL}/auth/v1/signup`, {
708
+ method:'POST',
709
+ headers:{'apikey':SB_KEY,'Content-Type':'application/json'},
710
+ body:JSON.stringify({email,password:pass})
711
+ });
712
+ const authData = await authRes.json();
713
+ if (!authRes.ok) throw new Error(authData.error_description || authData.message || 'Registration failed');
714
+ if (!authData.user?.id) throw new Error('No user ID returned β€” email may already be registered');
715
+
716
+ const userId = authData.user.id;
717
+ const token = authData.access_token || SB_KEY;
718
+
719
+ await fetch(`${SB_URL}/rest/v1/player_stats`, {
720
+ method:'POST',
721
+ headers:{ 'apikey':SB_KEY, 'Authorization':`Bearer ${token}`, 'Content-Type':'application/json', 'Prefer':'return=minimal' },
722
+ body:JSON.stringify({ id:userId, username, kills:0, deaths:0, rank_points:1000, gold:0, level:1, equipped_weapon:'Pistol', equipped_armor:'None', headshots:0, wins:0, losses:0, playtime_hours:0 })
723
+ });
724
+
725
+ setAlert('reg-alert','success','βœ“ CONFIRMATION EMAIL SENT via RESEND! Check inbox to activate.');
726
+ } catch(e) {
727
+ setAlert('reg-alert','error',`⚠ ${e.message.toUpperCase()}`);
728
+ } finally {
729
+ btn.disabled=false; btn.textContent='ENLIST';
730
+ }
731
+ }
732
+
733
+ async function doLogout(global = false) {
734
+ if (global) {
735
+ if (authUser && isBotAccount(authUser.email)) {
736
+ alert("⚠ ACTION DENIED: Bot test accounts cannot be globally signed out.");
737
+ return;
738
+ }
739
+ try {
740
+ await fetch(`${SB_URL}/auth/v1/logout?scope=global`, { method: 'POST', headers: { 'apikey': SB_KEY, 'Authorization': `Bearer ${authUser.token}` }});
741
+ } catch(e) { console.error("Global logout failed", e); }
742
+ } else {
743
+ try {
744
+ if(authUser) await fetch(`${SB_URL}/auth/v1/logout?scope=local`, { method: 'POST', headers: { 'apikey': SB_KEY, 'Authorization': `Bearer ${authUser.token}` }});
745
+ } catch(e) { console.error("Local logout failed", e); }
746
+ }
747
+
748
+ authUser = null;
749
+ localStorage.removeItem('warzone_session');
750
+ players = [];
751
+ Object.values(charts).forEach(c=>c.destroy()); charts={};
752
+ await loadData();
753
+ goto('login');
754
+ }
755
+
756
+ // ── TEST ACCOUNTS ──
757
+ function buildTestAccounts() {
758
+ const list = $('test-accounts-list');
759
+ if(!list) return;
760
+ list.innerHTML = TEST_ACCOUNTS.map((a,i) => `
761
+ <div class="test-account" onclick="fillLogin('${a.email}','${TEST_PASSWORD}')">
762
+ <div><div class="ta-user">${a.username}</div><div class="ta-email">${a.email}</div></div>
763
+ <div class="ta-use">USE β†’</div>
764
+ </div>
765
+ `).join('');
766
+ }
767
+ function fillLogin(email, pass) {
768
+ $('login-email').value = email;
769
+ $('login-password').value = pass;
770
+ $('login-email').focus();
771
+ }
772
+
773
+ // ── DATA ──
774
+ async function loadData() {
775
+ try {
776
+ const res = await fetch(`${SB_URL}/rest/v1/player_stats?select=*&order=rank_points.desc`, {
777
+ headers:{'apikey':SB_KEY,'Authorization':`Bearer ${SB_KEY}`}
778
+ });
779
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
780
+ const raw = await res.json();
781
+ players = raw.map(p => ({
782
+ ...p,
783
+ kd: p.deaths > 0 ? p.kills/p.deaths : p.kills,
784
+ win_rate: (p.wins+p.losses) > 0 ? p.wins/(p.wins+p.losses)*100 : 0
785
+ }));
786
+ renderKPIs();
787
+ renderCharts();
788
+ populateFilters();
789
+ renderTable();
790
+ populateHeroStats();
791
+ const now = new Date();
792
+ $('dash-last-refresh').textContent = now.toLocaleTimeString();
793
+ $('dash-error').style.display = 'none';
794
+ } catch(e) {
795
+ $('dash-error').style.display='block';
796
+ $('dash-error').textContent=`⚠ DATABASE ERROR: ${e.message}`;
797
+ }
798
+ }
799
+
800
+ function populateHeroStats() {
801
+ const totalKills = players.reduce((a,p)=>a+(p.kills||0),0);
802
+ const totalGold = players.reduce((a,p)=>a+(p.gold||0),0);
803
+ const top = players[0];
804
+ $('hs-players').textContent = players.length;
805
+ $('hs-kills').textContent = fmt(totalKills);
806
+ $('hs-gold').textContent = fmt(totalGold);
807
+ $('hs-top').textContent = top ? fmt(top.rank_points) : 'β€”';
808
+ }
809
+
810
+ function renderKPIs() {
811
+ const totalKills = players.reduce((a,p)=>a+(p.kills||0),0);
812
+ const totalGold = players.reduce((a,p)=>a+(p.gold||0),0);
813
+ const totalHS = players.reduce((a,p)=>a+(p.headshots||0),0);
814
+ const totalPlay = players.reduce((a,p)=>a+(parseFloat(p.playtime_hours)||0),0);
815
+ const top = players[0];
816
+ const topWR = [...players].sort((a,b)=>b.win_rate-a.win_rate)[0];
817
+ $('dk-players').textContent = players.length;
818
+ $('dk-gold').textContent = fmt(totalGold);
819
+ $('dk-kills').textContent = fmt(totalKills);
820
+ $('dk-hs').textContent = fmt(totalHS);
821
+ $('dk-play').textContent = Math.round(totalPlay).toLocaleString();
822
+ if (top) { $('dk-toprank').textContent=fmt(top.rank_points); $('dk-topplayer').textContent=top.username||'β€”'; }
823
+ if (topWR) { $('dk-topwr').textContent=topWR.win_rate.toFixed(0)+'%'; $('dk-topwrplayer').textContent=topWR.username||'β€”'; }
824
+ }
825
+
826
+ // ── CHARTS ──
827
+ const CC = { c:'#00d4ff',r:'#ff4444',g:'#ffd700',e:'#39ff14',p:'#b044ff',o:'#ff8800',b:'#4488ff' };
828
+
829
+ function renderCharts() {
830
+ Object.values(charts).forEach(c=>c.destroy()); charts={};
831
+ // FIXED: Stop chart initialization immediately if user is not logged in to prevent crashing scripts
832
+ if (!isLoggedIn() || !$('rankChart')) return;
833
+
834
+ try {
835
+ Chart.defaults.color='#4a7a9b';
836
+ Chart.defaults.font.family="'Share Tech Mono',monospace";
837
+ const top5=players.slice(0,5), top8=players.slice(0,8);
838
+
839
+ charts.rank = new Chart($('rankChart'), { type:'bar', data:{
840
+ labels:top5.map(p=>p.username),
841
+ datasets:[{label:'Rank Points',data:top5.map(p=>p.rank_points),backgroundColor:top5.map((_,i)=>i===0?CC.g:i===1?CC.c:CC.b+'99'),borderWidth:0,borderRadius:2}]
842
+ }, options:{responsive:true,maintainAspectRatio:true,plugins:{legend:{display:false}},scales:{x:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}},y:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}}}}});
843
+
844
+ const wm={}; players.forEach(p=>{const w=p.equipped_weapon||'Unknown';wm[w]=(wm[w]||0)+1;});
845
+ charts.weapon = new Chart($('weaponChart'), { type:'doughnut', data:{
846
+ labels:Object.keys(wm),datasets:[{data:Object.values(wm),backgroundColor:[CC.c,CC.r,CC.g,CC.e,CC.p,CC.o,CC.b],borderColor:'#060a0f',borderWidth:3}]
847
+ }, options:{responsive:true,maintainAspectRatio:true,cutout:'65%',plugins:{legend:{position:'right',labels:{color:'#cee8ff',font:{size:10},padding:8,boxWidth:10}}}}});
848
+
849
+ charts.kd = new Chart($('kdChart'), { type:'bar', data:{
850
+ labels:top8.map(p=>p.username),
851
+ datasets:[{label:'K/D',data:top8.map(p=>+p.kd.toFixed(2)),backgroundColor:top8.map(p=>p.kd>=3?CC.g+'cc':p.kd>=2?CC.e+'cc':p.kd>=1?CC.c+'cc':CC.r+'cc'),borderWidth:0,borderRadius:2}]
852
+ }, options:{responsive:true,maintainAspectRatio:true,plugins:{legend:{display:false}},scales:{x:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}},y:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}}}}});
853
+
854
+ const tg=[...players].sort((a,b)=>b.gold-a.gold).slice(0,5);
855
+ charts.gold = new Chart($('goldChart'), { type:'bar', data:{
856
+ labels:tg.map(p=>p.username),datasets:[{label:'Gold',data:tg.map(p=>p.gold),backgroundColor:CC.g+'bb',borderWidth:0,borderRadius:2}]
857
+ }, options:{indexAxis:'y',responsive:true,maintainAspectRatio:true,plugins:{legend:{display:false}},scales:{x:{grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}},y:{grid:{display:false},ticks:{color:'#cee8ff',font:{size:11}}}}}});
858
+
859
+ const wd=[...players].sort((a,b)=>b.win_rate-a.win_rate);
860
+ charts.win = new Chart($('winChart'), { type:'bar', data:{
861
+ labels:wd.map(p=>p.username),
862
+ datasets:[{label:'Wins',data:wd.map(p=>p.wins),backgroundColor:CC.e+'bb',borderWidth:0,borderRadius:2},{label:'Losses',data:wd.map(p=>p.losses),backgroundColor:CC.r+'bb',borderWidth:0,borderRadius:2}]
863
+ }, options:{responsive:true,maintainAspectRatio:true,plugins:{legend:{labels:{color:'#cee8ff',font:{size:10},boxWidth:10,padding:8}}},scales:{x:{stacked:true,grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:9},maxRotation:35}},y:{stacked:true,grid:{color:'#1e304840'},ticks:{color:'#4a7a9b',font:{size:10}}}}}});
864
+ } catch(err) { console.error("Chart build blocked:", err); }
865
+ }
866
+
867
+ // ── TABLE ──
868
+ function populateFilters() {
869
+ const weapons=[...new Set(players.map(p=>p.equipped_weapon).filter(Boolean))];
870
+ const sel=$('filter-weapon'); if(!sel) return;
871
+ sel.innerHTML='<option value="">ALL</option>';
872
+ weapons.forEach(w=>{const o=document.createElement('option');o.value=w;o.textContent=w;sel.appendChild(o);});
873
+ }
874
+
875
+ function renderTable() {
876
+ const li=isLoggedIn();
877
+ const lbHead = $('lb-head');
878
+ if(!lbHead) return;
879
+
880
+ lbHead.innerHTML = li
881
+ ? `<tr><th>#</th><th>PLAYER</th><th>TIER</th><th>RANK PTS</th><th>K/D</th><th>HEADSHOTS</th><th>WEAPON</th><th>ARMOR</th><th>GOLD</th><th>LEVEL</th><th>WIN RATE</th><th>PLAYTIME</th></tr>`
882
+ : `<tr><th>#</th><th>PLAYER</th><th>TIER</th><th>RANK PTS</th><th>GOLD</th><th>WIN RATE</th></tr>`;
883
+
884
+ const wf = li ? $('filter-weapon').value : '';
885
+ const sb = li ? $('sort-by').value : 'rank_points';
886
+ let data=[...players];
887
+ if (wf) data=data.filter(p=>p.equipped_weapon===wf);
888
+ data.sort((a,b)=>sb==='kd'?b.kd-a.kd:(b[sb]||0)-(a[sb]||0));
889
+ if (!li) data=data.slice(0,10);
890
+
891
+ const tbody=$('lb-body'); tbody.innerHTML='';
892
+ data.forEach((p,i)=>{
893
+ const rank=i+1, tier=getTier(p.rank_points);
894
+ const rc=rank===1?'r1':rank===2?'r2':rank===3?'r3':'rn';
895
+ const isOwn=li&&authUser&&p.id===authUser.id;
896
+ const tr=document.createElement('tr');
897
+ if(isOwn)tr.classList.add('own-row');
898
+ if(li){
899
+ const kd=p.kd.toFixed(2),kdC=p.kd>=3?'kd-g':p.kd>=1?'kd-o':'kd-b';
900
+ tr.innerHTML=`<td><span class="rank-badge ${rc}">${rank}</span></td>
901
+ <td><strong style="color:${isOwn?'var(--accent)':'#cee8ff'}">${p.username||'β€”'}${isOwn?' β—€':''}</strong></td>
902
+ <td><span class="tier ${tier.cls}">${tier.name}</span></td>
903
+ <td style="color:var(--accent);font-family:'Orbitron',sans-serif;font-size:13px">${(p.rank_points||0).toLocaleString()}</td>
904
+ <td class="${kdC}">${kd}</td><td style="color:var(--green)">${(p.headshots||0).toLocaleString()}</td>
905
+ <td><span class="weapon-tag">${p.equipped_weapon||'β€”'}</span></td>
906
+ <td><span class="armor-tag" style="${(!p.equipped_armor||p.equipped_armor==='None')?'color:var(--dim);border-color:transparent':''}"> ${p.equipped_armor||'None'}</span></td>
907
+ <td><span class="gold-val">⬑ ${(p.gold||0).toLocaleString()}</span></td>
908
+ <td style="color:var(--purple);font-family:'Orbitron',sans-serif;font-size:12px">LVL ${p.level||1}</td>
909
+ <td>${p.win_rate.toFixed(0)}%</td>
910
+ <td style="color:var(--dim)">${parseFloat(p.playtime_hours||0).toFixed(1)}h</td>`;
911
+ } else {
912
+ tr.innerHTML=`<td><span class="rank-badge ${rc}">${rank}</span></td>
913
+ <td><strong>${p.username||'β€”'}</strong></td>
914
+ <td><span class="tier ${tier.cls}">${tier.name}</span></td>
915
+ <td style="color:var(--accent);font-family:'Orbitron',sans-serif;font-size:13px">${(p.rank_points||0).toLocaleString()}</td>
916
+ <td><span class="gold-val">⬑ ${(p.gold||0).toLocaleString()}</span></td>
917
+ <td>${p.win_rate.toFixed(0)}%</td>`;
918
+ }
919
+ tbody.appendChild(tr);
920
+ });
921
+
922
+ if(!li&&players.length>10){
923
+ const tr=document.createElement('tr');
924
+ tr.innerHTML=`<td colspan="6" style="text-align:center;color:var(--dim);padding:14px;border-top:1px solid var(--border)">
925
+ <span style="color:var(--accent)">+${players.length-10} more players</span> β€”
926
+ <span style="color:var(--accent);cursor:pointer;text-decoration:underline" onclick="goto('login')">Login to see full leaderboard</span>
927
+ </td>`;
928
+ tbody.appendChild(tr);
929
+ }
930
+ }
931
+
932
+ // ── PROFILE ──
933
+ function loadProfile() {
934
+ if (!authUser) return;
935
+ const me = players.find(p=>p.id===authUser.id);
936
+
937
+ $('prof-username-nav').textContent = authUser.username || authUser.email;
938
+ $('prof-display-name').textContent = authUser.username || 'β€”';
939
+ $('prof-email').textContent = authUser.email || 'β€”';
940
+ $('prof-avatar').textContent = (authUser.username||'?')[0].toUpperCase();
941
+ $('prof-new-username').value = authUser.username || '';
942
+ $('prof-new-email').value = authUser.email || '';
943
+
944
+ if (!me) return;
945
+
946
+ const tier = getTier(me.rank_points);
947
+ $('prof-tier-badge').textContent = tier.name;
948
+ $('prof-tier-badge').className = `tier ${tier.cls}`;
949
+ $('prof-level-badge').textContent = `LVL ${me.level||1}`;
950
+ $('prof-gold-badge').textContent = `⬑ ${(me.gold||0).toLocaleString()} GOLD`;
951
+ $('prof-rank-pts').textContent = (me.rank_points||0).toLocaleString();
952
+
953
+ const Tiers = [{min:0,max:1200},{min:1200,max:1800},{min:1800,max:2500},{min:2500,max:3000},{min:3000,max:4000}];
954
+ const cur = Tiers.find(t=>me.rank_points>=t.min&&me.rank_points<t.max) || Tiers[4];
955
+ const pct = Math.min(100, ((me.rank_points-cur.min)/(cur.max-cur.min))*100);
956
+ $('prof-rank-bar').style.width = pct+'%';
957
+ $('prof-tier-cur').textContent = tier.name;
958
+ $('prof-tier-next').textContent = tier.next || 'MAX RANK';
959
+
960
+ const stats = [
961
+ {val:(me.kills||0).toLocaleString(), lbl:'KILLS'},
962
+ {val:(me.deaths||0).toLocaleString(), lbl:'DEATHS'},
963
+ {val:me.kd.toFixed(2), lbl:'K/D RATIO'},
964
+ {val:(me.headshots||0).toLocaleString(), lbl:'HEADSHOTS'},
965
+ {val:(me.wins||0).toLocaleString(), lbl:'WINS'},
966
+ {val:(me.losses||0).toLocaleString(), lbl:'LOSSES'},
967
+ {val:me.win_rate.toFixed(0)+'%', lbl:'WIN RATE'},
968
+ {val:(me.rank_points||0).toLocaleString(), lbl:'RANK PTS'},
969
+ {val:parseFloat(me.playtime_hours||0).toFixed(1)+'h', lbl:'PLAYTIME'},
970
+ ];
971
+ $('prof-stats-grid').innerHTML = stats.map(s=>`
972
+ <div class="pstat"><div class="pstat-val">${s.val}</div><div class="pstat-lbl">${s.lbl}</div></div>
973
+ `).join('');
974
+
975
+ $('sb-tier').innerHTML = `<span class="tier ${tier.cls}">${tier.name}</span>`;
976
+ $('sb-rp').textContent = (me.rank_points||0).toLocaleString();
977
+ $('sb-next').textContent = tier.nextPts ? tier.nextPts.toLocaleString() : 'MAX';
978
+ $('sb-needed').textContent = tier.nextPts ? Math.max(0,tier.nextPts-me.rank_points).toLocaleString()+' pts' : 'β€”';
979
+ $('sb-weapon').textContent = me.equipped_weapon||'β€”';
980
+ $('sb-armor').textContent = me.equipped_armor||'None';
981
+ $('sb-level').textContent = `LVL ${me.level||1}`;
982
+ $('sb-wins').textContent = me.wins||0;
983
+ $('sb-losses').textContent = me.losses||0;
984
+ $('sb-wr').textContent = me.win_rate.toFixed(0)+'%';
985
+ $('sb-play').textContent = parseFloat(me.playtime_hours||0).toFixed(1)+' hrs';
986
+
987
+ const lastSeen = me.last_seen ? new Date(me.last_seen).toLocaleDateString() : 'β€”';
988
+ $('sb-lastseen').textContent = lastSeen;
989
+
990
+ const pos = players.findIndex(p=>p.id===authUser.id)+1;
991
+ $('sb-lbpos').textContent = pos > 0 ? `#${pos}` : 'β€”';
992
+ }
993
+
994
+ // ── PROFILE EDITS ──
995
+ async function updateUsername() {
996
+ const newName = $('prof-new-username').value.trim();
997
+ clearAlert('prof-username-alert');
998
+ if (!newName) { setAlert('prof-username-alert','error','⚠ USERNAME CANNOT BE EMPTY'); return; }
999
+ if (!/^[a-zA-Z0-9_]+$/.test(newName)) { setAlert('prof-username-alert','error','⚠ LETTERS, NUMBERS AND UNDERSCORES ONLY'); return; }
1000
+ try {
1001
+ const res = await fetch(`${SB_URL}/rest/v1/player_stats?id=eq.${authUser.id}`, {
1002
+ method:'PATCH',
1003
+ headers:{
1004
+ 'apikey':SB_KEY,
1005
+ 'Authorization':`Bearer ${authUser.token}`,
1006
+ 'Content-Type':'application/json',
1007
+ 'Prefer':'return=minimal'
1008
+ },
1009
+ body:JSON.stringify({username:newName})
1010
+ });
1011
+ if (!res.ok) throw new Error('Database rejected update. Check RLS Policies or try again later.');
1012
+
1013
+ // Update local state variables immediately
1014
+ authUser.username = newName;
1015
+ localStorage.setItem('warzone_session', JSON.stringify(authUser));
1016
+
1017
+ $('prof-display-name').textContent = newName;
1018
+ $('prof-username-nav').textContent = newName;
1019
+ $('prof-avatar').textContent = newName[0].toUpperCase();
1020
+ if($('dash-username')) $('dash-username').textContent = newName;
1021
+
1022
+ const p=players.find(p=>p.id===authUser.id);
1023
+ if(p) p.username=newName;
1024
+
1025
+ setAlert('prof-username-alert','success','βœ“ CALLSIGN UPDATED SUCCESSFULLY IN DATABASE');
1026
+
1027
+ // Force a fresh silent database pull in the background
1028
+ await loadData();
1029
+ } catch(e) {
1030
+ setAlert('prof-username-alert','error',`⚠ ${e.message.toUpperCase()}`);
1031
+ }
1032
+ }
1033
+
1034
+ async function updateEmail() {
1035
+ const newEmail = $('prof-new-email').value.trim();
1036
+ clearAlert('prof-email-alert');
1037
+ if (!newEmail || !newEmail.includes('@')) { setAlert('prof-email-alert','error','⚠ ENTER A VALID EMAIL'); return; }
1038
+ try {
1039
+ const res = await fetch(`${SB_URL}/auth/v1/user`, {
1040
+ method:'PUT',
1041
+ headers:{'apikey':SB_KEY,'Authorization':`Bearer ${authUser.token}`,'Content-Type':'application/json'},
1042
+ body:JSON.stringify({email:newEmail})
1043
+ });
1044
+ const data = await res.json();
1045
+ if (!res.ok) throw new Error(data.message||'Update failed');
1046
+ authUser.email = newEmail;
1047
+ $('prof-email').textContent = newEmail;
1048
+ setAlert('prof-email-alert','success','βœ“ EMAIL UPDATED β€” CHECK YOUR INBOX TO CONFIRM');
1049
+ } catch(e) {
1050
+ setAlert('prof-email-alert','error',`⚠ ${e.message.toUpperCase()}`);
1051
+ }
1052
+ }
1053
+
1054
+ async function updatePassword() {
1055
+ const pw = $('prof-new-pw').value;
1056
+ const cpw = $('prof-confirm-pw').value;
1057
+ clearAlert('prof-pw-alert');
1058
+ if (!pw || pw.length < 6) { setAlert('prof-pw-alert','error','⚠ PASSWORD MUST BE AT LEAST 6 CHARACTERS'); return; }
1059
+ if (pw !== cpw) { setAlert('prof-pw-alert','error','⚠ PASSWORDS DO NOT MATCH'); return; }
1060
+ try {
1061
+ const res = await fetch(`${SB_URL}/auth/v1/user`, {
1062
+ method:'PUT',
1063
+ headers:{'apikey':SB_KEY,'Authorization':`Bearer ${authUser.token}`,'Content-Type':'application/json'},
1064
+ body:JSON.stringify({password:pw})
1065
+ });
1066
+ const data = await res.json();
1067
+ if (!res.ok) throw new Error(data.message||'Update failed');
1068
+ $('prof-new-pw').value=''; $('prof-confirm-pw').value='';
1069
+ setAlert('prof-pw-alert','success','βœ“ PASSWORD UPDATED SUCCESSFULLY');
1070
+ } catch(e) {
1071
+ setAlert('prof-pw-alert','error',`⚠ ${e.message.toUpperCase()}`);
1072
+ }
1073
+ }
1074
+
1075
+ // ── INIT ──
1076
+ (async function init() {
1077
+ // Check for email verification redirect first
1078
+ const hashSession = parseUrlHash();
1079
+ if (hashSession) {
1080
+ try {
1081
+ // Simulate fake user structure so completeLoginSession maps it out perfectly
1082
+ const fakeAuthData = {
1083
+ access_token: hashSession.access_token,
1084
+ user: { id: hashSession.uid || '', email: hashSession.email || 'operator@warzone.gg' }
1085
+ };
1086
+ await completeLoginSession(fakeAuthData);
1087
+ return; // Skip normal initialization
1088
+ } catch(err) { console.error("Redirect parsing failed", err); }
1089
+ }
1090
+
1091
+ // Fall back to localStorage session if no hash exists
1092
+ const savedSession = localStorage.getItem('warzone_session');
1093
+ if (savedSession) {
1094
+ try { authUser = JSON.parse(savedSession); } catch(e) { localStorage.removeItem('warzone_session'); }
1095
+ }
1096
+
1097
+ await loadData();
1098
+
1099
+ const overlay = $('loading-screen');
1100
+ if(overlay) {
1101
+ overlay.classList.add('hidden');
1102
+ setTimeout(()=>overlay.style.display='none',500);
1103
+ }
1104
+
1105
+ if (authUser) {
1106
+ goto('dashboard');
1107
+ } else {
1108
+ goto('login');
1109
+ }
1110
+ })();
1111
+ </script>
1112
+ </body>
1113
+ </html>