Popmundo tour planner β geographic optimization, template system and booking interface helper.
// ==UserScript==
// @name π Route49
// @name:tr π Route49
// @name:en π Route49
// @name:pt-BR π Route49
// @namespace pop.route49
// @version 1.7
// @description Popmundo turne planlayΔ±cΔ±sΔ± β coΔrafi optimizasyon, Εablon sistemi ve konser arayΓΌzΓΌ yardΔ±mcΔ±sΔ±.
// @description:tr Popmundo turne planlayΔ±cΔ±sΔ± β coΔrafi optimizasyon, Εablon sistemi ve konser arayΓΌzΓΌ yardΔ±mcΔ±sΔ±.
// @description:en Popmundo tour planner β geographic optimization, template system and booking interface helper.
// @description:pt-BR Planejador de turnΓͺ Popmundo β otimizaΓ§Γ£o geogrΓ‘fica, sistema de modelos e assistente de interface de shows.
// @author luke-james-gibson
// @license MIT
// @match https://*.popmundo.com/*
// @run-at document-end
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_info
// ==/UserScript==
(function () {
'use strict';
try { const p = JSON.parse(localStorage.getItem('ppc_enabled') || '{}'); if (p['route49'] === false) return; } catch {}
// βββ UTILS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const DEBUG = localStorage.getItem('r49_debug') === 'true';
const log = (...a) => DEBUG && console.log('[Route49]', ...a);
const delay = ms => new Promise(r => setTimeout(r, ms));
const rdelay = () => { try { const f = JSON.parse(localStorage.getItem('GreasyMonkey_r49_settings')||GM_getValue('r49_settings','{"fastMode":false}')).fastMode; return delay(f ? 500+Math.random()*1000 : 2000+Math.random()*4000); } catch { return delay(2000+Math.random()*4000); } };
const mk = (tag, cls, txt) => { const e = document.createElement(tag); if (cls) e.className = cls; if (txt != null) e.textContent = txt; return e; };
const mkB = (txt, cls, fn) => Object.assign(mk('button', cls, txt), { onclick: fn, type: 'button' });
const $ = s => document.querySelector(s);
const norm = s => { try { return String(s||'').normalize('NFD').replace(/\p{M}/gu,'').toLowerCase().trim(); } catch { return String(s||'').toLowerCase().replace(/[\u0300-\u036f]/g,'').trim(); } };
const fmtISO = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
const fmtDisp = d => `${String(d.getDate()).padStart(2,'0')}.${String(d.getMonth()+1).padStart(2,'0')}.${d.getFullYear()}`;
const addDays = (d, n) => { const r = new Date(d); r.setDate(r.getDate() + n); return r; };
// βββ LANGUAGE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const LANG = (() => { try { const m = document.cookie.match(/ppm_lang=([^;]+)/); return m ? m[1] : 'TR'; } catch { return 'TR'; } })();
const _D = (tr, en, pt) => ({ TR: tr, EN: en, PT: pt }[LANG] || tr);
const _LC = (() => { try { return JSON.parse(localStorage.getItem('ppc_lc_route49') || '{}'); } catch { return {}; } })();
const T = k => _LC[k] || S[k] || k;
function setLang(lang) {
document.cookie = `ppm_lang=${lang};path=/;max-age=31536000`;
location.reload();
}
// βββ PM DATE ENGINE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const PM_ANCHOR = new Date('2026-02-25T00:00:00');
const MS_DAY = 86400000;
const PM_YEAR_LEN = 56;
const TODAY = new Date();
const realToPM = d => { const days = Math.floor((d - PM_ANCHOR) / MS_DAY); return { year: 152 + Math.floor(days / PM_YEAR_LEN), day: ((days % PM_YEAR_LEN) + PM_YEAR_LEN) % PM_YEAR_LEN + 1 }; };
const pmToReal = (year, day) => new Date(PM_ANCHOR.getTime() + ((year - 152) * PM_YEAR_LEN + (day - 1)) * MS_DAY);
const dateLabel = d => { const p = realToPM(d); return `${fmtDisp(d)} (${_D('YΔ±l','Year','Ano')} ${p.year} ${_D('GΓΌn','Day','Dia')} ${p.day})`; };
// βββ SPECIAL DAYS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const SPECIAL_DAYS = {
1: { icon:'π', label:_D('Yeni YΔ±lΔ±n Δ°lk GΓΌnΓΌ','New Year\'s Day','Ano Novo'), anchor:false },
9: { icon:'π₯', label:_D('Big Bang','Big Bang','Big Bang'), anchor:true },
13: { icon:'π', label:_D('Popopalooza GΓΌnΓΌ','Popopalooza Day','Dia Popopalooza'), anchor:false },
22: { icon:'π¦', label:_D('Kraken\'in DΓΆnΓΌΕΓΌ','Return of the Kraken','Retorno do Kraken'), anchor:false },
28: { icon:'π', label:_D('ΓlΓΌler GΓΌnΓΌ','Day of the Dead','Dia dos Mortos'), anchor:true },
40: { icon:'ποΈ', label:_D('Aziz Kobe GΓΌnΓΌ','Saint Kobe\'s Day','Dia de SΓ£o Kobe'), anchor:true },
48: { icon:'π', label:_D('CadΔ±lar BayramΔ±','Halloween','Halloween'), anchor:false },
52: { icon:'π', label:_D('Noel','Christmas','Natal'), anchor:false },
54: { icon:'ποΈ', label:_D('KurtuluΕ GΓΌnΓΌ','Liberation Day','Dia da LibertaΓ§Γ£o'), anchor:false },
56: { icon:'π₯', label:_D('Yeni YΔ±l Arifesi','New Year\'s Eve','VΓ©spera de Ano Novo'), anchor:false },
};
const ANCHOR_DAYS = Object.entries(SPECIAL_DAYS).filter(([,v]) => v.anchor);
// βββ CITY CATALOG βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const CITIES =[
{id:8, en:'Amsterdam', tz:1 },
{id:35, en:'Ankara', tz:3 },
{id:61, en:'Antalya', tz:3 },
{id:58, en:'Baku', tr:'BakΓΌ', tz:4 },
{id:9, en:'Barcelona', tr:'Barselona', tz:1 },
{id:36, en:'Belgrade', tr:'Belgrad', tz:1 },
{id:7, en:'Berlin', tz:1 },
{id:33, en:'Brussels', tr:'BrΓΌksel', tz:1 },
{id:42, en:'Budapest', tr:'BudapeΕte', tz:1 },
{id:17, en:'Buenos Aires', tz:-3 },
{id:46, en:'Bucharest', tr:'BΓΌkreΕ', tz:2 },
{id:55, en:'Jakarta', tr:'Cakarta', tz:7 },
{id:29, en:'Dubrovnik', tz:1 },
{id:27, en:'Glasgow', tz:0 },
{id:19, en:'Helsinki', tz:2 },
{id:30, en:'Istanbul', tr:'Δ°stanbul', tz:3 },
{id:47, en:'Izmir', tr:'Δ°zmir', tz:3 },
{id:51, en:'Johannesburg', tz:2 },
{id:22, en:'Copenhagen', tr:'Kopenhag', tz:1 },
{id:56, en:'Kyiv', tz:2 },
{id:5, en:'London', tr:'Londra', tz:0 },
{id:14, en:'Los Angeles', tz:-8 },
{id:24, en:'Madrid', tz:1 },
{id:54, en:'Manila', tz:8 },
{id:10, en:'Melbourne', tz:11 },
{id:32, en:'Mexico City', tz:-6 },
{id:52, en:'Milan', tr:'Milano', tz:1 },
{id:38, en:'Montreal', tz:-5 },
{id:18, en:'Moscow', tr:'Moskova', tz:3 },
{id:11, en:'Nashville', tz:-6 },
{id:6, en:'New York', tz:-5 },
{id:20, en:'Paris', tz:1 },
{id:31, en:'Porto', tz:0 },
{id:25, en:'Rio de Janeiro', tz:-3 },
{id:23, en:'Rome', tr:'Roma', tz:1 },
{id:45, en:'Shanghai', tr:'Εangay', tz:8 },
{id:21, en:'Sao Paulo', tz:-3 },
{id:49, en:'Sarajevo', tr:'Saraybosna', tz:1 },
{id:50, en:'Seattle', tz:-8 },
{id:60, en:'Chicago', tr:'Εikago', tz:-6 },
{id:39, en:'Singapore', tr:'Singapur', tz:8 },
{id:53, en:'Sofia', tr:'Sofya', tz:2 },
{id:1, en:'Stockholm', tr:'Stokholm', tz:1 },
{id:34, en:'Tallinn', tz:2 },
{id:62, en:'Tokyo', tz:9 },
{id:16, en:'Toronto', tz:-5 },
{id:26, en:'TromsΓΈ', tz:1 },
{id:48, en:'Warsaw', tr:'VarΕova', tz:1 },
{id:28, en:'Vilnius', tz:2 },
];
const cityById = id => CITIES.find(c => c.id === id);
const cityByAny = str => CITIES.find(c => norm(c.en)===norm(str) || norm(c.tr)===norm(str));
const cityName = c => c ? (LANG==='TR' ? (c.tr||c.en) : c.en) : '?';
// βββ TOUR TYPES & PLANS (v0.5 Architecture) βββββββββββββββββββββββββββββββββββ
const _TT =[30,47,61,35,58,18,56,45,54,55,39,62,10,17,25,21,32,14,50,60,11,16,38,6,5,27,31,24,9,20,33,8,7,22,26,1,19,34,28,48,42,46,53,36,49,29,23,52,51];
const _LI =[5,27,6,38,16,11,60,50,14,32,21,25,17,10,55,39,54,45,62,51,31,24,9,20,33,8,7,22,26,1,19,34,28,48,42,46,53,36,49,29,23,52,18,56,58,35,61,47,30];
const TOUR_PLANS = {
'tt': { name:'βοΈ GΓΌneΕ Δ°zinde (Istanbul β Johannesburg)', legs:_TT },
'li': { name:'ποΈ Prime Line (London β Istanbul)', legs:_LI },
'custom': { name:_D('βοΈ Γzel Rota','βοΈ Custom Route','βοΈ Rota Personalizada'), legs:[] },
};
const TOUR_TYPES = {
'relax': { name:'π Relax (56 GΓΌn)', dpc:2, doubleDay:true, durationDays:56 },
'blitz': { name:'β‘ Blitz (25 GΓΌn)', dpc:1, doubleDay:true, durationDays:25, blitzMode:true },
'hardcore': { name:'π€ Hardcore (51 GΓΌn)', dpc:2, doubleDay:true, durationDays:51 },
'custom': { name:'βοΈ Γzel Tarihler', dpc:1, doubleDay:false, durationDays:56 },
};
// βββ STORAGE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const GMK = {
status: 'r49_status', // 'IDLE'|'RUNNING'|'PAUSED'
tour: 'r49_tour',
idx: 'r49_idx',
sets: 'r49_settings',
restore: 'r49_restore',
stadU: 'r49_stad_used',
stadY: 'r49_stad_year',
schemas: 'r49_schemas', // Dynamic array of saved schemas
};
const gmGet = (k,d) => { try { const v=GM_getValue(k,null); return v!==null?JSON.parse(v):d; } catch { return d; } };
const gmSet = (k,v) => { try { GM_setValue(k,JSON.stringify(v)); } catch {} };
const gmDel = k => { try { GM_deleteValue(k); } catch {} };
// βββ STRINGS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const S = {
title: _D('π Route49', 'π Route49', 'π Route49'),
tabPlan: _D('π Plan', 'π Plan', 'π Plano'),
tabSaved: _D('πΎ Εemalar', 'πΎ Schemas', 'πΎ Esquemas'),
tabSettings: _D('βοΈ Ayarlar', 'βοΈ Settings', 'βοΈ Config'),
tourType: _D('Turne TΓΌrΓΌ', 'Tour Type', 'Tipo de TurnΓͺ'),
tourPlan: _D('Turne PlanΔ±', 'Tour Plan', 'Plano de TurnΓͺ'),
startCity: _D('BaΕlangΔ±Γ§ Εehri', 'Start City', 'Cidade Inicial'),
customHint: _D('Εehir adlarΔ± (Δ°ngilizce), her satΔ±ra bir tane:','City names (English), one per line:','Nomes das cidades (inglΓͺs), um por linha:'),
artistId: _D('SanatΓ§Δ± ID', 'Artist ID', 'ID do Artista'),
startDate: _D('BaΕlangΔ±Γ§', 'Start', 'InΓcio'),
endDate: _D('BitiΕ', 'Fim', 'Fim'),
showsPerCity: _D('Εehir baΕΔ±na 2', '2 per city', '2 por cidade'),
doubleDay: _D('GΓΌnde 2 konser', '2 shows per day', '2 shows por dia'),
showsDDNote: _D('GΓΌnde 2 konser seΓ§ili deΔilse Εehir baΕΔ±na 2 konser yapΔ±lamaz.','Requires 2 shows/day to enable 2 per city.','Requer 2 shows/dia para ativar 2 por cidade.'),
showTimes: _D('Konser Saatleri (Ctrl+tΔ±k)', 'Show Times (Ctrl+click)', 'HorΓ‘rios (Ctrl+clique)'),
fameScore: _D('Ortalama ΕΓΆhret', 'Avg. Fame Score', 'Fama MΓ©dia'),
priceRange: _D('Bilet FiyatΔ± (M$)', 'Ticket Price (M$)', 'Ingresso (M$)'),
minStars: _D('Minimum YΔ±ldΔ±z', 'Min. Stars', 'Estrelas MΓnimas'),
sortMode: _D('KulΓΌp SΔ±ralamasΔ±', 'Club Sort', 'OrdenaΓ§Γ£o'),
sortHigh: _D('En YΓΌksek Fiyat', 'Highest Price', 'Maior PreΓ§o'),
sortLow: _D('En DΓΌΕΓΌk Fiyat', 'Lowest Price', 'Menor PreΓ§o'),
sortMid: _D('Orta Fiyat', 'Mid-Range', 'PreΓ§o MΓ©dio'),
sdTitle: _D('Γzel GΓΌn AyarlarΔ±', 'Special Day Settings', 'Config. de Dias Especiais'),
sdAra: _D('Ara', 'Rest', 'Descanso'),
stadCities: _D('Stadyum Εehirleri', 'Stadium Cities', 'Cidades EstΓ‘dio'),
citiesLabel: _D('Εehirler', 'Cities', 'Cidades'),
selAll: _D('TΓΌmΓΌ', 'All', 'Todos'),
selNone: _D('HiΓ§biri', 'None', 'Nenhum'),
btnPreview: _D('π Γnizle', 'π Preview', 'π PrΓ©-ver'),
btnStart: _D('βΆ BaΕlat', 'βΆ Start', 'βΆ Iniciar'),
btnStop: _D('βΉ Durdur', 'βΉ Stop', 'βΉ Parar'),
btnPause: _D('βΈ Duraklat', 'βΈ Pause', 'βΈ Pausar'),
btnResume: _D('βΆ Devam', 'βΆ Resume', 'βΆ Retomar'),
btnExportCSV: _D('β¬ CSV Δ°ndir', 'β¬ Export CSV', 'β¬ Exportar'),
btnImportCSV: _D('β¬ CSV YΓΌkle', 'β¬ Import CSV', 'β¬ Importar'),
btnClose: _D('β Kapat', 'β Close', 'β Fechar'),
btnReadme: _D('π Beni Oku', 'π README', 'π Leia-me'),
colDay: _D('Pop GΓΌnΓΌ', 'Pop Day', 'Dia Pop'),
colDate: _D('Tarih', 'Date', 'Data'),
colTime: _D('Saat', 'Time', 'Hora'),
colCity: _D('Εehir', 'City', 'Cidade'),
colVenue: _D('Mekan', 'Venue', 'Local'),
colPrice: _D('Fiyat', 'Price', 'PreΓ§o'),
venueBar: _D('Bar', 'Bar', 'Bar'),
venueStad: _D('π Stadyum', 'π Stadium', 'π EstΓ‘dio'),
warnTZ: _D('β° Saat FarkΔ±', 'β° TZ Gap', 'β° Fuso'),
warnFuture: _D('β³ HenΓΌz Erken', 'β³ Too Early', 'β³ Cedo'),
statusIdle: _D('HazΔ±r.', 'Ready.', 'Pronto.'),
statusDone: _D('β
Tur tamamlandΔ±!', 'β
Tour complete!', 'β
TurnΓͺ completa!'),
statusFuture: _D('β³ Devam iΓ§in bekle:', 'β³ Wait to continue:', 'β³ Aguardar:'),
limit57: _D('57 gΓΌn sΔ±nΔ±rΔ± β plan kaydedildi.','57-day limit β plan saved.','Limite 57 dias β plano salvo.'),
errorPaused: _D('Hata tespit edildi, duraklatΔ±ldΔ±.','Error detected, paused.','Erro detectado, pausado.'),
noTour: _D('Tur boΕ. Εehir veya tarih ayarlarΔ±nΔ± kontrol et.','Tour is empty.','TurnΓͺ vazia.'),
confirmStop: _D('Durdur ve verileri temizle?','Stop and clear data?','Parar e limpar dados?'),
savedPlanHdr: _D('Son Kaydedilen', 'Last Saved', 'Γltimo Salvo'),
btnEdit: _D('DΓΌzenle', 'Edit', 'Editar'),
btnStartPlan: _D('βΆ BaΕlat', 'βΆ Start', 'βΆ Iniciar'),
planName: _D('Εema AdΔ±', 'Schema Name', 'Nome do Esquema'),
btnSave: _D('πΎ Kaydet', 'πΎ Save', 'πΎ Salvar'),
summaryShows: _D('konser', 'shows', 'shows'),
// Artist quick-links (TOP VIP menΓΌsΓΌ)
mnuSchedule: _D('Program', 'Schedule', 'Agenda'),
mnuSetlist: _D('Performans PlanΔ± DΓΌzenleyici', 'Setlist Editor', 'Editor de Setlist'),
mnuPopularity: _D('PopΓΌlerlik', 'Popularity', 'Popularidade'),
mnuRepertoire: _D('Repertuar', 'Repertoire', 'RepertΓ³rio'),
mnuUpcoming: _D('Gelecek Konserler', 'Upcoming Concerts', 'PrΓ³ximos Shows'),
mnuEquipment: _D('Sahne EkipmanΔ±', 'Stage Equipment', 'Equipamento de Palco'),
mnuVehicle: _D('Tur AracΔ±', 'Tour Vehicle', 'VeΓculo de TurnΓͺ'),
mnuVehicleItems:_D('Εahsi EΕyalar', 'Personal Items', 'Itens Pessoais'),
mnuCrew: _D('Teknik Ekip', 'Technical Crew', 'Equipe TΓ©cnica'),
};
// βββ PRICE GUIDE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const PRICE_GUIDE =[
{score:0, min:1, max:5 },
{score:1, min:1, max:6 },
{score:2, min:2, max:7 },
{score:3, min:4, max:8 },
{score:4, min:6, max:9 },
{score:5, min:8, max:11 },
{score:6, min:10, max:13 },
{score:7, min:12, max:15 },
{score:8, min:14, max:20 },
{score:9, min:16, max:25 },
{score:10, min:18, max:30 },
{score:11, min:20, max:35 },
{score:12, min:25, max:45 },
{score:13, min:30, max:50 },
{score:14, min:35, max:55 },
{score:15, min:40, max:60 },
{score:16, min:45, max:75 },
{score:17, min:50, max:80 },
{score:18, min:55, max:85 },
{score:19, min:60, max:90 },
{score:20, min:65, max:95 },
{score:21, min:70, max:100},
{score:22, min:75, max:105},
{score:23, min:80, max:110},
{score:24, min:85, max:115},
{score:25, min:90, max:120},
{score:26, min:95, max:125},
];
// βββ DEFAULTS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const MIN_DATE_STR = fmtISO(addDays(TODAY, 3));
const DEF = {
tourType:'relax', tourPlan:'tt', startCityId:30,
doubleDay:true, dpc:2, fastMode:false,
showTimes:['14:00:00','22:00:00'],
priceMin:30, priceMax:50, minStars:50, sortMode:'price_desc',
startDate:MIN_DATE_STR, endDate:fmtISO(addDays(new Date(MIN_DATE_STR+'T00:00:00'), 56)),
artistId:'', enabledCities:CITIES.map(c=>c.id), stadiumCities:[], anchors:{},
};
// βββ BOOKSHOW SELECTORS βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const SEL = {
venueType:'#ctl00_cphLeftColumn_ctl01_ddlVenueTypes',
city: '#ctl00_cphLeftColumn_ctl01_ddlCities',
day: '#ctl00_cphLeftColumn_ctl01_ddlDays',
hour: '#ctl00_cphLeftColumn_ctl01_ddlHours',
findBtn: '#ctl00_cphLeftColumn_ctl01_btnFindClubs',
clubTable:'#tableclubs',
bookBtn: '#ctl00_cphLeftColumn_ctl01_btnBookShow',
};
const IS_BOOKSHOW = /\/Artist\/BookShow/i.test(window.location.pathname);
// βββ TOUR BUILDER (v0.5 Blitz & Anchor Logic) βββββββββββββββββββββββββββββββββ
function normLeg(l) { return typeof l==='number' ? {id:l,gapAfter:0} : {id:l.id,gapAfter:l.gapAfter||0}; }
function buildTour(settings) {
const typeKey = settings.tourType || 'relax';
const planKey = settings.tourPlan || 'tt';
const typeCfg = TOUR_TYPES[typeKey] || TOUR_TYPES['relax'];
let rawLegs;
if (planKey==='custom') rawLegs = parseCustomRoute(settings.customRoute||'').map(id=>({id,gapAfter:0}));
else rawLegs = (TOUR_PLANS[planKey]?.legs||[]).map(normLeg);
const enabled = new Set(settings.enabledCities||DEF.enabledCities);
const stadSet = new Set(settings.stadiumCities||[]);
const anchors = settings.anchors||{};
const minDateLimit = new Date(MIN_DATE_STR+'T00:00:00');
let stadUsed = (() => {
const cy=realToPM(new Date()).year, ly=gmGet(GMK.stadY,cy);
return ly===cy ? gmGet(GMK.stadU,0) : 0;
})();
let sDateStr = settings.startDate; if(new Date(sDateStr+'T00:00:00') < minDateLimit) sDateStr = MIN_DATE_STR;
const startDate = new Date(sDateStr+'T00:00:00');
const endDate = new Date(settings.endDate +'T00:00:00');
// Resolve anchor dates
const anchorDates = {}; // isoDate -> {cityId, restDays}
const anchorCities = new Set();
for (const[dayStr,anch] of Object.entries(anchors)) {
if (!anch.cityId && !anch.restDays) continue;
const pmDay = parseInt(dayStr);
const startPM = realToPM(startDate);
for (let yo=0; yo<=2; yo++) {
const cand = pmToReal(startPM.year+yo, pmDay);
if (cand>=startDate && cand<=endDate) {
anchorDates[fmtISO(cand)] = { cityId: anch.cityId, restDays: anch.restDays };
if (anch.cityId) anchorCities.add(anch.cityId);
break;
}
}
}
let pendingAnchors = Object.entries(anchorDates).map(([d, a]) => ({ date: new Date(d+'T00:00:00'), cityId: a.cityId, restDays: a.restDays })).sort((a,b)=>a.date-b.date);
// Remove anchored cities from normal flow, except start/end
const legs = rawLegs.filter((l, i) => {
if (i === 0 || i === rawLegs.length - 1) return true;
return !anchorCities.has(l.id);
});
let startIdx=0;
if (settings.startCityId && planKey!=='custom') {
const fi = legs.findIndex(l=>l.id===settings.startCityId);
if (fi>0) startIdx=fi;
}
const orderedLegs = legs.slice(startIdx).concat(legs.slice(0, startIdx));
let cursor = new Date(startDate);
const tour =[];
let blitzToggle = 0; // 0 = 14:00, 1 = 22:00
const time22 = settings.showTimes?.includes('22:00:00') ? '22:00:00' : (settings.showTimes?.slice(-1)[0]||'22:00:00');
const time14 = settings.showTimes?.includes('14:00:00') ? '14:00:00' : (settings.showTimes?.[0]||'14:00:00');
const singleTime = settings.showTimes?.[0]||'22:00:00';
for (let i=0; i<orderedLegs.length; i++) {
const leg = orderedLegs[i];
const cityId = leg.id;
if (!enabled.has(cityId)) { if (leg.gapAfter) cursor=addDays(cursor,leg.gapAfter); continue; }
if (cursor > endDate) break;
// Dinlenme gΓΌnlerini iΕleyen yardΔ±mcΔ± fonksiyon
const processAnchors = () => {
while (pendingAnchors.length > 0 && pendingAnchors[0].date <= cursor) {
const anc = pendingAnchors.shift();
if (anc.cityId) {
let aTime = time22;
if (tour.length > 0) {
const lc = cityById(tour[tour.length-1].cityId), tc = cityById(anc.cityId);
if (lc && tc && Math.abs(lc.tz - tc.tz) > 10) aTime = '22:00:00';
}
tour.push(mkShow(anc.cityId, anc.date, aTime, stadSet.has(anc.cityId)));
}
const nextAvail = addDays(anc.date, (anc.cityId ? 1 : 0) + (anc.restDays || 0));
if (cursor < nextAvail) cursor = nextAvail;
}
};
processAnchors();
if (cursor > endDate) break;
let baseT1 = time14, baseT2 = time22;
if (tour.length > 0) {
const lc = cityById(tour[tour.length-1].cityId), tc = cityById(cityId);
if (lc && tc && Math.abs(lc.tz - tc.tz) > 10) baseT1 = '22:00:00';
}
const isStad = stadSet.has(cityId) && stadUsed<10;
if (isStad) stadUsed++;
const isDouble = typeCfg.blitzMode ? true : settings.doubleDay;
const dpcVal = typeCfg.blitzMode ? 1 : settings.dpc;
if (typeCfg.blitzMode) {
tour.push(mkShow(cityId, cursor, blitzToggle === 0 ? baseT1 : baseT2, isStad));
if (blitzToggle === 1) cursor = addDays(cursor, 1);
blitzToggle = blitzToggle === 0 ? 1 : 0;
} else if (isDouble && dpcVal >= 2) {
tour.push(mkShow(cityId, cursor, baseT1, isStad));
tour.push(mkShow(cityId, cursor, baseT2, false));
cursor = addDays(cursor, 1);
} else {
tour.push(mkShow(cityId, cursor, isDouble ? baseT2 : singleTime, isStad));
cursor = addDays(cursor, 1);
}
if (leg.gapAfter) cursor=addDays(cursor,leg.gapAfter);
}
// Final sweep for remaining anchors
while(pendingAnchors.length > 0 && pendingAnchors[0].date <= endDate) {
const anc = pendingAnchors.shift();
if (anc.cityId) tour.push(mkShow(anc.cityId, anc.date, time22, stadSet.has(anc.cityId)));
}
// Sort naturally by date & time
tour.sort((a,b) => new Date(a.date+'T'+a.time) - new Date(b.date+'T'+b.time));
recalcWarnings(tour);
return tour;
}
function mkShow(cityId, dateObj, time, wantStad) {
return {cityId, date:fmtISO(dateObj), time, venueType:wantStad?'stadium':'bar', booked:false, skipped:false};
}
function parseCustomRoute(text) {
return text.split('\n').map(l=>l.trim()).filter(Boolean).map(l=>cityByAny(l)).filter(Boolean).map(c=>c.id);
}
function tourSummary(tour) {
if (!tour.length) return '';
const cities = new Set(tour.map(t=>t.cityId)).size;
const stads = tour.filter(t=>t.venueType==='stadium').length;
const booked = tour.filter(t=>t.booked).length;
const future = tour.filter(t=>(t.warnings||[]).includes('future')).length;
let s=`${tour.length} ${T('summaryShows')} Β· ${cities} ${_D('Εehir','cities','cidades')} Β· π${stads}/10`;
if (booked) s+=` Β· β
${booked}`;
if (future) s+=` Β· β³${future}`;
return s;
}
// βββ CSV ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function tourToCSV(tour, settings) {
const BOM='\uFEFF';
const hdr=['Pop Day','Date','Time','City','Venue','Price Min','Price Max','Status'].join(',');
const rows=tour.map(t=>{
const c=cityById(t.cityId), pm=realToPM(new Date(t.date+'T00:00:00'));
const sd=t.specialDay?SPECIAL_DAYS[t.specialDay]?.icon:'';
return[`${pm.year}-${pm.day}${sd?' '+sd:''}`, t.date, t.time.slice(0,5),
`"${c?.en||t.cityId}"`, t.venueType==='stadium'?'Stadium':'Bar',
settings?.priceMin??'', settings?.priceMax??'',
t.booked?'booked':t.skipped?'skipped':''].join(',');
});
return BOM+[hdr,...rows].join('\r\n');
}
function csvToTour(csvText) {
const lines=csvText.replace(/^\uFEFF/,'').trim().split(/\r?\n/);
const tour = lines.slice(1).map(line=>{
const cols=line.split(',').map(c=>c.replace(/^"|"$/g,'').trim());
if (cols.length<4) return null;
const city=cityByAny(cols[3]);
if (!city) return null;
let date=cols[1];
if(date.includes('.')) { const p=date.split('.'); date=`${p[2]}-${p[1].padStart(2,'0')}-${p[0].padStart(2,'0')}`; }
if(!date.match(/^\d{4}-\d{2}-\d{2}$/)) return null;
const time=cols[2].length===5?cols[2]+':00':cols[2];
return {cityId:city.id,date:cols[1],time,venueType:/stadium/i.test(cols[4]||'')?'stadium':'bar',booked:cols[7]==='booked',skipped:cols[7]==='skipped'};
}).filter(Boolean);
recalcWarnings(tour);
return tour;
}
function dlCSV(text, fname) {
const blob=new Blob([text],{type:'text/csv;charset=utf-8;'});
const url=URL.createObjectURL(blob);
const a=document.createElement('a'); a.href=url; a.download=fname; a.style.display='none';
document.body.appendChild(a); a.click(); a.remove();
setTimeout(()=>URL.revokeObjectURL(url),1000);
}
// βββ BOOKING ENGINE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function isStadRow(row) { return !!(row.cells[1]?.querySelector('img[title]')); }
function parseClubRow(row) {
const name = row.cells[0]?.querySelector('a')?.textContent?.trim()||'';
const radio = row.cells[0]?.querySelector('input[type="radio"]');
const avTxt = row.cells[1]?.textContent||'';
const avM = avTxt.match(/(\d+)\s*\/\s*(\d+)/);
const stars = parseInt(row.querySelector('.sortkey')?.textContent||'0');
const price = parseFloat((row.cells[row.cells.length-1]?.textContent||'').replace(/\s/g,'').replace(/M\$/,'').replace(',','.'))||0;
return {name,radio,remaining:avM?(parseInt(avM[2])-parseInt(avM[1])):99,stars,price,stadium:isStadRow(row)};
}
function detectError() { return !!document.querySelector('.notification-real.notification-error,.notification-error'); }
async function attemptBook(radio) {
if (!radio) return false;
radio.click();
await delay(400);
const btn=$(SEL.bookBtn); if (!btn) return false;
btn.click();
await delay(900);
const ok=Array.from(document.querySelectorAll('.ui-dialog-buttonpane button,.ui-dialog button'))
.find(b=>/yes|ok|onayla|evet|sim|confirm/i.test(b.textContent));
if (ok) { ok.click(); await delay(1200); }
if (detectError()) return false;
return true;
}
async function findAndBook(settings, show) {
const tbl=$(SEL.clubTable); if (!tbl) return false;
const dayEl = $(SEL.day);
if (dayEl && dayEl.value !== show.date) {
log(`[Strict Match] Table loaded but date (${dayEl.value}) differs from target (${show.date}).`);
return false;
}
const rows = Array.from(tbl.querySelectorAll('tbody tr'));
const minStar = settings.minStars ?? 50;
const pMin = settings.priceMin ?? DEF.priceMin;
const pMax = settings.priceMax ?? DEF.priceMax;
const wantSt = show.venueType==='stadium';
const sort = settings.sortMode||'price_desc';
let cands = rows.map(parseClubRow).filter(c=>{
if (!c.radio||c.remaining<=0) return false;
if (c.stars < minStar) return false;
if (c.price<pMin||c.price>pMax) return false;
return true;
});
// Fallback: drop stars
if (!cands.length) cands = rows.map(parseClubRow).filter(c=>c.radio&&c.remaining>0&&c.price>=pMin&&c.price<=pMax);
if (wantSt) { const st=cands.filter(c=>c.stadium); cands=st.length?st:cands.filter(c=>!c.stadium); }
else { const bars=cands.filter(c=>!c.stadium); if (bars.length) cands=bars; }
if (sort==='price_asc') cands.sort((a,b)=>a.price-b.price);
else if (sort==='price_mid') { const mid=(pMin+pMax)/2; cands.sort((a,b)=>Math.abs(a.price-mid)-Math.abs(b.price-mid)); }
else cands.sort((a,b)=>b.price-a.price);
for (const c of cands) {
const ok=await attemptBook(c.radio);
if (ok) {
if (c.stadium) {
const cy=realToPM(new Date()).year, ly=gmGet(GMK.stadY,cy);
gmSet(GMK.stadU,(ly===cy?gmGet(GMK.stadU,0):0)+1); gmSet(GMK.stadY,cy);
}
log(`Booked: ${c.name} (${c.price} M$) ${show.date} ${show.time}`);
return true;
}
if (detectError()) { log('Error detected, pausing.'); gmSet(GMK.status,'PAUSED'); updateFloatBar(); return false; }
await delay(400);
}
return false;
}
function waitForTable(cb, ms=20000) {
const t0=Date.now();
const iv=setInterval(()=>{
if ($(SEL.clubTable)) { clearInterval(iv); cb(); }
else if (Date.now()-t0>ms) { clearInterval(iv); log('Table timeout β pausing'); gmSet(GMK.status,'PAUSED'); updateFloatBar(); }
},300);
}
// βββ PROCESS NEXT SHOW ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function processNextShow(settings) {
if (gmGet(GMK.status,'IDLE')!=='RUNNING') return;
const tour = gmGet(GMK.tour,[]);
let idx = gmGet(GMK.idx,0);
// Skip already-handled (booked, failed max retries, or user unchecked)
while (idx<tour.length && (tour[idx].booked || tour[idx].skipped || tour[idx].willBook === false)) idx++;
if (idx>=tour.length) {
gmSet(GMK.status,'IDLE'); updateFloatBar();
showPanelStatus(T('statusDone'),'#28a745');
return;
}
const show = tour[idx];
// 57-day limit
const showDate=new Date(show.date+'T00:00:00');
const maxBook =addDays(TODAY,57);
if (showDate>maxBook) {
const wait=Math.ceil((showDate-maxBook)/MS_DAY);
gmSet(GMK.idx,idx); gmSet(GMK.status,'PAUSED');
updateFloatBar(`${T('statusFuture')} ${wait}d`);
showPanelStatus(`${T('limit57')} (+${wait}d)`,'#e67e00');
return;
}
const city=cityById(show.cityId);
updateFloatBar();
showPanelStatus(`βΆ ${idx+1}/${tour.length}: ${cityName(city)} ${show.date} ${show.time}`,'#28a745');
const handleInvalidDate = async () => {
log(`[Strict Match] Date ${show.date} not available in dropdown. Skipping show.`);
tour[idx]={...show, booked:false, skipped:true};
gmSet(GMK.tour,tour); gmSet(GMK.idx,idx+1);
await rdelay();
window.location.href=`https://${window.location.hostname}/World/Popmundo.aspx/Artist/BookShow/${settings.artistId}`;
};
// Restore mode (after city postback)
const restore=gmGet(GMK.restore,null);
if (restore) {
gmDel(GMK.restore);
const dayEl=$(SEL.day), hourEl=$(SEL.hour);
if (dayEl) { dayEl.value=restore.date; if (dayEl.value !== restore.date) return handleInvalidDate(); }
if (hourEl) { const o=Array.from(hourEl.options).find(o=>o.value.startsWith(restore.time.slice(0,5))); if(o) hourEl.value=o.value; }
const venEl=$(SEL.venueType); if(venEl) venEl.value='0';
await delay(250);
$(SEL.findBtn)?.click();
waitForTable(()=>bookAndAdvance(settings,show,idx,tour));
return;
}
// Set city
const cityEl=$(SEL.city); if(!cityEl) return;
if (parseInt(cityEl.value)!==show.cityId) {
gmSet(GMK.restore,{date:show.date,time:show.time});
cityEl.value=String(show.cityId);
cityEl.dispatchEvent(new Event('change',{bubbles:true}));
return; // postback
}
if (!$(SEL.clubTable)) {
const dayEl=$(SEL.day), hourEl=$(SEL.hour);
if (dayEl) { dayEl.value=show.date; if (dayEl.value !== show.date) return handleInvalidDate(); }
if (hourEl) { const o=Array.from(hourEl.options).find(o=>o.value.startsWith(show.time.slice(0,5))); if(o) hourEl.value=o.value; }
const venEl=$(SEL.venueType); if(venEl) venEl.value='0';
await delay(250);
$(SEL.findBtn)?.click();
waitForTable(()=>bookAndAdvance(settings,show,idx,tour));
} else {
await bookAndAdvance(settings,show,idx,tour);
}
}
async function bookAndAdvance(settings, show, idx, tour) {
if (gmGet(GMK.status,'IDLE')!=='RUNNING') return;
const booked = await findAndBook(settings, show);
if (gmGet(GMK.status,'IDLE')!=='RUNNING') return;
if (booked) {
tour[idx] = { ...show, booked: true, skipped: false };
delete tour[idx]._retry;
gmSet(GMK.tour, tour);
gmSet(GMK.idx, idx + 1);
} else {
const retries = (show._retry || 0) + 1;
if (retries >= 2) {
log(`Skipped (max retries): ${show.date} ${show.time}`);
tour[idx] = { ...show, booked: false, skipped: true };
delete tour[idx]._retry;
gmSet(GMK.tour, tour);
gmSet(GMK.idx, idx + 1);
} else {
log(`Retry ${retries} for: ${show.date} ${show.time}`);
tour[idx] = { ...show, _retry: retries };
gmSet(GMK.tour, tour);
// Do not increment idx, so it will retry
}
}
await rdelay();
const aid=settings.artistId;
window.location.href=`https://${window.location.hostname}/World/Popmundo.aspx/Artist/BookShow/${aid}`;
}
// βββ FLOATING STATUS BAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function injectFloatBar() {
if (document.getElementById('r49-float')) return;
const bar=mk('div'); bar.id='r49-float';
bar.style.cssText='position:fixed;top:0;left:0;right:0;z-index:2147483647;background:#2d1b5e;color:#fff;font-family:sans-serif;font-size:12px;display:none;align-items:center;gap:8px;padding:4px 12px;box-shadow:0 2px 8px rgba(0,0,0,.5);';
const txt=mk('span'); txt.id='r49-float-txt'; txt.style.flex='1';
const logBtn=mkB('π','',()=>openLogViewer());
logBtn.style.cssText='padding:2px 8px;border:1px solid #fcd34d;border-radius:4px;background:transparent;color:#fcd34d;cursor:pointer;font-size:11px;margin-right:4px;';
const saveBtn=mkB('πΎ','',()=>{
const t=gmGet(GMK.tour,[]); const s=gmGet(GMK.sets,null);
if(!t.length) return;
const name = prompt(_D('Εema AdΔ±:','Schema Name:','Nome do Esquema:'), `Saved_${fmtISO(new Date())}`);
if (name) {
const sc = gmGet(GMK.schemas,[]);
sc.push({ id: Date.now(), name, tour: t, settings: s, templateDays: 56 });
gmSet(GMK.schemas, sc);
stopBooking();
}
});
saveBtn.style.cssText='padding:2px 8px;border:1px solid #4ade80;border-radius:4px;background:transparent;color:#4ade80;cursor:pointer;font-size:11px;';
saveBtn.title=_D('Daha Sonra Devam Et','Save & Continue Later','Salvar e Continuar Mais Tarde');
const pbtn=mk('button'); pbtn.id='r49-float-pbtn';
pbtn.style.cssText='padding:2px 10px;border:1px solid #fff;border-radius:4px;background:transparent;color:#fff;cursor:pointer;font-size:11px;';
bar.append(txt,logBtn,saveBtn,pbtn);
document.body.prepend(bar);
setInterval(updateFloatBar,1500);
updateFloatBar();
}
function updateFloatBar(customMsg) {
const bar=document.getElementById('r49-float');
const txt=document.getElementById('r49-float-txt');
const pbtn=document.getElementById('r49-float-pbtn');
if (!bar||!txt) return;
const status=gmGet(GMK.status,'IDLE');
const tour =gmGet(GMK.tour,[]);
const idx =gmGet(GMK.idx,0);
const sets =gmGet(GMK.sets,null);
if (status==='IDLE'&&!tour.length) { bar.style.display='none'; return; }
bar.style.display='flex';
if (customMsg) { txt.textContent=customMsg; }
else if (status==='PAUSED' || status==='RUNNING') {
const s=tour[Math.min(idx, tour.length-1)];
if (s) {
const c = cityName(cityById(s.cityId)) || '?';
const vIcon = s.venueType==='stadium'?'π':'π΅';
const vName = s.venueType==='stadium'?T('venueStad'):T('venueBar');
const p = sets ? `${sets.priceMin}-${sets.priceMax}` : '';
const stars = sets ? Math.floor(sets.minStars/10) : '5';
const dStr = fmtDisp(new Date(s.date+'T00:00:00'));
const tStr = (s.time||'').slice(0,5);
const prefix = status==='PAUSED' ? 'βΈ' : 'βΆ';
txt.textContent=`${prefix} ${vIcon} ${c} | ${dStr} | ${tStr} | ${vName} | ${p} M$ | ${stars}β
| ${idx+1}/${tour.length}`;
} else {
txt.textContent=`${status==='PAUSED'?'βΈ':'βΆ'} Route49 β ${idx}/${tour.length}`;
}
} else {
txt.textContent=`π Route49 β ${idx}/${tour.length}`;
}
// Auto update active summary in schemas tab if present
const activeSumm = document.getElementById('r49-active-summary');
if (activeSumm) activeSumm.textContent = tourSummary(tour);
if (pbtn) {
pbtn.textContent=status==='PAUSED'?T('btnResume'):T('btnPause');
pbtn.onclick=()=>{
if (status==='PAUSED') {
gmSet(GMK.status,'RUNNING');
const s=gmGet(GMK.sets,null);
if (s?.artistId) window.location.href=`https://${window.location.hostname}/World/Popmundo.aspx/Artist/BookShow/${s.artistId}`;
} else {
gmSet(GMK.status,'PAUSED');
}
updateFloatBar();
};
}
}
// βββ UI HELPERS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const inpSt = 'padding:4px;border:1px solid #c9b8f0;border-radius:4px;box-sizing:border-box;font-family:inherit;font-size:12px;';
const btnSt = 'padding:4px 10px;border:1px solid #6f42c1;border-radius:4px;cursor:pointer;font-size:12px;background:#e8e0f9;color:#6f42c1;font-family:inherit;';
const fieldRow=(lbl,el,note)=>{ const w=mk('div'); w.style.cssText='margin-bottom:8px;'; const l=mk('label','',lbl); l.style.cssText='display:block;font-weight:600;font-size:11px;margin-bottom:3px;color:#444;'; w.append(l,el); if(note){const n=mk('div','',note);n.style.cssText='font-size:10px;color:#888;margin-top:2px;';w.appendChild(n);} return w; };
function showPanelStatus(txt,color) { const el=document.getElementById('r49-status'); if(!el) return; el.textContent=txt; if(color) el.style.color=color; }
// βββ GATHER SETTINGS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function gatherSettings() {
const saved = gmGet(GMK.sets, null);
const g=id=>document.getElementById(id);
if (!g('r49-panel') && saved) return saved; // Panel kapalΔ±yken eski ayarlarΔ± koru (Fiyat NaN hatasΔ± Γ§ΓΆzΓΌmΓΌ)
const type=g('r49-ttype')?.value||'relax';
const plan=g('r49-tplan')?.value||'tt';
const times=Array.from(document.querySelectorAll('#r49-times option:checked')).map(o=>o.value);
const doubleDay=g('r49-doubleday')?.checked===true;
const dpc=doubleDay && g('r49-dpc')?.checked ? 2 : 1;
const enabledCities=CITIES.filter(c=>{const el=g(`r49-city-${c.id}`);return el?el.checked:true;}).map(c=>c.id);
const stadiumCities=CITIES.filter(c=>g(`r49-stad-${c.id}`)?.checked).map(c=>c.id);
const anchors={};
for (const [day] of ANCHOR_DAYS) {
const cityId=parseInt(g(`r49-sd-city-${day}`)?.value||'0')||null;
const rest =parseInt(g(`r49-sd-rest-${day}`)?.value ||'0')||0;
if (cityId || rest > 0) anchors[day]={cityId,restDays:rest};
}
return {
artistId: g('r49-artist')?.value?.trim()||'',
tourType: type,
tourPlan: plan,
startCityId: parseInt(g('r49-startcity')?.value||'0')||DEF.startCityId,
customRoute: g('r49-custom')?.value||'',
startDate: g('r49-start')?.value||DEF.startDate,
endDate: g('r49-end')?.value ||DEF.endDate,
doubleDay, dpc,
fastMode: g('r49-fastmode')?.checked===true,
showTimes: times.length?times:DEF.showTimes,
priceMin: parseFloat(g('r49-pmin')?.value||String(DEF.priceMin)),
priceMax: parseFloat(g('r49-pmax')?.value||String(DEF.priceMax)),
minStars: parseInt(g('r49-minstars')?.value||'50'),
sortMode: g('r49-sort')?.value||'price_desc',
enabledCities, stadiumCities, anchors,
};
}
function applySettingsToUI(cfg) {
if (!cfg) return;
const g=id=>document.getElementById(id);
if (cfg.fastMode !== undefined && g('r49-fastmode')) g('r49-fastmode').checked = !!cfg.fastMode;
if (cfg.artistId && g('r49-artist')) g('r49-artist').value = cfg.artistId;
if (cfg.tourType && g('r49-ttype')) g('r49-ttype').value = cfg.tourType;
if (cfg.tourPlan && g('r49-tplan')) { g('r49-tplan').value = cfg.tourPlan; toggleCustom(); }
if (cfg.startCityId && g('r49-startcity')) g('r49-startcity').value = String(cfg.startCityId);
if (cfg.startDate && g('r49-start')) { g('r49-start').value=cfg.startDate; updateDL('r49-start','r49-start-lbl'); }
if (cfg.endDate && g('r49-end')) { g('r49-end').value=cfg.endDate; updateDL('r49-end', 'r49-end-lbl'); }
if (cfg.doubleDay !== undefined && g('r49-doubleday')) { g('r49-doubleday').checked=!!cfg.doubleDay; syncDoubleDayUI(); }
if (cfg.dpc !== undefined && g('r49-dpc')) g('r49-dpc').checked = cfg.dpc>=2;
if (cfg.priceMin !== undefined && g('r49-pmin')) g('r49-pmin').value=cfg.priceMin;
if (cfg.priceMax !== undefined && g('r49-pmax')) g('r49-pmax').value=cfg.priceMax;
if (cfg.minStars !== undefined && g('r49-minstars')) g('r49-minstars').value=String(cfg.minStars);
if (cfg.sortMode && g('r49-sort')) g('r49-sort').value=cfg.sortMode;
if (Array.isArray(cfg.showTimes) && g('r49-times')) Array.from(g('r49-times').options).forEach(o=>o.selected=cfg.showTimes.includes(o.value));
if (Array.isArray(cfg.enabledCities)) CITIES.forEach(c=>{const el=g(`r49-city-${c.id}`);if(el)el.checked=cfg.enabledCities.includes(c.id);});
if (Array.isArray(cfg.stadiumCities)) CITIES.forEach(c=>{const el=g(`r49-stad-${c.id}`);if(el)el.checked=cfg.stadiumCities.includes(c.id);});
if (cfg.anchors) for (const [d,a] of Object.entries(cfg.anchors)) {
const ce=g(`r49-sd-city-${d}`); if(ce&&a.cityId) ce.value=String(a.cityId);
const re=g(`r49-sd-rest-${d}`); if(re) re.value=String(a.restDays||0);
}
updateSummary();
}
function toggleCustom() {
const p=document.getElementById('r49-tplan')?.value;
const t=document.getElementById('r49-ttype')?.value;
const elC=document.getElementById('r49-custom-wrap');
const elS=document.getElementById('r49-startcity-wrap');
const elEnd=document.getElementById('r49-end-wrap');
const elG2=elEnd?.parentElement;
if (elC) elC.style.display=p==='custom'?'block':'none';
if (elS) elS.style.display=p!=='custom'?'flex':'none';
if (elEnd) elEnd.style.display=t==='custom'?'block':'none';
if (elG2) elG2.style.gridTemplateColumns = t==='custom'?'repeat(auto-fit, minmax(130px, 1fr))':'1fr';
}
function syncDoubleDayUI() {
const t=document.getElementById('r49-ttype')?.value;
const dd=document.getElementById('r49-doubleday')?.checked;
const dpcEl=document.getElementById('r49-dpc');
const dpcRow=document.getElementById('r49-dpc-row');
if (dpcEl) { if (!dd || t==='blitz') { dpcEl.checked=false; dpcEl.disabled=true; } else dpcEl.disabled=false; }
if (dpcRow) dpcRow.style.opacity=(dd && t!=='blitz')?'1':'0.4';
}
function updateDL(inputId,labelId) {
const inp=document.getElementById(inputId), lbl=document.getElementById(labelId);
if(!inp||!lbl||!inp.value) return;
lbl.textContent=dateLabel(new Date(inp.value+'T00:00:00'));
}
function updateSummary() {
const el=document.getElementById('r49-summary'); if(!el) return;
try { const s=gatherSettings(); el.textContent=tourSummary(buildTour(s)); } catch { el.textContent=''; }
}
function updateStadBadge() {
const el=document.getElementById('r49-stad-badge'); if(!el) return;
const cy=realToPM(new Date()).year, ly=gmGet(GMK.stadY,cy);
const used=ly===cy?gmGet(GMK.stadU,0):0;
el.textContent=`π ${used}/10`; el.style.color=used>=10?'#dc3545':'#28a745';
}
// βββ GLOBAL OPTIMIZATION ENGINE βββββββββββββββββββββββββββββββββββββββββββββββ
function recalcWarnings(tour) {
const maxBook = addDays(TODAY, 57);
let stadCountByYear = {};
for (let i = 0; i < tour.length; i++) {
const d = new Date(tour[i].date + 'T00:00:00');
tour[i].warnings = [];
if (d > maxBook) tour[i].warnings.push('future');
if (tour[i].venueType === 'stadium' && tour[i].willBook !== false) {
const pmYear = realToPM(d).year;
stadCountByYear[pmYear] = (stadCountByYear[pmYear] || 0) + 1;
if (stadCountByYear[pmYear] > 10) tour[i].warnings.push('stadium_limit');
}
if (i > 0) {
const p = cityById(tour[i-1].cityId), c = cityById(tour[i].cityId);
if (tour[i-1].date === tour[i].date && tour[i-1].cityId !== tour[i].cityId && tour[i].willBook !== false && tour[i-1].willBook !== false) {
tour[i].warnings.push('impossible_flight');
}
else if (p && c && Math.abs(p.tz - c.tz) > 8) {
tour[i].warnings.push('timezone');
}
}
}
}
// βββ PREVIEW MODAL ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function openPreview(extTour = null, extSettings = null, schemaId = null) {
try {
const settings = extSettings || gatherSettings();
let tour = extTour ? JSON.parse(JSON.stringify(extTour)) : buildTour(settings);
let isModified = false;
const ov = mk('div');
ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:99999;display:flex;align-items:flex-start;justify-content:center;padding:40px 12px;overflow-y:auto;';
const box = mk('div');
box.style.cssText = 'background:#fff;border-radius:10px;padding:18px;width:100%;max-width:860px;box-shadow:0 6px 32px rgba(0,0,0,.35);font-family:inherit;font-size:13px;';
const hdr = mk('div');
hdr.style.cssText = 'display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap;';
const htl = mk('span','',`π Route49 β ${tour.length} ${T('summaryShows')}`);
htl.style.cssText = 'font-weight:700;font-size:15px;flex:1;color:#6f42c1;';
const saveBtn = mkB('πΎ ' + T('btnSave'), '', () => {
if (schemaId === 'active') { gmSet(GMK.tour, tour); }
else if (schemaId) {
const sc = gmGet(GMK.schemas,[]); const idx = sc.findIndex(x=>x.id===schemaId);
if(idx!==-1){ sc[idx].tour = tour; gmSet(GMK.schemas, sc); }
} else {
gmSet(GMK.tour, tour); gmSet(GMK.idx, 0);
}
isModified = false;
const tab = document.getElementById('r49-tab-saved');
if (tab && tab.style.display === 'block') renderSchemas(tab);
const sumEl = document.getElementById('r49-summary');
if (sumEl && !schemaId) sumEl.textContent = tourSummary(tour);
alert(_D('DeΔiΕiklikler kaydedildi.','Changes saved.','AlteraΓ§Γ΅es salvas.'));
});
const closeBtn = mkB(T('btnClose'), '', () => {
if (isModified) {
if (confirm(_D('DeΔiΕiklikler kaydedilmeden Γ§Δ±kΔ±lsΔ±n mΔ±?', 'Exit without saving?', 'Sair sem salvar?'))) {
ov.remove();
}
} else {
ov.remove();
}
});
saveBtn.style.cssText = btnSt;
closeBtn.style.cssText = 'padding:5px 10px;border:1px solid #dc3545;border-radius:5px;cursor:pointer;font-size:12px;background:#fff0f0;color:#dc3545;font-family:inherit;';
const copyBtn = mkB('π Kopyala', '', () => {
const txt = tour.map(s => `${s.date} ${s.time.slice(0,5)} ${cityName(cityById(s.cityId))}`).join('\n');
navigator.clipboard.writeText(txt); alert('Panoya kopyalandΔ±!');
});
const csvBtn = mkB('β¬ CSV', '', () => dlCSV(tourToCSV(tour, settings), 'tour.csv'));
copyBtn.style.cssText = 'padding:4px 12px;border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:700;background:#6f42c1;color:#fff;box-shadow:0 2px 4px rgba(0,0,0,0.1);';
csvBtn.style.cssText = btnSt;
hdr.append(htl, copyBtn, csvBtn, saveBtn, closeBtn);
box.appendChild(hdr);
const sumBar = mk('div','',tourSummary(tour));
sumBar.style.cssText = 'font-size:11px;color:#6f42c1;font-weight:600;margin-bottom:10px;';
box.appendChild(sumBar);
// Bilgi Metni
const intro = mk('div', '', _D('Turnede ziyaret edilecek Εehirlerin sΔ±ralamasΔ±nΔ± sΓΌrΓΌkle bΔ±rak ile deΔiΕtirebilirsiniz. AynΔ± Εehirdeki konserler bΓΆlΓΌnmeden blok halinde taΕΔ±nΔ±r. SΓΌrΓΌkleme sΔ±rasΔ±nda oluΕan saat dezavantajlarΔ± tabloda uyarΔ± olarak belirir. 4 saatten fazla dezavantajΔ± olan konserleri saΔdaki kutucuktan devredΔ±ΕΔ± bΔ±rakmanΔ±z ΓΆnerilir. SonuΓ§tan memnun kalΔ±rsanΔ±z kaydedin.', 'Drag and drop rows to reorder. Shows in the same city move together. Time disadvantages appear as warnings. It is recommended to disable shows with >4h disadvantage. Save when satisfied.', 'Arraste e solte para reordenar. Shows na mesma cidade movem-se juntos. Desvantagens de tempo aparecem como avisos. Recomenda-se desativar shows com desvantagem >4h. Salve quando satisfeito.'));
intro.style.cssText = 'font-size:11px;color:#555;margin-bottom:12px;padding:8px;background:#f8f9fa;border-left:4px solid #6f42c1;border-radius:4px;';
box.appendChild(intro);
// Ayarla - Hepsi / HiΓ§biri ButonlarΔ±
const chkBtnRow = mk('div'); chkBtnRow.style.cssText = 'display:flex;gap:8px;margin-bottom:8px;align-items:center;';
const chkLbl = mk('span', '', _D('Tablo KontrolΓΌ:', 'Table Control:', 'Controle da Tabela:')); chkLbl.style.cssText = 'font-size:12px;font-weight:bold;color:#6f42c1;';
const chkAll = mkB(_D('TΓΌmΓΌnΓΌ Ayarla', 'Set All', 'Definir Todos'), '', () => { tour.forEach(s => s.willBook = true); isModified = true; renderTable(); });
const chkNone = mkB(_D('HiΓ§birini Ayarlama', 'Set None', 'Definir Nenhum'), '', () => { tour.forEach(s => s.willBook = false); isModified = true; renderTable(); });
[chkAll, chkNone].forEach(b => b.style.cssText = btnSt);
chkBtnRow.append(chkLbl, chkAll, chkNone);
box.appendChild(chkBtnRow);
const tbl = mk('table');
tbl.style.cssText = 'width:100%;border-collapse:collapse;font-size:12px;';
const thead = mk('thead');
const headRow = mk('tr');
[T('colDay'), T('colDate'), T('colTime'), T('colCity'), T('colVenue'), T('colPrice'), '', _D('Ayarla', 'Set', 'Def.'), _D('Sil', 'Del', 'Exc.')].forEach(h => {
const th = mk('th','',h);
th.style.cssText = 'padding:5px 6px;text-align:left;border-bottom:2px solid #6f42c1;font-size:11px;color:#6f42c1;white-space:nowrap;';
headRow.appendChild(th);
});
thead.appendChild(headRow);
tbl.appendChild(thead);
const tbody = mk('tbody');
let draggedIdx = null;
const renderTable = () => {
tbody.innerHTML = '';
tour.forEach((show, i) => {
const c = cityById(show.cityId);
const pm = realToPM(new Date(show.date + 'T00:00:00'));
const sd = show.specialDay ? SPECIAL_DAYS[show.specialDay] : null;
const warns = show.warnings ||[];
const tr = mk('tr');
tr.draggable = true;
tr.style.cursor = 'grab';
tr.ondragstart = e => { draggedIdx = i; e.dataTransfer.effectAllowed = 'move'; tr.style.opacity = '0.5'; };
tr.ondragend = e => { tr.style.opacity = '1'; };
tr.ondragover = e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; tr.style.borderTop = '2px solid #6f42c1'; };
tr.ondragleave = e => { tr.style.borderTop = ''; };
tr.ondrop = e => {
e.preventDefault(); tr.style.borderTop = '';
if (draggedIdx === null || draggedIdx === i) return;
const draggedItem = tour[draggedIdx];
const targetItem = tour[i];
// Diziden Γ§Δ±kar ve hedefe yerleΕtir
tour.splice(draggedIdx, 1);
tour.splice(i, 0, draggedItem);
// Tarih ve zaman dizilimini temizle
const chronoSlots = tour.map(s => ({ date: s.date, time: s.time, specialDay: s.specialDay }));
chronoSlots.sort((a,b) => new Date(a.date+'T'+a.time) - new Date(b.date+'T'+b.time));
for (let j = 0; j < tour.length; j++) {
tour[j].date = chronoSlots[j].date;
tour[j].time = chronoSlots[j].time;
tour[j].specialDay = chronoSlots[j].specialDay;
}
isModified = true;
recalcWarnings(tour);
renderTable();
};
let bg = i % 2 === 0 ? '#fff' : '#faf7ff';
let rowFw = 'normal';
let rowColor = 'inherit';
if (sd) {
const cm = {'π':'#fff0e0','ποΈ':'#e8f5e9','π₯':'#fff3e0','π':'#fff8e1','π':'#e8f5e9','π':'#f3e5f5','π¦':'#e0f7fa'};
bg = cm[sd.icon] || '#f3e5f5';
if (['π₯','π','ποΈ'].includes(sd.icon)) {
bg = '#fed7aa'; rowFw = 'bold'; rowColor = '#9a3412';
}
}
if (warns.includes('future') && rowFw === 'normal') bg = '#fff9e6';
tr.style.background = bg;
let wt = ''; let wtColor = rowColor; let wtFw = rowFw; let wtTitle = '';
if (warns.includes('timezone') && i > 0) {
const pC = cityById(tour[i-1].cityId);
if (pC && c) {
const diff = c.tz - pC.tz;
const h = Math.abs(diff);
if (diff < 0) {
wt = 'β° ' + _D(`+${h}s Avantaj`,`+${h}h Adv.`,`+${h}h Vant.`);
wtTitle = _D(`${h} Saat Avantaj`,`${h} Hours Advantage`,`${h} Horas Vantagem`);
wtColor = '#16a34a'; wtFw = '700';
} else {
wt = 'β° ' + _D(`-${h}s Dezavantaj`,`-${h}h Disadv.`,`-${h}h Desv.`);
wtTitle = _D(`${h} Saat Dezavantaj`,`${h} Hours Disadvantage`,`${h} Horas Desvantagem`);
wtColor = '#dc2626'; wtFw = '700';
}
}
}
if (warns.includes('future')) {
wt += (wt ? ' | ' : '') + 'β³';
wtTitle += (wtTitle ? ' | ' : '') + T('warnFuture');
}
if (warns.includes('impossible_flight')) {
wt += (wt ? ' | ' : '') + 'π¨ ' + _D('Δ°mkansΔ±z!','Impossible!','ImpossΓvel!');
wtTitle += (wtTitle ? ' | ' : '') + _D('AynΔ± gΓΌn farklΔ± Εehirde konser verilemez!','Cannot play in different cities on the same day!','NΓ£o pode tocar em cidades diferentes no mesmo dia!');
wtColor = '#dc2626'; wtFw = '900';
}
if (warns.includes('stadium_limit')) {
wt += (wt ? ' | ' : '') + 'β ' + _D('Kota Dolu','Limit Reached','Limite Atingido');
wtTitle += (wtTitle ? ' | ' : '') + _D('Stadyum KotasΔ± Dolu! (Maks 10)','Stadium Limit Reached! (Max 10)','Limite de EstΓ‘dio Atingido! (Max 10)');
wtColor = '#dc2626'; wtFw = '900';
}
if (i < tour.length - 1) {
const nC = cityById(tour[i+1].cityId);
if (c && nC) {
const diff = nC.tz - c.tz;
if (diff > 4 && show.time.startsWith('22') && i>0 && tour[i-1].date === show.date && tour[i-1].cityId === show.cityId) {
wt += (wt?' | ':'') + `β οΈ -${diff}s ` + _D('Riskli','Risky','Risco');
wtTitle += (wtTitle?' | ':'') + _D(`+${diff} Saat Dezavantaj - Bu konseri iptal etmeniz ΓΆnerilir.`,`+${diff}h Disadvantage - Recommended to disable this show.`,`+${diff}h Desvantagem - Recomendado desativar este show.`);
wtColor = '#dc2626'; wtFw = '700';
}
}
}
let dayStr = `${sd ? sd.icon + ' ' : ''}${pm.year}-${pm.day}`;
let dayFw = rowFw, dayColor = rowColor;
if (sd && ['π₯','π','ποΈ'].includes(sd.icon)) { dayFw = '900'; dayColor = '#b45309'; }
const setChk = mk('input'); setChk.type = 'checkbox';
setChk.checked = show.willBook !== false; // default true
if (show.booked) { setChk.disabled = true; setChk.style.opacity = '0.5'; }
setChk.style.cursor = 'pointer';
setChk.onchange = () => { show.willBook = setChk.checked; isModified = true; renderTable(); };
const delBtn = mkB('ποΈ', '', () => { tour.splice(i, 1); isModified = true; renderTable(); });
delBtn.style.cssText = 'background:transparent;border:none;cursor:pointer;font-size:14px;';
delBtn.title = _D('Konseri Sil', 'Delete Show', 'Excluir Show');
const rowData =[
{ txt: dayStr, fw: dayFw, col: dayColor },
{ txt: fmtDisp(new Date(show.date + 'T00:00:00')) },
{ txt: show.time.slice(0, 5) },
{ txt: cityName(c) || show.cityId },
{ txt: show.venueType === 'stadium' ? T('venueStad') : T('venueBar') },
{ txt: `${settings.priceMin}β${settings.priceMax} M$` },
{ txt: wt, fw: wtFw, col: wtColor, title: wtTitle },
{ el: setChk },
{ el: delBtn }
];
rowData.forEach(d => {
const td = mk('td');
if (d.txt !== undefined) td.textContent = d.txt;
if (d.el) td.appendChild(d.el);
if (d.title) td.title = d.title;
td.style.cssText = `padding:4px 6px;border-bottom:1px solid #f0ecff;white-space:nowrap;font-weight:${d.fw||rowFw};color:${d.col||rowColor};`;
tr.appendChild(td);
});
tbody.appendChild(tr);
});
};
renderTable();
tbl.appendChild(tbody);
const tblWrap = mk('div');
tblWrap.style.cssText = 'width:100%;overflow-x:auto;margin-bottom:10px;';
tblWrap.appendChild(tbl);
box.appendChild(tblWrap);
const legend = mk('div');
legend.style.cssText = 'margin-top:10px;font-size:11px;color:#666;display:flex;gap:12px;flex-wrap:wrap;';
[[T('warnTZ'), 'β°'],[T('warnFuture'), 'β³']].forEach(([l, i]) => legend.appendChild(mk('span','',`${i} = ${l}`)));
Object.values(SPECIAL_DAYS).forEach(sd => legend.appendChild(mk('span','',`${sd.icon} = ${sd.label}`)));
box.appendChild(legend);
ov.append(box);
ov.onclick = e => {
if (e.target === ov) {
if (isModified && !confirm(_D('DeΔiΕiklikler kaydedilmeden Γ§Δ±kΔ±lsΔ±n mΔ±?', 'Exit without saving?', 'Sair sem salvar?'))) return;
ov.remove();
}
};
document.body.appendChild(ov);
} catch (err) {
console.error("[Route49] Γnizleme HatasΔ±:", err);
alert("Γnizleme oluΕturulurken bir hata oluΕtu. LΓΌtfen konsolu (F12) kontrol edin.");
}
}
// βββ INJECT MAIN UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function injectUI() {
if (document.getElementById('r49-panel')) return;
const container=document.querySelector('#ppm-content')||document.querySelector('#content')||document.body;
const panel=mk('div'); panel.id='r49-panel';
panel.style.cssText='border:2px solid #6f42c1;border-radius:8px;background:#f5f0ff;padding:12px;margin-bottom:14px;font-family:inherit;font-size:13px;max-width:100%;box-sizing:border-box;';
// Header (Title + Artist ID)
const hdr=mk('div'); hdr.style.cssText='display:flex;align-items:center;gap:10px;margin-bottom:10px;';
const ttl=mk('span','',T('title')); ttl.style.cssText='font-weight:700;font-size:15px;color:#6f42c1;';
const artInp=mk('input'); artInp.id='r49-artist'; artInp.type='text'; artInp.placeholder=T('artistId');
artInp.style.cssText=inpSt+'width:100px;font-weight:600;color:#333;';
const urlAid=window.location.pathname.match(/\/BookShow\/(\d+)/)?.[1]; if(urlAid) artInp.value=urlAid;
const badge=mk('span'); badge.id='r49-stad-badge'; badge.style.cssText='font-size:12px;font-weight:700;padding:2px 8px;border-radius:10px;background:#e8f5e9;margin-left:auto;';
hdr.append(ttl,artInp,badge); panel.appendChild(hdr);
// Tabs
const tabBar=mk('div'); tabBar.style.cssText='display:flex;gap:4px;margin-bottom:10px;';
const TABS=[['plan',T('tabPlan')],['saved',T('tabSaved')],['settings',T('tabSettings')]];
const panes={};
TABS.forEach(([id,lbl])=>{
const btn=mkB(lbl,'',()=>{
TABS.forEach(([tid])=>{
document.getElementById(`r49-tab-${tid}`)?.style.setProperty('display','none');
const tb=document.getElementById(`r49-tbtn-${tid}`); if(tb){tb.style.background='#e8e0f9';tb.style.color='#6f42c1';}
});
document.getElementById(`r49-tab-${id}`)?.style.setProperty('display','block');
btn.style.background='#6f42c1'; btn.style.color='#fff';
if (id==='saved') renderSchemas(panes['saved']); // Render on click
});
btn.id=`r49-tbtn-${id}`;
btn.style.cssText='padding:4px 12px;border:1px solid #6f42c1;border-radius:4px;cursor:pointer;font-size:12px;background:#e8e0f9;color:#6f42c1;font-family:inherit;';
tabBar.appendChild(btn);
const pane=mk('div'); pane.id=`r49-tab-${id}`; pane.style.display='none';
panes[id]=pane;
});
panel.appendChild(tabBar);
// TAB: PLAN
const plan=panes['plan'];
const tpRow=mk('div'); tpRow.style.cssText='display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px;';
// ββ Turne TΓΌrΓΌ
const tySel=mk('select'); tySel.id='r49-ttype'; tySel.style.cssText=inpSt+'width:100%;';
Object.entries(TOUR_TYPES).forEach(([k,t])=>{ const o=mk('option','',t.name); o.value=k; tySel.appendChild(o); });
tySel.onchange=()=>{
syncDoubleDayUI();
toggleCustom();
const cfg = TOUR_TYPES[tySel.value];
if (cfg.durationDays) {
const sd=document.getElementById('r49-start')?.value||MIN_DATE_STR;
const ed=fmtISO(addDays(new Date(sd+'T00:00:00'), cfg.durationDays));
const endEl=document.getElementById('r49-end'); if(endEl){ endEl.value=ed; updateDL('r49-end','r49-end-lbl'); }
}
updateSummary();
};
const tyWrap=fieldRow(T('tourType'),tySel); tyWrap.style.flex='1';
// ββ Turne PlanΔ±
const plSel=mk('select'); plSel.id='r49-tplan'; plSel.style.cssText=inpSt+'width:100%;';
Object.entries(TOUR_PLANS).forEach(([k,t])=>{ const o=mk('option','',t.name); o.value=k; plSel.appendChild(o); });
plSel.onchange=()=>{ toggleCustom(); updateSummary(); };
const plWrap=fieldRow(T('tourPlan'),plSel); plWrap.style.flex='1';
tpRow.append(tyWrap, plWrap); plan.appendChild(tpRow);
// ββ BaΕlangΔ±Γ§ Εehri
const scWrap=mk('div'); scWrap.id='r49-startcity-wrap'; scWrap.style.cssText='display:flex;align-items:center;gap:8px;margin-bottom:8px;';
const scSel=mk('select'); scSel.id='r49-startcity'; scSel.style.cssText=inpSt;
CITIES.forEach(c=>{ const o=mk('option','',cityName(c)); o.value=String(c.id); if(c.id===DEF.startCityId)o.selected=true; scSel.appendChild(o); });
scSel.onchange=updateSummary;
scWrap.append(mk('label','',T('startCity')+':'),scSel);
plan.appendChild(scWrap);
// ββ Custom route
const customWrap=mk('div'); customWrap.id='r49-custom-wrap'; customWrap.style.display='none';
const customTA=mk('textarea'); customTA.id='r49-custom';
customTA.style.cssText='width:100%;height:80px;font-family:monospace;font-size:11px;border:1px solid #c9b8f0;border-radius:4px;box-sizing:border-box;padding:4px;';
customTA.placeholder=T('customHint'); customTA.onchange=updateSummary;
customWrap.append(customTA);
plan.appendChild(customWrap);
// ββ Dates (2-col)
const g2=mk('div'); g2.style.cssText='display:grid;grid-template-columns:repeat(auto-fit, minmax(130px, 1fr));gap:8px;margin-bottom:8px;';
const mkDateW=(id,lbl,def)=>{
const w=mk('div'); w.id=id+'-wrap';
const l=mk('label','',lbl+':'); l.style.cssText='display:block;font-weight:600;font-size:11px;margin-bottom:3px;color:#444;';
const inp=mk('input'); inp.type='date'; inp.id=id; inp.value=def; inp.min=MIN_DATE_STR; inp.style.cssText=inpSt+'width:100%;';
const dl=mk('div'); dl.id=id+'-lbl'; dl.style.cssText='font-size:10px;color:#777;margin-top:1px;';
inp.onchange=()=>{
updateDL(id,id+'-lbl');
if (id === 'r49-start') {
const tySel = document.getElementById('r49-ttype');
if (tySel && tySel.value !== 'custom') {
const cfg = TOUR_TYPES[tySel.value];
if (cfg && cfg.durationDays) {
const ed = fmtISO(addDays(new Date(inp.value+'T00:00:00'), cfg.durationDays));
const endEl = document.getElementById('r49-end');
if(endEl) { endEl.value=ed; updateDL('r49-end','r49-end-lbl'); }
}
}
}
updateSummary();
};
w.append(l,inp,dl); return w;
};
g2.appendChild(mkDateW('r49-start',T('startDate'),DEF.startDate));
g2.appendChild(mkDateW('r49-end',T('endDate'),DEF.endDate));
plan.appendChild(g2);
// ββ Times & Options (Flex row)
const toRow=mk('div'); toRow.style.cssText='display:flex;flex-wrap:wrap;gap:12px;margin-bottom:8px;align-items:stretch;';
const cbWrap=mk('div'); cbWrap.style.cssText='flex:1;border:1px solid #e0d8ff;border-radius:6px;padding:8px;';
const ddRow=mk('label'); ddRow.style.cssText='display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;margin-bottom:4px;';
const ddChk=mk('input'); ddChk.type='checkbox'; ddChk.id='r49-doubleday'; ddChk.checked=true;
ddChk.onchange=()=>{ syncDoubleDayUI(); updateSummary(); };
ddRow.append(ddChk, mk('span','',T('doubleDay')));
const dpcRow=mk('div'); dpcRow.id='r49-dpc-row';
const dpcLbl=mk('label'); dpcLbl.style.cssText='display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;';
const dpcChk=mk('input'); dpcChk.type='checkbox'; dpcChk.id='r49-dpc'; dpcChk.checked=true;
dpcChk.onchange=updateSummary;
dpcLbl.append(dpcChk, mk('span','',T('showsPerCity')));
dpcRow.appendChild(dpcLbl);
const ddNote=mk('div','',T('showsDDNote')); ddNote.style.cssText='font-size:10px;color:#888;margin-top:4px;';
cbWrap.append(ddRow,dpcRow,ddNote);
const timeSel=mk('select'); timeSel.id='r49-times'; timeSel.multiple=true; timeSel.size=5;
timeSel.style.cssText='width:120px;height:auto;overflow-y:hidden;padding:4px;border:1px solid #c9b8f0;border-radius:4px;font-size:12px;';['14:00:00','16:00:00','18:00:00','20:00:00','22:00:00'].forEach(t=>{
const o=mk('option','',t.slice(0,5)); o.value=t; if(t==='14:00:00'||t==='22:00:00') o.selected=true; timeSel.appendChild(o);
});
const timeWrap=mk('div');
const timeLbl=mk('label','',T('showTimes')); timeLbl.style.cssText='display:block;font-weight:600;font-size:11px;margin-bottom:3px;color:#444;';
timeWrap.append(timeLbl,timeSel);
toRow.append(cbWrap, timeWrap); plan.appendChild(toRow);
// ββ Fame score β price
const fameSel=mk('select'); fameSel.id='r49-fame'; fameSel.style.cssText=inpSt+'width:100%;margin-bottom:5px;';
PRICE_GUIDE.forEach(g=>{ const o=mk('option','',`${g.score}: ${g.min}β${g.max} M$`); o.value=String(g.score); fameSel.appendChild(o); });
fameSel.value='13';
fameSel.onchange=()=>{ const g=PRICE_GUIDE[parseInt(fameSel.value)]; if(g){document.getElementById('r49-pmin').value=g.min;document.getElementById('r49-pmax').value=g.max;} };
const pRow=mk('div'); pRow.style.cssText='display:flex;flex-wrap:wrap;align-items:center;gap:6px;';
const pMin=mk('input'); pMin.id='r49-pmin'; pMin.type='number'; pMin.min='0'; pMin.value=String(PRICE_GUIDE[13].min); pMin.style.cssText='width:70px;'+inpSt;
const pMax=mk('input'); pMax.id='r49-pmax'; pMax.type='number'; pMax.min='0'; pMax.value=String(PRICE_GUIDE[13].max); pMax.style.cssText='width:70px;'+inpSt;
pRow.append(mk('span','','Min:'),pMin,mk('span','','βMax:'),pMax);
const priceW=mk('div');
const prL=mk('label','',T('fameScore')+':'); prL.style.cssText='display:block;font-weight:600;font-size:11px;margin-bottom:3px;color:#444;';
const prL2=mk('label','',T('priceRange')+':'); prL2.style.cssText='display:block;font-weight:600;font-size:11px;margin-bottom:3px;margin-top:5px;color:#444;';
priceW.append(prL,fameSel,prL2,pRow); priceW.style.marginBottom='8px';
plan.appendChild(priceW);
// ββ Min stars + sort (2-col)
const g2b=mk('div'); g2b.style.cssText='display:grid;grid-template-columns:repeat(auto-fit, minmax(130px, 1fr));gap:8px;margin-bottom:8px;';
const starSel=mk('select'); starSel.id='r49-minstars'; starSel.style.cssText=inpSt+'width:100%;';
[[10,'1 β
'],[20,'2 β
β
'],[30,'3 β
β
β
'],[40,'4 β
β
β
β
'],[50,'5 β
β
β
β
β
']].forEach(([v,l])=>{ const o=mk('option','',l); o.value=String(v); if(v===50)o.selected=true; starSel.appendChild(o); });
const starW=fieldRow(T('minStars'),starSel);
const sortSel=mk('select'); sortSel.id='r49-sort'; sortSel.style.cssText=inpSt+'width:100%;';
[['price_desc',T('sortHigh')],['price_asc',T('sortLow')],['price_mid',T('sortMid')]].forEach(([v,l])=>{ const o=mk('option','',l); o.value=v; sortSel.appendChild(o); });
const sortW=fieldRow(T('sortMode'),sortSel);
g2b.append(starW,sortW); plan.appendChild(g2b);
// ββ Γzel GΓΌn AyarlarΔ±
const sdSec=mk('div'); sdSec.style.cssText='margin-bottom:8px;border:1px solid #e0d8ff;border-radius:6px;padding:8px;';
const sdTtl=mk('div','',T('sdTitle')+':'); sdTtl.style.cssText='font-weight:600;font-size:12px;margin-bottom:6px;';
sdSec.appendChild(sdTtl);
for (const [day,sd] of ANCHOR_DAYS) {
const row=mk('div'); row.style.cssText='display:flex;align-items:center;gap:6px;margin-bottom:5px;flex-wrap:wrap;';
const lbl=mk('span','',`${sd.icon} ${sd.label}`); lbl.style.cssText='font-size:11px;min-width:140px;';
const cSel=mk('select'); cSel.id=`r49-sd-city-${day}`; cSel.style.cssText=inpSt+'flex:1;min-width:100px;';
const noOpt=mk('option','',_D('β SeΓ§ β','β Pick β','β Escolha β')); noOpt.value='0'; cSel.appendChild(noOpt);
CITIES.forEach(c=>{ const o=mk('option','',cityName(c)); o.value=String(c.id); cSel.appendChild(o); });
cSel.onchange=updateSummary;
const araSel=mk('select'); araSel.id=`r49-sd-rest-${day}`; araSel.style.cssText=inpSt+'width:110px;';
[0,1,2,3,4,5].forEach(v=>{ const o=mk('option','',`${v} `+_D('GΓΌn Dinlen','Days Rest','Dias Resto')); o.value=String(v); araSel.appendChild(o); });
araSel.onchange=updateSummary;
row.append(lbl,cSel,araSel);
sdSec.appendChild(row);
}
plan.appendChild(sdSec);
// ββ Stadyum Εehirleri
const stadSec=mk('div'); stadSec.style.cssText='margin-bottom:8px;';
const stadTtlRow=mk('div'); stadTtlRow.style.cssText='display:flex;align-items:center;gap:8px;margin-bottom:4px;';
const stadTtl=mk('span','',T('stadCities')+':'); stadTtl.style.cssText='font-weight:600;font-size:12px;flex:1;';
const stNone=mkB(T('selNone'),'',()=>{ CITIES.forEach(c=>{const e=g(`r49-stad-${c.id}`);if(e)e.checked=false;}); updateSummary(); updateStadBadge(); });
stNone.style.cssText=btnSt; stadTtlRow.append(stadTtl, stNone);
const stadGrid=mk('div'); stadGrid.style.cssText='display:flex;flex-wrap:wrap;gap:4px;max-height:90px;overflow-y:auto;border:1px solid #e0d8ff;border-radius:4px;padding:4px;';
CITIES.forEach(c=>{
const lbl=mk('label'); lbl.style.cssText='display:flex;align-items:center;gap:3px;font-size:11px;background:#fff;border:1px solid #e0d8ff;border-radius:4px;padding:2px 6px;cursor:pointer;';
const chk=mk('input'); chk.type='checkbox'; chk.id=`r49-stad-${c.id}`;
chk.onchange=()=>{ updateSummary(); updateStadBadge(); };
lbl.append(chk,mk('span','',cityName(c))); stadGrid.appendChild(lbl);
});
stadSec.append(stadTtlRow,stadGrid); plan.appendChild(stadSec);
// ββ Εehirler
const cityTitle=mk('div'); cityTitle.style.cssText='display:flex;align-items:center;gap:8px;margin-bottom:4px;';
const cityLbl=mk('span','',T('citiesLabel')+':'); cityLbl.style.cssText='font-weight:600;font-size:12px;flex:1;';
const allBtn=mkB(T('selAll'),'',()=>{ CITIES.forEach(c=>{const e=document.getElementById(`r49-city-${c.id}`);if(e)e.checked=true;}); updateSummary(); });
const noneBtn=mkB(T('selNone'),'',()=>{ CITIES.forEach(c=>{const e=document.getElementById(`r49-city-${c.id}`);if(e)e.checked=false;}); updateSummary(); });[allBtn,noneBtn].forEach(b=>b.style.cssText=btnSt);
cityTitle.append(cityLbl,allBtn,noneBtn);
const cityGrid=mk('div'); cityGrid.style.cssText='display:flex;flex-wrap:wrap;gap:4px;max-height:160px;overflow-y:auto;border:1px solid #e0d8ff;border-radius:4px;padding:4px;margin-bottom:8px;';
CITIES.forEach(c=>{
const lbl=mk('label'); lbl.style.cssText='display:flex;align-items:center;gap:3px;font-size:11px;background:#fff;border:1px solid #e0d8ff;border-radius:4px;padding:2px 6px;cursor:pointer;min-width:90px;';
const chk=mk('input'); chk.type='checkbox'; chk.id=`r49-city-${c.id}`; chk.checked=true; chk.onchange=updateSummary;
lbl.append(chk,mk('span','',cityName(c))); cityGrid.appendChild(lbl);
});
plan.append(cityTitle,cityGrid);
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TAB: ΕEMALAR (SCHEMAS)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const saved=panes['saved'];
// Initial render deferred to tab click for performance
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TAB: SETTINGS
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const sets=panes['settings'];
sets.style.paddingTop='4px';
// Links
const mkLink=(lbl,url)=>{ const a=document.createElement('a'); a.href=url; a.textContent=lbl; a.target='_blank'; a.style.cssText='padding:6px 14px;border:1px solid #6f42c1;border-radius:5px;font-size:12px;color:#6f42c1;text-decoration:none;background:#fff;display:inline-block;'; return a; };
const linkRow=mk('div'); linkRow.style.cssText='display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;';
linkRow.append(mkLink(T('btnReadme'),'https://rentry.org/Route49oku'), mkLink(T('btnTemplates'),'https://rentry.org/Route49sablon'));
sets.appendChild(linkRow);
// Language buttons
const langLbl=mk('div','',_D('Dil / Language / Idioma')); langLbl.style.cssText='font-weight:600;font-size:12px;margin-bottom:6px;';
const langRow=mk('div'); langRow.style.cssText='display:flex;gap:6px;';
[['TR','πΉπ· TΓΌrkΓ§e'],['EN','π¬π§ English'],['PT','π§π· PortuguΓͺs']].forEach(([code,lbl])=>{
const b=mkB(lbl,'',()=>setLang(code));
b.style.cssText=btnSt+(LANG===code?'background:#6f42c1;color:#fff;font-weight:700;':'');
langRow.appendChild(b);
});
sets.append(langLbl,langRow);
// Advanced Settings
const advLbl = mk('div','',_D('GeliΕmiΕ Ayarlar','Advanced Settings','ConfiguraΓ§Γ΅es AvanΓ§adas'));
advLbl.style.cssText='font-weight:600;font-size:12px;margin:12px 0 6px;';
const fastLbl = mk('label'); fastLbl.style.cssText='display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;margin-bottom:8px;';
const fastChk = mk('input'); fastChk.type='checkbox'; fastChk.id='r49-fastmode';
fastChk.onchange = updateSummary;
fastLbl.append(fastChk, mk('span','','β‘ ' + _D('HΔ±zlΔ± Mod (Riskli)','Fast Mode (Risky)','Modo RΓ‘pido (Risco)')));
const resetBtn = mkB(_D('π VarsayΔ±lanlara DΓΆn','Reset Defaults','Redefinir PadrΓ΅es'),'',()=>{
if(confirm(_D('TΓΌm ayarlar sΔ±fΔ±rlansΔ±n mΔ±?','Reset all settings?','Redefinir tudo?'))) {
gmDel(GMK.sets); location.reload();
}
});
resetBtn.style.cssText = btnSt + 'background:#fff0f0;color:#dc3545;border-color:#dc3545;';
sets.append(advLbl, fastLbl, resetBtn);
// ββ Append panes
Object.values(panes).forEach(p=>panel.appendChild(p));
// (openPreview moved to global scope)
// ββ Summary + controls
const sumEl=mk('div'); sumEl.id='r49-summary'; sumEl.style.cssText='font-size:11px;color:#555;min-height:1.2em;margin:8px 0 4px;';
panel.appendChild(sumEl);
const ctrlRow=mk('div'); ctrlRow.style.cssText='display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px;';
const prevBtn=mkB(T('btnPreview'),'',()=>{
// If there's an active tour, preview the active tour's state instead of generating a new one
const t = gmGet(GMK.tour,[]);
if (t.length > 0) openPreview(t, gmGet(GMK.sets,{}), 'active');
else openPreview(); // Otherwise generate a fresh one from UI
});
const startBtn=mkB(T('btnStart'),'',()=>startBooking());
const stopBtn=mkB(T('btnStop'),'',()=>{ if(confirm(T('confirmStop'))) stopBooking(); });
prevBtn.style.cssText='padding:6px 14px;border:none;border-radius:5px;cursor:pointer;font-size:12px;font-weight:600;color:#fff;background:#6f42c1;';
startBtn.style.cssText='padding:6px 14px;border:none;border-radius:5px;cursor:pointer;font-size:12px;font-weight:600;color:#fff;background:#28a745;';
stopBtn.style.cssText='padding:6px 14px;border:none;border-radius:5px;cursor:pointer;font-size:12px;font-weight:600;color:#fff;background:#dc3545;';
ctrlRow.append(prevBtn,startBtn,stopBtn);
panel.appendChild(ctrlRow);
const statusEl=mk('div'); statusEl.id='r49-status'; statusEl.style.cssText='font-size:12px;font-weight:600;color:#555;min-height:1.2em;';
statusEl.textContent=T('statusIdle');
panel.appendChild(statusEl);
// Inject into page
const ref=container.querySelector('.entityLogo')||container.querySelector('h1')||container.firstElementChild;
if (ref) container.insertBefore(panel,ref); else container.prepend(panel);
// Activate plan tab
document.getElementById('r49-tbtn-plan').click();
// Post-inject
updateStadBadge();
updateDL('r49-start','r49-start-lbl');
updateDL('r49-end','r49-end-lbl');
toggleCustom();
syncDoubleDayUI();
updateSummary();
}
// βββ ΕEMALAR (SCHEMAS) ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function renderSchemas(container) {
container.innerHTML='';
// YalnΔ±zca Global CSV Import Butonu (Bilgisayardan Εema yΓΌklemek iΓ§in)
const hr = mk('div'); hr.style.cssText = 'display:flex; gap:8px; margin-bottom:12px; padding-bottom:10px; border-bottom:1px solid #e0d8ff;';
const iBtn = mkB(T('btnImportCSV'), '', () => {
const inp = mk('input'); inp.type = 'file'; inp.accept = '.csv,text/csv';
inp.onchange = e => {
const f = e.target.files?.[0]; if(!f) return;
const r = new FileReader();
r.onload = ev => {
try {
const imp = csvToTour(String(ev.target.result));
if(!imp.length){ alert('No valid rows'); return; }
const sc = gmGet(GMK.schemas,[]);
sc.push({ id: Date.now(), name: `Imported_${fmtISO(new Date())}`, tour: imp, settings: DEF, templateDays: 56 });
gmSet(GMK.schemas, sc);
renderSchemas(container);
alert(`${imp.length} ${T('summaryShows')} OK`);
} catch { alert('CSV error'); }
};
r.readAsText(f);
}; inp.click();
});
iBtn.style.cssText = btnSt;
hr.appendChild(iBtn); container.appendChild(hr);
// "Son Kaydedilen" = active GMK.tour
const activeTour=gmGet(GMK.tour,[]);
const activeSets=gmGet(GMK.sets,null);
if (activeTour.length) {
const hdrRow = mk('div'); hdrRow.style.cssText = 'display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;';
const hdr=mk('div','',T('savedPlanHdr')+':'); hdr.style.cssText='font-weight:700;font-size:12px;color:#6f42c1;';
const saveBtn=mkB(T('btnSave'), '', () => {
const sc = gmGet(GMK.schemas,[]);
sc.push({ id: Date.now(), name: `Schema ${fmtISO(new Date())}`, tour: activeTour, settings: activeSets, templateDays: 56 });
gmSet(GMK.schemas, sc);
renderSchemas(container);
});
saveBtn.style.cssText = 'padding:2px 8px; font-size:11px; background:#6f42c1; color:#fff; border:none; border-radius:4px; cursor:pointer;';
hdrRow.append(hdr, saveBtn); container.appendChild(hdrRow);
const card = buildPlanCard({id:'active', name:T('savedPlanHdr'), tour:activeTour, settings:activeSets, templateDays:56}, container);
// Dynamic summary
const as = mk('div', '', tourSummary(activeTour)); as.id = 'r49-active-summary'; as.style.cssText = 'font-size:11px;color:#555;margin-bottom:6px;';
card.insertBefore(as, card.firstChild);
container.appendChild(card);
const sep=mk('hr'); sep.style.cssText='border:none;border-top:1px solid #e0d8ff;margin:10px 0;'; container.appendChild(sep);
}
// Render dynamic schemas
const schemas = gmGet(GMK.schemas,[]);
if (!activeTour.length && !schemas.length) {
const emptyState = mk('div', '', _D('HenΓΌz kaydedilmiΕ bir turne ΕemasΔ± bulunmuyor.', 'No saved tour schemas found.', 'Nenhum esquema de turnΓͺ salvo encontrado.'));
emptyState.style.cssText = 'color:#888; font-style:italic; text-align:center; padding:20px 0; font-size:12px;';
container.appendChild(emptyState);
} else {
schemas.forEach(sc => {
const slotHdr=mk('div', '', sc.name);
slotHdr.style.cssText='font-weight:700;font-size:12px;color:#888;margin-bottom:6px;';
container.appendChild(slotHdr);
container.appendChild(buildPlanCard(sc, container));
});
}
}
function buildPlanCard(planObj, tabContainer) {
const {id, name, tour, settings, templateDays} = planObj;
const card=mk('div'); card.style.cssText='background:#fff;border:1px solid #e0d8ff;border-radius:6px;padding:8px;margin-bottom:12px;';
const actRow = mk('div'); actRow.style.cssText='display:flex;gap:4px;margin-bottom:6px;flex-wrap:wrap;';
const selAllBtn = mkB(T('selAll'), '', () => {
document.querySelectorAll(`[id^="r49-plan-chk-${id}-"]:not(:disabled)`).forEach(cb=>cb.checked=true);
});
const selNoneBtn = mkB(T('selNone'), '', () => {
document.querySelectorAll(`[id^="r49-plan-chk-${id}-"]:not(:disabled)`).forEach(cb=>cb.checked=false);
});
[selAllBtn, selNoneBtn].forEach(b => b.style.cssText='padding:2px 8px;font-size:11px;border:1px solid #c9b8f0;border-radius:4px;cursor:pointer;background:#fff;color:#6f42c1;');
actRow.append(selAllBtn, selNoneBtn);
card.appendChild(actRow);
// Show list with checkboxes
const listDiv=mk('div'); listDiv.style.cssText='max-height:160px;overflow-y:auto;border:1px solid #e0d8ff;border-radius:4px;padding:4px;margin-bottom:6px;font-size:11px;';
(tour||[]).forEach((show,si)=>{
const row=mk('div'); row.style.cssText='display:flex;align-items:center;gap:5px;padding:2px 4px;border-radius:3px;'+(si%2===0?'background:#faf7ff;':'');
const chk=mk('input'); chk.type='checkbox'; chk.id=`r49-plan-chk-${id}-${si}`;
chk.checked=!show.booked;
if (show.booked) { chk.style.opacity='0.5'; chk.disabled=true; }
const c=cityById(show.cityId);
const sd=show.specialDay?SPECIAL_DAYS[show.specialDay]?.icon||'π΅':'π΅';
const pm=realToPM(new Date(show.date+'T00:00:00'));
const dateParts = show.date.split('-');
const mmdd = `${dateParts[1]}-${dateParts[2]}`; // MM-DD
const bIcon=show.booked?'β
':show.skipped?'β':'';
const pMin=settings?.priceMin||0, pMax=settings?.priceMax||0;
const stars=settings?Math.floor(settings.minStars/10):5;
const vStr = show.venueType==='stadium' ? T('venueStad') : T('venueBar');
// Format: π΅ 152-16 03-12 22:00 Δ°stanbul | Bar | 10-20 M$ | 5β
const txt=mk('span','',`${sd} ${pm.year}-${pm.day} ${mmdd} ${show.time.slice(0,5)} ${cityName(c)} | ${vStr} | ${pMin}-${pMax} M$ | ${stars}β
${bIcon}`);
txt.style.flex='1';
row.append(chk,txt); listDiv.appendChild(row);
});
card.appendChild(listDiv);
// Action buttons
const btnRow=mk('div'); btnRow.style.cssText='display:flex;gap:6px;flex-wrap:wrap;';
const editBtn=mkB(T('btnEdit'),'',()=>openSchemaEditor(planObj, tabContainer)); editBtn.style.cssText=btnSt;
const startBtn=mkB(T('btnStartPlan'),'',()=>{
const selectedTour=(tour||[]).filter((_,si)=>document.getElementById(`r49-plan-chk-${id}-${si}`)?.checked);
if (!selectedTour.length) { alert(_D('Konser seΓ§ilmedi.','No shows selected.','Nenhum show selecionado.')); return; }
const aid=settings?.artistId||document.getElementById('r49-artist')?.value?.trim()||'';
if (!aid) { alert(T('artistId')+'?'); return; }
const s={...settings, artistId:aid};
selectedTour.forEach(show => { if (show.willBook === undefined) show.willBook = true; });
gmSet(GMK.tour,selectedTour); gmSet(GMK.idx,0); gmSet(GMK.sets,s); gmSet(GMK.status,'CHECK_UPCOMING');
updateFloatBar();
window.location.href=`https://${window.location.hostname}/World/Popmundo.aspx/Artist/UpcomingPerformances/${aid}`;
});
startBtn.style.cssText='padding:4px 10px;border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;color:#fff;background:#28a745;';
const expBtn=mkB(T('btnExportCSV'),'',()=>dlCSV(tourToCSV(tour, settings), `${name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.csv`));
expBtn.style.cssText=btnSt;
const schemaPrevBtn=mkB('π '+_D('Γnizle','Preview','PrΓ©-ver'),'',()=>openPreview(tour, settings, id));
schemaPrevBtn.style.cssText=btnSt;
const delBtn = id!=='active' ? mkB(_D('π Sil','π Del','π Exc'),'',()=>{
if(confirm(_D('Sil?','Delete?','Excluir?'))){ const sc=gmGet(GMK.schemas,[]);gmSet(GMK.schemas,sc.filter(x=>x.id!==id));renderSchemas(tabContainer); }
}) : null;
if(delBtn){delBtn.style.cssText='padding:4px 10px;border:1px solid #dc3545;border-radius:4px;cursor:pointer;font-size:12px;background:#fff0f0;color:#dc3545;font-family:inherit;';}
btnRow.append(editBtn,startBtn,schemaPrevBtn,expBtn,...(delBtn?[delBtn]:[])); card.appendChild(btnRow);
return card;
}
function openSchemaEditor(planObj, tabContainer) {
const name = prompt(T('planName'), planObj.name||'');
if (name === null) return;
const updated = {...planObj, name: name.trim()||planObj.name};
if (planObj.id === 'active') {
gmSet(GMK.sets, {...(planObj.settings||{}), name});
} else {
const sc = gmGet(GMK.schemas,[]);
const idx = sc.findIndex(x => x.id === planObj.id);
if (idx !== -1) { sc[idx] = updated; gmSet(GMK.schemas, sc); }
}
renderSchemas(tabContainer);
}
// βββ START / STOP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function startBooking() {
const settings=gatherSettings();
if (!settings.artistId) { alert(T('artistId')+'?'); return; }
const tour=buildTour(settings);
if (!tour.length) { alert(T('noTour')); return; }
// VarsayΔ±lan olarak hepsini ayarla (willBook) yap
tour.forEach(s => { if (s.willBook === undefined) s.willBook = true; });
gmSet(GMK.tour,tour); gmSet(GMK.idx,0); gmSet(GMK.sets,settings); gmSet(GMK.status,'CHECK_UPCOMING');
updateFloatBar();
showPanelStatus(_D('YaklaΕan konserler kontrol ediliyor...','Checking upcoming shows...','Verificando prΓ³ximos shows...'),'#6f42c1');
const path=`/World/Popmundo.aspx/Artist/UpcomingPerformances/${settings.artistId}`;
window.location.href=`https://${window.location.hostname}${path}`;
}
function stopBooking() {[GMK.status,GMK.tour,GMK.idx,GMK.sets,GMK.restore].forEach(gmDel);
updateFloatBar(); location.reload();
}
function checkUpcomingAndProceed() {
const tour = gmGet(GMK.tour, []);
const settings = gmGet(GMK.sets, null);
if (!tour.length || !settings) { gmSet(GMK.status, 'IDLE'); return; }
const cityLinks = document.querySelectorAll('a[href*="/World/Popmundo.aspx/City/"]');
const existingCityIds = new Set();
cityLinks.forEach(a => {
const m = a.href.match(/\/City\/(\d+)/);
if (m) existingCityIds.add(parseInt(m[1]));
});
const conflictCities = new Set();
tour.forEach(show => {
if (existingCityIds.has(show.cityId) && !show.booked && show.willBook !== false) {
conflictCities.add(show.cityId);
}
});
if (conflictCities.size > 0) {
const cityNames = Array.from(conflictCities).map(id => cityName(cityById(id)) || id).join(', ');
const msg = _D(
`Dikkat: ${cityNames} Εehirlerinde halihazΔ±rda konserleriniz var!\n\nBu Εehirlerdeki yeni konser planlarΔ± otomatik olarak devredΔ±ΕΔ± bΔ±rakΔ±lsΔ±n mΔ±?`,
`Warning: You already have shows in ${cityNames}!\n\nDisable new planned shows in these cities?`,
`Aviso: VocΓͺ jΓ‘ tem shows em ${cityNames}!\n\nDesativar novos shows planejados nestas cidades?`
);
if (confirm(msg)) {
tour.forEach(show => {
if (conflictCities.has(show.cityId)) show.willBook = false;
});
gmSet(GMK.tour, tour);
}
if (!confirm(_D('Turne planΔ±na devam etmek istiyor musunuz?', 'Continue with tour plan?', 'Continuar com o plano?'))) {
gmSet(GMK.status, 'IDLE');
updateFloatBar();
return;
}
}
gmSet(GMK.status, 'RUNNING');
window.location.href = `https://${window.location.hostname}/World/Popmundo.aspx/Artist/BookShow/${settings.artistId}`;
}
// βββ POPCONTROL BAΔLANTISI βββββββββββββββββββββββββββββββββββββββββββββββββββββ
function registerWithPopControl() {
if (window.PPC_Route49_Done) return;
const pc = (typeof unsafeWindow !== 'undefined' && unsafeWindow.PopControl) || window.PopControl;
if (!pc?.register) return;
try {
pc.register({
id:'route49', icon:'π', label:'Route49',
buttons:[{
icon:'π', label:'Route49',
onClick:()=>{ window.location.href='/World/Popmundo.aspx/Artist/BookShow/'; }
}],
onUndo:()=>{ window.PPC_Route49_Done = false; document.getElementById('r49-panel')?.remove(); },
});
window.PPC_Route49_Done = true;
_injectR49MenuItems();
log('PopControl baΔlantΔ±sΔ± baΕarΔ±lΔ±');
} catch(e) {
console.error('[Route49] PopControl baΔlantΔ± hatasΔ±:', e);
}
}
// βββ INIT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function init() {
// Floating bar on every page
if (document.body) injectFloatBar(); else window.addEventListener('load',injectFloatBar);
// PopControl baΔlantΔ±sΔ±
document.addEventListener('PopControlReady', () => setTimeout(registerWithPopControl, 50), { once: true });
(function _checkPC(n) {
if (window.PPC_Route49_Done) return;
const pc = (typeof unsafeWindow !== 'undefined' && unsafeWindow.PopControl) || window.PopControl;
if (pc?.register) registerWithPopControl();
else if (n < 20) setTimeout(() => _checkPC(n + 1), 150);
})(0);
// BookShow UI
if (IS_BOOKSHOW) {
const tryInject=()=>{ if(!document.getElementById('r49-panel')) { injectUI(); if(settings) applySettingsToUI(settings); } };
if (document.readyState==='complete') tryInject(); else window.addEventListener('load',tryInject);
const iv=setInterval(()=>{ if(!document.getElementById('r49-panel')) tryInject(); },3000);
window.addEventListener('beforeunload',()=>clearInterval(iv));
}
// Check running state
const status=gmGet(GMK.status,'IDLE');
if (status === 'CHECK_UPCOMING') {
if (window.location.pathname.includes('/UpcomingPerformances/')) {
checkUpcomingAndProceed();
}
return;
}
if (status!=='RUNNING') return;
if (!settings) { gmSet(GMK.status,'IDLE'); return; }
// If running but NOT on BookShow β redirect there
if (!IS_BOOKSHOW) {
const aid=settings.artistId;
if (aid) { await delay(500); window.location.href=`https://${window.location.hostname}/World/Popmundo.aspx/Artist/BookShow/${aid}`; }
return;
}
// On BookShow + RUNNING β continue
await delay(700);
const p=document.getElementById('r49-panel'); if(p) p.style.opacity='0.6';
processNextShow(settings);
}
// βββ LOGGING SYSTEM ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function addLog(msg) {
const logs = gmGet('r49_logs', []);
logs.push(`[${new Date().toLocaleTimeString()}] ${msg}`);
if(logs.length > 100) logs.shift();
gmSet('r49_logs', logs);
}
function openLogViewer() {
const ov = mk('div'); ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:99999;display:flex;align-items:center;justify-content:center;';
const box = mk('div'); box.style.cssText = 'background:#fff;padding:20px;width:400px;max-height:80vh;overflow-y:auto;border-radius:8px;';
const logs = gmGet('r49_logs', []);
box.innerHTML = `<div style="font-weight:bold;margin-bottom:10px;">Logs</div>` + logs.map(l => `<div style="font-size:11px;border-bottom:1px solid #eee;">${l}</div>`).join('');
box.appendChild(mkB('β Kapat', '', () => ov.remove()));
ov.onclick = e => { if(e.target === ov) ov.remove(); };
document.body.appendChild(ov);
}
// ββ PUBLIC API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
window.__r49_api = {
goBookShow: () => { window.location.href = '/World/Popmundo.aspx/Artist/BookShow/'; },
openPreview:() => typeof openPreview === 'function' && openPreview(),
openLog: () => typeof openLogViewer === 'function' && openLogViewer(),
};
// ββ TOP VIP MENΓSΓ ENJEKSΔ°YONU ββββββββββββββββββββββββββββββββββββββββββββββ
const _injectR49MenuItems = () => {
if (!location.href.includes('/World/Popmundo.aspx/Character')) return;
// Artist ID β stored settings
const aid = gmGet(GMK.sets, null)?.artistId || '';
// ββ Artist quick-links ββββββββββββββββββββββββββββββββββββββββββββββ
const _artistLinks = [
{ id: 'mnu-r49-schedule', icon: 'π
', key: 'mnuSchedule', url: `/World/Popmundo.aspx/Artist/Schedule/${aid}` },
{ id: 'mnu-r49-setlist', icon: 'π΅', key: 'mnuSetlist', url: `/World/Popmundo.aspx/Setlist/` },
{ id: 'mnu-r49-popularity', icon: 'π', key: 'mnuPopularity', url: `/World/Popmundo.aspx/Artist/Popularity/${aid}` },
{ id: 'mnu-r49-repertoire', icon: 'π', key: 'mnuRepertoire', url: `/World/Popmundo.aspx/Artist/Repertoire/${aid}` },
{ id: 'mnu-r49-upcoming', icon: 'π€', key: 'mnuUpcoming', url: `/World/Popmundo.aspx/Artist/UpcomingPerformances/${aid}` },
{ id: 'mnu-r49-equipment', icon: 'πΈ', key: 'mnuEquipment', url: `/World/Popmundo.aspx/Artist/Equipment/${aid}` },
{ id: 'mnu-r49-vehicle', icon: 'π', key: 'mnuVehicle', url: `/World/Popmundo.aspx/Artist/Vehicle/${aid}` },
{ id: 'mnu-r49-vehicleitems', icon: 'π', key: 'mnuVehicleItems', url: `/World/Popmundo.aspx/Artist/VehicleItems/${aid}` },
{ id: 'mnu-r49-crew', icon: 'π₯', key: 'mnuCrew', url: `/World/Popmundo.aspx/Artist/Crew/${aid}` },
];
const r49Buttons = [
{ id: 'mnu-r49-bookshow', label: 'π Route49', fn: () => window.__r49_api?.goBookShow() },
..._artistLinks.map(l => ({ id: l.id, label: `${l.icon} ${T(l.key)}`, fn: () => { window.location.href = l.url; } })),
];
const _pc = (typeof unsafeWindow !== 'undefined' && unsafeWindow.PopControl) || window.PopControl;
if (_pc?.MenuManager) {
_pc.MenuManager.registerMenu({id: 'top-vip', title: 'β TOP VIP β', position: 'above-career', items: r49Buttons, collapsible: true });
return;
}
// ββ Standalone fallback (PopControl yoksa) ββββββββββββββββββββββββββ
if (document.getElementById('mnu-r49-bookshow')) return;
let ul = document.querySelector('#top-vip-menu ul');
if (!ul) {
const ref = [...document.querySelectorAll('.menu h3')]
.find(h => /Kariyer|Career|Carreira/.test(h.textContent))?.closest('.menu');
if (!ref) return;
const isCol = localStorage.getItem('top-vip-collapsed') === 'true';
const h3 = Object.assign(document.createElement('h3'), { textContent: 'β TOP VIP β' });
h3.style.cssText = 'background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;text-align:center;padding:8px;margin:0;border-radius:6px 6px 0 0;cursor:pointer;box-shadow:0 2px 8px rgba(102,126,234,.3);user-select:none;';
ul = document.createElement('ul');
ul.style.cssText = `margin:0;padding:8px 0;background:#f8f9fa;border:1px solid #e9ecef;border-top:none;border-radius:0 0 6px 6px;${isCol ? 'display:none;' : ''}`;
h3.onclick = () => { const c = ul.style.display === 'none'; ul.style.display = c ? '' : 'none'; localStorage.setItem('top-vip-collapsed', !c); };
const menu = Object.assign(document.createElement('div'), { id: 'top-vip-menu', className: 'menu' });
menu.append(h3, ul); ref.before(menu);
menu.after(Object.assign(document.createElement('div'), { style: 'height:12px' }));
}
r49Buttons.forEach(btn => {
if (document.getElementById(btn.id)) return;
const a = Object.assign(document.createElement('a'), { href: '#', textContent: btn.label });
a.style.cssText = 'color:#667eea;font-weight:600;text-decoration:none;display:block;padding:4px 12px;border-radius:4px;transition:all .2s;';
a.onmouseover = () => { a.style.background = '#667eea'; a.style.color = '#fff'; };
a.onmouseout = () => { a.style.background = ''; a.style.color = '#667eea'; };
a.onclick = e => { e.preventDefault(); btn.fn(); };
const li = Object.assign(document.createElement('li'), { id: btn.id }); li.style.margin = '2px 0';
li.appendChild(a); ul.appendChild(li);
});
};
_injectR49MenuItems();
init().catch(e=>console.error('[Route49] init error:',e));
})();