bloxidnew / index.html
web3district's picture
undefined - Initial Deployment
194711b verified
Raw
History Blame Contribute Delete
260 kB
<!doctype html>
<?php
if(file_exists('./bot/.maintenance.txt')){
header('location: /maintenance');
die;
}
session_start();
// Force fresh fetch (help bypass CDN/browser caches during rapid deploys)
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<!-- build-tag: v-codex-live-headers-1 -->
<title>BloxID - Telegram WebApp Clicker Game</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Cache busting timestamp: 2025-09-01 03:17:00 -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
bg: '#000000',
card: '#111111',
muted: '#1a1a1a',
text: '#ffffff',
accent: 'rgb(228, 24, 95)',
accent2: '#ff2a6d',
secondary: '#00f0ff',
tertiary: '#8a8a8a'
},
fontFamily: {
sans: ['Inter', 'sans-serif']
}
}
}
}
// Telegram Login (desktop link) with direct OAuth fallback
function openTelegramLogin(){
// Direct OAuth URL (no widget). Bot ID taken from bot token.
const BOT_ID = '8487518701';
const origin = encodeURIComponent(window.location.origin);
const returnTo = encodeURIComponent('/api/telegram/auth_redirect.php');
const url = `https://oauth.telegram.org/auth?bot_id=${BOT_ID}&origin=${origin}&embed=0&request_access=write&return_to=${returnTo}`;
// Always navigate in the same tab to avoid popup blockers
window.location.href = url;
return;
// Start polling for cookie set by auth_redirect.php (last-resort fallback)
try {
let attempts = 0;
const maxAttempts = 40; // ~20s
const poll = setInterval(() => {
attempts++;
try {
const m = document.cookie.match(/(?:^|; )tg_user=([^;]+)/);
if (m) {
const raw = decodeURIComponent(m[1]);
const user = JSON.parse(atob(raw));
if (user && user.id) {
console.log('🕑 Cookie polling captured Telegram user');
clearInterval(poll);
document.cookie = 'tg_user=; Max-Age=0; path=/';
onTelegramAuth(user);
}
}
} catch(e) {}
if (attempts >= maxAttempts) clearInterval(poll);
}, 500);
} catch(e) {}
}
// Listen for fallback auth messages from auth_redirect.php
window.addEventListener('message', function(event){
try {
if (!event || !event.data) return;
if (event.data.type === 'telegram-auth' && event.data.user) {
console.log('📨 Received Telegram auth via postMessage');
onTelegramAuth(event.data.user);
}
} catch(e) { }
});
// Storage event fallback (fires when auth_redirect writes localStorage)
window.addEventListener('storage', function(e){
try {
if (e.key === 'tg_user' && e.newValue) {
const user = JSON.parse(atob(e.newValue));
if (user && user.id) {
console.log('🧰 Received Telegram auth via storage event');
onTelegramAuth(user);
}
}
} catch(err) {}
});
async function onTelegramAuth(user){
try {
const res = await fetch('/api/telegram/login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user })
});
const data = await res.json();
if (data && data.ok) {
currentUser = { id: data.tid, first_name: user.first_name, username: data.username, photo_url: user.photo_url };
isLoggedIn = true;
try { localStorage.setItem('bloxid_current_user', JSON.stringify(currentUser)); } catch(e) {}
// Provide an app token for downstream API calls (desktop web flow)
try { sessionStorage.setItem('app_token', data.hash || 'telegram'); } catch(e) {}
updateUserProfile(currentUser);
// Trigger data loads now that we are logged in
try { loadUserData(); } catch(e) {}
try { loadQuests(); } catch(e) {}
// Persist to Admin users with session type
try {
const sessionType = (window.Telegram && window.Telegram.WebApp) ? 'mobile' : 'web_desktop';
fetch('/api/track_user.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ telegram_user_id: Number(currentUser.id), username: currentUser.username||currentUser.first_name||null, session_type: sessionType })
}).catch(()=>{});
} catch(e) {}
showNotification('✅ Telegram linked successfully!', 'success');
// Update connect button UI
try {
const btn = document.getElementById('tgConnectBtn');
const txt = document.getElementById('tgBtnText');
if (btn) btn.classList.add('bg-blue-600','border-blue-500');
if (txt) txt.textContent = 'Connected';
} catch(e) {}
const m = document.getElementById('tgLoginModal'); if (m) m.remove();
} else {
showNotification('⚠️ Telegram link failed', 'error');
}
} catch (e) {
showNotification('⚠️ Telegram link error', 'error');
}
}
// Ensure Telegram widget can reach the handler in global scope
window.onTelegramAuth = onTelegramAuth;
</script>
<script>
// TEMP: Minimal Thirdweb login shim (no SDK) to unblock unified login
async function thirdwebLoginShim(){
try {
const email = prompt('Enter email to link Thirdweb'); if (!email) return;
let wallet = prompt('Paste wallet (0x...) or leave blank');
if (!wallet || wallet.length < 6) {
wallet = 'dev_' + (localStorage.getItem('tw_dev_wallet') || (function(){
const w = '0x' + Math.random().toString(16).slice(2) + Date.now().toString(16);
localStorage.setItem('tw_dev_wallet', w); return w;
})());
}
const thirdweb_user_id = 'tw_' + btoa(email).replace(/[^a-zA-Z0-9]/g,'').slice(0,24);
const session_type = 'web_desktop';
// Try server, but don’t block UX if it fails
let serverOk = false, appToken = null;
try {
const res = await fetch('/api/auth/thirdweb_login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ thirdweb_user_id, email, wallet_address: wallet, session_type })
});
const j = await res.json().catch(()=>null);
if (j && j.ok) { serverOk = true; appToken = j.app_token || null; }
} catch (e) {}
// Always mark connected locally (sync later if server failed)
try {
if (appToken) sessionStorage.setItem('app_token', appToken);
localStorage.setItem('thirdweb_email', email);
localStorage.setItem('thirdweb_wallet', wallet);
sessionStorage.setItem('thirdweb_connected', '1');
} catch (e) {}
if (typeof showNotification === 'function') {
showNotification(serverOk ? '✅ Thirdweb connected' : '✅ Connected (server sync pending)', serverOk ? 'success' : 'warning');
} else {
alert(serverOk ? '✅ Thirdweb connected' : '✅ Connected (server sync pending)');
}
// Optional: update button UI
try {
const btn = document.getElementById('twConnectBtn') || document.getElementById('twConnectBtnStatic');
if (btn) btn.textContent = 'Connected';
} catch (e) {}
} catch (e) {
if (typeof showNotification === 'function') showNotification('⚠️ Thirdweb error', 'error');
else alert('⚠️ Thirdweb error');
}
}
</script>
<script> document.addEventListener('DOMContentLoaded', function(){ try { if (sessionStorage.getItem('thirdweb_connected') === '1') { const btn = document.getElementById('twConnectBtn') || document.getElementById('twConnectBtnStatic'); if (btn) btn.textContent = 'Connected'; } } catch(e) {} }); </script>
<script>
// Inject a desktop-only Thirdweb connect button for MVP
document.addEventListener('DOMContentLoaded', function(){
try {
// Detect real Telegram WebApp via user agent (shim should not block desktop)
const isRealTG = /Telegram/i.test(navigator.userAgent || '');
if (isRealTG) return;
// Try to place below the Telegram connect button if it exists
const tgBtn = document.getElementById('tgConnectBtn');
const twBtn = document.createElement('button');
twBtn.id = 'twConnectBtn';
twBtn.textContent = 'Connect with Thirdweb';
twBtn.onclick = thirdwebLoginShim;
if (tgBtn) {
// Match size/styles by cloning classes; add margin-top
try { twBtn.className = tgBtn.className; } catch(e) {}
try { twBtn.style.marginTop = '8px'; } catch(e) {}
tgBtn.parentNode.insertBefore(twBtn, tgBtn.nextSibling);
} else {
// Fallback fixed button if we can't find tg button
twBtn.style.position = 'fixed';
twBtn.style.zIndex = '1000';
twBtn.style.right = '16px';
twBtn.style.top = '70px';
twBtn.style.padding = '10px 14px';
twBtn.style.borderRadius = '10px';
twBtn.style.border = '1px solid rgba(255,255,255,0.2)';
twBtn.style.background = 'linear-gradient(135deg, #6366f1, #06b6d4)';
twBtn.style.color = '#fff';
twBtn.style.cursor = 'pointer';
document.body.appendChild(twBtn);
}
} catch(e) {}
});
</script>
<style>
:root {
/* Cyberpunk Elegance Color Scheme */
--bg: #0f172a;
--card: #334155;
--muted: #475569;
--text: #f8fafc;
--accent: #6366f1;
--accent-2: #06b6d4;
--secondary: #f59e0b;
--tertiary: #64748b;
/* Additional colors */
--success: #10b981;
--warning: #f97316;
--error: #ef4444;
--bg-secondary: #1e293b;
}
/* Google Fonts loaded via <link> tags in <head> */
body {
background-color: var(--bg);
color: var(--text);
font-family: 'Inter', sans-serif;
overflow-x: hidden;
user-select: none;
line-height: 1.6;
/* Mobile optimizations */
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
/* Smooth scrolling */
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
margin: 0;
padding: 0;
}
/* Prevent zoom on double tap */
* {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
/* Mobile-specific styles */
@media (max-width: 768px) {
body {
overflow-x: hidden; /* Only prevent horizontal scrolling */
overflow-y: auto; /* Allow vertical scrolling */
width: 100%;
min-height: 100vh;
min-height: 100dvh;
}
/* Ensure main container allows scrolling */
.min-h-screen {
min-height: 100vh;
min-height: 100dvh;
}
/* Show Aura Display Box on mobile as requested */
.absolute.top-4.right-4 { display: block !important; }
.goldenblox-responsive {
display: none !important; /* Hide GBLX Display Box */
}
/* Fix mobile progress bar text alignment */
.absolute.bottom-0.left-0.right-0.p-4.md\\:hidden #passportTapLevelNextTextMobile {
display: none !important; /* Hide misaligned "Need X Aura" text */
}
/* Hide Force Update button and any problematic white text on mobile */
button[onclick="forceLevelUpdate()"] {
display: none !important;
}
/* Hide any white text elements that might be misaligned in Level Progress box */
@media (max-width: 768px) {
/* Only hide white text in specific problematic areas, not everywhere */
.card-glass .text-white:not(.my-bloxid-text):not(.wallet-button-text):not(.aura-progress-text):not(.level-info-text):not(.prevent-hide) {
color: white !important; /* Make white text visible on mobile */
}
/* But keep the level number visible */
#passportLevelNumber {
color: white !important;
}
/* Ensure My BloxID text is always visible */
.my-bloxid-text {
color: white !important;
}
/* Ensure wallet button text is always visible */
.wallet-button-text {
color: white !important;
fill: white !important;
opacity: 1 !important;
}
/* Ensure wallet icon stays visible */
.wallet-icon { fill: #ffffff !important; opacity: 1 !important; }
/* Make left info container full-width and allow button 100% width */
.left-info-container { width: calc(100% - 2rem); max-width: none; }
#walletConnectBtn { width: 6rem !important; } /* Match w-24 (96px) */
}
/* Also hide desktop level progress bar on mobile */
.absolute.bottom-0.left-0.right-0.p-4.hidden.md\\:block {
display: none !important; /* Hide Desktop Level Progress Bar */
}
/* Hide badges from user info on mobile */
.mt-2.flex.flex-col.space-y-1 {
display: none !important; /* Hide Badges Below User Info */
}
/* Move user info down by 78px more */
.absolute.top-4.left-4 .mt-2.card-glass {
position: fixed !important;
top: auto !important;
bottom: 17px !important; /* 95px - 78px = 17px from bottom navigation */
left: 50% !important;
transform: translateX(-50%) !important;
z-index: 20 !important;
margin-top: 0 !important;
}
/* Make user info more compact on mobile */
.absolute.top-4.left-4 .mt-2.card-glass .flex.items-center.space-x-2 {
justify-content: center !important;
}
/* Hide BloxID logo on mobile for cleaner tapping area */
.absolute.top-4.left-4 .card-glass.rounded-xl.overflow-hidden.bg-white\\/10:not(.mt-2) {
display: none !important; /* Hide BloxID Logo */
}
/* Hide "My BloxID" text label on mobile */
.absolute.top-4.left-1\\/2 {
display: none !important; /* Hide My BloxID Text Label */
}
/* Hide the level progress bar that appears in the mohawk head tapping zone */
.absolute.bottom-0.left-0.right-0.p-4 {
display: none !important; /* Hide all progress bars in tapping zone */
}
}
.card-glass {
background: linear-gradient(145deg, rgba(51, 65, 85, 0.9), rgba(30, 41, 59, 0.8));
backdrop-filter: blur(12px);
border: 1px solid rgba(99, 102, 241, 0.2);
transition: all 0.3s ease;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.card-glass:hover {
border: 1px solid rgba(99, 102, 241, 0.4);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transform: translateY(-2px);
}
/* Card hover animations for desktop */
.card-hover {
transition: all 0.3s ease;
transform: translateY(0);
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
border-color: rgba(99, 102, 241, 0.6);
}
.tap-button {
background: linear-gradient(145deg, rgba(228, 24, 95, 0.8), rgba(0, 240, 255, 0.6));
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.2s ease;
}
.tap-button:active {
transform: scale(0.95);
background: linear-gradient(145deg, rgba(228, 24, 95, 1), rgba(0, 240, 255, 0.8));
}
.nav-btn {
transition: all 0.3s ease;
border-radius: 0.75rem;
padding: 0.75rem 1.5rem;
font-weight: 600;
}
.nav-btn.active {
background: rgba(255, 255, 255, 0.1) !important;
backdrop-filter: blur(12px) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
color: white !important;
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1) !important;
transform: none !important;
}
.nav-btn:hover:not(.active) {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(6px);
border: 1px solid rgba(255, 255, 255, 0.08);
transform: translateY(-1px);
}
.notification {
background: linear-gradient(145deg, rgba(99, 102, 241, 0.2), rgba(6, 182, 212, 0.1));
backdrop-filter: blur(12px);
border: 1px solid rgba(99, 102, 241, 0.3);
}
/* New button styles */
.btn-primary {
background: linear-gradient(135deg, var(--accent), var(--accent-2));
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0.75rem;
font-weight: 600;
transition: all 0.3s ease;
border: none;
cursor: pointer;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.4);
}
.btn-secondary {
background: rgba(99, 102, 241, 0.1);
color: var(--accent);
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.3s ease;
border: 1px solid rgba(99, 102, 241, 0.2);
cursor: pointer;
}
.btn-secondary:hover {
background: rgba(99, 102, 241, 0.2);
border-color: rgba(99, 102, 241, 0.4);
transform: translateY(-1px);
}
/* Typography improvements */
.text-display {
font-size: 2.5rem;
font-weight: 800;
line-height: 1.2;
}
.text-heading {
font-size: 1.5rem;
font-weight: 700;
line-height: 1.3;
}
.text-subtitle {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.4;
}
.text-body {
font-size: 1rem;
font-weight: 400;
line-height: 1.6;
}
.text-caption {
font-size: 0.875rem;
font-weight: 400;
line-height: 1.5;
}
.tap-effect {
animation: floatUp 0.6s ease-out forwards;
}
@keyframes floatUp {
0% {
opacity: 1;
transform: translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateY(-40px) scale(1.1);
}
}
</style>
</head>
<body class="bg-slate-900 text-slate-100" data-build="v-raven-buddy-1">
<!-- Main App Container -->
<div class="min-h-screen flex flex-col">
<!-- Main Content -->
<main class="flex-1 px-4 sm:px-6 lg:px-8 py-6">
<div class="max-w-7xl mx-auto">
<!-- PASSPORT Tab -->
<div id="passportTab" class="hidden space-y-6">
<!-- Hero Section for Quests with BloxID Features -->
<section class="pt-2 pb-4 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<!-- Main Quest Hero Box with BloxID Features -->
<div class="card-glass rounded-2xl overflow-hidden p-0 lg:col-span-2 relative hover:border-accent/50 transition-colors" style="min-height: 400px; background: linear-gradient(135deg, rgba(124, 76, 228, 0.15) 0%, rgba(0, 255, 255, 0.15) 100%);">
<!-- Mini Logo with My BloxID Logo -->
<div class="absolute top-4 left-4 z-10 left-info-container">
<div class="card-glass rounded-xl overflow-hidden bg-white/10 border border-white/20 backdrop-blur-sm relative w-24 h-24 lg:w-32 lg:h-32 p-2">
<img src="bloxid_cube_NEW_PNG.png" alt="My BloxID Logo" class="w-full h-full object-contain">
</div>
<!-- Desktop-only: User info card under logo -->
<div class="hidden md:block mt-2 card-glass rounded-xl p-2 bg-white/10 border border-white/20 backdrop-blur-sm">
<div class="flex items-center space-x-2">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-accent to-secondary flex items-center justify-center font-bold text-xs">U</div>
<div class="text-left">
<div class="font-semibold text-xs" id="usernameDesktop">User</div>
<div class="text-xs text-gray-300">Level 1</div>
</div>
</div>
</div>
<!-- Connect buttons below logo (stack left on desktop) -->
<div class="mt-2 flex md:flex-col items-center md:items-start space-x-2 md:space-x-0 md:space-y-2">
<button id="walletConnectBtn" onclick="connectTelegramWallet()" class="card-glass rounded-xl px-3 py-2 border text-white font-semibold text-xs flex items-center space-x-2 wallet-button-text prevent-hide">
<svg class="w-4 h-4 wallet-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#0098EA"/><path d="M12 6l5.5 5.5L12 18 6.5 11.5 12 6z" fill="#fff"/></svg>
<span id="walletBtnText" class="wallet-button-text prevent-hide">Connect</span>
</button>
<button id="tgConnectBtn" onclick="openTelegramLogin()" class="hidden md:flex card-glass rounded-xl px-3 py-2 border text-white font-semibold text-xs items-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-4 h-4" fill="currentColor"><path d="M9.74 15.02l-.4 5.64c.57 0 .82-.24 1.12-.53l2.68-2.56 5.55 4.07c1.02.56 1.74.27 2.01-.95l3.63-17.03h.01c.32-1.48-.53-2.06-1.51-1.7L1.23 9.74c-1.46.57-1.44 1.39-.25 1.76l5.66 1.77L19.4 6.02c.63-.42 1.2-.19.73.23"/></svg>
<span id="tgBtnText">Connect</span>
</button>
</div>
</div>
<!-- Aura Display Box (top right corner) -->
<div class="absolute top-4 right-4 z-10">
<div class="card-glass rounded-xl overflow-hidden bg-white/10 border border-white/20 backdrop-blur-sm relative w-24 h-24 lg:w-32 lg:h-32 p-2">
<div class="text-center h-full flex flex-col justify-center">
<div class="flex flex-col items-center justify-center mb-1">
<img src="aura_new.png" alt="Aura" class="w-8 h-8 lg:w-10 lg:h-10 object-contain mb-1">
<h2 class="text-lg lg:text-2xl xl:text-3xl font-bold text-white" id="passportScoreDisplay">12,450</h2>
</div>
<p class="text-white text-xs">Total Aura</p>
</div>
</div>
</div>
<!-- GBLX Display Box (mobile perfect, desktop perfect) -->
<div class="absolute right-4 z-10 goldenblox-responsive">
<style>
.goldenblox-responsive {
top: 122px; /* Mobile default */
}
@media (min-width: 768px) {
.goldenblox-responsive {
top: 152px !important; /* Desktop override */
}
}
</style>
<div class="card-glass rounded-xl overflow-hidden bg-white/10 border border-white/20 backdrop-blur-sm relative w-24 h-24 lg:w-32 lg:h-32 p-2">
<div class="text-center h-full flex flex-col justify-center">
<div class="flex flex-col items-center justify-center mb-1">
<img src="GoldenBlox_NEW.png" alt="GBLX" class="w-8 h-8 lg:w-10 lg:h-10 object-contain mb-1">
<h2 class="text-lg lg:text-2xl xl:text-3xl font-bold text-white" id="passportTapGBLXDisplay">1,250</h2>
</div>
<p class="text-gray-300 text-xs">GBLX</p>
</div>
</div>
</div>
<!-- My BloxID Text Label at Top Center -->
<div class="absolute top-4 left-1/2 transform -translate-x-1/2 z-10">
<span class="text-sm font-bold text-white bg-black/20 px-3 py-1 rounded-full my-bloxid-text">My BloxID</span>
</div>
<!-- Bottom-left user info (separate container from logo/buttons) -->
<div class="absolute left-4 bottom-4 z-10 md:hidden">
<div class="card-glass rounded-lg p-2 bg-white/10 border border-white/20 backdrop-blur-sm">
<div class="flex items-center space-x-2">
<div class="w-7 h-7 rounded-full bg-gradient-to-r from-accent to-secondary flex items-center justify-center font-bold text-xs">U</div>
<div class="font-semibold text-xs truncate max-w-[160px]" id="usernameBottom">User</div>
</div>
</div>
</div>
<!-- Tap Area Content -->
<div class="absolute inset-0 flex flex-col justify-center items-center p-4 md:p-8 text-center">
<div class="space-y-4 md:space-y-6">
<!-- Tap Button with Mohawk Cube Head (30% bigger) -->
<div>
<button onclick="enhancedTap()" style="background: none; border: none; padding: 0;">
<img src="mohawk_cube_head.png" alt="Mohawk Cube Head"
class="tap-image"
style="max-width: 260px; max-height: 260px;">
</button>
</div>
<!-- Aura Progress Bar in Bottom Right Corner (Mobile) -->
<div class="absolute bottom-4 right-4 md:hidden z-20">
<div class="card-glass rounded-lg p-2 bg-white/10 border border-white/20 backdrop-blur-sm w-28" onclick="showAuraDetails()">
<div class="text-center mb-1">
<span class="text-xs text-white font-semibold aura-progress-text">Aura Progress</span>
<div class="text-xs text-gray-300 font-medium mt-0.5 level-info-text" id="mobileLevelInfo">Level 0 | Need 100 to level 2</div>
</div>
<div class="w-full bg-white/20 rounded-full h-2 backdrop-blur-sm relative overflow-hidden">
<div id="mobileAuraProgress" class="bg-gradient-to-r from-accent to-accent2 h-2 rounded-full" style="width: 0%"></div>
</div>
<div class="text-center">
<span class="text-xs text-white aura-progress-text" id="mobileAuraText">0 / 100</span>
</div>
</div>
</div>
</div>
</div>
<!-- Mobile Level Progress Bar at Bottom -->
<div class="absolute bottom-0 left-0 right-0 p-4 md:hidden">
<div class="card-glass rounded-xl p-2 bg-white/5 border border-white/10 backdrop-blur-sm">
<div class="flex items-center justify-between text-xs mb-1">
<span id="passportTapLevelNumberMobile" class="font-semibold text-xs">Level 1</span>
<span id="passportTapLevelNextTextMobile" class="text-gray-400 text-xs">Need 100 Aura</span>
</div>
<div class="w-full bg-white/10 rounded-full h-2 backdrop-blur-sm">
<div id="passportTapLevelProgressFillMobile" class="bg-gradient-to-r from-accent to-accent2 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
</div>
</div>
<!-- Desktop Level Progress Bar at Bottom -->
<div class="absolute bottom-0 left-0 right-0 p-4 hidden md:block">
<div class="card-glass rounded-xl p-2 bg-white/5 border border-white/10 backdrop-blur-sm">
<div class="flex items-center justify-between text-xs mb-1">
<span id="passportTapLevelNumberDesktop" class="font-semibold text-xs">Level 1</span>
<span id="passportTapLevelNextTextDesktop" class="text-gray-400 text-xs">Need 100 Aura to reach Level 2</span>
</div>
<div class="w-full bg-white/10 rounded-full h-2 backdrop-blur-sm">
<div id="passportTapLevelProgressFillDesktop" class="bg-gradient-to-r from-accent to-accent2 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
</div>
</div>
</div>
<!-- GBLX Box -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-yellow-900/20 to-orange-900/20 p-4 relative">
<!-- Tag in top left corner -->
<div class="absolute top-4 left-4 z-10">
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
GBLX
</div>
</div>
<!-- Content centered -->
<div class="h-full flex flex-col justify-center items-center">
<div class="flex items-center justify-center mb-2">
<img src="GoldenBlox_NEW.png" alt="GBLX" class="w-40 h-40">
</div>
<h3 class="text-lg font-bold mt-1" id="passportBalanceDisplay">1,250</h3>
<p class="text-gray-400 text-xs mt-1">Spendable GBLX</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs" onclick="tap()">
Tap to spend
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Additional BloxID Feature Boxes -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mt-4">
<!-- Badge Vault Box -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-blue-900/20 to-indigo-900/20 p-4 relative">
<!-- Tag in top left corner -->
<div class="absolute top-4 left-4 z-10">
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
🏆 Badge Vault
</div>
</div>
<!-- Content with 14 badge slots -->
<div class="h-full flex flex-col justify-center pt-8">
<div class="grid grid-cols-7 gap-2">
<!-- Filled Badge Slots (3) -->
<button onclick="showBadgeModal('blueblox')" class="badge-slot w-12 h-12 rounded-lg hover:bg-white/10 transition-all duration-200 flex items-center justify-center relative group">
<img src="BlueBlox_NEW.png" alt="BlueBlox Badge" class="w-8 h-8 object-contain">
</button>
<button onclick="showBadgeModal('problox')" class="badge-slot w-12 h-12 rounded-lg hover:bg-white/10 transition-all duration-200 flex items-center justify-center relative group">
<img src="ProBlox_NEW.png" alt="ProBlox Badge" class="w-8 h-8 object-contain">
</button>
<button onclick="showBadgeModal('mayorblox')" class="badge-slot w-12 h-12 rounded-lg hover:bg-white/10 transition-all duration-200 flex items-center justify-center relative group">
<img src="mayorblox_NEW.png" alt="MayorBlox Badge" class="w-8 h-8 object-contain">
</button>
<!-- Empty Badge Slots (11) -->
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
<div class="badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group">
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Monument Spotlight Box -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-purple-900/20 to-pink-900/20 p-4 relative" id="questMonumentSpotlightBox">
<!-- Tag in top left corner -->
<div class="absolute top-4 left-4 z-10">
<div id="questMonumentSpotlightTag" class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
StreetBlox Nearby
</div>
</div>
<!-- Content with single nearby monument -->
<div class="h-full flex flex-col justify-start items-center pt-16 space-y-4">
<!-- Single Nearby Monument Card -->
<div class="w-full">
<div class="card-glass rounded-lg p-3 bg-white/5 border border-white/10 backdrop-blur-sm">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 flex items-center justify-center">
<img src="Streetblox_NEW.png" alt="StreetBlox" class="w-5 h-5 object-contain">
</div>
<div>
<div class="text-sm font-semibold">Praça do Comércio</div>
<div class="text-xs text-gray-400">420 GBLX • Nearby</div>
</div>
</div>
<button class="text-xs text-accent hover:text-accent-2 transition-colors px-2 py-1 rounded bg-white/5">
Conquer
</button>
</div>
</div>
</div>
<!-- View All Button -->
<button class="inline-flex items-center text-accent font-medium text-xs mt-2" onclick="showTab('monuments')">
View All StreetBlox
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Level Progress Box -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-emerald-900/20 to-teal-900/20 p-4 relative">
<!-- Tag in top left corner -->
<div class="absolute top-2 left-2 z-10">
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium text-emerald-400">
Level Progress
</div>
</div>
<!-- Content centered -->
<div class="h-full flex flex-col justify-end pt-8">
<h3 class="text-lg font-bold mt-1" id="passportLevelNumber">Level 1</h3>
<div class="mt-2 bg-muted rounded-full h-2 overflow-hidden">
<div id="passportLevelProgressFill" class="bg-gradient-to-r from-accent to-secondary h-2 rounded-full transition-all duration-300 ease-out" style="width: 0%"></div>
</div>
<p class="text-gray-400 text-xs mt-2" id="passportLevelNextText">Need 100 Aura to reach Level 2</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs" onclick="forceLevelUpdate()">
Force Update
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Featured Quests Section -->
<section class="py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold">Featured Quests</h2>
<a href="#" class="flex items-center text-accent hover:text-accent-2 transition-colors">
View All
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
<div id="passportFeaturedGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Connect TON Wallet Quest (Main Featured) -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-pink-600/30 to-purple-600/30 h-48 flex items-center justify-center">
<img src="ProBlox_NEW.png" alt="ProBlox" class="w-20 h-20 object-contain opacity-90">
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Connect TON Wallet</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Featured
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Connect and earn 150 GBLX + 3× multiplier while connected.
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]" onclick="startTonWalletQuest()">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Add Friend Quest -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-emerald-600/30 to-teal-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M19 8l2 2-2 2"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Add Friend Quest</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Add a friend by their Telegram ID and earn 25 GBLX!
</p>
<button onclick="startAddFriendQuest()" class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-accent hover:text-white">
Start Quest (25 GBLX)
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Quest Card 1 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-purple-600/30 to-cyan-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">NFT Collector Quest</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Collect 10 unique NFTs to unlock exclusive rewards
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Friend Referral Test Quest -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-green-600/30 to-blue-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="m19 8 2 2-2 2"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Friend Referral Test</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Share this app with 1 friend and get 50 GBLX instantly!
</p>
<button onclick="startQuest(1, 'Friend Referral Test')" class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-accent hover:text-white">
Start Quest (50 GBLX)
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div> <!-- Quest Card 2 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-indigo-600/30 to-emerald-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">DeFi Master Quest</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Complete 5 DeFi protocols to earn master badge
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Quest Card 3 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-violet-600/30 to-blue-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">DAO Explorer Quest</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Participate in 3 DAO governance decisions
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Quest Card 4 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-amber-600/30 to-yellow-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Metaverse Pioneer</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Explore 5 different metaverse worlds
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- QUESTS Tab -->
<div id="questsTab" class="hidden space-y-6">
<!-- Quest Hero Section -->
<section class="pt-2 pb-4 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<!-- Main Quest Hero Box -->
<div class="card-glass rounded-2xl overflow-hidden p-0 lg:col-span-2 relative hover:border-accent/50 transition-colors" style="min-height: 400px; background: linear-gradient(135deg, rgba(124, 76, 228, 0.15) 0%, rgba(0, 255, 255, 0.15) 100%);">
<!-- Mini Bento Box with QuestBlox Logo -->
<div class="absolute top-4 left-4 w-24 h-24 lg:w-32 lg:h-32 z-10">
<div class="card-glass rounded-xl overflow-hidden h-full bg-white/10 border border-white/20 backdrop-blur-sm relative">
<div class="h-full flex items-center justify-center p-2">
<img src="QuestBlox_NEW.png" alt="QuestBlox" class="w-full h-full object-contain">
</div>
</div>
</div>
<!-- Quest Hero Content -->
<div class="absolute bottom-0 left-0 w-full h-full flex flex-col justify-end items-start p-4 md:p-8 text-left pointer-events-none">
<div>
<h2 class="text-xl md:text-3xl font-bold mb-2">Discover <span class="bg-gradient-to-r from-accent2 to-accent bg-clip-text text-transparent">Amazing Quests</span></h2>
<p class="text-xs md:text-sm text-gray-300 mb-4 max-w-md">
Complete challenges, earn rewards, and unlock exclusive experiences in the Web3 ecosystem.
</p>
<div class="flex flex-col sm:flex-row gap-2 pointer-events-auto">
<button class="px-3 py-1.5 bg-accent rounded-full font-medium hover:bg-[#7c4ce4] transition-colors hover-gradient flex items-center justify-center text-xs">
Start Questing
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 8 8 12 12 16"></polyline>
<line x1="16" y1="12" x2="8" y2="12"></line>
</svg>
</button>
<button class="px-3 py-1.5 bg-white/10 backdrop-blur-sm rounded-full font-medium hover:bg-white/20 transition-colors flex items-center justify-center text-xs border border-white/10">
View Leaderboard
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Quest Highlight Box 1 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-purple-900/20 to-cyan-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
Trending
</div>
<h3 class="text-lg font-bold mt-1">NFT Collection Quest</h3>
<p class="text-gray-400 text-xs mt-1">Collect rare digital artifacts</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Join Quest
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Additional Quest Highlight Boxes -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mt-4">
<!-- Quest Highlight Box 2 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-violet-900/20 to-blue-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium text-accent2">
New
</div>
<h3 class="text-lg font-bold mt-1">DeFi Yield Quest</h3>
<p class="text-gray-400 text-xs mt-1">Earn rewards through farming</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Start Farming
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Quest Highlight Box 3 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-amber-900/20 to-yellow-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium text-amber-400">
Hot
</div>
<h3 class="text-lg font-bold mt-1">DAO Governance Quest</h3>
<p class="text-gray-400 text-xs mt-1">Participate in decisions</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Vote Now
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Quest Highlight Box 4 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-emerald-900/20 to-teal-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium text-emerald-400">
Popular
</div>
<h3 class="text-lg font-bold mt-1">Metaverse Quest</h3>
<p class="text-gray-400 text-xs mt-1">Explore virtual worlds</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Explore
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Featured Quests Section -->
<section class="py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold">Featured Quests</h2>
<a href="#" class="flex items-center text-accent hover:text-accent-2 transition-colors">
View All
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
<div id="questsFeaturedGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Connect TON Wallet Quest (QuestBlox Featured) -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-pink-600/30 to-purple-600/30 h-48 flex items-center justify-center">
<img src="ProBlox_NEW.png" alt="ProBlox" class="w-20 h-20 object-contain opacity-90">
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Connect TON Wallet</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Featured
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Connect and earn 150 GBLX + 3× multiplier while connected.
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]" onclick="startTonWalletQuest()">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Quest Card 1 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-purple-600/30 to-cyan-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">NFT Collector Quest</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Collect 10 unique NFTs to unlock exclusive rewards
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Quest Card 2 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-indigo-600/30 to-emerald-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">DeFi Master Quest</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Complete 5 DeFi protocols to earn master badge
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Quest Card 3 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-violet-600/30 to-blue-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">DAO Explorer Quest</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Participate in 3 DAO governance decisions
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Quest Card 4 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-amber-600/30 to-yellow-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Metaverse Pioneer</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Explore 5 different metaverse worlds
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Start Quest
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- BUDDYBLOX Tab -->
<div id="friendsTab" class="hidden space-y-6">
<!-- BuddyBlox Hero Section -->
<section class="pt-2 pb-4 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<!-- Main BuddyBlox Hero Box -->
<div class="card-glass rounded-2xl overflow-hidden p-0 lg:col-span-2 relative hover:border-accent/50 transition-colors" style="min-height: 400px; background: linear-gradient(135deg, rgba(124, 76, 228, 0.15) 0%, rgba(0, 255, 255, 0.15) 100%);">
<!-- Mini Bento Box with BuddyBlox Logo -->
<div class="absolute top-4 left-4 w-24 h-24 lg:w-32 lg:h-32 z-10">
<div class="card-glass rounded-xl overflow-hidden h-full bg-white/10 border border-white/20 backdrop-blur-sm relative">
<div class="h-full flex items-center justify-center p-2">
<img src="Buddyblox_NEW.png" alt="BuddyBlox" class="w-full h-full object-contain">
</div>
</div>
</div>
<!-- BuddyBlox Hero Content -->
<div class="absolute bottom-0 left-0 w-full h-full flex flex-col justify-end items-start p-4 md:p-8 text-left pointer-events-none">
<div>
<h2 class="text-xl md:text-3xl font-bold mb-2">Connect with <span class="bg-gradient-to-r from-accent2 to-accent bg-clip-text text-transparent">Friends & Buddies</span></h2>
<p class="text-xs md:text-sm text-gray-300 mb-4 max-w-md">
Invite friends, earn rewards together, and build your network in the Web3 community.
</p>
<div class="flex flex-col sm:flex-row gap-2 pointer-events-auto">
<button class="px-3 py-1.5 bg-accent rounded-full font-medium hover:bg-[#7c4ce4] transition-colors hover-gradient flex items-center justify-center text-xs">
Invite Friends
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="m22 21-2-2"></path>
<path d="M16 16h6"></path>
</svg>
</button>
<button class="px-3 py-1.5 bg-white/10 backdrop-blur-sm rounded-full font-medium hover:bg-white/20 transition-colors flex items-center justify-center text-xs border border-white/10">
View Network
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="m23 21-2-2"></path>
<path d="M17 16h6"></path>
</svg>
</button>
</div>
</div>
</div>
<!-- BuddyBlox Inline Controls (pointer events enabled) -->
<div class="absolute top-4 left-4 z-20 pointer-events-auto hidden md:block">
<div class="mt-28 card-glass rounded-xl p-3 bg-white/10 border border-white/20">
<div class="text-xs mb-2">Add friend by Telegram ID</div>
<div class="flex items-center gap-2">
<input id="buddyTidInput" placeholder="123456789" class="px-2 py-1 rounded bg-white/10 border border-white/20 text-xs" />
<button onclick="(function(){ const v=document.getElementById('buddyTidInput').value.trim(); if(v) addBuddy(v); })()" class="px-3 py-1 text-xs rounded bg-accent/60">Add</button>
</div>
<div class="mt-3 text-xs opacity-80">Your buddies</div>
<div id="buddyList" class="mt-1 max-h-40 overflow-auto"></div>
</div>
</div>
</div>
<!-- BuddyBlox Highlight Box 1 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-green-900/20 to-emerald-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
Active
</div>
<h3 class="text-lg font-bold mt-1">Alice</h3>
<p class="text-gray-400 text-xs mt-1">Level 3 • +50 coins today</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
View Profile
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Additional BuddyBlox Highlight Boxes -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mt-4">
<!-- BuddyBlox Highlight Box 2 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-blue-900/20 to-cyan-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium text-blue-400">
Online
</div>
<h3 class="text-lg font-bold mt-1">Bob</h3>
<p class="text-gray-400 text-xs mt-1">Level 2 • +30 coins yesterday</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Send Message
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- BuddyBlox Highlight Box 3 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-purple-900/20 to-pink-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium text-purple-400">
New
</div>
<h3 class="text-lg font-bold mt-1">Charlie</h3>
<p class="text-gray-400 text-xs mt-1">Level 1 • Just joined</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Welcome
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- BuddyBlox Highlight Box 4 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-orange-900/20 to-red-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium text-orange-400">
Veteran
</div>
<h3 class="text-lg font-bold mt-1">Diana</h3>
<p class="text-gray-400 text-xs mt-1">Level 5 • +100 coins today</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Challenge
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Featured Buddies Section -->
<section class="py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold">Featured Buddies</h2>
<a href="#" class="flex items-center text-accent hover:text-accent-2 transition-colors">
View All
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Buddy Card 1 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-green-600/30 to-emerald-600/30 h-48 flex items-center justify-center">
<div class="w-16 h-16 rounded-full bg-gradient-to-r from-green-500 to-emerald-500 flex items-center justify-center text-white font-bold text-xl">
A
</div>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Alice</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-green-400 mr-1"></div>
Active
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Level 3 • +50 coins today
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Connect
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Buddy Card 2 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-blue-600/30 to-cyan-600/30 h-48 flex items-center justify-center">
<div class="w-16 h-16 rounded-full bg-gradient-to-r from-blue-500 to-cyan-500 flex items-center justify-center text-white font-bold text-xl">
B
</div>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Bob</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-blue-400 mr-1"></div>
Online
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Level 2 • +30 coins yesterday
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Message
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Buddy Card 3 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-purple-600/30 to-pink-600/30 h-48 flex items-center justify-center">
<div class="w-16 h-16 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center text-white font-bold text-xl">
C
</div>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Charlie</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-purple-400 mr-1"></div>
New
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Level 1 • Just joined
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Welcome
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Buddy Card 4 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-orange-600/30 to-red-600/30 h-48 flex items-center justify-center">
<div class="w-16 h-16 rounded-full bg-gradient-to-r from-orange-500 to-red-500 flex items-center justify-center text-white font-bold text-xl">
D
</div>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Diana</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-orange-400 mr-1"></div>
Veteran
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Level 5 • +100 coins today
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Challenge
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- STREETBLOX Tab -->
<div id="monumentsTab" class="hidden space-y-6">
<!-- StreetBlox Hero Section -->
<section class="pt-2 pb-4 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<!-- Main StreetBlox Hero Box -->
<div class="card-glass rounded-2xl overflow-hidden p-0 lg:col-span-2 relative hover:border-accent/50 transition-colors" style="min-height: 400px; background: linear-gradient(135deg, rgba(124, 76, 228, 0.15) 0%, rgba(0, 255, 255, 0.15) 100%);">
<!-- Mini Bento Box with StreetBlox Logo -->
<div class="absolute top-4 left-4 w-24 h-24 lg:w-32 lg:h-32 z-10">
<div class="card-glass rounded-xl overflow-hidden h-full bg-white/10 border border-white/20 backdrop-blur-sm relative">
<div class="h-full flex items-center justify-center p-2">
<img src="Streetblox_NEW.png" alt="StreetBlox" class="w-full h-full object-contain">
</div>
</div>
</div>
<!-- StreetBlox Hero Content -->
<div class="absolute bottom-0 left-0 w-full h-full flex flex-col justify-end items-start p-4 md:p-8 text-left pointer-events-none">
<div>
<h2 class="text-xl md:text-3xl font-bold mb-2">Discover <span class="bg-gradient-to-r from-accent2 to-accent bg-clip-text text-transparent">Real-World Locations</span></h2>
<p class="text-xs md:text-sm text-gray-300 mb-4 max-w-md">
Claim, conquer and defend monuments in your city. Build your empire one location at a time.
</p>
<div class="flex flex-col sm:flex-row gap-2 pointer-events-auto">
<button class="px-3 py-1.5 bg-accent rounded-full font-medium hover:bg-[#7c4ce4] transition-colors hover-gradient flex items-center justify-center text-xs">
Explore Monuments
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 8 8 12 12 16"></polyline>
<line x1="16" y1="12" x2="8" y2="12"></line>
</svg>
</button>
<button class="px-3 py-1.5 bg-white/10 backdrop-blur-sm rounded-full font-medium hover:bg-white/20 transition-colors flex items-center justify-center text-xs border border-white/10">
View Map
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Monument Highlight Box 1 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-purple-900/20 to-pink-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
Nearby
</div>
<h3 class="text-lg font-bold mt-1">Praça do Comércio</h3>
<p class="text-gray-400 text-xs mt-1">Claim cost: 420 GBLX</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Conquer Monument
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Monument Highlight Box 2 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-indigo-900/20 to-blue-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
Nearby
</div>
<h3 class="text-lg font-bold mt-1">Torre de Belém</h3>
<p class="text-gray-400 text-xs mt-1">Claim cost: 650 GBLX</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Conquer Monument
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Monument Highlight Box 3 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-violet-900/20 to-purple-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
Nearby
</div>
<h3 class="text-lg font-bold mt-1">Mosteiro dos Jerónimos</h3>
<p class="text-gray-400 text-xs mt-1">Claim cost: 850 GBLX</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Conquer Monument
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- Monument Highlight Box 4 -->
<div class="card-glass rounded-2xl overflow-hidden relative hover:border-accent/50 transition-colors">
<div class="hero-slide relative h-full">
<div class="h-full bg-gradient-to-br from-amber-900/20 to-yellow-900/20 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">
Nearby
</div>
<h3 class="text-lg font-bold mt-1">Castelo de São Jorge</h3>
<p class="text-gray-400 text-xs mt-1">Claim cost: 1200 GBLX</p>
<button class="mt-2 inline-flex items-center text-accent font-medium text-xs">
Conquer Monument
<svg class="ml-1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Featured Monuments Section -->
<section class="py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold">Featured StreetBlox</h2>
<a href="#" class="flex items-center text-accent hover:text-accent-2 transition-colors">
View All
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</a>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Monument Card 1 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-purple-600/30 to-cyan-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Praça do Comércio</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Available
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Historic square in Lisbon, Portugal
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Claim for 420 GBLX
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Monument Card 2 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-indigo-600/30 to-emerald-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Torre de Belém</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Available
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Iconic tower in Lisbon, Portugal
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Claim for 650 GBLX
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Monument Card 3 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-violet-600/30 to-blue-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Mosteiro dos Jerónimos</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Available
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Historic monastery in Lisbon
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Claim for 850 GBLX
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
<!-- Monument Card 4 -->
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="bg-gradient-to-br from-amber-600/30 to-yellow-600/30 h-48 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="opacity-50">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">Castelo de São Jorge</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Available
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
Medieval castle in Lisbon
</p>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]">
Claim for 1200 GBLX
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</main>
<!-- Bottom Navigation -->
<nav class="sticky bottom-0 z-30 py-2 px-4 sm:px-6 lg:px-8">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-4 gap-3">
<!-- BloxID Logo Button -->
<button class="nav-btn active py-3 px-2 rounded-xl font-medium text-sm transition-all flex flex-col items-center justify-center" onclick="showTab('passport')">
<img src="bloxid_cube_NEW_PNG.png" alt="BloxID" class="w-14 h-14 object-contain mb-1">
<span class="text-xs font-medium">BloxID</span>
</button>
<!-- QuestBlox Logo Button -->
<button class="nav-btn py-3 px-2 rounded-xl font-medium text-sm transition-all flex flex-col items-center justify-center" onclick="showTab('quests')">
<img src="QuestBlox_NEW.png" alt="QuestBlox" class="w-14 h-14 object-contain mb-1">
<span class="text-xs font-medium">QuestBlox</span>
</button>
<!-- StreetBlox Logo Button -->
<button id="streetbloxButton" class="nav-btn py-3 px-2 rounded-xl font-medium text-sm transition-all flex flex-col items-center justify-center" onclick="showTab('monuments')">
<img src="Streetblox_NEW.png" alt="StreetBlox" class="w-14 h-14 object-contain mb-1">
<span class="text-xs font-medium">StreetBlox</span>
</button>
<!-- BuddyBlox Logo Button -->
<button class="nav-btn py-3 px-2 rounded-xl font-medium text-sm transition-all flex flex-col items-center justify-center" onclick="showTab('friends')">
<img src="Buddyblox_NEW.png" alt="BuddyBlox" class="w-14 h-14 object-contain mb-1">
<span class="text-xs font-medium">BuddyBlox</span>
</button>
</div>
</div>
</nav>
</div>
<!-- Notification -->
<div class="notification fixed top-4 left-1/2 transform -translate-x-1/2 z-50 px-6 py-3 rounded-full text-sm font-medium opacity-0 transition-opacity" id="notification"></div>
<script>
// Telegram WebApp Integration
// Get start parameter from URL
const urlParams = new URLSearchParams(window.location.search);
const startParam = urlParams.get('start');
// Telegram WebApp Shim for Browser Mode
if (typeof Telegram === 'undefined' || !Telegram.WebApp) {
window.Telegram = {
WebApp: {
initData: '',
initDataUnsafe: {
user: {
id: startParam ? parseInt(startParam) : 123456789,
first_name: 'Test',
username: 'testuser'
}
},
expand: function() {},
setHeaderColor: function() {},
setBackgroundColor: function() {},
ready: function() {},
close: function() {},
showAlert: function() {},
showConfirm: function() {},
showPopup: function() {},
showScanQrPopup: function() {},
showMainButton: function() {},
hideMainButton: function() {},
showBackButton: function() {},
hideBackButton: function() {},
enableClosingConfirmation: function() {},
disableClosingConfirmation: function() {},
isClosingConfirmationEnabled: function() { return false; },
isExpanded: function() { return true; },
viewportHeight: window.innerHeight,
viewportStableHeight: window.innerHeight,
headerColor: '#2A2D36',
backgroundColor: '#202229',
themeParams: {
bg_color: '#202229',
text_color: '#ffffff',
hint_color: '#999999',
link_color: '#2481cc',
button_color: '#2481cc',
button_text_color: '#ffffff'
}
}
};
} else {
// If we have a start parameter and no user data, try to set it
if (startParam && (!Telegram.WebApp.initDataUnsafe || !Telegram.WebApp.initDataUnsafe.user)) {
console.log('Setting user data from start parameter:', startParam);
if (!Telegram.WebApp.initDataUnsafe) {
Telegram.WebApp.initDataUnsafe = {};
}
Telegram.WebApp.initDataUnsafe.user = {
id: parseInt(startParam),
first_name: 'User',
username: 'user'
};
}
}
// Initialize Telegram WebApp
try {
Telegram.WebApp.expand();
Telegram.WebApp.setHeaderColor('#2A2D36');
Telegram.WebApp.setBackgroundColor('#202229');
Telegram.WebApp.ready();
// Log Telegram WebApp info
console.log('Telegram WebApp initialized');
console.log('User:', Telegram.WebApp.initDataUnsafe?.user);
console.log('Init Data:', Telegram.WebApp.initData);
console.log('Start Param:', startParam);
} catch (e) {
console.log('Telegram WebApp not available, using browser mode');
}
// Game state - Simple localStorage (no platform separation for now)
let currentTab = 'passport';
let score = parseInt(localStorage.getItem('bloxid_unified_score')) || 0;
let balance = parseInt(localStorage.getItem('bloxid_unified_balance')) || 1000;
let multitap = 3;
// Log loaded values for debugging
console.log('🔄 Initial load - Score:', score, 'Balance:', balance);
// Force save current values to ensure they persist
localStorage.setItem('bloxid_unified_score', score);
localStorage.setItem('bloxid_unified_balance', balance);
// Global user variables for Telegram integration
let currentUser = null;
let userBadges = ['blueblox']; // Default BlueBlox badge
let isLoggedIn = false;
// Robust session restore (cookie or localStorage), plus badge persistence
(function hardRestoreTelegramSession(){
try {
// 1) Cookie from auth_redirect.php
const cookieMatch = document.cookie.match(/(?:^|; )tg_user=([^;]+)/);
if (!currentUser && cookieMatch) {
try {
const decoded = decodeURIComponent(cookieMatch[1]);
const tg = JSON.parse(atob(decoded));
if (tg && tg.id) {
currentUser = tg;
localStorage.setItem('tg_user', JSON.stringify(tg));
localStorage.setItem('bloxid_current_user', JSON.stringify(tg)); // Also save for fallback
// Clear one-time cookie
document.cookie = 'tg_user=; Max-Age=0; path=/';
}
} catch(_) {}
}
// 2) LocalStorage fallback
if (!currentUser) {
const ls = localStorage.getItem('tg_user');
if (ls) {
try {
const tg = JSON.parse(ls);
if (tg && tg.id) {
currentUser = tg;
localStorage.setItem('bloxid_current_user', JSON.stringify(tg)); // Sync both keys
}
} catch(_) {}
}
}
// 3) Apply to UI and badges
if (currentUser) {
isLoggedIn = true;
// Don't call updateUserProfile if it might overwrite username
const elU = document.getElementById('usernameDesktop');
if (elU) {
const displayName = currentUser.username ? ('@' + currentUser.username) :
(currentUser.first_name ? currentUser.first_name : ('ID ' + currentUser.id));
elU.textContent = displayName;
}
try {
const verified = localStorage.getItem('telegram_wallet_verified') === 'true';
if (verified && Array.isArray(userBadges) && !userBadges.includes('problox')) {
userBadges.push('problox');
updateBadgeDisplay();
}
// Reflect wallet connected button
const isConnected = localStorage.getItem('telegram_wallet_connected') === 'true';
const connectBtnInit = document.getElementById('walletConnectBtn');
const buttonTextInit = document.getElementById('walletBtnText');
if (isConnected && connectBtnInit) {
connectBtnInit.disabled = false;
connectBtnInit.className = 'card-glass rounded-xl px-4 py-2 w-full border text-white font-semibold text-xs flex items-center justify-center space-x-2 bg-green-600 hover:bg-green-700 border-green-400/50';
if (buttonTextInit) buttonTextInit.textContent = 'Connected!';
}
} catch(_) {}
// Refresh buddies on restore
try { loadBuddies(); } catch(_) {}
}
// Cross-tab updates
window.addEventListener('storage', (e) => {
if (e.key === 'tg_user' && e.newValue) {
try { const tg = JSON.parse(e.newValue); if (tg && tg.id) currentUser = tg; } catch(_) {}
}
});
console.log('<!-- session-restore-active -->');
} catch(_) {}
})();
// Badge multiplier system
const badgeMultipliers = {
blueblox: { base: 1.5, premium: 2.0 }, // x1.5 base, x2.0 if Premium
problox: { multiplier: 2.5 }, // x2.5 GBLX multiplier
mayorblox: { multiplier: 1.8 } // x1.8 location multiplier
};
// Get current user's total multiplier
function getTotalMultiplier() {
let multiplier = 1.0;
if (userBadges.includes('blueblox')) {
// Check if user has Telegram Premium
const isPremium = currentUser?.is_premium || false;
multiplier *= isPremium ? badgeMultipliers.blueblox.premium : badgeMultipliers.blueblox.base;
}
// Apply ProBlox multiplier only if badge present AND wallet is verified
try {
const wasVerified = localStorage.getItem('telegram_wallet_verified') === 'true';
if (wasVerified && userBadges.includes('problox')) {
multiplier *= badgeMultipliers.problox.multiplier;
}
} catch (e) {}
if (userBadges.includes('mayorblox')) {
multiplier *= badgeMultipliers.mayorblox.multiplier;
}
return multiplier;
}
// Monument UI state (preview only)
let monumentReady = false;
let monumentUnderAttack = false;
let nearestMonument = { name: 'Praça do Comércio', cost: 420 };
function tap() {
console.log('🎯 TAP FUNCTION CALLED!');
// Haptic feedback for Telegram WebApp
if (window.Telegram && window.Telegram.WebApp) {
window.Telegram.WebApp.HapticFeedback.impactOccurred('light');
}
// Enhanced tap logic - convert 1 GBLX to 1 Aura with badge multipliers
if (balance > 0) {
balance -= 1;
// Apply badge multipliers to Aura earned
const baseAura = 1;
const multiplier = getTotalMultiplier();
const auraEarned = Math.floor(baseAura * multiplier);
score += auraEarned;
// Save to localStorage immediately
localStorage.setItem('bloxid_unified_score', score);
localStorage.setItem('bloxid_unified_balance', balance);
console.log('💾 Saved - Score:', score, 'Balance:', balance);
console.log('🏆 Badge multiplier:', multiplier.toFixed(1) + 'x', 'Aura earned:', auraEarned);
console.log('✅ Tap successful - Balance:', balance, 'Score:', score);
// Simple localStorage save (no server complexity)
localStorage.setItem('bloxid_last_save', Date.now());
console.log('💾 Data saved to localStorage only');
// Update all displays
const displays = [
'questScoreDisplay', 'questBalanceDisplay',
'passportScoreDisplay', 'passportBalanceDisplay'
];
displays.forEach(id => {
const element = document.getElementById(id);
if (element) {
if (id.includes('Score')) {
element.textContent = score.toLocaleString();
} else {
element.textContent = balance.toLocaleString();
}
console.log('✅ Updated display:', id);
}
});
// Update level progress
updateLevelProgress();
updateQuestLevelProgress();
// Show tap effect
showTapEffect(`+${auraEarned || 1} Aura`);
// Show notification
showNotification('Converted 1 GB → 1 Aura', 'success');
} else {
console.log('❌ No GBLX to convert!');
showNotification('No GBLX to convert!', 'warning');
}
}
function questTap() {
// Same functionality as tap() but for QUESTS tab
if (balance > 0) {
balance -= 1;
score += 1;
// Update displays (only the elements that exist)
document.getElementById('questScoreDisplay').textContent = score.toLocaleString();
document.getElementById('questBalanceDisplay').textContent = balance.toLocaleString();
document.getElementById('passportScoreDisplay').textContent = score.toLocaleString();
document.getElementById('passportBalanceDisplay').textContent = balance.toLocaleString();
updateLevelProgress();
updateQuestLevelProgress();
// Show enhanced tap effect
showTapEffect(`+${auraEarned || 1} Aura`);
// Show notification
showNotification('Converted 1 GB → 1 Aura', 'success');
} else {
showNotification('No GBLX to convert!', 'warning');
}
}
function tapToAscend() {
if (balance >= 100) {
const conversionRate = 1.5; // ProBlox multiplier
const convertedAura = Math.floor(100 * conversionRate);
balance -= 100;
score += convertedAura;
// Update display
document.getElementById('questScoreDisplay').textContent = score.toLocaleString();
document.getElementById('questBalanceDisplay').textContent = balance.toLocaleString();
updateLevelProgress();
updateQuestLevelProgress();
// Show notification
showNotification(`🚀 Ascended! +${convertedAura} Aura`, 'success');
// Add visual effect
const button = event.target;
button.style.transform = 'scale(0.95)';
setTimeout(() => {
button.style.transform = 'scale(1)';
}, 150);
} else {
showNotification('Not enough GBLX!', 'warning');
}
}
function showTapEffect(text) {
// Find the tap image
let button = document.querySelector('img[src="mohawk_cube_head.png"]');
if (!button) {
return;
}
const rect = button.getBoundingClientRect();
const effect = document.createElement('div');
effect.className = 'tap-effect absolute pointer-events-none';
effect.textContent = text || '';
effect.style.cssText = `
position: absolute;
left: ${rect.left + rect.width / 2}px;
top: ${rect.top + rect.height / 2}px;
color: #00ff88;
font-weight: bold;
font-size: 20px;
z-index: 1000;
pointer-events: none;
animation: floatUp 0.6s ease-out forwards;
`;
document.body.appendChild(effect);
setTimeout(() => {
if (effect.parentNode) {
document.body.removeChild(effect);
}
}, 600);
}
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.style.opacity = '1';
setTimeout(() => {
notification.style.opacity = '0';
}, 3000);
}
// Update tap context + spotlight based on monument state
function updateMonumentUI() {
const ctx = document.getElementById('tapContextLabel');
const spotBox = document.getElementById('monumentSpotlightBox');
const spotTag = document.getElementById('monumentSpotlightTag');
const spotTitle = document.getElementById('monumentSpotlightTitle');
const spotText = document.getElementById('monumentSpotlightText');
if (monumentUnderAttack) {
if (ctx) ctx.textContent = 'RED ALERT: Defend your monument!';
if (spotTag) { spotTag.textContent = 'Red Alert'; spotTag.className = 'inline-block px-1.5 py-0.5 rounded-full text-xs font-medium bg-red-500/20 border border-red-500/30 text-red-600'; }
if (spotTitle) spotTitle.textContent = 'Your monument is under attack';
if (spotText) spotText.textContent = 'Tap to defend and keep your Mayor status';
if (spotBox) spotBox.className = 'h-full p-4 flex flex-col justify-end bg-red-500/10';
return;
}
if (monumentReady) {
if (ctx) ctx.textContent = `Tap to Conquer: ${nearestMonument.name} (Cost: ${nearestMonument.cost} GB)`;
if (spotTag) { spotTag.textContent = 'Nearby Monument'; spotTag.className = 'inline-block px-1.5 py-0.5 rounded-full text-xs font-medium bg-black/10 border border-black/10'; }
if (spotTitle) spotTitle.textContent = 'Nearby conquest available';
if (spotText) spotText.textContent = `${nearestMonument.name} • Cost: ${nearestMonument.cost} GB`;
if (spotBox) spotBox.className = 'h-full p-4 flex flex-col justify-end';
} else {
if (ctx) ctx.textContent = 'Tap to Convert: 1 GB → 1 Aura';
if (spotTag) { spotTag.textContent = 'Monument Spotlight'; spotTag.className = 'inline-block px-1.5 py-0.5 rounded-full text-xs font-medium bg-black/10 border border-black/10'; }
if (spotTitle) spotTitle.textContent = 'No monument nearby';
if (spotText) spotText.textContent = 'Open Monuments to explore conquests';
if (spotBox) spotBox.className = 'h-full p-4 flex flex-col justify-end';
}
}
// Dev preview toggles (use in console)
window.setMonumentReady = (v) => { monumentReady = !!v; monumentUnderAttack = false; updateMonumentUI(); };
window.setMonumentAlert = (v) => { monumentUnderAttack = !!v; updateMonumentUI(); };
// TEST FUNCTION - Run this in console to force level progress update
window.testLevelProgress = () => {
console.log('=== LEVEL PROGRESS TEST ===');
console.log('Current score:', score);
console.log('Current level:', getLevel(score));
updateLevelProgress();
updateQuestLevelProgress();
console.log('Level progress forced update complete!');
};
// TEST FUNCTION - Test tap functionality
window.testTap = () => {
console.log('=== TAP TEST ===');
console.log('Current balance:', balance);
console.log('Current score:', score);
tap();
console.log('After tap - balance:', balance, 'score:', score);
};
// FORCE LEVEL UPDATE FUNCTION - This will actually work!
function forceLevelUpdate() {
const aura = score;
const level = getLevel(aura);
// Find the correct next threshold
let nextThreshold;
if (level >= levelThresholds.length - 1) {
// If at max level, create next threshold
nextThreshold = levelThresholds[levelThresholds.length - 1] + 1000;
} else {
nextThreshold = levelThresholds[level + 1];
}
const prevThreshold = levelThresholds[level] ?? 0;
const progress = Math.max(0, Math.min(100, ((aura - prevThreshold) / (nextThreshold - prevThreshold)) * 100));
console.log('FORCE UPDATE:', {aura, level, progress, nextThreshold, prevThreshold});
// Force update the level progress bar
const levelEl = document.getElementById('questLevelNumber');
const fillEl = document.getElementById('questLevelProgressFill');
const nextEl = document.getElementById('questLevelNextText');
if (levelEl) {
levelEl.textContent = `Level ${level}`;
console.log('Updated level text to:', `Level ${level}`);
}
if (fillEl) {
fillEl.style.width = `${progress}%`;
console.log('Updated progress bar to:', `${progress}%`);
}
if (nextEl) {
nextEl.textContent = `Need ${Math.max(0, nextThreshold - aura)} Aura to reach Level ${level + 1}`;
console.log('Updated next level text');
}
// Force a visual update
if (fillEl) {
fillEl.style.transition = 'none';
fillEl.offsetHeight; // Force reflow
fillEl.style.transition = 'all 0.3s ease-out';
}
}
// Level progression preview
const levelThresholds = [0, 100, 250, 500, 1000, 1700, 2600, 3800, 5300, 7200, 9500, 12300, 15600, 19400, 23700, 28500, 33800, 39600, 45900, 52700, 60000];
function getLevel(aura) {
let lvl = 0; // Start at level 0
for (let i = 1; i < levelThresholds.length; i++) {
if (aura >= levelThresholds[i]) lvl = i; else break;
}
return lvl;
}
function updateLevelProgress() {
const aura = score; // Aura equals total score in preview
const level = getLevel(aura);
const nextThreshold = levelThresholds[level + 1] ?? (levelThresholds[levelThresholds.length - 1] + 1000);
const prevThreshold = levelThresholds[level] ?? 0;
const progress = Math.max(0, Math.min(100, ((aura - prevThreshold) / (nextThreshold - prevThreshold)) * 100));
// Update PASSPORT tab level progress
const passportLevelEl = document.getElementById('passportLevelNumber');
const passportFillEl = document.getElementById('passportLevelProgressFill');
const passportNextEl = document.getElementById('passportLevelNextText');
if (passportLevelEl) passportLevelEl.textContent = `Level ${level}`;
if (passportFillEl) passportFillEl.style.width = `${progress}%`;
if (passportNextEl) passportNextEl.textContent = `Need ${Math.max(0, nextThreshold - aura)} Aura to reach Level ${level + 1}`;
// Update PASSPORT tap area level progress (Mobile)
const passportTapLevelElMobile = document.getElementById('passportTapLevelNumberMobile');
const passportTapFillElMobile = document.getElementById('passportTapLevelProgressFillMobile');
const passportTapNextElMobile = document.getElementById('passportTapLevelNextTextMobile');
if (passportTapLevelElMobile) passportTapLevelElMobile.textContent = `Level ${level}`;
if (passportTapFillElMobile) passportTapFillElMobile.style.width = `${progress}%`;
if (passportTapNextElMobile) passportTapNextElMobile.textContent = `Need ${Math.max(0, nextThreshold - aura)} Aura`;
// Update PASSPORT tap area level progress (Desktop)
const passportTapLevelElDesktop = document.getElementById('passportTapLevelNumberDesktop');
const passportTapFillElDesktop = document.getElementById('passportTapLevelProgressFillDesktop');
const passportTapNextElDesktop = document.getElementById('passportTapLevelNextTextDesktop');
if (passportTapLevelElDesktop) passportTapLevelElDesktop.textContent = `Level ${level}`;
if (passportTapFillElDesktop) passportTapFillElDesktop.style.width = `${progress}%`;
if (passportTapNextElDesktop) passportTapNextElDesktop.textContent = `Need ${Math.max(0, nextThreshold - aura)} Aura to reach Level ${level + 1}`;
// Update Mobile Aura Progress Bar (Bottom Right Corner)
const mobileAuraProgressEl = document.getElementById('mobileAuraProgress');
const mobileAuraTextEl = document.getElementById('mobileAuraText');
const mobileLevelInfoEl = document.getElementById('mobileLevelInfo');
if (mobileAuraProgressEl) {
mobileAuraProgressEl.style.width = `${progress}%`;
}
if (mobileAuraTextEl) {
mobileAuraTextEl.textContent = `${aura} / ${nextThreshold}`;
}
if (mobileLevelInfoEl) {
mobileLevelInfoEl.textContent = `Level ${level} | Need ${Math.max(0, nextThreshold - aura)} to level ${level + 1}`;
}
// Update legacy elements (if they exist)
const levelEl = document.getElementById('levelNumber');
const fillEl = document.getElementById('levelProgressFill');
const nextEl = document.getElementById('levelNextText');
if (levelEl) levelEl.textContent = `Level ${level}`;
if (fillEl) fillEl.style.width = `${progress}%`;
if (nextEl) nextEl.textContent = `Need ${Math.max(0, nextThreshold - aura)} Aura to reach Level ${level + 1}`;
}
function updateQuestLevelProgress() {
const aura = score; // Aura equals total score in preview
const level = getLevel(aura);
const nextThreshold = levelThresholds[level + 1] ?? (levelThresholds[levelThresholds.length - 1] + 1000);
const prevThreshold = levelThresholds[level] ?? 0;
const progress = Math.max(0, Math.min(100, ((aura - prevThreshold) / (nextThreshold - prevThreshold)) * 100));
const levelEl = document.getElementById('questLevelNumber');
const fillEl = document.getElementById('questLevelProgressFill');
const nextEl = document.getElementById('questLevelNextText');
if (levelEl) levelEl.textContent = `Level ${level}`;
if (fillEl) fillEl.style.width = `${progress}%`;
if (nextEl) nextEl.textContent = `Need ${Math.max(0, nextThreshold - aura)} Aura to reach Level ${level + 1}`;
// Update QUEST tap area level progress
const questTapLevelEl = document.getElementById('questTapLevelNumber');
const questTapFillEl = document.getElementById('questTapLevelProgressFill');
const questTapNextEl = document.getElementById('questTapLevelNextText');
if (questTapLevelEl) questTapLevelEl.textContent = `Level ${level}`;
if (questTapFillEl) questTapFillEl.style.width = `${progress}%`;
if (questTapNextEl) questTapNextEl.textContent = `Need ${Math.max(0, nextThreshold - aura)} Aura to reach Level ${level + 1}`;
// Force immediate update and log for debugging
console.log(`FORCED UPDATE: Aura=${aura}, Level=${level}, Progress=${progress}%, Next=${nextThreshold}, Prev=${prevThreshold}`);
// Force the DOM update
if (fillEl) {
fillEl.style.width = `${progress}%`;
fillEl.style.transition = 'none';
setTimeout(() => {
fillEl.style.transition = 'all 0.3s ease-out';
}, 10);
}
// Force the QUEST tap area DOM update
if (questTapFillEl) {
questTapFillEl.style.width = `${progress}%`;
questTapFillEl.style.transition = 'none';
setTimeout(() => {
questTapFillEl.style.transition = 'all 0.3s ease-out';
}, 10);
}
}
function showTab(tab) {
currentTab = tab;
console.log('🔄 Switching to tab:', tab);
// Hide all tabs
document.getElementById('passportTab').classList.add('hidden');
document.getElementById('questsTab').classList.add('hidden');
document.getElementById('friendsTab').classList.add('hidden');
document.getElementById('monumentsTab').classList.add('hidden');
// Show selected tab
const targetTab = document.getElementById(tab + 'Tab');
if (targetTab) {
targetTab.classList.remove('hidden');
console.log('✅ Tab shown:', tab + 'Tab');
} else {
console.error('❌ Tab not found:', tab + 'Tab');
}
// Update nav buttons - remove active from all
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.classList.remove('active');
console.log('❌ Removed active from button');
});
// Add active to the correct button based on tab
let activeButton = null;
// Try different selectors for the active button
if (tab === 'monuments') {
// Special handling for StreetBlox
activeButton = document.getElementById('streetbloxButton');
console.log('🔍 StreetBlox: Using ID selector, found:', activeButton);
} else {
activeButton = document.querySelector(`[onclick="showTab('${tab}')"]`);
console.log('🔍 Other tabs: Using onclick selector, found:', activeButton);
}
if (activeButton) {
// Remove active from all buttons first
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.classList.remove('active');
btn.removeAttribute('data-active');
});
// Add active class AND data attribute
activeButton.classList.add('active');
activeButton.setAttribute('data-active', 'true');
console.log('✅ Active button updated for tab:', tab);
// Force the active state to persist with multiple attempts
setTimeout(() => {
activeButton.classList.add('active');
activeButton.setAttribute('data-active', 'true');
console.log('🔄 Re-applied active state for tab:', tab);
}, 10);
setTimeout(() => {
activeButton.classList.add('active');
activeButton.setAttribute('data-active', 'true');
console.log('🔄 Re-applied active state again for tab:', tab);
}, 100);
setTimeout(() => {
activeButton.classList.add('active');
activeButton.setAttribute('data-active', 'true');
console.log('🔄 Final re-application for tab:', tab);
}, 500);
} else {
console.error('❌ Active button not found for tab:', tab);
}
}
function buyUpgrade(type) {
if (type === 'multitap' && balance >= 300) {
balance -= 300;
multitap += 1;
document.getElementById('balanceDisplay').textContent = balance.toLocaleString();
showNotification('Multitap upgraded!', 'success');
} else {
showNotification('Not enough GBLX!', 'warning');
}
}
// No energy system; timer reserved for future live updates
// 3D tilt effect initialization
document.addEventListener('DOMContentLoaded', function() {
console.log('🚀 DOM Content Loaded - Starting initialization...');
// Handle Telegram OAuth return via URL hash: #tgAuthResult=BASE64_JSON
try {
const HASH_KEY = '#tgAuthResult=';
if (location.hash && location.hash.startsWith(HASH_KEY)) {
const enc = location.hash.substring(HASH_KEY.length);
const json = atob(decodeURIComponent(enc));
const user = JSON.parse(json);
if (user && user.id) {
console.log('🔗 Parsed Telegram auth from URL hash');
onTelegramAuth(user);
history.replaceState(null, '', location.pathname + location.search);
}
}
} catch(e) { console.log('Hash auth parse skipped:', e.message); }
// Restore Telegram login from localStorage (desktop web flow)
try {
const savedUserRaw = localStorage.getItem('bloxid_current_user');
if (savedUserRaw) {
const savedUser = JSON.parse(savedUserRaw);
if (savedUser && savedUser.id) {
currentUser = savedUser;
isLoggedIn = true;
updateUserProfile(currentUser);
giveBlueBloxBadge();
updateBadgeDisplay();
console.log('🔐 Restored Telegram session from localStorage for user', currentUser.id);
// Reflect connected state on button
try {
const btn = document.getElementById('tgConnectBtn');
const txt = document.getElementById('tgBtnText');
if (btn) btn.classList.add('bg-blue-600','border-blue-500');
if (txt) txt.textContent = 'Connected';
} catch(e) {}
}
}
} catch(e) { console.log('No saved Telegram session to restore'); }
// Telegram WebApp initialization
if (window.Telegram && window.Telegram.WebApp) {
const tg = window.Telegram.WebApp;
// Expand to full screen
tg.expand();
// Enable closing confirmation
tg.enableClosingConfirmation();
// Set header color to match theme
tg.setHeaderColor('#0f172a');
// Set background color
tg.setBackgroundColor('#0f172a');
// Ready to show
tg.ready();
console.log('📱 Telegram WebApp initialized and expanded');
// Initialize Telegram user profile and login
initializeTelegramUser(tg);
}
// Lightweight zoom prevention - only block multi-touch
document.addEventListener('touchstart', function(e) {
if (e.touches.length > 1) {
e.preventDefault();
}
}, { passive: false });
// Initialize displays with current values
try {
// Update all score displays immediately
updateAllDisplays();
// Restore ProBlox badge if wallet was verified previously
try {
const wasVerified = localStorage.getItem('telegram_wallet_verified') === 'true';
if (wasVerified && !userBadges.includes('problox')) {
userBadges.push('problox');
updateBadgeDisplay();
try { localStorage.setItem('bloxid_user_badges', JSON.stringify(userBadges)); } catch(e) {}
}
} catch(e) { }
// Load quests immediately on app start
setTimeout(() => {
loadQuests();
// Sync with server for cross-platform compatibility
syncWithServer();
}, 1000);
// Legacy display updates
document.getElementById('questScoreDisplay').textContent = score.toLocaleString();
document.getElementById('questBalanceDisplay').textContent = balance.toLocaleString();
} catch(e) {
console.log('Display initialization skipped:', e.message);
}
// FORCE LEVEL PROGRESS UPDATE IMMEDIATELY
setTimeout(() => {
try {
updateLevelProgress();
updateQuestLevelProgress();
forceLevelUpdate(); // ADD THIS TO FORCE IT TO WORK
// Initialize mobile aura progress bar specifically
const mobileAuraProgressEl = document.getElementById('mobileAuraProgress');
const mobileAuraTextEl = document.getElementById('mobileAuraText');
const mobileLevelInfoEl = document.getElementById('mobileLevelInfo');
if (mobileAuraProgressEl && mobileAuraTextEl && mobileLevelInfoEl) {
const aura = score;
const level = getLevel(aura);
const nextThreshold = levelThresholds[level + 1] ?? (levelThresholds[levelThresholds.length - 1] + 1000);
const prevThreshold = levelThresholds[level] ?? 0;
const progress = Math.max(0, Math.min(100, ((aura - prevThreshold) / (nextThreshold - prevThreshold)) * 100));
mobileAuraProgressEl.style.width = `${progress}%`;
mobileAuraTextEl.textContent = `${aura} / ${nextThreshold}`;
mobileLevelInfoEl.textContent = `Level ${level} | Need ${Math.max(0, nextThreshold - aura)} to level ${level + 1}`;
console.log('📱 Mobile aura progress bar initialized:', progress + '%');
}
console.log('IMMEDIATE LEVEL UPDATE - Score:', score, 'Level:', getLevel(score));
} catch(e) {
console.log('Level update skipped:', e.message);
}
}, 100);
const tapImage = document.querySelector('.tap-image');
if (tapImage) {
tapImage.addEventListener('mousemove', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / centerY * -8;
const rotateY = (x - centerX) / centerX * 8;
this.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.05) translateY(-4px)`;
});
tapImage.addEventListener('mouseleave', function() {
this.style.transform = 'rotateX(0deg) rotateY(0deg) scale(1) translateY(0px)';
});
tapImage.addEventListener('click', function() {
// Add click animation
this.style.transform = 'rotateX(-12deg) rotateY(0deg) scale(0.92) translateY(2px)';
setTimeout(() => {
this.style.transform = 'rotateX(0deg) rotateY(0deg) scale(1) translateY(0px)';
}, 150);
});
}
// Initialize monument UI once DOM is ready
try { updateMonumentUI(); } catch(e) {}
try { updateLevelProgress(); } catch(e) {}
try { updateQuestLevelProgress(); } catch(e) {}
// CRITICAL: Ensure proper tab initialization
console.log('🔧 Initializing tabs...');
// Get all tab elements
const passportTab = document.getElementById('passportTab');
const questsTab = document.getElementById('questsTab');
const friendsTab = document.getElementById('friendsTab');
const monumentsTab = document.getElementById('monumentsTab');
console.log('📋 Tab elements found:', {
passport: !!passportTab,
quests: !!questsTab,
friends: !!friendsTab,
monuments: !!monumentsTab
});
// Hide all tabs first
if (passportTab) passportTab.classList.add('hidden');
if (questsTab) questsTab.classList.add('hidden');
if (friendsTab) friendsTab.classList.add('hidden');
if (monumentsTab) monumentsTab.classList.add('hidden');
// Set passport tab as default active
if (passportTab) {
passportTab.classList.remove('hidden');
console.log('✅ Passport tab shown successfully');
} else {
console.error('❌ Passport tab not found');
}
// Set active navigation button
const navButtons = document.querySelectorAll('.nav-btn');
navButtons.forEach(btn => btn.classList.remove('active'));
const passportNavBtn = document.querySelector('[onclick="showTab(\'passport\')"]');
if (passportNavBtn) {
passportNavBtn.classList.add('active');
console.log('✅ Navigation button activated');
}
// Initialize badge system
try {
showBadgeDetails('blueblox'); // Show default badge
} catch(e) {
console.log('Badge system initialization skipped:', e.message);
}
// Verify tab is visible after a short delay
setTimeout(() => {
const passportTab = document.getElementById('passportTab');
if (passportTab && !passportTab.classList.contains('hidden')) {
console.log('✅ Page loaded successfully - Passport tab is visible');
} else {
console.error('❌ Page load issue - Passport tab is not visible');
// Force show the tab
if (passportTab) {
passportTab.classList.remove('hidden');
console.log('🔄 Forced passport tab to be visible');
}
}
}, 100);
console.log('🎉 Initialization complete!');
});
// FALLBACK: Immediate initialization in case DOMContentLoaded doesn't fire
(function() {
console.log('🔄 Fallback initialization running...');
// Check if DOM is already loaded
if (document.readyState === 'loading') {
console.log('📋 DOM still loading, waiting for DOMContentLoaded...');
return;
}
// Also restore Telegram session in fallback path
try {
const savedUserRaw = localStorage.getItem('bloxid_current_user');
if (savedUserRaw) {
const savedUser = JSON.parse(savedUserRaw);
if (savedUser && savedUser.id) {
currentUser = savedUser;
isLoggedIn = true;
updateUserProfile(currentUser);
giveBlueBloxBadge();
updateBadgeDisplay();
console.log('🔐 [fallback] Restored Telegram session from localStorage for user', currentUser.id);
}
}
} catch(e) {}
console.log('⚡ DOM already loaded, running immediate initialization...');
// Get all tab elements
const passportTab = document.getElementById('passportTab');
const questsTab = document.getElementById('questsTab');
const friendsTab = document.getElementById('friendsTab');
const monumentsTab = document.getElementById('monumentsTab');
// Hide all tabs first
if (passportTab) passportTab.classList.add('hidden');
if (questsTab) questsTab.classList.add('hidden');
if (friendsTab) friendsTab.classList.add('hidden');
if (monumentsTab) monumentsTab.classList.add('hidden');
// Show passport tab
if (passportTab) {
passportTab.classList.remove('hidden');
console.log('✅ Fallback: Passport tab shown');
}
// Set active navigation button
const navButtons = document.querySelectorAll('.nav-btn');
navButtons.forEach(btn => btn.classList.remove('active'));
const passportNavBtn = document.querySelector('[onclick="showTab(\'passport\')"]');
if (passportNavBtn) {
passportNavBtn.classList.add('active');
console.log('✅ Fallback: Navigation button activated');
}
console.log('🎉 Fallback initialization complete!');
})();
// BuddyBlox API helpers
async function addBuddy(buddyTid, buddyUsername = null) {
try {
const ownerTid = (window.currentUser && currentUser.id) ? String(currentUser.id) : '';
if (!ownerTid) {
console.log('BUDDY_FAIL: No currentUser.id');
showNotification('Please connect Telegram first', 'error');
return;
}
console.log('BUDDY_TRY: Adding buddy', buddyTid, 'for owner', ownerTid);
const res = await fetch('/api/buddies.php', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ owner_tid: ownerTid, buddy_tid: String(buddyTid), buddy_username: buddyUsername }) });
const j = await res.json();
console.log('BUDDY_RESPONSE:', j);
if (j && j.ok) {
console.log('BUDDY_OK: Added successfully');
showNotification('✅ Buddy added!', 'success');
loadBuddies();
} else {
console.log('BUDDY_FAIL: Server rejected');
showNotification('⚠️ Could not add buddy', 'error');
}
} catch(e) {
console.log('BUDDY_ERROR:', e.message);
showNotification('⚠️ Add buddy error', 'error');
}
}
async function loadBuddies() {
try {
const ownerTid = (window.currentUser && currentUser.id) ? String(currentUser.id) : '';
if (!ownerTid) return;
const res = await fetch(`/api/buddies.php?owner_tid=${encodeURIComponent(ownerTid)}`);
const j = await res.json();
if (j && j.ok && Array.isArray(j.buddies)) {
const el = document.getElementById('buddyList');
if (el) el.innerHTML = j.buddies.map(b => `<div class=\"flex items-center gap-2 py-1\"><div class=\"w-6 h-6 rounded-full bg-white/10 flex items-center justify-center text-xs\">${(b.username||'U').slice(0,1)}</div><div class=\"text-xs\">${b.username||b.tid}</div></div>`).join('');
}
} catch(e) {}
}
// Badge system functions
function showBadgeDetails(badgeType) {
// Find all badge detail elements (for both tabs)
const badgeDetails = document.querySelectorAll('#badgeDetails, #passportBadgeDetails');
const badgeImages = document.querySelectorAll('#badgeDetailImage, #passportBadgeDetailImage');
const badgeTitles = document.querySelectorAll('#badgeDetailTitle, #passportBadgeDetailTitle');
const badgePerks = document.querySelectorAll('#badgeDetailPerk, #passportBadgeDetailPerk');
// Remove active class from all badge buttons
document.querySelectorAll('.badge-btn').forEach(btn => {
btn.classList.remove('bg-white/20', 'border-indigo-500/50');
btn.classList.add('hover:bg-white/10');
});
// Add active class to clicked badge button
const activeBtns = document.querySelectorAll(`[onclick="showBadgeDetails('${badgeType}')"]`);
activeBtns.forEach(btn => {
btn.classList.add('bg-white/20', 'border-indigo-500/50');
btn.classList.remove('hover:bg-white/10');
});
// Update badge details based on type
let badgeImageSrc, badgeTitleText, badgePerkText;
switch(badgeType) {
case 'blueblox':
badgeImageSrc = 'BlueBlox_NEW.png';
badgeTitleText = 'BlueBlox';
badgePerkText = 'Multiplier: x1.2';
break;
case 'problox':
badgeImageSrc = 'ProBlox_NEW.png';
badgeTitleText = 'ProBlox';
badgePerkText = 'Multiplier: x1.5';
break;
case 'mayorblox':
badgeImageSrc = 'mayorblox_NEW.png';
badgeTitleText = 'MayorBlox';
badgePerkText = '+50 GBLX p/day';
break;
}
// Update all badge detail elements
badgeImages.forEach(img => img.src = badgeImageSrc);
badgeTitles.forEach(title => title.textContent = badgeTitleText);
badgePerks.forEach(perk => perk.textContent = badgePerkText);
}
// New Badge Modal System
function showBadgeModal(badgeType) {
const modal = document.getElementById('badgeModal');
const modalImage = document.getElementById('modalBadgeImage');
const modalTitle = document.getElementById('modalBadgeTitle');
const modalPerk = document.getElementById('modalBadgePerk');
// Update modal content based on badge type
let badgeImageSrc, badgeTitleText, badgePerkText;
switch(badgeType) {
case 'blueblox':
badgeImageSrc = 'BlueBlox_NEW.png';
badgeTitleText = 'BlueBlox';
badgePerkText = 'Multiplier: x1.2';
break;
case 'problox':
badgeImageSrc = 'ProBlox_NEW.png';
badgeTitleText = 'ProBlox';
badgePerkText = 'Multiplier: x1.5';
break;
case 'mayorblox':
badgeImageSrc = 'mayorblox_NEW.png';
badgeTitleText = 'MayorBlox';
badgePerkText = '+50 GBLX p/day';
break;
}
// Update modal elements
modalImage.src = badgeImageSrc;
modalTitle.textContent = badgeTitleText;
modalPerk.textContent = badgePerkText;
// Show modal
modal.classList.remove('hidden');
modal.classList.add('flex');
}
function closeBadgeModal() {
const modal = document.getElementById('badgeModal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
// Close modal when clicking outside
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('badgeModal');
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeBadgeModal();
}
});
// Check Telegram wallet connection status on page load
const isConnected = localStorage.getItem('telegram_wallet_connected') === 'true';
const connectBtnInit = document.getElementById('walletConnectBtn');
const buttonTextInit = document.getElementById('walletBtnText');
// Restore badges from localStorage if present
try {
const storedBadges = localStorage.getItem('bloxid_user_badges');
if (storedBadges) {
const parsed = JSON.parse(storedBadges);
if (Array.isArray(parsed)) {
userBadges = parsed;
}
}
} catch(e) {}
if (isConnected) {
if (!userBadges.includes('problox')) {
userBadges.push('problox');
try { localStorage.setItem('bloxid_user_badges', JSON.stringify(userBadges)); } catch(e) {}
}
updateBadgeDisplay();
enforceVerifiedProbloxBadge();
if (connectBtnInit) {
connectBtnInit.disabled = false;
connectBtnInit.className = 'card-glass rounded-xl px-4 py-2 w-full border text-white font-semibold text-xs flex items-center justify-center space-x-2 bg-green-600 hover:bg-green-700 border-green-400/50';
}
if (buttonTextInit) buttonTextInit.textContent = 'Connected!';
} else {
// Even if button shows Connect, if verified we still show the ProBlox badge
enforceVerifiedProbloxBadge();
if (connectBtnInit) {
connectBtnInit.disabled = false;
connectBtnInit.className = 'card-glass rounded-xl px-4 py-2 w-full border text-white font-semibold text-xs flex items-center justify-center space-x-2 bg-gray-600 hover:bg-gray-700 border-white/20';
}
if (buttonTextInit) buttonTextInit.textContent = 'Connect';
}
});
</script>
<style>
@keyframes floatUp {
0% {
opacity: 1;
transform: translateY(0) scale(1);
}
50% {
opacity: 0.8;
transform: translateY(-25px) scale(1.1);
}
100% {
opacity: 0;
transform: translateY(-50px) scale(1.2);
}
}
.tap-image {
cursor: pointer;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
}
/* No visual effects on mobile for maximum tap speed */
@media (max-width: 768px) {
.tap-image {
transition: none !important;
filter: none !important; /* Remove all shadows/effects */
transform: none !important;
}
.tap-image:active,
.tap-image:hover {
transform: none !important; /* No effects at all */
filter: none !important;
}
}
/* Desktop effects only */
@media (min-width: 769px) {
.tap-image {
transition: transform 0.2s ease;
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2));
}
.tap-image:hover {
transform: scale(1.05);
}
}
</style>
<!-- Badge Modal -->
<div id="badgeModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div class="card-glass rounded-2xl p-6 max-w-sm w-full mx-4 relative">
<!-- Close Button -->
<button onclick="closeBadgeModal()" class="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
<!-- Badge Content -->
<div class="text-center">
<div class="w-24 h-24 mb-4 mx-auto">
<img id="modalBadgeImage" src="BlueBlox_NEW.png" alt="Badge" class="w-full h-full object-contain">
</div>
<h3 id="modalBadgeTitle" class="text-xl font-bold mb-2">BlueBlox</h3>
<p id="modalBadgePerk" class="text-gray-300 mb-4">Multiplier: x1.2</p>
<div class="text-sm text-gray-400">
<p>This badge provides a permanent multiplier bonus to your aura generation.</p>
</div>
</div>
</div>
</div>
<!-- FIX SCRIPT - Tab switching is now fixed in the main function above -->
<script>
console.log('🔧 Tab switching fix loaded! The main showTab function has been updated.');
// Add direct event listeners to all navigation buttons
setTimeout(() => {
console.log('🔧 Setting up direct event listeners for all nav buttons...');
// BloxID button
const bloxidBtn = document.querySelector('[onclick="showTab(\'passport\')"]');
if (bloxidBtn) {
bloxidBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
showTab('passport');
});
}
// QuestBlox button
const questBtn = document.querySelector('[onclick="showTab(\'quests\')"]');
if (questBtn) {
questBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
showTab('quests');
});
}
// StreetBlox button
const streetbloxBtn = document.getElementById('streetbloxButton');
if (streetbloxBtn) {
streetbloxBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
showTab('monuments');
});
}
// BuddyBlox button
const buddyBtn = document.querySelector('[onclick="showTab(\'friends\')"]');
if (buddyBtn) {
buddyBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
showTab('friends');
});
}
console.log('✅ All navigation buttons now have direct event listeners!');
}, 1000);
// ===== COMPLETE TELEGRAM INTEGRATION =====
// Complete Telegram User Integration
function initializeTelegramUser(tg) {
console.log('👤 Initializing Telegram user...');
const user = tg.initDataUnsafe.user;
if (user) {
console.log('✅ Telegram user found:', user);
// Send user data to login API
fetch('/api/login/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Telegram-Data': tg.initData
},
body: JSON.stringify({ user: user })
})
.then(response => response.json())
.then(data => {
if (data.ok) {
console.log('✅ User logged in successfully');
currentUser = user;
isLoggedIn = true;
// Update user profile display
updateUserProfile(user);
// Store user token for API calls
sessionStorage.setItem('app_token', data.hash);
// Load user badges from login response
if (data.badges) {
userBadges = data.badges;
updateBadgeDisplay();
}
// Load user's current score and badges
loadUserData();
// Load available quests
loadQuests();
} else {
console.error('❌ Login failed:', data);
useTestMode();
}
})
.catch(error => {
console.error('❌ Login error:', error);
useTestMode();
});
} else {
console.log('⚠️ No Telegram user data, using test mode');
useTestMode();
}
}
// TON Wallet Quest start handler (shows dialog on mobile)
function startTonWalletQuest() {
const isMobile = window.matchMedia('(max-width: 768px)').matches;
if (isMobile) {
// Mobile: connect directly
connectTelegramWallet();
return;
}
// Desktop: show simple dialog similar to badge modal
const existing = document.getElementById('tonQuestDialog');
if (existing) existing.parentNode.removeChild(existing);
const dialog = document.createElement('div');
dialog.id = 'tonQuestDialog';
dialog.className = 'fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm';
dialog.innerHTML = `
<div class="card-glass rounded-2xl p-6 max-w-sm w-full mx-4 relative">
<button id="tonDialogClose" class="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
<div class="text-center">
<div class="w-20 h-20 mb-4 mx-auto">
<img src="ProBlox_NEW.png" alt="ProBlox" class="w-full h-full object-contain">
</div>
<h3 class="text-xl font-bold mb-2">Connect TON Wallet</h3>
<p class="text-gray-300 mb-4">Earn 150 GBLX + 3× multiplier while connected.</p>
<div class="grid grid-cols-2 gap-3">
<button id="tonChooseMobile" class="px-4 py-2 bg-muted rounded-lg text-sm">I'm on Mobile</button>
<button id="tonChooseDesktop" class="px-4 py-2 bg-muted rounded-lg text-sm">I'm on Desktop</button>
</div>
</div>
</div>
`;
document.body.appendChild(dialog);
document.getElementById('tonDialogClose').onclick = () => dialog.remove();
document.getElementById('tonChooseMobile').onclick = () => { dialog.remove(); connectTelegramWallet(); };
document.getElementById('tonChooseDesktop').onclick = () => { dialog.remove(); connectTelegramWallet(); };
}
// LAST-RESORT RESTORE: read tg_user cookie set by auth_redirect.php
try {
const m = document.cookie.match(/(?:^|; )tg_user=([^;]+)/);
if (m) {
const raw = decodeURIComponent(m[1]);
const user = JSON.parse(atob(raw));
if (user && user.id) {
console.log('🍪 Restored Telegram user from cookie');
onTelegramAuth(user);
// Clear cookie
document.cookie = 'tg_user=; Max-Age=0; path=/';
}
}
} catch(e) {}
// Show detailed aura information when progress bar is clicked
function showAuraDetails() {
const currentLevel = getLevel(score);
const nextThreshold = levelThresholds[currentLevel + 1] ?? (levelThresholds[levelThresholds.length - 1] + 1000);
const prevThreshold = levelThresholds[currentLevel] ?? 0;
const progress = Math.max(0, Math.min(100, ((score - prevThreshold) / (nextThreshold - prevThreshold)) * 100));
const message = `Level ${currentLevel}\n${score} / ${nextThreshold} Aura\n${progress.toFixed(1)}% Complete\n\nNext Level: ${nextThreshold - score} Aura needed`;
if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.showAlert) {
window.Telegram.WebApp.showAlert(message);
} else {
alert(message);
}
}
// Use test mode when Telegram data is not available
function useTestMode() {
currentUser = {
id: 123456789,
first_name: 'Test User',
username: 'testuser',
photo_url: null
};
isLoggedIn = true;
updateUserProfile(currentUser);
giveBlueBloxBadge();
updateBadgeDisplay();
// Load existing values from localStorage or use defaults
score = parseInt(localStorage.getItem('bloxid_unified_score')) || score;
balance = parseInt(localStorage.getItem('bloxid_unified_balance')) || balance;
// Save to localStorage
localStorage.setItem('bloxid_unified_score', score);
localStorage.setItem('bloxid_unified_balance', balance);
// Update displays with loaded values
updateAllDisplays();
// Load quests in test mode too
loadQuests();
// Ensure displays are updated with loaded values
setTimeout(() => {
updateAllDisplays();
console.log('✅ Displays updated with persisted data - Score:', score, 'Balance:', balance);
}, 500);
}
// Update user profile in UI
function updateUserProfile(user) {
console.log('🔄 Updating user profile display...');
// Update user avatar
const avatarElement = document.querySelector('.w-8.h-8.rounded-full');
if (avatarElement) {
if (user.photo_url) {
avatarElement.innerHTML = `<img src="${user.photo_url}" class="w-8 h-8 rounded-full object-cover">`;
} else {
// Use first letter of name with gradient background
const firstLetter = user.first_name ? user.first_name.charAt(0).toUpperCase() : 'U';
avatarElement.innerHTML = firstLetter;
avatarElement.className = 'w-8 h-8 rounded-full bg-gradient-to-r from-accent to-secondary flex items-center justify-center font-bold text-xs';
}
}
// Update username display
try {
const bottom = document.getElementById('usernameBottom');
if (bottom) bottom.textContent = user.first_name || 'User';
const desktopName = document.getElementById('usernameDesktop');
if (desktopName) desktopName.textContent = user.first_name || 'User';
} catch(e) {}
console.log('✅ User profile updated');
}
// Give BlueBlox badge to user
function giveBlueBloxBadge() {
console.log('🏆 Giving BlueBlox badge to user...');
if (!userBadges.includes('blueblox')) {
userBadges.push('blueblox');
updateBadgeDisplay();
showNotification('🏆 BlueBlox Badge Earned! Now you can do quests!', 'success');
}
}
// Update badge display with question marks for empty slots
function updateBadgeDisplay() {
// Ensure ProBlox is present if wallet is verified
try {
const wasVerified = localStorage.getItem('telegram_wallet_verified') === 'true';
if (wasVerified && !userBadges.includes('problox')) {
userBadges.push('problox');
try { localStorage.setItem('bloxid_user_badges', JSON.stringify(userBadges)); } catch(e) {}
}
if (!wasVerified && userBadges.includes('problox')) {
// If not verified anymore, ensure problox is removed from display list
userBadges = userBadges.filter(b => b !== 'problox');
try { localStorage.setItem('bloxid_user_badges', JSON.stringify(userBadges)); } catch(e) {}
}
} catch (e) {}
console.log('🏆 Updating badge display...');
const badgeSlots = document.querySelectorAll('.badge-slot');
badgeSlots.forEach((slot, index) => {
if (index < userBadges.length) {
const badgeType = userBadges[index];
let badgeImage = 'BlueBlox_NEW.png';
let badgeName = 'BlueBlox';
switch(badgeType) {
case 'blueblox':
badgeImage = 'BlueBlox_NEW.png';
badgeName = 'BlueBlox';
break;
case 'problox':
badgeImage = 'ProBlox_NEW.png';
badgeName = 'ProBlox';
break;
case 'mayorblox':
badgeImage = 'mayorblox_NEW.png';
badgeName = 'MayorBlox';
break;
}
slot.innerHTML = `<img src="${badgeImage}" alt="${badgeName} Badge" class="w-8 h-8 object-contain">`;
slot.onclick = () => showBadgeModal(badgeType);
slot.className = 'badge-slot w-12 h-12 rounded-lg hover:bg-white/10 transition-all duration-200 flex items-center justify-center relative group';
} else {
// Empty slot with question mark
slot.innerHTML = `
<div class="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<span class="text-xs text-gray-400">?</span>
</div>
`;
slot.onclick = null;
slot.className = 'badge-slot w-12 h-12 rounded-lg bg-white/5 border border-white/10 backdrop-blur-sm flex items-center justify-center relative group';
}
});
}
// Load user data from server
function loadUserData() {
if (!currentUser) return;
fetch('/api/getUser/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'App-Token': sessionStorage.getItem('app_token') || ''
},
body: JSON.stringify({ user_id: currentUser.id })
})
.then(response => response.json())
.then(data => {
if (data.ok || data.score !== undefined) {
console.log('✅ User data loaded:', data);
// Prioritize server data for cross-platform sync
score = parseInt(localStorage.getItem('bloxid_unified_score')) || 0 // DISABLED server override;
balance = data.balance || parseInt(localStorage.getItem('bloxid_unified_balance')) || 1000;
// Save server values to localStorage for offline use
localStorage.setItem('bloxid_unified_score', score);
localStorage.setItem('bloxid_unified_balance', balance);
console.log('🔄 Cross-platform sync: Server score:', data.score, 'Server balance:', data.balance);
// Load user badges (if stored in database)
if (data.badges) {
try {
userBadges = JSON.parse(data.badges);
} catch(e) {
userBadges = ['blueblox']; // Default
}
}
// Ensure user has BlueBlox badge
if (!userBadges.includes('blueblox')) {
userBadges.unshift('blueblox'); // Add at beginning
}
// Update all displays
updateAllDisplays();
// Re-enforce verified ProBlox after any server badge load
enforceVerifiedProbloxBadge();
updateBadgeDisplay();
}
})
.catch(error => {
console.error('❌ Error loading user data:', error);
// Give default badge anyway
giveBlueBloxBadge();
});
}
// Enhanced tap function with server saving
function enhancedTap() {
if (!isLoggedIn) {
console.log('⚠️ User not logged in, using local tap');
tap();
return;
}
// Call original tap function for immediate UI update
tap();
// Save to server
saveScoreToServer();
}
// Save score to server in real-time with cross-platform sync
function saveScoreToServer() {
if (!currentUser || !isLoggedIn) return;
fetch('/api/tap/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'App-Token': sessionStorage.getItem('app_token') || ''
},
body: JSON.stringify({
user_id: currentUser.id,
score: score,
balance: balance
})
})
.then(response => response.json())
.then(data => {
if (data.ok) {
console.log('💾 Score saved to server - synced across platforms');
// Update with server response for sync
if (data.new_score !== undefined) {
score = data.new_score;
balance = data.new_balance || balance;
// Update localStorage with server values
localStorage.setItem('bloxid_unified_score', score);
localStorage.setItem('bloxid_unified_balance', balance);
updateAllDisplays();
}
}
})
.catch(error => {
console.error('❌ Error saving score:', error);
});
}
// Update all score displays
function updateAllDisplays() {
const displays = [
'questScoreDisplay', 'questBalanceDisplay',
'passportScoreDisplay', 'passportBalanceDisplay',
'passportTapScoreDisplay', 'passportTapBalanceDisplay',
'passportTapGBLXDisplay'
];
displays.forEach(id => {
const element = document.getElementById(id);
if (element) {
if (id.includes('Score') || id.includes('Aura')) {
element.textContent = score.toLocaleString();
} else if (id.includes('Balance') || id.includes('GBLX')) {
element.textContent = balance.toLocaleString();
}
}
});
// Update mobile aura progress bar when displays are updated
updateLevelProgress();
}
// Badge earning system
function earnBadge(badgeType) {
if (!userBadges.includes(badgeType)) {
userBadges.push(badgeType);
updateBadgeDisplay();
const badgeNames = {
'blueblox': 'BlueBlox',
'problox': 'ProBlox',
'mayorblox': 'MayorBlox'
};
showNotification(`🏆 ${badgeNames[badgeType]} Badge Earned!`, 'success');
// Save badges to server
saveBadgesToServer();
}
}
// Save badges to server
function saveBadgesToServer() {
if (!currentUser) return;
fetch('/api/updateBadges/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'App-Token': sessionStorage.getItem('app_token') || ''
},
body: JSON.stringify({
user_id: currentUser.id,
badges: JSON.stringify(userBadges)
})
})
.then(response => response.json())
.then(data => {
if (data.ok) {
console.log('🏆 Badges saved to server');
}
})
.catch(error => {
console.error('❌ Error saving badges:', error);
});
}
// Load and display quests from Admin CMS JSON API
function loadQuests() {
console.log('🎮 Loading quests from Admin API...');
fetch('/admin/quests_api.php')
.then(r => r.json())
.then(data => {
if (!data || !data.ok) throw new Error('Invalid response');
const quests = Array.isArray(data.quests) ? data.quests : [];
console.log('📦 Quests from CMS:', quests);
renderCmsFeaturedQuests(quests);
displayQuestsListFallback(quests);
// After quests, load content blocks
loadContentBlocks();
})
.catch(err => {
console.error('❌ Failed to load CMS quests:', err);
// Even if quests fail, try to load content blocks
loadContentBlocks();
});
}
// Load content blocks (title/subtitle, wallet button text/link, images)
function loadContentBlocks(){
fetch('/admin/content_api.php')
.then(r=>r.json())
.then(data=>{
const c = (data && data.content) ? data.content : {};
// Title / subtitle
try {
const titleLabel = document.querySelector('.my-bloxid-text');
if (titleLabel && c.bloxid_title) titleLabel.textContent = c.bloxid_title;
} catch(e) {}
try {
const subtitleEls = document.querySelectorAll('#passportTapLevelNumberMobile, #passportTapLevelNumberDesktop, #passportLevelNumber');
if (c.bloxid_subtitle) {
// If subtitle provided, set it in the small profile area under user name
const userInfo = document.querySelector('.font-semibold.text-xs + .text-xs.text-gray-300');
if (userInfo) userInfo.textContent = c.bloxid_subtitle;
}
} catch(e) {}
// Wallet button
try {
const btnText = document.getElementById('walletBtnText');
if (btnText && c.wallet_button_text) btnText.textContent = c.wallet_button_text;
const btn = document.getElementById('walletConnectBtn');
if (btn && c.wallet_button_link) {
btn.onclick = function(){ window.open(c.wallet_button_link, '_blank'); };
}
} catch(e) {}
// Featured images
try {
const passportImg = document.querySelector('#passportFeaturedGrid img');
if (passportImg && c.featured_image_passport) passportImg.src = c.featured_image_passport;
const questbloxImg = document.querySelector('#questsFeaturedGrid img');
if (questbloxImg && c.featured_image_questblox) questbloxImg.src = c.featured_image_questblox;
} catch(e) {}
// QuestBlox hero: title/subtitle and featured image in featured grid (not the small logo)
try {
const qbHeroTitle = document.querySelector('#questsTab .card-glass.lg\\:col-span-2 h2');
if (qbHeroTitle && c.questblox_title) qbHeroTitle.textContent = c.questblox_title;
const qbHeroSub = document.querySelector('#questsTab .card-glass.lg\\:col-span-2 p');
if (qbHeroSub && c.questblox_subtitle) qbHeroSub.textContent = c.questblox_subtitle;
// Override first featured card image (regardless of data source)
const qbFeaturedImg = document.querySelector('#questsFeaturedGrid > div:first-child img');
if (qbFeaturedImg && c.questblox_featured_image) qbFeaturedImg.src = c.questblox_featured_image;
// Set hero background image behind the text box
const qbHeroBox = document.querySelector('#questsTab .card-glass.lg\\:col-span-2');
if (qbHeroBox && c.questblox_featured_image) {
qbHeroBox.style.backgroundImage = `url('${c.questblox_featured_image}')`;
qbHeroBox.style.backgroundSize = 'cover';
qbHeroBox.style.backgroundPosition = 'center';
}
} catch(e) {}
// StreetBlox titles/images
try {
const sbLogo = document.querySelector('img[alt="StreetBlox"]');
if (sbLogo && c.streetblox_featured_image) sbLogo.src = c.streetblox_featured_image;
const sbTag = document.getElementById('questMonumentSpotlightTag');
if (sbTag && c.streetblox_title) sbTag.textContent = c.streetblox_title + ' Nearby';
} catch(e) {}
// BuddyBlox titles/images
try {
const bbLogo = document.querySelector('img[alt="BuddyBlox"]');
if (bbLogo && c.buddyblox_featured_image) bbLogo.src = c.buddyblox_featured_image;
const bbHeader = document.querySelector('#friendsTab h2, #friendsTab .text-2xl.font-bold');
if (bbHeader && c.buddyblox_title) bbHeader.textContent = c.buddyblox_title;
} catch(e) {}
})
.catch(()=>{});
}
// Render featured quests into the two grids from CMS
function renderCmsFeaturedQuests(quests) {
const userId = (window.currentUser && currentUser.id) ? String(currentUser.id) : null;
const userQuests = userId ? quests.filter(q => q.audience === 'user' && String(q.telegram_user_id||'') === userId) : [];
const globalQuests = quests.filter(q => q.audience !== 'user');
const passportEl = document.getElementById('passportFeaturedGrid');
const questsEl = document.getElementById('questsFeaturedGrid');
if (!passportEl || !questsEl) return;
function cardHtml(q){
const img = q.image_url || 'ProBlox_NEW.png';
const btnText = q.button_text || 'Start';
const reward = Number.isFinite(q.reward_gblx) ? q.reward_gblx : 0;
const safeDesc = (q.description||'').replace(/</g,'&lt;').replace(/>/g,'&gt;');
return `
<div class="card-glass rounded-2xl overflow-hidden card-hover hover:border-accent/50 transition-colors">
<div class="relative h-48">
<div class="absolute inset-0" style="background-image:url('${img}');background-size:cover;background-position:center"></div>
<div class="absolute inset-0 bg-gradient-to-br from-black/20 to-black/10"></div>
</div>
<div class="p-6">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-lg">${q.title||'Quest'}</h3>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full bg-accent mr-1"></div>
Active
</div>
</div>
<p class="text-gray-300 text-sm mb-4">${safeDesc}</p>
<div class="flex items-center justify-between gap-2">
<div class="text-xs text-secondary font-medium">Reward: ${reward} GBLX</div>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]" onclick="startQuestOrLink('${q.id||''}', '${(q.title||'Quest').replace(/'/g,"\\'")}', ${reward}, ${q.button_link ? `'${q.button_link.replace(/'/g,"\\'")}'` : 'null'})">
${btnText}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1"><path d="M5 12h14M12 5l7 7-7 7"></path></svg>
</button>
<button class="text-xs font-medium px-3 py-1 bg-white/10 border border-white/20 rounded-lg" onclick="shareQuest('${(q.title||'Quest').replace(/'/g,"\\'")}', 'telegram', '${q.id||'generic'}')">Share</button>
</div>
</div>
</div>`;
}
// Passport tab: show per-user first, then global (no cap)
passportEl.innerHTML = '';
const passportList = [...userQuests, ...globalQuests];
passportList.forEach(q => { passportEl.insertAdjacentHTML('beforeend', cardHtml(q)); });
// QuestBlox tab: show all global quests (no cap)
questsEl.innerHTML = '';
globalQuests.forEach(q => { questsEl.insertAdjacentHTML('beforeend', cardHtml(q)); });
// Also map the 4 QuestBlox highlight cards (top-right + three below) to the first 4 published global quests
try {
// Grab the first two grids inside QuestBlox container
const grids = document.querySelectorAll('#questsTab .max-w-7xl .grid');
const topRow = grids && grids.length > 0 ? grids[0] : null;
const lowerRow = grids && grids.length > 1 ? grids[1] : null;
const topRightCard = topRow ? topRow.querySelectorAll(':scope > div')[1] : null;
const lowerCards = lowerRow ? lowerRow.querySelectorAll(':scope > div') : [];
function highlightCardHtml(q){
const img = q.image_url || 'ProBlox_NEW.png';
const btnText = q.button_text || 'Start';
const reward = Number.isFinite(q.reward_gblx) ? q.reward_gblx : 0;
const safeDesc = (q.description||'').replace(/</g,'&lt;').replace(/>/g,'&gt;');
const onclick = q.button_link ? `window.open('${q.button_link}','_blank')` : `startQuest('${q.id||''}', '${(q.title||'Quest').replace(/'/g,"\\'")}', ${reward})`;
return `
<div class="hero-slide relative h-full">
<div class="absolute inset-0" style="background-image:url('${img}');background-size:cover;background-position:center"></div>
<div class="h-full relative bg-gradient-to-br from-black/30 to-black/10 p-4 flex flex-col justify-end">
<div>
<div class="inline-block px-1.5 py-0.5 bg-muted rounded-full text-xs font-medium">Featured</div>
<h3 class="text-lg font-bold mt-1">${q.title||'Quest'}</h3>
<p class="text-gray-300 text-xs mt-1">${safeDesc}</p>
<div class="flex items-center justify-between mt-2">
<div class="text-xs text-secondary font-medium">Reward: ${reward} GBLX</div>
<button class="text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-[#22232e]" onclick="${onclick}">
${btnText}
<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" class=\"ml-1\"><path d=\"M5 12h14M12 5l7 7-7 7\"></path></svg>
</button>
</div>
</div>
</div>
</div>`;
}
// Use the most recent 4 global quests (end of array)
const featured = globalQuests.slice(-4);
const lastIdx = featured.length - 1;
if (topRightCard && lastIdx >= 0) {
topRightCard.innerHTML = highlightCardHtml(featured[lastIdx]);
}
const rest = featured.slice(0, Math.max(0, lastIdx));
for (let i=0; i<lowerCards.length && i<rest.length; i++) {
lowerCards[i].innerHTML = highlightCardHtml(rest[i]);
}
} catch(e) {
console.warn('Highlight mapping skipped:', e);
}
}
// Display quests list in QuestBlox section (fallback list renderer)
function displayQuestsListFallback(quests) {
console.log('🎮 Available quests:', quests);
// Find the quest container
const questContainer = document.querySelector('#questblox .space-y-4');
if (!questContainer) {
console.log('❌ Quest container not found');
return;
}
// Clear existing quests (keep the first static quest for demo)
const staticQuests = questContainer.children.length;
console.log('📋 Found', staticQuests, 'static quests, adding', quests.length, 'dynamic quests');
// Add dynamic quests
quests.forEach((quest, index) => {
const questElement = document.createElement('div');
questElement.className = 'bg-card rounded-xl p-4 border border-muted';
questElement.innerHTML = `
<div class="flex items-start justify-between mb-3">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-r from-accent to-secondary flex items-center justify-center">
<span class="text-sm font-bold">${(quest.title||'Q').charAt(0)}</span>
</div>
<div>
<h3 class="font-semibold text-sm">${quest.title||'Quest'}</h3>
<div class="flex items-center space-x-2 mt-1">
<span class="text-secondary text-xs font-medium">${Number.isFinite(quest.reward_gblx)?quest.reward_gblx:(quest.reward_amount||0)} GBLX</span>
<div class="px-2 py-1 bg-muted rounded-full text-xs flex items-center">
<div class="w-2 h-2 rounded-full ${quest.status === 'completed' ? 'bg-green-500' : 'bg-accent'} mr-1"></div>
${quest.status === 'completed' ? 'Completed' : 'Active'}
</div>
</div>
</div>
</div>
</div>
<p class="text-gray-400 text-sm mb-4">
${(quest.description||'')}
</p>
<button onclick="startQuest('${quest.id||''}', '${(quest.title||'Quest').replace(/'/g,"\'")}')"
class="quest-button text-xs font-medium px-3 py-1 bg-muted rounded-lg flex items-center transition-colors hover:bg-accent hover:text-white ${quest.status === 'completed' ? 'opacity-50 cursor-not-allowed' : ''}">
${quest.status === 'completed' ? 'Completed' : 'Start Quest'}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-1">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
`;
questContainer.appendChild(questElement);
});
console.log('✅ Quests displayed successfully');
}
// Quest interaction function
function startQuest(questId, questTitle, rewardOverride) {
console.log('🎯 Starting quest:', questId, questTitle, 'reward:', rewardOverride);
// Show quest started notification with countdown
showNotification(`🎮 Quest "${questTitle}" started! Auto-completing in 3 seconds...`, 'success');
// Disable the button temporarily
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = 'Processing... 3';
button.disabled = true;
let countdown = 3;
const countdownInterval = setInterval(() => {
countdown--;
if (countdown > 0) {
button.innerHTML = `Processing... ${countdown}`;
} else {
clearInterval(countdownInterval);
button.innerHTML = 'Completing...';
completeQuest(questId, questTitle, rewardOverride);
button.innerHTML = '✅ Completed';
button.style.backgroundColor = '#10b981';
}
}, 1000);
}
// Start quest or open link, but still reward
function startQuestOrLink(questId, questTitle, rewardOverride, link){
if (link) {
try { window.open(link, '_blank'); } catch(e) {}
// also award immediately to avoid losing credit
completeQuest(questId, questTitle, rewardOverride);
} else {
startQuest(questId, questTitle, rewardOverride);
}
}
// Complete quest function
async function completeQuest(questId, questTitle, rewardOverride) {
console.log('🏆 Completing quest:', questId, questTitle, 'reward:', rewardOverride);
// Award GBLX
const reward = Number.isFinite(rewardOverride) ? rewardOverride : 100;
balance += reward;
// Save to localStorage immediately
localStorage.setItem('bloxid_unified_balance', balance);
localStorage.setItem('bloxid_unified_score', score);
console.log('💾 Quest reward saved - Score:', score, 'Balance:', balance);
// Update displays
updateAllDisplays();
// Show completion notification
showNotification(`🏆 Quest "${questTitle}" completed! +${reward} GBLX earned!`, 'success');
console.log('✅ Quest completed, data saved to localStorage');
// Persist completion server-side (simple file-based progress)
try {
const tid = (window.currentUser && currentUser.id) ? String(currentUser.id) : 'guest';
await fetch('/api/quests_progress.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'complete', tid, quest_id: questId })
});
} catch (e) {
console.warn('progress save failed', e);
}
}
// Simple localStorage sync - No server complexity
function syncWithServer() {
console.log('🔄 Simple localStorage sync...');
// Save to localStorage
localStorage.setItem('bloxid_unified_score', score);
localStorage.setItem('bloxid_unified_balance', balance);
localStorage.setItem('bloxid_last_sync', Date.now());
console.log('✅ Data synced to localStorage - Score:', score, 'Balance:', balance);
}
// Simple save function - localStorage only
function saveToServer() {
console.log('💾 Simple localStorage save...');
localStorage.setItem('bloxid_unified_score', score);
localStorage.setItem('bloxid_unified_balance', balance);
console.log('✅ Data saved to localStorage');
}
// Ensure ProBlox badge if wallet is verified (helper)
function enforceVerifiedProbloxBadge() {
try {
const wasVerified = localStorage.getItem('telegram_wallet_verified') === 'true';
if (wasVerified && !userBadges.includes('problox')) {
userBadges.push('problox');
try { localStorage.setItem('bloxid_user_badges', JSON.stringify(userBadges)); } catch(e) {}
updateBadgeDisplay();
}
} catch (e) {}
}
// Quest sharing system for promotion (popup-based, non-blocking + resilient reward)
function shareQuest(questTitle, platform = 'telegram', questId = 'generic') {
const tid = (window.currentUser && currentUser.id) ? String(currentUser.id) : '';
const shareText = `🎮 Join me in "${questTitle}" on BloxID! Earn GBLX and complete amazing quests!`;
const shareUrl = `https://t.me/bloxid_bot?start=quest_${tid || 'demo'}`;
const tgShare = `https://t.me/share/url?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareText)}`;
// Always open share in a separate window to avoid blocking this page
const w = window.open(tgShare, '_blank', 'noopener,noreferrer,width=600,height=800');
let closedPoll = null;
let rewarded = false;
const sendReward = () => {
if (rewarded) return;
rewarded = true;
if (tid) {
fetch('/api/shares.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ telegram_user_id: tid, quest_id: String(questId || 'generic'), medium: platform })
}).then(r => r.json()).then(j => {
if (j && j.ok && typeof j.reward_gblx === 'number') {
balance += j.reward_gblx;
updateAllDisplays();
console.log('SHARE_OK server', j.reward_gblx);
showNotification(`🎉 +${j.reward_gblx} GBLX for sharing!`, 'success');
} else {
balance += 27;
updateAllDisplays();
console.log('SHARE_OK fallback', 27);
showNotification('🎉 +27 GBLX for sharing!', 'success');
}
}).catch(() => {
balance += 27;
updateAllDisplays();
console.log('SHARE_OK fallback', 27);
showNotification('🎉 +27 GBLX for sharing!', 'success');
});
} else {
balance += 27;
updateAllDisplays();
console.log('SHARE_OK fallback-noTID', 27);
showNotification('🎉 +27 GBLX for sharing!', 'success');
}
};
// Reward after popup closes or after timeout
const timeoutId = setTimeout(sendReward, 6000);
try {
if (w && typeof w.closed !== 'undefined') {
closedPoll = setInterval(() => {
if (w.closed) {
clearInterval(closedPoll);
clearTimeout(timeoutId);
sendReward();
}
}, 500);
} else {
console.log('Share popup blocked or unavailable; relying on timeout reward.');
}
} catch (e) {
console.log('Share popup check error:', e.message);
}
console.log('📤 Share started (popup), quest:', questTitle, 'id:', questId);
}
// Verify TonConnect session with server (issue + verify)
async function verifyTonConnectionWithServer() {
try {
// Use explicit files to avoid PATH_INFO 404s on some hosts
let issueRes = await fetch('/api/tonconnect/issue.php', { credentials: 'include' });
if (!issueRes.ok) throw new Error('Issue request failed');
const issueData = await issueRes.json();
if (!issueData || !issueData.ok || !issueData.challenge) throw new Error('Invalid issue response');
let verifyRes = await fetch('/api/tonconnect/verify.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ challenge: issueData.challenge })
});
if (!verifyRes.ok) throw new Error('Verify request failed');
const verifyData = await verifyRes.json();
if (verifyData && verifyData.ok && verifyData.verified) {
try { localStorage.setItem('telegram_wallet_verified', 'true'); } catch (e) {}
return true;
}
return false;
} catch (err) {
console.log('TON server verify error:', err.message);
return false;
}
}
// Function to connect/disconnect Telegram wallet (toggle)
function connectTelegramWallet() {
const connectBtn = document.getElementById('walletConnectBtn');
const buttonText = document.getElementById('walletBtnText');
const isConnected = localStorage.getItem('telegram_wallet_connected') === 'true';
if (isConnected) {
// DISCONNECT FLOW
// Remove badge
const idx = userBadges.indexOf('problox');
if (idx !== -1) {
userBadges.splice(idx, 1);
updateBadgeDisplay();
try { localStorage.setItem('bloxid_user_badges', JSON.stringify(userBadges)); } catch(e) {}
}
// Revert aura if previously rewarded
if (localStorage.getItem('telegram_wallet_rewarded') === 'true') {
balance = Math.max(0, balance - 150);
updateAllDisplays();
localStorage.removeItem('telegram_wallet_rewarded');
}
// Update state
localStorage.removeItem('telegram_wallet_connected');
localStorage.removeItem('telegram_wallet_verified');
// Update button to grey "Connect"
if (connectBtn) {
connectBtn.disabled = false;
connectBtn.className = 'card-glass rounded-xl px-4 py-2 w-full border text-white font-semibold text-xs flex items-center justify-center space-x-2 bg-gray-600 hover:bg-gray-700 border-white/20';
}
if (buttonText) buttonText.textContent = 'Connect';
showNotification('🔌 Disconnected from wallet', 'success');
return;
}
// CONNECT FLOW via TonConnect
if (connectBtn) {
connectBtn.disabled = true;
if (buttonText) buttonText.textContent = 'Connecting...';
}
const doSuccess = async () => {
// Verify with server before granting rewards/state
const verified = await verifyTonConnectionWithServer();
if (!verified) {
if (connectBtn) connectBtn.disabled = false;
if (buttonText) buttonText.textContent = 'Connect';
showNotification('⚠️ Wallet connection could not be verified. Please try again.', 'error');
return;
}
// Grant badge
giveProBloxBadge();
// Capture wallet address from TonConnect (if available) and link server-side
try {
if (typeof tonConnectUI !== 'undefined' && tonConnectUI?.connector?.wallet) {
const account = tonConnectUI.connector.wallet?.account;
const walletAddress = account?.address || account?.publicKey || null;
if (walletAddress) {
const telegramUserId = (window.currentUser && currentUser.id) || null;
fetch('/api/tonconnect/link.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
telegram_user_id: telegramUserId,
wallet_address: walletAddress,
chain: 'ton'
})
}).catch(() => {});
}
}
} catch (e) {
console.log('Wallet link skip:', e.message);
}
// Reward aura only once
if (localStorage.getItem('telegram_wallet_rewarded') !== 'true') {
balance += 150; // reward in GBLX
updateAllDisplays();
localStorage.setItem('telegram_wallet_rewarded', 'true');
}
// Persist connection
localStorage.setItem('telegram_wallet_connected', 'true');
localStorage.setItem('telegram_wallet_verified', 'true');
// Update button to green "Connected!"
if (connectBtn) {
connectBtn.disabled = false;
connectBtn.className = 'card-glass rounded-xl px-4 py-2 w-full border text-white font-semibold text-xs flex items-center justify-center space-x-2 bg-green-600 hover:bg-green-700 border-green-400/50';
}
if (buttonText) buttonText.textContent = 'Connected!';
showNotification('✅ Telegram wallet connected successfully!', 'success');
};
try {
if (tonConnectUI) {
tonConnectUI.openModal();
tonConnectUI.onStatusChange((w) => {
if (w) doSuccess();
});
return;
}
} catch (e) {
console.log('TonConnect not available, fallback:', e.message);
}
// Fallback: simulate success
setTimeout(() => { doSuccess(); }, 1200);
}
// Function to give ProBlox badge
function giveProBloxBadge() {
if (!userBadges.includes('problox')) {
userBadges.push('problox');
updateBadgeDisplay();
showNotification('🏆 ProBlox Badge Earned!', 'success');
// Persist locally as fallback when server data is unavailable
try { localStorage.setItem('bloxid_user_badges', JSON.stringify(userBadges)); } catch(e) {}
// Save badges to server
saveBadgesToServer();
}
}
// Add Friend Quest function
function startAddFriendQuest() {
const tid = prompt('Enter your friend\'s Telegram ID (numbers only):');
if (!tid || !/^\d{5,}$/.test(tid)) {
showNotification('Please enter a valid Telegram ID (numbers only)', 'error');
return;
}
addBuddy(tid, null).then(() => {
// Quest completion reward
balance += 25;
updateAllDisplays();
showNotification('🎉 Quest completed! +25 GBLX for adding a friend!', 'success');
console.log('FRIEND_QUEST_OK: +25 GBLX reward');
}).catch(() => {
showNotification('Quest failed - could not add friend', 'error');
console.log('FRIEND_QUEST_FAIL: Could not add friend');
});
}
// Function to show notifications
function showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg transition-all duration-300 transform translate-x-full`;
// Set background color based on type
if (type === 'success') {
notification.classList.add('bg-green-500', 'text-white');
} else if (type === 'error') {
notification.classList.add('bg-red-500', 'text-white');
} else {
notification.classList.add('bg-blue-500', 'text-white');
}
notification.textContent = message;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Remove after 3 seconds
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
}
</script>
<script src="https://unpkg.com/@tonconnect/ui@latest/dist/tonconnect-ui.min.js"></script>
<script>
// Initialize TonConnect UI (safe to load even if offline)
let tonConnectUI;
(function initTonConnect() {
try {
tonConnectUI = new TonConnectUI.TonConnectUI({
manifestUrl: 'https://bloxid.app/.well-known/tonconnect-manifest.json'
});
} catch (e) {
console.log('TonConnect UI not available yet:', e.message);
}
})();
</script>
<!-- Static desktop-only Thirdweb connect (high z-index) -->
<button id="twConnectBtnStatic" onclick="thirdwebLoginShim()"
style="position:fixed;right:16px;bottom:80px;z-index:999999;padding:10px 14px;border-radius:10px;border:1px solid rgba(255,255,255,0.2);background:linear-gradient(135deg,#6366f1,#06b6d4);color:#fff;cursor:pointer;display:none;">
Connect with Thirdweb
</button>
<script>
// Show static button on non-Telegram desktop only
(function(){
try {
const ua = navigator.userAgent || '';
const isRealTG = /Telegram/i.test(ua);
const isDesktop = (typeof window !== 'undefined') && window.matchMedia && window.matchMedia('(min-width: 900px)').matches;
const btn = document.getElementById('twConnectBtnStatic');
if (btn && !isRealTG && isDesktop) { btn.style.display = 'block'; }
} catch(e) {}
})();
</script>
<script>
// Fallback creator: ensure button exists and is visible on desktop non-Telegram
(function(){
try {
const ua = navigator.userAgent || '';
const isRealTG = /Telegram/i.test(ua);
const isDesktop = (typeof window !== 'undefined') && window.matchMedia && window.matchMedia('(min-width: 900px)').matches;
if (isRealTG || !isDesktop) return;
let btn = document.getElementById('twConnectBtnStatic');
if (!btn) {
btn = document.createElement('button');
btn.id = 'twConnectBtnStatic';
btn.textContent = 'Connect with Thirdweb';
btn.onclick = (typeof thirdwebLoginShim === 'function' ? thirdwebLoginShim : function(){ alert('Thirdweb not ready'); });
const s = btn.style; s.position='fixed'; s.right='16px'; s.bottom='80px'; s.zIndex='999999'; s.padding='10px 14px'; s.borderRadius='10px'; s.border='1px solid rgba(255,255,255,0.2)'; s.background='linear-gradient(135deg,#6366f1,#06b6d4)'; s.color='#fff'; s.cursor='pointer';
document.body.appendChild(btn);
}
btn.style.display = 'block';
} catch(e) {}
})();
</script>
<script src="/api/_inject_tw.js?v=1"></script>
</body>
<!-- build-tag: v-raven-buddy-3 -->
</html>