Supercell Store Ultimate Claimer

Fetches and claims free Supercell Store rewards on store.supercell.com via a floating widget.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Supercell Store Ultimate Claimer
// @namespace    http://tampermonkey.net/
// @version      7.11
// @description  Fetches and claims free Supercell Store rewards on store.supercell.com via a floating widget.
// @author       http://github.com/anhdung98
// @match        https://store.supercell.com/*
// @grant        GM_xmlhttpRequest
// @inject-into  content
// @connect      supercell.xpromo.net
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // --- CONFIG ---
  const API_BASE = 'https://supercell.xpromo.net';
  const STORE_ORIGIN = 'https://store.supercell.com';
  const LOGIN_URL = 'https://store.supercell.com/vi/oauth/begin';
  const STORAGE_KEY_MAIN = 'sc_v7_main_collapsed';

  const CUSTOM_HEADERS = {};

  let GAMES_LIST = [];

  // --- NATIVE CSS ---
  function addCustomStyle(css) {
    const style = document.createElement('style');
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);
  }

  addCustomStyle(`
        :root {
            --sc-bg: rgba(18, 18, 20, 0.98);
            --sc-overlay: rgba(0, 0, 0, 0.7);
            --sc-panel: #222;
            --sc-item-bg: rgba(255,255,255,0.05);
            --sc-text: #ffffff;
            --sc-text-muted: #999;
            --sc-gold-grad: linear-gradient(135deg, #ffd700, #e6ac00);
            --sc-blue-grad: linear-gradient(135deg, #3498db, #2980b9);
            --sc-green: #2ecc71;
            --sc-red: #e74c3c;
            --sc-border: rgba(255, 255, 255, 0.1);
            --sc-font-head: 'SupercellHeadline-Bold', 'Supercell-Magic', system-ui, sans-serif;
            --sc-font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        }

        #sc-dashboard {
            position: fixed; top: 90px; right: 20px; z-index: 100000;
            font-family: var(--sc-font-body); -webkit-font-smoothing: antialiased;
        }

        /* --- MAIN CARD --- */
        .sc-main-card {
            width: 340px; background: var(--sc-bg);
            backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 215, 0, 0.2); border-radius: 14px;
            overflow: hidden; box-shadow: 0 15px 40px rgba(0,0,0,0.6);
            display: flex; flex-direction: column;
            animation: slideUpFade 0.4s cubic-bezier(0.16, 1, 0.3, 1);
        }

        .sc-main-header {
            padding: 12px 16px; background: var(--sc-gold-grad);
            display: flex; justify-content: space-between; align-items: center;
        }

        .sc-main-title {
            font-family: var(--sc-font-head); font-size: 16px; color: #111;
            text-transform: uppercase; letter-spacing: 0.5px;
            display: flex; align-items: center; gap: 6px;
        }

        .sc-controls { display: flex; align-items: center; gap: 6px; }
        .sc-controls button {
            background: rgba(0,0,0,0.08); border: none; color: #111; cursor: pointer;
            width: 26px; height: 26px; border-radius: 50%;
            display: flex; align-items: center; justify-content: center; transition: all 0.2s;
        }
        .sc-controls button:hover { background: rgba(0,0,0,0.2); transform: scale(1.05); }

        /* --- BODY --- */
        .sc-body { max-height: 55vh; overflow-y: auto; padding: 10px; }
        .sc-body::-webkit-scrollbar { width: 4px; }
        .sc-body::-webkit-scrollbar-thumb { background: #444; border-radius: 2px; }

        /* --- FOOTER --- */
        .sc-main-footer {
            padding: 12px;
            background: rgba(0,0,0,0.3);
            border-top: 1px solid var(--sc-border);
            display: flex; gap: 10px;
        }

        .sc-footer-btn {
            flex: 1; padding: 10px; border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
            font-family: var(--sc-font-head); font-size: 11px; text-transform: uppercase;
            cursor: pointer; display: flex; align-items: center; justify-content: center;
            transition: all 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.2); letter-spacing: 0.5px;
            background: #333; color: #ccc;
        }
        .sc-footer-btn:hover { background: #444; color: #fff; border-color: rgba(255,255,255,0.3); }
        .sc-footer-btn:active { transform: scale(0.96); }

        /* --- GAME LIST STYLES --- */
        .sc-game-section {
            margin-bottom: 8px; background: rgba(255,255,255,0.02);
            border: 1px solid var(--sc-border); border-radius: 8px; overflow: hidden;
        }
        .sc-game-section:last-child { margin-bottom: 0; }

        .sc-game-header { padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; background: linear-gradient(to bottom, rgba(255,255,255,0.05), rgba(255,255,255,0.01)); }
        .sc-game-info { display: flex; align-items: center; gap: 10px; }
        .sc-game-icon { width: 20px; height: 20px; border-radius: 5px; background: #333; object-fit: cover; }
        .sc-game-name { font-weight: 700; font-size: 13px; color: #eee; }
        .sc-game-count { background: #333; color: #ddd; font-size: 9px; padding: 2px 6px; border-radius: 8px; font-weight: 700; }

        .sc-items-container { max-height: 500px; padding: 0 8px 8px 8px; overflow: hidden; transition: max-height 0.3s ease-out, padding 0.3s ease; }
        .sc-items-container.collapsed { max-height: 0; padding: 0; pointer-events: none; }

        .sc-item-row { display: flex; justify-content: space-between; align-items: center; background: var(--sc-item-bg); padding: 6px 10px; margin-top: 6px; border-radius: 6px; border-left: 2px solid transparent; }
        .sc-item-meta { display: flex; flex-direction: column; }
        .sc-item-name { font-size: 12px; font-weight: 500; color: #ddd; }
        .sc-item-msg { font-size: 9px; color: var(--sc-text-muted); }

        .btn-claim {
            background: #3a3a3a; color: #fff; border: none; padding: 6px 0;
            width: 72px; text-align: center; justify-content: center;
            font-size: 10px; border-radius: 4px; cursor: pointer; font-weight: 600;
        }

        .btn-claim-all { background: var(--sc-blue-grad); color: #fff; border: none; padding: 4px 10px; font-size: 10px; font-weight: 700; border-radius: 4px; cursor: pointer; }
        .btn-claim-all:disabled { background: #444; color: #888; box-shadow: none; cursor: not-allowed; }

        .sc-toggle-icon { font-size: 10px; color: #666; width: 24px; text-align: center; display: inline-block; transition: transform 0.3s ease; }
        .sc-game-section.open .sc-toggle-icon { transform: rotate(180deg); }

        .status-processing { border-color: #ffd700; } .status-success { border-color: var(--sc-green); } .status-error { border-color: var(--sc-red); } .status-done { opacity: 0.5; }
        .sc-fab { width: 52px; height: 52px; background: var(--sc-gold-grad); border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(255, 170, 0, 0.5); cursor: pointer; z-index: 100000; animation: bounceIn 0.5s; }
        .sc-fab:hover { transform: scale(1.1); } .sc-fab svg { width: 24px; height: 24px; fill: #111; }

        /* --- MODAL SYSTEM --- */
        .sc-modal-overlay {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: var(--sc-overlay); backdrop-filter: blur(5px);
            z-index: 200000; display: flex; align-items: center; justify-content: center;
            opacity: 0; animation: fadeIn 0.3s forwards;
        }

        .sc-modal {
            width: 380px;
            background: var(--sc-bg);
            border: 1px solid var(--sc-gold-grad);
            border-radius: 16px;
            overflow: visible;
            box-shadow: 0 20px 50px rgba(0,0,0,0.8);
            transform: scale(0.9); animation: scaleUp 0.3s forwards;
            display: flex; flex-direction: column;
        }

        .sc-modal-header {
            background: var(--sc-gold-grad); padding: 12px 16px;
            display: flex; justify-content: space-between; align-items: center;
            border-top-left-radius: 15px; border-top-right-radius: 15px;
        }

        .sc-modal-title { font-family: var(--sc-font-head); font-size: 16px; color: #111; text-transform: uppercase; }
        .sc-modal-close { background: none; border: none; font-size: 20px; cursor: pointer; color: #111; }

        .sc-modal-body {
            padding: 16px; display: flex; flex-direction: column; gap: 12px;
            border-bottom-left-radius: 15px; border-bottom-right-radius: 15px;
            background: var(--sc-bg);
        }

        /* --- CUSTOM DROPDOWN --- */
        .sc-dropdown { position: relative; width: 100%; font-family: -apple-system, sans-serif; }
        .sc-dropdown-btn {
            width: 100%; padding: 10px; background: rgba(0,0,0,0.3);
            border: 1px solid var(--sc-border); border-radius: 8px;
            color: #fff; cursor: pointer; display: flex; justify-content: space-between; align-items: center;
            font-size: 13px; box-sizing: border-box; transition: border-color 0.2s;
        }
        .sc-dropdown-btn:hover { border-color: rgba(255,255,255,0.3); }
        .sc-dropdown-btn.active { border-color: #ffd700; }

        .sc-dropdown-content {
            position: absolute; top: 105%; left: 0; width: 100%;
            background: #222; border: 1px solid rgba(255,255,255,0.15);
            border-radius: 8px; max-height: 250px; overflow-y: auto;
            z-index: 9999; display: none; box-shadow: 0 10px 30px rgba(0,0,0,0.9);
        }

        .sc-dropdown-content::-webkit-scrollbar { width: 4px; }
        .sc-dropdown-content::-webkit-scrollbar-thumb { background: #555; border-radius: 2px; }

        .sc-dropdown-content.show { display: block; animation: fadeIn 0.2s; }
        .sc-dropdown-item {
            padding: 10px; display: flex; align-items: center; gap: 10px;
            cursor: pointer; transition: background 0.2s; border-bottom: 1px solid rgba(255,255,255,0.05);
        }
        .sc-dropdown-item:hover { background: rgba(255,255,255,0.1); }
        .sc-dropdown-item:last-child { border-bottom: none; }
        .sc-select-img { width: 20px; height: 20px; border-radius: 4px; object-fit: cover; background: #333; }
        .sc-select-text { font-size: 13px; color: #eee; font-weight: 500; }

        .sc-input { width: 100%; background: rgba(0,0,0,0.3); border: 1px solid var(--sc-border); color: #fff; padding: 10px; border-radius: 8px; font-family: monospace; font-size: 16px; outline: none; box-sizing: border-box; }
        .sc-input:focus { border-color: #ffd700; }
        #code-input { text-align: center; letter-spacing: 4px; text-transform: uppercase; font-weight: bold; font-size: 18px; }
        .sc-textarea { width: 100%; height: 100px; background: rgba(0,0,0,0.3); border: 1px solid var(--sc-border); color: #fff; padding: 10px; border-radius: 8px; font-size: 16px; resize: none; outline: none; box-sizing: border-box; }
        .sc-btn-main { width: 100%; padding: 12px; border: none; border-radius: 8px; font-family: var(--sc-font-head); font-size: 14px; text-transform: uppercase; cursor: pointer; transition: all 0.2s; background: var(--sc-gold-grad); color: #111; }
        .sc-btn-main:disabled { background: #444; color: #888; cursor: not-allowed; }
        .sc-disclaimer { font-size: 11px; color: #777; font-style: italic; text-align: center; }


        @keyframes slideUpFade { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
        @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
        @keyframes fadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.9); } }
        @keyframes scaleUp { from { transform: scale(0.9); } to { transform: scale(1); } }
        @keyframes bounceIn { 0% { transform: scale(0); opacity: 0; } 60% { transform: scale(1.1); } 100% { transform: scale(1); } }
        .hidden { display: none !important; }
        .sc-closing { animation: fadeOut 0.3s forwards; pointer-events: none; }
        .sc-fab { cursor: grab; }
        .sc-fab:active { cursor: grabbing; }
        .sc-main-header { cursor: grab; }
        .sc-main-header:active { cursor: grabbing; }
    `);

  // --- LOGIC ---

  function init() {
    GM_xmlhttpRequest({
      method: "GET",
      url: `${API_BASE}/api/games`,
      onload: (res) => {
        if (res.status === 200) {
          try {
            GAMES_LIST = JSON.parse(res.responseText);
          } catch (e) { }
        }
      }
    });
    fetchRewards();
  }

  function fetchRewards() {
    try {
      GM_xmlhttpRequest({
        method: "GET",
        url: `${API_BASE}/api/v2/store/data`,
        headers: CUSTOM_HEADERS,
        onload: function (response) {
          if (response.status === 200) {
            try {
              renderDashboard(JSON.parse(response.responseText));
            } catch (e) {
              console.error("JSON Error", e);
            }
          }
        }
      });
    } catch (e) {
      console.error("GM Error", e);
    }
  }

  // --- DASHBOARD ---
  function renderDashboard(gamesData) {
    if (document.getElementById('sc-dashboard')) return;
    const isMainCollapsed = localStorage.getItem(STORAGE_KEY_MAIN) === 'true';

    const root = document.createElement('div');
    root.id = 'sc-dashboard';

    const card = document.createElement('div');
    card.className = `sc-main-card ${isMainCollapsed ? 'hidden' : ''}`;

    const header = document.createElement('div');
    header.className = 'sc-main-header';
    header.innerHTML = `
            <div class="sc-main-title">
                <svg viewBox="0 0 24 24" width="16" height="16" style="margin-right:5px"><path d="M20,6H16V5C16,3.89 15.1,3 14,3H10C8.9,3 8,3.89 8,5V6H4C2.89,6 2,6.89 2,8V19C2,20.11 2.89,21 4,21H20C21.11,21 22,20.11 22,19V8C22,6.89 21.11,6 20,6M15,5C15,4.45 14.55,4 14,4H10C9.45,4 9,4.45 9,5V6H15V5M20,19H4V8H20V19Z" fill="#1a1a1a"/></svg>
                Store Rewards
            </div>
            <div class="sc-controls">
                <button id="btn-min" title="Minimize">─</button>
                <button id="btn-close" title="Close">✕</button>
            </div>
        `;

    const body = document.createElement('div');
    body.className = 'sc-body';

    gamesData.forEach(game => {
      if (!game.items || game.items.length === 0) return;
      const gameSection = document.createElement('div');
      gameSection.className = 'sc-game-section';

      const gHeader = document.createElement('div');
      gHeader.className = 'sc-game-header';
      gHeader.innerHTML = `
                <div class="sc-game-info">
                    <img src="${game.image || ''}" class="sc-game-icon" onerror="this.style.display='none'">
                    <span class="sc-game-name">${game.name}</span>
                    <span class="sc-game-count">${game.items.length}</span>
                </div>
                <div class="sc-game-actions">
                    <button class="btn-claim-all">Claim All</button>
                    <span class="sc-toggle-icon">▼</span>
                </div>
            `;

      const iContainer = document.createElement('div');
      iContainer.className = 'sc-items-container collapsed';

      game.items.forEach(item => {
        const row = document.createElement('div');
        row.className = 'sc-item-row';
        row.innerHTML = `
                    <div class="sc-item-meta">
                        <span class="sc-item-name">${item.item_name}</span>
                        <span class="sc-item-msg">Ready</span>
                    </div>
                    <button class="btn-claim" data-sku="${item.item_value}">Claim</button>
                `;
        const btn = row.querySelector('.btn-claim');
        btn.onclick = (e) => {
          e.stopPropagation();
          handleClaim(game.slug, item, btn, row.querySelector('.sc-item-msg'), row);
        };
        iContainer.appendChild(row);
      });

      // Toggle Logic
      gHeader.onclick = (e) => {
        if (e.target.classList.contains('btn-claim-all')) return;
        const isCollapsed = iContainer.classList.contains('collapsed');
        if (isCollapsed) {
          iContainer.classList.remove('collapsed');
          gameSection.classList.add('open');
        } else {
          iContainer.classList.add('collapsed');
          gameSection.classList.remove('open');
        }
      };

      const btnClaimAll = gHeader.querySelector('.btn-claim-all');
      btnClaimAll.onclick = async () => {
        btnClaimAll.disabled = true;
        btnClaimAll.innerText = '...';
        iContainer.classList.remove('collapsed');
        gameSection.classList.add('open');
        const allBtns = Array.from(iContainer.querySelectorAll('.btn-claim')).filter(btn => !btn.disabled && !btn.classList.contains('sc-done'));
        if (allBtns.length === 0) btnClaimAll.innerText = 'Empty';
        else {
          for (let btn of allBtns) {
            btn.click();
            await new Promise(r => setTimeout(r, Math.floor(Math.random() * 600) + 700));
          }
          btnClaimAll.innerText = 'Done';
        }
        setTimeout(() => {
          btnClaimAll.disabled = false;
          btnClaimAll.innerText = 'Claim All';
        }, 2000);
      };

      gameSection.appendChild(gHeader);
      gameSection.appendChild(iContainer);
      body.appendChild(gameSection);
    });

    if (body.children.length === 0) body.innerHTML = '<div style="color:#888;text-align:center;padding:15px;font-size:12px">No active rewards</div>';

    // --- FOOTER ---
    const footer = document.createElement('div');
    footer.className = 'sc-main-footer';
    footer.innerHTML = `
            <button id="btn-manual" class="sc-footer-btn">MANUAL CODE</button>
            <button id="btn-contribute" class="sc-footer-btn">CONTRIBUTE</button>
        `;

    card.appendChild(header);
    card.appendChild(body);
    card.appendChild(footer);

    // FAB
    const fab = document.createElement('div');
    fab.className = `sc-fab ${!isMainCollapsed ? 'hidden' : ''}`;
    fab.innerHTML = `<svg viewBox="0 0 24 24"><path d="M20,6H16V5C16,3.89 15.1,3 14,3H10C8.9,3 8,3.89 8,5V6H4C2.89,6 2,6.89 2,8V19C2,20.11 2.89,21 4,21H20C21.11,21 22,20.11 22,19V8C22,6.89 21.11,6 20,6M15,5C15,4.45 14.55,4 14,4H10C9.45,4 9,4.45 9,5V6H15V5M20,19H4V8H20V19Z"/></svg>`;

    // Events
    const toggleMain = () => {
      const isHidden = card.classList.contains('hidden');
      if (isHidden) {
        card.classList.remove('hidden');
        fab.classList.add('hidden');
        localStorage.setItem(STORAGE_KEY_MAIN, 'false');
        snapToEdge(root);
      } else {
        card.classList.add('sc-closing');
        setTimeout(() => {
          card.classList.add('hidden');
          card.classList.remove('sc-closing');
          fab.classList.remove('hidden');
          localStorage.setItem(STORAGE_KEY_MAIN, 'true');
          snapToEdge(root);
        }, 280);
      }
    };

    header.querySelector('#btn-min').onclick = toggleMain;
    header.querySelector('#btn-close').onclick = () => {
      card.classList.add('sc-closing');
      setTimeout(() => {
        root.remove();
      }, 280);
    };
    fab.onclick = toggleMain;

    footer.querySelector('#btn-manual').onclick = () => showModal('manual');
    footer.querySelector('#btn-contribute').onclick = () => showModal('contribute');

    root.appendChild(card);
    root.appendChild(fab);
    document.body.appendChild(root);

    // --- DRAG LOGIC ---
    function snapToEdge(element) {
      const rect = element.getBoundingClientRect();
      const centerX = rect.left + rect.width / 2;
      const windowWidth = window.innerWidth;
      const windowHeight = window.innerHeight;

      let newLeft = element.style.left;
      let newTop = parseFloat(element.style.top) || rect.top;

      if (centerX < windowWidth / 2) {
        newLeft = '20px';
      } else {
        newLeft = (windowWidth - rect.width - 20) + 'px';
      }

      if (newTop < 10) newTop = 10;
      if (newTop + rect.height > windowHeight - 10) newTop = windowHeight - rect.height - 10;

      element.style.transition = 'all 0.3s cubic-bezier(0.25, 1, 0.5, 1)';
      element.style.left = newLeft;
      element.style.top = newTop + 'px';

      setTimeout(() => {
        element.style.transition = '';
      }, 300);
    }

    function makeDraggable(element, handle) {
      let isDragging = false;
      let startX, startY, initialLeft, initialTop;
      let hasMoved = false;

      const onStart = (e) => {
        if (e.type === 'mousedown' && e.button !== 0) return;
        if (e.type === 'touchstart') {
          // Prevent default scroll on touch
        }

        const clientX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX;
        const clientY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;

        if (e.type === 'mousedown') e.preventDefault();

        isDragging = true;
        hasMoved = false;
        startX = clientX;
        startY = clientY;

        const rect = element.getBoundingClientRect();
        if (element.style.left === '') {
          element.style.left = rect.left + 'px';
          element.style.top = rect.top + 'px';
          element.style.right = 'auto';
        }

        initialLeft = parseFloat(element.style.left);
        initialTop = parseFloat(element.style.top);

        handle.style.cursor = 'grabbing';
        element.style.transition = 'none';
      };

      const onMove = (e) => {
        if (!isDragging) return;

        const clientX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX;
        const clientY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;

        if (e.type === 'touchmove') e.preventDefault();

        const dx = clientX - startX;
        const dy = clientY - startY;

        if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
          hasMoved = true;
        }

        let newLeft = initialLeft + dx;
        let newTop = initialTop + dy;

        // Constrain Vertical
        const h = element.offsetHeight;
        const windowHeight = window.innerHeight;

        if (newTop < 10) newTop = 10;
        if (newTop + h > windowHeight - 10) newTop = windowHeight - h - 10;

        element.style.left = `${newLeft}px`;
        element.style.top = `${newTop}px`;
      };

      const onEnd = (e) => {
        if (isDragging) {
          isDragging = false;
          handle.style.cursor = 'grab';

          if (hasMoved) {
            snapToEdge(element);
          }
        }
      };

      // Mouse Events
      handle.addEventListener('mousedown', onStart);
      document.addEventListener('mousemove', onMove);
      document.addEventListener('mouseup', onEnd);

      // Touch Events
      handle.addEventListener('touchstart', onStart, { passive: false });
      document.addEventListener('touchmove', onMove, { passive: false });
      document.addEventListener('touchend', onEnd);

      handle.addEventListener('click', (e) => {
        if (hasMoved) {
          e.preventDefault();
          e.stopPropagation();
        }
      }, true);
    }

    makeDraggable(root, header);
    makeDraggable(root, fab);
  }

  // --- MODAL BUILDER ---
  function showModal(type) {
    const overlay = document.createElement('div');
    overlay.className = 'sc-modal-overlay';
    const modal = document.createElement('div');
    modal.className = 'sc-modal';
    let title = type === 'manual' ? 'Manual Code' : 'Contribute';

    let bodyHtml = `
            <div class="sc-dropdown" id="game-dropdown">
                <div class="sc-dropdown-btn" id="game-trigger">
                    <span id="game-selected-text">Select Game...</span>
                    <span>▼</span>
                </div>
                <div class="sc-dropdown-content" id="game-menu"></div>
            </div>
        `;

    if (type === 'manual') bodyHtml += `<input type="text" class="sc-input" id="code-input" maxlength="12" placeholder="Enter code"><button class="sc-btn-main" id="btn-submit" disabled>Claim</button>`;
    else bodyHtml += `<textarea class="sc-textarea" id="contrib-input" maxlength="3928" placeholder="Enter your contribution description..."></textarea><div class="sc-disclaimer">Clicking Send will not collect any of your personal information, only the content you contribute.</div><button class="sc-btn-main" id="btn-submit" disabled>Send</button>`;

    modal.innerHTML = `<div class="sc-modal-header"><span class="sc-modal-title">${title}</span><button class="sc-modal-close">✕</button></div><div class="sc-modal-body">${bodyHtml}</div>`;
    overlay.appendChild(modal);
    document.body.appendChild(overlay);

    const closeModal = () => {
      modal.classList.add('sc-closing');
      setTimeout(() => overlay.remove(), 280);
    };
    modal.querySelector('.sc-modal-close').onclick = closeModal;
    overlay.onclick = (e) => {
      if (e.target === overlay) closeModal();
    };

    // --- DROPDOWN LOGIC ---
    let selectedGameSlug = null;
    const trigger = modal.querySelector('#game-trigger');
    const menu = modal.querySelector('#game-menu');
    const selectedText = modal.querySelector('#game-selected-text');

    trigger.onclick = () => {
      menu.classList.toggle('show');
      trigger.classList.toggle('active');
    };

    GAMES_LIST.forEach(game => {
      const item = document.createElement('div');
      item.className = 'sc-dropdown-item';
      const imgUrl = game.image_url || '';
      const imgDisplay = imgUrl ? `<img src="${imgUrl}" class="sc-select-img">` : '';
      item.innerHTML = `${imgDisplay}<span class="sc-select-text">${game.name}</span>`;
      item.onclick = () => {
        selectedGameSlug = game.slug;
        selectedText.innerHTML = `${imgDisplay} ${game.name}`;
        selectedText.style.display = 'flex';
        selectedText.style.alignItems = 'center';
        selectedText.style.gap = '8px';
        menu.classList.remove('show');
        trigger.classList.remove('active');
        checkValid();
      };
      menu.appendChild(item);
    });

    window.addEventListener('click', (e) => {
      if (!trigger.contains(e.target) && !menu.contains(e.target)) {
        menu.classList.remove('show');
        trigger.classList.remove('active');
      }
    });

    const btnSubmit = modal.querySelector('#btn-submit');
    const input = type === 'manual' ? modal.querySelector('#code-input') : modal.querySelector('#contrib-input');

    function checkValid() {
      if (type === 'manual') {
        input.value = input.value.toUpperCase();
        btnSubmit.disabled = !(selectedGameSlug && input.value.length === 12);
      }
      else btnSubmit.disabled = !(input.value.length > 0);
    }
    input.oninput = checkValid;

    btnSubmit.onclick = async () => {
      btnSubmit.disabled = true;
      btnSubmit.innerText = 'Processing...';
      if (type === 'manual') {
        const fakeItem = {
          item_type: 'code',
          item_value: input.value,
          item_name: 'Manual Code'
        };
        const fakeBtn = document.createElement('button');
        const fakeStatus = document.createElement('span');
        const fakeRow = document.createElement('div');
        await handleClaim(selectedGameSlug, fakeItem, fakeBtn, fakeStatus, fakeRow);
        btnSubmit.innerText = fakeStatus.innerText;
        setTimeout(() => {
          btnSubmit.innerText = 'Claim';
          checkValid();
        }, 2000);
      } else {
        try {
          const res = await fetch(`${API_BASE}/api/contribute`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              gameSlug: selectedGameSlug,
              content: input.value
            })
          });
          if (res.ok) {
            btnSubmit.innerText = 'Sent!';
            setTimeout(closeModal, 1000);
          } else {
            btnSubmit.innerText = 'Error';
            setTimeout(() => {
              btnSubmit.disabled = false;
              btnSubmit.innerText = 'Send';
            }, 2000);
          }
        } catch (e) {
          btnSubmit.innerText = 'Network Error';
        }
      }
    };
  }

  // --- CLAIM LOGIC ---
  async function handleClaim(gameSlug, item, btn, statusEl, rowEl) {
    if (btn.disabled && !btn.innerText.includes('Success')) return;
    btn.disabled = true;
    btn.innerText = '...';
    statusEl.innerText = 'Wait...';
    statusEl.style.color = '#ffd700';
    rowEl.className = 'sc-item-row status-processing';

    let url = '';
    let body = {};
    const baseUrl = `${STORE_ORIGIN}/api/v3/${gameSlug}`;
    switch (item.item_type) {
      case 'voucher':
        url = `${baseUrl}/store-codes/${item.item_value}/vouchers/claim`;
        body = {
          "selectedSKU": 0
        };
        break;
      case 'reward':
        url = `${baseUrl}/rewards/claim`;
        body = {
          "offerId": item.item_value
        };
        break;
      case 'offer':
        url = `${baseUrl}/offer/claim`;
        body = {
          "skus": [item.item_value]
        };
        break;
      case 'code':
        url = `${baseUrl}/store-codes/${item.item_value}/claim`;
        body = {
          "accountName": ""
        };
        break;
    }

    const finish = (msg, color, btnTxt, isDone) => {
      statusEl.innerText = msg;
      statusEl.style.color = color;
      btn.innerText = btnTxt;
      if (btnTxt === 'Login') {
        btn.classList.remove('sc-done');
        btn.style.background = '#e67e22';
        btn.style.color = '#fff';
        btn.disabled = false;
        btn.onclick = (e) => {
          e.stopPropagation();
          window.location.href = LOGIN_URL;
        };
        rowEl.className = 'sc-item-row status-error';
        return;
      }
      if (isDone) {
        btn.classList.add('sc-done');
        btn.style.background = 'var(--sc-green)';
        btn.style.color = '#fff';
        if (btnTxt === 'Taken') {
          btn.style.background = '#444';
          rowEl.className = 'sc-item-row status-done';
        } else {
          rowEl.className = 'sc-item-row status-success';
        }
      } else {
        btn.disabled = false;
        rowEl.className = 'sc-item-row status-error';
      }
    };

    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'text/plain;charset=UTF-8'
        },
        body: JSON.stringify(body)
      });
      if ((item.item_type === 'voucher' || item.item_type === 'code') && res.status === 404) {
        finish('Expired or Invalid IP Location', 'var(--sc-red)', 'Retry', false);
        return;
      }
      if (res.status === 401 || res.status === 403) {
        finish('Login Required', '#e67e22', 'Login', false);
        return;
      }
      if ((item.item_type === 'voucher' && res.status === 400) || (item.item_type === 'code' && res.status === 500)) {
        finish('Already Claimed', '#999', 'Taken', true);
        return;
      }
      const data = await res.json().catch(() => ({}));
      if (res.status === 200) {
        if (data.ok === false) {
          const err = data.error || '';
          if (err.includes('already_claimed')) finish('Already Claimed', '#999', 'Taken', true);
          else finish('Error', 'var(--sc-red)', 'Retry', false);
        } else finish('Success', 'var(--sc-green)', '✔', true);
      } else finish(`Error ${res.status}`, 'var(--sc-red)', 'Retry', false);
    } catch (e) {
      console.error(e);
      finish('Network Error', 'var(--sc-red)', 'Retry', false);
    }
  }

  setTimeout(init, 500);
})();