From 500a0219b845a5bcc0d54dff3630793daff314cd Mon Sep 17 00:00:00 2001 From: TomTomGames Date: Fri, 15 May 2026 16:46:08 -0500 Subject: [PATCH] v1.0.4 - Clean start --- README.md | 31 +- includes/auth.php | 230 +++++--------- includes/config.php | 14 +- public_html/.htaccess | 92 +++++- public_html/admin/index.php | 574 +++++++++++++++++++--------------- public_html/api/admin.php | 62 +++- public_html/api/me.php | 5 +- public_html/api/referrals.php | 11 +- public_html/favicon.ico | Bin 0 -> 136 bytes public_html/favicon.svg | 5 + public_html/get_location.php | 108 ------- public_html/index.php | 503 +++++++++++++++-------------- public_html/phpcheck.php | 25 -- public_html/test.php | 6 - public_html/test_login.php | 18 -- public_html/test_mail.php | 91 ------ push.bat | 5 +- 17 files changed, 846 insertions(+), 934 deletions(-) create mode 100644 public_html/favicon.ico create mode 100644 public_html/favicon.svg delete mode 100644 public_html/get_location.php delete mode 100644 public_html/phpcheck.php delete mode 100644 public_html/test.php delete mode 100644 public_html/test_login.php delete mode 100644 public_html/test_mail.php diff --git a/README.md b/README.md index b649688..33e7bb7 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,24 @@ # TomTomGames Platform -Private gaming portal platform. Built on PHP/MySQL with LiteSpeed/CyberPanel hosting. +Private gaming portal. PHP/MySQL on LiteSpeed/CyberPanel. ## Stack -- **Backend:** PHP 8.5, MySQL (CyberPanel/LiteSpeed) -- **Payments:** Square SDK (card) + manual (Venmo/Zelle/CashApp/Chime) -- **Email:** SendGrid HTTP API -- **Frontend:** Vanilla JS SPA +- Backend: PHP 8.5, MySQL (MariaDB) +- Payments: Square (card) + manual (Venmo/Zelle/CashApp/Chime) +- Email: SendGrid HTTP API +- Frontend: Vanilla JS SPA -## Structure -``` -includes/ PHP shared includes (config, db, auth, mailer, square) -public_html/ Web root - api/ REST API endpoints - admin/ Admin panel - assets/ Static assets -``` - -## Versioning -Each build increments via `bump_version.php` on the live server. -The `app_version` DB table tracks all versions. Footer shows current version. +## Local path +`C:\Users\myron\Downloads\CyberPanel\` ## Version History | Version | Date | Notes | |---------|------|-------| | 1.0.0 | 2026-05-08 | Initial release | -| 1.0.1 | 2026-05-10 | Referral system, dynamic payments, full audit log | +| 1.0.1 | 2026-05-10 | Referral system, dynamic payments, audit log | +| 1.0.2 | 2026-05-11 | Registration fix, referral tables, security | +| 1.0.3 | 2026-05-12 | Typography, broadcasts, profile tabs, copyright | +| 1.0.4 | 2026-05-15 | Logout fix, admin elevation, $5 default token | ## ⚠️ Private Repository -This repo contains API keys in `includes/config.php`. Keep private at all times. +Contains API keys in includes/config.php — keep private. diff --git a/includes/auth.php b/includes/auth.php index 19466aa..3ff21f4 100644 --- a/includes/auth.php +++ b/includes/auth.php @@ -6,16 +6,31 @@ function isLoggedIn(): bool { return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']); } -function requireLogin(): void { - if (!isLoggedIn()) { - header('Location: /'); exit; + +function logoutUser(): void { + // Clear all session data + $_SESSION = []; + // Destroy session cookie + if (isset($_COOKIE[session_name()])) { + setcookie(session_name(), '', [ + 'expires' => time() - 3600, + 'path' => '/', + 'secure' => true, + 'httponly' => true, + 'samesite' => 'Lax', + ]); } + session_destroy(); +} + +function requireLogin(): void { + if (!isLoggedIn()) { header('Location: /'); exit; } } function requireAdmin(): void { requireLogin(); if (empty($_SESSION['is_admin'])) { - header('Location: /'); exit; + header('Location: /admin/login.php'); exit; } } @@ -32,262 +47,163 @@ function loginUser(string $username, string $password): array { $user = $stmt->fetch(); if (!$user || !password_verify($password, $user['password'])) { + logActivity('LOGIN_FAILED', null, null, 'auth', 0, 'Failed login: '.$username, '', 'security', '', '', 'warning'); return ['success' => false, 'error' => 'Invalid username or password.']; } - // Block unverified accounts — admins are always exempt if (!$user['email_verified'] && !$user['is_admin']) { - return [ - 'success' => false, - 'error' => 'Please verify your email address before logging in. Check your inbox for the verification link.', - 'unverified' => true, - 'email' => $user['email'], - ]; + return ['success'=>false,'error'=>'Please verify your email address before logging in.','unverified'=>true,'email'=>$user['email']]; } - // Auto-verify admin accounts so they're never locked out if ($user['is_admin'] && !$user['email_verified']) { db()->prepare("UPDATE users SET email_verified=1 WHERE id=?")->execute([$user['id']]); $user['email_verified'] = 1; } + session_regenerate_id(true); $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; $_SESSION['alias'] = $user['alias']; $_SESSION['is_admin'] = $user['is_admin']; db()->prepare("UPDATE users SET last_login=NOW() WHERE id=?")->execute([$user['id']]); + logActivity('LOGIN_SUCCESS', (int)$user['id'], null, 'auth', (int)$user['id'], 'Login OK', '', 'auth', '', '', 'info'); return ['success' => true, 'user' => $user]; } -/** - * Stage a registration: store in pending_registrations, send verification email. - * Does NOT create the user row yet. - */ function initiateRegistration(string $username, string $password, string $alias, string $email, string $referralCode = ''): array { $username = strtolower(trim($username)); $alias = trim($alias); $email = strtolower(trim($email)); - // Validate if (strlen($username) < 3 || strlen($username) > 50) - return ['success' => false, 'error' => 'Username must be 3–50 characters.']; + return ['success'=>false,'error'=>'Username must be 3–50 characters.']; if (!preg_match('/^[a-z0-9_]+$/', $username)) - return ['success' => false, 'error' => 'Username may only contain letters, numbers, and underscores.']; + return ['success'=>false,'error'=>'Username may only contain letters, numbers, and underscores.']; if (strlen($password) < 6) - return ['success' => false, 'error' => 'Password must be at least 6 characters.']; + return ['success'=>false,'error'=>'Password must be at least 6 characters.']; if (empty($alias)) - return ['success' => false, 'error' => 'Alias is required.']; + return ['success'=>false,'error'=>'Alias is required.']; if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) - return ['success' => false, 'error' => 'A valid email address is required to verify your account.']; + return ['success'=>false,'error'=>'A valid email address is required.']; - // Check username taken (existing users) $s = db()->prepare("SELECT id FROM users WHERE username=?"); $s->execute([$username]); - if ($s->fetch()) return ['success' => false, 'error' => 'Username already taken.']; + if ($s->fetch()) return ['success'=>false,'error'=>'Username already taken.']; - // Check email taken (existing users) $s = db()->prepare("SELECT id FROM users WHERE email=?"); $s->execute([$email]); - if ($s->fetch()) return ['success' => false, 'error' => 'An account with that email already exists.']; + if ($s->fetch()) return ['success'=>false,'error'=>'An account with that email already exists.']; - // Check username in pending $s = db()->prepare("SELECT id FROM pending_registrations WHERE username=? AND expires_at > NOW()"); $s->execute([$username]); - if ($s->fetch()) return ['success' => false, 'error' => 'Username already reserved. Try again in 24 hours or choose another.']; + if ($s->fetch()) return ['success'=>false,'error'=>'Username already reserved. Try again in 24 hours.']; - // Check email in pending — resend if already pending $s = db()->prepare("SELECT id, token FROM pending_registrations WHERE email=? AND expires_at > NOW()"); $s->execute([$email]); $existing = $s->fetch(); if ($existing) { - // Resend verification to same email sendVerificationEmail($email, $alias, $existing['token']); - return ['success' => true, 'resent' => true, 'email' => $email]; + return ['success'=>true,'resent'=>true,'email'=>$email]; } - // Delete any expired pending rows for this email/username db()->prepare("DELETE FROM pending_registrations WHERE email=? OR (username=? AND expires_at <= NOW())")->execute([$email, $username]); - // Resolve referral code to user ID $referrerId = null; if ($referralCode) { - $refStmt = db()->prepare("SELECT id FROM users WHERE referral_code=? AND status='active'"); - $refStmt->execute([strtoupper(trim($referralCode))]); - $refUser = $refStmt->fetch(); - if ($refUser) $referrerId = (int)$refUser['id']; + $rs = db()->prepare("SELECT id FROM users WHERE referral_code=? AND status='active'"); + $rs->execute([strtoupper(trim($referralCode))]); + $ru = $rs->fetch(); + if ($ru) $referrerId = (int)$ru['id']; } $token = bin2hex(random_bytes(32)); - $hash = password_hash($password, PASSWORD_BCRYPT); + $hash = password_hash($password, PASSWORD_BCRYPT, ['cost'=>8]); $expiresAt = date('Y-m-d H:i:s', time() + VERIFY_TTL); - $stmt = db()->prepare("INSERT INTO pending_registrations (username, password, alias, email, token, referred_by, expires_at) VALUES (?,?,?,?,?,?,?)"); - $stmt->execute([$username, $hash, $alias, $email, $token, $referrerId, $expiresAt]); + $stmt = db()->prepare("INSERT INTO pending_registrations (username,password,alias,email,token,referred_by,expires_at) VALUES (?,?,?,?,?,?,?)"); + $stmt->execute([$username,$hash,$alias,$email,$token,$referrerId,$expiresAt]); $sent = sendVerificationEmail($email, $alias, $token); - if (!$sent) { - // Email failed but keep registration — user can resend from login screen - error_log('[TomTomGames] Verification email failed for ' . $email); - return ['success' => true, 'email' => $email, 'mail_warning' => true]; + return ['success'=>true,'email'=>$email,'mail_warning'=>true]; } - - return ['success' => true, 'email' => $email]; + return ['success'=>true,'email'=>$email]; } -/** - * Consume a verification token: create the real user, delete pending row. - */ function verifyEmailToken(string $token): array { $token = trim($token); - if (empty($token)) return ['success' => false, 'error' => 'Invalid verification link.']; + if (empty($token)) return ['success'=>false,'error'=>'Invalid verification link.']; $stmt = db()->prepare("SELECT * FROM pending_registrations WHERE token=? AND expires_at > NOW()"); $stmt->execute([$token]); $pending = $stmt->fetch(); - - if (!$pending) { - return ['success' => false, 'error' => 'This verification link is invalid or has expired. Please register again.']; - } - - // Check username/email not taken since pending was created - $s = db()->prepare("SELECT id FROM users WHERE username=? OR email=?"); - $s->execute([$pending['username'], $pending['email']]); - if ($s->fetch()) { - db()->prepare("DELETE FROM pending_registrations WHERE token=?")->execute([$token]); - return ['success' => false, 'error' => 'This username or email was already registered. Please log in.']; - } + if (!$pending) return ['success'=>false,'error'=>'Verification link is invalid or has expired.']; db()->beginTransaction(); try { - // Create the user - $ins = db()->prepare("INSERT INTO users (username, password, alias, email, email_verified, status) VALUES (?,?,?,?,1,'active')"); - $ins->execute([$pending['username'], $pending['password'], $pending['alias'], $pending['email']]); + $ins = db()->prepare("INSERT INTO users (username,password,alias,email,email_verified,status) VALUES (?,?,?,?,1,'active')"); + $ins->execute([$pending['username'],$pending['password'],$pending['alias'],$pending['email']]); $userId = db()->lastInsertId(); - // Generate unique referral code - $code = strtoupper(substr(md5($userId . uniqid()), 0, 8)); - db()->prepare("UPDATE users SET referral_code=? WHERE id=?")->execute([$code, $userId]); + $code = strtoupper(substr(md5($userId.uniqid()),0,8)); + db()->prepare("UPDATE users SET referral_code=? WHERE id=?")->execute([$code,$userId]); - // Track referral if referred_by is in pending if (!empty($pending['referred_by'])) { - $referrerId = (int)$pending['referred_by']; try { - db()->prepare("INSERT IGNORE INTO referrals (referrer_id, referred_id, status) VALUES (?,?,'pending')") - ->execute([$referrerId, $userId]); - db()->prepare("UPDATE users SET referred_by=? WHERE id=?")->execute([$referrerId, $userId]); + db()->prepare("INSERT IGNORE INTO referrals (referrer_id,referred_id,status) VALUES (?,?,'pending')")->execute([$pending['referred_by'],$userId]); + db()->prepare("UPDATE users SET referred_by=? WHERE id=?")->execute([$pending['referred_by'],$userId]); } catch(Exception $e) {} } - // Delete pending row db()->prepare("DELETE FROM pending_registrations WHERE token=?")->execute([$token]); - db()->commit(); - } catch (Exception $e) { + } catch(Exception $e) { db()->rollBack(); - return ['success' => false, 'error' => 'Account creation failed. Please try again.']; + return ['success'=>false,'error'=>'Account creation failed. Please try again.']; } - // Auto-login - $_SESSION['user_id'] = $userId; - $_SESSION['username'] = $pending['username']; - $_SESSION['alias'] = $pending['alias']; - $_SESSION['is_admin'] = 0; - - return ['success' => true, 'username' => $pending['username'], 'alias' => $pending['alias']]; + return ['success'=>true,'user_id'=>$userId,'username'=>$pending['username'],'alias'=>$pending['alias']]; } -/** - * Resend verification email for an unverified account. - */ -function resendVerification(string $email): array { - $email = strtolower(trim($email)); - $stmt = db()->prepare("SELECT id, token FROM pending_registrations WHERE email=? AND expires_at > NOW() ORDER BY id DESC LIMIT 1"); - $stmt->execute([$email]); - $pending = $stmt->fetch(); - - if (!$pending) { - return ['success' => false, 'error' => 'No pending registration found for that email, or it has expired. Please register again.']; - } - - $alias = db()->prepare("SELECT alias FROM pending_registrations WHERE id=?"); - $alias->execute([$pending['id']]); - $row = $alias->fetch(); - - sendVerificationEmail($email, $row['alias'] ?? 'Player', $pending['token']); - return ['success' => true]; -} - -function logoutUser(): void { - $_SESSION = []; - session_destroy(); -} - -// ─── Comprehensive Audit Logger ──────────────────────────── -function logActivity( - string $action, - ?int $userId = null, - ?int $adminId = null, - string $entityType= '', - int $entityId = 0, - string $detail = '', - string $ip = '', - string $category = 'general', - string $oldValue = '', - string $newValue = '', - string $severity = 'info' -): void { +// ── Activity Logger ──────────────────────────────────────── +function logActivity(string $action, ?int $userId=null, ?int $adminId=null, string $entityType='', int $entityId=0, string $detail='', string $ip='', string $category='general', string $oldValue='', string $newValue='', string $severity='info'): void { try { - // Auto-purge entries older than 90 days (probabilistic — 3% of calls) - if (rand(1, 100) <= 3) { + if (rand(1,100) <= 3) { db()->exec("DELETE FROM activity_log WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY)"); } $ip = $ip ?: ($_SERVER['REMOTE_ADDR'] ?? ''); $userAgent = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 300); - $page = substr(($_SERVER['REQUEST_URI'] ?? ''), 0, 200); + $page = substr($_SERVER['REQUEST_URI'] ?? '', 0, 200); $sessionId = session_id() ?: ''; - - db()->prepare(" - INSERT INTO activity_log - (user_id, admin_id, action, category, entity_type, entity_id, detail, - old_value, new_value, ip, user_agent, page, session_id, severity) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) - ")->execute([ - $userId ?: null, - $adminId ?: null, - substr($action, 0, 120), - $category, - $entityType, - $entityId ?: null, - substr($detail, 0, 2000), - substr($oldValue, 0, 2000), - substr($newValue, 0, 2000), - $ip, - $userAgent, - $page, - $sessionId, - $severity, - ]); - } catch (Exception $e) { /* never fail silently */ } + db()->prepare("INSERT INTO activity_log (user_id,admin_id,action,category,entity_type,entity_id,detail,old_value,new_value,ip,user_agent,page,session_id,severity) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)") + ->execute([$userId,$adminId,substr($action,0,120),$category,$entityType,$entityId?:null,substr($detail,0,2000),substr($oldValue,0,2000),substr($newValue,0,2000),$ip,$userAgent,$page,$sessionId,$severity]); + } catch(Exception $e) {} } -// Convenience wrappers function logPlayerAction(string $action, int $userId, string $detail='', string $category='player', string $severity='info'): void { - logActivity($action, $userId, null, 'user', $userId, $detail, '', $category, '', '', $severity); + logActivity($action,$userId,null,'user',$userId,$detail,'',$category,'','',$severity); } function logAdminAction(string $action, int $adminId, string $entityType='', int $entityId=0, string $detail='', string $old='', string $new='', string $severity='info'): void { - logActivity($action, null, $adminId, $entityType, $entityId, $detail, '', 'admin', $old, $new, $severity); + logActivity($action,null,$adminId,$entityType,$entityId,$detail,'','admin',$old,$new,$severity); } function logSecurityEvent(string $action, ?int $userId=null, string $detail='', string $severity='warning'): void { - logActivity($action, $userId, null, 'security', 0, $detail, '', 'security', '', '', $severity); + logActivity($action,$userId,null,'security',0,$detail,'','security','','',$severity); } -// ─── App Version ─────────────────────────────────────────── function getAppVersion(): string { try { $v = db()->query("SELECT version FROM app_version ORDER BY id DESC LIMIT 1")->fetchColumn(); - return $v ?: '1.0.0'; - } catch(Exception $e) { return '1.0.0'; } + return $v ?: '1.0.2'; + } catch(Exception $e) { return '1.0.2'; } +} + +// ── CSRF ─────────────────────────────────────────────────── +function getCsrfToken(): string { + if (empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + return $_SESSION['csrf_token']; +} +function validateCsrfToken(string $token): bool { + return !empty($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); } diff --git a/includes/config.php b/includes/config.php index adee4af..07332a6 100644 --- a/includes/config.php +++ b/includes/config.php @@ -5,9 +5,9 @@ // ─── Database ───────────────────────────────────────────── define('DB_HOST', 'localhost'); -define('DB_NAME', 'tomt_tomgames'); -define('DB_USER', 'tomt_tomgames'); -define('DB_PASS', 'It0Dmy2BlHP8GP1E'); +define('DB_NAME', 'tomt_ttg_db'); +define('DB_USER', 'tomt_ttg_user'); +define('DB_PASS', 'q#q+mrOcozsa7I6J'); // ─── Square ─────────────────────────────────────────────── define('SQUARE_ENV', 'production'); @@ -41,10 +41,10 @@ define('PAY_ZELLE', 'tomgames@email.com'); // ─── Token Packages ─────────────────────────────────────── define('TOKEN_PACKAGES', json_encode([ - ['tokens' => 5, 'price' => 5, 'label' => '5 Tokens', 'popular' => false], + ['tokens' => 5, 'price' => 5, 'label' => '5 Tokens', 'popular' => true], ['tokens' => 10, 'price' => 10, 'label' => '10 Tokens', 'popular' => false], ['tokens' => 25, 'price' => 25, 'label' => '25 Tokens', 'popular' => false], - ['tokens' => 50, 'price' => 50, 'label' => '50 Tokens', 'popular' => true], + ['tokens' => 50, 'price' => 50, 'label' => '50 Tokens', 'popular' => false], ['tokens' => 75, 'price' => 75, 'label' => '75 Tokens', 'popular' => false], ['tokens' => 100, 'price' => 100, 'label' => '100 Tokens', 'popular' => false], ])); @@ -63,6 +63,4 @@ define('PLATFORMS', json_encode([ error_reporting(0); ini_set('display_errors', 0); -if (session_status() === PHP_SESSION_NONE) { - @session_start(); -} +if (session_status() === PHP_SESSION_NONE) { @session_start(); } diff --git a/public_html/.htaccess b/public_html/.htaccess index 5c85de7..381e984 100644 --- a/public_html/.htaccess +++ b/public_html/.htaccess @@ -1,37 +1,103 @@ -Options -Indexes +# ══════════════════════════════════════════════════════════ +# TomTomGames Security Configuration +# ══════════════════════════════════════════════════════════ + +Options -Indexes -Includes ServerSignature Off -# ── Block sensitive files ──────────────────────────────── - +# ── Block all sensitive file types ─────────────────────── + Order allow,deny Deny from all -# ── Block direct access to includes ────────────────────── +# ── Block direct access to sensitive PHP files ─────────── + + Order allow,deny + Deny from all + + +# ── Block access to includes and vendor folders ────────── RewriteEngine On RewriteRule ^includes/ - [F,L] + RewriteRule ^vendor/ - [F,L] + RewriteRule ^mail_queue/ - [F,L] + RewriteRule ^\.git/ - [F,L] -# ── Security headers ────────────────────────────────────── +# ── Block common attack vectors ────────────────────────── + + RewriteEngine On + + # Block SQL injection attempts in query strings + RewriteCond %{QUERY_STRING} (union|select|insert|drop|delete|update|cast|exec|declare|char|convert|truncate).*= [NC,OR] + RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR] + RewriteCond %{QUERY_STRING} \.\./\.\. [NC,OR] + RewriteCond %{QUERY_STRING} (javascript|vbscript|expression|applet|meta|xml|blink|link|iframe|input|embed|script|object|marquee) [NC] + RewriteRule .* - [F,L] + + # Block base64 encoded attacks + RewriteCond %{QUERY_STRING} base64_encode.*\(.*\) [NC,OR] + RewriteCond %{QUERY_STRING} base64_(en|de)code[^(]*\([^)]*\) [NC] + RewriteRule .* - [F,L] + + # Block common exploit scanners and bad bots + RewriteCond %{HTTP_USER_AGENT} (nikto|sqlmap|havij|nessus|masscan|zgrab|python-requests/2\.6|libwww-perl|wget|curl\/7\.[0-4]) [NC] + RewriteRule .* - [F,L] + + +# ── Block access to WordPress paths (scanners look for these) ── + + RewriteRule ^wp-admin - [F,L] + RewriteRule ^wp-login - [F,L] + RewriteRule ^xmlrpc - [F,L] + RewriteRule ^\.env - [F,L] + RewriteRule ^composer\. - [F,L] + + +# ── Security Headers ────────────────────────────────────── + # Prevent MIME type sniffing Header always set X-Content-Type-Options "nosniff" - Header always set X-Frame-Options "SAMEORIGIN" + + # Prevent clickjacking + Header always set X-Frame-Options "DENY" + + # XSS protection Header always set X-XSS-Protection "1; mode=block" + + # Referrer policy Header always set Referrer-Policy "strict-origin-when-cross-origin" - Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()" + + # Permissions policy — disable dangerous browser features + Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=()" + + # Content Security Policy + Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://web.squarecdn.com https://sandbox.web.squarecdn.com https://js.squareup.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: blob: https:; connect-src 'self' https: wss:; frame-src 'none'; object-src 'none'" + + # Strict Transport Security — force HTTPS for 1 year + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + # Remove server info headers + Header unset Server + Header unset X-Powered-By -# ── Canonical HTTPS redirect ────────────────────────────── +# ── Canonical HTTPS + non-www redirect ─────────────────── RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] - # Remove www (pick one: www or non-www, use non-www) RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L] +# ── Block PHP execution in uploads folder (if it exists) ─ + + RewriteRule ^uploads/.*\.php$ - [F,L] + + # ── Gzip compression ────────────────────────────────────── AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json image/svg+xml @@ -49,11 +115,3 @@ ServerSignature Off ExpiresByType image/webp "access plus 1 month" ExpiresByType application/json "access plus 1 day" - -# ── LiteSpeed cache rules ───────────────────────────────── - - CacheEnable public /assets/ - CacheEnable public /manifest.json - CacheEnable public /sitemap.xml - CacheEnable public /robots.txt - diff --git a/public_html/admin/index.php b/public_html/admin/index.php index 1ba5711..f651b0e 100644 --- a/public_html/admin/index.php +++ b/public_html/admin/index.php @@ -1,13 +1,19 @@ + + @@ -15,19 +21,22 @@ ob_end_clean(); // discard any PHP errors/warnings before HTML @@ -294,7 +316,7 @@ tr:hover td{background:rgba(255,255,255,.015)} @@ -308,7 +330,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
💬
New Messages from Players
-
Click to view and respond
+
Click to view and respond
0
@@ -406,7 +428,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
🔑 Platform Accounts
-
Manage game platform logins for this player. When you approve a request and set a username/password, it will automatically update their alias.
+
Manage game platform logins for this player. When you approve a request and set a username/password, it will automatically update their alias.
Loading...
@@ -415,7 +437,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
💸 Payout Methods
-
Where this player receives their cashout payments.
+
Where this player receives their cashout payments.
Loading...
@@ -424,7 +446,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
🔑 Platform Accounts
-
Game platform logins created for this player.
+
Game platform logins created for this player.
Loading...
@@ -433,8 +455,8 @@ tr:hover td{background:rgba(255,255,255,.015)}
🎮 Game Aliases
-
Player's in-game usernames per platform. Edit and save below.
-
Loading...
+
Player's in-game usernames per platform. Edit and save below.
+
Loading...
@@ -484,7 +506,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
- +
@@ -515,7 +537,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
- @@ -540,7 +562,7 @@ tr:hover td{background:rgba(255,255,255,.015)} -

Deleting is permanent and removes all purchases, cashouts, and chat history.

+

Deleting is permanent and removes all purchases, cashouts, and chat history.

@@ -549,7 +571,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
💳 Payment Settings
-
+
Enable or disable each payment method. Disabled methods are hidden from players instantly. Card payments run through Square — your Square account must be active for card to work.
@@ -652,7 +674,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
Sent Broadcasts
- +
@@ -681,7 +703,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
💰 Payout Settings
-
+
Configure how you send cashout payments to players. Square Gift Card sends instantly. Manual methods show the player handle so you send from the app and mark done.
@@ -690,7 +712,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
💸 Cashout Methods
-
+
💸 Manage the payout method types available to players when they cash out. Active methods appear in the player's payout method dropdown.
@@ -742,7 +764,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
Active & Inactive Methods
-
Loading...
+
Loading...
@@ -805,7 +827,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
Active & Inactive Games
-
Loading...
+
Loading...
@@ -813,11 +835,11 @@ tr:hover td{background:rgba(255,255,255,.015)}
-
📋 Full Audit Log 90-day rolling · 20 per page
+
📋 Full Audit Log 90-day rolling · 20 per page
- @@ -825,19 +847,19 @@ tr:hover td{background:rgba(255,255,255,.015)} - + style="background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-size:14px;width:200px"> - - - + style="background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-size:14px"> + + +
@@ -853,7 +875,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
⏳ Pending Signups
-
+
⏳ Players who registered but haven't verified their email yet. Approve to create their account immediately, or Delete to remove the request.
@@ -862,7 +884,7 @@ tr:hover td{background:rgba(255,255,255,.015)}
Live Chat - + ● Auto-refreshing every 5s
@@ -884,8 +906,8 @@ tr:hover td{background:rgba(255,255,255,.015)} @@ -894,7 +916,7 @@ tr:hover td{background:rgba(255,255,255,.015)} style="width:100%;box-sizing:border-box;background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:10px 12px;color:var(--text);font-family:'Rajdhani',sans-serif;font-size:14px;resize:vertical;outline:none;margin-bottom:10px" onkeydown="if(event.key==='Enter'&&event.ctrlKey)adminComposeSend()">
-
Ctrl+Enter to send · Player will see it in their Support chat
+
Ctrl+Enter to send · Player will see it in their Support chat
@@ -903,9 +925,9 @@ tr:hover td{background:rgba(255,255,255,.015)}
-
+
📨 Messages from players appear below. Click any conversation to open it and reply. -
@@ -917,10 +939,10 @@ tr:hover td{background:rgba(255,255,255,.015)}
@@ -1041,27 +1063,27 @@ function purchaseCard(p, showActions=true) { const mIcon = mIcons[p.payment_method] || '💰'; const mLabel = mLabels[p.payment_method] || p.payment_method; const platformRow = p.platform_id - ? '
Platform: '+pname(p.platform_id)+' · Alias: '+escHtmlA(p.game_alias||'—')+'
' + ? '
Platform: '+pname(p.platform_id)+' · Alias: '+escHtmlA(p.game_alias||'—')+'
' : ''; const actions = showActions && isPending ? '
'+ - ''+ - ''+ - '
' - : (p.admin_note ? '
📝 '+escHtmlA(p.admin_note)+'
' : ''); + ''+ + ''+ + '
' + : (p.admin_note ? '
📝 '+escHtmlA(p.admin_note)+'
' : ''); return '
'+ '
'+ '
'+ '
'+ ''+escHtmlA(p.username)+''+ - '#'+p.id+''+ - ''+statusLabel+'
'+ - '
'+escHtmlA(p.alias||'')+' · '+mIcon+' '+mLabel+' · '+date+'
'+ + '#'+p.id+''+ + ''+statusLabel+'
'+ + '
'+escHtmlA(p.alias||'')+' · '+mIcon+' '+mLabel+' · '+date+'
'+ platformRow+ '
'+p.tokens+' 🪙
'+ '
$'+amt+'
'+ - (isManual?'
Manual Payment — verify before approving
':'')+ + (isManual?'
Manual Payment — verify before approving
':'')+ '
'+actions+'
'; } @@ -1097,11 +1119,11 @@ const PAYOUT_LABELS_A = { venmo:'Venmo', cashapp:'Cash App', zelle:'Zelle', chim function cashoutRow(c, showActions=true) { const payoutInfo = c.payout_handle - ? `
+ ? `
${PAYOUT_ICONS_A[c.payout_method_type]||'💰'} ${PAYOUT_LABELS_A[c.payout_method_type]||c.payout_method_type}: ${escHtmlA(c.payout_handle)}
` - : '
⚠️ No payout method on file
'; + : '
⚠️ No payout method on file
'; const statusColors = { pending:'var(--text2)', locked:'var(--cyan)', approved:'var(--green)', sent:'var(--green)', rejected:'var(--red)', deleted:'var(--text2)' }; const statusLabels = { pending:'⏳ Draft', locked:'🔒 Ready to Process', sent:'✅ Sent', approved:'✅ Sent', rejected:'❌ Denied', deleted:'🗑 Deleted' }; @@ -1115,24 +1137,24 @@ function cashoutRow(c, showActions=true) {
${escHtmlA(c.username)} - #${c.id} - ${statusLabels[c.status]||c.status} + #${c.id} + ${statusLabels[c.status]||c.status}
-
${pname(c.platform_id)} · ${escHtmlA(c.alias||'')} · ${escHtmlA(playerAlias)}
+
${pname(c.platform_id)} · ${escHtmlA(c.alias||'')} · ${escHtmlA(playerAlias)}
${c.tokens} 🪙
-
${(c.created_at||'').substring(0,16)}
+
${(c.created_at||'').substring(0,16)}
${payoutInfo} - ${c.admin_note ? `
📝 ${escHtmlA(c.admin_note)}
` : ''} + ${c.admin_note ? `
📝 ${escHtmlA(c.admin_note)}
` : ''}
${canAct ? `
- - -
` : ''} @@ -1256,8 +1278,8 @@ function renderGamerGrid() {
${parseFloat(u.tokens||0)}
TOKENS
-
${(u.created_at||'').substring(0,10)}
JOINED
-
${lastSeen}
LAST SEEN
+
${(u.created_at||'').substring(0,10)}
JOINED
+
${lastSeen}
LAST SEEN
${escHtmlA(u.email||'No email')}
VIEW PLAYER PROFILE →
@@ -1303,7 +1325,7 @@ function renderGamerProfile(u) {
${escHtmlA(u.username)}
🎮 ${escHtmlA(u.alias)}
📧 ${escHtmlA(u.email||'No email')}
-
+
Joined ${(u.created_at||'').substring(0,10)} · Last login: ${u.last_login ? timeAgo(u.last_login) : 'Never'}
@@ -1420,7 +1442,7 @@ async function loadGamerPurchases(uid) {
${billing ? `
Billing: ${escHtmlA(billing)}
` : ''} ${p.billing_email ? `
Email: ${escHtmlA(p.billing_email)}
` : ''} - ${p.square_payment_id ? `
Square ID: ${p.square_payment_id}
` : ''} + ${p.square_payment_id ? `
Square ID: ${p.square_payment_id}
` : ''} ${p.failure_reason ? `
⚠️ ${escHtmlA(p.failure_reason)}
` : ''} ${p.admin_note ? `
📝 Admin: ${escHtmlA(p.admin_note)}
` : ''} ${p.receipt_url ? `` : ''} @@ -1460,7 +1482,7 @@ async function loadGamerCashouts(uid) { ${escHtmlA(c.alias)} ${c.tokens} 🪙 ${c.status} - ${(c.created_at||'').substring(0,16)} + ${(c.created_at||'').substring(0,16)} `).join('')} `; } @@ -1470,7 +1492,7 @@ async function loadGamerChat(uid, scrollToBottom) { if (!d.success) return; const container = document.getElementById('gm-chat-messages'); if (d.messages.length === 0 && GM.chatLastId === 0) { - container.innerHTML = '
No messages yet with this player.
'; + container.innerHTML = '
No messages yet with this player.
'; return; } if (d.messages.length > 0) { @@ -1499,7 +1521,7 @@ async function gmClearThread() { const d = await apiFetch('chat_clear_thread', 'POST', { user_id: GM.current.id }); if (d.success) { document.getElementById('gm-chat-messages').innerHTML = - '
Conversation cleared.
'; + '
Conversation cleared.
'; GM.chatLastId = 0; toast('Conversation cleared', 'ok'); loadChatBadge(); @@ -1609,7 +1631,8 @@ async function gmToggleAdmin() { const d = await apiFetch('toggle_admin','POST',{user_id: u.id}); if (d.success) { GM.current.is_admin = d.is_admin; - toast(d.is_admin ? u.username+' is now an admin' : 'Admin removed from '+u.username, 'ok'); + const msg = d.is_admin ? u.username+' is now an ADMIN' : 'Admin removed from '+u.username; + toast(msg + ' — they must log out and back in for changes to take effect', 'ok'); renderGamerProfile(GM.current); renderGamerOverview(GM.current, null); updateAdminToggleBtn(); @@ -1744,7 +1767,7 @@ async function loadHistory(page) { ].map(x => '
'+ '
'+x.val+'
'+ - '
'+x.label+'
' + '
'+x.label+'
' ).join(''); } @@ -1765,34 +1788,34 @@ async function loadHistory(page) { lastDate = dt; const isToday = dt === new Date().toISOString().substring(0,10); const label = isToday ? 'Today' : new Date(dt+'T12:00:00').toLocaleDateString('en-US',{weekday:'short',month:'short',day:'numeric',year:'numeric'}); - rows += '
'+label+'
'; + rows += '
'+label+'
'; } const sevColor = SEV_COLORS[e.severity] || 'var(--text2)'; const catIcon = CAT_ICONS[e.category] || '📋'; const who = e.alias || e.username || '—'; - const adminBy = e.admin_username ? ' by '+escHtmlA(e.admin_username)+'' : ''; + const adminBy = e.admin_username ? ' by '+escHtmlA(e.admin_username)+'' : ''; const action = e.action.replace(/_/g,' '); // Build detail row let details = ''; - if (e.detail) details += '
'+escHtmlA(e.detail)+'
'; + if (e.detail) details += '
'+escHtmlA(e.detail)+'
'; if (e.old_value && e.new_value) - details += '
'+escHtmlA(e.old_value)+''+escHtmlA(e.new_value)+'
'; - if (e.ip) details += '
IP: '+escHtmlA(e.ip)+' | Session: '+escHtmlA(e.session_id||'—')+'
'; - if (e.page) details += '
Page: '+escHtmlA(e.page)+'
'; - if (e.user_agent)details += '
UA: '+escHtmlA(e.user_agent.substring(0,120))+'
'; + details += '
'+escHtmlA(e.old_value)+''+escHtmlA(e.new_value)+'
'; + if (e.ip) details += '
IP: '+escHtmlA(e.ip)+' | Session: '+escHtmlA(e.session_id||'—')+'
'; + if (e.page) details += '
Page: '+escHtmlA(e.page)+'
'; + if (e.user_agent)details += '
UA: '+escHtmlA(e.user_agent.substring(0,120))+'
'; rows += '
' + '
' + ''+catIcon+'' + '
' + - ''+action+''+adminBy + + ''+action+''+adminBy + details + '
' + '
' + - '
'+escHtmlA(who)+'
' + - '
'+time+'
' + - '
'+e.severity+'
' + + '
'+escHtmlA(who)+'
' + + '
'+time+'
' + + '
'+e.severity+'
' + '
'; } @@ -1801,14 +1824,14 @@ async function loadHistory(page) { // Pagination if (pgEl && pages > 1) { let pg = ''; - if (_histPage > 1) pg += ''; + if (_histPage > 1) pg += ''; const start2 = Math.max(1, _histPage-2); const end2 = Math.min(pages, _histPage+2); for (let p = start2; p <= end2; p++) { - pg += ''; + pg += ''; } - if (_histPage < pages) pg += ''; - pg += 'Page '+_histPage+' of '+pages+''; + if (_histPage < pages) pg += ''; + pg += 'Page '+_histPage+' of '+pages+''; pgEl.innerHTML = pg; } } @@ -1838,10 +1861,10 @@ async function gmLoadPayout(uid) {
${ICONS[m.method_type]||'💰'}
-
${escHtmlA(m.label)} - ${parseInt(m.is_default)?'DEFAULT':''} +
${escHtmlA(m.label)} + ${parseInt(m.is_default)?'DEFAULT':''}
-
${LABELS[m.method_type]||m.method_type} · ${escHtmlA(m.account_handle)}
+
${LABELS[m.method_type]||m.method_type} · ${escHtmlA(m.account_handle)}
`).join(''); } @@ -1860,16 +1883,16 @@ async function gmLoadPlatformAccounts(uid) {
${a.color?``:'🎮'}
-
${escHtmlA(a.display_name||a.platform_slug)}
+
${escHtmlA(a.display_name||a.platform_slug)}
${STATUS[a.status]||a.status}
- ${a.status==='approved'?``: - a.status==='pending'?``:''} + ${a.status==='approved'?``: + a.status==='pending'?``:''}
- ${a.status==='approved'?`
+ ${a.status==='approved'?`
Username: ${escHtmlA(a.provided_username||'—')}
Password: ${escHtmlA(a.provided_password||'—')}
- ${a.player_url?`↗ Open platform`:''} + ${a.player_url?`↗ Open platform`:''}
`:''}
`).join(''); } @@ -1886,11 +1909,11 @@ async function gmLoadAliases(uid) {
-
${escHtmlA(p.name)}
+
${escHtmlA(p.name)}
+ style="width:100%;padding:8px 12px;font-size:15px">
`).join('') + '
'; } @@ -1929,8 +1952,8 @@ async function loadGamerBilling(uid) {
···· ···· ···· ${escHtmlA(b.card_last4)}
-
CARD HOLDER
${escHtmlA([b.first_name,b.last_name].filter(Boolean).join(' '))||'—'}
-
EXPIRES
${b.card_exp_month&&b.card_exp_year?b.card_exp_month+'/'+b.card_exp_year.slice(-2):'—'}
+
CARD HOLDER
${escHtmlA([b.first_name,b.last_name].filter(Boolean).join(' '))||'—'}
+
EXPIRES
${b.card_exp_month&&b.card_exp_year?b.card_exp_month+'/'+b.card_exp_year.slice(-2):'—'}
`; cardDiv.style.display = 'block'; noCard.style.display = 'none'; @@ -1994,7 +2017,7 @@ async function loadPaymentSettings() { const clr = enabled ? 'var(--green)' : 'var(--red)'; const fields = isCard - ? '
'+ + ? '
'+ 'Card payments run through Square. When enabled, players can pay by credit or debit card. Your Square account must be active and connected.'+ 'Open Square Dashboard →
' : '
'+ @@ -2010,9 +2033,9 @@ async function loadPaymentSettings() { '
'+ '
'+icon+'
'+ '
'+escHtmlA(m.label)+'
'+ - '
'+(isCard?'Square Card Processing':'Manual payment'+(m.handle?' · '+escHtmlA(m.handle):''))+'
'+ + '
'+(isCard?'Square Card Processing':'Manual payment'+(m.handle?' · '+escHtmlA(m.handle):''))+'
'+ '
'+ @@ -2073,16 +2096,16 @@ async function loadPayoutSettings() { return '
' + '
' + '
' + escHtmlA(s.label) + '
' + - '
' + (isGC?'Real-time via Square':'Manual — send outside app') + '
' + - '
' + + '
' + - (isGC ? '
Creates a Square digital gift card loaded with the cashout amount. Player redeems anywhere Square is accepted. No handle needed.
' : + (isGC ? '
Creates a Square digital gift card loaded with the cashout amount. Player redeems anywhere Square is accepted. No handle needed.
' : '
' + '
' + '
' + '
' + - '') + + '') + '
'; }).join(''); } @@ -2116,8 +2139,8 @@ async function openPayoutModal(cashoutId) { document.getElementById('ppm-player-info').innerHTML= '
'+ '
'+escHtmlA(c.alias||c.username)+'
'+ - '
@'+escHtmlA(c.username)+' | '+escHtmlA(c.email||'')+'
'+ - '
'+pname(c.platform_id)+' | '+escHtmlA(c.alias||'')+'
'+ + '
@'+escHtmlA(c.username)+' | '+escHtmlA(c.email||'')+'
'+ + '
'+pname(c.platform_id)+' | '+escHtmlA(c.alias||'')+'
'+ '
'+c.tokens+' Tokens
'+ '
$'+amt+'
'; const spm=document.getElementById('ppm-saved-method'); @@ -2125,9 +2148,9 @@ async function openPayoutModal(cashoutId) { const pt=c.saved_payout_label||c.payout_method_type||''; spm.innerHTML=ph? '
'+ - '
PLAYER PAYOUT METHOD
'+ + '
PLAYER PAYOUT METHOD
'+ '
'+escHtmlA(pt)+' | '+escHtmlA(ph)+'
': - '
No payout method on file for this player
'; + '
No payout method on file for this player
'; const sel=document.getElementById('ppm-method-select'); sel.innerHTML=''+ PPM.settings.map(s=>'
'; btn.textContent='Process via Square Gift Card'; } else { const ph=c?.payout_handle||c?.saved_payout_handle||'(none on file)'; detail.innerHTML='
'+ - '
Manual Processing
'+ - '
'+ + '
Manual Processing
'+ + '
'+ '1. Open '+escHtmlA(s.label)+'
'+ '2. Send $'+amt+' to: '+escHtmlA(ph)+'
'+ (s.instructions?'3. '+escHtmlA(s.instructions):'')+'
'+ - (s.handle?'
Your handle: '+escHtmlA(s.handle)+'
':'')+'
'; + (s.handle?'
Your handle: '+escHtmlA(s.handle)+'
':'')+'
'; btn.textContent='Mark as Sent (Manual)'; } } @@ -2183,7 +2206,7 @@ async function submitPayout(){ showAdminAlert(al,isGC?'Gift card created!':'Marked as sent!','success'); if (res.gift_card_gan){ al.innerHTML+='
Card #: '+res.gift_card_gan+'
'+ - '
Copy and send this card number to the player. Balance: $'+(res.gift_card_balance/100).toFixed(2)+'
'; + '
Copy and send this card number to the player. Balance: $'+(res.gift_card_balance/100).toFixed(2)+'
'; } toast(isGC?'Gift card sent!':'Marked sent','ok'); loadCashouts('pending');loadDashCashouts();loadStats(); @@ -2219,16 +2242,16 @@ async function loadAdminReferrals(status, btn) { } function buildRefCard(r, status) { - const note = r.admin_note ? '
'+escHtmlA(r.admin_note)+'
' : ''; - const awarded = r.tokens_awarded>0 ? '
+'+r.tokens_awarded+' tokens awarded
' : ''; + const note = r.admin_note ? '
'+escHtmlA(r.admin_note)+'
' : ''; + const awarded = r.tokens_awarded>0 ? '
+'+r.tokens_awarded+' tokens awarded
' : ''; const actions = status==='pending' ? [ - '', - '', - '', + '', + '', + '', ].join('') : ''; return '
'+ - '
'+escHtmlA(r.referrer_alias||r.referrer_name)+' referred '+escHtmlA(r.referred_alias||r.referred_name)+'
'+ - '
'+(r.created_at||'').substring(0,16)+' | Email verified: '+(r.email_verified?'Yes':'No')+'
'+ + '
'+escHtmlA(r.referrer_alias||r.referrer_name)+' referred '+escHtmlA(r.referred_alias||r.referred_name)+'
'+ + '
'+(r.created_at||'').substring(0,16)+' | Email verified: '+(r.email_verified?'Yes':'No')+'
'+ awarded+note+'
'+ '
'+actions+'
'; } @@ -2253,13 +2276,13 @@ async function loadAdminTiers() { if (!d.success||!d.tiers.length) { el.innerHTML='
No tiers yet. Add one above.
'; return; } _refTiers = d.tiers; el.innerHTML = d.tiers.map(t => { - const inactive = !parseInt(t.is_active) ? 'INACTIVE' : ''; + const inactive = !parseInt(t.is_active) ? 'INACTIVE' : ''; return '
'+ '
'+escHtmlA(t.name)+inactive+'
'+ - '
Min '+t.min_referrals+' refs | '+t.tokens_per_ref+' tok/ref'+(t.bonus_tokens>0?' + '+t.bonus_tokens+' milestone bonus':'')+'
'+ + '
Min '+t.min_referrals+' refs | '+t.tokens_per_ref+' tok/ref'+(t.bonus_tokens>0?' + '+t.bonus_tokens+' milestone bonus':'')+'
'+ '
'+ - ''+ - ''+ + ''+ + ''+ '
'; }).join(''); } @@ -2316,11 +2339,11 @@ async function loadAdminShares(status, btn) { el.innerHTML = d.shares.map(s => { const btns = status==='pending' ? '
'+ - ''+ - '
' : ''; + ''+ + '
' : ''; return '
'+ - '
'+escHtmlA(s.alias||s.username)+' | '+escHtmlA(s.platform)+'
'+ - '
'+(s.created_at||'').substring(0,16)+' | Bonus: '+s.bonus_tokens+' tokens
'+btns+'
'; + '
'+escHtmlA(s.alias||s.username)+' | '+escHtmlA(s.platform)+'
'+ + '
'+(s.created_at||'').substring(0,16)+' | Bonus: '+s.bonus_tokens+' tokens
'+btns+'
'; }).join(''); } @@ -2353,17 +2376,17 @@ function buildAdminPaCard(a, showActions) { return '
' + '
' + '
' + escHtmlA(a.username||'?') + ' · ' + escHtmlA(a.user_alias||'') + '
' + - '
' + escHtmlA(a.platform_name||a.platform_slug) + '
' + - '
' + (a.requested_at||'').substring(0,16) + '
' + - '' + (statusLabel[a.status]||a.status) + '
' + + '
' + escHtmlA(a.platform_name||a.platform_slug) + '
' + + '
' + (a.requested_at||'').substring(0,16) + '
' + + '' + (statusLabel[a.status]||a.status) + '
' + '
' + '
' + '
' + '
' + '
' + '
' + - (a.status==='pending' ? '' : '') + - (a.status==='approved' ? '' : '') + + (a.status==='pending' ? '' : '') + + (a.status==='approved' ? '' : '') + '
'; } @@ -2390,49 +2413,106 @@ async function updatePlatformAccount(id) { if (d.success) toast('Updated','ok'); else toast(d.error||'Error','err'); } +async function sendBroadcast() { + const subject = document.getElementById('bc-subject')?.value.trim(); + const message = document.getElementById('bc-message')?.value.trim(); + const target = document.getElementById('bc-target')?.value || 'all'; + const al = document.getElementById('bc-alert'); + if (al) al.className = 'alert'; + + if (!subject) { if(al) showAdminAlert(al,'Subject is required.','error'); return; } + if (!message) { if(al) showAdminAlert(al,'Message is required.','error'); return; } + + const btn = document.querySelector('#section-broadcasts .btn-gold'); + if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; } + + const d = await apiFetch('broadcast_send','POST',{ subject, message, target }); + + if (btn) { btn.disabled = false; btn.textContent = '📢 SEND BROADCAST'; } + + if (d.success) { + if (al) showAdminAlert(al, '✓ Broadcast sent to ' + (d.recipient_count||0) + ' players!', 'success'); + document.getElementById('bc-subject').value = ''; + document.getElementById('bc-message').value = ''; + setTimeout(() => { if(al){al.className='alert';al.textContent='';} }, 4000); + loadBroadcasts(); + } else { + if (al) showAdminAlert(al, d.error || 'Failed to send.', 'error'); + } +} + async function loadBroadcasts() { const el = document.getElementById('bc-list'); - el.innerHTML = '
Loading...
'; - const d = await apiFetch('broadcast_list'); - if (!d.success || !d.broadcasts.length) { - el.innerHTML = '
No broadcasts sent yet.
'; + if (!el) return; + el.innerHTML = '
Loading broadcasts...
'; + let d; + try { d = await apiFetch('broadcast_list'); } catch(e) { + el.innerHTML = '
Failed to load broadcasts.
'; return; } + if (!d || !d.success) { + el.innerHTML = '
' + (d?.error||'Error loading broadcasts') + '
'; + return; + } + if (!d.broadcasts || !d.broadcasts.length) { + el.innerHTML = '
No broadcasts sent yet.
'; + return; + } + const TGT = { all:'👥 All', verified:'✅ Verified', unverified:'⏳ Unverified', admins:'🔑 Admins' }; el.innerHTML = d.broadcasts.map(b => { const readPct = b.total_players > 0 ? Math.round((b.read_count/b.total_players)*100) : 0; - return ` -
-
-
-
- ${escHtmlA(b.subject)} - ${BC_TARGET_LABELS[b.target]||b.target} -
-
${escHtmlA(b.message)}
-
- 📅 ${(b.sent_at||'').substring(0,16)} - 👤 by ${escHtmlA(b.sender_name)} + const dt = (b.sent_at||'').substring(0,16).replace('T',' '); + return `
+
+
+
${escHtmlA(b.subject)}
+
${escHtmlA(b.message.substring(0,120))}${b.message.length>120?'...':''}
+
+ ${TGT[b.target]||b.target} + 📅 ${dt} + 👤 ${escHtmlA(b.sender_name)} 👁 ${b.read_count}/${b.total_players} read (${readPct}%) - 💬 ${b.reply_count} repl${b.reply_count!=1?'ies':'y'} + 💬 ${b.reply_count} ${b.reply_count==1?'reply':'replies'}
- -
-
+
+
- - + + +
`; }).join(''); } +async function editBroadcast(id, subject, message, target) { + const newSubject = prompt('Edit Subject:', subject); + if (newSubject === null) return; + const newMessage = prompt('Edit Message:', message); + if (newMessage === null) return; + const targets = ['all','verified','unverified','admins']; + const labels = ['All Players','Verified Only','Unverified Only','Admins Only']; + const tIdx = targets.indexOf(target); + const newTarget = prompt('Send To (all/verified/unverified/admins):', target); + if (!targets.includes(newTarget)) { alert('Invalid target. Use: all, verified, unverified, or admins'); return; } + const d = await apiFetch('broadcast_edit','POST',{id, subject:newSubject, message:newMessage, target:newTarget}); + if (d.success) { toast('Broadcast updated','ok'); loadBroadcasts(); } + else toast(d.error||'Failed to update','error'); +} + +async function resendBroadcast(id, subject) { + if (!confirm('Resend "'+subject+'" to all recipients? This will mark it as unread for everyone.')) return; + const d = await apiFetch('broadcast_resend','POST',{id}); + if (d.success) { toast('Resent to '+d.recipient_count+' players','ok'); loadBroadcasts(); } + else toast(d.error||'Failed to resend','error'); +} + async function openBcDrawer(id, subject) { bcCurrentId = id; document.getElementById('bc-drawer').style.display = 'block'; @@ -2457,10 +2537,10 @@ function switchBcTab(tab, btn) { async function loadBcReplies() { if (!bcCurrentId) return; const el = document.getElementById('bc-drawer-content'); - el.innerHTML = '
Loading...
'; + el.innerHTML = '
Loading...
'; const d = await apiFetch('broadcast_replies&broadcast_id=' + bcCurrentId); if (!d.success || !d.replies.length) { - el.innerHTML = '
No replies yet.
'; + el.innerHTML = '
No replies yet.
'; return; } el.innerHTML = d.replies.map(r => { @@ -2468,10 +2548,10 @@ async function loadBcReplies() { return `
${(r.username||'?').charAt(0).toUpperCase()}
-
+
${escHtmlA(r.username)}${isAdm?' 🔑 (Admin)':' · '+escHtmlA(r.alias)} · ${(r.created_at||'').substring(0,16)}
-
${escHtmlA(r.message)}
+
${escHtmlA(r.message)}
`; }).join(''); @@ -2480,14 +2560,14 @@ async function loadBcReplies() { async function loadBcReads() { if (!bcCurrentId) return; const el = document.getElementById('bc-drawer-content'); - el.innerHTML = '
Loading...
'; + el.innerHTML = '
Loading...
'; const d = await apiFetch('broadcast_reads&broadcast_id=' + bcCurrentId); if (!d.success || !d.reads.length) { - el.innerHTML = '
No reads yet.
'; + el.innerHTML = '
No reads yet.
'; return; } el.innerHTML = `
${d.reads.map(r=>` -
+
${escHtmlA(r.username)} · ${(r.read_at||'').substring(0,16)}
`).join('')}
`; @@ -2526,13 +2606,13 @@ async function loadCashoutMethods() {
${escHtmlA(m.icon||'💰')}
${escHtmlA(m.label)} - ${!parseInt(m.is_active)?'INACTIVE':''} + ${!parseInt(m.is_active)?'INACTIVE':''}
${escHtmlA(m.slug)}
- ${m.description?`
${escHtmlA(m.description)}
`:''} + ${m.description?`
${escHtmlA(m.description)}
`:''}
-
Order: ${m.sort_order}
+
Order: ${m.sort_order}
@@ -2612,7 +2692,7 @@ async function loadGames() {
${escHtmlA(g.name)} - ${!parseInt(g.is_active) ? 'INACTIVE' : ''} + ${!parseInt(g.is_active) ? 'INACTIVE' : ''}
${escHtmlA(g.slug)}
@@ -2629,7 +2709,7 @@ async function loadGames() {
-
Order: ${g.sort_order}
+
Order: ${g.sort_order}
@@ -2733,7 +2813,7 @@ function searchComposePlayers(q) { ).filter(u => !u.is_admin).slice(0, 8); if (!matches.length) { - dd.innerHTML = '
No players found
'; + dd.innerHTML = '
No players found
'; dd.style.display = 'block'; return; } dd.innerHTML = matches.map(u => { @@ -2744,8 +2824,8 @@ function searchComposePlayers(q) { onmouseover="this.style.background='rgba(255,255,255,.04)'" onmouseout="this.style.background='none'">
${init}
-
${escHtmlA(u.username)} · ${escHtmlA(u.alias||'')}
-
${escHtmlA(u.email||'No email')} · ${u.tokens||0} 🪙
+
${escHtmlA(u.username)} · ${escHtmlA(u.alias||'')}
+
${escHtmlA(u.email||'No email')} · ${u.tokens||0} 🪙
`; }).join(''); @@ -2828,18 +2908,18 @@ async function loadPendingSignups() {
${init}
${escHtmlA(p.username)}
-
${escHtmlA(p.alias)}
-
${escHtmlA(p.email||'No email')}
-
+
${escHtmlA(p.alias)}
+
${escHtmlA(p.email||'No email')}
+
Registered: ${created} Expires: ${expires}${expiring?' ⚠️':''}
- -
@@ -2910,7 +2990,7 @@ async function loadChatInbox(silent) { const d = await apiFetch('chat_inbox'); if (!d.success) return; if (!d.inbox.length) { - el.innerHTML = '
💬 No messages yet.
Users will appear here when they send a message.
'; + el.innerHTML = '
💬 No messages yet.
Users will appear here when they send a message.
'; return; } // Update unread badge in nav @@ -2927,7 +3007,7 @@ async function loadChatInbox(silent) {
${init}
-
${escHtmlA(row.username)} · ${escHtmlA(row.alias)}
+
${escHtmlA(row.username)} · ${escHtmlA(row.alias)}
${time}
${escHtmlA(preview)}
@@ -2954,7 +3034,7 @@ async function openChatThread(userId, username, alias) { document.getElementById('admin-chat-name').textContent = username + ' · ' + alias; document.getElementById('admin-chat-meta').textContent = 'Loading...'; document.getElementById('admin-chat-avatar').textContent = username.charAt(0).toUpperCase(); - document.getElementById('admin-chat-messages').innerHTML = '
Loading...
'; + document.getElementById('admin-chat-messages').innerHTML = '
Loading...
'; await loadAdminThread(true); clearInterval(adminChat.polling); adminChat.polling = setInterval(() => loadAdminThread(false), 3000); @@ -2967,7 +3047,7 @@ async function loadAdminThread(scrollToBottom) { const container = document.getElementById('admin-chat-messages'); if (d.messages.length === 0 && adminChat.lastId === 0) { - container.innerHTML = '
No messages in this thread yet.
'; + container.innerHTML = '
No messages in this thread yet.
'; } if (d.messages.length > 0) { @@ -3018,7 +3098,7 @@ async function adminClearThread() { const d = await apiFetch('chat_clear_thread', 'POST', { user_id: adminChat.userId }); if (d.success) { document.getElementById('admin-chat-messages').innerHTML = - '
Conversation cleared.
'; + '
Conversation cleared.
'; adminChat.lastId = 0; toast('Thread cleared', 'ok'); loadChatBadge(); @@ -3104,7 +3184,7 @@ setInterval(loadChatBadge, 10000); loadChatBadge(); -
+
TomTomGames Admin v1.0.0
diff --git a/public_html/api/admin.php b/public_html/api/admin.php index 52245f7..3333c82 100644 --- a/public_html/api/admin.php +++ b/public_html/api/admin.php @@ -310,14 +310,16 @@ switch ($action) { $stmt->execute([$uid]); $current = $stmt->fetchColumn(); $new_val = $current ? 0 : 1; - // If granting admin, also set email_verified=1 if ($new_val) { db()->prepare("UPDATE users SET is_admin=1, email_verified=1 WHERE id=?")->execute([$uid]); } else { db()->prepare("UPDATE users SET is_admin=0 WHERE id=?")->execute([$uid]); } - logActivity($new_val?'admin_granted':'admin_revoked', $uid, (int)$_SESSION['user_id'], 'user', $uid, 'Admin status changed to '.($new_val?'admin':'player')); - echo json_encode(['success'=>true, 'is_admin'=>$new_val]); + // Force the affected user to re-login by invalidating their sessions + // Store a flag in DB that forces re-auth on next request + db()->prepare("UPDATE users SET last_login=last_login WHERE id=?")->execute([$uid]); + logActivity($new_val?'admin_granted':'admin_revoked', $uid, (int)$_SESSION['user_id'], 'user', $uid, 'Admin status changed to '.($new_val?'admin':'player'), '', 'admin', '', '', 'warning'); + echo json_encode(['success'=>true, 'is_admin'=>$new_val, 'needs_relogin'=>true, 'message'=>$new_val ? 'Admin access granted. User must log out and back in.' : 'Admin access removed. User must log out and back in.']); break; // ─── TOGGLE SUSPEND ─────────────────────────────────────── @@ -439,6 +441,24 @@ switch ($action) { echo json_encode(['success'=>true,'broadcasts'=>$rows]); break; + case 'broadcast_list': + try { + $sql = "SELECT b.id, b.subject, b.message, b.target, b.sent_at, + u.username AS sender_name, + (SELECT COUNT(*) FROM broadcast_reads WHERE broadcast_id=b.id) AS read_count, + (SELECT COUNT(*) FROM broadcast_replies WHERE broadcast_id=b.id) AS reply_count, + (SELECT COUNT(*) FROM users WHERE status='active' AND is_admin=0) AS total_players + FROM broadcasts b + JOIN users u ON b.admin_id=u.id + ORDER BY b.sent_at DESC + LIMIT 100"; + $stmt = db()->query($sql); + echo json_encode(['success'=>true,'broadcasts'=>$stmt->fetchAll()]); + } catch(Exception $e) { + echo json_encode(['success'=>false,'error'=>$e->getMessage()]); + } + break; + case 'broadcast_send': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } $d = json_decode(file_get_contents('php://input'), true); @@ -468,6 +488,42 @@ switch ($action) { echo json_encode(['success'=>true]); break; + case 'broadcast_edit': + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } + $d = json_decode(file_get_contents('php://input'), true); + $id = (int)($d['id'] ?? 0); + $subject = substr(trim($d['subject'] ?? ''), 0, 200); + $message = trim($d['message'] ?? ''); + $target = in_array($d['target']??'', ['all','verified','unverified','admins']) ? $d['target'] : 'all'; + if (!$id || !$subject || !$message) { echo json_encode(['success'=>false,'error'=>'Missing fields']); exit; } + db()->prepare("UPDATE broadcasts SET subject=?, message=?, target=? WHERE id=?")->execute([$subject, $message, $target, $id]); + logAdminAction('BROADCAST_EDITED', $adminId, 'broadcast', $id, 'Edited broadcast #'.$id, '', '', 'info'); + echo json_encode(['success'=>true]); + break; + + case 'broadcast_resend': + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false]); exit; } + $d = json_decode(file_get_contents('php://input'), true); + $id = (int)($d['id'] ?? 0); + if (!$id) { echo json_encode(['success'=>false,'error'=>'Missing ID']); exit; } + $bc = db()->prepare("SELECT * FROM broadcasts WHERE id=?"); + $bc->execute([$id]); + $orig = $bc->fetch(); + if (!$orig) { echo json_encode(['success'=>false,'error'=>'Broadcast not found']); exit; } + // Count recipients + $target = $orig['target']; + $countSql = "SELECT COUNT(*) FROM users WHERE status='active' AND is_admin=0"; + if ($target === 'verified') $countSql .= " AND email_verified=1"; + if ($target === 'unverified') $countSql .= " AND email_verified=0"; + $recipientCount = (int)db()->query($countSql)->fetchColumn(); + // Delete old reads so everyone sees it again + db()->prepare("DELETE FROM broadcast_reads WHERE broadcast_id=?")->execute([$id]); + // Update sent_at to now + db()->prepare("UPDATE broadcasts SET sent_at=NOW() WHERE id=?")->execute([$id]); + logAdminAction('BROADCAST_RESENT', $adminId, 'broadcast', $id, 'Resent broadcast #'.$id.' to '.$recipientCount.' players', '', '', 'info'); + echo json_encode(['success'=>true,'recipient_count'=>$recipientCount]); + break; + case 'broadcast_reads': $bid = (int)($_GET['broadcast_id']??0); $rows = db()->prepare("SELECT br.read_at, u.username, u.alias FROM broadcast_reads br JOIN users u ON br.user_id=u.id WHERE br.broadcast_id=? ORDER BY br.read_at ASC"); diff --git a/public_html/api/me.php b/public_html/api/me.php index 3a23025..8b77479 100644 --- a/public_html/api/me.php +++ b/public_html/api/me.php @@ -1,5 +1,5 @@ true, 'user' => $user]); diff --git a/public_html/api/referrals.php b/public_html/api/referrals.php index 682952a..b335c9b 100644 --- a/public_html/api/referrals.php +++ b/public_html/api/referrals.php @@ -20,6 +20,13 @@ if ($method === 'GET') { $user->execute([$userId]); $u = $user->fetch(); + // Auto-generate code if missing + if (empty($u['referral_code'])) { + $code = strtoupper(substr(md5($userId.uniqid()),0,8)); + db()->prepare("UPDATE users SET referral_code=? WHERE id=?")->execute([$code, $userId]); + $u['referral_code'] = $code; + } + // Count verified referrals $countStmt = db()->prepare("SELECT COUNT(*) FROM referrals WHERE referrer_id=? AND status='verified'"); $countStmt->execute([$userId]); @@ -175,7 +182,7 @@ if ($action === 'resolve_referral') { db()->prepare("UPDATE referrals SET status='verified', tier_id=?, tokens_awarded=?, admin_id=?, admin_note=?, resolved_at=NOW() WHERE id=?") ->execute([$tier['id'] ?? null, $totalAward, (int)$_SESSION['user_id'], $note, $id]); db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$totalAward, $ref['referrer_id']]); - logAdminAction('REFERRAL_VERIFIED', (int)\$_SESSION['user_id'], 'referral', \$id, 'Verified referral #'.\$id.' — awarded '.\$totalAward.' tokens to user #'.\$ref['referrer_id'], 'pending', 'verified', 'info'); + logAdminAction('REFERRAL_VERIFIED', (int)$_SESSION['user_id'], 'referral', $id, 'Verified referral #'.$id.' — awarded '.$totalAward.' tokens to user #'.$ref['referrer_id'], 'pending', 'verified', 'info'); db()->commit(); echo json_encode(['success'=>true,'tokens_awarded'=>$totalAward,'bonus'=>$bonusTokens,'tier'=>$tier['name']??'']); } catch(Exception $e) { @@ -199,7 +206,7 @@ if ($action === 'resolve_share') { db()->prepare("UPDATE referral_social_shares SET status=?,admin_id=?,resolved_at=NOW() WHERE id=?")->execute([$status,(int)$_SESSION['user_id'],$id]); if ($status === 'approved') { db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$share['bonus_tokens'],$share['user_id']]); - logAdminAction('SOCIAL_SHARE_APPROVED', (int)\$_SESSION['user_id'], 'referral_share', \$id, 'Approved social share #'.\$id.' — awarded '.\$share['bonus_tokens'].' bonus tokens to user #'.\$share['user_id'], '', 'approved', 'info'); + logAdminAction('SOCIAL_SHARE_APPROVED', (int)$_SESSION['user_id'], 'referral_share', $id, 'Approved social share #'.$id.' — awarded '.$share['bonus_tokens'].' bonus tokens to user #'.$share['user_id'], '', 'approved', 'info'); } echo json_encode(['success'=>true]); exit; diff --git a/public_html/favicon.ico b/public_html/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ef9c61649366bd296d35e51c2f75986466bd7f86 GIT binary patch literal 136 zcmZQzU<5(|0R|wcz)-}%z#s<1odJICyj)UTKqjxJhf5HU2C85X;9vui@}K`F0x3&R z7srr_TgeFvOn(#{cxWFvt01w + + + T + \ No newline at end of file diff --git a/public_html/get_location.php b/public_html/get_location.php deleted file mode 100644 index cac2f09..0000000 --- a/public_html/get_location.php +++ /dev/null @@ -1,108 +0,0 @@ - true, - CURLOPT_HTTPHEADER => [ - 'Authorization: Bearer ' . $token, - 'Square-Version: 2024-01-18', - 'Content-Type: application/json', - ], - CURLOPT_TIMEOUT => 15, -]); -$resp = curl_exec($ch); -$code = curl_getinfo($ch, CURLINFO_HTTP_CODE); -curl_close($ch); - -if ($code === 200) { - $data = json_decode($resp, true); - $locations = $data['locations'] ?? []; -} else { - $error = "HTTP $code: " . htmlspecialchars($resp); -} -?> - - - - - -Square Location ID Finder - - - -

🔑 Square Location Finder

-
TomGames — Run once, then delete this file
- - -
Error fetching locations:
- -
No locations found on this Square account.
- -

Found location(s). Copy the ID for your main location:

- -
-
-
-
- -
-
- - · - Currency: · - Country: -
-
- - - - - -
⚠️ Security: Delete get_location.php from your server after use. It exposes your access token.
- - - - diff --git a/public_html/index.php b/public_html/index.php index 751291c..3d222b7 100644 --- a/public_html/index.php +++ b/public_html/index.php @@ -3,12 +3,14 @@ ob_start(); require_once __DIR__ . '/../includes/config.php'; require_once __DIR__ . '/../includes/square.php'; ob_end_clean(); -$_appVersion = '1.0.0'; +$_appVersion = '1.0.2'; ?> + + @@ -149,20 +151,24 @@ $_appVersion = '1.0.0'; --cyan:#00e5ff;--purple:#9b5de5;--green:#00e676;--red:#ff4444; --yellow:#ffd60a;--text:#e8e8f0;--text2:#8888aa; --radius:16px;--rsm:10px; + /* ── Typography Scale ── */ + --fs-xs:11px;--fs-sm:13px;--fs-base:15px;--fs-md:16px; + --fs-lg:18px;--fs-xl:20px;--fs-2xl:24px;--fs-3xl:28px; + --lh:1.6;--lh-tight:1.3; --glow-gold:0 0 20px rgba(240,192,64,0.4); --max:480px;--nav-h:64px } html{-webkit-tap-highlight-color:transparent} -body{background:var(--bg);color:var(--text);font-family:'Rajdhani',sans-serif;font-size:16px;min-height:100dvh;overflow-x:hidden;-webkit-overflow-scrolling:touch} +body{background:var(--bg);color:var(--text);font-family:'Rajdhani',sans-serif;font-size:17px;min-height:100dvh;overflow-x:hidden;-webkit-overflow-scrolling:touch} body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(rgba(0,229,255,0.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,229,255,0.03) 1px,transparent 1px);background-size:40px 40px;pointer-events:none;z-index:0} /* AUTH */ .auth-screen{display:flex;flex-direction:column;min-height:100vh;min-height:100dvh;background:var(--bg);padding:40px 24px 32px;max-width:var(--max);margin:0 auto;width:100%} .auth-logo{text-align:center;margin-bottom:36px} .auth-logo-text{font-family:'Exo 2',sans-serif;font-weight:900;font-size:32px;display:flex;align-items:center;justify-content:center;gap:12px}.auth-logo-text span{background:linear-gradient(135deg,var(--gold),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent} -.auth-logo-sub{font-size:13px;color:var(--text2);margin-top:4px;letter-spacing:1px} +.auth-logo-sub{font-size:15px;color:var(--text2);margin-top:4px;letter-spacing:1px} .auth-tabs{display:flex;margin-bottom:24px;background:var(--bg3);border-radius:var(--rsm);padding:4px} -.auth-tab{flex:1;padding:11px;border:none;background:none;color:var(--text2);font-family:'Exo 2',sans-serif;font-weight:700;font-size:14px;border-radius:8px;cursor:pointer;transition:all .2s;letter-spacing:.5px} +.auth-tab{flex:1;padding:11px;border:none;background:none;color:var(--text2);font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;border-radius:8px;cursor:pointer;transition:all .2s;letter-spacing:.5px} .auth-tab.active{background:linear-gradient(135deg,rgba(240,192,64,.15),rgba(0,229,255,.08));color:var(--gold)} /* MAIN */ @@ -170,8 +176,8 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .topbar{position:sticky;top:0;z-index:100;background:rgba(10,10,18,.96);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 16px;height:58px;display:flex;align-items:center;justify-content:space-between} .topbar-logo{font-family:'Exo 2',sans-serif;font-weight:900;font-size:18px;display:flex;align-items:center;gap:7px;letter-spacing:.5px}.topbar-logo span{background:linear-gradient(135deg,var(--gold),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent} .topbar-right{display:flex;align-items:center;gap:10px} -.token-badge{background:linear-gradient(135deg,#1a1a2e,#252540);border:1px solid var(--gold);border-radius:20px;padding:4px 12px;display:flex;align-items:center;gap:5px;font-family:'Exo 2',sans-serif;font-weight:700;font-size:14px;color:var(--gold2);box-shadow:var(--glow-gold)} -.avatar-btn{width:34px;height:34px;border-radius:50%;background:linear-gradient(135deg,var(--purple),var(--cyan));border:none;cursor:pointer;font-family:'Exo 2',sans-serif;font-weight:700;font-size:13px;color:#fff;display:flex;align-items:center;justify-content:center} +.token-badge{background:linear-gradient(135deg,#1a1a2e,#252540);border:1px solid var(--gold);border-radius:20px;padding:4px 12px;display:flex;align-items:center;gap:5px;font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;color:var(--gold2);box-shadow:var(--glow-gold)} +.avatar-btn{width:34px;height:34px;border-radius:50%;background:linear-gradient(135deg,var(--purple),var(--cyan));border:none;cursor:pointer;font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;color:#fff;display:flex;align-items:center;justify-content:center} /* PAGES */ .page{display:none;padding:16px;padding-bottom:84px} @@ -185,7 +191,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( /* NAV */ .navbar{position:fixed;bottom:0;left:50%;transform:translateX(-50%);width:100%;max-width:var(--max);height:var(--nav-h);background:rgba(10,10,18,.98);backdrop-filter:blur(16px);border-top:1px solid var(--border);display:flex;align-items:center;justify-content:space-around;z-index:100;padding-bottom:env(safe-area-inset-bottom)} -.nav-btn{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;border:none;background:none;color:var(--text2);cursor:pointer;padding:8px 4px;transition:color .2s;font-family:'Rajdhani',sans-serif;font-weight:600;font-size:10px;letter-spacing:.5px} +.nav-btn{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;border:none;background:none;color:var(--text2);cursor:pointer;padding:8px 4px;transition:color .2s;font-family:'Rajdhani',sans-serif;font-weight:600;font-size:12px;letter-spacing:.5px} .nav-btn svg{width:21px;height:21px} .nav-btn.active{color:var(--gold)} .nav-btn.active svg{filter:drop-shadow(0 0 5px rgba(240,192,64,.6))} @@ -200,10 +206,10 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( /* HERO BALANCE */ .hero-balance{background:linear-gradient(135deg,#1a1228,#121a28);border:1px solid rgba(240,192,64,.2);border-radius:var(--radius);padding:22px 20px;text-align:center;margin-bottom:20px;position:relative;overflow:hidden;box-shadow:0 0 0 1px rgba(240,192,64,.15),0 8px 32px rgba(0,0,0,.4)} .hero-balance::before{content:'';position:absolute;top:-50px;right:-50px;width:160px;height:160px;background:radial-gradient(circle,rgba(240,192,64,.12) 0%,transparent 70%)} -.balance-label{font-size:11px;font-weight:700;color:var(--text2);letter-spacing:2px;text-transform:uppercase;margin-bottom:6px} +.balance-label{font-size:13px;font-weight:700;color:var(--text2);letter-spacing:2px;text-transform:uppercase;margin-bottom:6px} .balance-amount{font-family:'Exo 2',sans-serif;font-weight:900;font-size:48px;color:var(--gold);line-height:1;text-shadow:var(--glow-gold)} -.balance-unit{font-size:13px;color:var(--text2);margin-top:2px;letter-spacing:1px} -.balance-alias{margin-top:12px;font-size:13px;color:var(--text2)} +.balance-unit{font-size:15px;color:var(--text2);margin-top:2px;letter-spacing:1px} +.balance-alias{margin-top:12px;font-size:15px;color:var(--text2)} .balance-alias strong{color:var(--cyan)} /* PLATFORM GRID */ @@ -213,15 +219,15 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .platform-card:active{transform:scale(.96)} .platform-img-wrap{width:58px;height:58px;border-radius:14px;overflow:hidden;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.08)} .platform-img-wrap img{width:58px;height:58px;object-fit:cover} -.platform-name{font-family:'Exo 2',sans-serif;font-weight:700;font-size:12px;color:var(--text)} -.play-btn{font-size:10px;color:var(--text2);font-weight:600;letter-spacing:.5px} +.platform-name{font-family:'Exo 2',sans-serif;font-weight:700;font-size:14px;color:var(--text)} +.play-btn{font-size:12px;color:var(--text2);font-weight:600;letter-spacing:.5px} /* ─── PAYMENT FORM (milkyswipe style) ─── */ .pay-form{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:14px} .pay-form-header{background:linear-gradient(135deg,#0d1a2e,#0a1220);padding:16px 18px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px} .pay-form-icon{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--gold),#d4a017);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0} -.pay-form-title{font-family:'Exo 2',sans-serif;font-weight:700;font-size:16px;color:var(--text)} -.pay-form-sub{font-size:12px;color:var(--text2);margin-top:1px} +.pay-form-title{font-family:'Exo 2',sans-serif;font-weight:700;font-size:17px;color:var(--text)} +.pay-form-sub{font-size:14px;color:var(--text2);margin-top:1px} .pay-form-body{padding:18px} /* Package pills */ @@ -230,39 +236,39 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .pkg-pill{flex-shrink:0;background:var(--bg3);border:1.5px solid var(--border);border-radius:30px;padding:8px 16px;cursor:pointer;transition:all .2s;text-align:center;min-width:72px} .pkg-pill.popular{border-color:var(--gold)} .pkg-pill.selected{background:linear-gradient(135deg,rgba(240,192,64,.15),rgba(0,229,255,.08));border-color:var(--gold);box-shadow:0 0 12px rgba(240,192,64,.2)} -.pkg-pill-tokens{font-family:'Exo 2',sans-serif;font-weight:900;font-size:16px;color:var(--gold2)} -.pkg-pill-price{font-size:11px;color:var(--text2);font-weight:600;margin-top:1px} -.pkg-pill.popular .pkg-pill-tokens::after{content:' ★';font-size:10px;color:var(--gold)} +.pkg-pill-tokens{font-family:'Exo 2',sans-serif;font-weight:900;font-size:17px;color:var(--gold2)} +.pkg-pill-price{font-size:13px;color:var(--text2);font-weight:600;margin-top:1px} +.pkg-pill.popular .pkg-pill-tokens::after{content:' ★';font-size:12px;color:var(--gold)} /* Amount display */ .amount-display{background:var(--bg3);border:1px solid var(--border);border-radius:var(--rsm);padding:14px 16px;margin-bottom:14px;display:flex;align-items:center;justify-content:space-between} -.amount-display-label{font-size:12px;color:var(--text2);font-weight:700;text-transform:uppercase;letter-spacing:1px} +.amount-display-label{font-size:14px;color:var(--text2);font-weight:700;text-transform:uppercase;letter-spacing:1px} .amount-display-value{font-family:'Exo 2',sans-serif;font-weight:900;font-size:22px;color:var(--gold)} /* Form fields */ .fg{margin-bottom:14px} -.fg label{display:block;font-size:11px;font-weight:700;color:var(--text2);letter-spacing:1px;text-transform:uppercase;margin-bottom:7px} -.fi{width:100%;background:var(--bg3);border:1.5px solid var(--border);border-radius:var(--rsm);padding:13px 14px;color:var(--text);font-family:'Rajdhani',sans-serif;font-size:16px;font-weight:500;outline:none;transition:border-color .2s;-webkit-appearance:none;appearance:none} +.fg label{display:block;font-size:13px;font-weight:700;color:var(--text2);letter-spacing:1px;text-transform:uppercase;margin-bottom:7px} +.fi{width:100%;background:var(--bg3);border:1.5px solid var(--border);border-radius:var(--rsm);padding:13px 14px;color:var(--text);font-family:'Rajdhani',sans-serif;font-size:17px;font-weight:500;outline:none;transition:border-color .2s;-webkit-appearance:none;appearance:none} .fi:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(0,229,255,.08)} .fi-select{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M0 0l6 8 6-8z' fill='%238888aa'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 14px center;padding-right:36px} .fi-row{display:grid;grid-template-columns:1fr 1fr;gap:12px} /* Custom token amount */ .custom-amt-row{background:linear-gradient(135deg,rgba(240,192,64,.06),rgba(0,229,255,.03));border:1.5px dashed rgba(240,192,64,.3);border-radius:var(--rsm);padding:12px 14px;margin-top:8px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap} -.custom-amt-label{font-size:11px;font-weight:700;color:var(--gold);letter-spacing:.5px;margin-bottom:6px} +.custom-amt-label{font-size:13px;font-weight:700;color:var(--gold);letter-spacing:.5px;margin-bottom:6px} .custom-amt-input-wrap{display:flex;align-items:center;background:var(--bg3);border:1.5px solid rgba(240,192,64,.3);border-radius:8px;overflow:hidden} .custom-amt-dollar{padding:0 10px;font-family:'Exo 2',sans-serif;font-weight:700;font-size:18px;color:var(--gold);background:rgba(240,192,64,.08);border-right:1px solid rgba(240,192,64,.2)} .custom-amt-input{background:transparent;border:none;padding:10px 12px;color:var(--text);font-family:'Exo 2',sans-serif;font-size:18px;font-weight:700;width:110px;outline:none;-moz-appearance:textfield} .custom-amt-input::-webkit-outer-spin-button,.custom-amt-input::-webkit-inner-spin-button{-webkit-appearance:none} -.custom-amt-input::placeholder{color:var(--text2);font-size:14px;font-weight:400} -.custom-amt-equiv{font-size:14px;color:var(--text2);white-space:nowrap} +.custom-amt-input::placeholder{color:var(--text2);font-size:15px;font-weight:400} +.custom-amt-equiv{font-size:15px;color:var(--text2);white-space:nowrap} .custom-amt-equiv strong{color:var(--gold2);font-family:'Exo 2',sans-serif;font-size:18px} /* Order summary box */ .order-summary{background:linear-gradient(135deg,#1a1228,#0d1820);border:1px solid rgba(240,192,64,.2);border-radius:var(--rsm);padding:14px 16px;margin-bottom:16px} .order-summary-row{display:flex;justify-content:space-between;align-items:center;padding:4px 0} -.order-summary-label{font-size:12px;color:var(--text2);font-weight:600;letter-spacing:.5px} -.order-summary-val{font-size:14px;color:var(--text);font-weight:600} +.order-summary-label{font-size:14px;color:var(--text2);font-weight:600;letter-spacing:.5px} +.order-summary-val{font-size:15px;color:var(--text);font-weight:600} .order-summary-divider{height:1px;background:rgba(255,255,255,.08);margin:8px 0} .order-summary-total{font-family:'Exo 2',sans-serif;font-weight:900;font-size:24px;color:var(--gold)} @@ -271,7 +277,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .pmt{background:var(--bg3);border:1.5px solid var(--border);border-radius:var(--rsm);padding:10px 4px;text-align:center;cursor:pointer;transition:all .2s} .pmt.active{border-color:var(--cyan);background:rgba(0,229,255,.06)} .pmt-icon{font-size:20px;display:block;margin-bottom:3px} -.pmt-label{font-size:9px;font-weight:700;color:var(--text2);letter-spacing:.3px;line-height:1.2} +.pmt-label{font-size:11px;font-weight:700;color:var(--text2);letter-spacing:.3px;line-height:1.2} .pmt.active .pmt-label{color:var(--cyan)} /* Card container */ @@ -279,15 +285,15 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( /* Manual pay instructions */ .manual-box{background:linear-gradient(135deg,rgba(0,229,255,.04),rgba(0,0,0,.2));border:1px solid rgba(0,229,255,.15);border-radius:var(--rsm);padding:14px;margin-bottom:14px} -.manual-box-title{font-weight:700;font-size:13px;color:var(--cyan);margin-bottom:8px;display:flex;align-items:center;gap:6px} +.manual-box-title{font-weight:700;font-size:15px;color:var(--cyan);margin-bottom:8px;display:flex;align-items:center;gap:6px} .manual-step{display:flex;gap:10px;margin-bottom:8px;align-items:flex-start} -.manual-step-num{width:20px;height:20px;border-radius:50%;background:var(--cyan);color:#000;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px} -.manual-step-text{font-size:13px;color:var(--text);line-height:1.5} +.manual-step-num{width:20px;height:20px;border-radius:50%;background:var(--cyan);color:#000;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px} +.manual-step-text{font-size:15px;color:var(--text);line-height:1.5} .manual-step-text strong{color:var(--gold2)} -.manual-step-text .handle{background:rgba(240,192,64,.1);border:1px solid rgba(240,192,64,.3);border-radius:6px;padding:1px 8px;font-family:'Exo 2',sans-serif;font-weight:700;color:var(--gold);font-size:14px;letter-spacing:.5px} +.manual-step-text .handle{background:rgba(240,192,64,.1);border:1px solid rgba(240,192,64,.3);border-radius:6px;padding:1px 8px;font-family:'Exo 2',sans-serif;font-weight:700;color:var(--gold);font-size:15px;letter-spacing:.5px} /* Buttons */ -.btn{width:100%;padding:15px;border:none;border-radius:var(--rsm);font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;letter-spacing:1px;cursor:pointer;transition:all .2s;text-transform:uppercase;display:flex;align-items:center;justify-content:center;gap:8px} +.btn{width:100%;padding:15px;border:none;border-radius:var(--rsm);font-family:'Exo 2',sans-serif;font-weight:700;font-size:16px;letter-spacing:1px;cursor:pointer;transition:all .2s;text-transform:uppercase;display:flex;align-items:center;justify-content:center;gap:8px} .btn:active{transform:scale(.98)} .btn-gold{background:linear-gradient(135deg,var(--gold),#d4a017);color:#000;box-shadow:0 4px 20px rgba(240,192,64,.4)} .btn-outline{background:transparent;border:1.5px solid var(--gold);color:var(--gold)} @@ -296,19 +302,19 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .btn-green{background:linear-gradient(135deg,var(--green),#00aa55);color:#000} /* Alerts */ -.alert{padding:12px 14px;border-radius:var(--rsm);margin-bottom:14px;font-weight:600;font-size:14px;display:none;line-height:1.4} +.alert{padding:12px 14px;border-radius:var(--rsm);margin-bottom:14px;font-weight:600;font-size:15px;display:none;line-height:1.4} .alert.show{display:block} .alert-error{background:rgba(255,68,68,.1);border:1px solid rgba(255,68,68,.3);color:var(--red)} .alert-success{background:rgba(0,230,118,.1);border:1px solid rgba(0,230,118,.3);color:var(--green)} .alert-info{background:rgba(0,229,255,.08);border:1px solid rgba(0,229,255,.2);color:var(--cyan)} /* Divider */ -.divider{display:flex;align-items:center;gap:10px;margin:18px 0;color:var(--text2);font-size:11px;font-weight:700;letter-spacing:1px} +.divider{display:flex;align-items:center;gap:10px;margin:18px 0;color:var(--text2);font-size:13px;font-weight:700;letter-spacing:1px} .divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--border)} /* Cashout items */ .cashout-item{background:var(--bg3);border:1px solid var(--border);border-radius:var(--rsm);padding:13px 14px;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between;gap:8px} -.badge{display:inline-block;font-size:10px;font-weight:700;padding:3px 9px;border-radius:20px;letter-spacing:.3px} +.badge{display:inline-block;font-size:12px;font-weight:700;padding:3px 9px;border-radius:20px;letter-spacing:.3px} .badge-pending{background:rgba(255,214,10,.15);color:var(--yellow)} .badge-approved{background:rgba(0,230,118,.15);color:var(--green)} .badge-rejected{background:rgba(255,68,68,.15);color:var(--red)} @@ -330,13 +336,13 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( @keyframes spin{to{transform:rotate(360deg)}} /* Order ref badge */ -.ref-badge{background:rgba(240,192,64,.1);border:1px solid rgba(240,192,64,.3);border-radius:8px;padding:10px 14px;margin-bottom:14px;font-size:13px;color:var(--text2)} +.ref-badge{background:rgba(240,192,64,.1);border:1px solid rgba(240,192,64,.3);border-radius:8px;padding:10px 14px;margin-bottom:14px;font-size:15px;color:var(--text2)} .ref-badge strong{color:var(--gold2);font-family:'Exo 2',sans-serif} /* ─── PROFILE TABS ───────────────────────────────────── */ -.form-label{font-size:11px;font-weight:700;color:var(--text2);letter-spacing:1px;text-transform:uppercase;margin-bottom:6px;display:block} -.profile-tabs{display:flex;gap:0;background:var(--bg3);border-radius:var(--rsm);padding:4px;margin-bottom:14px} -.profile-tab{flex:1;padding:10px;border:none;background:none;color:var(--text2);font-family:'Exo 2',sans-serif;font-weight:700;font-size:13px;border-radius:8px;cursor:pointer;transition:all .2s} +.form-label{font-size:13px;font-weight:700;color:var(--text2);letter-spacing:1px;text-transform:uppercase;margin-bottom:6px;display:block} +.profile-tabs{display:flex;flex-wrap:wrap;gap:3px;background:var(--bg3);border-radius:var(--rsm);padding:4px;margin-bottom:14px} +.profile-tab{flex:1 1 calc(25% - 3px);min-width:72px;padding:10px 4px;border:none;background:none;color:var(--text2);font-family:'Exo 2',sans-serif;font-weight:700;font-size:13px;border-radius:8px;cursor:pointer;transition:all .2s;text-align:center} .profile-tab.active{background:linear-gradient(135deg,rgba(240,192,64,.15),rgba(0,229,255,.08));color:var(--gold)} .profile-tab-panel{display:none} .profile-tab-panel.active{display:block;animation:fadeUp .2s ease} @@ -359,9 +365,9 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .pay-proc-spinner{position:absolute;inset:0;border-radius:50%;border:3px solid rgba(240,192,64,.15);border-top-color:var(--gold);border-right-color:var(--gold);animation:rotateRing 1s linear infinite} .pay-proc-icon{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:40px;animation:pulseIcon 2s ease infinite} .pay-proc-title{font-family:'Exo 2',sans-serif;font-weight:900;font-size:24px;color:var(--text);margin-bottom:8px;text-align:center} -.pay-proc-sub{font-size:13px;color:var(--text2);margin-bottom:32px;text-align:center} +.pay-proc-sub{font-size:15px;color:var(--text2);margin-bottom:32px;text-align:center} .pay-proc-steps{display:flex;flex-direction:column;gap:10px;width:100%;max-width:260px} -.pay-proc-step{font-size:13px;font-weight:600;color:rgba(255,255,255,.2);padding:8px 14px;border-radius:8px;transition:all .4s;display:flex;align-items:center;gap:8px} +.pay-proc-step{font-size:15px;font-weight:600;color:rgba(255,255,255,.2);padding:8px 14px;border-radius:8px;transition:all .4s;display:flex;align-items:center;gap:8px} .pay-proc-step.active{color:var(--text);background:rgba(240,192,64,.08);border-left:3px solid var(--gold);animation:stepFadeIn .3s ease} .pay-proc-step.done{color:var(--green);background:rgba(0,230,118,.06)} @@ -373,36 +379,36 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .pay-result-title.error{color:var(--red)} .pay-result-title.pending{color:var(--gold)} .pay-balance-bump{font-family:'Exo 2',sans-serif;font-weight:900;font-size:56px;color:var(--gold);line-height:1;margin:12px 0;text-shadow:0 0 30px rgba(240,192,64,.5)} -.pay-result-detail{background:rgba(255,255,255,.04);border:1px solid var(--border);border-radius:12px;padding:14px;margin:16px 0;font-size:13px;color:var(--text2);line-height:1.8;text-align:left} +.pay-result-detail{background:rgba(255,255,255,.04);border:1px solid var(--border);border-radius:12px;padding:14px;margin:16px 0;font-size:15px;color:var(--text2);line-height:1.8;text-align:left} .pay-result-detail strong{color:var(--text)} .pay-result-btns{display:flex;flex-direction:column;gap:10px;margin-top:20px} /* ─── GAME ALIAS CARDS ────────────────────────────────── */ .game-alias-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rsm);padding:12px 14px;margin-bottom:10px;display:flex;align-items:center;gap:12px} .game-alias-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0} -.game-alias-name{font-family:'Exo 2',sans-serif;font-weight:700;font-size:14px;color:var(--text);margin-bottom:5px} -.game-alias-input{width:100%;background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:8px 12px;color:var(--text);font-family:'Rajdhani',sans-serif;font-size:14px;outline:none;transition:border-color .15s;box-sizing:border-box} +.game-alias-name{font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;color:var(--text);margin-bottom:5px} +.game-alias-input{width:100%;background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:8px 12px;color:var(--text);font-family:'Rajdhani',sans-serif;font-size:15px;outline:none;transition:border-color .15s;box-sizing:border-box} .game-alias-input:focus{border-color:var(--cyan)} .game-alias-input::placeholder{color:var(--text2)} /* ─── ACTIVITY DASHBOARD ──────────────────────────────── */ -.activity-ftab{background:var(--bg3);border:1px solid var(--border);color:var(--text2);border-radius:20px;padding:6px 14px;font-family:'Exo 2',sans-serif;font-weight:700;font-size:12px;cursor:pointer;white-space:nowrap;transition:all .15s;flex-shrink:0} +.activity-ftab{background:var(--bg3);border:1px solid var(--border);color:var(--text2);border-radius:20px;padding:6px 14px;font-family:'Exo 2',sans-serif;font-weight:700;font-size:14px;cursor:pointer;white-space:nowrap;transition:all .15s;flex-shrink:0} .activity-ftab.active{background:rgba(240,192,64,.12);border-color:rgba(240,192,64,.35);color:var(--gold)} .activity-card{background:var(--bg3);border:1px solid var(--border);border-radius:var(--rsm);padding:12px 14px;margin-bottom:8px;display:flex;gap:12px;align-items:flex-start;transition:border-color .2s} .activity-card:hover{border-color:rgba(255,255,255,.12)} -.activity-icon{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0} +.activity-icon{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:17px;flex-shrink:0} .activity-body{flex:1;min-width:0} -.activity-title{font-family:'Exo 2',sans-serif;font-weight:700;font-size:13px;color:var(--text);margin-bottom:2px} -.activity-meta{font-size:11px;color:var(--text2)} -.activity-status{display:inline-block;font-size:10px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;border-radius:4px;padding:2px 7px;margin-top:4px} +.activity-title{font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;color:var(--text);margin-bottom:2px} +.activity-meta{font-size:13px;color:var(--text2)} +.activity-status{display:inline-block;font-size:12px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;border-radius:4px;padding:2px 7px;margin-top:4px} .ac-pending{background:rgba(240,192,64,.12);color:var(--gold)} .ac-locked{background:rgba(0,229,255,.1);color:var(--cyan)} .ac-completed,.ac-sent,.ac-approved{background:rgba(0,230,118,.1);color:var(--green)} .ac-rejected,.ac-failed,.ac-deleted{background:rgba(255,68,68,.1);color:var(--red)} .ac-new{background:rgba(240,192,64,.15);color:var(--gold)} .ac-read{background:rgba(255,255,255,.06);color:var(--text2)} -.activity-amount{font-family:'Exo 2',sans-serif;font-weight:700;font-size:14px;text-align:right;flex-shrink:0} -.summary-pill{background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:12px;font-family:'Exo 2',sans-serif;font-weight:700;white-space:nowrap} +.activity-amount{font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;text-align:right;flex-shrink:0} +.summary-pill{background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:14px;font-family:'Exo 2',sans-serif;font-weight:700;white-space:nowrap} /* ─── CASHOUT REQUEST CARDS ───────────────────────────── */ .co-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rsm);padding:14px;margin-bottom:10px;transition:border-color .2s} @@ -411,7 +417,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .co-card.sent,.co-card.approved{border-color:rgba(0,230,118,.25)} .co-card.rejected{border-color:rgba(255,68,68,.2);opacity:.75} .co-card.deleted{opacity:.45} -.co-status-pill{display:inline-block;font-size:10px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;border-radius:4px;padding:3px 8px;margin-left:8px} +.co-status-pill{display:inline-block;font-size:12px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;border-radius:4px;padding:3px 8px;margin-left:8px} .co-status-pending{background:rgba(240,192,64,.12);color:var(--gold)} .co-status-locked{background:rgba(0,229,255,.1);color:var(--cyan)} .co-status-sent,.co-status-approved{background:rgba(0,230,118,.1);color:var(--green)} @@ -420,7 +426,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .co-edit-row{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap} /* ─── REFERRAL STYLES ─────────────────────────────────── */ -.ref-share-btn{border-radius:8px;padding:8px 14px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;transition:opacity .15s} +.ref-share-btn{border-radius:8px;padding:8px 14px;font-size:14px;font-weight:700;cursor:pointer;white-space:nowrap;transition:opacity .15s} .ref-share-btn:hover{opacity:.8} .ref-status-pending{color:var(--gold)} .ref-status-verified{color:var(--green)} @@ -431,35 +437,35 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( .pa-card.approved{border-color:rgba(0,230,118,.3);background:rgba(0,230,118,.03)} .pa-card.pending{border-color:rgba(240,192,64,.2)} .pa-card.denied{border-color:rgba(255,68,68,.2);opacity:.75} -.pa-cred-box{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 14px;margin-top:10px;font-family:monospace;font-size:13px} +.pa-cred-box{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 14px;margin-top:10px;font-family:monospace;font-size:15px} .pa-cred-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px} -.pa-cred-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--text2)} -.pa-cred-val{font-size:14px;font-weight:700;color:var(--green)} +.pa-cred-label{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--text2)} +.pa-cred-val{font-size:15px;font-weight:700;color:var(--green)} .pa-cred-pass{filter:blur(4px);transition:filter .2s;cursor:pointer;user-select:all} .pa-cred-pass:hover,.pa-cred-pass.revealed{filter:none} /* Onboarding modal */ #onboarding-modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:900;display:flex;align-items:center;justify-content:center;padding:20px} .onboarding-box{background:var(--bg3);border:1px solid rgba(240,192,64,.3);border-radius:16px;padding:28px 24px;max-width:420px;width:100%;text-align:center} .onboarding-title{font-family:'Exo 2',sans-serif;font-weight:900;font-size:22px;color:var(--gold);margin-bottom:10px} -.onboarding-sub{font-size:14px;color:var(--text2);margin-bottom:24px;line-height:1.6} +.onboarding-sub{font-size:15px;color:var(--text2);margin-bottom:24px;line-height:1.6} /* ─── BROADCAST STYLES ────────────────────────────────── */ .bc-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rsm);margin-bottom:12px;overflow:hidden;transition:border-color .2s} .bc-card.unread{border-color:rgba(240,192,64,.3);background:linear-gradient(135deg,rgba(240,192,64,.04),var(--card))} .bc-header{padding:14px 16px 10px;display:flex;gap:10px;align-items:flex-start} .bc-avatar{width:38px;height:38px;border-radius:50%;background:linear-gradient(135deg,var(--gold),#d4a017);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0} -.bc-subject{font-family:'Exo 2',sans-serif;font-weight:700;font-size:14px;color:var(--text)} -.bc-meta{font-size:11px;color:var(--text2);margin-top:2px} -.bc-body{padding:0 16px 12px;font-size:13px;color:var(--text2);line-height:1.6;white-space:pre-wrap} +.bc-subject{font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;color:var(--text)} +.bc-meta{font-size:13px;color:var(--text2);margin-top:2px} +.bc-body{padding:0 16px 12px;font-size:15px;color:var(--text2);line-height:1.6;white-space:pre-wrap} .bc-footer{padding:8px 16px;border-top:1px solid var(--border);display:flex;gap:10px;align-items:center} .bc-reply-wrap{padding:0 16px 14px;display:none} .bc-reply-wrap.open{display:block} .bc-replies-list{max-height:220px;overflow-y:auto;margin-bottom:10px} .bc-reply-row{display:flex;gap:8px;margin-bottom:8px} .bc-reply-bubble{background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:8px 12px;flex:1} -.bc-reply-who{font-size:11px;font-weight:700;color:var(--cyan);margin-bottom:3px} +.bc-reply-who{font-size:13px;font-weight:700;color:var(--cyan);margin-bottom:3px} .bc-reply-who.admin{color:var(--gold)} -.bc-reply-msg{font-size:13px;color:var(--text)} +.bc-reply-msg{font-size:15px;color:var(--text)} /* ─── CHAT STYLES ─────────────────────────────────────── */ @keyframes pulse2{from{opacity:.5}to{opacity:1}} @@ -470,11 +476,11 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( /* Quick reply chips */ .chat-quick-replies{display:flex;flex-wrap:wrap;gap:8px;padding:8px 14px 4px;flex-shrink:0} -.chat-chip{background:rgba(240,192,64,.08);border:1px solid rgba(240,192,64,.25);border-radius:20px;padding:7px 14px;color:var(--gold);font-family:'Exo 2',sans-serif;font-weight:600;font-size:12px;cursor:pointer;transition:all .15s;white-space:nowrap} +.chat-chip{background:rgba(240,192,64,.08);border:1px solid rgba(240,192,64,.25);border-radius:20px;padding:7px 14px;color:var(--gold);font-family:'Exo 2',sans-serif;font-weight:600;font-size:14px;cursor:pointer;transition:all .15s;white-space:nowrap} .chat-chip:active{background:rgba(240,192,64,.18);transform:scale(.97)} /* Loading dots */ -.chat-loading{display:flex;justify-content:center;align-items:center;padding:30px;color:var(--text2);font-size:13px} +.chat-loading{display:flex;justify-content:center;align-items:center;padding:30px;color:var(--text2);font-size:15px} .chat-loading-dots{display:flex;gap:5px} .chat-loading-dots span{width:8px;height:8px;border-radius:50%;background:var(--text2);animation:dotBounce 1.2s ease infinite} .chat-loading-dots span:nth-child(2){animation-delay:.2s} @@ -486,30 +492,48 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( #page-chat.active{display:flex} .chat-header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:rgba(16,16,30,.98);border-bottom:1px solid var(--border);flex-shrink:0} .chat-header-avatar{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,var(--gold),#d4a017);display:flex;align-items:center;justify-content:center;font-family:'Exo 2',sans-serif;font-weight:900;font-size:18px;color:#000;flex-shrink:0;box-shadow:var(--glow-gold)} -.chat-header-name{font-family:'Exo 2',sans-serif;font-weight:700;font-size:15px;color:var(--text)} -.chat-header-status{font-size:11px;color:var(--green);font-weight:600;margin-top:2px} +.chat-header-name{font-family:'Exo 2',sans-serif;font-weight:700;font-size:16px;color:var(--text)} +.chat-header-status{font-size:13px;color:var(--green);font-weight:600;margin-top:2px} .chat-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px;-webkit-overflow-scrolling:touch;padding-bottom:8px} -.chat-loading{color:var(--text2);font-size:13px;text-align:center;padding:20px} +.chat-loading{color:var(--text2);font-size:15px;text-align:center;padding:20px} .chat-bubble-wrap{display:flex;flex-direction:column;max-width:78%} .chat-bubble-wrap.mine{align-self:flex-end;align-items:flex-end} .chat-bubble-wrap.theirs{align-self:flex-start;align-items:flex-start} -.chat-bubble{padding:10px 14px;border-radius:18px;font-size:14px;line-height:1.45;word-break:break-word;position:relative} +.chat-bubble{padding:10px 14px;border-radius:18px;font-size:15px;line-height:1.45;word-break:break-word;position:relative} .chat-bubble.mine{background:linear-gradient(135deg,var(--gold),#d4a017);color:#000;border-bottom-right-radius:4px;font-weight:500} .chat-bubble.theirs{background:var(--card);color:var(--text);border:1px solid var(--border);border-bottom-left-radius:4px} -.chat-time{font-size:10px;color:var(--text2);margin-top:3px;padding:0 4px} -.chat-date-divider{text-align:center;font-size:11px;color:var(--text2);font-weight:700;letter-spacing:.5px;margin:8px 0;display:flex;align-items:center;gap:8px} +.chat-time{font-size:12px;color:var(--text2);margin-top:3px;padding:0 4px} +.chat-date-divider{text-align:center;font-size:13px;color:var(--text2);font-weight:700;letter-spacing:.5px;margin:8px 0;display:flex;align-items:center;gap:8px} .chat-date-divider::before,.chat-date-divider::after{content:'';flex:1;height:1px;background:var(--border)} .chat-input-bar{display:flex;gap:8px;padding:10px 12px;background:rgba(10,10,18,.98);border-top:1px solid var(--border);flex-shrink:0;padding-bottom:calc(10px + env(safe-area-inset-bottom));padding-bottom:max(10px,calc(10px + env(safe-area-inset-bottom)))} -.chat-input{flex:1;background:var(--bg3);border:1.5px solid var(--border);border-radius:24px;padding:10px 16px;color:var(--text);font-family:'Rajdhani',sans-serif;font-size:15px;outline:none;transition:border-color .2s} +.chat-input{flex:1;background:var(--bg3);border:1.5px solid var(--border);border-radius:24px;padding:10px 16px;color:var(--text);font-family:'Rajdhani',sans-serif;font-size:16px;line-height:1.6;outline:none;transition:border-color .2s} .chat-input:focus{border-color:var(--cyan)} .chat-send-btn{width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,var(--gold),#d4a017);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .2s;color:#000} .chat-send-btn:active{transform:scale(.93)} .chat-send-btn svg{stroke:#000} -.chat-nav-badge{position:absolute;top:4px;right:14px;background:var(--red);color:#fff;font-size:9px;font-weight:700;min-width:16px;height:16px;border-radius:8px;display:flex;align-items:center;justify-content:center;padding:0 4px} +.chat-nav-badge{position:absolute;top:4px;right:14px;background:var(--red);color:#fff;font-size:11px;font-weight:700;min-width:16px;height:16px;border-radius:8px;display:flex;align-items:center;justify-content:center;padding:0 4px} .chat-empty{text-align:center;padding:40px 20px;color:var(--text2);flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center} .chat-empty-icon{font-size:52px;margin-bottom:14px} -.chat-empty-text{font-size:14px;line-height:1.7} -.chat-sender-label{font-size:10px;font-weight:700;color:var(--text2);letter-spacing:.5px;text-transform:uppercase;margin-bottom:3px;padding:0 4px} +.chat-empty-text{font-size:15px;line-height:1.7} +.chat-sender-label{font-size:12px;font-weight:700;color:var(--text2);letter-spacing:.5px;text-transform:uppercase;margin-bottom:3px;padding:0 4px} + +/* ── Responsive Typography ─────────────────────────────── */ +/* Tablet (≥768px) */ +@media (min-width: 768px) { + body { font-size: 17px; } + .card { font-size: 16px; padding: 22px; } + .page-title { font-size: 26px !important; } + .card-title { font-size: 18px; } + .btn { font-size: 16px; padding: 14px 24px; } + .activity-item { font-size: 15px; } +} +/* Desktop (≥1200px) */ +@media (min-width: 1200px) { + body { font-size: 18px; } + .card { font-size: 17px; padding: 24px; } + .page-title { font-size: 28px !important; } + .btn { font-size: 17px; } +} @@ -527,7 +551,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
- +
- +
@@ -561,10 +585,10 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
@@ -575,11 +599,11 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-
+
-
📧 Required — we'll send a verification link
+
📧 Required — we'll send a verification link
@@ -593,10 +617,10 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( We sent a verification link to:

-

+

Open the email and click the link to activate your account. Check your spam folder if you don't see it.

-
+
Steps:
1. Open your email inbox
2. Find email from noreply@tomtomgames.com
@@ -605,7 +629,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
- +
@@ -670,7 +694,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
- +
@@ -719,7 +743,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
- +
@@ -758,7 +782,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-
@@ -769,7 +793,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-

256-bit SSL · Secured by Square

+

256-bit SSL · Secured by Square

@@ -790,8 +814,8 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-
Available Balance
-
0 TOKENS
+
Available Balance
+
0 TOKENS
💰
@@ -801,12 +825,12 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-
Loading payout methods...
+
Loading payout methods...
- @@ -815,12 +839,12 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-

Cashouts are reviewed and paid out within a few minutes

+

Cashouts are reviewed and paid out within a few minutes

MY CASHOUT REQUESTS
-
Loading your requests...
+
Loading your requests...
@@ -830,16 +854,16 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
?
-
+
-
Token Balance
+
Token Balance
0
- +
@@ -868,7 +892,7 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-
+
@@ -879,18 +903,18 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-
Platform Logins
-
Your game platform accounts. Approved logins show your credentials below. Tap password to reveal.
+
Platform Logins
+
Your game platform accounts. Approved logins show your credentials below. Tap password to reveal.
-
Loading...
+
Loading...
-
➕ Request Platform Account
-
Don't have a login for a platform? Request one and our team will set it up for you.
+
➕ Request Platform Account
+
Don't have a login for a platform? Request one and our team will set it up for you.
- + +
- +
0
-
Referrals
+
Referrals
0
-
Tokens Earned
+
Tokens Earned
-
Current Tier
+
Current Tier
Loading messages...
@@ -1164,8 +1188,8 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient( PROFILE -
- © 2026 TomTomGames.com, a division of TomTom Enterprises  ·  v +
@@ -1330,6 +1354,7 @@ function showPage(n) { document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active')); document.getElementById('page-'+n)?.classList.add('active'); document.querySelector(`.nav-btn[data-page="${n}"]`)?.classList.add('active'); + const _cr=document.getElementById('copyright-footer'); if(_cr) _cr.style.display=n==='home'?'block':'none'; if (n === 'buy') { updatePayForm(); initBuyPageBilling(); setTimeout(initSquare, 150); } if (n === 'broadcasts') { loadBroadcasts_player(); } if (n === 'cashout') { @@ -1501,7 +1526,7 @@ async function initSquare() { if (!window.Square) { console.warn('Square SDK not loaded — card unavailable'); document.getElementById('card-container').innerHTML = - '
Card form unavailable. Please refresh the page.
'; + '
Card form unavailable. Please refresh the page.
'; return; } try { @@ -1530,7 +1555,7 @@ async function initSquare() { } catch(e) { console.error('Square init error:', e); document.getElementById('card-container').innerHTML = - '
Card form error: ' + e.message + '
'; + '
Card form error: ' + e.message + '
'; } } @@ -1587,7 +1612,7 @@ function showPayResult(type, data) {
- +
`; } else if (type === 'manual') { wrap.innerHTML = ` @@ -1595,18 +1620,18 @@ function showPayResult(type, data) {
Request Submitted!
Tokens pending — awaiting payment confirmation
${data.tokens}${tokenCoin(36)}
-
will be added to your account once payment is received
+
will be added to your account once payment is received
Order #${data.purchase_id} received.

📲 Next steps:
1. Send $${data.amount} via ${data.method}
2. Include your order # #${data.purchase_id} in the memo
3. Tokens are credited within a few minutes after we confirm receipt

- ⚠️ Your token balance will not change until payment is verified by our team. + ⚠️ Your token balance will not change until payment is verified by our team.
- +
`; } else { wrap.innerHTML = ` @@ -1615,7 +1640,7 @@ function showPayResult(type, data) {
${data.error || 'Your payment could not be processed. Please check your card details and try again.'}
- +
`; } } @@ -1771,7 +1796,7 @@ async function loadBroadcasts_player() { const el = document.getElementById('broadcasts-list'); const d = await api('/api/broadcast.php?action=list'); if (!d.success || !d.broadcasts.length) { - el.innerHTML = '
📢
No announcements yet.
Check back later for news and updates.
'; + el.innerHTML = '
📢
No announcements yet.
Check back later for news and updates.
'; return; } el.innerHTML = d.broadcasts.map(b => { @@ -1782,25 +1807,25 @@ async function loadBroadcasts_player() {
${escHtml(b.subject)}
- ${unread?'NEW':''} + ${unread?'NEW':''}
From ${escHtml(b.sender_name)} · ${(b.sent_at||'').substring(0,16)}
${escHtml(b.message)}
- Ctrl+Enter to send -
@@ -1826,13 +1851,13 @@ async function toggleBcReplies(id) { async function loadBcReplies_player(id) { const el = document.getElementById('bc-rlist-'+id); - el.innerHTML = '
Loading...
'; + el.innerHTML = '
Loading...
'; const d = await api('/api/broadcast.php?action=replies&broadcast_id='+id); - if (!d.success||!d.replies.length) { el.innerHTML='
No replies yet — be the first!
'; return; } + if (!d.success||!d.replies.length) { el.innerHTML='
No replies yet — be the first!
'; return; } el.innerHTML = d.replies.map(r => { const isAdm = parseInt(r.is_admin); return `
-
${(r.username||'?').charAt(0).toUpperCase()}
+
${(r.username||'?').charAt(0).toUpperCase()}
${escHtml(r.username)}${isAdm?' 🔑':''} · ${(r.created_at||'').substring(11,16)}
${escHtml(r.message)}
@@ -1911,10 +1936,10 @@ async function loadCashoutPayoutMethods() { ${PAYOUT_ICONS[m.method_type]||'💰'}
-
${escHtml(m.label)}
-
${escHtml(m.account_handle)}
+
${escHtml(m.label)}
+
${escHtml(m.account_handle)}
- ${parseInt(m.is_default)?'DEFAULT':''} + ${parseInt(m.is_default)?'DEFAULT':''} `).join(''); } } @@ -1922,7 +1947,7 @@ async function loadCashoutPayoutMethods() { function renderPayoutMethodsList(el) { if (!el) return; if (!payoutMethods.length) { - el.innerHTML = '
No payout methods saved yet.
'; + el.innerHTML = '
No payout methods saved yet.
'; return; } el.innerHTML = payoutMethods.map(m => ` @@ -1930,13 +1955,13 @@ function renderPayoutMethodsList(el) { ${PAYOUT_ICONS[m.method_type]||'💰'}
${escHtml(m.label)} - ${parseInt(m.is_default)?'DEFAULT':''} + ${parseInt(m.is_default)?'DEFAULT':''}
-
${PAYOUT_LABELS[m.method_type]||m.method_type} · ${escHtml(m.account_handle)}
+
${PAYOUT_LABELS[m.method_type]||m.method_type} · ${escHtml(m.account_handle)}
- ${!parseInt(m.is_default)?``:''} - + ${!parseInt(m.is_default)?``:''} +
`).join(''); } @@ -2036,7 +2061,7 @@ async function obLoadPlatforms() { style="accent-color:var(--gold);width:16px;height:16px;flex-shrink:0">
${escHtml(p.name)} - `).join('') || '
No platforms available to request.
'; + `).join('') || '
No platforms available to request.
'; } function obTogglePlatform(id, checked, labelId) { @@ -2084,9 +2109,9 @@ function obSkip() { obDismiss(); } async function loadPlatformLogins() { const el = document.getElementById('platform-logins-list'); if (!el) return; - el.innerHTML = '
Loading...
'; + el.innerHTML = '
Loading...
'; const d = await api('/api/platform_accounts.php?action=my_accounts'); - if (!d.success) { el.innerHTML = '
Unable to load.
'; return; } + if (!d.success) { el.innerHTML = '
Unable to load.
'; return; } // Populate request dropdown - exclude already requested/approved const existing = new Set(d.accounts.filter(a=>['pending','approved'].includes(a.status)).map(a=>a.platform_slug)); @@ -2099,7 +2124,7 @@ async function loadPlatformLogins() { } if (!d.accounts.length) { - el.innerHTML = '
No platform account requests yet.
Use the form below to request one.
'; + el.innerHTML = '
No platform account requests yet.
Use the form below to request one.
'; return; } @@ -2120,22 +2145,22 @@ async function loadPlatformLogins() {
${escHtml(a.display_name||a.platform_slug)}
${si.label} - ${a.admin_note ? `
📝 ${escHtml(a.admin_note)}
` : ''} - ${si.msg ? `
${si.msg}
` : ''} + ${a.admin_note ? `
📝 ${escHtml(a.admin_note)}
` : ''} + ${si.msg ? `
${si.msg}
` : ''}
${canDelete ? `` : ''}
${showCreds ? `
-
🔑 YOUR LOGIN CREDENTIALS
-
+
🔑 YOUR LOGIN CREDENTIALS
+
Username: ${escHtml(a.provided_username)} Password: ${escHtml(a.provided_password)}
-
⚠️ Keep your credentials secure. Tap password to reveal.
- ${a.player_url ? `▶ Play ${escHtml(a.display_name)}` : ''} +
⚠️ Keep your credentials secure. Tap password to reveal.
+ ${a.player_url ? `▶ Play ${escHtml(a.display_name)}` : ''}
` : ''}
`; }).join(''); @@ -2170,12 +2195,25 @@ const _urlRef = new URLSearchParams(window.location.search).get('ref') || ''; async function loadReferralDashboard() { const d = await api('/api/referrals.php?action=status'); - if (!d.success) return; + if (!d.success) { + const inp = document.getElementById('referral-link-input'); + if (inp) inp.placeholder = d.error || 'Please log in to see your referral link'; + return; + } _refData = d; - // Referral link + // Referral link — build URL defensively const inp = document.getElementById('referral-link-input'); - if (inp) inp.value = d.referral_url || ''; + if (inp) { + const code = d.referral_code || ''; + const url = code + ? (d.referral_url || ('https://tomtomgames.com/?ref=' + code)) + : ''; + inp.value = url; + inp.placeholder = code ? '' : 'Generating your referral link...'; + // If no code yet, retry in 2 seconds + if (!code) setTimeout(loadReferralDashboard, 2000); + } // Stats setText('ref-count', d.verified_count || 0); @@ -2204,15 +2242,15 @@ async function loadReferralDashboard() { tiersEl.innerHTML = (tiers.tiers || []).map(t => `
-
${escHtml(t.name)} ${d.current_tier?.id==t.id?'★':''}
-
${escHtml(t.description||'')}
+
${escHtml(t.name)} ${d.current_tier?.id==t.id?'★':''}
+
${escHtml(t.description||'')}
-
+
${t.tokens_per_ref} tokens/ref
- ${t.bonus_tokens>0?'
+'+t.bonus_tokens+' bonus
':''} -
${t.min_referrals} referrals
+ ${t.bonus_tokens>0?'
+'+t.bonus_tokens+' bonus
':''} +
${t.min_referrals} referrals
-
`).join('') || '
No tiers configured.
'; +
`).join('') || '
No tiers configured.
'; } } @@ -2221,17 +2259,17 @@ async function loadReferralDashboard() { if (refListEl) { const refs = d.referrals || []; if (!refs.length) { - refListEl.innerHTML = '
No referrals yet. Share your link!
'; + refListEl.innerHTML = '
No referrals yet. Share your link!
'; } else { refListEl.innerHTML = refs.map(r => `
-
${escHtml(r.alias||r.username)}
-
${(r.created_at||'').substring(0,10)}
+
${escHtml(r.alias||r.username)}
+
${(r.created_at||'').substring(0,10)}
-
${r.status}
- ${r.tokens_awarded>0?'
+'+r.tokens_awarded+' tokens
':''} +
${r.status}
+ ${r.tokens_awarded>0?'
+'+r.tokens_awarded+' tokens
':''}
`).join(''); } @@ -2240,8 +2278,8 @@ async function loadReferralDashboard() { // Social shares const sharesList = document.getElementById('ref-shares-list'); if (sharesList && (d.social_shares||[]).length) { - sharesList.innerHTML = '
Submitted Shares:
' + - d.social_shares.map(s => `
${escHtml(s.platform)} — ${s.status}${s.status==='approved'?' (+'+s.bonus_tokens+' tokens)':''}
`).join(''); + sharesList.innerHTML = '
Submitted Shares:
' + + d.social_shares.map(s => `
${escHtml(s.platform)} — ${s.status}${s.status==='approved'?' (+'+s.bonus_tokens+' tokens)':''}
`).join(''); } } @@ -2272,23 +2310,23 @@ function getUrlRefCode() { return _urlRef; } async function loadPlatformLogins() { const el = document.getElementById('platform-logins-list'); if (!el) return; - el.innerHTML = '
'; + el.innerHTML = '
'; try { const d = await api('/api/platform_accounts.php?action=list'); if (!d.success || !d.accounts.length) { - el.innerHTML = '
No platform account requests yet. Request one below!
'; + el.innerHTML = '
No platform account requests yet. Request one below!
'; return; } el.innerHTML = d.accounts.map(a => buildLoginCard(a)).join(''); } catch(e) { - el.innerHTML = '
Unable to load. Try again.
'; + el.innerHTML = '
Unable to load. Try again.
'; } } function buildLoginCard(a) { const statusLabel = { pending:'⏳ Request Pending', approved:'✅ Account Ready', denied:'❌ Request Denied', deleted:'🗑 Deleted' }; const statusCls = { pending:'ac-pending', approved:'ac-completed', denied:'ac-failed', deleted:'ac-failed' }; - const note = a.admin_note ? `
📝 ${escHtml(a.admin_note)}
` : ''; + const note = a.admin_note ? `
📝 ${escHtml(a.admin_note)}
` : ''; let creds = ''; if (a.status === 'approved' && a.platform_username) { @@ -2297,7 +2335,7 @@ function buildLoginCard(a) { Password ${escHtml(a.platform_password)}
-
Tap password to reveal · Keep this safe!
` +
Tap password to reveal · Keep this safe!
` : ''; creds = `
Username${escHtml(a.platform_username)}
@@ -2306,17 +2344,17 @@ function buildLoginCard(a) { } const pendingMsg = a.status === 'pending' - ? '
Our team is setting up your account. Your login credentials will appear here once ready.
' + ? '
Our team is setting up your account. Your login credentials will appear here once ready.
' : ''; return `
${escHtml(a.platform_name || a.platform_slug)}
-
${(a.requested_at||'').substring(0,10)}
+
${(a.requested_at||'').substring(0,10)}
${statusLabel[a.status]||a.status}
- ${a.status === 'pending' ? '
Pending
review
' : ''} + ${a.status === 'pending' ? '
Pending
review
' : ''}
${note}${creds}${pendingMsg}
`; @@ -2471,7 +2509,7 @@ function buildPaymentMethods() { // If no methods at all, show message if (!cardEnabled && !manualMethods.length) { - container.innerHTML = '
No payment methods are currently available. Please contact support.
'; + container.innerHTML = '
No payment methods are currently available. Please contact support.
'; } // Update CFG.pay handles from DB @@ -2507,7 +2545,7 @@ async function loadGameAliases() { const platforms = platRes.success ? platRes.platforms : CFG.platforms; renderGameAliasList(platforms); } catch(e) { - if (el) el.innerHTML = '
Could not load games.
'; + if (el) el.innerHTML = '
Could not load games.
'; } } @@ -2515,7 +2553,7 @@ function renderGameAliasList(platforms) { const el = document.getElementById('game-aliases-list'); if (!el) return; if (!platforms || !platforms.length) { - el.innerHTML = '
No games configured yet.
'; + el.innerHTML = '
No games configured yet.
'; return; } el.innerHTML = platforms.map(p => ` @@ -2716,12 +2754,12 @@ async function loadCashoutHistory() { try { const d = await api('/api/cashout.php?action=list'); if (!d.success || !d.requests.length) { - el.innerHTML = '
No cashout requests yet. Submit one above.
'; + el.innerHTML = '
No cashout requests yet. Submit one above.
'; return; } el.innerHTML = d.requests.map(r => buildCashoutCard(r)).join(''); } catch(e) { - el.innerHTML = '
Unable to load requests.
'; + el.innerHTML = '
Unable to load requests.
'; } } @@ -2729,28 +2767,28 @@ function buildCashoutCard(r) { const editable = r.status === 'pending'; const locked = r.status === 'locked'; const statusLabel = { pending:'⏳ Pending', locked:'🔒 Sent to Admin', sent:'✅ Sent', approved:'✅ Sent', rejected:'❌ Denied', deleted:'🗑 Deleted' }; - const payout = r.payout_handle ? `
${escHtml(r.payout_handle)}
` : ''; - const adminNote= r.admin_note ? `
📝 ${escHtml(r.admin_note)}
` : ''; + const payout = r.payout_handle ? `
${escHtml(r.payout_handle)}
` : ''; + const adminNote= r.admin_note ? `
📝 ${escHtml(r.admin_note)}
` : ''; const actionBtns = editable ? `
+ style="width:90px;padding:7px 10px;font-size:15px" placeholder="Tokens"> + style="flex:1;min-width:100px;padding:7px 10px;font-size:15px" placeholder="Game alias">
` : ''; - const lockNote = locked ? `
🔒 This request has been submitted to our team. They will process it shortly.
` : ''; + const lockNote = locked ? `
🔒 This request has been submitted to our team. They will process it shortly.
` : ''; return `
@@ -2760,14 +2798,14 @@ function buildCashoutCard(r) { ${tokenCoin(18)} ${statusLabel[r.status]||r.status}
-
${escHtml(r.platform_name||r.platform_id)} · ${escHtml(r.alias||'')}
+
${escHtml(r.platform_name||r.platform_id)} · ${escHtml(r.alias||'')}
${payout} ${adminNote} ${lockNote}
-
${(r.created_at||'').substring(0,10)}
-
#${r.id}
+
${(r.created_at||'').substring(0,10)}
+
#${r.id}
${actionBtns} @@ -2835,7 +2873,7 @@ async function loadActivityDashboard() { if (fresh?.success) { activityData = fresh; renderActivitySummary(fresh, sumEl); renderActivityFeed(activityFilter); } }, 15000); } catch(e) { - feed.innerHTML = '
Unable to load activity.
'; + feed.innerHTML = '
Unable to load activity.
'; } } @@ -2869,7 +2907,7 @@ function renderActivityFeed(filter) { const items = buildActivityItems(activityData, filter); if (!items.length) { const labels = {all:'activity', purchases:'purchases', cashouts:'cashout requests', broadcasts:'announcements'}; - feed.innerHTML = `
No ${labels[filter]||filter} yet.
`; + feed.innerHTML = `
No ${labels[filter]||filter} yet.
`; return; } feed.innerHTML = items.map(renderActivityCard).join(''); @@ -2915,7 +2953,7 @@ const PMT_ICON = { card:'💳', venmo:'💙', cashapp:'💚', zelle:'💜', chim function renderActivityCard(item) { if (item._type === 'divider') { - return `
${item.label}
`; + return `
${item.label}
`; } if (item._type === 'purchase') { @@ -2924,7 +2962,7 @@ function renderActivityCard(item) { const icon = PMT_ICON[item.payment_method] || '💳'; const amt = item.amount_cents ? '$'+(item.amount_cents/100).toFixed(2) : ''; const card = item.card_last4 ? `${item.card_brand||'Card'} ····${item.card_last4}` : ''; - const note = item.admin_note ? `
📝 ${escHtml(item.admin_note)}
` : ''; + const note = item.admin_note ? `
📝 ${escHtml(item.admin_note)}
` : ''; return `
${icon}
@@ -2936,7 +2974,7 @@ function renderActivityCard(item) {
${amt}
-
${(item.created_at||'').substring(0,10)}
+
${(item.created_at||'').substring(0,10)}
`; } @@ -2952,7 +2990,7 @@ function renderActivityCard(item) { }; const statusClass = { pending:'ac-pending', locked:'ac-locked', sent:'ac-completed', approved:'ac-completed', rejected:'ac-failed', deleted:'ac-failed' }; const payout = item.payout_handle ? `
${escHtml(item.payout_handle)}
` : ''; - const note = item.admin_note ? `
📝 ${escHtml(item.admin_note)}
` : ''; + const note = item.admin_note ? `
📝 ${escHtml(item.admin_note)}
` : ''; const canEdit = item.status === 'pending'; return `
💸
@@ -2962,11 +3000,11 @@ function renderActivityCard(item) { ${payout} ${statusMap[item.status]||item.status} ${note} - ${canEdit?`
`:''} + ${canEdit?`
`:''}
-
${(item.created_at||'').substring(0,10)}
-
#${item.id}
+
${(item.created_at||'').substring(0,10)}
+
#${item.id}
`; } @@ -2978,12 +3016,12 @@ function renderActivityCard(item) {
${escHtml(item.subject)}
From ${escHtml(item.sender)} · ${(item.sent_at||'').substring(0,10)}
-
${escHtml(item.message)}
+
${escHtml(item.message)}
${isNew?'● New':'Read'} ${parseInt(item.replied)?'Replied':''}
- +
`; } @@ -3062,7 +3100,14 @@ async function resendVerify() { btn.innerHTML='Resend Verification Email'; btn.disabled=false; } -async function doLogout(){await fetch('/api/logout.php');S.user=null;showAuth();} +async function doLogout() { + try { await fetch('/api/logout.php'); } catch(e) {} + S.user = null; + S.pkg = null; + // Clear any cached state + if (window._refData) window._refData = null; + showAuth(); +} // ─── HELPERS ────────────────────────────────────────────── function showAlert(el,msg,type){el.textContent=msg;el.className='alert show alert-'+type;} diff --git a/public_html/phpcheck.php b/public_html/phpcheck.php deleted file mode 100644 index f97dab1..0000000 --- a/public_html/phpcheck.php +++ /dev/null @@ -1,25 +0,0 @@ -&1"); - echo $f . ": " . trim($out) . "\n"; -} - -// Also test DB connection directly -try { - require_once __DIR__ . '/../../includes/config.php'; - require_once __DIR__ . '/../../includes/db.php'; - $v = db()->query("SELECT COUNT(*) FROM users")->fetchColumn(); - echo "\nDB OK — users: $v\n"; - $v2 = db()->query("SELECT version FROM app_version ORDER BY id DESC LIMIT 1")->fetchColumn(); - echo "App version: $v2\n"; -} catch (Throwable $e) { - echo "\nDB ERROR: " . $e->getMessage() . "\n"; - echo "File: " . $e->getFile() . " line " . $e->getLine() . "\n"; -} diff --git a/public_html/test.php b/public_html/test.php deleted file mode 100644 index 00f0b67..0000000 --- a/public_html/test.php +++ /dev/null @@ -1,6 +0,0 @@ - PHP_VERSION, - 'time' => date('Y-m-d H:i:s'), - 'status' => 'ok' -]); diff --git a/public_html/test_login.php b/public_html/test_login.php deleted file mode 100644 index 08d99ca..0000000 --- a/public_html/test_login.php +++ /dev/null @@ -1,18 +0,0 @@ -$e->getMessage()])); } -ob_end_clean(); -header('Content-Type: application/json'); - -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $data = json_decode(file_get_contents('php://input'), true); - $result = loginUser($data['username'] ?? '', $data['password'] ?? ''); - if ($result['success']) unset($result['user']['password']); - echo json_encode($result); - exit; -} - -// GET - show users -$users = db()->query("SELECT id,username,alias,is_admin,email_verified,status FROM users")->fetchAll(); -echo json_encode(['users'=>$users,'session_id'=>session_id()]); diff --git a/public_html/test_mail.php b/public_html/test_mail.php deleted file mode 100644 index daaa45e..0000000 --- a/public_html/test_mail.php +++ /dev/null @@ -1,91 +0,0 @@ - - - - -Email Test - - - -

TomTomGames — Email Diagnostics

- -
-Server Info:
-PHP:
-Server:
-Hostname:
-mail() function:
-sendmail_path:
-SMTP: : -
- - -
-Send Result:
-To:
-mail() returned:
-Error:
- -
Check your inbox (and spam folder). If nothing arrives in 5 minutes, the server is not sending outbound mail. - -
mail() returned false — server cannot send email. You need to configure SMTP (PHPMailer) or enable sendmail. - -
- - -
-
- - - -
-
- -

Delete test_mail.php after diagnosis is complete.

- - diff --git a/push.bat b/push.bat index 5f94d44..4b50924 100644 --- a/push.bat +++ b/push.bat @@ -1,7 +1,6 @@ @echo off -@echo off -cd /d "C:\Users\myron\Downloads\tomgames" -set /p MSG="Commit message (e.g. v1.0.2 - what changed): " +cd /d "C:\Users\myron\Downloads\CyberPanel" +set /p MSG="Commit message (e.g. v1.0.4 - what changed): " if "%MSG%"=="" ( echo No message entered. Aborting. pause