🛠️ PopControl

Popmundo scriptlerini tek panelden yönetmenizi sağlayan merkezi modül

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        🛠️ PopControl
// @name:tr     🛠️ PopControl
// @name:en     🛠️ PopControl
// @name:pt-BR  🛠️ PopControl
// @namespace   popmundo.popcontrol
// @version     1.7
// @description Popmundo scriptlerini tek panelden yönetmenizi sağlayan merkezi modül
// @description:tr Popmundo scriptlerini tek panelden yönetmenizi sağlayan merkezi modül
// @description:en Central hub to manage all Popmundo scripts from one panel
// @description:pt-BR Módulo central para gerenciar todos os scripts do Popmundo
// @author      luke-james-gibson
// @license     MIT
// @match       https://*.popmundo.com/*
// @run-at      document-end
// @grant       unsafeWindow
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// ==/UserScript==

(function () {
'use strict';

// ─── UTILS ───────────────────────────────────────────────────────────────────
const CK = {
    get: k => { const m = document.cookie.match(new RegExp('(?:^|; )' + k + '=([^;]*)')); return m ? decodeURIComponent(m[1]) : null; },
    set: (k, v) => { document.cookie = `${k}=${encodeURIComponent(v)};domain=.popmundo.com;path=/;max-age=31536000`; }
};
const LS = {
    get: (k, d) => { try { const v = localStorage.getItem(k); return v !== null ? JSON.parse(v) : d; } catch { return d; } },
    set: (k, v) => { try { localStorage.setItem(k, JSON.stringify(v)); } catch {} },
};
const mk  = (tag, cls, txt) => { const e = document.createElement(tag); if (cls) e.className = cls; if (txt != null) e.textContent = txt; return e; };
const mkB = (txt, cls, fn)  => Object.assign(mk('button', cls, txt), { onclick: fn, type: 'button' });

// ─── LANG ────────────────────────────────────────────────────────────────────
const LANG = CK.get('ppm_lang') || 'TR';
const _D   = (tr, en, pt) => ({ TR: tr, EN: en, PT: pt }[LANG] || tr);
const STR  = {
    posLabel:    _D('Konum',            'Position',          'Posição'),
    posBottom:   _D('⬇ Alt',           '⬇ Bottom',         '⬇ Baixo'),
    posTop:      _D('⬆ Üst',           '⬆ Top',            '⬆ Cima'),
    posLeft:     _D('◀ Sol',           '◀ Left',           '◀ Esquerda'),
    posRight:    _D('▶ Sağ',           '▶ Right',          '▶ Direita'),
    langLabel:   _D('Dil',              'Language',          'Idioma'),
    scripts:     _D('Scriptler',        'Scripts',           'Scripts'),
    order:       _D('Sıra',             'Order',             'Ordem'),
    orderHint:   _D('Barda butonları sürükleyip bırakarak sıralayabilirsiniz.',
                    'Drag & drop buttons on the bar to reorder.',
                    'Arraste os botões na barra para reordenar.'),
    colorBar:    _D('Bar Rengi',        'Bar Color',         'Cor da Barra'),
    colorText:   _D('Yazı Rengi',       'Text Color',        'Cor do Texto'),
    colorPicker: _D('Renk Seçici',      'Color Picker',      'Seletor de Cor'),  // [NEW] label for color input
    guide:       _D('📖 Beni Oku', '📖 Read Me', '📖 Leia-me'),
    integrate:   _D('🔗 Entegrasyon', '🔗 Integration', '🔗 Integração'),
    close:       _D('Kapat',            'Close',             'Fechar'),
};

// ─── STATE ───────────────────────────────────────────────────────────────────
// ppc settings in GM_setValue (cross-subdomain, stable)
// One-time migration from cookies
(function _migrateCookiesToGM() {
    const keys = ['ppc_enabled','ppc_order','ppc_pos','ppc_bg','ppc_fg'];
    keys.forEach(k => {
        if (GM_getValue(k, null) !== null) return;
        const m = document.cookie.match(new RegExp('(?:^|; )' + k + '=([^;]*)'));
        if (m) { GM_setValue(k, decodeURIComponent(m[1])); document.cookie = k + '=;domain=.popmundo.com;path=/;max-age=0'; }
    });
})();

const _getEnabledObj = () => { try { return JSON.parse(GM_getValue('ppc_enabled', '{}')); } catch { return {}; } };
const _saveEnabledObj= o  => GM_setValue('ppc_enabled', JSON.stringify(o));   // [FIX] removed redundant localStorage write
const _isEnabled  = id     => _getEnabledObj()[id] !== false;
const _setEnabled = (id,v) => { const e = _getEnabledObj(); e[id] = v; _saveEnabledObj(e); };
const _getPos     = ()     => GM_getValue('ppc_pos', 'bottom');
const _setPos     = p      => GM_setValue('ppc_pos', p);
const _getOrder   = ()     => { try { return JSON.parse(GM_getValue('ppc_order', '[]')); } catch { return []; } };
const _setOrder   = o      => GM_setValue('ppc_order', JSON.stringify(o));
const _getColBg   = ()     => GM_getValue('ppc_bg', '#f5f0ff');
const _getColFg   = ()     => GM_getValue('ppc_fg', '#5a30a0');
const _setColBg   = v      => GM_setValue('ppc_bg', v);
const _setColFg   = v      => GM_setValue('ppc_fg', v);

const _registry = [];
let   _collapsed = !!GM_getValue('ppc_collapsed', false);                      // [FIX] persist across pages
const _setCollapsed = v => { _collapsed = v; GM_setValue('ppc_collapsed', v); };

function _sortedRegistry() {
    const order = _getOrder();
    if (!order.length) return [..._registry];
    const res = [];
    order.forEach(id => { const e = _registry.find(r => r.id === id); if (e) res.push(e); });
    _registry.forEach(e => { if (!res.includes(e)) res.push(e); });
    return res;
}

// ─── COLORS ──────────────────────────────────────────────────────────────────
const PRESET_BG = [
    '#f5f0ff','#fff','#1a1035','#2c3e50','#0d1117',
    '#fff0f5','#f0fff4','#f0f8ff','#fffde7','#f3e5f5',
];
const PRESET_FG = [
    '#5a30a0','#6f42c1','#fff','#ecf0f1','#ccc',
    '#e83e8c','#28a745','#007bff','#fd7e14','#ffc107',
];

// ─── GEOMETRY ────────────────────────────────────────────────────────────────
function _mainGeom() {
    const m = document.getElementById('ppm-main') || document.getElementById('ppm-footer');
    if (!m) return { left: 0, width: window.innerWidth, right: window.innerWidth };
    const r = m.getBoundingClientRect();
    return { left: Math.round(r.left), width: m.offsetWidth, right: Math.round(r.right) };
}

// ─── PUSH STYLE ──────────────────────────────────────────────────────────────
function _applyPush(size) {
    document.getElementById('ppc-push')?.remove();
    if (!size) return;
    const s = document.createElement('style'); s.id = 'ppc-push';
    s.textContent = `#ppm-main{margin-top:${65 + size}px!important}#character-tools,#header-logo{margin-top:${size}px!important}`;
    document.head.appendChild(s);
}

// ─── FAB POSITION ────────────────────────────────────────────────────────────
// [NEW] Aligns FAB to bottom-right of #ppm-footer on desktop; fallback is bottom:16px right:16px
function _positionFab(fab) {
    const footer = document.getElementById('ppm-footer');
    if (footer && window.innerWidth >= 768) {
        const r = footer.getBoundingClientRect();
        fab.style.bottom = (window.innerHeight - r.bottom + 8) + 'px';
        fab.style.right  = (window.innerWidth  - r.right  + 8) + 'px';
    }
}

// ─── BAR ─────────────────────────────────────────────────────────────────────
let _dragId = null;

function _rebuild() {
    document.getElementById('ppc-bar')?.remove();
    document.getElementById('ppc-fab')?.remove();
    document.getElementById('ppc-push')?.remove();

    const pos       = _getPos();
    const g         = _mainGeom();
    const mob       = window.innerWidth < 768;
    const vert      = pos === 'left' || pos === 'right';
    const BG        = _getColBg();
    const BDR       = '1px solid ' + _shadeHex(BG, -25);
    const FG        = _getColFg();
    const SZ        = 46;
    const sortedReg = _sortedRegistry();                                        // [FIX] single parse, cache for this render

    // ── FAB (collapsed) ──────────────────────────────────────────────────────
    const fab = mk('button'); fab.id = 'ppc-fab'; fab.type = 'button';
    fab.textContent = '⚙️';
    fab.style.cssText = [
        'position:fixed;bottom:16px;right:16px;z-index:9996',
        `background:${BG};border:${BDR};border-radius:50%`,
        `width:${SZ}px;height:${SZ}px;font-size:20px;cursor:pointer`,
        'box-shadow:0 2px 10px rgba(0,0,0,.25);display:none;align-items:center;justify-content:center',
        'font-family:inherit'
    ].join(';');
    fab.onclick = () => { _setCollapsed(false); _rebuild(); };
    document.body.appendChild(fab);

    // ── BAR ──────────────────────────────────────────────────────────────────
    const bar = mk('div'); bar.id = 'ppc-bar';

    if (vert) {
        const side   = pos === 'left' ? `left:${mob ? 0 : Math.max(0, g.left - SZ)}px` : `right:${mob ? 0 : Math.max(0, window.innerWidth - g.right - SZ)}px`;
        const shadow = pos === 'left' ? '2px 0 8px rgba(0,0,0,.12)' : '-2px 0 8px rgba(0,0,0,.12)';
        bar.style.cssText = [
            `position:fixed;${side};top:65px;bottom:0;z-index:9990`,
            `background:${BG};border:${BDR};box-shadow:${shadow}`,
            `border-radius:${pos==='left'?'0 8px 8px 0':'8px 0 0 8px'}`,
            `width:${SZ}px;overflow-y:auto;overflow-x:hidden;scrollbar-width:none`,
            `display:flex;flex-direction:column;align-items:center;padding:4px 0;gap:0`,
        ].join(';');
    } else {
        const side   = pos === 'bottom' ? 'bottom:0' : 'top:0';
        const xPos   = mob ? 'left:0;right:0' : `left:${g.left}px;width:${g.width}px`;
        const zIdx   = pos === 'top' ? 99990 : 9990;
        const shadow = pos === 'bottom' ? '0 -2px 8px rgba(0,0,0,.12)' : '0 2px 8px rgba(0,0,0,.12)';
        bar.style.cssText = [
            `position:fixed;${xPos};${side};z-index:${zIdx}`,
            `background:${BG};border:${BDR};box-shadow:${shadow}`,
            `border-radius:${pos==='bottom'?'8px 8px 0 0':'0 0 8px 8px'}`,
            `display:flex;flex-direction:row;flex-wrap:nowrap;align-items:stretch`,
        ].join(';');
    }

    // ── BUTTON FACTORY ────────────────────────────────────────────────────────
    const BTN_W = vert ? SZ : 52;
    const mkBtn = (icon, label, onClick, draggable) => {
        const b = mk('button'); b.type = 'button';
        if (draggable) b.draggable = true;
        b.style.cssText = [
            `display:flex;flex-direction:column;align-items:center;justify-content:center`,
            vert ? `width:${BTN_W}px;min-height:${SZ}px;padding:4px 2px;gap:2px;flex-shrink:0` : `min-width:48px;flex:1;min-height:${SZ}px;padding:4px 6px;gap:2px`,
            `background:none;border:none;cursor:${draggable ? 'grab' : 'pointer'};font-family:inherit`
        ].join(';');
        const ico = mk('span'); ico.textContent = icon;
        ico.style.cssText = 'font-size:18px;line-height:1;pointer-events:none;flex-shrink:0';
        const lbl = mk('span'); lbl.textContent = label;
        lbl.dataset.ppcLbl = '1';
        lbl.style.cssText = `font-size:9px;color:${FG};font-weight:700;white-space:nowrap;text-align:center;line-height:1.15;pointer-events:none;`;
        b.append(ico, lbl);
        b.addEventListener('mouseenter', () => b.style.background = 'rgba(0,0,0,.07)');
        b.addEventListener('mouseleave', () => b.style.background = 'none');
        b.onclick = onClick;
        return b;
    };

    const mkSep = () => {
        const d = mk('div');
        d.style.cssText = vert
            ? `height:1px;background:${_shadeHex(BG,-18)};margin:2px 6px;width:${SZ-12}px;flex-shrink:0`
            : `width:1px;background:${_shadeHex(BG,-18)};margin:5px 0;flex-shrink:0;align-self:stretch`;
        return d;
    };

    // ── DESKTOP DRAG & DROP ───────────────────────────────────────────────────
    function _attachDrag(btn, groupId) {
        btn.addEventListener('dragstart', e => {
            _dragId = groupId;
            e.dataTransfer.effectAllowed = 'move';
            setTimeout(() => btn.style.opacity = '.4', 0);
        });
        btn.addEventListener('dragend', () => { _dragId = null; btn.style.opacity = '1'; });
        btn.addEventListener('dragover', e => { e.preventDefault(); btn.style.outline = '2px dashed ' + FG; });
        btn.addEventListener('dragleave', () => btn.style.outline = '');
        btn.addEventListener('drop', e => {
            e.preventDefault(); btn.style.outline = '';
            if (!_dragId || _dragId === groupId) return;
            const order = sortedReg.map(r => r.id);
            const from = order.indexOf(_dragId), to = order.indexOf(groupId);
            if (from < 0 || to < 0) return;
            order.splice(to, 0, order.splice(from, 1)[0]);
            _setOrder(order); _rebuild();
        });
    }

    // ── TOUCH DRAG (mobile) ───────────────────────────────────────────────────
    function _attachTouchDrag(container) {
        let dragEl = null, ghost = null, lastOver = null, startX = 0, startY = 0, origLeft = 0, origTop = 0;
        container.addEventListener('touchstart', e => {
            const h = e.target.closest('[data-ppc-drag]');
            if (!h) return;
            dragEl = h; startX = e.touches[0].clientX; startY = e.touches[0].clientY;
            const r = dragEl.getBoundingClientRect();
            origLeft = r.left; origTop = r.top;                                // [FIX] store original fixed position
            ghost = dragEl.cloneNode(true);
            ghost.style.cssText = `position:fixed;left:${origLeft}px;top:${origTop}px;width:${r.width}px;height:${r.height}px;opacity:.65;pointer-events:none;z-index:99999;border:2px dashed ${FG};border-radius:6px;background:${BG}`;
            document.body.appendChild(ghost);
            dragEl.style.opacity = '.3';
        }, { passive: true });
        container.addEventListener('touchmove', e => {
            if (!dragEl) return;
            e.preventDefault();
            const t = e.touches[0];
            ghost.style.left = (origLeft + t.clientX - startX) + 'px';       // [FIX] left/top instead of transform — scroll-safe
            ghost.style.top  = (origTop  + t.clientY - startY) + 'px';
            const over = document.elementFromPoint(t.clientX, t.clientY)?.closest('[data-ppc-drag]');
            if (lastOver && lastOver !== dragEl) lastOver.style.outline = '';
            if (over && over !== dragEl) { over.style.outline = '2px dashed ' + FG; lastOver = over; }
        }, { passive: false });
        container.addEventListener('touchend', e => {
            if (!dragEl) return;
            ghost?.remove(); ghost = null;
            dragEl.style.opacity = '1';
            if (lastOver && lastOver !== dragEl) lastOver.style.outline = '';
            const t = e.changedTouches[0];
            const over = document.elementFromPoint(t.clientX, t.clientY)?.closest('[data-ppc-drag]');
            if (over && over !== dragEl) {
                const fromId = dragEl.dataset.ppcDrag, toId = over.dataset.ppcDrag;
                const order = sortedReg.map(r => r.id);
                const fi = order.indexOf(fromId), ti = order.indexOf(toId);
                if (fi >= 0 && ti >= 0) { order.splice(ti, 0, order.splice(fi, 1)[0]); _setOrder(order); _rebuild(); }
            }
            dragEl = null; lastOver = null;
        }, { passive: true });
    }

    // ── COLLAPSE BUTTON ───────────────────────────────────────────────────────
    const colBtn = mk('button'); colBtn.type = 'button';
    colBtn.textContent = '▼';
    colBtn.style.cssText = [
        `font-size:9px;color:${FG};background:none;border:none;cursor:pointer`,
        `padding:2px 4px;font-family:inherit;flex-shrink:0`,
        vert ? `width:${SZ}px;text-align:center` : `min-width:20px;align-self:center`
    ].join(';');
    colBtn.title = _D('Küçült', 'Collapse', 'Recolher');
    colBtn.onclick = () => { _setCollapsed(true); _rebuild(); };               // [FIX] persist collapse

    // ── RENDER ENTRIES ────────────────────────────────────────────────────────
    if (!vert) {
        // [NEW] Horizontal: script buttons in scrolling container, ⚙️ + ▼ pinned to the right
        const scrollWrap = mk('div');
        scrollWrap.style.cssText = 'display:flex;flex:1;min-width:0;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;align-items:stretch';
        const fixedEnd = mk('div');
        fixedEnd.style.cssText = 'display:flex;flex-shrink:0;align-items:stretch';

        let hasAny = false;
        sortedReg.forEach(entry => {
            if (!_isEnabled(entry.id)) return;
            if (hasAny) scrollWrap.appendChild(mkSep());
            entry.buttons.forEach((btn, i) => {
                const b = mkBtn(btn.icon, btn.label, btn.onClick, i === 0);
                if (i === 0) { b.dataset.ppcDrag = entry.id; _attachDrag(b, entry.id); }
                scrollWrap.appendChild(b);
            });
            hasAny = true;
        });

        if (hasAny) fixedEnd.appendChild(mkSep());
        const sBtn = mkBtn('⚙️', 'PopControl', _openSettings, false);
        fixedEnd.appendChild(sBtn);
        fixedEnd.appendChild(colBtn);

        bar.appendChild(scrollWrap);
        bar.appendChild(fixedEnd);
        _attachTouchDrag(scrollWrap);
    } else {
        // Vertical: original layout
        let hasAny = false;
        sortedReg.forEach(entry => {
            if (!_isEnabled(entry.id)) return;
            if (hasAny) bar.appendChild(mkSep());
            entry.buttons.forEach((btn, i) => {
                const b = mkBtn(btn.icon, btn.label, btn.onClick, i === 0);
                if (i === 0) { b.dataset.ppcDrag = entry.id; _attachDrag(b, entry.id); }
                bar.appendChild(b);
            });
            hasAny = true;
        });
        if (hasAny) bar.appendChild(mkSep());
        const sBtn = mkBtn('⚙️', 'PopControl', _openSettings, false);
        sBtn.style.marginTop = 'auto';
        bar.appendChild(sBtn); bar.appendChild(mkSep()); bar.appendChild(colBtn);
        _attachTouchDrag(bar);
    }

    document.body.appendChild(bar);

    // Auto-shrink labels that overflow their button
    requestAnimationFrame(() => {
        bar.querySelectorAll('[data-ppc-lbl]').forEach(lbl => {
            const btn = lbl.closest('button');
            if (btn && lbl.scrollWidth > btn.clientWidth + 2) lbl.style.fontSize = '8px';
        });
    });

    // Push layout for top
    if (pos === 'top') _applyPush(bar.offsetHeight || 34);

    // Apply collapsed state
    if (_collapsed) {
        bar.style.display = 'none';
        fab.style.display = 'flex';
        _positionFab(fab);                                                     // [NEW] position FAB near footer
    }
}

// ─── COLOR UTILS ─────────────────────────────────────────────────────────────
function _shadeHex(hex, amt) {
    try {
        let h = hex.replace('#','');
        if (h.length === 3) h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2];
        const r = Math.max(0,Math.min(255,parseInt(h.slice(0,2),16)+amt));
        const g = Math.max(0,Math.min(255,parseInt(h.slice(2,4),16)+amt));
        const b = Math.max(0,Math.min(255,parseInt(h.slice(4,6),16)+amt));
        return '#'+[r,g,b].map(x=>x.toString(16).padStart(2,'0')).join('');
    } catch { return hex; }
}

// ─── SETTINGS PANEL ──────────────────────────────────────────────────────────
function _openSettings() {
    document.getElementById('ppc-ov')?.remove();
    const pos = _getPos();

    const ov = mk('div'); ov.id = 'ppc-ov';
    ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:99998;display:flex;align-items:flex-end;justify-content:center';

    const box = mk('div');
    box.style.cssText = 'background:#fff;border-radius:16px 16px 0 0;padding:20px;width:100%;max-width:520px;max-height:80vh;overflow-y:auto;box-sizing:border-box';

    const mkH  = t  => { const d = mk('div'); d.textContent = t; d.style.cssText = 'font-size:10px;font-weight:700;color:#888;text-transform:uppercase;letter-spacing:.5px;margin:14px 0 6px'; return d; };
    const mkHr = () => { const hr = mk('hr'); hr.style.cssText = 'border:none;border-top:1px solid #eee;margin:10px 0'; return hr; };
    const mkPillRow = (items) => {
        const row = mk('div'); row.style.cssText = 'display:flex;gap:6px;flex-wrap:wrap';
        items.forEach(([val, lbl, active, fn]) => {
            const b = mkB(lbl, '', fn);
            b.style.cssText = `flex:1;min-width:70px;padding:7px 4px;border-radius:6px;border:1px solid ${active?'#6f42c1':'#ddd'};background:${active?'#6f42c1':'#f8f9fa'};color:${active?'#fff':'#333'};font-size:11px;cursor:pointer;font-family:inherit`;
            row.appendChild(b);
        });
        return row;
    };

    // Title row
    const titleRow = mk('div'); titleRow.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:16px';
    titleRow.appendChild(Object.assign(mk('span'), { textContent: '🛠️ PopControl v' + GM_info.script.version, style: { fontWeight: 'bold', fontSize: '16px' } }));
    const xBtn = mkB('✕', '', () => ov.remove()); xBtn.style.cssText = 'background:none;border:none;font-size:20px;cursor:pointer;color:#aaa;padding:0';
    titleRow.appendChild(xBtn); box.appendChild(titleRow);

    // Beni Oku button — top
    const btnRow0 = mk('div'); btnRow0.style.cssText = 'display:flex;gap:6px;margin-bottom:10px';
    const guideBtn = mkB(STR.guide, '', () => window.open('https://rentry.org/PopControl', '_blank'));
    guideBtn.style.cssText = 'flex:1;padding:9px;border-radius:8px;background:#6f42c1;color:#fff;border:none;cursor:pointer;font-size:13px;font-weight:600;font-family:inherit';
    const intBtn = mkB(STR.integrate, '', () => window.open('https://rentry.org/PopControlEntegre', '_blank'));
    intBtn.style.cssText = 'flex:1;padding:9px;border-radius:8px;background:#5a30a0;color:#fff;border:none;cursor:pointer;font-size:13px;font-weight:600;font-family:inherit';
    btnRow0.append(guideBtn, intBtn);
    box.appendChild(btnRow0);
    box.appendChild(mkHr());

    // Position
    box.appendChild(mkH(STR.posLabel));
    box.appendChild(mkPillRow([
        ['bottom', STR.posBottom, pos==='bottom', () => { _setPos('bottom'); ov.remove(); location.reload(); }],
        ['top',    STR.posTop,    pos==='top',    () => { _setPos('top');    ov.remove(); location.reload(); }],
        ['left',   STR.posLeft,   pos==='left',   () => { _setPos('left');   ov.remove(); location.reload(); }],
        ['right',  STR.posRight,  pos==='right',  () => { _setPos('right');  ov.remove(); location.reload(); }],
    ]));
    box.appendChild(mkHr());

    // Language + Customize
    box.appendChild(mkH(STR.langLabel));
    const _hasCustom = ['ppc_lc_helper','ppc_lc_social','ppc_lc_social_mobile','ppc_lc_depot','ppc_lc_missionaid'].some(k => localStorage.getItem(k));
    box.appendChild(mkPillRow([
        ['TR','🇹🇷 Türkçe', LANG==='TR', () => { CK.set('ppm_lang','TR'); ov.remove(); location.reload(); }],
        ['EN','🇬🇧 English', LANG==='EN', () => { CK.set('ppm_lang','EN'); ov.remove(); location.reload(); }],
        ['PT','🇧🇷 Português', LANG==='PT', () => { CK.set('ppm_lang','PT'); ov.remove(); location.reload(); }],
        ['CU','🌍 Customize', _hasCustom, () => _openCustomize(ov)],
    ]));
    box.appendChild(mkHr());

    // Color pickers
    // [FIX] Removed all title/tooltip attributes from swatches and color input
    // [NEW] Separated swatches and color input rows with spacing; added "Renk Seçici" label
    const mkSwatches = (presets, current, onPick) => {
        const outerWrap = mk('div');
        outerWrap.style.cssText = 'margin:6px 0 4px';

        // Swatch row
        const swatchRow = mk('div');
        swatchRow.style.cssText = 'display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin-bottom:12px';
        presets.forEach(c => {
            const sw = mk('button'); sw.type = 'button';
            sw.style.cssText = `width:22px;height:22px;border-radius:4px;border:2px solid ${c===current?'#333':'transparent'};cursor:pointer;padding:0;background:${c};flex-shrink:0;box-shadow:0 1px 3px rgba(0,0,0,.2)`;
            sw.onclick = () => {
                swatchRow.querySelectorAll('button[data-sw]').forEach(b => b.style.borderColor = 'transparent');
                sw.style.borderColor = '#333';
                ci.value = c; onPick(c);
            };
            sw.dataset.sw = '1';
            swatchRow.appendChild(sw);
        });
        outerWrap.appendChild(swatchRow);

        // Color picker row: label + input
        const pickerRow = mk('div');
        pickerRow.style.cssText = 'display:flex;align-items:center;gap:8px';
        const ciLbl = mk('span', '', STR.colorPicker);
        ciLbl.style.cssText = 'font-size:11px;color:#666;flex-shrink:0';
        const ci = mk('input'); ci.type = 'color'; ci.value = current;
        ci.style.cssText = 'width:36px;height:26px;padding:1px;border:1px solid #ccc;border-radius:4px;cursor:pointer;flex-shrink:0';
        ci.oninput = () => {
            swatchRow.querySelectorAll('button[data-sw]').forEach(b => b.style.borderColor = 'transparent');
            onPick(ci.value);
        };
        pickerRow.append(ciLbl, ci);
        outerWrap.appendChild(pickerRow);
        return outerWrap;
    };

    box.appendChild(mkH(STR.colorBar));
    box.appendChild(mkSwatches(PRESET_BG, _getColBg(), v => { _setColBg(v); _applyColorsD(); }));

    box.appendChild(mkH(STR.colorText));
    box.appendChild(mkSwatches(PRESET_FG, _getColFg(), v => { _setColFg(v); _applyColorsD(); }));
    box.appendChild(mkHr());

    // Script toggles
    if (_registry.length) {
        box.appendChild(mkH(STR.scripts));
        _sortedRegistry().forEach(entry => {
            const row = mk('div'); row.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid #f0f0f0';
            const name = mk('span'); name.textContent = entry.icon + ' ' + entry.label; name.style.cssText = 'font-size:13px;font-weight:500';
            const swLbl = mk('label'); swLbl.style.cssText = 'position:relative;display:inline-block;width:46px;height:26px;cursor:pointer;flex-shrink:0';
            const inp   = mk('input'); inp.type = 'checkbox'; inp.checked = _isEnabled(entry.id); inp.style.cssText = 'opacity:0;width:0;height:0;position:absolute';
            const trk   = mk('span'); trk.style.cssText = `position:absolute;inset:0;border-radius:26px;background:${inp.checked?'#6f42c1':'#ccc'};transition:.25s`;
            const knob  = mk('span'); knob.style.cssText = `position:absolute;height:20px;width:20px;left:${inp.checked?'23px':'3px'};bottom:3px;background:#fff;border-radius:50%;transition:.25s;box-shadow:0 1px 3px rgba(0,0,0,.3)`;
            trk.appendChild(knob);
            inp.onchange = () => {
                const v = inp.checked;
                trk.style.background = v ? '#6f42c1' : '#ccc';
                knob.style.left = v ? '23px' : '3px';
                _setEnabled(entry.id, v);
                if (!v) entry.onUndo?.();
                _rebuild();
            };
            swLbl.append(inp, trk); row.append(name, swLbl); box.appendChild(row);
        });

        box.appendChild(mkHr());
        const hint = mk('div'); hint.textContent = STR.orderHint;
        hint.style.cssText = 'font-size:11px;color:#999;padding:4px 0 6px';
        box.appendChild(hint);
    }

    ov.onclick = e => { if (e.target === ov) ov.remove(); };
    ov.appendChild(box); document.body.appendChild(ov);
}


// ─── CUSTOMIZE (LANGUAGE IMPORT/EXPORT) ──────────────────────────────────────
function _openCustomize(parentOv) {
    const LC_KEYS = {
        helper:        'ppc_lc_helper',
        social:        'ppc_lc_social',
        social_mobile: 'ppc_lc_social_mobile',
        depot:         'ppc_lc_depot',
        missionaid:    'ppc_lc_missionaid',
    };
    const hasCustom = Object.values(LC_KEYS).some(k => localStorage.getItem(k));

    const ov = document.createElement('div');
    ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:100001;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box';
    const box = document.createElement('div');
    box.style.cssText = 'background:#fff;border-radius:14px;padding:20px;width:100%;max-width:480px;max-height:90vh;overflow-y:auto;box-sizing:border-box;position:relative;font-family:inherit';

    // Header
    const hdr = document.createElement('div');
    hdr.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:16px';
    const title = document.createElement('span');
    title.textContent = '🌍 Customize Language';
    title.style.cssText = 'font-weight:700;font-size:16px';
    const xBtn = document.createElement('button');
    xBtn.textContent = '✕'; xBtn.type = 'button';
    xBtn.style.cssText = 'background:none;border:none;font-size:20px;cursor:pointer;color:#aaa;padding:0';
    xBtn.onclick = () => ov.remove();
    hdr.append(title, xBtn);
    box.appendChild(hdr);

    // Active status
    if (hasCustom) {
        const badge = document.createElement('div');
        badge.textContent = '✅ Custom language active';
        badge.style.cssText = 'background:#d4edda;color:#155724;border-radius:6px;padding:7px 12px;font-size:12px;font-weight:600;margin-bottom:12px';
        box.appendChild(badge);
    }

    // Gemini tip
    const tip = document.createElement('div');
    tip.style.cssText = 'background:#e8f4fd;border-radius:8px;padding:10px 12px;font-size:12px;color:#1a5276;margin-bottom:14px;line-height:1.5';
    tip.innerHTML = '💡 <strong>Best results:</strong> Use <a href="https://gemini.google.com" target="_blank" style="color:#1a5276;font-weight:700">Google Gemini</a> for translation — it handles JSON format and emoji preservation reliably.';
    box.appendChild(tip);

    const mkHr = () => { const hr = document.createElement('hr'); hr.style.cssText = 'border:none;border-top:1px solid #eee;margin:14px 0'; return hr; };

    // ── EXPORT ──────────────────────────────────────────────────────────────
    box.appendChild(Object.assign(document.createElement('div'), {
        textContent: '📤 Export',
        style: 'font-weight:700;font-size:13px;margin-bottom:6px'
    }));
    const expHint = document.createElement('div');
    expHint.textContent = 'Copies the EN strings from all connected scripts. Paste into Gemini to translate.';
    expHint.style.cssText = 'font-size:11px;color:#666;margin-bottom:8px';
    box.appendChild(expHint);

    const expBtn = document.createElement('button');
    expBtn.textContent = '📋 Copy Export JSON + Prompt';
    expBtn.type = 'button';
    expBtn.style.cssText = 'width:100%;padding:10px;background:#0d6efd;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;font-family:inherit';
    expBtn.onclick = () => {
        const collected = {};
        _registry.forEach(entry => {
            if (entry.strings && Object.keys(entry.strings).length) {
                collected[entry.id] = entry.strings;
            }
        });
        if (!Object.keys(collected).length) {
            expBtn.textContent = '⚠️ No scripts registered yet — navigate first';
            setTimeout(() => expBtn.textContent = '📋 Copy Export JSON + Prompt', 2500);
            return;
        }
        const promptText =
            'Translate the JSON values below to the target language.\n' +
            'Rules: keep all emojis, {n}, %s and \\n exactly as-is. Return ONLY valid JSON, no markdown, no explanation.\n\n' +
            'Target language: [ENTER TARGET LANGUAGE HERE]\n\n' +
            JSON.stringify(collected, null, 2);
        const ta = document.createElement('textarea');
        ta.value = promptText;
        document.body.appendChild(ta); ta.select(); document.execCommand('copy');
        document.body.removeChild(ta);
        navigator.clipboard?.writeText(promptText).catch(() => {});
        expBtn.textContent = '✅ Copied!';
        setTimeout(() => expBtn.textContent = '📋 Copy Export JSON + Prompt', 2500);
    };
    box.appendChild(expBtn);

    box.appendChild(mkHr());

    // ── IMPORT ──────────────────────────────────────────────────────────────
    box.appendChild(Object.assign(document.createElement('div'), {
        textContent: '📥 Import',
        style: 'font-weight:700;font-size:13px;margin-bottom:6px'
    }));
    const impHint = document.createElement('div');
    impHint.textContent = 'Paste the translated JSON returned by Gemini (or any AI). Accepts full object or partial.';
    impHint.style.cssText = 'font-size:11px;color:#666;margin-bottom:8px';
    box.appendChild(impHint);

    const impTa = document.createElement('textarea');
    impTa.placeholder = '{ "helper": { "save": "Speichern", ... }, "social": { ... }, ... }';
    impTa.style.cssText = 'width:100%;height:160px;font-size:11px;font-family:monospace;padding:8px;border:1px solid #ddd;border-radius:6px;box-sizing:border-box;resize:vertical;margin-bottom:8px';
    box.appendChild(impTa);

    const errDiv = document.createElement('div');
    errDiv.style.cssText = 'font-size:12px;min-height:18px;margin-bottom:8px';
    box.appendChild(errDiv);

    const applyBtn = document.createElement('button');
    applyBtn.textContent = '✅ Apply & Reload';
    applyBtn.type = 'button';
    applyBtn.style.cssText = 'width:100%;padding:10px;background:#198754;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;font-family:inherit;margin-bottom:8px';
    applyBtn.onclick = () => {
        try {
            const raw = impTa.value.trim();
            if (!raw) { errDiv.style.color='#dc3545'; errDiv.textContent='Paste translated JSON first.'; return; }
            const data = JSON.parse(raw);
            let applied = 0;
            Object.entries(LC_KEYS).forEach(([id, lsKey]) => {
                if (data[id] && typeof data[id] === 'object') {
                    localStorage.setItem(lsKey, JSON.stringify(data[id]));
                    applied++;
                }
            });
            if (!applied) { errDiv.style.color='#dc3545'; errDiv.textContent='No matching script keys found (helper, social, social_mobile, depot, missionaid).'; return; }
            errDiv.style.color = '#198754';
            errDiv.textContent = '✅ Applied to ' + applied + ' script(s) — reloading...';
            setTimeout(() => { ov.remove(); if (parentOv) parentOv.remove(); location.reload(); }, 1200);
        } catch (err) {
            errDiv.style.color = '#dc3545';
            errDiv.textContent = '❌ Invalid JSON: ' + err.message;
        }
    };
    box.appendChild(applyBtn);

    box.appendChild(mkHr());

    // ── RESET ───────────────────────────────────────────────────────────────
    const resetBtn = document.createElement('button');
    resetBtn.textContent = '🗑️ Reset to Default Language';
    resetBtn.type = 'button';
    resetBtn.style.cssText = 'width:100%;padding:8px;background:none;color:#dc3545;border:1px solid #dc3545;border-radius:8px;cursor:pointer;font-size:12px;font-family:inherit';
    resetBtn.onclick = () => {
        if (!confirm('Remove all custom language data and restore defaults?')) return;
        Object.values(LC_KEYS).forEach(k => localStorage.removeItem(k));
        ov.remove(); if (parentOv) parentOv.remove(); location.reload();
    };
    if (hasCustom) box.appendChild(resetBtn);

    ov.onclick = e => { if (e.target === ov) ov.remove(); };
    ov.appendChild(box);
    document.body.appendChild(ov);
}

// ─── APPLY COLORS (live) ─────────────────────────────────────────────────────
// [FIX] Removed _applyColors wrapper that called _rebuild() causing double-rebuild loop
const _applyColorsD = (() => { let _t; return () => { clearTimeout(_t); _t = setTimeout(_rebuild, 300); }; })();

// ─── PUBLIC API ───────────────────────────────────────────────────────────────
unsafeWindow.PopControl = {
    register({ id, icon, label, buttons, strings, onUndo }) {
        if (_registry.some(r => r.id === id)) return;
        _registry.push({ id, icon: icon || '🔌', label: label || id, buttons: buttons || [], strings: strings || {}, onUndo });
        _rebuild();
    },
    unregister(id) {
        const i = _registry.findIndex(r => r.id === id);
        if (i < 0) return;
        const [entry] = _registry.splice(i, 1);
        entry.onUndo?.();
        _rebuild();
    },
    isEnabled: _isEnabled,
    getLang:   () => LANG,
};

// ─── ALT+P KISAYOL ──────────────────────────────────────────────────────────
document.addEventListener('keydown', function(e) {
    if (e.altKey && (e.key === 'p' || e.key === 'P')) {
        e.preventDefault();
        const bar = document.getElementById('ppc-bar');
        const fab = document.getElementById('ppc-fab');
        if (!bar) { _setCollapsed(!_collapsed); return; }                      // [FIX] works even before bar exists
        _setCollapsed(!_collapsed);
        bar.style.display = _collapsed ? 'none' : '';
        if (fab) {
            fab.style.display = _collapsed ? 'flex' : 'none';
            if (_collapsed) _positionFab(fab);
        }
    }
});

// ─── INIT ────────────────────────────────────────────────────────────────────
(function _init() {
    const go = () => _rebuild();
    if (document.getElementById('ppm-main') || document.getElementById('ppm-footer')) { go(); return; }
    const obs = new MutationObserver(() => {
        if (document.getElementById('ppm-main') || document.getElementById('ppm-footer')) { obs.disconnect(); go(); }
    });
    obs.observe(document.body, { childList: true, subtree: true });
    setTimeout(() => { obs.disconnect(); go(); }, 4000);
})();

})();