Spaces:
Running
Running
Delete index.html
Browse files- index.html +0 -1021
index.html
DELETED
|
@@ -1,1021 +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> 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 |
-
·
|
| 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> 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()" 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()" 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. Your stats and progress are always saved automatically.</p>
|
| 502 |
-
<button class="btn btn-danger" onclick="doLogout()" 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 ───────────────────────────────────────
|
| 611 |
-
function isLoggedIn() { return authUser !== null; }
|
| 612 |
-
|
| 613 |
-
async function doLogin() {
|
| 614 |
-
const email = $('login-email').value.trim();
|
| 615 |
-
const pass = $('login-password').value;
|
| 616 |
-
clearAlert('login-alert');
|
| 617 |
-
if (!email || !pass) { setAlert('login-alert','error','⚠ EMAIL AND PASSWORD REQUIRED'); return; }
|
| 618 |
-
const btn = $('login-btn');
|
| 619 |
-
btn.disabled = true; btn.textContent = 'AUTHENTICATING...';
|
| 620 |
-
try {
|
| 621 |
-
const res = await fetch(`${SB_URL}/auth/v1/token?grant_type=password`, {
|
| 622 |
-
method:'POST',
|
| 623 |
-
headers:{'apikey':SB_KEY,'Content-Type':'application/json'},
|
| 624 |
-
body:JSON.stringify({email,password:pass})
|
| 625 |
-
});
|
| 626 |
-
const data = await res.json();
|
| 627 |
-
if (!res.ok) throw new Error(data.error_description || data.message || 'Login failed');
|
| 628 |
-
|
| 629 |
-
const userId = data.user?.id;
|
| 630 |
-
let username = data.user?.email?.split('@')[0];
|
| 631 |
-
try {
|
| 632 |
-
const sr = await fetch(`${SB_URL}/rest/v1/player_stats?id=eq.${userId}&select=username`, {
|
| 633 |
-
headers:{'apikey':SB_KEY,'Authorization':`Bearer ${data.access_token}`}
|
| 634 |
-
});
|
| 635 |
-
const sd = await sr.json();
|
| 636 |
-
if (sd?.[0]?.username) username = sd[0].username;
|
| 637 |
-
} catch(_) {}
|
| 638 |
-
|
| 639 |
-
authUser = { id:userId, email:data.user?.email, username, token:data.access_token };
|
| 640 |
-
$('login-email').value=''; $('login-password').value='';
|
| 641 |
-
await loadData();
|
| 642 |
-
goto('dashboard');
|
| 643 |
-
} catch(e) {
|
| 644 |
-
setAlert('login-alert','error',`⚠ ${e.message.toUpperCase()}`);
|
| 645 |
-
} finally {
|
| 646 |
-
btn.disabled=false; btn.textContent='AUTHENTICATE';
|
| 647 |
-
}
|
| 648 |
-
}
|
| 649 |
-
|
| 650 |
-
async function doRegister() {
|
| 651 |
-
const username = $('reg-username').value.trim();
|
| 652 |
-
const email = $('reg-email').value.trim();
|
| 653 |
-
const pass = $('reg-password').value;
|
| 654 |
-
const confirm = $('reg-confirm').value;
|
| 655 |
-
clearAlert('reg-alert');
|
| 656 |
-
|
| 657 |
-
if (!username || !email || !pass) { setAlert('reg-alert','error','⚠ ALL FIELDS REQUIRED'); return; }
|
| 658 |
-
if (pass !== confirm) { setAlert('reg-alert','error','⚠ PASSWORDS DO NOT MATCH'); return; }
|
| 659 |
-
if (pass.length < 6) { setAlert('reg-alert','error','⚠ PASSWORD MUST BE AT LEAST 6 CHARACTERS'); return; }
|
| 660 |
-
if (!/^[a-zA-Z0-9_]+$/.test(username)) { setAlert('reg-alert','error','⚠ USERNAME: LETTERS, NUMBERS AND UNDERSCORES ONLY'); return; }
|
| 661 |
-
|
| 662 |
-
const btn = $('reg-btn');
|
| 663 |
-
btn.disabled=true; btn.textContent='ENLISTING...';
|
| 664 |
-
try {
|
| 665 |
-
// Create auth user
|
| 666 |
-
const authRes = await fetch(`${SB_URL}/auth/v1/signup`, {
|
| 667 |
-
method:'POST',
|
| 668 |
-
headers:{'apikey':SB_KEY,'Content-Type':'application/json'},
|
| 669 |
-
body:JSON.stringify({email,password:pass})
|
| 670 |
-
});
|
| 671 |
-
const authData = await authRes.json();
|
| 672 |
-
if (!authRes.ok) throw new Error(authData.error_description || authData.message || 'Registration failed');
|
| 673 |
-
if (!authData.user?.id) throw new Error('No user ID returned — email may already be registered');
|
| 674 |
-
|
| 675 |
-
const userId = authData.user.id;
|
| 676 |
-
const token = authData.access_token || SB_KEY;
|
| 677 |
-
|
| 678 |
-
// Insert player_stats row
|
| 679 |
-
const statsRes = await fetch(`${SB_URL}/rest/v1/player_stats`, {
|
| 680 |
-
method:'POST',
|
| 681 |
-
headers:{
|
| 682 |
-
'apikey':SB_KEY,
|
| 683 |
-
'Authorization':`Bearer ${token}`,
|
| 684 |
-
'Content-Type':'application/json',
|
| 685 |
-
'Prefer':'return=minimal'
|
| 686 |
-
},
|
| 687 |
-
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 })
|
| 688 |
-
});
|
| 689 |
-
if (!statsRes.ok && statsRes.status !== 201) {
|
| 690 |
-
const e = await statsRes.json().catch(()=>({}));
|
| 691 |
-
// Don't throw — stats row might already exist, user was created OK
|
| 692 |
-
console.warn('Stats insert:', e);
|
| 693 |
-
}
|
| 694 |
-
|
| 695 |
-
setAlert('reg-alert','success','✓ ACCOUNT CREATED! Redirecting to login...');
|
| 696 |
-
setTimeout(() => { goto('login'); setAlert('login-alert','success','✓ REGISTERED — SIGN IN NOW'); }, 1800);
|
| 697 |
-
} catch(e) {
|
| 698 |
-
setAlert('reg-alert','error',`⚠ ${e.message.toUpperCase()}`);
|
| 699 |
-
} finally {
|
| 700 |
-
btn.disabled=false; btn.textContent='ENLIST';
|
| 701 |
-
}
|
| 702 |
-
}
|
| 703 |
-
|
| 704 |
-
function doLogout() {
|
| 705 |
-
authUser = null;
|
| 706 |
-
players = [];
|
| 707 |
-
Object.values(charts).forEach(c=>c.destroy()); charts={};
|
| 708 |
-
loadData();
|
| 709 |
-
goto('login');
|
| 710 |
-
}
|
| 711 |
-
|
| 712 |
-
// ── TEST ACCOUNTS ──────────────────────────────
|
| 713 |
-
function buildTestAccounts() {
|
| 714 |
-
const list = $('test-accounts-list');
|
| 715 |
-
list.innerHTML = TEST_ACCOUNTS.map((a,i) => `
|
| 716 |
-
<div class="test-account" onclick="fillLogin('${a.email}','${TEST_PASSWORD}')">
|
| 717 |
-
<div><div class="ta-user">${a.username}</div><div class="ta-email">${a.email}</div></div>
|
| 718 |
-
<div class="ta-use">USE →</div>
|
| 719 |
-
</div>
|
| 720 |
-
`).join('');
|
| 721 |
-
}
|
| 722 |
-
function fillLogin(email, pass) {
|
| 723 |
-
$('login-email').value = email;
|
| 724 |
-
$('login-password').value = pass;
|
| 725 |
-
$('login-email').focus();
|
| 726 |
-
}
|
| 727 |
-
|
| 728 |
-
// ── DATA ───────────────────────────────────────
|
| 729 |
-
async function loadData() {
|
| 730 |
-
try {
|
| 731 |
-
const res = await fetch(`${SB_URL}/rest/v1/player_stats?select=*&order=rank_points.desc`, {
|
| 732 |
-
headers:{'apikey':SB_KEY,'Authorization':`Bearer ${SB_KEY}`}
|
| 733 |
-
});
|
| 734 |
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
| 735 |
-
const raw = await res.json();
|
| 736 |
-
players = raw.map(p => ({
|
| 737 |
-
...p,
|
| 738 |
-
kd: p.deaths > 0 ? p.kills/p.deaths : p.kills,
|
| 739 |
-
win_rate: (p.wins+p.losses) > 0 ? p.wins/(p.wins+p.losses)*100 : 0
|
| 740 |
-
}));
|
| 741 |
-
renderKPIs();
|
| 742 |
-
renderCharts();
|
| 743 |
-
populateFilters();
|
| 744 |
-
renderTable();
|
| 745 |
-
populateHeroStats();
|
| 746 |
-
const now = new Date();
|
| 747 |
-
$('dash-last-refresh').textContent = now.toLocaleTimeString();
|
| 748 |
-
$('dash-error').style.display = 'none';
|
| 749 |
-
} catch(e) {
|
| 750 |
-
$('dash-error').style.display='block';
|
| 751 |
-
$('dash-error').textContent=`⚠ DATABASE ERROR: ${e.message}`;
|
| 752 |
-
}
|
| 753 |
-
}
|
| 754 |
-
|
| 755 |
-
function populateHeroStats() {
|
| 756 |
-
const totalKills = players.reduce((a,p)=>a+(p.kills||0),0);
|
| 757 |
-
const totalGold = players.reduce((a,p)=>a+(p.gold||0),0);
|
| 758 |
-
const top = players[0];
|
| 759 |
-
$('hs-players').textContent = players.length;
|
| 760 |
-
$('hs-kills').textContent = fmt(totalKills);
|
| 761 |
-
$('hs-gold').textContent = fmt(totalGold);
|
| 762 |
-
$('hs-top').textContent = top ? fmt(top.rank_points) : '—';
|
| 763 |
-
}
|
| 764 |
-
|
| 765 |
-
function renderKPIs() {
|
| 766 |
-
const totalKills = players.reduce((a,p)=>a+(p.kills||0),0);
|
| 767 |
-
const totalGold = players.reduce((a,p)=>a+(p.gold||0),0);
|
| 768 |
-
const totalHS = players.reduce((a,p)=>a+(p.headshots||0),0);
|
| 769 |
-
const totalPlay = players.reduce((a,p)=>a+(parseFloat(p.playtime_hours)||0),0);
|
| 770 |
-
const top = players[0];
|
| 771 |
-
const topWR = [...players].sort((a,b)=>b.win_rate-a.win_rate)[0];
|
| 772 |
-
$('dk-players').textContent = players.length;
|
| 773 |
-
$('dk-gold').textContent = fmt(totalGold);
|
| 774 |
-
$('dk-kills').textContent = fmt(totalKills);
|
| 775 |
-
$('dk-hs').textContent = fmt(totalHS);
|
| 776 |
-
$('dk-play').textContent = Math.round(totalPlay).toLocaleString();
|
| 777 |
-
if (top) { $('dk-toprank').textContent=fmt(top.rank_points); $('dk-topplayer').textContent=top.username||'—'; }
|
| 778 |
-
if (topWR) { $('dk-topwr').textContent=topWR.win_rate.toFixed(0)+'%'; $('dk-topwrplayer').textContent=topWR.username||'—'; }
|
| 779 |
-
}
|
| 780 |
-
|
| 781 |
-
// ── CHARTS ─────────────────────────────────────
|
| 782 |
-
const CC = { c:'#00d4ff',r:'#ff4444',g:'#ffd700',e:'#39ff14',p:'#b044ff',o:'#ff8800',b:'#4488ff' };
|
| 783 |
-
|
| 784 |
-
function renderCharts() {
|
| 785 |
-
Object.values(charts).forEach(c=>c.destroy()); charts={};
|
| 786 |
-
if (!isLoggedIn()) return;
|
| 787 |
-
Chart.defaults.color='#4a7a9b';
|
| 788 |
-
Chart.defaults.font.family="'Share Tech Mono',monospace";
|
| 789 |
-
const top5=players.slice(0,5), top8=players.slice(0,8);
|
| 790 |
-
|
| 791 |
-
charts.rank = new Chart($('rankChart'), { type:'bar', data:{
|
| 792 |
-
labels:top5.map(p=>p.username),
|
| 793 |
-
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}]
|
| 794 |
-
}, 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}}}}}});
|
| 795 |
-
|
| 796 |
-
const wm={}; players.forEach(p=>{const w=p.equipped_weapon||'Unknown';wm[w]=(wm[w]||0)+1;});
|
| 797 |
-
charts.weapon = new Chart($('weaponChart'), { type:'doughnut', data:{
|
| 798 |
-
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}]
|
| 799 |
-
}, options:{responsive:true,maintainAspectRatio:true,cutout:'65%',plugins:{legend:{position:'right',labels:{color:'#cee8ff',font:{size:10},padding:8,boxWidth:10}}}}});
|
| 800 |
-
|
| 801 |
-
charts.kd = new Chart($('kdChart'), { type:'bar', data:{
|
| 802 |
-
labels:top8.map(p=>p.username),
|
| 803 |
-
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}]
|
| 804 |
-
}, 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}}}}}});
|
| 805 |
-
|
| 806 |
-
const tg=[...players].sort((a,b)=>b.gold-a.gold).slice(0,5);
|
| 807 |
-
charts.gold = new Chart($('goldChart'), { type:'bar', data:{
|
| 808 |
-
labels:tg.map(p=>p.username),datasets:[{label:'Gold',data:tg.map(p=>p.gold),backgroundColor:CC.g+'bb',borderWidth:0,borderRadius:2}]
|
| 809 |
-
}, 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}}}}}});
|
| 810 |
-
|
| 811 |
-
const wd=[...players].sort((a,b)=>b.win_rate-a.win_rate);
|
| 812 |
-
charts.win = new Chart($('winChart'), { type:'bar', data:{
|
| 813 |
-
labels:wd.map(p=>p.username),
|
| 814 |
-
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}]
|
| 815 |
-
}, 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}}}}}});
|
| 816 |
-
}
|
| 817 |
-
|
| 818 |
-
// ── TABLE ──────────────────────────────────────
|
| 819 |
-
function populateFilters() {
|
| 820 |
-
const weapons=[...new Set(players.map(p=>p.equipped_weapon).filter(Boolean))];
|
| 821 |
-
const sel=$('filter-weapon'); sel.innerHTML='<option value="">ALL</option>';
|
| 822 |
-
weapons.forEach(w=>{const o=document.createElement('option');o.value=w;o.textContent=w;sel.appendChild(o);});
|
| 823 |
-
}
|
| 824 |
-
|
| 825 |
-
function renderTable() {
|
| 826 |
-
const li=isLoggedIn();
|
| 827 |
-
$('lb-head').innerHTML = li
|
| 828 |
-
? `<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>`
|
| 829 |
-
: `<tr><th>#</th><th>PLAYER</th><th>TIER</th><th>RANK PTS</th><th>GOLD</th><th>WIN RATE</th></tr>`;
|
| 830 |
-
|
| 831 |
-
const wf = li ? $('filter-weapon').value : '';
|
| 832 |
-
const sb = li ? $('sort-by').value : 'rank_points';
|
| 833 |
-
let data=[...players];
|
| 834 |
-
if (wf) data=data.filter(p=>p.equipped_weapon===wf);
|
| 835 |
-
data.sort((a,b)=>sb==='kd'?b.kd-a.kd:(b[sb]||0)-(a[sb]||0));
|
| 836 |
-
if (!li) data=data.slice(0,10);
|
| 837 |
-
|
| 838 |
-
const tbody=$('lb-body'); tbody.innerHTML='';
|
| 839 |
-
data.forEach((p,i)=>{
|
| 840 |
-
const rank=i+1, tier=getTier(p.rank_points);
|
| 841 |
-
const rc=rank===1?'r1':rank===2?'r2':rank===3?'r3':'rn';
|
| 842 |
-
const isOwn=li&&authUser&&p.id===authUser.id;
|
| 843 |
-
const tr=document.createElement('tr');
|
| 844 |
-
if(isOwn)tr.classList.add('own-row');
|
| 845 |
-
if(li){
|
| 846 |
-
const kd=p.kd.toFixed(2),kdC=p.kd>=3?'kd-g':p.kd>=1?'kd-o':'kd-b';
|
| 847 |
-
tr.innerHTML=`<td><span class="rank-badge ${rc}">${rank}</span></td>
|
| 848 |
-
<td><strong style="color:${isOwn?'var(--accent)':'#cee8ff'}">${p.username||'—'}${isOwn?' ◀':''}</strong></td>
|
| 849 |
-
<td><span class="tier ${tier.cls}">${tier.name}</span></td>
|
| 850 |
-
<td style="color:var(--accent);font-family:'Orbitron',sans-serif;font-size:13px">${(p.rank_points||0).toLocaleString()}</td>
|
| 851 |
-
<td class="${kdC}">${kd}</td><td style="color:var(--green)">${(p.headshots||0).toLocaleString()}</td>
|
| 852 |
-
<td><span class="weapon-tag">${p.equipped_weapon||'—'}</span></td>
|
| 853 |
-
<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>
|
| 854 |
-
<td><span class="gold-val">⬡ ${(p.gold||0).toLocaleString()}</span></td>
|
| 855 |
-
<td style="color:var(--purple);font-family:'Orbitron',sans-serif;font-size:12px">LVL ${p.level||1}</td>
|
| 856 |
-
<td>${p.win_rate.toFixed(0)}%</td>
|
| 857 |
-
<td style="color:var(--dim)">${parseFloat(p.playtime_hours||0).toFixed(1)}h</td>`;
|
| 858 |
-
} else {
|
| 859 |
-
tr.innerHTML=`<td><span class="rank-badge ${rc}">${rank}</span></td>
|
| 860 |
-
<td><strong>${p.username||'—'}</strong></td>
|
| 861 |
-
<td><span class="tier ${tier.cls}">${tier.name}</span></td>
|
| 862 |
-
<td style="color:var(--accent);font-family:'Orbitron',sans-serif;font-size:13px">${(p.rank_points||0).toLocaleString()}</td>
|
| 863 |
-
<td><span class="gold-val">⬡ ${(p.gold||0).toLocaleString()}</span></td>
|
| 864 |
-
<td>${p.win_rate.toFixed(0)}%</td>`;
|
| 865 |
-
}
|
| 866 |
-
tbody.appendChild(tr);
|
| 867 |
-
});
|
| 868 |
-
|
| 869 |
-
if(!li&&players.length>10){
|
| 870 |
-
const tr=document.createElement('tr');
|
| 871 |
-
tr.innerHTML=`<td colspan="6" style="text-align:center;color:var(--dim);padding:14px;border-top:1px solid var(--border)">
|
| 872 |
-
<span style="color:var(--accent)">+${players.length-10} more players</span> —
|
| 873 |
-
<span style="color:var(--accent);cursor:pointer;text-decoration:underline" onclick="goto('login')">Login to see full leaderboard</span>
|
| 874 |
-
</td>`;
|
| 875 |
-
tbody.appendChild(tr);
|
| 876 |
-
}
|
| 877 |
-
}
|
| 878 |
-
|
| 879 |
-
// ── PROFILE ────────────────────────────────────
|
| 880 |
-
function loadProfile() {
|
| 881 |
-
if (!authUser) return;
|
| 882 |
-
const me = players.find(p=>p.id===authUser.id);
|
| 883 |
-
|
| 884 |
-
$('prof-username-nav').textContent = authUser.username || authUser.email;
|
| 885 |
-
$('prof-display-name').textContent = authUser.username || '—';
|
| 886 |
-
$('prof-email').textContent = authUser.email || '—';
|
| 887 |
-
$('prof-avatar').textContent = (authUser.username||'?')[0].toUpperCase();
|
| 888 |
-
$('prof-new-username').value = authUser.username || '';
|
| 889 |
-
$('prof-new-email').value = authUser.email || '';
|
| 890 |
-
|
| 891 |
-
if (!me) return;
|
| 892 |
-
|
| 893 |
-
const tier = getTier(me.rank_points);
|
| 894 |
-
$('prof-tier-badge').textContent = tier.name;
|
| 895 |
-
$('prof-tier-badge').className = `tier ${tier.cls}`;
|
| 896 |
-
$('prof-level-badge').textContent = `LVL ${me.level||1}`;
|
| 897 |
-
$('prof-gold-badge').textContent = `⬡ ${(me.gold||0).toLocaleString()} GOLD`;
|
| 898 |
-
$('prof-rank-pts').textContent = (me.rank_points||0).toLocaleString();
|
| 899 |
-
|
| 900 |
-
// Rank progress bar
|
| 901 |
-
const tiers = [{min:0,max:1200},{min:1200,max:1800},{min:1800,max:2500},{min:2500,max:3000},{min:3000,max:4000}];
|
| 902 |
-
const cur = tiers.find(t=>me.rank_points>=t.min&&me.rank_points<t.max) || tiers[4];
|
| 903 |
-
const pct = Math.min(100, ((me.rank_points-cur.min)/(cur.max-cur.min))*100);
|
| 904 |
-
$('prof-rank-bar').style.width = pct+'%';
|
| 905 |
-
$('prof-tier-cur').textContent = tier.name;
|
| 906 |
-
$('prof-tier-next').textContent = tier.next || 'MAX RANK';
|
| 907 |
-
|
| 908 |
-
// Stats grid
|
| 909 |
-
const stats = [
|
| 910 |
-
{val:(me.kills||0).toLocaleString(), lbl:'KILLS'},
|
| 911 |
-
{val:(me.deaths||0).toLocaleString(), lbl:'DEATHS'},
|
| 912 |
-
{val:me.kd.toFixed(2), lbl:'K/D RATIO'},
|
| 913 |
-
{val:(me.headshots||0).toLocaleString(), lbl:'HEADSHOTS'},
|
| 914 |
-
{val:(me.wins||0).toLocaleString(), lbl:'WINS'},
|
| 915 |
-
{val:(me.losses||0).toLocaleString(), lbl:'LOSSES'},
|
| 916 |
-
{val:me.win_rate.toFixed(0)+'%', lbl:'WIN RATE'},
|
| 917 |
-
{val:(me.rank_points||0).toLocaleString(), lbl:'RANK PTS'},
|
| 918 |
-
];
|
| 919 |
-
$('prof-stats-grid').innerHTML = stats.map(s=>`
|
| 920 |
-
<div class="pstat"><div class="pstat-val">${s.val}</div><div class="pstat-lbl">${s.lbl}</div></div>
|
| 921 |
-
`).join('');
|
| 922 |
-
|
| 923 |
-
// Sidebar
|
| 924 |
-
$('sb-tier').innerHTML = `<span class="tier ${tier.cls}">${tier.name}</span>`;
|
| 925 |
-
$('sb-rp').textContent = (me.rank_points||0).toLocaleString();
|
| 926 |
-
$('sb-next').textContent = tier.nextPts ? tier.nextPts.toLocaleString() : 'MAX';
|
| 927 |
-
$('sb-needed').textContent = tier.nextPts ? Math.max(0,tier.nextPts-me.rank_points).toLocaleString()+' pts' : '—';
|
| 928 |
-
$('sb-weapon').textContent = me.equipped_weapon||'—';
|
| 929 |
-
$('sb-armor').textContent = me.equipped_armor||'None';
|
| 930 |
-
$('sb-level').textContent = `LVL ${me.level||1}`;
|
| 931 |
-
$('sb-wins').textContent = me.wins||0;
|
| 932 |
-
$('sb-losses').textContent = me.losses||0;
|
| 933 |
-
$('sb-wr').textContent = me.win_rate.toFixed(0)+'%';
|
| 934 |
-
$('sb-play').textContent = parseFloat(me.playtime_hours||0).toFixed(1)+' hrs';
|
| 935 |
-
|
| 936 |
-
const lastSeen = me.last_seen ? new Date(me.last_seen).toLocaleDateString() : '—';
|
| 937 |
-
$('sb-lastseen').textContent = lastSeen;
|
| 938 |
-
|
| 939 |
-
const pos = players.findIndex(p=>p.id===authUser.id)+1;
|
| 940 |
-
$('sb-lbpos').textContent = pos > 0 ? `#${pos}` : '—';
|
| 941 |
-
}
|
| 942 |
-
|
| 943 |
-
// ── PROFILE EDITS ──────────────────────────────
|
| 944 |
-
async function updateUsername() {
|
| 945 |
-
const newName = $('prof-new-username').value.trim();
|
| 946 |
-
clearAlert('prof-username-alert');
|
| 947 |
-
if (!newName) { setAlert('prof-username-alert','error','⚠ USERNAME CANNOT BE EMPTY'); return; }
|
| 948 |
-
if (!/^[a-zA-Z0-9_]+$/.test(newName)) { setAlert('prof-username-alert','error','⚠ LETTERS, NUMBERS AND UNDERSCORES ONLY'); return; }
|
| 949 |
-
try {
|
| 950 |
-
const res = await fetch(`${SB_URL}/rest/v1/player_stats?id=eq.${authUser.id}`, {
|
| 951 |
-
method:'PATCH',
|
| 952 |
-
headers:{'apikey':SB_KEY,'Authorization':`Bearer ${authUser.token}`,'Content-Type':'application/json','Prefer':'return=minimal'},
|
| 953 |
-
body:JSON.stringify({username:newName})
|
| 954 |
-
});
|
| 955 |
-
if (!res.ok) throw new Error('Update failed');
|
| 956 |
-
authUser.username = newName;
|
| 957 |
-
$('prof-display-name').textContent = newName;
|
| 958 |
-
$('prof-username-nav').textContent = newName;
|
| 959 |
-
$('prof-avatar').textContent = newName[0].toUpperCase();
|
| 960 |
-
$('dash-username').textContent = newName;
|
| 961 |
-
const p=players.find(p=>p.id===authUser.id);
|
| 962 |
-
if(p) p.username=newName;
|
| 963 |
-
setAlert('prof-username-alert','success','✓ CALLSIGN UPDATED SUCCESSFULLY');
|
| 964 |
-
} catch(e) {
|
| 965 |
-
setAlert('prof-username-alert','error',`⚠ ${e.message.toUpperCase()}`);
|
| 966 |
-
}
|
| 967 |
-
}
|
| 968 |
-
|
| 969 |
-
async function updateEmail() {
|
| 970 |
-
const newEmail = $('prof-new-email').value.trim();
|
| 971 |
-
clearAlert('prof-email-alert');
|
| 972 |
-
if (!newEmail || !newEmail.includes('@')) { setAlert('prof-email-alert','error','⚠ ENTER A VALID EMAIL'); return; }
|
| 973 |
-
try {
|
| 974 |
-
const res = await fetch(`${SB_URL}/auth/v1/user`, {
|
| 975 |
-
method:'PUT',
|
| 976 |
-
headers:{'apikey':SB_KEY,'Authorization':`Bearer ${authUser.token}`,'Content-Type':'application/json'},
|
| 977 |
-
body:JSON.stringify({email:newEmail})
|
| 978 |
-
});
|
| 979 |
-
const data = await res.json();
|
| 980 |
-
if (!res.ok) throw new Error(data.message||'Update failed');
|
| 981 |
-
authUser.email = newEmail;
|
| 982 |
-
$('prof-email').textContent = newEmail;
|
| 983 |
-
setAlert('prof-email-alert','success','✓ EMAIL UPDATED — CHECK YOUR INBOX TO CONFIRM');
|
| 984 |
-
} catch(e) {
|
| 985 |
-
setAlert('prof-email-alert','error',`⚠ ${e.message.toUpperCase()}`);
|
| 986 |
-
}
|
| 987 |
-
}
|
| 988 |
-
|
| 989 |
-
async function updatePassword() {
|
| 990 |
-
const pw = $('prof-new-pw').value;
|
| 991 |
-
const cpw = $('prof-confirm-pw').value;
|
| 992 |
-
clearAlert('prof-pw-alert');
|
| 993 |
-
if (!pw || pw.length < 6) { setAlert('prof-pw-alert','error','⚠ PASSWORD MUST BE AT LEAST 6 CHARACTERS'); return; }
|
| 994 |
-
if (pw !== cpw) { setAlert('prof-pw-alert','error','⚠ PASSWORDS DO NOT MATCH'); return; }
|
| 995 |
-
try {
|
| 996 |
-
const res = await fetch(`${SB_URL}/auth/v1/user`, {
|
| 997 |
-
method:'PUT',
|
| 998 |
-
headers:{'apikey':SB_KEY,'Authorization':`Bearer ${authUser.token}`,'Content-Type':'application/json'},
|
| 999 |
-
body:JSON.stringify({password:pw})
|
| 1000 |
-
});
|
| 1001 |
-
const data = await res.json();
|
| 1002 |
-
if (!res.ok) throw new Error(data.message||'Update failed');
|
| 1003 |
-
$('prof-new-pw').value=''; $('prof-confirm-pw').value='';
|
| 1004 |
-
setAlert('prof-pw-alert','success','✓ PASSWORD UPDATED SUCCESSFULLY');
|
| 1005 |
-
} catch(e) {
|
| 1006 |
-
setAlert('prof-pw-alert','error',`⚠ ${e.message.toUpperCase()}`);
|
| 1007 |
-
}
|
| 1008 |
-
}
|
| 1009 |
-
|
| 1010 |
-
// ── INIT ───────────────────────────────────────
|
| 1011 |
-
(async function init() {
|
| 1012 |
-
buildTestAccounts();
|
| 1013 |
-
await loadData();
|
| 1014 |
-
const overlay = $('loading-screen');
|
| 1015 |
-
overlay.classList.add('hidden');
|
| 1016 |
-
setTimeout(()=>overlay.style.display='none',500);
|
| 1017 |
-
goto('login');
|
| 1018 |
-
})();
|
| 1019 |
-
</script>
|
| 1020 |
-
</body>
|
| 1021 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|