☰

πŸ› οΈ PopControl

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

TendrΓ‘s que instalar una extensiΓ³n para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

TendrΓ‘s que instalar una extensiΓ³n como Tampermonkey o Violentmonkey para instalar este script.

NecesitarΓ‘s instalar una extensiΓ³n como Tampermonkey o Userscripts para instalar este script.

TendrΓ‘s que instalar una extensiΓ³n como Tampermonkey antes de poder instalar este script.

NecesitarΓ‘s instalar una extensiΓ³n para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, dΓ©jame instalarlo)

TendrΓ‘s que instalar una extensiΓ³n como Stylus antes de poder instalar este script.

TendrΓ‘s que instalar una extensiΓ³n como Stylus antes de poder instalar este script.

TendrΓ‘s que instalar una extensiΓ³n como Stylus antes de poder instalar este script.

Para poder instalar esto tendrΓ‘s que instalar primero una extensiΓ³n de estilos de usuario.

Para poder instalar esto tendrΓ‘s que instalar primero una extensiΓ³n de estilos de usuario.

Para poder instalar esto tendrΓ‘s que instalar primero una extensiΓ³n de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, dΓ©jame instalarlo)

// ==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'),
    colorHint:   _D('🎨 Renk SeΓ§ici β€” Δ°stediğin rengi buradan seΓ§ebilirsin', '🎨 Color Picker β€” Pick any custom color here', '🎨 Seletor de Cor β€” Escolha qualquer cor aqui'),
    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  => { const v = JSON.stringify(o); GM_setValue('ppc_enabled', v); try { localStorage.setItem('ppc_enabled', v); } catch {} };
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 = false;

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);
}

// ─── BAR ─────────────────────────────────────────────────────────────────────
let _dragId   = null;
let _touchDrg = 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;  // fixed size for all modes

    // ── 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 = () => {
        _collapsed = false;
        _rebuild();
    };
    document.body.appendChild(fab);

    // ── BAR ──────────────────────────────────────────────────────────────────
    const bar = mk('div'); bar.id = 'ppc-bar';
    bar.style.display = _collapsed ? 'none' : '';

    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:hidden;overflow-y:auto;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`,
            `overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch`,
        ].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.style.cssText = `font-size:9px;color:${FG};font-weight:700;white-space:normal;word-break:break-word;text-align:center;line-height:1.15;pointer-events:none;max-width:${BTN_W}px`;
        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 = _sortedRegistry().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;
        const btnSel = '[data-ppc-drag]';
        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();
            ghost = dragEl.cloneNode(true);
            ghost.style.cssText = `position:fixed;left:${r.left}px;top:${r.top}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.transform = `translate(${t.clientX - startX}px,${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 = _sortedRegistry().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 = () => {
        _collapsed = true;
        _rebuild();
    };

    // ── RENDER ENTRIES ────────────────────────────────────────────────────────
    let hasAny = false;
    _sortedRegistry().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;
    });

    // ── SETTINGS + COLLAPSE ───────────────────────────────────────────────────
    if (hasAny) bar.appendChild(mkSep());
    const sBtn = mkBtn('βš™οΈ', 'PopControl', _openSettings, false);
    if (vert) { sBtn.style.marginTop = 'auto'; bar.appendChild(sBtn); bar.appendChild(mkSep()); bar.appendChild(colBtn); }
    else { sBtn.style.marginLeft = 'auto'; bar.appendChild(sBtn); bar.appendChild(colBtn); }

    document.body.appendChild(bar);

    // Attach touch drag to bar
    _attachTouchDrag(bar);

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

    // Apply collapsed state
    if (_collapsed) { bar.style.display = 'none'; fab.style.display = 'flex'; }
}

// ─── 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
    const mkSwatches = (presets, current, onPick) => {
        const wrap = mk('div'); wrap.style.cssText = 'display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin:6px 0 4px';
        let sel = current;
        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.title = c;
            sw.onclick = () => {
                sel = c;
                wrap.querySelectorAll('button[data-sw]').forEach(b => b.style.borderColor = 'transparent');
                sw.style.borderColor = '#333';
                ci.value = c; onPick(c);
            };
            sw.dataset.sw = '1';
            wrap.appendChild(sw);
        });
        const ci = mk('input'); ci.type = 'color'; ci.value = current;
        ci.style.cssText = 'width:26px;height:22px;padding:1px;border:1px solid #ccc;border-radius:4px;cursor:pointer;margin-left:2px;flex-shrink:0';
        ci.title = STR.colorHint;
        ci.oninput = () => {
            sel = ci.value;
            wrap.querySelectorAll('button[data-sw]').forEach(b => b.style.borderColor = 'transparent');
            onPick(ci.value);
        };
        const ciLbl = mk('span'); ciLbl.textContent = '🎨';
        ciLbl.style.cssText = 'font-size:14px;cursor:default;margin-left:2px;flex-shrink:0';
        ciLbl.title = STR.colorHint;
        wrap.append(ci, ciLbl);
        const outerWrap = mk('div');
        outerWrap.append(wrap);
        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) ─────────────────────────────────────────────────────
function _applyColors() {
    const bar = document.getElementById('ppc-bar');
    const fab = document.getElementById('ppc-fab');
    const BG  = _getColBg();
    const FG  = _getColFg();
    const BDR = '1px solid ' + _shadeHex(BG, -25);
    if (bar) {
        bar.style.background = BG;
        bar.style.border = BDR;
        bar.querySelectorAll('span:last-child').forEach(s => { if (s.style.fontSize === '9px') s.style.color = FG; });
    }
    if (fab) { fab.style.background = BG; fab.style.border = BDR; }
    // Full rebuild to repaint properly
    _rebuild();
}
const _applyColorsD = (() => { let _t; return () => { clearTimeout(_t); _t = setTimeout(_applyColors, 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) return;
        if (_collapsed) {
            // GΓΆster
            _collapsed = false;
            bar.style.display = '';
            if (fab) fab.style.display = 'none';
        } else {
            // Gizle β€” FAB gΓΆsterme, tamamen gizle
            _collapsed = true;
            bar.style.display = 'none';
            if (fab) fab.style.display = 'none';
        }
    }
});

// ─── 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);
})();

})();