🎨 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

})();