⚠ 18+ ONLY — This platform involves real money and skill-based competition. Please play responsibly. Not available where prohibited by law.
🔥 Season 1 — EU Competitive Scrims

PAY.
PLAY.
WIN.

Europe's most competitive Fortnite scrim platform. Real stakes. Real prizes. No excuses.

Online
live
Active Matches
updating
Paid Out Today
↑ rising
Process

HOW IT WORKS

Four steps from zero to cash in your pocket.

01
🪙

Get Tokens

Purchase P2W Tokens via PayPal, bank transfer, or Revolut. 1 Token = €1. Use them for match entry fees.

02
⚔️

Join a Match

Browse open matches, pay the entry fee in tokens, and claim your spawn via our Discord server.

03
🎮

Compete

Get your private lobby code on Discord. Play your best. Every placement and kill counts toward your score.

04
💸

Withdraw Winnings

Winners receive tokens instantly. Withdraw to PayPal or bank account anytime. Minimum 10 tokens.

Modes

GAME MODES

🎯
Realistics
Full competitive play. Real rotations, real storm. Mid-game kills count.
Active
📦
Boxfights
Pure mechanics. Get in a box, fight your way out. Highest skill ceiling.
Active
🌀
Zonewars
Storm surge mastery. Position or die. The meta player's playground.
Active
⚔️
Scrims
50-team lobbies with spawn-claim. The P2W flagship format. Max prize pools.
Flagship
Trust

BUILT DIFFERENT

Instant Payouts

Token winnings are credited the moment results are confirmed. Withdrawals processed within 24 hours.

🛡️

Skill-Based Only

P2W is a skill-based competition platform. Outcomes are determined purely by game performance, not chance.

💬

Discord Verified

Every match result is verified by staff on Discord. Anti-cheat monitoring on all sessions.

Start Now

START EARNING NOW

Your next win is one lobby away. Join thousands of EU players already competing on P2W.

18+ only · By participating you agree to our Terms of Service

Live Now

MATCHES

Join, pay tokens, get Discord lobby code. 18+ only.

Loading...
Live
Loading matches...
Rankings

LEADERBOARD

Top EU earners this week. Resets every Monday 00:00 CET.

Reset in
Loading...
#
Player
Earned
Wins
Win Rate
Trend
Season 1

P2W
BATTLEPASS

Season ends June 30, 2025 ·

Your Progress
Enter your Discord name
Progression

REWARD TRACK

🥇 Coins — shop currency (cosmetics)  ·  🪙 Tokens — real money (€1 each, entry fees)  ·  Premium only

Tiers

CHOOSE YOUR PASS

Token Store

SHOP

Spend your tokens on cosmetics. Flex your status on your profile.

🥇 0 coins
Finance

WALLET

Your Wallet
🪙 Tokens (€1 each — for entry fees)
0.00
🥇 Coins (earned — for shop)
0
Locked: 0 · Available: 0 Tokens
🔞 Token purchases are for 18+ users only. By purchasing, you confirm you are of legal age in your country.
Payment Method

1 Token = €1 · Tokens are non-refundable once used in matches · 18+ only

?
Login to view profile
BP Level
🪙 Tokens
🥇 Coins
Match Wins
Tokens Earned
Date
Mode
Result
Earnings
Kills
No match history yet.
No transactions yet.
Equipped

MY COSMETICS

No cosmetics yet. Visit the Shop to get some.
Bank Details
Required for token withdrawals. Stored securely in your profile.
Configuration

SETTINGS

👤 Account
🔒 Privacy
🔔 Notifications
📡 Streaming
🎨 Appearance
🛡️ Security
⚖️ Legal

Account Information

Your linked Discord account. Name and avatar sync automatically.

Danger Zone

Irreversible actions. Proceed with caution.

Privacy Settings

Control what others can see on your public profile.

Public Profile

Allow anyone to view your profile, stats and match history

Show Earnings

Display your total earnings on the leaderboard

Show Win Rate

Display your win rate percentage publicly

Allow Friend Requests

Let other players send you friend requests on the platform

Notification Preferences

Choose what you're notified about. Discord DM notifications require Discord to be linked.

Match Starting

Get notified when a match you joined is about to start

Match Result

Notification when your match result is confirmed

Payout Confirmed

Alert when your withdrawal is processed

Battlepass Level Up

Celebrate each time you reach a new battlepass level

New Shop Items

Be first to know when new cosmetics drop in the shop

Promotional Emails

Special events, bonus token offers, and seasonal deals

Streamer Mode

Protect your identity while streaming. Enable this before going live to hide sensitive information.

Streamer Mode

Hides your real username, replaces it with "P2W Player" everywhere on screen

Hide Balance

Masks your token balance in the navbar and wallet page

Hide IBAN / Bank Info

Replaces bank details with *** when streamer mode is active

Blur Match Codes

Blurs lobby codes and invite links on screen

Clip Mode

Optimised display settings for stream recording and clipping.

High Contrast UI

Boost UI contrast ratios for better visibility on stream overlays

Reduce Animations

Disable particle effects and transitions to improve encoding performance

Interface

Customise how the P2W platform looks for you.

Compact Mode

Reduce card sizes and spacing for a denser information layout

Animate Statistics

Number counters animate when pages load

Show 18+ Banner

Display the age verification reminder at the top of the page

Language

Platform language. More languages coming soon.

Security

Keep your account and winnings safe.

Two-Factor Authentication

Require Discord authentication for all withdrawals over 50 tokens

Withdrawal Confirmation

Send a confirmation email before processing any withdrawal

Login Alerts

Get notified via Discord when your account is accessed from a new device

Active Sessions

Manage where you're logged in.

Current Browser
Active now
Current
🔐
Admin Panel

P2W Control Center. Staff only.

Demo: admin123
Control

ADMIN DASHBOARD

PENDING PURCHASES
Approve after verifying payment in Discord #deposits
No pending purchases

Live Matches

Loading...

Recent Payouts

Loading...
BATTLEPASS CONTROL
BP Players
Premium
Avg Level

BP Players

Discord Level XP Pass Actions
Loading...

XP Event Log

No events yet
DATABASE VIEWER
Open JSONBin →

Live Database (JSONBin)

Bin: ${CFG.BIN_ID}
Click Refresh to load current database state
⚙️ SETUP CHECKLIST
1 JSONBin API Key DONE
Bin ID ${CFG.BIN_ID} is set.
API key is active. View your bin →
2 Discord Webhook SETUP NEEDED
To get notified in Discord when someone requests a token purchase:
  1. Open your Discord server
  2. Go to a channel like #deposits
  3. Channel Settings → Integrations → Webhooks → New Webhook
  4. Copy the webhook URL
  5. Open this HTML file and find DISCORD_WEBHOOK:
  6. Replace P2W — Pay. Play. Win.
    ⚠ 18+ ONLY — This platform involves real money and skill-based competition. Please play responsibly. Not available where prohibited by law.
    🔥 Season 1 — EU Competitive Scrims

    PAY.
    PLAY.
    WIN.

    Europe's most competitive Fortnite scrim platform. Real stakes. Real prizes. No excuses.

    Online
    live
    Active Matches
    updating
    Paid Out Today
    ↑ rising
    Process

    HOW IT WORKS

    Four steps from zero to cash in your pocket.

    01
    🪙

    Get Tokens

    Purchase P2W Tokens via PayPal, bank transfer, or Revolut. 1 Token = €1. Use them for match entry fees.

    02
    ⚔️

    Join a Match

    Browse open matches, pay the entry fee in tokens, and claim your spawn via our Discord server.

    03
    🎮

    Compete

    Get your private lobby code on Discord. Play your best. Every placement and kill counts toward your score.

    04
    💸

    Withdraw Winnings

    Winners receive tokens instantly. Withdraw to PayPal or bank account anytime. Minimum 10 tokens.

    Modes

    GAME MODES

    🎯
    Realistics
    Full competitive play. Real rotations, real storm. Mid-game kills count.
    Active
    📦
    Boxfights
    Pure mechanics. Get in a box, fight your way out. Highest skill ceiling.
    Active
    🌀
    Zonewars
    Storm surge mastery. Position or die. The meta player's playground.
    Active
    ⚔️
    Scrims
    50-team lobbies with spawn-claim. The P2W flagship format. Max prize pools.
    Flagship
    Trust

    BUILT DIFFERENT

    Instant Payouts

    Token winnings are credited the moment results are confirmed. Withdrawals processed within 24 hours.

    🛡️

    Skill-Based Only

    P2W is a skill-based competition platform. Outcomes are determined purely by game performance, not chance.

    💬

    Discord Verified

    Every match result is verified by staff on Discord. Anti-cheat monitoring on all sessions.

    Start Now

    START EARNING NOW

    Your next win is one lobby away. Join thousands of EU players already competing on P2W.

    18+ only · By participating you agree to our Terms of Service

    P2W
    EU competitive Fortnite scrim platform. 18+ skill-based competition. Not affiliated with Epic Games.
    Platform
    Matches Leaderboard Battlepass Shop
    Legal
    Terms of Service Privacy Policy Refund Policy 18+ Only
    © 2025 P2W · All rights reserved · Not affiliated with Epic Games Inc.
    🔞 18+ · Play responsibly · Skill-based competition only
    Live Now

    MATCHES

    Join, pay tokens, get Discord lobby code. 18+ only.

    Loading...
    Live
    Loading matches...
    Rankings

    LEADERBOARD

    Top EU earners this week. Resets every Monday 00:00 CET.

    Reset in
    Loading...
    #
    Player
    Earned
    Wins
    Win Rate
    Trend
    Season 1

    P2W
    BATTLEPASS

    Season ends June 30, 2025 ·

    Your Progress
    Enter your Discord name
    Progression

    REWARD TRACK

    🥇 Coins — shop currency (cosmetics)  ·  🪙 Tokens — real money (€1 each, entry fees)  ·  Premium only

    Tiers

    CHOOSE YOUR PASS

    Token Store

    SHOP

    Spend your tokens on cosmetics. Flex your status on your profile.

    🥇 0 coins
    Finance

    WALLET

    Your Wallet
    🪙 Tokens (€1 each — for entry fees)
    0.00
    🥇 Coins (earned — for shop)
    0
    Locked: 0 · Available: 0 Tokens
    🔞 Token purchases are for 18+ users only. By purchasing, you confirm you are of legal age in your country.
    Payment Method

    1 Token = €1 · Tokens are non-refundable once used in matches · 18+ only

    ?
    Login to view profile
    BP Level
    🪙 Tokens
    🥇 Coins
    Match Wins
    Tokens Earned
    Date
    Mode
    Result
    Earnings
    Kills
    No match history yet.
    No transactions yet.
    Equipped

    MY COSMETICS

    No cosmetics yet. Visit the Shop to get some.
    Bank Details
    Required for token withdrawals. Stored securely in your profile.
    Configuration

    SETTINGS

    👤 Account
    🔒 Privacy
    🔔 Notifications
    📡 Streaming
    🎨 Appearance
    🛡️ Security
    ⚖️ Legal

    Account Information

    Your linked Discord account. Name and avatar sync automatically.

    Danger Zone

    Irreversible actions. Proceed with caution.

    Privacy Settings

    Control what others can see on your public profile.

    Public Profile

    Allow anyone to view your profile, stats and match history

    Show Earnings

    Display your total earnings on the leaderboard

    Show Win Rate

    Display your win rate percentage publicly

    Allow Friend Requests

    Let other players send you friend requests on the platform

    Notification Preferences

    Choose what you're notified about. Discord DM notifications require Discord to be linked.

    Match Starting

    Get notified when a match you joined is about to start

    Match Result

    Notification when your match result is confirmed

    Payout Confirmed

    Alert when your withdrawal is processed

    Battlepass Level Up

    Celebrate each time you reach a new battlepass level

    New Shop Items

    Be first to know when new cosmetics drop in the shop

    Promotional Emails

    Special events, bonus token offers, and seasonal deals

    Streamer Mode

    Protect your identity while streaming. Enable this before going live to hide sensitive information.

    Streamer Mode

    Hides your real username, replaces it with "P2W Player" everywhere on screen

    Hide Balance

    Masks your token balance in the navbar and wallet page

    Hide IBAN / Bank Info

    Replaces bank details with *** when streamer mode is active

    Blur Match Codes

    Blurs lobby codes and invite links on screen

    Clip Mode

    Optimised display settings for stream recording and clipping.

    High Contrast UI

    Boost UI contrast ratios for better visibility on stream overlays

    Reduce Animations

    Disable particle effects and transitions to improve encoding performance

    Interface

    Customise how the P2W platform looks for you.

    Compact Mode

    Reduce card sizes and spacing for a denser information layout

    Animate Statistics

    Number counters animate when pages load

    Show 18+ Banner

    Display the age verification reminder at the top of the page

    Language

    Platform language. More languages coming soon.

    Security

    Keep your account and winnings safe.

    Two-Factor Authentication

    Require Discord authentication for all withdrawals over 50 tokens

    Withdrawal Confirmation

    Send a confirmation email before processing any withdrawal

    Login Alerts

    Get notified via Discord when your account is accessed from a new device

    Active Sessions

    Manage where you're logged in.

    Current Browser
    Active now
    Current
    🔐
    Admin Panel

    P2W Control Center. Staff only.

    Demo: admin123
    Control

    ADMIN DASHBOARD

    PENDING PURCHASES
    Approve after verifying payment in Discord #deposits
    No pending purchases

    Live Matches

    Loading...

    Recent Payouts

    Loading...
    BATTLEPASS CONTROL
    BP Players
    Premium
    Avg Level

    BP Players

    Discord Level XP Pass Actions
    Loading...

    XP Event Log

    No events yet
    DATABASE VIEWER
    Open JSONBin →

    Live Database (JSONBin)

    Bin: ${CFG.BIN_ID}
    Click Refresh to load current database state
    ⚙️ SETUP CHECKLIST
    1 JSONBin API Key DONE
    Bin ID ${CFG.BIN_ID} is set.
    API key is active. View your bin →
    2 Discord Webhook SETUP NEEDED
    To get notified in Discord when someone requests a token purchase:
    1. Open your Discord server
    2. Go to a channel like #deposits
    3. Channel Settings → Integrations → Webhooks → New Webhook
    4. Copy the webhook URL
    5. Open this HTML file and find DISCORD_WEBHOOK:
    6. Replace JOUW_WEBHOOK_URL with your webhook URL
    3 Payment Details UPDATE NEEDED
    Find CFG.PAY in the code and replace:
    paypal.link: 'https://paypal.me/JOUW_NAAM'
    bank.iban: 'BE00 0000 0000 0000'
    bank.name: 'Jouw Naam'
    revolut.link:'https://revolut.me/JOUW_TAG'
    4 Admin Password CHANGE THIS!
    Find ADMIN_PW: 'admin123' and replace with a strong password. Anyone with this password can approve purchases and manage players.
    5 Database Structure (JSONBin)
    Your JSONBin should have this structure. If it doesn't exist yet, the site will create it automatically on first battlepass lookup.
    {
      "battlepass": {
        "players": {
          "discordname": {
            "discord": "Name",
            "xp": 0,
            "premium": false,
            "wins": 0,
            "coins": 0,
            "claimedRewards": []
          }
        },
        "xpLog": [],
        "season": 1
      }
    }
    with your webhook URL
3 Payment Details UPDATE NEEDED
Find CFG.PAY in the code and replace:
paypal.link: 'https://paypal.me/JOUW_NAAM'
bank.iban: 'BE00 0000 0000 0000'
bank.name: 'Jouw Naam'
revolut.link:'https://revolut.me/JOUW_TAG'
4 Admin Password CHANGE THIS!
Find ADMIN_PW: 'admin123' and replace with a strong password. Anyone with this password can approve purchases and manage players.
5 Database Structure (JSONBin)
Your JSONBin should have this structure. If it doesn't exist yet, the site will create it automatically on first battlepass lookup.
{
  "battlepass": {
    "players": {
      "discordname": {
        "discord": "Name",
        "xp": 0,
        "premium": false,
        "wins": 0,
        "coins": 0,
        "claimedRewards": []
      }
    },
    "xpLog": [],
    "season": 1
  }
}
', // ─── Season ─────────────────────────────────────────────────────── SEASON_END: new Date('2025-06-30T23:59:59'), // ─── BP ─────────────────────────────────────────────────────────── XP_PER_LEVEL: 1000, MAX_LEVEL: 100, // ─── Economy ────────────────────────────────────────────────────── // TOKENS = real money (€1 = 1 Token). Used for match entry fees. // COINS = earned/rewarded currency. Used in the shop for cosmetics. HOUSE_CUT: 0.10, WITHDRAW_FEE_PCT: 0.05, WITHDRAW_FEE_FLAT: 1, // ─── Your Payment Details ───────────────────────────────────────── PAY: { paypal: { link: 'https://paypal.me/JOUW_NAAM' }, bank: { iban: 'BE00 0000 0000 0000', name: 'Jouw Naam', bic: 'GEBABEBB' }, revolut: { link: 'https://revolut.me/JOUW_TAG' }, }, }; // ═══════════════════════════════════════════════════ // STATE // ═══════════════════════════════════════════════════ const S = { user: null, token: localStorage.getItem('p2w_token'), // TOKENS = real money (€1 each). Used for match entry fees. tokens: parseFloat(localStorage.getItem('p2w_tokens') || '0'), // COINS = earned/rewarded. Used in the shop for cosmetics. coins: parseFloat(localStorage.getItem('p2w_coins') || '0'), socket: null, matches: [], leaderboard: [], battlepass: null, currentMatch: null, payMethod: 'paypal', bpPayMethod: 'paypal', tokPayMethod: 'paypal', shopFilter: 'all', currentShopItem: null, // Pending token purchase requests (waiting for admin approval) pendingPurchases: JSON.parse(localStorage.getItem('p2w_pending') || '[]'), settings: JSON.parse(localStorage.getItem('p2w_settings') || '{}'), bankDetails: JSON.parse(localStorage.getItem('p2w_bank') || '{}'), transactions: JSON.parse(localStorage.getItem('p2w_tx') || '[]'), matchHistory: JSON.parse(localStorage.getItem('p2w_mh') || '[]'), ownedCosmetics: JSON.parse(localStorage.getItem('p2w_cosmetics') || '[]'), equippedCosmetics: JSON.parse(localStorage.getItem('p2w_equipped') || '{}'), streamerMode: false, filterMode: 'all', }; // ═══════════════════════════════════════════════════ // JSONBIN // ═══════════════════════════════════════════════════ const BIN_URL = `https://api.jsonbin.io/v3/b/${CFG.BIN_ID}`; async function binGet() { const r = await fetch(BIN_URL + '/latest', { headers: { 'X-Master-Key': CFG.API_KEY } }); if (!r.ok) throw new Error('JSONBin read failed (' + r.status + ')'); return (await r.json()).record; } async function binPut(data) { const r = await fetch(BIN_URL, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Master-Key': CFG.API_KEY }, body: JSON.stringify(data), }); if (!r.ok) throw new Error('JSONBin write failed (' + r.status + ')'); return r.json(); } async function getBPData() { const d = await binGet(); if (!d.battlepass) { await binPut({ ...d, battlepass: { players: {}, xpLog: [], season: 1 } }); return { players: {}, xpLog: [], season: 1 }; } return d.battlepass; } async function saveBPData(bp) { const d = await binGet(); d.battlepass = bp; await binPut(d); } // ═══════════════════════════════════════════════════ // REWARD TRACK // ═══════════════════════════════════════════════════ function buildBPTrack() { // COINS = shop currency (earned via battlepass) // TOKENS = real money (€1 each), only rare milestone rewards const t = []; for (let i = 1; i <= CFG.MAX_LEVEL; i++) { // Default: coins reward (not real money) const coinAmt = i<=25?5:i<=50?10:i<=75?15:20; let r = { level:i, icon:'🥇', label:`${coinAmt} Coins`, type:'coins', premium:false }; if (i===5) r={level:i,icon:'⚡',label:'XP Boost', type:'xp', premium:false}; if (i===10) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // moved from higher if (i===15) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // replaced -10% entry if (i===20) r={level:i,icon:'🏅',label:'Free Badge', type:'badge', premium:false}; if (i===25) r={level:i,icon:'💎',label:'Premium Role', type:'role', premium:true}; if (i===30) r={level:i,icon:'🥇',label:'30 Coins', type:'coins', premium:true}; if (i===40) r={level:i,icon:'⚡',label:'XP Boost ×2', type:'xp', premium:false}; if (i===50) r={level:i,icon:'🥇',label:'50 Coins', type:'coins', premium:true}; if (i===55) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:false}; // real €1 if (i===60) r={level:i,icon:'🥇',label:'75 Coins', type:'coins', premium:true}; if (i===75) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:true}; // replaced -20% entry if (i===100)r={level:i,icon:'👑',label:'5 Tokens', type:'token', premium:true}; // real €5 t.push(r); } return t; } const BP_TRACK = buildBPTrack(); function bpLevel(xp){ return Math.min(Math.floor(xp/CFG.XP_PER_LEVEL)+1, CFG.MAX_LEVEL); } function bpPct(xp) { return Math.floor(((xp%CFG.XP_PER_LEVEL)/CFG.XP_PER_LEVEL)*100); } function bpNorm(n) { return n.trim().toLowerCase(); } function avatarColor(n){ let h=0; for(const c of n)h=c.charCodeAt(0)+((h<<5)-h); return `hsl(${Math.abs(h)%360},55%,35%)`; } function initials(n){ return n.slice(0,2).toUpperCase(); } // ═══════════════════════════════════════════════════ // NAV // ═══════════════════════════════════════════════════ function goPage(p) { document.querySelectorAll('.page').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-link,.drawer-link').forEach(el => el.classList.remove('active')); document.getElementById('page-' + p)?.classList.add('active'); document.querySelectorAll(`[data-page="${p}"]`).forEach(el => el.classList.add('active')); window.scrollTo(0, 0); if (p === 'matches') loadMatches(); if (p === 'lb') loadLB(); if (p === 'bp') loadBPPage(); if (p === 'shop') loadShop(); if (p === 'wallet') loadWallet(); if (p === 'profile') loadProfile(); if (p === 'admin') renderAdminGate(); updateNavTokens(); } function toggleDrawer() { const d = document.getElementById('nav-drawer'); const b = document.getElementById('hamburger'); d.classList.toggle('open'); b.classList.toggle('open'); } function closeDrawer() { document.getElementById('nav-drawer').classList.remove('open'); document.getElementById('hamburger').classList.remove('open'); } // ═══════════════════════════════════════════════════ // AUTH (simulated - Discord OAuth needs backend) // ═══════════════════════════════════════════════════ function loginWithDiscord() { // Simulated login for demo const name = prompt('Enter your Discord username to continue (demo):'); if (!name) return; S.user = { username: name, discordId: 'demo_' + Date.now(), avatar: null }; localStorage.setItem('p2w_user', JSON.stringify(S.user)); updateNavUser(); toast('✓ Welcome to P2W, ' + name + '!', 'ok'); } function updateNavUser() { if (!S.user) return; document.getElementById('login-btn').style.display = 'none'; document.getElementById('nav-avatar-btn').style.display = 'flex'; document.getElementById('nav-bal').style.display = 'flex'; const init = initials(S.streamerMode ? 'P2W Player' : S.user.username); document.getElementById('nav-av-initials').textContent = init; updateNavTokens(); } function updateNavTokens() { const display = document.getElementById('nav-tokens'); if (display) { if (S.streamerMode && S.settings['stream-hide-bal'] !== false) { display.innerHTML = '****'; } else { display.innerHTML = `🪙${S.tokens.toFixed(0)} | 🥇${S.coins.toFixed(0)}`; } } // Shop coins display const sd = document.getElementById('shop-coins-display'); if (sd) sd.textContent = S.coins.toFixed(0); // Wallet const wb = document.getElementById('wallet-bal-display'); if (wb) wb.textContent = S.tokens.toFixed(2); const cbd = document.getElementById('wallet-coins-display'); if (cbd) cbd.textContent = S.coins.toFixed(0); } // ═══════════════════════════════════════════════════ // LIVE STATS // ═══════════════════════════════════════════════════ let statsOnline = 247, statsPayout = 284; function tickStats() { statsOnline += Math.floor(Math.random() * 5) - 2; if (statsOnline < 100) statsOnline = 100; statsPayout += Math.floor(Math.random() * 3); const activeMatches = S.matches.filter(m => m.status === 'live').length; ['s-online','s-active','s-payouts'].forEach((id, i) => { const el = document.getElementById(id); if (!el) return; if (i === 0) el.textContent = statsOnline; if (i === 1) el.textContent = activeMatches; if (i === 2) el.textContent = '€' + statsPayout; }); const mc = document.getElementById('match-count'); if (mc) mc.textContent = `${activeMatches} live · ${S.matches.filter(m=>m.status==='waiting').length} open`; } setInterval(tickStats, 4000); tickStats(); // ═══════════════════════════════════════════════════ // SEASON COUNTDOWN // ═══════════════════════════════════════════════════ function updateCountdowns() { // Battlepass const bc = document.getElementById('bp-countdown'); if (bc) { const diff = CFG.SEASON_END - new Date(); if (diff > 0) { const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000); bc.textContent = `${d}d ${h}h remaining`; } } // Leaderboard const lc = document.getElementById('lb-countdown'); if (lc) { const now = new Date(), next = new Date(now); next.setDate(now.getDate() + ((8 - now.getDay()) % 7 || 7)); next.setHours(0,0,0,0); const diff = next - now; const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000), m = Math.floor((diff%3600000)/60000); lc.textContent = `${d}d ${h}h ${m}m`; } } setInterval(updateCountdowns, 60000); updateCountdowns(); // ═══════════════════════════════════════════════════ // MATCHES // ═══════════════════════════════════════════════════ const MODES = { realistics:'Realistics', boxfights:'Boxfights', scrims:'Scrims', zonewars:'Zonewars' }; const ICONS = { realistics:'🎯', boxfights:'📦', scrims:'⚔️', zonewars:'🌀' }; S.matches = [ { id:1, mode:'realistics', title:'EU Realistics #14', entryFee:5, players:7, maxPlayers:10, prize:45, status:'waiting' }, { id:2, mode:'realistics', title:'EU Realistics #13', entryFee:10, players:10, maxPlayers:10, prize:90, status:'live' }, { id:3, mode:'boxfights', title:'Boxfight Cup #7', entryFee:2, players:6, maxPlayers:16, prize:28, status:'waiting' }, { id:4, mode:'scrims', title:'EU Scrims #5', entryFee:10, players:80, maxPlayers:100, prize:800, status:'live' }, { id:5, mode:'boxfights', title:'Boxfight Ladder', entryFee:5, players:3, maxPlayers:8, prize:35, status:'waiting' }, { id:6, mode:'scrims', title:'EU Scrims #6', entryFee:5, players:22, maxPlayers:100, prize:200, status:'waiting' }, { id:7, mode:'zonewars', title:'Zonewars Alpha', entryFee:2, players:4, maxPlayers:6, prize:10, status:'waiting' }, ]; function loadMatches() { renderMatches(); } function filterMatch(mode, btn) { S.filterMode = mode; document.querySelectorAll('.fb').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderMatches(); } function renderMatches() { const grid = document.getElementById('matches-grid'); if (!grid) return; const list = S.filterMode === 'all' ? S.matches : S.matches.filter(m => m.mode === S.filterMode); if (!list.length) { grid.innerHTML = `
No matches in this mode right now.
`; return; } grid.innerHTML = list.map(m => { const pct = Math.round((m.players / m.maxPlayers) * 100); const full = m.players >= m.maxPlayers; const live = m.status === 'live'; const cut = Math.round(m.entryFee * m.maxPlayers * CFG.HOUSE_CUT); return `
${ICONS[m.mode]} ${MODES[m.mode]} ${live ? ` LIVE` : full ? `FULL` : `● OPEN`}
${m.title}
Hosted by P2W Staff
🪙${m.entryFee}
Entry
🪙${m.prize}
Prize
${m.players}/${m.maxPlayers}
Players
${m.players} / ${m.maxPlayers} players${pct}%
${full || live ? `` : ``}
House cut: 🪙${cut} · Net prize: 🪙${m.prize - cut}
`; }).join(''); } function joinMatch(id, mode, title, fee) { S.currentMatch = { id, mode, title, fee }; document.getElementById('m-mode').textContent = MODES[mode] || mode; document.getElementById('m-title').textContent = title; document.getElementById('m-price').textContent = '🪙' + fee; document.getElementById('f-duo-wrap').style.display = mode === 'scrims' ? 'block' : 'none'; document.getElementById('f-discord').value = S.user?.username || ''; const balEl = document.getElementById('join-balance-display'); if (balEl) balEl.textContent = `🪙 ${S.tokens.toFixed(0)} tokens`; // Warn if not enough const btn = document.getElementById('pay-btn'); if (S.tokens < fee) { if (btn) { btn.textContent = '⚠ Not enough tokens — Buy more'; btn.style.background = 'rgba(239,68,68,.15)'; btn.style.border = '1px solid rgba(239,68,68,.3)'; btn.style.color = 'var(--red)'; } } else { if (btn) { btn.textContent = '🪙 Pay & Join →'; btn.style.background = ''; btn.style.border = ''; btn.style.color = ''; } } document.getElementById('pay-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // LEADERBOARD // ═══════════════════════════════════════════════════ const SAMPLE_LB = [ {rank:1,name:'NexusEU', earn:312,wins:61,wr:81,ch:'+2',up:true}, {rank:2,name:'ZenithKO', earn:147,wins:38,wr:74,ch:'—', up:null}, {rank:3,name:'FrostRapide',earn:98,wins:27,wr:68,ch:'-1',up:false}, {rank:4,name:'ShadowLoop', earn:76,wins:21,wr:65,ch:'+3',up:true}, {rank:5,name:'KrypticFN', earn:62,wins:19,wr:59,ch:'-2',up:false}, {rank:6,name:'BlitzRunner',earn:55,wins:15,wr:62,ch:'+1',up:true}, {rank:7,name:'VoidCraft', earn:44,wins:12,wr:57,ch:'—', up:null}, {rank:8,name:'ApexRoll', earn:37,wins:10,wr:54,ch:'+4',up:true}, {rank:9,name:'DriftKing', earn:29,wins:8, wr:50,ch:'-1',up:false}, {rank:10,name:'TempestEU', earn:18,wins:5, wr:45,ch:'+2',up:true}, ]; function loadLB() { const pod = document.getElementById('lb-podium'); const [s1,s2,s3] = [SAMPLE_LB[1], SAMPLE_LB[0], SAMPLE_LB[2]]; const crowns = ['🥈','👑','🥉']; const podData = [s1,s2,s3]; const cls = ['','p1','']; if (pod) pod.innerHTML = podData.map((p,i) => { const hue = (p.rank*47)%360; return `
${crowns[i]}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}W · ${p.wr}% WR
`; }).join(''); document.getElementById('lb-rows').innerHTML = SAMPLE_LB.map(p => { const hue = (p.rank*47)%360; return `
${p.rank<=3?'★':p.rank}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}
${p.wr}%
${p.up===true?'▲':p.up===false?'▼':'─'} ${p.ch}
`; }).join(''); } // ═══════════════════════════════════════════════════ // BATTLEPASS // ═══════════════════════════════════════════════════ let bpCurrentPlayer = null, bpCurrentKey = null; function loadBPPage() { renderBPPublicTrack(); renderPasses(); if (bpCurrentPlayer) renderBPCard(bpCurrentPlayer); } function renderBPPublicTrack() { const el = document.getElementById('bp-public-track'); if (!el) return; const milestones = BP_TRACK.filter(r => r.level % 5 === 0 || r.level === 1); el.innerHTML = milestones.map(r => { const isToken = r.type === 'token'; const borderC = isToken ? 'rgba(240,180,41,.35)' : r.premium ? 'rgba(168,85,247,.2)' : 'var(--border)'; const glowC = isToken ? 'rgba(240,180,41,.08)' : 'transparent'; return `
${isToken ? '
' : ''} ${r.premium && !isToken ? '
PRO
' : ''}
${r.icon}
Lv ${r.level}
${r.label}
${isToken ? '
real money
' : r.type==='coins' ? '
shop coins
' : ''}
`; }).join(''); } async function bpLookup() { const raw = document.getElementById('bp-inp').value.trim(); if (!raw) { toast('Enter your Discord name.', 'err'); return; } const btn = document.getElementById('bp-lookup-btn'); btn.disabled = true; btn.innerHTML = '
'; try { const data = await getBPData(); const key = bpNorm(raw); let player = data.players[key]; if (!player) { player = { discord:raw, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; data.players[key] = player; await saveBPData(data); toast('✓ Battlepass profile created!', 'ok'); } bpCurrentPlayer = player; bpCurrentKey = key; renderBPCard(player); } catch(e) { toast('Error: ' + e.message, 'err'); } btn.disabled = false; btn.textContent = 'Look Up →'; } function bpReset() { document.getElementById('bp-lookup-box').style.display = 'block'; document.getElementById('bp-loaded-box').style.display = 'none'; document.getElementById('bp-claimable-section').style.display = 'none'; document.getElementById('bp-inp').value = ''; bpCurrentPlayer = null; bpCurrentKey = null; } function renderBPCard(p) { const lv = bpLevel(p.xp), pct = bpPct(p.xp), prog = p.xp % CFG.XP_PER_LEVEL; const isPrem = p.premium, claimed = new Set(p.claimedRewards || []); const claimable = BP_TRACK.filter(r => r.level <= lv && (!r.premium || isPrem) && !claimed.has(r.level)); document.getElementById('bp-lookup-box').style.display = 'none'; document.getElementById('bp-loaded-box').style.display = 'block'; const av = document.getElementById('bp-avatar'); av.textContent = initials(p.discord); av.style.background = avatarColor(p.discord); av.style.borderRadius = '50%'; av.style.width = '36px'; av.style.height = '36px'; av.style.display = 'flex'; av.style.alignItems = 'center'; av.style.justifyContent = 'center'; const displayName = S.streamerMode ? 'P2W Player' : p.discord; document.getElementById('bp-username').textContent = displayName; document.getElementById('bp-pass-badge').innerHTML = isPrem ? `⭐ Premium` : `Free`; document.getElementById('bp-level').textContent = lv; document.getElementById('bp-level-label').textContent = `Level ${lv} / 100`; document.getElementById('bp-xp-label').textContent = `${prog.toLocaleString('en')} / 1000 XP`; setTimeout(() => { const f = document.getElementById('bp-xp-fill'); if(f) f.style.width = pct + '%'; }, 80); const hint = document.getElementById('bp-claim-hint'); if (hint) hint.style.display = claimable.length > 0 ? 'block' : 'none'; if (claimable.length > 0) { document.getElementById('bp-claimable-section').style.display = 'block'; document.getElementById('bp-claimable-list').innerHTML = claimable.map(r => `
${r.icon}
Lv ${r.level} — ${r.label}
Contact staff in Discord to claim
`).join(''); } } function renderPasses() { const el = document.getElementById('passes-grid'); if (!el) return; el.innerHTML = `
FREE PASS
For everyone
FREE
🪙
50 tokens across 100 levels
XP boosts at milestones
📊
Full stats tracking
🏅
Free badge on Discord
⭐ PREMIUM
Season 1 — €10 one-time
€10
💰
€15+ value in tokens & credits
🎟
Up to 20% off match entry fees
🥇
Priority queue access
📈
+10% earnings boost on wins
💎
Exclusive Discord role + gold name
`; } function purchaseBP() { document.getElementById('bp-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // SHOP // ═══════════════════════════════════════════════════ const SHOP_ITEMS = [ // Badges { id:'b1', cat:'badge', icon:'💀', name:'Death Dealer', desc:'For those who eliminate first.', rarity:'rare', price:20, bgColor:'#1a0f0f' }, { id:'b2', cat:'badge', icon:'👑', name:'Champion Crown', desc:'Earned by top seasonal players.', rarity:'legendary', price:150, bgColor:'#1a1500' }, { id:'b3', cat:'badge', icon:'🔥', name:'On Fire', desc:'You\'re unstoppable right now.', rarity:'epic', price:75, bgColor:'#1a0d00' }, { id:'b4', cat:'badge', icon:'⚡', name:'Speed Demon', desc:'Quick entries, quick exits.', rarity:'rare', price:30, bgColor:'#0d0f1a' }, { id:'b5', cat:'badge', icon:'🎯', name:'Sharpshooter', desc:'Precision over brute force.', rarity:'common', price:10, bgColor:'#0f1a0f' }, // Borders { id:'bo1',cat:'border',icon:'🟦', name:'Blue Holo', desc:'Holographic blue profile border.', rarity:'rare', price:40, bgColor:'#0d1020' }, { id:'bo2',cat:'border',icon:'🟨', name:'Gold Crown', desc:'Legendary golden border frame.', rarity:'legendary', price:200, bgColor:'#1a1200' }, { id:'bo3',cat:'border',icon:'🟣', name:'Purple Haze', desc:'Epic purple animated border.', rarity:'epic', price:80, bgColor:'#110d1a' }, // Titles { id:'t1', cat:'title', icon:'📛', name:'The Untouchable', desc:'Show them you\'re unreachable.', rarity:'epic', price:60, bgColor:'#0d1015' }, { id:'t2', cat:'title', icon:'📛', name:'Cash King', desc:'Earnings speak louder than words.',rarity:'legendary', price:120, bgColor:'#1a1000' }, { id:'t3', cat:'title', icon:'📛', name:'Grinder', desc:'No days off. No excuses.', rarity:'common', price:15, bgColor:'#0f1015' }, { id:'t4', cat:'title', icon:'📛', name:'EU Certified', desc:'Representing EU at the highest.', rarity:'rare', price:35, bgColor:'#0d1520' }, // Effects { id:'e1', cat:'effect',icon:'✨', name:'Gold Sparkle', desc:'Golden particles on your profile.',rarity:'legendary', price:175, bgColor:'#1a1500' }, { id:'e2', cat:'effect',icon:'⚡', name:'Electric Aura', desc:'Electrical energy around cards.', rarity:'epic', price:90, bgColor:'#0d0f20' }, { id:'e3', cat:'effect',icon:'💨', name:'Smoke Trail', desc:'Subtle smoke effect, very clean.', rarity:'rare', price:45, bgColor:'#0f0f0f' }, // Emotes { id:'em1',cat:'emote', icon:'💸', name:'Money Rain', desc:'It\'s raining tokens.', rarity:'epic', price:55, bgColor:'#0f1a0d' }, { id:'em2',cat:'emote', icon:'🤝', name:'GG Shake', desc:'Respect after a good game.', rarity:'common', price:12, bgColor:'#0d1015' }, { id:'em3',cat:'emote', icon:'😈', name:'Villain Laugh', desc:'When you pop off on them.', rarity:'rare', price:38, bgColor:'#1a0d0d' }, ]; const RARITY_COLORS = { common:'#6b7299', rare:'var(--blue)', epic:'var(--purple)', legendary:'var(--gold)' }; function loadShop() { updateNavTokens(); renderShopGrid(); } function filterShop(cat, btn) { S.shopFilter = cat; document.querySelectorAll('.shop-cat').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderShopGrid(); } function renderShopGrid() { const grid = document.getElementById('shop-grid'); if (!grid) return; const items = S.shopFilter === 'all' ? SHOP_ITEMS : SHOP_ITEMS.filter(i => i.cat === S.shopFilter); grid.innerHTML = items.map(item => { const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; return `
${equipped ? '
Equipped
' : owned ? '
Owned
' : ''}
${item.icon}
${item.rarity.toUpperCase()}
${item.name}
${item.desc}
🥇 ${item.price} coins
${owned ? `
${equipped?'Equipped':'Own'}
` : ''}
`; }).join(''); } function openShopItem(id) { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return; S.currentShopItem = item; const owned = S.ownedCosmetics.includes(id); const equipped = S.equippedCosmetics[item.cat] === id; document.getElementById('shop-buy-preview').textContent = item.icon; document.getElementById('shop-buy-title').textContent = item.name; document.getElementById('shop-buy-desc').innerHTML = ` ${item.rarity.toUpperCase()}
${item.desc}`; document.getElementById('shop-buy-price').textContent = '🥇 ' + item.price + ' coins'; document.getElementById('shop-buy-balance').textContent = '🥇 ' + S.coins.toFixed(0) + ' coins'; const btn = document.querySelector('#shop-overlay .btn-gold'); if (btn) { if (equipped) { btn.textContent = '✓ Already Equipped'; btn.disabled = true; } else if (owned) { btn.textContent = 'Equip Item'; btn.disabled = false; } else { btn.textContent = '🛍 Purchase Item'; btn.disabled = false; } } document.getElementById('shop-overlay').classList.add('show'); } function confirmShopBuy() { const item = S.currentShopItem; if (!item) return; const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; if (equipped) { toast('Already equipped.', 'info'); return; } if (owned) { // Just equip S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); toast(`✓ ${item.name} equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); return; } if (S.coins < item.price) { toast('Not enough coins. Earn coins via Battlepass and matches!', 'err'); return; } S.coins -= item.price; S.ownedCosmetics.push(item.id); S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_coins', S.coins.toString()); localStorage.setItem('p2w_cosmetics', JSON.stringify(S.ownedCosmetics)); localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); addTx({ type:'buy', name:item.name, amount:-item.price, currency:'coins' }); toast(`✓ ${item.name} purchased and equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); updateNavTokens(); } // ═══════════════════════════════════════════════════ // WALLET // ═══════════════════════════════════════════════════ const TOKEN_PACKS = [ { tokens:5, price:5, label:'Starter Pack' }, { tokens:10, price:10, label:'Player Pack', popular:true }, { tokens:20, price:20, label:'Grinder Pack' }, { tokens:50, price:50, label:'Competitor Pack'}, { tokens:100, price:100, label:'Elite Pack' }, ]; let walletMethod = 'paypal'; let currentPack = null; function loadWallet() { updateWalletDisplay(); renderTokenPacks(); renderWalletTx(); // Check if bank details are saved const bank = S.bankDetails; const filled = document.getElementById('bank-details-filled'); const status = document.getElementById('bank-details-status'); const ibanDisplay = document.getElementById('bank-iban-display'); if (bank.iban) { if (filled) filled.style.display = 'block'; if (status) status.style.display = 'none'; if (ibanDisplay) ibanDisplay.textContent = S.streamerMode ? '****' : bank.iban; } } function updateWalletDisplay() { const tokenDisplay = S.streamerMode ? '****' : S.tokens.toFixed(2); const coinDisplay = S.streamerMode ? '****' : S.coins.toFixed(0); const bal = document.getElementById('wallet-bal-display'); const locked = document.getElementById('wallet-locked-display'); const wt = document.getElementById('w-total'); const wl = document.getElementById('w-locked'); const wa = document.getElementById('w-available'); const cbd = document.getElementById('wallet-coins-display'); if (bal) bal.textContent = tokenDisplay; if (locked) locked.textContent = `Locked: 0 · Available: ${S.streamerMode ? '****' : S.tokens.toFixed(0)} Tokens`; if (wt) wt.textContent = tokenDisplay; if (wl) wl.textContent = '0.00'; if (wa) wa.textContent = tokenDisplay; if (cbd) cbd.textContent = coinDisplay; updateNavTokens(); } function renderTokenPacks() { const list = document.getElementById('token-packs-list'); if (!list) return; list.innerHTML = TOKEN_PACKS.map(p => `
${p.popular ? '' : ''}
🪙
🪙 ${p.tokens} Tokens
${p.label}
€${p.price}.00
`).join(''); } function setWalletMethod(btn, method) { document.querySelectorAll('#wallet-pay-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); walletMethod = method; } function switchWalletTab(tab) { ['purchase','withdraw','history','redeem'].forEach(t => { const el = document.getElementById('wallet-' + t); const btn = document.getElementById('wt-' + t); if (el) el.style.display = t === tab ? 'block' : 'none'; if (btn) btn.classList.toggle('active', t === tab); }); } function openTokenPurchase(tokens, price, label) { currentPack = { tokens, price, label }; document.getElementById('tok-amount-display').textContent = `${tokens} Tokens`; document.getElementById('tok-price-display').textContent = `€${price}`; document.getElementById('tok-discord').value = S.user?.username || ''; setTokMethod(document.querySelector('#tok-methods .pay-m'), 'paypal'); document.getElementById('token-overlay').classList.add('show'); } let tokMethod = 'paypal'; function setTokMethod(btn, method) { document.querySelectorAll('#tok-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); tokMethod = method; const details = document.getElementById('tok-pay-details'); const pack = currentPack || { price: 0 }; const note = `P2W Tokens | ${document.getElementById('tok-discord').value || 'YourName'} | €${pack.price}`; if (method === 'paypal') { details.innerHTML = `
Send via PayPal
Link${CFG.PAY.paypal.link}
Note${note}
`; } else if (method === 'bank') { details.innerHTML = `
Bank Transfer
Name${CFG.PAY.bank.name}
IBAN${CFG.PAY.bank.iban}
Reference${note}
`; } else { details.innerHTML = `
Send via Revolut
Link${CFG.PAY.revolut.link}
Note${note}
`; } } function confirmTokenPurchase() { const discord = document.getElementById('tok-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const pack = currentPack; const note = `P2W Tokens | ${discord} | €${pack.price}`; const purchaseId = 'P' + Date.now().toString().slice(-6); // Open payment if (tokMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/${pack.price}`, '_blank'); if (tokMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); // Save pending purchase const pending = { id: purchaseId, discord, tokens: pack.tokens, price: pack.price, method: tokMethod, note, timestamp: new Date().toISOString(), status: 'pending', }; S.pendingPurchases.push(pending); localStorage.setItem('p2w_pending', JSON.stringify(S.pendingPurchases)); // Send Discord webhook notification sendDiscordWebhook(pending); document.getElementById('token-overlay').classList.remove('show'); toast(`💸 Payment opened. Reference: #${purchaseId} · Send proof to Discord #deposits · Tokens added after confirmation!`, 'info'); } async function sendDiscordWebhook(purchase) { if (!CFG.DISCORD_WEBHOOK || CFG.DISCORD_WEBHOOK.includes('JOUW_WEBHOOK_URL')) return; try { await fetch(CFG.DISCORD_WEBHOOK, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'P2W Platform', avatar_url: 'https://i.imgur.com/4M34hi2.png', embeds: [{ title: '🪙 New Token Purchase Request', color: 0xf0b429, fields: [ { name: '👤 Discord', value: purchase.discord, inline: true }, { name: '🪙 Tokens', value: `${purchase.tokens} Tokens`, inline: true }, { name: '💶 Amount', value: `€${purchase.price}`, inline: true }, { name: '💳 Method', value: purchase.method.toUpperCase(), inline: true }, { name: '🆔 Ref ID', value: `#${purchase.id}`, inline: true }, { name: '🕐 Time', value: new Date(purchase.timestamp).toLocaleString('nl-BE'), inline: true }, { name: '📋 Note', value: `\`${purchase.note}\``, inline: false }, ], footer: { text: 'Go to Admin Panel → Pending Purchases to approve' }, timestamp: purchase.timestamp, }], }), }); } catch(e) { console.warn('Webhook failed:', e); } } function calcWithdrawal(val) { const amount = parseFloat(val) || 0; if (amount < 10) { document.getElementById('withdraw-calc').style.opacity = '.4'; return; } document.getElementById('withdraw-calc').style.opacity = '1'; const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, CFG.WITHDRAW_FEE_FLAT + 1); const receive = Math.max(amount - fee, 0); document.getElementById('wc-amount').textContent = amount; document.getElementById('wc-fee').textContent = fee; document.getElementById('wc-receive').textContent = receive.toFixed(2); } function submitWithdraw() { const amount = parseFloat(document.getElementById('withdraw-amount').value); if (!amount || amount < 10) { toast('Minimum withdrawal is 10 tokens.', 'err'); return; } if (amount > S.tokens) { toast('Insufficient token balance.', 'err'); return; } if (!S.bankDetails.iban) { toast('Add bank details first in Profile → Bank Details.', 'err'); return; } const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, 2); S.tokens -= amount; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'withdraw', name:'Withdrawal to ' + (S.bankDetails.iban || 'Bank'), amount: -amount, currency:'tokens' }); toast(`✓ Withdrawal request submitted. You'll receive €${(amount-fee).toFixed(2)} within 24h.`, 'ok'); document.getElementById('withdraw-amount').value = ''; updateWalletDisplay(); renderWalletTx(); } function redeemCode() { const code = document.getElementById('redeem-code').value.trim().toUpperCase(); if (!code) { toast('Enter a code.', 'err'); return; } // Demo codes const CODES = { 'P2WSTART': {coins:50}, 'WELCOME5': {coins:25}, 'ELITE10': {coins:100} }; if (CODES[code]) { const reward = CODES[code]; if (reward.coins) { S.coins += reward.coins; localStorage.setItem('p2w_coins', S.coins.toString()); addTx({ type:'deposit', name:'Code redeemed: ' + code, amount: reward.coins, currency:'coins' }); toast(`✓ Code redeemed! +🥇${reward.coins} coins added.`, 'ok'); } document.getElementById('redeem-code').value = ''; updateNavTokens(); } else { toast('Invalid or already used code.', 'err'); } } function addTx(tx) { S.transactions.unshift({ ...tx, date: new Date().toLocaleDateString('en-GB'), id: Date.now() }); if (S.transactions.length > 50) S.transactions = S.transactions.slice(0, 50); localStorage.setItem('p2w_tx', JSON.stringify(S.transactions)); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆', entry:'⚔️' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn', entry:'dep' }; el.innerHTML = S.transactions.map(tx => { const currency = tx.currency === 'coins' ? '🥇' : '🪙'; const label = tx.currency === 'coins' ? 'coins' : 'tokens'; return `
${icons[tx.type]||'•'}
${tx.name}
${tx.date} · ${label}
${tx.amount>=0?'+':''}${currency}${Math.abs(tx.amount)}
`; }).join(''); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } // ═══════════════════════════════════════════════════ // PROFILE // ═══════════════════════════════════════════════════ function loadProfile() { const user = S.user || { username: 'Guest' }; const displayName = S.streamerMode ? 'P2W Player' : user.username; const av = document.getElementById('profile-av-text'); const avEl = document.getElementById('profile-avatar'); if (av) av.textContent = initials(displayName); if (avEl) avEl.style.background = avatarColor(user.username); const pn = document.getElementById('profile-name'); if (pn) pn.textContent = displayName; const pj = document.getElementById('profile-joined'); if (pj) pj.textContent = 'Member since ' + new Date().toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }); document.getElementById('prof-level').textContent = bpCurrentPlayer ? bpLevel(bpCurrentPlayer.xp) : '—'; document.getElementById('prof-tokens').textContent = S.streamerMode ? '****' : S.tokens.toFixed(0); document.getElementById('prof-coins').textContent = S.streamerMode ? '****' : S.coins.toFixed(0); document.getElementById('prof-wins').textContent = bpCurrentPlayer?.wins || '—'; document.getElementById('prof-earned').textContent = S.transactions.filter(t=>t.amount>0&&t.currency==='tokens').reduce((a,t)=>a+t.amount,0).toFixed(0); // Load bank fields if (S.bankDetails.iban) document.getElementById('bank-iban').value = S.bankDetails.iban; if (S.bankDetails.name) document.getElementById('bank-fullname').value = S.bankDetails.name; if (S.bankDetails.bic) document.getElementById('bank-bic').value = S.bankDetails.bic; if (S.bankDetails.paypal) document.getElementById('bank-paypal').value = S.bankDetails.paypal; renderProfileTx(); renderMatchHistory(); renderEquippedCosmetics(); } function switchProfileTab(tab) { const tabs = ['matches','transactions','cosmetics','bank']; tabs.forEach(t => { document.getElementById('pf-' + t)?.classList.toggle('active', t === tab); document.querySelectorAll('.profile-tab').forEach((btn,i) => { if (btn.getAttribute('onclick')?.includes(t)) btn.classList.toggle('active', t === tab); }); }); } function renderProfileTx() { const el = document.getElementById('profile-tx-list'); if (!el || !S.transactions.length) return; const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } function renderMatchHistory() { const el = document.getElementById('match-history-rows'); if (!el) return; if (!S.matchHistory.length) { el.innerHTML = '
No match history yet. Join a match to get started.
'; return; } el.innerHTML = S.matchHistory.map(m => `
${m.date}
${MODES[m.mode]||m.mode}
${m.result.toUpperCase()}
${m.result==='win'?'+🪙'+m.earned:'—'}
${m.kills||0}K
`).join(''); } function renderEquippedCosmetics() { const el = document.getElementById('equipped-items'); if (!el) return; const equipped = Object.values(S.equippedCosmetics); if (!equipped.length) { el.innerHTML = '
No cosmetics yet. Visit the Shop to get some.
'; return; } el.innerHTML = equipped.map(id => { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return ''; return `
${item.icon}
${item.rarity}
${item.name}
Equipped
`; }).join(''); } function saveBankDetails() { S.bankDetails = { iban: document.getElementById('bank-iban').value.trim(), name: document.getElementById('bank-fullname').value.trim(), bic: document.getElementById('bank-bic').value.trim(), paypal: document.getElementById('bank-paypal').value.trim(), }; localStorage.setItem('p2w_bank', JSON.stringify(S.bankDetails)); toast('✓ Bank details saved.', 'ok'); loadWallet(); } // ═══════════════════════════════════════════════════ // SETTINGS // ═══════════════════════════════════════════════════ function switchSetting(panel) { document.querySelectorAll('.settings-nav-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.settings-panel').forEach(el => el.classList.remove('active')); document.getElementById('sp-' + panel)?.classList.add('active'); event.currentTarget.classList.add('active'); } function toggleStreamerMode() { S.streamerMode = document.getElementById('streamer-mode').classList.contains('on'); updateNavUser(); updateNavTokens(); updateWalletDisplay(); if (S.streamerMode) toast('📡 Streamer Mode ON — username and balance hidden', 'info'); else toast('Streamer Mode OFF', 'info'); } function saveSettings() { const toggleIds = ['priv-public','priv-earnings','priv-winrate','priv-friends', 'notif-match','notif-result','notif-payout','notif-bp','notif-shop','notif-promo', 'streamer-mode','stream-hide-bal','stream-hide-bank','stream-blur-codes','stream-hc','stream-no-anim', 'app-compact','app-anim-stats','app-age-banner','sec-2fa','sec-withdraw-confirm','sec-login-alert']; toggleIds.forEach(id => { const el = document.getElementById(id); if (el) S.settings[id] = el.classList.contains('on'); }); localStorage.setItem('p2w_settings', JSON.stringify(S.settings)); } // ═══════════════════════════════════════════════════ // PAYMENT (match) // ═══════════════════════════════════════════════════ function setPay(btn, method) { document.querySelectorAll('#pay-s1 .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.payMethod = method; } function processPay() { const discord = document.getElementById('f-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const m = S.currentMatch, fee = m?.fee || 5; // Check wallet balance if (S.tokens < fee) { toast(`Not enough tokens. You have 🪙${S.tokens.toFixed(0)} — need 🪙${fee}. Buy more tokens in the Wallet.`, 'err'); closeModal('pay-overlay'); goPage('wallet'); return; } // Deduct tokens from wallet S.tokens -= fee; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'entry', name:`Entry fee — ${m.title}`, amount: -fee, currency:'tokens' }); // Update match player count in local state const match = S.matches.find(x => x.id === m.id); if (match) match.players = Math.min(match.players + 1, match.maxPlayers); closeModal('pay-overlay'); updateNavTokens(); renderMatches(); toast(`✅ Joined! 🪙${fee} tokens deducted. Check Discord #lobby-codes for your code.`, 'ok'); } function setBPPay(btn, method) { document.querySelectorAll('#bp-overlay .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.bpPayMethod = method; } function processBPPay() { const discord = document.getElementById('bp-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const note = `P2W Premium Battlepass | ${discord}`; if (S.bpPayMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/10`, '_blank'); if (S.bpPayMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); document.getElementById('bp-overlay').classList.remove('show'); setTimeout(() => toast(`✅ Payment opened · Note: "${note}" · Send proof to #battlepass-payments`, 'info'), 300); } // ═══════════════════════════════════════════════════ // ADMIN // ═══════════════════════════════════════════════════ function renderAdminGate() { if (S.adminLoggedIn) { document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); } } let S_adminLoggedIn = false; Object.defineProperty(S, 'adminLoggedIn', { get:()=>S_adminLoggedIn, set:(v)=>S_adminLoggedIn=v }); async function loadDBViewer() { const el = document.getElementById('db-viewer'); if (!el) return; el.innerHTML = '
Loading from JSONBin...
'; try { const data = await binGet(); const bp = data.battlepass || {}; const players = bp.players || {}; const count = Object.keys(players).length; el.innerHTML = `
${count}
BP Players
${Object.values(players).filter(p=>p.premium).length}
Premium
${(bp.xpLog||[]).length}
XP Events
Player Records
${count===0 ? '
No players yet
' : Object.entries(players).map(([k,p]) => `
${p.discord}${p.premium?' ⭐':''}
Lv ${bpLevel(p.xp||0)} ${(p.xp||0).toLocaleString()} XP ${p.wins||0}W ${p.coins ? `🥇${p.coins}` : ''}
`).join('')}
Synced ${new Date().toLocaleTimeString()} · Open full bin →
`; } catch(e) { el.innerHTML = `
Error: ${e.message}
Check CFG.API_KEY is correct.
`; } } async function adminLogin() { const pw = document.getElementById('admin-pw').value; if (pw !== CFG.ADMIN_PW) { toast('Wrong password.', 'err'); return; } S.adminLoggedIn = true; document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); toast('✓ Admin access granted.', 'ok'); } function adminLogout() { S.adminLoggedIn = false; document.getElementById('admin-gate').style.display = 'flex'; document.getElementById('admin-dash').classList.remove('show'); document.getElementById('admin-pw').value = ''; } async function loadAdminData() { // KPIs document.getElementById('admin-kpis').innerHTML = `
Revenue Today
€82
Active Players
${statsOnline}
Matches Live
${S.matches.filter(m=>m.status==='live').length}
Payouts Today
€48
`; // ── PENDING PURCHASES (most important for admin) ── renderPendingPurchases(); // Match list document.getElementById('admin-match-list').innerHTML = S.matches.slice(0,5).map(m => `
${m.title}
${MODES[m.mode]} · 🪙${m.entryFee} · ${m.players}/${m.maxPlayers}
`).join(''); document.getElementById('payout-total-badge').textContent = '€48 today'; document.getElementById('admin-payout-log').innerHTML = [ {user:'NexusEU',amt:'🪙32',time:'14:22'},{user:'ZenithKO',amt:'🪙18',time:'13:05'},{user:'FrostRapide',amt:'🪙14',time:'11:50'} ].map(p => `
${p.user}
${p.time}
${p.amt}
`).join(''); await loadBPAdminData(); } function renderPendingPurchases() { const el = document.getElementById('admin-pending-list'); if (!el) return; // Load from localStorage (all pending purchases submitted from this browser) const allPending = JSON.parse(localStorage.getItem('p2w_pending') || '[]').filter(p => p.status === 'pending'); const badge = document.getElementById('pending-badge'); if (badge) { badge.textContent = allPending.length > 0 ? allPending.length : ''; badge.style.display = allPending.length > 0 ? 'inline-flex' : 'none'; } if (!allPending.length) { el.innerHTML = '
No pending purchases
'; return; } el.innerHTML = allPending.map(p => `
#${p.id} Pending
${p.discord}
${p.method.toUpperCase()} · €${p.price} · ${new Date(p.timestamp).toLocaleString('nl-BE')}
🪙 ${p.tokens}
`).join(''); } function approvePurchase(id) { const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (!p) return; // Add tokens to user wallet S.tokens += p.tokens; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'deposit', name:`Token purchase approved — ${p.tokens} tokens via ${p.method}`, amount: p.tokens, currency:'tokens' }); // Mark as approved p.status = 'approved'; localStorage.setItem('p2w_pending', JSON.stringify(all)); // Remove from DOM document.getElementById('pending-' + id)?.remove(); toast(`✓ Approved — 🪙${p.tokens} tokens added for ${p.discord}!`, 'ok'); updateNavTokens(); renderPendingPurchases(); } function rejectPurchase(id) { if (!confirm('Reject this purchase request?')) return; const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (p) p.status = 'rejected'; localStorage.setItem('p2w_pending', JSON.stringify(all)); document.getElementById('pending-' + id)?.remove(); toast(`Purchase #${id} rejected.`, 'info'); renderPendingPurchases(); } function adminToggleMatch(id, status) { const m = S.matches.find(x => x.id === id); if (!m) return; m.status = m.status === 'live' ? 'waiting' : 'live'; renderMatches(); loadAdminData(); toast(`Match "${m.title}" → ${m.status.toUpperCase()}`, 'ok'); } function adminCancelMatch(id) { if (!confirm('Cancel this match?')) return; S.matches = S.matches.filter(m => m.id !== id); renderMatches(); loadAdminData(); toast('Match cancelled.', 'ok'); } function openCreateMatch() { document.getElementById('cm-overlay').classList.add('show'); } function createMatchAdmin() { const mode = document.getElementById('cm-mode').value; const fee = parseInt(document.getElementById('cm-fee').value) || 5; const max = parseInt(document.getElementById('cm-max').value) || 10; const title= document.getElementById('cm-title').value.trim() || `${MODES[mode]} #${Date.now().toString().slice(-4)}`; S.matches.unshift({ id: Date.now(), mode, title, entryFee:fee, players:0, maxPlayers:max, prize:Math.round(fee*max*0.9), status:'waiting' }); document.getElementById('cm-overlay').classList.remove('show'); toast('✓ Match created!', 'ok'); renderMatches(); loadAdminData(); } // ═══════════════════════════════════════════════════ // BATTLEPASS ADMIN (JSONBin) // ═══════════════════════════════════════════════════ async function loadBPAdminData() { try { const bpData = await getBPData(); const players = Object.values(bpData.players || {}); const total = players.length, prem = players.filter(p=>p.premium).length; const avgLvl = total ? Math.round(players.reduce((a,p)=>a+bpLevel(p.xp),0)/total) : 0; const ta=document.getElementById('bpa-total');if(ta)ta.textContent=total; const pr=document.getElementById('bpa-prem'); if(pr)pr.textContent=prem; const al=document.getElementById('bpa-lvl'); if(al)al.textContent=avgLvl; const tbody = document.getElementById('bp-admin-tbody'); if (tbody) { const keys = Object.keys(bpData.players||{}).sort(); if (!keys.length) { tbody.innerHTML = 'No players yet'; } else { tbody.innerHTML = keys.map(key => { const p = bpData.players[key], lv = bpLevel(p.xp); return ` ${p.discord} ${lv} ${p.xp.toLocaleString()} ${p.premium?'':'Free'} `; }).join(''); } } const logEl = document.getElementById('bp-xp-log'); if (logEl) { const log = (bpData.xpLog||[]).slice(-20).reverse(); logEl.innerHTML = log.length ? log.map(e => `
${e.discord}
${e.reason||'Match'} · ${e.date}
+${e.xp} XP
`).join('') : '
No events yet
'; } } catch(e) { toast('BP load error: ' + e.message, 'err'); } } function filterBPTable(q) { document.querySelectorAll('#bp-admin-tbody tr[data-bpkey]').forEach(r => { r.style.display = r.dataset.bpname?.includes(q.toLowerCase()) ? '' : 'none'; }); } function openBPAddXP(discord) { document.getElementById('bpxp-player').value = discord || ''; document.getElementById('bpxp-amount').value = 250; document.getElementById('bpxp-reason').value = ''; document.getElementById('bpxp-overlay').classList.add('show'); } async function submitBPXP() { const discord = document.getElementById('bpxp-player').value.trim(); const xp = parseInt(document.getElementById('bpxp-amount').value); const reason = document.getElementById('bpxp-reason').value.trim() || 'Match played'; if (!discord || !xp || xp < 1) { toast('Fill all fields.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (!bpData.players[key]) { bpData.players[key] = { discord, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; } const before = bpData.players[key].xp; bpData.players[key].xp += xp; if (reason.toLowerCase().includes('win')) bpData.players[key].wins = (bpData.players[key].wins||0)+1; bpData.players[key].matches = (bpData.players[key].matches||0)+1; bpData.xpLog = bpData.xpLog || []; bpData.xpLog.push({ discord, xp, reason, date:new Date().toISOString().slice(0,10) }); if (bpData.xpLog.length > 200) bpData.xpLog = bpData.xpLog.slice(-200); await saveBPData(bpData); const lvBefore = bpLevel(before), lvAfter = bpLevel(bpData.players[key].xp); document.getElementById('bpxp-overlay').classList.remove('show'); toast(lvAfter > lvBefore ? `⭐ ${discord} +${xp}XP — Level UP! ${lvBefore}→${lvAfter}` : `⚡ ${discord} +${xp}XP (Level ${lvAfter})`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: ' + e.message, 'err'); } } async function bpGrantPremium(key, discord) { if (!confirm(`Grant Premium to ${discord}?`)) return; try { const bpData = await getBPData(); if (!bpData.players[key]) { toast('Player not found.', 'err'); return; } bpData.players[key].premium = true; await saveBPData(bpData); toast(`⭐ Premium granted to ${discord}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } async function bpRemovePlayer(key, discord) { if (!confirm(`Remove ${discord}?`)) return; try { const bpData = await getBPData(); delete bpData.players[key]; await saveBPData(bpData); toast(`${discord} removed.`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } function openBPAddPlayer() { document.getElementById('bpadd-discord').value = ''; document.getElementById('bpadd-pass').value = 'free'; document.getElementById('bpadd-overlay').classList.add('show'); } async function submitBPAddPlayer() { const discord = document.getElementById('bpadd-discord').value.trim(); const pass = document.getElementById('bpadd-pass').value; if (!discord) { toast('Enter Discord name.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (bpData.players[key]) { toast('Player already exists.', 'err'); return; } bpData.players[key] = { discord, xp:0, premium:pass==='premium', wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; await saveBPData(bpData); document.getElementById('bpadd-overlay').classList.remove('show'); toast(`✓ ${discord} added${pass==='premium'?' with Premium':''}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } // ═══════════════════════════════════════════════════ // MODAL HELPERS // ═══════════════════════════════════════════════════ function closeModal(id) { document.getElementById(id)?.classList.remove('show'); } // ═══════════════════════════════════════════════════ // TOAST // ═══════════════════════════════════════════════════ let _tt; function toast(msg, type='ok') { clearTimeout(_tt); const t=document.getElementById('toast'); t.className='toast '+type; document.getElementById('toast-ico').textContent=type==='ok'?'✓':type==='err'?'✕':'ℹ'; document.getElementById('toast-msg').textContent=msg; t.classList.add('show'); _tt=setTimeout(()=>t.classList.remove('show'),4500); } // ═══════════════════════════════════════════════════ // BOOT // ═══════════════════════════════════════════════════ (function boot() { // Restore user const savedUser = localStorage.getItem('p2w_user'); if (savedUser) { try { S.user = JSON.parse(savedUser); updateNavUser(); } catch {} } // Restore settings toggles const settings = S.settings; Object.keys(settings).forEach(id => { const el = document.getElementById(id); if (el && el.classList.contains('toggle')) { if (settings[id]) el.classList.add('on'); else el.classList.remove('on'); } }); // Handle Discord OAuth return const params = new URLSearchParams(window.location.search); if (params.get('token')) { localStorage.setItem('p2w_token', params.get('token')); S.token = params.get('token'); history.replaceState({}, '', window.location.pathname); toast('✓ Logged in via Discord!', 'ok'); } if (params.get('payment') === 'success') { toast('🎉 Payment confirmed! Check Discord for your lobby code.', 'ok'); history.replaceState({}, '', window.location.pathname); } // Init pages renderBPPublicTrack(); goPage('home'); })(); ', // ─── Season ─────────────────────────────────────────────────────── SEASON_END: new Date('2025-06-30T23:59:59'), // ─── BP ─────────────────────────────────────────────────────────── XP_PER_LEVEL: 1000, MAX_LEVEL: 100, // ─── Economy ────────────────────────────────────────────────────── // TOKENS = real money (€1 = 1 Token). Used for match entry fees. // COINS = earned/rewarded currency. Used in the shop for cosmetics. HOUSE_CUT: 0.10, WITHDRAW_FEE_PCT: 0.05, WITHDRAW_FEE_FLAT: 1, // ─── Your Payment Details ───────────────────────────────────────── PAY: { paypal: { link: 'https://paypal.me/JOUW_NAAM' }, bank: { iban: 'BE00 0000 0000 0000', name: 'Jouw Naam', bic: 'GEBABEBB' }, revolut: { link: 'https://revolut.me/JOUW_TAG' }, }, }; // ═══════════════════════════════════════════════════ // STATE // ═══════════════════════════════════════════════════ const S = { user: null, token: localStorage.getItem('p2w_token'), // TOKENS = real money (€1 each). Used for match entry fees. tokens: parseFloat(localStorage.getItem('p2w_tokens') || '0'), // COINS = earned/rewarded. Used in the shop for cosmetics. coins: parseFloat(localStorage.getItem('p2w_coins') || '0'), socket: null, matches: [], leaderboard: [], battlepass: null, currentMatch: null, payMethod: 'paypal', bpPayMethod: 'paypal', tokPayMethod: 'paypal', shopFilter: 'all', currentShopItem: null, // Pending token purchase requests (waiting for admin approval) pendingPurchases: JSON.parse(localStorage.getItem('p2w_pending') || '[]'), settings: JSON.parse(localStorage.getItem('p2w_settings') || '{}'), bankDetails: JSON.parse(localStorage.getItem('p2w_bank') || '{}'), transactions: JSON.parse(localStorage.getItem('p2w_tx') || '[]'), matchHistory: JSON.parse(localStorage.getItem('p2w_mh') || '[]'), ownedCosmetics: JSON.parse(localStorage.getItem('p2w_cosmetics') || '[]'), equippedCosmetics: JSON.parse(localStorage.getItem('p2w_equipped') || '{}'), streamerMode: false, filterMode: 'all', }; // ═══════════════════════════════════════════════════ // JSONBIN // ═══════════════════════════════════════════════════ const BIN_URL = `https://api.jsonbin.io/v3/b/${CFG.BIN_ID}`; async function binGet() { const r = await fetch(BIN_URL + '/latest', { headers: { 'X-Master-Key': CFG.API_KEY } }); if (!r.ok) throw new Error('JSONBin read failed (' + r.status + ')'); return (await r.json()).record; } async function binPut(data) { const r = await fetch(BIN_URL, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Master-Key': CFG.API_KEY }, body: JSON.stringify(data), }); if (!r.ok) throw new Error('JSONBin write failed (' + r.status + ')'); return r.json(); } async function getBPData() { const d = await binGet(); if (!d.battlepass) { await binPut({ ...d, battlepass: { players: {}, xpLog: [], season: 1 } }); return { players: {}, xpLog: [], season: 1 }; } return d.battlepass; } async function saveBPData(bp) { const d = await binGet(); d.battlepass = bp; await binPut(d); } // ═══════════════════════════════════════════════════ // REWARD TRACK // ═══════════════════════════════════════════════════ function buildBPTrack() { // COINS = shop currency (earned via battlepass) // TOKENS = real money (€1 each), only rare milestone rewards const t = []; for (let i = 1; i <= CFG.MAX_LEVEL; i++) { // Default: coins reward (not real money) const coinAmt = i<=25?5:i<=50?10:i<=75?15:20; let r = { level:i, icon:'🥇', label:`${coinAmt} Coins`, type:'coins', premium:false }; if (i===5) r={level:i,icon:'⚡',label:'XP Boost', type:'xp', premium:false}; if (i===10) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // moved from higher if (i===15) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // replaced -10% entry if (i===20) r={level:i,icon:'🏅',label:'Free Badge', type:'badge', premium:false}; if (i===25) r={level:i,icon:'💎',label:'Premium Role', type:'role', premium:true}; if (i===30) r={level:i,icon:'🥇',label:'30 Coins', type:'coins', premium:true}; if (i===40) r={level:i,icon:'⚡',label:'XP Boost ×2', type:'xp', premium:false}; if (i===50) r={level:i,icon:'🥇',label:'50 Coins', type:'coins', premium:true}; if (i===55) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:false}; // real €1 if (i===60) r={level:i,icon:'🥇',label:'75 Coins', type:'coins', premium:true}; if (i===75) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:true}; // replaced -20% entry if (i===100)r={level:i,icon:'👑',label:'5 Tokens', type:'token', premium:true}; // real €5 t.push(r); } return t; } const BP_TRACK = buildBPTrack(); function bpLevel(xp){ return Math.min(Math.floor(xp/CFG.XP_PER_LEVEL)+1, CFG.MAX_LEVEL); } function bpPct(xp) { return Math.floor(((xp%CFG.XP_PER_LEVEL)/CFG.XP_PER_LEVEL)*100); } function bpNorm(n) { return n.trim().toLowerCase(); } function avatarColor(n){ let h=0; for(const c of n)h=c.charCodeAt(0)+((h<<5)-h); return `hsl(${Math.abs(h)%360},55%,35%)`; } function initials(n){ return n.slice(0,2).toUpperCase(); } // ═══════════════════════════════════════════════════ // NAV // ═══════════════════════════════════════════════════ function goPage(p) { document.querySelectorAll('.page').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-link,.drawer-link').forEach(el => el.classList.remove('active')); document.getElementById('page-' + p)?.classList.add('active'); document.querySelectorAll(`[data-page="${p}"]`).forEach(el => el.classList.add('active')); window.scrollTo(0, 0); if (p === 'matches') loadMatches(); if (p === 'lb') loadLB(); if (p === 'bp') loadBPPage(); if (p === 'shop') loadShop(); if (p === 'wallet') loadWallet(); if (p === 'profile') loadProfile(); if (p === 'admin') renderAdminGate(); updateNavTokens(); } function toggleDrawer() { const d = document.getElementById('nav-drawer'); const b = document.getElementById('hamburger'); d.classList.toggle('open'); b.classList.toggle('open'); } function closeDrawer() { document.getElementById('nav-drawer').classList.remove('open'); document.getElementById('hamburger').classList.remove('open'); } // ═══════════════════════════════════════════════════ // AUTH (simulated - Discord OAuth needs backend) // ═══════════════════════════════════════════════════ function loginWithDiscord() { // Simulated login for demo const name = prompt('Enter your Discord username to continue (demo):'); if (!name) return; S.user = { username: name, discordId: 'demo_' + Date.now(), avatar: null }; localStorage.setItem('p2w_user', JSON.stringify(S.user)); updateNavUser(); toast('✓ Welcome to P2W, ' + name + '!', 'ok'); } function updateNavUser() { if (!S.user) return; document.getElementById('login-btn').style.display = 'none'; document.getElementById('nav-avatar-btn').style.display = 'flex'; document.getElementById('nav-bal').style.display = 'flex'; const init = initials(S.streamerMode ? 'P2W Player' : S.user.username); document.getElementById('nav-av-initials').textContent = init; updateNavTokens(); } function updateNavTokens() { const display = document.getElementById('nav-tokens'); if (display) { if (S.streamerMode && S.settings['stream-hide-bal'] !== false) { display.innerHTML = '****'; } else { display.innerHTML = `🪙${S.tokens.toFixed(0)} | 🥇${S.coins.toFixed(0)}`; } } // Shop coins display const sd = document.getElementById('shop-coins-display'); if (sd) sd.textContent = S.coins.toFixed(0); // Wallet const wb = document.getElementById('wallet-bal-display'); if (wb) wb.textContent = S.tokens.toFixed(2); const cbd = document.getElementById('wallet-coins-display'); if (cbd) cbd.textContent = S.coins.toFixed(0); } // ═══════════════════════════════════════════════════ // LIVE STATS // ═══════════════════════════════════════════════════ let statsOnline = 247, statsPayout = 284; function tickStats() { statsOnline += Math.floor(Math.random() * 5) - 2; if (statsOnline < 100) statsOnline = 100; statsPayout += Math.floor(Math.random() * 3); const activeMatches = S.matches.filter(m => m.status === 'live').length; ['s-online','s-active','s-payouts'].forEach((id, i) => { const el = document.getElementById(id); if (!el) return; if (i === 0) el.textContent = statsOnline; if (i === 1) el.textContent = activeMatches; if (i === 2) el.textContent = '€' + statsPayout; }); const mc = document.getElementById('match-count'); if (mc) mc.textContent = `${activeMatches} live · ${S.matches.filter(m=>m.status==='waiting').length} open`; } setInterval(tickStats, 4000); tickStats(); // ═══════════════════════════════════════════════════ // SEASON COUNTDOWN // ═══════════════════════════════════════════════════ function updateCountdowns() { // Battlepass const bc = document.getElementById('bp-countdown'); if (bc) { const diff = CFG.SEASON_END - new Date(); if (diff > 0) { const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000); bc.textContent = `${d}d ${h}h remaining`; } } // Leaderboard const lc = document.getElementById('lb-countdown'); if (lc) { const now = new Date(), next = new Date(now); next.setDate(now.getDate() + ((8 - now.getDay()) % 7 || 7)); next.setHours(0,0,0,0); const diff = next - now; const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000), m = Math.floor((diff%3600000)/60000); lc.textContent = `${d}d ${h}h ${m}m`; } } setInterval(updateCountdowns, 60000); updateCountdowns(); // ═══════════════════════════════════════════════════ // MATCHES // ═══════════════════════════════════════════════════ const MODES = { realistics:'Realistics', boxfights:'Boxfights', scrims:'Scrims', zonewars:'Zonewars' }; const ICONS = { realistics:'🎯', boxfights:'📦', scrims:'⚔️', zonewars:'🌀' }; S.matches = [ { id:1, mode:'realistics', title:'EU Realistics #14', entryFee:5, players:7, maxPlayers:10, prize:45, status:'waiting' }, { id:2, mode:'realistics', title:'EU Realistics #13', entryFee:10, players:10, maxPlayers:10, prize:90, status:'live' }, { id:3, mode:'boxfights', title:'Boxfight Cup #7', entryFee:2, players:6, maxPlayers:16, prize:28, status:'waiting' }, { id:4, mode:'scrims', title:'EU Scrims #5', entryFee:10, players:80, maxPlayers:100, prize:800, status:'live' }, { id:5, mode:'boxfights', title:'Boxfight Ladder', entryFee:5, players:3, maxPlayers:8, prize:35, status:'waiting' }, { id:6, mode:'scrims', title:'EU Scrims #6', entryFee:5, players:22, maxPlayers:100, prize:200, status:'waiting' }, { id:7, mode:'zonewars', title:'Zonewars Alpha', entryFee:2, players:4, maxPlayers:6, prize:10, status:'waiting' }, ]; function loadMatches() { renderMatches(); } function filterMatch(mode, btn) { S.filterMode = mode; document.querySelectorAll('.fb').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderMatches(); } function renderMatches() { const grid = document.getElementById('matches-grid'); if (!grid) return; const list = S.filterMode === 'all' ? S.matches : S.matches.filter(m => m.mode === S.filterMode); if (!list.length) { grid.innerHTML = `
No matches in this mode right now.
`; return; } grid.innerHTML = list.map(m => { const pct = Math.round((m.players / m.maxPlayers) * 100); const full = m.players >= m.maxPlayers; const live = m.status === 'live'; const cut = Math.round(m.entryFee * m.maxPlayers * CFG.HOUSE_CUT); return `
${ICONS[m.mode]} ${MODES[m.mode]} ${live ? ` LIVE` : full ? `FULL` : `● OPEN`}
${m.title}
Hosted by P2W Staff
🪙${m.entryFee}
Entry
🪙${m.prize}
Prize
${m.players}/${m.maxPlayers}
Players
${m.players} / ${m.maxPlayers} players${pct}%
${full || live ? `` : ``}
House cut: 🪙${cut} · Net prize: 🪙${m.prize - cut}
`; }).join(''); } function joinMatch(id, mode, title, fee) { S.currentMatch = { id, mode, title, fee }; document.getElementById('m-mode').textContent = MODES[mode] || mode; document.getElementById('m-title').textContent = title; document.getElementById('m-price').textContent = '🪙' + fee; document.getElementById('f-duo-wrap').style.display = mode === 'scrims' ? 'block' : 'none'; document.getElementById('f-discord').value = S.user?.username || ''; const balEl = document.getElementById('join-balance-display'); if (balEl) balEl.textContent = `🪙 ${S.tokens.toFixed(0)} tokens`; // Warn if not enough const btn = document.getElementById('pay-btn'); if (S.tokens < fee) { if (btn) { btn.textContent = '⚠ Not enough tokens — Buy more'; btn.style.background = 'rgba(239,68,68,.15)'; btn.style.border = '1px solid rgba(239,68,68,.3)'; btn.style.color = 'var(--red)'; } } else { if (btn) { btn.textContent = '🪙 Pay & Join →'; btn.style.background = ''; btn.style.border = ''; btn.style.color = ''; } } document.getElementById('pay-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // LEADERBOARD // ═══════════════════════════════════════════════════ const SAMPLE_LB = [ {rank:1,name:'NexusEU', earn:312,wins:61,wr:81,ch:'+2',up:true}, {rank:2,name:'ZenithKO', earn:147,wins:38,wr:74,ch:'—', up:null}, {rank:3,name:'FrostRapide',earn:98,wins:27,wr:68,ch:'-1',up:false}, {rank:4,name:'ShadowLoop', earn:76,wins:21,wr:65,ch:'+3',up:true}, {rank:5,name:'KrypticFN', earn:62,wins:19,wr:59,ch:'-2',up:false}, {rank:6,name:'BlitzRunner',earn:55,wins:15,wr:62,ch:'+1',up:true}, {rank:7,name:'VoidCraft', earn:44,wins:12,wr:57,ch:'—', up:null}, {rank:8,name:'ApexRoll', earn:37,wins:10,wr:54,ch:'+4',up:true}, {rank:9,name:'DriftKing', earn:29,wins:8, wr:50,ch:'-1',up:false}, {rank:10,name:'TempestEU', earn:18,wins:5, wr:45,ch:'+2',up:true}, ]; function loadLB() { const pod = document.getElementById('lb-podium'); const [s1,s2,s3] = [SAMPLE_LB[1], SAMPLE_LB[0], SAMPLE_LB[2]]; const crowns = ['🥈','👑','🥉']; const podData = [s1,s2,s3]; const cls = ['','p1','']; if (pod) pod.innerHTML = podData.map((p,i) => { const hue = (p.rank*47)%360; return `
${crowns[i]}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}W · ${p.wr}% WR
`; }).join(''); document.getElementById('lb-rows').innerHTML = SAMPLE_LB.map(p => { const hue = (p.rank*47)%360; return `
${p.rank<=3?'★':p.rank}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}
${p.wr}%
${p.up===true?'▲':p.up===false?'▼':'─'} ${p.ch}
`; }).join(''); } // ═══════════════════════════════════════════════════ // BATTLEPASS // ═══════════════════════════════════════════════════ let bpCurrentPlayer = null, bpCurrentKey = null; function loadBPPage() { renderBPPublicTrack(); renderPasses(); if (bpCurrentPlayer) renderBPCard(bpCurrentPlayer); } function renderBPPublicTrack() { const el = document.getElementById('bp-public-track'); if (!el) return; const milestones = BP_TRACK.filter(r => r.level % 5 === 0 || r.level === 1); el.innerHTML = milestones.map(r => { const isToken = r.type === 'token'; const borderC = isToken ? 'rgba(240,180,41,.35)' : r.premium ? 'rgba(168,85,247,.2)' : 'var(--border)'; const glowC = isToken ? 'rgba(240,180,41,.08)' : 'transparent'; return `
${isToken ? '
' : ''} ${r.premium && !isToken ? '
PRO
' : ''}
${r.icon}
Lv ${r.level}
${r.label}
${isToken ? '
real money
' : r.type==='coins' ? '
shop coins
' : ''}
`; }).join(''); } async function bpLookup() { const raw = document.getElementById('bp-inp').value.trim(); if (!raw) { toast('Enter your Discord name.', 'err'); return; } const btn = document.getElementById('bp-lookup-btn'); btn.disabled = true; btn.innerHTML = '
'; try { const data = await getBPData(); const key = bpNorm(raw); let player = data.players[key]; if (!player) { player = { discord:raw, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; data.players[key] = player; await saveBPData(data); toast('✓ Battlepass profile created!', 'ok'); } bpCurrentPlayer = player; bpCurrentKey = key; renderBPCard(player); } catch(e) { toast('Error: ' + e.message, 'err'); } btn.disabled = false; btn.textContent = 'Look Up →'; } function bpReset() { document.getElementById('bp-lookup-box').style.display = 'block'; document.getElementById('bp-loaded-box').style.display = 'none'; document.getElementById('bp-claimable-section').style.display = 'none'; document.getElementById('bp-inp').value = ''; bpCurrentPlayer = null; bpCurrentKey = null; } function renderBPCard(p) { const lv = bpLevel(p.xp), pct = bpPct(p.xp), prog = p.xp % CFG.XP_PER_LEVEL; const isPrem = p.premium, claimed = new Set(p.claimedRewards || []); const claimable = BP_TRACK.filter(r => r.level <= lv && (!r.premium || isPrem) && !claimed.has(r.level)); document.getElementById('bp-lookup-box').style.display = 'none'; document.getElementById('bp-loaded-box').style.display = 'block'; const av = document.getElementById('bp-avatar'); av.textContent = initials(p.discord); av.style.background = avatarColor(p.discord); av.style.borderRadius = '50%'; av.style.width = '36px'; av.style.height = '36px'; av.style.display = 'flex'; av.style.alignItems = 'center'; av.style.justifyContent = 'center'; const displayName = S.streamerMode ? 'P2W Player' : p.discord; document.getElementById('bp-username').textContent = displayName; document.getElementById('bp-pass-badge').innerHTML = isPrem ? `⭐ Premium` : `Free`; document.getElementById('bp-level').textContent = lv; document.getElementById('bp-level-label').textContent = `Level ${lv} / 100`; document.getElementById('bp-xp-label').textContent = `${prog.toLocaleString('en')} / 1000 XP`; setTimeout(() => { const f = document.getElementById('bp-xp-fill'); if(f) f.style.width = pct + '%'; }, 80); const hint = document.getElementById('bp-claim-hint'); if (hint) hint.style.display = claimable.length > 0 ? 'block' : 'none'; if (claimable.length > 0) { document.getElementById('bp-claimable-section').style.display = 'block'; document.getElementById('bp-claimable-list').innerHTML = claimable.map(r => `
${r.icon}
Lv ${r.level} — ${r.label}
Contact staff in Discord to claim
`).join(''); } } function renderPasses() { const el = document.getElementById('passes-grid'); if (!el) return; el.innerHTML = `
FREE PASS
For everyone
FREE
🪙
50 tokens across 100 levels
XP boosts at milestones
📊
Full stats tracking
🏅
Free badge on Discord
⭐ PREMIUM
Season 1 — €10 one-time
€10
💰
€15+ value in tokens & credits
🎟
Up to 20% off match entry fees
🥇
Priority queue access
📈
+10% earnings boost on wins
💎
Exclusive Discord role + gold name
`; } function purchaseBP() { document.getElementById('bp-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // SHOP // ═══════════════════════════════════════════════════ const SHOP_ITEMS = [ // Badges { id:'b1', cat:'badge', icon:'💀', name:'Death Dealer', desc:'For those who eliminate first.', rarity:'rare', price:20, bgColor:'#1a0f0f' }, { id:'b2', cat:'badge', icon:'👑', name:'Champion Crown', desc:'Earned by top seasonal players.', rarity:'legendary', price:150, bgColor:'#1a1500' }, { id:'b3', cat:'badge', icon:'🔥', name:'On Fire', desc:'You\'re unstoppable right now.', rarity:'epic', price:75, bgColor:'#1a0d00' }, { id:'b4', cat:'badge', icon:'⚡', name:'Speed Demon', desc:'Quick entries, quick exits.', rarity:'rare', price:30, bgColor:'#0d0f1a' }, { id:'b5', cat:'badge', icon:'🎯', name:'Sharpshooter', desc:'Precision over brute force.', rarity:'common', price:10, bgColor:'#0f1a0f' }, // Borders { id:'bo1',cat:'border',icon:'🟦', name:'Blue Holo', desc:'Holographic blue profile border.', rarity:'rare', price:40, bgColor:'#0d1020' }, { id:'bo2',cat:'border',icon:'🟨', name:'Gold Crown', desc:'Legendary golden border frame.', rarity:'legendary', price:200, bgColor:'#1a1200' }, { id:'bo3',cat:'border',icon:'🟣', name:'Purple Haze', desc:'Epic purple animated border.', rarity:'epic', price:80, bgColor:'#110d1a' }, // Titles { id:'t1', cat:'title', icon:'📛', name:'The Untouchable', desc:'Show them you\'re unreachable.', rarity:'epic', price:60, bgColor:'#0d1015' }, { id:'t2', cat:'title', icon:'📛', name:'Cash King', desc:'Earnings speak louder than words.',rarity:'legendary', price:120, bgColor:'#1a1000' }, { id:'t3', cat:'title', icon:'📛', name:'Grinder', desc:'No days off. No excuses.', rarity:'common', price:15, bgColor:'#0f1015' }, { id:'t4', cat:'title', icon:'📛', name:'EU Certified', desc:'Representing EU at the highest.', rarity:'rare', price:35, bgColor:'#0d1520' }, // Effects { id:'e1', cat:'effect',icon:'✨', name:'Gold Sparkle', desc:'Golden particles on your profile.',rarity:'legendary', price:175, bgColor:'#1a1500' }, { id:'e2', cat:'effect',icon:'⚡', name:'Electric Aura', desc:'Electrical energy around cards.', rarity:'epic', price:90, bgColor:'#0d0f20' }, { id:'e3', cat:'effect',icon:'💨', name:'Smoke Trail', desc:'Subtle smoke effect, very clean.', rarity:'rare', price:45, bgColor:'#0f0f0f' }, // Emotes { id:'em1',cat:'emote', icon:'💸', name:'Money Rain', desc:'It\'s raining tokens.', rarity:'epic', price:55, bgColor:'#0f1a0d' }, { id:'em2',cat:'emote', icon:'🤝', name:'GG Shake', desc:'Respect after a good game.', rarity:'common', price:12, bgColor:'#0d1015' }, { id:'em3',cat:'emote', icon:'😈', name:'Villain Laugh', desc:'When you pop off on them.', rarity:'rare', price:38, bgColor:'#1a0d0d' }, ]; const RARITY_COLORS = { common:'#6b7299', rare:'var(--blue)', epic:'var(--purple)', legendary:'var(--gold)' }; function loadShop() { updateNavTokens(); renderShopGrid(); } function filterShop(cat, btn) { S.shopFilter = cat; document.querySelectorAll('.shop-cat').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderShopGrid(); } function renderShopGrid() { const grid = document.getElementById('shop-grid'); if (!grid) return; const items = S.shopFilter === 'all' ? SHOP_ITEMS : SHOP_ITEMS.filter(i => i.cat === S.shopFilter); grid.innerHTML = items.map(item => { const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; return `
${equipped ? '
Equipped
' : owned ? '
Owned
' : ''}
${item.icon}
${item.rarity.toUpperCase()}
${item.name}
${item.desc}
🥇 ${item.price} coins
${owned ? `
${equipped?'Equipped':'Own'}
` : ''}
`; }).join(''); } function openShopItem(id) { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return; S.currentShopItem = item; const owned = S.ownedCosmetics.includes(id); const equipped = S.equippedCosmetics[item.cat] === id; document.getElementById('shop-buy-preview').textContent = item.icon; document.getElementById('shop-buy-title').textContent = item.name; document.getElementById('shop-buy-desc').innerHTML = ` ${item.rarity.toUpperCase()}
${item.desc}`; document.getElementById('shop-buy-price').textContent = '🥇 ' + item.price + ' coins'; document.getElementById('shop-buy-balance').textContent = '🥇 ' + S.coins.toFixed(0) + ' coins'; const btn = document.querySelector('#shop-overlay .btn-gold'); if (btn) { if (equipped) { btn.textContent = '✓ Already Equipped'; btn.disabled = true; } else if (owned) { btn.textContent = 'Equip Item'; btn.disabled = false; } else { btn.textContent = '🛍 Purchase Item'; btn.disabled = false; } } document.getElementById('shop-overlay').classList.add('show'); } function confirmShopBuy() { const item = S.currentShopItem; if (!item) return; const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; if (equipped) { toast('Already equipped.', 'info'); return; } if (owned) { // Just equip S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); toast(`✓ ${item.name} equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); return; } if (S.coins < item.price) { toast('Not enough coins. Earn coins via Battlepass and matches!', 'err'); return; } S.coins -= item.price; S.ownedCosmetics.push(item.id); S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_coins', S.coins.toString()); localStorage.setItem('p2w_cosmetics', JSON.stringify(S.ownedCosmetics)); localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); addTx({ type:'buy', name:item.name, amount:-item.price, currency:'coins' }); toast(`✓ ${item.name} purchased and equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); updateNavTokens(); } // ═══════════════════════════════════════════════════ // WALLET // ═══════════════════════════════════════════════════ const TOKEN_PACKS = [ { tokens:5, price:5, label:'Starter Pack' }, { tokens:10, price:10, label:'Player Pack', popular:true }, { tokens:20, price:20, label:'Grinder Pack' }, { tokens:50, price:50, label:'Competitor Pack'}, { tokens:100, price:100, label:'Elite Pack' }, ]; let walletMethod = 'paypal'; let currentPack = null; function loadWallet() { updateWalletDisplay(); renderTokenPacks(); renderWalletTx(); // Check if bank details are saved const bank = S.bankDetails; const filled = document.getElementById('bank-details-filled'); const status = document.getElementById('bank-details-status'); const ibanDisplay = document.getElementById('bank-iban-display'); if (bank.iban) { if (filled) filled.style.display = 'block'; if (status) status.style.display = 'none'; if (ibanDisplay) ibanDisplay.textContent = S.streamerMode ? '****' : bank.iban; } } function updateWalletDisplay() { const tokenDisplay = S.streamerMode ? '****' : S.tokens.toFixed(2); const coinDisplay = S.streamerMode ? '****' : S.coins.toFixed(0); const bal = document.getElementById('wallet-bal-display'); const locked = document.getElementById('wallet-locked-display'); const wt = document.getElementById('w-total'); const wl = document.getElementById('w-locked'); const wa = document.getElementById('w-available'); const cbd = document.getElementById('wallet-coins-display'); if (bal) bal.textContent = tokenDisplay; if (locked) locked.textContent = `Locked: 0 · Available: ${S.streamerMode ? '****' : S.tokens.toFixed(0)} Tokens`; if (wt) wt.textContent = tokenDisplay; if (wl) wl.textContent = '0.00'; if (wa) wa.textContent = tokenDisplay; if (cbd) cbd.textContent = coinDisplay; updateNavTokens(); } function renderTokenPacks() { const list = document.getElementById('token-packs-list'); if (!list) return; list.innerHTML = TOKEN_PACKS.map(p => `
${p.popular ? '' : ''}
🪙
🪙 ${p.tokens} Tokens
${p.label}
€${p.price}.00
`).join(''); } function setWalletMethod(btn, method) { document.querySelectorAll('#wallet-pay-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); walletMethod = method; } function switchWalletTab(tab) { ['purchase','withdraw','history','redeem'].forEach(t => { const el = document.getElementById('wallet-' + t); const btn = document.getElementById('wt-' + t); if (el) el.style.display = t === tab ? 'block' : 'none'; if (btn) btn.classList.toggle('active', t === tab); }); } function openTokenPurchase(tokens, price, label) { currentPack = { tokens, price, label }; document.getElementById('tok-amount-display').textContent = `${tokens} Tokens`; document.getElementById('tok-price-display').textContent = `€${price}`; document.getElementById('tok-discord').value = S.user?.username || ''; setTokMethod(document.querySelector('#tok-methods .pay-m'), 'paypal'); document.getElementById('token-overlay').classList.add('show'); } let tokMethod = 'paypal'; function setTokMethod(btn, method) { document.querySelectorAll('#tok-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); tokMethod = method; const details = document.getElementById('tok-pay-details'); const pack = currentPack || { price: 0 }; const note = `P2W Tokens | ${document.getElementById('tok-discord').value || 'YourName'} | €${pack.price}`; if (method === 'paypal') { details.innerHTML = `
Send via PayPal
Link${CFG.PAY.paypal.link}
Note${note}
`; } else if (method === 'bank') { details.innerHTML = `
Bank Transfer
Name${CFG.PAY.bank.name}
IBAN${CFG.PAY.bank.iban}
Reference${note}
`; } else { details.innerHTML = `
Send via Revolut
Link${CFG.PAY.revolut.link}
Note${note}
`; } } function confirmTokenPurchase() { const discord = document.getElementById('tok-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const pack = currentPack; const note = `P2W Tokens | ${discord} | €${pack.price}`; const purchaseId = 'P' + Date.now().toString().slice(-6); // Open payment if (tokMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/${pack.price}`, '_blank'); if (tokMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); // Save pending purchase const pending = { id: purchaseId, discord, tokens: pack.tokens, price: pack.price, method: tokMethod, note, timestamp: new Date().toISOString(), status: 'pending', }; S.pendingPurchases.push(pending); localStorage.setItem('p2w_pending', JSON.stringify(S.pendingPurchases)); // Send Discord webhook notification sendDiscordWebhook(pending); document.getElementById('token-overlay').classList.remove('show'); toast(`💸 Payment opened. Reference: #${purchaseId} · Send proof to Discord #deposits · Tokens added after confirmation!`, 'info'); } async function sendDiscordWebhook(purchase) { if (!CFG.DISCORD_WEBHOOK || CFG.DISCORD_WEBHOOK.includes('JOUW_WEBHOOK_URL')) return; try { await fetch(CFG.DISCORD_WEBHOOK, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'P2W Platform', avatar_url: 'https://i.imgur.com/4M34hi2.png', embeds: [{ title: '🪙 New Token Purchase Request', color: 0xf0b429, fields: [ { name: '👤 Discord', value: purchase.discord, inline: true }, { name: '🪙 Tokens', value: `${purchase.tokens} Tokens`, inline: true }, { name: '💶 Amount', value: `€${purchase.price}`, inline: true }, { name: '💳 Method', value: purchase.method.toUpperCase(), inline: true }, { name: '🆔 Ref ID', value: `#${purchase.id}`, inline: true }, { name: '🕐 Time', value: new Date(purchase.timestamp).toLocaleString('nl-BE'), inline: true }, { name: '📋 Note', value: `\`${purchase.note}\``, inline: false }, ], footer: { text: 'Go to Admin Panel → Pending Purchases to approve' }, timestamp: purchase.timestamp, }], }), }); } catch(e) { console.warn('Webhook failed:', e); } } function calcWithdrawal(val) { const amount = parseFloat(val) || 0; if (amount < 10) { document.getElementById('withdraw-calc').style.opacity = '.4'; return; } document.getElementById('withdraw-calc').style.opacity = '1'; const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, CFG.WITHDRAW_FEE_FLAT + 1); const receive = Math.max(amount - fee, 0); document.getElementById('wc-amount').textContent = amount; document.getElementById('wc-fee').textContent = fee; document.getElementById('wc-receive').textContent = receive.toFixed(2); } function submitWithdraw() { const amount = parseFloat(document.getElementById('withdraw-amount').value); if (!amount || amount < 10) { toast('Minimum withdrawal is 10 tokens.', 'err'); return; } if (amount > S.tokens) { toast('Insufficient token balance.', 'err'); return; } if (!S.bankDetails.iban) { toast('Add bank details first in Profile → Bank Details.', 'err'); return; } const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, 2); S.tokens -= amount; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'withdraw', name:'Withdrawal to ' + (S.bankDetails.iban || 'Bank'), amount: -amount, currency:'tokens' }); toast(`✓ Withdrawal request submitted. You'll receive €${(amount-fee).toFixed(2)} within 24h.`, 'ok'); document.getElementById('withdraw-amount').value = ''; updateWalletDisplay(); renderWalletTx(); } function redeemCode() { const code = document.getElementById('redeem-code').value.trim().toUpperCase(); if (!code) { toast('Enter a code.', 'err'); return; } // Demo codes const CODES = { 'P2WSTART': {coins:50}, 'WELCOME5': {coins:25}, 'ELITE10': {coins:100} }; if (CODES[code]) { const reward = CODES[code]; if (reward.coins) { S.coins += reward.coins; localStorage.setItem('p2w_coins', S.coins.toString()); addTx({ type:'deposit', name:'Code redeemed: ' + code, amount: reward.coins, currency:'coins' }); toast(`✓ Code redeemed! +🥇${reward.coins} coins added.`, 'ok'); } document.getElementById('redeem-code').value = ''; updateNavTokens(); } else { toast('Invalid or already used code.', 'err'); } } function addTx(tx) { S.transactions.unshift({ ...tx, date: new Date().toLocaleDateString('en-GB'), id: Date.now() }); if (S.transactions.length > 50) S.transactions = S.transactions.slice(0, 50); localStorage.setItem('p2w_tx', JSON.stringify(S.transactions)); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆', entry:'⚔️' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn', entry:'dep' }; el.innerHTML = S.transactions.map(tx => { const currency = tx.currency === 'coins' ? '🥇' : '🪙'; const label = tx.currency === 'coins' ? 'coins' : 'tokens'; return `
${icons[tx.type]||'•'}
${tx.name}
${tx.date} · ${label}
${tx.amount>=0?'+':''}${currency}${Math.abs(tx.amount)}
`; }).join(''); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } // ═══════════════════════════════════════════════════ // PROFILE // ═══════════════════════════════════════════════════ function loadProfile() { const user = S.user || { username: 'Guest' }; const displayName = S.streamerMode ? 'P2W Player' : user.username; const av = document.getElementById('profile-av-text'); const avEl = document.getElementById('profile-avatar'); if (av) av.textContent = initials(displayName); if (avEl) avEl.style.background = avatarColor(user.username); const pn = document.getElementById('profile-name'); if (pn) pn.textContent = displayName; const pj = document.getElementById('profile-joined'); if (pj) pj.textContent = 'Member since ' + new Date().toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }); document.getElementById('prof-level').textContent = bpCurrentPlayer ? bpLevel(bpCurrentPlayer.xp) : '—'; document.getElementById('prof-tokens').textContent = S.streamerMode ? '****' : S.tokens.toFixed(0); document.getElementById('prof-coins').textContent = S.streamerMode ? '****' : S.coins.toFixed(0); document.getElementById('prof-wins').textContent = bpCurrentPlayer?.wins || '—'; document.getElementById('prof-earned').textContent = S.transactions.filter(t=>t.amount>0&&t.currency==='tokens').reduce((a,t)=>a+t.amount,0).toFixed(0); // Load bank fields if (S.bankDetails.iban) document.getElementById('bank-iban').value = S.bankDetails.iban; if (S.bankDetails.name) document.getElementById('bank-fullname').value = S.bankDetails.name; if (S.bankDetails.bic) document.getElementById('bank-bic').value = S.bankDetails.bic; if (S.bankDetails.paypal) document.getElementById('bank-paypal').value = S.bankDetails.paypal; renderProfileTx(); renderMatchHistory(); renderEquippedCosmetics(); } function switchProfileTab(tab) { const tabs = ['matches','transactions','cosmetics','bank']; tabs.forEach(t => { document.getElementById('pf-' + t)?.classList.toggle('active', t === tab); document.querySelectorAll('.profile-tab').forEach((btn,i) => { if (btn.getAttribute('onclick')?.includes(t)) btn.classList.toggle('active', t === tab); }); }); } function renderProfileTx() { const el = document.getElementById('profile-tx-list'); if (!el || !S.transactions.length) return; const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } function renderMatchHistory() { const el = document.getElementById('match-history-rows'); if (!el) return; if (!S.matchHistory.length) { el.innerHTML = '
No match history yet. Join a match to get started.
'; return; } el.innerHTML = S.matchHistory.map(m => `
${m.date}
${MODES[m.mode]||m.mode}
${m.result.toUpperCase()}
${m.result==='win'?'+🪙'+m.earned:'—'}
${m.kills||0}K
`).join(''); } function renderEquippedCosmetics() { const el = document.getElementById('equipped-items'); if (!el) return; const equipped = Object.values(S.equippedCosmetics); if (!equipped.length) { el.innerHTML = '
No cosmetics yet. Visit the Shop to get some.
'; return; } el.innerHTML = equipped.map(id => { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return ''; return `
${item.icon}
${item.rarity}
${item.name}
Equipped
`; }).join(''); } function saveBankDetails() { S.bankDetails = { iban: document.getElementById('bank-iban').value.trim(), name: document.getElementById('bank-fullname').value.trim(), bic: document.getElementById('bank-bic').value.trim(), paypal: document.getElementById('bank-paypal').value.trim(), }; localStorage.setItem('p2w_bank', JSON.stringify(S.bankDetails)); toast('✓ Bank details saved.', 'ok'); loadWallet(); } // ═══════════════════════════════════════════════════ // SETTINGS // ═══════════════════════════════════════════════════ function switchSetting(panel) { document.querySelectorAll('.settings-nav-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.settings-panel').forEach(el => el.classList.remove('active')); document.getElementById('sp-' + panel)?.classList.add('active'); event.currentTarget.classList.add('active'); } function toggleStreamerMode() { S.streamerMode = document.getElementById('streamer-mode').classList.contains('on'); updateNavUser(); updateNavTokens(); updateWalletDisplay(); if (S.streamerMode) toast('📡 Streamer Mode ON — username and balance hidden', 'info'); else toast('Streamer Mode OFF', 'info'); } function saveSettings() { const toggleIds = ['priv-public','priv-earnings','priv-winrate','priv-friends', 'notif-match','notif-result','notif-payout','notif-bp','notif-shop','notif-promo', 'streamer-mode','stream-hide-bal','stream-hide-bank','stream-blur-codes','stream-hc','stream-no-anim', 'app-compact','app-anim-stats','app-age-banner','sec-2fa','sec-withdraw-confirm','sec-login-alert']; toggleIds.forEach(id => { const el = document.getElementById(id); if (el) S.settings[id] = el.classList.contains('on'); }); localStorage.setItem('p2w_settings', JSON.stringify(S.settings)); } // ═══════════════════════════════════════════════════ // PAYMENT (match) // ═══════════════════════════════════════════════════ function setPay(btn, method) { document.querySelectorAll('#pay-s1 .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.payMethod = method; } function processPay() { const discord = document.getElementById('f-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const m = S.currentMatch, fee = m?.fee || 5; // Check wallet balance if (S.tokens < fee) { toast(`Not enough tokens. You have 🪙${S.tokens.toFixed(0)} — need 🪙${fee}. Buy more tokens in the Wallet.`, 'err'); closeModal('pay-overlay'); goPage('wallet'); return; } // Deduct tokens from wallet S.tokens -= fee; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'entry', name:`Entry fee — ${m.title}`, amount: -fee, currency:'tokens' }); // Update match player count in local state const match = S.matches.find(x => x.id === m.id); if (match) match.players = Math.min(match.players + 1, match.maxPlayers); closeModal('pay-overlay'); updateNavTokens(); renderMatches(); toast(`✅ Joined! 🪙${fee} tokens deducted. Check Discord #lobby-codes for your code.`, 'ok'); } function setBPPay(btn, method) { document.querySelectorAll('#bp-overlay .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.bpPayMethod = method; } function processBPPay() { const discord = document.getElementById('bp-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const note = `P2W Premium Battlepass | ${discord}`; if (S.bpPayMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/10`, '_blank'); if (S.bpPayMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); document.getElementById('bp-overlay').classList.remove('show'); setTimeout(() => toast(`✅ Payment opened · Note: "${note}" · Send proof to #battlepass-payments`, 'info'), 300); } // ═══════════════════════════════════════════════════ // ADMIN // ═══════════════════════════════════════════════════ function renderAdminGate() { if (S.adminLoggedIn) { document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); } } let S_adminLoggedIn = false; Object.defineProperty(S, 'adminLoggedIn', { get:()=>S_adminLoggedIn, set:(v)=>S_adminLoggedIn=v }); async function loadDBViewer() { const el = document.getElementById('db-viewer'); if (!el) return; el.innerHTML = '
Loading from JSONBin...
'; try { const data = await binGet(); const bp = data.battlepass || {}; const players = bp.players || {}; const count = Object.keys(players).length; el.innerHTML = `
${count}
BP Players
${Object.values(players).filter(p=>p.premium).length}
Premium
${(bp.xpLog||[]).length}
XP Events
Player Records
${count===0 ? '
No players yet
' : Object.entries(players).map(([k,p]) => `
${p.discord}${p.premium?' ⭐':''}
Lv ${bpLevel(p.xp||0)} ${(p.xp||0).toLocaleString()} XP ${p.wins||0}W ${p.coins ? `🥇${p.coins}` : ''}
`).join('')}
Synced ${new Date().toLocaleTimeString()} · Open full bin →
`; } catch(e) { el.innerHTML = `
Error: ${e.message}
Check CFG.API_KEY is correct.
`; } } async function adminLogin() { const pw = document.getElementById('admin-pw').value; if (pw !== CFG.ADMIN_PW) { toast('Wrong password.', 'err'); return; } S.adminLoggedIn = true; document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); toast('✓ Admin access granted.', 'ok'); } function adminLogout() { S.adminLoggedIn = false; document.getElementById('admin-gate').style.display = 'flex'; document.getElementById('admin-dash').classList.remove('show'); document.getElementById('admin-pw').value = ''; } async function loadAdminData() { // KPIs document.getElementById('admin-kpis').innerHTML = `
Revenue Today
€82
Active Players
${statsOnline}
Matches Live
${S.matches.filter(m=>m.status==='live').length}
Payouts Today
€48
`; // ── PENDING PURCHASES (most important for admin) ── renderPendingPurchases(); // Match list document.getElementById('admin-match-list').innerHTML = S.matches.slice(0,5).map(m => `
${m.title}
${MODES[m.mode]} · 🪙${m.entryFee} · ${m.players}/${m.maxPlayers}
`).join(''); document.getElementById('payout-total-badge').textContent = '€48 today'; document.getElementById('admin-payout-log').innerHTML = [ {user:'NexusEU',amt:'🪙32',time:'14:22'},{user:'ZenithKO',amt:'🪙18',time:'13:05'},{user:'FrostRapide',amt:'🪙14',time:'11:50'} ].map(p => `
${p.user}
${p.time}
${p.amt}
`).join(''); await loadBPAdminData(); } function renderPendingPurchases() { const el = document.getElementById('admin-pending-list'); if (!el) return; // Load from localStorage (all pending purchases submitted from this browser) const allPending = JSON.parse(localStorage.getItem('p2w_pending') || '[]').filter(p => p.status === 'pending'); const badge = document.getElementById('pending-badge'); if (badge) { badge.textContent = allPending.length > 0 ? allPending.length : ''; badge.style.display = allPending.length > 0 ? 'inline-flex' : 'none'; } if (!allPending.length) { el.innerHTML = '
No pending purchases
'; return; } el.innerHTML = allPending.map(p => `
#${p.id} Pending
${p.discord}
${p.method.toUpperCase()} · €${p.price} · ${new Date(p.timestamp).toLocaleString('nl-BE')}
🪙 ${p.tokens}
`).join(''); } function approvePurchase(id) { const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (!p) return; // Add tokens to user wallet S.tokens += p.tokens; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'deposit', name:`Token purchase approved — ${p.tokens} tokens via ${p.method}`, amount: p.tokens, currency:'tokens' }); // Mark as approved p.status = 'approved'; localStorage.setItem('p2w_pending', JSON.stringify(all)); // Remove from DOM document.getElementById('pending-' + id)?.remove(); toast(`✓ Approved — 🪙${p.tokens} tokens added for ${p.discord}!`, 'ok'); updateNavTokens(); renderPendingPurchases(); } function rejectPurchase(id) { if (!confirm('Reject this purchase request?')) return; const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (p) p.status = 'rejected'; localStorage.setItem('p2w_pending', JSON.stringify(all)); document.getElementById('pending-' + id)?.remove(); toast(`Purchase #${id} rejected.`, 'info'); renderPendingPurchases(); } function adminToggleMatch(id, status) { const m = S.matches.find(x => x.id === id); if (!m) return; m.status = m.status === 'live' ? 'waiting' : 'live'; renderMatches(); loadAdminData(); toast(`Match "${m.title}" → ${m.status.toUpperCase()}`, 'ok'); } function adminCancelMatch(id) { if (!confirm('Cancel this match?')) return; S.matches = S.matches.filter(m => m.id !== id); renderMatches(); loadAdminData(); toast('Match cancelled.', 'ok'); } function openCreateMatch() { document.getElementById('cm-overlay').classList.add('show'); } function createMatchAdmin() { const mode = document.getElementById('cm-mode').value; const fee = parseInt(document.getElementById('cm-fee').value) || 5; const max = parseInt(document.getElementById('cm-max').value) || 10; const title= document.getElementById('cm-title').value.trim() || `${MODES[mode]} #${Date.now().toString().slice(-4)}`; S.matches.unshift({ id: Date.now(), mode, title, entryFee:fee, players:0, maxPlayers:max, prize:Math.round(fee*max*0.9), status:'waiting' }); document.getElementById('cm-overlay').classList.remove('show'); toast('✓ Match created!', 'ok'); renderMatches(); loadAdminData(); } // ═══════════════════════════════════════════════════ // BATTLEPASS ADMIN (JSONBin) // ═══════════════════════════════════════════════════ async function loadBPAdminData() { try { const bpData = await getBPData(); const players = Object.values(bpData.players || {}); const total = players.length, prem = players.filter(p=>p.premium).length; const avgLvl = total ? Math.round(players.reduce((a,p)=>a+bpLevel(p.xp),0)/total) : 0; const ta=document.getElementById('bpa-total');if(ta)ta.textContent=total; const pr=document.getElementById('bpa-prem'); if(pr)pr.textContent=prem; const al=document.getElementById('bpa-lvl'); if(al)al.textContent=avgLvl; const tbody = document.getElementById('bp-admin-tbody'); if (tbody) { const keys = Object.keys(bpData.players||{}).sort(); if (!keys.length) { tbody.innerHTML = 'No players yet'; } else { tbody.innerHTML = keys.map(key => { const p = bpData.players[key], lv = bpLevel(p.xp); return ` ${p.discord} ${lv} ${p.xp.toLocaleString()} ${p.premium?'':'Free'} `; }).join(''); } } const logEl = document.getElementById('bp-xp-log'); if (logEl) { const log = (bpData.xpLog||[]).slice(-20).reverse(); logEl.innerHTML = log.length ? log.map(e => `
${e.discord}
${e.reason||'Match'} · ${e.date}
+${e.xp} XP
`).join('') : '
No events yet
'; } } catch(e) { toast('BP load error: ' + e.message, 'err'); } } function filterBPTable(q) { document.querySelectorAll('#bp-admin-tbody tr[data-bpkey]').forEach(r => { r.style.display = r.dataset.bpname?.includes(q.toLowerCase()) ? '' : 'none'; }); } function openBPAddXP(discord) { document.getElementById('bpxp-player').value = discord || ''; document.getElementById('bpxp-amount').value = 250; document.getElementById('bpxp-reason').value = ''; document.getElementById('bpxp-overlay').classList.add('show'); } async function submitBPXP() { const discord = document.getElementById('bpxp-player').value.trim(); const xp = parseInt(document.getElementById('bpxp-amount').value); const reason = document.getElementById('bpxp-reason').value.trim() || 'Match played'; if (!discord || !xp || xp < 1) { toast('Fill all fields.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (!bpData.players[key]) { bpData.players[key] = { discord, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; } const before = bpData.players[key].xp; bpData.players[key].xp += xp; if (reason.toLowerCase().includes('win')) bpData.players[key].wins = (bpData.players[key].wins||0)+1; bpData.players[key].matches = (bpData.players[key].matches||0)+1; bpData.xpLog = bpData.xpLog || []; bpData.xpLog.push({ discord, xp, reason, date:new Date().toISOString().slice(0,10) }); if (bpData.xpLog.length > 200) bpData.xpLog = bpData.xpLog.slice(-200); await saveBPData(bpData); const lvBefore = bpLevel(before), lvAfter = bpLevel(bpData.players[key].xp); document.getElementById('bpxp-overlay').classList.remove('show'); toast(lvAfter > lvBefore ? `⭐ ${discord} +${xp}XP — Level UP! ${lvBefore}→${lvAfter}` : `⚡ ${discord} +${xp}XP (Level ${lvAfter})`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: ' + e.message, 'err'); } } async function bpGrantPremium(key, discord) { if (!confirm(`Grant Premium to ${discord}?`)) return; try { const bpData = await getBPData(); if (!bpData.players[key]) { toast('Player not found.', 'err'); return; } bpData.players[key].premium = true; await saveBPData(bpData); toast(`⭐ Premium granted to ${discord}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } async function bpRemovePlayer(key, discord) { if (!confirm(`Remove ${discord}?`)) return; try { const bpData = await getBPData(); delete bpData.players[key]; await saveBPData(bpData); toast(`${discord} removed.`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } function openBPAddPlayer() { document.getElementById('bpadd-discord').value = ''; document.getElementById('bpadd-pass').value = 'free'; document.getElementById('bpadd-overlay').classList.add('show'); } async function submitBPAddPlayer() { const discord = document.getElementById('bpadd-discord').value.trim(); const pass = document.getElementById('bpadd-pass').value; if (!discord) { toast('Enter Discord name.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (bpData.players[key]) { toast('Player already exists.', 'err'); return; } bpData.players[key] = { discord, xp:0, premium:pass==='premium', wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; await saveBPData(bpData); document.getElementById('bpadd-overlay').classList.remove('show'); toast(`✓ ${discord} added${pass==='premium'?' with Premium':''}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } // ═══════════════════════════════════════════════════ // MODAL HELPERS // ═══════════════════════════════════════════════════ function closeModal(id) { document.getElementById(id)?.classList.remove('show'); } // ═══════════════════════════════════════════════════ // TOAST // ═══════════════════════════════════════════════════ let _tt; function toast(msg, type='ok') { clearTimeout(_tt); const t=document.getElementById('toast'); t.className='toast '+type; document.getElementById('toast-ico').textContent=type==='ok'?'✓':type==='err'?'✕':'ℹ'; document.getElementById('toast-msg').textContent=msg; t.classList.add('show'); _tt=setTimeout(()=>t.classList.remove('show'),4500); } // ═══════════════════════════════════════════════════ // BOOT // ═══════════════════════════════════════════════════ (function boot() { // Restore user const savedUser = localStorage.getItem('p2w_user'); if (savedUser) { try { S.user = JSON.parse(savedUser); updateNavUser(); } catch {} } // Restore settings toggles const settings = S.settings; Object.keys(settings).forEach(id => { const el = document.getElementById(id); if (el && el.classList.contains('toggle')) { if (settings[id]) el.classList.add('on'); else el.classList.remove('on'); } }); // Handle Discord OAuth return const params = new URLSearchParams(window.location.search); if (params.get('token')) { localStorage.setItem('p2w_token', params.get('token')); S.token = params.get('token'); history.replaceState({}, '', window.location.pathname); toast('✓ Logged in via Discord!', 'ok'); } if (params.get('payment') === 'success') { toast('🎉 Payment confirmed! Check Discord for your lobby code.', 'ok'); history.replaceState({}, '', window.location.pathname); } // Init pages renderBPPublicTrack(); goPage('home'); })(); ', // ─── Season ─────────────────────────────────────────────────────── SEASON_END: new Date('2025-06-30T23:59:59'), // ─── BP ─────────────────────────────────────────────────────────── XP_PER_LEVEL: 1000, MAX_LEVEL: 100, // ─── Economy ────────────────────────────────────────────────────── // TOKENS = real money (€1 = 1 Token). Used for match entry fees. // COINS = earned/rewarded currency. Used in the shop for cosmetics. HOUSE_CUT: 0.10, WITHDRAW_FEE_PCT: 0.05, WITHDRAW_FEE_FLAT: 1, // ─── Your Payment Details ───────────────────────────────────────── PAY: { paypal: { link: 'https://paypal.me/JOUW_NAAM' }, bank: { iban: 'BE00 0000 0000 0000', name: 'Jouw Naam', bic: 'GEBABEBB' }, revolut: { link: 'https://revolut.me/JOUW_TAG' }, }, }; // ═══════════════════════════════════════════════════ // STATE // ═══════════════════════════════════════════════════ const S = { user: null, token: localStorage.getItem('p2w_token'), // TOKENS = real money (€1 each). Used for match entry fees. tokens: parseFloat(localStorage.getItem('p2w_tokens') || '0'), // COINS = earned/rewarded. Used in the shop for cosmetics. coins: parseFloat(localStorage.getItem('p2w_coins') || '0'), socket: null, matches: [], leaderboard: [], battlepass: null, currentMatch: null, payMethod: 'paypal', bpPayMethod: 'paypal', tokPayMethod: 'paypal', shopFilter: 'all', currentShopItem: null, // Pending token purchase requests (waiting for admin approval) pendingPurchases: JSON.parse(localStorage.getItem('p2w_pending') || '[]'), settings: JSON.parse(localStorage.getItem('p2w_settings') || '{}'), bankDetails: JSON.parse(localStorage.getItem('p2w_bank') || '{}'), transactions: JSON.parse(localStorage.getItem('p2w_tx') || '[]'), matchHistory: JSON.parse(localStorage.getItem('p2w_mh') || '[]'), ownedCosmetics: JSON.parse(localStorage.getItem('p2w_cosmetics') || '[]'), equippedCosmetics: JSON.parse(localStorage.getItem('p2w_equipped') || '{}'), streamerMode: false, filterMode: 'all', }; // ═══════════════════════════════════════════════════ // JSONBIN // ═══════════════════════════════════════════════════ const BIN_URL = `https://api.jsonbin.io/v3/b/${CFG.BIN_ID}`; async function binGet() { const r = await fetch(BIN_URL + '/latest', { headers: { 'X-Master-Key': CFG.API_KEY } }); if (!r.ok) throw new Error('JSONBin read failed (' + r.status + ')'); return (await r.json()).record; } async function binPut(data) { const r = await fetch(BIN_URL, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Master-Key': CFG.API_KEY }, body: JSON.stringify(data), }); if (!r.ok) throw new Error('JSONBin write failed (' + r.status + ')'); return r.json(); } async function getBPData() { const d = await binGet(); if (!d.battlepass) { await binPut({ ...d, battlepass: { players: {}, xpLog: [], season: 1 } }); return { players: {}, xpLog: [], season: 1 }; } return d.battlepass; } async function saveBPData(bp) { const d = await binGet(); d.battlepass = bp; await binPut(d); } // ═══════════════════════════════════════════════════ // REWARD TRACK // ═══════════════════════════════════════════════════ function buildBPTrack() { // COINS = shop currency (earned via battlepass) // TOKENS = real money (€1 each), only rare milestone rewards const t = []; for (let i = 1; i <= CFG.MAX_LEVEL; i++) { // Default: coins reward (not real money) const coinAmt = i<=25?5:i<=50?10:i<=75?15:20; let r = { level:i, icon:'🥇', label:`${coinAmt} Coins`, type:'coins', premium:false }; if (i===5) r={level:i,icon:'⚡',label:'XP Boost', type:'xp', premium:false}; if (i===10) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // moved from higher if (i===15) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // replaced -10% entry if (i===20) r={level:i,icon:'🏅',label:'Free Badge', type:'badge', premium:false}; if (i===25) r={level:i,icon:'💎',label:'Premium Role', type:'role', premium:true}; if (i===30) r={level:i,icon:'🥇',label:'30 Coins', type:'coins', premium:true}; if (i===40) r={level:i,icon:'⚡',label:'XP Boost ×2', type:'xp', premium:false}; if (i===50) r={level:i,icon:'🥇',label:'50 Coins', type:'coins', premium:true}; if (i===55) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:false}; // real €1 if (i===60) r={level:i,icon:'🥇',label:'75 Coins', type:'coins', premium:true}; if (i===75) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:true}; // replaced -20% entry if (i===100)r={level:i,icon:'👑',label:'5 Tokens', type:'token', premium:true}; // real €5 t.push(r); } return t; } const BP_TRACK = buildBPTrack(); function bpLevel(xp){ return Math.min(Math.floor(xp/CFG.XP_PER_LEVEL)+1, CFG.MAX_LEVEL); } function bpPct(xp) { return Math.floor(((xp%CFG.XP_PER_LEVEL)/CFG.XP_PER_LEVEL)*100); } function bpNorm(n) { return n.trim().toLowerCase(); } function avatarColor(n){ let h=0; for(const c of n)h=c.charCodeAt(0)+((h<<5)-h); return `hsl(${Math.abs(h)%360},55%,35%)`; } function initials(n){ return n.slice(0,2).toUpperCase(); } // ═══════════════════════════════════════════════════ // NAV // ═══════════════════════════════════════════════════ function goPage(p) { document.querySelectorAll('.page').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-link,.drawer-link').forEach(el => el.classList.remove('active')); document.getElementById('page-' + p)?.classList.add('active'); document.querySelectorAll(`[data-page="${p}"]`).forEach(el => el.classList.add('active')); window.scrollTo(0, 0); if (p === 'matches') loadMatches(); if (p === 'lb') loadLB(); if (p === 'bp') loadBPPage(); if (p === 'shop') loadShop(); if (p === 'wallet') loadWallet(); if (p === 'profile') loadProfile(); if (p === 'admin') renderAdminGate(); updateNavTokens(); } function toggleDrawer() { const d = document.getElementById('nav-drawer'); const b = document.getElementById('hamburger'); d.classList.toggle('open'); b.classList.toggle('open'); } function closeDrawer() { document.getElementById('nav-drawer').classList.remove('open'); document.getElementById('hamburger').classList.remove('open'); } // ═══════════════════════════════════════════════════ // AUTH (simulated - Discord OAuth needs backend) // ═══════════════════════════════════════════════════ function loginWithDiscord() { // Simulated login for demo const name = prompt('Enter your Discord username to continue (demo):'); if (!name) return; S.user = { username: name, discordId: 'demo_' + Date.now(), avatar: null }; localStorage.setItem('p2w_user', JSON.stringify(S.user)); updateNavUser(); toast('✓ Welcome to P2W, ' + name + '!', 'ok'); } function updateNavUser() { if (!S.user) return; document.getElementById('login-btn').style.display = 'none'; document.getElementById('nav-avatar-btn').style.display = 'flex'; document.getElementById('nav-bal').style.display = 'flex'; const init = initials(S.streamerMode ? 'P2W Player' : S.user.username); document.getElementById('nav-av-initials').textContent = init; updateNavTokens(); } function updateNavTokens() { const display = document.getElementById('nav-tokens'); if (display) { if (S.streamerMode && S.settings['stream-hide-bal'] !== false) { display.innerHTML = '****'; } else { display.innerHTML = `🪙${S.tokens.toFixed(0)} | 🥇${S.coins.toFixed(0)}`; } } // Shop coins display const sd = document.getElementById('shop-coins-display'); if (sd) sd.textContent = S.coins.toFixed(0); // Wallet const wb = document.getElementById('wallet-bal-display'); if (wb) wb.textContent = S.tokens.toFixed(2); const cbd = document.getElementById('wallet-coins-display'); if (cbd) cbd.textContent = S.coins.toFixed(0); } // ═══════════════════════════════════════════════════ // LIVE STATS // ═══════════════════════════════════════════════════ let statsOnline = 247, statsPayout = 284; function tickStats() { statsOnline += Math.floor(Math.random() * 5) - 2; if (statsOnline < 100) statsOnline = 100; statsPayout += Math.floor(Math.random() * 3); const activeMatches = S.matches.filter(m => m.status === 'live').length; ['s-online','s-active','s-payouts'].forEach((id, i) => { const el = document.getElementById(id); if (!el) return; if (i === 0) el.textContent = statsOnline; if (i === 1) el.textContent = activeMatches; if (i === 2) el.textContent = '€' + statsPayout; }); const mc = document.getElementById('match-count'); if (mc) mc.textContent = `${activeMatches} live · ${S.matches.filter(m=>m.status==='waiting').length} open`; } setInterval(tickStats, 4000); tickStats(); // ═══════════════════════════════════════════════════ // SEASON COUNTDOWN // ═══════════════════════════════════════════════════ function updateCountdowns() { // Battlepass const bc = document.getElementById('bp-countdown'); if (bc) { const diff = CFG.SEASON_END - new Date(); if (diff > 0) { const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000); bc.textContent = `${d}d ${h}h remaining`; } } // Leaderboard const lc = document.getElementById('lb-countdown'); if (lc) { const now = new Date(), next = new Date(now); next.setDate(now.getDate() + ((8 - now.getDay()) % 7 || 7)); next.setHours(0,0,0,0); const diff = next - now; const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000), m = Math.floor((diff%3600000)/60000); lc.textContent = `${d}d ${h}h ${m}m`; } } setInterval(updateCountdowns, 60000); updateCountdowns(); // ═══════════════════════════════════════════════════ // MATCHES // ═══════════════════════════════════════════════════ const MODES = { realistics:'Realistics', boxfights:'Boxfights', scrims:'Scrims', zonewars:'Zonewars' }; const ICONS = { realistics:'🎯', boxfights:'📦', scrims:'⚔️', zonewars:'🌀' }; S.matches = [ { id:1, mode:'realistics', title:'EU Realistics #14', entryFee:5, players:7, maxPlayers:10, prize:45, status:'waiting' }, { id:2, mode:'realistics', title:'EU Realistics #13', entryFee:10, players:10, maxPlayers:10, prize:90, status:'live' }, { id:3, mode:'boxfights', title:'Boxfight Cup #7', entryFee:2, players:6, maxPlayers:16, prize:28, status:'waiting' }, { id:4, mode:'scrims', title:'EU Scrims #5', entryFee:10, players:80, maxPlayers:100, prize:800, status:'live' }, { id:5, mode:'boxfights', title:'Boxfight Ladder', entryFee:5, players:3, maxPlayers:8, prize:35, status:'waiting' }, { id:6, mode:'scrims', title:'EU Scrims #6', entryFee:5, players:22, maxPlayers:100, prize:200, status:'waiting' }, { id:7, mode:'zonewars', title:'Zonewars Alpha', entryFee:2, players:4, maxPlayers:6, prize:10, status:'waiting' }, ]; function loadMatches() { renderMatches(); } function filterMatch(mode, btn) { S.filterMode = mode; document.querySelectorAll('.fb').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderMatches(); } function renderMatches() { const grid = document.getElementById('matches-grid'); if (!grid) return; const list = S.filterMode === 'all' ? S.matches : S.matches.filter(m => m.mode === S.filterMode); if (!list.length) { grid.innerHTML = `
No matches in this mode right now.
`; return; } grid.innerHTML = list.map(m => { const pct = Math.round((m.players / m.maxPlayers) * 100); const full = m.players >= m.maxPlayers; const live = m.status === 'live'; const cut = Math.round(m.entryFee * m.maxPlayers * CFG.HOUSE_CUT); return `
${ICONS[m.mode]} ${MODES[m.mode]} ${live ? ` LIVE` : full ? `FULL` : `● OPEN`}
${m.title}
Hosted by P2W Staff
🪙${m.entryFee}
Entry
🪙${m.prize}
Prize
${m.players}/${m.maxPlayers}
Players
${m.players} / ${m.maxPlayers} players${pct}%
${full || live ? `` : ``}
House cut: 🪙${cut} · Net prize: 🪙${m.prize - cut}
`; }).join(''); } function joinMatch(id, mode, title, fee) { S.currentMatch = { id, mode, title, fee }; document.getElementById('m-mode').textContent = MODES[mode] || mode; document.getElementById('m-title').textContent = title; document.getElementById('m-price').textContent = '🪙' + fee; document.getElementById('f-duo-wrap').style.display = mode === 'scrims' ? 'block' : 'none'; document.getElementById('f-discord').value = S.user?.username || ''; const balEl = document.getElementById('join-balance-display'); if (balEl) balEl.textContent = `🪙 ${S.tokens.toFixed(0)} tokens`; // Warn if not enough const btn = document.getElementById('pay-btn'); if (S.tokens < fee) { if (btn) { btn.textContent = '⚠ Not enough tokens — Buy more'; btn.style.background = 'rgba(239,68,68,.15)'; btn.style.border = '1px solid rgba(239,68,68,.3)'; btn.style.color = 'var(--red)'; } } else { if (btn) { btn.textContent = '🪙 Pay & Join →'; btn.style.background = ''; btn.style.border = ''; btn.style.color = ''; } } document.getElementById('pay-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // LEADERBOARD // ═══════════════════════════════════════════════════ const SAMPLE_LB = [ {rank:1,name:'NexusEU', earn:312,wins:61,wr:81,ch:'+2',up:true}, {rank:2,name:'ZenithKO', earn:147,wins:38,wr:74,ch:'—', up:null}, {rank:3,name:'FrostRapide',earn:98,wins:27,wr:68,ch:'-1',up:false}, {rank:4,name:'ShadowLoop', earn:76,wins:21,wr:65,ch:'+3',up:true}, {rank:5,name:'KrypticFN', earn:62,wins:19,wr:59,ch:'-2',up:false}, {rank:6,name:'BlitzRunner',earn:55,wins:15,wr:62,ch:'+1',up:true}, {rank:7,name:'VoidCraft', earn:44,wins:12,wr:57,ch:'—', up:null}, {rank:8,name:'ApexRoll', earn:37,wins:10,wr:54,ch:'+4',up:true}, {rank:9,name:'DriftKing', earn:29,wins:8, wr:50,ch:'-1',up:false}, {rank:10,name:'TempestEU', earn:18,wins:5, wr:45,ch:'+2',up:true}, ]; function loadLB() { const pod = document.getElementById('lb-podium'); const [s1,s2,s3] = [SAMPLE_LB[1], SAMPLE_LB[0], SAMPLE_LB[2]]; const crowns = ['🥈','👑','🥉']; const podData = [s1,s2,s3]; const cls = ['','p1','']; if (pod) pod.innerHTML = podData.map((p,i) => { const hue = (p.rank*47)%360; return `
${crowns[i]}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}W · ${p.wr}% WR
`; }).join(''); document.getElementById('lb-rows').innerHTML = SAMPLE_LB.map(p => { const hue = (p.rank*47)%360; return `
${p.rank<=3?'★':p.rank}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}
${p.wr}%
${p.up===true?'▲':p.up===false?'▼':'─'} ${p.ch}
`; }).join(''); } // ═══════════════════════════════════════════════════ // BATTLEPASS // ═══════════════════════════════════════════════════ let bpCurrentPlayer = null, bpCurrentKey = null; function loadBPPage() { renderBPPublicTrack(); renderPasses(); if (bpCurrentPlayer) renderBPCard(bpCurrentPlayer); } function renderBPPublicTrack() { const el = document.getElementById('bp-public-track'); if (!el) return; const milestones = BP_TRACK.filter(r => r.level % 5 === 0 || r.level === 1); el.innerHTML = milestones.map(r => { const isToken = r.type === 'token'; const borderC = isToken ? 'rgba(240,180,41,.35)' : r.premium ? 'rgba(168,85,247,.2)' : 'var(--border)'; const glowC = isToken ? 'rgba(240,180,41,.08)' : 'transparent'; return `
${isToken ? '
' : ''} ${r.premium && !isToken ? '
PRO
' : ''}
${r.icon}
Lv ${r.level}
${r.label}
${isToken ? '
real money
' : r.type==='coins' ? '
shop coins
' : ''}
`; }).join(''); } async function bpLookup() { const raw = document.getElementById('bp-inp').value.trim(); if (!raw) { toast('Enter your Discord name.', 'err'); return; } const btn = document.getElementById('bp-lookup-btn'); btn.disabled = true; btn.innerHTML = '
'; try { const data = await getBPData(); const key = bpNorm(raw); let player = data.players[key]; if (!player) { player = { discord:raw, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; data.players[key] = player; await saveBPData(data); toast('✓ Battlepass profile created!', 'ok'); } bpCurrentPlayer = player; bpCurrentKey = key; renderBPCard(player); } catch(e) { toast('Error: ' + e.message, 'err'); } btn.disabled = false; btn.textContent = 'Look Up →'; } function bpReset() { document.getElementById('bp-lookup-box').style.display = 'block'; document.getElementById('bp-loaded-box').style.display = 'none'; document.getElementById('bp-claimable-section').style.display = 'none'; document.getElementById('bp-inp').value = ''; bpCurrentPlayer = null; bpCurrentKey = null; } function renderBPCard(p) { const lv = bpLevel(p.xp), pct = bpPct(p.xp), prog = p.xp % CFG.XP_PER_LEVEL; const isPrem = p.premium, claimed = new Set(p.claimedRewards || []); const claimable = BP_TRACK.filter(r => r.level <= lv && (!r.premium || isPrem) && !claimed.has(r.level)); document.getElementById('bp-lookup-box').style.display = 'none'; document.getElementById('bp-loaded-box').style.display = 'block'; const av = document.getElementById('bp-avatar'); av.textContent = initials(p.discord); av.style.background = avatarColor(p.discord); av.style.borderRadius = '50%'; av.style.width = '36px'; av.style.height = '36px'; av.style.display = 'flex'; av.style.alignItems = 'center'; av.style.justifyContent = 'center'; const displayName = S.streamerMode ? 'P2W Player' : p.discord; document.getElementById('bp-username').textContent = displayName; document.getElementById('bp-pass-badge').innerHTML = isPrem ? `⭐ Premium` : `Free`; document.getElementById('bp-level').textContent = lv; document.getElementById('bp-level-label').textContent = `Level ${lv} / 100`; document.getElementById('bp-xp-label').textContent = `${prog.toLocaleString('en')} / 1000 XP`; setTimeout(() => { const f = document.getElementById('bp-xp-fill'); if(f) f.style.width = pct + '%'; }, 80); const hint = document.getElementById('bp-claim-hint'); if (hint) hint.style.display = claimable.length > 0 ? 'block' : 'none'; if (claimable.length > 0) { document.getElementById('bp-claimable-section').style.display = 'block'; document.getElementById('bp-claimable-list').innerHTML = claimable.map(r => `
${r.icon}
Lv ${r.level} — ${r.label}
Contact staff in Discord to claim
`).join(''); } } function renderPasses() { const el = document.getElementById('passes-grid'); if (!el) return; el.innerHTML = `
FREE PASS
For everyone
FREE
🪙
50 tokens across 100 levels
XP boosts at milestones
📊
Full stats tracking
🏅
Free badge on Discord
⭐ PREMIUM
Season 1 — €10 one-time
€10
💰
€15+ value in tokens & credits
🎟
Up to 20% off match entry fees
🥇
Priority queue access
📈
+10% earnings boost on wins
💎
Exclusive Discord role + gold name
`; } function purchaseBP() { document.getElementById('bp-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // SHOP // ═══════════════════════════════════════════════════ const SHOP_ITEMS = [ // Badges { id:'b1', cat:'badge', icon:'💀', name:'Death Dealer', desc:'For those who eliminate first.', rarity:'rare', price:20, bgColor:'#1a0f0f' }, { id:'b2', cat:'badge', icon:'👑', name:'Champion Crown', desc:'Earned by top seasonal players.', rarity:'legendary', price:150, bgColor:'#1a1500' }, { id:'b3', cat:'badge', icon:'🔥', name:'On Fire', desc:'You\'re unstoppable right now.', rarity:'epic', price:75, bgColor:'#1a0d00' }, { id:'b4', cat:'badge', icon:'⚡', name:'Speed Demon', desc:'Quick entries, quick exits.', rarity:'rare', price:30, bgColor:'#0d0f1a' }, { id:'b5', cat:'badge', icon:'🎯', name:'Sharpshooter', desc:'Precision over brute force.', rarity:'common', price:10, bgColor:'#0f1a0f' }, // Borders { id:'bo1',cat:'border',icon:'🟦', name:'Blue Holo', desc:'Holographic blue profile border.', rarity:'rare', price:40, bgColor:'#0d1020' }, { id:'bo2',cat:'border',icon:'🟨', name:'Gold Crown', desc:'Legendary golden border frame.', rarity:'legendary', price:200, bgColor:'#1a1200' }, { id:'bo3',cat:'border',icon:'🟣', name:'Purple Haze', desc:'Epic purple animated border.', rarity:'epic', price:80, bgColor:'#110d1a' }, // Titles { id:'t1', cat:'title', icon:'📛', name:'The Untouchable', desc:'Show them you\'re unreachable.', rarity:'epic', price:60, bgColor:'#0d1015' }, { id:'t2', cat:'title', icon:'📛', name:'Cash King', desc:'Earnings speak louder than words.',rarity:'legendary', price:120, bgColor:'#1a1000' }, { id:'t3', cat:'title', icon:'📛', name:'Grinder', desc:'No days off. No excuses.', rarity:'common', price:15, bgColor:'#0f1015' }, { id:'t4', cat:'title', icon:'📛', name:'EU Certified', desc:'Representing EU at the highest.', rarity:'rare', price:35, bgColor:'#0d1520' }, // Effects { id:'e1', cat:'effect',icon:'✨', name:'Gold Sparkle', desc:'Golden particles on your profile.',rarity:'legendary', price:175, bgColor:'#1a1500' }, { id:'e2', cat:'effect',icon:'⚡', name:'Electric Aura', desc:'Electrical energy around cards.', rarity:'epic', price:90, bgColor:'#0d0f20' }, { id:'e3', cat:'effect',icon:'💨', name:'Smoke Trail', desc:'Subtle smoke effect, very clean.', rarity:'rare', price:45, bgColor:'#0f0f0f' }, // Emotes { id:'em1',cat:'emote', icon:'💸', name:'Money Rain', desc:'It\'s raining tokens.', rarity:'epic', price:55, bgColor:'#0f1a0d' }, { id:'em2',cat:'emote', icon:'🤝', name:'GG Shake', desc:'Respect after a good game.', rarity:'common', price:12, bgColor:'#0d1015' }, { id:'em3',cat:'emote', icon:'😈', name:'Villain Laugh', desc:'When you pop off on them.', rarity:'rare', price:38, bgColor:'#1a0d0d' }, ]; const RARITY_COLORS = { common:'#6b7299', rare:'var(--blue)', epic:'var(--purple)', legendary:'var(--gold)' }; function loadShop() { updateNavTokens(); renderShopGrid(); } function filterShop(cat, btn) { S.shopFilter = cat; document.querySelectorAll('.shop-cat').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderShopGrid(); } function renderShopGrid() { const grid = document.getElementById('shop-grid'); if (!grid) return; const items = S.shopFilter === 'all' ? SHOP_ITEMS : SHOP_ITEMS.filter(i => i.cat === S.shopFilter); grid.innerHTML = items.map(item => { const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; return `
${equipped ? '
Equipped
' : owned ? '
Owned
' : ''}
${item.icon}
${item.rarity.toUpperCase()}
${item.name}
${item.desc}
🥇 ${item.price} coins
${owned ? `
${equipped?'Equipped':'Own'}
` : ''}
`; }).join(''); } function openShopItem(id) { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return; S.currentShopItem = item; const owned = S.ownedCosmetics.includes(id); const equipped = S.equippedCosmetics[item.cat] === id; document.getElementById('shop-buy-preview').textContent = item.icon; document.getElementById('shop-buy-title').textContent = item.name; document.getElementById('shop-buy-desc').innerHTML = ` ${item.rarity.toUpperCase()}
${item.desc}`; document.getElementById('shop-buy-price').textContent = '🥇 ' + item.price + ' coins'; document.getElementById('shop-buy-balance').textContent = '🥇 ' + S.coins.toFixed(0) + ' coins'; const btn = document.querySelector('#shop-overlay .btn-gold'); if (btn) { if (equipped) { btn.textContent = '✓ Already Equipped'; btn.disabled = true; } else if (owned) { btn.textContent = 'Equip Item'; btn.disabled = false; } else { btn.textContent = '🛍 Purchase Item'; btn.disabled = false; } } document.getElementById('shop-overlay').classList.add('show'); } function confirmShopBuy() { const item = S.currentShopItem; if (!item) return; const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; if (equipped) { toast('Already equipped.', 'info'); return; } if (owned) { // Just equip S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); toast(`✓ ${item.name} equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); return; } if (S.coins < item.price) { toast('Not enough coins. Earn coins via Battlepass and matches!', 'err'); return; } S.coins -= item.price; S.ownedCosmetics.push(item.id); S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_coins', S.coins.toString()); localStorage.setItem('p2w_cosmetics', JSON.stringify(S.ownedCosmetics)); localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); addTx({ type:'buy', name:item.name, amount:-item.price, currency:'coins' }); toast(`✓ ${item.name} purchased and equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); updateNavTokens(); } // ═══════════════════════════════════════════════════ // WALLET // ═══════════════════════════════════════════════════ const TOKEN_PACKS = [ { tokens:5, price:5, label:'Starter Pack' }, { tokens:10, price:10, label:'Player Pack', popular:true }, { tokens:20, price:20, label:'Grinder Pack' }, { tokens:50, price:50, label:'Competitor Pack'}, { tokens:100, price:100, label:'Elite Pack' }, ]; let walletMethod = 'paypal'; let currentPack = null; function loadWallet() { updateWalletDisplay(); renderTokenPacks(); renderWalletTx(); // Check if bank details are saved const bank = S.bankDetails; const filled = document.getElementById('bank-details-filled'); const status = document.getElementById('bank-details-status'); const ibanDisplay = document.getElementById('bank-iban-display'); if (bank.iban) { if (filled) filled.style.display = 'block'; if (status) status.style.display = 'none'; if (ibanDisplay) ibanDisplay.textContent = S.streamerMode ? '****' : bank.iban; } } function updateWalletDisplay() { const tokenDisplay = S.streamerMode ? '****' : S.tokens.toFixed(2); const coinDisplay = S.streamerMode ? '****' : S.coins.toFixed(0); const bal = document.getElementById('wallet-bal-display'); const locked = document.getElementById('wallet-locked-display'); const wt = document.getElementById('w-total'); const wl = document.getElementById('w-locked'); const wa = document.getElementById('w-available'); const cbd = document.getElementById('wallet-coins-display'); if (bal) bal.textContent = tokenDisplay; if (locked) locked.textContent = `Locked: 0 · Available: ${S.streamerMode ? '****' : S.tokens.toFixed(0)} Tokens`; if (wt) wt.textContent = tokenDisplay; if (wl) wl.textContent = '0.00'; if (wa) wa.textContent = tokenDisplay; if (cbd) cbd.textContent = coinDisplay; updateNavTokens(); } function renderTokenPacks() { const list = document.getElementById('token-packs-list'); if (!list) return; list.innerHTML = TOKEN_PACKS.map(p => `
${p.popular ? '' : ''}
🪙
🪙 ${p.tokens} Tokens
${p.label}
€${p.price}.00
`).join(''); } function setWalletMethod(btn, method) { document.querySelectorAll('#wallet-pay-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); walletMethod = method; } function switchWalletTab(tab) { ['purchase','withdraw','history','redeem'].forEach(t => { const el = document.getElementById('wallet-' + t); const btn = document.getElementById('wt-' + t); if (el) el.style.display = t === tab ? 'block' : 'none'; if (btn) btn.classList.toggle('active', t === tab); }); } function openTokenPurchase(tokens, price, label) { currentPack = { tokens, price, label }; document.getElementById('tok-amount-display').textContent = `${tokens} Tokens`; document.getElementById('tok-price-display').textContent = `€${price}`; document.getElementById('tok-discord').value = S.user?.username || ''; setTokMethod(document.querySelector('#tok-methods .pay-m'), 'paypal'); document.getElementById('token-overlay').classList.add('show'); } let tokMethod = 'paypal'; function setTokMethod(btn, method) { document.querySelectorAll('#tok-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); tokMethod = method; const details = document.getElementById('tok-pay-details'); const pack = currentPack || { price: 0 }; const note = `P2W Tokens | ${document.getElementById('tok-discord').value || 'YourName'} | €${pack.price}`; if (method === 'paypal') { details.innerHTML = `
Send via PayPal
Link${CFG.PAY.paypal.link}
Note${note}
`; } else if (method === 'bank') { details.innerHTML = `
Bank Transfer
Name${CFG.PAY.bank.name}
IBAN${CFG.PAY.bank.iban}
Reference${note}
`; } else { details.innerHTML = `
Send via Revolut
Link${CFG.PAY.revolut.link}
Note${note}
`; } } function confirmTokenPurchase() { const discord = document.getElementById('tok-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const pack = currentPack; const note = `P2W Tokens | ${discord} | €${pack.price}`; const purchaseId = 'P' + Date.now().toString().slice(-6); // Open payment if (tokMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/${pack.price}`, '_blank'); if (tokMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); // Save pending purchase const pending = { id: purchaseId, discord, tokens: pack.tokens, price: pack.price, method: tokMethod, note, timestamp: new Date().toISOString(), status: 'pending', }; S.pendingPurchases.push(pending); localStorage.setItem('p2w_pending', JSON.stringify(S.pendingPurchases)); // Send Discord webhook notification sendDiscordWebhook(pending); document.getElementById('token-overlay').classList.remove('show'); toast(`💸 Payment opened. Reference: #${purchaseId} · Send proof to Discord #deposits · Tokens added after confirmation!`, 'info'); } async function sendDiscordWebhook(purchase) { if (!CFG.DISCORD_WEBHOOK || CFG.DISCORD_WEBHOOK.includes('JOUW_WEBHOOK_URL')) return; try { await fetch(CFG.DISCORD_WEBHOOK, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'P2W Platform', avatar_url: 'https://i.imgur.com/4M34hi2.png', embeds: [{ title: '🪙 New Token Purchase Request', color: 0xf0b429, fields: [ { name: '👤 Discord', value: purchase.discord, inline: true }, { name: '🪙 Tokens', value: `${purchase.tokens} Tokens`, inline: true }, { name: '💶 Amount', value: `€${purchase.price}`, inline: true }, { name: '💳 Method', value: purchase.method.toUpperCase(), inline: true }, { name: '🆔 Ref ID', value: `#${purchase.id}`, inline: true }, { name: '🕐 Time', value: new Date(purchase.timestamp).toLocaleString('nl-BE'), inline: true }, { name: '📋 Note', value: `\`${purchase.note}\``, inline: false }, ], footer: { text: 'Go to Admin Panel → Pending Purchases to approve' }, timestamp: purchase.timestamp, }], }), }); } catch(e) { console.warn('Webhook failed:', e); } } function calcWithdrawal(val) { const amount = parseFloat(val) || 0; if (amount < 10) { document.getElementById('withdraw-calc').style.opacity = '.4'; return; } document.getElementById('withdraw-calc').style.opacity = '1'; const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, CFG.WITHDRAW_FEE_FLAT + 1); const receive = Math.max(amount - fee, 0); document.getElementById('wc-amount').textContent = amount; document.getElementById('wc-fee').textContent = fee; document.getElementById('wc-receive').textContent = receive.toFixed(2); } function submitWithdraw() { const amount = parseFloat(document.getElementById('withdraw-amount').value); if (!amount || amount < 10) { toast('Minimum withdrawal is 10 tokens.', 'err'); return; } if (amount > S.tokens) { toast('Insufficient token balance.', 'err'); return; } if (!S.bankDetails.iban) { toast('Add bank details first in Profile → Bank Details.', 'err'); return; } const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, 2); S.tokens -= amount; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'withdraw', name:'Withdrawal to ' + (S.bankDetails.iban || 'Bank'), amount: -amount, currency:'tokens' }); toast(`✓ Withdrawal request submitted. You'll receive €${(amount-fee).toFixed(2)} within 24h.`, 'ok'); document.getElementById('withdraw-amount').value = ''; updateWalletDisplay(); renderWalletTx(); } function redeemCode() { const code = document.getElementById('redeem-code').value.trim().toUpperCase(); if (!code) { toast('Enter a code.', 'err'); return; } // Demo codes const CODES = { 'P2WSTART': {coins:50}, 'WELCOME5': {coins:25}, 'ELITE10': {coins:100} }; if (CODES[code]) { const reward = CODES[code]; if (reward.coins) { S.coins += reward.coins; localStorage.setItem('p2w_coins', S.coins.toString()); addTx({ type:'deposit', name:'Code redeemed: ' + code, amount: reward.coins, currency:'coins' }); toast(`✓ Code redeemed! +🥇${reward.coins} coins added.`, 'ok'); } document.getElementById('redeem-code').value = ''; updateNavTokens(); } else { toast('Invalid or already used code.', 'err'); } } function addTx(tx) { S.transactions.unshift({ ...tx, date: new Date().toLocaleDateString('en-GB'), id: Date.now() }); if (S.transactions.length > 50) S.transactions = S.transactions.slice(0, 50); localStorage.setItem('p2w_tx', JSON.stringify(S.transactions)); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆', entry:'⚔️' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn', entry:'dep' }; el.innerHTML = S.transactions.map(tx => { const currency = tx.currency === 'coins' ? '🥇' : '🪙'; const label = tx.currency === 'coins' ? 'coins' : 'tokens'; return `
${icons[tx.type]||'•'}
${tx.name}
${tx.date} · ${label}
${tx.amount>=0?'+':''}${currency}${Math.abs(tx.amount)}
`; }).join(''); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } // ═══════════════════════════════════════════════════ // PROFILE // ═══════════════════════════════════════════════════ function loadProfile() { const user = S.user || { username: 'Guest' }; const displayName = S.streamerMode ? 'P2W Player' : user.username; const av = document.getElementById('profile-av-text'); const avEl = document.getElementById('profile-avatar'); if (av) av.textContent = initials(displayName); if (avEl) avEl.style.background = avatarColor(user.username); const pn = document.getElementById('profile-name'); if (pn) pn.textContent = displayName; const pj = document.getElementById('profile-joined'); if (pj) pj.textContent = 'Member since ' + new Date().toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }); document.getElementById('prof-level').textContent = bpCurrentPlayer ? bpLevel(bpCurrentPlayer.xp) : '—'; document.getElementById('prof-tokens').textContent = S.streamerMode ? '****' : S.tokens.toFixed(0); document.getElementById('prof-coins').textContent = S.streamerMode ? '****' : S.coins.toFixed(0); document.getElementById('prof-wins').textContent = bpCurrentPlayer?.wins || '—'; document.getElementById('prof-earned').textContent = S.transactions.filter(t=>t.amount>0&&t.currency==='tokens').reduce((a,t)=>a+t.amount,0).toFixed(0); // Load bank fields if (S.bankDetails.iban) document.getElementById('bank-iban').value = S.bankDetails.iban; if (S.bankDetails.name) document.getElementById('bank-fullname').value = S.bankDetails.name; if (S.bankDetails.bic) document.getElementById('bank-bic').value = S.bankDetails.bic; if (S.bankDetails.paypal) document.getElementById('bank-paypal').value = S.bankDetails.paypal; renderProfileTx(); renderMatchHistory(); renderEquippedCosmetics(); } function switchProfileTab(tab) { const tabs = ['matches','transactions','cosmetics','bank']; tabs.forEach(t => { document.getElementById('pf-' + t)?.classList.toggle('active', t === tab); document.querySelectorAll('.profile-tab').forEach((btn,i) => { if (btn.getAttribute('onclick')?.includes(t)) btn.classList.toggle('active', t === tab); }); }); } function renderProfileTx() { const el = document.getElementById('profile-tx-list'); if (!el || !S.transactions.length) return; const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } function renderMatchHistory() { const el = document.getElementById('match-history-rows'); if (!el) return; if (!S.matchHistory.length) { el.innerHTML = '
No match history yet. Join a match to get started.
'; return; } el.innerHTML = S.matchHistory.map(m => `
${m.date}
${MODES[m.mode]||m.mode}
${m.result.toUpperCase()}
${m.result==='win'?'+🪙'+m.earned:'—'}
${m.kills||0}K
`).join(''); } function renderEquippedCosmetics() { const el = document.getElementById('equipped-items'); if (!el) return; const equipped = Object.values(S.equippedCosmetics); if (!equipped.length) { el.innerHTML = '
No cosmetics yet. Visit the Shop to get some.
'; return; } el.innerHTML = equipped.map(id => { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return ''; return `
${item.icon}
${item.rarity}
${item.name}
Equipped
`; }).join(''); } function saveBankDetails() { S.bankDetails = { iban: document.getElementById('bank-iban').value.trim(), name: document.getElementById('bank-fullname').value.trim(), bic: document.getElementById('bank-bic').value.trim(), paypal: document.getElementById('bank-paypal').value.trim(), }; localStorage.setItem('p2w_bank', JSON.stringify(S.bankDetails)); toast('✓ Bank details saved.', 'ok'); loadWallet(); } // ═══════════════════════════════════════════════════ // SETTINGS // ═══════════════════════════════════════════════════ function switchSetting(panel) { document.querySelectorAll('.settings-nav-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.settings-panel').forEach(el => el.classList.remove('active')); document.getElementById('sp-' + panel)?.classList.add('active'); event.currentTarget.classList.add('active'); } function toggleStreamerMode() { S.streamerMode = document.getElementById('streamer-mode').classList.contains('on'); updateNavUser(); updateNavTokens(); updateWalletDisplay(); if (S.streamerMode) toast('📡 Streamer Mode ON — username and balance hidden', 'info'); else toast('Streamer Mode OFF', 'info'); } function saveSettings() { const toggleIds = ['priv-public','priv-earnings','priv-winrate','priv-friends', 'notif-match','notif-result','notif-payout','notif-bp','notif-shop','notif-promo', 'streamer-mode','stream-hide-bal','stream-hide-bank','stream-blur-codes','stream-hc','stream-no-anim', 'app-compact','app-anim-stats','app-age-banner','sec-2fa','sec-withdraw-confirm','sec-login-alert']; toggleIds.forEach(id => { const el = document.getElementById(id); if (el) S.settings[id] = el.classList.contains('on'); }); localStorage.setItem('p2w_settings', JSON.stringify(S.settings)); } // ═══════════════════════════════════════════════════ // PAYMENT (match) // ═══════════════════════════════════════════════════ function setPay(btn, method) { document.querySelectorAll('#pay-s1 .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.payMethod = method; } function processPay() { const discord = document.getElementById('f-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const m = S.currentMatch, fee = m?.fee || 5; // Check wallet balance if (S.tokens < fee) { toast(`Not enough tokens. You have 🪙${S.tokens.toFixed(0)} — need 🪙${fee}. Buy more tokens in the Wallet.`, 'err'); closeModal('pay-overlay'); goPage('wallet'); return; } // Deduct tokens from wallet S.tokens -= fee; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'entry', name:`Entry fee — ${m.title}`, amount: -fee, currency:'tokens' }); // Update match player count in local state const match = S.matches.find(x => x.id === m.id); if (match) match.players = Math.min(match.players + 1, match.maxPlayers); closeModal('pay-overlay'); updateNavTokens(); renderMatches(); toast(`✅ Joined! 🪙${fee} tokens deducted. Check Discord #lobby-codes for your code.`, 'ok'); } function setBPPay(btn, method) { document.querySelectorAll('#bp-overlay .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.bpPayMethod = method; } function processBPPay() { const discord = document.getElementById('bp-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const note = `P2W Premium Battlepass | ${discord}`; if (S.bpPayMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/10`, '_blank'); if (S.bpPayMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); document.getElementById('bp-overlay').classList.remove('show'); setTimeout(() => toast(`✅ Payment opened · Note: "${note}" · Send proof to #battlepass-payments`, 'info'), 300); } // ═══════════════════════════════════════════════════ // ADMIN // ═══════════════════════════════════════════════════ function renderAdminGate() { if (S.adminLoggedIn) { document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); } } let S_adminLoggedIn = false; Object.defineProperty(S, 'adminLoggedIn', { get:()=>S_adminLoggedIn, set:(v)=>S_adminLoggedIn=v }); async function loadDBViewer() { const el = document.getElementById('db-viewer'); if (!el) return; el.innerHTML = '
Loading from JSONBin...
'; try { const data = await binGet(); const bp = data.battlepass || {}; const players = bp.players || {}; const count = Object.keys(players).length; el.innerHTML = `
${count}
BP Players
${Object.values(players).filter(p=>p.premium).length}
Premium
${(bp.xpLog||[]).length}
XP Events
Player Records
${count===0 ? '
No players yet
' : Object.entries(players).map(([k,p]) => `
${p.discord}${p.premium?' ⭐':''}
Lv ${bpLevel(p.xp||0)} ${(p.xp||0).toLocaleString()} XP ${p.wins||0}W ${p.coins ? `🥇${p.coins}` : ''}
`).join('')}
Synced ${new Date().toLocaleTimeString()} · Open full bin →
`; } catch(e) { el.innerHTML = `
Error: ${e.message}
Check CFG.API_KEY is correct.
`; } } async function adminLogin() { const pw = document.getElementById('admin-pw').value; if (pw !== CFG.ADMIN_PW) { toast('Wrong password.', 'err'); return; } S.adminLoggedIn = true; document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); toast('✓ Admin access granted.', 'ok'); } function adminLogout() { S.adminLoggedIn = false; document.getElementById('admin-gate').style.display = 'flex'; document.getElementById('admin-dash').classList.remove('show'); document.getElementById('admin-pw').value = ''; } async function loadAdminData() { // KPIs document.getElementById('admin-kpis').innerHTML = `
Revenue Today
€82
Active Players
${statsOnline}
Matches Live
${S.matches.filter(m=>m.status==='live').length}
Payouts Today
€48
`; // ── PENDING PURCHASES (most important for admin) ── renderPendingPurchases(); // Match list document.getElementById('admin-match-list').innerHTML = S.matches.slice(0,5).map(m => `
${m.title}
${MODES[m.mode]} · 🪙${m.entryFee} · ${m.players}/${m.maxPlayers}
`).join(''); document.getElementById('payout-total-badge').textContent = '€48 today'; document.getElementById('admin-payout-log').innerHTML = [ {user:'NexusEU',amt:'🪙32',time:'14:22'},{user:'ZenithKO',amt:'🪙18',time:'13:05'},{user:'FrostRapide',amt:'🪙14',time:'11:50'} ].map(p => `
${p.user}
${p.time}
${p.amt}
`).join(''); await loadBPAdminData(); } function renderPendingPurchases() { const el = document.getElementById('admin-pending-list'); if (!el) return; // Load from localStorage (all pending purchases submitted from this browser) const allPending = JSON.parse(localStorage.getItem('p2w_pending') || '[]').filter(p => p.status === 'pending'); const badge = document.getElementById('pending-badge'); if (badge) { badge.textContent = allPending.length > 0 ? allPending.length : ''; badge.style.display = allPending.length > 0 ? 'inline-flex' : 'none'; } if (!allPending.length) { el.innerHTML = '
No pending purchases
'; return; } el.innerHTML = allPending.map(p => `
#${p.id} Pending
${p.discord}
${p.method.toUpperCase()} · €${p.price} · ${new Date(p.timestamp).toLocaleString('nl-BE')}
🪙 ${p.tokens}
`).join(''); } function approvePurchase(id) { const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (!p) return; // Add tokens to user wallet S.tokens += p.tokens; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'deposit', name:`Token purchase approved — ${p.tokens} tokens via ${p.method}`, amount: p.tokens, currency:'tokens' }); // Mark as approved p.status = 'approved'; localStorage.setItem('p2w_pending', JSON.stringify(all)); // Remove from DOM document.getElementById('pending-' + id)?.remove(); toast(`✓ Approved — 🪙${p.tokens} tokens added for ${p.discord}!`, 'ok'); updateNavTokens(); renderPendingPurchases(); } function rejectPurchase(id) { if (!confirm('Reject this purchase request?')) return; const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (p) p.status = 'rejected'; localStorage.setItem('p2w_pending', JSON.stringify(all)); document.getElementById('pending-' + id)?.remove(); toast(`Purchase #${id} rejected.`, 'info'); renderPendingPurchases(); } function adminToggleMatch(id, status) { const m = S.matches.find(x => x.id === id); if (!m) return; m.status = m.status === 'live' ? 'waiting' : 'live'; renderMatches(); loadAdminData(); toast(`Match "${m.title}" → ${m.status.toUpperCase()}`, 'ok'); } function adminCancelMatch(id) { if (!confirm('Cancel this match?')) return; S.matches = S.matches.filter(m => m.id !== id); renderMatches(); loadAdminData(); toast('Match cancelled.', 'ok'); } function openCreateMatch() { document.getElementById('cm-overlay').classList.add('show'); } function createMatchAdmin() { const mode = document.getElementById('cm-mode').value; const fee = parseInt(document.getElementById('cm-fee').value) || 5; const max = parseInt(document.getElementById('cm-max').value) || 10; const title= document.getElementById('cm-title').value.trim() || `${MODES[mode]} #${Date.now().toString().slice(-4)}`; S.matches.unshift({ id: Date.now(), mode, title, entryFee:fee, players:0, maxPlayers:max, prize:Math.round(fee*max*0.9), status:'waiting' }); document.getElementById('cm-overlay').classList.remove('show'); toast('✓ Match created!', 'ok'); renderMatches(); loadAdminData(); } // ═══════════════════════════════════════════════════ // BATTLEPASS ADMIN (JSONBin) // ═══════════════════════════════════════════════════ async function loadBPAdminData() { try { const bpData = await getBPData(); const players = Object.values(bpData.players || {}); const total = players.length, prem = players.filter(p=>p.premium).length; const avgLvl = total ? Math.round(players.reduce((a,p)=>a+bpLevel(p.xp),0)/total) : 0; const ta=document.getElementById('bpa-total');if(ta)ta.textContent=total; const pr=document.getElementById('bpa-prem'); if(pr)pr.textContent=prem; const al=document.getElementById('bpa-lvl'); if(al)al.textContent=avgLvl; const tbody = document.getElementById('bp-admin-tbody'); if (tbody) { const keys = Object.keys(bpData.players||{}).sort(); if (!keys.length) { tbody.innerHTML = 'No players yet'; } else { tbody.innerHTML = keys.map(key => { const p = bpData.players[key], lv = bpLevel(p.xp); return ` ${p.discord} ${lv} ${p.xp.toLocaleString()} ${p.premium?'':'Free'} `; }).join(''); } } const logEl = document.getElementById('bp-xp-log'); if (logEl) { const log = (bpData.xpLog||[]).slice(-20).reverse(); logEl.innerHTML = log.length ? log.map(e => `
${e.discord}
${e.reason||'Match'} · ${e.date}
+${e.xp} XP
`).join('') : '
No events yet
'; } } catch(e) { toast('BP load error: ' + e.message, 'err'); } } function filterBPTable(q) { document.querySelectorAll('#bp-admin-tbody tr[data-bpkey]').forEach(r => { r.style.display = r.dataset.bpname?.includes(q.toLowerCase()) ? '' : 'none'; }); } function openBPAddXP(discord) { document.getElementById('bpxp-player').value = discord || ''; document.getElementById('bpxp-amount').value = 250; document.getElementById('bpxp-reason').value = ''; document.getElementById('bpxp-overlay').classList.add('show'); } async function submitBPXP() { const discord = document.getElementById('bpxp-player').value.trim(); const xp = parseInt(document.getElementById('bpxp-amount').value); const reason = document.getElementById('bpxp-reason').value.trim() || 'Match played'; if (!discord || !xp || xp < 1) { toast('Fill all fields.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (!bpData.players[key]) { bpData.players[key] = { discord, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; } const before = bpData.players[key].xp; bpData.players[key].xp += xp; if (reason.toLowerCase().includes('win')) bpData.players[key].wins = (bpData.players[key].wins||0)+1; bpData.players[key].matches = (bpData.players[key].matches||0)+1; bpData.xpLog = bpData.xpLog || []; bpData.xpLog.push({ discord, xp, reason, date:new Date().toISOString().slice(0,10) }); if (bpData.xpLog.length > 200) bpData.xpLog = bpData.xpLog.slice(-200); await saveBPData(bpData); const lvBefore = bpLevel(before), lvAfter = bpLevel(bpData.players[key].xp); document.getElementById('bpxp-overlay').classList.remove('show'); toast(lvAfter > lvBefore ? `⭐ ${discord} +${xp}XP — Level UP! ${lvBefore}→${lvAfter}` : `⚡ ${discord} +${xp}XP (Level ${lvAfter})`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: ' + e.message, 'err'); } } async function bpGrantPremium(key, discord) { if (!confirm(`Grant Premium to ${discord}?`)) return; try { const bpData = await getBPData(); if (!bpData.players[key]) { toast('Player not found.', 'err'); return; } bpData.players[key].premium = true; await saveBPData(bpData); toast(`⭐ Premium granted to ${discord}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } async function bpRemovePlayer(key, discord) { if (!confirm(`Remove ${discord}?`)) return; try { const bpData = await getBPData(); delete bpData.players[key]; await saveBPData(bpData); toast(`${discord} removed.`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } function openBPAddPlayer() { document.getElementById('bpadd-discord').value = ''; document.getElementById('bpadd-pass').value = 'free'; document.getElementById('bpadd-overlay').classList.add('show'); } async function submitBPAddPlayer() { const discord = document.getElementById('bpadd-discord').value.trim(); const pass = document.getElementById('bpadd-pass').value; if (!discord) { toast('Enter Discord name.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (bpData.players[key]) { toast('Player already exists.', 'err'); return; } bpData.players[key] = { discord, xp:0, premium:pass==='premium', wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; await saveBPData(bpData); document.getElementById('bpadd-overlay').classList.remove('show'); toast(`✓ ${discord} added${pass==='premium'?' with Premium':''}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } // ═══════════════════════════════════════════════════ // MODAL HELPERS // ═══════════════════════════════════════════════════ function closeModal(id) { document.getElementById(id)?.classList.remove('show'); } // ═══════════════════════════════════════════════════ // TOAST // ═══════════════════════════════════════════════════ let _tt; function toast(msg, type='ok') { clearTimeout(_tt); const t=document.getElementById('toast'); t.className='toast '+type; document.getElementById('toast-ico').textContent=type==='ok'?'✓':type==='err'?'✕':'ℹ'; document.getElementById('toast-msg').textContent=msg; t.classList.add('show'); _tt=setTimeout(()=>t.classList.remove('show'),4500); } // ═══════════════════════════════════════════════════ // BOOT // ═══════════════════════════════════════════════════ (function boot() { // Restore user const savedUser = localStorage.getItem('p2w_user'); if (savedUser) { try { S.user = JSON.parse(savedUser); updateNavUser(); } catch {} } // Restore settings toggles const settings = S.settings; Object.keys(settings).forEach(id => { const el = document.getElementById(id); if (el && el.classList.contains('toggle')) { if (settings[id]) el.classList.add('on'); else el.classList.remove('on'); } }); // Handle Discord OAuth return const params = new URLSearchParams(window.location.search); if (params.get('token')) { localStorage.setItem('p2w_token', params.get('token')); S.token = params.get('token'); history.replaceState({}, '', window.location.pathname); toast('✓ Logged in via Discord!', 'ok'); } if (params.get('payment') === 'success') { toast('🎉 Payment confirmed! Check Discord for your lobby code.', 'ok'); history.replaceState({}, '', window.location.pathname); } // Init pages renderBPPublicTrack(); goPage('home'); })(); ', // ─── Season ─────────────────────────────────────────────────────── SEASON_END: new Date('2025-06-30T23:59:59'), // ─── BP ─────────────────────────────────────────────────────────── XP_PER_LEVEL: 1000, MAX_LEVEL: 100, // ─── Economy ────────────────────────────────────────────────────── // TOKENS = real money (€1 = 1 Token). Used for match entry fees. // COINS = earned/rewarded currency. Used in the shop for cosmetics. HOUSE_CUT: 0.10, WITHDRAW_FEE_PCT: 0.05, WITHDRAW_FEE_FLAT: 1, // ─── Your Payment Details ───────────────────────────────────────── PAY: { paypal: { link: 'https://paypal.me/JOUW_NAAM' }, bank: { iban: 'BE00 0000 0000 0000', name: 'Jouw Naam', bic: 'GEBABEBB' }, revolut: { link: 'https://revolut.me/JOUW_TAG' }, }, }; // ═══════════════════════════════════════════════════ // STATE // ═══════════════════════════════════════════════════ const S = { user: null, token: localStorage.getItem('p2w_token'), // TOKENS = real money (€1 each). Used for match entry fees. tokens: parseFloat(localStorage.getItem('p2w_tokens') || '0'), // COINS = earned/rewarded. Used in the shop for cosmetics. coins: parseFloat(localStorage.getItem('p2w_coins') || '0'), socket: null, matches: [], leaderboard: [], battlepass: null, currentMatch: null, payMethod: 'paypal', bpPayMethod: 'paypal', tokPayMethod: 'paypal', shopFilter: 'all', currentShopItem: null, // Pending token purchase requests (waiting for admin approval) pendingPurchases: JSON.parse(localStorage.getItem('p2w_pending') || '[]'), settings: JSON.parse(localStorage.getItem('p2w_settings') || '{}'), bankDetails: JSON.parse(localStorage.getItem('p2w_bank') || '{}'), transactions: JSON.parse(localStorage.getItem('p2w_tx') || '[]'), matchHistory: JSON.parse(localStorage.getItem('p2w_mh') || '[]'), ownedCosmetics: JSON.parse(localStorage.getItem('p2w_cosmetics') || '[]'), equippedCosmetics: JSON.parse(localStorage.getItem('p2w_equipped') || '{}'), streamerMode: false, filterMode: 'all', }; // ═══════════════════════════════════════════════════ // JSONBIN // ═══════════════════════════════════════════════════ const BIN_URL = `https://api.jsonbin.io/v3/b/${CFG.BIN_ID}`; async function binGet() { const r = await fetch(BIN_URL + '/latest', { headers: { 'X-Master-Key': CFG.API_KEY } }); if (!r.ok) throw new Error('JSONBin read failed (' + r.status + ')'); return (await r.json()).record; } async function binPut(data) { const r = await fetch(BIN_URL, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Master-Key': CFG.API_KEY }, body: JSON.stringify(data), }); if (!r.ok) throw new Error('JSONBin write failed (' + r.status + ')'); return r.json(); } async function getBPData() { const d = await binGet(); if (!d.battlepass) { await binPut({ ...d, battlepass: { players: {}, xpLog: [], season: 1 } }); return { players: {}, xpLog: [], season: 1 }; } return d.battlepass; } async function saveBPData(bp) { const d = await binGet(); d.battlepass = bp; await binPut(d); } // ═══════════════════════════════════════════════════ // REWARD TRACK // ═══════════════════════════════════════════════════ function buildBPTrack() { // COINS = shop currency (earned via battlepass) // TOKENS = real money (€1 each), only rare milestone rewards const t = []; for (let i = 1; i <= CFG.MAX_LEVEL; i++) { // Default: coins reward (not real money) const coinAmt = i<=25?5:i<=50?10:i<=75?15:20; let r = { level:i, icon:'🥇', label:`${coinAmt} Coins`, type:'coins', premium:false }; if (i===5) r={level:i,icon:'⚡',label:'XP Boost', type:'xp', premium:false}; if (i===10) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // moved from higher if (i===15) r={level:i,icon:'🥇',label:'15 Coins', type:'coins', premium:false}; // replaced -10% entry if (i===20) r={level:i,icon:'🏅',label:'Free Badge', type:'badge', premium:false}; if (i===25) r={level:i,icon:'💎',label:'Premium Role', type:'role', premium:true}; if (i===30) r={level:i,icon:'🥇',label:'30 Coins', type:'coins', premium:true}; if (i===40) r={level:i,icon:'⚡',label:'XP Boost ×2', type:'xp', premium:false}; if (i===50) r={level:i,icon:'🥇',label:'50 Coins', type:'coins', premium:true}; if (i===55) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:false}; // real €1 if (i===60) r={level:i,icon:'🥇',label:'75 Coins', type:'coins', premium:true}; if (i===75) r={level:i,icon:'🪙',label:'1 Token', type:'token', premium:true}; // replaced -20% entry if (i===100)r={level:i,icon:'👑',label:'5 Tokens', type:'token', premium:true}; // real €5 t.push(r); } return t; } const BP_TRACK = buildBPTrack(); function bpLevel(xp){ return Math.min(Math.floor(xp/CFG.XP_PER_LEVEL)+1, CFG.MAX_LEVEL); } function bpPct(xp) { return Math.floor(((xp%CFG.XP_PER_LEVEL)/CFG.XP_PER_LEVEL)*100); } function bpNorm(n) { return n.trim().toLowerCase(); } function avatarColor(n){ let h=0; for(const c of n)h=c.charCodeAt(0)+((h<<5)-h); return `hsl(${Math.abs(h)%360},55%,35%)`; } function initials(n){ return n.slice(0,2).toUpperCase(); } // ═══════════════════════════════════════════════════ // NAV // ═══════════════════════════════════════════════════ function goPage(p) { document.querySelectorAll('.page').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-link,.drawer-link').forEach(el => el.classList.remove('active')); document.getElementById('page-' + p)?.classList.add('active'); document.querySelectorAll(`[data-page="${p}"]`).forEach(el => el.classList.add('active')); window.scrollTo(0, 0); if (p === 'matches') loadMatches(); if (p === 'lb') loadLB(); if (p === 'bp') loadBPPage(); if (p === 'shop') loadShop(); if (p === 'wallet') loadWallet(); if (p === 'profile') loadProfile(); if (p === 'admin') renderAdminGate(); updateNavTokens(); } function toggleDrawer() { const d = document.getElementById('nav-drawer'); const b = document.getElementById('hamburger'); d.classList.toggle('open'); b.classList.toggle('open'); } function closeDrawer() { document.getElementById('nav-drawer').classList.remove('open'); document.getElementById('hamburger').classList.remove('open'); } // ═══════════════════════════════════════════════════ // AUTH (simulated - Discord OAuth needs backend) // ═══════════════════════════════════════════════════ function loginWithDiscord() { // Simulated login for demo const name = prompt('Enter your Discord username to continue (demo):'); if (!name) return; S.user = { username: name, discordId: 'demo_' + Date.now(), avatar: null }; localStorage.setItem('p2w_user', JSON.stringify(S.user)); updateNavUser(); toast('✓ Welcome to P2W, ' + name + '!', 'ok'); } function updateNavUser() { if (!S.user) return; document.getElementById('login-btn').style.display = 'none'; document.getElementById('nav-avatar-btn').style.display = 'flex'; document.getElementById('nav-bal').style.display = 'flex'; const init = initials(S.streamerMode ? 'P2W Player' : S.user.username); document.getElementById('nav-av-initials').textContent = init; updateNavTokens(); } function updateNavTokens() { const display = document.getElementById('nav-tokens'); if (display) { if (S.streamerMode && S.settings['stream-hide-bal'] !== false) { display.innerHTML = '****'; } else { display.innerHTML = `🪙${S.tokens.toFixed(0)} | 🥇${S.coins.toFixed(0)}`; } } // Shop coins display const sd = document.getElementById('shop-coins-display'); if (sd) sd.textContent = S.coins.toFixed(0); // Wallet const wb = document.getElementById('wallet-bal-display'); if (wb) wb.textContent = S.tokens.toFixed(2); const cbd = document.getElementById('wallet-coins-display'); if (cbd) cbd.textContent = S.coins.toFixed(0); } // ═══════════════════════════════════════════════════ // LIVE STATS // ═══════════════════════════════════════════════════ let statsOnline = 247, statsPayout = 284; function tickStats() { statsOnline += Math.floor(Math.random() * 5) - 2; if (statsOnline < 100) statsOnline = 100; statsPayout += Math.floor(Math.random() * 3); const activeMatches = S.matches.filter(m => m.status === 'live').length; ['s-online','s-active','s-payouts'].forEach((id, i) => { const el = document.getElementById(id); if (!el) return; if (i === 0) el.textContent = statsOnline; if (i === 1) el.textContent = activeMatches; if (i === 2) el.textContent = '€' + statsPayout; }); const mc = document.getElementById('match-count'); if (mc) mc.textContent = `${activeMatches} live · ${S.matches.filter(m=>m.status==='waiting').length} open`; } setInterval(tickStats, 4000); tickStats(); // ═══════════════════════════════════════════════════ // SEASON COUNTDOWN // ═══════════════════════════════════════════════════ function updateCountdowns() { // Battlepass const bc = document.getElementById('bp-countdown'); if (bc) { const diff = CFG.SEASON_END - new Date(); if (diff > 0) { const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000); bc.textContent = `${d}d ${h}h remaining`; } } // Leaderboard const lc = document.getElementById('lb-countdown'); if (lc) { const now = new Date(), next = new Date(now); next.setDate(now.getDate() + ((8 - now.getDay()) % 7 || 7)); next.setHours(0,0,0,0); const diff = next - now; const d = Math.floor(diff/86400000), h = Math.floor((diff%86400000)/3600000), m = Math.floor((diff%3600000)/60000); lc.textContent = `${d}d ${h}h ${m}m`; } } setInterval(updateCountdowns, 60000); updateCountdowns(); // ═══════════════════════════════════════════════════ // MATCHES // ═══════════════════════════════════════════════════ const MODES = { realistics:'Realistics', boxfights:'Boxfights', scrims:'Scrims', zonewars:'Zonewars' }; const ICONS = { realistics:'🎯', boxfights:'📦', scrims:'⚔️', zonewars:'🌀' }; S.matches = [ { id:1, mode:'realistics', title:'EU Realistics #14', entryFee:5, players:7, maxPlayers:10, prize:45, status:'waiting' }, { id:2, mode:'realistics', title:'EU Realistics #13', entryFee:10, players:10, maxPlayers:10, prize:90, status:'live' }, { id:3, mode:'boxfights', title:'Boxfight Cup #7', entryFee:2, players:6, maxPlayers:16, prize:28, status:'waiting' }, { id:4, mode:'scrims', title:'EU Scrims #5', entryFee:10, players:80, maxPlayers:100, prize:800, status:'live' }, { id:5, mode:'boxfights', title:'Boxfight Ladder', entryFee:5, players:3, maxPlayers:8, prize:35, status:'waiting' }, { id:6, mode:'scrims', title:'EU Scrims #6', entryFee:5, players:22, maxPlayers:100, prize:200, status:'waiting' }, { id:7, mode:'zonewars', title:'Zonewars Alpha', entryFee:2, players:4, maxPlayers:6, prize:10, status:'waiting' }, ]; function loadMatches() { renderMatches(); } function filterMatch(mode, btn) { S.filterMode = mode; document.querySelectorAll('.fb').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderMatches(); } function renderMatches() { const grid = document.getElementById('matches-grid'); if (!grid) return; const list = S.filterMode === 'all' ? S.matches : S.matches.filter(m => m.mode === S.filterMode); if (!list.length) { grid.innerHTML = `
No matches in this mode right now.
`; return; } grid.innerHTML = list.map(m => { const pct = Math.round((m.players / m.maxPlayers) * 100); const full = m.players >= m.maxPlayers; const live = m.status === 'live'; const cut = Math.round(m.entryFee * m.maxPlayers * CFG.HOUSE_CUT); return `
${ICONS[m.mode]} ${MODES[m.mode]} ${live ? ` LIVE` : full ? `FULL` : `● OPEN`}
${m.title}
Hosted by P2W Staff
🪙${m.entryFee}
Entry
🪙${m.prize}
Prize
${m.players}/${m.maxPlayers}
Players
${m.players} / ${m.maxPlayers} players${pct}%
${full || live ? `` : ``}
House cut: 🪙${cut} · Net prize: 🪙${m.prize - cut}
`; }).join(''); } function joinMatch(id, mode, title, fee) { S.currentMatch = { id, mode, title, fee }; document.getElementById('m-mode').textContent = MODES[mode] || mode; document.getElementById('m-title').textContent = title; document.getElementById('m-price').textContent = '🪙' + fee; document.getElementById('f-duo-wrap').style.display = mode === 'scrims' ? 'block' : 'none'; document.getElementById('f-discord').value = S.user?.username || ''; const balEl = document.getElementById('join-balance-display'); if (balEl) balEl.textContent = `🪙 ${S.tokens.toFixed(0)} tokens`; // Warn if not enough const btn = document.getElementById('pay-btn'); if (S.tokens < fee) { if (btn) { btn.textContent = '⚠ Not enough tokens — Buy more'; btn.style.background = 'rgba(239,68,68,.15)'; btn.style.border = '1px solid rgba(239,68,68,.3)'; btn.style.color = 'var(--red)'; } } else { if (btn) { btn.textContent = '🪙 Pay & Join →'; btn.style.background = ''; btn.style.border = ''; btn.style.color = ''; } } document.getElementById('pay-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // LEADERBOARD // ═══════════════════════════════════════════════════ const SAMPLE_LB = [ {rank:1,name:'NexusEU', earn:312,wins:61,wr:81,ch:'+2',up:true}, {rank:2,name:'ZenithKO', earn:147,wins:38,wr:74,ch:'—', up:null}, {rank:3,name:'FrostRapide',earn:98,wins:27,wr:68,ch:'-1',up:false}, {rank:4,name:'ShadowLoop', earn:76,wins:21,wr:65,ch:'+3',up:true}, {rank:5,name:'KrypticFN', earn:62,wins:19,wr:59,ch:'-2',up:false}, {rank:6,name:'BlitzRunner',earn:55,wins:15,wr:62,ch:'+1',up:true}, {rank:7,name:'VoidCraft', earn:44,wins:12,wr:57,ch:'—', up:null}, {rank:8,name:'ApexRoll', earn:37,wins:10,wr:54,ch:'+4',up:true}, {rank:9,name:'DriftKing', earn:29,wins:8, wr:50,ch:'-1',up:false}, {rank:10,name:'TempestEU', earn:18,wins:5, wr:45,ch:'+2',up:true}, ]; function loadLB() { const pod = document.getElementById('lb-podium'); const [s1,s2,s3] = [SAMPLE_LB[1], SAMPLE_LB[0], SAMPLE_LB[2]]; const crowns = ['🥈','👑','🥉']; const podData = [s1,s2,s3]; const cls = ['','p1','']; if (pod) pod.innerHTML = podData.map((p,i) => { const hue = (p.rank*47)%360; return `
${crowns[i]}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}W · ${p.wr}% WR
`; }).join(''); document.getElementById('lb-rows').innerHTML = SAMPLE_LB.map(p => { const hue = (p.rank*47)%360; return `
${p.rank<=3?'★':p.rank}
${initials(p.name)}
${p.name}
🪙${p.earn}
${p.wins}
${p.wr}%
${p.up===true?'▲':p.up===false?'▼':'─'} ${p.ch}
`; }).join(''); } // ═══════════════════════════════════════════════════ // BATTLEPASS // ═══════════════════════════════════════════════════ let bpCurrentPlayer = null, bpCurrentKey = null; function loadBPPage() { renderBPPublicTrack(); renderPasses(); if (bpCurrentPlayer) renderBPCard(bpCurrentPlayer); } function renderBPPublicTrack() { const el = document.getElementById('bp-public-track'); if (!el) return; const milestones = BP_TRACK.filter(r => r.level % 5 === 0 || r.level === 1); el.innerHTML = milestones.map(r => { const isToken = r.type === 'token'; const borderC = isToken ? 'rgba(240,180,41,.35)' : r.premium ? 'rgba(168,85,247,.2)' : 'var(--border)'; const glowC = isToken ? 'rgba(240,180,41,.08)' : 'transparent'; return `
${isToken ? '
' : ''} ${r.premium && !isToken ? '
PRO
' : ''}
${r.icon}
Lv ${r.level}
${r.label}
${isToken ? '
real money
' : r.type==='coins' ? '
shop coins
' : ''}
`; }).join(''); } async function bpLookup() { const raw = document.getElementById('bp-inp').value.trim(); if (!raw) { toast('Enter your Discord name.', 'err'); return; } const btn = document.getElementById('bp-lookup-btn'); btn.disabled = true; btn.innerHTML = '
'; try { const data = await getBPData(); const key = bpNorm(raw); let player = data.players[key]; if (!player) { player = { discord:raw, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; data.players[key] = player; await saveBPData(data); toast('✓ Battlepass profile created!', 'ok'); } bpCurrentPlayer = player; bpCurrentKey = key; renderBPCard(player); } catch(e) { toast('Error: ' + e.message, 'err'); } btn.disabled = false; btn.textContent = 'Look Up →'; } function bpReset() { document.getElementById('bp-lookup-box').style.display = 'block'; document.getElementById('bp-loaded-box').style.display = 'none'; document.getElementById('bp-claimable-section').style.display = 'none'; document.getElementById('bp-inp').value = ''; bpCurrentPlayer = null; bpCurrentKey = null; } function renderBPCard(p) { const lv = bpLevel(p.xp), pct = bpPct(p.xp), prog = p.xp % CFG.XP_PER_LEVEL; const isPrem = p.premium, claimed = new Set(p.claimedRewards || []); const claimable = BP_TRACK.filter(r => r.level <= lv && (!r.premium || isPrem) && !claimed.has(r.level)); document.getElementById('bp-lookup-box').style.display = 'none'; document.getElementById('bp-loaded-box').style.display = 'block'; const av = document.getElementById('bp-avatar'); av.textContent = initials(p.discord); av.style.background = avatarColor(p.discord); av.style.borderRadius = '50%'; av.style.width = '36px'; av.style.height = '36px'; av.style.display = 'flex'; av.style.alignItems = 'center'; av.style.justifyContent = 'center'; const displayName = S.streamerMode ? 'P2W Player' : p.discord; document.getElementById('bp-username').textContent = displayName; document.getElementById('bp-pass-badge').innerHTML = isPrem ? `⭐ Premium` : `Free`; document.getElementById('bp-level').textContent = lv; document.getElementById('bp-level-label').textContent = `Level ${lv} / 100`; document.getElementById('bp-xp-label').textContent = `${prog.toLocaleString('en')} / 1000 XP`; setTimeout(() => { const f = document.getElementById('bp-xp-fill'); if(f) f.style.width = pct + '%'; }, 80); const hint = document.getElementById('bp-claim-hint'); if (hint) hint.style.display = claimable.length > 0 ? 'block' : 'none'; if (claimable.length > 0) { document.getElementById('bp-claimable-section').style.display = 'block'; document.getElementById('bp-claimable-list').innerHTML = claimable.map(r => `
${r.icon}
Lv ${r.level} — ${r.label}
Contact staff in Discord to claim
`).join(''); } } function renderPasses() { const el = document.getElementById('passes-grid'); if (!el) return; el.innerHTML = `
FREE PASS
For everyone
FREE
🪙
50 tokens across 100 levels
XP boosts at milestones
📊
Full stats tracking
🏅
Free badge on Discord
⭐ PREMIUM
Season 1 — €10 one-time
€10
💰
€15+ value in tokens & credits
🎟
Up to 20% off match entry fees
🥇
Priority queue access
📈
+10% earnings boost on wins
💎
Exclusive Discord role + gold name
`; } function purchaseBP() { document.getElementById('bp-overlay').classList.add('show'); } // ═══════════════════════════════════════════════════ // SHOP // ═══════════════════════════════════════════════════ const SHOP_ITEMS = [ // Badges { id:'b1', cat:'badge', icon:'💀', name:'Death Dealer', desc:'For those who eliminate first.', rarity:'rare', price:20, bgColor:'#1a0f0f' }, { id:'b2', cat:'badge', icon:'👑', name:'Champion Crown', desc:'Earned by top seasonal players.', rarity:'legendary', price:150, bgColor:'#1a1500' }, { id:'b3', cat:'badge', icon:'🔥', name:'On Fire', desc:'You\'re unstoppable right now.', rarity:'epic', price:75, bgColor:'#1a0d00' }, { id:'b4', cat:'badge', icon:'⚡', name:'Speed Demon', desc:'Quick entries, quick exits.', rarity:'rare', price:30, bgColor:'#0d0f1a' }, { id:'b5', cat:'badge', icon:'🎯', name:'Sharpshooter', desc:'Precision over brute force.', rarity:'common', price:10, bgColor:'#0f1a0f' }, // Borders { id:'bo1',cat:'border',icon:'🟦', name:'Blue Holo', desc:'Holographic blue profile border.', rarity:'rare', price:40, bgColor:'#0d1020' }, { id:'bo2',cat:'border',icon:'🟨', name:'Gold Crown', desc:'Legendary golden border frame.', rarity:'legendary', price:200, bgColor:'#1a1200' }, { id:'bo3',cat:'border',icon:'🟣', name:'Purple Haze', desc:'Epic purple animated border.', rarity:'epic', price:80, bgColor:'#110d1a' }, // Titles { id:'t1', cat:'title', icon:'📛', name:'The Untouchable', desc:'Show them you\'re unreachable.', rarity:'epic', price:60, bgColor:'#0d1015' }, { id:'t2', cat:'title', icon:'📛', name:'Cash King', desc:'Earnings speak louder than words.',rarity:'legendary', price:120, bgColor:'#1a1000' }, { id:'t3', cat:'title', icon:'📛', name:'Grinder', desc:'No days off. No excuses.', rarity:'common', price:15, bgColor:'#0f1015' }, { id:'t4', cat:'title', icon:'📛', name:'EU Certified', desc:'Representing EU at the highest.', rarity:'rare', price:35, bgColor:'#0d1520' }, // Effects { id:'e1', cat:'effect',icon:'✨', name:'Gold Sparkle', desc:'Golden particles on your profile.',rarity:'legendary', price:175, bgColor:'#1a1500' }, { id:'e2', cat:'effect',icon:'⚡', name:'Electric Aura', desc:'Electrical energy around cards.', rarity:'epic', price:90, bgColor:'#0d0f20' }, { id:'e3', cat:'effect',icon:'💨', name:'Smoke Trail', desc:'Subtle smoke effect, very clean.', rarity:'rare', price:45, bgColor:'#0f0f0f' }, // Emotes { id:'em1',cat:'emote', icon:'💸', name:'Money Rain', desc:'It\'s raining tokens.', rarity:'epic', price:55, bgColor:'#0f1a0d' }, { id:'em2',cat:'emote', icon:'🤝', name:'GG Shake', desc:'Respect after a good game.', rarity:'common', price:12, bgColor:'#0d1015' }, { id:'em3',cat:'emote', icon:'😈', name:'Villain Laugh', desc:'When you pop off on them.', rarity:'rare', price:38, bgColor:'#1a0d0d' }, ]; const RARITY_COLORS = { common:'#6b7299', rare:'var(--blue)', epic:'var(--purple)', legendary:'var(--gold)' }; function loadShop() { updateNavTokens(); renderShopGrid(); } function filterShop(cat, btn) { S.shopFilter = cat; document.querySelectorAll('.shop-cat').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderShopGrid(); } function renderShopGrid() { const grid = document.getElementById('shop-grid'); if (!grid) return; const items = S.shopFilter === 'all' ? SHOP_ITEMS : SHOP_ITEMS.filter(i => i.cat === S.shopFilter); grid.innerHTML = items.map(item => { const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; return `
${equipped ? '
Equipped
' : owned ? '
Owned
' : ''}
${item.icon}
${item.rarity.toUpperCase()}
${item.name}
${item.desc}
🥇 ${item.price} coins
${owned ? `
${equipped?'Equipped':'Own'}
` : ''}
`; }).join(''); } function openShopItem(id) { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return; S.currentShopItem = item; const owned = S.ownedCosmetics.includes(id); const equipped = S.equippedCosmetics[item.cat] === id; document.getElementById('shop-buy-preview').textContent = item.icon; document.getElementById('shop-buy-title').textContent = item.name; document.getElementById('shop-buy-desc').innerHTML = ` ${item.rarity.toUpperCase()}
${item.desc}`; document.getElementById('shop-buy-price').textContent = '🥇 ' + item.price + ' coins'; document.getElementById('shop-buy-balance').textContent = '🥇 ' + S.coins.toFixed(0) + ' coins'; const btn = document.querySelector('#shop-overlay .btn-gold'); if (btn) { if (equipped) { btn.textContent = '✓ Already Equipped'; btn.disabled = true; } else if (owned) { btn.textContent = 'Equip Item'; btn.disabled = false; } else { btn.textContent = '🛍 Purchase Item'; btn.disabled = false; } } document.getElementById('shop-overlay').classList.add('show'); } function confirmShopBuy() { const item = S.currentShopItem; if (!item) return; const owned = S.ownedCosmetics.includes(item.id); const equipped = S.equippedCosmetics[item.cat] === item.id; if (equipped) { toast('Already equipped.', 'info'); return; } if (owned) { // Just equip S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); toast(`✓ ${item.name} equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); return; } if (S.coins < item.price) { toast('Not enough coins. Earn coins via Battlepass and matches!', 'err'); return; } S.coins -= item.price; S.ownedCosmetics.push(item.id); S.equippedCosmetics[item.cat] = item.id; localStorage.setItem('p2w_coins', S.coins.toString()); localStorage.setItem('p2w_cosmetics', JSON.stringify(S.ownedCosmetics)); localStorage.setItem('p2w_equipped', JSON.stringify(S.equippedCosmetics)); addTx({ type:'buy', name:item.name, amount:-item.price, currency:'coins' }); toast(`✓ ${item.name} purchased and equipped!`, 'ok'); document.getElementById('shop-overlay').classList.remove('show'); renderShopGrid(); updateNavTokens(); } // ═══════════════════════════════════════════════════ // WALLET // ═══════════════════════════════════════════════════ const TOKEN_PACKS = [ { tokens:5, price:5, label:'Starter Pack' }, { tokens:10, price:10, label:'Player Pack', popular:true }, { tokens:20, price:20, label:'Grinder Pack' }, { tokens:50, price:50, label:'Competitor Pack'}, { tokens:100, price:100, label:'Elite Pack' }, ]; let walletMethod = 'paypal'; let currentPack = null; function loadWallet() { updateWalletDisplay(); renderTokenPacks(); renderWalletTx(); // Check if bank details are saved const bank = S.bankDetails; const filled = document.getElementById('bank-details-filled'); const status = document.getElementById('bank-details-status'); const ibanDisplay = document.getElementById('bank-iban-display'); if (bank.iban) { if (filled) filled.style.display = 'block'; if (status) status.style.display = 'none'; if (ibanDisplay) ibanDisplay.textContent = S.streamerMode ? '****' : bank.iban; } } function updateWalletDisplay() { const tokenDisplay = S.streamerMode ? '****' : S.tokens.toFixed(2); const coinDisplay = S.streamerMode ? '****' : S.coins.toFixed(0); const bal = document.getElementById('wallet-bal-display'); const locked = document.getElementById('wallet-locked-display'); const wt = document.getElementById('w-total'); const wl = document.getElementById('w-locked'); const wa = document.getElementById('w-available'); const cbd = document.getElementById('wallet-coins-display'); if (bal) bal.textContent = tokenDisplay; if (locked) locked.textContent = `Locked: 0 · Available: ${S.streamerMode ? '****' : S.tokens.toFixed(0)} Tokens`; if (wt) wt.textContent = tokenDisplay; if (wl) wl.textContent = '0.00'; if (wa) wa.textContent = tokenDisplay; if (cbd) cbd.textContent = coinDisplay; updateNavTokens(); } function renderTokenPacks() { const list = document.getElementById('token-packs-list'); if (!list) return; list.innerHTML = TOKEN_PACKS.map(p => `
${p.popular ? '' : ''}
🪙
🪙 ${p.tokens} Tokens
${p.label}
€${p.price}.00
`).join(''); } function setWalletMethod(btn, method) { document.querySelectorAll('#wallet-pay-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); walletMethod = method; } function switchWalletTab(tab) { ['purchase','withdraw','history','redeem'].forEach(t => { const el = document.getElementById('wallet-' + t); const btn = document.getElementById('wt-' + t); if (el) el.style.display = t === tab ? 'block' : 'none'; if (btn) btn.classList.toggle('active', t === tab); }); } function openTokenPurchase(tokens, price, label) { currentPack = { tokens, price, label }; document.getElementById('tok-amount-display').textContent = `${tokens} Tokens`; document.getElementById('tok-price-display').textContent = `€${price}`; document.getElementById('tok-discord').value = S.user?.username || ''; setTokMethod(document.querySelector('#tok-methods .pay-m'), 'paypal'); document.getElementById('token-overlay').classList.add('show'); } let tokMethod = 'paypal'; function setTokMethod(btn, method) { document.querySelectorAll('#tok-methods .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); tokMethod = method; const details = document.getElementById('tok-pay-details'); const pack = currentPack || { price: 0 }; const note = `P2W Tokens | ${document.getElementById('tok-discord').value || 'YourName'} | €${pack.price}`; if (method === 'paypal') { details.innerHTML = `
Send via PayPal
Link${CFG.PAY.paypal.link}
Note${note}
`; } else if (method === 'bank') { details.innerHTML = `
Bank Transfer
Name${CFG.PAY.bank.name}
IBAN${CFG.PAY.bank.iban}
Reference${note}
`; } else { details.innerHTML = `
Send via Revolut
Link${CFG.PAY.revolut.link}
Note${note}
`; } } function confirmTokenPurchase() { const discord = document.getElementById('tok-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const pack = currentPack; const note = `P2W Tokens | ${discord} | €${pack.price}`; const purchaseId = 'P' + Date.now().toString().slice(-6); // Open payment if (tokMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/${pack.price}`, '_blank'); if (tokMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); // Save pending purchase const pending = { id: purchaseId, discord, tokens: pack.tokens, price: pack.price, method: tokMethod, note, timestamp: new Date().toISOString(), status: 'pending', }; S.pendingPurchases.push(pending); localStorage.setItem('p2w_pending', JSON.stringify(S.pendingPurchases)); // Send Discord webhook notification sendDiscordWebhook(pending); document.getElementById('token-overlay').classList.remove('show'); toast(`💸 Payment opened. Reference: #${purchaseId} · Send proof to Discord #deposits · Tokens added after confirmation!`, 'info'); } async function sendDiscordWebhook(purchase) { if (!CFG.DISCORD_WEBHOOK || CFG.DISCORD_WEBHOOK.includes('JOUW_WEBHOOK_URL')) return; try { await fetch(CFG.DISCORD_WEBHOOK, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'P2W Platform', avatar_url: 'https://i.imgur.com/4M34hi2.png', embeds: [{ title: '🪙 New Token Purchase Request', color: 0xf0b429, fields: [ { name: '👤 Discord', value: purchase.discord, inline: true }, { name: '🪙 Tokens', value: `${purchase.tokens} Tokens`, inline: true }, { name: '💶 Amount', value: `€${purchase.price}`, inline: true }, { name: '💳 Method', value: purchase.method.toUpperCase(), inline: true }, { name: '🆔 Ref ID', value: `#${purchase.id}`, inline: true }, { name: '🕐 Time', value: new Date(purchase.timestamp).toLocaleString('nl-BE'), inline: true }, { name: '📋 Note', value: `\`${purchase.note}\``, inline: false }, ], footer: { text: 'Go to Admin Panel → Pending Purchases to approve' }, timestamp: purchase.timestamp, }], }), }); } catch(e) { console.warn('Webhook failed:', e); } } function calcWithdrawal(val) { const amount = parseFloat(val) || 0; if (amount < 10) { document.getElementById('withdraw-calc').style.opacity = '.4'; return; } document.getElementById('withdraw-calc').style.opacity = '1'; const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, CFG.WITHDRAW_FEE_FLAT + 1); const receive = Math.max(amount - fee, 0); document.getElementById('wc-amount').textContent = amount; document.getElementById('wc-fee').textContent = fee; document.getElementById('wc-receive').textContent = receive.toFixed(2); } function submitWithdraw() { const amount = parseFloat(document.getElementById('withdraw-amount').value); if (!amount || amount < 10) { toast('Minimum withdrawal is 10 tokens.', 'err'); return; } if (amount > S.tokens) { toast('Insufficient token balance.', 'err'); return; } if (!S.bankDetails.iban) { toast('Add bank details first in Profile → Bank Details.', 'err'); return; } const fee = Math.max(Math.ceil(amount * CFG.WITHDRAW_FEE_PCT) + CFG.WITHDRAW_FEE_FLAT, 2); S.tokens -= amount; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'withdraw', name:'Withdrawal to ' + (S.bankDetails.iban || 'Bank'), amount: -amount, currency:'tokens' }); toast(`✓ Withdrawal request submitted. You'll receive €${(amount-fee).toFixed(2)} within 24h.`, 'ok'); document.getElementById('withdraw-amount').value = ''; updateWalletDisplay(); renderWalletTx(); } function redeemCode() { const code = document.getElementById('redeem-code').value.trim().toUpperCase(); if (!code) { toast('Enter a code.', 'err'); return; } // Demo codes const CODES = { 'P2WSTART': {coins:50}, 'WELCOME5': {coins:25}, 'ELITE10': {coins:100} }; if (CODES[code]) { const reward = CODES[code]; if (reward.coins) { S.coins += reward.coins; localStorage.setItem('p2w_coins', S.coins.toString()); addTx({ type:'deposit', name:'Code redeemed: ' + code, amount: reward.coins, currency:'coins' }); toast(`✓ Code redeemed! +🥇${reward.coins} coins added.`, 'ok'); } document.getElementById('redeem-code').value = ''; updateNavTokens(); } else { toast('Invalid or already used code.', 'err'); } } function addTx(tx) { S.transactions.unshift({ ...tx, date: new Date().toLocaleDateString('en-GB'), id: Date.now() }); if (S.transactions.length > 50) S.transactions = S.transactions.slice(0, 50); localStorage.setItem('p2w_tx', JSON.stringify(S.transactions)); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆', entry:'⚔️' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn', entry:'dep' }; el.innerHTML = S.transactions.map(tx => { const currency = tx.currency === 'coins' ? '🥇' : '🪙'; const label = tx.currency === 'coins' ? 'coins' : 'tokens'; return `
${icons[tx.type]||'•'}
${tx.name}
${tx.date} · ${label}
${tx.amount>=0?'+':''}${currency}${Math.abs(tx.amount)}
`; }).join(''); } function renderWalletTx() { const el = document.getElementById('wallet-tx-list'); if (!el) return; if (!S.transactions.length) { el.innerHTML = '
No transactions yet.
'; return; } const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; const cls = { deposit:'dep', withdraw:'wit', buy:'buy', earn:'earn' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } // ═══════════════════════════════════════════════════ // PROFILE // ═══════════════════════════════════════════════════ function loadProfile() { const user = S.user || { username: 'Guest' }; const displayName = S.streamerMode ? 'P2W Player' : user.username; const av = document.getElementById('profile-av-text'); const avEl = document.getElementById('profile-avatar'); if (av) av.textContent = initials(displayName); if (avEl) avEl.style.background = avatarColor(user.username); const pn = document.getElementById('profile-name'); if (pn) pn.textContent = displayName; const pj = document.getElementById('profile-joined'); if (pj) pj.textContent = 'Member since ' + new Date().toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }); document.getElementById('prof-level').textContent = bpCurrentPlayer ? bpLevel(bpCurrentPlayer.xp) : '—'; document.getElementById('prof-tokens').textContent = S.streamerMode ? '****' : S.tokens.toFixed(0); document.getElementById('prof-coins').textContent = S.streamerMode ? '****' : S.coins.toFixed(0); document.getElementById('prof-wins').textContent = bpCurrentPlayer?.wins || '—'; document.getElementById('prof-earned').textContent = S.transactions.filter(t=>t.amount>0&&t.currency==='tokens').reduce((a,t)=>a+t.amount,0).toFixed(0); // Load bank fields if (S.bankDetails.iban) document.getElementById('bank-iban').value = S.bankDetails.iban; if (S.bankDetails.name) document.getElementById('bank-fullname').value = S.bankDetails.name; if (S.bankDetails.bic) document.getElementById('bank-bic').value = S.bankDetails.bic; if (S.bankDetails.paypal) document.getElementById('bank-paypal').value = S.bankDetails.paypal; renderProfileTx(); renderMatchHistory(); renderEquippedCosmetics(); } function switchProfileTab(tab) { const tabs = ['matches','transactions','cosmetics','bank']; tabs.forEach(t => { document.getElementById('pf-' + t)?.classList.toggle('active', t === tab); document.querySelectorAll('.profile-tab').forEach((btn,i) => { if (btn.getAttribute('onclick')?.includes(t)) btn.classList.toggle('active', t === tab); }); }); } function renderProfileTx() { const el = document.getElementById('profile-tx-list'); if (!el || !S.transactions.length) return; const icons = { deposit:'✅', withdraw:'💸', buy:'🛍', earn:'🏆' }; el.innerHTML = S.transactions.map(tx => `
${icons[tx.type]||'•'}
${tx.name}
${tx.date}
${tx.amount>=0?'+':''}🪙${Math.abs(tx.amount)}
`).join(''); } function renderMatchHistory() { const el = document.getElementById('match-history-rows'); if (!el) return; if (!S.matchHistory.length) { el.innerHTML = '
No match history yet. Join a match to get started.
'; return; } el.innerHTML = S.matchHistory.map(m => `
${m.date}
${MODES[m.mode]||m.mode}
${m.result.toUpperCase()}
${m.result==='win'?'+🪙'+m.earned:'—'}
${m.kills||0}K
`).join(''); } function renderEquippedCosmetics() { const el = document.getElementById('equipped-items'); if (!el) return; const equipped = Object.values(S.equippedCosmetics); if (!equipped.length) { el.innerHTML = '
No cosmetics yet. Visit the Shop to get some.
'; return; } el.innerHTML = equipped.map(id => { const item = SHOP_ITEMS.find(i => i.id === id); if (!item) return ''; return `
${item.icon}
${item.rarity}
${item.name}
Equipped
`; }).join(''); } function saveBankDetails() { S.bankDetails = { iban: document.getElementById('bank-iban').value.trim(), name: document.getElementById('bank-fullname').value.trim(), bic: document.getElementById('bank-bic').value.trim(), paypal: document.getElementById('bank-paypal').value.trim(), }; localStorage.setItem('p2w_bank', JSON.stringify(S.bankDetails)); toast('✓ Bank details saved.', 'ok'); loadWallet(); } // ═══════════════════════════════════════════════════ // SETTINGS // ═══════════════════════════════════════════════════ function switchSetting(panel) { document.querySelectorAll('.settings-nav-item').forEach(el => el.classList.remove('active')); document.querySelectorAll('.settings-panel').forEach(el => el.classList.remove('active')); document.getElementById('sp-' + panel)?.classList.add('active'); event.currentTarget.classList.add('active'); } function toggleStreamerMode() { S.streamerMode = document.getElementById('streamer-mode').classList.contains('on'); updateNavUser(); updateNavTokens(); updateWalletDisplay(); if (S.streamerMode) toast('📡 Streamer Mode ON — username and balance hidden', 'info'); else toast('Streamer Mode OFF', 'info'); } function saveSettings() { const toggleIds = ['priv-public','priv-earnings','priv-winrate','priv-friends', 'notif-match','notif-result','notif-payout','notif-bp','notif-shop','notif-promo', 'streamer-mode','stream-hide-bal','stream-hide-bank','stream-blur-codes','stream-hc','stream-no-anim', 'app-compact','app-anim-stats','app-age-banner','sec-2fa','sec-withdraw-confirm','sec-login-alert']; toggleIds.forEach(id => { const el = document.getElementById(id); if (el) S.settings[id] = el.classList.contains('on'); }); localStorage.setItem('p2w_settings', JSON.stringify(S.settings)); } // ═══════════════════════════════════════════════════ // PAYMENT (match) // ═══════════════════════════════════════════════════ function setPay(btn, method) { document.querySelectorAll('#pay-s1 .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.payMethod = method; } function processPay() { const discord = document.getElementById('f-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const m = S.currentMatch, fee = m?.fee || 5; // Check wallet balance if (S.tokens < fee) { toast(`Not enough tokens. You have 🪙${S.tokens.toFixed(0)} — need 🪙${fee}. Buy more tokens in the Wallet.`, 'err'); closeModal('pay-overlay'); goPage('wallet'); return; } // Deduct tokens from wallet S.tokens -= fee; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'entry', name:`Entry fee — ${m.title}`, amount: -fee, currency:'tokens' }); // Update match player count in local state const match = S.matches.find(x => x.id === m.id); if (match) match.players = Math.min(match.players + 1, match.maxPlayers); closeModal('pay-overlay'); updateNavTokens(); renderMatches(); toast(`✅ Joined! 🪙${fee} tokens deducted. Check Discord #lobby-codes for your code.`, 'ok'); } function setBPPay(btn, method) { document.querySelectorAll('#bp-overlay .pay-m').forEach(b => b.classList.remove('active')); btn.classList.add('active'); S.bpPayMethod = method; } function processBPPay() { const discord = document.getElementById('bp-discord').value.trim(); if (!discord) { toast('Enter your Discord username.', 'err'); return; } const note = `P2W Premium Battlepass | ${discord}`; if (S.bpPayMethod === 'paypal') window.open(`${CFG.PAY.paypal.link}/10`, '_blank'); if (S.bpPayMethod === 'revolut') window.open(CFG.PAY.revolut.link, '_blank'); document.getElementById('bp-overlay').classList.remove('show'); setTimeout(() => toast(`✅ Payment opened · Note: "${note}" · Send proof to #battlepass-payments`, 'info'), 300); } // ═══════════════════════════════════════════════════ // ADMIN // ═══════════════════════════════════════════════════ function renderAdminGate() { if (S.adminLoggedIn) { document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); } } let S_adminLoggedIn = false; Object.defineProperty(S, 'adminLoggedIn', { get:()=>S_adminLoggedIn, set:(v)=>S_adminLoggedIn=v }); async function loadDBViewer() { const el = document.getElementById('db-viewer'); if (!el) return; el.innerHTML = '
Loading from JSONBin...
'; try { const data = await binGet(); const bp = data.battlepass || {}; const players = bp.players || {}; const count = Object.keys(players).length; el.innerHTML = `
${count}
BP Players
${Object.values(players).filter(p=>p.premium).length}
Premium
${(bp.xpLog||[]).length}
XP Events
Player Records
${count===0 ? '
No players yet
' : Object.entries(players).map(([k,p]) => `
${p.discord}${p.premium?' ⭐':''}
Lv ${bpLevel(p.xp||0)} ${(p.xp||0).toLocaleString()} XP ${p.wins||0}W ${p.coins ? `🥇${p.coins}` : ''}
`).join('')}
Synced ${new Date().toLocaleTimeString()} · Open full bin →
`; } catch(e) { el.innerHTML = `
Error: ${e.message}
Check CFG.API_KEY is correct.
`; } } async function adminLogin() { const pw = document.getElementById('admin-pw').value; if (pw !== CFG.ADMIN_PW) { toast('Wrong password.', 'err'); return; } S.adminLoggedIn = true; document.getElementById('admin-gate').style.display = 'none'; document.getElementById('admin-dash').classList.add('show'); loadAdminData(); toast('✓ Admin access granted.', 'ok'); } function adminLogout() { S.adminLoggedIn = false; document.getElementById('admin-gate').style.display = 'flex'; document.getElementById('admin-dash').classList.remove('show'); document.getElementById('admin-pw').value = ''; } async function loadAdminData() { // KPIs document.getElementById('admin-kpis').innerHTML = `
Revenue Today
€82
Active Players
${statsOnline}
Matches Live
${S.matches.filter(m=>m.status==='live').length}
Payouts Today
€48
`; // ── PENDING PURCHASES (most important for admin) ── renderPendingPurchases(); // Match list document.getElementById('admin-match-list').innerHTML = S.matches.slice(0,5).map(m => `
${m.title}
${MODES[m.mode]} · 🪙${m.entryFee} · ${m.players}/${m.maxPlayers}
`).join(''); document.getElementById('payout-total-badge').textContent = '€48 today'; document.getElementById('admin-payout-log').innerHTML = [ {user:'NexusEU',amt:'🪙32',time:'14:22'},{user:'ZenithKO',amt:'🪙18',time:'13:05'},{user:'FrostRapide',amt:'🪙14',time:'11:50'} ].map(p => `
${p.user}
${p.time}
${p.amt}
`).join(''); await loadBPAdminData(); } function renderPendingPurchases() { const el = document.getElementById('admin-pending-list'); if (!el) return; // Load from localStorage (all pending purchases submitted from this browser) const allPending = JSON.parse(localStorage.getItem('p2w_pending') || '[]').filter(p => p.status === 'pending'); const badge = document.getElementById('pending-badge'); if (badge) { badge.textContent = allPending.length > 0 ? allPending.length : ''; badge.style.display = allPending.length > 0 ? 'inline-flex' : 'none'; } if (!allPending.length) { el.innerHTML = '
No pending purchases
'; return; } el.innerHTML = allPending.map(p => `
#${p.id} Pending
${p.discord}
${p.method.toUpperCase()} · €${p.price} · ${new Date(p.timestamp).toLocaleString('nl-BE')}
🪙 ${p.tokens}
`).join(''); } function approvePurchase(id) { const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (!p) return; // Add tokens to user wallet S.tokens += p.tokens; localStorage.setItem('p2w_tokens', S.tokens.toString()); addTx({ type:'deposit', name:`Token purchase approved — ${p.tokens} tokens via ${p.method}`, amount: p.tokens, currency:'tokens' }); // Mark as approved p.status = 'approved'; localStorage.setItem('p2w_pending', JSON.stringify(all)); // Remove from DOM document.getElementById('pending-' + id)?.remove(); toast(`✓ Approved — 🪙${p.tokens} tokens added for ${p.discord}!`, 'ok'); updateNavTokens(); renderPendingPurchases(); } function rejectPurchase(id) { if (!confirm('Reject this purchase request?')) return; const all = JSON.parse(localStorage.getItem('p2w_pending') || '[]'); const p = all.find(x => x.id === id); if (p) p.status = 'rejected'; localStorage.setItem('p2w_pending', JSON.stringify(all)); document.getElementById('pending-' + id)?.remove(); toast(`Purchase #${id} rejected.`, 'info'); renderPendingPurchases(); } function adminToggleMatch(id, status) { const m = S.matches.find(x => x.id === id); if (!m) return; m.status = m.status === 'live' ? 'waiting' : 'live'; renderMatches(); loadAdminData(); toast(`Match "${m.title}" → ${m.status.toUpperCase()}`, 'ok'); } function adminCancelMatch(id) { if (!confirm('Cancel this match?')) return; S.matches = S.matches.filter(m => m.id !== id); renderMatches(); loadAdminData(); toast('Match cancelled.', 'ok'); } function openCreateMatch() { document.getElementById('cm-overlay').classList.add('show'); } function createMatchAdmin() { const mode = document.getElementById('cm-mode').value; const fee = parseInt(document.getElementById('cm-fee').value) || 5; const max = parseInt(document.getElementById('cm-max').value) || 10; const title= document.getElementById('cm-title').value.trim() || `${MODES[mode]} #${Date.now().toString().slice(-4)}`; S.matches.unshift({ id: Date.now(), mode, title, entryFee:fee, players:0, maxPlayers:max, prize:Math.round(fee*max*0.9), status:'waiting' }); document.getElementById('cm-overlay').classList.remove('show'); toast('✓ Match created!', 'ok'); renderMatches(); loadAdminData(); } // ═══════════════════════════════════════════════════ // BATTLEPASS ADMIN (JSONBin) // ═══════════════════════════════════════════════════ async function loadBPAdminData() { try { const bpData = await getBPData(); const players = Object.values(bpData.players || {}); const total = players.length, prem = players.filter(p=>p.premium).length; const avgLvl = total ? Math.round(players.reduce((a,p)=>a+bpLevel(p.xp),0)/total) : 0; const ta=document.getElementById('bpa-total');if(ta)ta.textContent=total; const pr=document.getElementById('bpa-prem'); if(pr)pr.textContent=prem; const al=document.getElementById('bpa-lvl'); if(al)al.textContent=avgLvl; const tbody = document.getElementById('bp-admin-tbody'); if (tbody) { const keys = Object.keys(bpData.players||{}).sort(); if (!keys.length) { tbody.innerHTML = 'No players yet'; } else { tbody.innerHTML = keys.map(key => { const p = bpData.players[key], lv = bpLevel(p.xp); return ` ${p.discord} ${lv} ${p.xp.toLocaleString()} ${p.premium?'':'Free'} `; }).join(''); } } const logEl = document.getElementById('bp-xp-log'); if (logEl) { const log = (bpData.xpLog||[]).slice(-20).reverse(); logEl.innerHTML = log.length ? log.map(e => `
${e.discord}
${e.reason||'Match'} · ${e.date}
+${e.xp} XP
`).join('') : '
No events yet
'; } } catch(e) { toast('BP load error: ' + e.message, 'err'); } } function filterBPTable(q) { document.querySelectorAll('#bp-admin-tbody tr[data-bpkey]').forEach(r => { r.style.display = r.dataset.bpname?.includes(q.toLowerCase()) ? '' : 'none'; }); } function openBPAddXP(discord) { document.getElementById('bpxp-player').value = discord || ''; document.getElementById('bpxp-amount').value = 250; document.getElementById('bpxp-reason').value = ''; document.getElementById('bpxp-overlay').classList.add('show'); } async function submitBPXP() { const discord = document.getElementById('bpxp-player').value.trim(); const xp = parseInt(document.getElementById('bpxp-amount').value); const reason = document.getElementById('bpxp-reason').value.trim() || 'Match played'; if (!discord || !xp || xp < 1) { toast('Fill all fields.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (!bpData.players[key]) { bpData.players[key] = { discord, xp:0, premium:false, wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; } const before = bpData.players[key].xp; bpData.players[key].xp += xp; if (reason.toLowerCase().includes('win')) bpData.players[key].wins = (bpData.players[key].wins||0)+1; bpData.players[key].matches = (bpData.players[key].matches||0)+1; bpData.xpLog = bpData.xpLog || []; bpData.xpLog.push({ discord, xp, reason, date:new Date().toISOString().slice(0,10) }); if (bpData.xpLog.length > 200) bpData.xpLog = bpData.xpLog.slice(-200); await saveBPData(bpData); const lvBefore = bpLevel(before), lvAfter = bpLevel(bpData.players[key].xp); document.getElementById('bpxp-overlay').classList.remove('show'); toast(lvAfter > lvBefore ? `⭐ ${discord} +${xp}XP — Level UP! ${lvBefore}→${lvAfter}` : `⚡ ${discord} +${xp}XP (Level ${lvAfter})`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: ' + e.message, 'err'); } } async function bpGrantPremium(key, discord) { if (!confirm(`Grant Premium to ${discord}?`)) return; try { const bpData = await getBPData(); if (!bpData.players[key]) { toast('Player not found.', 'err'); return; } bpData.players[key].premium = true; await saveBPData(bpData); toast(`⭐ Premium granted to ${discord}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } async function bpRemovePlayer(key, discord) { if (!confirm(`Remove ${discord}?`)) return; try { const bpData = await getBPData(); delete bpData.players[key]; await saveBPData(bpData); toast(`${discord} removed.`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } function openBPAddPlayer() { document.getElementById('bpadd-discord').value = ''; document.getElementById('bpadd-pass').value = 'free'; document.getElementById('bpadd-overlay').classList.add('show'); } async function submitBPAddPlayer() { const discord = document.getElementById('bpadd-discord').value.trim(); const pass = document.getElementById('bpadd-pass').value; if (!discord) { toast('Enter Discord name.', 'err'); return; } try { const bpData = await getBPData(); const key = bpNorm(discord); if (bpData.players[key]) { toast('Player already exists.', 'err'); return; } bpData.players[key] = { discord, xp:0, premium:pass==='premium', wins:0, matches:0, joinedAt:new Date().toISOString().slice(0,10), claimedRewards:[] }; await saveBPData(bpData); document.getElementById('bpadd-overlay').classList.remove('show'); toast(`✓ ${discord} added${pass==='premium'?' with Premium':''}!`, 'ok'); loadBPAdminData(); } catch(e) { toast('Error: '+e.message,'err'); } } // ═══════════════════════════════════════════════════ // MODAL HELPERS // ═══════════════════════════════════════════════════ function closeModal(id) { document.getElementById(id)?.classList.remove('show'); } // ═══════════════════════════════════════════════════ // TOAST // ═══════════════════════════════════════════════════ let _tt; function toast(msg, type='ok') { clearTimeout(_tt); const t=document.getElementById('toast'); t.className='toast '+type; document.getElementById('toast-ico').textContent=type==='ok'?'✓':type==='err'?'✕':'ℹ'; document.getElementById('toast-msg').textContent=msg; t.classList.add('show'); _tt=setTimeout(()=>t.classList.remove('show'),4500); } // ═══════════════════════════════════════════════════ // BOOT // ═══════════════════════════════════════════════════ (function boot() { // Restore user const savedUser = localStorage.getItem('p2w_user'); if (savedUser) { try { S.user = JSON.parse(savedUser); updateNavUser(); } catch {} } // Restore settings toggles const settings = S.settings; Object.keys(settings).forEach(id => { const el = document.getElementById(id); if (el && el.classList.contains('toggle')) { if (settings[id]) el.classList.add('on'); else el.classList.remove('on'); } }); // Handle Discord OAuth return const params = new URLSearchParams(window.location.search); if (params.get('token')) { localStorage.setItem('p2w_token', params.get('token')); S.token = params.get('token'); history.replaceState({}, '', window.location.pathname); toast('✓ Logged in via Discord!', 'ok'); } if (params.get('payment') === 'success') { toast('🎉 Payment confirmed! Check Discord for your lobby code.', 'ok'); history.replaceState({}, '', window.location.pathname); } // Init pages renderBPPublicTrack(); goPage('home'); })();