/**
 * BTC Catch Miner — Game Controller
 */

'use strict';

// ─── CONFIG ──────────────────────────────────────────────────────────────────
const API_URL  = 'api.php';
const EVT_URL  = 'events.php';
const BOT_NAME = 'BitcoinCatcherGameBot';

// ─── STATE ───────────────────────────────────────────────────────────────────
const State = {
  player:     null,
  tgUser:     null,
  initData:   '',
  config:     {},
  referrerId: '',
  gameActive: false,
  score:      0,
  coins:      0,
  level:      1,
  sseSource:  null,
  feedItems:  [],
};

// ─── TELEGRAM INIT ───────────────────────────────────────────────────────────
const tg = window.Telegram?.WebApp;

function initTelegram() {
  if (!tg) { console.warn('Not in Telegram WebApp'); return; }
  tg.ready();
  tg.expand();
  tg.lockOrientation();
  tg.disableVerticalSwipes();
  tg.setHeaderColor('#080813');
  tg.setBackgroundColor('#080813');

  State.tgUser   = tg.initDataUnsafe?.user ?? null;
  State.initData = tg.initData ?? '';

  const startParam = tg.initDataUnsafe?.start_param ?? '';
  if (startParam.startsWith('ref')) {
    State.referrerId = startParam.slice(3);
  }
}

// ─── HAPTIC ──────────────────────────────────────────────────────────────────
function haptic(type = 'light')       { try { tg?.HapticFeedback?.impactOccurred(type); } catch {} }
function hapticNotif(type = 'success'){ try { tg?.HapticFeedback?.notificationOccurred(type); } catch {} }
function hapticSelect()                { try { tg?.HapticFeedback?.selectionChanged(); } catch {} }

// ─── API ─────────────────────────────────────────────────────────────────────
async function apiCall(action, body = {}) {
  const res = await fetch(`${API_URL}?action=${action}`, {
    method:  'POST',
    headers: {
      'Content-Type':         'application/json',
      'X-Telegram-Init-Data': State.initData,
    },
    body: JSON.stringify({ ...body, initData: State.initData }),
  });
  const data = await res.json();
  if (data.status === 'error') throw new Error(data.message || 'API error');
  return data.data;
}

async function apiGet(action, params = {}) {
  const qs  = new URLSearchParams({ action, ...params }).toString();
  const res = await fetch(`${API_URL}?${qs}`, {
    headers: { 'X-Telegram-Init-Data': State.initData },
  });
  const data = await res.json();
  if (data.status === 'error') throw new Error(data.message || 'API error');
  return data.data;
}

// ─── TOASTS ──────────────────────────────────────────────────────────────────
// ─── POST-AD REWARD BANNER — 5-second auto-close ────────────────────────────
function showAdRewardBanner(coins) {
  const banner  = document.getElementById('ad-reward-banner');
  const titleEl = document.getElementById('arb-title');
  const secsEl  = document.getElementById('arb-secs');
  const ringEl  = document.getElementById('arb-ring-fill');
  const barEl   = document.getElementById('arb-progress-bar');
  if (!banner) return;

  // Populate
  titleEl.textContent = `+${coins.toLocaleString()} coins earned!`;

  // Reset and show
  banner.classList.remove('hidden', 'arb-leave');
  void banner.offsetWidth;
  banner.classList.add('arb-enter');

  // Progress bar drains over 5 s
  barEl.style.transition = 'none';
  barEl.style.width = '100%';
  void barEl.offsetWidth;
  barEl.style.transition = 'width 5s linear';
  barEl.style.width = '0%';

  // Circular ring: stroke-dasharray = 2π×15 ≈ 94.25
  const CIRC = 94.25;
  ringEl.style.transition = 'none';
  ringEl.style.strokeDashoffset = '0';
  void ringEl.offsetWidth;
  ringEl.style.transition = `stroke-dashoffset 5s linear`;
  ringEl.style.strokeDashoffset = String(CIRC);

  // Countdown ticks
  let secsLeft = 5;
  secsEl.textContent = secsLeft;
  const ticker = setInterval(() => {
    secsLeft--;
    secsEl.textContent = Math.max(0, secsLeft);
    if (secsLeft <= 0) {
      clearInterval(ticker);
      _dismissAdRewardBanner(banner);
    }
  }, 1000);

  // Tap to dismiss early
  banner._dismissTicker = ticker;
  banner.onclick = () => { clearInterval(ticker); _dismissAdRewardBanner(banner); };
}

function _dismissAdRewardBanner(banner) {
  banner.classList.remove('arb-enter');
  banner.classList.add('arb-leave');
  setTimeout(() => {
    banner.classList.add('hidden');
    banner.classList.remove('arb-leave');
    banner.onclick = null;
  }, 350);
}

function showToast(msg, type = 'info') {
  const el = document.createElement('div');
  el.className   = `toast ${type}`;
  el.textContent = msg;
  document.getElementById('toast-container').prepend(el);
  setTimeout(() => el.remove(), 3200);
}

// ─── LOADING ─────────────────────────────────────────────────────────────────
function setLoadingProgress(pct, text) {
  const bar = document.getElementById('loading-bar');
  const txt = document.getElementById('loading-text');
  if (bar) bar.style.width = pct + '%';
  if (txt) txt.textContent = text;
}

// ─── ANIMATED COUNTER ────────────────────────────────────────────────────────
function animCounter(el, from, to, dur = 700) {
  if (!el) return;
  const start = performance.now();
  const step = ts => {
    const t   = Math.min((ts - start) / dur, 1);
    const val = Math.floor(from + (to - from) * easeOut(t));
    el.textContent = val.toLocaleString();
    if (t < 1) requestAnimationFrame(step);
  };
  requestAnimationFrame(step);
}
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }

// ─── UI UPDATE ───────────────────────────────────────────────────────────────
function updateUI(player) {
  if (!player) return;
  State.player = player;
  State.level  = player.level ?? 1;

  // Header
  animCounter(document.getElementById('header-coins'), 0, player.coins);
  document.getElementById('header-level').textContent = `Lv.${player.level}`;

  // Avatar
  const photoEl = document.getElementById('avatar-photo');
  const charEl  = document.getElementById('avatar-char');
  if (player.photo_url) {
    photoEl.src = player.photo_url;
    photoEl.classList.remove('hidden');
    charEl.classList.add('hidden');
    photoEl.onerror = () => {
      photoEl.classList.add('hidden');
      charEl.classList.remove('hidden');
    };
  } else {
    photoEl.classList.add('hidden');
    charEl.classList.remove('hidden');
    charEl.textContent = (player.username || 'P')[0].toUpperCase();
  }

  document.getElementById('profile-name').textContent = player.username || 'Player';
  document.getElementById('profile-id').textContent   = `ID: ${player.telegram_id}`;
  document.getElementById('profile-level-badge').textContent = `Lv.${player.level}`;

  animCounter(document.getElementById('stat-coins'),    0, player.coins);
  animCounter(document.getElementById('stat-btc'),      0, player.btc_balance);
  animCounter(document.getElementById('stat-withdraw'), 0, player.total_withdraw);
  animCounter(document.getElementById('stat-refs'),     0, player.referrals);
  animCounter(document.getElementById('stat-games'),    0, player.games_played);
  animCounter(document.getElementById('stat-best'),     0, player.best_score);

  updateXPBar(player);

  // Daily bonus
  const dailyBtn       = document.getElementById('daily-btn');
  const dailyCard      = document.getElementById('daily-card');
  const dailyCountdown = document.getElementById('daily-countdown');
  if (player.can_daily) {
    dailyBtn.disabled    = false;
    dailyBtn.textContent = 'CLAIM';
    dailyCard.style.borderColor = 'rgba(0,255,136,0.4)';
    if (dailyCountdown) dailyCountdown.textContent = '';
    State.nextDailyTs = 0;
  } else {
    dailyBtn.disabled    = true;
    dailyBtn.textContent = '⏳';
    dailyCard.style.borderColor = '';
    // API returns seconds until next claim; convert to absolute Unix timestamp (seconds)
    const secs = player.next_daily_seconds ?? 0;
    State.nextDailyTs = secs > 0 ? Math.floor(Date.now() / 1000) + secs : 0;
    startDailyCountdown();
  }

  // Referral page
  const refLink = `https://t.me/${BOT_NAME}?start=ref${player.telegram_id}`;
  const refBox  = document.getElementById('ref-link-box');
  if (refBox) refBox.textContent = refLink;
  animCounter(document.getElementById('ref-count'), 0, player.referrals);
  animCounter(document.getElementById('ref-coins'), 0, player.referral_coins);

  // Update game points display (only on game tab)
  updateGamePointsUI(player.game_points ?? 0);

  updateWithdrawUI(player);
}

// ─── GAME POINTS UI ──────────────────────────────────────────────────────────
function updateGamePointsUI(pts) {
  const el    = document.getElementById('game-points-val');
  const barEl = document.getElementById('game-points-bar');
  const msgEl = document.getElementById('no-points-msg');
  const btnEl = document.getElementById('start-game-btn');

  if (el) el.textContent = pts;
  if (barEl) {
    const pct = Math.min((pts / 10) * 100, 100);
    barEl.style.width = pct + '%';
    barEl.style.background = pts > 3
      ? 'linear-gradient(90deg,#00ff88,#00e5ff)'
      : pts > 0
        ? 'linear-gradient(90deg,#f7c843,#ff9500)'
        : '#ff4466';
  }
  if (msgEl) {
    msgEl.classList.toggle('hidden', pts > 0);
  }
  if (btnEl) {
    if (pts <= 0) {
      btnEl.disabled = true;
      btnEl.style.opacity = '0.4';
    } else {
      // Re-enable if not in cooldown
      const lastGame = State.player?.last_game ? new Date(State.player.last_game).getTime() : 0;
      const diff     = Date.now() - lastGame;
      const cd       = (State.config.gameCooldown ?? 10) * 1000;
      if (diff >= cd) {
        btnEl.disabled    = false;
        btnEl.style.opacity = '1';
      }
    }
  }
}

function updateWithdrawUI(player) {
  const sat = player.btc_balance;
  document.getElementById('wd-balance').textContent     = `${sat} SAT`;
  document.getElementById('wd-coins-label').textContent = `${player.coins.toLocaleString()} coins`;
  if (player.faucet_email) {
    const emailEl = document.getElementById('wd-email');
    emailEl.value       = player.faucet_email;
    emailEl.placeholder = player.faucet_email;
  }

  // Show/hide daily-tasks gate notice
  const gateEl  = document.getElementById('wd-tasks-gate');
  const wdCount = player.withdrawal_count ?? 0;
  if (gateEl) {
    if (wdCount >= 5) {
      const allClaimed = TaskManager.allDailyTasksClaimed();
      gateEl.style.display = allClaimed ? 'none' : 'block';
    } else {
      // Show how many free withdrawals remain
      const freeLeft = 5 - wdCount;
      const freeEl   = document.getElementById('wd-free-count');
      if (freeEl) freeEl.textContent = freeLeft;
      gateEl.style.display = 'none';
    }
  }
  const freeNoticeEl = document.getElementById('wd-free-notice');
  if (freeNoticeEl) {
    if (wdCount < 5) {
      const freeLeft = 5 - wdCount;
      freeNoticeEl.style.display = 'block';
      const spanEl = document.getElementById('wd-free-count');
      if (spanEl) spanEl.textContent = freeLeft;
    } else {
      freeNoticeEl.style.display = 'none';
    }
  }

  recalcWithdrawSummary();
}

// ─── XP BAR ──────────────────────────────────────────────────────────────────
const LEVELS = [0,500,1500,3500,7000,12000,20000,35000,55000,80000];

function updateXPBar(player) {
  const xp  = player.xp ?? 0;
  const lv  = player.level ?? 1;
  const cur = LEVELS[lv - 1] ?? 0;
  const nxt = LEVELS[lv]     ?? LEVELS[LEVELS.length - 1];
  const pct = nxt > cur ? Math.min(((xp - cur) / (nxt - cur)) * 100, 100) : 100;

  document.getElementById('xp-label').textContent      = `${xp.toLocaleString()} XP`;
  document.getElementById('xp-current-lv').textContent = `Lv.${lv}`;
  document.getElementById('xp-next-lv').textContent    = `Lv.${lv < 10 ? lv + 1 : 'MAX'}`;
  document.getElementById('xp-bar-fill').style.width   = pct + '%';
}

// ═══════════════════════════════════════════════════════════════════════════
// LIVE WITHDRAW FEED
// ═══════════════════════════════════════════════════════════════════════════

function initLiveFeed() {
  loadInitialFeed();
  connectSSE();
  _startPollSafetyNet();
  startFeedTicker();
}

async function loadInitialFeed() {
  try {
    const res  = await apiGet('global_withdrawals', { limit: 20 });
    const now = Date.now();
    const list = res.withdrawals ?? [];
    list.forEach(w => { w._addedAt = now; });
    State.feedItems = list;
    renderFeedList(list);
    updateFeedCount();
  } catch (e) {
    console.warn('Feed init error:', e.message);
  }
}

function connectSSE() {
  if (State.sseSource) { State.sseSource.close(); State.sseSource = null; }
  if (!window.EventSource) { _startPollFallback(); return; }

  // Always use the latest known item's timestamp so we never miss events across reconnects
  const latestTs = State.feedItems[0]?.time ?? State.feedItems[0]?.created_at
    ?? new Date(Date.now() - 3600000).toISOString().replace('T',' ').slice(0,19);
  const url = `${EVT_URL}?since=${encodeURIComponent(latestTs)}&initData=${encodeURIComponent(State.initData)}`;

  const es = new EventSource(url);
  State.sseSource = es;

  es.addEventListener('withdrawal', e => {
    try { prependFeedItem(JSON.parse(e.data)); } catch {}
  });
  es.addEventListener('reconnect', () => { es.close(); setTimeout(connectSSE, 300); });
  es.onerror = () => { es.close(); State.sseSource = null; setTimeout(connectSSE, 500); };
}

// ── Polling fallback for envs where SSE is unreliable ────────────────────────
let _pollFallbackTimer = null;
function _startPollFallback() {
  if (_pollFallbackTimer) return;
  _pollFallbackTimer = setInterval(async () => {
    try {
      const res  = await apiGet('global_withdrawals', { limit: 5 });
      const list = res.withdrawals ?? [];
      list.forEach(w => prependFeedItem(w));
    } catch {}
  }, 5000);
}
// Also run polling as safety net even when SSE is active (catches any gaps)
function _startPollSafetyNet() {
  // Runs every 3 s as a gap-filler — ensures nothing is missed between SSE reconnects
  setInterval(async () => {
    try {
      const res  = await apiGet('global_withdrawals', { limit: 5 });
      const list = res.withdrawals ?? [];
      list.forEach(w => prependFeedItem(w));
    } catch {}
  }, 3000);
}

function prependFeedItem(w) {
  if (State.feedItems.find(f => f.id === w.id || f.withdrawal_id === w.id)) return;
  w._addedAt = Date.now(); // stamp local arrival time — used for live countdown
  State.feedItems.unshift(w);
  if (State.feedItems.length > 50) State.feedItems = State.feedItems.slice(0, 50);
  renderFeedList(State.feedItems);
  updateFeedCount();

  const list  = document.getElementById('live-feed-list');
  const first = list.firstElementChild;
  if (first) { first.classList.add('feed-item-new'); setTimeout(() => first.classList.remove('feed-item-new'), 2000); }

  // Use first_name from SSE event (populated by events.php JOIN) — fall back to masked username
  const displayName = w.first_name || w.username || '???';
  const activePage = document.querySelector('.page.active')?.id;
  if (activePage !== 'page-account') showToast(`💸 +${displayName} withdrew ${w.amount_sat} SAT!`, 'info');
}

function renderFeedList(items) {
  const list = document.getElementById('live-feed-list');
  if (!items.length) { list.innerHTML = '<div class="live-feed-empty">Waiting for withdrawals...</div>'; return; }

  list.innerHTML = items.map(w => {
    const name    = w.username || w.masked_username || '???';
    const sat     = w.amount_sat ?? 0;
    const timeAgo = timeAgoStr(w._addedAt ?? Date.now());
    const initial = name[0]?.toUpperCase() || '?';
    const photo   = w.photo_url;

    const avatarHtml = photo
      ? `<img class="feed-avatar-img" src="${escHtml(photo)}" alt="${escHtml(name)}"
             onerror="this.style.display='none';this.nextSibling.style.display='flex'" />
         <div class="feed-avatar-char" style="display:none">${initial}</div>`
      : `<div class="feed-avatar-char">${initial}</div>`;

    return `
      <div class="feed-item">
        <div class="feed-avatar">${avatarHtml}</div>
        <div class="feed-info">
          <div class="feed-name">${escHtml(name)}</div>
          <div class="feed-time">${timeAgo}</div>
        </div>
        <div class="feed-amount">
          <span class="feed-sat">+${sat} SAT</span>
          <span class="feed-btc">₿</span>
        </div>
      </div>`;
  }).join('');
}

function updateFeedCount() {
  const el = document.getElementById('live-feed-count');
  if (!el) return;
  const today = new Date().toLocaleDateString();
  const count = State.feedItems.filter(w => {
    const d = new Date(w.created_at ?? w.time);
    return d.toLocaleDateString() === today;
  }).length;
  el.textContent = `${count} today`;
}

function timeAgoStr(addedAt) {
  const diff = Math.max(0, Math.floor((Date.now() - addedAt) / 1000));
  if (diff < 60)   return `${diff}s ago`;
  if (diff < 3600) {
    const m = Math.floor(diff / 60);
    const s = diff % 60;
    return `${m}m ${s}s ago`;
  }
  const h = Math.floor(diff / 3600);
  const m = Math.floor((diff % 3600) / 60);
  return `${h}h ${m}m ago`;
}

// Live ticker: re-renders feed times every second so counters count up in real time
let _feedTickerInterval = null;
function startFeedTicker() {
  if (_feedTickerInterval) return;
  _feedTickerInterval = setInterval(() => {
    const list = document.getElementById('live-feed-list');
    if (!list || !State.feedItems.length) return;
    // Update only the time elements without rebuilding the full list (perf)
    const timeEls = list.querySelectorAll('.feed-time');
    timeEls.forEach((el, i) => {
      const w = State.feedItems[i];
      if (w) el.textContent = timeAgoStr(w._addedAt ?? Date.now());
    });
  }, 1000);
}

// ═══════════════════════════════════════════════════════════════════════════
// WITHDRAW
// ═══════════════════════════════════════════════════════════════════════════

function recalcWithdrawSummary() {
  const player  = State.player;
  if (!player) return;

  const maxSat  = player.btc_balance;
  const inputEl = document.getElementById('wd-amount');
  let sat = parseInt(inputEl.value) || 0;
  if (sat <= 0 || sat > maxSat) sat = maxSat;
  if (sat > 10000) sat = 10000;

  const coins   = sat * 1000;
  const fee     = 0;
  const receive = sat - fee;

  document.getElementById('ws-amount').textContent  = sat > 0 ? `${sat} SAT`  : '—';
  document.getElementById('ws-fee').textContent     = `${fee} SAT`;
  document.getElementById('ws-receive').textContent = receive > 0 ? `${receive} SAT` : '—';
  document.getElementById('ws-coins').textContent   = sat > 0 ? coins.toLocaleString() + ' 🪙' : '—';
  document.getElementById('ws-time').textContent    = '~1–5 min';
}

function setWithdrawStep(step, status) {
  const el   = document.getElementById(`ws-step-${step}`);
  const icon = el?.querySelector('.step-icon');
  if (!icon) return;
  icon.className = 'step-icon';
  if (status === 'active') { icon.textContent = '⏳'; icon.classList.add('step-active'); }
  if (status === 'done')   { icon.textContent = '✅'; icon.classList.add('step-done'); }
  if (status === 'error')  { icon.textContent = '❌'; icon.classList.add('step-error'); }
}

function resetWithdrawSteps() {
  for (let i = 1; i <= 4; i++) {
    const icon = document.querySelector(`#ws-step-${i} .step-icon`);
    if (icon) { icon.className = 'step-icon step-wait'; icon.textContent = '⏳'; }
  }
}

async function submitWithdraw() {
  const email = document.getElementById('wd-email').value.trim();
  if (!email) return showToast('Enter your FaucetPay email.', 'error');
  if (!email.includes('@')) return showToast('Invalid email.', 'error');

  const player = State.player;
  if (!player) return;

  const maxSat = player.btc_balance;
  let amountInput = parseInt(document.getElementById('wd-amount').value) || 0;
  let sat = amountInput > 0 ? amountInput : maxSat;
  if (sat > 10000) sat = 10000;

  if (sat < 1) return showToast('Minimum 1 SAT required.', 'error');
  if (sat > maxSat) return showToast(`You only have ${maxSat} SAT.`, 'error');

  // ── Withdrawal gate: after first 5 free withdrawals, all daily tasks must be claimed ──
  const wdCount = player.withdrawal_count ?? 0;
  if (wdCount >= 5) {
    const allClaimed = TaskManager.allDailyTasksClaimed();
    if (!allClaimed) {
      showToast('⚠️ Complete & claim ALL Daily Tasks first to unlock withdrawal today.', 'error');
      hapticNotif('error');
      // Navigate to tasks page to help player
      document.querySelector('[data-page="tasks"]')?.click();
      return;
    }
  }

  const btn = document.getElementById('wd-submit-btn');
  btn.disabled    = true;
  btn.textContent = 'Processing...';

  const stepsEl = document.getElementById('wd-steps');
  stepsEl.classList.remove('hidden');
  resetWithdrawSteps();

  haptic('medium');

  // Pass daily_tasks_claimed flag to backend
  const dailyTasksClaimed = TaskManager.allDailyTasksClaimed();

  try {
    setWithdrawStep(1, 'active');
    await sleep(300);
    setWithdrawStep(1, 'done');

    setWithdrawStep(2, 'active');
    await sleep(200);
    setWithdrawStep(2, 'done');

    setWithdrawStep(3, 'active');
    const res = await apiCall('withdraw', { email, amount_sat: sat, daily_tasks_complete: dailyTasksClaimed });
    setWithdrawStep(3, 'done');

    setWithdrawStep(4, 'active');
    await sleep(300);
    setWithdrawStep(4, 'done');

    updateUI(res.player);
    showWithdrawSuccess(res.withdrawal);
    loadWithdrawalHistory();
    hapticNotif('success');
    playSound('withdraw');

  } catch (e) {
    for (let i = 1; i <= 4; i++) {
      const icon = document.querySelector(`#ws-step-${i} .step-icon`);
      if (icon?.classList.contains('step-active')) setWithdrawStep(i, 'error');
    }
    showToast(e.message, 'error');
    hapticNotif('error');
  } finally {
    btn.disabled    = false;
    btn.textContent = '💸 WITHDRAW NOW';
    setTimeout(() => stepsEl.classList.add('hidden'), 4000);
  }
}

function showWithdrawSuccess(w) {
  document.getElementById('sm-amount').textContent = `${w.amount_sat} SAT`;
  document.getElementById('sm-email').textContent  = w.email ?? '—';
  document.getElementById('sm-txid').textContent   = w.tx_id ?? '—';

  const modal = document.getElementById('success-modal');
  modal.classList.remove('hidden');

  const wrap = document.getElementById('confetti-canvas-wrap');
  wrap.innerHTML = '';
  launchConfetti(wrap);
}

async function loadWithdrawalHistory() {
  try {
    const res  = await apiCall('withdrawals');
    const list = document.getElementById('withdrawal-list');
    const wds  = res.withdrawals ?? [];

    // Sync withdrawal count into player state so gate logic is accurate
    if (State.player) {
      State.player.withdrawal_count = res.success_count ?? 0;
      updateWithdrawUI(State.player);
    }

    if (!wds.length) { list.innerHTML = '<div class="wd-empty">No withdrawals yet</div>'; return; }
    list.innerHTML = wds.map(w => {
      const statusColor = w.status === 'success' ? '#00ff88' : w.status === 'failed' ? '#ff4466' : '#f7c843';
      const statusIcon  = w.status === 'success' ? '✓' : w.status === 'failed' ? '✗' : '⏳';
      return `
        <div class="wd-item wd-item-${w.status}">
          <div class="wd-item-left">
            <div class="wd-item-id">${escHtml(w.id)}</div>
            <div class="wd-item-date">${escHtml(w.date)}</div>
            ${w.tx_id && w.tx_id !== 'PENDING' ? `<div class="wd-item-txid">TX: ${escHtml(w.tx_id)}</div>` : ''}
          </div>
          <div class="wd-item-right">
            <div class="wd-item-amount">${w.amount_sat} SAT</div>
            <div class="wd-item-status" style="color:${statusColor}">${statusIcon} ${w.status}</div>
          </div>
        </div>`;
    }).join('');
  } catch {}
}

// ═══════════════════════════════════════════════════════════════════════════
// GAME ENGINE
// ═══════════════════════════════════════════════════════════════════════════

const ITEM_TYPES = {
  coin:    { emoji:'🟡', value:  3,  color:'#f7c843', chance: 55 }, // reduced from 5
  diamond: { emoji:'💎', value: 10,  color:'#00e5ff', chance: 20 }, // reduced from 25
  boost:   { emoji:'⚡', value:  0,  color:'#ff9500', chance: 15 },
  bomb:    { emoji:'💣', value:-30,  color:'#ff4466', chance: 10 }, // reduced from -50
};

class GameEngine {
  constructor(canvas) {
    this.canvas   = canvas;
    this.ctx      = canvas.getContext('2d');
    this.items    = [];
    this.running  = false;
    this.score    = 0;
    this.combo    = 1;
    this.comboTimer = 0;
    this.boosted  = false;
    this.boostEnd = 0;
    this.duration = 30;
    this.timeLeft = 30;
    this.startTs  = 0;
    this.lastSpawn= 0;
    this.spawnInterval = 900;
    this.animId   = null;
    this.onEnd    = null;
    this.touchHandler = null;
  }

  resize() {
    const rect = this.canvas.parentElement.getBoundingClientRect();
    this.canvas.width  = rect.width;
    this.canvas.height = rect.height - 52;
  }

  start(duration, onEnd) {
    this.resize();
    this.items   = [];
    this.score   = 0;
    this.combo   = 1;
    this.boosted = false;
    this.timeLeft= duration;
    this.duration= duration;
    this.startTs = Date.now();
    this.lastSpawn = 0;
    this.running = true;
    this.onEnd   = onEnd;
    this.spawnInterval = 900;

    this.touchHandler = (e) => {
      e.preventDefault();
      const touches = e.changedTouches || [e];
      for (const t of touches) {
        const rect = this.canvas.getBoundingClientRect();
        this.tryHit(t.clientX - rect.left, t.clientY - rect.top);
      }
    };
    this.canvas.addEventListener('touchstart', this.touchHandler, { passive: false });
    this.canvas.addEventListener('mousedown', this.touchHandler);
    this.loop(Date.now());
  }

  stop() {
    this.running = false;
    if (this.animId) cancelAnimationFrame(this.animId);
    this.canvas.removeEventListener('touchstart', this.touchHandler);
    this.canvas.removeEventListener('mousedown', this.touchHandler);
  }

  loop(now) {
    if (!this.running) return;
    this.animId = requestAnimationFrame(t => this.loop(t));
    const elapsed = (Date.now() - this.startTs) / 1000;
    this.timeLeft  = Math.max(0, this.duration - elapsed);

    document.getElementById('hud-time').textContent  = Math.ceil(this.timeLeft);
    document.getElementById('hud-score').textContent = this.score;
    document.getElementById('hud-combo').textContent = `×${this.combo}`;

    if (this.boosted && Date.now() > this.boostEnd) {
      this.boosted = false;
      document.getElementById('boost-banner').classList.add('hidden');
    }
    if (Date.now() > this.comboTimer) this.combo = 1;

    if (Date.now() - this.lastSpawn > this.spawnInterval) {
      this.spawnItem();
      this.lastSpawn     = Date.now();
      this.spawnInterval = Math.max(400, 900 - (elapsed * 12));
    }

    this.items = this.items.filter(item => {
      item.y   += item.speed;
      item.rot += item.rotSpeed;
      return item.y < this.canvas.height + item.size;
    });

    this.draw();

    if (this.timeLeft <= 0) {
      this.stop();
      this.onEnd && this.onEnd(this.score);
    }
  }

  spawnItem() {
    const type = this.pickType();
    const info = ITEM_TYPES[type];
    const size = type === 'diamond' ? 36 : 32;
    const x    = Math.random() * (this.canvas.width - size * 2) + size;
    this.items.push({
      type, info, size,
      x, y: -size,
      speed:    2.5 + Math.random() * 2.5,
      rotSpeed: (Math.random() - 0.5) * 0.08,
      rot:      0,
      id:       Math.random(),
      alpha:    1,
    });
  }

  pickType() {
    const r = Math.random() * 100;
    let acc = 0;
    for (const [type, info] of Object.entries(ITEM_TYPES)) {
      acc += info.chance;
      if (r < acc) return type;
    }
    return 'coin';
  }

  tryHit(x, y) {
    let hit = false;
    for (let i = this.items.length - 1; i >= 0; i--) {
      const item = this.items[i];
      if (Math.hypot(x - item.x, y - item.y) < item.size * 1.2) {
        this.items.splice(i, 1);
        this.handleItem(item, x, y);
        hit = true;
        break;
      }
    }
    if (!hit) { playSound('tap'); haptic('soft'); }
  }

  handleItem(item, x, y) {
    let val = item.info.value;
    if (item.type === 'bomb') {
      this.score = Math.max(0, this.score + val); // -50
      this.startTs -= 5000;
      this.timeLeft = Math.max(0, this.timeLeft - 5);
      haptic('heavy'); hapticNotif('error');
      document.getElementById('game-arena').classList.add('shake-me');
      setTimeout(() => document.getElementById('game-arena').classList.remove('shake-me'), 400);
      showScorePop(x, y - 20, '-50 💰', '#ff4466');
      showScorePop(x, y + 20, '-5s ⏱️', '#ff6600');
      playSound('bomb');
      this.combo = 1;
      const hudTime = document.getElementById('hud-time');
      hudTime.style.color = '#ff4466';
      hudTime.style.textShadow = '0 0 15px #ff4466';
      setTimeout(() => { hudTime.style.color = ''; hudTime.style.textShadow = ''; }, 800);
      return;
    }
    if (item.type === 'boost') {
      this.boosted  = true;
      this.boostEnd = Date.now() + 5000;
      document.getElementById('boost-banner').classList.remove('hidden');
      haptic('rigid'); hapticNotif('success');
      showScorePop(x, y, '⚡ BOOST!', '#ff9500');
      playSound('boost');
      return;
    }
    if (item.type === 'diamond') { haptic('medium'); playSound('diamond'); }
    else { haptic(this.combo >= 5 ? 'rigid' : this.combo >= 3 ? 'medium' : 'light'); playSound('coin'); }

    let mult  = this.boosted ? 2 : 1;
    mult *= this.combo >= 5 ? 2 : this.combo >= 3 ? 1.5 : 1;
    const earned = Math.floor(val * mult);
    this.score  += earned;
    this.combo   = Math.min(this.combo + 1, 8);
    this.comboTimer = Date.now() + 2000;

    const color = this.combo >= 5 ? '#b060ff' : item.info.color;
    showScorePop(x, y, `+${earned}`, color);
    burstParticles(x, y, item.info.color);
  }

  draw() {
    const ctx = this.ctx;
    ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.drawStars(ctx);
    for (const item of this.items) {
      ctx.save();
      ctx.translate(item.x, item.y);
      ctx.rotate(item.rot);
      ctx.globalAlpha  = item.alpha;
      ctx.shadowColor  = item.info.color;
      ctx.shadowBlur   = 18;
      ctx.font         = `${item.size * 1.2}px serif`;
      ctx.textAlign    = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(item.info.emoji, 0, 0);
      ctx.restore();
    }
  }

  drawStars(ctx) {
    if (!this._stars) {
      this._stars = Array.from({ length: 40 }, () => ({
        x: Math.random() * 400, y: Math.random() * 800,
        r: Math.random() * 1.5, a: Math.random(),
      }));
    }
    ctx.save();
    for (const s of this._stars) {
      ctx.beginPath();
      ctx.arc(s.x % this.canvas.width, s.y % this.canvas.height, s.r, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(255,255,255,${s.a * 0.4})`;
      ctx.fill();
    }
    ctx.restore();
  }
}

// ─── SCORE POPUPS ────────────────────────────────────────────────────────────
function showScorePop(x, y, text, color) {
  const el = document.createElement('div');
  el.className = 'score-pop';
  el.style.cssText = `left:${x}px;top:${y}px;color:${color};text-shadow:0 0 10px ${color};transform:translateX(-50%)`;
  el.textContent = text;
  document.getElementById('score-popups').appendChild(el);
  setTimeout(() => el.remove(), 1000);
}

function burstParticles(x, y, color) {
  const container = document.getElementById('score-popups');
  for (let i = 0; i < 6; i++) {
    const p   = document.createElement('div');
    const ang = (i / 6) * Math.PI * 2;
    const d   = 40 + Math.random() * 30;
    p.style.cssText = `position:absolute;left:${x}px;top:${y}px;width:6px;height:6px;border-radius:50%;background:${color};box-shadow:0 0 6px ${color};--tx:${Math.cos(ang)*d}px;--ty:${Math.sin(ang)*d}px;animation:particleBurst 0.6s ease-out forwards`;
    container.appendChild(p);
    setTimeout(() => p.remove(), 650);
  }
}

function launchConfetti(container) {
  const colors = ['#f7c843','#00e5ff','#ff9500','#b060ff','#00ff88','#ff4466'];
  for (let i = 0; i < 80; i++) {
    const el = document.createElement('div');
    const c  = colors[Math.floor(Math.random() * colors.length)];
    const w  = 6 + Math.random() * 8;
    const h  = 4 + Math.random() * 6;
    el.style.cssText = `position:absolute;left:${Math.random()*100}%;top:-20px;width:${w}px;height:${h}px;background:${c};border-radius:${Math.random()>.5?'50%':'2px'};animation:confettiDrop ${1.5+Math.random()*2}s ease-in ${Math.random()*0.8}s forwards`;
    container.appendChild(el);
  }
}

function gameOverParticles() {
  const container = document.getElementById('go-particles');
  container.innerHTML = '';
  const colors = ['#f7c843','#00e5ff','#ff9500','#b060ff'];
  for (let i = 0; i < 30; i++) {
    const p = document.createElement('div');
    const c = colors[Math.floor(Math.random() * colors.length)];
    p.style.cssText = `position:absolute;left:${10+Math.random()*80}%;top:${10+Math.random()*80}%;width:${4+Math.random()*8}px;height:${4+Math.random()*8}px;border-radius:50%;background:${c};--tx:${(Math.random()-.5)*160}px;--ty:${(Math.random()-.5)*160}px;animation:particleBurst 0.8s ease-out ${Math.random()*.3}s forwards`;
    container.appendChild(p);
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// SOUND ENGINE
// ═══════════════════════════════════════════════════════════════════════════
let _audioCtx   = null;
let _audioReady = false;

function getAudioCtx() {
  if (!_audioCtx) _audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  return _audioCtx;
}

function unlockAudio() {
  if (_audioReady) return;
  try {
    const ctx = getAudioCtx();
    if (ctx.state === 'suspended') ctx.resume().then(() => { _audioReady = true; });
    else _audioReady = true;
    const buf = ctx.createBuffer(1, 1, 22050);
    const src = ctx.createBufferSource();
    src.buffer = buf; src.connect(ctx.destination); src.start(0);
  } catch {}
}

function initSounds() {
  document.addEventListener('touchstart', unlockAudio, { once: true, passive: true });
  document.addEventListener('mousedown',  unlockAudio, { once: true });
}

function playSound(name) {
  try {
    const ctx = getAudioCtx();
    if (ctx.state === 'suspended') ctx.resume();
    _synth(ctx, name);
  } catch {}
}

function _synth(ctx, name) {
  const now = ctx.currentTime;
  switch (name) {
    case 'coin': {
      const o = ctx.createOscillator(), g = ctx.createGain();
      o.connect(g); g.connect(ctx.destination);
      o.type = 'sine'; o.frequency.setValueAtTime(880, now);
      o.frequency.exponentialRampToValueAtTime(1320, now + 0.06);
      g.gain.setValueAtTime(0.35, now); g.gain.exponentialRampToValueAtTime(0.001, now + 0.18);
      o.start(now); o.stop(now + 0.18); break;
    }
    case 'diamond': {
      [1047, 1568].forEach((freq, i) => {
        const o = ctx.createOscillator(), g = ctx.createGain();
        o.connect(g); g.connect(ctx.destination); o.type = 'triangle';
        o.frequency.setValueAtTime(freq, now + i * 0.07);
        g.gain.setValueAtTime(0, now + i * 0.07);
        g.gain.linearRampToValueAtTime(0.3, now + i * 0.07 + 0.02);
        g.gain.exponentialRampToValueAtTime(0.001, now + i * 0.07 + 0.28);
        o.start(now + i * 0.07); o.stop(now + i * 0.07 + 0.3);
      }); break;
    }
    case 'bomb': {
      const o = ctx.createOscillator(), g = ctx.createGain();
      o.connect(g); g.connect(ctx.destination); o.type = 'sawtooth';
      o.frequency.setValueAtTime(120, now); o.frequency.exponentialRampToValueAtTime(40, now + 0.25);
      g.gain.setValueAtTime(0.6, now); g.gain.exponentialRampToValueAtTime(0.001, now + 0.3);
      o.start(now); o.stop(now + 0.3); break;
    }
    case 'boost': {
      const o = ctx.createOscillator(), g = ctx.createGain();
      o.connect(g); g.connect(ctx.destination); o.type = 'sine';
      o.frequency.setValueAtTime(440, now); o.frequency.exponentialRampToValueAtTime(1320, now + 0.3);
      g.gain.setValueAtTime(0.2, now); g.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
      o.start(now); o.stop(now + 0.4); break;
    }
    case 'gameover': {
      [523, 415, 370, 311].forEach((freq, i) => {
        const o = ctx.createOscillator(), g = ctx.createGain();
        o.connect(g); g.connect(ctx.destination); o.type = 'triangle';
        o.frequency.setValueAtTime(freq, now + i * 0.18);
        g.gain.setValueAtTime(0.3, now + i * 0.18); g.gain.exponentialRampToValueAtTime(0.001, now + i * 0.18 + 0.3);
        o.start(now + i * 0.18); o.stop(now + i * 0.18 + 0.35);
      }); break;
    }
    case 'tap': {
      const o = ctx.createOscillator(), g = ctx.createGain();
      o.connect(g); g.connect(ctx.destination); o.type = 'sine';
      o.frequency.setValueAtTime(600, now); o.frequency.exponentialRampToValueAtTime(300, now + 0.05);
      g.gain.setValueAtTime(0.12, now); g.gain.exponentialRampToValueAtTime(0.001, now + 0.06);
      o.start(now); o.stop(now + 0.06); break;
    }
    case 'daily': {
      [523, 659, 784, 1047].forEach((freq, i) => {
        const o = ctx.createOscillator(), g = ctx.createGain();
        o.connect(g); g.connect(ctx.destination); o.type = 'sine';
        o.frequency.setValueAtTime(freq, now + i * 0.1);
        g.gain.setValueAtTime(0.25, now + i * 0.1); g.gain.exponentialRampToValueAtTime(0.001, now + i * 0.1 + 0.2);
        o.start(now + i * 0.1); o.stop(now + i * 0.1 + 0.22);
      }); break;
    }
    case 'nav': {
      const o = ctx.createOscillator(), g = ctx.createGain();
      o.connect(g); g.connect(ctx.destination); o.type = 'sine';
      o.frequency.setValueAtTime(440, now); o.frequency.exponentialRampToValueAtTime(660, now + 0.04);
      g.gain.setValueAtTime(0.1, now); g.gain.exponentialRampToValueAtTime(0.001, now + 0.07);
      o.start(now); o.stop(now + 0.07); break;
    }
    case 'withdraw': {
      [523, 659, 784].forEach((freq, i) => {
        const o = ctx.createOscillator(), g = ctx.createGain();
        o.connect(g); g.connect(ctx.destination); o.type = 'sine';
        o.frequency.setValueAtTime(freq, now);
        g.gain.setValueAtTime(0.22, now); g.gain.exponentialRampToValueAtTime(0.001, now + 0.6);
        o.start(now); o.stop(now + 0.65);
      }); break;
    }
    default: break;
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// GAME CONTROLLER
// ═══════════════════════════════════════════════════════════════════════════
let engine        = null;
let gameTimestamp = 0;

async function startGame() {
  if (!State.player) return showToast('Please wait for login.', 'error');

  // Check game points
  const gpts = State.player.game_points ?? 0;
  if (gpts <= 0) {
    showToast('❌ No game points! Invite friends to earn more.', 'error');
    hapticNotif('error');
    return;
  }

  // Check cooldown
  const lastGame = State.player.last_game ? new Date(State.player.last_game).getTime() : 0;
  const diff     = Date.now() - lastGame;
  const cooldown = (State.config.gameCooldown ?? 10) * 1000;
  if (diff < cooldown) {
    const rem = Math.ceil((cooldown - diff) / 1000);
    showToast(`Cooldown: ${rem}s remaining`, 'error');
    return;
  }

  // Maybe show ad before game (40% chance)
  const adResult = await AdManager.maybeBeforeGame();
  if (adResult.rewardCoins > 0) {
    showAdRewardBanner(adResult.rewardCoins);
    if (State.player) {
      State.player.coins += adResult.rewardCoins;
      animCounter(document.getElementById('header-coins'), State.player.coins - adResult.rewardCoins, State.player.coins);
    }
    // Persist ad reward to database
    try {
      const res = await apiCall('task_reward', { amount: adResult.rewardCoins, reason: 'Ad reward (before game)' });
      if (res?.player) updateUI(res.player);
    } catch (_) { /* display already updated locally */ }
  }

  document.getElementById('pre-game').classList.add('hidden');
  document.getElementById('game-over').classList.add('hidden');
  document.getElementById('game-arena').classList.remove('hidden');

  const canvas   = document.getElementById('game-canvas');
  engine         = new GameEngine(canvas);
  const duration = State.config.gameDuration ?? 30;
  gameTimestamp  = Math.floor(Date.now() / 1000);

  engine.start(duration, async (finalScore) => {
    document.getElementById('game-arena').classList.add('hidden');
    document.getElementById('game-over').classList.remove('hidden');

    const mult   = getLevelMult(State.level);
    const earned = Math.floor(finalScore * mult);
    document.getElementById('go-score').textContent = finalScore;
    document.getElementById('go-coins').textContent = `+${earned}`;

    gameOverParticles();
    playSound('gameover');
    hapticNotif('success');

    const token = await signScore(State.player.telegram_id, finalScore, gameTimestamp);
    try {
      const res = await apiCall('submit_score', { score: finalScore, token, timestamp: gameTimestamp });
      updateUI(res.player);
      showToast(`+${res.coins_earned} coins earned!`, 'success');
      TaskManager.onGameCompleted(res.coins_earned || earned);
    } catch (e) {
      showToast(e.message, 'error');
      TaskManager.onGameCompleted(earned);
    }

    // Maybe show ad after game (35% chance)
    setTimeout(async () => {
      const afterAd = await AdManager.maybeAfterGame();
      if (afterAd.rewardCoins > 0) {
        showAdRewardBanner(afterAd.rewardCoins);
        if (State.player) {
          State.player.coins += afterAd.rewardCoins;
          animCounter(document.getElementById('header-coins'), State.player.coins - afterAd.rewardCoins, State.player.coins);
        }
        // Persist ad reward to database
        try {
          const res = await apiCall('task_reward', { amount: afterAd.rewardCoins, reason: 'Ad reward (after game)' });
          if (res?.player) updateUI(res.player);
        } catch (_) { /* display already updated locally */ }
      }
    }, 800);
  });
}

async function signScore(tid, score, ts) {
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode('f8c3e9a1b2d4e5f6a7b8c9d0e1f2a3b4'),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
  );
  const msg = `${tid}:${score}:${ts}`;
  const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(msg));
  return Array.from(new Uint8Array(sig)).map(b => b.toString(16).padStart(2,'0')).join('');
}

function getLevelMult(level) {
  return [1,1.1,1.25,1.4,1.6,1.8,2.0,2.3,2.6,3.0][(level ?? 1) - 1] ?? 1;
}

function playAgain() {
  document.getElementById('game-over').classList.add('hidden');
  document.getElementById('pre-game').classList.remove('hidden');
}

// ═══════════════════════════════════════════════════════════════════════════
// DAILY BONUS
// ═══════════════════════════════════════════════════════════════════════════
async function claimDaily() {
  const btn = document.getElementById('daily-btn');
  btn.disabled    = true;
  btn.textContent = '...';
  try {
    const res = await apiCall('daily_bonus');
    updateUI(res.player);
    showToast(`🎁 +${res.bonus} coins claimed!`, 'success');
    hapticNotif('success');
    playSound('daily');
    TaskManager.onDailyClaimed();
  } catch (e) {
    showToast(e.message, 'error');
    btn.disabled = false;
    btn.textContent = 'CLAIM';
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// DAILY BONUS COUNTDOWN
// ═══════════════════════════════════════════════════════════════════════════
let _dailyCountdownTimer = null;

function startDailyCountdown() {
  if (_dailyCountdownTimer) clearInterval(_dailyCountdownTimer);
  _tickDailyCountdown();
  _dailyCountdownTimer = setInterval(_tickDailyCountdown, 1000);
}

function _tickDailyCountdown() {
  const el = document.getElementById('daily-countdown');
  if (!el) return;
  const ts = State.nextDailyTs || 0;
  if (!ts) { el.textContent = ''; return; }
  const diff = ts * 1000 - Date.now();
  if (diff <= 0) {
    // New day — re-enable button
    if (_dailyCountdownTimer) clearInterval(_dailyCountdownTimer);
    el.textContent = '';
    const btn  = document.getElementById('daily-btn');
    const card = document.getElementById('daily-card');
    if (btn)  { btn.disabled = false; btn.textContent = 'CLAIM'; }
    if (card) card.style.borderColor = 'rgba(0,255,136,0.4)';
    State.nextDailyTs = 0;
    return;
  }
  const h = Math.floor(diff / 3600000);
  const m = Math.floor((diff % 3600000) / 60000);
  const s = Math.floor((diff % 60000) / 1000);
  el.textContent = `Next in ${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
}

// ═══════════════════════════════════════════════════════════════════════════
// LEADERBOARD
// ═══════════════════════════════════════════════════════════════════════════
let _lbPollTimer = null;
let _lbPrevScores = {}; // telegram_id -> coins

async function loadLeaderboard() {
  const list = document.getElementById('leaderboard-list');
  list.innerHTML = '<div class="lb-loading">Loading...</div>';
  try {
    const res = await apiCall('leaderboard');
    renderLeaderboard(res.leaderboard ?? []);
    startLeaderboardLive();
  } catch (e) {
    list.innerHTML = `<div class="lb-loading">${e.message}</div>`;
  }
}

function startLeaderboardLive() {
  stopLeaderboardLive();
  _lbPollTimer = setInterval(async () => {
    const activePage = document.querySelector('.page.active')?.id;
    if (activePage !== 'page-leaderboard') { stopLeaderboardLive(); return; }
    try {
      const res = await apiCall('leaderboard');
      renderLeaderboard(res.leaderboard ?? [], true);
    } catch {}
  }, 3000); // 3 s — snappy live updates
}

function stopLeaderboardLive() {
  if (_lbPollTimer) { clearInterval(_lbPollTimer); _lbPollTimer = null; }
}

function renderLeaderboard(entries, isLiveUpdate = false) {
  const list   = document.getElementById('leaderboard-list');
  const medals = ['🥇','🥈','🥉'];

  if (isLiveUpdate && list.children.length > 0) {
    // ── Rank-aware smart update: find each row by telegram_id, not position ──
    let rankChanged = false;
    entries.forEach((p, i) => {
      const row = list.querySelector(`[data-tid="${p.telegram_id}"]`);
      const prevCoins = _lbPrevScores[p.telegram_id];
      const diff = prevCoins != null ? p.coins - prevCoins : 0;

      if (!row) { rankChanged = true; return; } // new entry — need full re-render

      // Check if rank (DOM position) changed
      const domIdx = Array.from(list.children).indexOf(row);
      if (domIdx !== i) rankChanged = true;

      // Update coins with animated diff badge
      const coinsEl = row.querySelector('.lb-coins');
      if (coinsEl) {
        const diffHtml = diff > 0
          ? `<span class="lb-score-diff">+${diff.toLocaleString()}</span>`
          : '';
        coinsEl.innerHTML = `${p.coins.toLocaleString()} 🪙${diffHtml}`;
        if (diff !== 0) {
          coinsEl.classList.remove('lb-score-up', 'lb-score-down');
          void coinsEl.offsetWidth;
          coinsEl.classList.add(diff > 0 ? 'lb-score-up' : 'lb-score-down');
          setTimeout(() => coinsEl.classList.remove('lb-score-up', 'lb-score-down'), 1800);
        }
      }

      // Update rank badge in case it changed (e.g. climbed from #4 to #3)
      const rankEl = row.querySelector('.lb-rank');
      if (rankEl) rankEl.textContent = medals[i] ?? `#${i + 1}`;

      const lvlEl = row.querySelector('.lb-level');
      if (lvlEl) lvlEl.textContent = `Lv.${p.level}`;

      _lbPrevScores[p.telegram_id] = p.coins;
    });

    if (!rankChanged) return; // all rows patched in-place — done
    // Fall through to full re-render if ranks reordered
  }

  // ── Full render (initial load or rank order changed) ─────────────────────
  list.innerHTML = entries.map((p, i) => {
    const medal      = medals[i] ?? `#${p.rank}`;
    const rankClass  = i < 3 ? ['gold','silver','bronze'][i] : '';
    const initial    = (p.username || 'P')[0].toUpperCase();
    const avatarHtml = p.photo_url
      ? `<img class="lb-avatar" src="${escHtml(p.photo_url)}" alt="${escHtml(p.username)}"
             onerror="this.style.display='none'" />`
      : `<div class="lb-avatar-char">${initial}</div>`;

    return `
      <div class="lb-item${i < 3 ? ' top3' : ''}" data-tid="${escHtml(p.telegram_id)}" style="animation-delay:${i*40}ms">
        <div class="lb-rank ${rankClass}">${medal}</div>
        <div class="lb-avatar-wrap">${avatarHtml}</div>
        <div class="lb-info">
          <div class="lb-name">${escHtml(p.username)}</div>
          <div class="lb-level">Lv.${p.level}</div>
        </div>
        <div class="lb-coins">${p.coins.toLocaleString()} 🪙</div>
      </div>`;
  }).join('') || '<div class="lb-loading">No players yet.</div>';

  entries.forEach(p => { _lbPrevScores[p.telegram_id] = p.coins; });

  // Inject LIVE badge once
  const h = document.querySelector('#page-leaderboard .section-title, #page-leaderboard h2');

}


// ═══════════════════════════════════════════════════════════════════════════
// REFERRAL
// ═══════════════════════════════════════════════════════════════════════════
function getRefLink() {
  return `https://t.me/${BOT_NAME}?start=ref${State.player?.telegram_id ?? ''}`;
}

function copyRefLink() {
  const link = getRefLink();
  if (navigator.clipboard) {
    navigator.clipboard.writeText(link).then(() => showToast('Link copied!', 'success'));
  } else {
    const el = document.createElement('textarea');
    el.value = link;
    document.body.appendChild(el);
    el.select();
    document.execCommand('copy');
    document.body.removeChild(el);
    showToast('Link copied!', 'success');
  }
  haptic('medium');
  playSound('nav');
}

function shareToTelegram() {
  const link = getRefLink();

  // Telegram supports basic Markdown formatting
  const text = encodeURIComponent(
    `**🚀 Join BTC Catch Game and earn real BTC!**\n\n💸 Play & Earn FREE Satoshis Daily\n🏆 Level up, and Withdraw BTC Instantly.\n\n${link}`
  );

  window.open(`https://t.me/share/url?text=${text}`);
}

function shareToWhatsApp() {
  const text = encodeURIComponent(`🚀 Join BTC Catch Miner and earn real BTC! ${getRefLink()}`);
  window.open(`https://wa.me/?text=${text}`);
}

// ═══════════════════════════════════════════════════════════════════════════
// NAVIGATION
// ═══════════════════════════════════════════════════════════════════════════
function navigateTo(pageId) {
  document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
  document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));

  const page = document.getElementById(`page-${pageId}`);
  const btn  = document.querySelector(`.nav-btn[data-page="${pageId}"]`);
  if (page) page.classList.add('active');
  if (btn)  btn.classList.add('active');

  hapticSelect();
  playSound('nav');

  if (pageId === 'leaderboard') loadLeaderboard();
  if (pageId === 'withdraw')    loadWithdrawalHistory();
  if (pageId === 'game')        checkCooldown();
  if (pageId === 'tasks') {
    TaskManager.renderAll();
    document.querySelectorAll('.task-tab').forEach(tab => {
      tab.addEventListener('click', () => {
        document.querySelectorAll('.task-tab').forEach(t => t.classList.remove('active'));
        document.querySelectorAll('.task-category').forEach(c => c.classList.add('hidden'));
        tab.classList.add('active');
        const cat = tab.dataset.category;
        const catEl = document.getElementById(`tasks-${cat}`);
        if (catEl) catEl.classList.remove('hidden');
        hapticSelect();
      });
    });
  }
}

function checkCooldown() {
  if (!State.player?.last_game) return;
  const diff = Date.now() - new Date(State.player.last_game).getTime();
  const cd   = (State.config.gameCooldown ?? 10) * 1000;
  const rem  = Math.max(0, Math.ceil((cd - diff) / 1000));

  const msg = document.getElementById('cooldown-msg');
  const btn = document.getElementById('start-game-btn');
  if (rem > 0) {
    msg.classList.remove('hidden');
    btn.disabled = true;
    btn.style.opacity = '0.5';
    let r = rem;
    const iv = setInterval(() => {
      r--;
      document.getElementById('cooldown-timer').textContent = r;
      if (r <= 0) {
        clearInterval(iv);
        msg.classList.add('hidden');
        const gpts = State.player?.game_points ?? 0;
        if (gpts > 0) {
          btn.disabled    = false;
          btn.style.opacity = '1';
        }
      }
    }, 1000);
    document.getElementById('cooldown-timer').textContent = r;
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// HELPERS
// ═══════════════════════════════════════════════════════════════════════════
function escHtml(str) {
  const d = document.createElement('div');
  d.textContent = String(str);
  return d.innerHTML;
}

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

// ═══════════════════════════════════════════════════════════════════════════
// BOOT
// ═══════════════════════════════════════════════════════════════════════════
async function boot() {
  initTelegram();
  initSounds();
  setLoadingProgress(20, 'Connecting...');

  if (!State.referrerId) {
    const params = new URLSearchParams(window.location.search);
    const ref    = params.get('ref') ?? params.get('start') ?? '';
    if (ref.startsWith('ref')) State.referrerId = ref.slice(3);
    else if (ref)              State.referrerId = ref;
  }

  try {
    setLoadingProgress(35, 'Loading config...');
    State.config = await apiCall('config');

    setLoadingProgress(55, 'Logging in...');
    const loginBody = {
      telegram_id: State.tgUser?.id?.toString() ?? 'guest_' + Math.random().toString(36).slice(2,8),
      username:    State.tgUser?.username ?? State.tgUser?.first_name ?? 'Player',
      first_name:  State.tgUser?.first_name ?? '',
      referrer:    State.referrerId,
    };
    const loginRes = await apiCall('login', loginBody);
    updateUI(loginRes.player);

    if (loginRes.is_new) showToast('🎉 Welcome! Check your daily bonus!', 'success');

    // ── Preload ad videos silently in the background ──
    AdManager.init(); // no await, no callback — runs hidden

    setLoadingProgress(88, 'Loading live feed...');
    initLiveFeed();

    setLoadingProgress(100, 'Ready!');
    await sleep(400);
  } catch (e) {
    setLoadingProgress(100, 'Error: ' + e.message);
    await sleep(1000);
  }

  document.getElementById('loading-screen').classList.add('hidden');
  document.getElementById('app').classList.remove('hidden');

  wireEvents();

  TaskManager.init();
  TaskManager.refreshBaseline();
  BTCPrice.init();
}

function wireEvents() {
  document.querySelectorAll('.nav-btn').forEach(btn => {
    btn.addEventListener('click', () => navigateTo(btn.dataset.page));
  });

  document.getElementById('start-game-btn').addEventListener('click', () => {
    haptic('medium'); playSound('boost'); startGame();
  });
  document.getElementById('play-again-btn').addEventListener('click', () => {
    haptic('light'); playSound('nav'); playAgain();
  });

  document.getElementById('daily-btn').addEventListener('click', claimDaily);

  document.getElementById('wd-submit-btn').addEventListener('click', () => {
    haptic('medium'); submitWithdraw();
  });
  document.getElementById('wd-max-btn').addEventListener('click', () => {
    const maxSat = State.player?.btc_balance ?? 0;
    document.getElementById('wd-amount').value = maxSat;
    recalcWithdrawSummary();
    haptic('light');
  });
  document.getElementById('wd-amount').addEventListener('input', recalcWithdrawSummary);
  document.getElementById('wd-email').addEventListener('input', recalcWithdrawSummary);
  document.getElementById('wd-refresh-btn').addEventListener('click', () => {
    hapticSelect(); loadWithdrawalHistory();
  });

  document.getElementById('ref-copy').addEventListener('click', copyRefLink);
  document.getElementById('ref-share-tg').addEventListener('click', () => { haptic('light'); shareToTelegram(); });
  document.getElementById('ref-share-wa').addEventListener('click', () => { haptic('light'); shareToWhatsApp(); });

  document.getElementById('lb-refresh').addEventListener('click', () => {
    hapticSelect(); playSound('nav'); loadLeaderboard();
  });

  document.getElementById('modal-close').addEventListener('click', () => {
    haptic('light');
    document.getElementById('success-modal').classList.add('hidden');
  });

  // ── Email: auto-save on blur + duplicate check ──────────────────────────
  const emailInput      = document.getElementById('wd-email');
  const emailSavedBadge = document.getElementById('email-saved-badge');
  const emailConflict   = document.getElementById('email-conflict-notice');

  emailInput.addEventListener('blur', async () => {
    const email = emailInput.value.trim();
    if (!email || !email.includes('@')) return;

    // Hide stale state
    emailConflict.style.display = 'none';
    emailSavedBadge.classList.remove('visible');

    try {
      await apiCall('save_faucet_email', { email });
      // Update local player state so placeholder stays on revisit
      if (State.player) State.player.faucet_email = email;
      emailSavedBadge.classList.add('visible');
      setTimeout(() => emailSavedBadge.classList.remove('visible'), 3000);
    } catch (err) {
      // If the error is a conflict, show the inline warning
      if (err.message && (err.message.toLowerCase().includes('another account') || err.message.toLowerCase().includes('already'))) {
        emailConflict.style.display = 'block';
      }
    }
  });
  // ────────────────────────────────────────────────────────────────────────

  window.addEventListener('resize', () => { if (engine?.running) engine.resize(); });

  document.querySelectorAll('.nav-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      if (btn.dataset.page === 'account') setTimeout(updateFeedCount, 500);
    });
  });
}

// ═══════════════════════════════════════════════════════════════════════════
// MEDIA CACHE
// — Always fetches fresh on every page load (cache-busting) so updated
//   video files (ads1.mp4 etc.) are ALWAYS served to the user.
// — Converts each video to a Blob URL for instant in-session playback.
// — No persistent Cache API: avoids stale videos when files change.
// ═══════════════════════════════════════════════════════════════════════════

const MediaCache = (() => {
  // In-session store: src → blob URL (valid until page unload)
  const _blobUrls = {};

  /**
   * Fetch `src` fresh from the server (cache-busted), convert to Blob URL.
   * Subsequent calls within the same session return the cached Blob URL.
   */
  async function getMedia(src) {
    if (_blobUrls[src]) return _blobUrls[src];

    // Purge any stale browser/service-worker caches from old versions
    try {
      if ('caches' in window) {
        const keys = await caches.keys();
        await Promise.all(keys.map(k => caches.delete(k)));
      }
    } catch (_) {}

    // Cache-bust: append a timestamp so the browser always goes to the
    // server — even if a CDN or browser cache holds an old copy.
    const bustUrl = src + (src.includes('?') ? '&' : '?') + '_cb=' + Date.now();

    const res = await fetch(bustUrl, { cache: 'no-store' });
    if (!res.ok) throw new Error('HTTP ' + res.status);

    const blob = await res.blob();
    const url  = URL.createObjectURL(blob);
    _blobUrls[src] = url;
    return url;
  }

  /**
   * Preload an array of sources sequentially.
   * `onProgress(loaded, total)` is called after each one finishes.
   */
  async function preloadAll(srcs, onProgress) {
    for (let i = 0; i < srcs.length; i++) {
      try {
        await getMedia(srcs[i]);
      } catch (e) {
        console.warn('MediaCache: failed to preload', srcs[i], e.message);
      }
      if (onProgress) onProgress(i + 1, srcs.length);
    }
  }

  function getType(src) {
    const ext = src.split('?')[0].split('.').pop().toLowerCase();
    if (['mp4', 'webm', 'mov', 'ogg'].includes(ext)) return 'video';
    if (ext === 'gif') return 'gif';
    return 'image';
  }

  return { getMedia, preloadAll, getType };
})();

// ═══════════════════════════════════════════════════════════════════════════
// AD MANAGER
// — Tap anywhere on the ad overlay opens the advertiser URL immediately.
// — Ad finishing (video end / image timeout) also auto-opens the URL,
//   then proceeds to the landing card.
// ═══════════════════════════════════════════════════════════════════════════

const AdManager = (() => {
  const AD_LIST = [
    {
      src: 'image/ads1.mp4',
      advertiser: 'GET BITCOIN',
      tagline: 'The #1 Crypto Mining Game',
      cta: 'Play & Earn Real Bitcoin',
      description: 'Mine real satoshis every day. Instant FaucetPay withdrawals. 100,000+ players already earning.',
      features: ['✅ Real BTC Payouts', '⚡ Instant Withdrawals', '🎮 Fun & Addictive'],
      url: 'https://t.me/BitcoinMinerPayBot/startapp',
      btnText: '🚀 Start Earning Now',
    },
    {
      src: 'image/ads2.mp4',
      advertiser: 'Claim Crypto Rewards',
      tagline: 'Daily Free Satoshis',
      cta: 'Mine Bitcoin Daily',
      description: 'Claim your free daily bonus coins. Refer friends. Level up. Withdraw real BTC.',
      features: ['🎁 Daily Free Coins', '👥 Referral Bonuses', '₿ Real BTC Rewards'],
      url: 'https://t.me/BitcoinMinerPayBot/startapp',
      btnText: '💰 Claim Free Coins',
    },
    {
      src: 'image/ads3.mp4',
      advertiser: 'Join Elite Mining Hub',
      tagline: 'Top Crypto Earning App',
      cta: 'Instant Crypto Withdrawals',
      description: 'Join the fastest growing Telegram crypto game. Earn, compete, and withdraw instantly.',
      features: ['🏆 Leaderboard Rewards', '🔐 Secure & Verified', '📱 Play Anywhere'],
      url: 'https://t.me/BitcoinMinerPayBot/startapp',
      btnText: '⛏️ Join & Mine Now',
    },
    {
      src: 'image/ads4.mp4',
      advertiser: 'Start Mining Bitcoin',
      tagline: 'Earn While You Play',
      cta: 'Tap Coins, Earn Real BTC',
      description: 'The most rewarding crypto tap game on Telegram. No investment needed. Start for free.',
      features: ['🆓 Free to Play', '💸 No Investment', '🌍 Global Players'],
      url: 'https://t.me/BitcoinMinerPayBot/startapp',
      btnText: '▶ Play for Free',
    },
    {
      src: 'image/ads5.mp4',
      advertiser: 'BITCOIN MINERX',
      tagline: 'Top Crypto Earning App',
      cta: 'Instant Crypto Withdrawals',
      description: 'Join the fastest growing Telegram crypto game. Earn, compete, and withdraw instantly.',
      features: ['🏆 Leaderboard Rewards', '🔐 Secure & Verified', '📱 Play Anywhere'],
      url: 'https://t.me/BitcoinMinerPayBot/startapp',
      btnText: '⛏️ START NOW',
    },
  ];

  const SKIP_AFTER_SECS = 5;

  let _resolve          = null;
  let _progressInterval = null;
  let _skipInterval     = null;
  let _rewardCoins      = 0;
  let _currentAd        = null;
  let _imageDuration    = 10;

  // Tap handler reference so it can be removed cleanly
  let _overlayTapHandler = null;

  // Prevent duplicate opens from touch/click sequences or repeated taps
  let _urlOpenLocked = false;
  let _urlOpenTimer   = null;

  function _pick() {
    return AD_LIST[Math.floor(Math.random() * AD_LIST.length)];
  }

  function _unlockUrlOpen() {
    _urlOpenLocked = false;
    if (_urlOpenTimer) {
      clearTimeout(_urlOpenTimer);
      _urlOpenTimer = null;
    }
  }

  // Opens the ad URL only once per tap/close sequence
  function _openAdUrlOnce(ad) {
    if (!ad?.url || ad.url === '#') return;
    if (_urlOpenLocked) return;

    _urlOpenLocked = true;
    window.open(ad.url, '_blank');

    // Keep it locked briefly so a touch event + click event cannot double-open
    _urlOpenTimer = setTimeout(() => {
      _unlockUrlOpen();
    }, 900);
  }

  async function _showAd(ad, rewardCoins) {
    return new Promise(async (resolve) => {
      _resolve      = resolve;
      _rewardCoins  = rewardCoins || 0;
      _currentAd    = ad;
      _unlockUrlOpen();

      const overlay       = document.getElementById('ad-overlay');
      const videoEl       = document.getElementById('ad-video');
      const imageEl       = document.getElementById('ad-image');
      const placeholder   = document.getElementById('ad-video-placeholder');
      const skipBtn       = document.getElementById('ad-skip-btn');
      const skipCountdown = document.getElementById('ad-skip-countdown');
      const skipSecs      = document.getElementById('ad-skip-secs');
      const progressBar   = document.getElementById('ad-progress-bar');
      const advertiser    = document.getElementById('ad-advertiser');
      const ctaEl         = document.getElementById('ad-cta');
      const visitBtn      = document.getElementById('ad-visit-btn');
      const rewardHint    = document.getElementById('ad-reward-hint');
      const landingCard   = document.getElementById('ad-landing-card');

      // Hide landing card initially
      if (landingCard) landingCard.classList.add('hidden');

      // Reset UI
      advertiser.textContent       = ad.advertiser;
      ctaEl.textContent            = ad.cta;
      visitBtn.onclick             = (e) => {
        e?.stopPropagation?.();
        _openAdUrlOnce(ad);
      };
      rewardHint.textContent       = rewardCoins > 0 ? `+${rewardCoins} coins for watching!` : 'Watch for reward';
      skipBtn.classList.add('hidden');
      skipCountdown.classList.remove('hidden');
      skipSecs.textContent         = SKIP_AFTER_SECS;
      progressBar.style.width      = '0%';
      progressBar.style.transition  = 'none';

      videoEl.style.opacity = '0';
      imageEl.classList.add('hidden');
      imageEl.style.opacity = '0';
      placeholder.style.display = 'flex';

      const mediaType = MediaCache.getType(ad.src);

      overlay.classList.remove('hidden');
      overlay.style.animation = 'adFadeIn 0.3s ease';

      // Remove any previous handler first
      if (_overlayTapHandler) {
        overlay.removeEventListener('click', _overlayTapHandler);
        overlay.removeEventListener('touchend', _overlayTapHandler);
        overlay.removeEventListener('pointerup', _overlayTapHandler);
      }

      // Use ONE tap path to avoid double-firing on mobile.
      // Pointer events are preferred because one user action = one event.
      _overlayTapHandler = (e) => {
        const target = e.target;

        // Ignore interactive elements
        if (target?.closest?.('button,a,input,select,textarea,[data-no-ad-open]')) {
          return;
        }

        // Ignore non-primary mouse clicks
        if (e.type === 'pointerup' && typeof e.button === 'number' && e.button !== 0) {
          return;
        }

        _openAdUrlOnce(ad);
        haptic('light');
      };

      if (window.PointerEvent) {
        overlay.addEventListener('pointerup', _overlayTapHandler);
      } else {
        // Fallback for older browsers: use touchend OR click, not both at once.
        if ('ontouchend' in window) {
          overlay.addEventListener('touchend', _overlayTapHandler, { passive: true });
        } else {
          overlay.addEventListener('click', _overlayTapHandler);
        }
      }

      let mediaUrl = ad.src;
      try {
        mediaUrl = await MediaCache.getMedia(ad.src);
      } catch {}

      // Skip countdown
      let secsLeft = SKIP_AFTER_SECS;
      clearInterval(_skipInterval);
      _skipInterval = setInterval(() => {
        secsLeft--;
        skipSecs.textContent = secsLeft;
        if (secsLeft <= 0) {
          clearInterval(_skipInterval);
          skipCountdown.classList.add('hidden');
          skipBtn.classList.remove('hidden');
          skipBtn.onclick = (e) => {
            e?.stopPropagation?.();
            _showLandingCard(false);
          };
        }
      }, 1000);

      clearInterval(_progressInterval);

      if (mediaType === 'video') {
        videoEl.muted  = false;
        videoEl.volume = 1.0;
        videoEl.src    = mediaUrl;
        videoEl.load();

        videoEl.oncanplay = () => {
          placeholder.style.display = 'none';
          videoEl.style.opacity = '1';
          videoEl.play().catch(() => {
            videoEl.muted = true;
            videoEl.play().catch(() => {
              placeholder.style.display = 'flex';
              videoEl.style.opacity = '0';
            });
          });
        };

        videoEl.onerror = () => {
          placeholder.style.display = 'flex';
          videoEl.style.opacity = '0';
        };

        // On video end: open URL once, then show landing card
        videoEl.onended = () => {
          _openAdUrlOnce(ad);
          _showLandingCard(true);
        };

        _progressInterval = setInterval(() => {
          const dur = videoEl.duration && isFinite(videoEl.duration) ? videoEl.duration : 15;
          const pct = Math.min((videoEl.currentTime / dur) * 100, 100);
          progressBar.style.transition = 'width 0.3s linear';
          progressBar.style.width = pct + '%';

          if (pct >= 100) {
            clearInterval(_progressInterval);
            _openAdUrlOnce(ad);
            _showLandingCard(true);
          }
        }, 300);

      } else {
        imageEl.onload = () => {
          placeholder.style.display = 'none';
          imageEl.classList.remove('hidden');
          imageEl.style.opacity = '1';
        };

        imageEl.onerror = () => {
          placeholder.style.display = 'flex';
        };

        imageEl.src = mediaUrl;

        let elapsed = 0;
        const tick = 200;

        _progressInterval = setInterval(() => {
          elapsed += tick;
          const pct = Math.min((elapsed / (_imageDuration * 1000)) * 100, 100);
          progressBar.style.transition = 'width 0.2s linear';
          progressBar.style.width = pct + '%';

          if (pct >= 100) {
            clearInterval(_progressInterval);
            _openAdUrlOnce(ad);
            _showLandingCard(true);
          }
        }, tick);
      }
    });
  }

  // Show ad landing card after video finishes or skip is clicked
  function _showLandingCard(watched) {
    clearInterval(_progressInterval);
    clearInterval(_skipInterval);

    const videoEl = document.getElementById('ad-video');
    if (videoEl) videoEl.pause();

    const ad = _currentAd;
    const landingCard = document.getElementById('ad-landing-card');
    if (!landingCard || !ad) {
      _dismiss(watched);
      return;
    }

    // Populate landing card
    document.getElementById('alc-brand').textContent   = ad.advertiser;
    document.getElementById('alc-tagline').textContent = ad.tagline || ad.cta;
    document.getElementById('alc-desc').textContent    = ad.description || ad.cta;

    const featuresList = document.getElementById('alc-features');
    featuresList.innerHTML = (ad.features || []).map(f =>
      `<div class="alc-feature-item">${f}</div>`
    ).join('');

    const ctaBtn = document.getElementById('alc-cta-btn');
    ctaBtn.textContent = ad.btnText || '▶ Open Now';
    ctaBtn.onclick = (e) => {
      e?.stopPropagation?.();
      _openAdUrlOnce(ad);
      haptic('medium');
      _dismiss(watched);
    };

    const closeBtn = document.getElementById('alc-close-btn');
    closeBtn.onclick = (e) => {
      e.stopPropagation();
      clearInterval(_autoCloseInterval);
      _dismiss(watched);
    };

    // Auto-close after 5 seconds with animated countdown bar
    let _autoCloseInterval = null;
    const AUTO_CLOSE_SECS = 5;
    let _autoCloseSecs = AUTO_CLOSE_SECS;
    const countdownEl = document.getElementById('alc-countdown');
    const barEl = document.getElementById('alc-autoclose-bar');

    if (countdownEl) countdownEl.textContent = _autoCloseSecs;

    if (barEl) {
      barEl.style.transition = 'none';
      barEl.style.width = '100%';
      void barEl.offsetWidth;
      barEl.style.transition = `width ${AUTO_CLOSE_SECS}s linear`;
      barEl.style.width = '0%';
    }

    _autoCloseInterval = setInterval(() => {
      _autoCloseSecs--;
      if (countdownEl) countdownEl.textContent = _autoCloseSecs;
      if (_autoCloseSecs <= 0) {
        clearInterval(_autoCloseInterval);
        _dismiss(watched);
      }
    }, 1000);

    // Hide video area, show landing
    const videoWrap = document.getElementById('ad-video-wrap');
    if (videoWrap) videoWrap.style.display = 'none';

    document.querySelector('.ad-progress-wrap') && (document.querySelector('.ad-progress-wrap').style.display = 'none');
    document.querySelector('.ad-bottom-bar') && (document.querySelector('.ad-bottom-bar').style.display = 'none');
    document.querySelector('.ad-top-bar') && (document.querySelector('.ad-top-bar').style.display = 'none');

    landingCard.classList.remove('hidden');
    landingCard.style.animation = 'adLandingIn 0.4s cubic-bezier(0.34,1.56,0.64,1) forwards';

    setTimeout(() => {
      ctaBtn.classList.add('alc-cta-pulse');
    }, 600);
  }

  function _dismiss(watched) {
    clearInterval(_progressInterval);
    clearInterval(_skipInterval);
    _unlockUrlOpen();

    const overlay = document.getElementById('ad-overlay');
    const videoEl = document.getElementById('ad-video');
    const imageEl = document.getElementById('ad-image');
    const landingCard = document.getElementById('ad-landing-card');

    // Remove tap-anywhere listener
    if (_overlayTapHandler) {
      overlay.removeEventListener('click', _overlayTapHandler);
      overlay.removeEventListener('touchend', _overlayTapHandler);
      overlay.removeEventListener('pointerup', _overlayTapHandler);
      _overlayTapHandler = null;
    }

    if (videoEl) {
      videoEl.pause();
      videoEl.muted = false;
      videoEl.src = '';
      videoEl.style.opacity = '0';
      videoEl.oncanplay = null;
      videoEl.onerror = null;
      videoEl.onended = null;
    }

    if (imageEl) {
      imageEl.src = '';
      imageEl.classList.add('hidden');
    }

    // Restore hidden elements
    const videoWrap = document.getElementById('ad-video-wrap');
    if (videoWrap) videoWrap.style.display = '';
    document.querySelectorAll('.ad-progress-wrap, .ad-bottom-bar, .ad-top-bar').forEach(el => {
      el.style.display = '';
    });
    if (landingCard) landingCard.classList.add('hidden');

    overlay.style.animation = 'adFadeOut 0.2s ease forwards';
    setTimeout(() => {
      overlay.classList.add('hidden');
      overlay.style.animation = '';
    }, 200);

    if (_resolve) {
      _resolve({ watched, rewardCoins: watched ? _rewardCoins : 0 });
      _resolve = null;
    }
  }

  async function _preload(onProgress) {
    const srcs = [...new Set(AD_LIST.map(a => a.src))];
    await MediaCache.preloadAll(srcs, onProgress);
  }

  return {
    /** Preload all ad videos into Blob URLs. Pass onProgress(loaded,total) for UI feedback. */
    async init(onProgress) {
      await _preload(onProgress);
    },

    async maybeBeforeGame() {
      if (Math.random() > 0.40) return { watched: false, rewardCoins: 0 };
      showToast('📺 Ad loading...', 'info');
      return await _showAd(_pick(), 10);
    },

    async maybeAfterGame() {
      if (Math.random() > 0.35) return { watched: false, rewardCoins: 0 };
      return await _showAd(_pick(), 15);
    },

    async showRewarded(rewardCoins = 20) {
      return await _showAd(_pick(), rewardCoins);
    },
  };
})();

// ═══════════════════════════════════════════════════════════════════════════
// TASK MANAGER
// ═══════════════════════════════════════════════════════════════════════════

const TaskManager = (() => {
  const STORAGE_KEY = 'btc_tasks_v1';
  const STREAK_KEY  = 'btc_streak_v1';

  // ── TASK DEFINITIONS ──
  const TASK_DEFS = {
    daily: [
      { id: 'd1', title: 'Recruiter',      desc: 'Invite 2 referrals today',        icon: '👥', reward: 300, type: 'daily_referrals', target: 2 },
      { id: 'd2', title: 'Mining Shift',   desc: 'Play 5 games today',              icon: '🎮', reward: 30,  type: 'games_today',     target: 5 },
      { id: 'd3', title: 'Coin Collector', desc: 'Earn 1000 coins today',           icon: '🪙', reward: 150,  type: 'coins_today',     target: 1000 },
      { id: 'd4', title: 'Daily Devotion', desc: 'Claim daily bonus',               icon: '🎁', reward: 20,  type: 'claim_daily',     target: 1 },
      { id: 'd5', title: 'Big Earner',     desc: 'Earn 3000 coins today',           icon: '💰', reward: 500, type: 'coins_today',     target: 3000 },
      { id: 'd6', title: 'Watch & Earn',   desc: 'Watch a reward video',            icon: '📺', reward: 50,  type: 'watch_ad',        target: 1, action: 'watch_ad' },
      { id: 'd7', title: 'Join Channel',   desc: 'Join Telegram channel', icon: '📢', reward: 100, type: 'join_channel',    target: 1, action: 'join_channel_verify', url: 'https://t.me/+SoNfSFAi5PEyNGJi', channel: '-1001378237836' },
    ],
    special: [
      { id: 's1', title: 'Level Up!',      desc: 'Reach Level 2',             icon: '⬆️', reward: 50,   type: 'reach_level', target: 4 },
      { id: 's2', title: 'Seasoned Miner', desc: 'Reach Level 5',             icon: '🥈', reward: 150,  type: 'reach_level', target: 5 },
      { id: 's3', title: 'Legend',         desc: 'Reach Level 10',            icon: '🏆', reward: 2000,  type: 'reach_level', target: 10 },
      { id: 's4', title: 'Game Marathon',  desc: 'Play 20 total games',        icon: '🎯', reward: 200,   type: 'total_games', target: 20 },
      { id: 's5', title: 'Veteran',        desc: 'Play 100 total games',        icon: '⭐', reward: 2000,  type: 'total_games', target: 100 },
      { id: 's6', title: 'High Scorer',    desc: 'Score 600+ in one game',     icon: '🎖️', reward: 200,   type: 'best_score',  target:600 },
      { id: 's7', title: 'Diamond Hunter', desc: 'Score 900+ in one game',     icon: '💎', reward: 300,  type: 'best_score',  target: 900 },
      { id: 's8', title: 'Recruiter',      desc: 'Refer 3 friend',             icon: '👋', reward: 200,   type: 'referrals',   target: 3 },
      { id: 's9', title: 'Ambassador',     desc: 'Refer 10 friends',            icon: '🌟', reward: 2000,  type: 'referrals',   target: 10 },
    ],
    social: [
      { id: 'sc1', title: 'Join Channel 1',  desc: 'Join Telegram channel', icon: '✈️', reward: 50,  type: 'social_action', target: 1, action: 'join_channel_verify', url: 'https://t.me/+SoNfSFAi5PEyNGJi',    channel: '-1001378237836' },
      { id: 'sc6', title: 'Join Channel 2', desc: 'Join our Telegram channel', icon: '📣', reward: 80, type: 'social_action', target: 1, action: 'join_channel_verify', url: 'https://t.me/+OEN6chsXIIRmNmUx',   channel: '-1003985208717' },
      { id: 'sc7', title: 'Join Channel 3',   desc: 'Join our Telegram channel',   icon: '👑', reward: 80, type: 'social_action', target: 1, action: 'join_channel_verify', url: 'https://t.me/+iOKqXCjGRXExODdh',    channel: '-1003945461724' },
      { id: 'sc8', title: 'Join Channel 4',  desc: 'Join our Telegram channel',           icon: '🛡️', reward: 50, type: 'social_action', target: 1, action: 'join_channel_verify', url: 'https://t.me/+SoNfSFAi5PEyNGJi', channel: '-1001378237836' },
      { id: 'sc9', title: 'Join Global Chat',   desc: 'Join Telegram community',     icon: '🌍', reward: 80, type: 'social_action', target: 1, action: 'join_channel_verify', url: 'https://t.me/ShareRefLink1',  channel: '@ShareRefLink1' },
      { id: 'sc2', title: 'Twitter Follow',  desc: 'Follow us on Twitter',      icon: '🐦', reward: 100,  type: 'social_action', target: 1, action: 'twitter_follow', url: 'http://twitter.com/BotsNews1' },
      { id: 'sc3', title: 'Facebook Page',  desc: 'Follow us on Facebook Page',      icon: '🔥', reward: 100,  type: 'social_action', target: 1, action: 'fb_follow', url: 'https://www.facebook.com/Bots-News-Updates-102171852087869' },
      { id: 'sc4', title: 'Facebook Group 1',  desc: 'Follow Facebook Group & Share',      icon: '👤', reward: 100,  type: 'social_action', target: 1, action: 'fb_follow', url: 'https://www.facebook.com/groups/494179918668105' },
      { id: 'sc5', title: 'Facebook Group 2',  desc: 'Follow Facebook Group & Share',      icon: '💬', reward: 100,  type: 'social_action', target: 1, action: 'fb_follow', url: 'https://www.facebook.com/groups/853586528685794' },

    ],
  };

  let _state  = null;
  let _streak = null;

  function _loadState() {
    try {
      const raw = localStorage.getItem(STORAGE_KEY);
      if (raw) _state = JSON.parse(raw);
    } catch {}
    if (!_state) _state = { completed: {}, progress: {}, claimed: {}, coinsEarned: 0 };

    const today = new Date().toDateString();
    if (_state.lastDay !== today) {
      Object.keys(_state.completed).forEach(id => { if (id.startsWith('d')) delete _state.completed[id]; });
      Object.keys(_state.progress).forEach(id => { if (id.startsWith('d')) delete _state.progress[id]; });
      Object.keys(_state.claimed).forEach(id => { if (id.startsWith('d')) delete _state.claimed[id]; });
      _state.lastDay = today;
      _state.referralBaseline = State.player?.referrals ?? _state.referralBaseline ?? 0;
      _save();
    }
    if (_state.referralBaseline === undefined || _state.referralBaseline === null) {
      _state.referralBaseline = State.player?.referrals ?? 0;
      _save();
    }
  }

  function _loadStreak() {
    try {
      const raw = localStorage.getItem(STREAK_KEY);
      if (raw) _streak = JSON.parse(raw);
    } catch {}
    if (!_streak) _streak = { count: 0, lastDay: null };
  }

  function _save() {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(_state)); } catch {}
  }

  function _saveStreak() {
    try { localStorage.setItem(STREAK_KEY, JSON.stringify(_streak)); } catch {}
  }

  function _getProgress(task) {
    const player = State.player;
    if (!player) return 0;
    switch (task.type) {
      case 'reach_level':      return player.level ?? 1;
      case 'total_games':      return player.games_played ?? 0;
      case 'best_score':       return player.best_score ?? 0;
      case 'referrals':        return player.referrals ?? 0;
      case 'daily_referrals': {
        const base    = _state.referralBaseline ?? 0;
        const current = player.referrals ?? 0;
        return Math.max(0, current - base);
      }
      case 'games_today':      return _state.progress[task.id] ?? 0;
      case 'coins_today':      return _state.progress[task.id] ?? 0;
      case 'claim_daily':      return player.can_daily ? 0 : 1;
      case 'watch_ad':         return _state.progress[task.id] ?? 0;
      case 'social_action':    return _state.progress[task.id] ?? 0;
      case 'join_channel':     return _state.progress[task.id] ?? 0;
      default: return 0;
    }
  }

  function _isCompleted(task) { return _getProgress(task) >= task.target; }
  function _isClaimed(task)   { return !!_state.claimed[task.id]; }

  function _renderTask(task) {
    const prog    = _getProgress(task);
    const done    = _isCompleted(task);
    const claimed = _isClaimed(task);
    const pct     = Math.min((prog / task.target) * 100, 100);

    let statusHtml;
    if (claimed) {
      statusHtml = `<button class="task-btn task-btn-claimed" disabled>✓ Claimed</button>`;
    } else if (done) {
      statusHtml = `<button class="task-btn task-btn-claim" data-task="${task.id}" data-cat="${task._cat}">Claim +${task.reward}</button>`;
    } else if (task.action === 'watch_ad') {
      statusHtml = `<button class="task-btn task-btn-action" data-task="${task.id}" data-action="watch_ad">▶ Watch</button>`;
    } else if (task.action === 'join_channel_verify') {
      statusHtml = `<button class="task-btn task-btn-action task-btn-verify" data-task="${task.id}" data-action="join_channel_verify" data-url="${task.url || ''}">Join & Verify</button>`;
    } else if (task.action && task.type === 'social_action') {
      statusHtml = `<button class="task-btn task-btn-action" data-task="${task.id}" data-action="${task.action}" data-url="${task.url || ''}">Join & Verify</button>`;
    } else {
      statusHtml = `<div class="task-progress-text">${prog}/${task.target}</div>`;
    }

    const showBar = !claimed && task.type !== 'social_action' && task.type !== 'watch_ad' || (!done && !claimed);

    return `
      <div class="task-item ${claimed ? 'task-claimed' : done ? 'task-done' : ''}" data-task-id="${task.id}">
        <div class="task-item-left">
          <div class="task-icon">${task.icon}</div>
          <div class="task-info">
            <div class="task-title">${task.title}</div>
            <div class="task-desc">${task.desc}</div>
            ${!claimed && !done && showBar ? `
              <div class="task-bar-wrap">
                <div class="task-bar-fill" style="width:${pct}%"></div>
              </div>` : ''}
          </div>
        </div>
        <div class="task-item-right">
          <div class="task-reward-badge">🪙 ${task.reward}</div>
          ${statusHtml}
        </div>
      </div>`;
  }

  function renderCategory(cat) {
    const tasks  = TASK_DEFS[cat].map(t => ({ ...t, _cat: cat }));
    const listEl = document.getElementById(`task-list-${cat}`);
    if (!listEl) return;
    listEl.innerHTML = tasks.map(_renderTask).join('');

    listEl.querySelectorAll('.task-btn-claim').forEach(btn => {
      btn.addEventListener('click', () => _claimTask(btn.dataset.task, btn.dataset.cat));
    });

    listEl.querySelectorAll('.task-btn-action').forEach(btn => {
      btn.addEventListener('click', () => _doAction(btn.dataset.action, btn.dataset.url, btn.dataset.task, cat));
    });
  }

  async function _doAction(action, url, taskId, cat) {
    haptic('medium');
    switch (action) {
      case 'watch_ad': {
        const result = await AdManager.showRewarded(20);
        if (result.watched) {
          _state.progress[taskId] = (_state.progress[taskId] || 0) + 1;
          _save();
          renderAll();
          showToast('📺 Ad watched! Task updated.', 'success');
          if (result.rewardCoins > 0) { showAdRewardBanner(result.rewardCoins); await _awardCoins(result.rewardCoins, 'Ad reward'); }
        }
        break;
      }
      case 'telegram_join':
        window.open(url || 'https://t.me/');
        _markSocialDone(taskId, cat);
        break;
      case 'twitter_follow':
        window.open(url || 'https://twitter.com/');
        _markSocialDone(taskId, cat);
        break;
      case 'fb_follow':
        window.open(url || 'https://facebook.com/');
        _markSocialDone(taskId, cat);
        break;
      case 'share_game':
        if (tg?.shareMessage) { tg.shareMessage('🚀 Play BTC Catch Miner and earn real BTC!'); }
        else { navigator.share?.({ title: 'BTC Catch Miner', text: '🚀 Earn real BTC!' }); }
        _markSocialDone(taskId, cat);
        break;
      case 'share_ref': {
        const refLink = `https://t.me/${BOT_NAME}?start=ref${State.player?.telegram_id ?? ''}`;
        navigator.clipboard?.writeText(refLink);
        _state.progress[taskId] = (_state.progress[taskId] || 0) + 1;
        _save();
        renderAll();
        showToast('🔗 Link copied! Share it to earn more.', 'success');
        break;
      }

      case 'join_channel_verify':
        await _verifyChannelJoin(url, taskId, cat);
        break;
    }
  }

  function _markSocialDone(taskId, cat) {
    _state.progress[taskId] = 1;
    _save();
    renderAll();
    showToast('✅ Action completed! Claim your reward.', 'success');
  }

  /**
   * _verifyChannelJoin
   * Step 1: Opens the channel URL so the user can join.
   * Step 2: Swaps the button to "Verify ✓" — user taps it after joining.
   * Step 3: Calls api.php?action=verify_channel_join which uses Telegram
   *         getChatMember to confirm real membership before granting reward.
   */
  async function _verifyChannelJoin(url, taskId, cat) {
    // Open the channel so the user can join
    if (url) window.open(url, '_blank');

    showToast('⏳ Opening channel… tap Verify after joining.', 'info');

    // Resolve the channel username for this specific task
    const allTasks = [...TASK_DEFS.daily, ...TASK_DEFS.special, ...TASK_DEFS.social];
    const task = allTasks.find(t => t.id === taskId);
    // Prefer explicit channel field; fall back to extracting from URL (e.g. https://t.me/channame)
    const channel = task?.channel || (url ? url.replace(/.*t\.me\//, '').split(/[/?#]/)[0] : '');

    // Swap button to "Verify ✓" so user can confirm after joining
    const btn = document.querySelector(`[data-action="join_channel_verify"][data-task="${taskId}"]`);
    if (btn) {
      const newBtn = btn.cloneNode(true);       // clone to strip old listeners
      newBtn.textContent    = 'Verify ✓';
      newBtn.dataset.action = 'join_channel_verify_check';
      btn.replaceWith(newBtn);
      newBtn.addEventListener('click', async () => {
        newBtn.disabled     = true;
        newBtn.textContent  = '⏳ Checking…';
        await _doVerifyCheck(taskId, cat, channel);
      });
    }
  }

  /**
   * _doVerifyCheck
   * Calls backend verify_channel_join endpoint (Telegram getChatMember).
   * On confirmed membership: marks task progress = 1 so Claim button appears.
   * On failure: restores the Join & Verify button.
   */
  async function _doVerifyCheck(taskId, cat, channel = '') {
    try {
      // Pass the specific channel username so the backend calls getChatMember on the right channel
      const params = channel ? { channel } : {};
      const res = await apiGet('verify_channel_join', params);
      if (res?.joined === true) {
        _state.progress[taskId] = 1;
        _save();
        renderAll();
        showToast('✅ Membership verified! Claim your reward.', 'success');
        hapticNotif('success');
      } else {
        showToast("❌ You haven't joined yet. Please join the channel first.", 'error');
        hapticNotif('error');
        renderAll();   // restores Join & Verify button
      }
    } catch {
      showToast('⚠️ Verification failed. Please try again.', 'error');
      renderAll();
    }
  }

  async function _claimTask(taskId, cat) {
    if (_state.claimed[taskId]) return;
    const allTasks = [...TASK_DEFS.daily, ...TASK_DEFS.special, ...TASK_DEFS.social];
    const task = allTasks.find(t => t.id === taskId);
    if (!task) return;

    _state.claimed[taskId] = true;
    _state.coinsEarned = (_state.coinsEarned || 0) + task.reward;
    _save();

    haptic('medium');
    hapticNotif('success');
    playSound('daily');

    await _awardCoins(task.reward, `Task: ${task.title}`);
    showToast(`🎉 +${task.reward} coins! Task complete!`, 'success');
    renderAll();
    updateBadgeCount();
  }

  async function _awardCoins(amount, reason) {
    try {
      const res = await apiCall('task_reward', { amount, reason });
      if (res?.player) updateUI(res.player);
    } catch {
      if (State.player) {
        State.player.coins = (State.player.coins || 0) + amount;
        animCounter(document.getElementById('header-coins'), State.player.coins - amount, State.player.coins);
        animCounter(document.getElementById('stat-coins'), 0, State.player.coins);
      }
    }
  }

  function renderAll() {
    renderCategory('daily');
    renderCategory('special');
    renderCategory('social');
    const el = document.getElementById('tasks-total-earned');
    if (el) el.textContent = (_state.coinsEarned || 0).toLocaleString();
    updateBadgeCount();
    updateStreakBanner();
    updateDailyResetTimer();
  }

  function updateBadgeCount() {
    const allTasks = [...TASK_DEFS.daily, ...TASK_DEFS.special, ...TASK_DEFS.social];
    const claimable = allTasks.filter(t => _isCompleted(t) && !_isClaimed(t)).length;
    const badge = document.getElementById('tasks-badge');
    if (badge) {
      badge.textContent = claimable;
      badge.style.display = claimable > 0 ? 'flex' : 'none';
    }
  }

  function updateStreakBanner() {
    const el    = document.getElementById('streak-count');
    const bonus = document.getElementById('streak-bonus');
    if (el) el.textContent = `${_streak.count} day${_streak.count !== 1 ? 's' : ''}`;
    if (bonus) bonus.textContent = Math.min(_streak.count * 50, 500);
  }

  function updateDailyResetTimer() {
    const el = document.getElementById('daily-reset-timer');
    if (!el) return;
    const now      = new Date();
    const tomorrow = new Date(now);
    tomorrow.setHours(24, 0, 0, 0);
    const diff = tomorrow - now;
    const h = Math.floor(diff / 3600000);
    const m = Math.floor((diff % 3600000) / 60000);
    const s = Math.floor((diff % 60000) / 1000);
    el.textContent = `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
  }

  function onGameCompleted(coinsEarned) {
    _state.progress['d2'] = (_state.progress['d2'] || 0) + 1;
    _state.progress['d3'] = (_state.progress['d3'] || 0) + coinsEarned;
    _state.progress['d5'] = (_state.progress['d5'] || 0) + coinsEarned;
    _save();
    renderAll();

    const today = new Date().toDateString();
    if (_streak.lastDay !== today) {
      const yesterday = new Date(Date.now() - 86400000).toDateString();
      _streak.count = _streak.lastDay === yesterday ? _streak.count + 1 : 1;
      _streak.lastDay = today;
      _saveStreak();
    }
  }

  function onDailyClaimed() {
    _state.progress['d4'] = 1;
    _save();
    renderAll();
  }

  function onReferralMade() {
    _state.progress['d1'] = (_state.progress['d1'] || 0) + 1;
    _save();
    renderAll();
  }

  function allDailyTasksClaimed() {
    return TASK_DEFS.daily.every(t => !!_state.claimed[t.id]);
  }

  return {
    init() {
      _loadStreak();
      _loadState();
      renderAll();
      setInterval(updateDailyResetTimer, 1000);
      setInterval(updateBadgeCount, 5000);
    },
    refreshBaseline() {
      if (!State.player) return;
      const today = new Date().toDateString();
      if (_state.lastDay === today && (_state.referralBaseline === undefined || _state.referralBaseline === null)) {
        _state.referralBaseline = State.player.referrals ?? 0;
        _save();
      }
    },
    renderAll,
    onGameCompleted,
    onDailyClaimed,
    onReferralMade,
    allDailyTasksClaimed,
    updateBadgeCount,
  };
})();

// ═══════════════════════════════════════════════════════════════════════════
// BTC LIVE PRICE
// ═══════════════════════════════════════════════════════════════════════════

const BTCPrice = (() => {
  const API = 'https://api.binance.com/api/v3/ticker/24hr?symbol=BTCUSDT';
  let _interval  = null;
  let _lastPrice = 0;

  async function fetch() {
    try {
      const res  = await window.fetch(API);
      const data = await res.json();
      if (!data.lastPrice) return;

      const price  = parseFloat(data.lastPrice);
      const change = parseFloat(data.priceChangePercent);
      const high   = parseFloat(data.highPrice);
      const low    = parseFloat(data.lowPrice);
      const vol    = parseFloat(data.volume);

      _render(price, change, high, low, vol);
      _lastPrice = price;
    } catch (e) {}
  }

  let _ws = null;
  function _connectWS() {
    try {
      _ws = new WebSocket('wss://stream.binance.com:9443/ws/btcusdt@miniTicker');
      _ws.onmessage = (event) => {
        try {
          const d = JSON.parse(event.data);
          const price = parseFloat(d.c);
          if (!price) return;
          const headerValEl = document.getElementById('header-btc-val');
          if (headerValEl) {
            const compact = '$' + Math.round(price).toLocaleString('en-US');
            const wasLower  = _lastPrice > 0 && price < _lastPrice;
            const wasHigher = _lastPrice > 0 && price > _lastPrice;
            headerValEl.textContent = compact;
            headerValEl.classList.remove('price-up', 'price-down');
            if (wasHigher) { headerValEl.classList.add('price-up');   setTimeout(() => headerValEl.classList.remove('price-up'), 2000); }
            if (wasLower)  { headerValEl.classList.add('price-down'); setTimeout(() => headerValEl.classList.remove('price-down'), 2000); }
            _lastPrice = price;
          }
        } catch {}
      };
      _ws.onerror = () => { _ws = null; };
      _ws.onclose = () => { _ws = null; setTimeout(_connectWS, 5000); };
    } catch (e) {}
  }

  function _render(price, change, high, low, vol) {
    const priceEl   = document.getElementById('btc-price');
    const changeEl  = document.getElementById('btc-change');
    const highEl    = document.getElementById('btc-high');
    const lowEl     = document.getElementById('btc-low');
    const volEl     = document.getElementById('btc-vol');
    const updatedEl = document.getElementById('btc-updated');
    const barEl     = document.getElementById('btc-price-bar');
    const barLowEl  = document.getElementById('btc-bar-low-label');
    const barHighEl = document.getElementById('btc-bar-high-label');

    const headerValEl = document.getElementById('header-btc-val');
    if (headerValEl) {
      const compactPrice = '$' + Math.round(price).toLocaleString('en-US');
      const wasLowerH  = _lastPrice > 0 && price < _lastPrice;
      const wasHigherH = _lastPrice > 0 && price > _lastPrice;
      headerValEl.textContent = compactPrice;
      headerValEl.classList.remove('price-up', 'price-down');
      if (wasHigherH) { headerValEl.classList.add('price-up');   setTimeout(() => headerValEl.classList.remove('price-up'), 2000); }
      if (wasLowerH)  { headerValEl.classList.add('price-down'); setTimeout(() => headerValEl.classList.remove('price-down'), 2000); }
    }

    if (priceEl) {
      const formatted = '$' + price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
      const wasLower  = _lastPrice > 0 && price < _lastPrice;
      const wasHigher = _lastPrice > 0 && price > _lastPrice;
      priceEl.textContent = formatted;
      priceEl.classList.remove('price-up', 'price-down');
      if (wasHigher) { priceEl.classList.add('price-up');   setTimeout(() => priceEl.classList.remove('price-up'), 1500); }
      if (wasLower)  { priceEl.classList.add('price-down'); setTimeout(() => priceEl.classList.remove('price-down'), 1500); }
    }

    if (changeEl) {
      const up = change >= 0;
      changeEl.textContent = (up ? '+' : '') + change.toFixed(2) + '%';
      changeEl.className   = 'btc-dash-change ' + (up ? 'change-positive' : 'change-negative');
    }

    if (highEl)    highEl.textContent    = '$' + high.toLocaleString('en-US', { maximumFractionDigits: 0 });
    if (lowEl)     lowEl.textContent     = '$' + low.toLocaleString('en-US', { maximumFractionDigits: 0 });
    if (volEl)     volEl.textContent     = vol > 1000 ? (vol / 1000).toFixed(1) + 'K BTC' : vol.toFixed(0) + ' BTC';
    if (updatedEl) updatedEl.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
    if (barLowEl)  barLowEl.textContent  = '$' + low.toLocaleString('en-US', { maximumFractionDigits: 0 });
    if (barHighEl) barHighEl.textContent = '$' + high.toLocaleString('en-US', { maximumFractionDigits: 0 });

    if (barEl && high > low) {
      const pct = ((price - low) / (high - low)) * 100;
      barEl.style.width = pct + '%';
    }
  }

  return {
    init() {
      fetch();
      _interval = setInterval(fetch, 15000);
      _connectWS();

      const refreshBtn = document.getElementById('btc-refresh-btn');
      if (refreshBtn) {
        refreshBtn.addEventListener('click', () => {
          refreshBtn.style.transform  = 'rotate(360deg)';
          refreshBtn.style.transition = 'transform 0.5s ease';
          setTimeout(() => { refreshBtn.style.transform = ''; refreshBtn.style.transition = ''; }, 500);
          fetch();
          hapticSelect();
        });
      }
    },
  };
})();

const backgrounds = [
  'https://i.postimg.cc/1Xrtd4sJ/background1.png'
];

const randomBg = backgrounds[Math.floor(Math.random() * backgrounds.length)];

const bg = document.createElement('div');
bg.style.cssText = `
  position: absolute;
  inset: 0;
  background: url('${randomBg}') center / cover no-repeat;
  opacity: 0.7;
  z-index: 0;
`;

const img = new Image();
img.onload = () => document.getElementById('loading-screen').appendChild(bg);
img.src = randomBg;

// ─── GO ──────────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', boot);