🎨 Helper

Popmundo için kapsamlı oyun yardımcısı — görsel iyileştirmeler, envanter & ticaret araçları, müzik ve şehir kısayolları. Masaüstü, Android ve iPhone ile tam uyumlu.

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.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

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            🎨 Helper
// @name:en         🎨 Helper
// @name:pt-BR      🎨 Helper
// @namespace       popmundo.helper
// @version         4.9
// @description     Popmundo için kapsamlı oyun yardımcısı — görsel iyileştirmeler, envanter & ticaret araçları, müzik ve şehir kısayolları. Masaüstü, Android ve iPhone ile tam uyumlu.
// @description:en  Comprehensive Popmundo game assistant — UI enhancements, inventory & trading tools, music and city shortcuts. Fully compatible with desktop, Android and iPhone.
// @description:pt-BR Assistente completo para Popmundo — melhorias visuais, ferramentas de inventário e comércio, música e atalhos de cidade. Compatível com desktop, Android e iPhone.
// @author          luke-james-gibson
// @license         MIT
// @id              9g1a6x
// @match           https://*.popmundo.com/*
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_deleteValue
// @run-at          document-end
// @grant        unsafeWindow
// ==/UserScript==

(function () {
'use strict';

// ─── POPCONTROL DISABLE CHECK ─────────────────────────────────────────────────
try { const _ppc = JSON.parse(localStorage.getItem('ppc_enabled')||'{}'); if (_ppc['helper'] === false) return; } catch {}

// ─── MOBILE DETECTION ────────────────────────────────────────────────────────
const isMobile = window.innerWidth < 768 || 'ontouchstart' in window;

// ─── CSS ──────────────────────────────────────────────────────────────────────
document.head.appendChild(Object.assign(document.createElement('style'), { textContent: `
.tvip-bar{position:fixed;top:0;right:0;z-index:9990;background:#fff;border:1px solid #ddd;border-radius:0 0 0 6px;padding:3px 10px;font-size:11px;display:flex;gap:10px;align-items:center;box-shadow:0 1px 6px rgba(0,0,0,.15)}
.tvip-bar a{font-weight:bold;text-decoration:none;color:inherit;cursor:pointer}
.tvip-hpanel{display:none;position:fixed;top:26px;right:0;z-index:9989;background:#fff;border:1px solid #ddd;border-radius:0 0 0 6px;padding:12px;min-width:240px;max-width:290px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-height:82vh;overflow-y:auto}
.tvip-ov{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:99999;display:flex;align-items:center;justify-content:center}
.tvip-box{background:#fff;border-radius:8px;padding:18px;min-width:300px;max-width:500px;width:90%;box-shadow:0 4px 24px rgba(0,0,0,.35);max-height:80vh;overflow-y:auto}
.tvip-title{font-weight:bold;font-size:14px;margin-bottom:12px}
.tvip-chk{display:flex;align-items:flex-start;gap:6px;margin-bottom:8px;cursor:pointer;font-size:12px;line-height:1.4}
.tvip-chk input{margin-top:2px;flex-shrink:0;cursor:pointer}
.tvip-hr{border:none;border-top:1px solid #e0e0e0;margin:6px 0}
.tvip-sec{font-size:10px;font-weight:bold;color:#888;margin:8px 0 3px;text-transform:uppercase;letter-spacing:.5px}
.tvip-pct{float:left;font-size:9px;pointer-events:none}
.tvip-badge{padding:0 5px;border-radius:10px;font-weight:bold;margin-left:4px;font-size:inherit;display:inline-block}
.tvip-search-wrap{margin-bottom:6px;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.tvip-search-done{display:none}
.tvip-ticket-price{margin-left:8px;color:#d6021e;font-weight:bold}
.tvip-item-id,.tvip-item-page-id{margin-left:6px;color:#777;font-size:11px}
.tvip-tbl-avg{font-weight:bold;background:#f5f5f5}
.tvip-heist-avg{font-weight:bold;background:#f5f5f5}
.tvip-lang-row{display:flex;gap:4px;margin:6px 0}
.tvip-lang-btn{flex:1;padding:3px 4px;border:1px solid #ccc;border-radius:4px;cursor:pointer;font-size:11px;background:#f8f9fa;text-align:center}
.tvip-lang-btn.active{background:#17a2b8;color:#fff;border-color:#17a2b8;font-weight:bold}
.tvip-bulk-panel{background:#f0f0f0;border:1px solid #dcdcdc;border-radius:6px;padding:12px;margin-bottom:16px;font-size:13px}
.tvip-bulk-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;margin-bottom:10px}
.tvip-bulk-field label{display:block;font-weight:bold;font-size:11px;color:#555;margin-bottom:3px}
.tvip-bulk-field input{width:100%;padding:5px 6px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;font-size:12px}
.tvip-bulk-actions{display:flex;gap:6px;margin-bottom:8px}
.tvip-bulk-actions button{flex:1;padding:5px 10px;border-radius:4px;font-weight:bold;font-size:12px;cursor:pointer;border:1px solid #555}
.tvip-bulk-actions button:disabled{opacity:.5;cursor:not-allowed}
.tvip-status{font-size:12px;text-align:center;background:#e9ecef;padding:5px;border-radius:4px;margin-bottom:4px;color:#495057;font-weight:500}
.tvip-spent{font-size:12px;text-align:center;background:#d1fae5;padding:5px;border-radius:4px;color:#065f46;font-weight:500}
.tvip-btn-start{background:linear-gradient(to bottom,#d4edda,#c3e6cb);border-color:#28a745!important}
.tvip-btn-stop{background:linear-gradient(to bottom,#f8d7da,#f5c6cb);border-color:#dc3545!important}
.tvip-send-wrap{margin-top:10px;padding:10px;border-radius:5px}
.tvip-send-running{background:#d4edda;border:2px solid #28a745}
.tvip-send-setup{background:#fff3cd;border:2px solid #ffc107}
.btn-g{padding:5px 14px;background:#218838;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}
.btn-b{padding:5px 14px;background:#17a2b8;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}
.btn-r{padding:5px 14px;background:#e74c3c;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}
.btn-grey{padding:5px 14px;background:#6c757d;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}
.btn-sm{padding:2px 8px!important;font-size:11px!important}
` }));



if (isMobile) document.head.appendChild(Object.assign(document.createElement('style'), { textContent: `
/* ── Touch targets ── */
.tvip-chk{padding:10px 4px;font-size:14px;gap:10px}
.tvip-chk input{width:20px;height:20px;flex-shrink:0}
.tvip-lang-btn{padding:10px 4px;font-size:13px}
.btn-g,.btn-b,.btn-r,.btn-grey{padding:10px 18px;font-size:14px}
.btn-sm{padding:8px 12px!important;font-size:13px!important}
/* ── Sections ── */
.tvip-sec{font-size:12px;margin:12px 0 6px}
.tvip-hr{margin:10px 0}
/* ── Modal ── */
.tvip-box{width:96%!important;max-width:96%!important;padding:16px;max-height:88vh}
.tvip-title{font-size:16px;margin-bottom:14px}
/* ── Bulk panels ── */
.tvip-bulk-grid{grid-template-columns:1fr!important}
.tvip-bulk-field input,.tvip-bulk-field select{font-size:14px;padding:8px 10px}
.tvip-bulk-field label{font-size:13px}
.tvip-bulk-actions button{padding:10px;font-size:14px}
.tvip-status,.tvip-spent{font-size:13px;padding:8px}
/* ── Table search ── */
.tvip-search-wrap input{font-size:14px;padding:7px 10px;width:100%;box-sizing:border-box}
.tvip-search-wrap{flex-wrap:wrap}
/* ── Send wrap ── */
.tvip-send-wrap{padding:14px}
.tvip-send-wrap input[type=number]{font-size:14px;padding:6px 8px}
/* ── City manager ── */
#tvip-bulk-offer-ui h3,#tvip-bulk-accept-ui h3,#tvip-bulk-cancel-ui h3{font-size:15px}
` }));

// ─── 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 mk  = (tag, cls, txt) => { const e = document.createElement(tag); if (cls) e.className = cls; if (txt !== undefined) e.textContent = txt; return e; };
const mkB = (txt, cls, fn)  => Object.assign(mk('button', cls, txt), { onclick: fn, type: 'button' });
const isOn = k => CK.get(k) !== '0';
const guard = (key, ...urls) => isOn(key) && (!urls.length || urls.some(u => location.href.includes(u)));

const mkModal = (title, renderFn) => {
    document.getElementById('tvip-modal')?.remove();
    const ov  = mk('div', 'tvip-ov'); ov.id = 'tvip-modal';
    const box = mk('div', 'tvip-box');
    box.appendChild(mk('div', 'tvip-title', title));
    const cont = mk('div'); box.appendChild(cont);
    const close = () => ov.remove();
    renderFn(cont, close);
    const cb = mkB('✕ ' + s('close'), 'btn-grey', close);
    cb.style.marginTop = '12px';
    box.appendChild(cb);
    ov.onclick = e => { if (e.target === ov) close(); };
    ov.appendChild(box); document.body.appendChild(ov);
};

// ─── LANG ─────────────────────────────────────────────────────────────────────
const LANG = CK.get('ppm_lang') || 'TR';
const _D = (tr, en, pt) => ({ TR: tr, EN: en, PT: pt });

const STR = {
    menuTitle:      _D('🎨 Helper',                    '🎨 Helper',                      '🎨 Helper'),
    save:           _D('✔ Tamam',                      '✔ Save',                         '✔ Salvar'),
    readMe:         _D('📖 Beni Oku',                  '📖 Read Me',                     '📖 Leia-me'),
    langLabel:      _D('Dil',                          'Language',                       'Idioma'),
    backup:         _D('📤 Yedekle',                   '📤 Backup',                      '📤 Exportar'),
    restore:        _D('📥 Geri Yükle',                '📥 Restore',                     '📥 Importar'),
    backupOk:       _D('Yedek alındı.',                'Backup created.',                'Backup criado.'),
    restoreOk:      _D('Geri yüklendi.',               'Restored.',                      'Restaurado.'),
    restoreErr:     _D('Geçersiz dosya.',              'Invalid file.',                  'Arquivo inválido.'),
    restoreQ:       _D('Mevcut veriler ne olsun?',     'What to do with current data?',  'O que fazer com os dados atuais?'),
    mergeLbl:       _D('Birleştir',                    'Merge',                          'Mesclar'),
    replaceLbl:     _D('Üzerine Yaz',                  'Replace',                        'Substituir'),
    cancelLbl:      _D('İptal',                        'Cancel',                         'Cancelar'),
    close:          _D('Kapat',                        'Close',                 'Fechar'),
    // sections
    secGeneral:     _D('GENEL GÖRÜNÜM',                'GENERAL',               'GERAL'),
    secTable:       _D('TABLO',                        'TABLE',                 'TABELA'),
    secItems:       _D('EŞYA',                         'ITEMS',                 'ITENS'),
    secMusic:       _D('MÜZİK',                        'MUSIC',                 'MÚSICA'),
    secNav:         _D('GEZİNME',                      'NAVIGATION',            'NAVEGAÇÃO'),
    // features
    pb:             _D('📊 Barlarda yüzde göster',     '📊 Show % on bars',     '📊 Mostrar % nas barras'),
    alignLeft:      _D('⬅️ Sayfayı sola yasla',                  '⬅️ Align page to left',                  '⬅️ Alinhar página à esquerda'),
    tableTools:     _D('🔍 Tablolarda sıralama ve arama',         '🔍 Table sort & search',                 '🔍 Ordenar e buscar tabelas'),
    itemId:         _D('🏷️ Eşya ID — Eşya linklerinin yanına ID numarasını ekler',
                        '🏷️ Item ID — Appends ID number next to item links',
                        '🏷️ ID do Item — Adiciona o número de ID ao lado dos links de item'),
    colorScoring:   _D('🌈 Renkli Puanlama Sistemi — Şöhret linklerinin yanına renkli rozet ekler (0-26)',
                        '🌈 Coloured Scoring System — Adds a coloured badge next to fame links (0-26)',
                        '🌈 Sistema de Pontuação Colorida — Adiciona emblema colorido ao lado dos links de fama'),
    autoDelivery:   _D('✅ Otomatik Teslimat — Teslim onay kutularını otomatik işaretler',
                        '✅ Auto Delivery — Automatically checks delivery confirmation checkboxes',
                        '✅ Entrega Automática — Marca automaticamente as caixas de confirmação de entrega'),
    ticketPricer:   _D('🎟️ Bilet Fiyatlandırıcı — Sanatçı davet sayfasında önerilen bilet fiyatını gösterir',
                        '🎟️ Ticket Pricer — Shows recommended ticket price on artist invite page',
                        '🎟️ Precificador de Ingressos — Mostra o preço recomendado na página de convite de artista'),
    imageCtrl:      _D('🖼️ Yavaş/Yüklenmez Resim Kontrolü — 5sn timeout; placeholder + tıklayınca yeniden dene',
                        '🖼️ Slow/Broken Image Control — 5s timeout; placeholder + click to retry',
                        '🖼️ Controle de Imagens Lentas — Timeout 5s; placeholder + clique para tentar novamente'),
    tableAvg:       _D('📈 Tablolara ortalama satırı ekle',       '📈 Add average row to tables',           '📈 Adicionar linha de média às tabelas'),
    itemFilters:    _D('🎒 Sadece Alınabilecek Eşyaları Göster', '🎒 Show Only Takeable Items', '🎒 Mostrar Apenas Itens Disponíveis'),
    jamHelper:      _D('🎸 Jam için eksik şarkıları seç',         '🎸 Select incomplete songs for Jam',     '🎸 Selecionar músicas incompletas para Jam'),
    repertoireF:    _D('🎵 Repertuarı kategoriye göre filtrele',  '🎵 Filter repertoire by category',       '🎵 Filtrar repertório por categoria'),
    sendItems:      _D('📦 Aynı eşyayı sırayla otomatik gönder', '📦 Auto-send same item multiple times',  '📦 Enviar o mesmo item várias vezes'),
    bulkOffer:      _D('🏷️ Eşyaları toplu fiyatla teklif et',   '🏷️ Offer items in bulk at set price',   '🏷️ Ofertar itens em massa com preço fixo'),
    bulkAccept:     _D('🛒 Gelen teklifleri otomatik kabul et',   '🛒 Auto-accept incoming offers',         '🛒 Aceitar ofertas recebidas automaticamente'),
    cityShortcuts:  _D('🏙️ Şehirde duş evi ve patikaları göster','🏙️ Show shower house & paths in city',  '🏙️ Mostrar casa de banho e trilhas na cidade'),

    bbMinimize:     _D('—',                                       '—',                                      '—'),
    bbRestore:      _D('🎮',                                      '🎮',                                     '🎮'),
    // table search
    psPlh:          _D('Tabloda ara...',               'Search table...',                'Buscar na tabela...'),
    psCount:        _D('sonuç',                        'results',                        'resultados'),
    tdConfirm:      _D('Bu tablonun arama kutusunu gizlemek istiyor musun?', 'Hide the search box for this table?', 'Ocultar a caixa de busca desta tabela?'),
    tdHide:         _D('Aramayı gizle',                'Hide search',                    'Ocultar busca'),
    tdClear:        _D('Gizli Tabloları Sıfırla',      'Reset Hidden Tables',            'Redefinir Tabelas Ocultas'),
    // item filters
    btnShowAll:     _D('👁 Tüm Eşyaları Göster',       '👁 Show All Items',               '👁 Mostrar Todos os Itens'),
    btnOnlyTake:    _D('🔒 Sadece Alınabilecekler',    '🔒 Only Takeable',               '🔒 Só os Pegáveis'),
    resetOffered:   _D('🗑️ Teklif Listesini Sıfırla', '🗑️ Reset Offer List',            '🗑️ Redefinir Lista'),
    confirmReset:   _D('Teklif listesi sıfırlansın mı?', 'Reset offered items list?',   'Redefinir lista de itens ofertados?'),
    resetDone:      _D('Sıfırlandı.',                  'Done.',                          'Redefinido.'),
    hideOfferedChk: _D('Teklif edilenleri gizle',      'Hide offered items',             'Ocultar itens ofertados'),
    // jam
    jamBtn:         _D('⏱ %100 Olmayan Jam',           '⏱ Jam <100%',                   '⏱ Jam <100%'),
    // repertoire
    repAll:         _D('Tüm Şarkılar',                 'All Songs',                      'Todas as Músicas'),
    repMarket:      _D('Pazar Şarkıları',              'Market Songs',                   'Músicas do Mercado'),
    repNoMarket:    _D('Pazar Dışı',                   'Non-Market',                     'Fora do Mercado'),
    repJam:         _D('Jam Şarkıları',                'Jam Songs',                      'Músicas de Jam'),
    repSetlist:     _D('Setlist Şarkıları',            'Setlist Songs',                  'Músicas do Setlist'),
    repSecret:      _D('Gizli Şarkılar',               'Secret Songs',                   'Músicas Secretas'),
    repTitle:       _D('🎵 Repertuar Filtresi',        '🎵 Repertoire Filter',           '🎵 Filtro de Repertório'),
    // quick send
    siWarning:      _D('⚠️ Birden fazla aynı eşya seçin', '⚠️ Select an item with multiple copies', '⚠️ Selecione um item com múltiplas cópias'),
    siCount:        _D('Kaç adet?',                    'How many?',                      'Quantas?'),
    siSend:         _D('🔄 Gönder',                    '🔄 Send',                        '🔄 Enviar'),
    siCancel:       _D('❌ İptal',                      '❌ Cancel',                      '❌ Cancelar'),
    siStop:         _D('⏹ Durdur',                     '⏹ Stop',                         '⏹ Parar'),
    siStopping:     _D('Durduruluyor...',               'Stopping...',                    'Parando...'),
    siDone:         _D('✅ Tamamlandı!',               '✅ Done!',                        '✅ Concluído!'),
    // bulk offer
    boTitle:        _D('Toplu Teklif',                 'Bulk Offer',                     'Oferta em Massa'),
    boItemName:     _D('Eşya adı (başlangıç):',        'Item name (prefix):',            'Nome do item (prefixo):'),
    boQty:          _D('Adet:',                        'Quantity:',                      'Quantidade:'),
    boPrice:        _D('Fiyat (M$):',                  'Price (M$):',                    'Preço (M$):'),
    boStart:        _D('▶ Teklif Et',                  '▶ Offer',                        '▶ Ofertar'),
    boStop:         _D('■ Durdur',                     '■ Stop',                         '■ Parar'),
    boReady:        _D('Hazır.',                        'Ready.',                         'Pronto.'),
    boDone:         _D('Tüm teklifler tamamlandı!',    'All offers completed!',          'Todas as ofertas concluídas!'),
    boStopped:      _D('Kullanıcı tarafından durduruldu.', 'Stopped by user.',           'Parado pelo usuário.'),
    boResumed:      _D('Yeniden yüklendi, devam ediliyor...', 'Reloaded, resuming...',   'Recarregado, retomando...'),
    boCritErr:      _D('Kritik hata: Sayfa öğeleri kayboldu.', 'Critical error: Page elements gone.', 'Erro crítico: Elementos da página desapareceram.'),
    boErrName:      _D('Hata: Eşya adı girin.',        'Error: Enter item name.',        'Erro: Digite o nome do item.'),
    boErrQty:       _D('Hata: Geçersiz adet.',         'Error: Invalid quantity.',       'Erro: Quantidade inválida.'),
    boErrPrice:     _D('Hata: Geçersiz fiyat.',        'Error: Invalid price.',          'Erro: Preço inválido.'),
    // bulk accept
    baTitle:        _D('Toplu Kabul',                  'Bulk Accept',                    'Aceitar em Massa'),
    baItemName:     _D('Eşya Adı (Opsiyonel):',        'Item Name (Optional):',          'Nome do Item (Opcional):'),
    baItemPlh:      _D('Tümü için boş bırakın',        'Leave blank for all',            'Deixe em branco para todos'),
    baMaxPrice:     _D('Maks. Fiyat (M$):',            'Max Price (M$):',                'Preço Máximo (M$):'),
    baStart:        _D('▶ Kabul Et',                   '▶ Accept',                       '▶ Aceitar'),
    baStop:         _D('■ Durdur',                     '■ Stop',                         '■ Parar'),
    baReady:        _D('Hazır.',                        'Ready.',                         'Pronto.'),
    baNoSection:    _D('Teklif bölümü bulunamadı.',    'Offer section not found.',       'Seção de ofertas não encontrada.'),
    baResumed:      _D('Yeniden yüklendi, devam ediliyor...', 'Reloaded, resuming...',   'Recarregado, retomando...'),
    baErrPrice:     _D('Hata: Geçersiz fiyat.',        'Error: Invalid price.',          'Erro: Preço inválido.'),
    baFree:         _D('Bedava',                        'Free',                           'Grátis'),
    // bulk cancel
    bcTitle:        _D('Toplu İptal',                  'Bulk Cancel',                    'Cancelar em Massa'),
    bcItemName:     _D('Eşya Adı (Opsiyonel):',        'Item Name (Optional):',          'Nome do Item (Opcional):'),
    bcFilter:       _D('Filtre:',                      'Filter:',                        'Filtro:'),
    bcFilterAll:    _D('Tümü',                         'All',                            'Todos'),
    bcFilterFree:   _D('Sadece Bedava',                'Free Only',                      'Só Grátis'),
    bcFilterPaid:   _D('Sadece Ücretli',               'Paid Only',                      'Só Pagos'),
    bcStart:        _D('▶ İptal Et',                   '▶ Cancel',                       '▶ Cancelar'),
    bcStop:         _D('■ Durdur',                     '■ Stop',                         '■ Parar'),
    bcReady:        _D('Hazır.',                        'Ready.',                         'Pronto.'),
    bcNoSection:    _D('Teklif bölümü bulunamadı.',    'Offer section not found.',       'Seção não encontrada.'),
    // city shortcuts
    csShower:       _D('Duş Evi',                      'Shower House',                   'Casa de Banho'),
    csPath:         _D('Patika',                       'Path',                           'Trilha'),
    csGoShower:     _D('Duş evine git',                'Go to shower house',             'Ir para casa de banho'),
    csGoPath:       _D('Patikaya git',                 'Go to path',                     'Ir para trilha'),
    csMin:          _D('Dakika',                       'Minutes',                        'Minutos'),
    csOther:        _D('Diğer Mekan',          'Other Location',      'Outro Local'),
    cityMgr:        _D('Şehir Kısayolları Yönet','Manage City Shortcuts','Gerenciar Atalhos'),
    cityMgrReset:   _D('↩ Sıfırla',            '↩ Reset',             '↩ Redefinir'),
    cityMgrResetQ:  _D('Bu şehir varsayılana dönsün mü?','Reset this city to default?','Redefinir esta cidade?'),
    cityMgrAdd:     _D('Mekan Ekle',           'Add Location',        'Adicionar Local'),
    cityMgrAddBtn:  _D('Ekle',                 'Add',                 'Adicionar'),
    cityMgrName:    _D('Mekan Adı',            'Location Name',       'Nome do Local'),
    cityMgrCustom:  _D('Özelleştirilmiş',      'Customized',          'Personalizado'),
    cityMgrDelQ:    _D('Bu mekan silinsin mi?','Delete this location?','Excluir este local?'),
    cityMgrEmpty:   _D('Mekan yok.',           'No locations.',       'Sem locais.'),
    // table avg
    taAvg:          _D('🌍 Ortalama',                  '🌍 Average',                     '🌍 Média'),
    taDiscAvg:      _D('🎯 Keşif Ortalaması:',         '🎯 Heist Avg:',                  '🎯 Média de Descoberta:'),
};

const _clHelper = (() => { try { const v = localStorage.getItem('ppc_lc_helper'); return v ? JSON.parse(v) : null; } catch { return null; } })();
const s = k => {
    if (_clHelper && _clHelper[k]) return _clHelper[k];
    const v = STR[k];
    if (!v) return k;
    return v[LANG] ?? v['TR'] ?? k;
};

// Dynamic string helpers
const sf = {
    boNoItem:    n    => ({ TR:`"${n}" ile başlayan eşya bulunamadı.`,          EN:`No items starting with "${n}" found.`,                       PT:`Nenhum item começando com "${n}" encontrado.`                    }[LANG]),
    boFound:     (t,q,p) => ({ TR:`${t} eşya bulundu. ${q} adet ${p}M$ tekliflenecek...`,  EN:`Found ${t}. Offering ${q} at ${p}M$...`,           PT:`${t} itens. Ofertando ${q} por ${p}M$...`                        }[LANG]),
    boOfferring: (c,t,n) => ({ TR:`Teklif ${c}/${t}: '${n}'...`,                           EN:`Offering ${c}/${t}: '${n}'...`,                    PT:`Ofertando ${c}/${t}: '${n}'...`                                  }[LANG]),
    boSkip:      n    => ({ TR:`Hata: '${n}' seçilemedi, atlandı.`,              EN:`Error: Could not select '${n}', skipping.`,                  PT:`Erro: Não foi possível selecionar '${n}', pulando.`               }[LANG]),
    siStopped:   c    => ({ TR:`⏹ Durduruldu. Gönderilen: ${c}`,                 EN:`⏹ Stopped. Sent: ${c}`,                                      PT:`⏹ Parado. Enviados: ${c}`                                        }[LANG]),
    siConfirm:   (n,c) => ({ TR:`"${n}" ${c} kez gönderilecek. Onayla?`,        EN:`Send "${n}" ${c} times. Confirm?`,                           PT:`Enviar "${n}" ${c} vezes. Confirmar?`                             }[LANG]),
    siSending:   (c,t,n) => ({ TR:`🔄 Gönderiliyor: ${c}/${t} — ${n}`,          EN:`🔄 Sending: ${c}/${t} — ${n}`,                               PT:`🔄 Enviando: ${c}/${t} — ${n}`                                   }[LANG]),
    baSpent:     n    => ({ TR:`Toplam Harcama: ${n} M$`,                        EN:`Total Spent: ${n} M$`,                                       PT:`Total Gasto: ${n} M$`                                            }[LANG]),
    baInit:      (n,p) => ({ TR:`Başlatılıyor... "${n||'tümü'}" maks. ${p}M$`,   EN:`Starting... "${n||'all'}" up to ${p}M$`,                     PT:`Iniciando... "${n||'todos'}" até ${p}M$`                          }[LANG]),
    baAccepting: (i,n,p) => ({ TR:`Kabul ediliyor #${i}: '${n}' (${p})...`,     EN:`Accepting #${i}: '${n}' (${p})...`,                          PT:`Aceitando #${i}: '${n}' (${p})...`                               }[LANG]),
    baDone:      (c,sp) => ({ TR:`Tamamlandı! Kabul: ${c}. Harcama: ${sp}M$.`,  EN:`Done! Accepted: ${c}. Spent: ${sp}M$.`,                      PT:`Concluído! Aceitos: ${c}. Gasto: ${sp}M$.`                        }[LANG]),
    baStopped:   (c,sp) => ({ TR:`Durduruldu. Kabul: ${c}. Harcama: ${sp}M$.`,  EN:`Stopped. Accepted: ${c}. Spent: ${sp}M$.`,                   PT:`Parado. Aceitos: ${c}. Gasto: ${sp}M$.`                          }[LANG]),
    baNoMatch:   (c,sp) => ({ TR:`Eşleşen teklif yok. Kabul: ${c}. Harcama: ${sp}M$.`, EN:`No more matches. Accepted: ${c}. Spent: ${sp}M$.`,    PT:`Sem correspondências. Aceitos: ${c}. Gasto: ${sp}M$.`            }[LANG]),
    bcCancelling:(i,n)  => ({ TR:`İptal ediliyor #${i}: '${n}'...`,                    EN:`Cancelling #${i}: '${n}'...`,                          PT:`Cancelando #${i}: '${n}'...`                                     }[LANG]),
    bcDone:      c      => ({ TR:`Tamamlandı! İptal edilen: ${c}.`,                    EN:`Done! Cancelled: ${c}.`,                               PT:`Concluído! Cancelados: ${c}.`                                    }[LANG]),
    bcStopped:   c      => ({ TR:`Durduruldu. İptal edilen: ${c}.`,                    EN:`Stopped. Cancelled: ${c}.`,                            PT:`Parado. Cancelados: ${c}.`                                       }[LANG]),
    bcNoMatch:   c      => ({ TR:`Eşleşen teklif yok. İptal edilen: ${c}.`,            EN:`No more matches. Cancelled: ${c}.`,                    PT:`Sem correspondências. Cancelados: ${c}.`                         }[LANG]),
};

// ─── SETTINGS KEYS ────────────────────────────────────────────────────────────
const K = {
    pb:           'tvip_feat_pb',
    alignLeft:    'tvip_align_left',
    imageCtrl:    'tvip_feat_imgctrl',
    tableTools:   'tvip_feat_ttls',
    itemId:       'tvip_feat_itemid',
    colorScoring: 'tvip_feat_colorsc',
    autoDelivery: 'tvip_feat_autodlv',
    ticketPricer: 'tvip_feat_ticket',
    tableAvg:     'tvip_feat_tavg',
    itemFilters:  'tvip_feat_itmf',
    jamHelper:    'tvip_feat_jam',
    repertoireF:  'tvip_feat_repf',
    sendItems:    'tvip_feat_snd',
    bulkOffer:    'tvip_feat_bkoffer',
    bulkAccept:   'tvip_feat_bkaccept',
    cityShortcuts:'tvip_feat_citysc',
    popControl:   'tvip_feat_popcontrol',
    onlyYoursSt:  'tvip_oy_state',
    hideOffBox:   'tvip_hideoff_chk',
};

// ─── DATA KEYS (GM_setValue) ──────────────────────────────────────────────────
const DK = {
    TD:            'tvip_table_deny',
    OFFERED:       'tvip_offered_list',
    BO_ITEMS:      'tvip_bo_items',
    BO_RUNNING:    'tvip_bo_running',
    BO_PRICE:      'tvip_bo_price',
    BO_COUNT:      'tvip_bo_count',
    BO_TOTAL:      'tvip_bo_total',
    BA_RUNNING:    'tvip_ba_running',
    BA_MAX_PRICE:  'tvip_ba_max_price',
    BA_ITEM_NAME:  'tvip_ba_item_name',
    BA_COUNT:      'tvip_ba_count',
    BA_SPENT:      'tvip_ba_spent',
    SI_QUEUE:      'tvip_si_queue',
    SI_NAME:       'tvip_si_name',
    SI_TOTAL:      'tvip_si_total',
    SI_STOP:       'tvip_si_stop',
    CITY_CUSTOM:   'tvip_city_custom',
    BC_RUNNING:    'tvip_bc_running',
    BC_ITEM_NAME:  'tvip_bc_item_name',
    BC_FILTER:     'tvip_bc_filter',
    BC_COUNT:      'tvip_bc_count',
};

// ─── RAINBOW (scoring badges) ─────────────────────────────────────────────────
const RAINBOW = [
    ['#ff0000','#fff'],['#ff0036','#fff'],['#ff006c','#fff'],['#ff00a2','#fff'],
    ['#ff00d8','#fff'],['#f000ff','#fff'],['#ba00ff','#fff'],['#8400ff','#fff'],
    ['#4e00ff','#fff'],['#1900ff','#fff'],['#001dff','#fff'],['#0053ff','#fff'],
    ['#0089ff','#fff'],['#00bfff','#fff'],['#00f5ff','#000'],['#00ffd3','#000'],
    ['#00ff9d','#000'],['#00ff67','#000'],['#00ff31','#000'],['#05ff00','#000'],
    ['#3bff00','#000'],['#71ff00','#000'],['#a7ff00','#000'],['#ddff00','#000'],
    ['#ffeb00','#000'],['#ffb500','#000'],['#ff8000','#000']
];

// ─── TABLE DENY ───────────────────────────────────────────────────────────────
const getTableDeny  = ()         => { try { return JSON.parse(GM_getValue(DK.TD, '[]')); } catch { return []; } };
const addTableDeny  = (path, i)  => { const l = getTableDeny(), k = `${path}::${i}`; if (!l.includes(k)) { l.push(k); GM_setValue(DK.TD, JSON.stringify(l)); } };
const clearTableDeny = ()        => GM_setValue(DK.TD, '[]');

// ─── BACKUP & RESTORE ─────────────────────────────────────────────────────────
const DB_KEYS_GM  = [DK.TD, DK.OFFERED, DK.CITY_CUSTOM, DK.BA_RUNNING, DK.BA_MAX_PRICE, DK.BA_ITEM_NAME, DK.BA_COUNT, DK.BA_SPENT, DK.BC_RUNNING, DK.BC_ITEM_NAME, DK.BC_FILTER, DK.BC_COUNT];
const DB_KEYS_CK  = Object.values(K);

const dbExport = () => {
    const data = { v: 1, script: 'helper', cookies: {}, gm: {} };
    DB_KEYS_CK.forEach(k => { const v = CK.get(k); if (v !== null) data.cookies[k] = v; });
    DB_KEYS_GM.forEach(k => { const v = GM_getValue(k, null); if (v !== null) data.gm[k] = v; });
    data.cookies['ppm_lang'] = CK.get('ppm_lang') || 'TR';
    const d = new Date(), p = n => String(n).padStart(2,'0');
    const a = document.createElement('a');
    a.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }));
    a.download = `ppm-helper-${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}.json`;
    a.click();
};

const dbImport = () => {
    const inp = Object.assign(document.createElement('input'), { type: 'file', accept: '.json' });
    inp.onchange = () => {
        const f = inp.files[0]; if (!f) return;
        const reader = new FileReader();
        reader.onload = ev => {
            let data; try { data = JSON.parse(ev.target.result); } catch { alert(s('restoreErr')); return; }
            mkModal(s('restore'), (cont, close) => {
                cont.appendChild(mk('div', '', s('restoreQ'))).style.cssText = 'font-size:13px;margin-bottom:12px';
                const applyMerge = () => {
                    close();
                    if (data.cookies) Object.entries(data.cookies).forEach(([k, v]) => { if (CK.get(k) === null) CK.set(k, v); });
                    if (data.gm)      Object.entries(data.gm).forEach(([k, v])      => { if (GM_getValue(k, null) === null) GM_setValue(k, v); });
                    location.reload();
                };
                const applyReplace = () => {
                    close();
                    DB_KEYS_CK.forEach(k => CK.set(k, ''));
                    DB_KEYS_GM.forEach(k => GM_deleteValue(k));
                    if (data.cookies) Object.entries(data.cookies).forEach(([k, v]) => CK.set(k, v));
                    if (data.gm)      Object.entries(data.gm).forEach(([k, v])      => GM_setValue(k, v));
                    location.reload();
                };
                const row = mk('div'); row.style.cssText = 'display:flex;gap:8px';
                row.append(mkB(s('mergeLbl'), 'btn-b', applyMerge), mkB(s('replaceLbl'), 'btn-r', applyReplace), mkB(s('cancelLbl'), 'btn-grey', close));
                cont.appendChild(row);
            });
        };
        reader.readAsText(f);
    };
    inp.click();
};

// ─── ALIGN LEFT ───────────────────────────────────────────────────────────────
const applyAlignLeft = () => {
    if (!isOn(K.alignLeft)) return;
    document.head.appendChild(Object.assign(document.createElement('style'), {
        textContent: '#aspnetForm>div{margin-left:20px!important}body{justify-content:flex-start!important}'
    }));
};

// ─── PROGRESS BAR ─────────────────────────────────────────────────────────────
const applyProgressBar = () => {
    if (!isOn(K.pb)) return;
    document.querySelectorAll('.progressBar,.greenProgressBar,.blueProgressBar').forEach(e => {
        let pct = e.getAttribute('title') || ''; if (pct.includes(' ')) pct = pct.split(' ').pop();
        if (pct && !e.querySelector('.tvip-pct')) e.prepend(mk('div', 'tvip-pct', pct));
    });
    document.querySelectorAll('.plusMinusBar').forEach(e => {
        let pct = e.getAttribute('title') || ''; if (pct.includes(' ')) pct = pct.split(' ').pop();
        const tgt = e.querySelector('.negholder');
        if (tgt && pct && !tgt.querySelector('.tvip-pct')) tgt.prepend(mk('div', 'tvip-pct', pct));
    });
};

// ─── ITEM ID ──────────────────────────────────────────────────────────────────
const applyItemId = () => {
    if (!isOn(K.itemId)) return;
    document.querySelectorAll("a[href*='/ItemDetails/']").forEach(link => {
        if (link.parentElement.querySelector('.tvip-item-id')) return;
        const m = link.href.match(/\/ItemDetails\/(\d+)/); if (!m) return;
        link.parentElement.appendChild(mk('span', 'tvip-item-id', `(#${m[1]})`));
    });
    const pm = location.href.match(/\/ItemDetails\/(\d+)/);
    if (pm && !document.querySelector('.tvip-item-page-id')) {
        const h2 = document.querySelector('h2');
        if (h2) h2.appendChild(mk('span', 'tvip-item-page-id', `ID: ${pm[1]}`));
    }
};

// ─── COLOUR SCORING ───────────────────────────────────────────────────────────
const applyColorScoring = () => {
    if (!isOn(K.colorScoring)) return;
    document.querySelectorAll('a[href*="Help/Scoring/"]:not(.tvip-ng)').forEach(a => {
        const m = a.href.match(/\/Help\/Scoring\/(\d+)/i); if (!m) return;
        const idx = parseInt(m[1]) - 1; if (idx < 0 || idx > 26) return;
        const [bg, fg] = RAINBOW[idx];
        const badge = mk('span', 'tvip-badge', String(idx));
        badge.style.cssText = `background:${bg};color:${fg}`;
        a.classList.add('tvip-ng'); a.parentNode.insertBefore(badge, a.nextSibling);
    });
};

// ─── AUTO DELIVERY ────────────────────────────────────────────────────────────
const applyAutoDelivery = () => {
    if (!isOn(K.autoDelivery)) return;
    document.querySelectorAll("input[type=checkbox][id$='chkDelivery']").forEach(cb => cb.checked = true);
};

// ─── TICKET PRICER ────────────────────────────────────────────────────────────
const applyTicketPricer = () => {
    if (!isOn(K.ticketPricer)) return;
    if (!location.href.includes('/InviteArtist/')) return;
    const pm = {0:'5$',1:'5$',2:'5$',3:'7$',4:'9$',5:'12$',6:'15$',7:'18$',8:'20$',9:'25$',
                10:'30$',11:'35$',12:'40$',13:'45$',14:'50$',15:'65$',16:'70$',17:'75$',18:'80$',19:'85$',20:'90$'};
    document.querySelectorAll("a[href^='/World/Popmundo.aspx/Help/Scoring/']").forEach(link => {
        const raw = link.getAttribute('title'); if (!raw) return;
        const score = parseInt(raw.replace('/26','').trim()); if (isNaN(score)) return;
        if (pm[score] && !link.parentElement.querySelector('.tvip-ticket-price'))
            link.parentElement.appendChild(mk('span', 'tvip-ticket-price', pm[score]));
    });
};

// ─── IMAGE CONTROL ────────────────────────────────────────────────────────────
const applyImageCtrl = () => {
    if (!isOn(K.imageCtrl)) return;
    const TIMEOUT_MS = 5000;
    const POPMUNDO_RE = /^https?:\/\/([^/]*\.)?popmundo\.com\//i;
    const placeholder = (img) => {
        if (img.dataset.tvipImgHandled) return;
        img.dataset.tvipImgHandled = '1';
        const src = img.src;
        const wrap = img.parentElement;
        const ph = mk('span', 'tvip-img-ph');
        ph.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;width:'+(img.width||48)+'px;height:'+(img.height||48)+'px;min-width:24px;min-height:24px;background:#eee;border:1px dashed #ccc;border-radius:4px;font-size:13px;cursor:pointer;color:#aaa;box-sizing:border-box;';
        ph.textContent = '🖼️';
        ph.title = 'Yüklenemedi — yeniden denemek için tıkla';
        ph.onclick = () => {
            ph.remove();
            const ni = mk('img');
            ni.src = src + (src.includes('?') ? '&' : '?') + '_r=' + Date.now();
            ni.className = img.className;
            ni.style.cssText = img.style.cssText;
            if (wrap) wrap.insertBefore(ni, img.nextSibling);
        };
        img.style.display = 'none';
        if (wrap) wrap.insertBefore(ph, img.nextSibling);
    };

    const observe = (img) => {
        if (img.dataset.tvipImgHandled) return;
        if (!img.src || POPMUNDO_RE.test(img.src)) return;
        if (img.complete && img.naturalWidth > 0) return;
        let timer = setTimeout(() => placeholder(img), TIMEOUT_MS);
        img.addEventListener('load', () => clearTimeout(timer), { once: true });
        img.addEventListener('error', () => { clearTimeout(timer); placeholder(img); }, { once: true });
    };

    document.querySelectorAll('img').forEach(observe);
    const mo = new MutationObserver(muts => {
        muts.forEach(m => m.addedNodes.forEach(n => {
            if (n.tagName === 'IMG') observe(n);
            else if (n.querySelectorAll) n.querySelectorAll('img').forEach(observe);
        }));
    });
    mo.observe(document.body, { childList: true, subtree: true });
};

// ─── ITEM FILTERS ─────────────────────────────────────────────────────────────
const applyOnlyYours = () => {
    if (!isOn(K.itemFilters)) return;
    const list = document.getElementById('checkedlist'); if (!list) return;
    const filter = on => list.querySelectorAll('tr:not(:first-child)').forEach(row => {
        const inputs = row.querySelector('td:first-child')?.querySelectorAll('input') || [];
        row.style.display = (on && inputs.length < 2 && row.className !== 'group') ? 'none' : '';
    });
    const active = CK.get(K.onlyYoursSt) === '1';
    filter(active);
    const btn = mk('a', '', active ? s('btnShowAll') : s('btnOnlyTake'));
    btn.href = '#';
    btn.style.cssText = 'display:inline-block;margin-bottom:12px;padding:5px 10px;background:#17a2b8;color:#fff;text-decoration:none;border-radius:4px;font-size:12px';
    btn.onclick = e => {
        e.preventDefault();
        const cur = CK.get(K.onlyYoursSt) === '1';
        CK.set(K.onlyYoursSt, cur ? '0' : '1');
        btn.textContent = cur ? s('btnOnlyTake') : s('btnShowAll');
        filter(!cur);
    };
    list.before(btn);
};

const getOfferedList  = ()  => { try { return JSON.parse(GM_getValue(DK.OFFERED, '[]')); } catch { return []; } };
const saveOfferedList = (l) => GM_setValue(DK.OFFERED, JSON.stringify(l));

const applyHideOffered = () => {
    if (!isOn(K.itemFilters)) return;
    if (isOn(K.bulkOffer)) return; // cleanHideOfferedCheckbox handles it
    const sel = document.querySelector('#ctl00_cphLeftColumn_ctl00_ddlItem'); if (!sel) return;
    getOfferedList().forEach(id => sel.querySelector(`option[value="${id}"]`)?.remove());
    document.querySelector('#ctl00_cphLeftColumn_ctl00_btnGive')?.addEventListener('click', () => {
        const id = sel.value; if (!id || id === '-1') return;
        const l = getOfferedList(); if (!l.includes(id)) { l.push(id); saveOfferedList(l); }
    });
};

// ─── TABLE SORT ───────────────────────────────────────────────────────────────
const applyTableSort = () => {
    if (!guard(K.tableTools)) return;
    const deny = getTableDeny();
    document.querySelectorAll('table').forEach((table, ti) => {
        if (deny.includes(`${location.pathname}::${ti}`)) return;
        const ths = table.querySelectorAll('tr:first-child th'); if (!ths.length) return;
        ths.forEach((th, ci) => {
            th.style.cursor = 'pointer'; let asc = true;
            th.addEventListener('click', () => {
                const body = table.querySelector('tbody') || table;
                const rows = [...body.querySelectorAll('tr')].filter(r => r.cells.length && !r.classList.contains('tvip-tbl-avg') && !r.classList.contains('tvip-heist-avg'));
                rows.sort((a, b) => {
                    const av = a.cells[ci]?.textContent.trim() || '', bv = b.cells[ci]?.textContent.trim() || '';
                    const an = parseFloat(av.replace(/[^\d.-]/g, '')), bn = parseFloat(bv.replace(/[^\d.-]/g, ''));
                    if (!isNaN(an) && !isNaN(bn)) return asc ? an - bn : bn - an;
                    return asc ? av.localeCompare(bv, LANG === 'PT' ? 'pt' : LANG === 'EN' ? 'en' : 'tr') : bv.localeCompare(av, LANG === 'PT' ? 'pt' : LANG === 'EN' ? 'en' : 'tr');
                });
                rows.forEach(r => body.appendChild(r)); asc = !asc;
                // avg satırı her zaman en altta kalsın
                body.querySelectorAll('.tvip-tbl-avg,.tvip-heist-avg').forEach(r => body.appendChild(r));
            });
        });
    });
};

// ─── TABLE SEARCH ─────────────────────────────────────────────────────────────
const applyPageSearch = () => {
    if (!guard(K.tableTools)) return;
    const deny = getTableDeny();
    document.querySelectorAll('table').forEach((table, ti) => {
        if (table.closest('#tvip-bar,.tvip-ov') || table.querySelector('.tvip-search-done')) return;
        if (deny.includes(`${location.pathname}::${ti}`)) return;
        const rows = table.querySelectorAll('tbody tr'); if (rows.length < 8) return;
        const wrap = mk('div', 'tvip-search-wrap');
        const inp  = mk('input');
        inp.style.cssText = 'padding:4px 8px;border:1px solid #ccc;border-radius:4px;font-size:12px;width:180px';
        inp.placeholder = s('psPlh');
        const info = mk('span'); info.style.cssText = 'font-size:11px;color:#666';
        inp.addEventListener('input', () => {
            const q = inp.value.trim().toLowerCase(); let count = 0;
            rows.forEach(r => { const m = !q || r.textContent.toLowerCase().includes(q); r.style.display = m ? '' : 'none'; if (m) count++; });
            info.textContent = q ? `${count} ${s('psCount')}` : '';
        });
        const hideBtn = mkB('🚫', 'btn-sm btn-grey', () => {
            if (!confirm(s('tdConfirm'))) return;
            addTableDeny(location.pathname, ti); wrap.remove();
        });
        hideBtn.title = s('tdHide');
        wrap.append(inp, info, hideBtn); table.parentNode.insertBefore(wrap, table);
        table.appendChild(mk('span', 'tvip-search-done'));
    });
};

// ─── QUICK TOOLS ──────────────────────────────────────────────────────────────
// ─── TABLE AVG ────────────────────────────────────────────────────────────────
const applyTableAvg = () => {
    if (!isOn(K.tableAvg)) return;
    const table = document.querySelector('#tablefame');
    if (table && !table.querySelector('.tvip-tbl-avg')) {
        const rows = table.querySelectorAll('tbody tr');
        let fameSum = 0, mcSum = 0, count = 0;
        rows.forEach(row => {
            const sl  = row.querySelector("a[href*='/Help/Scoring/']");
            if (sl)  { const v = parseInt((sl.getAttribute('title')||'').replace('/26','').trim()); if (!isNaN(v)) fameSum += v; }
            const bar = row.querySelector("div[class$='ProgressBar']");
            if (bar) { const mv = bar.getAttribute('title')?.match(/(\d+)%/); if (mv) mcSum += parseInt(mv[1]); }
            count++;
        });
        if (count) {
            const row = mk('tr', 'even tvip-tbl-avg');
            row.innerHTML = `<td>${s('taAvg')}</td><td>💫 ${(fameSum/count).toFixed(2)}</td><td>🔛 ${(mcSum/count).toFixed(2)}%</td>`;
            table.querySelector('tbody').prepend(row);
        }
    }
    if (!location.href.includes('CrewReconnaissance')) return;
    const rows = document.querySelectorAll("tr[id*='repReconnaissance'][id*='trCrewMember']"); if (!rows.length) return;
    let sum = 0, cnt = 0;
    rows.forEach(r => { const sk = r.querySelector('span.sortkey'); if (sk) { const v = parseInt(sk.textContent); if (!isNaN(v)) { sum += v; cnt++; } } });
    if (!cnt) return;
    const tbody = rows[0].parentElement;
    if (!tbody.querySelector('.tvip-heist-avg')) {
        const row = mk('tr', 'tvip-heist-avg');
        row.innerHTML = `<td colspan="2">${s('taDiscAvg')}</td><td class="width20">${(sum/cnt).toFixed(0)}%</td><td class="width10"></td>`;
        tbody.prepend(row);
    }
};

// ─── JAM HELPER ───────────────────────────────────────────────────────────────
const applyJamHelper = () => {
    if (!guard(K.jamHelper, '/Artist/Repertoire/')) return;
    if (!document.querySelector('a[href*="/Artist/BookingAssistant"]')) return;
    const sel = "tr[id*='ctl00_cphLeftColumn_ctl01_repArtistRepertoire']";
    const btn = mk('input'); btn.type = 'button'; btn.value = s('jamBtn');
    btn.style.cssText = 'margin:4px 2px;padding:4px 10px;cursor:pointer;font-size:12px';
    btn.onclick = () => {
        document.querySelectorAll(sel).forEach(tr => {
            const bar = tr.querySelector('td:nth-child(5) div[title]');
            if (!bar) return;
            const m  = bar.getAttribute('title')?.match(/(\d{1,3})%/);
            const cb = tr.querySelector('td:first-child input[type="checkbox"]');
            if (cb) cb.checked = (m ? parseInt(m[1]) : 100) < 100;
        });
    };
    document.querySelector('p.actionbuttons')?.prepend(btn);
};

// ─── REPERTOIRE FILTER ────────────────────────────────────────────────────────
const applyRepertoireFilter = () => {
    if (!guard(K.repertoireF, '/Artist/Repertoire/') || document.querySelector('#tvip-rep-filter')) return;
    const div = mk('div'); div.id = 'tvip-rep-filter';
    const labels = [s('repAll'), s('repMarket'), s('repNoMarket'), s('repJam'), s('repSetlist'), s('repSecret')];
    div.innerHTML = `<h3 style="margin-top:10px">${s('repTitle')}</h3>` +
        labels.map((l, i) => `<label><input type="radio" name="tvipRepF" value="${i}"> ${l}</label><br>`).join('');
    document.querySelector('div.box')?.insertAdjacentElement('beforebegin', div);
    const checks  = ['_', 'imgSongMarket', 'imgSongMarket', '_Rep_PracOn', '_imgDefaultSetlist', '_imgSecret'];
    const invert  = [true, true, false, true, true, true];
    const rowSel  = "tr[id^='ctl00_cphLeftColumn_ctl01_repArtistRepertoire_ct']";
    const update  = id => document.querySelectorAll(rowSel).forEach(tr => {
        tr.style.display = invert[id]
            ? (tr.innerHTML.includes(checks[id]) ? '' : 'none')
            : (tr.innerHTML.includes(checks[id]) ? 'none' : '');
    });
    div.querySelectorAll('input[name=tvipRepF]').forEach(r => r.addEventListener('change', () => update(parseInt(r.value))));
};

// ─── QUICK SEND ITEMS ─────────────────────────────────────────────────────────
const applyQuickSend = () => {
    if (!guard(K.sendItems, '/Character/OfferItem/')) return;
    const waitFor = (cb, tries = 0) => {
        const dd = document.getElementById('ctl00_cphLeftColumn_ctl00_ddlItem');
        const bn = document.getElementById('ctl00_cphLeftColumn_ctl00_btnGive');
        if (dd && bn) cb(dd, bn); else if (tries < 20) setTimeout(() => waitFor(cb, tries + 1), 500);
    };
    waitFor((dropdown, btnGive) => {
        // Resume if running
        if (GM_getValue(DK.SI_STOP, '') === '1') {
            const cnt = parseInt(GM_getValue(DK.SI_QUEUE, '0'));
            GM_deleteValue(DK.SI_QUEUE); GM_deleteValue(DK.SI_NAME); GM_deleteValue(DK.SI_TOTAL); GM_deleteValue(DK.SI_STOP);
            alert(sf.siStopped(cnt));
            location.reload(); return;
        }
        const name  = GM_getValue(DK.SI_NAME, '');
        const total = parseInt(GM_getValue(DK.SI_TOTAL, '0'));
        let counter = parseInt(GM_getValue(DK.SI_QUEUE, '0'));

        if (name && counter < total) {
            const opt = [...dropdown.options].find(o => o.text === name);
            if (!opt) {
                GM_deleteValue(DK.SI_QUEUE); GM_deleteValue(DK.SI_NAME); GM_deleteValue(DK.SI_TOTAL);
                alert(`✅ ${counter}/${total}`); return;
            }
            const wrap = mk('div', 'tvip-send-wrap tvip-send-running');
            const info = mk('div'); info.style.cssText = 'font-size:12px;margin-bottom:8px';
            info.innerHTML = sf.siSending(counter, total, name);
            const stopBtn = mkB(s('siStop'), 'btn-r btn-sm', () => {
                GM_setValue(DK.SI_STOP, '1'); stopBtn.disabled = true; stopBtn.textContent = s('siStopping');
            });
            wrap.append(info, stopBtn);
            btnGive.parentElement?.appendChild(wrap);
            dropdown.value = opt.value; counter++;
            GM_setValue(DK.SI_QUEUE, String(counter));
            setTimeout(() => btnGive.click(), 500); return;
        }
        if (name && counter >= total) {
            GM_deleteValue(DK.SI_QUEUE); GM_deleteValue(DK.SI_NAME); GM_deleteValue(DK.SI_TOTAL);
            alert(s('siDone')); location.reload(); return;
        }

        // Setup UI
        const wrap = mk('div', 'tvip-send-wrap tvip-send-setup');
        btnGive.parentElement?.appendChild(wrap);
        const update = () => {
            const sel  = dropdown.options[dropdown.selectedIndex]; if (!sel) return;
            const same = [...dropdown.options].filter(o => o.text === sel.text);
            wrap.innerHTML = '';
            if (same.length <= 1) { wrap.appendChild(mk('em', '', s('siWarning'))).style.cssText = 'color:#999;font-size:12px'; return; }
            const info  = mk('div', '', `"${sel.text}" — ${same.length}`); info.style.cssText = 'font-size:12px;margin-bottom:8px';
            const cRow  = mk('div'); cRow.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:8px';
            const lbl   = mk('span', '', s('siCount') + ' '); lbl.style.fontSize = '12px';
            const num   = mk('input'); num.type = 'number'; num.min = 1; num.max = same.length; num.value = same.length;
            num.style.cssText = 'width:60px;padding:3px 5px;border:1px solid #ccc;border-radius:3px;font-size:12px';
            cRow.append(lbl, num);
            const bRow  = mk('div'); bRow.style.cssText = 'display:flex;gap:6px';
            const sendB = mkB(s('siSend'), 'btn-b btn-sm', () => {
                const cnt = Math.max(1, Math.min(parseInt(num.value) || 1, same.length));
                if (!confirm(sf.siConfirm(sel.text, cnt))) return;
                GM_setValue(DK.SI_NAME, sel.text); GM_setValue(DK.SI_TOTAL, String(cnt)); GM_setValue(DK.SI_QUEUE, '0');
                location.reload();
            });
            const canB  = mkB(s('siCancel'), 'btn-grey btn-sm', () => wrap.remove());
            bRow.append(sendB, canB);
            wrap.append(info, cRow, bRow);
        };
        update(); dropdown.addEventListener('change', () => setTimeout(update, 100));
    });
};

// ─── BULK OFFER ───────────────────────────────────────────────────────────────
const SEL_DD    = '#ctl00_cphLeftColumn_ctl00_ddlItem';
const SEL_PRICE = '#ctl00_cphLeftColumn_ctl00_txtPriceTag';
const SEL_BTN   = '#ctl00_cphLeftColumn_ctl00_btnGive';
const SEL_FORM  = '#ctl00_cphLeftColumn_ctl00_updMain';

const removeWarningBox = () => {
    const h2 = [...document.querySelectorAll('.box h2')].find(h => h.textContent.includes('Quem avisa') || h.textContent.includes('Uyarı') || h.textContent.includes('Warning'));
    h2?.closest('.box')?.remove();
};

const cleanHideOfferedCheckbox = () => {
    const hide = CK.get(K.hideOffBox) === '1';
    const deliveryParent = document.querySelector('#ctl00_cphLeftColumn_ctl00_chkDelivery')?.parentElement;
    if (!deliveryParent) return;
    const p   = document.createElement('p');
    const chk = document.createElement('input'); chk.type = 'checkbox'; chk.id = 'tvip-hideoff-chk'; chk.checked = hide;
    const lbl = document.createElement('label'); lbl.htmlFor = 'tvip-hideoff-chk';
    lbl.style.cssText = 'font-weight:normal;color:#333;font-size:12px;margin-left:4px';
    lbl.textContent   = s('hideOfferedChk');
    chk.addEventListener('change', () => { CK.set(K.hideOffBox, chk.checked ? '1' : '0'); location.reload(); });
    p.append(chk, lbl); deliveryParent.before(p);
    const sel = document.querySelector(SEL_DD);
    if (hide && sel) getOfferedList().forEach(id => sel.querySelector(`option[value="${id}"]`)?.remove());
    // Track manual offers so the list stays accurate
    document.querySelector(SEL_BTN)?.addEventListener('click', () => {
        if (!chk.checked || !sel) return;
        const id = sel.value; if (!id || id === '-1') return;
        const l = getOfferedList(); if (!l.includes(id)) { l.push(id); saveOfferedList(l); }
    });
};

const cleanOfferedOnItemsPage = () => {
    const offered = getOfferedList(); if (!offered.length) return;
    const box = [...document.querySelectorAll('.box h2')]
        .find(h => h.textContent.includes('Itens que você está ofertando') || h.textContent.includes('Items you are offering') || h.textContent.includes('Teklif ettiğiniz'))
        ?.closest('.box');
    if (!box) { saveOfferedList([]); return; }
    const currentIDs = [...box.querySelectorAll('a[href*="/Character/ItemDetails/"]')]
        .map(a => { const m = a.href.match(/\/ItemDetails\/(\d+)/); return m ? m[1] : null; }).filter(Boolean);
    if (!currentIDs.length) { saveOfferedList([]); return; }
    saveOfferedList(offered.filter(id => currentIDs.includes(id)));
};

const setupBulkOfferUI = () => {
    if (!guard(K.bulkOffer, '/Character/OfferItem/')) return;
    const formBox = document.querySelector(SEL_FORM)?.closest('.box');
    if (!formBox || document.getElementById('tvip-bulk-offer-ui')) return;

    removeWarningBox();
    cleanHideOfferedCheckbox();

    const ui = mk('div'); ui.id = 'tvip-bulk-offer-ui';
    ui.innerHTML = `
        <h3 style="margin:0 0 8px;font-size:13px">${s('boTitle')}</h3>
        <div class="tvip-bulk-panel">
            <div class="tvip-bulk-grid">
                <div class="tvip-bulk-field"><label>${s('boItemName')}</label><input type="text" id="tvip-bo-name" placeholder="Analgés..."></div>
                <div class="tvip-bulk-field"><label>${s('boQty')}</label><input type="number" id="tvip-bo-qty" min="1" value="1"></div>
                <div class="tvip-bulk-field" style="grid-column:1/-1"><label>${s('boPrice')}</label><input type="number" id="tvip-bo-price" min="0" step="1" value="0"></div>
            </div>
            <div class="tvip-bulk-actions">
                <button id="tvip-bo-start" class="tvip-btn-start">${s('boStart')}</button>
                <button id="tvip-bo-stop"  class="tvip-btn-stop">${s('boStop')}</button>
            </div>
            <div id="tvip-bo-status" class="tvip-status">${s('boReady')}</div>
        </div>`;
    formBox.insertBefore(ui, formBox.firstChild);

    const setDisabled = on => {
        document.getElementById('tvip-bo-start').disabled =  on;
        document.getElementById('tvip-bo-stop').disabled  = !on;
        document.getElementById('tvip-bo-name').disabled  =  on;
        document.getElementById('tvip-bo-qty').disabled   =  on;
        document.getElementById('tvip-bo-price').disabled =  on;
    };

    const processNext = () => {
        if (!GM_getValue(DK.BO_RUNNING, false)) { setDisabled(false); return; }
        let items = [];
        try { items = JSON.parse(GM_getValue(DK.BO_ITEMS, '[]')); } catch {}
        const status = document.getElementById('tvip-bo-status');
        if (!items.length) { status.textContent = s('boDone'); stopOffer(); return; }
        const dropdown = document.querySelector(SEL_DD);
        const offerBtn = document.querySelector(SEL_BTN);
        const priceInp = document.querySelector(SEL_PRICE);
        if (!dropdown || !offerBtn) { status.textContent = s('boCritErr'); stopOffer(); return; }
        const item  = items.shift();
        const price = GM_getValue(DK.BO_PRICE, 0);
        const total = parseInt(GM_getValue(DK.BO_TOTAL, '0'));
        const count = total - items.length;
        if (priceInp) priceInp.value = String(price);
        GM_setValue(DK.BO_ITEMS, JSON.stringify(items));
        status.textContent = sf.boOfferring(count, total, item.text);
        dropdown.value = item.value;
        if (dropdown.value !== item.value) {
            status.textContent = sf.boSkip(item.text);
            setTimeout(processNext, 1000); return;
        }
        // Track offered item
        const l = getOfferedList(); if (!l.includes(item.value)) { l.push(item.value); saveOfferedList(l); }
        setTimeout(() => {
            if (!GM_getValue(DK.BO_RUNNING, false)) { setDisabled(false); return; }
            offerBtn.click();
        }, 2000);
    };

    const stopOffer = () => {
        GM_deleteValue(DK.BO_ITEMS); GM_deleteValue(DK.BO_RUNNING);
        GM_deleteValue(DK.BO_PRICE); GM_deleteValue(DK.BO_TOTAL);
        setDisabled(false);
    };

    document.getElementById('tvip-bo-start').onclick = () => {
        const nameVal  = document.getElementById('tvip-bo-name').value.trim();
        const qtyVal   = parseInt(document.getElementById('tvip-bo-qty').value, 10);
        const priceVal = parseInt(document.getElementById('tvip-bo-price').value, 10);
        const status   = document.getElementById('tvip-bo-status');
        if (!nameVal)                          { status.textContent = s('boErrName');  return; }
        if (isNaN(qtyVal)   || qtyVal < 1)    { status.textContent = s('boErrQty');   return; }
        if (isNaN(priceVal) || priceVal < 0)  { status.textContent = s('boErrPrice'); return; }
        const found = [...document.querySelectorAll(`${SEL_DD} option`)]
            .filter(o => o.value && o.value !== '-1' && o.text.trim().toLowerCase().startsWith(nameVal.toLowerCase()))
            .map(o => ({ value: o.value, text: o.text.trim() }));
        if (!found.length) { status.textContent = sf.boNoItem(nameVal); return; }
        const toOffer = found.slice(0, qtyVal);
        status.textContent = sf.boFound(found.length, toOffer.length, priceVal);
        GM_setValue(DK.BO_PRICE, priceVal);
        GM_setValue(DK.BO_ITEMS, JSON.stringify(toOffer));
        GM_setValue(DK.BO_TOTAL, String(toOffer.length));
        GM_setValue(DK.BO_RUNNING, 'true');
        setDisabled(true);
        setTimeout(processNext, 300);
    };

    document.getElementById('tvip-bo-stop').onclick = () => {
        const status = document.getElementById('tvip-bo-status');
        status.textContent = s('boStopped'); stopOffer();
    };

    // Resume on page reload
    if (GM_getValue(DK.BO_RUNNING, false)) {
        setDisabled(true);
        document.getElementById('tvip-bo-status').textContent = s('boResumed');
        setTimeout(processNext, 500);
    } else { setDisabled(false); }
};

// ─── BULK ACCEPT + BULK CANCEL ────────────────────────────────────────────────
const setupBulkAcceptUI = () => {
    if (!guard(K.bulkAccept, '/Character/ItemsOffered')) return;
    if (document.getElementById('tvip-bulk-accept-ui')) return;

    cleanOfferedOnItemsPage();

    const findBox = txt => [...document.querySelectorAll('.box')]
        .find(b => b.querySelector('h2')?.textContent.includes(txt));

    const incomingBox = findBox('Teklif edilen eşyalar') || findBox('Items you are being offered') || findBox('Itens sendo ofertados');
    const outgoingBox = findBox('Teklif ettiğiniz')      || findBox('Items you are offering') || findBox('Itens que você está ofertando');

    const parseCurrency = str => parseInt((str || '').replace(/\./g, '').split(',')[0].replace(/[^\d]/g, ''), 10) || 0;
    const getPrice = p => {
        const m = p.textContent.match(/Tutar:\s*([\d.,]+)\s*M\$|cost:\s*([\d.,]+)\s*M\$|custo:\s*([\d.,]+)\s*M\$/i);
        return m ? parseCurrency(m[1] || m[2] || m[3]) : 0;
    };
    const isFree = p => /Tutar:\s*Bedava|cost:\s*Free|custo:\s*Grátis/i.test(p.textContent);

    // ── ACCEPT UI ──
    if (incomingBox) {
        const ui = mk('div'); ui.id = 'tvip-bulk-accept-ui';
        ui.innerHTML = `
            <h3 style="margin:0 0 8px;font-size:13px">${s('baTitle')}</h3>
            <div class="tvip-bulk-panel">
                <div class="tvip-bulk-grid">
                    <div class="tvip-bulk-field"><label>${s('baItemName')}</label><input type="text" id="tvip-ba-name" placeholder="${s('baItemPlh')}"></div>
                    <div class="tvip-bulk-field"><label>${s('baMaxPrice')}</label><input type="number" id="tvip-ba-price" min="0" step="1" value="55000"></div>
                </div>
                <div class="tvip-bulk-actions">
                    <button id="tvip-ba-start" class="tvip-btn-start">${s('baStart')}</button>
                    <button id="tvip-ba-stop"  class="tvip-btn-stop">${s('baStop')}</button>
                </div>
                <div id="tvip-ba-status" class="tvip-status">${s('baReady')}</div>
                <div id="tvip-ba-spent"  class="tvip-spent">${sf.baSpent(0)}</div>
            </div>`;
        incomingBox.insertBefore(ui, incomingBox.firstChild);

        const setDisabledA = on => {
            document.getElementById('tvip-ba-start').disabled =  on;
            document.getElementById('tvip-ba-stop').disabled  = !on;
            document.getElementById('tvip-ba-name').disabled  =  on;
            document.getElementById('tvip-ba-price').disabled =  on;
        };

        const processNextAccept = () => {
            if (!GM_getValue(DK.BA_RUNNING, false)) { setDisabledA(false); return; }
            const maxP    = parseInt(GM_getValue(DK.BA_MAX_PRICE, '0'));
            const tgtName = GM_getValue(DK.BA_ITEM_NAME, '');
            const count   = parseInt(GM_getValue(DK.BA_COUNT, '0'));
            const spent   = parseInt(GM_getValue(DK.BA_SPENT, '0'));
            const status  = document.getElementById('tvip-ba-status');
            const spentEl = document.getElementById('tvip-ba-spent');
            const box     = findBox('Teklif edilen eşyalar') || findBox('Items you are being offered') || findBox('Itens sendo ofertados');
            if (!box) { status.textContent = s('baNoSection'); stopAccept(); return; }
            const paragraphs = [...box.querySelectorAll('p.nobmargin')].filter(p => p.querySelector('a[id*="lnkItem"]'));
            if (!paragraphs.length) { status.textContent = sf.baDone(count, spent); stopAccept(); return; }

            let found = false;
            for (const p of paragraphs) {
                const itemName = p.querySelector('a[id*="lnkItem"]')?.textContent.trim() || ''; if (!itemName) continue;
                const price = isFree(p) ? 0 : getPrice(p);
                const priceLabel = price ? `${price} M$` : s('baFree');
                if ((tgtName === '' || itemName.toLowerCase().includes(tgtName)) && price <= maxP) {
                    const newCount = count + 1, newSpent = spent + price;
                    GM_setValue(DK.BA_COUNT, String(newCount)); GM_setValue(DK.BA_SPENT, String(newSpent));
                    status.textContent = sf.baAccepting(newCount, itemName, priceLabel);
                    if (spentEl) spentEl.textContent = sf.baSpent(newSpent);
                    found = true;
                    const btn = p.querySelector('input[id*="btnAcceptAndPay"], input[name*="btnAcceptAndPay"]');
                    if (!btn) continue;
                    setTimeout(() => {
                        if (!GM_getValue(DK.BA_RUNNING, false)) { setDisabledA(false); return; }
                        btn.click();
                    }, 2000);
                    break;
                }
            }
            if (!found) { status.textContent = sf.baNoMatch(count, spent); stopAccept(); }
        };

        const stopAccept = () => {
            GM_deleteValue(DK.BA_RUNNING); GM_deleteValue(DK.BA_MAX_PRICE);
            GM_deleteValue(DK.BA_ITEM_NAME); GM_deleteValue(DK.BA_COUNT); GM_deleteValue(DK.BA_SPENT);
            setDisabledA(false);
        };

        document.getElementById('tvip-ba-start').onclick = () => {
            const price = parseInt(document.getElementById('tvip-ba-price').value.replace(/[.,]/g,''), 10);
            const name  = document.getElementById('tvip-ba-name').value.trim().toLowerCase();
            if (isNaN(price) || price < 0) { document.getElementById('tvip-ba-status').textContent = s('baErrPrice'); return; }
            document.getElementById('tvip-ba-status').textContent = sf.baInit(name, price);
            document.getElementById('tvip-ba-spent').textContent  = sf.baSpent(0);
            GM_setValue(DK.BA_RUNNING, 'true'); GM_setValue(DK.BA_MAX_PRICE, price);
            GM_setValue(DK.BA_ITEM_NAME, name); GM_setValue(DK.BA_COUNT, '0'); GM_setValue(DK.BA_SPENT, '0');
            setDisabledA(true); setTimeout(processNextAccept, 300);
        };

        document.getElementById('tvip-ba-stop').onclick = () => {
            const c = parseInt(GM_getValue(DK.BA_COUNT, '0')), sp = parseInt(GM_getValue(DK.BA_SPENT, '0'));
            document.getElementById('tvip-ba-status').textContent = sf.baStopped(c, sp);
            stopAccept();
        };

        if (GM_getValue(DK.BA_RUNNING, false)) {
            setDisabledA(true);
            document.getElementById('tvip-ba-status').textContent = s('baResumed');
            document.getElementById('tvip-ba-spent').textContent  = sf.baSpent(parseInt(GM_getValue(DK.BA_SPENT, '0')));
            setTimeout(processNextAccept, 500);
        } else { setDisabledA(false); }
    }

    // ── CANCEL UI ──
    if (outgoingBox) {
        const uic = mk('div'); uic.id = 'tvip-bulk-cancel-ui';
        uic.innerHTML = `
            <h3 style="margin:0 0 8px;font-size:13px">${s('bcTitle')}</h3>
            <div class="tvip-bulk-panel">
                <div class="tvip-bulk-grid">
                    <div class="tvip-bulk-field"><label>${s('bcItemName')}</label><input type="text" id="tvip-bc-name" placeholder="${s('baItemPlh')}"></div>
                    <div class="tvip-bulk-field">
                        <label>${s('bcFilter')}</label>
                        <select id="tvip-bc-filter" style="width:100%;padding:5px 6px;border:1px solid #ccc;border-radius:4px;font-size:12px">
                            <option value="all">${s('bcFilterAll')}</option>
                            <option value="free">${s('bcFilterFree')}</option>
                            <option value="paid">${s('bcFilterPaid')}</option>
                        </select>
                    </div>
                </div>
                <div class="tvip-bulk-actions">
                    <button id="tvip-bc-start" class="tvip-btn-start">${s('bcStart')}</button>
                    <button id="tvip-bc-stop"  class="tvip-btn-stop">${s('bcStop')}</button>
                </div>
                <div id="tvip-bc-status" class="tvip-status">${s('bcReady')}</div>
            </div>`;
        outgoingBox.insertBefore(uic, outgoingBox.firstChild);

        const setDisabledC = on => {
            document.getElementById('tvip-bc-start').disabled =  on;
            document.getElementById('tvip-bc-stop').disabled  = !on;
            document.getElementById('tvip-bc-name').disabled  =  on;
            document.getElementById('tvip-bc-filter').disabled =  on;
        };

        const processNextCancel = () => {
            if (!GM_getValue(DK.BC_RUNNING, false)) { setDisabledC(false); return; }
            const tgtName = GM_getValue(DK.BC_ITEM_NAME, '');
            const filter  = GM_getValue(DK.BC_FILTER, 'all');
            const count   = parseInt(GM_getValue(DK.BC_COUNT, '0'));
            const status  = document.getElementById('tvip-bc-status');
            const box     = findBox('Teklif ettiğiniz') || findBox('Items you are offering') || findBox('Itens que você está ofertando');
            if (!box) { status.textContent = s('bcNoSection'); stopCancel(); return; }
            const paragraphs = [...box.querySelectorAll('p.nobmargin')].filter(p => p.querySelector('a[id*="lnkItem"]'));
            if (!paragraphs.length) { status.textContent = sf.bcDone(count); stopCancel(); return; }

            let found = false;
            for (const p of paragraphs) {
                const itemName = p.querySelector('a[id*="lnkItem"]')?.textContent.trim() || ''; if (!itemName) continue;
                const free = isFree(p);
                const matchesFilter = filter === 'all' || (filter === 'free' && free) || (filter === 'paid' && !free);
                const matchesName   = tgtName === '' || itemName.toLowerCase().includes(tgtName);
                if (matchesFilter && matchesName) {
                    const newCount = count + 1;
                    GM_setValue(DK.BC_COUNT, String(newCount));
                    status.textContent = sf.bcCancelling(newCount, itemName);
                    found = true;
                    const btn = p.querySelector('input[id*="btnStop"], input[name*="btnStop"]');
                    if (!btn) continue;
                    setTimeout(() => {
                        if (!GM_getValue(DK.BC_RUNNING, false)) { setDisabledC(false); return; }
                        btn.click();
                    }, 2000);
                    break;
                }
            }
            if (!found) { status.textContent = sf.bcNoMatch(count); stopCancel(); }
        };

        const stopCancel = () => {
            GM_deleteValue(DK.BC_RUNNING); GM_deleteValue(DK.BC_ITEM_NAME);
            GM_deleteValue(DK.BC_FILTER);  GM_deleteValue(DK.BC_COUNT);
            setDisabledC(false);
        };

        document.getElementById('tvip-bc-start').onclick = () => {
            const name   = document.getElementById('tvip-bc-name').value.trim().toLowerCase();
            const filter = document.getElementById('tvip-bc-filter').value;
            document.getElementById('tvip-bc-status').textContent = s('bcReady');
            GM_setValue(DK.BC_RUNNING, 'true'); GM_setValue(DK.BC_ITEM_NAME, name);
            GM_setValue(DK.BC_FILTER, filter);  GM_setValue(DK.BC_COUNT, '0');
            setDisabledC(true); setTimeout(processNextCancel, 300);
        };

        document.getElementById('tvip-bc-stop').onclick = () => {
            const c = parseInt(GM_getValue(DK.BC_COUNT, '0'));
            document.getElementById('tvip-bc-status').textContent = sf.bcStopped(c);
            stopCancel();
        };

        if (GM_getValue(DK.BC_RUNNING, false)) {
            setDisabledC(true);
            setTimeout(processNextCancel, 500);
        } else { setDisabledC(false); }
    }
};

// ─── CITY SHORTCUTS ───────────────────────────────────────────────────────────
    // CITY NAMES
const CITY_NAMES = {
  "1":"Stockholm","5":"Londra","6":"New York","7":"Berlin","8":"Amsterdam",
  "9":"Barselona","10":"Melbourne","11":"Nashville","14":"Los Angeles",
  "16":"Toronto","17":"Buenos Aires","18":"Moskova","19":"Helsinki",
  "20":"Paris","21":"Sao Paulo","22":"Kopenhag","23":"Roma","24":"Madrid",
  "25":"Rio de Janeiro","26":"Tromsø","27":"Glasgow","28":"Vilnius",
  "29":"Dubrovnik","30":"İstanbul","31":"Porto","32":"Mexico City",
  "33":"Brüksel","34":"Tallinn","35":"Ankara","36":"Belgrad","38":"Montreal",
  "39":"Singapur","42":"Budapeşte","45":"Şangay","46":"Bükreş","47":"İzmir",
  "48":"Varşova","49":"Saraybosna","50":"Seattle","51":"Johannesburg",
  "52":"Milano","53":"Sofya","54":"Manila","55":"Cakarta","56":"Kyiv",
  "58":"Bakü","60":"Şikago","61":"Antalya","62":"Tokyo"
};
// CITY DATA
const CITY_DATA = {
    "1":  { locations: [ {id:"s",type:"shower",placeId:3160405,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49044, name:"Årsta Havsbad",   dur:90 } ]},
    "5":  { locations: [ {id:"s",type:"shower",placeId:3161774,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:234234,name:"Herman's Palace",  dur:5  } ]},
    "6":  { locations: [ {id:"s",type:"shower",placeId:2986433,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49087, name:"Cape Cod",         dur:95 } ]},
    "7":  { locations: [ {id:"s",type:"shower",placeId:3231072,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:233224,name:"Schliemann's Zimmer",dur:10} ]},
    "8":  { locations: [ {id:"s",type:"shower",placeId:3161145,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49089, name:"Breskens",         dur:90 } ]},
    "9":  { locations: [ {id:"s",type:"shower",placeId:3159414,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49090, name:"Costa Brava",      dur:20 } ]},
    "10": { locations: [ {id:"s",type:"shower",placeId:3198312,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49091, name:"Niney Mile Beach",  dur:50 } ]},
    "11": { locations: [ {id:"s",type:"shower",placeId:3245177,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:76469, name:"Little house on the Prairie",dur:10} ]},
    "14": { locations: [ {id:"s",type:"shower",placeId:3196672,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49198, name:"Santa Monica Beach",dur:20 } ]},
    "16": { locations: [ {id:"s",type:"shower",placeId:3268245,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49094, name:"Sunnyside",        dur:15 } ]},
    "17": { locations: [ {id:"s",type:"shower",placeId:3161537,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49095, name:"La Pampa",         dur:90 } ]},
    "18": { locations: [ {id:"s",type:"shower",placeId:3204377,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49096, name:"Волга",            dur:120} ]},
    "19": { locations: [ {id:"s",type:"shower",placeId:3237480,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49097, name:"Pyhäjärvi",        dur:95 } ]},
    "20": { locations: [ {id:"s",type:"shower",placeId:3162065,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:49098, name:"Charente",         dur:65 } ]},
    "21": { locations: [ {id:"s",type:"shower",placeId:3262551,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:53596, name:"Guarujá",          dur:90 } ]},
    "22": { locations: [ {id:"s",type:"shower",placeId:3204935,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:67582, name:"Gilleleje",        dur:95 } ]},
    "23": { locations: [ {id:"s",type:"shower",placeId:3181531,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:72404, name:"Ostia Lido",       dur:50 } ]},
    "24": { locations: [ {id:"s",type:"shower",placeId:3162492,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:103128,name:"La Rioja",         dur:50 } ]},
    "25": { locations: [ {id:"s",type:"shower",placeId:3199641,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:104742,name:"Ipanema",          dur:20 } ]},
    "26": { locations: [ {id:"s",type:"shower",placeId:3203190,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:106202,name:"Telegrafbukta",    dur:15 } ]},
    "27": { locations: [ {id:"s",type:"shower",placeId:3205233,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:182793,name:"Öğretmenin evi",   dur:5  } ]},
    "28": { locations: [ {id:"s",type:"shower",placeId:3220498,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:122919,name:"Merkys",           dur:90 } ]},
    "29": { locations: [ {id:"s",type:"shower",placeId:3220722,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:131991,name:"Korcula",          dur:95 } ]},
    "30": { locations: [ {id:"s",type:"shower",placeId:3160535,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:137942,name:"Gala Gölü",        dur:90 } ]},
    "31": { locations: [ {id:"s",type:"shower",placeId:2986566,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:140964,name:"Costa Verde",      dur:20 } ]},
    "32": { locations: [ {id:"s",type:"shower",placeId:3218344,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:170268,name:"Acapulco",         dur:90 } ]},
    "33": { locations: [ {id:"s",type:"shower",placeId:2965425,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:188643,name:"Blankenberge",     dur:95 } ]},
    "34": { locations: [ {id:"s",type:"shower",placeId:3222559,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:195084,name:"Pirita",           dur:15 } ]},
    "35": { locations: [ {id:"s",type:"shower",placeId:3198434,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:249590,name:"Hatay",            dur:65 } ]},
    "36": { locations: [ {id:"s",type:"shower",placeId:3218479,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:282985,name:"Srebrno",          dur:95 } ]},
    "38": { locations: [ {id:"s",type:"shower",placeId:3198981,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:358359,name:"St Lawrence River",dur:95 } ]},
    "39": { locations: [ {id:"s",type:"shower",placeId:3222154,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:473018,name:"Sentosa",          dur:36 } ]},
    "42": { locations: [ {id:"s",type:"shower",placeId:3231282,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:653963,name:"Tisza",            dur:90 } ]},
    "45": { locations: [ {id:"s",type:"shower",placeId:3255714,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:670043,name:"Putuo Shan",       dur:90 } ]},
    "46": { locations: [ {id:"s",type:"shower",placeId:3198948,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:773546,name:"Constanţa",        dur:95 } ]},
    "47": { locations: [ {id:"s",type:"shower",placeId:3204448,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:782567,name:"Urla",             dur:90 } ]},
    "48": { locations: [ {id:"s",type:"shower",placeId:3231449,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:847919,name:"Wielkopolskie",    dur:100} ]},
    "49": { locations: [ {id:"s",type:"shower",placeId:3177850,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:1174002,name:"Pliva",           dur:95 } ]},
    "50": { locations: [ {id:"s",type:"shower",placeId:3023079,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:1349118,name:"Elliott Bay Park", dur:20 } ]},
    "51": { locations: [ {id:"s",type:"shower",placeId:3202857,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:1845324,name:"St Lucia",        dur:90 } ]},
    "52": { locations: [ {id:"s",type:"shower",placeId:3218697,name:"Duş Evi",dur:0}, {id:"p",type:"path",placeId:1886305,name:"Lago di Garda",   dur:95 } ]},
    "53": { locations: [ {id:"s",type:"shower",placeId:3201807,name:"Duş Evi",dur:0} ]},
    "54": { locations: [ {id:"s",type:"shower",placeId:3188092,name:"Duş Evi",dur:0} ]},
    "55": { locations: [ {id:"s",type:"shower",placeId:3222289,name:"Duş Evi",dur:0} ]},
    "56": { locations: [ {id:"s",type:"shower",placeId:3187003,name:"Duş Evi",dur:0} ]},
    "58": { locations: [ {id:"s",type:"shower",placeId:3230603,name:"Duş Evi",dur:0} ]},
    "60": { locations: [ {id:"s",type:"shower",placeId:3200260,name:"Duş Evi",dur:0} ]},
    "61": { locations: [ {id:"s",type:"shower",placeId:3263617,name:"Duş Evi",dur:0} ]},
    "62": { locations: [ {id:"s",type:"shower",placeId:3227018,name:"Duş Evi",dur:0} ]},
};

// CITY SHORTCUTS — helper
const getCityData = () => {
    const custom = (() => { try { return JSON.parse(GM_getValue('tvip_city_custom','null')); } catch { return null; } })();
    if (!custom) return CITY_DATA;
    const merged = {};
    Object.keys({...CITY_DATA,...custom}).forEach(id => {
        merged[id] = custom[id] ?? CITY_DATA[id];
    });
    return merged;
};

const typeIcon = t => t==='shower'?'🚿':t==='path'?'🌿':'📍';
const typeLabel = t => t==='shower'?s('csShower'):t==='path'?s('csPath'):s('csOther');

const PM = '/World/Popmundo.aspx';

const applyCityShortcuts = () => {
    if (!guard(K.cityShortcuts)) return;
    const airport = document.getElementById('ctl00_cphLeftColumn_ctl00_lnkAirport'); if (!airport) return;
    const cityDd  = document.getElementById('ctl00_cphRightColumn_ctl01_ddlCities'); if (!cityDd) return;
    const props   = getCityData()[cityDd.value]; if (!props) return;
    const tbody   = airport.closest('tr')?.parentElement; if (!tbody) return;
    props.locations.forEach(loc => {
        const tr = document.createElement('tr');
        const label  = `${typeIcon(loc.type)} ${loc.name}`;
        const durStr = loc.type==='path' && loc.dur ? ` — ${loc.dur} ${s('csMin')}` : '';
        tr.innerHTML = `
            <td>${typeLabel(loc.type)}:</td>
            <td><a href="${PM}/Locale/${loc.placeId}">${label}</a>${durStr}</td>
            <td class="right"><a class="icon" href="${PM}/Locale/MoveToLocale/${loc.placeId}"><img src="../../../Static/Icons/movetolocale.png" alt="" style="border:0"></a></td>`;
        tbody.appendChild(tr);
    });
};
// CITY MANAGER MODAL
const openCityManager = () => {
    const cityDd = document.getElementById('ctl00_cphRightColumn_ctl01_ddlCities');
    const activeCityId = cityDd?.value || Object.keys(CITY_DATA)[0];

    mkModal('🏙️ ' + s('cityMgr'), (cont) => {
        // Şehir dropdown
        const topRow = mk('div'); topRow.style.cssText='display:flex;gap:6px;align-items:center;margin-bottom:10px';
        const citySelect = mk('select'); citySelect.style.cssText='flex:1;padding:4px 6px;border:1px solid #ccc;border-radius:4px;font-size:12px';
        const allCityIds = [...new Set([...Object.keys(CITY_DATA), ...Object.keys((() => { try{return JSON.parse(GM_getValue('tvip_city_custom','{}'));}catch{return {};} })())])].sort((a,b)=>+a-+b);
        allCityIds.forEach(id => {
            const o = mk('option','', CITY_NAMES[id] ? `${CITY_NAMES[id]} (ID ${id})` : `ID ${id}`); o.value=id;
            if (id===activeCityId) o.selected=true;
            citySelect.appendChild(o);
        });
        const resetBtn = mkB(s('cityMgrReset'),'btn-sm btn-r',() => {
            if (!confirm(s('cityMgrResetQ'))) return;
            const custom = (() => { try{return JSON.parse(GM_getValue('tvip_city_custom','{}'));}catch{return {};} })();
            delete custom[citySelect.value];
            GM_setValue('tvip_city_custom', JSON.stringify(custom));
            renderList();
        });
        topRow.append(citySelect, resetBtn);
        cont.appendChild(topRow);

        // Liste
        const listWrap = mk('div'); cont.appendChild(listWrap);

        // Ekle formu
        const formWrap = mk('div'); formWrap.style.cssText='border-top:1px solid #eee;margin-top:8px;padding-top:8px';
        formWrap.appendChild(mk('div','tvip-sec', s('cityMgrAdd')));
        const fRow1 = mk('div'); fRow1.style.cssText='display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:6px';
        const typeSel = mk('select'); typeSel.style.cssText='padding:4px;border:1px solid #ccc;border-radius:4px;font-size:11px';
        [['shower',s('csShower')],['path',s('csPath')],['other',s('csOther')]].forEach(([v,l])=>{ const o=mk('option','',`${typeIcon(v)} ${l}`);o.value=v;typeSel.appendChild(o); });
        const placeIdInp = mk('input'); placeIdInp.type='number'; placeIdInp.placeholder='Mekan ID'; placeIdInp.style.cssText='padding:4px 6px;border:1px solid #ccc;border-radius:4px;font-size:11px;width:100%;box-sizing:border-box';
        fRow1.append(typeSel, placeIdInp);
        const fRow2 = mk('div'); fRow2.style.cssText='display:grid;grid-template-columns:1fr auto auto;gap:6px;margin-bottom:6px;align-items:end';
        const nameInp = mk('input'); nameInp.type='text'; nameInp.placeholder=s('cityMgrName'); nameInp.style.cssText='padding:4px 6px;border:1px solid #ccc;border-radius:4px;font-size:11px;width:100%;box-sizing:border-box';
        const durInp  = mk('input'); durInp.type='number'; durInp.placeholder='dk'; durInp.min='0'; durInp.style.cssText='padding:4px 6px;border:1px solid #ccc;border-radius:4px;font-size:11px;width:52px';
        const durLbl  = mk('span','','dk'); durLbl.style.cssText='font-size:11px;color:#888;white-space:nowrap';

        // dur satırı sadece path seçilince göster
        const durWrap = mk('div'); durWrap.style.cssText='display:flex;align-items:center;gap:4px';
        durWrap.append(durInp, durLbl);
        const toggleDur = () => { durWrap.style.display = typeSel.value==='path'?'flex':'none'; };
        typeSel.addEventListener('change', toggleDur); toggleDur();
        fRow2.append(nameInp, durWrap);

        const addBtn = mkB('+ '+s('cityMgrAddBtn'),'btn-g btn-sm',() => {
            const cityId = citySelect.value;
            const pid    = parseInt(placeIdInp.value);
            const nm     = nameInp.value.trim();
            if (!pid || !nm) return;
            const custom = (() => { try{return JSON.parse(GM_getValue('tvip_city_custom','{}'));}catch{return {};} })();
            if (!custom[cityId]) custom[cityId] = { locations: JSON.parse(JSON.stringify((CITY_DATA[cityId]?.locations)||[])) };
            custom[cityId].locations.push({ id:'c'+Date.now(), type:typeSel.value, placeId:pid, name:nm, dur: typeSel.value==='path'?parseInt(durInp.value)||0:0 });
            GM_setValue('tvip_city_custom', JSON.stringify(custom));
            placeIdInp.value=''; nameInp.value=''; durInp.value='';
            renderList();
        });
        formWrap.append(fRow1, fRow2, addBtn);
        cont.appendChild(formWrap);

        const renderList = () => {
            listWrap.innerHTML='';
            const cityId  = citySelect.value;
            const custom  = (() => { try{return JSON.parse(GM_getValue('tvip_city_custom','{}'));}catch{return {};} })();
            const base    = CITY_DATA[cityId]?.locations || [];
            const locs    = custom[cityId]?.locations ?? base;
            const isCustom= !!custom[cityId];

            if (isCustom) {
                const tag = mk('span','', '✏️ ' + s('cityMgrCustom')); tag.style.cssText='font-size:10px;color:#6f42c1;display:block;margin-bottom:6px';
                listWrap.appendChild(tag);
            }

            locs.forEach((loc, i) => {
                const row = mk('div'); row.style.cssText='display:flex;align-items:center;gap:6px;padding:5px 6px;background:#f8f9fa;border-radius:4px;margin-bottom:3px;border:1px solid #e8e8e8;font-size:12px';
                const ico  = mk('span','', typeIcon(loc.type)); ico.style.flexShrink='0';
                const info = mk('span','', `${loc.name} (${loc.placeId})${loc.type==='path'&&loc.dur?' — '+loc.dur+' dk':''}`); info.style.flex='1';
                const editB = mkB('✏','btn-sm btn-grey',() => {
                    const nName = prompt(s('cityMgrName')+':',loc.name); if(nName===null) return;
                    const nId   = prompt('Mekan ID:',String(loc.placeId)); if(nId===null) return;
                    const nDur  = loc.type==='path' ? (prompt('Dakika:',String(loc.dur))||'0') : '0';
                    const c2 = (() => { try{return JSON.parse(GM_getValue('tvip_city_custom','{}'));}catch{return {};} })();
                    if (!c2[cityId]) c2[cityId]={ locations: JSON.parse(JSON.stringify(base)) };
                    c2[cityId].locations[i] = {...loc, name:nName.trim()||loc.name, placeId:parseInt(nId)||loc.placeId, dur:parseInt(nDur)||0};
                    GM_setValue('tvip_city_custom', JSON.stringify(c2)); renderList();
                });
                const delB  = mkB('🗑','btn-sm btn-r',() => {
                    if (!confirm(s('cityMgrDelQ'))) return;
                    const c2 = (() => { try{return JSON.parse(GM_getValue('tvip_city_custom','{}'));}catch{return {};} })();
                    if (!c2[cityId]) c2[cityId]={ locations: JSON.parse(JSON.stringify(base)) };
                    c2[cityId].locations.splice(i,1);
                    GM_setValue('tvip_city_custom', JSON.stringify(c2)); renderList();
                });
                row.append(ico, info, editB, delB); listWrap.appendChild(row);
            });
            if (!locs.length) { const e=mk('div','',s('cityMgrEmpty')); e.style.cssText='color:#999;font-size:12px'; listWrap.appendChild(e); }
        };

        citySelect.addEventListener('change', renderList);
        renderList();
    });
};

// EN strings for PopControl export
window.__ppcStrHelper = {"menuTitle": "🎨 Helper", "save": "✔ Save", "readMe": "📖 Read Me", "langLabel": "Language", "backup": "📤 Backup", "restore": "📥 Restore", "backupOk": "Backup created.", "restoreOk": "Restored.", "restoreErr": "Invalid file.", "restoreQ": "What to do with current data?", "mergeLbl": "Merge", "replaceLbl": "Replace", "cancelLbl": "Cancel", "close": "Close", "secGeneral": "GENERAL", "secTable": "TABLE", "secItems": "ITEMS", "secMusic": "MUSIC", "secNav": "NAVIGATION", "pb": "📊 Show % on bars", "alignLeft": "⬅️ Align page to left", "tableTools": "🔍 Table sort & search", "itemId": "🏷️ Item ID — Appends ID number next to item links", "colorScoring": "🌈 Coloured Scoring System — Adds a coloured badge next to fame links (0-26)", "autoDelivery": "✅ Auto Delivery — Automatically checks delivery confirmation checkboxes", "ticketPricer": "🎟️ Ticket Pricer — Shows recommended ticket price on artist invite page", "imageCtrl": "🖼️ Slow/Broken Image Control — 5s timeout; placeholder + click to retry", "tableAvg": "📈 Add average row to tables", "itemFilters": "🎒 Show Only Takeable Items", "jamHelper": "🎸 Select incomplete songs for Jam", "repertoireF": "🎵 Filter repertoire by category", "sendItems": "📦 Auto-send same item multiple times", "bulkOffer": "🏷️ Offer items in bulk at set price", "bulkAccept": "🛒 Auto-accept incoming offers", "cityShortcuts": "🏙️ Show shower house & paths in city", "bbMinimize": "—", "bbRestore": "🎮", "psPlh": "Search table...", "psCount": "results", "tdConfirm": "Hide the search box for this table?", "tdHide": "Hide search", "tdClear": "Reset Hidden Tables", "btnShowAll": "👁 Show All Items", "btnOnlyTake": "🔒 Only Takeable", "resetOffered": "🗑️ Reset Offer List", "confirmReset": "Reset offered items list?", "resetDone": "Done.", "hideOfferedChk": "Hide offered items", "jamBtn": "⏱ Jam <100%", "repAll": "All Songs", "repMarket": "Market Songs", "repNoMarket": "Non-Market", "repJam": "Jam Songs", "repSetlist": "Setlist Songs", "repSecret": "Secret Songs", "repTitle": "🎵 Repertoire Filter", "siWarning": "⚠️ Select an item with multiple copies", "siCount": "How many?", "siSend": "🔄 Send", "siCancel": "❌ Cancel", "siStop": "⏹ Stop", "siStopping": "Stopping...", "siDone": "✅ Done!", "boTitle": "Bulk Offer", "boItemName": "Item name (prefix):", "boQty": "Quantity:", "boPrice": "Price (M$):", "boStart": "▶ Offer", "boStop": "■ Stop", "boReady": "Ready.", "boDone": "All offers completed!", "boStopped": "Stopped by user.", "boResumed": "Reloaded, resuming...", "boCritErr": "Critical error: Page elements gone.", "boErrName": "Error: Enter item name.", "boErrQty": "Error: Invalid quantity.", "boErrPrice": "Error: Invalid price.", "baTitle": "Bulk Accept", "baItemName": "Item Name (Optional):", "baItemPlh": "Leave blank for all", "baMaxPrice": "Max Price (M$):", "baStart": "▶ Accept", "baStop": "■ Stop", "baReady": "Ready.", "baNoSection": "Offer section not found.", "baResumed": "Reloaded, resuming...", "baErrPrice": "Error: Invalid price.", "baFree": "Free", "bcTitle": "Bulk Cancel", "bcItemName": "Item Name (Optional):", "bcFilter": "Filter:", "bcFilterAll": "All", "bcFilterFree": "Free Only", "bcFilterPaid": "Paid Only", "bcStart": "▶ Cancel", "bcStop": "■ Stop", "bcReady": "Ready.", "bcNoSection": "Offer section not found.", "csShower": "Shower House", "csPath": "Path", "csGoShower": "Go to shower house", "csGoPath": "Go to path", "csMin": "Minutes", "csOther": "Other Location", "cityMgr": "Manage City Shortcuts", "cityMgrReset": "↩ Reset", "cityMgrResetQ": "Reset this city to default?", "cityMgrAdd": "Add Location", "cityMgrAddBtn": "Add", "cityMgrName": "Location Name", "cityMgrCustom": "Customized", "cityMgrDelQ": "Delete this location?", "cityMgrEmpty": "No locations.", "taAvg": "🌍 Average", "taDiscAvg": "🎯 Heist Avg:"};

function _waitPC(cb,n){n=n||0;if(unsafeWindow.PopControl){cb();return;}if(n<20)setTimeout(function(){_waitPC(cb,n+1);},300);}

// ─── MENU ─────────────────────────────────────────────────────────────────────
const injectMenu = () => {
    if (document.getElementById('tvip-bar')) return;

    // ── PANEL ──
    const hpanel = mk('div', 'tvip-hpanel');
    hpanel.id = 'tvip-hpanel'; hpanel.style.display = 'none';

    const checks = {};
    const mkCheck = (ck, lk) => {
        const lbl = mk('label', 'tvip-chk');
        const chk = Object.assign(mk('input'), { type: 'checkbox', checked: isOn(ck) });
        checks[ck] = chk; lbl.append(chk, mk('span', '', s(lk))); hpanel.appendChild(lbl);
    };
    const mkHr  = ()  => hpanel.appendChild(mk('hr', 'tvip-hr'));
    const mkSec = txt => hpanel.appendChild(mk('div', 'tvip-sec', txt));

    mkSec(s('secGeneral'));
    mkCheck(K.pb,           'pb');
    mkCheck(K.alignLeft,    'alignLeft');
    mkCheck(K.imageCtrl,    'imageCtrl');
    mkHr();
    mkSec(s('secTable'));
    mkCheck(K.tableTools,   'tableTools');
    mkCheck(K.itemId,       'itemId');
    mkCheck(K.colorScoring, 'colorScoring');
    mkCheck(K.tableAvg,     'tableAvg');
    mkHr();
    mkSec(s('secItems'));
    mkCheck(K.itemFilters,  'itemFilters');
    mkCheck(K.hideOffBox,   'hideOfferedChk');
    mkCheck(K.autoDelivery, 'autoDelivery');
    mkCheck(K.ticketPricer, 'ticketPricer');
    mkCheck(K.sendItems,    'sendItems');
    mkCheck(K.bulkOffer,    'bulkOffer');
    mkCheck(K.bulkAccept,   'bulkAccept');
    mkHr();
    mkSec(s('secMusic'));
    mkCheck(K.jamHelper,    'jamHelper');
    mkCheck(K.repertoireF,  'repertoireF');
    mkHr();
    mkSec(s('secNav'));
    mkCheck(K.cityShortcuts,'cityShortcuts');
    hpanel.appendChild(mkB('🏙️ ' + s('cityMgr'), 'btn-sm btn-grey', () => openCityManager()));
    mkHr();

    // LANGUAGE SELECTOR
    mkSec(s('langLabel'));
    const langRow = mk('div', 'tvip-lang-row');
    [['TR','🇹🇷 Türkçe'],['EN','🇬🇧 English'],['PT','🇧🇷 Português']].forEach(([code, label]) => {
        const b = mk('button', 'tvip-lang-btn' + (LANG === code ? ' active' : ''), label);
        b.onclick = () => { CK.set('ppm_lang', code); location.reload(); };
        langRow.appendChild(b);
    });
    hpanel.appendChild(langRow);
    mkHr();

    // ACTION BUTTONS
    const row1 = mk('div'); row1.style.cssText = 'display:flex;flex-wrap:wrap;gap:4px;margin-top:4px';
    const row2 = mk('div'); row2.style.cssText = 'display:flex;flex-wrap:wrap;gap:4px;margin-top:4px';

    const saveBtn = mkB(s('save'), 'btn-g', () => {
        Object.entries(checks).forEach(([k, c]) => CK.set(k, c.checked ? '1' : '0'));
        location.reload();
    });
    const readBtn = mk('a', 'btn-grey btn-sm', s('readMe'));
    readBtn.href = 'https://rentry.org/HelperOku';
    readBtn.target = '_blank'; readBtn.style.textDecoration = 'none';
    row1.append(
        saveBtn, readBtn,
        mkB(s('backup'),  'btn-b btn-sm',    () => dbExport()),
        mkB(s('restore'), 'btn-grey btn-sm', () => dbImport())
    );
    const tdClearBtn = mkB(s('tdClear'), 'btn-sm btn-grey', () => { clearTableDeny(); alert(s('resetDone')); });
    const resetOfferBtn = mkB(s('resetOffered'), 'btn-sm', () => {
        if (confirm(s('confirmReset'))) { saveOfferedList([]); alert(s('resetDone')); }
    });
    resetOfferBtn.style.cssText = 'padding:2px 8px;background:none;color:#c0392b;border:1px solid #c0392b;border-radius:3px;cursor:pointer;font-size:11px';
    row2.append(tdClearBtn, resetOfferBtn);
    hpanel.append(row1, row2);

    const closePanel = () => {
        hpanel.style.display = 'none';
        document.getElementById('tvip-helper-btn')?.classList.remove('active');
    };
    const togglePanel = () => {
        const open = hpanel.style.display !== 'none';
        hpanel.style.display = open ? 'none' : 'block';
    };

    // Modal — hem standalone bar hem PopControl butonu kullanır
    const _isModalOpen = () => !!document.getElementById('tvip-modal-ov');
    const closeModal = () => {
        const ov = document.getElementById('tvip-modal-ov');
        if (!ov) return;
        document.body.appendChild(hpanel);
        hpanel.style.cssText = 'display:none';
        ov.remove();
    };
    const openModal = () => {
        if (_isModalOpen()) { closeModal(); return; }
        const novEl = document.createElement('div'); novEl.id = 'tvip-modal-ov';
        novEl.style.cssText = [
            'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:99997',
            'display:flex;align-items:center;justify-content:center',
            'padding:20px;box-sizing:border-box'
        ].join(';');
        // Reset hpanel inline style so CSS class takes over inside the overlay
        hpanel.removeAttribute('style');
        hpanel.style.cssText = [
            'display:block!important;position:relative!important',
            'top:auto!important;right:auto!important;left:auto!important',
            'max-height:85vh;overflow-y:auto;border-radius:10px',
            'min-width:240px;max-width:500px;width:92%;box-sizing:border-box'
        ].join(';');
        novEl.appendChild(hpanel);
        novEl.addEventListener('click', function(e) { if (e.target === novEl) closeModal(); });
        document.body.appendChild(novEl);
    };
    const togglePanelModal = () => _isModalOpen() ? closeModal() : openModal();

    // ── KLASİK BAR ──
    const bar = mk('div', 'tvip-bar'); bar.id = 'tvip-bar';
    const lnk = (txt, fn) => { const a = mk('a', '', txt); a.href = '#'; a.onclick = e => { e.preventDefault(); fn(); }; return a; };
    bar.appendChild(lnk(s('menuTitle'), togglePanelModal));
    document.body.append(bar, hpanel);
    // click-outside: sadece modal açık değilken hpanel'i kapat
    document.addEventListener('click', e => {
        if (_isModalOpen()) return;
        if (!bar.contains(e.target) && !hpanel.contains(e.target)) closePanel();
    });

    // Auto-connect to PopControl if available
    _waitPC(function() {
        unsafeWindow.PopControl.register({
            id: 'helper', icon: '🎨', label: 'Helper',
            strings: window.__ppcStrHelper || {},
            buttons: [{ icon: '🎨', label: 'Helper', onClick: togglePanelModal }],
            onUndo: function() {
                closeModal();
                bar.style.display = '';
            },
        });
        bar.style.display = 'none';
    });
};

// ─── INIT ─────────────────────────────────────────────────────────────────────
applyAlignLeft();
applyProgressBar();
applyItemId();
applyColorScoring();
applyImageCtrl();
applyTableSort();
applyPageSearch();
applyAutoDelivery();
applyTicketPricer();
applyTableAvg();
applyJamHelper();
applyRepertoireFilter();
applyQuickSend();
setupBulkOfferUI();
setupBulkAcceptUI();
injectMenu();

if (location.href.includes('/Locale/ItemsEquipment/')) applyOnlyYours();
if (location.href.includes('/Character/OfferItem/'))   applyHideOffered();
if (document.getElementById('ctl00_cphLeftColumn_ctl00_lnkAirport')) applyCityShortcuts();

})();