Barkmaster commited on
Commit
c18d2a6
·
verified ·
1 Parent(s): 3f017fc

Delete index.html

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