☰

Escort.club Helper

Pomaga w weryfikacji anonsów Escort.club: łączy dane z Escorti.pl i garsoniera.com.pl, analizuje powiązane profile i recenzje, wykrywa rozbieżności oraz dodaje filtry, cache i obserwowanie anonsów.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Escort.club Helper
// @namespace    escort-helper
// @version      2.000
// @description  Pomaga w weryfikacji anonsów Escort.club: łączy dane z Escorti.pl i garsoniera.com.pl, analizuje powiązane profile i recenzje, wykrywa rozbieżności oraz dodaje filtry, cache i obserwowanie anonsów.
// @author       Kut1234
// @copyright    2026, Kut1234
// @license      GPL-3.0-only
// @match       https://*.escort.club/*
// @match       https://escorti.pl/*
// @match       https://www.escorti.pl/*
// @match       https://garsoniera.com.pl/forum/*
// @match       https://www.garsoniera.com.pl/forum/*
// @match       https://picdetective.com/*
// @match       https://www.picdetective.com/*
// @match       https://hivedetect.ai/*
// @match       https://www.hivedetect.ai/*
// @resource    escortRadiusLeafletJs https://unpkg.com/[email protected]/dist/leaflet.js
// @resource    escortRadiusLeafletCss https://unpkg.com/[email protected]/dist/leaflet.css
// @grant       GM_getResourceText
// @grant       GM_xmlhttpRequest
// @grant       GM_openInTab
// @grant       window.close
// @grant       GM_notification
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.deleteValue
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_deleteValue
// @grant       GM_listValues
// @grant       GM_addValueChangeListener
// @grant       GM_removeValueChangeListener
// @grant       GM_registerMenuCommand
// @grant       GM_unregisterMenuCommand
// @grant       GM_setClipboard
// @grant       unsafeWindow
// @connect     www.garsoniera.com.pl
// @connect     garsoniera.com.pl
// @connect     escort.club
// @connect     escorti.pl
// @connect     www.escorti.pl
// @connect     static.escort.club
// @connect     ktoto.info
// @connect     www.ktoto.info
// @connect     numerytelefonu.com
// @connect     www.numerytelefonu.com
// @connect     sprawdzenienumeru.pl
// @connect     www.sprawdzenienumeru.pl
// @run-at       document-end
// ==/UserScript==
(async function () {
	'use strict';
	const helperDialogStack=[];
	const GARSO_BASE_URL='https://www.garsoniera.com.pl/forum/';
	const SCRIPT_VERSION='2.000';
	const GARSO_SEARCH_MIN_GAP_MS=7000;
	const GARSO_PHONE_SEARCH_CACHE_VERSION=6;
	const GARSO_EXTENDED_PHONE_SCAN_VERSION=5;
	const GARSO_SUMMARY_PARSER_VERSION=6;
	const GARSO_TOPIC_COUNT_CHECK_INTERVAL_MS=60 * 60 * 1000;
	const GARSO_TOPIC_BRIDGE_PREFIX='vm_garso_topic_bridge_';
	const GARSO_TOPIC_REVISION_KEY='vm_garso_topic_analysis_revision';
	const GARSO_SEARCH_GLOBAL_SLOT_KEY= 'vm_garso_global_search_slot';
	const GARSO_SEARCH_REQUEST_TIMEOUT_MS=20000;
	const GARSO_SEARCH_GLOBAL_SLOT_LEASE_MS=GARSO_SEARCH_REQUEST_TIMEOUT_MS + 10000;
	const GARSO_SEARCH_TICKET_PREFIX='vm_garso_search_ticket_';
	const GARSO_SEARCH_TICKET_TTL_MS=5000;
	const GARSO_SEARCH_NATIVE_UNTIL_KEY='vm_garso_native_search_until';
	const GARSO_SEARCH_GLOBAL_SLOT_VERIFY_MS=40;
	const GARSO_SEARCH_GLOBAL_SLOT_SIGNAL_TIMEOUT_MS=1000;
	let garsoSearchPageClosed=false;
	const GARSO_SEARCH_INSTANCE_ID= `${Date.now()}-${Math.random().toString(36).slice(2)}`;
	const GARSO_TOPIC_FETCH_CONCURRENT=6;
	const GARSO_TOPIC_MIN_GAP_MS=0;
	const GARSO_TOPIC_MAX_GAP_MS=3000;
	const GARSO_TOPIC_GAP_STEP_UP_MS=1000;
	const GARSO_TOPIC_GAP_STEP_DOWN_MS=500;
	const GARSO_TOPIC_RECOVERY_SUCCESSES=4;
	const GARSO_TOPIC_LINKS_CACHE_TTL_MS=12 * 60 * 60 * 1000;
	const GARSO_TOPIC_PAGE_CACHE_PREFIX='vm_garso_topic_page_';
	const GARSO_ANTIFLOOD_RETRY_LIMIT=3;
	const GARSO_REQUEST_MAX_ATTEMPTS=GARSO_ANTIFLOOD_RETRY_LIMIT + 1;
	const GARSO_OPEN_BRIDGE_PARAM='vm_garso_open';
	const GARSO_OPEN_BRIDGE_KEY_PREFIX='vm_garso_open_bridge_';
	const ESCORTI_BASE_URL='https://escorti.pl/';
	const CURRENT_HOST=location.hostname.toLowerCase();
	if (initReverseImageUrlPrefill()) return;
	function isEscortClubHost(host) {
		return /^(?:(?:[a-z]{2}|www)\.)?escort\.club$/i.test(String(host || ''));
	}
	function getEscortAdIdFromPath(pathname=location.pathname) {
		return String(pathname || '').match(/^\/(?:anons|personal-ads)\/(\d+)\.html\/?$/i)?.[1] || null;
	}
	function getEscortAdPath(adId,host=CURRENT_HOST) {
		if (!/^\d+$/.test(String(adId || ''))) return '';
		const directory=/^(?:(?:www|en)\.)?escort\.club$/i.test(host) ? 'personal-ads':'anons';
		return `/${directory}/${adId}.html`;
	}
	const IS_ESCORT_CLUB=isEscortClubHost(CURRENT_HOST);
	const ESCORT_CLUB_ORIGIN=IS_ESCORT_CLUB ? location.origin:'https://pl.escort.club';
	if (/(^|\.)escort\.club$/i.test(CURRENT_HOST) && !IS_ESCORT_CLUB) return;
	const GARSO_MENU_ONLY_MODE=[ 'garsoniera.com.pl', 'www.garsoniera.com.pl' ].includes(CURRENT_HOST);
	const GARSO_TOPIC_WORKER_ID=GARSO_MENU_ONLY_MODE ? new URLSearchParams(location.hash.slice(1)).get('vm_garso_topic_worker') || '':'';
	function makeElement(tagName,className='',textContent=null,properties=null) {
		const element=document.createElement(tagName);
		if (className) element.className=className;
		if (textContent !== null) element.textContent=textContent;
		if (properties) Object.assign(element,properties);
		return element;
	}
	function makeButton(className='',textContent=null,properties=null) {
		return makeElement('button',className,textContent,{type:'button',...properties});
	}
	function setControlTitle(element,title) {
		element.title=title;
		element.setAttribute('aria-label',title);
	}
	function stopUiEvent(event) {
		event.preventDefault();
		event.stopPropagation();
	}
	function showStatusText(element,text) {
		element.style.display='block';
		element.textContent=text;
	}
	function setTrashIcon(button) {
		button.textContent='';
		button.innerHTML= '<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true" ' +
			'style="display:block;margin:auto;fill:none;stroke:currentColor;stroke-width:2;' +
			'stroke-linecap:round;stroke-linejoin:round">' + '<path d="M4 7h16"/>' + '<path d="M9 7V4h6v3"/>' +
			'<path d="M6.5 7l1 13h9l1-13"/>' + '<path d="M10 11v5M14 11v5"/>' + '</svg>';
	}
	function makeFavoriteViewButton(view,onClick) {
		const tiles=view === 'tiles',button=makeButton('vm-ui-button vm-favorites-view-button');
		const paths=tiles ? '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>'
			: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M3 14h18M9 4v16"/>';
		button.innerHTML=`<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">${paths}</svg>`;
		button.appendChild(makeElement('span','vft-action-label',tiles ? 'Kafelki':'Tabela'));
		setControlTitle(button,tiles ? 'Otwórz widok kafelkowy Ulubionych':'Otwórz tabelę Ulubionych');
		button.addEventListener('click',onClick);
		return button;
	}
	function appendSelectOptions(select,options,selectedValue=null) {
		const selected=selectedValue == null ? null:String(selectedValue);
		for (const [value,label] of options) {
			const option=makeElement('option','',label,{value:String(value)});
			if (selected !== null) option.selected=option.value === selected;
			select.appendChild(option);
		}
		return select;
	}
	function createAnimationFrameScheduler(callback) {
		let scheduled=false;
		return () => {
			if (scheduled) return;
			scheduled=true;
			requestAnimationFrame(() => {
				scheduled=false;
				callback();
			});
		};
	}
	function createConcurrentJobQueue(maxConcurrent,errorLabel) {
		const queue=[];
		const getLimit=typeof maxConcurrent === 'function' ? maxConcurrent:() => maxConcurrent;
		let activeJobs=0;
		const pump=() => {
			while (queue.length && activeJobs < getLimit()) {
				const job=queue.shift();
				activeJobs++;
				Promise.resolve() .then(job) .catch(error => log(errorLabel,error)) .finally(() => {
						activeJobs--;
						pump();
					});
			}
		};
		return job => {
			queue.push(job);
			pump();
		};
	}
	function parseGarsoAuth(html) {
		const hm=html.match(/ipb\.vars\[['"]secure_hash['"]\]\s*=\s*['"]([^'"]+)['"]/)
			|| html.match(/name=["']secure_hash["']\s+value=["']([^"']+)["']/);
		const sm=html.match(/ipb\.vars\[['"]session_id['"]\]\s*=\s*['"]([^'"]+)['"]/);
		return hm && sm ? {secureHash:hm[1],sessionId:sm[1]}:null;
	}
	async function handleGarsoOpenBridgePage() {
		if (!GARSO_MENU_ONLY_MODE) return false;
		const pageUrl=new URL(location.href);
		const fragment=new URLSearchParams(pageUrl.hash.slice(1));
		const token=pageUrl.searchParams.get(GARSO_OPEN_BRIDGE_PARAM) || fragment.get(GARSO_OPEN_BRIDGE_PARAM);
		const storageKey=GARSO_OPEN_BRIDGE_KEY_PREFIX + token;
		const pendingKey=GARSO_OPEN_BRIDGE_KEY_PREFIX + 'pending';
		let payload=token ? await GM.getValue(storageKey,null):null;
		if (!token) {try {payload=JSON.parse(sessionStorage.getItem(pendingKey) || 'null');} catch {}}
		const cleanMarker=() => {
			if (!token) return;
			pageUrl.searchParams.delete(GARSO_OPEN_BRIDGE_PARAM);
			fragment.delete(GARSO_OPEN_BRIDGE_PARAM);
			pageUrl.hash=fragment.toString();
			try {history.replaceState(history.state,'',pageUrl.href);} catch {}
		};
		if (!payload?.term || Date.now() - Number(payload.createdAt || 0) > 5 * 60 * 1000) {
			if (token) deleteHelperStorageValue(storageKey);
			try {sessionStorage.removeItem(pendingKey);} catch {}
			cleanMarker();
			return false;
		}
		const submit=() => {
			const inputs=document.querySelectorAll('input[name="search_term"], textarea[name="search_term"], #main_search, #search_term');
			for (const input of inputs) {
				if (!('value' in input)) continue;
				input.value=payload.term;
				input.dispatchEvent(new Event('input',{bubbles:true}));
				input.dispatchEvent(new Event('change',{bubbles:true}));
			}
			const andorType=payload.andorType === 'or' ? 'or':'and';
			const matchSelect=document.querySelector('select[name="andor_type"]');
			if (matchSelect) {matchSelect.value=andorType; matchSelect.dispatchEvent(new Event('change',{bubbles:true}));}
			if (!token) {
				try {sessionStorage.removeItem(pendingKey);} catch {}
				return;
			}
			cleanMarker();
			const auth=parseGarsoAuth(document.documentElement.outerHTML || '') || payload;
			if (!auth.secureHash || !auth.sessionId) {deleteHelperStorageValue(storageKey); return;}
			try {sessionStorage.setItem(pendingKey,JSON.stringify({term:payload.term,andorType,createdAt:Date.now()}));} catch {}
			const form=makeElement('form','',null,{method:'POST',action:`${location.origin}/forum/index.php?app=core&module=search&do=search&fromMainBar=1&s=${encodeURIComponent(auth.sessionId)}`});
			form.style.display='none';
			for (const [name,value] of Object.entries({search_term:payload.term,andor_type:andorType,search_app:'forums',secure_hash:auth.secureHash,submit:'Szukaj'})) {
				const input=makeElement('input','',null,{type:'hidden',name,value});
				form.appendChild(input);
			}
			document.body.appendChild(form);
			try {noteGarsoNativeSearch(); HTMLFormElement.prototype.submit.call(form);}
			catch {detachHelperNode(form); inputs[0]?.focus();}
			deleteHelperStorageValue(storageKey);
		};
		if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded',submit,{once:true});
		else submit();
		return !!token;
	}
	if (GARSO_MENU_ONLY_MODE) document.addEventListener('submit',event => {
		if (event.target?.querySelector('[name="search_term"]')) noteGarsoNativeSearch();
	},true);
	const SETTINGS_STORAGE_KEY='vm_garso_settings';
	const SETTINGS_ONBOARDING_SEEN_KEY='vm_garso_settings_onboarding_seen';
	const WATCH_ITEMS_STORAGE_KEY='vm_garso_watch_items';
	const WATCH_AD_DATA_MIRROR_STORAGE_KEY='vm_garso_watch_ad_data';
	let hiddenWatchIndex=null;
	let watchedItemsReadCache=null;
	let helperSettingsReadCache=null;
	let watchedAdDataMirrorMemory=null;
	const helperRawSetValue=GM_setValue,helperRawDeleteValue=GM_deleteValue;
	function invalidateHelperReadCache(key) {
		if (key === SETTINGS_STORAGE_KEY) {helperSettingsReadCache=null; watchedItemsReadCache=null; hiddenWatchIndex=null;}
		if (key === WATCH_ITEMS_STORAGE_KEY) {watchedItemsReadCache=null; hiddenWatchIndex=null;}
		if (key === WATCH_AD_DATA_MIRROR_STORAGE_KEY) watchedAdDataMirrorMemory=null;
	}
	function writeHelperStorageValue(key,value) {
		invalidateHelperReadCache(key);
		return helperRawSetValue(key,value);
	}
	function deleteHelperStorageValue(key) {
		invalidateHelperReadCache(key);
		return helperRawDeleteValue(key);
	}
	if (await handleGarsoOpenBridgePage()) return;
	function readHelperSettingsSnapshot() {
		return helperSettingsReadCache ||= loadHelperSettingsFromStorage();
	}

	const WATCH_EVENTS_STORAGE_KEY='vm_garso_watch_events';
	const FAVORITE_TABLE_STATE_STORAGE_KEY='vm_garso_favorite_table_state';
	const FAVORITE_LAST_ALERT_MODES_STORAGE_KEY= 'vm_garso_favorite_last_alert_modes';
	const WATCH_CHECK_LOCK_KEY='vm_garso_watch_check_lock';
	const SCRIPT_DATA_EXPORT_FORMAT='escort-helper-script-data';
	const DIAGNOSTICS_STORAGE_KEY='vm_garso_diagnostics';
	const DIAGNOSTICS_TAB_PREFIX=DIAGNOSTICS_STORAGE_KEY + '_tab_';
	const DIAGNOSTICS_EPOCH_KEY=DIAGNOSTICS_STORAGE_KEY + '_epoch';
	const DIAGNOSTICS_EXPORT_FORMAT='vm-garso-diagnostics-sanitized';
	const DIAGNOSTICS_MAX_EVENTS=10;
	const DIAGNOSTICS_REQUEST_BUCKET_MS=60 * 1000;
	const DIAGNOSTICS_REQUEST_BUCKET_RETENTION_MS=25 * 60 * 60 * 1000;
	const DIAGNOSTICS_SAVE_DELAY_MS=5000;
	const ADAPTIVE_BACKOFF_STORAGE_KEY= 'vm_garso_adaptive_request_backoff';
	let diagnosticsState=null;
	let diagnosticsSaveTimer=null;
	let diagnosticsSnapshotInProgress=false;
	const WATCH_BAR_ID='vm-garso-watch-notification-bar';
	const WATCH_BAR_SPACER_ID='vm-garso-watch-notification-spacer';
	const WATCH_SETTINGS_OVERLAY_ID='vm-garso-watch-settings-overlay';
	const HIDDEN_PROFILE_CHECK_INTERVAL_MS=60 * 60 * 1000;
	const TRACKED_ITEMS_LABEL='Zapisane anonse';
	const GLOBAL_QUICK_SEARCH_ID='vm-global-quick-search';
	const ESCORT_MULTI_LOCATION_UI_ID='vm-escort-multi-location-ui';
	const ESCORT_MULTI_LOCATION_SESSION_KEY='vm_escort_multi_locations_session';
	const FAVORITE_SEARCH_EDIT_KEY='vm_favorite_search_edit_session';
	const FAVORITE_SEARCH_EDIT_PARAM='vm_edit_search';
	const FAVORITE_SEARCH_VIEW_KEY='vm_favorite_search';
	let favoriteSearchViewSession=null;
	let favoriteSearchEditSession=null,favoriteSearchEditReadFilters=null,favoriteSearchEditCancelToken=null;
	let favoriteSearchEditFrameNotify=null;
	const ESCORT_MULTI_LOCATION_MAX=30;
	// Miasta i ID: eksport kolektora z 2026-10-06; współrzędne: GeoNames, CC BY 4.0, zaokrąglone do 4 miejsc.
	// Źródła: https://pl.escort.club/ - https://download.geonames.org/export/dump/PL.zip
	const ESCORT_RADIUS_PROVINCES=["dolnośląskie","kujawsko-pomorskie","łódzkie","lubelskie","lubuskie","małopolskie","mazowieckie","opolskie","podkarpackie","podlaskie","pomorskie","śląskie","świętokrzyskie","warmińsko-mazurskie","wielkopolskie","zachodniopomorskie"];
	const ESCORT_RADIUS_PROVINCE_IDS=[2,4,10,6,8,12,14,16,18,20,22,24,26,28,30,32];
	// Miasto: nazwa, indeks województwa, szerokość, długość, ID Escort.club.
	const ESCORT_RADIUS_CITIES=[
		["Aleksandrów Kujawski",1,52.8766,18.6934,92],["Andrychów",5,49.855,19.3383,329],["Augustów",9,53.8432,22.9798,508],["Barczewo",13,53.8306,20.6911,723],
		["Bardo",0,50.5059,16.7399,76],["Bartoszyce",13,54.2535,20.8082,693],["Będzin",11,50.3261,19.1257,590],["Bełchatów",2,51.3688,19.3567,232],
		["Bełżyce",3,51.1741,22.2803,162],["Biała Podlaska",3,52.0324,23.1165,186],["Białobrzegi",6,51.6469,20.9504,337],["Białogard",15,54.007,15.9875,854],
		["Białystok",9,53.1333,23.1643,545],["Biecz",5,49.736,21.263,287],["Bielawa",0,50.6908,16.623,3],["Bielsko-Biała",11,49.8225,19.0469,642],
		["Bielsk Podlaski",9,52.7651,23.1865,519],["Biłgoraj",3,50.5411,22.722,146],["Błaszki",2,51.6516,18.4347,259],["Bobowa",5,49.7087,20.9477,288],
		["Bochnia",5,49.9691,20.4303,276],["Bogatynia",0,50.9075,14.9563,82],["Bolesławiec",0,51.2642,15.5697,1],["Bolków",0,50.922,16.1011,12],
		["Braniewo",13,54.3797,19.8196,697],["Brodnica",1,53.2597,19.3965,95],["Brzeg",7,50.8608,17.4674,422],["Brzesko",5,49.9691,20.6061,278],
		["Brzeszcze",5,49.982,19.1516,312],["Buk",14,52.3553,16.5196,812],["Busko-Zdrój",12,50.4708,20.7188,661],["Bydgoszcz",1,53.1235,18.0076,140],
		["Bytom",11,50.348,18.9328,643],["Bytów",10,54.1706,17.4919,548],["Chełm",3,51.1431,23.4716,187],["Chełmno",1,53.3486,18.4251,100],
		["Chmielnik",12,50.6144,20.7521,670],["Chojnice",10,53.6955,17.557,550],["Chorzów",11,50.3058,18.9742,644],["Chrzanów",5,50.1355,19.402,281],
		["Ciechanów",6,52.8814,20.62,339],["Ciechocinek",1,52.8791,18.795,93],["Cieszyn",11,49.7513,18.6321,598],["Czarnków",14,52.9021,16.5641,745],
		["Czechowice-Dziedzice",11,49.9134,19.0048,596],["Czeladź",11,50.3154,19.0782,591],["Częstochowa",11,50.7965,19.1241,645],["Człuchów",10,53.6672,17.3588,553],
		["Czyżew",9,52.7977,22.3124,542],["Dąbrowa Górnicza",11,50.3339,19.2048,646],["Daleszyce",12,50.8023,20.8079,671],["Dębica",8,50.0515,21.4114,459],
		["Dobrodzień",7,50.7287,18.445,441],["Drawsko Pomorskie",15,53.5306,15.8097,862],["Dynów",8,49.8151,22.2339,490],["Działdowo",13,53.2396,20.17,700],
		["Dzierzgoń",10,53.922,19.347,584],["Dzierżoniów",0,50.7282,16.6514,4],["Elbląg",13,54.1522,19.4088,740],["Ełk",13,53.8282,22.3647,705],
		["Garwolin",6,51.8975,21.6147,341],["Gdańsk",10,54.3523,18.6491,586],["Gdynia",10,54.5189,18.5319,587],["Giżycko",13,54.0381,21.7644,706],
		["Gliwice",11,50.2976,18.6766,647],["Głogów",0,51.6636,16.0845,8],["Głowno",2,51.9646,19.7157,267],["Głuchołazy",7,50.315,17.3835,436],
		["Gniezno",14,52.5348,17.5826,749],["Goleniów",15,53.5639,14.8285,865],["Góra",0,51.6664,16.5349,9],["Góra Kalwaria",6,51.9765,21.2154,375],
		["Gorlice",5,49.6556,21.1604,286],["Gorzów Wielkopolski",4,52.7337,15.225,230],["Gostyń",14,51.8825,17.0123,755],["Grodzisk Mazowiecki",6,52.1039,20.6337,348],
		["Grodzisk Wielkopolski",14,52.2276,16.3653,759],["Grójec",6,51.8625,20.8676,349],["Grudziądz",1,53.4841,18.7537,141],["Gryfino",15,53.2524,14.4883,874],
		["Hajnówka",9,52.7433,23.5812,524],["Hrubieszów",3,50.805,23.8925,152],["Iława",13,53.596,19.5685,708],["Inowrocław",1,52.7989,18.2639,105],
		["Jabłonna",6,52.3788,20.9174,3634],["Jarocin",14,51.9727,17.5026,763],["Jarosław",8,50.0162,22.6778,462],["Jasło",8,49.7451,21.4725,465],
		["Jastrzębia Góra",10,54.8314,18.313,3646],["Jastrzębie-Zdrój",11,49.9554,18.5748,648],["Jaworzno",11,50.2053,19.275,649],["Jędrzejów",12,50.6394,20.3045,663],
		["Jelcz-Laskowice",0,51.0213,17.3165,50],["Jelenia Góra",0,50.8997,15.729,88],["Kalisz",14,51.7611,18.091,850],["Kalwaria Zebrzydowska",5,49.8676,19.6772,330],
		["Kartuzy",10,54.3342,18.1974,557],["Katowice",11,50.2597,19.0217,650],["Kędzierzyn-Koźle",7,50.3498,18.2261,428],["Kępno",14,51.2784,17.9891,766],
		["Kęty",5,49.8821,19.2233,314],["Kielce",12,50.8703,20.6275,692],["Kłodzko",0,50.4349,16.6614,20],["Kluczbork",7,50.9728,18.2182,430],
		["Knurów",11,50.2197,18.6507,605],["Koło",14,52.2002,18.6386,767],["Kołobrzeg",15,54.1756,15.5834,883],["Konin",14,52.2234,18.2512,851],
		["Końskie",12,51.1917,20.4061,672],["Konstancin-Jeziorna",6,52.0938,21.1176,376],["Kórnik",14,52.2477,17.0895,814],["Kościan",14,52.0883,16.6487,776],
		["Kościerzyna",10,54.1223,17.9812,559],["Kostrzyn",14,52.3985,17.2281,813],["Kostrzyn nad Odrą",4,52.5871,14.6495,190],["Koszalin",15,54.1944,16.1722,916],
		["Kozienice",6,51.5829,21.5478,353],["Kraków",5,50.0614,19.9366,334],["Kraśnik",3,50.9236,22.2271,156],["Krobia",14,51.7741,16.9824,756],
		["Krośniewice",2,52.2559,19.1704,235],["Krosno",8,49.6887,21.7706,504],["Krotoszyn",14,51.6987,17.4374,783],["Krynica-Zdrój",5,49.4222,20.9594,301],
		["Krzeszowice",5,50.1425,19.6322,289],["Kutno",2,52.2306,19.3641,234],["Kwidzyn",10,53.7249,18.9311,560],["Łańcut",8,50.0687,22.2291,478],
		["Łask",2,51.5906,19.1328,237],["Lębork",10,54.5392,17.7501,562],["Łęczyca",2,52.0596,19.1997,238],["Legionowo",6,52.4015,20.9266,354],
		["Legnica",0,51.2101,16.1619,89],["Leszno",14,51.8403,16.5749,852],["Leżajsk",8,50.2626,22.4193,472],["Limanowa",5,49.7059,20.422,294],
		["Łobez",15,53.6392,15.6213,913],["Łódź",2,51.7706,19.4739,273],["Łomianki",6,52.3341,20.886,401],["Łomża",9,53.1781,22.0593,546],
		["Łowicz",2,52.1071,19.9453,239],["Lubaczów",8,50.157,23.1234,474],["Lubin",0,51.4009,16.2015,36],["Lublin",3,51.2506,22.5701,188],
		["Lubliniec",11,50.669,18.6844,611],["Luboń",14,52.3471,16.8927,810],["Łuków",3,51.929,22.3796,165],["Maków Mazowiecki",6,52.8649,21.1005,358],
		["Malbork",10,54.0359,19.0266,564],["Marki",6,52.3207,21.1047,406],["Miasteczko Śląskie",11,50.5026,18.9395,625],["Miechów",5,50.3565,20.0279,296],
		["Międzychód",14,52.5988,15.897,787],["Międzyrzecz",4,52.4446,15.578,194],["Międzyzdroje",15,53.9292,14.451,881],["Mielec",8,50.2871,21.4239,479],
		["Mielno",15,54.2609,16.0621,921],["Mikołajki",13,53.8029,21.5701,719],["Mikołów",11,50.171,18.9041,614],["Milanówek",6,52.1188,20.6715,346],
		["Milicz",0,51.5277,17.2714,43],["Mińsk Mazowiecki",6,52.1793,21.5725,360],["Mława",6,53.1128,20.3841,365],["Mosina",14,52.2454,16.8471,815],
		["Mrągowo",13,53.8644,21.3051,718],["Mszana Dolna",5,49.6743,20.0799,295],["Mszczonów",6,51.9742,20.5208,417],["Murowana Goślina",14,52.5746,17.0093,816],
		["Myślenice",5,49.8338,19.9383,298],["Mysłowice",11,50.2075,19.1667,651],["Myszków",11,50.5752,19.3246,616],["Nakło nad Notecią",1,53.1421,17.6018,117],
		["Namysłów",7,51.0759,17.7228,435],["Niemodlin",7,50.642,17.6193,445],["Nowa Sól",4,51.8033,15.717,197],["Nowe Miasto Lubawskie",13,53.4208,19.5952,721],
		["Nowy Dwór Gdański",10,54.2131,19.1177,567],["Nowy Dwór Mazowiecki",6,52.4302,20.7165,366],["Nowy Sącz",5,49.6218,20.6971,335],["Nowy Targ",5,49.4778,20.0323,305],
		["Nowy Tomyśl",14,52.3195,16.1284,790],["Nysa",7,50.4738,17.3344,438],["Oborniki",14,52.6474,16.8141,793],["Oława",0,50.9466,17.2926,49],
		["Oleśnica",0,51.2134,17.3899,44],["Olesno",7,50.877,18.4209,443],["Olkusz",5,50.2813,19.565,309],["Olsztyn",13,53.7838,20.4927,741],
		["Opole",7,50.6712,17.926,456],["Orzesze",11,50.1559,18.7792,615],["Ostróda",13,53.6967,19.9649,728],["Ostrołęka",6,53.0862,21.5757,418],
		["Ostrowiec Świętokrzyski",12,50.9294,21.3852,676],["Ostrów Mazowiecka",6,52.8025,21.8951,370],["Ostrów Wielkopolski",14,51.655,17.8069,795],["Ostrzeszów",14,51.4264,17.9336,801],
		["Oświęcim",5,50.0344,19.2104,311],["Otmuchów",7,50.4663,17.1735,439],["Otwock",6,52.1058,21.2613,373],["Ozorków",2,51.9634,19.2914,268],
		["Pabianice",2,51.6645,19.3547,246],["Piaseczno",6,52.0814,21.024,377],["Piastów",6,52.1844,20.8395,384],["Piła",14,53.1514,16.7378,802],
		["Piotrków Trybunalski",2,51.4055,19.7032,274],["Pisz",13,53.6274,21.8125,734],["Pleszew",14,51.8964,17.7855,809],["Płock",6,52.5468,19.7064,419],
		["Płońsk",6,52.6235,20.3755,382],["Pniewy",14,52.5094,16.2567,828],["Podkowa Leśna",6,52.1224,20.7266,347],["Polanica-Zdrój",0,50.4037,16.5127,23],
		["Polkowice",0,51.5039,16.0726,52],["Poznań",14,52.4069,16.9299,853],["Prudnik",7,50.3212,17.5746,450],["Pruszcz Gdański",10,54.2622,18.6363,556],
		["Pruszków",6,52.1707,20.8121,385],["Przedecz",14,52.3344,18.8991,770],["Przemyśl",8,49.785,22.7673,505],["Przeworsk",8,50.0591,22.4941,485],
		["Pszczyna",11,49.9804,18.9538,619],["Puławy",3,51.4166,21.9694,170],["Rabka-Zdrój",5,49.6089,19.9665,307],["Racibórz",11,50.0919,18.2193,620],
		["Radlin",11,50.0502,18.4763,632],["Radom",6,51.4025,21.1471,420],["Radomsko",2,51.0671,19.4448,253],["Radymno",8,49.9472,22.8238,463],
		["Radzymin",6,52.4159,21.1841,409],["Radzyń Podlaski",3,51.7833,22.6167,173],["Rajgród",9,53.731,22.7051,522],["Rawicz",14,51.6095,16.8585,823],
		["Reda",10,54.6053,18.3472,581],["Ruda Śląska",11,50.2584,18.8563,653],["Rumia",10,54.5709,18.388,582],["Rybnik",11,50.0971,18.5418,654],
		["Rydułtowy",11,50.0586,18.417,633],["Rypin",1,53.066,19.4094,121],["Rzeszów",8,50.0413,21.999,506],["Sandomierz",12,50.6827,21.749,681],
		["Sanok",8,49.5557,22.2056,496],["Sędziszów",12,50.5659,20.0556,665],["Sępólno Krajeńskie",1,53.452,17.5317,123],["Serock",6,52.5104,21.0691,355],
		["Siedlce",6,52.1677,22.2901,421],["Siemianowice Śląskie",11,50.3274,19.029,655],["Sieradz",2,51.5958,18.7302,258],["Skała",5,50.2305,19.8536,290],
		["Skarżysko-Kamienna",12,51.1131,20.8716,684],["Skawina",5,49.9752,19.8287,291],["Skierniewice",2,51.9549,20.1584,275],["Skoczów",11,49.8009,18.7877,601],
		["Słubice",4,52.3509,14.5607,204],["Słupca",14,52.2873,17.8719,824],["Słupsk",10,54.4641,17.0287,588],["Sochaczew",6,52.2294,20.2384,396],
		["Sokołów Podlaski",6,52.4068,22.2531,397],["Sopot",10,54.4418,18.56,589],["Sosnowiec",11,50.2868,19.1039,656],["Śrem",14,52.0887,17.0151,834],
		["Środa Wielkopolska",14,52.2284,17.2762,831],["Stalowa Wola",8,50.5829,22.0533,498],["Starachowice",12,51.0374,21.0713,686],["Stargard",15,53.3367,15.0499,897],
		["Starogard Gdański",10,53.964,18.5264,576],["Stęszew",14,52.2837,16.7008,818],["Stryków",2,51.9022,19.6054,271],["Strzegom",0,50.9626,16.3501,60],
		["Strzelce Opolskie",7,50.5107,18.3006,453],["Strzelin",0,50.7816,17.0648,54],["Sucha Beskidzka",5,49.7419,19.5943,319],["Sulejówek",6,52.2522,21.269,364],
		["Suwałki",9,54.1118,22.9309,547],["Swarzędz",14,52.4129,17.085,819],["Świdnica",0,50.8438,16.4886,57],["Świdnik",3,51.219,22.6962,176],
		["Świebodzice",0,50.8597,16.3197,58],["Świebodzin",4,52.2475,15.5335,211],["Świecie",1,53.4095,18.4474,126],["Świętochłowice",11,50.2964,18.9173,657],
		["Świnoujście",15,53.9105,14.2471,918],["Szamotuły",14,52.612,16.5779,829],["Szczecin",15,53.4289,14.553,917],["Szczecinek",15,53.7079,16.6994,902],
		["Szczyrk",11,49.7172,19.0318,595],["Szczytno",13,53.5626,20.9875,736],["Tarnobrzeg",8,50.573,21.6794,507],["Tarnów",5,50.0138,20.987,336],
		["Tarnowo Podgórne",14,52.4664,16.6633,3653],["Tarnowskie Góry",11,50.4455,18.8615,627],["Tczew",10,54.0924,18.7779,578],["Tomaszów Mazowiecki",2,51.5313,20.0085,262],
		["Toruń",1,53.0138,18.5981,142],["Trzebnica",0,51.3108,17.0633,64],["Tuchola",1,53.5879,17.859,128],["Turek",14,52.0155,18.5006,835],
		["Tychowo",15,53.9277,16.2577,856],["Tychy",11,50.1372,18.9664,658],["Uniejów",2,51.9743,18.7931,252],["Ustka",10,54.5805,16.8619,572],
		["Ustroń",11,49.7215,18.802,599],["Wadowice",5,49.8834,19.4929,331],["Wągrowiec",14,52.8084,17.1996,838],["Wałbrzych",0,50.7714,16.2843,91],
		["Wałcz",15,53.2779,16.4712,908],["Warka",6,51.7843,21.1909,352],["Warszawa",6,52.2298,21.0118,919],["Wejherowo",10,54.6057,18.2356,583],
		["Wieliczka",5,49.9874,20.0647,333],["Wieluń",2,51.221,18.5696,263],["Wisła",11,49.6563,18.8591,600],["Władysławowo",10,54.7909,18.4009,571],
		["Włocławek",1,52.6482,19.0678,143],["Wodzisław Śląski",11,50.0038,18.472,634],["Wolbrom",5,50.3796,19.7583,310],["Wołomin",6,52.3401,21.2421,411],
		["Wolsztyn",14,52.1155,16.1171,841],["Wrocław",0,51.1029,17.0301,90],["Września",14,52.3251,17.5652,845],["Wyszków",6,52.5928,21.4584,412],
		["Ząbki",6,52.2927,21.1054,407],["Ząbkowice Śląskie",0,50.5897,16.8124,77],["Zabrze",11,50.3249,18.7858,659],["Żagań",4,51.6176,15.3149,219],
		["Zakopane",5,49.299,19.9489,328],["Zambrów",9,52.9855,22.2432,544],["Zamość",3,50.7231,23.252,189],["Żary",4,51.642,15.1373,224],
		["Zator",5,49.996,19.438,315],["Zawiercie",11,50.4877,19.4168,636],["Zduńska Wola",2,51.5992,18.9397,265],["Zgierz",2,51.8556,19.4062,269],
		["Zgorzelec",0,51.1494,15.0084,81],["Zielona Góra",4,51.9355,15.5064,231],["Zielonka",6,52.3038,21.1602,408],["Żmigród",0,51.4667,16.9056,65],
		["Żnin",1,52.8496,17.7199,139],["Żory",11,50.0452,18.7006,660],["Żyrardów",6,52.0488,20.446,416],["Żywiec",11,49.6853,19.1924,641]
	];
	const escortMultiLocationResultCounts=new Map();
	const escortMultiLocationPageCounts=new Map();
	const escortMultiLocationUiRefreshers=new Set();
	const WATCH_INSTANCE_ID=`${Date.now()}-${Math.random().toString(36).slice(2)}`;
	const DEFAULT_WATCH_SETTINGS={
		enabled:true, notesEnabled:true, intervalMinutes:30, showOnlineCount:true, showAlertBar:true, nonAlertChangeNewness:'until-open'
	};
	const WATCH_INTERVAL_OPTIONS=[5,15,30,60,180,360];
	const WATCH_INTERVAL_SELECT_OPTIONS=WATCH_INTERVAL_OPTIONS.map(minutes => [ minutes,
		minutes < 60 ? `${minutes} min`:`${minutes / 60} h` ]);
	const WATCH_NON_ALERT_NEWNESS_OPTIONS=[ ['until-open','Do czasu otwarcia tabeli ulubionych'], ['60','1 h'], ['180','3 h'],
		['360','6 h'], ['720','12 h'], ['1440','1 dzień'], ['4320','3 dni'], ['10080','7 dni'], ['20160','14 dni'] ];
	const WATCH_NON_ALERT_NEWNESS_VALUES=new Set( WATCH_NON_ALERT_NEWNESS_OPTIONS.map(([value]) => value) );
	function normalizeWatchNonAlertChangeNewness( value, fallback=DEFAULT_WATCH_SETTINGS.nonAlertChangeNewness ) {
		const normalized=String(value ?? '').trim();
		return WATCH_NON_ALERT_NEWNESS_VALUES.has(normalized)
			? normalized
			:fallback;
	}
	const WATCH_PROFILE_RESOLUTION_OPTIONS=[ ['address-escorti','adres anonsu → Escorti.pl'],
		['phone-escorti','nr telefonu → Escorti.pl'], ['phone-escort-search','nr telefonu → wyszukiwarka Escort.club'],
		['ask','Zawsze pytaj'] ];
	const FAVORITE_SEARCH_MAX_ADS=200;
	const ESCORT_DATA_FIELD_CATALOG_STORAGE_KEY='vm_escort_data_field_catalog';
	const ESCORT_DATA_FIELD_CATALOG_MAX=240;
	const FAVORITES_MAX_DATA_COLUMNS=6;
	const SEARCH_RESULT_MAX_EXTRA_FIELDS=6;
	const DEFAULT_FAVORITE_CATEGORIES=[ 'Do odbycia', 'Odbyte', 'Wtopa', 'Saper', 'Polecane' ];
	const DEFAULT_FAVORITE_CATEGORY_COLORS={
		'Do odbycia':'#2e7d32', 'Odbyte':'#a39e9e', 'Wtopa':'#c62424', 'Saper':'#f7c455', 'Polecane':'#287d28'
	};
	const DEFAULT_HISTORY_CLEANUP_KEYWORDS=[ 'escort.club', 'escorti.pl', 'garsoniera.com.pl', 'dolores.sex', 'tineye.com',
		'picdetective.com', 'google.com/search?vsrid' ];
	const HIVE_IMAGE_DETECTOR={id:'hive-ai',name:'Hive AI',enabled:true,urlPrefix:'https://hivedetect.ai/#vm_image_url='};
	const DEFAULT_IMAGE_SEARCH_ENGINES=[
		{id:'google-lens',name:'Google',enabled:true,urlPrefix:'https://lens.google.com/uploadbyurl?url='},
		{id:'picdetective',name:'PicDet',enabled:true,urlPrefix:'https://picdetective.com/?vm_image_url='},
		{id:'yandex',name:'Yandex',enabled:true,urlPrefix:'https://yandex.ru/images/search?rpt=imageview&url='},
		HIVE_IMAGE_DETECTOR ];
	function normalizeImageSearchUrlPrefix(value) {
		const prefix=String(value || '').trim();
		if (!prefix) return '';
		try {
			const url=new URL(prefix);
			return /^https?:$/.test(url.protocol) ? prefix:'';
		} catch {return '';}
	}
	function normalizeExternalServices(value,fallback,urlKey,normalizeUrl,namePrefix,idPrefix='custom-') {
		const source=Array.isArray(value) ? value:fallback;
		return source.slice(0,20).map((entry,index) => {
			const item=entry && typeof entry === 'object' ? entry:{};
			const url=normalizeUrl(item[urlKey]);
			return url ? {
				id:String(item.id || `${idPrefix}${index + 1}`).slice(0,80),
				name:normalizeEscortAdText(item.name || `${namePrefix} ${index + 1}`).slice(0,40), enabled:item.enabled !== false,
				[urlKey]:url
			}:null;
		}).filter(Boolean);
	}
	function normalizeImageSearchEngines(value) {
		return normalizeExternalServices(value,DEFAULT_IMAGE_SEARCH_ENGINES,'urlPrefix',normalizeImageSearchUrlPrefix,'Wyszukiwarka');
	}
	const DEFAULT_MENU_PAGES=[ {
			id:'garso-forum', name:'Garso', enabled:true, url:'https://www.garsoniera.com.pl/forum/forum/52-krakow-bohema-i/'
		}, {
			id:'escort-search', name:'Escort', enabled:true, url:'https://pl.escort.club/anonse/towarzyskie/warszawa/'
		} ];
	function normalizeMenuPageUrl(value) {
		const raw=String(value || '').trim();
		if (!raw) return '';
		try {
			const url=new URL(raw);
			return /^https?:$/.test(url.protocol)
				? url.href
				:'';
		} catch {
			return '';
		}
	}
	function normalizeMenuPages(value) {
		return normalizeExternalServices(value,[],'url',normalizeMenuPageUrl,'Strona','custom-menu-');
	}
	const DEFAULT_PHONE_REPUTATION_SERVICES=[
		{id:'ktoto',name:'KtoTo.info',enabled:true,urlTemplate:'https://www.ktoto.info/numer/{phone}/'},
		{id:'numerytelefonu',name:'NumeryTelefonu.com',enabled:true,urlTemplate:'https://numerytelefonu.com/nrtel/{phone}'},
		{id:'sprawdzenienumeru',name:'SprawdzenieNumeru.pl',enabled:true,urlTemplate:'https://sprawdzenienumeru.pl/numer/{phone48}'},
		{id:'google',name:'Google',enabled:true,urlTemplate:'https://www.google.com/search?q={query}'} ];
	const PHONE_REPUTATION_AUTO_SOURCE_IDS=new Set([ 'ktoto','numerytelefonu','sprawdzenienumeru' ]);
	function normalizePhoneReputationUrlTemplate(value) {
		const template=String(value || '').trim();
		if (!template) return '';
		try {
			const probe=template .replaceAll('{phone}','500000000') .replaceAll('{phone48}','48500000000')
				.replaceAll('{query}','500000000');
			const url=new URL(probe);
			return /^https?:$/.test(url.protocol) ? template:'';
		} catch {return '';}
	}
	function normalizePhoneReputationServices(value) {
		return normalizeExternalServices(value,DEFAULT_PHONE_REPUTATION_SERVICES,'urlTemplate',normalizePhoneReputationUrlTemplate,'Serwis');
	}
	const DEFAULT_TAG_HIGHLIGHT_GROUPS=[ {
			id:'group-3', name:'negatywne', color:'#000000', tags:[ 'Jedno zbliżenie w godzinie', 'Nieogolone' ]
		}, {
			id:'group-4', name:'wyjątkowe', color:'#28b428', tags:[ 'Nagrywanie video', 'Robienie zdjęć' ]
		}, {
			id:'group-1', name:'ważne', color:'#ff2878', tags:[ 'Dwa zbliżenia w godzinie', 'Więcej zbliżeń w godzinie',
				'Namiętne Pocałunki', 'Francuz bez zabezpieczenia' ]
		}, {
			id:'group-2', name:'mniej ważne', color:'#ffa0c8', tags:[ 'Klimat GFE', 'Finał na twarz', 'Finał w buzi', 'Minetka',
				'Pocałunki' ]
		}, {
			id:'other', name:'pozostałe', color:'#ffdce6', tags:[], locked:true
		} ];
	function normalizeTagHighlightGroups(value) {
		const source=Array.isArray(value) ? value:DEFAULT_TAG_HIGHLIGHT_GROUPS;
		const seenTags=new Set();
		const editable=source .filter(item => item && typeof item === 'object' && item.id !== 'other' && item.locked !== true)
			.slice(0,12) .map((item,index) => {
				const id=String(item.id || `group-${index + 1}`).slice(0,80);
				const tags=[];
				for (const rawTag of Array.isArray(item.tags) ? item.tags:[]) {
					const tag=normalizeEscortAdText(rawTag).slice(0,80);
					const key=normalizeEscortChangeComparisonValue(tag);
					if (!tag || !key || seenTags.has(key)) continue;
					seenTags.add(key);
					tags.push(tag);
				}
				const defaultGroup= DEFAULT_TAG_HIGHLIGHT_GROUPS.find( group => group.id === id ) ||
					DEFAULT_TAG_HIGHLIGHT_GROUPS.find( group => group.locked !== true );
				return {
					id, name:(normalizeEscortAdText(item.name) || defaultGroup.name).slice(0,40),
					color:normalizeFavoriteCategoryColor(item.color) || defaultGroup.color, tags
				};
			});
		if (!editable.length) editable.push({...DEFAULT_TAG_HIGHLIGHT_GROUPS[0],tags:[]});
		const other=source.find(item => item?.id === 'other' || item?.locked === true)
			|| DEFAULT_TAG_HIGHLIGHT_GROUPS.at(-1);
		return [...editable,{
			id:'other', name:'pozostałe', color:normalizeFavoriteCategoryColor(other?.color)
				|| DEFAULT_TAG_HIGHLIGHT_GROUPS.at(-1).color, tags:[], locked:true
		}];
	}
	function getKnownEscortTagLabels(extraGroups=[]) {
		const labels=new Map();
		const add=value => {
			const label=normalizeEscortAdText(value).slice(0,80);
			const key=normalizeEscortChangeComparisonValue(label);
			if (label && key && !labels.has(key)) labels.set(key,label);
		};
		for (const group of extraGroups) for (const tag of group?.tags || []) add(tag);
		try {document.querySelectorAll('.sub-info-box .tags-box .tag').forEach(node => add(node.textContent));} catch {}
		try {
			for (const key of getEscortAdDataCacheKeys()) {
				const data=readPersistentCacheValue(key,null);
				for (const tag of Array.isArray(data?.tags) ? data.tags:[]) add(tag);
			}
		} catch {}
		return [...labels.values()].sort((a,b) => a.localeCompare(b,'pl-PL'));
	}
	const DEFAULT_SETTINGS={
		quickAdPreview:true, autoGarsoCheck:true, garsoCheckEscortLinks:true, garsoShowSearchIcons:true,
		garsoShowTopicSummary:true, garsoShowTopicTools:true, garsoShowTopicHelperData:true, autoGarsoAnalysisMode:'content',
		mergeEscortAds:true, escortAggregationMode:'address-escorti', compactSummaryComparisonMode:'phone-escorti',
		showRelatedAdsTileLink:true, relatedAdsTileLinkMode:'phone-escorti', openAllActiveRelatedAdsFromTile:false,
		maxRelatedAdsTabs:10, showEscortiTileData:true, showPricesInSearchResults:true, searchResultPriceDuration:'60',
		searchResultExtraFields:['ad:declaredBmi'], hideRecommendedSearchSection:true, hideRecommendedAdSection:true,
		hideSingleAdContactButtons:true, hideSingleAdActions:true, hideFeaturedSearchSection:true,
		hidePopularCitiesSection:true, showImageSearchButtons:true,
		imageSearchEngines:DEFAULT_IMAGE_SEARCH_ENGINES.map(item => ({...item})), imageSearchEnginesRevision:2,
		showPhoneReputationLinks:true, autoPhoneReputationSearch:true,
		phoneReputationServices:DEFAULT_PHONE_REPUTATION_SERVICES.map(item => ({...item})), showTopPhoneSearch:true,
		showBottomLeftMenu:true, showMenuPagesAsFavicons:true, showMenuLinksInline:true,
		menuPages:DEFAULT_MENU_PAGES.map(item => ({...item})), topPhoneSearchEnterTarget:'escort-club',
		topPhoneSearchOpenMode:'new-tab', multiLocationEnabled:true, saveSearchLocationsEnabled:true, searchLocations:[],
		savedLocationGroups:[], showSummarySidePanel:true, showCompactSummaryAboveDescription:true, alarmingTagCountLimit:69,
		showTagHighlights:true,
		tagHighlightGroups:DEFAULT_TAG_HIGHLIGHT_GROUPS.map(item => ({...item,tags:[...item.tags]})),
		showPhoneCopyButton:true, phoneClipboardFormat:'local-hyphens', usePersistentCache:true, showLiveDiagnostics:false,
		escortLimitPerMinute:120, escortMaxConcurrent:4, escortFastStart:true,
		watchEnabled:true, notesEnabled:true, watchIntervalMinutes:30, showWatchOnlineCount:true,
		showWatchAlertBar:true, watchSystemNotifications:true, watchNotificationAnonymous:true, watchNotificationText:'Nowy alert',
		nonAlertChangeNewness:'until-open', watchProfileResolutionMode:'address-escorti', favoritesDefaultView:'table', favoritesDataColumns:['ad:prices:60'],
		favoriteCategories:[...DEFAULT_FAVORITE_CATEGORIES], favoriteCategoryColors:{...DEFAULT_FAVORITE_CATEGORY_COLORS},
		historyCleanupKeywords:[...DEFAULT_HISTORY_CLEANUP_KEYWORDS]
	};
	const ESCORT_CACHE_REFRESH_HOURS=7 * 24;
	const GARSO_CACHE_REFRESH_HOURS=14 * 24;
	const DEFAULT_LIST_PAGES_TO_SHOW=1;
	const ESCORT_LIST_PAGES_RELOAD_KEY='vm_escort_list_pages_reload_once';
	const LIST_PAGE_OPTIONS=[ [1,'1 stronę'], [2,'2 strony'], [3,'3 strony'], [4,'4 strony'], [5,'5 stron'],
		[10,'10 stron'], ['all','Wszystkie'] ];
	const ESCORT_PRICE_DURATION_OPTIONS=[ ['15','15 min'], ['30','0,5 h'], ['60','1 h'], ['120','2 h'],
		['night','całą noc'] ];
	const ESCORT_PRICE_DISPLAY_OPTIONS=[ ['hide','ukryj'], ...ESCORT_PRICE_DURATION_OPTIONS ];
	const PHONE_CLIPBOARD_FORMAT_OPTIONS=[ ['international-spaces','+48 123 456 789'], ['local-hyphens','123-456-789'] ];
	const TOP_PHONE_SEARCH_TARGET_OPTIONS=[ ['garso','Garsoniera'], ['escort-club','Escort.club'],
		['escorti','Escorti.pl'] ];
	const TOP_PHONE_SEARCH_OPEN_MODE_OPTIONS=[ ['new-tab','w nowej karcie'], ['same-tab','na tej samej karcie'] ];
	const ESCORT_AGGREGATION_MODE_OPTIONS=[ ['address-escorti','Według adresu anonsu → Escorti.pl'],
		['phone-escorti','Według numeru telefonu → Escorti.pl'],
		['phone-escort-search','Według numeru telefonu → wyszukiwarka Escort.club'],
		['exact-phone','Tylko identyczny numer telefonu → załadowana lista i cache'] ];
	const ESCORT_RELATED_ADS_TILE_LINK_MODE_OPTIONS= ESCORT_AGGREGATION_MODE_OPTIONS.slice(0,3);
	const ESCORT_AGGREGATION_MODE_DESCRIPTIONS={
		'address-escorti':
			'Najlepiej znajduje powiązane anonse, ale czasami może błędnie przypisać do jednej grupy niezwiązane anonse.',
		'phone-escorti':
			'Wyszukuje w Escorti.pl według numeru telefonu, dlatego powoduje mniej błędnych przypisań niż wyszukiwanie według adresu.',
		'phone-escort-search':
			'Dla numeru telefonu z kafelka przeszukuje Escort.club, a następnie uwzględnia wyniki wyszukiwania, załadowane kafelki oraz anonse zapisane w aktualnym cache.',
		'exact-phone':
			'Łączy kafelki z aktualnie załadowanej listy, które mają identyczny numer telefonu i uwzględnia również anonse zapisane w aktualnym cache. Zapewnia wysoką pewność powiązania ale nie pokazuje liczby wszystkich ogłoszeń.'
	};
	const ESCORT_COMPACT_SUMMARY_MODE_OPTIONS=[...ESCORT_AGGREGATION_MODE_OPTIONS.slice(0,2),
		['exact-phone',ESCORT_AGGREGATION_MODE_OPTIONS[2][1]]];
	const ESCORT_COMPACT_SUMMARY_MODE_DESCRIPTIONS={
		'address-escorti':ESCORT_AGGREGATION_MODE_DESCRIPTIONS['address-escorti'],
		'phone-escorti':ESCORT_AGGREGATION_MODE_DESCRIPTIONS['phone-escorti'],
		'exact-phone':
			'Wyszukuje na Escort.club aktualnie dostępne anonse znalezione po numerze telefonu i porównuje ich dane. Cache ogranicza ponowne pobieranie szczegółów, ale nie wyznacza listy porównywanych anonsów.'
	};
	const ESCORT_SAVED_FILTERS_STORAGE_KEY='vm_escort_saved_search_filters';
	const ESCORT_SAVED_FILTER_SELECT_MIN_WIDTH=145;
	const ESCORT_SAVED_FILTER_SELECT_MAX_WIDTH=280;
	const ESCORT_CUSTOM_FILTERS_STORAGE_KEY='vm_escort_custom_result_filters';
	const ESCORT_CUSTOM_FILTER_AGE_UNITS=['days','weeks','months','years'];
	const ESCORT_CUSTOM_FILTER_AGE_UNIT_OPTIONS=[ ['days','dni'], ['weeks','tygodnie'], ['months','miesiące'],
		['years','lata'] ];
	const ESCORT_CUSTOM_FILTER_WEEKDAYS=[ ['monday','Poniedziałek'], ['tuesday','Wtorek'], ['wednesday','Środa'],
		['thursday','Czwartek'], ['friday','Piątek'], ['saturday','Sobota'], ['sunday','Niedziela'] ];
	const DEFAULT_ESCORT_CUSTOM_FILTERS={
		expanded:false, hideWithoutOpinions:false, hideProfileAdsOver:false, profileAdsLimit:10,
		hideLocationChangesOver:false, locationChangesLimit:1, hideActiveAdsOver:false, activeAdsLimit:1,
		hideAgencyPhone:false, hideCity:false, hideCityQuery:'', hideVisited:false, hideUnvisited:false,
		hideVisitedOlderThan:false, hideVisitedOlderValue:30, hideVisitedOlderUnit:'days', hideVisitedRecentThan:false,
		hideVisitedRecentValue:7, hideVisitedRecentUnit:'days', hideHasNote:false, hideHasCategory:false, hideCategory:'',
		hideTagCountOver:false, tagCountLimit:null, hideYoungerThan:false, youngerValue:2, youngerUnit:'days',
		hideOlderThan:false, olderValue:2, olderUnit:'days', hideAdYoungerThan:false, adYoungerValue:2, adYoungerUnit:'days',
		hideAdOlderThan:false, adOlderValue:2, adOlderUnit:'days', hideAlwaysAvailable:false, hideUnavailableAt:false,
		hideUnavailableDay:'monday', hideUnavailableTime:'12:00', hideAvailableAt:false, hideAvailableDay:'monday',
		hideAvailableTime:'12:00', hideDescriptionMatch:false, hideDescriptionQuery:'', showOnlyUnavailableAt:false,
		showOnlyUnavailableDay:'monday', showOnlyUnavailableTime:'12:00', showOnlyAvailableAt:false,
		showOnlyAvailableDay:'monday', showOnlyAvailableTime:'12:00', showOnlyWithoutOpinions:false,
		showOnlySingleProfileAd:false, showOnlyAgencyPhone:false, showOnlyBmi:false, showOnlyBmiMin:0, showOnlyBmiMax:100,
		showOnlyHasNote:false, showOnlyHasCategory:false, showOnlyCategory:'', showOnlyVisitedOlderThan:false,
		showOnlyVisitedOlderValue:30, showOnlyVisitedOlderUnit:'days', showOnlyVisitedRecentThan:false,
		showOnlyVisitedRecentValue:7, showOnlyVisitedRecentUnit:'days', showOnlyDescriptionMatch:false,
		showOnlyDescriptionQuery:''
	};
	function normalizeCustomFilterInteger(value,fallback,min=0,max=100000) {
		const number=Math.round(Number(value));
		return Number.isFinite(number)
			? Math.min(max,Math.max(min,number))
			:fallback;
	}
	function normalizeCustomFilterNumber( value, fallback, min=0, max=100000 ) {
		const number=Number( String(value ?? '').replace(',','.') );
		return Number.isFinite(number)
			? Math.min(max,Math.max(min,number))
			:fallback;
	}
	function normalizeCustomFilterAgeUnit(value) {
		return ESCORT_CUSTOM_FILTER_AGE_UNITS.includes(value) ? value:'days';
	}
	function normalizeCustomFilterWeekday(value) {
		return ESCORT_CUSTOM_FILTER_WEEKDAYS.some(([allowed]) => allowed === value)
			? value
			:'monday';
	}
	function normalizeCustomFilterTime(value,fallback='12:00') {
		const match=String(value || '').match(/^(\d{1,2}):(\d{2})$/);
		if (!match) return fallback;
		const hours=Number(match[1]);
		const minutes=Number(match[2]);
		if (hours > 23 || minutes > 59) return fallback;
		return `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`;
	}
	function normalizeCustomFilterQuery(value) {
		return String(value || '').replace(/\s+/g,' ').trim().slice(0,200);
	}
	function normalizeCustomFilterCategory(value) {
		return normalizeEscortAdText(value).slice(0,60);
	}
	function appendHelperStyle(id,css) {
		const style=makeElement('style');
		style.id=id;
		style.textContent=css;
		document.head.appendChild(style);
	}
	const HELPER_STYLE_API_VERSION='2';
	const HELPER_STYLE_API_CLASS='vm-helper-ui';
	// API motywów pozostaje w wersji 2. Klasy komponentów są dodawane jawnie.
	function ensureHelperStyleApi() {
		const root=document.documentElement;
		if (root) {
			root.dataset.vmEscortHelperStyleApi=HELPER_STYLE_API_VERSION;
		}
		const styleId='vm-escort-helper-style-api';
		if (document.getElementById(styleId)) return;
		const style=makeElement('style');
		style.id=styleId;
		style.textContent=`.vm-helper-ui.vm-ad-summary-box { box-sizing:border-box; padding:8px 9px; border:1px solid var(--vm-ui-accent,#f54da3); border-radius:7px; background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 7%,transparent); color:var(--vm-ui-panel-text,var(--vm-ui-text,#222)); font:400 12px/1.3 var(--vm-ui-font-family,Lato,Arial,sans-serif); }
.vm-helper-ui[data-vm-style-api="2"] { color-scheme:var(--vm-ui-color-scheme,normal); font-family:var(--vm-ui-font-family,Arial,sans-serif); }
.vm-helper-ui input:not([type=checkbox]):not([type=radio]):not([type=color]), .vm-helper-ui textarea, .vm-helper-ui select { background-color:var(--vm-ui-input-bg,#fff); color:var(--vm-ui-input-text,var(--vm-ui-text,#222)); border-color:var(--vm-ui-input-border,#bbb); }
.vm-helper-ui input::placeholder, .vm-helper-ui textarea::placeholder { color:var(--vm-ui-text-faint,#999); }
.vm-helper-ui input[type=checkbox], .vm-helper-ui input[type=radio] { accent-color:var(--vm-ui-accent,#f54da3); }
.vm-helper-ui.vm-ui-overlay { position:fixed; inset:0; z-index:2147483647; display:flex; align-items:center; justify-content:center; box-sizing:border-box; padding:16px; background:var(--vm-ui-overlay,rgba(0,0,0,.55)); }
.vm-ui-dialog { width:min(540px,calc(100vw - 32px)); max-height:calc(100vh - 32px); min-width:0; box-sizing:border-box; overflow:auto; padding:var(--vm-ui-dialog-padding,18px); border:0; border-top:4px solid var(--vm-ui-accent,#f54da3); border-radius:var(--vm-ui-dialog-radius,10px); background:var(--vm-ui-bg,#fff); color:var(--vm-ui-text,#222); box-shadow:var(--vm-ui-shadow,0 10px 35px rgba(0,0,0,.35)); font:var(--vm-ui-font-size,13px)/1.4 var(--vm-ui-font-family,Arial,sans-serif); }
.vm-ui-title { margin:0 0 12px; color:inherit; font-size:19px; font-weight:700; line-height:1.25; }
.vm-ui-header, .vm-ui-row, .vm-ui-actions { display:flex; align-items:center; flex-wrap:wrap; gap:var(--vm-ui-gap,8px); }
.vm-ui-header { justify-content:space-between; flex-wrap:nowrap; margin-bottom:12px; }
.vm-ui-header .vm-ui-title { margin:0; }
.vm-ui-actions { justify-content:flex-end; margin-top:12px; padding-top:12px; border-top:1px solid var(--vm-ui-border,#ddd); }
.vm-ui-stack { display:grid; gap:var(--vm-ui-gap,8px); }
.vm-ui-panel { min-width:0; box-sizing:border-box; padding:12px; border:1px solid var(--vm-ui-border,#ddd); border-radius:var(--vm-ui-radius,6px); background:var(--vm-ui-bg-alt,#fafafa); color:var(--vm-ui-text,#222); }
.vm-ui-button, .vm-ui-field { box-sizing:border-box; min-height:var(--vm-ui-control-height,32px); padding:5px 9px; border:1px solid var(--vm-ui-input-border,#bbb); border-radius:var(--vm-ui-radius,6px); font:var(--vm-ui-font-size,13px)/1.35 var(--vm-ui-font-family,Arial,sans-serif); }
.vm-ui-button { display:inline-flex; align-items:center; justify-content:center; gap:4px; background:var(--vm-ui-bg,#fff); color:var(--vm-ui-text,#222); cursor:pointer; font-weight:700; text-align:center; }
.vm-helper-ui .vm-ui-button.vm-favorites-view-button { flex:none; gap:5px; height:30px; min-height:30px; margin:0; padding:4px 8px; border-radius:6px; font-size:11px; line-height:1.3; white-space:nowrap; border-color:var(--vm-ui-accent,#ce3986); background:color-mix(in srgb,var(--vm-ui-accent,#ce3986) 24%,var(--vm-ui-bg,#fff)); color:var(--vm-ui-text,#292c35); }
.vm-helper-ui .vm-ui-button.vm-favorites-view-button:is(:hover,:focus-visible) { background:color-mix(in srgb,var(--vm-ui-accent,#ce3986) 36%,var(--vm-ui-bg,#fff)); }
.vm-helper-ui .vm-favorites-view-button > svg { display:block; flex:none; width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.vm-ui-button.-accent { border-color:var(--vm-ui-accent,#f54da3); color:var(--vm-ui-accent,#f54da3); }
.vm-ui-button.-primary { border-color:var(--vm-ui-accent,#f54da3); background:var(--vm-ui-accent,#f54da3); color:var(--vm-ui-accent-contrast,#fff); }
.vm-ui-button.-danger { border-color:var(--vm-ui-danger,#c62828); color:var(--vm-ui-danger,#c62828); }
.vm-ui-button:hover:enabled { box-shadow:inset 0 0 0 1px currentColor; }
.vm-ui-button:disabled, .vm-ui-field:disabled { opacity:.5; cursor:not-allowed; }
.vm-ui-button:focus-visible, .vm-ui-field:focus-visible { outline:2px solid var(--vm-ui-accent,#f54da3); outline-offset:2px; box-shadow:var(--vm-ui-focus-ring,none); }
.vm-ui-field { min-width:0; background:var(--vm-ui-input-bg,#fff); color:var(--vm-ui-input-text,var(--vm-ui-text,#222)); }
input.vm-ui-field:not(.vm-ui-color) { height:24px; min-height:24px; padding:2px 6px; }
select.vm-ui-field { height:24px; min-height:24px; padding:2px 24px 2px 6px; }
.vm-ui-textarea { display:block; width:100%; resize:vertical; }
.vm-ui-compact { min-height:var(--vm-ui-compact-height,24px); padding:2px 6px; font-size:var(--vm-ui-small-font-size,11px); }
.vm-ui-icon { flex:0 0 auto; width:var(--vm-ui-control-height,32px); height:var(--vm-ui-control-height,32px); padding:0; font-size:18px; line-height:1; }
.vm-ui-icon.vm-ui-compact { width:var(--vm-ui-compact-height,24px); height:var(--vm-ui-compact-height,24px); }
.vm-ui-icon.vm-ui-table-icon { width:18px; height:18px; min-height:18px; padding:0; font-size:11px; }
.vm-helper-ui .vm-ui-color { appearance:none; -webkit-appearance:none; box-sizing:border-box; width:34px !important; min-width:34px !important; max-width:34px !important; height:24px !important; min-height:24px !important; max-height:24px !important; align-self:center !important; padding:2px !important; border:1px solid var(--vm-ui-input-border,#c9c9c9); border-radius:4px; background:var(--vm-ui-bg,#fff); box-shadow:none; cursor:pointer; }
.vm-ui-color::-webkit-color-swatch-wrapper { padding:0; }
.vm-ui-color::-webkit-color-swatch { border:0; border-radius:2px; }
.vm-ui-category-row { display:grid; grid-template-columns:34px minmax(0,1fr) 28px; gap:var(--vm-ui-gap,8px); align-items:center; }
.vm-ui-category-row .vm-ui-field:not(.vm-ui-color) { height:24px; min-height:24px; padding:2px 6px; }
.vm-ui-category-row .vm-ui-button.vm-ui-icon { width:28px; height:28px; min-height:28px; }
.vm-ui-label { display:block; margin:0 0 6px; font-size:var(--vm-ui-font-size,13px); font-weight:700; }
.vm-ui-help { color:var(--vm-ui-text-muted,#666); font-size:var(--vm-ui-small-font-size,11px); line-height:1.4; }
.vm-ui-error { color:var(--vm-ui-danger,#c62828); font-weight:700; }
.vm-ui-empty { padding:14px; color:var(--vm-ui-text-muted,#666); text-align:center; }
.vm-ui-choice { display:flex; align-items:flex-start; gap:var(--vm-ui-gap,8px); margin:8px 0; cursor:pointer; }
.vm-ui-choice > input { flex:0 0 auto; width:16px; height:16px; margin:2px 0 0; }
.vm-ui-timeline-dialog { height:auto; padding:var(--vm-ui-dialog-padding,18px); overflow:auto; }
#vm-escort-list-pages-progress, .vm-custom-filter-progress { display:flex; align-items:center; justify-content:center; gap:10px; box-sizing:border-box; width:100%; margin:2px 0 6px; padding:8px 12px; border:1px solid var(--vm-ui-accent,#f54da3); border-radius:7px; background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 12%,var(--vm-ui-bg,#fff)); color:var(--vm-ui-text,#211d22) !important; font:700 12px/16px var(--vm-ui-font-family,Lato,Arial,sans-serif); white-space:nowrap; user-select:none; }
#vm-escort-list-pages-progress[hidden], .vm-custom-filter-progress[hidden] { display:none !important; }
#vm-escort-list-pages-progress > .vm-list-pages-progress-text, .vm-custom-filter-progress > .vm-custom-filter-progress-text { color:inherit !important; opacity:1; }
#vm-escort-list-pages-progress .vm-list-pages-cancel, .vm-custom-filter-progress .vm-custom-filter-cancel { flex:0 0 auto; min-height:24px; padding:3px 10px; border:1px solid var(--vm-ui-accent,#f54da3); border-radius:6px; background:var(--vm-ui-bg,#fff); color:var(--vm-ui-text,#211d22) !important; font:800 11px var(--vm-ui-font-family,Lato,Arial,sans-serif); cursor:pointer; }
#vm-escort-list-pages-progress .vm-list-pages-cancel:hover, .vm-custom-filter-progress .vm-custom-filter-cancel:hover { background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 8%,var(--vm-ui-bg,#fff)); }
`;
		document.head?.appendChild(style);
		try {
			document.dispatchEvent(new CustomEvent( 'vm-escort-helper-style-ready',
				{detail:{version:HELPER_STYLE_API_VERSION}} ));
		} catch {}
	}
	const HELPER_THEME_CUSTOM_PROPERTIES=[ '--vm-ui-accent', '--vm-ui-accent-contrast', '--vm-ui-bg', '--vm-ui-bg-alt',
		'--vm-ui-bg-hover', '--vm-ui-text', '--vm-ui-text-muted', '--vm-ui-text-faint', '--vm-ui-border',
		'--vm-ui-border-strong', '--vm-ui-input-bg', '--vm-ui-input-text', '--vm-ui-input-border', '--vm-ui-overlay',
		'--vm-ui-shadow', '--vm-ui-panel-bg', '--vm-ui-panel-bg-alt', '--vm-ui-panel-text', '--vm-ui-panel-muted',
		'--vm-ui-panel-border', '--vm-ui-success', '--vm-ui-warning', '--vm-ui-danger', '--vm-ui-color-scheme',
		'--vm-ui-font-family', '--vm-ui-control-height','--vm-ui-compact-height','--vm-ui-radius',
		'--vm-ui-dialog-radius','--vm-ui-dialog-padding','--vm-ui-gap',
		'--vm-ui-font-size','--vm-ui-small-font-size','--vm-ui-focus-ring' ];
	function copyHelperThemeVariablesIntoDocument( targetDocument, sourceDocument=document ) {
		if ( !targetDocument?.documentElement || !sourceDocument?.documentElement ) {
			return;
		}
		const sourceView= sourceDocument.defaultView || window;
		let computed=null;
		try {
			computed= sourceView.getComputedStyle( sourceDocument.documentElement );
		} catch {
			return;
		}
		const targetRoot= targetDocument.documentElement;
		for (const property of
			HELPER_THEME_CUSTOM_PROPERTIES) {
			const value= computed .getPropertyValue(property) .trim();
			if (value) {
				targetRoot.style.setProperty( property, value );
			}
		}
		targetRoot.dataset.vmEscortHelperStyleApi= HELPER_STYLE_API_VERSION;
	}
	function prepareHelperStyleApi(element) {
		if (!element) return element;
		ensureHelperStyleApi();
		element.classList.add(HELPER_STYLE_API_CLASS);
		element.dataset.vmStyleApi=HELPER_STYLE_API_VERSION;
		return element;
	}
	function getEscortPagePinkColor() {
		const fallback='#f54da3';
		try {
			if (typeof getComputedStyle === 'function') {
				const themed=getComputedStyle( document.documentElement ).getPropertyValue('--vm-ui-accent').trim();
				if (themed) return themed;
			}
			const sample=[...document.querySelectorAll('.btn.btn-pink')]
				.find(element => !element.closest('[id^="vm-escort-"]'));
			if (!sample || typeof getComputedStyle !== 'function') return fallback;
			const computed=getComputedStyle(sample);
			const candidates=[computed.backgroundColor,computed.borderTopColor];
			return candidates.find(value => value && value !== 'transparent' && value !== 'rgba(0, 0, 0, 0)' ) || fallback;
		} catch {
			return fallback;
		}
	}
	ensureHelperStyleApi();
	const ESCORT_CUSTOM_FILTER_FIELDS=Object.fromEntries(
		Object.entries(DEFAULT_ESCORT_CUSTOM_FILTERS).map(([key,fallback]) => {
			if (typeof fallback === 'boolean') return [key,[Boolean]];
			if (key === 'tagCountLimit') return [key,[normalizeCustomFilterInteger,69,0,10000]];
			if (typeof fallback === 'number') return [key,key.startsWith('showOnlyBmi')
				? [normalizeCustomFilterNumber,fallback,0,100]
				:[normalizeCustomFilterInteger,fallback,key.endsWith('Value') ? 1:0]];
			const normalize=key.endsWith('Unit') ? normalizeCustomFilterAgeUnit
				:key.endsWith('Day') ? normalizeCustomFilterWeekday
				:key.endsWith('Time') ? normalizeCustomFilterTime
				:key.endsWith('Category') ? normalizeCustomFilterCategory
				:normalizeCustomFilterQuery;
			return [key,[normalize]];
		}) );
	function normalizeEscortCustomFilterField(key,value,tagFallback=69) {
		const [normalize,...args]=ESCORT_CUSTOM_FILTER_FIELDS[key];
		if (key === 'tagCountLimit') args[0]=tagFallback;
		return normalize(value,...args);
	}
	function normalizeEscortCustomFilters(value={}) {
		const filters={...DEFAULT_ESCORT_CUSTOM_FILTERS,...value};
		const inheritedTagCountLimit=Object.prototype.hasOwnProperty.call(value || {},'tagCountLimit') && value.tagCountLimit != null
			? value.tagCountLimit
			:getSettings().alarmingTagCountLimit;
		return Object.fromEntries(Object.keys(ESCORT_CUSTOM_FILTER_FIELDS).map(key => [
			key,normalizeEscortCustomFilterField(key,key === 'tagCountLimit' ? inheritedTagCountLimit:filters[key]) ]));
	}
	function getEscortCustomFilters() {
		const session=getFavoriteSearchSession();
		if (session) return normalizeEscortCustomFilters(session.search.filters);
		try {
			const saved=GM_getValue(ESCORT_CUSTOM_FILTERS_STORAGE_KEY,null);
			const source=saved && typeof saved === 'object' ? saved:{};
			return normalizeEscortCustomFilters(source);
		} catch {
			return normalizeEscortCustomFilters();
		}
	}
	function saveEscortCustomFilters(filters) {
		const normalized=normalizeEscortCustomFilters(filters);
		const session=getFavoriteSearchSession();
		if (session) {
			session.search.filters=normalized; persistFavoriteSearchSession(); return normalized;
		}
		try {
			writeHelperStorageValue(ESCORT_CUSTOM_FILTERS_STORAGE_KEY,normalized);
		} catch (error) {
			log('Nie udało się zapisać filtrów niestandardowych',error);
		}
		return normalized;
	}
	function normalizeListPagesToShow(value) {
		if (value === 'all') return 'all';
		const count=Number(value);
		return LIST_PAGE_OPTIONS.some(([allowed]) => allowed === count)
			? count
			:DEFAULT_LIST_PAGES_TO_SHOW;
	}
	function consumeOneTimeListPagesToShow() {
		// Jednorazowy wybór zużywa tylko główna karta, nigdy ramka.
		if (window.top !== window.self) return DEFAULT_LIST_PAGES_TO_SHOW;
		try {
			const saved=sessionStorage.getItem(ESCORT_LIST_PAGES_RELOAD_KEY);
			sessionStorage.removeItem(ESCORT_LIST_PAGES_RELOAD_KEY);
			return saved == null
				? DEFAULT_LIST_PAGES_TO_SHOW
				:normalizeListPagesToShow(saved);
		} catch {
			return DEFAULT_LIST_PAGES_TO_SHOW;
		}
	}
	function requestOneTimeListPagesReload(value) {
		try {
			sessionStorage.setItem( ESCORT_LIST_PAGES_RELOAD_KEY, String(normalizeListPagesToShow(value)) );
			return true;
		} catch {
			return false;
		}
	}
	function normalizeOptionValue(value,options,fallback,convert=String) {
		const normalized=convert(value ?? '');
		return options.some(([allowed]) => allowed === normalized)
			? normalized
			:fallback;
	}
	function normalizeAutoGarsoAnalysisMode(value) {
		return value === 'count' || value === 'content'
			? value
			:DEFAULT_SETTINGS.autoGarsoAnalysisMode;
	}
	function normalizeEscortPriceDuration(value) {
		return normalizeOptionValue( value, ESCORT_PRICE_DISPLAY_OPTIONS, DEFAULT_SETTINGS.searchResultPriceDuration );
	}
	function normalizePhoneClipboardFormat(value) {
		return normalizeOptionValue( value, PHONE_CLIPBOARD_FORMAT_OPTIONS, DEFAULT_SETTINGS.phoneClipboardFormat );
	}
	function normalizeTopPhoneSearchEnterTarget(value) {
		return normalizeOptionValue( value, TOP_PHONE_SEARCH_TARGET_OPTIONS, DEFAULT_SETTINGS.topPhoneSearchEnterTarget );
	}
	function normalizeTopPhoneSearchOpenMode(value) {
		return normalizeOptionValue( value, TOP_PHONE_SEARCH_OPEN_MODE_OPTIONS, DEFAULT_SETTINGS.topPhoneSearchOpenMode );
	}
	function normalizeDefaultSearchLocation(value) {
		const source=value && typeof value === 'object' ? value:{};
		const clean=(field,maxLength=100) => String(field ?? '') .replace(/\s+/g,' ') .trim() .slice(0,maxLength);
		return {
			countryValue:'33', countryLabel:'Polska', provinceValue:clean(source.provinceValue,20),
			provinceLabel:clean(source.provinceLabel), cityValue:clean(source.cityValue,20), cityLabel:clean(source.cityLabel),
			districtValue:clean(source.districtValue,20), districtLabel:clean(source.districtLabel)
		};
	}
	function normalizeSearchLocations(value) {
		const source=Array.isArray(value) ? value:[];
		const seen=new Set();
		const result=[];
		for (const candidate of source) {
			const locationData=normalizeDefaultSearchLocation(candidate);
			const [province,city,district]=['province','city','district']
				.map(key => locationData[key + 'Value'] || locationData[key + 'Label']);
			if ((!province && !city) || (district && !city)) continue;
			const key=getEscortLocationKey(locationData);
			if (seen.has(key)) continue;
			seen.add(key);
			result.push(locationData);
			if (result.length >= ESCORT_MULTI_LOCATION_MAX) break;
		}
		return result;
	}
	function normalizeSavedLocationGroups(value) {
		const source=Array.isArray(value) ? value:[];
		const groups=[];
		const seen=new Set();
		for (const candidate of source) {
			const rawLocations=Array.isArray(candidate)
				? candidate
				:(Array.isArray(candidate?.locations) ? candidate.locations:[candidate]);
			const locations=normalizeSearchLocations(rawLocations);
			if (!locations.length) continue;
			const key=locations .map(getEscortLocationKey) .sort() .join('||');
			if (!key || seen.has(key)) continue;
			seen.add(key);
			groups.push(locations);
			if (groups.length >= 30) break;
		}
		return groups;
	}
	function normalizeEscortAggregationMode(value) {
		return normalizeOptionValue( value, ESCORT_AGGREGATION_MODE_OPTIONS, DEFAULT_SETTINGS.escortAggregationMode );
	}
	function normalizeEscortRelatedAdsTileLinkMode(value) {
		return normalizeOptionValue( value, ESCORT_RELATED_ADS_TILE_LINK_MODE_OPTIONS, DEFAULT_SETTINGS.relatedAdsTileLinkMode
		);
	}
	function normalizeEscortCompactSummaryMode(value) {
		const normalized=String(value || '');
		return normalizeOptionValue( normalized, ESCORT_COMPACT_SUMMARY_MODE_OPTIONS,
			DEFAULT_SETTINGS.compactSummaryComparisonMode );
	}
	function getEscortListSearchMode(settings=SETTINGS) {
		return settings?.mergeEscortAds && normalizeEscortAggregationMode(settings.escortAggregationMode) === 'phone-escorti'
			? 'phone-escorti'
			:'address-escorti';
	}
	function getEscortPriceDurationLabel(value) {
		const normalized=normalizeEscortPriceDuration(value);
		return ESCORT_PRICE_DISPLAY_OPTIONS.find(([allowed]) => allowed === normalized)?.[1]
			|| '1 h';
	}
	function isEscortSearchPriceDisplayed(settings=SETTINGS) {
		return !!settings?.showPricesInSearchResults &&
			normalizeEscortPriceDuration(settings.searchResultPriceDuration) !== 'hide';
	}
	function normalizeHistoryCleanupKeywords(value) {
		const source=value == null
			? DEFAULT_HISTORY_CLEANUP_KEYWORDS
			:(Array.isArray(value) ? value:String(value).split(/[\n,;]+/));
		const seen=new Set();
		const keywords=[];
		for (const candidate of source) {
			let keyword=String(candidate || '').replace(/\s+/g,' ').trim().slice(0,160);
			if (keyword.toLocaleLowerCase('pl-PL') === 'lens.google.com') {
				keyword='google.com/search?vsrid';
			}
			const key=keyword.toLocaleLowerCase('pl-PL');
			if (!keyword || seen.has(key)) continue;
			seen.add(key);
			keywords.push(keyword);
			if (keywords.length >= 100) break;
		}
		return keywords;
	}
	function normalizeWatchProfileResolutionMode(value) {
		const normalized=String(value || '');
		return WATCH_PROFILE_RESOLUTION_OPTIONS.some(([allowed]) => allowed === normalized)
			? normalized
			:DEFAULT_SETTINGS.watchProfileResolutionMode;
	}
	function normalizeStoredWatchProfileResolutionMode(value) {
		const raw=String(value || '');
		if (raw === 'search-escort') return raw;
		if (raw) {
			const normalized=normalizeWatchProfileResolutionMode(raw);
			if (normalized !== 'ask') return normalized;
		}
		const configured=normalizeWatchProfileResolutionMode( getSettings().watchProfileResolutionMode );
		return configured === 'ask' ? 'address-escorti':configured;
	}
	function normalizeEscortDataFieldId(value) {
		const raw=String(value || '').trim();
		if (!raw || raw === 'none') return 'none';
		if (!/^(?:ad|profile|escorti|garso):/i.test(raw)) return 'none';
		const parts=raw.split(':');
		if (parts.length < 2 || parts.some(part => !part)) return 'none';
		return raw.slice(0,320);
	}
	function normalizeEscortDataFieldList(value,maxItems) {
		const source=Array.isArray(value) ? value:[];
		const result=[];
		const seen=new Set();
		for (const candidate of source) {
			const field=normalizeEscortDataFieldId(candidate);
			if (!field || field === 'none' || seen.has(field)) continue;
			seen.add(field);
			result.push(field);
			if (result.length >= maxItems) break;
		}
		return result;
	}
	function normalizeSearchResultExtraFields(value) {
		return normalizeEscortDataFieldList( value, SEARCH_RESULT_MAX_EXTRA_FIELDS );
	}
	function getSearchResultExtraFields(settings=SETTINGS) {
		return normalizeSearchResultExtraFields( settings?.searchResultExtraFields );
	}
	function normalizeFavoritesDefaultView(value) {
		return value === 'tiles' ? 'tiles':'table';
	}
	function normalizeFavoritesDataColumns(value) {
		return normalizeEscortDataFieldList( value, FAVORITES_MAX_DATA_COLUMNS );
	}
	const ESCORT_DATA_FIELD_TECHNICAL_ROOT_KEYS=new Set([ 'status','adId','adUrl','phoneId','phoneDigits','checkedAt',
		'fetchTransport','phoneFetchStatus','phoneCheckedAt','phoneAttemptedAt','lastVisitedAt',
		'pageCheckedAt','visitSnapshot','watchSnapshot', 'watchSnapshotCheckedAt','escortiActivity' ]);
	const ESCORT_PROFILE_FIELD_TECHNICAL_KEYS=new Set([
		'adDetails','activeUrls','unknownCount','partial','locationPartial','lastKnownCities','escortiData' ]);
	const ESCORTI_DATA_FIELD_TECHNICAL_KEYS=new Set([ 'status','searchMode','profileIds','profileUrls','profileUrl',
		'adIds','adUrls','adLinksVersion','profileSummaries','cityHistory','partial', 'merged','checkedAt' ]);
	let escortDataFieldCatalogMemory=null;
	let escortDataFieldCatalogBootstrapped=false;
	let escortDataFieldCatalogSaveTimer=null;
	let escortDataFieldCatalogUiTimer=null;
	function encodeEscortDataFieldSegment(value) {
		return encodeURIComponent(String(value || '').trim());
	}
	function decodeEscortDataFieldSegment(value) {
		try {return decodeURIComponent(String(value || ''));} catch {return String(value || '');}
	}
	function buildEscortDataFieldId(source,path) {
		const cleanSource=['profile','escorti','garso'].includes(source) ? source:'ad';
		const segments=(path || []).map(encodeEscortDataFieldSegment).filter(Boolean);
		return segments.length ? `${cleanSource}:${segments.join(':')}`:'none';
	}
	function parseEscortDataFieldId(field) {
		const normalized=normalizeEscortDataFieldId(field);
		if (normalized === 'none') return null;
		const [source,...encodedPath]=normalized.split(':');
		return {
			id:normalized, source, path:encodedPath.map(decodeEscortDataFieldSegment)
		};
	}
	function prettifyEscortDataFieldKey(value) {
		const text=String(value || '') .replace(/[_-]+/g,' ') .replace(/([a-ząćęłńóśźż0-9])([A-ZĄĆĘŁŃÓŚŹŻ])/g,'$1 $2')
			.replace(/\s+/g,' ') .trim();
		return text ? text.charAt(0).toLocaleUpperCase('pl-PL') + text.slice(1):'Dane';
	}
	function getEscortDataFieldLabel(source,path) {
		const [root,second,...rest]=path || [];
		if (source === 'garso') return `Garso - ${getGarsoDataFields().find(([key]) => key === root)?.[1] || root} (cache)`;
		if (source === 'profile') {
			const known={
				totalAds:'Liczba anonsów', activeCount:'Znane anonse', cities:'Lokalizacja',
				profileDates:'Profil - daty założenia powiązanych profili Escorti', online:'Stan online',
				checkedAt:'Ostatnie sprawdzenie przez skrypt', hidden:'Anons/Profil - czy ukryty'
			};
			if (known[root]) return known[root];
			return `Profil - ${[root,second,...rest].filter(Boolean).map(prettifyEscortDataFieldKey).join(' - ')}`;
		}
		if (source === 'escorti') {
			const known={
				profiles:'Escorti - liczba profili', adLinks:'Escorti - liczba anonsów',
				creationDate:'Escorti - data założenia profilu', currentCity:'Escorti - aktualne miasto', historicalCityCount:
					'Escorti - liczba miast (historycznie)', garsoTopics:'Escorti - liczba tematów Garso',
				garsoTopicTitles:'Escorti - tytuły tematów Garso'
			};
			if (known[root]) return known[root];
			return `Escorti - ${[root,second,...rest].filter(Boolean).map(prettifyEscortDataFieldKey).join(' - ')}`;
		}
		if (root === 'prices' && second) {
			const knownDuration=ESCORT_PRICE_DURATION_OPTIONS.find(([value]) => String(value) === String(second))?.[1];
			const minutes=Number(second);
			const durationLabel=knownDuration || (Number.isFinite(minutes) && minutes > 0
				? (minutes % 60 === 0 ? `${minutes / 60} h`:`${minutes} min`)
				:prettifyEscortDataFieldKey(second));
			return `Cena ${durationLabel}`;
		}
		if (root === 'stats' && second) {
			return [second,...rest].filter(Boolean).map(prettifyEscortDataFieldKey).join(' - ');
		}
		if (root === 'tags' && second) {
			return `Tagi - ${normalizeEscortAdText(second) || prettifyEscortDataFieldKey(second)}`;
		}
		if (root === 'availability' && second) {
			return `Dostępność - ${[second,...rest].filter(Boolean).map(prettifyEscortDataFieldKey).join(' - ')}`;
		}
		if (root === 'location' && second) {
			const locationKnown={
				country:'Kraj', province:'Województwo', city:'Miasto', district:'Dzielnica', area:'Obszar',
				addressText:'Adres w anonsie', addressParts:'Adres na mapie'
			};
			return `Lokalizacja - ${locationKnown[second] || prettifyEscortDataFieldKey(second)}`;
		}
		const known={
			title:'Nazwa anonsu', description:'Opis', phone:'Numer telefonu', datePosted:'Data włączenia anonsu',
			lastVisitedAt:'Ostatnie odwiedziny anonsu', tagCount:'Liczba usług (tagów)', declaredBmi:'Deklarowane BMI',
			dimensions:'Wymiary', tags:'Tagi', isAgencyOrSalonPhone:'Telefon agencji/salonu'
		};
		if (known[root]) return known[root];
		return `Anons - ${[root,second,...rest].filter(Boolean).map(prettifyEscortDataFieldKey).join(' - ')}`;
	}
	function getEscortDataFieldKind(source,path,value) {
		const [root]=path || [];
		if (source === 'garso') return 'number';
		if (source === 'ad' && root === 'prices') return 'price';
		if (source === 'ad' && root === 'tags' && path.length > 1) return 'boolean';
		if (source === 'ad' && root === 'lastVisitedAt') return 'relative-time';
		if ( source === 'ad' && ( root === 'tagCount' || root === 'declaredBmi' ) ) return 'number';
		if (source === 'ad' && root === 'dimensions') return 'text';
		if ( source === 'escorti' && root === 'historicalCityCount' ) return 'number';
		if (root === 'description') return 'long-text';
		if (/date/i.test(root || '')) return 'date';
		if (/checkedAt$/i.test(root || '')) return 'datetime';
		if (typeof value === 'boolean') return 'boolean';
		if (Array.isArray(value)) return 'list';
		if (typeof value === 'number') return 'number';
		return 'text';
	}
	function makeEscortDataFieldDescriptor(source,path,value=null) {
		const id=buildEscortDataFieldId(source,path);
		if (id === 'none') return null;
		return {
			id, source, path:[...path], label:getEscortDataFieldLabel(source,path),
			kind:getEscortDataFieldKind(source,path,value)
		};
	}
	function readEscortDataFieldCatalog() {
		if (escortDataFieldCatalogMemory) return escortDataFieldCatalogMemory;
		const map=new Map();
		try {
			const stored=GM_getValue(ESCORT_DATA_FIELD_CATALOG_STORAGE_KEY,[]);
			for (const entry of Array.isArray(stored) ? stored:[]) {
				const parsed=parseEscortDataFieldId(entry?.id);
				if (!parsed) continue;
				map.set(parsed.id,{
					id:parsed.id, source:parsed.source, path:parsed.path, label:getEscortDataFieldLabel(parsed.source,parsed.path),
					kind:normalizeEscortAdText(entry?.kind) || 'text'
				});
				if (map.size >= ESCORT_DATA_FIELD_CATALOG_MAX) break;
			}
		} catch {}
		escortDataFieldCatalogMemory=map;
		return map;
	}
	function persistEscortDataFieldCatalog() {
		clearTimeout(escortDataFieldCatalogSaveTimer);
		escortDataFieldCatalogSaveTimer=null;
		try {
			const values=[...readEscortDataFieldCatalog().values()] .sort((a,b) => a.label.localeCompare(b.label,'pl'))
				.slice(0,ESCORT_DATA_FIELD_CATALOG_MAX) .map(entry => ({id:entry.id,kind:entry.kind}));
			writeHelperStorageValue(ESCORT_DATA_FIELD_CATALOG_STORAGE_KEY,values);
		} catch {}
	}
	function scheduleEscortDataFieldCatalogChanged() {
		clearTimeout(escortDataFieldCatalogSaveTimer);
		escortDataFieldCatalogSaveTimer=setTimeout(persistEscortDataFieldCatalog,450);
		clearTimeout(escortDataFieldCatalogUiTimer);
		escortDataFieldCatalogUiTimer=setTimeout(() => {
			escortDataFieldCatalogUiTimer=null;
			try {window.dispatchEvent(new CustomEvent('vmEscortDataCatalogChanged'));} catch {}
		},120);
	}
	function isUsefulEscortDataValue(value) {
		if (value == null || value === '') return false;
		if (Array.isArray(value)) return value.some(item => item != null && String(item).trim());
		if (typeof value === 'object') return Object.keys(value).length > 0;
		return true;
	}
	function registerEscortDataField(source,path,value,schedule=true) {
		if (!isUsefulEscortDataValue(value)) return false;
		const descriptor=makeEscortDataFieldDescriptor(source,path,value);
		if (!descriptor) return false;
		const catalog=readEscortDataFieldCatalog();
		const existing=catalog.get(descriptor.id);
		if (existing) {
			if (existing.kind !== 'text' || descriptor.kind === 'text') return false;
			existing.kind=descriptor.kind;
		} else {
			if (catalog.size >= ESCORT_DATA_FIELD_CATALOG_MAX) return false;
			catalog.set(descriptor.id,descriptor);
		}
		if (schedule) scheduleEscortDataFieldCatalogChanged();
		return true;
	}
	function walkEscortDataFields(source,value,path=[],depth=0,schedule=true) {
		if (depth > 4 || !isUsefulEscortDataValue(value)) return false;
		const rootKey=path[0] || '';
		if (source === 'ad' && path.length === 1 && ESCORT_DATA_FIELD_TECHNICAL_ROOT_KEYS.has(rootKey)) return false;
		if (source === 'profile' && path.length === 1 && ESCORT_PROFILE_FIELD_TECHNICAL_KEYS.has(rootKey)) return false;
		if (source === 'escorti' && path.length === 1 && ESCORTI_DATA_FIELD_TECHNICAL_KEYS.has(rootKey)) return false;
		if (source === 'ad' && rootKey === 'prices' && path.length === 2) {
			return registerEscortDataField(source,path,value,schedule);
		}
		if (Array.isArray(value) || typeof value !== 'object' || value === null) {
			return registerEscortDataField(source,path,value,schedule);
		}
		let changed=false;
		for (const [key,child] of Object.entries(value)) {
			if (!isUsefulEscortDataValue(child)) continue;
			changed=walkEscortDataFields(source,child,[...path,key],depth + 1,schedule) || changed;
		}
		return changed;
	}
	function registerEscortDataFieldsFromAdData(adData,schedule=true) {
		if (!adData || typeof adData !== 'object') return false;
		let changed=false;
		for (const [key,value] of Object.entries(adData)) {
			if (ESCORT_DATA_FIELD_TECHNICAL_ROOT_KEYS.has(key)) continue;
			if (key === 'tags' && Array.isArray(value)) {
				for (const rawTag of value) {
					const tag=normalizeEscortAdText(rawTag);
					if (!tag) continue;
					changed=registerEscortDataField('ad',['tags',tag],true,schedule) || changed;
				}
			}
			changed=walkEscortDataFields('ad',value,[key],0,schedule) || changed;
		}
		return changed;
	}
	function registerEscortDataFieldsFromProfileState(state,schedule=true) {
		if (!state || typeof state !== 'object') return false;
		let changed=false;
		for (const [key,value] of Object.entries(state)) {
			if (ESCORT_PROFILE_FIELD_TECHNICAL_KEYS.has(key)) continue;
			changed=walkEscortDataFields('profile',value,[key],0,schedule) || changed;
		}
		return changed;
	}
	function registerEscortDataFieldsFromEscortiResult(result,schedule=true) {
		if (!result || typeof result !== 'object') return false;
		let changed=false;
		for (const [key,value] of Object.entries(result)) {
			if (ESCORTI_DATA_FIELD_TECHNICAL_KEYS.has(key)) continue;
			if (Array.isArray(value) && value.some(item => item && typeof item === 'object')) continue;
			changed=walkEscortDataFields('escorti',value,[key],0,schedule) || changed;
		}
		return changed;
	}
	function ensureEscortDataFieldCatalog() {
		const catalog=readEscortDataFieldCatalog();
		if (escortDataFieldCatalogBootstrapped || (deferredNonTopicCache && catalog.size)) return catalog;
		escortDataFieldCatalogBootstrapped=true;
		if (catalog.size) return catalog;
		let changed=false;
		try {
			for (const key of getEscortAdDataCacheKeys()) {
				const value=readPersistentCacheValue(key,null);
				changed=registerEscortDataFieldsFromAdData(value,false) || changed;
			}
		} catch {}
		try {
			for (const key of getEscortListCacheKeys()) {
				const store=readPersistentCacheValue(key,null);
				for (const [mode,entry] of Object.entries(store?.modes || {})) {
					const expanded=expandEscortListCacheEntry(entry,mode);
					changed=registerEscortDataFieldsFromEscortiResult(expanded,false) || changed;
				}
			}
		} catch {}
		try {
			for (const item of getWatchedItems()) {
				changed=registerEscortDataFieldsFromProfileState(item?.state,false) || changed;
			}
		} catch {}
		if (changed) scheduleEscortDataFieldCatalogChanged();
		return catalog;
	}
	function getEscortDataFieldDescriptor(field) {
		const parsed=parseEscortDataFieldId(field);
		if (!parsed) return null;
		const stored=ensureEscortDataFieldCatalog().get(parsed.id);
		return stored || makeEscortDataFieldDescriptor(parsed.source,parsed.path,null);
	}
	function getEscortDataFieldOptions(selectedField='none',forFavorites=false) {
		const selected=normalizeEscortDataFieldId(selectedField);
		const descriptors=[...ensureEscortDataFieldCatalog().values()];
		if (!forFavorites) {
			for (const [alias,canonical] of [['profile:totalAds','escorti:adLinks'],['profile:profileDates','escorti:creationDate']]) {
				if (descriptors.some(entry => entry.id === alias) && !descriptors.some(entry => entry.id === canonical)) {
					descriptors.push(getEscortDataFieldDescriptor(canonical));
				}
			}
		}
		const virtualDescriptors=[ ...getGarsoDataFields().map(([key]) => makeEscortDataFieldDescriptor('garso',[key])),
			...(forFavorites ? [makeEscortDataFieldDescriptor('profile',['hidden'],false)]:[]), makeEscortDataFieldDescriptor(
				'ad', ['lastVisitedAt'], null ), makeEscortDataFieldDescriptor( 'ad', ['tagCount'], null ),
			makeEscortDataFieldDescriptor( 'ad', ['declaredBmi'], null ), makeEscortDataFieldDescriptor( 'ad', ['dimensions'], null ),
			makeEscortDataFieldDescriptor( 'escorti', ['historicalCityCount'], null ) ].filter(Boolean);
		for (const descriptor of virtualDescriptors) {
			if (!descriptors.some(entry => entry.id === descriptor.id)) descriptors.push(descriptor);
		}
		if (selected !== 'none' && !descriptors.some(entry => entry.id === selected)) {
			const descriptor=getEscortDataFieldDescriptor(selected);
			if (descriptor) descriptors.push(descriptor);
		}
		descriptors.sort((a,b) => {
			const sourceOrder={ad:0,escorti:1,profile:2};
			if (a.source !== b.source) return (sourceOrder[a.source] ?? 9) - (sourceOrder[b.source] ?? 9);
			return a.label.localeCompare(b.label,'pl');
		});
		const hasTopicTitles=descriptors.some(entry => entry.id === 'escorti:garsoTopicTitles') &&
			getEscortListCacheKeys().some(key => Object.values(readPersistentCacheValue(key,null)?.modes || {})
				.some(entry => entry?.garsoTopicTitles?.some(title => String(title || '').trim())));
		return [['none','- brak -'],...descriptors.filter(entry =>
			(entry.id !== 'escorti:garsoTopicTitles' || hasTopicTitles) &&
			(forFavorites || entry.id === selected || !['profile:totalAds','profile:profileDates'].includes(entry.id))
		).map(entry => [entry.id,entry.label])];
	}
	function getEscortDataValueByPath(object,path) {
		let value=object;
		for (const key of path || []) {
			if (value == null || typeof value !== 'object') return null;
			if (Object.prototype.hasOwnProperty.call(value,key)) {
				value=value[key];
				continue;
			}
			const wanted=normalizeEscortChangeComparisonValue(key).replace(/:\s*$/,'');
			const matchedKey=Object.keys(value).find(candidate =>
				normalizeEscortChangeComparisonValue(candidate).replace(/:\s*$/,'') === wanted );
			if (!matchedKey) return null;
			value=value[matchedKey];
		}
		return value;
	}
	function getDeclaredEscortBmi(adData) {
		if ( !adData || !adData.stats || typeof adData.stats !== 'object' ) {
			return null;
		}
		let heightCm=null;
		let weightKg=null;
		for (const [rawLabel,rawValue] of Object.entries( adData.stats )) {
			const label= normalizeEscortChangeComparisonValue( rawLabel ).replace(/:\s*$/,'');
			const match=String(rawValue ?? '') .replace(',','.') .match(/\d+(?:\.\d+)?/);
			const number=match
				? Number(match[0])
				:NaN;
			if (!Number.isFinite(number)) continue;
			if (label === 'wzrost') {
				heightCm=number;
			} else if (label === 'waga') {
				weightKg=number;
			}
		}
		if ( !Number.isFinite(heightCm) || !Number.isFinite(weightKg) || heightCm <= 0 || weightKg <= 0 ) {
			return null;
		}
		const heightM=heightCm / 100;
		const bmi= weightKg / (heightM * heightM);
		return Number.isFinite(bmi)
			? bmi
			:null;
	}
	function getEscortiHistoricalCityStats( results ) {
		const counts=new Map();
		const displayNames=new Map();
		const seen=new Set();
		const add=( cityValue, profileIdentity, dateIdentity='current' ) => {
			const city= normalizeEscortCity(cityValue);
			if (!city) return;
			const cityKey= normalizeEscortCityKey(city);
			if (!cityKey) return;
			const profileKey= String( profileIdentity || 'profil' );
			const occurrenceKey= `${profileKey}|${cityKey}|${
					String(dateIdentity || '')
				}`;
			if (seen.has(occurrenceKey)) return;
			seen.add(occurrenceKey);
			if (!displayNames.has(cityKey)) {
				displayNames.set( cityKey, city );
			}
			counts.set( cityKey, (counts.get(cityKey) || 0) + 1 );
		};
		for (const result of
			Array.isArray(results)
				? results
				:[]) {
			const profiles= getEscortiProfileSummariesForResearch( result );
			for (const profile of profiles) {
				const profileIdentity= profile?.profileUrl || profile?.profileName || result?.profileUrl || 'profil';
				add( profile?.currentCity, profileIdentity, 'current' );
				for (const entry of
					Array.isArray( profile?.cityHistory )
						? profile.cityHistory
						:[]) {
					add( entry?.city, profileIdentity, entry?.date || entry?.city );
				}
			}
		}
		return [...counts.entries()] .map(([cityKey,count]) => ({
				city: displayNames.get(cityKey) || cityKey, count
			})) .sort((a,b) => b.count - a.count || a.city.localeCompare( b.city, 'pl-PL' ) );
	}
	function getEscortiCityHistoryOverlaps(results) {
		const days=new Map();
		for (const result of results) for (const profile of getEscortiProfileSummariesForResearch(result)) {
			const identity=profile.profileUrl || profile.profileName;
			if (!identity) continue;
			for (const entry of profile.cityHistory || []) {
				const date=formatEscortiDate(entry.date),city=normalizeEscortCity(entry.city);
				if (!date || !city) continue;
				const key=`${identity}|${date}`;
				if (!days.has(key)) days.set(key,{date,url:profile.profileUrl,cities:new Map()});
				days.get(key).cities.set(normalizeEscortCityKey(city),city);
			}
		}
		return [...days.values()].filter(day => day.cities.size > 1);
	}
	function getResearchConsistencySections(facts) {
		const sameEscorti=facts.sameEscortiResult === true ||
			areSameEscortiResults(facts.escortiAddressResult,facts.escortiPhoneResult);
		const sources=[
			['Escorti.pl',sameEscorti ? 'anonse znalezione po adresie ogłoszenia i numerze telefonu na Escorti.pl'
				:'anonse znalezione po adresie ogłoszenia na Escorti.pl',facts.escortiAddressSummary],
			...(!sameEscorti ? [['Escorti.pl - nr tel.','anonse znalezione po numerze telefonu na Escorti.pl',facts.escortiPhoneSummary]]:[]),
			['Escort.club - nr tel.','anonse znalezione po numerze telefonu na Escort.club',facts.escortPhoneSummary] ];
		const sections=new Map();
		for (const [title,subtitle,summary] of sources) {
			const statFields=[];
			const warnings=getEscortActiveAdsConsistencyWarnings(summary,statFields);
			if (!warnings.length) continue;
			const ads=(summary.results || []).filter(item => item.active && item.adData);
			const ids=ads.map(item => parseAdIdFromUrl(item.url)).filter(Boolean);
			const key=ids.length === ads.length ? [...new Set(ids)].sort().join('|'):title;
			const existing=sections.get(key);
			if (existing) {
				existing.title += ` / ${title}`;
				existing.subtitle += `; ${subtitle}`;
			} else sections.set(key,{title,subtitle,warnings,statFields});
		}
		return [...sections.values()];
	}
	function getEscortiHistoricalCitiesFromResults( results ) {
		return getEscortiHistoricalCityStats( results ).map(item => item.city);
	}
	function getEscortiHistoricalCityCount(result) {
		const count= getEscortiHistoricalCityStats( [result] ).length;
		return count || null;
	}
	function getEscortDataFieldRawValue(object,descriptor) {
		if (!descriptor) return null;
		if (descriptor.source === 'ad' && descriptor.path?.[0] === 'tags' && descriptor.path.length > 1) {
			const tags=object?.tags;
			if (!Array.isArray(tags)) return null;
			const wanted=normalizeEscortChangeComparisonValue(descriptor.path[1]);
			return tags.some(tag => normalizeEscortChangeComparisonValue(tag) === wanted);
		}
		if ( descriptor.source === 'ad' && descriptor.path?.[0] === 'tagCount' ) {
			return Array.isArray(object?.tags)
				? object.tags.length
				:null;
		}
		if ( descriptor.source === 'ad' && descriptor.path?.[0] === 'declaredBmi' ) {
			return getDeclaredEscortBmi(object);
		}
		if ( descriptor.source === 'ad' && descriptor.path?.[0] === 'dimensions' ) {
			return getFavoriteTileDimensionsText(object) || null;
		}
		return getEscortDataValueByPath( object, descriptor.path );
	}
	function formatEscortDataDateValue(value) {
		if (value == null || value === '') return '';
		if (Number.isFinite(Number(value)) && Number(value) > 100000000000) {
			try {
				return new Intl.DateTimeFormat('pl-PL',{dateStyle:'short',timeStyle:'short'}) .format(new Date(Number(value)));
			} catch {}
		}
		return formatEscortiDate(value) || normalizeEscortAdText(value);
	}
	function formatEscortUniversalDataValue(descriptor,value,compact=false) {
		if (!descriptor || value == null || value === '') return '';
		if (descriptor.source === 'garso') return descriptor.path[0] === 'reviewCount' ? String(value):`${value}/10`;
		if ( descriptor.source === 'ad' && descriptor.path?.[0] === 'declaredBmi' ) {
			const number=Number(value);
			return Number.isFinite(number)
				? number .toFixed(1) .replace('.',',')
				:'';
		}
		if (descriptor.kind === 'price') {
			return formatEscortTilePrice(value);
		}
		if (descriptor.kind === 'relative-time') {
			return formatEscortLastVisit(value);
		}
		if (Array.isArray(value)) {
			const parts=value.map(item => descriptor.kind === 'date' || descriptor.kind === 'datetime'
					? formatEscortDataDateValue(item)
					:normalizeEscortAdText(item) ).filter(Boolean);
			const joined=[...new Set(parts)].join(' • ');
			return compact && joined.length > 70 ? `${joined.slice(0,67)}…`:joined;
		}
		if (descriptor.kind === 'date' || descriptor.kind === 'datetime') return formatEscortDataDateValue(value);
		if (descriptor.kind === 'boolean') {
			if (descriptor.source === 'profile' && descriptor.path?.[0] === 'online') return value ? 'online':'offline';
			return value ? 'tak':'nie';
		}
		if (typeof value === 'object') {
			try {
				const text=JSON.stringify(value);
				return compact && text.length > 70 ? `${text.slice(0,67)}…`:text;
			} catch {return '';}
		}
		const text=normalizeEscortAdText(String(value));
		if (!compact) return text;
		const limit=descriptor.kind === 'long-text' ? 70:90;
		return text.length > limit ? `${text.slice(0,limit - 1)}…`:text;
	}
	function normalizeFavoriteCategoryList(value) {
		const source=Array.isArray(value) ? value:[value];
		const seen=new Set();
		const result=[];
		for (const candidate of source) {
			const label=normalizeEscortAdText(candidate).slice(0,60);
			const key=label.toLocaleLowerCase('pl-PL');
			if (!label || seen.has(key)) continue;
			seen.add(key);
			result.push(label);
			if (result.length >= 40) break;
		}
		return result;
	}
	function normalizeFavoriteCategories(value) {
		return normalizeFavoriteCategoryList( Array.isArray(value)
				? value
				:DEFAULT_FAVORITE_CATEGORIES );
	}
	function normalizeFavoriteAssignedCategories(value) {
		return normalizeFavoriteCategoryList(value);
	}
	function normalizeFavoriteCategoryColor(value) {
		const color=String(value || '').trim();
		return /^#[0-9a-f]{6}$/i.test(color) ? color.toLowerCase():'';
	}
	function normalizeFavoriteCategoryColors(value,categories=[]) {
		const source=value && typeof value === 'object' ? value:{};
		const result={};
		for (const category of normalizeFavoriteCategories(categories)) {
			const color=normalizeFavoriteCategoryColor(source[category]);
			if (color) result[category]=color;
		}
		return result;
	}
	function favoriteCategoryTint(color,alpha=0.11) {
		const normalized=normalizeFavoriteCategoryColor(color);
		if (!normalized) return '';
		const r=parseInt(normalized.slice(1,3),16);
		const g=parseInt(normalized.slice(3,5),16);
		const b=parseInt(normalized.slice(5,7),16);
		return `rgba(${r}, ${g}, ${b}, ${alpha})`;
	}
	function ensureFavoriteCategoryHoverStyle() {
		if (document.getElementById('vm-favorites-category-hover-style')) return;
		appendHelperStyle('vm-favorites-category-hover-style',` .vm-favorites-category-chip { display:inline-flex;align-items:center;gap:3px;max-width:100%;min-width:0;height:18px; padding:0 3px 0 6px;border:1px solid var(--vm-favorite-category-color);border-radius:999px; background:var(--vm-favorite-category-tint);color:var(--vm-fav-text,var(--vm-ui-text,#222)); font-size:8.2px;font-weight:700;line-height:16px;box-sizing:border-box; }
.vm-favorites-category-chip-label { overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0; }
.vm-favorites-category-remove { flex:0 0 13px;width:13px;height:13px;padding:0;margin:0;border:0;border-radius:50%; background:transparent;color:var(--vm-ui-text-muted,#777);font-size:11px;font-weight:700; line-height:12px;text-align:center;cursor:pointer;opacity:.30;transition:opacity .10s ease; }
.vm-favorites-category-add-button { display:inline-flex;flex:0 0 18px;align-items:center;justify-content:center;width:18px;min-width:18px; height:18px;padding:0;margin:0;border:1px solid var(--vm-ui-border,#d4d4d8);border-radius:999px; background:var(--vm-ui-bg-alt,rgba(250,250,250,.82));color:var(--vm-ui-text-muted,#777); font-size:13px;font-weight:700;line-height:16px;text-align:center;box-sizing:border-box;cursor:pointer; opacity:.28;visibility:visible;pointer-events:auto;touch-action:manipulation; transition:opacity .10s ease,border-color .10s ease,background .10s ease,color .10s ease,box-shadow .10s ease; }
.vm-favorites-category-cell .vm-favorites-category-remove, .vm-favorites-category-cell .vm-favorites-category-add-button { opacity:.28 !important; }
.vm-favorites-category-cell:hover .vm-favorites-category-remove, .vm-favorites-category-cell:hover .vm-favorites-category-add-button { opacity:1 !important; }
.vm-favorites-category-cell:hover .vm-favorites-category-add-button { border-color:var(--vm-ui-border-strong,#a9a9af) !important;background:var(--vm-ui-bg,#fff) !important; color:var(--vm-ui-text,#444) !important;box-shadow:var(--vm-ui-shadow,0 1px 4px rgba(0,0,0,.12)) !important; }
`);
	}
	function createFavoriteCategoryChip(value,color,onRemove) {
		ensureFavoriteCategoryHoverStyle();
		const chip=makeElement('span','vm-favorites-category-chip');
		chip.title=`${value} - kliknij ×, aby usunąć`;
		chip.style.setProperty('--vm-favorite-category-color',color);
		chip.style.setProperty('--vm-favorite-category-tint',favoriteCategoryTint(color,.10));
		const label=makeElement('span','vm-favorites-category-chip-label',value);
		const remove=makeElement('button','vm-favorites-category-remove','×',{type:'button',title:`Kategoria „${value}”. ${trackedRemovalHint('category')}`});
		remove.addEventListener('click',event => {
			stopUiEvent(event);
			onRemove(value);
		});
		chip.append(label,remove);
		return chip;
	}
	function createFavoriteCategoryAddButton(onActivate,pointerDown=false) {
		ensureFavoriteCategoryHoverStyle();
		const button=makeElement('button','vm-favorites-category-add-button','+');
		button.type='button';
		button.title='Dodaj kategorię';
		const activate=event => {
			if (pointerDown && event.button != null && event.button !== 0) return;
			stopUiEvent(event);
			onActivate(button);
		};
		if (pointerDown) {
			button.addEventListener('pointerdown',activate,true);
			button.addEventListener('click',stopUiEvent);
		} else {
			button.addEventListener('click',activate);
		}
		return button;
	}
	function normalizeEscortRequestLimitPerMinute(value,fallback=DEFAULT_SETTINGS.escortLimitPerMinute,max=840) {
		const number=typeof value === 'number' || (typeof value === 'string' && value.trim()) ? Number(value):NaN;
		return Number.isFinite(number) ? Math.min(max,Math.max(1,Math.floor(number))):fallback;
	}
	function normalizeEscortRequestMaxConcurrent(value,fallback=DEFAULT_SETTINGS.escortMaxConcurrent,max=24) {
		return normalizeEscortRequestLimitPerMinute(value,fallback,max);
	}
	function normalizeEscortQueueSettings(settings={}) {
		// Zachowaj sumę dawnych limitów, także zaokrąglenia ustawień sprzed 1.976.
		const legacyRate=settings.escortRequestLimitPerMinute == null ? null:
			Math.ceil(Math.max(5,normalizeEscortRequestLimitPerMinute(settings.escortRequestLimitPerMinute,240,420)) / 2);
		const hasLegacyRate=legacyRate !== null || settings.escortGetLimitPerMinute != null || settings.escortHeadLimitPerMinute != null;
		const combinedRate=hasLegacyRate
			? normalizeEscortRequestLimitPerMinute(settings.escortGetLimitPerMinute ?? legacyRate,100,420) +
				normalizeEscortRequestLimitPerMinute(settings.escortHeadLimitPerMinute ?? legacyRate,120,420)
			:DEFAULT_SETTINGS.escortLimitPerMinute;
		const legacyConcurrent=settings.escortRequestMaxConcurrent == null ? null:
			normalizeEscortRequestMaxConcurrent(settings.escortRequestMaxConcurrent,4,12);
		const combinedConcurrent=settings.escortGetMaxConcurrent != null || settings.escortHeadMaxConcurrent != null || legacyConcurrent !== null
			? normalizeEscortRequestMaxConcurrent(settings.escortGetMaxConcurrent ?? (legacyConcurrent === null ? null:Math.ceil(legacyConcurrent / 3)),4,12) +
				normalizeEscortRequestMaxConcurrent(settings.escortHeadMaxConcurrent ?? (legacyConcurrent === null ? null:Math.ceil(legacyConcurrent * 2 / 3)),4,12)
			:DEFAULT_SETTINGS.escortMaxConcurrent;
		return {
			escortLimitPerMinute:normalizeEscortRequestLimitPerMinute(settings.escortLimitPerMinute,combinedRate),
			escortMaxConcurrent:normalizeEscortRequestMaxConcurrent(settings.escortMaxConcurrent,combinedConcurrent)
		};
	}

	function normalizeWatchNotificationText(value) {
		return String(value ?? '').trim().slice(0,240) || DEFAULT_SETTINGS.watchNotificationText;
	}
	function loadHelperSettingsFromStorage() {
		try {
			const saved=GM_getValue(SETTINGS_STORAGE_KEY,null);
			if (saved && typeof saved === 'object') {
				const normalized=normalizeHelperSettings({...DEFAULT_SETTINGS,...saved,...normalizeEscortQueueSettings(saved)},false);
				for (const key of ['escortGetLimitPerMinute','escortHeadLimitPerMinute','escortRequestLimitPerMinute','escortRequestMaxConcurrent']) delete normalized[key];
				normalized.garsoShowTopicHelperData=Object.prototype.hasOwnProperty.call(saved,'garsoShowTopicHelperData')
					? saved.garsoShowTopicHelperData !== false
					:(saved.garsoShowTrackedTopics !== false || saved.garsoShowVisitedAds !== false);
				delete normalized.garsoShowTrackedTopics;
				delete normalized.garsoShowVisitedAds;
				delete normalized.showMenuPageNamesOnTiles;
				if (!(Number(saved.imageSearchEnginesRevision) >= 2)) normalized.imageSearchEngines=normalized.imageSearchEngines
					.filter(item => item.id !== 'tineye' && !/(^|\.)tineye\.com$/i.test(new URL(item.urlPrefix).hostname));
				if (!(Number(saved.imageSearchEnginesRevision) >= 1) && normalized.imageSearchEngines.length < 20 &&
					!normalized.imageSearchEngines.some(item => item.id === HIVE_IMAGE_DETECTOR.id || /^https:\/\/(?:www\.)?hivedetect\.ai(?:[/?#]|$)/i.test(item.urlPrefix))) {
					normalized.imageSearchEngines.push({...HIVE_IMAGE_DETECTOR});
				}
				return normalized;
			}
			return {...DEFAULT_SETTINGS};
		} catch {
			return {...DEFAULT_SETTINGS};
		}
	}
	function getSettings() {
		return copyWatchedValue(readHelperSettingsSnapshot());
	}
	function normalizeHelperSettings(settings,forStorage=true) {
		const {escortRequestLimitPerMinute,escortRequestMaxConcurrent,escortGetMaxConcurrent,escortHeadMaxConcurrent,...current}=settings;
		const normalized=forStorage ? {}:current;
		for (const [keys,read] of [
			[['garsoCheckEscortLinks','garsoShowSearchIcons','garsoShowTopicSummary','garsoShowTopicTools','garsoShowTopicHelperData',
				'showRelatedAdsTileLink','showEscortiTileData','showPhoneReputationLinks','autoPhoneReputationSearch',
				'showTopPhoneSearch','showBottomLeftMenu','multiLocationEnabled','saveSearchLocationsEnabled',
				'showSummarySidePanel','showCompactSummaryAboveDescription','showTagHighlights','showPhoneCopyButton',
				'escortFastStart','showWatchAlertBar','watchNotificationAnonymous'],value => value !== false],
			[['showMenuPagesAsFavicons','showLiveDiagnostics','watchEnabled','watchSystemNotifications'],value => value === true],
			[['openAllActiveRelatedAdsFromTile','showPricesInSearchResults','hideRecommendedAdSection',
				'hideSingleAdContactButtons','hideSingleAdActions','showWatchOnlineCount'],Boolean],
			[['autoGarsoCheck','mergeEscortAds','hideRecommendedSearchSection','hideFeaturedSearchSection',
				'hidePopularCitiesSection','showImageSearchButtons'],value => forStorage ? !!value:value]
		]) {
			for (const key of keys) normalized[key]=read(settings[key]);
		}
		const normalizers={
			autoGarsoAnalysisMode:normalizeAutoGarsoAnalysisMode,
			escortAggregationMode:normalizeEscortAggregationMode,
			compactSummaryComparisonMode:normalizeEscortCompactSummaryMode,
			relatedAdsTileLinkMode:normalizeEscortRelatedAdsTileLinkMode,
			searchResultPriceDuration:normalizeEscortPriceDuration,
			searchResultExtraFields:normalizeSearchResultExtraFields,
			imageSearchEngines:normalizeImageSearchEngines,
			phoneReputationServices:normalizePhoneReputationServices,
			menuPages:normalizeMenuPages,
			topPhoneSearchEnterTarget:normalizeTopPhoneSearchEnterTarget,
			topPhoneSearchOpenMode:normalizeTopPhoneSearchOpenMode,
			searchLocations:normalizeSearchLocations,
			savedLocationGroups:normalizeSavedLocationGroups,
			tagHighlightGroups:normalizeTagHighlightGroups,
			phoneClipboardFormat:normalizePhoneClipboardFormat,
			watchNotificationText:normalizeWatchNotificationText,
			nonAlertChangeNewness:normalizeWatchNonAlertChangeNewness,
			watchProfileResolutionMode:normalizeWatchProfileResolutionMode,
			favoritesDefaultView:normalizeFavoritesDefaultView,
			favoritesDataColumns:normalizeFavoritesDataColumns,
			favoriteCategories:normalizeFavoriteCategories,
			historyCleanupKeywords:normalizeHistoryCleanupKeywords
		};
		for (const [key,read] of Object.entries(normalizers)) normalized[key]=read(settings[key]);
		return Object.assign(normalized,normalizeEscortQueueSettings(settings),{
			quickAdPreview:forStorage ? settings.quickAdPreview !== false:settings.quickAdPreview,
			maxRelatedAdsTabs:Math.max(1,Math.floor(Number(settings.maxRelatedAdsTabs) || DEFAULT_SETTINGS.maxRelatedAdsTabs)),
			imageSearchEnginesRevision:forStorage ? 2:settings.imageSearchEnginesRevision,
			showMenuLinksInline:true, usePersistentCache:true, notesEnabled:settings.watchEnabled === true,
			alarmingTagCountLimit:normalizeCustomFilterInteger(settings.alarmingTagCountLimit,DEFAULT_SETTINGS.alarmingTagCountLimit,0,10000),
			watchIntervalMinutes:WATCH_INTERVAL_OPTIONS.includes(Number(settings.watchIntervalMinutes))
				? Number(settings.watchIntervalMinutes):DEFAULT_SETTINGS.watchIntervalMinutes,
			favoriteCategoryColors:normalizeFavoriteCategoryColors(settings.favoriteCategoryColors,settings.favoriteCategories)
		});
	}
	function saveSettings(settings,waitForStorage=false) {
		const normalized=normalizeHelperSettings(settings);
		// Odświeżenie strony musi poczekać na potwierdzenie zapisu przez rozszerzenie.
		if (waitForStorage) return GM.setValue(SETTINGS_STORAGE_KEY,normalized).then(() => {
			invalidateHelperReadCache(SETTINGS_STORAGE_KEY);
			return normalized;
		});
		writeHelperStorageValue(SETTINGS_STORAGE_KEY,normalized);
		return normalized;
	}
	function getShareableHelperSettings(settings) {
		const normalized=normalizeHelperSettings({...DEFAULT_SETTINGS,...settings,...normalizeEscortQueueSettings(settings)});
		const keys=Object.keys(DEFAULT_SETTINGS).filter(key => ['boolean','number'].includes(typeof DEFAULT_SETTINGS[key]));
		keys.push('autoGarsoAnalysisMode','escortAggregationMode','compactSummaryComparisonMode','relatedAdsTileLinkMode',
			'searchResultPriceDuration','topPhoneSearchEnterTarget','topPhoneSearchOpenMode','phoneClipboardFormat',
			'nonAlertChangeNewness','watchProfileResolutionMode','favoritesDefaultView');
		const migrated=new Set();
		if (['escortGetLimitPerMinute','escortHeadLimitPerMinute','escortRequestLimitPerMinute'].some(key => Object.hasOwn(settings,key))) {
			migrated.add('escortLimitPerMinute');
		}
		if (['escortRequestMaxConcurrent','escortGetMaxConcurrent','escortHeadMaxConcurrent'].some(key => Object.hasOwn(settings,key))) {
			migrated.add('escortMaxConcurrent');
		}
		return Object.fromEntries(keys.filter(key => Object.hasOwn(settings,key) || migrated.has(key)).map(key => [key,normalized[key]]));
	}
	function buildScriptDataExportPayload(settingsOnly=false) {
		if (settingsOnly) return {
			format:'escort-helper-settings-only', exportedAt:new Date().toISOString(), settings:getShareableHelperSettings(getSettings())
		};
		return {
			format:SCRIPT_DATA_EXPORT_FORMAT, exportedAt:new Date().toISOString(),
			data:getScriptDataStorageKeys().map(key => [key,GM_getValue(key,null)])
		};
	}
	function downloadJsonFile(payload,fileName) {
		const blob=new Blob( [JSON.stringify(payload,null,2)], {type:'application/json;charset=utf-8'} );
		const url=URL.createObjectURL(blob);
		const link=makeElement('a','',null,{href:url,download:fileName});
		link.style.display='none';
		document.body.appendChild(link);
		link.click();
		detachHelperNode(link);
		setTimeout(() => URL.revokeObjectURL(url),1000);
	}
	function downloadScriptDataExport(settingsOnly=false) {
		const payload=buildScriptDataExportPayload(settingsOnly);
		const date=new Date().toISOString().slice(0,10);
		downloadJsonFile( payload, `escort-helper-ustawienia-${settingsOnly ? 'bez-danych-prywatnych-':''}${date}.json` );
	}
	function validateScriptDataImportPayload(payload) {
		if (payload?.format === 'escort-helper-settings-only') {
			if (!payload.settings || typeof payload.settings !== 'object' || Array.isArray(payload.settings)) {
				throw new Error('Plik nie zawiera prawidłowych ustawień.');
			}
			const settings=getShareableHelperSettings(payload.settings);
			if (!Object.keys(settings).length) throw new Error('Plik nie zawiera obsługiwanych ustawień.');
			return {settings};
		}
		if (!payload || typeof payload !== 'object' || payload.format !== SCRIPT_DATA_EXPORT_FORMAT) {
			throw new Error('To nie jest plik kopii danych tej wersji Escort Helper.');
		}
		if (!Array.isArray(payload.data)) {
			throw new Error('Plik nie zawiera prawidłowych danych skryptu.');
		}
		const data=payload.data.map(entry => {
			if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== 'string') {
				throw new Error('Plik zawiera nieprawidłowy wpis danych.');
			}
			if (entry[0].startsWith('vm_garso_watch_notification_claim:') || isRemovedDiagnosticStorageKey(entry[0])) return null;
			if (isScriptCacheStorageKey(entry[0]) || isTemporaryScriptStorageKey(entry[0])) {
				throw new Error('Plik zawiera dane cache lub tymczasowe i nie może zostać zaimportowany.');
			}
			return entry;
		}).filter(Boolean);
		return {data};
	}
	async function restoreScriptDataExport(payload) {
		const checked=validateScriptDataImportPayload(payload);
		if (checked.settings) {
			await GM.setValue(SETTINGS_STORAGE_KEY,normalizeHelperSettings({...getSettings(),...checked.settings},false));
			invalidateHelperReadCache(SETTINGS_STORAGE_KEY);
			return Object.keys(checked.settings).length;
		}
		const {data}=checked;
		await clearScriptDataStorage();
		for (const [key,value] of data) {
			await GM.setValue(key,value);
			invalidateHelperReadCache(key);
		}
		return data.length;
	}
	if (typeof GM_addValueChangeListener === 'function') {
		for (const key of [SETTINGS_STORAGE_KEY,WATCH_ITEMS_STORAGE_KEY,WATCH_AD_DATA_MIRROR_STORAGE_KEY]) GM_addValueChangeListener(key,() => invalidateHelperReadCache(key));
	}
	let SETTINGS=getSettings();
	function updateSettings(patchOrUpdater) {
		const current=getSettings();
		const patch= typeof patchOrUpdater === 'function'
				? patchOrUpdater(current)
				:patchOrUpdater;
		const normalized=saveSettings({
			...current, ...(patch && typeof patch === 'object' ? patch:{})
		});
		SETTINGS={
			...DEFAULT_SETTINGS, ...normalized
		};
		applyFavoriteSearchPageSettings();
		return SETTINGS;
	}
	let LIST_PAGES_TO_SHOW=consumeOneTimeListPagesToShow();
	const ESCORT_CACHED_AD_HASH_KEY='vm_cached_ad';
	const ESCORT_AD_TEMPLATE_CACHE_KEY='vm_escort_ad_template_v1';
	const ESCORT_CACHED_AD_ARCHIVE_VIEW_HASH_KEY='vm_cached_archive_view';
	const ESCORT_CACHED_AD_SESSION_KEY='vm_cached_ad_navigation';
	let escortCachedArchiveRuntimeOverride=false;
	const PERSISTENT_CACHE_DB_NAME='vm-garso-escorti-cache';
	const PERSISTENT_CACHE_DB_VERSION=1;
	const PERSISTENT_CACHE_STORE_NAME='entries';
	const ESCORT_CACHE_HOST_SUFFIX=IS_ESCORT_CLUB && CURRENT_HOST !== 'pl.escort.club' ? '_' + CURRENT_HOST:'';
	const PERSISTENT_CACHE_STATS_STORAGE_KEY='vm_garso_cache_stats_snapshot' + ESCORT_CACHE_HOST_SUFFIX;
	const ESCORT_AD_CACHE_INDEX_STORAGE_KEY='vm_escort_ad_cache_index' + ESCORT_CACHE_HOST_SUFFIX;
	const PERSISTENT_CACHE_BRIDGE_PARAM='vm_cache_bridge';
	const PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX='vm_garso_cache_bridge_request_';
	const PERSISTENT_CACHE_BRIDGE_RESPONSE_PREFIX='vm_garso_cache_bridge_response_';
	const ESCORT_VISIT_SUMMARY_ID='vm-escort-visit-summary';
	const ESCORT_LAST_VISIT_ID='vm-escort-last-visited';
	const ESCORT_CHANGE_STYLE_ID='vm-escort-change-style';
	const ESCORT_PHONE_ENDPOINT=`${ESCORT_CLUB_ORIGIN}/includes/ajax.show-phone.php`;
	const LIST_MAX_CONCURRENT=12;
	const ESCORT_AD_DATA_MAX_CONCURRENT=6;
	const ESCORT_AD_DATA_FETCH_TIMEOUT_MS=12000;
	const ESCORT_ACTIVE_CHECK_CONCURRENT=6;
	const ESCORT_ACTIVE_PROBE_CONCURRENT=12;
	const escortActiveAdsSummaryCache=new Map();
	const escortActiveAdCheckCache=new Map();
	const escortActiveAdProbeCache=new Map();
	function getListCacheTtlMs() {
		return ESCORT_CACHE_REFRESH_HOURS * 60 * 60 * 1000;
	}
	function getGarsoCacheTtlMs() {
		return GARSO_CACHE_REFRESH_HOURS * 60 * 60 * 1000;
	}
	const persistentCacheMemory=new Map();
	const persistentCacheEntrySizeBytes=new Map();
	let persistentCacheDb=null;
	let persistentCacheUsesIndexedDb=false;
	let persistentCacheDbOperationChain=Promise.resolve();
	let fallbackPersistentCacheStatsMemo=null;
	let persistentCacheStatsSnapshotTimer=null;
	let deferredTopicCacheRange=null;
	let deferredTopicCacheCount=0;
	// Odbudowywalny indeks; źródłem prawdy są rekordy DB.
	const PERSISTENT_STARTUP_INDEX_KEY='vm_helper_startup_index_v1';
	const PERSISTENT_CACHE_CHANGE_KEY='vm_helper_cache_changed' + ESCORT_CACHE_HOST_SUFFIX;
	let persistentCacheCatalog=null, persistentCacheCatalogStartedAt=0;
	let deferredNonTopicCache=false, persistentFullCachePromise=null;
	const persistentCacheLoadedKeys=new Set(),persistentCacheKeyJobs=new Map();
	const persistentCacheMutations=new Map(),persistentCacheChangedKeys=new Set();
	let persistentCacheChangeTimer=null,persistentCacheClearEpoch=0;
	let persistentRelatedListIndex=null,persistentPhoneAdIndex=null;
	function isDeferredGarsoPageKey(key) {return String(key).startsWith(GARSO_TOPIC_PAGE_CACHE_PREFIX);}
	function makePersistentCacheMetadata(record) {
		const key=String(record.key),value=record.value;
		const metadata={key,sizeBytes:Number(record.sizeBytes) || getPersistentCacheEntrySizeBytes(key,value)};
		if (/^vm_escort_ad_data_\d+$/.test(key)) {
			Object.assign(metadata,{status:value?.status,checkedAt:Number(value?.checkedAt) || 0,
				phone:getEscortAdPhoneDigits(value),lastVisitedAt:Number(value?.lastVisitedAt) || 0});
		} else if (/^vm_escorti_list_\d+$/.test(key)) {
			metadata.modes=Object.fromEntries(Object.entries(value?.modes || {}).map(([mode,entry]) => [mode,{
				adIds:Array.isArray(entry?.adIds) ? entry.adIds.map(String).filter(id => /^\d+$/.test(id)):[],
				profileIds:Array.isArray(entry?.profileIds) ? entry.profileIds.map(String).filter(id => /^\d+$/.test(id)):[],
				adLinksVersion:entry?.adLinksVersion,checkedAt:Number(entry?.checkedAt) || 0
			}]));
		}
		return metadata;
	}
	function notePersistentCacheMetadata(record) {
		if (!persistentCacheCatalog || !isIndexedDbCacheKey(record?.key) || isDeferredGarsoPageKey(record.key)) return;
		persistentCacheCatalog.set(record.key,makePersistentCacheMetadata(record));
		persistentRelatedListIndex=null; persistentPhoneAdIndex=null;
	}
	function getPersistentCacheRelatedListIndex() {
		if (persistentRelatedListIndex) return persistentRelatedListIndex;
		const result=new Map(),profiles=new Map();
		for (const [key,metadata] of persistentCacheCatalog || []) {
			const id=key.match(/^vm_escorti_list_(\d+)$/)?.[1];
			if (!id) continue;
			for (const relatedId of new Set([id,...Object.values(metadata.modes || {}).flatMap(mode => mode.adIds || [])])) {
				if (!result.has(relatedId)) result.set(relatedId,new Set());
				result.get(relatedId).add(key);
			}
		}
		for (const [key,metadata] of persistentCacheCatalog || []) {
			if (!/^vm_escorti_list_\d+$/.test(key)) continue;
			for (const mode of Object.values(metadata.modes || {})) {
				if (mode.profileIds?.length !== 1 || (mode.adIds?.length && mode.adLinksVersion !== 1)) continue;
				const profile=mode.profileIds[0];
				if (!profiles.has(profile)) profiles.set(profile,{keys:new Set(),ids:new Set()});
				profiles.get(profile).keys.add(key);
				for (const id of [key.slice('vm_escorti_list_'.length),...(mode.adIds || [])]) profiles.get(profile).ids.add(id);
			}
		}
		for (const profile of profiles.values()) for (const id of profile.ids) {
			if (!result.has(id)) result.set(id,new Set());
			for (const key of profile.keys) result.get(id).add(key);
		}
		return persistentRelatedListIndex=result;
	}
	function getPersistentCachePhoneAdIndex() {
		if (persistentPhoneAdIndex) return persistentPhoneAdIndex;
		const result=new Map();
		for (const [key,metadata] of persistentCacheCatalog || []) {
			const id=key.match(/^vm_escort_ad_data_(\d+)$/)?.[1];
			if (!id || metadata.status !== 'ok' || !metadata.phone) continue;
			if (!result.has(metadata.phone)) result.set(metadata.phone,new Set());
			result.get(metadata.phone).add(id);
		}
		return persistentPhoneAdIndex=result;
	}
	function readPersistentDbKeys(db,range=null,indexName=null) {
		return new Promise((resolve,reject) => {
			const transaction=db.transaction(PERSISTENT_CACHE_STORE_NAME,'readonly');
			const store=transaction.objectStore(PERSISTENT_CACHE_STORE_NAME);
			const request=(indexName ? store.index(indexName):store).getAllKeys(range);
			request.onsuccess=() => resolve(request.result || []);
			request.onerror=() => reject(request.error); transaction.onabort=() => reject(transaction.error);
		});
	}
	function readPersistentDbRecords(db,keys) {
		if (!keys.length) return Promise.resolve([]);
		return new Promise((resolve,reject) => {
			const transaction=db.transaction(PERSISTENT_CACHE_STORE_NAME,'readonly');
			const store=transaction.objectStore(PERSISTENT_CACHE_STORE_NAME),results=new Array(keys.length);
			keys.forEach((key,index) => {
				const request=store.get(key);
				request.onsuccess=() => {results[index]=request.result || null;};
			});
			transaction.oncomplete=() => resolve(results);
			transaction.onerror=() => reject(transaction.error); transaction.onabort=() => reject(transaction.error);
		});
	}
	async function refreshPersistentCacheCatalog(db,stored=null) {
		const startedAt=Date.now();
		const keys=await readPersistentDbKeys(db);
		const wanted=new Set(keys.filter(key => isIndexedDbCacheKey(key) && !isDeferredGarsoPageKey(key)));
		const entries=stored?.schema === 1 && Array.isArray(stored.entries) && stored.startedAt > 0 && stored.startedAt <= startedAt
			? new Map(stored.entries.filter(item => item?.key && wanted.has(item.key)).map(item => [item.key,item])):null;
		if (!entries) return null;
		const changed=await readPersistentDbKeys(db,IDBKeyRange.lowerBound(stored.startedAt),'updatedAt');
		const reload=[...new Set([...changed.filter(key => wanted.has(key)),...[...wanted].filter(key => !entries.has(key))])];
		for (let offset=0; offset < reload.length; offset += 128) {
			const records=await readPersistentDbRecords(db,reload.slice(offset,offset + 128));
			for (let index=0; index < records.length; index++) {
				const record=records[index];
				if (record) entries.set(record.key,makePersistentCacheMetadata(record));
				else entries.delete(reload[offset + index]);
			}
			if (offset + 128 < reload.length) await yieldHelperWork();
		}
		persistentCacheCatalog=entries; persistentCacheCatalogStartedAt=startedAt;
		persistentRelatedListIndex=null; persistentPhoneAdIndex=null;
		return {keys,entries};
	}
	function savePersistentCacheCatalog() {
		if (!persistentCacheDb || !persistentCacheCatalog || !persistentCacheCatalogStartedAt) return Promise.resolve();
		// Zachowaj początek skanu: późniejsze zapisy innych kart wymagają ponownego odczytu.
		const value={schema:1,startedAt:persistentCacheCatalogStartedAt,entries:[...persistentCacheCatalog.values()]};
		return enqueuePersistentCacheDbOperation(() => putPersistentCacheRecords(persistentCacheDb,[{
			key:PERSISTENT_STARTUP_INDEX_KEY,value,updatedAt:0,sizeBytes:0
		}]));
	}
	function persistentCacheKeyIsLoaded(key) {
		return !persistentCacheUsesIndexedDb || persistentCacheLoadedKeys.has(key) || persistentCacheMemory.has(key) ||
			(!deferredNonTopicCache && !isDeferredGarsoPageKey(key));
	}
	async function ensurePersistentCacheKeys(keys,{refresh=false}={}) {
		if (!persistentCacheUsesIndexedDb || !persistentCacheDb) return;
		const wanted=[...new Set(keys.filter(key => key && isIndexedDbCacheKey(key)))];
		const pending=[],fresh=[];
		for (const key of wanted) {
			if (persistentCacheKeyJobs.has(key)) pending.push(persistentCacheKeyJobs.get(key));
			else if (refresh || !persistentCacheKeyIsLoaded(key)) fresh.push(key);
		}
		if (fresh.length) {
			const epoch=persistentCacheClearEpoch;
			const generations=new Map(fresh.map(key => [key,persistentCacheMutations.get(key) || 0]));
			const job=(async () => {
				for (let offset=0; offset < fresh.length; offset += 96) {
					const batch=fresh.slice(offset,offset + 96);
					const records=await readPersistentDbRecords(persistentCacheDb,batch);
					for (let i=0; i < batch.length; i++) {
						const key=batch[i];
						if (epoch !== persistentCacheClearEpoch || (persistentCacheMutations.get(key) || 0) !== generations.get(key)) continue;
						if (records[i]) rememberPersistentCacheRecord(records[i]);
						else {persistentCacheMemory.delete(key); persistentCacheEntrySizeBytes.delete(key); persistentCacheCatalog?.delete(key);}
						persistentCacheLoadedKeys.add(key);
					}
					persistentRelatedListIndex=null; persistentPhoneAdIndex=null;
					if (offset + 96 < fresh.length) await yieldHelperWork();
				}
			})();
			for (const key of fresh) persistentCacheKeyJobs.set(key,job);
			pending.push(job);
			job.finally(() => {for (const key of fresh) if (persistentCacheKeyJobs.get(key) === job) persistentCacheKeyJobs.delete(key);}).catch(() => {});
		}
		await Promise.all(pending);
	}
	function getPersistentGarsoKeys(adId,phone='') {
		const address=buildGarsoAdLinkSearchTerm(adId),term=getGarsoSearchTerms(phone,'phone')[0] || '';
		return [garsoExtendedCacheKey(adId),garsoSearchCountCacheKey('address',address),garsoSummaryCacheKey('address',address),
			garsoSummaryCacheKey('combined',buildCombinedGarsoSummaryTerm(term,address)),
			...(term ? [garsoSearchCountCacheKey('phone',term),garsoSummaryCacheKey('phone',term),
				phoneReputationCacheKey(normalizePhoneReputationPhone(phone))]:[])].filter(Boolean);
	}
	async function ensureEscortPageCache(adIds,phone='') {
		if (!persistentCacheUsesIndexedDb) return;
		const ids=new Set(adIds.map(String).filter(id => /^\d+$/.test(id)));
		const listKeys=new Set(),related=getPersistentCacheRelatedListIndex();
		for (const id of ids) {
			listKeys.add(listCacheKey(id));
			for (const key of related.get(id) || []) listKeys.add(key);
		}
		await ensurePersistentCacheKeys([...listKeys,...[...ids].map(escortAdDataCacheKey)]);
		for (const key of listKeys) {
			for (const entry of Object.values(persistentCacheMemory.get(key)?.modes || {})) {
				for (const id of entry.adIds || []) if (/^\d+$/.test(String(id))) ids.add(String(id));
			}
		}
		const phoneIndex=getPersistentCachePhoneAdIndex();
		for (const id of [...ids]) {
			const digits=getEscortAdPhoneDigits(persistentCacheMemory.get(escortAdDataCacheKey(id))) || persistentCacheCatalog?.get(escortAdDataCacheKey(id))?.phone;
			if (digits && ['exact-phone','phone-escort-search'].includes(SETTINGS.escortAggregationMode)) {
				for (const other of phoneIndex.get(digits) || []) ids.add(other);
			}
		}
		await ensurePersistentCacheKeys([...ids].flatMap(id => [escortAdDataCacheKey(id),listCacheKey(id)]));
		await ensurePersistentCacheKeys([...ids].flatMap(id => getPersistentGarsoKeys(id,
			(adIds.map(String).includes(id) ? phone:'') || getEscortAdPhoneDigits(persistentCacheMemory.get(escortAdDataCacheKey(id))) || '')));
	}
	function getInitialEscortCacheIds() {
		const ids=[getEscortAdIdFromPath(),getEscortCachedArchiveMarkerFromHash()];
		if (location.pathname === '/') ids.push(readRecentEscortCachedAdNavigation());
		const context=getEscortClubListContext(document,location.href);
		for (const anchor of context?.anchors || []) ids.push(parseAdIdFromUrl(anchor.href));
		return [...new Set(ids.filter(Boolean))];
	}
	function notePersistentCacheMutation(key) {
		persistentCacheMutations.set(key,(persistentCacheMutations.get(key) || 0) + 1);
		persistentCacheLoadedKeys.add(key);
		persistentCacheChangedKeys.add(key);
		persistentRelatedListIndex=null; persistentPhoneAdIndex=null;
		if (!persistentCacheChangeTimer) persistentCacheChangeTimer=setTimeout(() => {
			persistentCacheChangeTimer=null;
			const keys=[...persistentCacheChangedKeys]; persistentCacheChangedKeys.clear();
			persistentCacheDbOperationChain.then(() => {
				writeHelperStorageValue(PERSISTENT_CACHE_CHANGE_KEY,{session:GARSO_SEARCH_INSTANCE_ID,keys,at:Date.now()});
			}).catch(() => {});
		},150);
	}
	function initPersistentCacheChanges() {
		if (!IS_ESCORT_CLUB || typeof GM_addValueChangeListener !== 'function') return;
		GM_addValueChangeListener(PERSISTENT_CACHE_CHANGE_KEY,(_key,_old,value) => {
			if (!value || value.session === GARSO_SEARCH_INSTANCE_ID || !Array.isArray(value.keys)) return;
			const keys=value.keys.filter(isIndexedDbCacheKey);
			ensurePersistentCacheKeys(keys,{refresh:true}).then(() => {
				invalidateEscortiProfileAdIndexMemory();
				document.dispatchEvent(new CustomEvent('vm-helper-cache-change',{detail:{keys}}));
			}).catch(error => log('Nie udało się odświeżyć cache z innej karty',error));
		});
	}
	function deferUntilFullPersistentCacheReady(action) {
		if (deferUntilPersistentCacheReady(action)) return true;
		if (!deferredNonTopicCache) return false;
		showWatchToast('Wczytywanie zapisanych danych…');
		ensureFullPersistentCache().then(() => {detachHelperNode(document.getElementById('vm-garso-watch-toast')); action();})
			.catch(error => {log('Nie udało się odczytać zapisanych danych',error); showWatchToast('Nie udało się odczytać danych. Spróbuj ponownie.',true);});
		return true;
	}

	function getPersistentCacheEntrySizeBytes(key,value) {
		try {
			const encoder=new TextEncoder();
			return encoder.encode(String(key || '')).length + encoder.encode(JSON.stringify(value) ?? '').length;
		} catch {
			return 0;
		}
	}
	let escortiProfileAdIndexMemory=null;
	function isIndexedDbCacheKey(key) {
		const value=String(key || '');
		return /^vm_garso_search_count_/i.test(value) || /^vm_garso_summary_/i.test(value) ||
			/^vm_garso_extended_\d+$/i.test(value) || /^vm_garso_topic_page_/i.test(value) ||
			/^vm_escorti_list_\d+$/i.test(value) || /^vm_escort_ad_data_\d+$/i.test(value) ||
			/^vm_phone_reputation_/i.test(value);
	}
	function openPersistentCacheDb() {
		return new Promise((resolve,reject) => {
			let indexedDbApi=null;
			try {
				indexedDbApi=globalThis.indexedDB || unsafeWindow?.indexedDB || null;
			} catch {
				indexedDbApi=globalThis.indexedDB || null;
			}
			if (!indexedDbApi) {
				reject(new Error('IndexedDB jest niedostępne'));
				return;
			}
			const request=indexedDbApi.open( PERSISTENT_CACHE_DB_NAME, PERSISTENT_CACHE_DB_VERSION );
			request.onupgradeneeded=() => {
				const db=request.result;
				const transaction=request.transaction;
				let store;
				if (!db.objectStoreNames.contains(PERSISTENT_CACHE_STORE_NAME)) {
					store=db.createObjectStore( PERSISTENT_CACHE_STORE_NAME, {keyPath:'key'} );
				} else {
					store=transaction.objectStore( PERSISTENT_CACHE_STORE_NAME );
				}
				if (!store.indexNames.contains('updatedAt')) {
					store.createIndex( 'updatedAt', 'updatedAt', {unique:false} );
				}
			};
			request.onsuccess=() => resolve(request.result);
			request.onerror=() => reject(request.error || new Error('Nie udało się otworzyć IndexedDB'));
			request.onblocked=() => reject(new Error('Otwarcie IndexedDB zostało zablokowane'));
		});
	}
	function waitForIndexedDbTransaction(transaction) {
		return new Promise((resolve,reject) => {
			transaction.oncomplete=() => resolve();
			transaction.onerror=() => reject( transaction.error || new Error('Błąd transakcji IndexedDB') );
			transaction.onabort=() => reject( transaction.error || new Error('Transakcja IndexedDB została przerwana') );
		});
	}
	async function loadPersistentCacheRecords(db,ranges,onBatch) {
		const batchSize=256;
		for (const initialRange of ranges) {
			let range=initialRange;
			while (true) {
				const records=await new Promise((resolve,reject) => {
					const transaction=db.transaction(PERSISTENT_CACHE_STORE_NAME,'readonly');
					const request=transaction.objectStore(PERSISTENT_CACHE_STORE_NAME).getAll(range,batchSize);
					request.onsuccess=() => resolve(request.result || []);
					request.onerror=() => reject(request.error || new Error('Błąd odczytu IndexedDB'));
					transaction.onabort=() => reject(transaction.error || new Error('Przerwano odczyt IndexedDB'));
				});
				onBatch(records);
				if (records.length < batchSize) break;
				const lastKey=records[records.length - 1].key;
				if (initialRange?.upper !== undefined && indexedDB.cmp(lastKey,initialRange.upper) >= 0) break;
				range=initialRange?.upper !== undefined
					? IDBKeyRange.bound(lastKey,initialRange.upper,true,initialRange.upperOpen)
					:IDBKeyRange.lowerBound(lastKey,true);
			}
		}
	}
	function rememberPersistentCacheRecord(record) {
		if (!record?.key || !isIndexedDbCacheKey(record.key)) return;
		persistentCacheMemory.set(record.key,record.value);
		persistentCacheLoadedKeys.add(record.key);
		notePersistentCacheMetadata(record);
		persistentCacheEntrySizeBytes.set(record.key, Number.isFinite(Number(record.sizeBytes))
				? Math.max(0,Number(record.sizeBytes))
				:getPersistentCacheEntrySizeBytes(record.key,record.value));
	}
	async function ensureFullPersistentCache() {
		if (!persistentCacheUsesIndexedDb || (!deferredNonTopicCache && !deferredTopicCacheRange)) return;
		if (persistentFullCachePromise) return persistentFullCachePromise;
		persistentFullCachePromise=(async () => {
			const loadRanges=ranges => {
				const epoch=persistentCacheClearEpoch,generations=new Map(persistentCacheMutations);
				return loadPersistentCacheRecords(persistentCacheDb,ranges,records => {
					for (const record of records) if (epoch === persistentCacheClearEpoch && (persistentCacheMutations.get(record.key) || 0) === (generations.get(record.key) || 0)) rememberPersistentCacheRecord(record);
				});
			};
			if (deferredNonTopicCache) {
				await loadRanges([IDBKeyRange.upperBound(GARSO_TOPIC_PAGE_CACHE_PREFIX,true),
					IDBKeyRange.lowerBound(GARSO_TOPIC_PAGE_CACHE_PREFIX + '\uffff',true)]);
				deferredNonTopicCache=false; escortDataFieldCatalogBootstrapped=false;
			}
			if (deferredTopicCacheRange) {
				await loadRanges([deferredTopicCacheRange]);
				deferredTopicCacheRange=null; deferredTopicCacheCount=0;
			}
			await rebuildWatchedAdDataMirrorFromCentralCache();
			savePersistentCacheStatsSnapshot(calculatePersistentCacheStats());
		})().finally(() => {persistentFullCachePromise=null;});
		return persistentFullCachePromise;
	}
	async function putPersistentCacheRecords(db,records) {
		if (!records.length) return;
		const transaction=db.transaction(PERSISTENT_CACHE_STORE_NAME,'readwrite');
		const store=transaction.objectStore(PERSISTENT_CACHE_STORE_NAME);
		for (const record of records) store.put(record);
		await waitForIndexedDbTransaction(transaction);
	}
	function enqueuePersistentCacheDbOperation(operation) {
		const result=persistentCacheDbOperationChain .catch(() => {}) .then(operation);
		persistentCacheDbOperationChain=result.catch(error => {
			log('Błąd zapisu trwałego cache w IndexedDB',error);
		});
		return result;
	}
	function readPersistentCacheValue(key,fallback=null) {
		const diagnosticCacheKey=isIndexedDbCacheKey(key);
		if (persistentCacheUsesIndexedDb && diagnosticCacheKey) {
			if (!persistentCacheKeyIsLoaded(key)) {
				return fallback;
			}
			const hit=persistentCacheMemory.has(key);
			recordDiagnosticCacheLookup(hit);
			return hit ? persistentCacheMemory.get(key):fallback;
		}
		try {
			const value=GM_getValue(key,fallback);
			if (diagnosticCacheKey) {
				recordDiagnosticCacheLookup( value !== null && value !== undefined && value !== fallback );
			}
			return value;
		} catch {
			if (diagnosticCacheKey) recordDiagnosticCacheLookup(false);
			return fallback;
		}
	}
	let escortAdCacheAvailabilityIndexMemory=null;
	let escortAdCacheAvailabilityIndexSaveTimer=null;
	if (!IS_ESCORT_CLUB && typeof GM_addValueChangeListener === 'function') {
		GM_addValueChangeListener(ESCORT_AD_CACHE_INDEX_STORAGE_KEY,() => {escortAdCacheAvailabilityIndexMemory=null;});
	}
	function readEscortAdCacheAvailabilityIndex() {
		if (escortAdCacheAvailabilityIndexMemory) {
			return escortAdCacheAvailabilityIndexMemory;
		}
		let ids=[];
		try {
			const stored=GM_getValue(ESCORT_AD_CACHE_INDEX_STORAGE_KEY,[]);
			ids=Array.isArray(stored) ? stored:[];
		} catch {}
		escortAdCacheAvailabilityIndexMemory=new Set( ids.map(String).filter(id => /^\d+$/.test(id)) );
		return escortAdCacheAvailabilityIndexMemory;
	}
	function persistEscortAdCacheAvailabilityIndex() {
		clearTimeout(escortAdCacheAvailabilityIndexSaveTimer);
		escortAdCacheAvailabilityIndexSaveTimer=null;
		try {
			writeHelperStorageValue( ESCORT_AD_CACHE_INDEX_STORAGE_KEY, [...readEscortAdCacheAvailabilityIndex()] );
		} catch {}
	}
	function scheduleEscortAdCacheAvailabilityIndexSave() {
		clearTimeout(escortAdCacheAvailabilityIndexSaveTimer);
		escortAdCacheAvailabilityIndexSaveTimer=setTimeout( persistEscortAdCacheAvailabilityIndex, 250 );
	}
	function updateEscortAdCacheAvailabilityIndexFromKey(key,available) {
		if (!IS_ESCORT_CLUB) return;
		const match=String(key || '').match(/^vm_escort_ad_data_(\d+)$/i);
		if (!match) return;
		const index=readEscortAdCacheAvailabilityIndex();
		if (available) index.add(match[1]);
		else index.delete(match[1]);
		scheduleEscortAdCacheAvailabilityIndexSave();
	}
	const escortForeignCacheIndices=new Map();
	function getEscortCachedAdHost(adId) {
		const id=String(adId || '');
		if (!/^\d+$/.test(id)) return '';
		if (readEscortAdCacheAvailabilityIndex().has(id)) return IS_ESCORT_CLUB ? CURRENT_HOST:'pl.escort.club';
		try {
			for (const key of GM_listValues()) {
				if (key === ESCORT_AD_CACHE_INDEX_STORAGE_KEY || !isStorageKeyInNamespace(key,'vm_escort_ad_cache_index')) continue;
				const host=key === 'vm_escort_ad_cache_index' ? 'pl.escort.club':key.slice('vm_escort_ad_cache_index_'.length);
				if (!isEscortClubHost(host)) continue;
				if (!escortForeignCacheIndices.has(key)) {
					const stored=GM_getValue(key,[]);
					escortForeignCacheIndices.set(key,new Set(Array.isArray(stored) ? stored.map(String):[]));
					if (typeof GM_addValueChangeListener === 'function') GM_addValueChangeListener(key,(_key,_old,value) => {
						escortForeignCacheIndices.set(key,new Set(Array.isArray(value) ? value.map(String):[]));
					});
				}
				if (escortForeignCacheIndices.get(key).has(id)) return host;
			}
		} catch {}
		return '';
	}
	function writePersistentCacheValue(key,value) {
		fallbackPersistentCacheStatsMemo=null;
		if (/^vm_escorti_list_\d+$/i.test(String(key || ''))) {
			invalidateEscortiProfileAdIndexMemory();
		}
		if ( /^vm_escort_ad_data_\d+$/i.test(String(key || '')) && value?.status === 'ok' && Number(value.checkedAt) > 0 ) {
			updateEscortAdCacheAvailabilityIndexFromKey(key,true);
		}
		if (!persistentCacheUsesIndexedDb || !isIndexedDbCacheKey(key)) {
			writeHelperStorageValue(key,value);
			schedulePersistentCacheStatsSnapshot();
			return Promise.resolve();
		}
		notePersistentCacheMutation(key);
		persistentCacheMemory.set(key,value);
		notePersistentCacheMetadata({key,value});
		const sizeBytes=getPersistentCacheEntrySizeBytes(key,value);
		persistentCacheEntrySizeBytes.set(key,sizeBytes);
		schedulePersistentCacheStatsSnapshot();
		return enqueuePersistentCacheDbOperation(async () => {
			await putPersistentCacheRecords(persistentCacheDb,[{
				key, value, updatedAt:Date.now(), sizeBytes
			}]);
		}).catch(() => undefined);
	}
	function deletePersistentCacheValue(key) {
		fallbackPersistentCacheStatsMemo=null;
		if (/^vm_escorti_list_\d+$/i.test(String(key || ''))) {
			invalidateEscortiProfileAdIndexMemory();
		}
		updateEscortAdCacheAvailabilityIndexFromKey(key,false);
		if (!persistentCacheUsesIndexedDb || !isIndexedDbCacheKey(key)) {
			try {deleteHelperStorageValue(key);} catch {}
			schedulePersistentCacheStatsSnapshot();
			return Promise.resolve();
		}
		notePersistentCacheMutation(key);
		persistentCacheCatalog?.delete(key);
		persistentCacheMemory.delete(key);
		persistentCacheEntrySizeBytes.delete(key);
		schedulePersistentCacheStatsSnapshot();
		return enqueuePersistentCacheDbOperation(async () => {
			const transaction=persistentCacheDb.transaction( PERSISTENT_CACHE_STORE_NAME, 'readwrite' );
			transaction.objectStore(PERSISTENT_CACHE_STORE_NAME).delete(key);
			await waitForIndexedDbTransaction(transaction);
		});
	}
	function listPersistentCacheKeys(pattern) {
		if (persistentCacheUsesIndexedDb) {
			return [...persistentCacheMemory.keys()].filter(key => pattern.test(key));
		}
		try {
			return GM_listValues().filter(key => pattern.test(key));
		} catch {
			return [];
		}
	}
	function invalidateEscortiProfileAdIndexMemory() {
		escortiProfileAdIndexMemory=null;
	}
	function getEscortiProfileAdIndexMemory() {
		if (escortiProfileAdIndexMemory) return escortiProfileAdIndexMemory;
		const profiles=new Map();
		for (const key of (persistentCacheCatalog ? [...persistentCacheCatalog.keys()].filter(key => /^vm_escorti_list_\d+$/.test(key)):getEscortListCacheKeys())) {
			const match=String(key).match(/^vm_escorti_list_(\d+)$/i);
			if (!match) continue;
			const store=persistentCacheCatalog?.get(key) || readPersistentCacheValue(key,null);
			if (!store?.modes || typeof store.modes !== 'object') continue;
			const sourceAdId=match[1];
			for (const entry of Object.values(store.modes)) {
				if (entry?.adIds?.length && entry.adLinksVersion !== 1) continue;
				const profileIds=[...new Set( (Array.isArray(entry?.profileIds) ? entry.profileIds:[])
						.map(value => String(value || '').trim()) .filter(value => /^\d+$/.test(value)) )];
				if (profileIds.length !== 1) continue;
				const profileId=profileIds[0];
				if (!profiles.has(profileId)) profiles.set(profileId,new Set());
				const adIds=profiles.get(profileId);
				adIds.add(sourceAdId);
				for (const adId of Array.isArray(entry?.adIds) ? entry.adIds:[]) {
					const normalizedId=String(adId || '').trim();
					if (/^\d+$/.test(normalizedId)) adIds.add(normalizedId);
				}
			}
		}
		escortiProfileAdIndexMemory=profiles;
		return profiles;
	}
	async function initializePersistentCacheStorage() {
		if (!IS_ESCORT_CLUB) return;
		const startedAt=Date.now();
		try {
			const db=await openPersistentCacheDb();
			persistentCacheDb=db;
			db.onversionchange=() => {db.close(); log('Cache zmienił wersję; odśwież kartę.');};
			const token=new URL(location.href).searchParams.get(PERSISTENT_CACHE_BRIDGE_PARAM);
			const request=token ? GM_getValue(persistentCacheBridgeRequestKey(token),null):null;
			if (request?.action === 'read-ad-data') {
				persistentCacheUsesIndexedDb=true; deferredNonTopicCache=true;
				await ensurePersistentCacheKeys((request.adIds || []).flatMap(id => [escortAdDataCacheKey(id),listCacheKey(id)]));
				return;
			}
			const topicEnd=GARSO_TOPIC_PAGE_CACHE_PREFIX + '\uffff';
			const topicRange=IDBKeyRange.bound(GARSO_TOPIC_PAGE_CACHE_PREFIX,topicEnd);
			const stored=(await readPersistentDbRecords(db,[PERSISTENT_STARTUP_INDEX_KEY]))[0]?.value;
			let fast=null;
			try {if (stored?.schema === 1) fast=await refreshPersistentCacheCatalog(db,stored);}
			catch (error) {log('Odbudowuję indeks uruchamiania cache',error);}
			if (fast) {
				try {
				deferredTopicCacheCount=fast.keys.filter(isDeferredGarsoPageKey).length;
				deferredTopicCacheRange=deferredTopicCacheCount ? topicRange:null;
				persistentCacheUsesIndexedDb=true; deferredNonTopicCache=true;
				await ensureEscortPageCache(getInitialEscortCacheIds(),getFormattedPhoneNumber() || '');
				} catch (error) {
					log('Odczyt selektywny nie powiódł się; wczytuję pełny cache',error);
					fast=null; deferredNonTopicCache=false;
					persistentCacheMemory.clear(); persistentCacheLoadedKeys.clear(); persistentCacheEntrySizeBytes.clear();
				}
			}
			if (!fast) {
				persistentCacheCatalog=new Map(); persistentCacheCatalogStartedAt=startedAt;
				await loadPersistentCacheRecords(db,[IDBKeyRange.upperBound(GARSO_TOPIC_PAGE_CACHE_PREFIX,true),
					IDBKeyRange.lowerBound(topicEnd,true)],records => {for (const record of records) rememberPersistentCacheRecord(record);});
				deferredTopicCacheCount=(await readPersistentDbKeys(db,topicRange)).length;
				deferredTopicCacheRange=deferredTopicCacheCount ? topicRange:null;
				persistentCacheUsesIndexedDb=true;
			}
			const adIds=new Set([...persistentCacheCatalog].filter(([key,value]) =>
				/^vm_escort_ad_data_\d+$/.test(key) && value.status === 'ok' && value.checkedAt > 0).map(([key]) => key.slice('vm_escort_ad_data_'.length)));
			const storedIds=GM_getValue(ESCORT_AD_CACHE_INDEX_STORAGE_KEY,[]);
			escortAdCacheAvailabilityIndexMemory=adIds;
			if (!Array.isArray(storedIds) || storedIds.length !== adIds.size || storedIds.some(id => !adIds.has(String(id)))) persistEscortAdCacheAvailabilityIndex();
			// Zapis indeksu nie blokuje gotowości UI.
			savePersistentCacheCatalog().catch(error => log('Nie udało się zapisać indeksu uruchamiania cache',error));
			if (!deferredTopicCacheRange && !deferredNonTopicCache) savePersistentCacheStatsSnapshot(calculatePersistentCacheStats());
		} catch (error) {
			persistentCacheDb?.close?.(); persistentCacheDb=null;
			deferredTopicCacheRange=null; deferredTopicCacheCount=0; deferredNonTopicCache=false;
			persistentCacheCatalog=null; persistentCacheUsesIndexedDb=false;
			persistentCacheMemory.clear(); persistentCacheEntrySizeBytes.clear(); persistentCacheLoadedKeys.clear();
			log('IndexedDB niedostępne - pozostawiam cache w Violentmonkey',error);
		}
	}
	function getGarsoSearchCountCacheKeys() {
		return listPersistentCacheKeys(/^vm_garso_search_count_/i);
	}
	function getGarsoExtendedCacheKeys() {
		return listPersistentCacheKeys(/^vm_garso_extended_\d+$/i);
	}
	function getGarsoSummaryCacheKeys() {
		return listPersistentCacheKeys(/^vm_garso_summary_/i);
	}
	function getGarsoTopicPageCacheKeys() {
		return listPersistentCacheKeys(/^vm_garso_topic_page_/i);
	}
	function getEscortListCacheKeys() {
		return listPersistentCacheKeys(/^vm_escorti_list_\d+$/i);
	}
	function getEscortAdDataCacheKeys() {
		return listPersistentCacheKeys(/^vm_escort_ad_data_\d+$/i);
	}
	function getPhoneReputationCacheKeys() {
		return listPersistentCacheKeys(/^vm_phone_reputation_/i);
	}
	function calculatePersistentCacheStats() {
		if (deferredTopicCacheRange || deferredNonTopicCache) {
			const snapshot=readPersistentCacheStatsSnapshot();
			if (snapshot) return snapshot;
		}
		if (!persistentCacheUsesIndexedDb && fallbackPersistentCacheStatsMemo) {
			return {...fallbackPersistentCacheStatsMemo};
		}
		const keys=persistentCacheUsesIndexedDb
			? (deferredNonTopicCache && persistentCacheCatalog ? [...persistentCacheCatalog.keys()]:[...persistentCacheMemory.keys()])
			:(() => {
				try {return GM_listValues().filter(isIndexedDbCacheKey);}
				catch {return [];}
			})();
		let entries=0;
		let garsoEntries=0;
		let listEntries=0;
		let escortAdDataEntries=0;
		let phoneReputationEntries=0;
		let bytes=0;
		for (const key of keys) {
			if (!isIndexedDbCacheKey(key)) continue;
			entries++;
			if ( /^vm_garso_search_count_/i.test(key) || /^vm_garso_summary_/i.test(key) ||
				/^vm_garso_extended_\d+$/i.test(key) || /^vm_garso_topic_page_/i.test(key) ) garsoEntries++;
			else if (/^vm_escorti_list_\d+$/i.test(key)) listEntries++;
			else if (/^vm_escort_ad_data_\d+$/i.test(key)) escortAdDataEntries++;
			else if (/^vm_phone_reputation_/i.test(key)) phoneReputationEntries++;
			if (persistentCacheUsesIndexedDb) {
				bytes += persistentCacheEntrySizeBytes.get(key) || persistentCacheCatalog?.get(key)?.sizeBytes || 0;
			} else {
				bytes += getPersistentCacheEntrySizeBytes( key, readPersistentCacheValue(key,null) );
			}
		}
		const stats={
			entries:entries + deferredTopicCacheCount, garsoEntries:garsoEntries + deferredTopicCacheCount, listEntries,
			escortAdDataEntries, phoneReputationEntries, bytes, megabytes:bytes / (1024 * 1024),
			backend:persistentCacheUsesIndexedDb ? 'IndexedDB':'Violentmonkey'
		};
		if (!persistentCacheUsesIndexedDb) fallbackPersistentCacheStatsMemo={...stats};
		return stats;
	}
	function savePersistentCacheStatsSnapshot(stats) {
		if (!IS_ESCORT_CLUB || deferredTopicCacheRange || deferredNonTopicCache) return;
		try {
			writeHelperStorageValue(PERSISTENT_CACHE_STATS_STORAGE_KEY,{
				...stats, updatedAt:Date.now()
			});
		} catch {}
	}
	function schedulePersistentCacheStatsSnapshot() {
		if (!IS_ESCORT_CLUB) return;
		clearTimeout(persistentCacheStatsSnapshotTimer);
		persistentCacheStatsSnapshotTimer=setTimeout(() => {
			persistentCacheStatsSnapshotTimer=null;
			savePersistentCacheStatsSnapshot(calculatePersistentCacheStats());
		},150);
	}
	function readPersistentCacheStatsSnapshot() {
		try {
			const stats=GM_getValue(PERSISTENT_CACHE_STATS_STORAGE_KEY,null);
			if (!stats || typeof stats !== 'object') return null;
			const bytes=Number(stats.bytes);
			const entries=Number(stats.entries);
			if (!Number.isFinite(bytes) || bytes < 0 || !Number.isFinite(entries) || entries < 0) {
				return null;
			}
			return {
				entries, garsoEntries:Math.max(0,Number(stats.garsoEntries) || 0),
				listEntries:Math.max(0,Number(stats.listEntries) || 0),
				escortAdDataEntries:Math.max(0,Number(stats.escortAdDataEntries) || 0), bytes, megabytes:bytes / (1024 * 1024),
				backend:stats.backend || 'IndexedDB', updatedAt:Number(stats.updatedAt) || 0
			};
		} catch {
			return null;
		}
	}
	function getSharedEscortActivityCacheKeys() {
		try {return GM_listValues().filter(key => key.startsWith(ESCORT_SHARED_ACTIVITY_CACHE_PREFIX));}
		catch {return [];}
	}
	function getPersistentCacheStats() {
		const base=(!IS_ESCORT_CLUB && readPersistentCacheStatsSnapshot()) || calculatePersistentCacheStats();
		const activity=getGmStorageStatsForKeys(getSharedEscortActivityCacheKeys());
		let templateKeys=[];
		try {templateKeys=GM_listValues().filter(key => key === 'vm_escort_ad_template_v1');} catch {}
		const template=getGmStorageStatsForKeys(templateKeys),bytes=base.bytes + activity.bytes + template.bytes;
		return {...base,entries:base.entries + activity.entries + template.entries,
			activityEntries:activity.entries,activityBytes:activity.bytes,templateEntries:template.entries,templateBytes:template.bytes,
			bytes,megabytes:bytes / (1024 * 1024)};
	}
	function formatCacheSize(megabytes) {
		const mb=Number(megabytes) || 0;
		const decimals=mb < 0.1 ? 3:2;
		return `${mb.toFixed(decimals).replace('.',',')} MB`;
	}
	function isStorageKeyInNamespace(key,canonicalKey) {
		const value=String(key || '');
		const canonical=String(canonicalKey || '');
		return !!canonical && (value === canonical || value.startsWith(`${canonical}_`));
	}
	function isAuxiliaryCacheStorageKey(key) {
		const value=String(key || '');
		return value === 'vm_escort_ad_template_v1' || value.startsWith(ESCORT_SHARED_ACTIVITY_CACHE_PREFIX) || isStorageKeyInNamespace(value,'vm_garso_cache_stats_snapshot') ||
			isStorageKeyInNamespace(value,WATCH_AD_DATA_MIRROR_STORAGE_KEY) ||
			isStorageKeyInNamespace(value,ESCORT_DATA_FIELD_CATALOG_STORAGE_KEY) ||
			isStorageKeyInNamespace(value,'vm_escort_ad_cache_index') ||
			value.startsWith(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX) ||
			value.startsWith(PERSISTENT_CACHE_BRIDGE_RESPONSE_PREFIX);
	}
	function isScriptCacheStorageKey(key) {
		return isIndexedDbCacheKey(String(key || '')) || isAuxiliaryCacheStorageKey(key);
	}
	function isRemovedDiagnosticStorageKey(key) {
		return key === 'vm_escort_ad_data_last_error' || key === 'vm_temp_garso_parser_samples_v1' || key.startsWith('vm_garso_request_trace_') ||
			key === 'vm_garso_search_queue_test_visible' || key.startsWith('vm_garso_search_queue_test:') ||
			key === 'vm_helper_request_audit_epoch' || key.startsWith('vm_helper_request_audit_v1:') ||
			['traffic','activity','live'].some(name => key.startsWith(`vm_helper_temporary_${name}_v1:`));
	}
	function clearRemovedDiagnosticStorage() {
		try {for (const key of GM_listValues()) if (isRemovedDiagnosticStorageKey(key)) deleteHelperStorageValue(key);}
		catch (error) {log('Nie udało się usunąć starej diagnostyki',error);}
	}
	function isTemporaryScriptStorageKey(key) {
		const value=String(key || '');
		if (['vm_escort_ad_request_v1_','vm_escort_ad_result_v1_','vm_escort_ad_consumer_v1_'].some(prefix => value.startsWith(prefix))) return true;
		return value === WATCH_CHECK_LOCK_KEY || value === DIAGNOSTICS_STORAGE_KEY + '_salt' || isRemovedDiagnosticStorageKey(value) ||
			value.startsWith('vm_helper_request_rates_v1:') || value.startsWith('vm_escort_activity_pending_v1_') ||
			value.startsWith('vm_escort_request_queue_') ||
			value.startsWith('vm_garso_watch_notification_claim:') || value.startsWith('vm_helper_cache_read_tab_') ||
			value.startsWith(GARSO_SEARCH_TICKET_PREFIX) || value.startsWith(GARSO_OPEN_BRIDGE_KEY_PREFIX) ||
			value.startsWith('vm_escort_location_tab_') ||
			/^vm_escorti_(?:result|boot)_(?:list|watch)_/i.test(value);
	}
	function isTechnicalScriptStorageKey(key) {
		const value=String(key || '');
		return isAuxiliaryCacheStorageKey(value) || isStorageKeyInNamespace(value,DIAGNOSTICS_STORAGE_KEY) ||
			value === GARSO_SEARCH_GLOBAL_SLOT_KEY || value === GARSO_SEARCH_NATIVE_UNTIL_KEY ||
			value === ADAPTIVE_BACKOFF_STORAGE_KEY ||
			value === 'vm_escorti_profile_ad_index' || isTemporaryScriptStorageKey(value);
	}
	function getTechnicalScriptStorageKeys() {
		try {
			return GM_listValues().filter(isTechnicalScriptStorageKey);
		} catch {
			return [];
		}
	}
	function getScriptDataStorageKeys() {
		try {
			return GM_listValues().filter(key => !isScriptCacheStorageKey(key) && !isTemporaryScriptStorageKey(key) );
		} catch {
			return [];
		}
	}
	function getGmStorageStatsForKeys(keys) {
		const uniqueKeys=[...new Set(Array.isArray(keys) ? keys:[])];
		let bytes=0;
		const encoder=new TextEncoder();
		for (const key of uniqueKeys) {
			try {
				const value=GM_getValue(key,null);
				const serialized=JSON.stringify(value) ?? String(value ?? '');
				bytes += encoder.encode(String(key)).length + encoder.encode(serialized).length;
			} catch {}
		}
		return {entries:uniqueKeys.length,bytes};
	}
	function getScriptDataStats() {
		return getGmStorageStatsForKeys(getScriptDataStorageKeys());
	}
	function getUserDataStorageKeys() {
		return getScriptDataStorageKeys().filter( key => !isTechnicalScriptStorageKey(key) );
	}
	function getUserDataStats() {
		return getGmStorageStatsForKeys(getUserDataStorageKeys());
	}
	function getTechnicalScriptDataStats() {
		return getGmStorageStatsForKeys(getTechnicalScriptStorageKeys());
	}
	function getViolentmonkeyStorageStats() {
		try {
			return getGmStorageStatsForKeys(GM_listValues());
		} catch {
			return {entries:0,bytes:0};
		}
	}
	async function clearScriptDataStorage() {
		const keys=getScriptDataStorageKeys();
		await Promise.all(keys.map(async key => {
			await GM.deleteValue(key);
			invalidateHelperReadCache(key);
		}));
		try {sessionStorage.removeItem(ESCORT_LIST_PAGES_RELOAD_KEY);} catch {}
		try {sessionStorage.removeItem(ESCORT_MULTI_LOCATION_SESSION_KEY);} catch {}
		return keys.length;
	}
	function clearTechnicalScriptDataStorage() {
		const keys=getTechnicalScriptStorageKeys().filter(key =>
			key !== GARSO_SEARCH_GLOBAL_SLOT_KEY && key !== GARSO_SEARCH_NATIVE_UNTIL_KEY && !key.startsWith(GARSO_SEARCH_TICKET_PREFIX));
		clearTimeout(escortDataFieldCatalogSaveTimer);
		escortDataFieldCatalogSaveTimer=null;
		clearTimeout(escortAdCacheAvailabilityIndexSaveTimer);
		escortAdCacheAvailabilityIndexSaveTimer=null;
		clearTimeout(persistentCacheStatsSnapshotTimer);
		persistentCacheStatsSnapshotTimer=null;
		if (diagnosticsSaveTimer) {
			clearTimeout(diagnosticsSaveTimer);
			diagnosticsSaveTimer=null;
		}
		for (const key of keys) {
			try {deleteHelperStorageValue(key);} catch {}
		}
		try {watchedAdDataMirrorMemory=null;} catch {}
		escortDataFieldCatalogMemory=null;
		escortDataFieldCatalogBootstrapped=false;
		escortAdCacheAvailabilityIndexMemory=null;
		invalidateEscortiProfileAdIndexMemory();
		fallbackPersistentCacheStatsMemo=null;
		clearDiagnostics();
		clearSharedEscortActivityCache();
		return keys.length;
	}
	function clearCacheEntries(getKeys,errorMessage,afterClear=null) {
		try {
			const keys=getKeys();
			for (const key of keys) deletePersistentCacheValue(key);
			afterClear?.();
			return keys.length;
		} catch (error) {
			log(errorMessage,error);
			return 0;
		}
	}
	function clearGarsoCache() {
		return clearCacheEntries( () => [ ...getGarsoSearchCountCacheKeys(), ...getGarsoSummaryCacheKeys(),
				...getGarsoExtendedCacheKeys(), ...getGarsoTopicPageCacheKeys() ], 'Błąd czyszczenia cache Garsoniery' );
	}
	function clearEscortListCache() {
		clearEscortiRecentProfileData();
		const removed=clearCacheEntries( getEscortListCacheKeys, 'Błąd czyszczenia cache Escorti' ) + clearSharedEscortActivityCache();
		invalidateEscortiProfileAdIndexMemory();
		return removed;
	}
	function clearEscortAdDataCache() {
		return clearCacheEntries( () => [...getEscortAdDataCacheKeys(),
			...GM_listValues().filter(key => key === 'vm_escort_ad_template_v1')], 'Błąd czyszczenia cache danych Escort.club' );
	}
	function clearPhoneReputationCache() {
		return clearCacheEntries( getPhoneReputationCacheKeys, 'Błąd czyszczenia cache sprawdzania numerów telefonu' );
	}
	function clearPersistentCacheAuxiliaryData() {
		clearEscortiRecentProfileData();
		clearSharedEscortActivityCache();
		clearTimeout(escortDataFieldCatalogSaveTimer);
		escortDataFieldCatalogSaveTimer=null;
		clearTimeout(escortAdCacheAvailabilityIndexSaveTimer);
		escortAdCacheAvailabilityIndexSaveTimer=null;
		try {
			for (const key of GM_listValues()) {
				if ( key === 'vm_escort_ad_template_v1' || isStorageKeyInNamespace(key,WATCH_AD_DATA_MIRROR_STORAGE_KEY) ||
					isStorageKeyInNamespace(key,ESCORT_DATA_FIELD_CATALOG_STORAGE_KEY) || key === ESCORT_AD_CACHE_INDEX_STORAGE_KEY
				) deleteHelperStorageValue(key);
			}
		} catch {}
		try {watchedAdDataMirrorMemory=null;} catch {}
		escortDataFieldCatalogMemory=null;
		escortDataFieldCatalogBootstrapped=false;
		escortAdCacheAvailabilityIndexMemory=null;
		invalidateEscortiProfileAdIndexMemory();
	}
	function clearPersistentCache() {
		if (persistentCacheUsesIndexedDb) {
			const entries=persistentCacheMemory.size;
			persistentCacheMemory.clear();
			persistentCacheEntrySizeBytes.clear();
			persistentCacheClearEpoch++;
			for (const key of persistentCacheCatalog?.keys() || []) notePersistentCacheMutation(key);
			persistentCacheCatalog?.clear(); persistentCacheLoadedKeys.clear();
			deferredNonTopicCache=false; deferredTopicCacheRange=null; deferredTopicCacheCount=0;
			persistentRelatedListIndex=null; persistentPhoneAdIndex=null;
			try {escortAdDataMemoryCache.clear();} catch {}
			fallbackPersistentCacheStatsMemo=null;
			clearPersistentCacheAuxiliaryData();
			savePersistentCacheStatsSnapshot(calculatePersistentCacheStats());
			enqueuePersistentCacheDbOperation(async () => {
				const transaction=persistentCacheDb.transaction( PERSISTENT_CACHE_STORE_NAME, 'readwrite' );
				transaction.objectStore(PERSISTENT_CACHE_STORE_NAME).clear();
				await waitForIndexedDbTransaction(transaction);
			});
			return entries;
		}
		const removed=clearGarsoCache() + clearEscortListCache() + clearEscortAdDataCache() + clearPhoneReputationCache();
		clearPersistentCacheAuxiliaryData();
		try {escortAdDataMemoryCache.clear();} catch {}
		return removed;
	}
	function persistentCacheBridgeRequestKey(token) {
		return `${PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX}${token}`;
	}
	function persistentCacheBridgeResponseKey(token) {
		return `${PERSISTENT_CACHE_BRIDGE_RESPONSE_PREFIX}${token}`;
	}
	async function handlePersistentCacheBridgePage(sharedStateReady=false) {
		if (!IS_ESCORT_CLUB) return false;
		const pageUrl=new URL(location.href);
		const token=pageUrl.searchParams.get(PERSISTENT_CACHE_BRIDGE_PARAM);
		if (!token) return false;
		const requestKey=persistentCacheBridgeRequestKey(token);
		const responseKey=persistentCacheBridgeResponseKey(token);
		const request=GM_getValue(requestKey,null);
		if (request?.action === 'read-ad-data' && !sharedStateReady) return false;
		if (request?.action === 'read-ad-data' || GM_getValue(persistentCacheReadTabKey(location.hostname),null)?.token === token) {
			initEscortAdCacheReader(token);
			await servePersistentCacheAdRequest(token,true);
			return true;
		}
		let response;
		try {
			if ( !request || typeof request !== 'object' || Date.now() - Number(request.createdAt || 0) > 2 * 60 * 1000 ) {
				throw new Error('Żądanie dostępu do cache wygasło lub jest nieprawidłowe.');
			}
			await ensureFullPersistentCache();
			let removed=0;
			if (request.action === 'clear-all') {
				removed=clearPersistentCache();
			} else if (request.action === 'clear-expired') {
				removed=clearExpiredPersistentCache({
					garsoTtlMs:Math.max(0,Number(request.garsoTtlMs) || getGarsoCacheTtlMs()),
					escortTtlMs:Math.max(0,Number(request.escortTtlMs) || getListCacheTtlMs())
				});
			} else {
				throw new Error('Nieznana operacja cache.');
			}
			await persistentCacheDbOperationChain.catch(() => {});
			const stats=calculatePersistentCacheStats();
			savePersistentCacheStatsSnapshot(stats);
			response={ok:true,removed,stats,completedAt:Date.now()};
		} catch (error) {
			response={
				ok:false, error:formatHelperDiagnosticError(error), completedAt:Date.now()
			};
		}
		try {deleteHelperStorageValue(requestKey);} catch {}
		try {writeHelperStorageValue(responseKey,response);} catch {}
		setTimeout(() => {
			try {window.close();} catch {}
		},80);
		return true;
	}
	function initEscortAdCacheReader(auxiliaryToken='') {
		if (!IS_ESCORT_CLUB || window.top !== window.self || persistentCacheReaderInstalled || typeof GM_addValueChangeListener !== 'function') return;
		persistentCacheReaderInstalled=true;
		const ownerKey=persistentCacheReaderKey(location.hostname);
		let heartbeat=null,listenerId=null,idleSince=0,stopped=false;
		const publish=() => {
			if (stopped) return;
			if (auxiliaryToken) {
				const key=persistentCacheReadTabKey(location.hostname),lease=GM_getValue(key,null);
				const now=Date.now(),pending=findPendingPersistentCacheReadToken(location.hostname);
				if (pending) idleSince=0;
				else if (!idleSince) idleSince=now;
				// Brak żywych żądań kończy również odczyt zawieszony w IndexedDB.
				if ((idleSince && now - idleSince >= 2000) || (lease?.token !== auxiliaryToken && !persistentCacheReaderBusy)) {
					stopped=true;
					clearInterval(heartbeat);
					if (listenerId != null && typeof GM_removeValueChangeListener === 'function') GM_removeValueChangeListener(listenerId);
					if (lease?.token === auxiliaryToken) deleteHelperStorageValue(key);
					if (GM_getValue(ownerKey,null)?.session === GARSO_SEARCH_INSTANCE_ID) deleteHelperStorageValue(ownerKey);
					try {window.close();} catch {}
					return;
				}
			}
			const owner=getReadyPersistentCacheReader(location.hostname);
			if (auxiliaryToken || !owner || owner.session === GARSO_SEARCH_INSTANCE_ID) writeHelperStorageValue(ownerKey,
				{protocol:1,host:location.hostname,session:GARSO_SEARCH_INSTANCE_ID,ready:true,auxiliary:!!auxiliaryToken,token:auxiliaryToken,at:Date.now()});
		};
		publish();
		if (stopped) return;
		heartbeat=setInterval(publish,5000);
		window.addEventListener('pageshow',event => {
			if (event.persisted) {publish(); clearInterval(heartbeat); if (!stopped) heartbeat=setInterval(publish,5000);}
		});
		listenerId=GM_addValueChangeListener(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX + 'signal',(_key,_old,token) => {
			servePersistentCacheAdRequest(token).catch(error => log('Błąd odbiorcy cache',error));
		});
		window.addEventListener('pagehide',() => {
			clearInterval(heartbeat);
			if (getReadyPersistentCacheReader(location.hostname)?.session === GARSO_SEARCH_INSTANCE_ID) deleteHelperStorageValue(ownerKey);
		});
		for (const key of GM_listValues()) if (key.startsWith(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX) &&
			!key.includes('reader_') && !key.endsWith('signal')) servePersistentCacheAdRequest(key.slice(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX.length)).catch(() => {});
	}
	function requestPersistentCacheBridgeAction(action,options={}) {
		if (action !== 'read-ad-data' || IS_ESCORT_CLUB) return requestPersistentCacheBridgeDirect(action,options);
		if (options.cancelToken?.cancelled) return Promise.reject(createOperationCancelledError(options.cancelToken.reason));
		const host=String(options.host || 'pl.escort.club').toLowerCase();
		let batch=persistentReadBridgeJobs.get(host);
		if (!batch) {
			batch={consumers:new Set(),token:createOperationCancelToken('Odczyt cache Escort.club')};
			persistentReadBridgeJobs.set(host,batch);
			setTimeout(async () => {
				if (persistentReadBridgeJobs.get(host) === batch) persistentReadBridgeJobs.delete(host);
				if (!batch.consumers.size) return;
				try {
					const adIds=[...new Set([...batch.consumers].flatMap(item => item.ids))];
					const result=await requestPersistentCacheBridgeDirect(action,{host,adIds,cancelToken:batch.token});
					for (const item of batch.consumers) {item.unsubscribe?.(); item.resolve(copyWatchedValue({...result,ads:(result.ads || []).filter(ad => item.ids.includes(String(ad.adData?.adId)))}));}
				} catch (error) {for (const item of batch.consumers) {item.unsubscribe?.(); item.reject(error);}}
				finally {batch.consumers.clear();}
			},20);
		}
		return new Promise((resolve,reject) => {
			const item={resolve,reject,ids:(options.adIds || []).map(String).filter(id => /^\d+$/.test(id))};
			batch.consumers.add(item);
			item.unsubscribe=options.cancelToken?.onCancel(reason => {
				batch.consumers.delete(item); reject(createOperationCancelledError(reason));
				if (!batch.consumers.size) {if (persistentReadBridgeJobs.get(host) === batch) persistentReadBridgeJobs.delete(host); batch.token.cancel(reason);}
			});
		});
	}
	async function runPersistentCacheClearAcrossDomains( action, options={} ) {
		if (!IS_ESCORT_CLUB) {
			return requestPersistentCacheBridgeAction( action, options );
		}
		await ensureFullPersistentCache();
		const removed= action === 'clear-expired'
				? clearExpiredPersistentCache(options)
				:clearPersistentCache();
		await persistentCacheDbOperationChain.catch(() => {});
		const stats=calculatePersistentCacheStats();
		savePersistentCacheStatsSnapshot(stats);
		return {ok:true,removed,stats};
	}
	async function clearPersistentCacheAcrossDomains() {
		return runPersistentCacheClearAcrossDomains( 'clear-all' );
	}
	async function clearExpiredPersistentCacheAcrossDomains(options={}) {
		return runPersistentCacheClearAcrossDomains( 'clear-expired', options );
	}
	function isPersistentCacheValueExpired(value,ttlMs,now=Date.now()) {
		const checkedAt=Number(value?.checkedAt);
		return !Number.isFinite(checkedAt) || checkedAt <= 0 || now - checkedAt >= ttlMs;
	}
	function scanExpiredPersistentCache({
		garsoTtlMs=getGarsoCacheTtlMs(), escortTtlMs=getListCacheTtlMs()
	}={}) {
		if ( !IS_ESCORT_CLUB && readPersistentCacheStatsSnapshot()?.backend === 'IndexedDB' ) {
			return {
				available:false, entries:0, bytes:0, megabytes:0, actions:[], listCacheChanged:false
			};
		}
		const now=Date.now();
		const actions=[];
		let entries=0;
		let bytes=0;
		let listCacheChanged=false;
		const readValue=key => {
			if ( persistentCacheUsesIndexedDb && isIndexedDbCacheKey(key) ) {
				return persistentCacheMemory.has(key)
					? persistentCacheMemory.get(key)
					:null;
			}
			try {
				return GM_getValue(key,null);
			} catch {
				return null;
			}
		};
		const entrySize=(key,value) => {
			if (persistentCacheUsesIndexedDb) {
				return ( persistentCacheEntrySizeBytes.get(key) || getPersistentCacheEntrySizeBytes(key,value) );
			}
			return getPersistentCacheEntrySizeBytes(key,value);
		};
		const addDelete=(key,value,count=1) => {
			const normalizedCount=Math.max( 1, Number(count) || 1 );
			const actionBytes=entrySize(key,value);
			actions.push({
				type:'delete', key, count:normalizedCount, bytes:actionBytes
			});
			entries += normalizedCount;
			bytes += actionBytes;
		};
		const addWrite=( key, currentValue, nextValue, count ) => {
			const normalizedCount=Math.max( 1, Number(count) || 1 );
			const currentBytes= entrySize(key,currentValue);
			const nextBytes= getPersistentCacheEntrySizeBytes( key, nextValue );
			const actionBytes=Math.max( 0, currentBytes - nextBytes );
			actions.push({
				type:'write', key, value:nextValue, count:normalizedCount, bytes:actionBytes
			});
			entries += normalizedCount;
			bytes += actionBytes;
		};
		for (const key of getSharedEscortActivityCacheKeys()) {
			const value=readValue(key);
			if (!isSharedEscortActivityEntryValid(value,now)) addDelete(key,value);
		}
		const garsoKeys=[ ...getGarsoSearchCountCacheKeys(), ...getGarsoSummaryCacheKeys(), ...getGarsoExtendedCacheKeys(),
			...getGarsoTopicPageCacheKeys() ];
		for (const key of garsoKeys) {
			const value=readValue(key);
			if ( isPersistentCacheValueExpired( value, garsoTtlMs, now ) ) {
				addDelete(key,value);
			}
		}
		for (const key of getEscortListCacheKeys()) {
			const value=readValue(key);
			const sourceModes= value?.modes && typeof value.modes === 'object'
					? value.modes
					:{};
			const freshModes=Object.fromEntries( Object.entries(sourceModes).filter( ([,entry]) =>
						!isPersistentCacheValueExpired( entry, escortTtlMs, now ) ) );
			const expiredModes= Object.keys(sourceModes).length - Object.keys(freshModes).length;
			if (!Object.keys(freshModes).length) {
				addDelete( key, value, Math.max(1,expiredModes) );
				listCacheChanged=true;
			} else if (expiredModes > 0) {
				addWrite( key, value, {
						...value, modes:freshModes
					}, expiredModes );
				listCacheChanged=true;
			}
		}
		const watchedAdIds=new Set( getWatchedItems().flatMap(item => getWatchedItemAdUrls(item,false) .map(parseAdIdFromUrl)
					.filter(Boolean) .map(String) ) );
		for (const key of getEscortAdDataCacheKeys()) {
			const value=readValue(key);
			if ( !isPersistentCacheValueExpired( value, escortTtlMs, now ) ) continue;
			const adId= String(key).match( /(\d+)(?!.*\d)/ )?.[1] || '';
			if ( adId && watchedAdIds.has(adId) ) continue;
			addDelete(key,value);
		}
		for (const key of getPhoneReputationCacheKeys()) {
			const value=readValue(key);
			if ( isPersistentCacheValueExpired( value, escortTtlMs, now ) ) {
				addDelete(key,value);
			}
		}
		return {
			available:true, entries, bytes, megabytes:bytes / (1024 * 1024), actions, listCacheChanged
		};
	}
	function getExpiredPersistentCacheStats( options={} ) {
		const scan= scanExpiredPersistentCache(options);
		return {
			available:scan.available, entries:scan.entries, bytes:scan.bytes, megabytes:scan.megabytes
		};
	}
	function clearExpiredPersistentCache( options={} ) {
		const scan= scanExpiredPersistentCache(options);
		if (!scan.available) return 0;
		for (const action of scan.actions) {
			if (action.type === 'write') {
				writePersistentCacheValue( action.key, action.value );
			} else {
				deletePersistentCacheValue( action.key );
			}
		}
		if (scan.listCacheChanged) {
			invalidateEscortiProfileAdIndexMemory();
		}
		return scan.entries;
	}
	let persistentCacheReady=!IS_ESCORT_CLUB;
	const persistentCacheInitialization=initializePersistentCacheStorage().finally(() => {
		persistentCacheReady=true;
	});
	let pendingCacheUiAction=null;
	function deferUntilPersistentCacheReady(action) {
		if (persistentCacheReady) return false;
		if (!pendingCacheUiAction) {
			showWatchToast('Wczytywanie zapisanych danych…');
			persistentCacheInitialization.then(() => {
				const resume=pendingCacheUiAction;
				pendingCacheUiAction=null;
				detachHelperNode(document.getElementById('vm-garso-watch-toast'));
				resume?.();
			});
		}
		pendingCacheUiAction=action;
		return true;
	}
	const BUTTON_COLORS={
		checking:'#777777', found:'#28a745', empty:'#2878c8', error:'#dc3545'
	};
	const CATEGORY_DEFS=[ {
			key:'appointment', label:'Umówienie', patterns:[
				'kontakt / umówienie','kontakt/umówienie','umówienie się','umowienie sie',
				'the date','setup','umówienie','umowienie','umawianie','ustawka','kontakt','formalizmy' ]
		}, {
			key:'location', label:'Lokalizacja', patterns:[ 'miejsce spotkania','lokalizacja','lokalizacjia','mieszkanie',
				'the place','apartment','miejscówka','miejscowka','lokum','lokal','miejsce','łazienka','lazienka' ]
		}, {
			key:'girl', label:'Wygląd', patterns:[
				'ocena dziewczyny','ocena wyglądu','ocena wygladu','wygląd dziewczyny','wyglad dziewczyny','wygląd panny','wyglad panny',
				'wygląd pani','wyglad pani','zgodność ze zdjęciami','zgodnosc ze zdjeciami',
				'wygląd laski','wyglad laski','wygląd','wyglad','appearance','masażystka','masazystka',
				'dziewoja','dziewczyna','bohaterka','figura','uroda','ciało','cialo','twarz',
				'buzia','panna','pani','diva','laska','osoba' ]
		}, {
			key:'oral', label:'Francuz', patterns:[ 'seks oralny','sex oralny','francuz bez gumki','francuz w gumce','francuski','francuz',
				'fbg / fwg','fwg / fbg','fbg/fwg','fwg/fbg','fbg','fwg',
				'fgb','bj','blowjob','oral','lodowanie','lodzik','francja','lód','lod' ]
		}, {
			key:'action', label:'Akcja', patterns:[ 'zgodność usługi z ofertą','zgodnosc uslugi z oferta','przebieg spotkania',
				'akcja / serwis','akcja + fbg','akcja / seks','akcja / sex','akcja/fbg',
				'akcja/seks','akcja/sex','seks/akcja','sex/akcja','sex/anal',
				'całość akcji','calosc akcji','akcja właściwa','akcja wlasciwa',
				'anal fingering','anal sex','feet licking','zakończenie','zakonczenie',
				'usługi','uslugi','przebieg','klasyczny','seks analny','sex analny','klasyka','klasyk', 'rimming','fisting','uległość','uleglosc','serwis',
				'action','zabawy','analny','akcja','seks','sex','anal','piss' ]
		}, {key:'massage',label:'Masaż',patterns:['masaż','masaz','massage']}, {
			key:'atmosphere', label:'Atmosfera', shortPatterns:['charakter'], patterns:[
				'podejście do klienta','podejscie do klienta','klimat spotkania',
				'erotyka','the atmosphere','atmosphere','atmosfera','aranżacja','aranzacja','klimat','atmo' ]
		}, {
			key:'summary', label:'Podsumowanie', overall:true, patterns:[
				'ogólna ocena','ogolna ocena','ocena ogólna','ocena ogolna', 'wrażenie ogólne','wrazenie ogolne',
				'overall','całościowo','calosciowo','podsumowując','podsumowujac','podsumowanie','całokształt','caloksztalt',
				'ogółem','ogolem','ogólnie','ogolnie','całość','calosc', 'spotkanie','ogół','ogol','ocena' ]
		} ];
	const summaryCache=new Map();
	let garsoAuthPromise=null;
	let garsoRequestQueue=Promise.resolve();
	const garsoAutoSearchJobs=new Map();
	let garsoTopicStartQueue=Promise.resolve();
	let garsoTopicLastStartedAt=0;
	let garsoTopicGapMs=GARSO_TOPIC_MIN_GAP_MS;
	let garsoTopicSuccessStreak=0;
	let garsoTopicActiveRequests=0;
	const garsoTopicRequestWaiters=[];
	const enqueueListJob=createConcurrentJobQueue( LIST_MAX_CONCURRENT, 'Błąd kolejki listy' );
	const enqueueEscortAdDataJob=createConcurrentJobQueue(() => Math.max(ESCORT_AD_DATA_MAX_CONCURRENT,
		normalizeEscortQueueSettings(readHelperSettingsSnapshot()).escortMaxConcurrent),
		'Błąd kolejki danych Escort.club');
	const escortAdDataMemoryCache=new Map();
	const escortAdDataInflight=new Map();
	const pendingEscortiActivityCacheWrites=new Map();
	function yieldHelperWork() {
		if (typeof globalThis.scheduler?.yield === 'function') return globalThis.scheduler.yield();
		return new Promise(resolve => setTimeout(resolve,0));
	}
	window.addEventListener('pagehide',() => {
		garsoSearchPageClosed=true;
		deleteHelperStorageValue(GARSO_SEARCH_TICKET_PREFIX + GARSO_SEARCH_INSTANCE_ID);
		flushDiagnostics();
	});
	window.addEventListener('pageshow',() => {garsoSearchPageClosed=false;});
	document.addEventListener('visibilitychange',() => {
		if (document.visibilityState === 'hidden') {flushDiagnostics();}
	});
	function createEmptyDiagnostics() {
		return {updatedAt:Date.now(),counters:{requests:0,garsoRequests:0,escortiRequests:0,escortClubRequests:0,
			garsoRetries:0,http429:0,antiflood:0,errors:0,parserIssues:0,parserEscortClub:0,parserEscorti:0,parserGarso:0,
			parserOther:0,cacheLookups:0,cacheHits:0,cacheMisses:0},requestBuckets:{},network:{escortClub:{
			lastSuccessAt:0,lastErrorAt:0,lastErrorStatus:0,last429At:0,last503At:0,last403At:0}},
			watch:{lastScheduledRunAt:0,lastScheduledFullRunAt:0},errors:[],parserIssues:[]};
	}
	function normalizeDiagnostics(value) {
		const result=createEmptyDiagnostics(),source=value && typeof value === 'object' ? value:{};
		const count=value => Number.isFinite(Number(value)) && Number(value) >= 0 ? Number(value):0;
		for (const [target,input] of [[result.counters,source.counters],[result.network.escortClub,source.network?.escortClub],[result.watch,source.watch]])
			for (const key of Object.keys(target)) target[key]=count(input?.[key]);
		const network=result.network.escortClub;
		if ([403,503].includes(network.lastErrorStatus)) network[`last${network.lastErrorStatus}At`]=Math.max(network[`last${network.lastErrorStatus}At`],network.lastErrorAt);
		result.updatedAt=count(source.updatedAt);
		const now=Date.now(),cutoff=now - DIAGNOSTICS_REQUEST_BUCKET_RETENTION_MS;
		for (const [at,input] of Object.entries(source.requestBuckets || {})) {
			if (!Number.isSafeInteger(Number(at)) || Number(at) < cutoff || Number(at) > now) continue;
			result.requestBuckets[at]=Object.fromEntries(['requests','garso','escorti','escortClub','other','http429'].map(key => [key,count(input?.[key])]));
		}
		for (const key of ['errors','parserIssues']) result[key]=mergeDiagnosticEvents((Array.isArray(source[key]) ? source[key]:[])
			.slice(-100).filter(event => getDiagnosticEventTimestamp(event)).map(event => ({
				at:getDiagnosticEventTimestamp(event),scriptVersion:/^[\d.]{1,24}$/.test(event.scriptVersion || '') ? event.scriptVersion:'',
				area:sanitizeDiagnosticText(event.area),message:sanitizeDiagnosticText(event.message),origin:normalizeDiagnosticErrorOrigin(event.origin),
				count:Math.min(1000000000,Math.max(1,Math.floor(count(event.count)))),
				firstAt:Math.min(getDiagnosticEventTimestamp(event),getDiagnosticEventTimestamp({at:event.firstAt}) || getDiagnosticEventTimestamp(event))})));
		return result;
	}
	function getDiagnosticsState() {
		const epoch=String(GM_getValue(DIAGNOSTICS_EPOCH_KEY,'0'));
		if (!diagnosticsState || diagnosticsState.epoch !== epoch) diagnosticsState={...createEmptyDiagnostics(),epoch};
		return diagnosticsState;
	}
	function flushDiagnostics() {
		clearTimeout(diagnosticsSaveTimer); diagnosticsSaveTimer=null;
		if (!diagnosticsState) return;
		try {
			if (diagnosticsState.epoch !== String(GM_getValue(DIAGNOSTICS_EPOCH_KEY,'0'))) {diagnosticsState=null; return;}
			writeHelperStorageValue(DIAGNOSTICS_TAB_PREFIX + diagnosticsState.epoch + ':' + GARSO_SEARCH_INSTANCE_ID,diagnosticsState);
		} catch {}
	}
	function getMergedDiagnosticsState() {
		const local=getDiagnosticsState(),epoch=local.epoch,prefix=DIAGNOSTICS_TAB_PREFIX + epoch + ':';
		const merged=normalizeDiagnostics(epoch === '0' ? GM_getValue(DIAGNOSTICS_STORAGE_KEY,null):null);
		const states=[local,...GM_listValues().filter(key => key.startsWith(prefix) && key !== prefix + GARSO_SEARCH_INSTANCE_ID)
			.map(key => GM_getValue(key,null)).filter(state => state?.epoch === epoch)];
		for (const value of states) {
			const state=normalizeDiagnostics(value);
			for (const key of Object.keys(merged.counters)) merged.counters[key]+=state.counters[key];
			for (const [minute,bucket] of Object.entries(state.requestBuckets)) {
				const target=merged.requestBuckets[minute] ||= {};
				for (const [key,count] of Object.entries(bucket)) target[key]=(target[key] || 0) + count;
			}
			for (const key of ['errors','parserIssues']) merged[key].push(...state[key]);
			const network=merged.network.escortClub,next=state.network.escortClub;
			if (next.lastErrorAt > network.lastErrorAt) network.lastErrorStatus=next.lastErrorStatus;
			for (const key of ['lastSuccessAt','lastErrorAt','last429At','last503At','last403At']) network[key]=Math.max(network[key],next[key]);
			for (const key of Object.keys(merged.watch)) merged.watch[key]=Math.max(merged.watch[key],state.watch[key]);
			merged.updatedAt=Math.max(merged.updatedAt,state.updatedAt);
		}
		for (const key of ['errors','parserIssues']) merged[key]=mergeDiagnosticEvents(merged[key]);
		return merged;
	}
	function scheduleDiagnosticsSave() {
		if (!diagnosticsSaveTimer) diagnosticsSaveTimer=setTimeout(flushDiagnostics,DIAGNOSTICS_SAVE_DELAY_MS);
	}
	function incrementDiagnosticCounter(key,amount=1) {
		const state=getDiagnosticsState();
		if (!Object.prototype.hasOwnProperty.call(state.counters,key)) return;
		state.counters[key]=Math.max(0,state.counters[key] + (Number(amount) || 0));
		state.updatedAt=Date.now(); scheduleDiagnosticsSave();
	}
	function mergeDiagnosticEvents(events) {
		const groups=new Map();
		for (const event of events) {
			const frame=event.origin?.[0],key=JSON.stringify([event.scriptVersion,event.area,event.message,frame?.fn,frame?.line,frame?.column]);
			const old=groups.get(key);
			groups.set(key,{...(old && old.at > event.at ? old:event),
				count:Math.min(1000000000,(old?.count || 0) + (event.count || 1)),
				firstAt:Math.min(old?.firstAt || event.firstAt || event.at,event.firstAt || event.at)});
		}
		return [...groups.values()].sort((a,b) => a.at - b.at).slice(-DIAGNOSTICS_MAX_EVENTS);
	}
	function addDiagnosticEvent(bucket,area,message='',origin=[]) {
		const state=getDiagnosticsState();
		if (!['errors','parserIssues'].includes(bucket)) return;
		const now=Date.now();
		state[bucket]=mergeDiagnosticEvents([...state[bucket],{at:now,firstAt:now,count:1,scriptVersion:SCRIPT_VERSION,
			area:sanitizeDiagnosticText(area || 'skrypt'),message:sanitizeDiagnosticText(message),origin}]);
		state.updatedAt=Date.now(); scheduleDiagnosticsSave();
	}
	function recordDiagnosticError(area,error='') {
		if (isOperationCancelledError(error)) return;
		incrementDiagnosticCounter('errors');
		addDiagnosticEvent('errors',area,formatHelperDiagnosticError(error),getDiagnosticErrorOrigin(error));
	}
	function getDiagnosticEventTimestamp(event) {
		const numeric=Number(event?.at);
		const at=Number.isFinite(numeric) ? numeric:Date.parse(event?.at);
		return at > 0 && Number.isFinite(new Date(at).getTime()) ? at:0;
	}
	function getParserIssueServiceKey(area) {
		const value=String(area || '').toLocaleLowerCase('pl-PL');
		if (value.includes('escort.club')) return 'escortClub';
		if (value.includes('escorti.pl')) return 'escorti';
		if (value.includes('garso')) return 'garso';
		return 'other';
	}
	function recordParserIssue(area,message='') {
		incrementDiagnosticCounter('parserIssues');
		const service=getParserIssueServiceKey(area);
		const counterKey={
			escortClub:'parserEscortClub', escorti:'parserEscorti', garso:'parserGarso', other:'parserOther'
		}[service];
		if (counterKey) {
			incrementDiagnosticCounter(counterKey);
		}
		addDiagnosticEvent('parserIssues',area,message);
	}
	function getDiagnosticRequestHostKey(url) {
		try {
			const host=new URL(url || '',location.href) .hostname.toLowerCase();
			if (/garsoniera\.com\.pl$/.test(host)) return 'garso';
			if (/(?:^|\.)escorti\.pl$/.test(host)) return 'escorti';
			if (isEscortClubHost(host)) return 'escortClub';
		} catch {}
		return 'other';
	}
	function getDiagnosticRequestBucket(state,at=Date.now()) {
		const bucketAt= Math.floor(at / DIAGNOSTICS_REQUEST_BUCKET_MS) * DIAGNOSTICS_REQUEST_BUCKET_MS;
		const key=String(bucketAt);
		if (!state.requestBuckets[key]) {
			state.requestBuckets[key]={
				requests:0, garso:0, escorti:0, escortClub:0, other:0, http429:0
			};
		}
		const cutoff=at - DIAGNOSTICS_REQUEST_BUCKET_RETENTION_MS;
		for (const oldKey of Object.keys(state.requestBuckets)) {
			if ((Number(oldKey) || 0) < cutoff) {
				delete state.requestBuckets[oldKey];
			}
		}
		return state.requestBuckets[key];
	}
	function addDiagnosticRollingRequest(hostKey) {
		const state=getDiagnosticsState();
		const bucket=getDiagnosticRequestBucket(state);
		bucket.requests++;
		bucket[hostKey]=Number(bucket[hostKey] || 0) + 1;
		state.updatedAt=Date.now();
		scheduleDiagnosticsSave();
	}
	function addDiagnosticRolling429() {
		const state=getDiagnosticsState();
		const bucket=getDiagnosticRequestBucket(state);
		bucket.http429=Number(bucket.http429 || 0) + 1;
		state.updatedAt=Date.now();
		scheduleDiagnosticsSave();
	}
	function recordDiagnosticResponse(url,status=0) {
		const numericStatus=Number(status) || 0;
		const hostKey= getDiagnosticRequestHostKey(url);
		if (hostKey !== 'escortClub') return;
		const state=getDiagnosticsState();
		const network=state.network.escortClub;
		const now=Date.now();
		if ([403,429,503].includes(numericStatus)) network[`last${numericStatus}At`]=now;
		if (numericStatus === 429) addDiagnosticRolling429();
		if (numericStatus === 403) captureEscortBanEvidence();
		if (numericStatus >= 200 && numericStatus < 400) {
			network.lastSuccessAt=now;
		} else if (numericStatus >= 400) {
			network.lastErrorAt=now;
			network.lastErrorStatus=numericStatus;
		}
		state.updatedAt=now;
		scheduleDiagnosticsSave();
	}
	function recordDiagnosticRequestFailure(url,error='') {
		if (Number(error?.status) >= 400) {recordDiagnosticResponse(url,error.status); return;}
		if (getDiagnosticRequestHostKey(url) !== 'escortClub') return;
		const state=getDiagnosticsState();
		state.network.escortClub.lastErrorAt=Date.now();
		state.network.escortClub.lastErrorStatus= Number(error?.status) || 0;
		state.updatedAt=Date.now();
		scheduleDiagnosticsSave();
	}
	function recordDiagnosticCacheLookup(hit) {
		try {
			if (diagnosticsSnapshotInProgress) return;
			const state=getDiagnosticsState();
			state.counters.cacheLookups= Number(state.counters.cacheLookups || 0) + 1;
			if (hit) {
				state.counters.cacheHits= Number(state.counters.cacheHits || 0) + 1;
			} else {
				state.counters.cacheMisses= Number(state.counters.cacheMisses || 0) + 1;
			}
			state.updatedAt=Date.now();
			scheduleDiagnosticsSave();
		} catch {}
	}
	function recordDiagnosticWatchScheduleRun(fullCheckCompleted=false) {
		const state=getDiagnosticsState();
		const now=Date.now();
		state.watch.lastScheduledRunAt=now;
		if (fullCheckCompleted) {
			state.watch.lastScheduledFullRunAt=now;
		}
		state.updatedAt=now;
		scheduleDiagnosticsSave();
	}
	function getDiagnosticRequestWindow(state,minutes) {
		const cutoff=Date.now() - minutes * 60 * 1000;
		const result={
			requests:0, garso:0, escorti:0, escortClub:0, other:0, http429:0
		};
		for (const [key,bucket] of Object.entries( state.requestBuckets || {} )) {
			if ((Number(key) || 0) < cutoff) continue;
			for (const field of Object.keys(result)) {
				result[field] += Number(bucket?.[field]) || 0;
			}
		}
		return result;
	}
	function recordDiagnosticRequest(url,method='GET',requestContext=null,transport='gm') {
		incrementDiagnosticCounter('requests');
		const hostKey=getDiagnosticRequestHostKey(url),counter={garso:'garsoRequests',escorti:'escortiRequests',escortClub:'escortClubRequests'}[hostKey];
		if (counter) incrementDiagnosticCounter(counter);
		addDiagnosticRollingRequest(hostKey);
		recordRequestRate(hostKey,method);
		return beginDiagnosticRequest(hostKey,method,requestContext,transport,url);
	}
	function clearDiagnostics() {
		writeHelperStorageValue(DIAGNOSTICS_EPOCH_KEY,`${Date.now()}-${Math.random()}`);
		deleteHelperStorageValue(DIAGNOSTICS_STORAGE_KEY);
		deleteHelperStorageValue(DIAGNOSTICS_STORAGE_KEY + '_ban');
		deleteHelperStorageValue(DIAGNOSTICS_STORAGE_KEY + '_salt');
		for (const key of GM_listValues()) if (key.startsWith(DIAGNOSTICS_TAB_PREFIX)) deleteHelperStorageValue(key);
		diagnosticsState=null; clearTimeout(diagnosticsSaveTimer); diagnosticsSaveTimer=null;
		clearLiveDiagnosticsReports();
	}
	function sanitizeDiagnosticText(value) {
		return String(value || '') .replace(/https?:\/\/[^\s"'<>]+/gi,'[URL]')
			.replace(/[\w.+-]+@[\w.-]+\.[a-z]{2,}/gi,'[E-MAIL]') .replace(/(?:\+?48[\s.-]*)?(?:\d[\s.-]*){9,}/g,'[NUMER]')
			.replace(/\b\d{5,}\b/g,'[ID]') .replace(/[„“”"]([^„“”"]{2,80})[„“”"]/g,'[NAZWA]') .slice(0,500);
	}
	function getWatchDiagnosticsSnapshot(state=getMergedDiagnosticsState()) {
		const settings=getWatchSettings();
		const items=getWatchedItems();
		const now=Date.now();
		let nextDueAt=Infinity;
		const result={
			enabled:settings.enabled,
			tracked:items.length, favorites:items.filter(item => item.favorite).length, profiles:items.filter(
				item => item.sourceType !== 'escort-ad' ).length, ads:items.filter( item => item.sourceType === 'escort-ad'
			).length, onlineAlerts:0, locationAlerts:0, changesAlerts:0, newAdsAlerts:0, withErrors:0, overdue:0,
			intervalMinutes:settings.intervalMinutes, lastScheduledRunAt: Number(state.watch?.lastScheduledRunAt) || 0,
			lastScheduledFullRunAt: Number(state.watch?.lastScheduledFullRunAt) || 0, nextScheduledRunAt:0
		};
		for (const item of items) {
			if (item.modes?.online) result.onlineAlerts++;
			if (item.modes?.location) result.locationAlerts++;
			if (item.modes?.changes) result.changesAlerts++;
			if (item.modes?.newAds) result.newAdsAlerts++;
			if (item.lastError) result.withErrors++;
			if (settings.enabled && item.hidden && item.sourceType !== 'escort-ad') {
				nextDueAt=Math.min(nextDueAt,(Number(item.hiddenLastCheckedAt) || 0) + HIDDEN_PROFILE_CHECK_INTERVAL_MS);
			}
			const {eligible,expectedMs,referenceAt,dueAt}=getWatchedItemCheckTiming(item,settings);
			if (!eligible) continue;
			nextDueAt=Math.min(nextDueAt,dueAt);
			const graceMs=Math.max( 2 * 60 * 1000, expectedMs * 0.5 );
			if ( !referenceAt || now - referenceAt > expectedMs + graceMs ) {
				result.overdue++;
			}
		}
		if (Number.isFinite(nextDueAt)) result.nextScheduledRunAt=Math.max(now,nextDueAt);
		return result;
	}
	function formatDiagnosticBackoffStatus(label,state) {
		if (state?.reloadRequired) return `${label}: wstrzymano po HTTP 403 do odświeżenia strony`;
		const seconds=Math.max(0,Math.ceil(Number(state?.waitMs || 0) / 1000));
		const reason=state?.reason ? ` (${state.reason})`:'';

		if (state?.limitPerMinute > 0) {
			const rate=`${state.limitPerMinute}/min łącznie (HEAD + GET/POST)`;
			if (seconds) return `${label}: pauza ok. ${seconds} s${reason}; po wznowieniu ${rate}.`;
			if (state.recovery503) return `${label}: ${state.probePending ? 'sprawdzanie':'oczekiwanie na próbę'} po HTTP 503; limit ${rate}.`;
			if (state.rateFactor < 1) return `${label}: tempo ${state.rateFactor * 100}% po HTTP 503; limit ${rate}.`;
			return `${label}: wspólny limit ${rate}`;
		}
		if (state?.adaptiveEnabled === false) return `${label}: automatyczne spowalnianie wyłączone`;
		const gaps=state?.adaptiveEnabled ? `; odstęp wyszukiwania ${Number(state.searchGapMs || 0) / 1000} s, tematów ${Number(state.topicGapMs || 0) / 1000} s`:'';
		if (seconds) return `${label}: pauza ok. ${seconds} s${reason}${gaps}`;
		return `${label}: ${state?.active ? 'zwiększony odstęp między zapytaniami':'normalne tempo'}${gaps}`;
	}
	function getDiagnosticsSnapshot() {
		flushDiagnostics();
		const state=getMergedDiagnosticsState(),rate=getLiveEscortDiagnostics(Date.now(),true),requestWindows={day:getDiagnosticRequestWindow(state,1440)};
		for (const [key,seconds] of [['minute',60],['fiveMinutes',300],['halfHour',1800]]) {
			const counts=rate.requestCounts[seconds];
			requestWindows[key]={escortClub:counts[0]+counts[1],escorti:counts[2],garso:counts[3]};
		}
		diagnosticsSnapshotInProgress=true;
		let cache={},storage={};
		try {
			const stats=getPersistentCacheStats();
			cache={enabled:true,backend:stats.backend || 'pamięć',entries:stats.entries || 0,bytes:stats.bytes || 0,
				garsoEntries:stats.garsoEntries || 0,escortiEntries:stats.listEntries || 0,escortAdEntries:stats.escortAdDataEntries || 0,
				phoneReputationEntries:stats.phoneReputationEntries || 0,activityEntries:stats.activityEntries || 0,templateEntries:stats.templateEntries || 0};
			for (const [key,read] of [['violentmonkey',getViolentmonkeyStorageStats],['userData',getUserDataStats]]) {
				const value=read(); storage[key]={bytes:Number(value.bytes) || 0,entries:Number(value.entries) || 0};
			}
		} catch (error) {cache.error=sanitizeDiagnosticText(formatHelperDiagnosticError(error));}
		finally {diagnosticsSnapshotInProgress=false;}
		const adaptiveBackoff=getAdaptiveBackoffDiagnostics(),watch=getWatchDiagnosticsSnapshot(state),problems=[];
		for (const value of Object.values(adaptiveBackoff)) if (value.reason) value.reason=sanitizeDiagnosticText(value.reason);
		if (adaptiveBackoff.escortClub.reloadRequired) problems.push('Escort.club: BAN na IP — pobieranie wstrzymane do odświeżenia strony');
		else if (adaptiveBackoff.escortClub.waitMs > 0) problems.push('Escort.club: pauza po odpowiedzi serwera');
		else if (adaptiveBackoff.escortClub.recovery503) problems.push('Escort.club: oczekiwanie na wznowienie po HTTP 503');
		else if (adaptiveBackoff.escortClub.rateFactor < 1) problems.push(`Escort.club: tempo ${adaptiveBackoff.escortClub.rateFactor * 100}% po HTTP 503`);
		if (adaptiveBackoff.garso.active) problems.push(adaptiveBackoff.garso.waitMs > 0
			? 'Garso: pauza po komunikacie serwera':'Garso: zwiększony odstęp między zapytaniami');
		if (watch.overdue) problems.push(`${watch.overdue} obserwowanych pozycji opóźnionych w odświeżaniu`);
		for (const [events,label] of [[state.errors,'błędów'],[state.parserIssues,'problemów parsera']]) {
			const recent=events.filter(event => event.at >= Date.now() - 86400000).length;
			if (recent) problems.push(`Grupy ${label} z ostatnich 24 h: ${recent}`);
		}
		if (cache.error) problems.push('Nie udało się odczytać stanu cache');
		return {format:DIAGNOSTICS_EXPORT_FORMAT,scriptVersion:SCRIPT_VERSION,generatedAt:new Date().toISOString(),sanitized:true,
			requestEvidence:getRequestEvidenceExport(),counters:state.counters,requestWindows,network:state.network,adaptiveBackoff,watch,cache,cacheDetails:cache,storage,
			errors:state.errors,parserIssues:state.parserIssues,health:{ok:!problems.length,problems,text:problems.join(' • ')}};
	}
	function downloadDiagnosticsExport() {
		const payload=getDiagnosticsSnapshot();
		const stamp=new Date().toISOString().replace(/[:.]/g,'-');
		downloadJsonFile( payload, `escort-helper-diagnostyka-${stamp}.json` );
	}
	function getResponseHeader(headers,name) {
		try {
			if (typeof headers?.get === 'function') return String(headers.get(name) || '');
			if (headers && typeof headers === 'object') return String(Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1] || '');
			return String(headers || '').match(new RegExp('^' + name + ':\\s*([^\\r\\n]*)','im'))?.[1]?.trim() || '';
		} catch {return '';}
	}
	function classifyResponseProtection(status,body='',headers=null) {
		const text=typeof body === 'string' ? body.slice(0,120000):'';
		const signals=[];
		if (status === 403) signals.push('http-403');
		if (status === 429) signals.push('http-429');
		if (status === 503) signals.push('http-503');
		const markers=[['rate-limit',/too many requests|rate.limit.exceeded|zbyt wiele zapyta[nń]|zbyt du[zż]o zapyta[nń]/i],
			['access-denied',/<title[^>]*>[^<]*(?:access denied|forbidden|blocked)|your (?:ip|access).{0,40}(?:block|ban)|(?:dost[eę]p|adres\s+ip|twoje\s+ip).{0,60}zablokowan|(?:ip address|adres\s+ip).{0,60}(?:banned|blocked)|<title[^>]*>[^<]*site unavailable/i],
			['challenge',/cf-chl-|challenge-platform|<title[^>]*>\s*just a moment|verify (?:that )?you are human|checking your browser/i],
			['antiflood',/kontrola antyfloodowa|flood control|anty.?flood/i],
			['captcha',/<(?:iframe|div)[^>]*(?:g-recaptcha|h-captcha)|<title[^>]*>[^<]*captcha/i]];
		for (const [name,pattern] of markers) if (pattern.test(text)) signals.push(name);
		return {blockSignals:signals};
	}
	var requestRateState;
	function getRequestRateState() {
		const epoch=String(GM_getValue(DIAGNOSTICS_EPOCH_KEY,'0'));
		if (!requestRateState || requestRateState.epoch !== epoch)
			requestRateState={epoch,startedAt:Date.now(),buckets:new Map()};
		return requestRateState;
	}
	function getRequestRateRows(now=Date.now()) {
		const state=getRequestRateState(),cutoff=Math.floor(now / 1000) * 1000 - 1800000;
		for (const at of state.buckets.keys()) if (at < cutoff || at > now) state.buckets.delete(at);
		return [...state.buckets].map(([at,counts]) => [at,...counts]);
	}
	function recordRequestRate(service,method) {
		const index=service === 'escortClub' ? String(method).toUpperCase() === 'HEAD' ? 1:0:
			service === 'escorti' ? 2:service === 'garso' ? 3:-1;
		if (index < 0) return;
		const state=getRequestRateState(),at=Math.floor(Date.now() / 1000) * 1000;
		const counts=state.buckets.get(at) || [0,0,0,0];
		counts[index]++; state.buckets.set(at,counts);
		publishLiveDiagnosticsReport();
	}
	function getLiveEscortQueueState(queue,status,now) {
		if (status.reloadRequired) return {text:'BAN na IP — do odświeżenia',tone:'blocked'};
		if (status.blockedUntil > now) return {text:`${status.recovery503 ? 'HTTP 503 · pauza':'Pauza serwera'} · ${Math.ceil((status.blockedUntil - now) / 1000)} s`,tone:'blocked'};
		if (status.recovery503) return {text:'Ograniczenie po HTTP 503',tone:'waiting'};
		if (status.rateFactor < 1) return {text:`HTTP 503 · tempo ${status.rateFactor * 100}% · ${status.limitPerMinute}/min`,tone:'waiting'};
		if (Array.isArray(queue.states)) {
			const waits=queue.states.filter(state => state.tone === 'waiting' || state.tone === 'blocked');
			if (waits.length) return {text:waits.map(state => state.text).join(' · '),tone:waits.some(state => state.tone === 'blocked') ? 'blocked':'waiting'};
			return {text:'',tone:queue.active ? 'running':'idle'};
		}
		if (!queue.queued) return {text:'',tone:queue.active ? 'running':'idle'};
		if ((queue.maxConcurrent > 0 && (queue.sharedActive ?? queue.globalActive ?? queue.active) >= queue.maxConcurrent) || ['global-concurrency-limit','concurrency-limit'].includes(queue.waitReason)) return {text:'Limit równoczesnych zapytań',tone:'waiting'};
		if (queue.wakeAt && now - queue.wakeAt > 2000) return {text:'Opóźnione wybudzenie kolejki',tone:'waiting'};
		if (queue.waitReason === 'rate-limit' && queue.nextEligibleAt > now) return {text:'Wspólny limit zapytań/min',tone:'waiting'};
		return {text:'',tone:'running'};
	}
	function ensureLiveDiagnosticsStyles() {
		if (document.getElementById('vm-live-diagnostics-style')) return;
		appendHelperStyle('vm-live-diagnostics-style',`
.vm-helper-ui.vm-live-floating { position:fixed; left:12px; bottom:12px; z-index:2147482999; width:80px; box-sizing:border-box; padding:9px 17px 9px 9px; border:1px solid #fff2; border-radius:12px 12px 0 12px; --vm-live-bg:rgba(0,0,0,.6); background:var(--vm-live-bg) !important; color:#fff !important; box-shadow:0 3px 14px #0002; text-shadow:0 1px 2px #0006; backdrop-filter:blur(9px); -webkit-backdrop-filter:blur(9px); font:11px/1.3 var(--vm-ui-font-family,Arial,sans-serif); }
.vm-live-floating .vm-live-name { display:flex; align-items:center; gap:4px; margin:0 -8px 4px 0; color:#f0f2f5 !important; font-size:9px; white-space:nowrap; }
.vm-live-floating .vm-live-name::before { content:''; width:4px; height:4px; flex:none; border-radius:50%; background:currentColor; }
.vm-live-floating .vm-live-metrics { display:grid; gap:6px; }
.vm-live-floating .vm-live-value { display:block; height:23px; color:#fff !important; font-size:19px; font-weight:600; line-height:23px; letter-spacing:-.5px; font-variant-numeric:tabular-nums; white-space:nowrap; }
.vm-live-floating .vm-live-value[data-high],.vm-live-floating[data-tone="blocked"] .vm-live-name { color:#ffdddd !important; }
.vm-live-floating[data-tone="waiting"] .vm-live-name { color:#fff0c2 !important; }
.vm-live-floating .vm-live-unit { display:block; height:13px; color:#f0f2f5 !important; font-size:9px; white-space:nowrap; }
.vm-live-floating :is(.vm-live-trail,.vm-live-status) { position:absolute; left:calc(100% + 1px); width:min(240px,calc(100vw - 104px)); }
.vm-live-floating .vm-live-trail { bottom:15px; height:62px; pointer-events:none; color:color-mix(in srgb,var(--vm-ui-accent,#64748b) 25%,#fff); }
.vm-live-floating .vm-live-status { bottom:-1px; height:16px; box-sizing:border-box; padding:1px 4px; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; background:linear-gradient(to right,var(--vm-live-bg),transparent); color:#f0f2f5 !important; font-size:10px; line-height:14px; text-shadow:0 1px 2px #000,0 0 3px #000; }
.vm-live-floating[data-tone="blocked"] .vm-live-status { color:#ffdddd !important; }
.vm-live-floating[data-tone="waiting"] .vm-live-status { color:#fff0c2 !important; }
.vm-live-floating :is(.vm-live-trail,.vm-live-trail-bars) { background:transparent; border:0; box-shadow:none; }
.vm-live-floating .vm-live-trail-bars { position:absolute; inset:0; display:grid; grid-template-columns:repeat(60,minmax(0,1fr)); align-items:end; gap:1px; mask-image:linear-gradient(to right,#000,transparent); -webkit-mask-image:linear-gradient(to right,#000,transparent); }
.vm-live-floating .vm-live-trail-bars::before { content:''; position:absolute; inset:50% 0 0; background:linear-gradient(to top,var(--vm-live-bg) 4px,transparent); }
.vm-live-floating .vm-live-trail-bar { min-width:0; position:relative; border-radius:2px 2px 0 0; background:currentColor; box-shadow:inset 0 0 0 1px #0009; }
.vm-live-floating .vm-live-scale { position:absolute; left:-15px; width:12px; text-align:right; color:#f0f2f5 !important; font:9px/1 var(--vm-ui-font-family,Arial,sans-serif); font-variant-numeric:tabular-nums; }
.vm-live-floating .vm-live-scale-top { top:0; }
.vm-live-floating .vm-live-scale-zero { bottom:0; }
`);
	}
	function createLiveDiagnosticsBadge() {
		ensureLiveDiagnosticsStyles();
		const element=prepareHelperStyleApi(makeElement('section','vm-live-floating'));
		element.setAttribute('aria-label','Tempo zapytań Escort.club ze wszystkich kart');
		const name=makeElement('div','vm-live-name','Escort.club'),status=makeElement('div','vm-live-status');
		name.title='Zapytania ze wszystkich raportujących kart. Kropka: żółta — oczekiwanie, czerwona — blokada serwera.';
		const metrics=makeElement('div','vm-live-metrics');
		const fields=['zap./sek','zap./min','zap./5 min'].map(label => {
			const cell=makeElement('div','vm-live-metric'),value=makeElement('span','vm-live-value','—');
			cell.append(value,makeElement('span','vm-live-unit',label)); metrics.append(cell); return {cell,value};
		});
		const chart=makeElement('div','vm-live-trail'),columns=makeElement('div','vm-live-trail-bars');
		const scale=makeElement('span','vm-live-scale vm-live-scale-top');
		chart.setAttribute('role','img');
		const bars=Array(60).fill().map(() => {const bar=makeElement('span','vm-live-trail-bar'); columns.append(bar); return bar;});
		chart.append(columns,scale,makeElement('span','vm-live-scale vm-live-scale-zero','0')); element.append(name,metrics,chart,status);
		const format=new Intl.NumberFormat('pl-PL',{maximumFractionDigits:0});
		return {element,render(snapshot=getLiveEscortDiagnostics()) {
			const {get,head}=snapshot.lanes,values=[(get.last5 + head.last5) / 5,get.minute + head.minute,get.fiveMinutes + head.fiveMinutes];
			const windows=[5,60,300];
			fields.forEach(({cell,value},index) => {
				value.textContent=snapshot.available ? format.format(values[index]):'—';
				value.toggleAttribute('data-high',snapshot.available && values[index] > [Infinity,60,220][index]);
				cell.title=(index === 0 ? `Zaokrąglona średnia zapytań na sekundę (${get.last5 + head.last5} zap. / 5 s)`:'Liczba wysłanych zapytań') + ` z ostatnich ${windows[index]} pełnych sekund. HEAD + GET/POST ze wszystkich raportujących kart.` +
					(index === 2 && snapshot.historyLimited ? ' Starsze wersje w innych kartach raportują tylko minutę historii.':'') +
					(snapshot.unavailable || snapshot.truncated ? ' Niepełny odczyt innych kart.':'');
			});
			const states=['get','head'].map(lane => getLiveEscortQueueState(snapshot.lanes[lane],snapshot.status,snapshot.now));
			element.dataset.tone=states.some(state => state.tone === 'blocked') ? 'blocked':states.some(state => state.tone === 'waiting') ? 'waiting':'running';
			status.textContent=[...new Set(states.filter(state => state.tone === 'blocked' || state.tone === 'waiting').map(state => state.text))].join(' · ');
			status.title=status.textContent ? status.textContent + '. Stan wszystkich raportujących kart.':'';
			const peak=Math.max(1,...snapshot.points.map(point => point.get + point.head));
			scale.textContent=String(peak);
			snapshot.points.forEach((point,index) => {bars[bars.length - 1 - index].style.height=`${(point.get + point.head) / peak * 100}%`;});
			chart.setAttribute('aria-label',`Zapytania na sekundę, ostatnie 60 sekund. Skala 0–${peak}. Najnowsze po lewej, przy panelu.`);
		}};
	}
	let liveDiagnosticsView=null,liveDiagnosticsTimer=null,liveDiagnosticsInstalled=false;
	function refreshLiveDiagnosticsPanel() {
		if (!liveDiagnosticsView || document.hidden) return;
		liveDiagnosticsView.render();
	}
	function syncLiveDiagnosticsPanel() {
		clearInterval(liveDiagnosticsTimer); liveDiagnosticsTimer=null;
		if (readHelperSettingsSnapshot().showLiveDiagnostics !== true) {
			if (liveDiagnosticsView) detachHelperNode(liveDiagnosticsView.element);
			liveDiagnosticsView=null; return;
		}
		if (document.hidden) return;
		if (!liveDiagnosticsView) {liveDiagnosticsView=createLiveDiagnosticsBadge(); document.body.append(liveDiagnosticsView.element);}
		refreshLiveDiagnosticsPanel();
		liveDiagnosticsTimer=setInterval(refreshLiveDiagnosticsPanel,1000);
	}
	function initLiveDiagnosticsPanel() {
		if (liveDiagnosticsInstalled || window.top !== window.self) return;
		liveDiagnosticsInstalled=true;
		initLiveDiagnosticsReports();
		if (typeof GM_addValueChangeListener === 'function') GM_addValueChangeListener(SETTINGS_STORAGE_KEY,syncLiveDiagnosticsPanel);
		document.addEventListener('visibilitychange',syncLiveDiagnosticsPanel);
		window.addEventListener('pageshow',syncLiveDiagnosticsPanel);
		window.addEventListener('pagehide',() => {clearInterval(liveDiagnosticsTimer); liveDiagnosticsTimer=null;});
		syncLiveDiagnosticsPanel();
	}
	const LIVE_DIAGNOSTICS_PREFIX='vm_helper_request_rates_v1:';
	let liveDiagnosticsPublisher=null,liveDiagnosticsLastPublish=0,liveDiagnosticsSignature='',liveDiagnosticsClosed=false;
	let liveDiagnosticsRemoteCache={at:0,keysAt:0,keys:[],pages:[],truncated:false,unavailable:false};
	function getLiveDiagnosticsPage() {
		const state=getRequestRateState();
		return {pageId:ESCORT_REQUEST_INSTANCE_ID,epoch:state.epoch,startedAt:state.startedAt};
	}
	function makeLiveDiagnosticsReport(now=Date.now()) {
		const status=getEscortRequestQueueStatus();
		const queues=Object.fromEntries(['get','head'].map(lane => {
			const value=status.queues[lane],wait=escortRequestQueueDiagnostics[lane];
			return [lane,{queued:value.queued,active:value.active,maxConcurrent:value.maxConcurrent,
				sharedActive:value.sharedActive,waitReason:wait.waitReason,nextEligibleAt:wait.nextEligibleAt,wakeAt:wait.wakeAt,
				waitSince:wait.waitSince,lastWakeAt:wait.lastWakeAt,wakeLatenessMs:wait.wakeLatenessMs,
				oldestQueuedAt:escortRequestQueues[lane].jobs.reduce((at,job) => Math.min(at || job.queuedAt,job.queuedAt),0)}];
		}));
		return {schemaVersion:1,scriptVersion:SCRIPT_VERSION,queueHistory:getEscortQueueEvidence(now),cacheDecisions:getRequestRateState().cacheDecisions || [],...getLiveDiagnosticsPage(),updatedAt:now,closed:liveDiagnosticsClosed,
			service:getDiagnosticRequestHostKey(location.href),limits:[status.configuredLimitPerMinute,status.configuredMaxConcurrent,
				readHelperSettingsSnapshot().escortFastStart === false ? 0:1],
			queues,rows:getRequestRateRows(now),requests:getDiagnosticRequestRows(now),discarded:getRequestRateState().discarded || 0};
	}
	function publishLiveDiagnosticsReport(force=false) {
		try {
			if (!force && Date.now() - liveDiagnosticsLastPublish < 2000) return;
			const report=makeLiveDiagnosticsReport(),{updatedAt,...payload}=report,signature=JSON.stringify(payload);
			if (!force && signature === liveDiagnosticsSignature && updatedAt - liveDiagnosticsLastPublish < 15000) return;
			writeHelperStorageValue(LIVE_DIAGNOSTICS_PREFIX + report.pageId,report);
			liveDiagnosticsSignature=signature; liveDiagnosticsLastPublish=updatedAt;
		} catch {}
	}
	function clearLiveDiagnosticsReports() {
		for (const key of GM_listValues()) if (key.startsWith(LIVE_DIAGNOSTICS_PREFIX)) deleteHelperStorageValue(key);
		requestRateState=undefined; liveDiagnosticsSignature=''; liveDiagnosticsLastPublish=0;
		liveDiagnosticsRemoteCache={at:0,keysAt:0,keys:[],pages:[],truncated:false,unavailable:false};
	}
	function initLiveDiagnosticsReports() {
		if (liveDiagnosticsPublisher !== null) return;
		try {
			const now=Date.now(),epoch=getLiveDiagnosticsPage().epoch;
			for (const key of GM_listValues()) if (key.startsWith(LIVE_DIAGNOSTICS_PREFIX)) {
				const page=GM_getValue(key,null);
				if (!page || page.epoch !== epoch || !Number.isFinite(page.updatedAt) || page.updatedAt < now - 1800000)
					deleteHelperStorageValue(key);
			}
		} catch {}
		const resume=() => {
			liveDiagnosticsClosed=false; publishLiveDiagnosticsReport(true);
			clearInterval(liveDiagnosticsPublisher); liveDiagnosticsPublisher=setInterval(publishLiveDiagnosticsReport,2000);
		};
		window.addEventListener('pagehide',() => {
			liveDiagnosticsClosed=true; publishLiveDiagnosticsReport(true);
			clearInterval(liveDiagnosticsPublisher); liveDiagnosticsPublisher=null;
		});
		window.addEventListener('pageshow',resume);
		document.addEventListener('visibilitychange',() => publishLiveDiagnosticsReport(true));
		resume();
	}
	function readLiveDiagnosticsReports(now,force=false) {
		const cache=liveDiagnosticsRemoteCache;
		if (!force && cache.at && now >= cache.at && now - cache.at < 2000) return cache;
		try {
			if (force || !cache.keysAt || now < cache.keysAt || now - cache.keysAt >= 5000) {
				cache.keys=GM_listValues().filter(key => key.startsWith(LIVE_DIAGNOSTICS_PREFIX)); cache.keysAt=now;
			}
			cache.truncated=cache.keys.length > 128;
			cache.pages=cache.keys.slice(-128).map(key => GM_getValue(key,null)).filter(page => page?.schemaVersion === 1 &&
				typeof page.pageId === 'string' && /^[a-z0-9-]{1,80}$/.test(page.pageId) &&
				Number.isFinite(page.updatedAt) && page.updatedAt <= now && page.updatedAt >= now - 1800000);
			cache.unavailable=false;
		} catch {cache.pages=[]; cache.unavailable=true;}
		cache.at=now; return cache;
	}
	function getLiveEscortDiagnostics(now=Date.now(),force=false) {
		const own=makeLiveDiagnosticsReport(now),remote=readLiveDiagnosticsReports(now,force),end=Math.floor(now / 1000) * 1000;
		const status=getEscortRequestQueueStatus(),points=Array(60).fill().map((_,index) => ({at:end - (60 - index) * 1000,get:0,head:0}));
		const requestCounts={60:[0,0,0,0],300:[0,0,0,0],1800:[0,0,0,0]},lanes={};
		for (const lane of ['get','head']) lanes[lane]={...status.queues[lane],queued:0,active:0,states:[]};
		const count=value => Number.isSafeInteger(value) && value >= 0 ? value:0,seen=new Set();
		for (const report of [own,...remote.pages]) {
			if (report.epoch !== own.epoch || seen.has(report.pageId)) continue;
			seen.add(report.pageId);
			if (!report.closed && now - report.updatedAt <= 90000) for (const lane of ['get','head']) {
				const value=report.queues?.[lane] || {},queue=lanes[lane];
				queue.queued+=count(value.queued); queue.active+=count(value.active);
				if (now - report.updatedAt > 5000 || !count(value.queued)) continue;
				const state=getLiveEscortQueueState(value,status,report.updatedAt);
				if (state.text && !queue.states.some(entry => entry.text === state.text)) queue.states.push(state);
			}
			const seconds=new Set();
			for (const row of (Array.isArray(report.rows) ? report.rows:[]).slice(-1801)) {
				const at=row?.[0];
				if (!Number.isSafeInteger(at) || at % 1000 || at < end - 1800000 || at >= end || seconds.has(at)) continue;
				seconds.add(at);
				for (const seconds of [60,300,1800]) if (at >= end - seconds * 1000)
					for (let index=0;index<4;index++) requestCounts[seconds][index]+=count(row[index + 1]);
				const index=(at - points[0].at) / 1000;
				if (index >= 0) {points[index].get+=count(row[1]); points[index].head+=count(row[2]);}
			}
		}
		for (const [index,lane] of [[0,'get'],[1,'head']]) {
			const queue=lanes[lane];
			queue.active=status.queues[lane].globalActive ?? queue.active;
			queue.minute=requestCounts[60][index]; queue.fiveMinutes=requestCounts[300][index];
			queue.last5=points.slice(-5).reduce((sum,point) => sum + point[lane],0);
		}
		return {now,points,status,available:true,requestCounts,lanes,truncated:remote.truncated,unavailable:remote.unavailable};
	}
	function normalizeDiagnosticErrorOrigin(value) {
		if (!Array.isArray(value)) return [];
		return value.slice(0,5).filter(frame => Number.isSafeInteger(frame?.line) && frame.line > 0 && frame.line <= 1000000 &&
			Number.isSafeInteger(frame.column) && frame.column >= 0 && frame.column <= 1000000).map(frame => ({
			fn:/^(?:[A-Za-z_$][\w.$<>]{0,79}|<anonymous>)$/.test(frame.fn || '') && !/\d{5}/.test(frame.fn) ? frame.fn:'<anonymous>',
			line:frame.line,column:frame.column}));
	}
	function getDiagnosticErrorOrigin(error) {
		try {
			if (!error || typeof error !== 'object' || typeof error.stack !== 'string') return [];
			const frames=[];
			for (const line of error.stack.slice(0,8000).split('\n')) {
				const match=line.match(/^(?:\s*at (?:(.*?) \()?|\s*([^@\s]*)@).+?:(\d+):(\d+)\)?$/);
				if (!match) continue;
				frames.push({fn:(match[1] || match[2] || '').replace(/^(?:async|new) /,''),line:Number(match[3]),column:Number(match[4])});
				if (frames.length === 5) break;
			}
			return normalizeDiagnosticErrorOrigin(frames);
		} catch {return [];}
	}
	function getDiagnosticRequestRows(now=Date.now()) {
		const state=getRequestRateState(),rows=state.requests || [];
		state.requests=rows.filter(row => row[0] >= now - 600000).slice(-1000);
		state.discarded=(state.discarded || 0) + rows.length - state.requests.length;
		return state.requests;
	}
	function beginDiagnosticRequest(service,method,context,transport,url) {
		try {
			if (!['escortClub','escorti','garso'].includes(service)) return;
			if (service === 'escortClub') recordDiagnosticCacheDecision(context);
			const state=getRequestRateState(),queue=service === 'escortClub' ? getEscortRequestQueueStatus({
				foregroundWork:context?.source !== 'access.probe' && (context?.manual || !/^(?:watch[.-]|cache-bridge$)/.test(context?.source || ''))}):{};
			const label=value => /^[a-z][a-z.-]{0,59}$/i.test(value || '') ? value:'unknown';
			const row=[Date.now(),0,0,service,['GET','HEAD','POST','IFRAME'].includes(String(method).toUpperCase()) ? String(method).toUpperCase():'OTHER',label(context?.source),label(transport),
				document.hidden ? 0:1,queue.limitPerMinute || 0,queue.maxConcurrent || 0,'pending','',0,{},state.requestSequence=(state.requestSequence || 0) + 1];
			const rows=getDiagnosticRequestRows(); rows.push(row);
			if (rows.length > 1000) {rows.shift(); state.discarded++;}
			if (service === 'escortClub' && ['GET','HEAD'].includes(row[4]) && url) hashDiagnosticRequestTarget(url).then(hash => {
				if (state === getRequestRateState()) {row[11]=hash; publishLiveDiagnosticsReport();}
			}).catch(() => {});
			return (status,outcome='response',response=null) => {
				try {
					if (row[1] || state !== getRequestRateState()) return;
					row[10]=outcome;
					if (!row[12] && ((Number(status) >= 100 && Number(status) <= 599) || (response && ['headers','response'].includes(outcome)))) {
						row[2]=Number(status) || 0; row[12]=Date.now();
						if (response) row[13]=getDiagnosticResponseMeta(response,url);
					}
					if (outcome !== 'headers') row[1]=Date.now();
					publishLiveDiagnosticsReport();
				} catch {}
			};
		} catch {}
	}
	function normalizeRequestEvidence(value) {
		const number=value => Number.isSafeInteger(value) && value >= 0 ? value:0;
		const label=value => /^[a-z][a-z.-]{0,59}$/i.test(value || '') ? value:'unknown';
		const now=number(value?.at),pages=[],seen=new Set();
		for (const page of (Array.isArray(value?.pages) ? value.pages:[]).slice(0,128)) {
			if (!/^[a-z0-9-]{1,80}$/.test(page?.pageId || '') || seen.has(page.pageId)) continue;
			seen.add(page.pageId);
			const queues={};
			for (const lane of ['get','head']) {
				const queue=page.queues?.[lane] || {};
				queues[lane]=Object.fromEntries(['queued','active','sharedActive','maxConcurrent','nextEligibleAt','wakeAt',
					'waitSince','lastWakeAt','wakeLatenessMs','oldestQueuedAt'].map(key => [key,number(queue[key])]));
				queues[lane].waitReason=label(queue.waitReason);
			}
			const rows=(Array.isArray(page.rows) ? page.rows:[]).slice(-1801)
				.filter(row => Array.isArray(row) && Number.isSafeInteger(row[0]) && row[0] > 0 && row[0] >= now - 1800000 && row[0] <= now)
				.map(row => Array(5).fill().map((_,index) => number(row[index])));
			const requests=(Array.isArray(page.requests) ? page.requests:[]).slice(-1000)
				.filter(row => Array.isArray(row) && Number.isSafeInteger(row[0]) && row[0] > 0 && row[0] >= now - 600000 && row[0] <= now)
				.map(row => {
					const end=number(row[1]) <= now ? number(row[1]):0,headers=number(row[12]) <= now ? number(row[12]):0;
					const known=end || headers || (!row[1] && !row[12]);
					return [number(row[0]),end,known ? number(row[2]):0,label(row[3]),
						['GET','HEAD','POST','IFRAME'].includes(row[4]) ? row[4]:'OTHER',label(row[5]),label(row[6]),
						row[7] === 1 ? 1:0,number(row[8]),number(row[9]),end ? label(row[10]):headers ? 'headers':'pending',
						/^[a-f0-9]{32}$/.test(row[11] || '') ? row[11]:'',headers,normalizeDiagnosticResponseMeta(known ? row[13]:null),number(row[14]),Math.min(now,number(row[15]) || number(page.updatedAt))];
				});
			pages.push({pageId:page.pageId,service:label(page.service),updatedAt:number(page.updatedAt),
				startedAt:number(page.startedAt),closed:page.closed === true,discarded:number(page.discarded),
				scriptVersion:/^[\d.]{1,24}$/.test(page.scriptVersion || '') ? page.scriptVersion:'',enrichedAt:number(page.enrichedAt),
				queueSnapshotUnavailable:page.queueSnapshotUnavailable === true,queueHistory:normalizeDiagnosticQueueHistory(page.queueHistory,now),
				cacheDecisions:getDiagnosticCacheDecisions(page.cacheDecisions),
				limits:(Array.isArray(page.limits) ? page.limits:[]).slice(0,3).map(number),queues,rows,requests});
		}
		return {at:now,truncated:value?.truncated === true || value?.pages?.length > 128,unavailable:value?.unavailable === true,pages};
	}
	function getRequestEvidence(now=Date.now()) {
		const own=makeLiveDiagnosticsReport(now),remote=readLiveDiagnosticsReports(now,true);
		return normalizeRequestEvidence({at:now,truncated:remote.truncated,unavailable:remote.unavailable,
			pages:[own,...remote.pages.filter(page => page.epoch === own.epoch && page.pageId !== own.pageId)]});
	}
	function captureEscortBanEvidence() {
		try {
			const key=DIAGNOSTICS_STORAGE_KEY + '_ban',previous=GM_getValue(key,null),now=Date.now();
			if (previous?.at && getEscortRequestQueueStatus().reloadRequired) return;
			writeHelperStorageValue(key,{at:now,scriptVersion:SCRIPT_VERSION,hour:getDiagnosticRequestWindow(getMergedDiagnosticsState(),60).escortClub,evidence:getRequestEvidence(now)});
			flushDiagnostics();
			const epoch=String(GM_getValue(DIAGNOSTICS_EPOCH_KEY,'0'));
			for (const delay of [3000,10000]) setTimeout(() => enrichEscortBanEvidence(now,epoch),delay);
		} catch {}
	}
	function getRequestEvidenceExport() {
		const previous=GM_getValue(DIAGNOSTICS_STORAGE_KEY + '_ban',null),current=getRequestEvidence();
		const evidence=previous?.at ? normalizeRequestEvidence(previous.evidence):null;
		return {schemaVersion:2,scope:'script requests; remote reports may lag; iframe HTTP status unknown',
			cacheDecisionColumns:['source','kind','missing','expired','incomplete','forced','joined'],
			admissionColumns:['queuedAt','admittedAt','startedAt','lane','seenActive','lastStartedAt','rateLimit','concurrentLimit','fastStart'],
			requestColumns:['startAt','endAt','httpStatus','service','method','source','transport','visible','rateLimit','concurrentLimit','outcome','targetFingerprint','firstResponseAt','responseMeta','sequence','observedUntilAt'],
			rowColumns:['at','escortGetPost','escortHead','escorti','garso'],limitColumns:['perMinute','concurrent','fastStartEnabled'],
			current,analysis:analyzeRequestEvidence(current),lastBan:Number.isSafeInteger(previous?.at) && previous.at > 0 ? {at:previous.at,enrichedAt:Number.isSafeInteger(previous.enrichedAt) ? previous.enrichedAt:0,hour:Number.isSafeInteger(previous.hour) ? previous.hour:0,
				scriptVersion:/^[\d.]{1,24}$/.test(previous.scriptVersion || '') ? previous.scriptVersion:'',
				evidence,analysis:analyzeRequestEvidence(evidence)}:null};
	}
	function analyzeRequestEvidence(evidence) {
		const at=Number(evidence?.at) || 0,end=Math.floor(at / 1000) * 1000,seconds=new Map(),trace=[],seen=new Set();
		const coverage={windowEndAt:end,counterWindowSeconds:1800,traceWindowSeconds:600,pages:0,stalePages:0,closedPages:0,
			discardedRequests:0,oldestTraceAt:0,remoteUnavailable:evidence?.unavailable === true,truncated:evidence?.truncated === true};
		const count=value => Number.isSafeInteger(value) && value >= 0 ? value:0;
		for (const page of evidence?.pages || []) {
			if (seen.has(page.pageId)) continue;
			seen.add(page.pageId); coverage.pages++;
			if (page.closed) coverage.closedPages++;
			else if (at - page.updatedAt > 5000) coverage.stalePages++;
			coverage.discardedRequests+=count(page.discarded);
			const bucketTimes=new Set();
			for (const row of page.rows || []) {
				if (row[0] < end - 1800000 || row[0] >= end || row[0] % 1000 || bucketTimes.has(row[0])) continue;
				bucketTimes.add(row[0]);
				seconds.set(row[0],(seconds.get(row[0]) || 0) + count(row[1]) + count(row[2]));
			}
			for (const row of page.requests || []) if (row[3] === 'escortClub' && row[0] >= at - 600000 && row[0] <= at) {
				const observed=Number.isSafeInteger(row[15]) && row[15] >= 0 ? row[15]:page.updatedAt;
				const complete=row[1] >= row[0] && row[1] <= at,until=Math.min(at,complete ? row[1]:observed);
				trace.push({row,complete,until:Math.max(row[0],until)});
			}
		}
		const buckets=[...seconds].sort((a,b) => a[0] - b[0]),windows={},peaks={};
		for (const size of [5,60,300,1800]) {
			windows[size]=buckets.reduce((sum,[time,value]) => sum + (time >= end - size * 1000 ? value:0),0);
			if (size === 1800) continue;
			let sum=0,left=0,peak=0,peakEndAt=0;
			for (let right=0;right<buckets.length;right++) {
				sum+=buckets[right][1];
				while (buckets[left][0] <= buckets[right][0] - size * 1000) sum-=buckets[left++][1];
				if (sum > peak) {peak=sum; peakEndAt=buckets[right][0] + 1000;}
			}
			peaks[size]={count:peak,endAt:peakEndAt};
		}
		trace.sort((a,b) => a.row[0] - b.row[0]);
		coverage.oldestTraceAt=trace[0]?.row[0] || 0;
		const gaps=[],durations=[],events=[],sources=new Map(),targets=new Map();
		const statuses=Object.create(null),outcomes=Object.create(null),methods=Object.create(null);
		const violations={spacing:0,concurrency:0,spacingToleranceMs:5,spacingExamples:[],concurrencyExamples:[]};
		let pending=0,repeatedTargets=0,overlappingTargets=0,fingerprinted=0;
		for (let index=0;index<trace.length;index++) {
			const {row,complete,until}=trace[index],start=row[0],method=row[4];
			const knownStatus=complete || (row[12] > 0 && row[12] <= at) || (!row[1] && !row[12]);
			const status=knownStatus && row[2] >= 100 && row[2] <= 599 ? row[2]:0;
			const outcome=complete ? row[10]:'pending',source=row[5];
			statuses[status]=(statuses[status] || 0) + 1; outcomes[outcome]=(outcomes[outcome] || 0) + 1;
			methods[method]=(methods[method] || 0) + 1; sources.set(source,(sources.get(source) || 0) + 1);
			if (complete) durations.push(row[1] - start); else pending++;
			if (index) {
				const gap=start - trace[index - 1].row[0],expected=row[8] > 0 ? Math.ceil(60000 / row[8]):0;
				gaps.push(gap);
				if (gap + violations.spacingToleranceMs < expected) {
					violations.spacing++;
					if (violations.spacingExamples.length < 5) violations.spacingExamples.push({at:start,gapMs:gap,expectedMs:expected});
				}
			}
			if (until > start) {events.push([start,1,row[9]]); events.push([until,-1,0]);}
			if (/^[a-z0-9-]{8,80}$/i.test(row[11] || '')) {
				fingerprinted++;
				const key=method + ':' + row[11],previous=targets.get(key);
				if (previous !== undefined) {repeatedTargets++; if (previous > start) overlappingTargets++;}
				targets.set(key,Math.max(previous || 0,until));
			}
		}
		events.sort((a,b) => a[0] - b[0] || a[1] - b[1] || (a[2] || 1000000) - (b[2] || 1000000));
		let active=0,peakConcurrent=trace.length ? 1:0;
		for (const [time,delta,limit] of events) {
			active+=delta; peakConcurrent=Math.max(peakConcurrent,active);
			if (delta > 0 && limit > 0 && active > limit) {
				violations.concurrency++;
				if (violations.concurrencyExamples.length < 5) violations.concurrencyExamples.push({at:time,active,limit});
			}
		}
		const distribution=values => {
			values.sort((a,b) => a - b);
			return {samples:values.length,min:values[0] ?? null,median:values.length
				? (values[Math.floor((values.length - 1) / 2)] + values[Math.floor(values.length / 2)]) / 2:null,
				p95:values.length ? values[Math.ceil(values.length * 0.95) - 1]:null};
		};
		return {coverage,countedRequests:windows,peakWindows:peaks,traceRequests:trace.length,pendingAtCapture:pending,
			observedPeakConcurrent:peakConcurrent,startGapMs:distribution(gaps),durationMs:distribution(durations),
			statuses,outcomes,methods,sources:[...sources].sort((a,b) => b[1] - a[1]).slice(0,10),violations,
			repeatedTargets:{fingerprinted,requests:repeatedTargets,overlapping:overlappingTargets},
			notes:'Counters use complete seconds. Trace and overlap are observed lower bounds; repeated targets may be legitimate retries. Spacing flags allow 5 ms jitter and require review. Reports do not identify IP changes.'};
	}
	function getDiagnosticCacheDecisions(value) {
		return (Array.isArray(value) ? value:[]).slice(0,32).filter(row => Array.isArray(row) &&
			/^[a-z][a-z.-]{0,59}$/i.test(row[0]) && ['details','phone','activity'].includes(row[1]))
			.map(row => [row[0],row[1],...Array(5).fill().map((_,i) => Number.isSafeInteger(row[i + 2]) && row[i + 2] > 0 ? row[i + 2]:0)]);
	}
	function recordDiagnosticCacheDecision(context,reason=context?.cacheReason) {
		try {
			const index=['missing','expired','incomplete','forced','joined'].indexOf(reason),kind=context?.cacheKind;
			if (index < 0 || !['details','phone','activity'].includes(kind)) return;
			const state=getRequestRateState(),rows=state.cacheDecisions ||= [];
			const source=/^[a-z][a-z.-]{0,59}$/i.test(context.source || '') ? context.source:'unknown';
			let row=rows.find(row => row[0] === source && row[1] === kind);
			if (!row) {if (rows.length >= 32) return; rows.push(row=[source,kind,0,0,0,0,0]);}
			row[index + 2]++;
		} catch {}
	}
	async function hashDiagnosticRequestTarget(value) {
		try {
			if (typeof crypto === 'undefined' || !crypto.subtle) return '';
			const key=DIAGNOSTICS_STORAGE_KEY + '_salt';
			let salt=GM_getValue(key,'');
			if (!/^[a-f0-9]{64}$/.test(salt)) {
				salt=[...crypto.getRandomValues(new Uint8Array(32))].map(n => n.toString(16).padStart(2,'0')).join('');
				writeHelperStorageValue(key,salt);
			}
			const url=new URL(value,location.href),ad=url.pathname.match(/^\/(?:anons|personal-ads)\/(\d+)\.html\/?$/);
			url.hash='';
			for (const name of [...url.searchParams.keys()]) if (name.startsWith('vm_')) url.searchParams.delete(name);
			url.searchParams.sort();
			const input=ad ? 'escort-ad:' + ad[1]:url.href;
			const hash=await crypto.subtle.digest('SHA-256',new TextEncoder().encode(salt + ':' + input));
			return [...new Uint8Array(hash).slice(0,16)].map(n => n.toString(16).padStart(2,'0')).join('');
		} catch {return '';}
	}
	function normalizeDiagnosticResponseMeta(value) {
		const number=value => Number.isSafeInteger(value) && value >= 0 ? value:0;
		return {type:['html','json','text','image','other'].includes(value?.type) ? value.type:'unknown',
			redirect:['same','same-ad','home','login','external','other'].includes(value?.redirect) ? value.redirect:'unknown',
			retryAfterMs:Math.min(86400000,number(value?.retryAfterMs)),bytes:number(value?.bytes),challenge:value?.challenge === true};
	}
	function getDiagnosticResponseMeta(response,url) {
		const headers=response?.headers || response?.responseHeaders;
		const type=getResponseHeader(headers,'content-type').split(';')[0].toLowerCase(),retry=getResponseHeader(headers,'retry-after');
		let redirect='unknown';
		try {
			if (response?.finalUrl || response?.url) {
				const from=new URL(url,location.href),to=new URL(response.finalUrl || response.url,from);
				redirect=from.href === to.href ? 'same':from.origin !== to.origin ? 'external':
					/\/(?:login|logowanie|sign-in|auth)(?:\/|$)/i.test(to.pathname) ? 'login':
					to.pathname === '/' && from.pathname !== '/' ? 'home':
					/\/(?:anons|personal-ads)\/\d+\.html/.test(from.pathname) && from.pathname === to.pathname ? 'same-ad':'other';
			}
		} catch {}
		return normalizeDiagnosticResponseMeta({type:/html/.test(type) ? 'html':/json/.test(type) ? 'json':
			/^text\//.test(type) ? 'text':/^image\//.test(type) ? 'image':type ? 'other':'unknown',redirect,
			retryAfterMs:retry ? Math.max(0,/^\d+(?:\.\d+)?$/.test(retry) ? Math.round(Number(retry) * 1000):Date.parse(retry) - Date.now()):0,
			bytes:Number(getResponseHeader(headers,'content-length')),challenge:getResponseHeader(headers,'cf-mitigated') === 'challenge'});
	}
	function normalizeDiagnosticQueueHistory(value,now) {
		const number=value => Number.isSafeInteger(value) && value >= 0 ? value:0;
		const reasons=['none','reload-required','global-concurrency-limit','recovery-probe','pending-dispatch','other-tab-ticket',
			'cooldown','start-lease','legacy-lease','rate-limit','verifying-start','concurrency-limit'];
		const lanes={};
		for (const lane of ['get','head']) {
			const source=value?.lanes?.[lane] || {};
			lanes[lane]=Object.fromEntries(['leaseConflicts','expiredReservations','expiredStartLeases','permitReschedules',
				'admissionRetries','lateWakes','maxWakeLatenessMs'].map(key => [key,number(source[key])]));
			lanes[lane].waitMs=Object.fromEntries(reasons.filter(key => source.waitMs?.[key] > 0).map(key => [key,number(source.waitMs[key])]));
		}
		return {since:number(value?.since),lanes,admissions:(Array.isArray(value?.admissions) ? value.admissions:[]).slice(-40)
			.filter(row => Array.isArray(row) && row.length >= 9 && number(row[1]) > 0 && row[1] >= now - 1800000 && row[1] <= now && ['get','head'].includes(row[3]))
			.map(row => row.slice(0,9).map((v,i) => i === 3 ? v:i === 8 ? Number(v === 1):i === 2 && v > now ? 0:number(v))),
			events:(Array.isArray(value?.events) ? value.events:[]).slice(-40)
			.filter(row => Array.isArray(row) && Number.isSafeInteger(row[0]) && row[0] >= now - 1800000 && row[0] <= now &&
				['get','head'].includes(row[1]) && ['wait','block','timer'].includes(row[2]) && reasons.includes(row[3]))
			.map(row => [row[0],row[1],row[2],row[3],number(row[4]),number(row[5]),number(row[6])])};
	}
	function enrichEscortBanEvidence(at,epoch) {
		try {
			const key=DIAGNOSTICS_STORAGE_KEY + '_ban',previous=GM_getValue(key,null),now=Date.now();
			if (previous?.at !== at || String(GM_getValue(DIAGNOSTICS_EPOCH_KEY,'0')) !== epoch || now - at > 30000) return;
			const recent=getRequestEvidence(now),frozen=normalizeRequestEvidence(previous.evidence),byId=new Map(frozen.pages.map(page => [page.pageId,page]));
			for (const page of recent.pages) {
				if (page.startedAt > at) continue;
				const known=byId.get(page.pageId),cut=normalizeRequestEvidence({at,pages:[page]}).pages[0];
				if (!cut) continue;
				if (known) {
					const rows=new Map(known.requests.map((row,index) => [row[14] || 'old-' + index,row]));
					for (const row of cut.requests) if (row[14]) {
						const old=rows.get(row[14]);
						if (!old || (row[15] >= old[15] && (row[1] || !old[1]) && row[12] >= old[12])) {
							if (old && !row[11]) row[11]=old[11];
							rows.set(row[14],row);
						} else if (!old[11] && row[11]) old[11]=row[11];
					}
					known.requests=[...rows.values()].sort((a,b) => a[0] - b[0]).slice(-1000);
					const seconds=new Map(known.rows.map(row => [row[0],row]));
					for (const row of cut.rows) if (row[0] + 1000 <= at) {
						const old=seconds.get(row[0]);
						seconds.set(row[0],row.map((count,index) => index ? Math.max(count,old?.[index] || 0):count));
					}
					known.rows=[...seconds.values()].sort((a,b) => a[0] - b[0]);
					known.enrichedAt=now;
				} else {
					cut.queues={}; cut.queueHistory=null; cut.cacheDecisions=[]; cut.limits=[]; cut.queueSnapshotUnavailable=true;
					cut.rows=cut.rows.filter(row => row[0] + 1000 <= at); cut.enrichedAt=now;
					frozen.pages.push(cut); byId.set(page.pageId,cut);
				}
			}
			frozen.truncated ||= recent.truncated; frozen.unavailable ||= recent.unavailable;
			writeHelperStorageValue(key,{...previous,enrichedAt:now,evidence:normalizeRequestEvidence(frozen)});
		} catch {}
	}
	function createRequestContext(source='',parent=null) {
		return {source,manual:parent?.manual === true || /manual|^tile\.related$/.test(source)};
	}
	function createOperationCancelledError(reason='Operacja przerwana') {
		const error=new Error(reason);
		error.name='OperationCancelledError';
		error.code='VM_OPERATION_CANCELLED';
		return error;
	}
	function isOperationCancelledError(error) {
		return error?.code === 'VM_OPERATION_CANCELLED' || error?.name === 'OperationCancelledError';
	}
	function createOperationCancelToken(label) {
		const listeners=new Set();
		return {
			label:String(label || 'Długa operacja'), cancelled:false, reason:'',
			cancel(reason='Przerwano na żądanie użytkownika') {
				if (this.cancelled) return;
				this.cancelled=true;
				this.reason=String(reason || 'Operacja przerwana');
				for (const listener of [...listeners]) {
					try {listener(this.reason);} catch {}
				}
				listeners.clear();
			}, throwIfCancelled() {
				if (this.cancelled) {
					throw createOperationCancelledError(this.reason);
				}
			}, onCancel(listener) {
				if (typeof listener !== 'function') return () => {};
				if (this.cancelled) {
					listener(this.reason);
					return () => {};
				}
				listeners.add(listener);
				return () => listeners.delete(listener);
			}
		};
	}
	function waitWithCancellation(delayMs,cancelToken=null) {
		if (!cancelToken) {
			return new Promise(resolve => setTimeout(resolve,delayMs));
		}
		cancelToken.throwIfCancelled();
		return new Promise((resolve,reject) => {
			const timer=setTimeout(() => {
				unsubscribe();
				resolve();
			},Math.max(0,Number(delayMs) || 0));
			const unsubscribe=cancelToken.onCancel(reason => {
				clearTimeout(timer);
				reject(createOperationCancelledError(reason));
			});
		});
	}
	function log(msg,data='') {
		console.log(`[GARSO-ESCORTI] ${msg}`,data);
		if ( data instanceof Error || /(?:\bbłąd\b|nie udało|error|wyjątek)/i.test(String(msg || '')) ) {
			recordDiagnosticError(msg,data);
		}
	}
	function normalizeText(text) {return (text || '').replace(/\s+/g,' ').trim().toLowerCase();}
	function escapeHtml(text) {return String(text ?? '').replace(/[&<>"']/g,c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));}
	function setButtonColor(btn,status) {
		if (btn?.classList?.contains('vm-research-panel-row')) {
			btn.dataset.vmResultStatus=status;
			btn.style.backgroundColor='transparent';
			const resultLine=btn.querySelector('.vm-research-panel-value');
			if (resultLine) {
				resultLine.style.color={
					stale:'#9ca3af', checking:'var(--vm-ui-panel-muted, #d8c8d9)', found:'var(--vm-ui-success, #62cf7b)',
					empty:'var(--vm-ui-accent, #80b9ee)', error:'var(--vm-ui-danger, #ff6262)'
				}[status] || 'var(--vm-ui-panel-muted, #d8c8d9)';
			}
			return;
		}
		btn.style.backgroundColor=BUTTON_COLORS[status] || BUTTON_COLORS.checking;
	}
	function avg(arr) {return arr.length ? arr.reduce((a,b) => a+b,0) / arr.length:null;}
	function round1(n) {return n == null ? null:Math.round(n * 10) / 10;}
	function median(arr) {
		if (!arr.length) return null;
		const x=[...arr].sort((a,b) => a-b);
		const m=Math.floor(x.length / 2);
		return x.length % 2 ? x[m]:(x[m-1] + x[m]) / 2;
	}
	function digitsOnly(phone) {
		let digits=String(phone || '').replace(/\D/g,'');
		if (digits.length === 11 && digits.startsWith('48')) digits=digits.substring(2);
		return digits;
	}
	function formatEscortiDate(value) {
		if (!value) return null;
		let m=value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
		if (m) return `${m[3]}.${m[2]}.${m[1]}`;
		m=value.match(/^(\d{2})[.\-/](\d{2})[.\-/](\d{4})$/);
		if (m) return `${m[1]}.${m[2]}.${m[3]}`;
		return value;
	}
	function formatShortDate(value) {
		const full=formatEscortiDate(value);
		if (!full) return '?';
		const m=full.match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
		return m ? `${m[1]}.${m[2]}.${m[3].slice(-2)}`:full;
	}
	function profileDateToTime(value) {
		const full=formatEscortiDate(value);
		const m=full && full.match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
		return m ? Date.UTC(Number(m[3]),Number(m[2]) - 1,Number(m[1])):null;
	}
	function normalizeTopicTitleKey(value) {
		return normalizeText(String(value || '').normalize('NFKC'));
	}
	const SETTINGS_UI_STYLE_ID='vm-garso-settings-style';
	function ensureSettingsUiStyles() {
		if (document.getElementById(SETTINGS_UI_STYLE_ID)) return;
		appendHelperStyle(SETTINGS_UI_STYLE_ID,` 
.vm-helper-ui .vm-settings-box { max-height: calc(100vh - 32px); width: min(1060px, calc(100vw - 32px)); height: min(96vh, 980px); display: flex; flex-direction: column; overflow: hidden; }
.vm-helper-ui .vm-settings-title { margin-bottom: 4px; }
.vm-helper-ui .vm-settings-subtitle { margin-bottom: 10px; }
.vm-helper-ui .vm-settings-tabs { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 0; margin: 0 0 10px; border-bottom: 1px solid var(--vm-ui-border, #d8d8d8); }
.vm-helper-ui .vm-settings-tab-button { margin: 0 1px -1px 0; padding: 8px 11px 7px; border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: transparent; color: var(--vm-ui-text-muted, #666); cursor: pointer; font-size: 11px; font-weight: 600; line-height: 1.2; }
.vm-helper-ui .vm-settings-tab-button:hover { background: var(--vm-ui-bg-hover, #f7f7f7); color: var(--vm-ui-text, #333); }
.vm-helper-ui .vm-settings-tab-button.-active { border-bottom-color: var(--vm-settings-pink); background: var(--vm-ui-bg, #fff); color: var(--vm-settings-pink); font-weight: 700; }
.vm-helper-ui .vm-settings-grid { min-height: 0; flex: 1 1 auto; display: block; overflow-y: auto; padding: 1px 5px 3px 1px; }
.vm-helper-ui .vm-settings-tab-panel { display: none; }
.vm-helper-ui .vm-settings-tab-panel.-active { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); gap: 12px; }
.vm-helper-ui .vm-settings-section { min-width: 0; }
.vm-helper-ui .vm-settings-section.-full-width { grid-column: 1 / -1; }
.vm-helper-ui .vm-settings-section-title { margin-bottom: 7px; color: var(--vm-ui-text, #222); font-size: 14px; font-weight: 700; }
.vm-helper-ui .vm-settings-section-title.-with-description { margin-bottom: 3px; }
.vm-helper-ui .vm-settings-section-description { margin-bottom: 7px; }
.vm-helper-ui .vm-settings-request-limits > .vm-settings-inline-row { display:grid; grid-template-columns:minmax(0,1fr) 64px; gap:7px; max-width:390px; margin:6px 0; white-space:normal; }
.vm-helper-ui .vm-settings-request-limits .vm-settings-field-label { white-space:normal; }
.vm-helper-ui .vm-settings-actions { flex: 0 0 auto; }
.vm-helper-ui .vm-settings-note { margin: 0 auto 0 0; color: var(--vm-ui-text-muted, #666); font-size: 12px; }
.vm-helper-ui .vm-settings-footer-button { min-width: 92px; }
.vm-helper-ui .vm-settings-inline-row { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; }
.vm-helper-ui .vm-settings-inline-row.-indented { margin-left: 25px; }
.vm-helper-ui .vm-settings-inline-row.-nowrap { flex-wrap: nowrap; white-space: nowrap; }
.vm-helper-ui .vm-settings-info.-indented { margin-left: 25px; }
.vm-helper-ui .vm-settings-options-box { margin: -1px 0 10px 25px; }
.vm-helper-ui .vm-settings-field-label { flex: 0 0 auto; margin-bottom: 0; color: var(--vm-ui-text, #444); font-size: 12px; font-weight: 600; white-space: nowrap; }
.vm-helper-ui .vm-settings-field-description { margin-top: 5px; }
.vm-helper-ui .vm-settings-cache-size, .vm-helper-ui .vm-settings-transfer-status { min-width: 0; }
.vm-helper-ui .vm-settings-cache-size { white-space: nowrap; }
.vm-helper-ui .vm-settings-watch-interval { margin: 7px 0 5px 25px; color: var(--vm-ui-text-muted, #555); white-space: nowrap; }
.vm-helper-ui .vm-settings-runtime-info { margin: 2px 0 5px 25px; }
.vm-helper-ui .vm-settings-auto-warning { margin: 3px 0 2px; }
.vm-helper-ui .vm-settings-auto-box { margin: -2px 0 8px 25px; }
.vm-helper-ui .vm-settings-top-phone-option { margin: 0; color: var(--vm-ui-text-muted, #555); font-size: 12px; }
.vm-helper-ui .vm-settings-top-phone-options { column-gap: 20px; row-gap: 7px; margin: 3px 0 10px 25px; }
.vm-helper-ui .vm-settings-phone-copy-row { gap: 8px; margin: 10px 0 4px; }
.vm-helper-ui .vm-settings-cache-controls-stack { display: grid; gap: 5px; margin: 2px 0 4px 25px; }
.vm-helper-ui .vm-settings-cache-control-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.vm-helper-ui .vm-settings-transfer-row { gap: 8px; }
.vm-helper-ui .vm-diagnostics-health { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px; margin:9px 0; }
.vm-helper-ui .vm-diagnostics-health-text { min-width:0; flex:1 1 240px; overflow-wrap:anywhere; }
.vm-helper-ui .vm-diagnostics-health-version { flex:none; text-decoration:none; white-space:nowrap; }
.vm-helper-ui .vm-diagnostics-health.-ok { border-color:var(--vm-ui-success,#2e9d55); }
.vm-helper-ui .vm-diagnostics-health.-warning { border-color:var(--vm-ui-warning,#c78b00); }
.vm-helper-ui .vm-diagnostics-detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; margin:12px 0; }
.vm-helper-ui :is(.vm-diagnostics-detail-title,.vm-diagnostics-list-title) { margin-bottom:6px; color:var(--vm-ui-text,#222); font-size:12px; font-weight:700; }
.vm-helper-ui .vm-diagnostics-detail-line { color:var(--vm-ui-text-muted,#666); margin:3px 0; overflow-wrap:anywhere; }
.vm-helper-ui .vm-diagnostics-overview { display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:8px; margin:8px 0; }
.vm-helper-ui .vm-diagnostics-card { grid-column:span 3; text-align:center; background:color-mix(in srgb,var(--vm-ui-accent,#925a2b) 7%,var(--vm-ui-bg,#fff)); border-top:2px solid color-mix(in srgb,var(--vm-ui-accent,#925a2b) 45%,var(--vm-ui-border,#ddd)); }
.vm-helper-ui .vm-diagnostics-card.-requests { grid-column:span 4; }
.vm-helper-ui :is(.vm-diagnostics-card,.vm-diagnostics-detail-box,.vm-diagnostics-list) { min-width:0; overflow-wrap:anywhere; }
.vm-helper-ui .vm-diagnostics-card-value { color:var(--vm-ui-text,#333); margin:7px 0 4px; font-size:18px; font-weight:800; line-height:1.2; font-variant-numeric:tabular-nums; }
.vm-helper-ui .vm-diagnostics-card-label { color:var(--vm-ui-text,#333); font-size:11px; font-weight:600; line-height:1.3; }
.vm-helper-ui .vm-diagnostics-card-foot { margin-top:7px; padding-top:5px; border-top:1px solid var(--vm-ui-border,#ddd); }
.vm-helper-ui .vm-diagnostics-list { max-height:180px; overflow:auto; }
@media (max-width:900px) { .vm-helper-ui .vm-diagnostics-card { grid-column:span 6; } }
@media (max-width:650px) { .vm-helper-ui :is(.vm-diagnostics-overview,.vm-diagnostics-detail-grid) { grid-template-columns:1fr; } .vm-helper-ui .vm-diagnostics-overview > .vm-diagnostics-card { grid-column:1 / -1; } }
.vm-helper-ui .vm-diagnostics-event { padding:5px 0; border-top:1px solid var(--vm-ui-border,#ddd); }
.vm-helper-ui .vm-diagnostics-empty { color:var(--vm-ui-text-muted,#666); }
.vm-helper-ui .vm-diagnostics-privacy { margin-top:9px; }
.vm-helper-ui .vm-settings-separator { height:1px; margin:8px 0; background:var(--vm-ui-border,#ddd); }
.vm-helper-ui .vm-settings-small-label { font-size:12px; }
.vm-helper-ui .vm-settings-single-ad-subsection-box, .vm-helper-ui .vm-settings-compact-inset-box { width:calc(100% - 22px); margin:4px 0 8px 22px; }
.vm-helper-ui .vm-settings-compact-inset-box { margin-bottom:4px; }
.vm-helper-ui .vm-settings-number-input { width:64px; }
.vm-helper-ui .vm-settings-mini-input { min-width:0; }
.vm-helper-ui .vm-settings-compact-info { margin-bottom:2px; }
.vm-helper-ui .vm-settings-compact-info.-top { margin:4px 0 0; }
.vm-helper-ui .vm-settings-compact-info.-spaced { margin-bottom:5px; }
.vm-helper-ui .vm-settings-external-service-row, #vm-garso-settings-overlay .vm-settings-tag-group-row { display:grid; gap:8px; align-items:center; min-height:34px; padding:4px 0; }
.vm-helper-ui .vm-settings-external-service-row { grid-template-columns:24px 140px minmax(0,1fr) 28px; }
.vm-helper-ui .vm-settings-external-service-row .vm-settings-mini-input, #vm-garso-settings-overlay .vm-settings-tag-group-name { width:100%; height:24px; min-height:24px; padding:2px 6px; border:1px solid var(--vm-ui-input-border,#c9c9c9); border-radius:4px; background:var(--vm-ui-input-bg,#fff); box-shadow:none; font-size:12px; line-height:18px; }
.vm-helper-ui .vm-settings-external-service-row .vm-settings-mini-remove, #vm-garso-settings-overlay .vm-settings-mini-remove { width:28px; height:28px; min-height:28px; padding:0; border:1px solid var(--vm-ui-input-border,#c9c9c9); border-radius:5px; background:var(--vm-ui-bg,#fff); color:var(--vm-ui-text,#222); font-size:17px; line-height:1; }
.vm-helper-ui .vm-settings-mini-remove.-hidden { visibility:hidden; }
.vm-helper-ui .vm-settings-tag-add-group { margin-top:3px; }
#vm-garso-settings-overlay .vm-settings-tag-group-row { grid-template-columns:140px 36px minmax(0,1fr) 28px; }
.vm-helper-ui .vm-settings-tag-group-row.-separated { border-top:1px solid var(--vm-ui-border,#eee); }
#vm-garso-settings-overlay .vm-settings-tag-list { display:flex; flex-wrap:wrap; align-items:center; gap:4px 5px; min-width:0; min-height:28px; }
.vm-helper-ui .vm-settings-tag-automatic { color:var(--vm-ui-text-muted,#777); font-size:9px; line-height:21px; }
.vm-helper-ui .vm-settings-tag-chip { height:19px; min-height:19px; padding:0 5px; border:1px solid var(--vm-settings-tag-color); border-radius:10px; background:var(--vm-settings-tag-color); color:#fff; cursor:pointer; font-size:8.8px; line-height:17px; }
#vm-garso-settings-overlay .vm-settings-tag-select { flex:0 0 190px; width:190px; max-width:190px; height:24px; min-height:24px; padding:2px 26px 2px 8px; border:1px solid var(--vm-ui-input-border,#c9c9c9); border-radius:5px; background:var(--vm-ui-input-bg,#fff); color:var(--vm-ui-input-text,var(--vm-ui-text,#222)); box-shadow:none; font-size:12px; line-height:1.2; }
.vm-helper-ui .vm-settings-notes-actions { display:flex; flex-direction:column; align-items:flex-start; gap:6px; margin-left:25px; }
.vm-helper-ui .vm-settings-notes-actions-row { display:flex; flex-wrap:wrap; align-items:center; gap:7px; margin:0; }
.vm-helper-ui .vm-settings-notes-actions-row .vm-settings-cache-button { flex:0 0 auto; white-space:nowrap; }
.vm-helper-ui .vm-settings-notes-actions-status { display:block; min-height:0; margin:0; }
.vm-escort-list-init-error { box-sizing: border-box; margin: 10px 0; padding: 9px 11px; border: 1px solid var(--vm-ui-danger, #d9534f); border-radius: 7px; background: var(--vm-ui-bg-alt, rgba(217,83,79,.1)); color: var(--vm-ui-danger, #a52320); font:700 12px/1.35 var(--vm-ui-font-family,Arial,sans-serif); }
`);
	}
	function makeSettingsTabGearButton( tabId, title='Ustawienia skryptu' ) {
		const button=makeButton('vm-settings-tab-gear');
		setControlTitle(button,title);
		button.innerHTML= '<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true" ' +
			'style="display:block;margin:auto;fill:none;stroke:currentColor;' +
			'stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round">' + '<circle cx="12" cy="12" r="3"/>' +
			'<path d="M19.4 15a1.7 1.7 0 0 0 .34 1.88l.06.06-2.83 2.83-.06-.06' +
			'a1.7 1.7 0 0 0-1.88-.34 1.7 1.7 0 0 0-1.03 1.56V21h-4v-.09' +
			'A1.7 1.7 0 0 0 9 19.36a1.7 1.7 0 0 0-1.88.34l-.06.06-2.83-2.83' +
			'.06-.06A1.7 1.7 0 0 0 4.63 15a1.7 1.7 0 0 0-1.56-1.03H3v-4h.09' +
			'A1.7 1.7 0 0 0 4.64 9a1.7 1.7 0 0 0-.34-1.88l-.06-.06 2.83-2.83' +
			'.06.06A1.7 1.7 0 0 0 9 4.63a1.7 1.7 0 0 0 1.03-1.56V3h4v.09' +
			'A1.7 1.7 0 0 0 15 4.64a1.7 1.7 0 0 0 1.88-.34l.06-.06 2.83 2.83' +
			'-.06.06A1.7 1.7 0 0 0 19.37 9a1.7 1.7 0 0 0 1.56 1.03H21v4h-.09' + 'A1.7 1.7 0 0 0 19.4 15z"/>' + '</svg>';
		button.classList.add('vm-ui-button','vm-ui-icon','vm-ui-compact');
		button.addEventListener('click',event => {
			stopUiEvent(event);
			const watchOverlay= document.getElementById(WATCH_SETTINGS_OVERLAY_ID);
			if (watchOverlay) {
				if (typeof watchOverlay._vmClose === 'function') {
					watchOverlay._vmClose();
				} else {
					detachHelperNode(watchOverlay);
				}
			}
			openSettingsModal({tab:tabId});
		});
		return button;
	}
	function getSettingsCheckboxDefinitions(current,currentWatch={enabled:current.watchEnabled}) {
		return {
			quickAdPreview:['Pokazuj dymek z szybkim podglądem anonsu po najechaniu na link Escort.club (na Escort.club, Escorti.pl i Garsonierze)',current.quickAdPreview !== false],
			watchEnabled:['Włącz ulubione, ukrywanie, notatki i kategorie anonsów oraz profili',currentWatch.enabled,false],
			showWatchOnlineCount:['Stale pokazuj w lewym górnym rogu strony liczbę profili/anonsów online z włączonym alertem online/offline',current.showWatchOnlineCount,false],
			showWatchAlertBar:['Pokazuj alerty na górnym pasku',current.showWatchAlertBar !== false],
			watchSystemNotifications:['Włącz powiadomienia systemowe o nowych alertach',current.watchSystemNotifications],
			watchNotificationAnonymous:['Anonimizuj powiadomienia systemowe',current.watchNotificationAnonymous],
			autoGarsoCheck:['Automatycznie sprawdzaj Garsonierę po otwarciu strony z anonsem',current.autoGarsoCheck],
			garsoCheckEscortLinks:['Pokazuj przy linkach Escort.club czy strona z anonsem jest dostępna (online).',current.garsoCheckEscortLinks !== false],
			garsoShowSearchIcons:['Pokazuj lupkę przy linkach do Escort.club i numerach telefonu',current.garsoShowSearchIcons !== false],
			garsoShowTopicTools:['Pokazuj filtr postów oraz historię numerów i linków w temacie',current.garsoShowTopicTools !== false],
			garsoShowTopicSummary:['Pokazuj podsumowanie ocen i wzmianek w tematach',current.garsoShowTopicSummary !== false],
			garsoShowTopicHelperData:['W tematach na forum pokazuj notatki, kategorie i czy w ulubionych. Pokazuj w temacie listę odwiedzonych powiązanych anonsów',current.garsoShowTopicHelperData !== false],
			mergeEscortAds:['Agreguj powiązane anonse Escort.club',current.mergeEscortAds,false],
			showRelatedAdsTileLink:['Pokaż na kafelku link do profilu, czyli do listy ogłoszeń tej osoby (niezależne od agregowania)',current.showRelatedAdsTileLink !== false],
			openAllActiveRelatedAdsFromTile:['Zamiast otwierać stronę profilu otwórz w nowych kartach wszystkie aktywne ogłoszenia',!!current.openAllActiveRelatedAdsFromTile],
			showEscortiTileData:['Pokaż dane z escorti.pl na kafelkach (ilość powiązanych anonsów, datę założenia profilu, ilość tematów na Garso)',current.showEscortiTileData],
			showPricesInSearchResults:['Pokazuj cenę na kafelkach (za jednostkę czasu wybraną na stronie wyników)',current.showPricesInSearchResults,false],
			multiLocationEnabled:['Włącz multiwybór lokalizacji',current.multiLocationEnabled !== false],
			saveSearchLocationsEnabled:['Włącz możliwość dodawania lokalizacji do „ulubionych”',current.saveSearchLocationsEnabled !== false],
			showBottomLeftMenu:['Pokaż menu Escort Helper na górnym pasku',current.showBottomLeftMenu !== false],
			showMenuPagesAsFavicons:['Pokazuj linki do stron',current.showMenuPagesAsFavicons === true],
			showTopPhoneSearch:['Pokaż podręczną wyszukiwarkę w prawym dolnym rogu stron Escort.club, Escorti.pl i Garsoniera',current.showTopPhoneSearch !== false],
			hideRecommendedSearchSection:['Ukryj sekcję „Polecane” na stronie wyników',current.hideRecommendedSearchSection],
			hideRecommendedAdSection:['Ukryj sekcję „Polecane” na stronie pojedynczego anonsu',current.hideRecommendedAdSection],
			hideSingleAdContactButtons:['Ukryj przyciski „Daj napiwek” i „Wyślij wiadomość”',current.hideSingleAdContactButtons],
			hideSingleAdActions:['Ukryj sekcję z przyciskami „Dodaj do ulubionych”, „Ukryj w wynikach”, „Zgłoś naruszenie”, „Dodaj notatkę”',current.hideSingleAdActions],
			hideFeaturedSearchSection:['Ukryj sekcję „Wyróżnione” na stronie wyników',current.hideFeaturedSearchSection],
			hidePopularCitiesSection:['Ukryj sekcję „Popularne Miasta”',current.hidePopularCitiesSection],
			showImageSearchButtons:['Na zdjęciach pokazuj przyciski do wyszukiwania grafiki w serwisach zewnętrznych.',current.showImageSearchButtons],
			showPhoneReputationLinks:['Włącz wyświetlanie linków do sprawdzania numeru telefonu.',current.showPhoneReputationLinks],
			autoPhoneReputationSearch:['Automatycznie wyszukuj numer w obsługiwanych serwisach po wejściu na anons.',current.autoPhoneReputationSearch],
			showSummarySidePanel:['Pokazuj panel boczny „Podsumowanie”',current.showSummarySidePanel],
			showCompactSummaryAboveDescription:['Wyświetl skrócone podsumowanie nad opisem',current.showCompactSummaryAboveDescription],
			showTagHighlights:['Włącz prezentację usług / tagów',current.showTagHighlights !== false],
			showPhoneCopyButton:['Pokazuj przycisk do kopiowania numeru telefonu',current.showPhoneCopyButton !== false],
			escortFastStart:['Szybki start aktywnej karty: 30 zapytań z podwójnymi limitami',current.escortFastStart !== false],
			showLiveDiagnostics:['Pokazuj prędkość zapytań do Escort.club w lewym dolnym rogu stron',current.showLiveDiagnostics === true]
		};
	}
	function createSettingsFields(current) {
		function fitSelectToLongestOption(select,minimumCharacters=0) {
			const longest=[...select.options].reduce( (length,option) => Math.max(length,option.textContent.length), 0 );
			select.style.width=`${Math.max(minimumCharacters,longest) + 4}ch`;
			select.style.maxWidth='100%';
			select.style.flex='0 1 auto';
			select.style.textAlign='left';
		}
		const settingsFieldControls={};
		const watchSelect={rowTag:'label',labelTag:'span',rowClass:'vm-settings-inline-row -nowrap vm-settings-watch-interval',
			labelClass:'vm-settings-small-label',selectClass:'vm-settings-select -compact vm-ui-field'};
		const settingsSelectDefinitions={
			watchIntervalMinutes:['Sprawdzaj co:',WATCH_INTERVAL_SELECT_OPTIONS,String,{...watchSelect,minChars:8,read:Number}],
			watchProfileResolutionMode:['Profil osoby ustalaj przez:',WATCH_PROFILE_RESOLUTION_OPTIONS,normalizeWatchProfileResolutionMode,watchSelect],
			nonAlertChangeNewness:['Zmianę w oknie Ulubione (w kolumnie „Zmiany”) pokazuj wyróżnioną kolorem jako nową przez:',
				WATCH_NON_ALERT_NEWNESS_OPTIONS,normalizeWatchNonAlertChangeNewness,{...watchSelect,
					rowClass:watchSelect.rowClass+' -indented',read:normalizeWatchNonAlertChangeNewness}],
			escortAggregationMode:['Sposób agregowania kafelków:',ESCORT_AGGREGATION_MODE_OPTIONS,normalizeEscortAggregationMode],
			phoneClipboardFormat:['Format kopiowanego numeru:',PHONE_CLIPBOARD_FORMAT_OPTIONS,normalizePhoneClipboardFormat,
				{rowTag:'label',rowClass:'vm-settings-inline-row vm-settings-phone-copy-row',labelTag:'span',labelClass:''}],
			favoritesDefaultView:['Listę ulubionych otwieraj domyślnie w widoku:',[['table','tabeli'],['tiles','kafelków']],normalizeFavoritesDefaultView,{rowClass:'vm-settings-inline-row -indented',labelClass:'vm-settings-small-label',minChars:12}],
			compactSummaryComparisonMode:['Sposób znajdowania anonsów tej samej osoby w skróconym podsumowaniu:',ESCORT_COMPACT_SUMMARY_MODE_OPTIONS,normalizeEscortCompactSummaryMode],
			relatedAdsTileLinkMode:['Sposób znajdowania ogłoszeń:',ESCORT_RELATED_ADS_TILE_LINK_MODE_OPTIONS,normalizeEscortRelatedAdsTileLinkMode],
			topPhoneSearchEnterTarget:['Po naciśnięciu Enter wyszukuj domyślnie w:',TOP_PHONE_SEARCH_TARGET_OPTIONS,normalizeTopPhoneSearchEnterTarget,{
				rowClass:'vm-settings-inline-row vm-settings-top-phone-option', labelClass:'', labelTag:'span',
				selectClass:"vm-settings-select -wide-arrow vm-ui-field", minChars:16
			}],
			topPhoneSearchOpenMode:['Otwieraj wyniki:',TOP_PHONE_SEARCH_OPEN_MODE_OPTIONS,normalizeTopPhoneSearchOpenMode,{
				rowClass:'vm-settings-inline-row vm-settings-top-phone-option', labelClass:'', labelTag:'span',
				selectClass:"vm-settings-select -wide-arrow vm-ui-field"
			}]
		};
		function makeSettingsSelectRow(key) {
			const [labelText,options,normalize,config={}]=settingsSelectDefinitions[key];
			const {rowTag='div',rowClass='vm-settings-inline-row',labelClass='vm-settings-field-label',
				labelTag='label',selectClass="vm-settings-select vm-ui-field",minChars=0,read=String}=config;
			const value=normalize(current[key]);
			const row=makeElement(rowTag,rowClass);
			const label=makeElement(labelTag,labelClass,labelText);
			const select=makeElement('select');
			appendSelectOptions(select,options,value);
			select.className=selectClass;
			fitSelectToLongestOption(select,minChars);
			row.append(label,select);
			settingsFieldControls[key]=[select,read];
			return {row,label,select};
		}
		const settingsNumberDefinitions={
			maxRelatedAdsTabs:['Maksymalna liczba otwieranych anonsów:',1,null,'vm-settings-field-label',
				value => Math.max(1,Number(value) || DEFAULT_SETTINGS.maxRelatedAdsTabs),
				value => Math.max(1,Math.floor(Number(value) || DEFAULT_SETTINGS.maxRelatedAdsTabs))],
			alarmingTagCountLimit:['Alarmująca liczba oferowanych usług / tagów: powyżej',0,10000,'vm-settings-small-label',
				value => value,value => normalizeCustomFilterInteger(value,DEFAULT_SETTINGS.alarmingTagCountLimit,0,10000)]
		};
		const requestRate=value => normalizeEscortRequestLimitPerMinute(value);
		settingsNumberDefinitions.escortLimitPerMinute=['Łączny limit zapytań do Escort.club na minutę:',1,840,'vm-settings-field-label',requestRate,requestRate];
		const concurrent=value => normalizeEscortRequestMaxConcurrent(value,DEFAULT_SETTINGS.escortMaxConcurrent);
		settingsNumberDefinitions.escortMaxConcurrent=['Łącznie równoczesnych — GET / POST i HEAD:',1,24,'vm-settings-field-label',concurrent,concurrent];
		function makeSettingsNumberRow(key) {
			const [text,min,max,labelClass,load,save]=settingsNumberDefinitions[key];
			const row=makeElement('label','vm-settings-inline-row -nowrap');
			const label=makeElement('span',labelClass,text);
			const input=makeElement('input',"vm-settings-number-input vm-ui-field",null,{type:'number',min:String(min)});
			if (max != null) input.max=String(max);
			input.step='1';
			input.inputMode='numeric';
			input.value=String(load(current[key]));
			row.append(label,input);
			settingsFieldControls[key]=[input,save];
			return {row,input};
		}
		return {select:makeSettingsSelectRow,number:makeSettingsNumberRow,controls:settingsFieldControls};
	}
	function createEscortRequestSettingsRows(makeNumberRow,fastStartCheck=null) {
		const box=makeElement('div','vm-settings-request-limits');
		for (const key of ['escortLimitPerMinute','escortMaxConcurrent']) {
			const {row,input}=makeNumberRow(key);
			row.title=input.title=key === 'escortLimitPerMinute'
				? 'Bazowy limit 1–840 zapytań/min, wspólny dla HEAD i GET / POST ze wszystkich kart.'
				: 'Bazowy limit 1–24 jednoczesnych zapytań GET / POST i HEAD razem, we wszystkich kartach.';
			box.appendChild(row);
		}
		if (fastStartCheck) {
			box.append(fastStartCheck,makeElement('div','vm-settings-info -indented vm-ui-help',
				'Podwaja tempo i równoległość dla oglądanej strony i ręcznych akcji. Ponownie dostępny po min. 5 min, jeśli w ostatnich 5 min wysłano łącznie najwyżej 90 zapytań.'));
		}
		return box;
	}
	function waitForSettingsPaint() {
		return new Promise(resolve => {
			let frame=0;
			const finish=() => {clearTimeout(timer); cancelAnimationFrame(frame); resolve();};
			let timer=setTimeout(finish,100);
			if (!document.hidden) frame=requestAnimationFrame(() => {
				clearTimeout(timer);
				timer=setTimeout(finish,0);
			});
		});
	}
	function createDeferredSettingsTabLoader(overlay,panels,loaders) {
		let generation=0;
		return async tabId => {
			const ticket=++generation, panel=panels.get(tabId);
			for (const node of panels.values()) node.removeAttribute('aria-busy');
			if (!loaders[tabId]) return;
			const current=() => overlay.isConnected && ticket === generation;
			panel.querySelector('.vm-settings-load-error')?.remove();
			panel.setAttribute('aria-busy','true');
			await waitForSettingsPaint();
			if (!current()) return;
			try {
				await loaders[tabId](current);
			} catch (error) {
				if (current()) {
					const message=makeElement('div','vm-settings-load-error vm-ui-help vm-ui-error',
						'Nie udało się wczytać tej sekcji. Otwórz zakładkę ponownie, aby spróbować jeszcze raz.');
					panel.prepend(message);
				}
				log('Błąd wczytywania zakładki ustawień',error);
			} finally {
				if (current()) panel.removeAttribute('aria-busy');
			}
		};
	}
	async function openSettingsModal(options={}) {
		const existingOverlay= document.getElementById('vm-garso-settings-overlay');
		if (existingOverlay) {
			const requestedTab=String(options.tab || '').trim();
			if ( requestedTab && typeof existingOverlay._vmActivateTab === 'function' ) {
				existingOverlay._vmActivateTab(requestedTab);
			}
			existingOverlay.style.display='flex';
			return;
		}
		ensureSettingsUiStyles();
		const firstRun=!!options.firstRun;
		let requestedTab=String(options.tab || '').trim();
		const overlay=makeElement('div');
		overlay.id='vm-garso-settings-overlay';
		prepareHelperStyleApi(overlay);
		overlay.classList.add('vm-ui-overlay');
		overlay.style.setProperty( '--vm-settings-pink', `var(--vm-ui-accent, ${getEscortPagePinkColor()})` );
		const box=makeElement('div',"vm-settings-box vm-ui-dialog");
		const title=makeElement('div',"vm-settings-title vm-ui-title",'Ustawienia');
		const subtitle=firstRun
			? makeElement( 'div', "vm-settings-subtitle vm-ui-help",
				'Dostosuj działanie skryptu do swoich potrzeb i zapisz ustawienia.' )
			:null;
		const tabBar=makeElement('div','vm-settings-tabs');
		tabBar.setAttribute('role','tablist');
		const contentGrid=makeElement('div','vm-settings-grid');
		contentGrid.setAttribute('aria-busy','true');
		const note=makeElement('div','vm-settings-note','Wczytywanie ustawień…');
		note.setAttribute('role','status');
		const actions=makeElement('div',"vm-settings-actions vm-ui-actions");
		const cancelBtn=makeButton("vm-settings-footer-button -secondary vm-ui-button",'Anuluj');
		const saveBtn=makeButton("vm-settings-footer-button -primary vm-ui-button",'Zapisz');
		saveBtn.disabled=true;
		const closeController=createDialogCloseController(overlay,{onClose:() => overlay._vmDispose?.()});
		closeController.bindButton(cancelBtn);
		overlay._vmActivateTab=tabId => {requestedTab=tabId;};
		actions.append(note,cancelBtn,saveBtn);
		box.append(title);
		if (subtitle) box.append(subtitle);
		box.append(tabBar,contentGrid,actions);
		overlay.appendChild(box);
		document.body.appendChild(overlay);
		await waitForSettingsPaint();
		if (!overlay.isConnected) return;
		try {
			populateSettingsModal({overlay,tabBar,contentGrid,saveBtn,closeController,requestedTab});
			note.textContent='Zmiany zostaną zastosowane po zapisaniu i odświeżeniu strony.';
			saveBtn.disabled=false;
		} catch (error) {
			overlay._vmDispose?.();
			note.textContent='Nie udało się wczytać ustawień. Zamknij okno i spróbuj ponownie.';
			log('Błąd wczytywania ustawień',error);
		} finally {contentGrid.removeAttribute('aria-busy');}
	}
	function populateSettingsModal({overlay,tabBar,contentGrid,saveBtn,closeController,requestedTab}) {
		let diagnosticsStatusTimer=null;
		overlay._vmDispose=() => {
			clearInterval(diagnosticsStatusTimer);
		};
		const current=getSettings();
		const currentWatch=getWatchSettings();
		function makeSettingsSection(sectionTitle,description='',fullWidth=false,...children) {
			const section=makeElement('section',"vm-settings-section vm-ui-panel");
			section.classList.toggle('-full-width',fullWidth);
			const heading=makeElement('div','vm-settings-section-title',sectionTitle);
			heading.classList.toggle('-with-description',!!description);
			section.appendChild(heading);
			if (description) section.appendChild(makeElement('div',"vm-settings-section-description vm-ui-help",description));
			if (children.length) section.append(...children.filter(Boolean));
			return section;
		}
		function makeCheckbox(labelText,checked) {
			const label=makeElement('label',"vm-settings-choice vm-ui-choice");
			const input=makeElement('input','',null,{type:'checkbox',checked});
			const text=makeElement('span','',labelText);
			label.appendChild(input);
			label.appendChild(text);
			return {label,input};
		}
		function makeRadioOption(name,value,labelText,checked) {
			const label=makeElement('label',"vm-settings-choice -radio vm-ui-choice");
			const input=makeElement('input','',null,{type:'radio',name,value,checked});
			const text=makeElement('div','',labelText);
			label.append(input,text);
			return {label,input,text};
		}
		const {select:makeSettingsSelectRow,number:makeSettingsNumberRow,controls:settingsFieldControls}=createSettingsFields({
			...current,watchIntervalMinutes:currentWatch.intervalMinutes,nonAlertChangeNewness:currentWatch.nonAlertChangeNewness
		});
		const setSettingsVisualEnabled=(element,enabled,strong=false) => {
			element.style.opacity=enabled ? '1':(strong ? '.42':'.5');
			element.style.cursor=enabled ? '':'not-allowed';
		};
		function bindSettingsEnabled(input,container,controls,strong=false) {
			const sync=() => {
				const enabled=input.checked;
				for (const control of controls) control.disabled=!enabled;
				setSettingsVisualEnabled(container,enabled,strong);
				container.setAttribute('aria-disabled',String(!enabled));
			};
			input.addEventListener('change',sync);
			sync();
		}
		const checkboxDefinitions=getSettingsCheckboxDefinitions(current,currentWatch);
		const checks=Object.fromEntries( Object.entries(checkboxDefinitions).map(
				([key,[label,checked]]) => [key,makeCheckbox(label,checked)] ) );
		const readDeclarativeCheckboxSettings=() => Object.fromEntries( Object.entries(checkboxDefinitions)
					.filter(([,definition]) => definition[2] !== false) .map(([key]) => [key,checks[key].input.checked]) );
		const favoritesDefaultView=makeSettingsSelectRow('favoritesDefaultView');
		favoritesDefaultView.select.id='vm-favorites-default-view';favoritesDefaultView.label.htmlFor=favoritesDefaultView.select.id;
		const watchFavoritesInfo=makeElement( 'div', "vm-settings-info -indented vm-ui-help",
			'Skrypt używa własnych list Ulubione i ukryte. Po włączeniu ukrywa natywne, czyli standardowe przyciski Escort.club służące do dodawania do ulubionych i ukrywania anonsów. Wyłączenie wstrzymuje działanie obu list, zachowując zapisane pozycje.'
		);
		const editFavoriteCategoriesRow=makeElement( 'div', 'vm-settings-notes-actions-row' );
		const editFavoriteCategoriesBtn=makeButton( "vm-settings-cache-button vm-ui-button", 'Dodaj / edytuj kategorie' );
		editFavoriteCategoriesBtn.addEventListener('click',event => {
			event.preventDefault();
			openFavoriteCategoriesEditor(syncClearNotesAndCategoriesButtons);
		});
		const clearNotesRow=makeElement('div','vm-settings-notes-actions-row');
		const clearAllNotesBtn=makeButton( "vm-settings-cache-button -danger vm-ui-button", 'Usuń wszystkie notatki' );
		const clearAllCategoriesBtn=makeButton( "vm-settings-cache-button -danger vm-ui-button", 'Usuń wszystkie kategorie' );
		const clearNotesStatus=makeElement('span','vm-settings-info vm-settings-notes-actions-status vm-ui-help');
		const clearCategoriesStatus=makeElement('span','vm-settings-info vm-settings-notes-actions-status vm-ui-help');
		const syncClearNotesAndCategoriesButtons=() => {
			const items=getWatchedItems();
			clearAllNotesBtn.disabled= !items.some( item => !!String( item?.note || '' ).trim() );
			clearAllCategoriesBtn.disabled= !items.some( item => normalizeFavoriteAssignedCategories( item?.categories
						).length > 0 );
		};
		clearAllNotesBtn.addEventListener( 'click', event => {
				event.preventDefault();
				if (clearAllNotesBtn.disabled) return;
				const confirmed=window.confirm( 'Usunąć wszystkie zapisane notatki ze wszystkich anonsów i profili?\n\n' +
					'Kategorie, Ulubione i ustawienia alertów pozostaną bez zmian. Po usunięciu przez 10 sekund będzie dostępne „Cofnij”.' );
				if (!confirmed) return;
				const removed= clearAllFavoriteNotes();
				clearNotesStatus.textContent= removed
						? `Usunięto notatki z ${removed} pozycji.`
						:'Brak notatek do usunięcia.';
				syncClearNotesAndCategoriesButtons();
				setTimeout( syncClearNotesAndCategoriesButtons, 0 );
			} );
		clearAllCategoriesBtn.addEventListener( 'click', event => {
				event.preventDefault();
				if (clearAllCategoriesBtn.disabled) return;
				const confirmed=window.confirm( 'Usunąć wszystkie przypisania kategorii ze wszystkich anonsów i profili?\n\n' +
					'Lista dostępnych kategorii i ich kolory pozostaną bez zmian. Ulubione, notatki i alerty również pozostaną bez zmian. Przez 10 sekund będzie dostępne „Cofnij”.'
				);
				if (!confirmed) return;
				const removed= clearAllFavoriteAssignedCategories();
				clearCategoriesStatus.textContent= removed
						? `Usunięto kategorie z ${removed} pozycji.`
						:'Brak przypisanych kategorii do usunięcia.';
				syncClearNotesAndCategoriesButtons();
			} );
		clearNotesRow.appendChild(clearAllNotesBtn);
		editFavoriteCategoriesRow.append(editFavoriteCategoriesBtn,clearAllCategoriesBtn);
		const notesActionsBox=makeElement( 'div', 'vm-settings-notes-actions' );
		notesActionsBox.append(clearNotesRow,clearNotesStatus);
		clearAllNotesBtn.disabled=clearAllCategoriesBtn.disabled=true;
		const watchOnlineSeparator=makeElement( 'div', 'vm-settings-separator' );
		const {row:nonAlertChangeNewnessRow,select:nonAlertChangeNewnessSelect}=makeSettingsSelectRow('nonAlertChangeNewness');
		const nonAlertChangeNewnessInfo=makeElement( 'div', "vm-settings-info -indented vm-ui-help",
			'Poniższe dotyczy tylko zmian, które nie wywołały alertu. Zmiana związana z alertem pozostaje nowa do jego odczytania: przyciskiem ✓/× lub przez otwarcie strony alertu, także w tle. Samo otwarcie tabeli lub kafelków nie odczytuje alertów.'
		);
		const clearWatchHistoryRow=makeElement( 'div', 'vm-settings-inline-row -indented' );
		const clearWatchHistoryBtn=makeButton( "vm-settings-cache-button -danger vm-ui-button", 'Wyczyść historię zmian' );
		const clearWatchHistoryStatus=makeElement( 'span', "vm-settings-info vm-ui-help" );
		clearWatchHistoryBtn.disabled=true;
		clearWatchHistoryBtn.addEventListener( 'click', event => {
				event.preventDefault();
				if (clearWatchHistoryBtn.disabled) return;
				const confirmed=window.confirm( 'Usunąć całą historię zmian?\n\n' +
					'Lepiej tego nie robić, ponieważ po usunięciu nie będzie można podejrzeć tych zmian w inny sposób. Tej operacji nie można cofnąć.'
				);
				if (!confirmed) return;
				saveWatchEvents([]);
				renderWatchNotificationBar();
				clearWatchHistoryBtn.disabled=true;
				clearWatchHistoryStatus.textContent= 'Historia zmian została usunięta.';
			} );
		clearWatchHistoryRow.append( clearWatchHistoryBtn, clearWatchHistoryStatus );
		const clearFavoritesRow=makeElement('div','vm-settings-inline-row -indented');
		const clearAllFavoritesBtn=makeButton('vm-settings-cache-button -danger vm-ui-button','Usuń wszystkie ulubione');
		clearAllFavoritesBtn.id='vm-settings-clear-all-favorites';
		const clearFavoritesStatus=makeElement('span','vm-settings-info vm-ui-help');
		clearFavoritesStatus.setAttribute('role','status');
		const syncClearAllFavoritesButton=()=>{clearAllFavoritesBtn.disabled=!getWatchedItems().some(item=>item.favorite);};
		clearAllFavoritesBtn.addEventListener('click',event=>{
			stopUiEvent(event);
			const count=getWatchedItems().filter(item=>item.favorite).length;
			if(!count){syncClearAllFavoritesButton();return;}
			if(!window.confirm(`Usunąć wszystkie ulubione (${count}) i wyłączyć ich alerty?\n\n`+
				'Profile i anonse pozostaną zapisane, także te bez notatek. Notatki, kategorie, ukrywanie, powiązania i historia zmian nie zostaną usunięte.\n\n'+
				'Oczekujące alerty tych pozycji zostaną oznaczone jako odczytane. Operacja zadziała od razu, bez zapisywania ustawień.'))return;
			clearAllFavoritesBtn.disabled=true;
			try{
				const removed=clearAllFavorites();
				clearFavoritesStatus.textContent=`Usunięto z Ulubionych: ${removed}. Alerty wyłączone; profile, notatki i kategorie zachowane.`;
			}catch(error){clearFavoritesStatus.textContent='Nie udało się usunąć wszystkich ulubionych.';log('Błąd usuwania wszystkich ulubionych',error);}
			finally{syncClearAllFavoritesButton();}
		});
		const clearFavoritesInfo=makeElement('div','vm-settings-info -indented vm-ui-help',
			'Usuwa tylko z Ulubionych i wyłącza alerty tych pozycji. Nie usuwa profili, anonsów, notatek, kategorii, ukrywania ani historii. Działa od razu po potwierdzeniu.');
		clearFavoritesRow.append(clearAllFavoritesBtn,clearFavoritesStatus);
		clearAllFavoritesBtn.disabled=true;
		let watchEnabledDraft=currentWatch.enabled === true;
		checks.watchEnabled.input.style.pointerEvents='none';
		checks.watchEnabled.label.tabIndex=0;
		checks.watchEnabled.label.setAttribute('role','checkbox');
		const {row:watchIntervalRow,select:watchIntervalSelect}=makeSettingsSelectRow('watchIntervalMinutes');
		Object.assign(watchIntervalSelect.style,{width:'110px',minWidth:'110px',flex:'0 0 110px'});
		const notificationTextRow=makeElement('label','vm-settings-inline-row');
		const notificationText=makeElement('input','vm-ui-field',null,{type:'text',maxLength:240,value:current.watchNotificationText,placeholder:'Nowy alert'});
		notificationText.style.flex='1'; notificationText.style.minWidth='120px';
		notificationTextRow.append(makeElement('span','vm-settings-field-label','Treść anonimowego alertu:'),notificationText);
		settingsFieldControls.watchNotificationText=[notificationText,normalizeWatchNotificationText];
		const notificationInfo=makeElement('div','vm-ui-help','Kliknięcie otwiera powiązaną stronę i odczytuje alert. Anonimizacja ukrywa dane profilu; system może nadal pokazywać nazwę rozszerzenia.');
		const watchRuntimeInfo=makeElement('div',"vm-settings-info vm-settings-runtime-info vm-ui-help",'Automatyczne sprawdzanie działa przy otwartej karcie Escort.club, Escorti.pl lub Garsoniera.com.pl. Przy włączonych alertach częstotliwość poniżej dotyczy aktywności i nowych anonsów. Pełne dane oraz profile tylko z notatką są sprawdzane nie częściej niż co godzinę. Ukryte profile są sprawdzane pod kątem nowych anonsów co około godzinę.');
		const {row:watchProfileModeRow,select:watchProfileModeSelect}=makeSettingsSelectRow('watchProfileResolutionMode');
		watchProfileModeSelect.title='Wspólny sposób ustalania profili w Ulubionych i Ukrytych na podstawie anonsu Escort.club. Przy „Zawsze pytaj” wybór pojawia się przy dodawaniu lub edycji całego profilu, także zapisanego wcześniej. Automatyczne sprawdzanie używa zapisanego sposobu.';
		watchProfileModeRow.title=watchProfileModeSelect.title;
		const syncWatchSettingsControls=() => {
			checks.watchEnabled.input.checked=watchEnabledDraft;
			checks.watchEnabled.label.setAttribute( 'aria-checked', watchEnabledDraft ? 'true':'false' );
			const onlineCountEnabled=watchEnabledDraft;
			favoritesDefaultView.select.disabled=!onlineCountEnabled;
			setSettingsVisualEnabled(favoritesDefaultView.row,onlineCountEnabled);
			checks.showWatchOnlineCount.input.disabled=!onlineCountEnabled;
			setSettingsVisualEnabled( checks.showWatchOnlineCount.label, onlineCountEnabled );
			const notificationsEnabled=onlineCountEnabled && checks.watchSystemNotifications.input.checked;
			for (const [key,enabled] of [['showWatchAlertBar',onlineCountEnabled],['watchSystemNotifications',onlineCountEnabled],['watchNotificationAnonymous',notificationsEnabled]]) {
				checks[key].input.disabled=!enabled; setSettingsVisualEnabled(checks[key].label,enabled);
			}
			notificationTextRow.hidden=!checks.watchNotificationAnonymous.input.checked;
			notificationTextRow.style.display=notificationTextRow.hidden ? 'none':'';
			notificationText.disabled=!notificationsEnabled || notificationTextRow.hidden;
			setSettingsVisualEnabled(notificationTextRow,!notificationText.disabled);
			watchIntervalSelect.disabled= !watchEnabledDraft;
			watchIntervalRow.style.opacity= watchEnabledDraft
					? '1'
					:'.5';
			const watchProfileModeEnabled= watchEnabledDraft;
			watchProfileModeSelect.disabled= !watchProfileModeEnabled;
			setSettingsVisualEnabled( watchProfileModeRow, watchProfileModeEnabled );
			const nonAlertNewnessEnabled= watchEnabledDraft;
			nonAlertChangeNewnessSelect.disabled= !nonAlertNewnessEnabled;
			nonAlertChangeNewnessRow.style.opacity= nonAlertNewnessEnabled
					? '1'
					:'.5';
		};
		for (const key of ['watchSystemNotifications','watchNotificationAnonymous']) checks[key].input.addEventListener('change',syncWatchSettingsControls);
		const toggleWatchEnabledDraft=() => {
			watchEnabledDraft=!watchEnabledDraft;
			syncWatchSettingsControls();
		};
		checks.watchEnabled.label.addEventListener('click',event => {
			stopUiEvent(event);
			toggleWatchEnabledDraft();
		});
		checks.watchEnabled.label.addEventListener('keydown',event => {
			if (event.key !== ' ' && event.key !== 'Enter') return;
			stopUiEvent(event);
			toggleWatchEnabledDraft();
		});
		syncWatchSettingsControls();
		const selectedAutoGarsoAnalysisMode=normalizeAutoGarsoAnalysisMode( current.autoGarsoAnalysisMode );
		const autoGarsoCountRadio=makeRadioOption( 'vm-auto-garso-analysis-mode', 'count',
			'Sprawdzaj, czy są tematy (2 zapytania)', selectedAutoGarsoAnalysisMode === 'count' );
		const autoGarsoContentRadio=makeRadioOption( 'vm-auto-garso-analysis-mode', 'content',
			'Sprawdzaj treść tematów (analizuj posty) (>2 zapytania)', selectedAutoGarsoAnalysisMode === 'content' );
		const autoGarsoModeWarning=makeElement('div',"vm-settings-auto-warning vm-ui-help vm-ui-error",'Uwaga: częste pełne analizy mogą uruchomić zabezpieczenie antyfloodowe i spowodować czasową blokadę na forum a może nawet bana.');
		const autoGarsoModeBox=makeElement('div',"vm-settings-auto-box vm-ui-panel");
		autoGarsoContentRadio.text.appendChild(autoGarsoModeWarning);
		autoGarsoModeBox.append( autoGarsoCountRadio.label, autoGarsoContentRadio.label );
		const autoCheckInfo=makeElement('div',"vm-settings-info vm-ui-help vm-settings-compact-info -top",'Jeżeli odpowiedni cache dla wybranego trybu nie jest przeterminowany, skrypt korzysta z zapisanych danych i nie odpytuje garsoniera.com.pl.');
		styleSingleAdSubsectionBox( autoGarsoModeBox );
		autoGarsoModeBox.appendChild( autoCheckInfo );
		const aggregationModeBox=makeElement('div',"vm-settings-options-box vm-ui-panel");
		const {row:aggregationModeSelectRow,select:aggregationModeSelect}=makeSettingsSelectRow('escortAggregationMode');
		const aggregationModeDescription=makeElement('div',"vm-settings-field-description vm-ui-help");
		const aggregationModeWarning=makeElement('div',"vm-settings-warning vm-settings-field-description vm-ui-help vm-ui-error",'Uwaga: agregowanie z wykorzystaniem wyszukiwarki Escort.club może trwać dłużej.');
		aggregationModeWarning.style.display='none';
		function syncAggregationModeControls() {
			const enabled= checks.mergeEscortAds.input.checked;
			const mode=normalizeEscortAggregationMode(aggregationModeSelect.value);
			aggregationModeSelect.disabled=!enabled;
			aggregationModeBox.style.opacity=enabled ? '1':'.5';
			aggregationModeDescription.textContent= ESCORT_AGGREGATION_MODE_DESCRIPTIONS[mode] || '';
			aggregationModeWarning.style.display=mode === 'phone-escort-search'
				? 'block'
				:'none';
		}
		aggregationModeSelect.addEventListener('change',syncAggregationModeControls);
		checks.mergeEscortAds.input.addEventListener('change',syncAggregationModeControls);
		aggregationModeBox.append( aggregationModeSelectRow, aggregationModeDescription, aggregationModeWarning );
		syncAggregationModeControls();
		function styleSingleAdSubsectionBox(box) {
			box.classList.add("vm-settings-single-ad-subsection-box","vm-ui-panel");
		}
		const compactSummaryComparisonModeBox=makeElement( 'div', "vm-settings-options-box vm-ui-panel" );
		styleSingleAdSubsectionBox( compactSummaryComparisonModeBox );
		const {
			row:compactSummaryComparisonModeRow, select:compactSummaryComparisonModeSelect
		}=makeSettingsSelectRow('compactSummaryComparisonMode');
		const compactSummaryComparisonModeDescription=makeElement('div',"vm-settings-field-description vm-ui-help");
		const syncCompactSummaryComparisonMode=() => {
			const mode=normalizeEscortCompactSummaryMode( compactSummaryComparisonModeSelect.value );
			compactSummaryComparisonModeDescription.textContent= ESCORT_COMPACT_SUMMARY_MODE_DESCRIPTIONS[mode] || '';
		};
		compactSummaryComparisonModeSelect.addEventListener( 'change', syncCompactSummaryComparisonMode );
		compactSummaryComparisonModeBox.append( compactSummaryComparisonModeRow, compactSummaryComparisonModeDescription );
		syncCompactSummaryComparisonMode();
		const {row:maxRelatedAdsTabsRow,input:maxRelatedAdsTabsInput}=makeSettingsNumberRow('maxRelatedAdsTabs');
		const relatedAdsTileLinkBox=makeElement('div',"vm-settings-options-box vm-ui-panel");
		const {
			row:relatedAdsTileLinkRow, select:relatedAdsTileLinkSelect
		}=makeSettingsSelectRow('relatedAdsTileLinkMode');
		const relatedAdsTileLinkDescription=makeElement( 'div', "vm-settings-field-description vm-ui-help" );
		const syncRelatedAdsTileLinkControls=() => {
			const enabled=checks.showRelatedAdsTileLink.input.checked;
			const mode=normalizeEscortRelatedAdsTileLinkMode( relatedAdsTileLinkSelect.value );
			relatedAdsTileLinkSelect.disabled=!enabled;
			checks.openAllActiveRelatedAdsFromTile.input.disabled=!enabled;
			const openAllEnabled=enabled && checks.openAllActiveRelatedAdsFromTile.input.checked;
			maxRelatedAdsTabsInput.disabled=!openAllEnabled;
			maxRelatedAdsTabsRow.style.opacity=openAllEnabled ? '1':'.5';
			relatedAdsTileLinkBox.style.opacity=enabled ? '1':'.5';
			relatedAdsTileLinkDescription.textContent= ESCORT_AGGREGATION_MODE_DESCRIPTIONS[mode] || '';
		};
		checks.showRelatedAdsTileLink.input.addEventListener( 'change', syncRelatedAdsTileLinkControls );
		relatedAdsTileLinkSelect.addEventListener( 'change', syncRelatedAdsTileLinkControls );
		checks.openAllActiveRelatedAdsFromTile.input.addEventListener( 'change', syncRelatedAdsTileLinkControls );
		relatedAdsTileLinkBox.append( relatedAdsTileLinkRow, relatedAdsTileLinkDescription,
			checks.openAllActiveRelatedAdsFromTile.label, maxRelatedAdsTabsRow );
		syncRelatedAdsTileLinkControls();
		const multiLocationInfo=makeElement( 'div', "vm-settings-info -indented vm-ui-help",
			'Multiwybór łączy wyniki kilku miast na jednej liście; pozostałe natywne filtry są wspólne dla wszystkich lokalizacji.'
		);
		Object.assign( checks.showMenuPagesAsFavicons.label.style, {
				marginLeft:'25px', marginTop:'4px'
			} );
		const menuPagesDraft= normalizeMenuPages( current.menuPages ).map(item => ({...item}));
		const menuPagesEditor= createExternalServicesEditor({
				masterCheck:null, items:menuPagesDraft, infoText:
					'Linki są wyświetlane bezpośrednio na pasku menu. Nazwa pojawia się po najechaniu na ikonę.',
				addButtonText: '+ Dodaj link', makeNewItem:count => ({
					id: `custom-menu-${Date.now()}-${count + 1}`, name:'Nowa strona', enabled:true, url:'https://'
				}), urlKey:'url', normalizeUrl: normalizeMenuPageUrl, urlPlaceholder: 'https://example.com/', enabledTitle:
					'Pokaż ten link', removeTitle: 'Usuń link', itemNoun: 'linku'
			});
		const syncBottomLeftMenuSettings=() => {
			const enabled= checks.showBottomLeftMenu.input.checked;
			checks.showMenuPagesAsFavicons.input.disabled= !enabled;
			checks.showMenuPagesAsFavicons.label.style.opacity= enabled ? '1':'.5';
			menuPagesEditor.box.style.opacity= enabled ? '1':'.5';
		};
		checks.showBottomLeftMenu.input.addEventListener( 'change', syncBottomLeftMenuSettings );
		checks.showMenuPagesAsFavicons.input.addEventListener( 'change', syncBottomLeftMenuSettings );
		syncBottomLeftMenuSettings();
		const {
			row:topPhoneSearchEnterTargetRow, select:topPhoneSearchEnterTargetSelect
		}=makeSettingsSelectRow('topPhoneSearchEnterTarget');
		const {
			row:topPhoneSearchOpenModeRow, select:topPhoneSearchOpenModeSelect
		}=makeSettingsSelectRow('topPhoneSearchOpenMode');
		const topPhoneSearchOptionsRow=makeElement('div','vm-settings-inline-row vm-settings-top-phone-options');
		topPhoneSearchOptionsRow.append( topPhoneSearchEnterTargetRow, topPhoneSearchOpenModeRow );
		bindSettingsEnabled(checks.showTopPhoneSearch.input,topPhoneSearchOptionsRow,[topPhoneSearchEnterTargetSelect,topPhoneSearchOpenModeSelect]);
		bindSettingsEnabled(checks.showTopPhoneSearch.input,checks.garsoShowSearchIcons.label,[checks.garsoShowSearchIcons.input],true);
		function createExternalServicesEditor({
			masterCheck, items, infoText, addButtonText, makeNewItem, urlKey, normalizeUrl, urlPlaceholder, enabledTitle,
			removeTitle, itemNoun, beforeInfo=[]
		}) {
			const box=makeElement('div',"vm-settings-image-search-engines vm-settings-compact-inset-box vm-ui-panel");
			const info=makeElement('div','',infoText);
			info.className="vm-settings-compact-info vm-ui-help";
			const rows=makeElement('div');
			const addRow=makeElement('div','vm-settings-inline-row');
			const addButton=makeButton("vm-settings-action-button vm-ui-button -accent",addButtonText);
			let rendered=false;
			const sync=() => {
				const enabled= !masterCheck || masterCheck.input.checked;
				setSettingsVisualEnabled(box,enabled,true);
				box.setAttribute( 'aria-disabled', String(!enabled) );
				for (const control of
					box.querySelectorAll( 'input, button' )) {
					control.disabled=!enabled;
				}
			};
			const render=() => {
				rendered=true;
				rows.replaceChildren();
				items.forEach((item,index) => {
					const row=makeElement('div','vm-settings-external-service-row');
					const enabled=makeElement('input','',null,{type:'checkbox'});
					enabled.checked=item.enabled !== false;
					enabled.title=enabledTitle;
					const name=makeElement('input','',null,{type:'text'});
					name.value=item.name;
					name.placeholder='Nazwa';
					name.maxLength=40;
					const url=makeElement('input','',null,{type:'text',value:item[urlKey],placeholder:urlPlaceholder});
					const remove=makeButton("vm-settings-cache-button vm-ui-button",'×',{title:removeTitle});
					name.className=url.className="vm-settings-mini-input vm-ui-field vm-ui-compact";
					remove.classList.add("vm-settings-mini-remove","vm-ui-icon","vm-ui-compact");
					enabled.addEventListener('change',() => {item.enabled=enabled.checked;});
					name.addEventListener('input',() => {item.name=name.value;});
					url.addEventListener('input',() => {item[urlKey]=url.value;});
					remove.addEventListener('click',event => {
						event.preventDefault();
						items.splice(index,1);
						render();
					});
					row.append(enabled,name,url,remove);
					rows.appendChild(row);
				});
				sync();
			};
			addButton.addEventListener('click',event => {
				event.preventDefault();
				items.push(makeNewItem(items.length));
				render();
				rows.lastElementChild?.querySelector('input[type="text"]')?.focus();
			});
			addRow.appendChild(addButton);
			box.append(...beforeInfo,info,rows,addRow);
			if (masterCheck?.input) {
				masterCheck.input.addEventListener( 'change', sync );
			}
			sync();
			const read=() => items.map((item,index) => {
				const name=normalizeEscortAdText(item.name).slice(0,40);
				const normalizedUrl=normalizeUrl(item[urlKey]);
				if (!name) throw new Error(`Podaj nazwę ${itemNoun} w wierszu ${index + 1}.`);
				if (!normalizedUrl) throw new Error(`Podaj prawidłowy adres http/https ${itemNoun} w wierszu ${index + 1}.`);
				return {...item,name,[urlKey]:normalizedUrl,enabled:item.enabled !== false};
			});
			return {box,read,mount:() => {if (!rendered) render();}};
		}
		const imageSearchEnginesDraft=normalizeImageSearchEngines(current.imageSearchEngines) .map(item => ({...item}));
		const imageSearchEditor=createExternalServicesEditor({
			masterCheck:checks.showImageSearchButtons, items:imageSearchEnginesDraft,
			infoText:'Adres zdjęcia zostanie dopisany do URL usługi. Google i Yandex: wyszukiwanie. PicDet i Hive AI: tylko wklejenie do pola, bez wysyłania formularza. Wynik Hive może być błędny i nie wyklucza retuszu.',
			addButtonText:'+ Dodaj wyszukiwarkę',
			makeNewItem:count => ({id:`custom-${Date.now()}-${count + 1}`,name:'Nowa',enabled:true,urlPrefix:''}),
			urlKey:'urlPrefix', normalizeUrl:normalizeImageSearchUrlPrefix, urlPlaceholder:'https://serwis.example/search?url=',
			enabledTitle:'Włącz / wyłącz tę wyszukiwarkę', removeTitle:'Usuń wyszukiwarkę', itemNoun:'wyszukiwarki obrazem'
		});
		styleSingleAdSubsectionBox( imageSearchEditor.box );
		checks.autoPhoneReputationSearch.label.style.margin='0 0 4px';
		const phoneReputationServicesDraft=normalizePhoneReputationServices( current.phoneReputationServices
		).map(item => ({...item}));
		const phoneReputationEditor=createExternalServicesEditor({
			masterCheck:checks.showPhoneReputationLinks, items:phoneReputationServicesDraft,
			beforeInfo:[checks.autoPhoneReputationSearch.label],
			infoText:'W URL możesz użyć {phone} (9 cyfr), {phone48} (48 + 9 cyfr) lub {query} (kilka formatów numeru połączonych OR). Automatyczne odczytywanie ocen działa dla wbudowanych KTOTO, NumeryTelefonu i SprawdzenieNumeru; własne serwisy są dodawane jako linki.',
			addButtonText:'+ Dodaj serwis',
			makeNewItem:count => ({id:`custom-${Date.now()}-${count + 1}`,name:'Nowy serwis',enabled:true,urlTemplate:'https://'}),
			urlKey:'urlTemplate', normalizeUrl:normalizePhoneReputationUrlTemplate,
			urlPlaceholder:'https://serwis.example/numer/{phone}', enabledTitle:'Pokaż ten serwis w sekcji sprawdzania numeru',
			removeTitle:'Usuń serwis', itemNoun:'serwisu numerów'
		});
		styleSingleAdSubsectionBox( phoneReputationEditor.box );
		const {row:alarmingTagCountRow,input:alarmingTagCountInput}=makeSettingsNumberRow('alarmingTagCountLimit');
		compactSummaryComparisonModeBox.appendChild( alarmingTagCountRow );
		bindSettingsEnabled(checks.showCompactSummaryAboveDescription.input,compactSummaryComparisonModeBox,[compactSummaryComparisonModeSelect,alarmingTagCountInput]);
		const tagHighlightGroupsDraft=normalizeTagHighlightGroups(current.tagHighlightGroups)
			.map(group => ({...group,tags:[...group.tags]}));
		const tagHighlightBox=makeElement( 'div', 'vm-settings-tag-highlight-groups' );
		styleSingleAdSubsectionBox( tagHighlightBox );
		const tagHighlightInfo=makeElement( 'div', "vm-settings-compact-info -spaced vm-ui-help",
			'Tagi wybierasz z listy odczytanej z aktualnej strony i cache. „pozostałe” obejmuje automatycznie wszystkie nieprzypisane tagi.'
		);
		const tagHighlightRows=makeElement('div','','Wczytywanie listy tagów…');
		let tagHighlightRendered=false;
		const tagHighlightAddGroup=makeButton(
			"vm-settings-action-button vm-settings-tag-add-group vm-ui-button -accent vm-ui-compact", '+ Dodaj grupę' );
		const removeTagFromAllHighlightGroups=tag => {
			const wanted=normalizeEscortChangeComparisonValue(tag);
			for (const group of tagHighlightGroupsDraft) {
				if (group.locked) continue;
				group.tags=group.tags.filter(value => normalizeEscortChangeComparisonValue(value) !== wanted);
			}
		};
		const renderTagHighlightGroups=() => {
			tagHighlightRows.replaceChildren();
			const knownTagLabels=getKnownEscortTagLabels(tagHighlightGroupsDraft);
			tagHighlightGroupsDraft.forEach((group,index) => {
				const locked=group.locked === true || group.id === 'other';
				const row=makeElement( 'div', `vm-settings-tag-group-row${index ? ' -separated':''}` );
				const name=makeElement('input');
				name.type='text';
				name.value=locked ? 'pozostałe':group.name;
				name.disabled=locked;
				name.maxLength=40;
				name.className="vm-settings-mini-input vm-settings-tag-group-name vm-ui-field vm-ui-compact";
				if (!locked) name.addEventListener('input',() => {group.name=name.value;});
				const color=makeElement('input');
				color.type='color';
				color.value=normalizeFavoriteCategoryColor(group.color) || DEFAULT_TAG_HIGHLIGHT_GROUPS.at(-1).color;
				color.title='Kolor grupy';
				color.className="vm-settings-tag-color vm-ui-field vm-ui-color vm-ui-compact";
				color.addEventListener('input',() => {group.color=color.value;});
				const tags=makeElement('div','vm-settings-tag-list');
				if (locked) {
					const automatic=makeElement( 'span', 'vm-settings-tag-automatic', 'wszystkie nieprzypisane' );
					tags.appendChild(automatic);
				} else {
					for (const tag of group.tags) {
						const chip=makeButton('vm-settings-tag-chip',`${tag} ×`,{title:'Usuń tag z tej grupy'});
						chip.style.setProperty('--vm-settings-tag-color',group.color);
						chip.addEventListener('click',event => {
							event.preventDefault();
							group.tags=group.tags.filter(value => normalizeEscortChangeComparisonValue(value) !== normalizeEscortChangeComparisonValue(tag));
							renderTagHighlightGroups();
						});
						tags.appendChild(chip);
					}
					const assignedHere=new Set(group.tags.map(normalizeEscortChangeComparisonValue));
					const choices=knownTagLabels .filter(tag => !assignedHere.has(normalizeEscortChangeComparisonValue(tag)));
					const addTag=makeElement('select');
					appendSelectOptions(addTag,[['',choices.length ? '+ wybierz tag…':'brak tagów w cache'],...choices.map(tag => [tag,tag])],'');
					addTag.disabled=!choices.length;
					addTag.className="vm-settings-tag-select vm-ui-field vm-ui-compact";
					addTag.addEventListener('change',() => {
						const tag=normalizeEscortAdText(addTag.value).slice(0,80);
						if (!tag) return;
						removeTagFromAllHighlightGroups(tag);
						group.tags.push(tag);
						renderTagHighlightGroups();
					});
					tags.appendChild(addTag);
				}
				const removeGroup=makeButton(`vm-settings-cache-button vm-ui-button vm-ui-icon vm-ui-compact vm-settings-mini-remove${locked ? ' -hidden':''}`,locked ? '':'×',{disabled:locked,title:locked ? 'Grupa „pozostałe” jest stała':'Usuń grupę'});
				if (!locked) removeGroup.addEventListener('click',event => {
					event.preventDefault();
					tagHighlightGroupsDraft.splice(index,1);
					renderTagHighlightGroups();
				});
				row.append(name,color,tags,removeGroup);
				tagHighlightRows.appendChild(row);
			});
			tagHighlightRendered=true;
			syncTagHighlightControls();
		};
		tagHighlightAddGroup.addEventListener('click',event => {
			event.preventDefault();
			const otherIndex=tagHighlightGroupsDraft.findIndex(group => group.locked || group.id === 'other');
			const insertAt=otherIndex >= 0 ? otherIndex:tagHighlightGroupsDraft.length;
			tagHighlightGroupsDraft.splice(insertAt,0,{
				id:`group-${Date.now()}`, name:`Grupa ${insertAt + 1}`, color:'#e6a0bf', tags:[]
			});
			renderTagHighlightGroups();
		});
		tagHighlightBox.append( tagHighlightInfo, tagHighlightRows, tagHighlightAddGroup );
		const syncTagHighlightControls=() => {
			const enabled= checks.showTagHighlights.input.checked;
			setSettingsVisualEnabled( tagHighlightBox, enabled, true );
			tagHighlightBox.setAttribute( 'aria-disabled', String(!enabled) );
			for (const control of
				tagHighlightBox.querySelectorAll( 'input, select, button' )) {
				control.disabled=!enabled || !tagHighlightRendered;
			}
		};
		checks.showTagHighlights.input.addEventListener( 'change', syncTagHighlightControls );
		syncTagHighlightControls();
		const readTagHighlightGroupsDraft=() => normalizeTagHighlightGroups(tagHighlightGroupsDraft);
		const syncSummarySidePanelControls=() => {
			checks.autoGarsoCheck.input.disabled=false;
			checks.autoGarsoCheck.label.style.opacity='1';
			checks.autoGarsoCheck.label.style.cursor='pointer';
			checks.showCompactSummaryAboveDescription.input.disabled=false;
			checks.showCompactSummaryAboveDescription.label.style.opacity='1';
			checks.showCompactSummaryAboveDescription.label.style.cursor='pointer';
			const autoModeEnabled= checks.autoGarsoCheck.input.checked;
			for (const option of [autoGarsoCountRadio,autoGarsoContentRadio]) {
				option.input.disabled=!autoModeEnabled;
				option.label.style.cursor=autoModeEnabled
					? 'pointer'
					:'not-allowed';
			}
			autoGarsoCountRadio.label.style.opacity=checks.autoGarsoCheck.input.checked
				? '1'
				:'.5';
			autoGarsoContentRadio.label.style.opacity=autoModeEnabled
				? '1'
				:'.5';
			setSettingsVisualEnabled(autoGarsoModeBox,checks.autoGarsoCheck.input.checked,true);
			autoGarsoModeBox.setAttribute( 'aria-disabled', String(!checks.autoGarsoCheck.input.checked) );
		};
		checks.showSummarySidePanel.input.addEventListener( 'change', syncSummarySidePanelControls );
		checks.autoGarsoCheck.input.addEventListener('change',syncSummarySidePanelControls);
		syncSummarySidePanelControls();
		const phoneClipboardFormatBox=makeElement( 'div', 'vm-settings-phone-copy-box' );
		styleSingleAdSubsectionBox( phoneClipboardFormatBox );
		const {row:phoneClipboardFormatRow,label:phoneClipboardFormatLabel,select:phoneClipboardFormatSelect}=makeSettingsSelectRow('phoneClipboardFormat');
		phoneClipboardFormatLabel.style.whiteSpace='nowrap';
		phoneClipboardFormatBox.appendChild( phoneClipboardFormatRow );
		bindSettingsEnabled(checks.showPhoneCopyButton.input,phoneClipboardFormatBox,[phoneClipboardFormatSelect],true);
		const persistentCacheInfo=makeElement('div','vm-settings-info vm-ui-help',
			'Cache jest zawsze włączony. Ogranicza ponowne zapytania i pozwala otwierać zapisane anonse. Dane Garsoniery są aktualne przez 14 dni, pozostałe szczegóły przez 7 dni. Status online/offline pozostaje zapisany bezterminowo: po 30 minutach jest szary i może zostać odświeżony w tle. Cache IndexedDB jest osobny dla poddomen językowych; czyszczenie dotyczy bieżącej poddomeny.');
		const clearCacheRow=makeElement( 'div', 'vm-settings-cache-controls-stack' );
		const clearExpiredCacheRow=makeElement( 'div', 'vm-settings-cache-control-row' );
		const clearExpiredCacheBtn=makeButton( "vm-settings-cache-button vm-ui-button", 'Wyczyść przeterminowany cache' );
		const clearExpiredCacheSize=makeElement( 'span', "vm-settings-cache-size vm-ui-help" );
		const clearSavedCacheRow=makeElement( 'div', 'vm-settings-cache-control-row' );
		const clearCacheBtn=makeButton( "vm-settings-cache-button vm-ui-button", 'Wyczyść zapisany cache' );
		const clearCacheSize=makeElement( 'span', "vm-settings-cache-size vm-ui-help" );
		function refreshExpiredCacheSizeLabel() {
			const stats= getExpiredPersistentCacheStats();
			if (!stats.available) {
				clearExpiredCacheSize.textContent='b/d';
				clearExpiredCacheSize.title=
					'Dokładny rozmiar przeterminowanego cache IndexedDB można policzyć na stronie Escort.club, na której go zapisano. Skrypt nie wykonuje dodatkowego zapytania tylko dla tej informacji.';
				return;
			}
			clearExpiredCacheSize.textContent= formatCacheSize(stats.megabytes);
			clearExpiredCacheSize.title= `Przeterminowany cache możliwy do usunięcia: ${
					formatPolishCount( stats.entries, 'wpis', 'wpisy', 'wpisów' )
				}, około ${
					formatCacheSize(stats.megabytes)
				}. Nie obejmuje przeterminowanych danych anonsów zachowywanych dlatego, że należą do obserwowanych pozycji.`;
		}
		function refreshCacheSizeLabel() {
			const stats=getPersistentCacheStats();
			clearCacheSize.textContent= formatCacheSize(stats.megabytes);
			clearCacheSize.title= `Przybliżony rozmiar trwałego cache: ${
					stats.entries
				} ${
					stats.entries === 1
						? 'wpis'
						:'wpisów'
				} (${stats.garsoEntries} Garsoniera, ${
					stats.listEntries
				} Escorti, ${
					stats.escortAdDataEntries
				} Escort.club, ${stats.activityEntries || 0} statusów aktywności, ${stats.templateEntries || 0} szablonów anonsu). ` + 'To przybliżony rozmiar zserializowanych danych w IndexedDB i Violentmonkey.';
			refreshExpiredCacheSizeLabel();
		}
		clearCacheSize.textContent=clearExpiredCacheSize.textContent='Obliczanie…';
		clearCacheBtn.disabled=clearExpiredCacheBtn.disabled=true;
		for (const [button,run,done,errorText] of [
			[clearExpiredCacheBtn,clearExpiredPersistentCacheAcrossDomains,
				result => result.removed ? `Usunięto: ${result.removed}`:'Brak przeterminowanego cache','Nie udało się wyczyścić przeterminowanego cache'],
			[clearCacheBtn,clearPersistentCacheAcrossDomains,() => 'Cache wyczyszczony','Nie udało się wyczyścić zapisanego cache']
		]) {
			const label=button.textContent;
			button.addEventListener('click',async event => {
				stopUiEvent(event); button.disabled=true; button.textContent='Czyszczenie…';
				try {
					const result=await run();
					refreshCacheSizeLabel(); button.textContent=done(result);
				} catch (error) {button.textContent='Błąd czyszczenia'; log(errorText,error);}
				setTimeout(() => {button.disabled=false; button.textContent=label;},1800);
			});
		}
		clearExpiredCacheRow.append( clearExpiredCacheBtn, clearExpiredCacheSize );
		clearSavedCacheRow.append( clearCacheBtn, clearCacheSize );
		const clearTechnicalDataRow=makeElement( 'div', 'vm-settings-cache-control-row' );
		const clearTechnicalDataBtn=makeButton( "vm-settings-cache-button -danger vm-ui-button",
			'Usuń dane techniczne skryptu' );
		const clearTechnicalDataSize=makeElement( 'span', "vm-settings-cache-size vm-ui-help" );
		function refreshTechnicalDataSizeLabel() {
			const stats=getTechnicalScriptDataStats();
			clearTechnicalDataSize.textContent= formatCacheSize( stats.bytes / (1024 * 1024) );
			clearTechnicalDataSize.title= `Techniczne dane skryptu: ${
					formatPolishCount( stats.entries, 'wpis', 'wpisy', 'wpisów' )
				}. Nie obejmuje ustawień, ulubionych, notatek, kategorii, historii ani cache IndexedDB.`;
		}
		clearTechnicalDataSize.textContent='Obliczanie…';
		clearTechnicalDataBtn.disabled=true;
		clearTechnicalDataBtn.addEventListener( 'click', event => {
				stopUiEvent(event);
				const stats= getTechnicalScriptDataStats();
				const confirmed=window.confirm( `Usunąć techniczne dane skryptu (${
						formatCacheSize( stats.bytes / (1024 * 1024) )
					})?\n\n` + 'Zostaną usunięte pomocnicze indeksy, mirrory, diagnostyka, snapshoty i dane tymczasowe.\n\n' +
					'Ustawienia, ulubione i obserwowane, notatki, kategorie, historia, zapisane filtry i lokalizacje oraz cache IndexedDB pozostaną bez zmian.'
				);
				if (!confirmed) return;
				const removed= clearTechnicalScriptDataStorage();
				clearTechnicalDataBtn.disabled=true;
				clearTechnicalDataBtn.textContent= removed
						? `Usunięto: ${removed}`
						:'Brak danych technicznych';
				refreshTechnicalDataSizeLabel();
				setTimeout( () => location.reload(), 900 );
			} );
		clearTechnicalDataRow.append( clearTechnicalDataBtn, clearTechnicalDataSize );
		clearCacheRow.append( clearExpiredCacheRow, clearSavedCacheRow, clearTechnicalDataRow );
		saveBtn.addEventListener('click',async event => {
			stopUiEvent(event);
			saveBtn.disabled=true;
			saveBtn.textContent='Zapisywanie…';
			try {
				const nextSettings={
					...readDeclarativeCheckboxSettings(),
					...Object.fromEntries(Object.entries(settingsFieldControls).map(([key,[input,read]]) => [key,read(input.value)])),
					autoGarsoAnalysisMode:autoGarsoContentRadio.input.checked
						? 'content'
						:'count', mergeEscortAds:checks.mergeEscortAds.input.checked,
					showPricesInSearchResults:checks.showPricesInSearchResults.input.checked,
					searchResultPriceDuration:current.searchResultPriceDuration,
					searchResultExtraFields:normalizeSearchResultExtraFields( current.searchResultExtraFields ),
					imageSearchEngines:imageSearchEditor.read(), phoneReputationServices:phoneReputationEditor.read(), menuPages:
						menuPagesEditor.read(),
					savedLocationGroups:current.savedLocationGroups, tagHighlightGroups:readTagHighlightGroupsDraft(),
					usePersistentCache:true, notesEnabled:watchEnabledDraft, watchEnabled:watchEnabledDraft, showWatchOnlineCount:
						checks.showWatchOnlineCount.input.checked,
					favoritesDataColumns:current.favoritesDataColumns, favoriteCategories: getSettings().favoriteCategories,
					favoriteCategoryColors: getSettings().favoriteCategoryColors,
					historyCleanupKeywords:current.historyCleanupKeywords
				};
				const savedSettings=await saveSettings({...getSettings(),...nextSettings},true);
				SETTINGS={...DEFAULT_SETTINGS,...savedSettings};
				applyFavoriteSearchPageSettings();
				closeController.close('save');
				location.reload();
			} catch (error) {
				saveBtn.disabled=false;
				saveBtn.textContent='Zapisz';
				window.alert(error?.message || 'Nie udało się zapisać ustawień.');
			}
		});
		const watchSection=makeSettingsSection( 'Ulubione i zapisane anonse',
			'Ulubione umożliwiają obserwowanie aktywności, lokalizacji i zmian danych. Ukryte usuwają wybrane anonse lub profile tylko z listy wyników. Strony anonsów i linki na Escorti pozostają dostępne.',
			true, checks.watchEnabled.label, watchFavoritesInfo, favoritesDefaultView.row, watchRuntimeInfo, watchIntervalRow, watchProfileModeRow,
			checks.showWatchOnlineCount.label, checks.showWatchAlertBar.label, checks.watchSystemNotifications.label, checks.watchNotificationAnonymous.label,
			notificationTextRow, notificationInfo, watchOnlineSeparator, nonAlertChangeNewnessInfo, nonAlertChangeNewnessRow,
			clearWatchHistoryRow, clearFavoritesRow, clearFavoritesInfo );
		const notesSection=makeSettingsSection( 'Notatki',
			'Wyłączenie funkcji zachowuje zapisane notatki i kategorie.', true, notesActionsBox );
		const categoriesSection=makeSettingsSection( 'Kategorie','',true,editFavoriteCategoriesRow,clearCategoriesStatus );
		const hidePageElementsSection=makeSettingsSection( 'Ukryj elementy strony Escort.club',
			'Wybierz elementy natywnej strony Escort.club, które mają być ukrywane przez skrypt.', true,
			checks.hideRecommendedSearchSection.label, checks.hideRecommendedAdSection.label,
			checks.hideSingleAdContactButtons.label, checks.hideSingleAdActions.label, checks.hideFeaturedSearchSection.label,
			checks.hidePopularCitiesSection.label );
		const searchButtonSection=makeSettingsSection( 'Przycisk wyszukiwarki',
			'Ustawienia podręcznej wyszukiwarki dostępnej na obsługiwanych stronach.', true, checks.showTopPhoneSearch.label,
			topPhoneSearchOptionsRow );
		const quickPreviewSection=makeSettingsSection( 'Dymki podglądu anonsów',
			'Wspólne ustawienie dla linków prowadzących do anonsów Escort.club na Escort.club, Escorti.pl i Garsonierze.', true,
			checks.quickAdPreview.label );
		const scriptMenuSection=makeSettingsSection( 'Menu skryptu', 'Ustawienia górnego paska Escort Helper', true,
			checks.showBottomLeftMenu.label, checks.showMenuPagesAsFavicons.label,
			menuPagesEditor.box );
		const singleAdSection=makeSettingsSection( 'Widok pojedynczego anonsu escort.club',
			'Narzędzia dostępne po otwarciu konkretnego anonsu Escort.club.', true, checks.showSummarySidePanel.label,
			checks.showCompactSummaryAboveDescription.label, compactSummaryComparisonModeBox, checks.showTagHighlights.label,
			tagHighlightBox, checks.autoGarsoCheck.label, autoGarsoModeBox, checks.showImageSearchButtons.label,
			imageSearchEditor.box, checks.showPhoneReputationLinks.label, phoneReputationEditor.box,
			checks.showPhoneCopyButton.label, phoneClipboardFormatBox );
		const resultsTileAppearanceSection=makeSettingsSection( 'Wygląd kafelka w wynikach wyszukiwania Escort.club',
			'Ustawienia informacji i dodatkowych elementów wyświetlanych bezpośrednio na kafelkach wyników.', true,
			checks.showEscortiTileData.label, checks.showPricesInSearchResults.label, checks.showRelatedAdsTileLink.label,
			relatedAdsTileLinkBox );
		const resultsAggregationSection=makeSettingsSection( 'Agregowanie w wynikach wyszukiwania Escort.club',
			'Sposób łączenia powiązanych anonsów i wykorzystywania zapisanych danych do agregowania wyników.', true,
			checks.mergeEscortAds.label, aggregationModeBox );
		const resultsSearchToolsSection=makeSettingsSection( 'Narzędzia wyszukiwania',
			'Opcje ułatwiające wyszukiwanie w wielu lokalizacjach i ponowne używanie zapisanych lokalizacji.', true,
			checks.multiLocationEnabled.label, multiLocationInfo, checks.saveSearchLocationsEnabled.label );
		relatedAdsTileLinkBox.classList.add("vm-settings-compact-inset-box","vm-ui-panel");
		aggregationModeBox.classList.add("vm-settings-compact-inset-box","vm-ui-panel");
		const escortRequestLimits=createEscortRequestSettingsRows(makeSettingsNumberRow,checks.escortFastStart.label);
		const requestLimitsSection=makeSettingsSection('Limity zapytań do Escort.club',
			'Wspólne limity dla HEAD i GET / POST we wszystkich kartach.',true,escortRequestLimits);
		const cacheSection=makeSettingsSection('Pamięć podręczna',
			'Lokalnie zapisane dane Garsoniery, Escorti i anonsów Escort.club.',true,persistentCacheInfo,clearCacheRow);
		const liveDiagnosticsSection=makeSettingsSection('Panel na stronach','',true,checks.showLiveDiagnostics.label);
		const diagnosticsSection=makeSettingsSection('Zapis diagnostyczny',
			'Zbiorcze liczniki i ostatnie błędy ze wszystkich kart. Przycisk „Odśwież” aktualizuje odczyt; zapisane zdarzenie nie oznacza trwającego problemu.',true);
		const diagnosticsHealth=makeElement( 'div', "vm-diagnostics-health vm-ui-panel" );
		const diagnosticsOverview=makeElement( 'div', 'vm-diagnostics-overview' );
		const diagnosticsDetailGrid=makeElement( 'div', 'vm-diagnostics-detail-grid' );
		const diagnosticsActions=makeElement('div','vm-settings-inline-row vm-settings-transfer-row');
		const diagnosticsRefreshBtn=makeButton("vm-settings-action-button vm-ui-button -accent",'Odśwież');
		const diagnosticsExportBtn=makeButton("vm-settings-action-button vm-ui-button -accent",'Eksportuj diagnostykę');
		const diagnosticsClearBtn=makeButton("vm-settings-cache-button vm-ui-button",'Wyczyść diagnostykę');
		const diagnosticsStatus=makeElement('span',"vm-settings-transfer-status vm-ui-help",'Wczytywanie diagnostyki…');
		diagnosticsExportBtn.disabled=diagnosticsClearBtn.disabled=true;
		const diagnosticsPrivacy=makeElement('div',"vm-diagnostics-privacy vm-ui-help",'Eksport zawiera źródła błędów, stan kolejek, krótki przebieg zapytań i zapis sprzed ostatniego bana. Nie zapisuje adresów anonsów, telefonów ani treści odpowiedzi. Wyczyszczenie diagnostyki zachowuje ustawienia, anonse i cache.');
		const makeDiagnosticCard=(value,label,detail='',foot='') => {
			const card=makeElement('div',"vm-diagnostics-card vm-ui-panel");
			const valueLine=makeElement('div','vm-diagnostics-card-value',String(value));
			const labelLine=makeElement('div','vm-diagnostics-card-label',label);
			card.append(labelLine,valueLine);
			if (detail) card.append(makeElement('div','vm-ui-help',detail));
			if (foot) {
				card.classList.add('-requests');
				card.append(makeElement('div','vm-diagnostics-card-foot vm-ui-help',foot));
			}
			return card;
		};
		const makeDiagnosticList=(titleText,events) => {
			const list=makeElement('div',"vm-diagnostics-list vm-ui-panel");
			const listTitle=makeElement('div','vm-diagnostics-list-title',titleText);
			list.appendChild(listTitle);
			const recent=(Array.isArray(events) ? events:[]).slice(-10).reverse();
			if (!recent.length) {
				const empty=makeElement('div','vm-diagnostics-empty','Brak zapisanych zdarzeń.');
				list.appendChild(empty);
				return list;
			}
			for (const event of recent) {
				const row=makeElement('div','vm-diagnostics-event');
				const timestamp=getDiagnosticEventTimestamp(event);
				const time=timestamp ? new Date(timestamp).toLocaleString('pl-PL',{
					day:'2-digit',month:'2-digit',year:'numeric', hour:'2-digit',minute:'2-digit',second:'2-digit'
				}):'Nieznany czas';
				row.textContent=`${time}${event.scriptVersion ? ` • v${event.scriptVersion}`:''} - ${event.area}` +
					(event.message ? `: ${event.message}`:'') + (event.count > 1 ? ` · ×${event.count}`:'');
				list.appendChild(row);
			}
			return list;
		};
		const formatDiagnosticTime=value => {
			const timestamp=Number(value) || 0;
			if (!timestamp) return 'brak danych';
			return new Date(timestamp).toLocaleString( 'pl-PL', {
					day:'2-digit', month:'2-digit', hour:'2-digit', minute:'2-digit'
				} );
		};
		const makeDiagnosticDetailBox=( titleText, lines ) => {
			const box=makeElement( 'div', "vm-diagnostics-detail-box vm-ui-panel" );
			box.appendChild( makeElement( 'div', 'vm-diagnostics-detail-title', titleText ) );
			for (const line of lines.filter(Boolean)) {
				box.appendChild( makeElement( 'div', 'vm-diagnostics-detail-line', line ) );
			}
			return box;
		};
		const diagnosticBackoffBox=makeDiagnosticDetailBox('Prędkość zapytań',[]);
		const diagnosticBackoffLines=[['Escort.club','escortClub'],['Escorti.pl','escorti'],['Garso','garso']].map(([label,key]) => {
			const node=makeElement('div','vm-diagnostics-detail-line');
			diagnosticBackoffBox.appendChild(node);
			return {label,key,node};
		});
		const renderDiagnosticBackoff=(backoff=getAdaptiveBackoffDiagnostics()) => {
			for (const {label,key,node} of diagnosticBackoffLines) {
				const text=key === 'escorti' ? 'Escorti.pl: bez automatycznego spowalniania':formatDiagnosticBackoffStatus(label,backoff[key]);
				if (node.textContent !== text) node.textContent=text;
			}
		};
		const renderDiagnostics=() => {
			const snapshot=getDiagnosticsSnapshot(),counters=snapshot.counters || {};
			const cache=snapshot.cache || {},details=snapshot.cacheDetails || {},watch=snapshot.watch || {};
			const network=snapshot.network?.escortClub || {},windows=snapshot.requestWindows || {};
			diagnosticsHealth.className=`vm-diagnostics-health vm-ui-panel ${snapshot.health?.ok ? '-ok':'-warning'}`;
			diagnosticsHealth.replaceChildren(
				makeElement('span','vm-diagnostics-health-text',snapshot.health?.ok
					? 'Brak ostrzeżeń w odczytanych pomiarach.' : `Ostrzeżenia w odczycie: ${snapshot.health?.text || 'brak podsumowania'}`),
				makeElement('a','vm-diagnostics-health-version vm-settings-action-button vm-ui-button -accent',`Wersja ${snapshot.scriptVersion || SCRIPT_VERSION}`,
					{href:'https://sleazyfork.org/pl/scripts/593145-escort-helper',target:'_blank',rel:'noopener noreferrer',title:'Strona aktualizacji skryptu'}));
			const lookups=Number(counters.cacheLookups) || 0,hits=Number(counters.cacheHits) || 0;
			diagnosticsOverview.replaceChildren(
				...['escortClub','escorti','garso'].map((key,index) => makeDiagnosticCard(
					['minute','fiveMinutes','halfHour'].map(window => windows[window]?.[key] ?? '—').join(' / '),
					`Zapytania do ${['Escort.club','Escorti.pl','Garsoniery'][index]}`,'1 min / 5 min / 30 min',`Ostatnie 24h: ${windows.day?.[key] ?? '—'}`)),
				makeDiagnosticCard(lookups ? `${Math.round(hits / lookups * 100)}%`:'—','Odczyty z cache',`${hits} trafień / ${lookups} odczytów`),
				makeDiagnosticCard(watch.overdue || 0,'Opóźnione odświeżenia','Względem harmonogramu obserwowania'),
				makeDiagnosticCard(counters.errors || 0,'Zapisane błędy'),
				makeDiagnosticCard(`${counters.parserEscortClub || 0} / ${counters.parserEscorti || 0} / ${counters.parserGarso || 0}`,
					'Problemy odczytu stron','Escort.club / Escorti.pl / Garso'),
				makeDiagnosticCard(counters.garsoRetries || 0,'Ponowienia Garso',`Antyflood: ${counters.antiflood || 0}`),
				makeDiagnosticCard(cache.error ? '—':formatCacheSize((cache.bytes || 0) / (1024 * 1024)),'Pamięć CACHE',
					cache.error ? 'Nie udało się odczytać stanu cache':`${cache.entries || 0} wpisów`),
				...[['violentmonkey','Pamięć skryptu łącznie'],['userData','Ustawienia i dane użytkownika']].map(([key,label]) =>
					makeDiagnosticCard(formatCacheSize((snapshot.storage?.[key]?.bytes || 0) / (1024 * 1024)),label,`${snapshot.storage?.[key]?.entries || 0} wpisów`)));
			renderDiagnosticBackoff(snapshot.adaptiveBackoff || {});
			const cacheLines=[
				cache.error ? 'Nie udało się odczytać stanu cache.' : `Rozmiar: ${formatCacheSize((cache.bytes || 0) / (1024 * 1024))} · wpisów: ${cache.entries || 0}`,
				cache.backend ? `Sposób zapisu: ${cache.backend === 'Violentmonkey' ? 'pamięć skryptu (tryb zastępczy)':cache.backend}`:'',
				`Garso: ${details.garsoEntries || 0} · Escorti.pl: ${details.escortiEntries || 0} · anonse: ${details.escortAdEntries || 0}`,
				details.phoneReputationEntries ? `Opinie o numerach: ${details.phoneReputationEntries} wpisów`:''
			];
			const networkLines=[429,503,403].map(status =>
				`Ostatnie HTTP ${status}${status === 403 ? ' (ban)':''}: ${formatDiagnosticTime(network[`last${status}At`])}`);
			const watchLines=[
				`Zapisane pozycje: ${watch.tracked || 0} · profile: ${watch.profiles || 0} · anonse: ${watch.ads || 0}`,
				`Ulubione: ${watch.favorites || 0} · opóźnione odświeżenia: ${watch.overdue || 0} · z ostatnim błędem: ${watch.withErrors || 0}`,
				`Ostatni cykl automatyczny: ${formatDiagnosticTime(watch.lastScheduledRunAt)}`,
				`Ostatnie pełne sprawdzenie: ${formatDiagnosticTime(watch.lastScheduledFullRunAt)}`,
				watch.nextScheduledRunAt ? `Najbliższe odświeżenie: ${formatDiagnosticTime(watch.nextScheduledRunAt)}`
					:watch.enabled ? 'Brak terminu automatycznego odświeżenia.':'Automatyczne sprawdzanie wyłączone.'
			];
			diagnosticsDetailGrid.replaceChildren(diagnosticBackoffBox,
				makeDiagnosticDetailBox('Blokady Escort.club',networkLines),
				makeDiagnosticDetailBox('Zapisane anonse i alerty',watchLines),
				makeDiagnosticList('Problemy odczytu stron (parsera) - ostatnie 10',snapshot.parserIssues),
				makeDiagnosticDetailBox('Pamięć CACHE',cacheLines),
				makeDiagnosticList('Historia błędów - ostatnie 10',snapshot.errors));
		};
		diagnosticsRefreshBtn.addEventListener('click',event => {
			event.preventDefault();
			loadSettingsTab('diagnostics');
		});
		diagnosticsExportBtn.addEventListener('click',event => {
			event.preventDefault();
			try {
				downloadDiagnosticsExport();
				diagnosticsStatus.textContent='Zapisano eksport diagnostyczny.';
			} catch (error) {
				diagnosticsStatus.textContent='Nie udało się wyeksportować diagnostyki.';
				log('Błąd eksportu diagnostyki',error);
			}
		});
		diagnosticsClearBtn.addEventListener('click',event => {
			event.preventDefault();
			clearDiagnostics();
			loadSettingsTab('diagnostics');
		});
		diagnosticsActions.append(diagnosticsRefreshBtn,diagnosticsExportBtn,diagnosticsClearBtn,diagnosticsStatus);
		diagnosticsSection.append(diagnosticsHealth,diagnosticsOverview,diagnosticsDetailGrid,diagnosticsPrivacy,diagnosticsActions);
		const transferSection=makeSettingsSection( 'Eksport i import ustawień',
			'Eksport bez danych prywatnych zapisuje tylko przełączniki, limity i tryby działania. Pomija lokalizacje, ulubione, notatki, historię, diagnostykę, własne teksty, linki i listy. Eksport wszystkich danych obejmuje również dane prywatne, bez cache i danych tymczasowych. Eksportowane są zapisane ustawienia — zmiany najpierw zatwierdź przyciskiem „Zapisz”.',
			true );
		const transferRow=makeElement('div','vm-settings-inline-row vm-settings-transfer-row');
		const exportSettingsBtn=makeButton("vm-settings-action-button vm-ui-button -accent",'Eksportuj wszystkie dane');
		const exportShareableSettingsBtn=makeButton("vm-settings-action-button vm-ui-button -accent",'Eksportuj ustawienia bez danych prywatnych');
		const importSettingsBtn=makeButton("vm-settings-action-button vm-ui-button -accent",'Importuj');
		const clearSettingsDataBtn=makeButton("vm-settings-cache-button -danger vm-ui-button",'Usuń dane ustawień');
		const importSettingsInput=makeElement('input','',null,{type:'file',accept:'.json,application/json'});
		importSettingsInput.style.display='none';
		const transferStatus=makeElement('span',"vm-settings-transfer-status vm-ui-help");
		for (const [button,settingsOnly] of [[exportSettingsBtn,false],[exportShareableSettingsBtn,true]]) button.addEventListener('click',event => {
			stopUiEvent(event);
			try {
				downloadScriptDataExport(settingsOnly);
				transferStatus.textContent='Zapisano plik eksportu.';
			} catch (error) {
				transferStatus.textContent='Nie udało się wyeksportować danych.';
				log('Błąd eksportu danych ustawień',error);
			}
		});
		clearSettingsDataBtn.addEventListener('click',async event => {
			stopUiEvent(event);
			const stats=getScriptDataStats();
			const confirmed=window.confirm(
				`Usunąć ${formatPolishCount(stats.entries,'wpis','wpisy','wpisów')} danych ustawień?\n\n` +
				'Zostaną usunięte ustawienia główne, zapisane filtry i lokalizacje, ulubione i obserwowane, notatki i kategorie, historia zmian, diagnostyka oraz stan interfejsu.\n\n' +
				'Cache pozostanie bez zmian.' );
			if (!confirmed) return;
			clearSettingsDataBtn.disabled=true;
			try {
				await clearScriptDataStorage();
				transferStatus.textContent='Dane ustawień usunięte. Odświeżam stronę…';
				location.reload();
			} catch (error) {
				transferStatus.textContent='Nie udało się usunąć danych ustawień.';
				log('Błąd usuwania danych ustawień',error);
			} finally {
				clearSettingsDataBtn.disabled=false;
			}
		});
		importSettingsBtn.addEventListener('click',event => {
			stopUiEvent(event);
			importSettingsInput.click();
		});
		importSettingsInput.addEventListener('change',async () => {
			const file=importSettingsInput.files?.[0];
			if (!file) return;
			importSettingsBtn.disabled=true;
			transferStatus.textContent='Sprawdzam plik…';
			try {
				const payload=JSON.parse(await file.text());
				const checked=validateScriptDataImportPayload(payload);
				const confirmed=window.confirm(checked.settings
					? 'Zaimportować ustawienia działania skryptu? Ulubione, notatki, lokalizacje, linki i własne listy pozostaną zachowane.'
					:
					`Zaimportować ${formatPolishCount(checked.data.length,'wpis','wpisy','wpisów')} danych ustawień?\n\n` +
					'Całe obecne dane ustawień zostaną usunięte i zastąpione zawartością tej kopii. Cache pozostanie bez zmian.' );
				if (!confirmed) {
					transferStatus.textContent='Import anulowany.';
					return;
				}
				await restoreScriptDataExport(payload);
				transferStatus.textContent='Zaimportowano. Odświeżam stronę…';
				location.reload();
			} catch (error) {
				transferStatus.textContent=error?.message || 'Nie udało się zaimportować pliku.';
				log('Błąd importu danych skryptu',error);
			} finally {
				importSettingsBtn.disabled=false;
				importSettingsInput.value='';
			}
		});
		transferRow.append( exportShareableSettingsBtn, exportSettingsBtn, importSettingsBtn, clearSettingsDataBtn, importSettingsInput, transferStatus );
		transferSection.appendChild(transferRow);
		const garsoSection=makeSettingsSection( 'Garsoniera', 'Niezależne opcje dla stron forum Garsoniera.com.pl', true,
			checks.garsoCheckEscortLinks.label, checks.garsoShowSearchIcons.label,
			makeElement('div','vm-settings-info -indented vm-ui-help','Kliknięcie w lupkę wstawia link lub nr tel do podręcznej wyszukiwarki i można wybrać gdzie się chce wyszukać.'),
			checks.garsoShowTopicHelperData.label,
			makeElement('div','vm-settings-info -indented vm-ui-help','Jeżeli temat oznaczony jest numerem telefonu z "xxx" to anons oraz jego notatki i kategorie nie zostaną odszukane.'),
			checks.garsoShowTopicTools.label,
			makeElement('div','vm-settings-info -indented vm-ui-help','Filtr dotyczy bieżącej strony. Historia obejmuje cały temat i korzysta ze wspólnej analizy oraz cache.'),
			checks.garsoShowTopicSummary.label,
			makeElement('div','vm-settings-info -indented vm-ui-help','Podsumowanie pojawia się w tematach z numerem telefonu w adresie. Automatyczna ponowna analiza następuje po wzroście liczby postów.')
		);
		const settingsTabs=[
			[ 'page', 'Wygląd strony', [ hidePageElementsSection, searchButtonSection, quickPreviewSection, scriptMenuSection ] ],
			[ 'results', 'Lista wyników', [ resultsTileAppearanceSection, resultsAggregationSection, resultsSearchToolsSection ]
			], ['ad','Pojedynczy anons',[singleAdSection]], ['garso','Garsoniera',[garsoSection]],
			['watch','Ulubione i zapisane anonse',[watchSection,notesSection,categoriesSection]],
			['data','Dane i pamięć podręczna',[requestLimitsSection,cacheSection,transferSection]],
			['diagnostics','Diagnostyka skryptu',[liveDiagnosticsSection,diagnosticsSection]] ];
		const tabButtons=new Map();
		const tabPanels=new Map();
		const loadSettingsCache=async current => {
			await persistentCacheInitialization;
			if (!current()) return false;
			await ensureFullPersistentCache();
			return current();
		};
		const loadSettingsTab=createDeferredSettingsTabLoader(overlay,tabPanels,{
			page:() => menuPagesEditor.mount(),
			ad:async current => {
				imageSearchEditor.mount();
				phoneReputationEditor.mount();
				if (!tagHighlightRendered && await loadSettingsCache(current)) renderTagHighlightGroups();
			},
			watch:() => {
				syncClearNotesAndCategoriesButtons();
				syncClearAllFavoritesButton();
				clearWatchHistoryBtn.disabled=!getWatchEvents().length;
			},
			data:async current => {
				if (!await loadSettingsCache(current)) return;
				refreshCacheSizeLabel();
				refreshTechnicalDataSizeLabel();
				clearCacheBtn.disabled=clearExpiredCacheBtn.disabled=clearTechnicalDataBtn.disabled=false;
			},
			diagnostics:async current => {
				clearInterval(diagnosticsStatusTimer);
				diagnosticsStatusTimer=setInterval(() => {
					if (!document.hidden && current()) renderDiagnosticBackoff();
				},1000);
				diagnosticsRefreshBtn.disabled=true;
				try {
					if (!await loadSettingsCache(current)) return;
					renderDiagnostics();
					diagnosticsExportBtn.disabled=diagnosticsClearBtn.disabled=false;
					diagnosticsStatus.textContent='';
				} finally {diagnosticsRefreshBtn.disabled=false;}
			}
		});
		const activateSettingsTab=tabId => {
			for (const [id,button] of tabButtons) {
				const active=id === tabId;
				button.classList.toggle('-active',active);
				button.setAttribute('aria-selected',active ? 'true':'false');
				tabPanels.get(id)?.classList.toggle('-active',active);
			}
			contentGrid.scrollTop=0;
			clearInterval(diagnosticsStatusTimer);
			loadSettingsTab(tabId);
		};
		for (const [id,label,sections] of settingsTabs) {
			const button=makeButton('vm-settings-tab-button',label);
			button.dataset.vmSettingsTab=id;
			button.setAttribute('role','tab');
			button.addEventListener('click',() => activateSettingsTab(id));
			const panel=makeElement('div','vm-settings-tab-panel');
			panel.setAttribute('role','tabpanel');
			panel.append(...sections);
			tabButtons.set(id,button);
			tabPanels.set(id,panel);
			tabBar.appendChild(button);
			contentGrid.appendChild(panel);
		}
		overlay._vmActivateTab=tabId => {
			if (tabButtons.has(tabId)) activateSettingsTab(tabId);
		};
		activateSettingsTab(tabButtons.has(requestedTab) ? requestedTab:'page');
	}
	function saveHistoryCleanupKeywords(value) {
		const keywords=normalizeHistoryCleanupKeywords(value);
		updateSettings({
			historyCleanupKeywords:keywords
		});
		return keywords;
	}
	function openBrowserHistorySearchTabs(sourceKeywords=null) {
		const keywords=normalizeHistoryCleanupKeywords( sourceKeywords ?? getSettings().historyCleanupKeywords );
		if (!keywords.length) {
			window.alert('Lista słów kluczowych jest pusta. Uzupełnij ją w tym oknie.');
			return;
		}
		keywords.forEach((keyword,index) => {
			const historyUrl= `edge://history/all?q=${encodeURIComponent(keyword)}`;
			GM_openInTab(historyUrl,{
				active:index === keywords.length - 1
			});
		});
	}
	function openBrowserHistoryCleanupModal() {
		const overlayId='vm-garso-history-cleanup-overlay';
		if (document.getElementById(overlayId)) return;
		const overlay=makeElement('div');
		overlay.id=overlayId;
		prepareHelperStyleApi(overlay);
		overlay.classList.add('vm-ui-overlay');
		const closeController=createDialogCloseController(overlay);
		const box=makeElement('div',"vm-ui-dialog");
		Object.assign(box.style,{width:'min(570px, calc(100vw - 32px))'});
		const titleRow=makeElement('div',"vm-ui-header");
		const title=makeElement('div',"vm-ui-title",'Historia przeglądarki');
		const close=makeButton("vm-ui-button vm-ui-icon",'×');
		close.addEventListener( 'click', () => closeController.close('button') );
		titleRow.append(title,close);
		const keywordsLabel=makeElement('label',"vm-ui-label",'Frazy do wyszukania w historii przeglądarki:');
		const keywordsInput=makeElement('textarea',"vm-ui-field vm-ui-textarea",null,{value:normalizeHistoryCleanupKeywords(
			getSettings().historyCleanupKeywords ).join('\n'),rows:8,placeholder:'Każde słowo kluczowe w osobnym wierszu'});
		Object.assign(keywordsInput.style,{minHeight:'145px'});
		const info=makeElement('div',"vm-ui-help",'Każdy wpis otworzy osobną kartę historii Edge. Na każdej karcie użyj Ctrl+A, a następnie przycisku „Usuń”.');
		Object.assign(info.style,{marginTop:'7px'});
		const actions=makeElement('div',"vm-ui-actions");
		const save=makeButton('','Zapisz');
		const open=makeButton();
		for (const button of [save,open]) {
			button.classList.add('vm-ui-button');
		}
		save.classList.add('-accent');
		open.classList.add('-primary');
		const updateOpenLabel=() => {
			const count=normalizeHistoryCleanupKeywords(keywordsInput.value).length;
			open.textContent=count === 1
				? 'Otwórz 1 kartę'
				:`Otwórz ${count} kart`;
			open.disabled=count === 0;
			open.style.opacity=count === 0 ? '.5':'1';
		};
		keywordsInput.addEventListener('input',updateOpenLabel);
		updateOpenLabel();
		save.addEventListener('click',() => {
			saveHistoryCleanupKeywords(keywordsInput.value);
			closeController.close('button');
		});
		open.addEventListener('click',() => {
			const keywords=saveHistoryCleanupKeywords(keywordsInput.value);
			if (!keywords.length) return;
			closeController.close('button');
			openBrowserHistorySearchTabs(keywords);
		});
		actions.append(save,open);
		box.append(titleRow,keywordsLabel,keywordsInput,info,actions);
		overlay.appendChild(box);
		document.body.appendChild(overlay);
	}
	const vmMenuIcon=glyph => `data:image/svg+xml;charset=utf-8,${encodeURIComponent( typeof glyph === 'string'
			? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><text x="8" y="12.3" text-anchor="middle" font-family="Arial,sans-serif" font-size="13" fill="%23333">${glyph}</text></svg>`
			:new XMLSerializer().serializeToString(glyph.firstElementChild).replace(/currentColor/g,'#777') )}`;
	const HELPER_MENU_ID= 'vm-escort-helper-menu';
	const HELPER_MENU_PANEL_ID= 'vm-escort-helper-menu-panel';
	function detachHelperNode(node) {
		if (node?.parentNode) node.parentNode.removeChild(node);
	}
	function attachWatchCheckProgress(status,isLocalCheck) {
		const shared=makeElement('div',status.className);
		shared.setAttribute('role','status');
		status.after(shared);
		let listener,closed=false;
		const update=() => {
			if (closed) return;
			const lock=GM_getValue(WATCH_CHECK_LOCK_KEY,null),progress=lock?.progress;
			const active=lock?.owner && lock.expiresAt > Date.now() && progress?.total > 0 && !isLocalCheck();
			const text=active ? `${progress.checked || 0}/${progress.total} pozycji${progress.adTotal ? ` • anonse ${progress.adChecked || 0}/${progress.adTotal}`:''}`:'';
			if (shared.textContent !== text) shared.textContent=text;
			shared.hidden=!active;
			shared.style.display=active ? 'block':'none';
		};
		try {listener=GM_addValueChangeListener(WATCH_CHECK_LOCK_KEY,update);} catch {}
		const timer=setInterval(update,1000);
		update();
		return () => {
			closed=true; clearInterval(timer);
			if (listener != null) try {GM_removeValueChangeListener(listener);} catch {}
			detachHelperNode(shared);
		};
	}
	function readFavoriteViewPreferences(key,defaults={}) {
		try {
			const saved=GM_getValue(key,null);
			if (saved && typeof saved === 'object' && !Array.isArray(saved)) return {...defaults,...saved};
		} catch {}
		return {...defaults};
	}
	function saveFavoriteViewPreferences(key,patch) {
		const saved=readFavoriteViewPreferences(key);
		if (Object.keys(patch).some(name=>JSON.stringify(saved[name])!==JSON.stringify(patch[name])))
			writeHelperStorageValue(key,{...saved,...patch});
	}
	function switchFavoriteView(view) {
		if (view === 'table') return openWatchedSettingsModal(false,true);
		const options=getFavoriteTilesOptions();
		return openFavoriteTiles(options.fields,options.extra);
	}
	function createFavoriteViewData(events=[]) {
		const histories=new Map(),entries=new Map(),now=new Date();
		for (const event of events) {
			if (!histories.has(event.itemId)) histories.set(event.itemId,[]);
			histories.get(event.itemId).push(event);
		}
		return item => {
			if (entries.has(item)) return entries.get(item);
			const values=new Map(),history=histories.get(item.id) || [];
			const read=(key,load) => {
				if (!values.has(key)) values.set(key,load());
				return values.get(key);
			};
			const entry={
				events:history, stateRank:item.state?.online === true ? 0:item.state?.online === false ? 1:2,
				get unread() {return read('unread',() => history.filter(event => event.alertTriggered && !event.acknowledged));},
				get latestChange() {return read('latest',() => history.reduce((latest,event) => Math.max(latest,Number(event.timestamp) || 0),0));},
				get active() {return read('active',() => getWatchedItemAdUrls(item,true));},
				get all() {return read('all',() => getWatchedItemAdUrls(item,false));},
				get details() {return read('details',() => getWatchedItemCachedAdDetails(item,true));},
				get allDetails() {return read('allDetails',() => getWatchedItemCachedAdDetails(item,false));},
				get escorti() {return read('escorti',() => getCachedEscortiResultsForFavoriteItem(item));},
				get cities() {return read('cities',() => getFavoriteLocationCities(item,entry));},
				get lastVisited() {return read('visited',() => getFavoriteLastVisitedAt(item));},
				get availability() {return read('availability',() => getFavoriteAvailabilityNow(item,now,entry.details));},
				get availabilityTooltip() {return read('hours',() => getFavoriteAvailabilityTodayTooltip(item,now,entry.details));},
				column(field) {return read('column:'+field,() => formatFavoriteColumnValue(item,field,entry));},
				sortValue(field) {return read('sort:'+field,() => favoriteDataSortValue(item,field,entry.column(field),entry));}
			};
			entries.set(item,entry);
			return entry;
		};
	}
	function createFavoriteListRenderer(host) {
		const rows=new Map();
		return {
			get(id,signature) {const saved=rows.get(id);return saved?.signature===signature ? saved.node:null;},
			set(id,signature,node) {rows.set(id,{signature,node});return node;},
			render(nodes) {
				const wanted=new Set(nodes);
				for (const node of [...host.childNodes]) if (!wanted.has(node)) detachHelperNode(node);
				let cursor=host.firstChild;
				for (const node of nodes) {
					if (node===cursor) cursor=cursor.nextSibling;
					else host.insertBefore(node,cursor);
				}
				for (const [id,saved] of rows) if (!wanted.has(saved.node)) rows.delete(id);
			},
			clear() {rows.clear();}
		};
	}
	function fillFavoriteCategoryControls(host,item,settings,onChange,{pointerDown=false,disabled=false,decorate=null}={}) {
		const assigned=normalizeFavoriteAssignedCategories(item.categories);
		assigned.forEach((value,index) => {
			const chip=createFavoriteCategoryChip(value,settings.categoryColors[value] || '#b8b8bd',
				() => onChange(current => current.filter(entry => entry !== value)));
			const remove=chip.querySelector('button');
			remove.disabled=disabled;
			decorate?.(remove,`category-${index}`);
			host.appendChild(chip);
		});
		const available=settings.categories.filter(value => !assigned.includes(value));
		if (available.length) {
			const add=createFavoriteCategoryAddButton(anchor => openEscortInlineCategoryPicker(anchor,available,settings,
				value => onChange(current => [...current,value]),String(item.id || '')),pointerDown);
			add.disabled=disabled;
			add.setAttribute('aria-label','Dodaj kategorię');
			add.setAttribute('aria-haspopup','menu');
			decorate?.(add,'categories');
			host.appendChild(add);
		}
		return assigned;
	}
	const FAVORITE_TILES_PARAM='vm_favorite_tiles',FAVORITE_TILES_FIELDS_PARAM='vm_favorite_tiles_fields',FAVORITE_TILES_EXTRA_PARAM='vm_favorite_tiles_extra';
	const FAVORITE_TILES_DEFAULT='image,profile,city,age,price,online,alerts,visited,note,categories,aggregate';
	const FAVORITE_TILES_FIELDS=new Set((FAVORITE_TILES_DEFAULT+',height,weight,breasts').split(','));
	const FAVORITE_TILES_UI_KEY='vm_favorite_tiles_ui_v1';
	let favoriteTilesOpenGeneration=0;
	function getFavoritePageMode(){return IS_ESCORT_CLUB?new URL(location.href).searchParams.get(FAVORITE_TILES_PARAM):null;}
	function getFavoriteTilesPreferences(){return readFavoriteViewPreferences(FAVORITE_TILES_UI_KEY);}
	function saveFavoriteTilesPreferences(patch){saveFavoriteViewPreferences(FAVORITE_TILES_UI_KEY,patch);}
	function getFavoriteTilesOptions(url=location.href){
		const q=new URL(url,location.href).searchParams,saved=getFavoriteTilesPreferences();
		// Zapisane wybory, także puste, mają pierwszeństwo przed starym URL.
		const fields=new Set((Array.isArray(saved.fields)?saved.fields:(q.has(FAVORITE_TILES_FIELDS_PARAM)?q.get(FAVORITE_TILES_FIELDS_PARAM):FAVORITE_TILES_DEFAULT).split(',')).filter(v=>FAVORITE_TILES_FIELDS.has(v)));
		return {fields,extra:normalizeEscortDataFieldList(Array.isArray(saved.extra)?saved.extra:(q.get(FAVORITE_TILES_EXTRA_PARAM)||'').split(','),FAVORITES_MAX_DATA_COLUMNS)};
	}
	function openFavoriteTiles(fields,extra=[]){
		saveFavoriteTilesPreferences({fields:[...fields].filter(v=>FAVORITE_TILES_FIELDS.has(v)),extra:normalizeEscortDataFieldList(extra,FAVORITES_MAX_DATA_COLUMNS)});
		closeHelperDialog(document.getElementById(WATCH_SETTINGS_OVERLAY_ID));
		const host=document.getElementById('vm-favorite-tiles-test');
		if(host?._vmApplyOptions)host._vmApplyOptions(getFavoriteTilesOptions());
		else return renderFavoriteTilesView().catch(error=>{log('Błąd otwierania kafelków',error);showWatchToast('Nie udało się otworzyć kafelków.',true);});
	}
	function getFavoriteTilesData(item,view=null){
		const active=view?.active || getWatchedItemAdUrls(item,true),all=view?.all || getWatchedItemAdUrls(item,false),url=active[0]||all[0]||'',adId=parseAdIdFromUrl(url);
		const details=[...new Map((item.sourceType==='escort-ad'?getWatchedItemCachedAdDetails({adUrl:item.adUrl},true):view?.details || getWatchedItemCachedAdDetails(item,true)).map(detail=>[detail.adId,detail])).values()];
		const data=details.find(detail=>detail.adId===adId)?.adData||(adId?(getStoredEscortAdData(adId)||getWatchedAdDataMirror(adId)):null);
		return {active,all,url,adId,data,details,activeIds:new Set(active.map(parseAdIdFromUrl))};
	}
	function getFavoriteTileStat(data,label,unit=''){const value=normalizeEscortAdText(getEscortObjectEntryByNormalizedKey(data?.stats||{},label)?.[1]);return value&&unit&&/^\d+(?:[.,]\d+)?$/.test(value)?`${value} ${unit}`:value||'';}
	function getFavoriteTileBreastsText(data){
		const shortType=value=>value.replace(/\b(naturaln[yae]|sztuczn[yae])\b/gi,word=>/^n/i.test(word)?'N':'S');
		let breasts=shortType(getFavoriteTileStat(data,'biust'));
		const type=shortType(getFavoriteTileStat(data,'typ biustu')||getFavoriteTileStat(data,'rodzaj biustu'));
		if(breasts&&type&&!normalizeText(breasts).split(/[\s,;()/]+/).includes(normalizeText(type)))breasts+=` ${type}`;
		return breasts;
	}
	function getFavoriteTileDimensionsText(data){
		const height=getFavoriteTileStat(data,'wzrost','cm'),weight=getFavoriteTileStat(data,'waga','kg'),breasts=getFavoriteTileBreastsText(data);
		return [height,weight,breasts?`biust ${breasts}`:''].filter(Boolean).join(', ');
	}
	function getFavoriteTileProfileFacts(item,entry,duration,view=null){
		// Aktywne anonse profilu; przy braku ich listy ostatnie zapisane dane.
		const details=entry.details,profile=item.sourceType!=='escort-ad',active=profile&&entry.active.length>0;
		const unique=values=>[...new Map(values.map(normalizeEscortAdText).filter(Boolean).map(value=>[normalizeEscortChangeComparisonValue(value),value])).values()]
			.sort((a,b)=>a.localeCompare(b,'pl-PL',{numeric:true,sensitivity:'base'}));
		const range=(values,unit)=>{
			const list=unique(values);if(!list.length)return '';
			const suffix=unit==='lat'?/^(?:lat|lata|l\.?|rok|r\.?|y|years?)?$/i:new RegExp(`^(?:${unit})?$`,'i');
			const numbers=list.map(value=>{const match=value.match(/^(\d+(?:[.,]\d+)?)\s*(.*)$/);return match&&suffix.test(match[2])?Number(match[1].replace(',','.')):NaN;});
			if(!numbers.every(Number.isFinite))return list.join(' / ');
			const min=Math.min(...numbers),max=Math.max(...numbers),format=value=>value.toLocaleString('pl-PL',{maximumFractionDigits:2});
			return `${format(min)}${min===max?'':`–${format(max)}`} ${unit}`;
		};
		const stat=(label,unit)=>range(details.map(({adData})=>getFavoriteTileStat(adData,label)),unit);
		const age=stat('wiek','lat'),height=stat('wzrost','cm'),weight=stat('waga','kg'),breasts=unique(details.map(({adData})=>getFavoriteTileBreastsText(adData))).join(' / ');
		const dimensions=[height,weight,breasts?`biust ${breasts}`:''].filter(Boolean).join(', '),priceGroups=new Map();
		for(const {adData} of details){
			const price=adData.prices?.[duration];if(!formatEscortTilePrice(price))continue;
			const currency=String(price.currency||'').toUpperCase();if(!priceGroups.has(currency))priceGroups.set(currency,new Set());
			priceGroups.get(currency).add(Number(price.amount));
		}
		const price=[...priceGroups].sort(([a],[b])=>a.localeCompare(b)).map(([currency,values])=>{
			const min=Math.min(...values),max=Math.max(...values);
			return min===max?formatEscortTilePrice({amount:min,currency}):`${formatEscortTilePrice({amount:min})}–${formatEscortTilePrice({amount:max,currency})}`;
		}).join(' / ');
		const savedCities=profile?unique((item.state?.cities||[]).map(normalizeEscortCity)):[],cachedCities=details.map(({adData})=>normalizeEscortCity(adData.location?.city));
		// Pełny stan miast ma pierwszeństwo przed cache anonsów.
		const cities=savedCities.length&&!item.state?.locationPartial?savedCities:unique([...savedCities,...(profile&&!item.state?.cities?.length?(view?.cities || getFavoriteLocationCities(item)):cachedCities)]);
		const expected=profile?(active?entry.active.length:entry.all.length):(item.adUrl?1:0);
		const basis=`${profile?(active?'Aktywne anonse':'Ostatnie zapisane dane'):'Anons'} · cache ${details.length}/${expected}${item.state?.partial||item.state?.locationPartial?' · niepełne dane':''}`;
		return {age,height,weight,breasts,dimensions,price,cities,basis};
	}

	async function renderFavoriteTilesView(generation=++favoriteTilesOpenGeneration){
		await persistentCacheInitialization;
		await ensureFullPersistentCache();
		if(generation!==favoriteTilesOpenGeneration)return false;
		if(document.getElementById('vm-favorite-tiles-test'))return true;
		if(document.readyState==='loading')await new Promise(resolve=>document.addEventListener('DOMContentLoaded',resolve,{once:true}));
		if(generation!==favoriteTilesOpenGeneration)return false;
		if(document.getElementById('vm-favorite-tiles-test'))return true;
		const returnFocus=document.activeElement,pageScroll={left:scrollX,top:scrollY,behavior:'instant'};
		document.documentElement.classList.add('vm-favorite-tiles-test-mode');document.body.classList.add('vm-favorite-tiles-test-page');
		let {fields,extra}=getFavoriteTilesOptions();
		const host=prepareHelperStyleApi(makeElement('section','vm-favorite-tiles-test',null,{id:'vm-favorite-tiles-test'}));
		host.setAttribute('aria-label','Ulubione — widok kafelkowy');host.setAttribute('role','dialog');host.setAttribute('aria-modal','true');
		const updateTop=() => {
			const bar=document.getElementById(WATCH_BAR_ID),menu=document.getElementById(HELPER_MENU_ID);
			const bottom=Math.max(0,...[bar,menu].filter(node => node && !node.hidden).map(node => node.getBoundingClientRect().bottom));
			host.style.setProperty('--vm-favorite-top-offset',`${Math.ceil(bottom)}px`);
		};
		const topObserver=new ResizeObserver(updateTop);
		for (const node of [document.body,document.getElementById(WATCH_BAR_ID),document.getElementById(HELPER_MENU_ID)].filter(Boolean)) topObserver.observe(node);
		updateTop();
		const lifetime=new AbortController(),{signal}=lifetime,storageListeners=[];
		window.addEventListener('resize',updateTop,{signal});
		signal.addEventListener('abort',() => topObserver.disconnect(),{once:true});
		// Skrót #vft rozwijamy do dotychczasowego ID; specyficzność CSS nie zmienia się.
		const style=makeElement('style');style.textContent=`
html.vm-favorite-tiles-test-mode,body.vm-favorite-tiles-test-page{overflow:hidden!important}
#vft{position:fixed;z-index:2147483500;inset:var(--vm-favorite-top-offset,0px) 0 0;margin:0!important;padding:0!important;overflow:auto;overscroll-behavior:contain;background:var(--vm-ui-bg-alt,#f4f5f8);color:var(--vm-ui-text,#292c35);font:400 13px/1.45 var(--vm-ui-font-family,Arial,sans-serif);text-align:left;isolation:isolate}
#vft::before{content:'';position:fixed;z-index:11;inset:0 0 auto;height:var(--vm-favorite-top-offset,0px);background:var(--vm-ui-bg,#fff);pointer-events:auto}
#vft *,#vft *::before,#vft *::after{box-sizing:border-box}
#vft [hidden]{display:none!important}
#vft a{color:inherit;text-decoration:none}
#vft a:hover{text-decoration:underline}
#vft button,#vft input,#vft select,#vft summary{font-family:inherit}
#vft :is(button,a,input,select,summary):focus-visible{outline:2px solid var(--vm-ui-accent,#ce3986);outline-offset:2px}
#vft .vm-ui-button,#vft .vm-ui-field{min-height:28px;height:28px;font-size:11px;line-height:1.2;border-radius:5px;padding:3px 7px}
#vft select.vm-ui-field{padding-right:22px}
#vft .vft-toolbar{margin:0!important;padding:0!important;position:sticky;top:0;z-index:10;background:var(--vm-ui-bg,#fff);border-bottom:1px solid var(--vm-ui-border,#e1e3e9);box-shadow:0 2px 6px #00000006}
#vft .vft-toolbar-inner{display:flex;align-items:center;gap:8px;min-width:0;max-width:1660px;margin:0 auto;padding:5px 20px}
#vft .vft-toolbar .vm-ui-button,#vft .vft-toolbar .vm-ui-field{height:30px;min-height:30px;min-width:0;margin:0;padding:4px 8px;font-size:11px;line-height:1.3;border-radius:6px}
#vft .vft-toolbar select.vm-ui-field{padding-right:24px}
#vft .vft-heading{display:flex;flex:none;align-items:center;gap:6px;white-space:nowrap}
#vft .vft-title{margin:0;font-size:14px;line-height:1.2;font-weight:750;color:inherit}
#vft .vft-count{font-size:10px;line-height:1.2;padding:3px 5px;border-radius:5px;background:var(--vm-ui-bg-alt,#eef0f4);color:var(--vm-ui-text-muted,#697080);font-variant-numeric:tabular-nums}
#vft .vft-search{position:relative;display:block;flex:1 1 220px;min-width:80px;margin:0}
#vft .vft-search>.vft-icon{position:absolute;top:7px;left:9px;color:var(--vm-ui-text-muted,#697080);pointer-events:none}
#vft .vft-search>.vm-ui-field{width:100%;padding-left:30px;padding-right:30px;background:var(--vm-ui-input-bg,#fff)}
#vft .vft-search input::-webkit-search-cancel-button{display:none}
#vft .vft-search-clear{position:absolute;right:3px;top:3px;display:flex;align-items:center;justify-content:center;width:24px;height:24px;margin:0;padding:0;border:0;border-radius:4px;background:transparent;color:var(--vm-ui-text-muted,#697080);font-size:18px;line-height:1;cursor:pointer}
#vft .vft-search-clear:hover{background:var(--vm-ui-bg-hover,#e9ecf1);color:var(--vm-ui-text,#292c35)}
#vft .vft-toolbar-controls{display:flex;flex:none;align-items:center;gap:6px;min-width:0}
#vft .vft-icon{display:block;flex:none;width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
#vft .vft-toolbar :is(.vft-tool-toggle,.vft-toolbar-action){display:inline-flex;align-items:center;justify-content:center;gap:5px;flex:none;white-space:nowrap;cursor:pointer}
#vft .vft-toolbar .vft-close{flex:0 0 28px;width:28px;padding:0;border-color:transparent;background:transparent;color:var(--vm-ui-text-muted,#697080)}
#vft .vft-tool{flex:none}
#vft .vft-tool-toggle[aria-expanded=true],#vft .vft-filter-tool[data-active=true]>.vft-tool-toggle{border-color:var(--vm-ui-accent,#ce3986);background:color-mix(in srgb,var(--vm-ui-accent,#ce3986) 9%,var(--vm-ui-bg,#fff));color:var(--vm-ui-text,#292c35)}
#vft .vft-filter-count{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 3px;border-radius:4px;background:var(--vm-ui-accent,#ce3986);color:var(--vm-ui-accent-contrast,#fff);font-size:10px}
#vft .vft-sort{display:flex;align-items:center;gap:5px;flex:0 1 200px;min-width:0;width:200px;margin:0;color:var(--vm-ui-text-muted,#697080);font-size:10px;white-space:nowrap}
#vft .vft-sort>.vm-ui-field{flex:1;min-width:0;width:100%;text-overflow:ellipsis;color:var(--vm-ui-text,#292c35)}
#vft .vft-tool-panel{position:fixed;z-index:30;width:344px;max-width:calc(100vw - 16px);max-height:420px;overflow:auto;overscroll-behavior:contain;padding:10px;border:1px solid var(--vm-ui-border,#dfe2e9);border-radius:9px;background:var(--vm-ui-bg,#fff);color:var(--vm-ui-text,#292c35);box-shadow:var(--vm-ui-shadow,0 8px 24px #0003);text-align:left}
#vft .vft-panel-head,#vft .vft-panel-footer{display:flex;align-items:center;justify-content:space-between;gap:8px;min-width:0}
#vft .vft-panel-head{margin-bottom:8px;font-size:12px;font-weight:700}
#vft .vft-panel-result{color:var(--vm-ui-text-muted,#697080);font-size:10px;font-weight:400;text-align:right}
#vft .vft-panel-footer{margin-top:10px;padding-top:8px;border-top:1px solid var(--vm-ui-border,#e1e3e9)}
#vft .vft-filters{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
#vft .vft-filters>.vft-field:first-child{grid-column:1/-1}
#vft .vft-field{display:flex;flex-direction:column;gap:3px;min-width:0;margin:0;color:var(--vm-ui-text-muted,#697080);font-size:10px}
#vft .vft-field>.vm-ui-field{width:100%;font-weight:400;color:var(--vm-ui-text,#292c35)}
#vft .vft-filter-group{display:flex;flex:none;align-items:center;gap:4px;min-width:0;padding:2px;border:1px solid var(--vm-ui-border,#dfe2e9);border-radius:8px;background:var(--vm-ui-bg-alt,#f4f5f8)}
#vft .vft-filter-group .vft-tool-toggle{height:28px;min-height:28px;border-color:transparent;background:transparent}
#vft .vft-quick{display:flex;align-items:center;flex-wrap:nowrap;gap:3px;min-width:0;margin:0;padding-left:4px;border-left:1px solid var(--vm-ui-border,#dfe2e9)}
#vft .vft-chip{min-height:28px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--vm-ui-text-muted,#697080);padding:4px 6px;font-family:inherit;font-size:11px;font-weight:600;line-height:1.3;cursor:pointer;white-space:nowrap}
#vft .vft-chip:hover{background:var(--vm-ui-bg-hover,#e9ecf1);color:var(--vm-ui-text,#292c35)}
#vft .vft-chip[aria-pressed=true]{background:color-mix(in srgb,var(--vm-ui-accent,#ce3986) 13%,var(--vm-ui-bg,#fff));border-color:var(--vm-ui-accent,#ce3986);color:var(--vm-ui-text,#292c35)}
#vft .vft-view-tools{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
#vft .vft-extra-controls{grid-column:1/-1;min-width:0;padding-top:8px;border-top:1px solid var(--vm-ui-border,#e1e3e9)}
#vft .vft-extra-controls>.vft-panel-head{margin-bottom:5px;font-size:11px}
#vft .vft-extra-controls>.vm-ui-field{width:100%;max-width:100%;margin-bottom:5px}
#vft .vft-field-chips{display:grid;gap:4px;min-width:0}
#vft .vft-field-chip{display:flex;align-items:center;gap:5px;min-width:0;padding:1px 3px 1px 7px;border:1px solid var(--vm-ui-border,#dfe2e9);border-radius:5px;background:var(--vm-ui-bg-alt,#f4f5f8);font-size:11px}
#vft .vft-field-chip>span{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#vft .vft-field-chip>button{flex:none;width:24px;min-height:24px;padding:0;border:0;background:transparent;color:var(--vm-ui-text-muted,#697080);font-size:17px;cursor:pointer}
@media(max-width:1400px){#vft .vft-toolbar-inner{padding-inline:12px;gap:6px}#vft .vft-toolbar-action>.vft-action-label{display:none}#vft .vft-sort{width:176px;flex-basis:176px}#vft .vft-toolbar .vft-toolbar-action{width:30px;padding:0}}
@media(max-width:1100px){#vft .vft-toolbar-inner{flex-wrap:wrap;gap:4px 6px;padding:5px 10px}#vft .vft-search{flex-basis:100px;min-width:60px}#vft .vft-toolbar-controls{order:1;flex:1 0 100%;width:100%;gap:5px}#vft .vft-sort{flex:1 1 0;width:auto}#vft .vft-sort>span{display:none}#vft .vft-toolbar .vft-tool-toggle{padding-inline:6px}#vft .vft-tool-toggle>.vft-caret{display:none}}
@media(max-width:760px){#vft .vft-toolbar-controls{flex-wrap:wrap}#vft .vft-filter-group{flex:1 0 100%;width:100%}#vft .vft-quick{flex:1;flex-wrap:wrap}#vft .vft-chip{flex:1;padding-inline:5px}#vft .vft-filter-group .vft-tool-toggle{padding-inline:6px}}
@media(max-width:480px){#vft .vft-filter-group{gap:3px}#vft .vft-filter-group .vft-tool-toggle{position:relative;padding-inline:5px}#vft .vft-filter-group .vft-tool-toggle>.vft-icon{display:none}#vft .vft-filter-count{position:absolute;right:-3px;top:-6px;font-size:9px;height:14px;min-width:14px}#vft .vft-quick{gap:2px;padding-left:3px}#vft .vft-chip{font-size:10px;padding-inline:3px}}
#vft .vft-content{max-width:1660px;margin:0 auto;padding:14px 20px}
#vft .vft-status{margin-bottom:12px;padding:7px 10px;border:1px solid var(--vm-ui-border,#ddd);border-radius:6px;background:var(--vm-ui-bg,#fff);font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}
#vft .vft-status[data-error=true]{color:var(--vm-ui-danger,#b42335)}
#vft .vft-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr));gap:14px;align-items:start}
#vft[data-density=compact] .vft-grid{grid-template-columns:repeat(auto-fill,minmax(min(225px,100%),1fr));gap:10px}
#vft .vft-card{position:relative;min-width:0;border:1px solid var(--vm-ui-border,#dfe2e9);border-radius:10px;background:var(--vm-ui-bg,#fff);box-shadow:0 2px 6px #18263b07}
#vft .vft-card:has(.vft-menu[open]){z-index:3}
#vft .vft-card[data-unread=true]{border-color:var(--vm-ui-accent,#ce3986)}
#vft .vft-hero{position:relative;display:flex;flex-direction:column;justify-content:space-between;min-height:330px;border-radius:9px 9px 0 0;background:#273242;color:#fff}
#vft[data-density=compact] .vft-hero{min-height:295px}
#vft .vft-hero.-text-only{min-height:0;background:linear-gradient(145deg,#354256,#202a39)}
#vft .vft-hero:last-child,#vft .vft-hero:last-child .vft-media{border-radius:9px}
#vft .vft-media{position:absolute;inset:0;z-index:0;border-radius:inherit;overflow:hidden}
#vft .vft-media::after{content:'';position:absolute;inset:0;z-index:1;background:linear-gradient(180deg,rgba(6,12,20,0) 52%,rgba(6,12,20,.08) 66%,rgba(6,12,20,.28) 82%,rgba(6,12,20,.62) 100%);pointer-events:none}
#vft .vft-media:has(.vft-placeholder)::after{display:none}
#vft .vft-photo-link,#vft .vft-img{display:block;width:100%;height:100%}
#vft .vft-photo-link{position:relative}
#vft .vft-img{object-fit:cover;object-position:center 25%}
#vft .vft-placeholder{display:flex;flex-direction:column;gap:5px;align-items:center;justify-content:center;width:100%;height:100%;background:var(--vm-ui-bg-alt,#edf0f4);color:var(--vm-ui-text-muted,#697080);font-size:11px}
#vft .vft-media .vft-placeholder{justify-content:flex-start;padding-top:58px;background:linear-gradient(145deg,#3b4a61,#202a39);color:#dbe3ee}
#vft .vft-initials{font-size:36px;font-weight:700;opacity:.5;line-height:1.1}
#vft .vft-photo-top{position:relative;z-index:2;display:flex;flex-shrink:0;gap:6px;align-items:flex-start;justify-content:space-between;padding:9px;border-radius:inherit;background:transparent;pointer-events:none}
#vft .vft-photo-top button,#vft .vft-photo-top details{pointer-events:auto}
#vft .vft-badges{display:flex;gap:4px;align-items:flex-start;flex-wrap:wrap;min-width:0}
#vft .vft-badge{display:inline-flex;align-items:center;gap:4px;max-width:100%;padding:3px 6px;border:1px solid #ffffff28;border-radius:5px;background:#17202ddd;color:#fff;font-size:10px;font-weight:600;line-height:1.4;overflow-wrap:anywhere}
#vft .vft-badge.on{background:#16513eeb;color:#deffeb;border-color:#8df5b84d}
#vft .vft-badge.new{background:#7f2054ed;color:#fff;border:2px solid #ff3b30;padding:2px 5px;cursor:pointer}
#vft .vft-status-mark{display:inline-block;flex:none;min-width:1em;text-align:center;font:800 14px/1 Arial,sans-serif;vertical-align:-1px;text-shadow:none}
#vft .vft-status-mark.on{color:var(--vm-ui-success,#187747)}
#vft .vft-status-mark.off{color:var(--vm-ui-danger,#b42335)}
#vft .vft-status-mark.unknown{color:var(--vm-ui-text-muted,#697080)}
#vft .vft-status-outside{color:var(--vm-ui-warning,#96631d);font-weight:600}
#vft .vft-badge .vft-status-mark.on{color:#8df5b8}
#vft .vft-badge .vft-status-mark.off{color:#ff9292}
#vft .vft-badge .vft-status-mark.unknown{color:#cfd7e3}
#vft .vft-photo-actions{display:flex;flex:none;gap:4px;align-items:center}
#vft .vft-photo-action,#vft .vft-menu>summary{display:flex;align-items:center;justify-content:center;list-style:none;width:28px;height:28px;min-height:28px;padding:0;border:1px solid #ffffff40;border-radius:5px;background:#17202dc9;color:#fff;font-size:18px;line-height:1;cursor:pointer}
#vft .vft-photo-action:hover,#vft .vft-menu>summary:hover{background:#17202df2}
#vft .vft-photo-action:disabled{opacity:.55;cursor:wait}
#vft .vft-photo-content{position:relative;z-index:1;flex-shrink:0;margin-top:auto;padding:8px 11px 9px;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;background:transparent;text-shadow:0 1px 2px #000e,0 0 4px #0009;pointer-events:none}
#vft .vft-photo-content a,#vft .vft-photo-content button{pointer-events:auto;text-shadow:inherit}
#vft .vft-photo-content :is(.vft-name,.vft-source,.vft-city,.vft-price){padding:0;border-radius:0;background:transparent}
#vft .vft-photo-content :is(.vft-name,.vft-source){width:fit-content;max-width:100%}
#vft .vft-hero.-text-only .vft-photo-top{padding-bottom:8px}
#vft .vft-hero.-text-only .vft-photo-content{padding-top:6px;background:transparent}
#vft .vft-name,#vft .vft-city,#vft .vft-dimensions,#vft .vft-clamp{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
#vft .vft-name{margin:0;font-size:17px;line-height:1.25;font-weight:700;overflow-wrap:anywhere;color:var(--vft-title-color,#ffb3d5)!important;text-shadow:inherit}
#vft .vft-name>a{color:inherit!important}
#vft :is(.vft-name,.vft-name>a,.vft-source){-webkit-text-fill-color:currentColor!important}
#vft .vft-source{display:block;margin-top:3px;color:var(--vft-source-color,#a6dcff)!important;font-size:10px;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#vft .vft-dimensions{margin-top:3px;font-size:11px;line-height:1.35;color:#f3f5f8;overflow-wrap:anywhere;}
#vft :is(.vft-city,.vft-price,.vft-dimensions){pointer-events:auto}
#vft .vft-keyfacts{display:flex;gap:8px;justify-content:space-between;align-items:baseline;margin-top:5px}
#vft .vft-city{font-weight:600;font-size:12px;min-width:0;overflow-wrap:anywhere}
#vft .vft-price{margin-left:auto;text-align:right;flex:0 0 auto;max-width:58%;font-size:17px;font-weight:750;line-height:1.25;overflow-wrap:anywhere}
#vft .vft-price-period{font-size:10px;font-weight:400;color:#dbe1eb;white-space:nowrap}
#vft .vft-caption{display:block;color:var(--vm-ui-text-muted,#697080);font-size:11px;font-weight:400;line-height:1.4}
#vft .vft-clamp{white-space:pre-wrap}
#vft .vft-body{padding:0;display:grid;grid-template-columns:minmax(0,1fr);gap:0;min-width:0;text-align:left}
#vft .vft-personal{display:grid;grid-template-columns:minmax(0,1fr);align-content:start;justify-items:stretch;gap:2px;min-width:0;width:100%;padding:4px 10px;text-align:left;background:var(--vm-ui-bg,#fff)}
#vft .vft-bottom-row{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:8px;align-items:start;width:100%;min-width:0}
#vft .vft-cats{grid-column:1;display:flex;gap:4px;align-items:center;align-content:flex-start;justify-content:flex-start;flex-wrap:wrap;min-width:0;min-height:26px}
#vft .vft-cats .vm-favorites-category-chip{max-width:100%;height:20px;font-size:9px;line-height:18px}
#vft .vft-cats .vm-favorites-category-add-button{width:20px;min-width:20px;height:20px;flex:0 0 20px}
#vft .vft-cats:focus-within :is(.vm-favorites-category-remove,.vm-favorites-category-add-button){opacity:1!important}
#vft .vft-cats button:disabled{cursor:not-allowed;opacity:.4!important}
#vft .vft-bottom-icons{grid-column:2;display:flex;align-items:center;justify-content:flex-end;align-self:start;min-width:26px;height:26px}
#vft .vft-bottom-icons .vm-escort-inline-alerts{margin:0}
#vft .vft-bottom-icons svg{display:block;flex:none}
#vft .vft-note{display:flex;align-items:flex-start;justify-content:flex-start;gap:5px;width:100%;min-width:0;max-width:100%;min-height:20px;margin:0;text-align:left;padding:2px 4px;border:0;border-radius:4px;background:var(--vm-ui-bg-alt,#f5f6f8);color:inherit;cursor:pointer;font-size:11px;line-height:1.3;white-space:normal;overflow-wrap:anywhere}
#vft .vft-note .vft-clamp{min-width:0;flex:1;text-align:left}
#vft .vft-note-icon{flex:none;color:var(--vm-ui-text-muted,#697080)}
#vft .vft-note.-empty{min-height:20px;padding:2px 0;background:transparent;color:var(--vm-ui-text-muted,#697080);font-size:10px}
#vft .vft-note:disabled{cursor:not-allowed;opacity:.6}
#vft .vft-extra-wrap{width:100%;min-width:0;padding:4px 10px;border:0;border-radius:0;background:color-mix(in srgb,var(--vm-ui-text,#292c35) 8%,var(--vm-ui-bg,#fff))}
#vft .vft-extra{display:grid;grid-template-columns:minmax(0,1fr);gap:0;margin:0;font-size:11px}
#vft .vft-extra-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);column-gap:8px;align-items:center;padding:1px 0;line-height:16px}
#vft .vft-extra dt{margin:0;text-align:left;color:var(--vm-ui-text-muted,#697080);font-size:10px}
#vft .vft-extra dd{margin:0;text-align:right;font-weight:600}
#vft .vft-extra :is(dt,dd){min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;overflow-wrap:normal}
#vft .vft-error{padding:4px 10px;color:var(--vm-ui-danger,#b42335);font-size:11px;overflow-wrap:anywhere}
#vft .vft-error summary{cursor:pointer;min-height:24px;display:flex;justify-content:space-between;gap:8px;align-items:center}
#vft .vft-error summary span:first-child{color:var(--vm-ui-text-muted,#697080)}
#vft .vft-menu{position:relative;flex:none;color:var(--vm-ui-text,#292c35)}
#vft .vft-menu>summary::-webkit-details-marker{display:none}
#vft .vft-menu-panel{position:fixed;z-index:20;width:230px;max-width:calc(100vw - 16px);max-height:360px;overflow:auto;overscroll-behavior:contain;padding:5px;border:1px solid var(--vm-ui-border,#ddd);border-radius:8px;background:var(--vm-ui-bg,#fff);color:var(--vm-ui-text,#292c35);box-shadow:0 8px 28px #0004;text-shadow:none}
#vft .vft-menu-panel button{display:block;width:100%;border:0;border-radius:5px;padding:8px 10px;min-height:34px;background:transparent;color:inherit;text-align:left;font-size:12px;cursor:pointer}
#vft .vft-menu-panel button:hover{background:var(--vm-ui-bg-alt,#f1f3f6)}
#vft .vft-menu-panel button:disabled{opacity:.5;cursor:not-allowed}
#vft .vft-menu-panel .vft-danger{color:var(--vm-ui-danger,#b42335);border-top:1px solid var(--vm-ui-border,#ddd);margin-top:4px}
#vft .vft-extra-wrap,#vft .vft-extra-wrap+.vft-personal{border-top:1px solid var(--vm-ui-border-strong,#bec3cf)}
#vft .vft-related{border-top:2px solid var(--vm-ui-accent,#ce3986);border-radius:0 0 9px 9px;background:var(--vm-ui-bg,#fff)}
#vft .vft-related>summary{display:flex;align-items:center;gap:6px;list-style:none;padding:5px 10px;min-height:32px;font-size:11px;line-height:1.3;font-weight:700;cursor:pointer;color:var(--vm-ui-text,#292c35);background:color-mix(in srgb,var(--vm-ui-accent,#ce3986) 18%,var(--vm-ui-bg,#fff));border-radius:inherit}
#vft .vft-related>summary:hover,#vft .vft-related>summary:focus-visible{background:color-mix(in srgb,var(--vm-ui-accent,#ce3986) 27%,var(--vm-ui-bg,#fff))}
#vft .vft-related[open]>summary{border-radius:0;border-bottom:1px solid var(--vm-ui-border,#e2e4e9)}
#vft .vft-related>summary::-webkit-details-marker{display:none}
#vft .vft-related>summary::before{content:'▸';flex:none;font-size:16px;color:var(--vm-ui-accent,#ce3986)}
#vft .vft-related[open]>summary::before{content:'▾'}
#vft .vft-related-count{margin-left:auto;flex:none;padding:2px 6px;border:1px solid color-mix(in srgb,var(--vm-ui-accent,#ce3986) 35%,var(--vm-ui-border,#e2e4e9));border-radius:4px;text-align:right;color:var(--vm-ui-text,#292c35);background:var(--vm-ui-bg,#fff);font-size:10px;font-weight:750;white-space:nowrap}
#vft .vft-children{max-height:340px;overflow:auto;overscroll-behavior:contain;padding:2px 8px 8px;display:grid;gap:6px}
#vft .vft-child{display:flex;gap:8px;min-width:0;padding:6px;border:1px solid var(--vm-ui-border,#e2e4e9);border-radius:6px;font-size:11px}
#vft .vft-child-photo{width:48px;height:60px;object-fit:cover;object-position:top;border-radius:4px;flex:none;overflow:hidden}
#vft .vft-child-data{min-width:0;overflow-wrap:anywhere}
#vft .vft-child-name{font-weight:600}
#vft .vft-cache{margin-left:4px;font-size:10px;text-decoration:underline}
#vft .vft-open-all{justify-self:start}
#vft .vft-child .vft-initials{font-size:20px}
#vft .vft-empty{grid-column:1/-1;padding:40px 20px;text-align:center;border:1px dashed var(--vm-ui-border,#ccc);border-radius:10px;background:var(--vm-ui-bg,#fff);color:var(--vm-ui-text-muted,#697080)}
#vft .vft-empty strong{display:block;font-size:18px;margin-bottom:6px;color:var(--vm-ui-text,#292c35)}
#vft .vft-empty .vm-ui-button{margin-top:12px}
@media(max-width:700px){#vft .vft-content{padding:10px}#vft .vft-grid{grid-template-columns:repeat(auto-fill,minmax(min(260px,100%),1fr));gap:10px}#vft .vft-hero{min-height:330px}#vft .vft-hero.-text-only{min-height:0}}
`.replaceAll('#vft','#vm-favorite-tiles-test');host.appendChild(style);
		const initialDuration=normalizeEscortPriceDuration(SETTINGS.searchResultPriceDuration);
		const state={q:'',category:'all',online:'all',alerts:'all',kind:'all',sort:'added-desc',density:'comfortable',duration:initialDuration==='hide'?'60':initialDuration};
		try{const saved=JSON.parse(sessionStorage.getItem(FAVORITE_TILES_UI_KEY)||'null');if(saved&&typeof saved==='object')for(const key of Object.keys(state))if(typeof saved[key]==='string')state[key]=saved[key];}catch {}
		const savedView=getFavoriteTilesPreferences();
		for(const key of ['sort','density','duration'])if(typeof savedView[key]==='string')state[key]=savedView[key];
		const pending=new Set(),expanded=new Set(),toolbar=makeElement('header','vft-toolbar'),toolbarInner=makeElement('div','vft-toolbar-inner'),heading=makeElement('div','vft-heading'),count=makeElement('div','vft-count');
		count.setAttribute('role','status');heading.append(makeElement('h1','vft-title','Ulubione'),count);
		const button=(label,action,className='vm-ui-button')=>{const b=makeButton(className,label);b.addEventListener('click',action);return b;};
		const labeled=(label,control)=>{control.setAttribute('aria-label',label);const wrapper=makeElement('label','vft-field');wrapper.append(makeElement('span','',label),control);return wrapper;};
		const select=(key,options=[])=>{const input=makeElement('select','vm-ui-field');if(options.length&&!options.some(([value])=>value===state[key]))state[key]=options[0][0];appendSelectOptions(input,options,state[key]);input.addEventListener('change',()=>{state[key]=input.value;render();if(['sort','density','duration'].includes(key))saveFavoriteTilesPreferences({[key]:state[key]});});return input;};
		const icon=paths=>{const node=document.createElementNS('http://www.w3.org/2000/svg','svg');node.setAttribute('viewBox','0 0 24 24');node.setAttribute('class','vft-icon');node.setAttribute('aria-hidden','true');node.innerHTML=paths;return node;};
		const toolPanels=[];
		const closeToolPanels=(restoreFocus=false)=>{for(const tool of toolPanels)if(!tool.panel.hidden){tool.panel.hidden=true;tool.toggle.setAttribute('aria-expanded','false');if(restoreFocus)tool.toggle.focus({preventScroll:true});}};
		const positionToolPanel=tool=>{
			if(tool.panel.hidden||!tool.root.isConnected)return;
			const rect=tool.toggle.getBoundingClientRect(),top=Math.min(toolbar.getBoundingClientRect().bottom+4,innerHeight-56);
			tool.panel.style.maxHeight=`${Math.max(40,Math.min(420,innerHeight-top-8))}px`;
			tool.panel.style.left=`${Math.max(8,Math.min(rect.left,innerWidth-tool.panel.offsetWidth-8))}px`;tool.panel.style.top=`${Math.max(8,top)}px`;
		};
		const makeToolPanel=(key,label,paths)=>{
			const root=makeElement('div',`vft-tool vft-${key}-tool`),toggle=makeButton('vm-ui-button vft-tool-toggle'),panel=makeElement('div','vft-tool-panel',null,{id:`vft-${key}-panel`,hidden:true});
			const tool={root,toggle,panel};toggle.id=`vft-${key}-toggle`;toggle.append(icon(paths),makeElement('span','',label),makeElement('span','vft-caret','▾'));toggle.setAttribute('aria-expanded','false');toggle.setAttribute('aria-controls',panel.id);
			panel.setAttribute('role','region');panel.setAttribute('aria-labelledby',toggle.id);
			toggle.addEventListener('click',()=>{const open=panel.hidden;closeToolPanels();if(open){for(const menu of host.querySelectorAll('.vft-menu[open]'))menu.open=false;panel.hidden=false;toggle.setAttribute('aria-expanded','true');positionToolPanel(tool);}});
			root.addEventListener('keydown',event=>{if(event.key==='Escape'&&!panel.hidden){stopUiEvent(event);closeToolPanels(true);}else if(event.key==='ArrowDown'&&event.target===toggle){event.preventDefault();if(panel.hidden)toggle.click();panel.querySelector('button,input,select')?.focus({preventScroll:true});}});
			root.addEventListener('focusout',event=>{if(event.relatedTarget&&!root.contains(event.relatedTarget)&&!panel.hidden)closeToolPanels();});
			root.append(toggle,panel);toolPanels.push(tool);return tool;
		};
		const filtersTool=makeToolPanel('filter','Filtry','<path d="M4 7h16M4 17h16M8 4v6M16 14v6"/>'),viewTool=makeToolPanel('view','Widok','<rect x="3" y="4" width="7" height="16" rx="1.5"/><rect x="14" y="4" width="7" height="16" rx="1.5"/>');
		const filterCount=makeElement('span','vft-filter-count',null,{hidden:true}),filterResult=makeElement('span','vft-panel-result');filterCount.setAttribute('aria-hidden','true');filtersTool.toggle.insertBefore(filterCount,filtersTool.toggle.lastElementChild);
		const q=makeElement('input','vm-ui-field',null,{type:'search',value:state.q,placeholder:'Szukaj w Ulubionych…'}),searchField=makeElement('div','vft-search');q.setAttribute('aria-label','Szukaj w Ulubionych');q.title='Szukaj po nazwie, mieście, telefonie lub notatce';
		const searchClear=button('×',()=>{q.value=state.q='';q.focus({preventScroll:true});render();},'vft-search-clear');setControlTitle(searchClear,'Wyczyść wyszukiwanie');
		const syncSearchClear=()=>{searchClear.hidden=!q.value.length;};syncSearchClear();
		searchField.append(icon('<circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/>'),q,searchClear);
		const category=select('category'),online=select('online',[['all','Wszystkie'],['online','Online'],['offline','Offline'],['unknown','Brak danych']]),alerts=select('alerts',[['all','Wszystkie'],['unread','Nieodczytane'],['active','Włączone'],['none','Wyłączone']]);
		const filters=makeElement('div','vft-filters');filters.append(labeled('Kategoria',category),labeled('Stan',online),labeled('Alerty',alerts));
		const quick=makeElement('div','vft-quick'),quickButtons=new Map();quick.setAttribute('role','group');quick.setAttribute('aria-label','Szybki wybór stanu, alertów i wyszukiwań');
		for(const [key,label] of [['all','Wszystkie'],['online','Online'],['unread','Nowe alerty'],['search','Wyszukiwania']]){
			const b=button(label,()=>{closeToolPanels();state.kind=key==='search'?'search':'all';state.online=key==='online'?'online':'all';state.alerts=key==='unread'?'unread':'all';online.value=state.online;alerts.value=state.alerts;render();},'vft-chip');b.title=key==='search'?'Pokaż zapisane wyszukiwania; pozostaw wpisany tekst i kategorię':'Zmień stan i alerty; pozostaw wpisany tekst i kategorię';quickButtons.set(key,b);quick.appendChild(b);
		}
		const clear=button('Wyczyść filtry',()=>{resetFilters();filtersTool.toggle.focus({preventScroll:true});}),filterHead=makeElement('div','vft-panel-head'),filterFooter=makeElement('div','vft-panel-footer');
		filterHead.append(makeElement('span','','Filtruj Ulubione'),filterResult);filterFooter.append(clear,button('Gotowe',()=>closeToolPanels(true),'vm-ui-button -primary'));filtersTool.panel.append(filterHead,filters,filterFooter);
		const sort=select('sort'),sortField=labeled('Sortuj',sort),sortGroup=makeElement('optgroup','',null,{label:'Dane z profilu'});sortField.className='vft-sort';
		appendSelectOptions(sort,[['added-desc','Ostatnio dodane'],['added-asc','Najstarsze'],['name','Nazwa A–Z'],['online','Online najpierw'],['active','Najwięcej aktywnych'],['checked','Ostatnio sprawdzone'],['changes','Ostatnia zmiana']]);sort.appendChild(sortGroup);
		let sortFields=new Map(),sortSignature='';
		const refreshSortOptions=()=>{
			const options=[['profile:cities','Miasto','cities',fields.has('city')],['ad:stats:wiek','Wiek','age',fields.has('age')],['ad:stats:wzrost','Wzrost','height',true],['ad:stats:waga','Waga','weight',true],['ad:stats:biust','Biust','breasts',true],['price',`Cena ${getEscortPriceDurationLabel(state.duration)}`,'price',fields.has('price')],...extra.map(field=>[field,getFavoriteColumnLabel(field),'',true])].filter(v=>v[3]);
			const signature=JSON.stringify(options);if(signature!==sortSignature){
				sortSignature=signature;sortFields.clear();sortGroup.replaceChildren();
				for(const [field,label,fact] of options){const key=field===`ad:prices:${state.duration}`&&fields.has('price')?'price':decodeEscortDataFieldSegment(field).toLowerCase();if(sortFields.has(key))continue;
					sortFields.set(key,{field,fact});appendSelectOptions(sortGroup,[['data:+:'+key,label+' ↑'],['data:-:'+key,label+' ↓']]);}
			}
			sort.value=state.sort;if(!sort.value)sort.value=state.sort='added-desc';
		};
		const duration=select('duration',ESCORT_PRICE_DURATION_OPTIONS),density=select('density',[['comfortable','Wygodny'],['compact','Zwarty']]),priceField=labeled('Cena za',duration),tools=makeElement('div','vft-view-tools');
		const extraControls=makeElement('div','vft-extra-controls'),extraHead=makeElement('div','vft-panel-head'),extraCount=makeElement('span','vft-panel-result');
		const extraSelect=makeElement('select','vm-ui-field',null,{id:'vft-extra-select',hidden:true}),extraChips=makeElement('div','vft-field-chips');
		extraSelect.setAttribute('aria-label','Dodaj informację');extraSelect.title='Dane z cache; bez pobierania.';
		extraCount.setAttribute('role','status');
		const extraAdd=button('Dodaj',()=>{refreshViewOptions();extraSelect.hidden=!extraSelect.hidden;extraAdd.setAttribute('aria-expanded',String(!extraSelect.hidden));positionToolPanel(viewTool);if(!extraSelect.hidden)extraSelect.focus({preventScroll:true});});
		extraAdd.setAttribute('aria-controls',extraSelect.id);extraAdd.setAttribute('aria-expanded','false');
		extraHead.append(makeElement('span','','Wyświetl dodatkowo:'),extraCount,extraAdd);extraControls.append(extraHead,extraSelect,extraChips);
		const refreshViewOptions=()=>{
			const available=getEscortDataFieldOptions('none',true).filter(([field])=>field!=='none'&&!extra.includes(field));
			extraSelect.replaceChildren();appendSelectOptions(extraSelect,[['','wybierz…'],...available],'');extraAdd.disabled=!available.length||extra.length>=FAVORITES_MAX_DATA_COLUMNS;
			extraAdd.title=extraAdd.disabled?`Maksymalnie ${FAVORITES_MAX_DATA_COLUMNS} pól`:'Dodaj informację';extraCount.textContent=`${extra.length}/${FAVORITES_MAX_DATA_COLUMNS}`;
			if(extraAdd.disabled)extraSelect.hidden=true;extraAdd.setAttribute('aria-expanded',String(!extraSelect.hidden));extraChips.replaceChildren();
			for(let index=0;index<extra.length;index++){
				const field=extra[index];
				const label=getFavoriteColumnLabel(field),chip=makeElement('span','vft-field-chip');
				const remove=button('×',()=>{openFavoriteTiles(fields,extra.filter(value=>value!==field));(extraChips.querySelectorAll('button')[Math.min(index,extra.length-1)]||extraAdd).focus({preventScroll:true});},'');
				setControlTitle(remove,`Usuń informację: ${label}`);chip.append(makeElement('span','',label,{title:label}),remove);extraChips.appendChild(chip);
			}
			positionToolPanel(viewTool);
		};
		extraSelect.addEventListener('change',()=>{
			const field=normalizeEscortDataFieldId(extraSelect.value);if(field==='none'||extra.includes(field)||extraAdd.disabled)return;
			extraSelect.hidden=true;openFavoriteTiles(fields,normalizeEscortDataFieldList([...extra,field],FAVORITES_MAX_DATA_COLUMNS));
			(extraAdd.disabled?extraChips.lastElementChild?.querySelector('button'):extraAdd)?.focus({preventScroll:true});
		});
		tools.append(priceField,labeled('Układ kafelków',density),extraControls);viewTool.panel.append(makeElement('div','vft-panel-head','Widok kafelków'),tools);
		window.addEventListener('vmEscortDataCatalogChanged',()=>{if(host.isConnected)refreshViewOptions();},{signal});
		const refresh=button('',()=>{closeToolPanels();runChecks(visibleIds);},'vm-ui-button vft-toolbar-action -primary'),refreshLabel=makeElement('span','vft-action-label','Sprawdź');
		refresh.append(icon('<path d="M20 7v5h-5M4 17v-5h5"/><path d="M6.1 6.1A8 8 0 0 1 19.8 11M4.2 13a8 8 0 0 0 13.7 4.9"/>'),refreshLabel);
		const tableView=makeFavoriteViewButton('table',()=>{closeToolPanels(true);switchFavoriteView('table');});tableView.classList.add('vft-toolbar-action');
		const closeView=button('',()=>closeHelperDialog(host,'button'),'vm-ui-button vft-close');closeView.append(icon('<path d="m6 6 12 12M6 18 18 6"/>'));setControlTitle(closeView,'Zamknij');
		const filterGroup=makeElement('div','vft-filter-group');filterGroup.setAttribute('role','group');filterGroup.setAttribute('aria-label','Filtry i szybki wybór');filterGroup.append(filtersTool.root,quick);
		const controls=makeElement('div','vft-toolbar-controls');controls.append(filterGroup,sortField,viewTool.root,refresh,tableView);toolbarInner.append(heading,searchField,controls,closeView);toolbar.appendChild(toolbarInner);
		document.addEventListener('pointerdown',event=>{if(!toolPanels.some(tool=>tool.root.contains(event.target)))closeToolPanels();},{capture:true,signal});
		const content=makeElement('main','vft-content'),status=makeElement('div','vft-status'),grid=makeElement('div','vft-grid');status.hidden=true;status.setAttribute('role','status');grid.setAttribute('aria-label','Lista Ulubionych');content.append(status,grid);host.append(toolbar,content);
		const stopCheckProgress=attachWatchCheckProgress(status,() => pending.size > 0);
		const tileRows=createFavoriteListRenderer(grid);
		let visibleIds=[];
		const updateCategoryOptions=items=>{const selected=state.category,settings=getFavoriteSettings(),values=[...new Set([...settings.categories,...items.flatMap(item=>normalizeFavoriteAssignedCategories(item.categories))])].sort((a,b)=>a.localeCompare(b,'pl-PL'));category.replaceChildren();appendSelectOptions(category,[['all','Wszystkie'],['__none__','Bez kategorii'],...values.map(value=>[value,value])],values.includes(selected)||selected==='__none__'?selected:'all');state.category=category.value;};
		const fullDate=timestamp=>new Date(Number(timestamp)).toLocaleString('pl-PL');
		const relative=timestamp=>formatEscortLastVisit(timestamp)||'przed chwilą';
		const period=()=>getEscortPriceDurationLabel(state.duration);
		const link=(url,text,className='')=>makeElement('a',className,text,{href:url,target:'_blank',rel:'noopener noreferrer'});
		const keyed=(node,item,key)=>{node.dataset.vftFocus=`${item.id}:${key}`;return node;};
		const statusMark=(value,label='')=>{
			const mark=makeElement('span',`vft-status-mark ${value===true?'on':value===false?'off':'unknown'}`,value===true?'✓':value===false?'×':'?');
			if(label){setControlTitle(mark,label);mark.setAttribute('role','img');}else mark.setAttribute('aria-hidden','true');return mark;
		};
		const runChecks=async ids=>{
			if(pending.size)return;ids=[...new Set(ids)];if(!ids.length)return;ids.forEach(id=>pending.add(id));status.hidden=false;status.dataset.error='false';status.textContent=`Sprawdzanie ${ids.length} pozycji…`;render();
			try{const result=await runWatchedChecks({force:true,itemIds:ids,onProgress:progress=>status.textContent=progress.message||`Sprawdzanie ${progress.itemIndex||progress.checked||0}/${progress.total||ids.length}: ${progress.itemName||''}`});status.dataset.error=String(!!result.failed);status.textContent=result.message||`Sprawdzono: ${result.checked||0}/${result.total||ids.length}.${result.failed?` Błędy: ${result.failed}. Szczegóły na kafelkach.`:''}`;}
			catch(error){status.textContent=error?.message||'Nie udało się sprawdzić danych.';status.dataset.error='true';}
			finally{pending.clear();render();}
		};
		const editNote=async item=>{await changeTrackedEntry(item.id,'note');render();};
		const editAlerts=async item=>{await changeTrackedEntry(item.id,'alerts');render();};
		const placeholder=(name,small=false)=>{const box=makeElement('div','vft-placeholder');box.appendChild(makeElement('span','vft-initials',String(name||'?').trim().slice(0,1).toUpperCase()));if(!small)box.appendChild(makeElement('span','','Brak zdjęcia'));return box;};
		const photo=(src,name,small=false)=>{if(!src)return placeholder(name,small);const img=makeElement('img',small?'vft-child-photo':'vft-img',null,{src,alt:small?'':`Zdjęcie — ${name}`,loading:'lazy',decoding:'async',referrerPolicy:'no-referrer'});img.addEventListener('error',()=>{const replacement=placeholder(name,small);if(small)replacement.classList.add('vft-child-photo');img.replaceWith(replacement);},{once:true});return img;};
		const makeChild=(item,entry,url)=>{
			const id=parseAdIdFromUrl(url),data=(id&&(getStoredEscortAdData(id)||getWatchedAdDataMirror(id)))||{},name=normalizeEscortAdText(data.title)||item.displayName||'Anons';
			const row=link(url,null,'vft-child'),info=makeElement('div','vft-child-data'),active=entry.activeIds.has(id),availability=active?(item.sourceType==='escort-search'?'W bieżących wynikach':'Aktywny'):item.lastCheckedAt?(item.sourceType==='escort-search'?'Poza bieżącymi wynikami':'Nieaktywny'):'Stan nieznany';
			if(fields.has('image')){const image=photo(data.imageUrl,name,true);image.classList.add('vft-child-photo');row.appendChild(image);}
			const caption=makeElement('span','vft-caption',`Anons ${id||'—'} · `);
			if(!active&&item.lastCheckedAt&&item.sourceType==='escort-search')caption.appendChild(makeElement('span','vft-status-outside','Poza wynikami',{title:'Poza wynikami; anons może nadal być dostępny.'}));
			else caption.appendChild(statusMark(active?true:item.lastCheckedAt?false:null,availability));
			const cacheUrl=!active&&getAvailableEscortAdUrls([[url,{active:false}]])[0];
			if(cacheUrl){row.href=cacheUrl;caption.appendChild(makeElement('span','vft-cache','cache',{title:'Otwórz zapisaną wersję anonsu'}));}
			info.append(makeElement('div','vft-child-name',name),caption);
			const details=[];if(fields.has('city'))details.push(normalizeEscortCity(data.location?.city)||'Lokalizacja: b/d');
			const age=fields.has('age')?getFavoriteTileStat(data,'wiek','lat'):'';if(age)details.push(age);
			if(details.length)info.appendChild(makeElement('div','',details.join(', ')));
			info.appendChild(makeElement('div','',getFavoriteTileDimensionsText(data)||'Wymiary: b/d'));
			if(fields.has('price'))info.appendChild(makeElement('div','',`${formatEscortTilePrice(data.prices?.[state.duration])||'Cena: b/d'} / ${period()}`));
			row.appendChild(info);return row;
		};
		const positionMenu=menu=>{
			if(!menu?.open||!menu.isConnected)return;
			const toggle=menu.querySelector('summary'),panel=menu.querySelector('.vft-menu-panel'),rect=toggle.getBoundingClientRect(),limitTop=Math.max(8,toolbar.getBoundingClientRect().bottom+4);
			if(rect.bottom<limitTop||rect.top>innerHeight){menu.open=false;return;}
			const below=Math.max(0,innerHeight-rect.bottom-10),above=Math.max(0,rect.top-limitTop-6),up=below<Math.min(260,panel.scrollHeight)&&above>below;
			panel.style.maxHeight=`${Math.min(360,up?above:below)}px`;
			panel.style.left=`${Math.max(8,Math.min(rect.right-panel.offsetWidth,innerWidth-panel.offsetWidth-8))}px`;
			panel.style.top=`${up?Math.max(limitTop,rect.top-panel.offsetHeight-6):rect.bottom+6}px`;
		};
		const scheduleMenuPosition=createAnimationFrameScheduler(()=>{if(!host.isConnected)return;for(const menu of host.querySelectorAll('.vft-menu[open]'))positionMenu(menu);for(const tool of toolPanels)positionToolPanel(tool);});
		host.addEventListener('scroll',scheduleMenuPosition,{passive:true});window.addEventListener('resize',scheduleMenuPosition,{signal});
		let viewData;
		const makeCard=(item,itemEvents,entry,settings)=>{
			const data=entry.data||{},isSearch=item.sourceType==='escort-search',url=isSearch?watchItemSourceUrl(item):entry.url||watchItemSourceUrl(item),name=item.displayName||normalizeEscortAdText(data.title)||'Ulubione',unread=viewData(item).unread;
			const card=makeElement('article','vft-card'),body=makeElement('div','vft-body'),hero=makeElement('div',`vft-hero${fields.has('image')?'':' -text-only'}`);
			card.dataset.itemId=item.id;card.dataset.unread=String(unread.length>0);card.setAttribute('aria-label',name);
			if(fields.has('image')){const media=makeElement('div','vft-media'),a=url?link(url,null,'vft-photo-link'):makeElement('div','vft-photo-link');if(url)a.setAttribute('aria-label',`Otwórz ${isSearch?'wyszukiwanie':entry.url?'anons':'profil'}: ${name}`);a.appendChild(photo(data.imageUrl||item.imageUrl,name));media.appendChild(a);hero.appendChild(media);}
			const top=makeElement('div','vft-photo-top'),badges=makeElement('div','vft-badges'),photoActions=makeElement('div','vft-photo-actions'),photoContent=makeElement('div','vft-photo-content');
			if(fields.has('online')){const online=item.state?.online,badge=makeElement('span',`vft-badge ${online===true?'on':''}`);badge.append(statusMark(online),document.createTextNode(online===true?'Online':online===false?'Offline':'Brak danych'));badge.title=item.lastCheckedAt?`Sprawdzono: ${fullDate(item.lastCheckedAt)}`:'Jeszcze niesprawdzone';badges.appendChild(badge);}
			if(fields.has('alerts')&&unread.length){const alert=keyed(button(formatPolishCount(unread.length,'nowy alert','nowe alerty','nowych alertów'),()=>openWatchChangesTimeline(item,itemEvents),'vft-badge new'),item,'unread');setControlTitle(alert,`${unread.length} nowych alertów — historia`);badges.appendChild(alert);}
			if(item.hidden)badges.appendChild(makeElement('span','vft-badge','Ukryty w wynikach'));
			const title=makeElement('h2','vft-name');title.title=name;title.appendChild(url?link(url,name):makeElement('span','',name));photoContent.appendChild(title);
			if(fields.has('profile')){const source=watchItemSourceUrl(item),text=watchItemSourceText(item),node=source?link(source,text,'vft-source'):makeElement('div','vft-source',text);node.title=`Źródło profilu: ${text}`;photoContent.appendChild(node);}
			const facts=entry.facts||=getFavoriteTileProfileFacts(item,entry,state.duration,viewData(item)),keyfacts=makeElement('div','vft-keyfacts'),ageText=fields.has('age')?facts.age:'';
			if(fields.has('city')||ageText){const cityText=[fields.has('city')?(facts.cities.join(', ')||'Lokalizacja: b/d'):'',ageText].filter(Boolean).join(', ');keyfacts.appendChild(makeElement('div','vft-city',cityText,{title:`${cityText}\n${facts.basis}`}));}
			if(fields.has('price')){const price=makeElement('div','vft-price',facts.price||'b/d',{title:`${facts.price||'b/d'} / ${period()}\n${facts.basis}`});price.appendChild(makeElement('span','vft-price-period',` / ${period()}`));keyfacts.appendChild(price);}
			if(keyfacts.children.length)photoContent.appendChild(keyfacts);
			photoContent.appendChild(makeElement('div','vft-dimensions',facts.dimensions||'Wymiary: b/d',{title:`${facts.dimensions||'b/d'}\n${facts.basis}`}));
			const notesEnabled=getWatchSettings().enabled,latest=fields.has('alerts')?(unread[0]||itemEvents[0]):null,personal=makeElement('div','vft-personal');
			if(extra.length){
				const wrap=makeElement('div','vft-extra-wrap'),list=makeElement('dl','vft-extra');
				for(const field of extra){const row=makeElement('div','vft-extra-row'),value=viewData(item).column(field)||'b/d',label=getFavoriteColumnLabel(field);row.append(makeElement('dt','',label,{title:label}),makeElement('dd','',String(value).replace(/\s+/g,' ').trim(),{title:value}));list.appendChild(row);}
				wrap.appendChild(list);body.appendChild(wrap);
			}
			if(fields.has('categories')||fields.has('alerts')){const bottom=makeElement('div','vft-bottom-row');
				if(fields.has('categories')){
					const categoryCell=makeElement('div','vft-cats vm-favorites-category-cell');
					const assignedCategories=fillFavoriteCategoryControls(categoryCell,item,settings,async change => {
						await changeTrackedEntry(item.id,'categories',change);render();
					},{disabled:!notesEnabled,decorate:(control,key) => keyed(control,item,key)});
					categoryCell.title=assignedCategories.length?assignedCategories.join(', '):'Dodaj kategorię';bottom.appendChild(categoryCell);
				}
				if(fields.has('alerts')){
					const icons=makeElement('div','vft-bottom-icons'),alertButton=createTrackedAlertsControl({
						alertItems:[item],onEditAlerts:()=>editAlerts(item),labelText:item.sourceType==='escort-ad'?'Anons':'Profil',alertsDisabled:!item.favorite
					});
					icons.appendChild(keyed(alertButton,item,'alerts'));bottom.appendChild(icons);
				}
				personal.appendChild(bottom);}
			if(fields.has('note')){const note=keyed(button('',()=>editNote(item),`vft-note${item.note?'':' -empty'}`),item,'note');note.disabled=!notesEnabled;note.title=item.note||'Dodaj notatkę';note.setAttribute('aria-label',`Edytuj notatkę: ${name}`);if(item.note){const icon=makeElement('span','vft-note-icon','✎');icon.setAttribute('aria-hidden','true');note.append(icon,makeElement('span','vft-clamp',item.note));}else note.textContent='+ Notatka';personal.appendChild(note);}
			if(personal.children.length)body.appendChild(personal);
			if(item.lastError){const error=makeElement('details','vft-error'),summary=makeElement('summary');summary.title='Szczegóły błędu';summary.append(makeElement('span','','Sprawdzenie'),makeElement('span','','⚠ Błąd · rozwiń'));error.append(summary,makeElement('div','',item.lastError));body.appendChild(error);}
			const history=keyed(button('◷',()=>openWatchChangesTimeline(item,itemEvents),'vft-photo-action'),item,'history'),check=keyed(button(pending.has(item.id)?'…':'↻',()=>runChecks([item.id]),'vft-photo-action'),item,'check');
			const historyTitle=latest?`Ostatnia zmiana: ${relative(latest.timestamp)}\n${latest.message||latest.description||''}`:`Historia zmian (${itemEvents.length})`;
			setControlTitle(history,historyTitle);setControlTitle(check,`Sprawdź dane: ${name}`);check.disabled=pending.size>0;
			const menu=makeElement('details','vft-menu'),menuToggle=keyed(makeElement('summary','','⋯'),item,'menu'),menuPanel=makeElement('div','vft-menu-panel');setControlTitle(menuToggle,`Więcej działań: ${name}`);
			const menuAction=(label,action,danger=false)=>{const b=button(label,()=>{menu.open=false;menuToggle.focus({preventScroll:true});action();},danger?'vft-danger':'');keyed(b,item,`menu-${menuPanel.children.length}`);menuPanel.appendChild(b);return b;};
			if(unread.length)menuAction('Oznacz alerty jako przeczytane',()=>{const ids=new Set(unread.map(event=>event.id));saveWatchEvents(getWatchEvents().map(event=>ids.has(event.id)?{...event,acknowledged:true}:event));renderWatchNotificationBar();render();});
			menuAction('Zmień nazwę',async()=>{await changeTrackedEntry(item.id,'rename');render();});
			menuAction(item.hidden?'Pokaż w wynikach':'Ukryj w wynikach',()=>{changeTrackedItemStatus(item.id,'hidden');render();});
			menuAction('Usuń z Ulubionych',()=>{changeTrackedItemStatus(item.id,'favorite');render();}).title=trackedRemovalHint('favorite');
			menuAction('Usuń cały wpis…',()=>{changeTrackedItemStatus(item.id,'remove');render();},true).title=trackedRemovalHint('profile');
			menu.append(menuToggle,menuPanel);menu.addEventListener('toggle',()=>{if(menu.open&&menu.isConnected){for(const other of host.querySelectorAll('.vft-menu[open]'))if(other!==menu)other.open=false;positionMenu(menu);}});
			photoActions.append(history,check,menu);top.append(badges,photoActions);hero.append(top,photoContent);card.appendChild(hero);if(body.children.length)card.appendChild(body);
			if(fields.has('aggregate')&&item.sourceType!=='escort-ad'){
				const all=isSearch?getWatchedItemHistoricalAdUrls(item,itemEvents):entry.all,active=Number.isFinite(item.state?.activeCount)?item.state.activeCount:entry.active.length,total=isSearch?all.length:Number.isFinite(item.state?.totalAds)?item.state.totalAds:all.length;
				const related=makeElement('details','vft-related'),summary=keyed(makeElement('summary'),item,'related'),children=makeElement('div','vft-children');let built=false;
				const relatedCount=makeElement('span','vft-related-count');
				if(item.lastCheckedAt)relatedCount.append(`${active} `,statusMark(true,isSearch?'W bieżących wynikach':'Aktywne anonse'),` / ${total}`);else relatedCount.textContent=`${all.length} zapisanych · stan b/d`;
				summary.append(makeElement('span','','Anonse profilu'),relatedCount);summary.title=isSearch?'Bieżące i wszystkie dotąd znalezione anonse':'Aktywne i wszystkie zapisane anonse';
				const build=()=>{
					if(built)return;built=true;
					const urls=[...all].sort((a,b)=>Number(entry.activeIds.has(parseAdIdFromUrl(b)))-Number(entry.activeIds.has(parseAdIdFromUrl(a)))),results=new Map(urls.map(url=>[url,{active:entry.activeIds.has(parseAdIdFromUrl(url))}]));
					const openAll=keyed(button('',event=>openAvailableEscortAds(results,event),'vm-ui-button vm-ui-compact vft-open-all'),item,'open-all');
					updateOpenAvailableEscortAdsButton(openAll,results);children.appendChild(openAll);
					if(!all.length)children.appendChild(makeElement('div','vft-caption','Brak adresów anonsów. Sprawdź profil.'));
					else if(total!==all.length)children.appendChild(makeElement('div','vft-caption',`Zapisane adresy: ${all.length}. Licznik dotyczy ostatniego sprawdzenia.`));
					urls.forEach(value=>children.appendChild(makeChild(item,entry,value)));
				};
				related.append(summary,children);related.open=expanded.has(item.id);if(related.open)build();related.addEventListener('toggle',()=>{if(!related.isConnected)return;if(related.open){expanded.add(item.id);build();}else expanded.delete(item.id);});card.appendChild(related);
			}
			return card;
		};
		const resetFilters=()=>{state.q='';state.category=state.online=state.alerts=state.kind='all';q.value='';online.value=alerts.value='all';render();};
		const render=()=>{
			if(!host.isConnected)return;
			const focusKey=document.activeElement?.dataset.vftFocus,openMenuId=host.querySelector('.vft-menu[open]')?.closest('.vft-card')?.dataset.itemId,scrollTop=host.scrollTop,items=getWatchedItems().filter(item=>item.favorite),events=getWatchEvents(),settings=getFavoriteSettings();
			viewData=createFavoriteViewData(events);
			const itemEvents=item=>viewData(item).events,hasUnread=item=>viewData(item).unread.length>0,entries=new Map(items.map(item=>[item.id,getFavoriteTilesData(item,viewData(item))]));
			const searchCount=items.filter(item=>item.sourceType==='escort-search').length;
			if(state.kind!=='search'||!searchCount)state.kind='all';
			const searchButton=quickButtons.get('search');searchButton.textContent=`Wyszukiwania (${searchCount})`;
			if(!searchCount&&document.activeElement===searchButton)quickButtons.get('all').focus({preventScroll:true});searchButton.hidden=!searchCount;
			updateCategoryOptions(items);host.dataset.density=state.density;priceField.hidden=!fields.has('price');syncSearchClear();
			const terms=normalizeText(state.q).split(/\s+/).filter(Boolean),filtered=items.filter(item=>{
				if(state.kind==='search'&&item.sourceType!=='escort-search')return false;
				if(terms.length){const entry=entries.get(item.id),text=normalizeText([item.displayName,watchItemSourceText(item),item.phoneDigits,item.note,(item.categories||[]).join(' '),watchCitiesText(viewData(item).cities),...entry.details.map(({adData})=>adData.location?.city),...entry.all].filter(Boolean).join(' '));if(!terms.every(term=>text.includes(term)))return false;}
				const cats=normalizeFavoriteAssignedCategories(item.categories);if(state.category==='__none__'&&cats.length)return false;if(state.category!=='all'&&state.category!=='__none__'&&!cats.includes(state.category))return false;
				const status=item.state?.online===true?'online':item.state?.online===false?'offline':'unknown';if(state.online!=='all'&&state.online!==status)return false;
				const active=watchedItemHasActiveAlerts(item);return !((state.alerts==='active'&&!active)||(state.alerts==='none'&&active)||(state.alerts==='unread'&&!hasUnread(item)));
			});
			refreshSortOptions();
			const dataSort=state.sort.match(/^data:([+-]):(.+)$/),sortValues=new Map();
			if(dataSort){const {field,fact}=sortFields.get(dataSort[2]);for(const item of filtered){
				const entry=entries.get(item.id),text=fact?(entry.facts||=getFavoriteTileProfileFacts(item,entry,state.duration,viewData(item)))[fact]:undefined;
				sortValues.set(item.id,fact ? favoriteDataSortValue(item,field==='price'?`ad:prices:${state.duration}`:field,text,viewData(item)):viewData(item).sortValue(field==='price'?`ad:prices:${state.duration}`:field));}}
			const cmp=(a,b)=>compareFavoriteValues(a.displayName||'',b.displayName||''),rank=item=>viewData(item).stateRank;
			filtered.sort((a,b)=>(dataSort?compareFavoriteValues(sortValues.get(a.id),sortValues.get(b.id),dataSort[1]==='-'?-1:1):state.sort==='name'?cmp(a,b):state.sort==='added-asc'?(Number(a.createdAt)||0)-(Number(b.createdAt)||0):state.sort==='online'?rank(a)-rank(b):state.sort==='active'?(Number(b.state?.activeCount)||0)-(Number(a.state?.activeCount)||0):state.sort==='checked'?(Number(b.lastCheckedAt)||0)-(Number(a.lastCheckedAt)||0):state.sort==='changes'?(Number(itemEvents(b)[0]?.timestamp)||0)-(Number(itemEvents(a)[0]?.timestamp)||0):(Number(b.createdAt)||0)-(Number(a.createdAt)||0))||cmp(a,b));
			const nodes=[];
			for(const item of filtered){
				const entry=entries.get(item.id),history=itemEvents(item),signature=JSON.stringify([item,history,entry,viewData(item).allDetails,settings,[...fields],extra,state.duration,pending.size>0,getWatchSettings().enabled,Math.floor(Date.now()/60000)]);
				const card=tileRows.get(item.id,signature)||tileRows.set(item.id,signature,makeCard(item,history,entry,settings));nodes.push(card);
			}
			if(!filtered.length){const empty=makeElement('div','vft-empty');empty.append(makeElement('strong','',items.length?'Brak pasujących pozycji':'Nie masz jeszcze Ulubionych'),makeElement('div','',items.length?'Zmień lub wyczyść filtry.':'Dodaj anons lub profil gwiazdką.'));if(items.length)empty.appendChild(button('Wyczyść filtry',resetFilters));nodes.push(empty);}tileRows.render(nodes);if(openMenuId){const menu=[...grid.querySelectorAll('.vft-card')].find(node=>node.dataset.itemId===openMenuId)?.querySelector('.vft-menu');if(menu)menu.open=true;}
			visibleIds=filtered.map(item=>item.id);const onlineCount=items.filter(item=>item.state?.online===true).length,unreadCount=items.filter(hasUnread).length;
			count.textContent=`${filtered.length}/${items.length}`;setControlTitle(count,`Widoczne: ${filtered.length}/${items.length}`);quickButtons.get('all').textContent=`Wszystkie (${items.length})`;quickButtons.get('online').textContent=`Online (${onlineCount})`;quickButtons.get('unread').textContent=`Nowe alerty (${unreadCount})`;
			for(const [key,b] of quickButtons)b.setAttribute('aria-pressed',String(key==='search'?state.kind==='search':state.kind==='all'&&(key==='all'?state.online==='all'&&state.alerts==='all':key==='online'?state.online==='online'&&state.alerts==='all':state.online==='all'&&state.alerts==='unread')));
			const activeFilters=[!!state.q.trim(),state.category!=='all',state.online!=='all',state.alerts!=='all',state.kind==='search'].filter(Boolean).length;
			filterCount.hidden=!activeFilters;filterCount.textContent=String(activeFilters);filtersTool.root.dataset.active=String(activeFilters>0);filterResult.textContent=`${filtered.length} z ${items.length} pozycji`;
			const filterDescription=[state.q.trim()?`Szukaj: ${state.q.trim()}`:'',state.kind==='search'?'Typ: zapisane wyszukiwania':'',...[['Kategoria',category],['Stan',online],['Alerty',alerts]].filter(([,control])=>control.value!=='all').map(([label,control])=>`${label}: ${control.selectedOptions[0]?.textContent||control.value}`)].filter(Boolean).join('; ');
			setControlTitle(filtersTool.toggle,activeFilters?`Filtry (${activeFilters}) — ${filterDescription}`:'Filtry — kategorie, stan i alerty');clear.disabled=!activeFilters;
			sort.title=`Sortuj: ${sort.selectedOptions[0]?.textContent||''}`;setControlTitle(viewTool.toggle,`Widok — ${density.selectedOptions[0]?.textContent||''}${fields.has('price')?`; cena za ${period()}`:''}`);
			refresh.disabled=pending.size>0||!filtered.length;refreshLabel.textContent=pending.size?'Sprawdzanie…':'Sprawdź';setControlTitle(refresh,pending.size?'Sprawdzanie widocznych pozycji…':`Sprawdź dane widocznych pozycji (${filtered.length})`);refresh.setAttribute('aria-busy',String(pending.size>0));
			try{sessionStorage.setItem(FAVORITE_TILES_UI_KEY,JSON.stringify(state));}catch {}
			if(focusKey){const control=[...host.querySelectorAll('[data-vft-focus]')].find(node=>node.dataset.vftFocus===focusKey);control?.focus({preventScroll:true});}host.scrollTop=scrollTop;scheduleMenuPosition();
		};
		const scheduleRender=createAnimationFrameScheduler(()=>{if(host.isConnected)render();});
		q.addEventListener('input',()=>{state.q=q.value;syncSearchClear();scheduleRender();});
		host.addEventListener('click',event=>{for(const menu of host.querySelectorAll('.vft-menu[open]'))if(!menu.contains(event.target))menu.open=false;});
		host._vmRender=scheduleRender;host._vmApplyOptions=options=>{fields=options.fields;extra=options.extra;refreshViewOptions();render();};
		const closeController=createDialogCloseController(host,{closeOnBackdrop:false,beforeClose:reason=>{
			if(reason!=='escape')return true;
			const menu=host.querySelector('.vft-menu[open]');if(menu){menu.open=false;menu.querySelector('summary').focus();return false;}
			if(toolPanels.some(tool=>!tool.panel.hidden)){closeToolPanels(true);return false;}return true;
		},onClose:()=>{
			if(signal.aborted)return;
			stopCheckProgress();
			favoriteTilesOpenGeneration++;
			lifetime.abort();watchPageButtonRefreshers.delete(scheduleRender);
			for(const id of storageListeners)try{GM_removeValueChangeListener(id);}catch {}
			try{sessionStorage.setItem(FAVORITE_TILES_UI_KEY,JSON.stringify(state));}catch {}
			const picker=document.querySelector('.vm-favorites-category-picker');if(host.contains(picker?._vmAnchor))closeHelperDialog(picker);
			host._vmRender=host._vmApplyOptions=null;tileRows.clear();host.replaceChildren();
			document.documentElement.classList.remove('vm-favorite-tiles-test-mode');document.body.classList.remove('vm-favorite-tiles-test-page');
			window.scrollTo(pageScroll);if(returnFocus?.isConnected)returnFocus.focus({preventScroll:true});
		}});
		host._vmDestroy=()=>closeController.close('destroy');
		document.body.appendChild(host);refreshViewOptions();render();q.focus({preventScroll:true});
		// Migracja sesji i URL; potem zapis tylko po zmianie wyboru.
		saveFavoriteTilesPreferences({fields:[...fields],extra,sort:state.sort,density:state.density,duration:state.duration});
		watchPageButtonRefreshers.add(scheduleRender);
		try{for(const key of [WATCH_EVENTS_STORAGE_KEY,WATCH_ITEMS_STORAGE_KEY,WATCH_AD_DATA_MIRROR_STORAGE_KEY,SETTINGS_STORAGE_KEY])storageListeners.push(GM_addValueChangeListener(key,scheduleRender));}catch {}
		window.addEventListener('focus',scheduleRender,{signal});document.addEventListener('visibilitychange',()=>{if(!document.hidden)scheduleRender();},{signal});
		window.addEventListener('pagehide',event=>{if(!event.persisted)host._vmDestroy();},{signal});
		return true;
	}

	function openScriptMenuPage(url) {
		const normalized= normalizeMenuPageUrl(url);
		if (!normalized) return;
		try {
			GM_openInTab( normalized, {
					active:true, insert:true
				} );
		} catch {
			window.open( normalized, '_blank', 'noopener' );
		}
	}
	function getEnabledMenuPages() {
		return normalizeMenuPages( SETTINGS.menuPages ).filter( item => item.enabled !== false );
	}
	function getMenuPageFaviconUrl(url) {
		try {
			const parsed=new URL(url);
			if (/^(?:www\.)?sleazyfork\.org$/i.test(parsed.hostname)) return 'https://raw.githubusercontent.com/greasyfork-org/greasyfork/main/app/javascript/images/blacklogo32.png';
			return `${parsed.origin}/favicon.ico`;
		} catch {
			return '';
		}
	}
	function getHelperMenuActions() {
		let enabled=false;
		try {
			enabled=getWatchSettings().enabled;
		} catch {}
		return [ {
				icon:'★', label:'Ulubione', visible:enabled === true, action:() => openWatchedSettingsModal(false)
			}, {
				icon:'✎', label:TRACKED_ITEMS_LABEL, visible:enabled === true, action:() =>
					openWatchedSettingsModal(true)
			}, {
				icon:'⚙︎', label:'Ustawienia', visible:true, action:() => openSettingsModal()
			}, {
				icon:'⌫', label:'Otwórz historię do usunięcia', visible:true, action:() => openBrowserHistoryCleanupModal()
			} ].filter(item => item.visible);
	}
	function initHelperToolbar() {
		if (window.top !== window.self || (!GARSO_MENU_ONLY_MODE &&
			!IS_ESCORT_CLUB && !['escorti.pl','www.escorti.pl'].includes(CURRENT_HOST))) return;
		if (!document.body) {setTimeout(initHelperToolbar,100); return;}
		if (document.getElementById(HELPER_MENU_ID)) return;
		const root=prepareHelperStyleApi(makeElement('div'));
		root.id=HELPER_MENU_ID;
		root.setAttribute('role','toolbar');
		root.setAttribute('aria-label','Escort Helper');
		const style=makeElement('style');
		style.textContent=`
#${HELPER_MENU_ID} { display:flex; flex:0 0 auto; flex-wrap:wrap; min-width:0; max-width:calc(100vw - 16px); align-items:center; gap:2px; }
#${HELPER_MENU_ID}[hidden], #${HELPER_MENU_ID} [hidden] { display:none !important; }
#${HELPER_MENU_ID} > .vm-ui-button, #${HELPER_MENU_ID} .vm-helper-inline-link { flex:0 0 auto; text-decoration:none; min-height:26px; height:26px; min-width:28px; padding:3px 5px; border-color:transparent; border-radius:5px; background:transparent; color:var(--vm-ui-accent-contrast,#fff); font-size:16px; line-height:1; }
#${HELPER_MENU_ID} > .vm-ui-button:hover, #${HELPER_MENU_ID} > .vm-ui-button[aria-expanded="true"], #${HELPER_MENU_ID} .vm-helper-inline-link:hover { background:color-mix(in srgb,var(--vm-ui-accent-contrast,#fff) 18%,transparent); }
#${HELPER_MENU_ID} > .vm-ui-button:focus-visible, #${HELPER_MENU_ID} .vm-helper-inline-link:focus-visible { outline-color:var(--vm-ui-accent-contrast,#fff); outline-offset:0; }
#${HELPER_MENU_ID} .vm-helper-menu-separator { flex:0 0 1px; width:1px; height:18px; margin:0 5px; background:var(--vm-ui-accent-contrast,#fff); opacity:.55; }
#${HELPER_MENU_ID} .vm-helper-inline-pages { display:flex; flex:0 1 auto; flex-wrap:wrap; align-items:center; gap:2px; min-width:0; overflow:visible; }
#${HELPER_MENU_ID} .vm-helper-inline-link { width:28px; padding:2px; }
#${HELPER_MENU_ID} .vm-helper-inline-link img { display:block; width:22px; height:22px; object-fit:contain; }
#${HELPER_MENU_ID} .vm-helper-inline-link .vm-helper-page-initial { display:flex; flex:0 0 auto; align-items:center; justify-content:center; width:22px; height:22px; color:inherit; font-size:22px; font-weight:800; line-height:1; }
#${HELPER_MENU_ID} .vm-helper-menu-badge { min-width:14px; padding:2px 4px; border-radius:999px; background:color-mix(in srgb,var(--vm-ui-accent-contrast,#fff) 22%,transparent); font:700 10px/1.2 var(--vm-ui-font-family,Arial,sans-serif); }
#${HELPER_MENU_PANEL_ID} { position:fixed; z-index:1; width:min(290px,calc(100vw - 16px)); overflow:auto; box-sizing:border-box; padding:5px; border:1px solid var(--vm-ui-border,#ddd); border-radius:8px; background:var(--vm-ui-bg,#fff); color:var(--vm-ui-text,#222); box-shadow:var(--vm-ui-shadow,0 7px 24px rgba(0,0,0,.28)); }
#${HELPER_MENU_PANEL_ID} .vm-helper-menu-item { display:flex; width:100%; gap:8px; padding:8px; border:0; background:transparent; color:inherit; font-weight:400; text-align:left; justify-content:flex-start; white-space:normal; text-decoration:none; }
#${HELPER_MENU_PANEL_ID} .vm-helper-menu-item:hover, #${HELPER_MENU_PANEL_ID} .vm-helper-menu-item:focus-visible { background:var(--vm-ui-bg-hover,#f3f3f3); }
#${HELPER_MENU_PANEL_ID} .vm-helper-menu-icon { flex:0 0 18px; color:var(--vm-ui-accent,#f54da3); text-align:center; font-weight:700; }
`;
		const favorite=makeButton('vm-ui-button vm-helper-menu-favorite','★',{title:'Ulubione'});
		favorite.setAttribute('aria-label','Ulubione');
		const toggle=makeButton('vm-ui-button vm-helper-menu-toggle','☰');
		const separator=makeElement('span','vm-helper-menu-separator');
		separator.setAttribute('aria-hidden','true');
		const inlinePages=makeElement('div','vm-helper-inline-pages');
		inlinePages.setAttribute('role','group');
		inlinePages.setAttribute('aria-label','Linki do stron');
		setControlTitle(toggle,'Menu Escort Helper');
		toggle.setAttribute('aria-haspopup','menu');
		toggle.setAttribute('aria-controls',HELPER_MENU_PANEL_ID);
		toggle.setAttribute('aria-expanded','false');
		const badge=makeElement('span','vm-helper-menu-badge',null,{hidden:true});
		badge.setAttribute('aria-hidden','true');
		favorite.appendChild(badge);
		root._vmSetAlertCount=count => {
			badge.textContent=String(count); badge.hidden=!count;
			favorite.setAttribute('aria-label',count ? `Ulubione - nowe alerty: ${count}`:'Ulubione');
		};
		const panel=makeElement('div','',null,{id:HELPER_MENU_PANEL_ID,hidden:true});
		panel.setAttribute('role','menu');
		root.append(style,favorite,toggle,separator,inlinePages,panel);
		document.body.appendChild(root);
		let positionFrame=0;
		const closeMenu=() => {
			panel.hidden=true;
			toggle.setAttribute('aria-expanded','false');
		};
		const makePageLink=page => {
			const link=makeElement('a','vm-ui-button vm-helper-inline-link',null,{title:page.name});
			link.setAttribute('aria-label',page.name);
			link.href=page.url; link.target='_blank'; link.rel='noopener noreferrer';
			const icon=makeElement('img','',null,{alt:'',referrerPolicy:'no-referrer',src:getMenuPageFaviconUrl(page.url)});
			icon.addEventListener('error',() => {
				const initial=makeElement('span','vm-helper-page-initial',(page.name.trim().match(/\p{L}/u)?.[0] || [...page.name.trim()][0] || '?').toLocaleUpperCase('pl-PL'));
				initial.setAttribute('aria-hidden','true');
				icon.replaceWith(initial);
			},{once:true});
			link.appendChild(icon);
			link.addEventListener('click',event => {
				if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
				event.preventDefault(); closeMenu(); openScriptMenuPage(page.url);
			});
			return link;
		};
		const renderItems=() => {
			panel.replaceChildren();
			panel.setAttribute('aria-label','Menu Escort Helper');
			for (const item of getHelperMenuActions()) {
				const button=makeButton('vm-ui-button vm-helper-menu-item');
				button.setAttribute('role','menuitem');
				const icon=makeElement('span','vm-helper-menu-icon');
				icon.setAttribute('aria-hidden','true'); icon.append(item.icon);
				button.append(icon,makeElement('span','',item.label));
				button.addEventListener('click',() => {closeMenu(); item.action();});
				panel.appendChild(button);
			}
		};
		const openMenu=() => {
			if (!panel.hidden) return;
			renderItems(); panel.hidden=false;
			toggle.setAttribute('aria-expanded','true');
			syncPosition();
		};
		favorite.addEventListener('click',() => {closeMenu(); openWatchedSettingsModal(false);});
		toggle.addEventListener('click',() => panel.hidden ? openMenu():closeMenu());
		root.addEventListener('keydown',event => {
			if (event.key === 'Escape' && !panel.hidden) {
				stopUiEvent(event); closeMenu(); toggle.focus(); return;
			}
			if (!['ArrowDown','ArrowUp','Home','End'].includes(event.key) ||
				(event.target !== toggle && !panel.contains(event.target))) return;
			stopUiEvent(event);
			openMenu();
			const items=[...panel.querySelectorAll('[role="menuitem"]')],index=items.indexOf(document.activeElement);
			const next=event.key === 'Home' ? 0:event.key === 'End' ? items.length - 1
				:index < 0 ? (event.key === 'ArrowUp' ? items.length - 1:0):(index + (event.key === 'ArrowUp' ? -1:1) + items.length) % items.length;
			items[next]?.focus();
		});
		document.addEventListener('click',event => {if (!root.contains(event.target)) closeMenu();},true);
		root.addEventListener('focusout',event => {if (!root._vmMoving && !root.contains(event.relatedTarget)) closeMenu();});
		function syncPosition() {
			positionFrame=0;
			if (panel.hidden || root.hidden) return;
			const rect=toggle.getBoundingClientRect(),top=Math.round(rect.bottom + 4);
			panel.style.left=`${Math.max(8,Math.min(Math.round(rect.left),innerWidth - panel.offsetWidth - 8))}px`;
			panel.style.top=`${top}px`;
			panel.style.maxHeight=`${Math.max(40,innerHeight - top - 8)}px`;
		}
		const schedulePosition=() => {if (!positionFrame) positionFrame=requestAnimationFrame(syncPosition);};
		root._vmPosition=schedulePosition;
		window.addEventListener('resize',schedulePosition);
		const render=() => {
			root.hidden=SETTINGS.showBottomLeftMenu === false;
			favorite.hidden=!getWatchSettings().enabled;
			const pages=getEnabledMenuPages();
			const showLinks=!root.hidden && SETTINGS.showMenuPagesAsFavicons && pages.length > 0;
			separator.hidden=!showLinks;
			inlinePages.hidden=!showLinks;
			inlinePages.replaceChildren(...(inlinePages.hidden ? []:pages.map(makePageLink)));
			closeMenu(); renderWatchNotificationBar();
		};
		try {
			GM_addValueChangeListener(SETTINGS_STORAGE_KEY,() => {SETTINGS=getSettings(); applyFavoriteSearchPageSettings(); queueMicrotask(render);});
		} catch {}
		render();
	}
	const watchedMenuCommands=[
		{label:'Ulubione',icon:'★',notesOnly:false,id:null},
		{label:TRACKED_ITEMS_LABEL,icon:'✎',notesOnly:true,id:null}
	];
	if ((GARSO_MENU_ONLY_MODE || IS_ESCORT_CLUB || ['escorti.pl','www.escorti.pl'].includes(CURRENT_HOST)) &&
		typeof GM_registerMenuCommand === 'function') {
		GM_registerMenuCommand('Ustawienia',() => openSettingsModal(),{icon:vmMenuIcon('⚙︎')});
		const syncWatchedMenuCommand=() => {
			try {
				const enabled=getWatchSettings().enabled;
				for (const command of watchedMenuCommands) {
					if (enabled && command.id == null) {
						command.id=GM_registerMenuCommand(command.label,() => openWatchedSettingsModal(command.notesOnly),{icon:vmMenuIcon(command.icon)});
					} else if (!enabled && command.id != null && typeof GM_unregisterMenuCommand === 'function') {
						GM_unregisterMenuCommand(command.id); command.id=null;
					}
				}
			} catch (error) {
				log('Nie udało się zsynchronizować menu Ulubionych i Zapisanych anonsów',error);
			}
		};
		syncWatchedMenuCommand();
		try {
			GM_addValueChangeListener( SETTINGS_STORAGE_KEY, syncWatchedMenuCommand );
		} catch {}
		GM_registerMenuCommand( 'Otwórz historię do usunięcia', () => openBrowserHistoryCleanupModal(),
			{icon:vmMenuIcon('⌫')} );
	}
	function maybeOpenSettingsOnFirstRun() {
		if (!IS_ESCORT_CLUB) return;
		try {
			if (GM_getValue(SETTINGS_ONBOARDING_SEEN_KEY,false)) return;
			const cacheIsEmpty=getPersistentCacheStats().entries === 0;
			writeHelperStorageValue(SETTINGS_ONBOARDING_SEEN_KEY,true);
			if (cacheIsEmpty) {
				openSettingsModal({firstRun:true});
			}
		} catch (error) {
			log('Nie udało się sprawdzić pierwszego uruchomienia ustawień',error);
		}
	}
	const ESCORT_REQUEST_QUEUE_PREFIX='vm_escort_request_queue_';
	const ESCORT_REQUEST_START_KEY=ESCORT_REQUEST_QUEUE_PREFIX + 'start';
	const ESCORT_REQUEST_TURN_MS=15000;
	const ESCORT_REQUEST_COOLDOWN_KEY=ESCORT_REQUEST_QUEUE_PREFIX + 'cooldown';
	const ESCORT_REQUEST_TICKET_PREFIX=ESCORT_REQUEST_QUEUE_PREFIX + 'ticket_';
	const ESCORT_REQUEST_FAILURE_PREFIX=ESCORT_REQUEST_QUEUE_PREFIX + 'failure503_';
	let escort503FailureSequence=0;
	const ESCORT_REQUEST_PERMIT_PREFIX=ESCORT_REQUEST_QUEUE_PREFIX + 'permit_';
	const ESCORT_REQUEST_PERMITS_CHANGED_KEY=ESCORT_REQUEST_QUEUE_PREFIX + 'permits_changed';
	const ESCORT_REQUEST_PERMIT_TTL_MS=5 * 60 * 1000;
	const escortRequestLocalPermits=new Map();
	let escortRequestPermitTimer=null;
	const ESCORT_REQUEST_FOREGROUND_KEY=ESCORT_REQUEST_QUEUE_PREFIX + 'foreground';
	const ESCORT_REQUEST_BACKGROUND_WAIT_MS=30000;
	const ESCORT_FAST_START_KEY=ESCORT_REQUEST_QUEUE_PREFIX + 'fast_start';
	const ESCORT_FAST_START_WINDOW_MS=5 * 60 * 1000;
	const ESCORT_REQUEST_INSTANCE_ID=`${Date.now()}-${Math.random().toString(36).slice(2)}`;
	const escortRequestQueues={get:{jobs:[],pumping:false,active:0,lastStartedAt:0},head:{jobs:[],pumping:false,active:0,lastStartedAt:0}};
	const escortRequestQueueDiagnostics=Object.fromEntries(['get','head'].map(lane => [lane,{
		waitReason:'none',nextEligibleAt:0,wakeAt:0
	}]));
	let escortQueueEvidenceState;
	function getEscortQueueEvidenceState() {
		const epoch=String(GM_getValue('vm_garso_diagnostics_epoch','0'));
		if (!escortQueueEvidenceState || escortQueueEvidenceState.epoch !== epoch) {
			const now=Date.now();
			escortQueueEvidenceState={epoch,since:now,events:[],admissions:[],lanes:Object.fromEntries(['get','head'].map(lane => [lane,{
				reason:escortRequestQueueDiagnostics[lane].waitReason,since:now,waitMs:{},leaseConflicts:0,
				expiredReservations:0,expiredStartLeases:0,permitReschedules:0,admissionRetries:0,lateWakes:0,maxWakeLatenessMs:0}]))};
		}
		return escortQueueEvidenceState;
	}
	function recordEscortQueueEvidence(lane,details={},counter='',admission=null) {
		try {
			const evidence=getEscortQueueEvidenceState(),state=evidence.lanes[lane],now=Date.now(),queue=escortRequestQueues[lane];
			if (!state || !queue) return;
			if (admission) {
				evidence.admissions.push(admission);
				evidence.admissions=evidence.admissions.filter(row => row[1] >= now - 1800000).slice(-40);
			}
			const event=(kind,reason,elapsed) => evidence.events.push([now,lane,kind,reason,elapsed,queue.jobs.length,queue.active]);
			if (counter && ['leaseConflicts','expiredReservations','expiredStartLeases','permitReschedules','admissionRetries'].includes(counter)) state[counter]++;
			if (details.waitReason && details.waitReason !== state.reason) {
				const elapsed=Math.max(0,now - state.since);
				if (state.reason !== 'none') {
					state.waitMs[state.reason]=(state.waitMs[state.reason] || 0) + elapsed;
					if (elapsed >= 1000) event('wait',state.reason,elapsed);
				}
				state.reason=details.waitReason; state.since=now;
				if (['reload-required','cooldown','recovery-probe'].includes(state.reason)) event('block',state.reason,0);
			}
			if (details.wakeLatenessMs >= 1000) {
				state.lateWakes++; state.maxWakeLatenessMs=Math.max(state.maxWakeLatenessMs,details.wakeLatenessMs);
				event('timer',state.reason,details.wakeLatenessMs);
			}
			evidence.events=evidence.events.filter(row => row[0] >= now - 1800000).slice(-40);
		} catch {}
	}
	function getEscortQueueEvidence(now=Date.now()) {
		try {
			const value=getEscortQueueEvidenceState(),lanes={};
			for (const [lane,state] of Object.entries(value.lanes)) {
				const {reason,since,waitMs,...counters}=state,totals={...waitMs};
				if (reason !== 'none') totals[reason]=(totals[reason] || 0) + Math.max(0,now - since);
				lanes[lane]={...counters,waitMs:totals};
			}
			return {since:value.since,lanes,events:value.events.filter(row => row[0] >= now - 1800000),
				admissions:value.admissions.filter(row => row[1] >= now - 1800000).map(row => row.slice())};
		} catch {return null;}
	}
	let escortRequestSequence=0;
	function setEscortQueueWait(lane,details,admission=null) {
		recordEscortQueueEvidence(lane,details,'',admission);
		const state=escortRequestQueueDiagnostics[lane];
		if (details.waitReason && details.waitReason !== state.waitReason) state.waitSince=Date.now();
		Object.assign(state,details);
	}
	const escortRequestWakeListeners=new Set();
	const escortRequestStatusListeners=new Set();
	let escortRequestPageClosed=false;
	let escortRequestCooldownMemory={until:0,revision:0,lastProblemAt:0,level:0,reason:'',reloadRequired:false};
	function isEscortQueuedRequestUrl(url) {
		try {return /(^|\.)escort\.club$/i.test(new URL(url,location.href).hostname);} catch {return false;}
	}
	function normalizeEscortQueueTimestamp(value) {
		const number=Number(value);
		return Number.isFinite(number) && number > 0 && number <= 8640000000000000 ? number:0;
	}
	function readEscortRequestStart() {
		const value=GM_getValue(ESCORT_REQUEST_START_KEY,null) || {};
		const legacy=['get','head'].map(name => GM_getValue(ESCORT_REQUEST_START_KEY + '_' + name,null) || {});
		return {owner:String(value.owner || ''),leaseUntil:normalizeEscortQueueTimestamp(value.leaseUntil),
			lastStartedAt:Math.max(normalizeEscortQueueTimestamp(value.lastStartedAt),...legacy.map(item => normalizeEscortQueueTimestamp(item.lastStartedAt))),
			legacyLeaseUntil:Math.max(0,...legacy.map(item => item.owner ? normalizeEscortQueueTimestamp(item.leaseUntil):0))};
	}
	function readEscortRequestCooldown() {
		let value=null;
		try {value=GM_getValue(ESCORT_REQUEST_COOLDOWN_KEY,null);} catch {}
		const revision=normalizeEscortQueueTimestamp(value?.revision) || normalizeEscortQueueTimestamp(value?.lastProblemAt);
		if (value && (revision > escortRequestCooldownMemory.revision ||
			(revision === escortRequestCooldownMemory.revision && normalizeEscortQueueTimestamp(value.until) >= escortRequestCooldownMemory.until))) {
			const legacy403=!('reloadRequired' in value) && value.reason === 'HTTP 403' && value.until > Date.now();
			const reloadRequired=value.reloadRequired === true || legacy403;
			escortRequestCooldownMemory={until:reloadRequired ? 0:normalizeEscortQueueTimestamp(value.until),revision,reloadRequired,
				lastProblemAt:normalizeEscortQueueTimestamp(value.lastProblemAt),ramp503At:normalizeEscortQueueTimestamp(value.ramp503At),
				level:Math.max(0,Math.min(4,Number(value.level) || 0)),reason:String(value.reason || ''),
				recovery503:!reloadRequired && value.recovery503 === true,recoveryStep:Math.max(0,Math.min(3,Number(value.recoveryStep) || 0)),
				recoveryProbeId:String(value.recoveryProbeId || ''),recoverySince:normalizeEscortQueueTimestamp(value.recoverySince),
				failures503:Array.isArray(value.failures503) ? value.failures503.filter(at => Number.isFinite(at) && at > Date.now() - 30000).slice(-3):[]};
			if (legacy403) {try {writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);} catch {}}
		}
		return escortRequestCooldownMemory;
	}
	function getEscort503RateFactor(cooldown,now=Date.now()) {
		if (!cooldown.ramp503At || cooldown.reloadRequired) return 1;
		const elapsed=now - cooldown.ramp503At;
		return cooldown.recovery503 || elapsed < 30000 ? 0.5:elapsed < 150000 ? 0.75:1;
	}
	function readEscortFastStart(now=Date.now()) {
		const value=GM_getValue(ESCORT_FAST_START_KEY,null) || {};
		return {startedAt:normalizeEscortQueueTimestamp(value.startedAt),used:Math.max(0,Math.min(30,Number(value.used) || 0)),
			starts:(Array.isArray(value.starts) ? value.starts:[]).filter(at => Number.isFinite(at) && at > now - ESCORT_FAST_START_WINDOW_MS && at <= now).slice(-91)};
	}
	function getEscortFastStartState(settings,cooldown,now=Date.now()) {
		const data=readEscortFastStart(now),expired=!data.startedAt || now - data.startedAt >= ESCORT_FAST_START_WINDOW_MS;
		const allowed=settings.escortFastStart !== false && getEscort503RateFactor(cooldown,now) === 1 && !cooldown.reloadRequired && !cooldown.recovery503 && cooldown.until <= now &&
			(!cooldown.lastProblemAt || now - cooldown.lastProblemAt >= ESCORT_FAST_START_WINDOW_MS);
		return {...data,remaining:allowed ? expired ? data.starts.length <= 90 ? 30:0:30 - data.used:0};
	}
	function isEscortRequestForeground() {
		return !escortRequestPageClosed && typeof document === 'object' && !document.hidden &&
			document.visibilityState !== 'hidden' && document.hasFocus?.() === true &&
			GM_getValue(ESCORT_REQUEST_FOREGROUND_KEY,'') === ESCORT_REQUEST_INSTANCE_ID;
	}
	function recordEscortFastStartDispatch(job,now) {
		const data=readEscortFastStart(now);
		if (job.permit.fastStart) {
			if (!data.startedAt || now - data.startedAt >= ESCORT_FAST_START_WINDOW_MS) {data.startedAt=now; data.used=0;}
			data.used=Math.min(30,data.used + 1);
		}
		data.starts.push(now); data.starts=data.starts.slice(-91);
		writeHelperStorageValue(ESCORT_FAST_START_KEY,data);
	}
	function getEscortRequestQueueStatus(job=null) {
		const cooldown=readEscortRequestCooldown(),raw=readHelperSettingsSnapshot(),settings=normalizeEscortQueueSettings(raw),queues={};
		const fast=getEscortFastStartState(raw,cooldown),foreground=job ? job.foregroundWork && isEscortRequestForeground():!!GM_getValue(ESCORT_REQUEST_FOREGROUND_KEY,'');
		const multiplier=fast.remaining && foreground ? 2:1,rateFactor=getEscort503RateFactor(cooldown);
		const permits=getEscortActivePermits(),configuredLimitPerMinute=settings.escortLimitPerMinute,limitPerMinute=Math.max(1,Math.floor(configuredLimitPerMinute * multiplier * rateFactor));
		for (const [lane,queue] of Object.entries(escortRequestQueues)) {
			queues[lane]={configuredLimitPerMinute,limitPerMinute,sharedConcurrencyLimit:true,sharedActive:permits.length,configuredMaxConcurrent:settings.escortMaxConcurrent,maxConcurrent:settings.escortMaxConcurrent * multiplier,
				queued:queue.jobs.length,active:queue.active,globalActive:permits.filter(permit => permit.lane === lane).length};
		}
		const maxConcurrent=settings.escortMaxConcurrent * multiplier;
		return {configuredLimitPerMinute,limitPerMinute,rateFactor,ramp503At:cooldown.ramp503At || 0,sharedRateLimit:true,sharedConcurrencyLimit:true,concurrencyScope:'all-tabs',
			fastStartActive:multiplier === 2,fastStartRemaining:fast.remaining,fastStartRecentCount:fast.starts.length,
			level:cooldown.level,lastProblemAt:cooldown.lastProblemAt,configuredMaxConcurrent:maxConcurrent / multiplier,queues,maxConcurrent,
			queued:queues.get.queued + queues.head.queued,active:queues.get.active + queues.head.active,globalActive:permits.length,
			blockedUntil:cooldown.until,reloadRequired:cooldown.reloadRequired,reason:cooldown.reason,
			recovery503:cooldown.recovery503 === true,recoveryStep:cooldown.recoveryStep || 0,
			probePending:!!cooldown.recovery503 && permits.some(permit => permit.id === cooldown.recoveryProbeId)};
	}
	function wakeEscortRequestQueue() {
		updateEscortCoordination();
		for (const wake of [...escortRequestWakeListeners]) wake();
	}
	function getEscortActivityDetailConcurrency() {
		return Math.max(ESCORT_ACTIVE_CHECK_CONCURRENT,getEscortRequestQueueStatus().queues.get.maxConcurrent);
	}
	function subscribeEscortRequestQueueStatus(listener) {
		escortRequestStatusListeners.add(listener);
		return () => escortRequestStatusListeners.delete(listener);
	}
	function notifyEscortRequestQueueStatus() {
		if (!escortRequestStatusListeners.size) return;
		const status=getEscortRequestQueueStatus();
		for (const listener of escortRequestStatusListeners) {try {listener(status);} catch {}}
	}
	function reportEscortRequestQueue() {
		notifyEscortRequestQueueStatus();
		publishLiveDiagnosticsReport();
	}
	function waitForEscortRequestQueue(delayMs,cancelToken,lane=null,notBefore=0) {
		cancelToken?.throwIfCancelled();
		return new Promise((resolve,reject) => {
			let timer=null,unsubscribe=() => {},finished=false;
			const waitMs=Math.max(1,Math.min(1000,Number(delayMs) || 1)),expectedAt=delayMs === null ? 0:Date.now() + waitMs;
			if (lane) setEscortQueueWait(lane,{wakeAt:expectedAt});
			const finish=error => {
				if (finished) return;
				finished=true;
				clearTimeout(timer); unsubscribe(); escortRequestWakeListeners.delete(wake);
				if (lane) setEscortQueueWait(lane,{wakeAt:0,lastWakeAt:Date.now(),wakeLatenessMs:expectedAt ? Math.max(0,Date.now() - expectedAt):0});
				if (error) reject(error); else resolve();
			};
			const wake=() => {
				if (Date.now() >= notBefore) finish();
				else if (!finished && delayMs !== null) {
					clearTimeout(timer); timer=setTimeout(wake,Math.max(1,notBefore - Date.now()));
				}
			};
			escortRequestWakeListeners.add(wake);
			if (delayMs !== null) timer=setTimeout(wake,waitMs);
			unsubscribe=cancelToken?.onCancel(reason => finish(createOperationCancelledError(reason))) || (() => {});
		});
	}
	function getEscortActivePermits(now=Date.now(),prune=false) {
		const result=[];
		for (const key of GM_listValues()) {
			if (!key.startsWith(ESCORT_REQUEST_PERMIT_PREFIX)) continue;
			const value=GM_getValue(key,null);
			if (!value || !['get','head'].includes(value.lane) || !normalizeEscortQueueTimestamp(value.expiresAt) || value.expiresAt <= now) {
				if (prune) deleteHelperStorageValue(key);
				continue;
			}
			result.push({...value,key});
		}
		return result;
	}
	function refreshEscortRequestPermits() {
		clearTimeout(escortRequestPermitTimer); escortRequestPermitTimer=null;
		for (const permit of escortRequestLocalPermits.values()) {
			try {
				const key=ESCORT_REQUEST_PERMIT_PREFIX + permit.id,shared=GM_getValue(key,null);
				if (!shared || shared.owner !== permit.owner || shared.expiresAt <= Date.now()) {
					if (shared?.expiresAt <= Date.now()) recordEscortQueueEvidence(permit.lane,{},'expiredReservations');
					escortRequestLocalPermits.delete(permit.id); continue;
				}
				// Niewysłane rezerwacje też wygasają.
				if (!permit.startedAt) continue;
				permit.expiresAt=Date.now() + ESCORT_REQUEST_PERMIT_TTL_MS;
				writeHelperStorageValue(key,permit);
			} catch {}
		}
		if (escortRequestLocalPermits.size) escortRequestPermitTimer=setTimeout(refreshEscortRequestPermits,15000);
	}
	function releaseEscortSharedPermit(permit) {
		if (!permit) return;
		escortRequestLocalPermits.delete(permit.id);
		try {
			deleteHelperStorageValue(ESCORT_REQUEST_PERMIT_PREFIX + permit.id);
			writeHelperStorageValue(ESCORT_REQUEST_PERMITS_CHANGED_KEY,`${Date.now()}-${permit.id}`);
		} catch {}
		if (!escortRequestLocalPermits.size) {clearTimeout(escortRequestPermitTimer); escortRequestPermitTimer=null;}
	}
	function getEscortQueueJobPriority(job,now=Date.now()) {
		return job.probe ? 3:job.manual || now - job.queuedAt >= ESCORT_REQUEST_BACKGROUND_WAIT_MS ? 2:0;
	}
	function sortEscortQueueJobs(queue) {
		const now=Date.now();
		queue.jobs.sort((a,b) => getEscortQueueJobPriority(b,now) - getEscortQueueJobPriority(a,now) || a.queuedAt - b.queuedAt);
	}
	function readEscort503Failures(now=Date.now(),prune=false) {
		const result=[];
		for (const key of GM_listValues()) {
			if (!key.startsWith(ESCORT_REQUEST_FAILURE_PREFIX)) continue;
			const at=Number(GM_getValue(key,0));
			if (at > now - 30000 && at <= now) result.push(at);
			else if (prune) deleteHelperStorageValue(key);
		}
		return result.sort((a,b) => a - b).slice(-3);
	}
	function clearEscort503Failures() {
		for (const key of GM_listValues()) if (key.startsWith(ESCORT_REQUEST_FAILURE_PREFIX)) deleteHelperStorageValue(key);
	}
	function synchronizeEscort503Failures() {
		const previous=readEscortRequestCooldown(),now=Date.now();
		if (previous.reloadRequired || previous.recovery503) return previous;
		const failures=readEscort503Failures(now,true);
		if (failures.length < 3) return previous;
		escortRequestCooldownMemory={...previous,until:Math.max(previous.until,now + 10000),revision:Math.max(now,previous.revision + 1),
			reason:'HTTP 503',lastProblemAt:failures[2],ramp503At:Math.max(previous.until,now + 10000),recovery503:true,recoveryStep:1,recoverySince:now,recoveryProbeId:'',failures503:failures};
		writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);
		return escortRequestCooldownMemory;
	}
	function getEscortRequestTickets(lane,now=Date.now()) {
		const foreground=GM_getValue(ESCORT_REQUEST_FOREGROUND_KEY,''),raw=readHelperSettingsSnapshot(),settings=normalizeEscortQueueSettings(raw);
		const fast=getEscortFastStartState(raw,readEscortRequestCooldown(),now);
		const permits=getEscortActivePermits(now);
		return GM_listValues().filter(key => key.startsWith(ESCORT_REQUEST_TICKET_PREFIX)).map(key => {
			const value=GM_getValue(key,null) || {},type=value.lane || (key.startsWith(ESCORT_REQUEST_TICKET_PREFIX + 'head_') ? 'head':'get');
			const queuedAt=normalizeEscortQueueTimestamp(value.queuedAt),updatedAt=normalizeEscortQueueTimestamp(value.updatedAt);
			if (updatedAt + 60000 < now) deleteHelperStorageValue(key);
			const active=key === ESCORT_REQUEST_TICKET_PREFIX + type + '_' + foreground;
			const cap=settings.escortMaxConcurrent * (fast.remaining && value.foregroundWork && active ? 2:1);
			return {key,claim:String(value.claim || ''),claimAt:normalizeEscortQueueTimestamp(value.claimAt),servedAt:normalizeEscortQueueTimestamp(value.servedAt),lane:type,probe:value.probe === true,queuedAt,updatedAt,
				available:permits.length < cap,
				priority:value.manual || now - queuedAt >= ESCORT_REQUEST_BACKGROUND_WAIT_MS ? 2:
					key === ESCORT_REQUEST_TICKET_PREFIX + type + '_' + foreground ? 1:0};
		}).filter(ticket => ticket.queuedAt > 0 && ticket.updatedAt + ESCORT_REQUEST_TURN_MS > now && ticket.available)
			.sort((a,b) => Number(b.probe) - Number(a.probe) || b.priority - a.priority || a.servedAt - b.servedAt || a.queuedAt - b.queuedAt || a.key.localeCompare(b.key));
	}
	function updateEscortRequestForeground() {
		try {
			const owner=GM_getValue(ESCORT_REQUEST_FOREGROUND_KEY,'');
			const focused=!escortRequestPageClosed && typeof document === 'object' && !document.hidden &&
				document.visibilityState !== 'hidden' && document.hasFocus?.() === true;
			if (focused && owner !== ESCORT_REQUEST_INSTANCE_ID) writeHelperStorageValue(ESCORT_REQUEST_FOREGROUND_KEY,ESCORT_REQUEST_INSTANCE_ID);
			else if (!focused && owner === ESCORT_REQUEST_INSTANCE_ID) deleteHelperStorageValue(ESCORT_REQUEST_FOREGROUND_KEY);
		} catch {}
	}
	const ESCORT_COORD_PREFIX=ESCORT_REQUEST_QUEUE_PREFIX + 'peer_';
	const ESCORT_COORD_SIGNAL=ESCORT_REQUEST_QUEUE_PREFIX + 'coordination';
	const ESCORT_COORD_JOIN_MS=2500;
	let escortCoordPeer=null,escortCoordUpdating=false;
	function readEscortCoordPeers(now=Date.now()) {
		const peers=[];
		for (const key of GM_listValues()) {
			if (!key.startsWith(ESCORT_COORD_PREFIX)) continue;
			const peer=GM_getValue(key,{});
			if (Math.max(peer.until || 0,peer.lastStartedAt || 0) < now - ESCORT_REQUEST_PERMIT_TTL_MS) deleteHelperStorageValue(key);
			else if (peer.until > now && peer.until <= now + ESCORT_REQUEST_TURN_MS) peers.push(peer);
		}
		return peers;
	}
	// Głosy należą do pojedynczych kart; rezerwacja wymaga zgody wszystkich aktywnych uczestników.
	function updateEscortCoordination(join=false) {
		if (escortCoordUpdating || escortRequestPageClosed) return;
		join=join || escortRequestLocalPermits.size > 0 || Object.values(escortRequestQueues).some(queue => queue.jobs.length &&
			(queue.jobs[0].probe || !readEscortRequestCooldown().reloadRequired));
		escortCoordUpdating=true;
		try {
			const now=Date.now(),key=ESCORT_COORD_PREFIX + ESCORT_REQUEST_INSTANCE_ID;
			if (!escortCoordPeer || escortCoordPeer.until <= now) {
				if (!join) return;
				if (escortCoordPeer) for (const [lane,queue] of Object.entries(escortRequestQueues)) {
					if (queue.jobs.length) recordEscortQueueEvidence(lane,{},'expiredStartLeases');
				}
				escortCoordPeer={until:now + ESCORT_REQUEST_TURN_MS,readyAt:now + ESCORT_COORD_JOIN_MS,vote:'',voteUntil:0,lastStartedAt:escortCoordPeer?.lastStartedAt || 0};
			}
			const peer=escortCoordPeer,before=JSON.stringify(GM_getValue(key,null));
			if (join && peer.until < now + ESCORT_REQUEST_TURN_MS / 2) peer.until=now + ESCORT_REQUEST_TURN_MS;
			const tickets=getEscortRequestTickets(null,now),permits=getEscortActivePermits(now);
			peer.permits=[...escortRequestLocalPermits.values()].map(({id,lane,startedAt,expiresAt}) => ({id,lane,startedAt,expiresAt}));
			const retained=peer.voteUntil > now && (tickets.some(ticket => ticket.claim === peer.vote) ||
				permits.some(permit => permit.turn === peer.vote && !permit.startedAt));
			if (!retained) {
				peer.vote=now >= peer.readyAt ? tickets.filter(ticket => ticket.claim).sort((a,b) => a.claimAt - b.claimAt || a.claim.localeCompare(b.claim))[0]?.claim || '':'';
				peer.voteUntil=peer.vote ? Math.min(peer.until,now + ESCORT_REQUEST_TURN_MS):0;
			}
			if (before !== JSON.stringify(peer)) {
				writeHelperStorageValue(key,peer);
				writeHelperStorageValue(ESCORT_COORD_SIGNAL,`${now}-${Math.random()}`);
			}
		} catch {} finally {escortCoordUpdating=false;}
	}
	function getEscortCoordinationTurn(claim,now=Date.now()) {
		updateEscortCoordination(true);
		const peers=readEscortCoordPeers(now);
		return {readyAt:Math.max(now,...peers.map(peer => peer.readyAt || 0)),
			until:peers.length && peers.every(peer => peer.vote === claim && peer.voteUntil > now)
				? Math.min(...peers.map(peer => Math.min(peer.until,peer.voteUntil))):0,
			lastStartedAt:Math.max(0,...peers.map(peer => peer.lastStartedAt || 0)),
			permits:[...new Map([...getEscortActivePermits(now,true),...peers.flatMap(peer => peer.permits || [])].filter(permit => permit.expiresAt > now).map(permit => [permit.id,permit])).values()]};
	}
	async function acquireEscortSharedRequestStart(cancelToken,job,lane) {
		const queue=escortRequestQueues[lane],ticketKey=ESCORT_REQUEST_TICKET_PREFIX + lane + '_' + ESCORT_REQUEST_INSTANCE_ID;
		let claim='',claimAt=0,attempt=0;
		const publish=() => {
			const previous=GM_getValue(ticketKey,null);
			writeHelperStorageValue(ticketKey,{claim,claimAt,queuedAt:job.queuedAt,servedAt:Math.max(escortRequestQueues.get.lastStartedAt,escortRequestQueues.head.lastStartedAt),updatedAt:Date.now(),probe:job.probe,manual:job.manual,foregroundWork:job.foregroundWork && isEscortRequestForeground(),lane});
			if (!previous || previous.claim !== claim) writeHelperStorageValue(ESCORT_COORD_SIGNAL,`${Date.now()}-${Math.random()}`);
		};
		try {
			while (true) {
				cancelToken.throwIfCancelled();
				if (escortRequestPageClosed) throw createOperationCancelledError('Zamknięto stronę');
				sortEscortQueueJobs(queue);
				if (queue.jobs[0] !== job) return false;
				let now=Date.now(),cooldown=synchronizeEscort503Failures();
				if (!job.probe && (cooldown.reloadRequired || cooldown.until > now)) {
					deleteHelperStorageValue(ticketKey); updateEscortCoordination();
					claim=''; claimAt=0;
					setEscortQueueWait(lane,{waitReason:cooldown.reloadRequired ? 'reload-required':'cooldown',nextEligibleAt:cooldown.until});
					await waitForEscortRequestQueue(cooldown.reloadRequired ? null:cooldown.until - now,cancelToken,lane); continue;
				}
				publish();
				const tickets=getEscortRequestTickets(null,now),claimant=tickets.filter(ticket => ticket.claim).sort((a,b) => a.claimAt - b.claimAt || a.claim.localeCompare(b.claim))[0];
				if (claim && claimant?.claim !== claim) {
					recordEscortQueueEvidence(lane,{},'leaseConflicts');
					recordEscortQueueEvidence(lane,{},'admissionRetries');
					claim=''; claimAt=0; publish();
				}
				else if (!claim && !claimant && tickets[0]?.key === ticketKey) {claim=ticketKey + '_' + job.id + '_' + ++attempt; claimAt=now; publish();}
				const turn=getEscortCoordinationTurn(claim,now),state=readEscortRequestStart(),status=getEscortRequestQueueStatus(job),permits=turn.permits;
				const lastStartedAt=Math.max(state.lastStartedAt,turn.lastStartedAt),gap=Math.ceil(60000 / status.limitPerMinute);
				const readyAt=Math.max(job.probe ? 0:cooldown.until,lastStartedAt ? lastStartedAt + gap:0,state.legacyLeaseUntil,state.owner ? state.leaseUntil:0,turn.readyAt);
				const saturated=permits.length >= status.maxConcurrent,dispatchWaiting=permits.some(permit => !permit.startedAt);
				const probeWaiting=!job.probe && cooldown.recovery503 && permits.length > 0;
				if (readyAt > now || saturated || probeWaiting || dispatchWaiting || !claim || !turn.until) {
					const waitReason=dispatchWaiting ? 'pending-dispatch':saturated ? 'global-concurrency-limit':probeWaiting ? 'recovery-probe':
						state.owner && readyAt === state.leaseUntil ? 'start-lease':readyAt === state.legacyLeaseUntil ? 'legacy-lease':
						turn.readyAt > now && readyAt === turn.readyAt ? 'verifying-start':readyAt > now ? 'rate-limit':'other-tab-ticket';
					setEscortQueueWait(lane,{waitReason,nextEligibleAt:readyAt > now ? readyAt:0});
					await waitForEscortRequestQueue(readyAt > now ? readyAt - now:1000,cancelToken,lane);
					if (readyAt > now && Date.now() >= readyAt) writeHelperStorageValue(ESCORT_COORD_SIGNAL,'wake:' + Date.now());
					continue;
				}
				cancelToken.throwIfCancelled();
				job.permit={id:ESCORT_REQUEST_INSTANCE_ID + '_' + job.id,lane,owner:ESCORT_REQUEST_INSTANCE_ID,
					admittedAt:now,startedAt:0,expiresAt:turn.until,turn:claim,turnUntil:turn.until,
					fastStart:status.fastStartActive,limitPerMinute:status.limitPerMinute,recovery503:!job.probe && cooldown.recovery503 === true};
				writeHelperStorageValue(ESCORT_REQUEST_PERMIT_PREFIX + job.permit.id,job.permit);
				escortRequestLocalPermits.set(job.permit.id,job.permit); refreshEscortRequestPermits();
				if (job.permit.recovery503) {
					escortRequestCooldownMemory={...cooldown,recoveryProbeId:job.permit.id,revision:Math.max(now,cooldown.revision + 1)};
					writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);
				}
				job.admission=[job.queuedAt,now,0,lane,permits.length,lastStartedAt,status.limitPerMinute,status.maxConcurrent,Number(status.fastStartActive)];
				queue.lastStartedAt=now;
				writeHelperStorageValue(ESCORT_REQUEST_START_KEY,{owner:'',leaseUntil:0,lastStartedAt:now});
				return true;
			}
		} finally {deleteHelperStorageValue(ticketKey); updateEscortCoordination(); writeHelperStorageValue(ESCORT_COORD_SIGNAL,`${Date.now()}-${Math.random()}`);}
	}
	async function pumpEscortRequestQueue(lane) {
		const queue=escortRequestQueues[lane];
		if (queue.pumping) return;
		queue.pumping=true;
		try {
			while (queue.jobs.length) {
				sortEscortQueueJobs(queue);
				const job=queue.jobs[0];
				try {
					if (!job.probe && readEscortRequestCooldown().reloadRequired) {
						setEscortQueueWait(lane,{waitReason:'reload-required',nextEligibleAt:0});
						await waitForEscortRequestQueue(null,job.token,lane); continue;
					}
					if (queue.active >= getEscortRequestQueueStatus(job).queues[lane].maxConcurrent) {
						setEscortQueueWait(lane,{waitReason:'concurrency-limit',nextEligibleAt:0});
						await waitForEscortRequestQueue(1000,job.token,lane); continue;
					}
					if (!await acquireEscortSharedRequestStart(job.token,job,lane)) continue;
					job.token.throwIfCancelled();
					if (queue.jobs[0] !== job) {releaseEscortSharedPermit(job.permit); continue;}
					queue.jobs.shift(); job.off(); job.settled=true; queue.active++;
					setEscortQueueWait(lane,{waitReason:'none',nextEligibleAt:0,wakeAt:0},job.admission);
					reportEscortRequestQueue();
					let released=false,started=false;
					const release=() => {
						if (released) return;
						released=true; releaseEscortSharedPermit(job.permit); queue.active--;
						try {reportEscortRequestQueue();} catch {}
						wakeEscortRequestQueue();
					};
					let observed503=false;
					release.observeResponse=(response,complete=true) => {
						if (Number(response?.status) === 503) {if (observed503) return; observed503=true;}
						return observeEscortRequestResponse(job.url,response,{permitId:job.permit.id,startedAt:job.permit.startedAt,complete});
					};
					release.beforeDispatch=() => {
						const state=readEscortRequestCooldown();
						if (job.probe && state.revision !== job.probeRevision) throw createOperationCancelledError('Zmienił się stan blokady Escort.club');
						if (!job.probe && state.reloadRequired) throw createEscortRequestBlockedError(state,403);
						if (!job.probe && state.until > Date.now()) throw createEscortRequestBlockedError(state,state.recovery503 ? 503:429);
						const shared=GM_getValue(ESCORT_REQUEST_PERMIT_PREFIX + job.permit.id,null);
						const limits=getEscortRequestQueueStatus(job);
						if (job.permit.turnUntil <= Date.now() || released || !escortRequestLocalPermits.has(job.permit.id) || !shared || shared.owner !== job.permit.owner || shared.expiresAt <= Date.now() || job.permit.limitPerMinute > limits.limitPerMinute || (job.permit.fastStart && !limits.fastStartActive)) {
							recordEscortQueueEvidence(lane,{},'permitReschedules');
							if (shared?.expiresAt <= Date.now() && escortRequestLocalPermits.has(job.permit.id)) recordEscortQueueEvidence(lane,{},'expiredReservations');
							escortRequestLocalPermits.delete(job.permit.id);
							const error=new Error('Rezerwacja lub limity zapytania zmieniły się. Ponowienie przez wspólną kolejkę.');
							error.code='ESCORT_QUEUE_RESCHEDULE'; error.status=0; error.retryable=true;
							throw error;
						}
					};
					release.markStarted=() => {
						if (started) return;
						if (released || job.permit.turnUntil <= Date.now()) release.beforeDispatch();
						started=true;
						const now=Date.now();
						try {recordEscortFastStartDispatch(job,now);} catch (error) {if (job.permit.fastStart) throw error;}
						if (escortQueueEvidenceState?.admissions.includes(job.admission)) job.admission[2]=now;
						escortCoordPeer.lastStartedAt=now;
						job.permit.startedAt=now; refreshEscortRequestPermits();
						queue.lastStartedAt=Math.max(queue.lastStartedAt,now);
						try {
							const state=readEscortRequestStart();
							writeHelperStorageValue(ESCORT_REQUEST_START_KEY,{...state,lastStartedAt:Math.max(state.lastStartedAt,now)});
						} catch {}
						wakeEscortRequestQueue();
					};
					job.resolve(release);
				} catch (error) {
					releaseEscortSharedPermit(job.permit);
					const index=queue.jobs.indexOf(job);
					if (index >= 0) queue.jobs.splice(index,1);
					job.off(); job.settled=true; job.reject(error);
					setEscortQueueWait(lane,{wakeAt:0});
				}
			}
		} finally {queue.pumping=false; setEscortQueueWait(lane,{wakeAt:0,waitReason:queue.jobs.length ? escortRequestQueueDiagnostics[lane].waitReason:'none'});}
	}
	async function acquireEscortRequestSlot(url,cancelToken=null,probe=false,method='GET',requestContext=null) {
		cancelToken?.throwIfCancelled();
		if (!isEscortQueuedRequestUrl(url)) return () => {};
		if (escortRequestPageClosed) throw createOperationCancelledError('Zamknięto stronę');
		const lane=String(method).toUpperCase() === 'HEAD' ? 'head':'get',queue=escortRequestQueues[lane];
		return new Promise((resolve,reject) => {
			const job={resolve,reject,token:createOperationCancelToken('Zapytanie Escort.club'),off:() => {},settled:false,probe:probe === true,
				probeRevision:probe ? readEscortRequestCooldown().revision:0,id:'q' + ++escortRequestSequence,queuedAt:Date.now()};
			job.url=url; job.manual=requestContext?.manual === true || /manual|^tile\.related$/.test(requestContext?.source || '');
			job.foregroundWork=!job.probe && (job.manual || !/^(?:watch[.-]|cache-bridge$)/.test(requestContext?.source || ''));
			if (job.probe) queue.jobs.unshift(job); else queue.jobs.push(job);
			sortEscortQueueJobs(queue);
			reportEscortRequestQueue();
			job.off=cancelToken?.onCancel(reason => {
				if (job.settled) return;
				job.settled=true; job.token.cancel(reason);
				const index=queue.jobs.indexOf(job);
				if (index >= 0) queue.jobs.splice(index,1);
				job.off(); reject(createOperationCancelledError(reason)); reportEscortRequestQueue(); wakeEscortRequestQueue();
			}) || (() => {});
			wakeEscortRequestQueue(); pumpEscortRequestQueue(lane);
		});
	}
	function isEscortRequestBlockedError(error) {
		return error?.code === 'ESCORT_REQUEST_BLOCKED';
	}
	function isEscortAntifloodResponse(html) {
		const source=String(html || '').replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi,'');
		const marker=/\b(?:anti|anty)[\s-]*flood(?:ow\w*)?\b|\bflood control\b|\btoo many requests\b|zbyt (?:wiele|du[zż]o) zapyta[nń]/i;
		if (/^\s*\{/.test(source)) {
			try {
				const value=JSON.parse(source);
				return !value.phone && !value.number && [value.error,value.message].some(text => typeof text === 'string' && marker.test(text));
			} catch {return false;}
		}
		const notices=source.match(/<(?:title|h[12])\b[^>]*>[\s\S]*?<\/(?:title|h[12])>|<(?:p|div)\b[^>]*class\s*=\s*["'][^"']*\b(?:error|alert|warning)\b[^"']*["'][^>]*>[\s\S]*?<\/(?:p|div)>/gi) || [];
		if (notices.some(value => marker.test(value.replace(/<[^>]*>/g,' ')))) return true;
		const text=source.replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim();
		return text.length < 1200 && /^(?:system |kontrola |ochrona |error[: ]*|błąd[: ]*)?(?:(?:anti|anty)[\s-]*flood(?:ow\w*)?|flood control|too many requests|zbyt (?:wiele|du[zż]o) zapyta[nń])\b/i.test(text);
	}
	function createEscortRequestBlockedError(state,status) {
		const message=state.reloadRequired ? 'Wstrzymano zapytania do odświeżenia strony.':state.until > Date.now()
			? `Wstrzymano zapytania do ${new Date(state.until).toLocaleTimeString('pl-PL')}.`:'Ponowienie przez wspólną kolejkę.';
		const error=new Error(`Escort.club: ${state.reason}. ${message}`);
		error.code='ESCORT_REQUEST_BLOCKED'; error.status=status; error.retryable=status === 503 && !state.reloadRequired && state.reason === 'HTTP 503'; error.blockedUntil=state.until; error.reloadRequired=state.reloadRequired;
		return error;
	}
	function refineEscortRequestBlockedError(url,response,error) {
		if (error?.reloadRequired || !isEscortQueuedRequestUrl(url) || !isEscortRequestBlockedError(error)) return error;
		const html=response?.responseText || '';
		if (error.status === 503 && (isEscortAntifloodResponse(html) || isEscortAccessChallengeResponse(html,response?.headers || response?.responseHeaders))) {
			try {observeEscortRequestResponse(url,{...response,status:503});} catch (refined) {return refined;}
		}
		if (!isEscortAntifloodResponse(html)) return error;
		const state=readEscortRequestCooldown();
		if (state.until === error.blockedUntil && state.reason !== 'Kontrola antyfloodowa Escort.club') {
			escortRequestCooldownMemory={...state,reason:'Kontrola antyfloodowa Escort.club'};
			try {writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);} catch {}
			reportEscortRequestQueue();
		}
		error.retryable=false;
		error.message=`Escort.club: kontrola antyfloodowa. Wstrzymano zapytania do ${new Date(error.blockedUntil).toLocaleTimeString('pl-PL')}.`;
		return error;
	}
	function isEscortAccessChallengeResponse(html,headers) {
		return getResponseHeader(headers,'cf-mitigated').toLowerCase() === 'challenge' ||
			(/<title[^>]*>\s*(?:just a moment|attention required|checking your browser)/i.test(html) && /cf-chl-|challenge-platform|cloudflare/i.test(html)) ||
			(/<title[^>]*>\s*(?:access denied|forbidden|request blocked|site unavailable)/i.test(html) &&
				/<h[12][^>]*>\s*(?:access denied|forbidden|request blocked)|your (?:ip|access).{0,50}(?:blocked|banned)|cloudflare/i.test(html));
	}
	function observeEscortRequestResponse(url,response={},context={}) {
		if (!isEscortQueuedRequestUrl(url)) return;
		const status=Number(response.status) || 0,headers=response.headers || response.responseHeaders;
		const retryAfter=getResponseHeader(headers,'retry-after'),now=Date.now();
		let retryUntil=0;
		if (/^\d+(?:\.\d+)?$/.test(retryAfter)) retryUntil=now + Number(retryAfter) * 1000;
		else if (retryAfter && Number.isFinite(Date.parse(retryAfter))) retryUntil=Date.parse(retryAfter);
		retryUntil=normalizeEscortQueueTimestamp(retryUntil);
		const html=typeof response.responseText === 'string' ? response.responseText.slice(0,120000):'';
		const antiflood=isEscortAntifloodResponse(html);
		const challenge=isEscortAccessChallengeResponse(html,headers);
		const previous=readEscortRequestCooldown();
		if (!previous.reloadRequired && status === 503 && !challenge && !antiflood) {
			writeHelperStorageValue(ESCORT_REQUEST_FAILURE_PREFIX + (context.permitId || ESCORT_REQUEST_INSTANCE_ID + '_' + ++escort503FailureSequence),now);
			const failures=readEscort503Failures(now,true);
			const probeFailed=previous.recovery503 && context.permitId === previous.recoveryProbeId;
			const recovery503=previous.recovery503 || failures.length >= 3 || retryUntil > now;
			const recoveryStep=recovery503 ? Math.min(3,Math.max(1,(previous.recoveryStep || 0) + (probeFailed ? 1:0))):0;
			const until=recovery503 ? Math.max(previous.until,retryUntil,
				!previous.recovery503 || probeFailed ? now + recoveryStep * 10000:0):previous.until;
			escortRequestCooldownMemory={...previous,until,revision:Math.max(now,previous.revision + 1),reason:'HTTP 503',
				lastProblemAt:now,ramp503At:Math.max(now,until),recovery503,recoveryStep,recoveryProbeId:probeFailed ? '':previous.recoveryProbeId || '',
				recoverySince:previous.recovery503 ? previous.recoverySince:recovery503 ? now:0,failures503:failures};
			writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);
			reportEscortRequestQueue(); wakeEscortRequestQueue();
			throw createEscortRequestBlockedError(escortRequestCooldownMemory,503);
		}
		if (status !== 403 && status !== 429 && !challenge && !antiflood) {
			if (!previous.reloadRequired && previous.recovery503 && context.complete !== false && status >= 200 && status < 400 &&
				context.permitId && context.permitId === previous.recoveryProbeId && context.startedAt >= previous.recoverySince) {
				escortRequestCooldownMemory={...previous,until:0,reason:'',level:0,recovery503:false,recoveryStep:0,
					recoveryProbeId:'',recoverySince:0,failures503:[],ramp503At:now,revision:Math.max(now,previous.revision + 1)};
				clearEscort503Failures();
				writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);
				reportEscortRequestQueue(); wakeEscortRequestQueue();
			}
			return;
		}
		const reloadRequired=status === 403 || previous.reloadRequired,samePause=previous.reloadRequired || (previous.until > now && !previous.recovery503);
		const level=reloadRequired ? 1:samePause ? Math.max(1,previous.level):previous.lastProblemAt && now <= previous.until + 15 * 60 * 1000 ? Math.min(4,previous.level + 1):1;
		const reason=reloadRequired ? 'HTTP 403':antiflood ? 'Kontrola antyfloodowa Escort.club':challenge ? 'Kontrola dostępu Escort.club':`HTTP ${status}`;
		const until=reloadRequired ? 0:Math.max(previous.until,retryUntil,samePause ? 0:now + Math.min(30 * 60 * 1000,5 * 60 * 1000 * 2 ** (level - 1)));
		escortRequestCooldownMemory={until,reloadRequired,revision:Math.max(now,previous.revision + 1),lastProblemAt:samePause ? previous.lastProblemAt || now:now,
			level,reason,ramp503At:reloadRequired ? 0:status === 503 || getEscort503RateFactor(previous,now) < 1 ? until:0,recovery503:false,recoveryStep:0,recoveryProbeId:'',recoverySince:0,failures503:[]};
		try {clearEscort503Failures(); writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);} catch {}
		reportEscortRequestQueue(); wakeEscortRequestQueue();
		throw createEscortRequestBlockedError(escortRequestCooldownMemory,status);
	}
	try {
		for (const key of [ESCORT_REQUEST_START_KEY,ESCORT_REQUEST_START_KEY + '_get',ESCORT_REQUEST_START_KEY + '_head',ESCORT_REQUEST_FOREGROUND_KEY,ESCORT_REQUEST_PERMITS_CHANGED_KEY,ESCORT_COORD_SIGNAL]) {
			GM_addValueChangeListener(key,wakeEscortRequestQueue);
		}
		GM_addValueChangeListener(ESCORT_REQUEST_COOLDOWN_KEY,(_key,previous,next) => {
			reportEscortRequestQueue(); wakeEscortRequestQueue();
			if (next?.reloadRequired && !previous?.reloadRequired) publishLiveDiagnosticsReport(true);
		});
	} catch {}
	window.addEventListener('pagehide',() => {
		escortRequestPageClosed=true;
		try {if (escortCoordPeer) {escortCoordPeer.until=0; writeHelperStorageValue(ESCORT_COORD_PREFIX + ESCORT_REQUEST_INSTANCE_ID,escortCoordPeer);}} catch {}
		updateEscortRequestForeground();
		clearTimeout(escortRequestPermitTimer); escortRequestPermitTimer=null;
		for (const [lane,queue] of Object.entries(escortRequestQueues)) {
			for (const job of [...queue.jobs]) job.token.cancel('Zamknięto stronę');
			try {deleteHelperStorageValue(ESCORT_REQUEST_TICKET_PREFIX + lane + '_' + ESCORT_REQUEST_INSTANCE_ID);} catch {}
		}
		wakeEscortRequestQueue();
	});
	function resumeEscortRequestQueue() {
		if (escortRequestPageClosed) return;
		updateEscortRequestForeground();
		refreshEscortRequestPermits();
		wakeEscortRequestQueue();
		for (const lane of ['get','head']) if (escortRequestQueues[lane].jobs.length) void pumpEscortRequestQueue(lane);
		notifyEscortRequestQueueStatus();
	}
	window.addEventListener('pageshow',() => {escortRequestPageClosed=false; resumeEscortRequestQueue();});
	window.addEventListener('focus',resumeEscortRequestQueue);
	window.addEventListener('blur',updateEscortRequestForeground);
	if (typeof document === 'object') {
		document.addEventListener('visibilitychange',() => {if (document.hidden) updateEscortRequestForeground(); else resumeEscortRequestQueue();});
		document.addEventListener('resume',resumeEscortRequestQueue);
	}
	updateEscortRequestForeground();
	const ESCORT_COOLDOWN_PROBE_KEY=ESCORT_REQUEST_QUEUE_PREFIX + 'probe';
	const ESCORT_COOLDOWN_PROBE_TIMEOUT_MS=15000;
	let escortCooldownProbeStarted=false;
	const escortCooldownStartupState={...readEscortRequestCooldown()};
	function canProbeEscortCooldownOnStartup() {
		try {
			if (window.top !== window.self || escortRequestPageClosed) return false;
			const page=new URL(location.href),hash=new URLSearchParams(page.hash.slice(1));
			if (!isEscortClubHost(page.hostname) && !/^(?:www\.)?(?:escorti\.pl|garsoniera\.com\.pl)$/.test(page.hostname)) return false;
			if (['vm_mode',PERSISTENT_CACHE_BRIDGE_PARAM,FAVORITE_SEARCH_EDIT_PARAM]
				.some(key => page.searchParams.has(key)) || hash.has(GARSO_OPEN_BRIDGE_PARAM) || hash.has('vm_garso_topic_worker')) return false;
			if (hash.has('vm_cached_template') || hash.get('vm_cached_archive_view') === '1') return false;
			if (getEscortCachedArchiveMarkerFromHash() && !getEscortAdIdFromPath(page.pathname)) return false;
			if (isEscortClubHost(page.hostname) && page.pathname === '/' && readRecentEscortCachedAdNavigation()) return false;
			return !sessionStorage.getItem(FAVORITE_SEARCH_EDIT_KEY);
		} catch {return false;}
	}
	function isConfirmedEscortCooldownProbeResponse(response) {
		if (Number(response?.status) !== 200 || typeof response.responseText !== 'string') return false;
		try {
			const finalUrl=new URL(response.finalUrl);
			if (finalUrl.protocol !== 'https:' || !isEscortClubHost(finalUrl.hostname)) return false;
			const adId=getEscortAdIdFromPath(finalUrl.pathname);
			if (!adId && finalUrl.pathname !== '/' && !isEscortSearchPath(finalUrl.pathname)) return false;
			const html=response.responseText,headers=response.responseHeaders || response.headers;
			const type=getResponseHeader(headers,'content-type');
			if (type && !/^(?:text\/html|application\/xhtml\+xml)\b/i.test(type)) return false;
			if (/<title\b[^>]*>[^<]*\b(?:captcha|log[\s-]*in|sign[\s-]*in|logowanie|zaloguj)\b/i.test(html) ||
				getResponseHeader(headers,'cf-mitigated').toLowerCase() === 'challenge') return false;
			if (isEscortAntifloodResponse(html) || classifyResponseProtection(200,html,headers).blockSignals.some(signal => signal !== 'captcha')) return false;
			const doc=new DOMParser().parseFromString(html,'text/html');
			if (adId) return !!doc.querySelector('.content-info-col .content-name h1, .content-name.-desc-name h1') &&
				!!doc.querySelector('.content-desc, .content-contact, .content-prices, .galleryContainer');
			return !!findEscortSearchLocationControls(doc) || [...doc.querySelectorAll('.item-col.col > a[href]')].some(link => {
				const url=new URL(link.getAttribute('href'),finalUrl);
				return isEscortClubHost(url.hostname) && /^\/(?:anons|personal-ads)\/\d+\.html\/?$/.test(url.pathname);
			});
		} catch {return false;}
	}
	function clearEscortCooldownAfterProbe(target) {
		const current=readEscortRequestCooldown();
		if (current.revision !== target.revision || current.until !== target.until || current.lastProblemAt !== target.lastProblemAt) return false;
		const next={...current,until:0,reloadRequired:false,recovery503:false,recoveryStep:0,recoveryProbeId:'',recoverySince:0,failures503:[],ramp503At:current.reloadRequired ? 0:current.ramp503At ? Date.now():0,
			revision:Math.max(Date.now(),current.revision + 1)};
		try {writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,next);} catch {return false;}
		clearEscort503Failures();
		escortRequestCooldownMemory=next;
		reportEscortRequestQueue(); wakeEscortRequestQueue();
		return true;
	}
	async function probeEscortCooldownOnStartup() {
		if (escortCooldownProbeStarted || !canProbeEscortCooldownOnStartup()) return;
		escortCooldownProbeStarted=true;
		const navigation=typeof performance === 'object' ? performance.getEntriesByType?.('navigation')?.[0]:null;
		if (isEscortClubHost(location.hostname) && navigation?.responseStatus === 403) {
			try {recordDiagnosticResponse(location.href,403); observeEscortRequestResponse(location.href,{status:403});} catch {}
			return;
		}
		const target={...readEscortRequestCooldown()};
		if (target.recovery503 && !target.reloadRequired) return;
		if (target.reloadRequired) {
			if (!escortCooldownStartupState.reloadRequired ||
				target.revision !== escortCooldownStartupState.revision) return;
		} else if (target.until <= Date.now()) return;
		// Świeża odpowiedź dokumentu może potwierdzić dostęp bez dodatkowego GET-a.
		if (isEscortClubHost(location.hostname) && ['reload','navigate'].includes(navigation?.type) &&
			navigation.responseStatus === 200 && navigation.transferSize > 0 && !navigation.workerStart &&
			Number(performance.timeOrigin) + Number(navigation.requestStart) >= target.revision &&
			isConfirmedEscortCooldownProbeResponse({status:200,finalUrl:location.href,responseText:document.documentElement?.outerHTML})) {
			clearEscortCooldownAfterProbe(target);
			return;
		}
		if (target.reloadRequired && navigation?.type !== 'reload') return;
		const token=createOperationCancelToken('Sprawdzenie dostępu do Escort.club'),owner=ESCORT_REQUEST_INSTANCE_ID;
		const timeoutMs=ESCORT_COOLDOWN_PROBE_TIMEOUT_MS + Math.ceil(60000 / getEscortRequestQueueStatus().queues.get.limitPerMinute);
		let lease=null,timer=null,handle=null,release=() => {};
		const pagehide=() => token.cancel('Zamknięto stronę');
		try {
			const now=Date.now(),previous=GM_getValue(ESCORT_COOLDOWN_PROBE_KEY,null);
			if (normalizeEscortQueueTimestamp(previous?.until) > now) return;
			lease={owner,until:now + timeoutMs + 5000};
			writeHelperStorageValue(ESCORT_COOLDOWN_PROBE_KEY,lease);
			await waitWithCancellation(40,token);
			if (GM_getValue(ESCORT_COOLDOWN_PROBE_KEY,null)?.owner !== owner || lease.until <= Date.now()) return;
			window.addEventListener('pagehide',pagehide,{once:true});
			timer=setTimeout(() => token.cancel('Upłynął czas sprawdzania dostępu'),timeoutMs);
			const origin=isEscortClubHost(location.hostname) ? location.origin:'https://pl.escort.club';
			const url=`${origin}/?vm_access_probe=${Date.now()}-${owner}`;
			release=await acquireEscortRequestSlot(url,token,true,'GET');
			token.throwIfCancelled();
			const current=readEscortRequestCooldown();
			if ((!current.reloadRequired && current.until <= Date.now()) || current.revision !== target.revision || current.until !== target.until) {
				return;
			}
			await new Promise(resolve => {
				let finished=false,trace=null,off=() => {};
				const finish=(response,outcome,error=null) => {
					if (finished) return;
					finished=true; off();
					trace?.(response?.status || error?.status,outcome,response || error);
					try {
						if (outcome === 'response') recordDiagnosticResponse(url,response?.status);
						else recordDiagnosticRequestFailure(url,error || response);
						observeEscortRequestResponse(url,response || {});
						if (outcome === 'response' && isConfirmedEscortCooldownProbeResponse(response)) clearEscortCooldownAfterProbe(target);
					} catch {}
					resolve();
				};
				off=token.onCancel(reason => {
					finish(null,'cancelled',createOperationCancelledError(reason));
					try {handle?.abort?.();} catch {}
				});
				try {
					release.beforeDispatch?.();
					trace=recordDiagnosticRequest(url,'GET',{source:'access.probe'});
					handle=GM_xmlhttpRequest({url,method:'GET',timeout:ESCORT_COOLDOWN_PROBE_TIMEOUT_MS,nocache:true,
						headers:{Accept:'text/html,application/xhtml+xml','Cache-Control':'no-cache','Pragma':'no-cache'},
						onload:response => finish(response,'response'),onerror:response => finish(response,'error',response),
						ontimeout:response => finish(response,'timeout',response),
						onabort:() => finish(null,'cancelled',createOperationCancelledError('Przerwano sprawdzanie dostępu'))});
					release.markStarted?.();
				} catch (error) {finish(null,'error',error);}
			});
		} catch {} finally {
			clearTimeout(timer); window.removeEventListener('pagehide',pagehide); release();
			try {
				if (lease && GM_getValue(ESCORT_COOLDOWN_PROBE_KEY,null)?.owner === owner) {
					writeHelperStorageValue(ESCORT_COOLDOWN_PROBE_KEY,{owner:'',until:target.reloadRequired ? 0:Date.now() + 2000});
				}
			} catch {}
		}
	}
	function migrateEscortAdaptiveBackoff() {
		try {
			const state=GM_getValue(ADAPTIVE_BACKOFF_STORAGE_KEY,null);
			if (!state) return;
			const previous=state.services?.escortClub,until=normalizeEscortQueueTimestamp(previous?.until);
			if (until > Date.now()) {
				const cooldown=readEscortRequestCooldown(),changed=!cooldown.reloadRequired && until > cooldown.until;
				if (changed) escortRequestCooldownMemory={...cooldown,until,
					revision:Math.max(Date.now(),cooldown.revision + 1),level:Math.max(1,cooldown.level),
					lastProblemAt:Math.max(cooldown.lastProblemAt,normalizeEscortQueueTimestamp(previous.lastProblemAt)),
					reason:String(previous.reason || 'HTTP 429')};
				writeHelperStorageValue(ESCORT_REQUEST_COOLDOWN_KEY,escortRequestCooldownMemory);
				if (changed) {reportEscortRequestQueue(); wakeEscortRequestQueue();}
			}
			deleteHelperStorageValue(ADAPTIVE_BACKOFF_STORAGE_KEY);
		} catch {}
	}
	function getAdaptiveBackoffDiagnostics() {
		const now=Date.now(),queue=getEscortRequestQueueStatus(),search=readGarsoGlobalSearchSlot();
		const until=search.serverRetryAt,waitMs=Math.max(0,until - now),topicGapMs=garsoTopicGapMs;
		return {
			escortClub:{active:queue.reloadRequired || queue.blockedUntil > now || queue.recovery503 === true || queue.rateFactor < 1,adaptiveEnabled:true,reloadRequired:queue.reloadRequired,
				level:queue.level,rateFactor:queue.rateFactor,ramp503At:queue.ramp503At,until:queue.blockedUntil,waitMs:Math.max(0,queue.blockedUntil - now),
				reason:queue.reason,lastProblemAt:queue.lastProblemAt,
				sharedRateLimit:queue.sharedRateLimit,concurrencyScope:queue.concurrencyScope,
				recovery503:queue.recovery503,recoveryStep:queue.recoveryStep,probePending:queue.probePending,
				limitPerMinute:queue.limitPerMinute,configuredLimitPerMinute:queue.configuredLimitPerMinute,queues:queue.queues},
			escorti:{active:false,adaptiveEnabled:false},
			garso:{active:waitMs > 0 || topicGapMs > GARSO_TOPIC_MIN_GAP_MS,adaptiveEnabled:true,until,waitMs,
				reason:waitMs > 0 ? 'Kontrola antyfloodowa Garso':topicGapMs > GARSO_TOPIC_MIN_GAP_MS ? 'Zwiększony odstęp pobierania tematów':'',
				searchGapMs:GARSO_SEARCH_MIN_GAP_MS,topicGapMs}
		};
	}
	migrateEscortAdaptiveBackoff();
	try {GM_addValueChangeListener(ADAPTIVE_BACKOFF_STORAGE_KEY,migrateEscortAdaptiveBackoff);} catch {}
	async function gmRequest(options) {
		while (true) {
			try {
				const {
					cancelToken=null, requestContext=null, beforeDispatch=null, ...requestOptions
				}=options || {};
				let releaseSlot=() => {};
				const observeResponse=(response,complete=true) => releaseSlot.observeResponse
					? releaseSlot.observeResponse(response,complete):observeEscortRequestResponse(requestOptions.url,response);
				try {
					cancelToken?.throwIfCancelled();
					releaseSlot=await acquireEscortRequestSlot(requestOptions.url,cancelToken,false,requestOptions.method,requestContext);
					cancelToken?.throwIfCancelled();
					const skipped=beforeDispatch?.();
					if (skipped) {
						releaseSlot();
						return skipped.value;
					}
				} catch (error) {
					releaseSlot();
					throw error;
				}
				return await new Promise((resolve,reject) => {
					let settled=false;
					let requestHandle=null,trace=null;
					let unsubscribe=() => {};
					const finish=(callback,value) => {
						if (settled) return;
						settled=true;
						if (callback === reject) trace?.(value?.status,isOperationCancelledError(value) ? 'cancelled':'error',value);
						unsubscribe();
						callback(value);
					};
					const fail=(error,outcome) => {
						if (settled) return;
						trace?.(error?.status,outcome,error);
						recordDiagnosticRequestFailure(requestOptions.url,error);
						try {observeResponse(error || {},false);}
						catch (blockedError) {finish(reject,blockedError); return;}
						const failure=new Error(outcome === 'timeout'
							? `Przekroczono czas oczekiwania na odpowiedź (limit ${(requestOptions.timeout ?? 20000) / 1000} s)`
							:Number(error?.status) > 0 ? `Błąd żądania HTTP ${Number(error.status)}`:'Błąd połączenia — nie otrzymano poprawnej odpowiedzi HTTP');
						failure.code=outcome === 'timeout' ? 'REQUEST_TIMEOUT':'REQUEST_NETWORK_ERROR';
						failure.status=Number(error?.status) || 0;
						failure.readyState=Number(error?.readyState) || 0;
						finish(reject,failure);
					};
					try {
						releaseSlot.beforeDispatch?.();
						trace=recordDiagnosticRequest(requestOptions.url,requestOptions.method,requestContext);
						requestHandle=GM_xmlhttpRequest({
							timeout:20000, ...requestOptions, onload:response => {
								if (settled) return;
								trace?.(response?.status,'response',response);
								recordDiagnosticResponse(requestOptions.url,response?.status);
								try {
									observeResponse(response);
									finish(resolve,response);
								} catch (error) {finish(reject,error);}
							}, onerror:error => fail(error,'error'), ontimeout:error => fail(error,'timeout'), onabort:() => finish(reject,
								createOperationCancelledError(cancelToken?.reason || 'Żądanie przerwane'))
						});
						releaseSlot.markStarted?.();
					} catch (error) {finish(reject,error);}
					if (cancelToken && !settled) {
						unsubscribe=cancelToken.onCancel(reason => {
							try {requestHandle?.abort?.();} catch {}
							finish(reject,createOperationCancelledError(reason));
						});
					}
				}).finally(() => releaseSlot());
			} catch (error) {
				if (options?.cancelToken?.cancelled && !isOperationCancelledError(error)) throw createOperationCancelledError(options.cancelToken.reason);
				if (error?.retryable && (error.status === 503 || error.code === 'ESCORT_QUEUE_RESCHEDULE') && !options?.cancelToken?.cancelled) continue;
				throw error;
			}
		}
	}
	function isGarsoRequestUrl(value) {
		try {
			const host=new URL(value || '',GARSO_BASE_URL).hostname.toLowerCase();
			return host === 'garsoniera.com.pl' || host === 'www.garsoniera.com.pl';
		} catch {
			return false;
		}
	}
	function updateBoundedPacingState( state, problem, {
			minGapMs, maxGapMs, stepUpMs, stepDownMs, recoverySuccesses
		} ) {
		let gapMs=Math.max( minGapMs, Math.min(maxGapMs,Number(state?.gapMs) || minGapMs) );
		let successStreak=Math.max(0,Number(state?.successStreak) || 0);
		if (problem) {
			return {
				gapMs:Math.min(maxGapMs,gapMs + stepUpMs), successStreak:0
			};
		}
		successStreak++;
		if (successStreak >= recoverySuccesses) {
			gapMs=Math.max(minGapMs,gapMs - stepDownMs);
			successStreak=0;
		}
		return {gapMs,successStreak};
	}
	function normalizeGarsoGlobalSearchSlot(value={}) {
		const gapMs=GARSO_SEARCH_MIN_GAP_MS;
		const lastStartedAt=Math.max(0,Number(value?.lastStartedAt) || 0);
		const lastFinishedAt=Math.max(0,Number(value?.lastFinishedAt) || 0);
		const serverRetryAt=Math.max(0,Number(value?.serverRetryAt) || 0);
		return {owner:String(value?.owner || ''),leaseUntil:Math.max(0,Number(value?.leaseUntil) || 0),
			lastStartedAt,lastFinishedAt,gapMs,serverRetryAt,
			nextAllowedAt:serverRetryAt || (lastFinishedAt ? lastFinishedAt + gapMs:lastStartedAt + gapMs),
			lastAntifloodAt:Math.max(0,Number(value?.lastAntifloodAt) || 0)};
	}
	function readGarsoGlobalSearchSlot() {
		return normalizeGarsoGlobalSearchSlot(GM_getValue(GARSO_SEARCH_GLOBAL_SLOT_KEY,null));
	}
	function writeGarsoGlobalSearchSlot(value) {
		writeHelperStorageValue(GARSO_SEARCH_GLOBAL_SLOT_KEY,normalizeGarsoGlobalSearchSlot(value));
	}
	function getGarsoResponseWait(response,antiflood=false) {
		const header=String(response?.responseHeaders || '').match(/^retry-after:\s*([^\r\n]+)/im)?.[1]?.trim();
		const retryAfterMs=!header ? null:/^\d+(?:\.\d+)?$/.test(header) ? Number(header) * 1000:Math.max(0,Date.parse(header) - Date.now()) || null;
		const text=String(response?.responseText || '').replace(/<[^>]*>/g,' ').replace(/&nbsp;|&#160;|&#xA0;/gi,' ');
		const suggestedWaitMs=antiflood ? Number(text.match(/(?:odczekaj|poczekaj|zaczekaj|odczeka[ćc]|wait)[^\d]{0,80}(\d+(?:[.,]\d+)?)\s*(?:sek|sec)/i)?.[1]?.replace(',','.')) * 1000 || null:null;
		return {retryAfterMs,suggestedWaitMs};
	}
	function syncGarsoNativeSearchCooldown() {
		if (!isGarsoRequestUrl(location.href)) return;
		const url=new URL(location.href);
		if (!/\/search\b/.test(url.pathname) && url.searchParams.get('module') !== 'search') return;
		const responseText=document.body?.innerText || '',status=performance.getEntriesByType('navigation')[0]?.responseStatus;
		const antiflood=Number(status) === 429 || (/(?:anti?[-\s]*flood|anty|odczekaj|poczekaj|zaczekaj|flood control)/i.test(responseText) && isGarsoAntifloodResponse(responseText));
		const waits=getGarsoResponseWait({responseText},antiflood);
		noteGarsoNativeSearch(antiflood ? waits.suggestedWaitMs || GARSO_SEARCH_MIN_GAP_MS:GARSO_SEARCH_MIN_GAP_MS,!!waits.suggestedWaitMs);
	}
	function noteGarsoNativeSearch(waitMs=GARSO_SEARCH_MIN_GAP_MS,serverWait=false) {
		writeHelperStorageValue(GARSO_SEARCH_NATIVE_UNTIL_KEY,Math.max(serverWait ? 0:Number(GM_getValue(GARSO_SEARCH_NATIVE_UNTIL_KEY,0)) || 0,Date.now() + waitMs));
	}
	function getGarsoSearchTickets() {
		const now=Date.now(),tickets=[];
		for (const key of GM_listValues().filter(key => key.startsWith(GARSO_SEARCH_TICKET_PREFIX))) {
			const ticket=GM_getValue(key,null);
			// Starsze wersje zapisywały rezerwację na 120 s, bez znacznika odnowienia.
			const renewedAt=Number(ticket?.updatedAt ?? (ticket?.expiresAt - 120000));
			if (ticket?.expiresAt > now && renewedAt + GARSO_SEARCH_TICKET_TTL_MS > now) tickets.push({...ticket,key});
		}
		return tickets.sort((a,b) => a.queuedAt - b.queuedAt || a.key.localeCompare(b.key));
	}
	async function waitForGarsoGlobalSearchSlotSignal( requestedWaitMs, cancelToken=null ) {
		const waitMs=Math.max( 1, Math.min( GARSO_SEARCH_GLOBAL_SLOT_SIGNAL_TIMEOUT_MS, Number(requestedWaitMs) ||
					GARSO_SEARCH_GLOBAL_SLOT_SIGNAL_TIMEOUT_MS ) );
		if ( typeof GM_addValueChangeListener !== 'function' || typeof GM_removeValueChangeListener !== 'function' ) {
			await waitWithCancellation( waitMs, cancelToken );
			return;
		}
		await new Promise((resolve,reject) => {
			let settled=false;
			let listenerId=null;
			let timer=null;
			let unsubscribe=() => {};
			const cleanup=() => {
				if (listenerId != null) {
					try {
						GM_removeValueChangeListener( listenerId );
					} catch {}
				}
				if (timer) clearTimeout(timer);
				unsubscribe();
			};
			const finish=error => {
				if (settled) return;
				settled=true;
				cleanup();
				if (error) reject(error);
				else resolve();
			};
			try {
				listenerId= GM_addValueChangeListener( GARSO_SEARCH_GLOBAL_SLOT_KEY, () => finish() );
			} catch {
				listenerId=null;
			}
			timer=setTimeout( () => finish(), waitMs );
			if (cancelToken) {
				unsubscribe= cancelToken.onCancel( reason => finish( createOperationCancelledError( reason ) ) );
			}
		});
	}
	function releaseGarsoGlobalSearchSlot(sent=false,problem=null,response=null) {
		const current=readGarsoGlobalSearchSlot();
		if (current.owner !== GARSO_SEARCH_INSTANCE_ID) return;
		let next=current;
		if (sent) {
			const now=Date.now(),waits=getGarsoResponseWait(response,problem === true);
			const serverWaitMs=Math.max(waits.retryAfterMs || 0,waits.suggestedWaitMs || 0);
			next={...current,
				serverRetryAt:serverWaitMs ? now + serverWaitMs:0,
				lastFinishedAt:now,lastAntifloodAt:problem ? now:current.lastAntifloodAt};
		}
		writeGarsoGlobalSearchSlot({...next,owner:'',leaseUntil:0});
	}
	async function acquireGarsoGlobalSearchSlot(cancelToken=null) {
		const key=GARSO_SEARCH_TICKET_PREFIX + GARSO_SEARCH_INSTANCE_ID,queuedAt=Date.now();
		try {
			while (true) {
				cancelToken?.throwIfCancelled();
				if (garsoSearchPageClosed) throw createOperationCancelledError('Karta została zamknięta.');
				// Opóźnione odnowienie nie zmienia pierwotnej kolejności zgłoszenia.
				const now=Date.now();
				writeHelperStorageValue(key,{queuedAt,updatedAt:now,expiresAt:now + GARSO_SEARCH_TICKET_TTL_MS});
				const current=readGarsoGlobalSearchSlot();
				const readyAt=Math.max(current.nextAllowedAt,Number(GM_getValue(GARSO_SEARCH_NATIVE_UNTIL_KEY,0)) || 0,
					current.owner ? current.leaseUntil:0);
				if (readyAt > now || getGarsoSearchTickets()[0]?.key !== key) {
					await waitForGarsoGlobalSearchSlotSignal(readyAt > now ? readyAt - now:GARSO_SEARCH_GLOBAL_SLOT_SIGNAL_TIMEOUT_MS,cancelToken);
					continue;
				}
				writeGarsoGlobalSearchSlot({...current,owner:GARSO_SEARCH_INSTANCE_ID,leaseUntil:now + GARSO_SEARCH_TICKET_TTL_MS});
				await waitWithCancellation(GARSO_SEARCH_GLOBAL_SLOT_VERIFY_MS,cancelToken);
				cancelToken?.throwIfCancelled();
				const verified=readGarsoGlobalSearchSlot();
				if (verified.owner !== GARSO_SEARCH_INSTANCE_ID) continue;
				const head=getGarsoSearchTickets()[0]?.key;
				if (garsoSearchPageClosed || (head && head !== key) ||
						Math.max(verified.nextAllowedAt,Number(GM_getValue(GARSO_SEARCH_NATIVE_UNTIL_KEY,0)) || 0) > Date.now()) {
					releaseGarsoGlobalSearchSlot();
					continue;
				}
				const startedAt=Date.now();
				const leaseUntil=startedAt + GARSO_SEARCH_GLOBAL_SLOT_LEASE_MS;
				writeGarsoGlobalSearchSlot({...verified,lastStartedAt:startedAt,leaseUntil});
				return startedAt;
			}
		} catch (error) {
			releaseGarsoGlobalSearchSlot();
			throw error;
		} finally {
			deleteHelperStorageValue(key);
		}
	}
	function enqueueGarsoRequest(options) {
		const cancelToken=options?.cancelToken || null;
		const scheduled=garsoRequestQueue.catch(() => {}).then(async () => {
			cancelToken?.throwIfCancelled();
			await acquireGarsoGlobalSearchSlot(cancelToken);
			let response=null,problem=null;
			try {
				cancelToken?.throwIfCancelled();
				if (garsoSearchPageClosed) throw createOperationCancelledError('Karta została zamknięta.');
				response=await gmRequest({...options,timeout:GARSO_SEARCH_REQUEST_TIMEOUT_MS});
				if (Number(response?.status) === 429 || isGarsoAntifloodResponse(response?.responseText || '')) problem=true;
				else if (response?.status >= 200 && response.status < 400) problem=false;
				return response;
			} finally {
				releaseGarsoGlobalSearchSlot(true,problem,response);
			}
		});
		garsoRequestQueue=scheduled.catch(() => {});
		return scheduled;
	}
	async function garsoRequest( options, maxAttempts=GARSO_REQUEST_MAX_ATTEMPTS ) {
		const attempts=Math.max(1,Number(maxAttempts) || 1);
		let lastResponse=null;
		const cancelToken=options?.cancelToken || null;
		for (let attempt=1; attempt <= attempts; attempt++) {
			cancelToken?.throwIfCancelled();
			const response=await enqueueGarsoRequest(options);
			lastResponse=response;
			const http429=Number(response?.status) === 429;
			const antiflood=http429 || isGarsoAntifloodResponse( response?.responseText || '' );
			if (!antiflood) {
				return response;
			}
			if (http429) {
				incrementDiagnosticCounter('http429');
			} else {
				incrementDiagnosticCounter('antiflood');
			}
			if (attempt < attempts) incrementDiagnosticCounter('garsoRetries');
		}
		const error=new Error('Garso: przekroczono limit ponowień po antyfloodzie');
		error.code='GARSO_ANTIFLOOD';
		error.response=lastResponse;
		throw error;
	}
	function updateGarsoTopicPacing(problem) {
		const next=updateBoundedPacingState( {
				gapMs:garsoTopicGapMs, successStreak:garsoTopicSuccessStreak
			}, problem, {
				minGapMs:GARSO_TOPIC_MIN_GAP_MS, maxGapMs:GARSO_TOPIC_MAX_GAP_MS, stepUpMs:GARSO_TOPIC_GAP_STEP_UP_MS,
				stepDownMs:GARSO_TOPIC_GAP_STEP_DOWN_MS, recoverySuccesses:GARSO_TOPIC_RECOVERY_SUCCESSES
			} );
		garsoTopicGapMs=next.gapMs;
		garsoTopicSuccessStreak=next.successStreak;
		return garsoTopicGapMs;
	}
	function waitForGarsoTopicStartSlot(cancelToken=null) {
		const scheduled=garsoTopicStartQueue .catch(() => {}) .then(async () => {
				cancelToken?.throwIfCancelled();
				const waitMs=Math.max( 0, garsoTopicGapMs - (Date.now() - garsoTopicLastStartedAt) );
				if (waitMs > 0) {
					await waitWithCancellation(waitMs,cancelToken);
				}
				cancelToken?.throwIfCancelled();
				garsoTopicLastStartedAt=Date.now();
			});
		garsoTopicStartQueue=scheduled.catch(() => {});
		return scheduled;
	}
	async function acquireGarsoTopicRequestSlot(cancelToken=null) {
		cancelToken?.throwIfCancelled();
		if (garsoTopicActiveRequests < GARSO_TOPIC_FETCH_CONCURRENT) {
			garsoTopicActiveRequests++;
			return;
		}
		await new Promise((resolve,reject) => {
			let unsubscribe=() => {};
			const waiter={
				resolve:() => {
					unsubscribe();
					garsoTopicActiveRequests++;
					resolve();
				}, reject:error => {
					unsubscribe();
					reject(error);
				}
			};
			garsoTopicRequestWaiters.push(waiter);
			if (cancelToken) {
				unsubscribe=cancelToken.onCancel(reason => {
					const index=garsoTopicRequestWaiters.indexOf(waiter);
					if (index >= 0) garsoTopicRequestWaiters.splice(index,1);
					waiter.reject(createOperationCancelledError(reason));
				});
			}
		});
	}
	function releaseGarsoTopicRequestSlot() {
		garsoTopicActiveRequests=Math.max(0,garsoTopicActiveRequests - 1);
		const next=garsoTopicRequestWaiters.shift();
		if (next) next.resolve();
	}
	async function garsoTopicRequest(options) {
		const cancelToken=options?.cancelToken || null;
		let acquired=false;
		try {
			await acquireGarsoTopicRequestSlot(cancelToken);
			acquired=true;
			await waitForGarsoTopicStartSlot(cancelToken);
			cancelToken?.throwIfCancelled();
			const response=await gmRequest(options);
			const http429=Number(response?.status) === 429;
			const responseText=String(response?.responseText || '');
			const maybeAntiflood=http429 || /(?:anti?[-\s]*flood|flood control|odczekaj|poczekaj|zaczekaj)/i .test(responseText);
			const antiflood=http429 || ( maybeAntiflood && isGarsoAntifloodResponse(responseText) );
			if (antiflood) {
				updateGarsoTopicPacing(true);
				if (http429) incrementDiagnosticCounter('http429');
				else incrementDiagnosticCounter('antiflood');
				const error=new Error('Garso: antyflood podczas pobierania tematu');
				error.code='GARSO_ANTIFLOOD';
				error.response=response;
				throw error;
			}
			updateGarsoTopicPacing(false);
			return response;
		} finally {
			if (acquired) releaseGarsoTopicRequestSlot();
		}
	}
	async function gmGetText( url, cancelToken=null, {searchRequest=false}={} ) {
		let r;
		if (!isGarsoRequestUrl(url)) {
			r=await gmRequest({method:'GET',url,cancelToken});
		} else if (searchRequest) {
			r=await garsoRequest({method:'GET',url,cancelToken});
		} else {
			r=await garsoTopicRequest({method:'GET',url,cancelToken});
		}
		if (r.status && (r.status < 200 || r.status >= 400)) {
			throw new Error(`HTTP ${r.status}`);
		}
		return r.responseText || '';
	}
	let watchedCheckPromise=null;
	let watchedPollTimer=null;
	let watchedPollIntervalMs=0;
	let watchStorageListenersInitialized=false;
	let watchedInitialCheckScheduled=false;
	const watchPageButtonRefreshers=new Set();
	const watchBarShiftedTopElements=new Map();

	const hiddenUiRefreshers=new Set();
	function getHiddenWatchIndex() {
		const enabled=SETTINGS.watchEnabled;
		if (hiddenWatchIndex?.enabled === enabled) return hiddenWatchIndex;
		const byAdId=new Map();
		const byProfileId=new Map();
		const add=(map,key,item) => {
			if (!key) return;
			key=String(key);
			if (!map.has(key)) map.set(key,[]);
			if (!map.get(key).includes(item)) map.get(key).push(item);
		};
		const items=enabled ? getWatchedItems().filter(entry => entry.hidden):[];
		for (const item of items) {
			const urls=item.sourceType === 'escort-ad' ? [item.adUrl]:getWatchedItemAdUrls(item);
			for (const url of urls) add(byAdId,parseAdIdFromUrl(url),item);
			if (item.sourceType === 'escort-ad') continue;
			for (const url of item.profileUrls) add(byProfileId,getEscortiProfileId(url),item);
			if (item.watchKey.startsWith('profiles:')) {
				for (const id of item.watchKey.slice(9).split(',')) {
					if (/^\d+$/.test(id)) add(byProfileId,id,item);
				}
			}
		}
		return hiddenWatchIndex={byAdId,byProfileId,items,enabled};
	}
	function getHiddenResultProfileUrls(result) {
		return [...(result?.profileUrls || []),result?.profileUrl,
			...(result?.profileSummaries || []).map(profile => profile?.profileUrl)].filter(Boolean);
	}
	function getHiddenAdMatches(adId,profileUrls=[]) {
		const index=getHiddenWatchIndex();
		const matches=new Set(index.byAdId.get(String(adId)) || []);
		for (const url of profileUrls) {
			for (const item of index.byProfileId.get(String(getEscortiProfileId(url))) || []) matches.add(item);
		}
		return [...matches];
	}
	function isWatchedItemHidden(item) {
		if (item.sourceType !== 'escort-ad') {
			return getHiddenWatchIndex().items.some(hidden => hidden.sourceType !== 'escort-ad' && watchedItemsOverlap(hidden,item));
		}
		const adId=parseAdIdFromUrl(item.adUrl);
		return getHiddenAdMatches(adId,[...item.profileUrls,...getHiddenResultProfileUrls(getListCache(adId))]).length > 0;
	}
	function refreshHiddenItemsUi() {
		hiddenWatchIndex=null;
		for (const refresh of hiddenUiRefreshers) {
			try {refresh();} catch (error) {log('Błąd odświeżania ukrytych',error);}
		}
	}
	function rememberHiddenResultAd(adId,result) {
		const matches=getHiddenAdMatches(adId,getHiddenResultProfileUrls(result))
			.filter(item => item.sourceType !== 'escort-ad' && !getWatchedItemAdUrls(item).some(url => parseAdIdFromUrl(url) === String(adId)));
		if (!matches.length || !/^\d+$/.test(String(adId))) return;
		const ids=new Set(matches.map(item => item.id));
		saveWatchedItems(getWatchedItems().map(item => ids.has(item.id)
			? {...item,resolvedAdUrls:[...item.resolvedAdUrls,`https://pl.escort.club/anons/${adId}.html`]}
			:item));
	}
	function createHiddenIcon(hidden=true) {
		const icon=makeElement('span','vm-hidden-icon');
		icon.setAttribute('aria-hidden','true');
		icon.innerHTML=`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>${hidden ? '<path d="m3 3 18 18"/>':''}</svg>`;
		Object.assign(icon.style,{display:'inline-flex',flex:'0 0 auto',verticalAlign:'middle'});
		return icon;
	}
	function restoreHiddenItems(itemIds) {
		const ids=new Set(itemIds.map(String));
		const items=getWatchedItems().map(item => ids.has(item.id)
			? {...item,hidden:false,hiddenAt:0,hiddenLastCheckedAt:0,hiddenLastError:null}
			:item);
		const removedIds=new Set(items.filter(item => ids.has(item.id) && canRemoveEmptyTrackedItem(item)).map(item => item.id));
		saveWatchedItems(items.filter(item => !removedIds.has(item.id)));
		if (removedIds.size) saveWatchEvents(getWatchEvents().filter(event => !removedIds.has(event.itemId)));
		initLocalWatchSystem();
		document.getElementById(WATCH_SETTINGS_OVERLAY_ID)?._vmRender?.();
	}
	async function editHiddenForCurrentPage() {
		if (!getWatchSettings().enabled) return;
		const matches=getFavoriteMatchesForCurrentEscortiProfile().filter(item => item.hidden);
		if (matches.length > 1) return chooseTrackedEntriesDialog(matches,true,true);
		if (matches.length === 1) return changeTrackedItemStatus(matches[0].id,'hidden');
		const context=getTrackedScopeItem(getFavoriteMatchesForCurrentEscortiProfile(),'person') || getCurrentEscortiWatchContextFast();
		if (!context) return;
		const item=saveFavoriteItemFromContext({...context,
			resolvedAdUrls:[...new Set([...(context.resolvedAdUrls || []),...getEscortiAdUrls()])]
		},{hidden:true,favorite:context.favorite === true},'person');
		initLocalWatchSystem();
		showWatchToast('Profil ukryty. Lista jego anonsów zostanie sprawdzona automatycznie.');
		runWatchedChecks({force:true,itemIds:[item.id],hiddenOnly:true})
			.catch(error => showWatchToast(`Profil pozostaje ukryty. ${error?.message || 'Nie udało się sprawdzić nowych anonsów.'}`,true));
	}
	function getWatchSettings() {
		try {
			const main=readHelperSettingsSnapshot();
			return {
				enabled:main.watchEnabled === true, notesEnabled:main.watchEnabled === true,
				showOnlineCount: main.watchEnabled === true &&
					main.showWatchOnlineCount === true, showAlertBar:main.showWatchAlertBar !== false, nonAlertChangeNewness: normalizeWatchNonAlertChangeNewness(
						main.nonAlertChangeNewness, DEFAULT_WATCH_SETTINGS.nonAlertChangeNewness ),
				intervalMinutes:WATCH_INTERVAL_OPTIONS.includes( Number(main.watchIntervalMinutes) )
					? Number(main.watchIntervalMinutes)
					:DEFAULT_WATCH_SETTINGS.intervalMinutes
			};
		} catch {
			return {...DEFAULT_WATCH_SETTINGS};
		}
	}
	function normalizeWatchUrl(value) {
		try {
			const url=new URL(value);
			if (!/^https?:$/.test(url.protocol)) return null;
			url.search='';
			url.hash='';
			return url.href;
		} catch {
			return null;
		}
	}
	function normalizeWatchedDisplayName(value,fallback='Obserwowane anons') {
		let name=normalizeEscortAdText(value);
		if (!name) return fallback;
		if (/^opinie\s+o\s+/i.test(name)) {
			name=name.replace(/^opinie\s+o\s+/i,'').trim();
			const citySeparator=name.toLocaleLowerCase('pl-PL').lastIndexOf(' z ');
			if (citySeparator > 0) name=name.slice(0,citySeparator).trim();
		}
		return name || fallback;
	}
	function copyWatchedValue(value) {
		if (!value || typeof value !== 'object') return value;
		if (Array.isArray(value)) return value.map(copyWatchedValue);
		const copy={...value};
		for (const key of Object.keys(copy)) {
			if (copy[key] && typeof copy[key] === 'object') copy[key]=copyWatchedValue(copy[key]);
		}
		return copy;
	}
	function getWatchedItems() {
		try {
			if (!watchedItemsReadCache) {
				const saved=GM_getValue(WATCH_ITEMS_STORAGE_KEY,[]);
				watchedItemsReadCache={value:(Array.isArray(saved) ? saved:[]).map(normalizeWatchedItem).filter(Boolean)};
			}
			return copyWatchedValue(watchedItemsReadCache.value);
		} catch {return [];}
	}
	function saveWatchedItems(items) {
		const normalized=(Array.isArray(items) ? items:[]).map(normalizeWatchedItem).filter(Boolean);
		const removed=new Set(normalized.filter(item => item.sourceType !== 'escort-ad' && !item.keepProfile && canRemoveEmptyTrackedItem(item)).map(item => item.id));
		if (removed.size) saveWatchEvents(getWatchEvents().filter(event => !removed.has(event.itemId)));
		writeHelperStorageValue(WATCH_ITEMS_STORAGE_KEY,normalized.filter(item => !removed.has(item.id)));
		refreshWatchPageButtons();
	}
	function refreshWatchPageButtons() {
		if (watchButtonsRefreshScheduled) return;
		watchButtonsRefreshScheduled=true;
		// Timer działa też w ukrytej karcie; requestAnimationFrame może tam czekać.
		setTimeout(() => {
			watchButtonsRefreshScheduled=false;
			refreshHiddenItemsUi();
			for (const refresh of watchPageButtonRefreshers) {
				try {refresh();} catch (error) {log('Błąd odświeżania Ulubionych',error);}
			}
		},40);
	}
	function watchEventTypeToAlertMode(type) {
		if (type === 'online') return 'online';
		if (type === 'location') return 'location';
		if (type === 'changes') return 'changes';
		if (type === 'new-ad') return 'newAds';
		return '';
	}
	function getWatchEvents() {
		try {
			const saved=GM_getValue(WATCH_EVENTS_STORAGE_KEY,[]);
			if (!Array.isArray(saved)) return [];
			const normalizedEvents=saved .filter(event => event && event.id && event.itemId && event.message ) .map(event => {
					const normalized={
						...event, id:String(event.id), itemId:String(event.itemId), timestamp: Number(event.timestamp) || Date.now(),
						acknowledged: !!event.acknowledged, alertTriggered: !!event.alertTriggered, alertMode: String( event.alertMode ||
								watchEventTypeToAlertMode(event.type) )
					};
					if (!normalized.alertMode) {
						normalized.alertMode= watchEventTypeToAlertMode( normalized.type );
					}
					return normalized;
				}) .sort((a,b) => b.timestamp - a.timestamp);
			const settings=getWatchSettings();
			const nonAlertNewness= settings.nonAlertChangeNewness;
			if (nonAlertNewness !== 'until-open') {
				const minutes= Number(nonAlertNewness);
				if (Number.isFinite(minutes) && minutes > 0) {
					const cutoff= Date.now() - minutes * 60000;
					let changed=false;
					for (const event of normalizedEvents) {
						if ( !event.acknowledged && !event.alertTriggered && event.timestamp <= cutoff ) {
							event.acknowledged=true;
							changed=true;
						}
					}
					if (changed) {
						writeHelperStorageValue( WATCH_EVENTS_STORAGE_KEY, normalizedEvents.slice(0,200) );
					}
				}
			}
			return normalizedEvents;
		} catch {
			return [];
		}
	}
	function saveWatchEvents(events) {
		writeHelperStorageValue( WATCH_EVENTS_STORAGE_KEY, (Array.isArray(events) ? events:[])
				.sort((a,b) => Number(b.timestamp || 0) - Number(a.timestamp || 0)) .slice(0,200) );
	}
	function acknowledgeNonAlertWatchEvents() {
		const events=getWatchEvents();
		let changed=false;
		const next=events.map(event => {
			if ( !event.acknowledged && !event.alertTriggered ) {
				changed=true;
				return {
					...event, acknowledged:true
				};
			}
			return event;
		});
		if (changed) {
			saveWatchEvents(next);
		}
		return changed;
	}
	function refreshWatchEventViews() {
		renderWatchNotificationBar();
		for (const id of [WATCH_SETTINGS_OVERLAY_ID,'vm-favorite-tiles-test','vm-favorites-changes-timeline-overlay']) {
			try {document.getElementById(id)?._vmRender?.();} catch (error) {log('Błąd odświeżania alertów',error);}
		}
	}
	function acknowledgeWatchEvent(eventId) {
		const events=getWatchEvents(),event=events.find(value => value.id === String(eventId));
		if (!event || event.acknowledged) return false;
		event.acknowledged=true; saveWatchEvents(events); refreshWatchEventViews();
		return true;
	}
	function acknowledgeWatchEventOnArrival() {
		if (window.top !== window.self) return;
		const url=new URL(location.href),parts=url.hash.slice(1).split('&');
		const marker=parts.find(part => part.startsWith('vm_watch_read='));
		if (!marker) return;
		url.hash=parts.filter(part => !part.startsWith('vm_watch_read=')).join('&');
		try {history.replaceState(history.state,'',url.href);} catch {}
		try {
			const id=decodeURIComponent(marker.slice('vm_watch_read='.length));
			if (getWatchEvents().some(event => event.id === id && event.alertTriggered && !event.acknowledged)) acknowledgeWatchEvent(id);
			if (url.hash) document.getElementById(decodeURIComponent(url.hash.slice(1)))?.scrollIntoView();
		} catch (error) {log('Nie udało się odczytać alertu po otwarciu strony',error);}
	}
	function acknowledgeAllAlertWatchEvents() {
		saveWatchEvents(getWatchEvents().map(event => ( event.alertTriggered
				? {...event,acknowledged:true}
				:event )));
		refreshWatchEventViews();
	}
	function extractWatchPageName(doc,fallback='Obserwowane anons') {
		if (!doc) return fallback;
		const candidates=[ extractEscortClubTitle(doc),
			doc.querySelector('meta[property="og:title"]')?.getAttribute('content'), doc.querySelector('h1')?.textContent,
			doc.title ];
		for (const candidate of candidates) {
			const name=normalizeEscortAdText(candidate) .replace(/\s*[|–\u2014-]\s*(?:Escorti(?:\.pl)?|Escort\.club).*$/i,'') .trim();
			if (name && !/^escorti(?:\.pl)?$/i.test(name)) {
				return normalizeWatchedDisplayName(name,fallback).slice(0,120);
			}
		}
		return fallback;
	}
	function extractWatchPageImage(doc,pageUrl=location.href) {
		if (!doc) return '';
		const candidates=[ doc.querySelector('#lightSlider img[src], #lightSlider img[data-src]')?.getAttribute('src'),
			doc.querySelector('.galleryContainer img[src], .galleryContainer img[data-src]')?.getAttribute('src'),
			doc.querySelector('.content-gallery-col img[src], .content-gallery-col img[data-src]')?.getAttribute('src'),
			doc.querySelector('.profile-image img[src], .profile-photo img[src], .escort-image img[src]')?.getAttribute('src'),
			doc.querySelector('meta[property="og:image"]')?.getAttribute('content'),
			doc.querySelector('main img[src], main img[data-src]')?.getAttribute('src') ];
		for (const candidate of candidates) {
			if (!candidate || /^data:/i.test(candidate)) continue;
			try {
				return new URL(candidate,pageUrl).href;
			} catch {}
		}
		return '';
	}
	const unavailableWatchThumbnailUrls=new Set();
	function normalizeWatchThumbnailUrl(value) {
		const url=String(value || '').trim();
		if (!url) return '';
		try {
			const parsed=new URL(url,location.href);
			if (/(^|\.)escorti\.pl$/i.test(parsed.hostname) && /^\/default_avatar\.jpg$/i.test(parsed.pathname)) return '';
			return parsed.href;
		} catch {
			return url;
		}
	}
	function markWatchThumbnailUnavailable(url) {
		const normalized=normalizeWatchThumbnailUrl(url);
		if (normalized) {
			unavailableWatchThumbnailUrls.add(normalized);
		}
	}
	function isWatchThumbnailKnownUnavailable(url) {
		const normalized=normalizeWatchThumbnailUrl(url);
		return !!normalized && unavailableWatchThumbnailUrls.has(normalized);
	}
	function probeWatchThumbnailUrl(url,timeoutMs=7000) {
		const normalized=normalizeWatchThumbnailUrl(url);
		if (!normalized) return Promise.resolve(false);
		if (isWatchThumbnailKnownUnavailable(normalized)) {
			return Promise.resolve(false);
		}
		return new Promise(resolve => {
			const image=new Image();
			let finished=false;
			const finish=available => {
				if (finished) return;
				finished=true;
				clearTimeout(timer);
				image.onload=null;
				image.onerror=null;
				if (!available) {
					markWatchThumbnailUnavailable(normalized);
				}
				resolve(available);
			};
			const timer=setTimeout( () => finish(false), timeoutMs );
			image.referrerPolicy='no-referrer';
			image.onload=() => finish( image.naturalWidth > 0 && image.naturalHeight > 0 );
			image.onerror=() => finish(false);
			image.src=normalized;
		});
	}
	async function findNextAvailableWatchThumbnail( currentUrl, candidates ) {
		const current=normalizeWatchThumbnailUrl(currentUrl);
		const unique=[];
		const seen=new Set();
		for (const candidate of candidates || []) {
			const normalized= normalizeWatchThumbnailUrl(candidate);
			if ( !normalized || normalized === current || seen.has(normalized) || isWatchThumbnailKnownUnavailable(normalized)
			) {
				continue;
			}
			seen.add(normalized);
			unique.push(normalized);
		}
		for (const candidate of unique) {
			if (await probeWatchThumbnailUrl(candidate)) {
				return candidate;
			}
		}
		return '';
	}
	function hasUsefulWatchThumbnail(item) {
		const imageUrl=normalizeWatchThumbnailUrl(item?.imageUrl);
		if (!imageUrl) return false;
		if (item.sourceType !== 'escort-ad') return true;
		return /static\.escort\.club\/galleries\//i.test(imageUrl);
	}
	function formatWatchDateTime(value) {
		if (!Number(value)) return 'jeszcze nie sprawdzono';
		try {
			return new Intl.DateTimeFormat('pl-PL',{
				day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit'
			}).format(new Date(Number(value)));
		} catch {
			return new Date(Number(value)).toLocaleString();
		}
	}
	function getEscortiWatchSearchValue(item) {
		if (!item || ['escort-ad','escort-search'].includes(item.sourceType) || item.escortiProfileOnly || item.phoneProfileOnly) return '';
		const mode=normalizeStoredWatchProfileResolutionMode(item.resolutionMode);
		return mode === 'address-escorti' ? normalizeEscortiAdUrl(item.adUrl) || ''
			:mode === 'phone-escorti' ? getEscortAdPhoneDigits(item) || '':'';
	}
	function getWatchKey(context) {
		const search=getEscortiWatchSearchValue(context);
		if (search) return `escorti-search:${search}`;
		if (context.watchKey) return context.watchKey;
		const profileIds=(context.profileUrls || []) .map(getEscortiProfileId) .filter(Boolean)
			.sort((a,b) => Number(a) - Number(b));
		if (profileIds.length) return `profiles:${profileIds.join(',')}`;
		const phone=getEscortAdPhoneDigits(context);
		if (phone) return `phone:${phone}`;
		const adId=parseAdIdFromUrl(context.adUrl);
		if (adId) return `ad:${adId}`;
		return `other:${Math.random().toString(36).slice(2)}`;
	}
	function watchedItemsOverlap(first,second) {
		if (normalizeStoredWatchProfileResolutionMode(first.resolutionMode) !== normalizeStoredWatchProfileResolutionMode(second.resolutionMode)) return false;
		const firstSearch=getEscortiWatchSearchValue(first),secondSearch=getEscortiWatchSearchValue(second);
		if (firstSearch || secondSearch) return !!firstSearch && firstSearch === secondSearch;
		if (first.watchKey && second.watchKey && first.watchKey === second.watchKey) return true;
		const firstIsSingleAd=first.sourceType === 'escort-ad';
		const secondIsSingleAd=second.sourceType === 'escort-ad';
		if (firstIsSingleAd || secondIsSingleAd) {
			return firstIsSingleAd && secondIsSingleAd && !!first.adUrl && !!second.adUrl &&
				parseAdIdFromUrl(first.adUrl) === parseAdIdFromUrl(second.adUrl);
		}
		if ( first.phoneDigits && second.phoneDigits && getEscortAdPhoneDigits(first) === getEscortAdPhoneDigits(second)
		) return true;
		const firstProfiles=new Set((first.profileUrls || []).map(getEscortiProfileId).filter(Boolean));
		return (second.profileUrls || []).some(url => firstProfiles.has(getEscortiProfileId(url)));
	}
	function upsertWatchedItem(context,modePatch={},locationTarget=null) {
		return saveFavoriteItemFromContext(context,{
			favorite:true, modes:Object.fromEntries(Object.entries(modePatch).filter(([,enabled]) => enabled)), locationTarget
		});
	}
	function updateWatchedItem(itemId,update) {
		const items=getWatchedItems();
		const index=items.findIndex(item => item.id === itemId);
		if (index < 0) return null;
		items[index]=normalizeWatchedItem({
			...items[index], ...(typeof update === 'function' ? update(items[index]):update), keepProfile:false
		});
		saveWatchedItems(items);
		return items[index];
	}
	function renameWatchedItem(itemId,value) {
		const newName=normalizeEscortAdText(value).slice(0,120);
		if (!newName) return null;
		const items=getWatchedItems();
		const index=items.findIndex(item => item.id === itemId);
		if (index < 0) return null;
		const previousName=items[index].displayName;
		items[index]=normalizeWatchedItem({
			...items[index], displayName:newName, customDisplayName:newName
		});
		saveWatchedItems(items);
		const escapePattern=text => String(text || '') .replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
		saveWatchEvents(getWatchEvents().map(event => {
			if (event.itemId !== itemId) return event;
			const oldEventName=event.name || previousName;
			let message=String(event.message || '');
			for (const oldName of [oldEventName,previousName]) {
				if (!oldName) continue;
				const pattern=new RegExp(`^${escapePattern(oldName)}(?=\\s|$)`,'i');
				if (pattern.test(message)) {
					message=message.replace(pattern,newName);
					break;
				}
			}
			return {...event,name:newName,message};
		}));
		renderWatchNotificationBar();
		return items[index];
	}
	function removeWatchedItem(itemId) {
		return withTrackedUndo('Usunięto wpis.',()=>{
			saveWatchedItems(getWatchedItems().filter(item => item.id !== itemId));
			saveWatchEvents(getWatchEvents().filter(event => event.itemId !== itemId));
			renderWatchNotificationBar();
		});
	}
	const WATCH_CHECK_LOCK_LEASE_MS=5 * 60 * 1000;
	const WATCH_CHECK_LOCK_HEARTBEAT_MS=45 * 1000;
	const WATCH_CHECK_LOCK_VERIFY_MS=90;
	const WATCH_MANUAL_CHECK_PREFIX=WATCH_CHECK_LOCK_KEY + '_manual_';
	const watchManualRequests=new Set();
	let watchCheckPageClosed=false,watchProgressWrittenAt=0;
	function publishWatchManualRequests() {
		const key=WATCH_MANUAL_CHECK_PREFIX + WATCH_INSTANCE_ID;
		if (!watchManualRequests.size || watchCheckPageClosed) {deleteHelperStorageValue(key); return;}
		const at=Math.min(...[...watchManualRequests].map(request => request.at)),now=Date.now(),old=GM_getValue(key,null);
		if (old?.at === at && old.expiresAt > now + WATCH_CHECK_LOCK_LEASE_MS / 2) return;
		writeHelperStorageValue(key,{at,expiresAt:now + WATCH_CHECK_LOCK_LEASE_MS});
	}
	function getWatchManualTurn() {
		const pending=[],now=Date.now();
		for (const key of GM_listValues()) {
			if (!key.startsWith(WATCH_MANUAL_CHECK_PREFIX)) continue;
			const value=GM_getValue(key,null);
			if (!value || !(value.expiresAt > now)) deleteHelperStorageValue(key);
			else pending.push({key,at:Number(value.at) || 0});
		}
		return pending.sort((a,b) => a.at - b.at || a.key.localeCompare(b.key))[0]?.key || '';
	}
	async function acquireWatchCheckLock(kind='scheduled') {
		const now=Date.now();
		const current=GM_getValue(WATCH_CHECK_LOCK_KEY,null);
		if ( current?.owner && current.owner !== WATCH_INSTANCE_ID && Number(current.expiresAt) > now ) return false;
		writeHelperStorageValue(WATCH_CHECK_LOCK_KEY,{
			owner:WATCH_INSTANCE_ID, claimedAt:now, expiresAt:now + WATCH_CHECK_LOCK_LEASE_MS,kind
		});
		// GM_setValue nie blokuje innych kart; po zapisie sprawdź właściciela blokady.
		await new Promise(resolve => setTimeout(resolve,WATCH_CHECK_LOCK_VERIFY_MS));
		const verified=GM_getValue(WATCH_CHECK_LOCK_KEY,null);
		if (verified?.owner !== WATCH_INSTANCE_ID || Number(verified.expiresAt) <= Date.now()) return false;
		touchWatchCheckLock();
		return true;
	}
	function ownsWatchCheckLock() {
		const current=GM_getValue(WATCH_CHECK_LOCK_KEY,null);
		return current?.owner === WATCH_INSTANCE_ID && Number(current.expiresAt) > Date.now();
	}
	function touchWatchCheckLock(progress=null) {
		const current=GM_getValue(WATCH_CHECK_LOCK_KEY,null);
		if (current?.owner !== WATCH_INSTANCE_ID) return false;
		const now=Date.now();
		if (progress && now - watchProgressWrittenAt < 1000 && progress.phase === current.progress?.phase) return true;
		watchProgressWrittenAt=now;
		writeHelperStorageValue(WATCH_CHECK_LOCK_KEY,{
			...current,expiresAt:now + WATCH_CHECK_LOCK_LEASE_MS,
			...(progress ? {progress:{phase:progress.phase,checked:progress.checked,total:progress.total,adChecked:progress.adChecked,adTotal:progress.adTotal}}:{})
		});
		return true;
	}
	function releaseWatchCheckLock() {
		const current=GM_getValue(WATCH_CHECK_LOCK_KEY,null);
		if (current?.owner === WATCH_INSTANCE_ID) deleteHelperStorageValue(WATCH_CHECK_LOCK_KEY);
	}
	async function watchMapLimit(values,limit,worker) {
		const source=Array.isArray(values) ? values:[];
		const results=new Array(source.length);
		let index=0;
		async function run() {
			while (true) {
				const current=index++;
				if (current >= source.length) return;
				results[current]=await worker(source[current],current);
			}
		}
		const workerCount=Math.min( Math.max(1,limit), Math.max(1,source.length) );
		const workers=[];
		for (let workerIndex=0; workerIndex < workerCount; workerIndex++) {
			workers.push(run());
		}
		await Promise.all(workers);
		return results;
	}
	function watchCitiesKey(cities) {
		return [...new Set((cities || []).map(normalizeEscortCityKey).filter(Boolean))] .sort() .join('|');
	}
	function watchCitiesText(cities) {
		const normalized=[...new Set((cities || []).map(normalizeEscortCity).filter(Boolean))];
		return normalized.length ? normalized.join(', '):'b/d';
	}
	function showWatchToast(message,error=false,{id='vm-garso-watch-toast',duration=2800,style=null,actionLabel='',onAction=null}={}) {
		detachHelperNode(document.getElementById(id));
		const toast=prepareHelperStyleApi( makeElement('div') );
		toast.id=id;
		toast.textContent=message;toast.setAttribute('role','status');toast.setAttribute('aria-live','polite');
		Object.assign(toast.style,{
			position:'fixed', right:'18px', bottom:'18px', zIndex:'2147483647', padding:'10px 14px', borderRadius:'8px',
			background:error
				? 'var(--vm-ui-danger, #c62828)'
				:'var(--vm-ui-accent, #f54da3)', color:'var(--vm-ui-accent-contrast, #fff)', fontSize:'13px', fontWeight:'700',
			boxShadow:'var(--vm-ui-shadow, 0 4px 18px rgba(0,0,0,.28))'
		});
		if (style) Object.assign(toast.style,style);
		document.body.appendChild(toast);
		const expiresAt=Date.now()+duration;
		const timer=setTimeout(()=>detachHelperNode(toast),duration);
		if (onAction) {
			const button=makeButton('vm-ui-button vm-ui-compact',actionLabel || 'Cofnij');
			button.style.marginLeft='12px';
			button.addEventListener('click',()=>{
				if (button.disabled) return;
				button.disabled=true;clearTimeout(timer);detachHelperNode(toast);
				if (Date.now()<=expiresAt) onAction();
			},{once:true});
			toast.appendChild(button);
		}
	}
	function restoreWatchBarTopElements() {
		for (const [element,original] of watchBarShiftedTopElements) {
			try {
				if (original.value) {
					element.style.setProperty('top',original.value,original.priority || '');
				} else {
					element.style.removeProperty('top');
				}
			} catch {}
		}
		watchBarShiftedTopElements.clear();
		document.documentElement.style.setProperty( '--vm-escort-summary-panel-top-offset', '0px' );
	}
	function syncWatchBarPageOffset(bar,spacer) {
		if (!bar?.isConnected || !spacer?.isConnected) return;
		const height=Math.ceil(bar.getBoundingClientRect().height);
		spacer.style.height=`${height}px`;
		document.documentElement.style.setProperty( '--vm-escort-summary-panel-top-offset', `${height}px` );
		const topFixedElements=location.hostname.toLowerCase().endsWith('escorti.pl')
			? document.querySelectorAll('#main-app > nav.fixed.top-0')
			:[];
		for (const element of topFixedElements) {
			if (!watchBarShiftedTopElements.has(element)) {
				watchBarShiftedTopElements.set(element,{
					value:element.style.getPropertyValue('top'), priority:element.style.getPropertyPriority('top')
				});
			}
			element.style.setProperty('top',`${height}px`,'important');
		}
	}
	let escortRequestNotificationTimer=null,escortRequestNotificationSignature='';
	function getEscortRequestNotification(status=getEscortRequestQueueStatus()) {
		if (status.reloadRequired) return {until:0,text:'Escort.club: BAN na IP. Wstrzymano pobieranie danych do odświeżenia strony. Skrypt sprawdzi dostęp na podstawie wczytanej strony lub jednego zapytania. Jeśli ponownie otrzyma HTTP 403, pozostanie zatrzymany.'};
		if (!(status.blockedUntil > Date.now())) return null;
		const retryAt=new Date(status.blockedUntil).toLocaleTimeString('pl-PL');
		if (status.reason === 'HTTP 403' || status.reason === 'Kontrola antyfloodowa Escort.club') {
			return {until:status.blockedUntil,text:`Escort.club: Tymczasowy ban na IP. Wstrzymano pobieranie danych. Czas trwania bana nie jest jeszcze ustalony. Kolejna próba skryptu o ${retryAt}. Możesz kontynuować przeglądanie z wykorzystaniem VPN.`};
		}
		const reason=status.reason === 'HTTP 429' ? 'limit zapytań (HTTP 429)':status.reason || 'odmowa dostępu';
		return {until:status.blockedUntil,text:`Escort.club: ${reason}. Wstrzymano pobieranie danych do ${retryAt}.`};
	}
	function refreshEscortRequestNotification(status) {
		if (window.top !== window.self) return;
		const notice=getEscortRequestNotification(status);
		if ((notice?.text || '') !== escortRequestNotificationSignature || (notice?.until > Date.now() && !escortRequestNotificationTimer)) renderWatchNotificationBar();
	}
	subscribeEscortRequestQueueStatus(refreshEscortRequestNotification);
	window.addEventListener('pagehide',() => {clearTimeout(escortRequestNotificationTimer); escortRequestNotificationTimer=null;});
	function renderWatchNotificationBar() {
		if (window.top !== window.self) {
			detachHelperNode(document.getElementById(WATCH_BAR_ID));
			detachHelperNode(document.getElementById(WATCH_BAR_SPACER_ID));
			return;
		}
		const render=() => {
			const requestNotice=getEscortRequestNotification();
			escortRequestNotificationSignature=requestNotice?.text || '';
			clearTimeout(escortRequestNotificationTimer);
			escortRequestNotificationTimer=requestNotice?.until > Date.now() ? setTimeout(() => refreshEscortRequestNotification(),Math.min(2147483647,Math.max(1,requestNotice.until - Date.now() + 1))):null;
			const toolbar=document.getElementById(HELPER_MENU_ID);
			const focused=toolbar?.contains(document.activeElement) ? document.activeElement:null;
			if (toolbar) {toolbar._vmMoving=true; document.body?.appendChild(toolbar);}
			const restoreToolbar=() => {
				if (!toolbar) return;
				toolbar._vmMoving=false;
				if (!toolbar.hidden && focused?.isConnected) focused.focus({preventScroll:true});
				toolbar._vmPosition?.();
			};
			const previousBar=document.getElementById(WATCH_BAR_ID);
			try {previousBar?._vmResizeObserver?.disconnect();} catch {}
			try {previousBar?._vmResizeCleanup?.();} catch {}
			detachHelperNode(previousBar);
			detachHelperNode(document.getElementById(WATCH_BAR_SPACER_ID));
			restoreWatchBarTopElements();
			const watchSettings=getWatchSettings();
			if (!document.body) {restoreToolbar(); return;}
			const showMenu=toolbar && !toolbar.hidden;
			const pendingEvents=watchSettings.enabled ? getWatchEvents().filter( event => !event.acknowledged && event.alertTriggered
			):[];
			toolbar?._vmSetAlertCount?.(pendingEvents.length);
			const events=watchSettings.showAlertBar ? pendingEvents:[];
			const showOnlineCount=watchSettings.enabled && watchSettings.showOnlineCount === true;
			if (!events.length && !showOnlineCount && !showMenu && !requestNotice) {restoreToolbar(); return;}
			const watchedItems=showOnlineCount ? getWatchedItems():[];
			const onlineItems=watchedItems.filter(item => item.modes?.online && item.state?.online === true );
			const onlineCount=onlineItems.length;
			const bar=prepareHelperStyleApi( makeElement('div') );
			bar.id=WATCH_BAR_ID;
			Object.assign(bar.style,{
				position:'fixed', left:'0', right:'0', top:'0', zIndex:'2147483645', display:'flex', alignItems:'center',
				gap:'8px', padding:'4px 8px', boxSizing:'border-box', background:'var(--vm-ui-accent, #f54da3)',
				color:'var(--vm-ui-accent-contrast, #fff)', boxShadow:'var(--vm-ui-shadow, 0 1px 5px rgba(0,0,0,.28))',
				fontSize:'12px', lineHeight:'1.25'
			});
			if (requestNotice) {
				bar.style.flexWrap='wrap';
				const warning=makeElement('div','vm-escort-request-warning',requestNotice.text);
				warning.setAttribute('role','status'); warning.setAttribute('aria-live','polite');
				Object.assign(warning.style,{flex:'1 0 100%',minWidth:'0',fontWeight:'800',whiteSpace:'normal',overflowWrap:'anywhere'});
				bar.appendChild(warning);
			}
			const createOnlineSummary=compact => {
				const onlineSummary=makeButton('',`Online: ${onlineCount}`);
				const onlineNames=onlineItems.map(item => {
					const type=item.sourceType === 'escort-ad' ? 'Anons':'Profil';
					return `${type}: ${item.displayName}`;
				});
				onlineSummary.setAttribute( 'aria-label', onlineNames.length
						? `Online: ${onlineCount}. ${onlineNames.join('. ')}. Kliknij, aby otworzyć listę ulubionych.`
						:'Brak profili lub anonsów online z włączonym alertem online/offline. Kliknij, aby otworzyć listę ulubionych.' );
				Object.assign(onlineSummary.style,{
					flex:'0 0 auto', whiteSpace:'nowrap', padding:compact ? '3px 8px':'1px 6px', border:compact
						? '0'
						:'1px solid color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 72%, transparent)',
					borderRadius:compact ? '0 0 7px 7px':'10px',
					background:compact ? 'transparent':'color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 14%, transparent)',
					color:'var(--vm-ui-accent-contrast, #fff)', cursor:'pointer', font:'inherit', fontWeight:'800'
				});
				const tooltip=makeElement('div');
				tooltip.id=`${WATCH_BAR_ID}-online-tooltip`;
				tooltip.setAttribute('role','tooltip');
				Object.assign(tooltip.style,{
					display:'none', position:'fixed', zIndex:'2147483647', width:'min(330px, calc(100vw - 16px))',
					maxHeight:'min(520px, calc(100vh - 48px))', overflowY:'auto', padding:'8px',
					border:'1px solid var(--vm-ui-panel-border, rgba(255,255,255,.28))', borderRadius:'8px',
					background:'var(--vm-ui-panel-bg, #35103d)', color:'var(--vm-ui-panel-text, var(--vm-ui-text, #222))',
					boxShadow:'var(--vm-ui-shadow, 0 5px 18px rgba(0,0,0,.42))', boxSizing:'border-box', textAlign:'left',
					whiteSpace:'normal', pointerEvents:'auto'
				});
				const tooltipHeading=makeElement('div','',onlineItems.length
					? `Online z włączonym alertem online/offline: ${onlineCount}`
					:'Brak pozycji online z włączonym alertem online/offline');
				Object.assign(tooltipHeading.style,{
					margin:'0 0 6px', color:'var(--vm-ui-accent, #f54da3)', fontSize:'12px', fontWeight:'800'
				});
				tooltip.appendChild(tooltipHeading);
				for (const item of onlineItems) {
					const activeAdUrl=item.state?.activeUrls?.[0] || item.adUrl || item.profileUrls?.[0] || '';
					const row=makeElement(activeAdUrl ? 'a':'div');
					if (activeAdUrl) {
						row.href=activeAdUrl;
						row.target='_blank';
						row.rel='noopener noreferrer';
						row.title='Otwórz aktywny anons';
					}
					Object.assign(row.style,{
						display:'grid', gridTemplateColumns:'42px minmax(0, 1fr)', gap:'8px', alignItems:'center', padding:'5px 0',
						borderTop:tooltip.childElementCount > 1
							? '1px solid var(--vm-ui-panel-border, rgba(255,255,255,.12))'
							:'0', color:'var(--vm-ui-panel-text, var(--vm-ui-text, #222))', textDecoration:'none'
					});
					const imageBox=makeElement('div');
					const initial=normalizeEscortAdText(item.displayName) .charAt(0) .toLocaleUpperCase('pl-PL') || '•';
					imageBox.textContent=initial;
					Object.assign(imageBox.style,{
						position:'relative', display:'flex', alignItems:'center', justifyContent:'center', width:'42px', height:'42px',
						overflow:'hidden', border:'1px solid var(--vm-ui-panel-border, rgba(255,255,255,.18))', borderRadius:'6px',
						background:'var(--vm-ui-panel-bg-alt, rgba(255,255,255,.09))',
						color:'var(--vm-ui-panel-muted, rgba(255,255,255,.75))', fontSize:'17px', fontWeight:'800',
						boxSizing:'border-box'
					});
					if (hasUsefulWatchThumbnail(item)) {
						const image=makeElement('img','',null,{src:item.imageUrl,alt:'',loading:'lazy',referrerPolicy:'no-referrer'});
						Object.assign(image.style,{
							position:'absolute', inset:'0', width:'100%', height:'100%', objectFit:'cover'
						});
						image.addEventListener('error',() => detachHelperNode(image),{once:true});
						imageBox.appendChild(image);
					}
					const details=makeElement('div');
					details.style.minWidth='0';
					const type=makeElement('div','',item.sourceType === 'escort-ad'
						? 'Anons Escort.club'
						:'Profil Escorti');
					Object.assign(type.style,{
						marginBottom:'2px', color:'var(--vm-ui-panel-muted, rgba(255,255,255,.68))', fontSize:'10px'
					});
					const name=makeElement('div','',item.displayName);
					Object.assign(name.style,{
						color:'var(--vm-ui-panel-text, var(--vm-ui-text, #222))', fontSize:'12px', fontWeight:'700', lineHeight:'1.25',
						overflowWrap:'anywhere'
					});
					details.append(type,name);
					row.append(imageBox,details);
					tooltip.appendChild(row);
				}
				const positionTooltip=() => {
					const rect=onlineSummary.getBoundingClientRect();
					const margin=8;
					const tooltipWidth=tooltip.offsetWidth;
					const tooltipHeight=tooltip.offsetHeight;
					const left=Math.min( Math.max(margin,rect.left), Math.max(margin,window.innerWidth - tooltipWidth - margin) );
					const below=rect.bottom + 5;
					const top=below + tooltipHeight <= window.innerHeight - margin
						? below
						:Math.max(margin,rect.top - tooltipHeight - 5);
					tooltip.style.left=`${Math.round(left)}px`;
					tooltip.style.top=`${Math.round(top)}px`;
				};
				const showTooltip=() => {
					tooltip.style.display='block';
					requestAnimationFrame(positionTooltip);
				};
				const hideTooltip=() => {
					tooltip.style.display='none';
				};
				let tooltipHideTimer=null;
				const cancelTooltipHide=() => {
					if (tooltipHideTimer) clearTimeout(tooltipHideTimer);
					tooltipHideTimer=null;
				};
				const scheduleTooltipHide=() => {
					cancelTooltipHide();
					tooltipHideTimer=setTimeout(hideTooltip,180);
				};
				onlineSummary.addEventListener('mouseenter',() => {cancelTooltipHide(); showTooltip();});
				onlineSummary.addEventListener('mouseleave',scheduleTooltipHide);
				onlineSummary.addEventListener('focus',showTooltip);
				onlineSummary.addEventListener('blur',scheduleTooltipHide);
				tooltip.addEventListener('mouseenter',cancelTooltipHide);
				tooltip.addEventListener('mouseleave',scheduleTooltipHide);
				onlineSummary.addEventListener('click',() => {
					hideTooltip();
					openWatchedSettingsModal();
				});
				bar.appendChild(tooltip);
				return onlineSummary;
			};
			if (showOnlineCount) bar.appendChild(createOnlineSummary(!events.length));
			if (showMenu) bar.appendChild(toolbar);
			if (!events.length) {
				if (!requestNotice) Object.assign(bar.style,{
					left:'8px', right:'auto', width:'auto', maxWidth:'calc(100vw - 16px)', gap:'3px',
					padding:showMenu ? '2px 4px':'0', borderRadius:'0 0 7px 7px'
				});
				mountBar();
				return;
			}
			const list=makeElement('div');
			Object.assign(list.style,{
				display:'flex', alignItems:'center', gap:'6px', flex:'1 1 auto', minWidth:'0', overflow:'hidden',
				whiteSpace:'nowrap'
			});
			const eventEntries=[];
			for (const event of events) {
				const entry=makeElement('span');
				Object.assign(entry.style,{
					display:'inline-flex', alignItems:'center', gap:'4px', flex:'0 0 auto', minWidth:'0', padding:'1px 3px 1px 6px',
					border:'1px solid color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 72%, transparent)', borderRadius:'10px',
					whiteSpace:'nowrap'
				});
				const link=makeWatchEventLink(event,event.message || '',true);
				Object.assign(link.style,{
					color:'var(--vm-ui-accent-contrast, #fff)', textDecoration:'none'
				});
				const accept=makeButton('','×',{title:'Oznacz alert jako odczytany bez otwierania strony'});
				accept.setAttribute('aria-label',accept.title);
				Object.assign(accept.style,{
					width:'18px', height:'18px', padding:'0', border:'0', borderRadius:'8px',
					background:'color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 18%, transparent)',
					color:'var(--vm-ui-accent-contrast, #fff)', cursor:'pointer', fontWeight:'800'
				});
				accept.addEventListener('click',click => {stopUiEvent(click); acknowledgeWatchEvent(event.id);});
				entry.append(link,accept);
				list.appendChild(entry);
				eventEntries.push(entry);
			}
			const more=makeButton();
			more.title='Zobacz pozostałe powiadomienia';
			Object.assign(more.style,{
				display:'none', flex:'0 0 auto', padding:'1px 7px',
				border:'1px solid color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 72%, transparent)', borderRadius:'10px',
				background:'color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 14%, transparent)',
				color:'var(--vm-ui-accent-contrast, #fff)', cursor:'pointer', font:'inherit', fontWeight:'800', whiteSpace:'nowrap'
			});
			more.addEventListener('click',openWatchedSettingsModal);
			list.appendChild(more);
			bar.appendChild(list);
			const fitNotificationEntries=() => {
				if (!list.isConnected) return;
				for (const entry of eventEntries) entry.style.display='inline-flex';
				more.style.display='none';
				const available=list.clientWidth;
				const gap=6;
				const allWidth=eventEntries.reduce( (sum,entry,index) => sum + entry.offsetWidth + (index ? gap:0), 0 );
				if (allWidth <= available) return;
				more.textContent=`+${eventEntries.length}`;
				more.style.display='inline-flex';
				const reserve=more.offsetWidth + gap;
				let used=0;
				let visible=0;
				for (const entry of eventEntries) {
					const needed=entry.offsetWidth + (visible ? gap:0);
					if (used + needed + reserve <= available) {
						used += needed;
						visible++;
					} else {
						entry.style.display='none';
					}
				}
				const hidden=eventEntries.length - visible;
				more.textContent=`+${hidden}`;
			};
			const acceptAll=makeButton('','Zaakceptuj wszystkie',{title:'Zaakceptuj wszystkie'});
			Object.assign(acceptAll.style,{
				flex:'0 0 auto', height:'24px', padding:'0 9px',
				border:'1px solid color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 82%, transparent)', borderRadius:'5px',
				background:'color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 15%, transparent)',
				color:'var(--vm-ui-accent-contrast, #fff)', cursor:'pointer', fontSize:'11px', fontWeight:'800', lineHeight:'1',
				whiteSpace:'nowrap'
			});
			acceptAll.addEventListener( 'click', acknowledgeAllAlertWatchEvents );
			bar.appendChild(acceptAll);
			function mountBar(fitEntries) {
				if (!events.length && !requestNotice) {document.body.appendChild(bar); restoreToolbar(); return;}
				const spacer=makeElement('div','',null,{id:WATCH_BAR_SPACER_ID});
				spacer.setAttribute('aria-hidden','true');
				Object.assign(spacer.style,{
					display:'block', width:'100%', height:'0px', flex:'0 0 auto', padding:'0', margin:'0', border:'0',
					pointerEvents:'none', visibility:'hidden'
				});
				document.body.insertBefore(spacer,document.body.firstChild);
				document.body.appendChild(bar);
				restoreToolbar();
				const syncSpacerHeight=() => {
					fitEntries?.();
					syncWatchBarPageOffset(bar,spacer);
				};
				if (typeof ResizeObserver === 'function') {
					const observer=new ResizeObserver(syncSpacerHeight);
					observer.observe(bar);
					bar._vmResizeObserver=observer;
				}
				const onWindowResize=() => requestAnimationFrame(syncSpacerHeight);
				window.addEventListener('resize',onWindowResize);
				bar._vmResizeCleanup=() => window.removeEventListener('resize',onWindowResize);
				requestAnimationFrame(syncSpacerHeight);
			}
			mountBar(fitNotificationEntries);
		};
		if (document.body) render();
		else document.addEventListener('DOMContentLoaded',render,{once:true});
	}
	function prepareHiddenHelperFrame(iframe) {
		iframe.setAttribute('aria-hidden','true');
		iframe.tabIndex=-1;
		Object.assign(iframe.style,{
				position:'fixed', left:'-10000px', top:'-10000px', width:'1px', height:'1px', opacity:'0', pointerEvents:'none',
				border:'0', zIndex:'-999999'
			});
	}
	function createWatchDialogBase(id,titleText) {
		closeHelperDialog(document.getElementById(id));
		const overlay=makeElement('div','',null,{id});
		prepareHelperStyleApi(overlay);
		overlay.classList.add('vm-ui-overlay');
		const box=makeElement('div',"vm-ui-dialog");
		const title=makeElement('h2',"vm-ui-title",titleText);
		box.appendChild(title);
		overlay.appendChild(box);
		document.body.appendChild(overlay);
		return {overlay,box};
	}
	function getTopHelperDialog() {
		return helperDialogStack.findLast(entry => entry.overlay.isConnected &&
			!entry.overlay.hidden && entry.overlay.style.display !== 'none') || null;
	}
	function handleHelperDialogKey(event) {
		if (event.key !== 'Escape' || event.defaultPrevented || event.isComposing) return;
		const top=getTopHelperDialog();
		if (!top) return;
		event.preventDefault();
		event.stopImmediatePropagation();
		if (!event.repeat && top.closeOnEscape) top.close('escape');
	}
	function closeHelperDialog(overlay,reason='replace') {
		if (typeof overlay?._vmClose === 'function') overlay._vmClose(reason);
		else detachHelperNode(overlay);
	}
	function createDialogCloseController(overlay,{
		onClose=null,closeOnBackdrop=true,closeOnEscape=true,beforeClose=null
	}={}) {
		let closed=false;
		const onBackdrop=event => {
			if (event.target === overlay && getTopHelperDialog() === entry) close('backdrop');
		};
		const close=reason => {
			if (closed || beforeClose?.(reason) === false) return;
			closed=true;
			overlay.removeEventListener('click',onBackdrop);
			const index=helperDialogStack.indexOf(entry);
			if (index >= 0) helperDialogStack.splice(index,1);
			if (!helperDialogStack.length) document.removeEventListener('keydown',handleHelperDialogKey,true);
			try {onClose?.(reason);} finally {detachHelperNode(overlay);}
		};
		const entry={overlay,close,closeOnEscape};
		if (!helperDialogStack.length) document.addEventListener('keydown',handleHelperDialogKey,true);
		helperDialogStack.push(entry);
		if (closeOnBackdrop) overlay.addEventListener('click',onBackdrop);
		overlay._vmClose=close;
		return {close,bindButton:button => button.addEventListener('click',() => close('button'))};
	}
	function styleDialogActionButton(button,primary=false) {
		button.classList.add('vm-ui-button');
		button.classList.toggle('-primary',primary);
		return button;
	}
	function createDialogCancelButton(controller) {
		const button=makeButton("vm-ui-button",'Anuluj');
		Object.assign(button.style,{marginTop:'10px'});
		button.addEventListener('click',() => controller.close('cancel'));
		return button;
	}
	function chooseWatchOptionDialog({id,title,info,choices,primaryValue}) {
		return new Promise(resolve => {
			const {overlay,box}=createWatchDialogBase(id,title);
			let result=null;
			const controller=createDialogCloseController(overlay,{
				onClose:() => resolve(result)
			});
			const infoElement=makeElement('p','',info);
			infoElement.style.margin='0 0 14px';
			const actions=makeElement('div');
			Object.assign(actions.style,{display:'grid',gap:'9px'});
			for (const [value,label,disabled=false] of choices) {
				const button=styleDialogActionButton( makeButton('',label), value === primaryValue );
				button.disabled=disabled;
				button.addEventListener('click',() => {
					if (button.disabled) return;
					result=value;
					controller.close('choice');
				});
				actions.appendChild(button);
			}
			const cancel=createDialogCancelButton(controller);
			box.append(infoElement,actions,cancel);
		});
	}
	function chooseWatchPhoneCoverage(profileCount) {
		return chooseWatchOptionDialog({
			id:'vm-garso-watch-phone-scope-overlay', title:'Co chcesz dodać?',
			info:`Ten numer znaleziono w ${profileCount} profilach Escorti.`, choices:[ ['single','Jeden wybrany profil'],
				['all',`Wszystkie profile (${profileCount})`] ], primaryValue:'all'
		});
	}
	function chooseWatchPhoneProfile(profileInfos) {
		return new Promise(resolve => {
			const {overlay,box}=createWatchDialogBase( 'vm-garso-watch-phone-profile-overlay', 'Wybierz profil Escorti' );
			let result=null;
			const controller=createDialogCloseController(overlay,{
				onClose:() => resolve(result)
			});
			const list=makeElement('div');
			Object.assign(list.style,{
				display:'grid', gap:'7px', maxHeight:'55vh', overflow:'auto'
			});
			for (const profile of profileInfos) {
				const button=styleDialogActionButton(makeButton());
				button.textContent=profile.profileName || `Profil Escorti ${getEscortiProfileId(profile.profileUrl)}`;
				button.style.textAlign='left';
				button.addEventListener('click',() => {
					result=profile;
					controller.close('choice');
				});
				list.appendChild(button);
			}
			const cancel=createDialogCancelButton(controller);
			box.append(list,cancel);
		});
	}
	function createWatchLocationTargetControls(current=null) {
		const root=makeElement('div',"vm-watch-location-target-controls vm-ui-panel");
		Object.assign(root.style,{margin:'2px 0 8px 25px'});
		const label=makeElement('label',"vm-ui-label",'Powiadom, gdy pojawi się w mieście:');
		const input=makeElement('input',"vm-ui-field",null,{type:'text',value:current?.type === 'city' ? current.city || '':'',placeholder:'np. Kraków'});
		Object.assign(input.style,{width:'100%'});
		const anyLabel=makeElement('label',"vm-ui-choice");
		const anyInput=makeElement('input','',null,{type:'checkbox',checked:!current || current.type !== 'city'});
		anyInput.style.accentColor=getEscortPagePinkColor();
		anyLabel.append( anyInput, document.createTextNode( 'Dowolne - zgłaszaj każdą zmianę lokalizacji' ) );
		let enabled=true;
		const sync=() => {
			root.style.display=enabled ? 'block':'none';
			input.disabled=!enabled || anyInput.checked;
			input.style.opacity=input.disabled ? '.55':'1';
			anyInput.disabled=!enabled;
		};
		anyInput.addEventListener('change',sync);
		sync();
		root.append(label,input,anyLabel);
		return {
			root, input, anyInput, setEnabled(value) {
				enabled=!!value;
				sync();
			}, read() {
				if (anyInput.checked) return {type:'any',city:''};
				const city=normalizeEscortCity(input.value);
				return city ? {type:'city',city}:null;
			}
		};
	}
	function getCurrentEscortiWatchContextFast() {
		const profileUrl=normalizeWatchUrl(`${location.origin}${location.pathname}`);
		const source=parseEscortiSearchSource(new URLSearchParams(location.hash.slice(1)).get('vm_escorti_source'));
		return {
			sourceType:'escorti-profile', profileUrls:profileUrl ? [profileUrl]:[], adUrl:null, phoneDigits:null,
			resolutionMode:'address-escorti', ...source, escortiProfileOnly:true,
			displayName:extractWatchPageName(document,'Profil Escorti'), imageUrl:extractWatchPageImage(document,profileUrl),
			watchKey:`profiles:${getEscortiProfileId(profileUrl) || profileUrl}`
		};
	}
	async function addWatchedContext(context,mode,locationTarget=null) {
		const candidate=normalizeWatchedItem({
			id:'watch-candidate', ...context, watchKey:getWatchKey(context), modes:{[mode]:true}
		});
		const existing=getWatchedItems().find(item => watchedItemsOverlap(item,candidate) );
		const alreadyWatching=!!existing?.modes?.[mode];
		const item=upsertWatchedItem( context, {[mode]:true}, locationTarget );
		showWatchToast(alreadyWatching
			? 'Ta pozycja jest już zapisana.'
			:(mode === 'online'
				? 'Dodano obserwowanie aktywności.'
				:'Dodano obserwowanie lokalizacji.'));
		await runWatchedChecks({force:true,itemIds:[item.id]});
		document.getElementById(WATCH_SETTINGS_OVERLAY_ID)?._vmRender?.();
		return {item,alreadyWatching};
	}
	async function addWatchFromManualInput(rawValue) {
		const value=String(rawValue || '').trim();
		if (!value) throw new Error('Wklej adres profilu, anonsu albo numer telefonu');
		let url=null;
		try {
			url=new URL(/^https?:\/\//i.test(value) ? value:`https://${value}`);
		} catch {}
		if (url) {
			if ( ['escorti.pl','www.escorti.pl'].includes(url.hostname) && /^\/escort\/\d+\/?$/i.test(url.pathname) ) {
				const profileUrl=normalizeWatchUrl(url.href);
				let info=null;
				try {info=await readEscortiProfileDirect(profileUrl);} catch {}
				return addWatchedContext({
					sourceType:'escorti-profile', profileUrls:[profileUrl], escortiProfileOnly:true,
					displayName:info?.profileName || `Profil Escorti ${getEscortiProfileId(profileUrl)}`,
					imageUrl:info?.imageUrl || '', watchKey:`profiles:${getEscortiProfileId(profileUrl)}`
				},'online');
			}
			if (isEscortClubHost(url.hostname) && !!getEscortAdIdFromPath(url.pathname)) {
				const scope=await chooseFavoriteScope();
				if (!scope) return null;
				const context=await buildFavoriteContextFromEscortAd(url.href,scope.startsWith('person:') ? 'person':scope,scope.startsWith('person:') ? scope.slice(7):null);
				if (!context) return null;
				return addWatchedContext(context,'online');
			}
		}
		const phoneDigits=normalizeEscortCachedPhone(value);
		if (!phoneDigits || phoneDigits.length < 9) {
			throw new Error('Nie rozpoznano adresu ani numeru telefonu');
		}
		let profileUrls=[];
		try {
			const found=await searchEscortiProfilesDirect(phoneDigits);
			profileUrls=found.profileUrls || [];
		} catch {}
		const profileInfos=(await watchMapLimit(profileUrls,2,async profileUrl => {
			try {
				const info=await readEscortiProfileDirect(profileUrl);
				return {
					profileUrl, profileName:normalizeWatchedDisplayName( info?.profileName,
						`Profil Escorti ${getEscortiProfileId(profileUrl)}` ), imageUrl:info?.imageUrl || ''
				};
			} catch {
				return {
					profileUrl, profileName:`Profil Escorti ${getEscortiProfileId(profileUrl)}`, imageUrl:''
				};
			}
		})).filter(Boolean);
		let selectedProfiles=profileInfos;
		let coverage=profileInfos.length > 1
			? await chooseWatchPhoneCoverage(profileInfos.length)
			:'single';
		if (!coverage) return null;
		if (coverage === 'single' && profileInfos.length > 1) {
			const selected=await chooseWatchPhoneProfile(profileInfos);
			if (!selected) return null;
			selectedProfiles=[selected];
		}
		const selectedProfileUrls=selectedProfiles.map(profile => profile.profileUrl);
		const formattedPhone=phoneDigits.replace(/(\d{3})(?=\d)/g,'$1 ');
		const selectedName=selectedProfiles.length === 1
			? selectedProfiles[0].profileName
			:(selectedProfiles.length
				? `Wszystkie profile numeru ${formattedPhone}`
				:`Telefon ${formattedPhone}`);
		return addWatchedContext({
			sourceType:selectedProfileUrls.length ? 'escorti-profile':'phone', profileUrls:selectedProfileUrls,
			...(coverage === 'single' && selectedProfileUrls.length ? {escortiProfileOnly:true}:{}),
			phoneDigits:coverage === 'all' || !selectedProfileUrls.length
				? phoneDigits
				:null, displayName:selectedName || `Telefon ${formattedPhone}`, imageUrl:selectedProfiles[0]?.imageUrl || '',
			watchKey:selectedProfileUrls.length
				? `profiles:${selectedProfileUrls.map(getEscortiProfileId).filter(Boolean).sort().join(',')}`
				:`phone:${phoneDigits}`
		},'online');
	}
	function getEscortiAdFavoriteNoteState(adUrl) {
		const normalizedUrl=normalizeEscortiAdUrl(adUrl);
		if (!normalizedUrl) return {favorite:false,note:false,noteText:''};
		let favorite=false;
		let noteText='';
		for (const item of getWatchedItems()) {
			if (item?.sourceType !== 'escort-ad') continue;
			if (normalizeEscortiAdUrl(item.adUrl) !== normalizedUrl) continue;
			if (item.favorite) favorite=true;
			if (item.note) noteText=String(item.note).trim();
		}
		return {favorite,note:!!noteText,noteText};
	}
	function renderEscortAdLinkStatusMarker(marker,result) {
		if (!marker) return;
		marker.replaceChildren();
		if (result?.stale && typeof result.active === 'boolean') {
			marker.textContent=result.active ? '✓':'×';
			marker.style.color='#9ca3af';
			marker.title=`Ostatni zapis: anons ${result.active ? 'online':'offline'} • wynik starszy niż 30 minut${result.refreshFailed ? ' • odświeżenie nie powiodło się':''}`;
		} else if (result?.unknown || !result) {
			marker.textContent='?';
			marker.style.color='#d97706';
			marker.title='Nie udało się sprawdzić aktywności';
		} else if (result.active) {
			marker.textContent='✓';
			marker.style.color='#16a34a';
			marker.title='Anons online';
		} else {
			marker.append(document.createTextNode('×'));
			marker.style.color='#dc2626';
			marker.title='Anons offline';
		}
		const visitedCard=marker.closest('.vm-garso-visited-card');
		if ((result?.active === false && !result.unknown) || (visitedCard && (!result || result.unknown))) {
			const adId=parseAdIdFromUrl(marker.dataset.adUrl);
			const cacheHost=adId && getEscortCachedAdHost(adId);
			if (cacheHost) {
				const cacheBadge=makeElement('a','vm-escorti-ad-cache-badge',visitedCard ? '(cache)':'cache',{href:`https://${cacheHost}${getEscortAdPath(adId,cacheHost)}#${ESCORT_CACHED_AD_HASH_KEY}=${adId}`,target:'_blank',rel:'noopener noreferrer',title:`Otwórz zapisaną wersję (${cacheHost})`});
				Object.assign(cacheBadge.style,{
					display:'inline-block', padding:'1px 3px', border:'1px solid var(--vm-ui-border,rgba(95,95,105,.42))',
					borderRadius:'4px', color:'var(--vm-ui-text-muted,#777)', background:'var(--vm-ui-bg-alt,rgba(255,255,255,.55))',
					fontSize:'8px', fontWeight:'600', lineHeight:'1.15', letterSpacing:'0'
				});
				marker.appendChild(cacheBadge);
				marker.title+=' • dostępna zapisana wersja z cache';
			} else if (visitedCard) marker.title+=' • Brak pełnej kopii w cache. Historia odwiedzin i skrócone dane są zapisywane osobno.';
		}
		marker.setAttribute('aria-label',marker.title);
		marker.dataset.vmAdStatus=!result || result.unknown ? 'unknown':result.active === false ? 'offline':'online';
		marker.dataset.vmStatusReady='1';
	}
	function getAvailableEscortAdUrls(results) {
		const urls=new Set();
		for (const [url,result] of results) {
			if (!result || result.unknown) continue;
			if (result.active === true) urls.add(url);
			else if (result.active === false) {
				const id=parseAdIdFromUrl(url),host=id && getEscortCachedAdHost(id);
				if (host) urls.add(addEscortCachedArchiveMarkerToUrl(`https://${host}${getEscortAdPath(id,host)}`));
			}
		}
		return [...urls];
	}
	function openAvailableEscortAds(results,event) {
		stopUiEvent(event);
		for (const url of getAvailableEscortAdUrls(results)) GM_openInTab(url,{active:false,insert:true});
	}
	function updateOpenAvailableEscortAdsButton(button,results,pending=false) {
		if (!button) return;
		const count=getAvailableEscortAdUrls(results).length;
		button.disabled=pending || !count;
		button.style.opacity=button.disabled ? '.45':'1';
		button.style.cursor=button.disabled ? 'default':'pointer';
		const label=count ? `Otwórz wszystkie (${count})`:'Otwórz wszystkie';
		if (button.textContent !== label) button.textContent=label;
		button.title=pending ? 'Trwa sprawdzanie dostępności anonsów…'
			:'Otwórz w nowych kartach anonse online oraz zapisane kopie anonsów offline';
	}
	function initEscortiProfileAdLinkStatuses() {
		if (!/^\/escort\/\d+\/?$/i.test(location.pathname)) return;
		if (new URL(location.href).searchParams.has('vm_mode')) return;
		const statusByUrl=new Map();
		const pendingUrls=new Set();
		const OPEN_ALL_AVAILABLE_ID='vm-escorti-open-all-available';
		const refreshFavoriteNoteMarker=marker => {
			if (!marker) return;
			const state=getEscortiAdFavoriteNoteState(marker.dataset.adUrl);
			const features=getWatchSettings();
			const showFavorite=features.enabled && state.favorite;
			const showNote=features.enabled && state.note;
			const hiddenMatches=getHiddenAdMatches(parseAdIdFromUrl(marker.dataset.adUrl))
				.filter(item => item.sourceType === 'escort-ad');
			marker.textContent=showNote ? '✎':'';
			if (showFavorite) {
				const star=makeElement('span','','★');
				star.style.color='#f4c542';
				marker.prepend(star);
			}
			if (hiddenMatches.length) marker.appendChild(createHiddenIcon());
			marker.style.visibility=showFavorite || showNote || hiddenMatches.length ? 'visible':'hidden';
			setControlTitle(marker,[ showFavorite ? 'Anons w ulubionych':'', showNote ? `Notatka: ${state.noteText}`:'',
				hiddenMatches.length ? `Ukryty w wynikach: ${[...new Set(hiddenMatches.map(item => item.sourceType === 'escort-ad' ? 'anons':'cały profil'))].join(' + ')}`:''
			].filter(Boolean).join(' • '));
		};
		const refreshAllFavoriteNoteMarkers=() => document.querySelectorAll('.vm-escorti-ad-favorite-note-status').forEach(refreshFavoriteNoteMarker);
		hiddenUiRefreshers.add(refreshAllFavoriteNoteMarkers);
		const refreshOpenAllAvailableButton=heading => {
			if (!heading) return;
			let button=document.getElementById(OPEN_ALL_AVAILABLE_ID);
			if (!button) {
				button=prepareHelperStyleApi(makeButton('','Otwórz wszystkie'));
				button.id=OPEN_ALL_AVAILABLE_ID;
				button.type='button';
				Object.assign(button.style,{
					display:'block',margin:'6px auto 10px',padding:'5px 10px',
					border:'1px solid var(--vm-ui-border,#bbb)',borderRadius:'5px',background:'var(--vm-ui-bg,#fff)',
					color:'var(--vm-ui-text,#555)',cursor:'pointer',fontSize:'11px',fontWeight:'700',
					position:'relative',zIndex:'2147483647',pointerEvents:'auto', touchAction:'manipulation'
				});
				heading.insertAdjacentElement('afterend',button);
			}
			if (button.dataset.vmOpenAllHandlerVersion !== SCRIPT_VERSION) {
				const replacement=button.cloneNode(true);
				replacement.dataset.vmOpenAllHandlerVersion=SCRIPT_VERSION;
				replacement.addEventListener('click',event => openAvailableEscortAds(statusByUrl,event));
				button.replaceWith(replacement);
				button=replacement;
			}
			Object.assign(button.style,{
				position:'relative',zIndex:'2147483647',pointerEvents:'auto', touchAction:'manipulation'
			});
			updateOpenAvailableEscortAdsButton(button,statusByUrl,pendingUrls.size > 0);
		};
		const showResultForUrl=(adUrl,result) => {
			if (!adUrl) return;
			statusByUrl.set(adUrl,result || null);
			for (const marker of document.querySelectorAll( '.vm-escorti-ad-online-status' )) {
				if (marker.dataset.adUrl === adUrl) {
					renderEscortAdLinkStatusMarker(marker,result);
				}
			}
			const heading=getEscortiAdLinkSection()?.heading;
			refreshOpenAllAvailableButton(heading);
		};
		const checkRows=rows => {
			const urls=[...new Set( rows .map(row => row.adUrl)
					.filter(adUrl => !statusByUrl.has(adUrl) && !pendingUrls.has(adUrl)) )];
			if (!urls.length) return;
			urls.forEach(adUrl => pendingUrls.add(adUrl));
			updateOpenAvailableEscortAdsButton(document.getElementById(OPEN_ALL_AVAILABLE_ID),statusByUrl,true);
			scanActiveEscortAds(urls,progress => {
				const resultUrl=normalizeEscortiAdUrl(progress?.result?.url);
				if (resultUrl) showResultForUrl(resultUrl,progress.result);
			},false,{activityOnly:true,useActivityCache:true,cacheActivity:true}) .then(summary => {
					const byUrl=new Map( (summary?.results || []).map(result => [ normalizeEscortiAdUrl(result?.url) || result?.url,
							result ]) );
					for (const adUrl of urls) {
						if (!statusByUrl.has(adUrl)) {
							showResultForUrl(adUrl,byUrl.get(adUrl));
						}
					}
				}) .catch(() => {
					for (const adUrl of urls) {
						if (!statusByUrl.has(adUrl)) showResultForUrl(adUrl,null);
					}
				}) .finally(() => {
					urls.forEach(adUrl => pendingUrls.delete(adUrl));
					updateOpenAvailableEscortAdsButton(document.getElementById(OPEN_ALL_AVAILABLE_ID),statusByUrl,pendingUrls.size > 0);
				});
		};
		const insert=() => {
			const section=getEscortiAdLinkSection();
			if (!section) return false;
			const {heading}=section;
			refreshOpenAllAvailableButton(heading);
			const rows=[];
			for (const anchor of section.elements.filter(node => node.matches('a[href]'))) {
				if (anchor.closest('[id^="vm-"], [class*="vm-"]')) continue;
				const adUrl=normalizeEscortiAdUrl(anchor.getAttribute('href'),location.href);
				if (!adUrl) continue;
				let marker=[...(anchor.parentElement?.children || [])].find(element =>
					element.classList?.contains('vm-escorti-ad-online-status') && element.dataset.adUrl === adUrl );
				if (!marker) {
					const row=makeElement('div','vm-escorti-ad-status-row');
					Object.assign(row.style,{
						display:'grid', gridTemplateColumns:'44px auto 58px', alignItems:'center', columnGap:'3px', width:'fit-content',
						minWidth:'0', maxWidth:'100%', margin:'0 auto', verticalAlign:'middle'
					});
					const favoriteNoteMarker=makeElement('span',"vm-escorti-ad-favorite-note-status");
					favoriteNoteMarker.dataset.adUrl=adUrl;
					Object.assign(favoriteNoteMarker.style,{
						display:'inline-flex',alignItems:'center',justifyContent:'flex-end',
						width:'44px',minWidth:'44px',overflow:'visible',gap:'2px',
						color:getEscortiUiAccentColor(),fontSize:'13px',fontWeight:'800',
						lineHeight:'1',whiteSpace:'nowrap',letterSpacing:'-1px'
					});
					refreshFavoriteNoteMarker(favoriteNoteMarker);
					marker=makeElement('span');
					marker.className='vm-escorti-ad-online-status';
					marker.dataset.adUrl=adUrl;
					marker.textContent='…';
					setControlTitle(marker,'Sprawdzanie aktywności anonsu');
					Object.assign(marker.style,{
						width:'58px', minWidth:'58px', display:'inline-flex', alignItems:'center', justifyContent:'center', gap:'4px',
						textAlign:'center', color:'#9ca3af', fontSize:'18px', fontWeight:'900', lineHeight:'1'
					});
					const linkCell=makeElement('span','vm-escorti-ad-link-cell');
					Object.assign(linkCell.style,{
						display:'inline-flex',alignItems:'center',gap:'3px',minWidth:'0',maxWidth:'100%'
					});
					anchor.parentElement.insertBefore(row,anchor);
					linkCell.appendChild(anchor);
					if (SETTINGS.showTopPhoneSearch !== false) linkCell.appendChild(makeGarsoInlineSearchButton(adUrl));
					row.append(favoriteNoteMarker,linkCell,marker);
					Object.assign(anchor.style,{
						width:'auto', minWidth:'0'
					});
				}
				rows.push({adUrl,marker});
			}
			if (!rows.length) return false;
			for (const row of rows) {
				if (statusByUrl.has(row.adUrl)) {
					renderEscortAdLinkStatusMarker(row.marker,statusByUrl.get(row.adUrl));
				}
			}
			checkRows(rows);
			return true;
		};
		const scheduleInsert=createAnimationFrameScheduler(insert);
		const observer=new MutationObserver(mutations => {
			if (mutations.some(mutation => !mutation.target?.closest?.('[id^="vm-"], [class*="vm-"]'))) {
				scheduleInsert();
			}
		});
		const startObserver=() => {
			if (!document.body) return false;
			observer.observe(document.body,{childList:true,subtree:true});
			return true;
		};
		if (document.body) {
			startObserver();
			insert();
		} else {
			document.addEventListener('DOMContentLoaded',() => {
				startObserver();
				insert();
			},{once:true});
		}
		let attempts=0;
		const timer=setInterval(() => {
			if (insert() || ++attempts >= 40) clearInterval(timer);
		},250);
	}
	function normalizeWatchedItem(item) {
		if (!item || typeof item !== 'object' || !item.id) return null;
		const customDisplayName=normalizeEscortAdText(item.customDisplayName).slice(0,120);
		const profileUrls=[...new Set( (Array.isArray(item.profileUrls) ? item.profileUrls:[]) .map(normalizeWatchUrl)
				.filter(Boolean) )];
		const adUrl=item.adUrl
			? normalizeEscortiAdUrl(item.adUrl) || normalizeWatchUrl(item.adUrl)
			:null;
		const phoneDigits=normalizeEscortCachedPhone(item.phoneDigits);
		const resolvedAdUrls=[...new Set( (Array.isArray(item.resolvedAdUrls) ? item.resolvedAdUrls:[])
				.map(url => normalizeEscortiAdUrl(url)) .filter(Boolean) )];
		const targetType=item.locationTarget?.type === 'city' ? 'city':'any';
		const targetCity=targetType === 'city'
			? normalizeEscortCity(item.locationTarget?.city)
			:'';
		const note=String(item.note || '').replace(/\r\n/g,'\n').trim().slice(0,2500);
		const categories=normalizeFavoriteAssignedCategories(item.categories);
		const state=item.state && typeof item.state === 'object' ? item.state:null;
		const sourceType=['escorti-profile','escort-ad','phone','escort-search'].includes(item.sourceType)
			? item.sourceType:(adUrl ? 'escort-ad':(phoneDigits ? 'phone':'escorti-profile'));
		const search=getEscortiWatchSearchValue({...item,sourceType,adUrl,phoneDigits});
		return {
			id:String(item.id), sourceType, watchKey:search ? `escorti-search:${search}`:String(item.watchKey || ''),
			displayName:customDisplayName || normalizeWatchedDisplayName(item.displayName,'Ulubione'),
			customDisplayName:customDisplayName || null, imageUrl:normalizeWatchThumbnailUrl(item.imageUrl), profileUrls, adUrl,
			resolvedAdUrls, phoneDigits:phoneDigits || null,
			...(item.escortiProfileOnly === true || item.phoneProfileOnly === true ? {escortiProfileOnly:true}:{}), ...(item.sourceType === 'escort-search' ? {
				search:normalizeFavoriteSearch(item.search), knownAdIds:[...new Set([
					...(Array.isArray(item.knownAdIds) ? item.knownAdIds:[]), ...resolvedAdUrls.map(parseAdIdFromUrl),
					...(Array.isArray(state?.activeUrls) ? state.activeUrls:[]).map(parseAdIdFromUrl)
				].filter(id => /^\d+$/.test(String(id))).map(String))]
			}:{}), favorite:item.favorite !== false,
			...(item.preserveProfile === true ? {preserveProfile:true}:{}),
			...(item.sourceType !== 'escort-ad' && item.keepProfile === true && !item.favorite && !item.hidden && !watchedItemHasNotesOrCategories(item) ? {keepProfile:true}:{}),
			hidden:item.hidden === true, hiddenAt:Number(item.hiddenAt) || 0,
			hiddenLastCheckedAt:Number(item.hiddenLastCheckedAt) || 0,
			hiddenLastError:item.hiddenLastError ? String(item.hiddenLastError):null, note, categories,
			resolutionMode:normalizeStoredWatchProfileResolutionMode( item.resolutionMode ), modes:{
				online:!!item.modes?.online, location:!!item.modes?.location, changes:!!item.modes?.changes, newAds:
					item.sourceType !== 'escort-ad' && !!item.modes?.newAds
			}, locationTarget:{
				type:targetType, city:targetCity
			}, createdAt:Number(item.createdAt) || Date.now(), lastCheckedAt:Number(item.lastCheckedAt) || 0, lastFullCheckedAt:
				Number(item.lastFullCheckedAt) || 0, lastError: item.lastError
					? String(item.lastError)
					:null, state:state ? {
				online:typeof state.online === 'boolean' ? state.online:null, activeCount:Number(state.activeCount) || 0,
				totalAds:Number(state.totalAds) || 0, cities:Array.isArray(state.cities)
					? state.cities.map(normalizeEscortCity).filter(Boolean)
					:[], activeUrls:Array.isArray(state.activeUrls)
					? state.activeUrls.map(url => normalizeEscortiAdUrl(url)).filter(url => url && (item.sourceType !== 'escort-search' || resolvedAdUrls.includes(url)))
					:[], profileDates:Array.isArray(state.profileDates)
					? state.profileDates.map(formatEscortiDate).filter(Boolean)
					:[], unknownCount:Math.max(0,Number(state.unknownCount) || 0), partial:!!state.partial,
				locationPartial:!!state.locationPartial, checkedAt:Number(state.checkedAt) || 0,
				lastKnownCities:(Array.isArray(state.lastKnownCities)?state.lastKnownCities:Array.isArray(state.cities)?state.cities:[]).map(normalizeEscortCity).filter(Boolean),
				escortiData:state.escortiData&&typeof state.escortiData==='object'&&!Array.isArray(state.escortiData)?state.escortiData:null
			}:null
		};
	}
	function readWatchedAdDataMirror() {
		if (watchedAdDataMirrorMemory) return watchedAdDataMirrorMemory;
		try {
			const stored=GM_getValue(WATCH_AD_DATA_MIRROR_STORAGE_KEY,{});
			watchedAdDataMirrorMemory=stored && typeof stored === 'object' && !Array.isArray(stored) ? stored:{};
		} catch {
			watchedAdDataMirrorMemory={};
		}
		return watchedAdDataMirrorMemory;
	}
	function getWatchedAdDataMirror(adId) {
		const value=readWatchedAdDataMirror()[String(adId || '')];
		return value && typeof value === 'object' && !Array.isArray(value) ? value:null;
	}
	function makeWatchedAdDataMirrorValue(data) {
		if (!data || typeof data !== 'object') return null;
		const keepDescription=normalizeFavoritesDataColumns(readHelperSettingsSnapshot().favoritesDataColumns).includes('ad:description') || getFavoriteTilesOptions().extra.includes('ad:description');
		const value={
			status:data.status || 'ok', adId:data.adId || null, adUrl:data.adUrl || null, title:data.title || null,
			imageUrl:data.imageUrl || null, phone:data.phone || null, phoneDigits:data.phoneDigits || null,
			datePosted:data.datePosted || null, oldestPhotoDate:data.oldestPhotoDate || null, location:data.location || null,
			availability:data.availability && typeof data.availability === 'object'
				? {...data.availability}
				:null, prices:data.prices && typeof data.prices === 'object'
				? {...data.prices}
				:null, stats:data.stats && typeof data.stats === 'object'
				? {...data.stats}
				:null, tags:Array.isArray(data.tags) ? [...data.tags]:[], isAgencyOrSalonPhone:data.isAgencyOrSalonPhone ?? null,
			lastVisitedAt:Number(data.lastVisitedAt) || null, checkedAt:Number(data.checkedAt) || null
		};
		if (keepDescription) value.description=data.description || null;
		return value;
	}
	function areJsonCacheValuesEqual(first,second) {
		if (first === second) return true;
		try {return JSON.stringify(first) === JSON.stringify(second);}
		catch {return false;}
	}
	function storeWatchedAdDataMirror(adId,data) {
		const id=String(adId || '');
		const clean=makeWatchedAdDataMirrorValue(data);
		if (!id || !clean) return;
		const mirror=readWatchedAdDataMirror();
		if (areJsonCacheValuesEqual(mirror[id],clean)) return;
		mirror[id]=clean;
		try {writeHelperStorageValue(WATCH_AD_DATA_MIRROR_STORAGE_KEY,mirror);} catch {}
	}
	function getWatchedAdDataMirrorIds() {
		const events=getWatchEvents();
		const ids=new Set(getWatchedItems() .flatMap(item => getWatchedItemHistoricalAdUrls(item,events))
			.map(parseAdIdFromUrl).filter(Boolean).map(String));
		for (const key of getEscortAdDataCacheKeys()) {
			const value=persistentCacheCatalog?.get(key) || readPersistentCacheValue(key,null);
			const match=String(key).match(/^vm_escort_ad_data_(\d+)$/i);
			if (match && Number(value?.lastVisitedAt) && getEscortAdPhoneDigits(value)) ids.add(match[1]);
		}
		return ids;
	}
	async function rebuildWatchedAdDataMirrorFromCentralCache() {
		if (CURRENT_HOST !== 'pl.escort.club' || (deferredNonTopicCache && !persistentCacheCatalog)) return 0;
		const wantedIds=getWatchedAdDataMirrorIds();
		await ensurePersistentCacheKeys([...wantedIds].map(escortAdDataCacheKey));
		const mirror=readWatchedAdDataMirror();
		let changed=0;
		for (const id of wantedIds) {
			const data=getStoredEscortAdData(id);
			if (!data || Number(mirror[id]?.checkedAt) > Number(data.checkedAt)) continue;
			const clean=makeWatchedAdDataMirrorValue(data);
			if (!clean || areJsonCacheValuesEqual(mirror[id],clean)) continue;
			mirror[id]=clean;
			changed++;
		}
		const retainedIds=getWatchedAdDataMirrorIds();
		for (const id of Object.keys(mirror)) {
			if (!persistentCacheUsesIndexedDb || retainedIds.has(id)) continue;
			delete mirror[id];
			changed++;
		}
		if (changed) {
			try {writeHelperStorageValue(WATCH_AD_DATA_MIRROR_STORAGE_KEY,mirror);} catch {}
		}
		return changed;
	}
	function getWatchedItemAdUrls(item,activeOnly=false) {
		const source=activeOnly
			? (item?.state?.activeUrls || [])
			:[ item?.adUrl, ...(item?.resolvedAdUrls || []), ...(item?.state?.activeUrls || []) ];
		return [...new Set( source.map(url => normalizeEscortiAdUrl(url)).filter(Boolean) )];
	}
	function getWatchedItemHistoricalAdUrls(item,events=null) {
		const urls=new Map();
		const addId=id => {
			const value=String(id || '');
			if (!/^\d+$/.test(value)) return;
			urls.set(value,`https://pl.escort.club/anons/${value}.html`);
		};
		const addUrl=url => {
			const normalized=normalizeEscortiAdUrl(url);
			const id=parseAdIdFromUrl(normalized);
			if (id) urls.set(String(id),normalized);
		};
		if (item?.sourceType === 'escort-search') for (const id of item.knownAdIds || []) addId(id);
		for (const url of getWatchedItemAdUrls(item,false)) addUrl(url);
		const itemId=String(item?.id || '');
		const history=Array.isArray(events) ? events:getWatchEvents();
		for (const event of history) {
			if (!itemId || String(event?.itemId || '') !== itemId) continue;
			addUrl(event?.url);
			for (const source of [event?.message,event?.description]) {
				for (const line of String(source || '').split(/\r?\n/)) {
					if (!/anons/i.test(line)) continue;
					for (const match of line.matchAll(/\b(\d{3,7})\b/g)) addId(match[1]);
				}
			}
		}
		return [...urls.values()];
	}
	function getWatchedItemVisitedAdUrls(item,mirror=null,events=null) {
		const dataMirror=mirror && typeof mirror === 'object' ? mirror:readWatchedAdDataMirror();
		const urls=new Map();
		const phones=new Set();
		const addUrl=url => {
			const normalized=normalizeEscortiAdUrl(url);
			const id=parseAdIdFromUrl(normalized);
			if (id) urls.set(String(id),normalized);
		};
		const ownPhone=normalizeEscortCachedPhone(item?.phoneDigits);
		if (ownPhone) phones.add(ownPhone);
		for (const url of getWatchedItemHistoricalAdUrls(item,events)) {
			addUrl(url);
			const id=parseAdIdFromUrl(url);
			const phone=getEscortAdPhoneDigits(dataMirror[String(id || '')]);
			if (phone) phones.add(phone);
		}
		if (phones.size) {
			for (const [id,data] of Object.entries(dataMirror)) {
				if (!Number(data?.lastVisitedAt)) continue;
				const phone=getEscortAdPhoneDigits(data);
				if (!phone || !phones.has(phone)) continue;
				addUrl(data?.adUrl || `https://pl.escort.club/anons/${id}.html`);
			}
		}
		return [...urls.values()];
	}
	function getWatchedItemCachedAdDetails(item,activeOnly=true) {
		let urls=getWatchedItemAdUrls(item,activeOnly);
		if (activeOnly && !urls.length) urls=getWatchedItemAdUrls(item,false);
		return urls .map(url => {
				const adId=parseAdIdFromUrl(url);
				if (!adId) return null;
				const central=getStoredEscortAdData(adId);
				const mirror=getWatchedAdDataMirror(adId);
				if (!central && !mirror) return null;
				const adData={
					...(mirror || {}), ...(central || {}), imageUrl:central?.imageUrl || mirror?.imageUrl || null
				};
				return {url,adId:String(adId),adData};
			}) .filter(Boolean);
	}
	function storeWatchedAdDataInCentralCache(adId,adData={},checkedAt=Date.now(),watchSnapshot=undefined) {
		if (!persistentCacheKeyIsLoaded(escortAdDataCacheKey(adId))) {
			ensurePersistentCacheKeys([escortAdDataCacheKey(adId)]).then(() => storeWatchedAdDataInCentralCache(adId,adData,checkedAt,watchSnapshot)).catch(error => log('Nie udało się scalić zapisu cache',error));
			return null;
		}
		const id=String(adId || '');
		if (!id) return null;
		try {
			const existing=getStoredEscortAdData(id);
			const value={
				status:'ok', ...(existing || {}), ...(adData || {}), imageUrl:adData?.imageUrl || existing?.imageUrl || null,
				...(watchSnapshot !== undefined
					? {
						watchSnapshot, watchSnapshotCheckedAt:Number(checkedAt) || Date.now()
					}
					:{}), checkedAt:Math.max( Number(existing?.checkedAt) || 0, Number(checkedAt) || 0 ) || Date.now()
			};
			writePersistentCacheValue(escortAdDataCacheKey(id),value);
			storeWatchedAdDataMirror(id,value);
			registerEscortDataFieldsFromAdData(value);
			escortAdDataMemoryCache.set(id,value);
			return value;
		} catch (error) {
			log(`Nie udało się scalić danych obserwowanego anonsu ${id} z cache`,error);
			return null;
		}
	}
	const WATCH_SYSTEM_NOTIFICATION_CLAIM_PREFIX='vm_garso_watch_notification_claim:';
	let watchNotificationPrunedAt=0;
	const watchSystemNotificationsPending=new Set();
	const readWatchNotificationStorage=key=>typeof GM!=='undefined'&&typeof GM.getValue==='function'?GM.getValue(key,null):Promise.resolve(GM_getValue(key,null));
	const writeWatchNotificationStorage=(key,value)=>typeof GM!=='undefined'&&typeof GM.setValue==='function'?GM.setValue(key,value):Promise.resolve(writeHelperStorageValue(key,value));
	async function pruneWatchSystemNotificationClaims() {
		const now=Date.now(); if (now-watchNotificationPrunedAt<600000) return;
		for (const key of GM_listValues()) if (key.startsWith(WATCH_SYSTEM_NOTIFICATION_CLAIM_PREFIX) && !(Number((await readWatchNotificationStorage(key))?.expiresAt)>now)) deleteHelperStorageValue(key);
		watchNotificationPrunedAt=now;
	}
	async function showWatchSystemNotification(event) {
		const enabled=s => s.watchEnabled && s.watchSystemNotifications;
		const grouped=Array.isArray(event),events=(grouped ? event:[event]).filter(value => value?.id && value.alertTriggered && !value.acknowledged),claims=[];
		const owner=`${WATCH_INSTANCE_ID}:${Math.random().toString(36).slice(2)}`;
		const release=async key => {if ((await readWatchNotificationStorage(key))?.owner===owner) await writeWatchNotificationStorage(key,null);};
		try {
			if (!events.length || !enabled(getSettings()) || typeof GM_notification!=='function') return false;
			await pruneWatchSystemNotificationClaims();
			for (const value of events) {
				const adId=value.type==='new-ad' ? getWatchEventAdId(value):'';
				const key=WATCH_SYSTEM_NOTIFICATION_CLAIM_PREFIX+(adId ? `${value.itemId}:new-ad:${adId}`:value.id);
				if (watchSystemNotificationsPending.has(key)) continue;
				watchSystemNotificationsPending.add(key); claims.push({key,id:value.id});
				if (!(Number((await readWatchNotificationStorage(key))?.expiresAt)>Date.now()))
					await writeWatchNotificationStorage(key,{owner,expiresAt:Date.now()+7*86400000});
			}
			if (!claims.length) return false;
			await new Promise(resolve => setTimeout(resolve,120));
			const settings=getSettings(),storedEvents=await readWatchNotificationStorage(WATCH_EVENTS_STORAGE_KEY),current=[];
			const stored=new Map((Array.isArray(storedEvents)?storedEvents:[]).map(value => [value.id,value]));
			for (const {key,id} of claims) {
				if ((await readWatchNotificationStorage(key))?.owner!==owner) continue;
				const value=stored.get(id);
				if (enabled(settings) && value?.alertTriggered && !value.acknowledged) current.push(value);
				else await release(key);
			}
			if (!current.length) return false;
			await GM_notification({
				title:settings.watchNotificationAnonymous ? 'Powiadomienie':'Escort Helper',
				text:settings.watchNotificationAnonymous ? normalizeWatchNotificationText(settings.watchNotificationText)
					:grouped ? `Nowe alerty: ${current.length}. Pozycje: ${new Set(current.map(value => value.itemId)).size}.`:current[0].message,
				tag:`watch:${grouped ? 'summary':claims[0].key.slice(WATCH_SYSTEM_NOTIFICATION_CLAIM_PREFIX.length)}`,
				onclick:() => {if (grouped) openWatchedSettingsModal(); else if (current[0].url) void openWatchEvent(current[0]); else acknowledgeWatchEvent(current[0].id);}
			});
			return true;
		} catch (error) {
			for (const {key} of claims) try {await release(key);} catch {}
			log('Nie udało się wyświetlić powiadomienia systemowego',error); return false;
		} finally {for (const {key} of claims) watchSystemNotificationsPending.delete(key);}
	}
	function getWatchEventAdId(event) {
		const fromUrl=parseAdIdFromUrl(event?.url);
		if (fromUrl) return String(fromUrl);
		for (const source of [event?.message,event?.description]) {
			const match=String(source || '').match(/(?:nowy\s+anons(?:\s+profilu)?|anons)\s*[:#-]?\s*(\d{3,7})/i);
			if (match) return match[1];
		}
		return '';
	}
	function getWatchTransitionEventId(itemId,transitionAt,type,message,description) {
		let hash=14695981039346656037n;
		for(const char of JSON.stringify([type,message,description]))hash=BigInt.asUintN(64,(hash^BigInt(char.codePointAt(0)))*1099511628211n);
		return `change:${itemId}:${transitionAt}:${hash.toString(36)}`;
	}
	function createWatchEvent( item, type, message, url, description='', {
			alertTriggered=false, alertMode='', transitionAt=0, notificationEvents=null
		}={} ) {
		const events=getWatchEvents(),adId=type === 'new-ad' ? getWatchEventAdId({message,description,url}):'';
		const transitionId=Number(transitionAt)>0?getWatchTransitionEventId(item.id,transitionAt,type,message,description):'';
		const existing=transitionId&&events.find(event=>event.id===transitionId);if(existing)return existing;
		if (type === 'new-ad') {
			const duplicate=adId && events.find(event => event?.type === 'new-ad' && String(event?.itemId || '') === String(item.id) && getWatchEventAdId(event) === adId);
			if (duplicate) return duplicate;
		}
		const event={
			id:type === 'new-ad' && adId
				? `new-ad:${item.id}:${adId}`
				:transitionId||`${Date.now()}-${Math.random().toString(36).slice(2)}`, itemId:item.id, type, name:item.displayName, message,
			description:String(description || '').slice(0,5000),
			url:url || item.state?.activeUrls?.[0] || item.profileUrls?.[0] || item.adUrl || '', timestamp:Date.now(),
			acknowledged:false, alertTriggered:!!alertTriggered, alertMode: String( alertMode || watchEventTypeToAlertMode(type)
				)
		};
		events.unshift(event);
		saveWatchEvents(events);
		if (notificationEvents) {if (event.alertTriggered) notificationEvents.push(event);}
		else showWatchSystemNotification(event);
	}
	function hasFavoriteStar(item) {
		return !!item?.favorite && item.sourceType !== 'escort-search';
	}
	function getWatchItemSourcePhone(item) {
		const own=getEscortAdPhoneDigits(item) || normalizeEscortCachedPhone(String(item.watchKey || '').match(/^phone:(\d+)$/)?.[1]);
		if (own) return own;
		const phones=new Set(getWatchedItemCachedAdDetails(item,false)
			.map(({adData}) => getEscortAdPhoneDigits(adData)).filter(Boolean));
		return phones.size === 1 ? phones.values().next().value:'';
	}
	function watchItemSourceText(item) {
		if (item.sourceType === 'escort-search') return 'Wyszukiwarka';
		if (item.sourceType === 'escort-ad') return 'Anons';
		if (item.escortiProfileOnly || item.phoneProfileOnly) return 'Escorti.pl';
		const mode=normalizeStoredWatchProfileResolutionMode(item.resolutionMode);
		const phoneDigits=mode.startsWith('phone-') || !item.adUrl ? getWatchItemSourcePhone(item):'';
		const phone=mode.startsWith('phone-') || !!phoneDigits;
		const target=mode === 'phone-escort-search' ? 'Escort.club':'Escorti.pl';
		const value=phone ? formatEscortPhoneForClipboard(phoneDigits,'local-hyphens')
			:(normalizeEscortiAdUrl(item.adUrl) || '').split('/').pop()?.split(/[?#]/)[0];
		return value ? `[${value}] → ${target}`:phone ? `nr tel. → ${target}`
			:item.profileUrls?.length ? 'Escorti.pl':'adres anonsu → Escorti.pl';
	}
	function watchItemSourceUrl(item) {
		if (!item) return '';
		if (item.sourceType === 'escort-search') return getFavoriteSearchViewUrl(item);
		if (item.sourceType === 'escort-ad') return normalizeEscortiAdUrl(item.adUrl) || '';
		if (item.escortiProfileOnly || item.phoneProfileOnly) return normalizeWatchUrl(item.profileUrls?.[0]) || '';
		const mode=normalizeStoredWatchProfileResolutionMode(item.resolutionMode);
		const phoneDigits=mode.startsWith('phone-') ? getWatchItemSourcePhone(item):'';
		if (mode === 'phone-escort-search' && phoneDigits) {
			return buildEscortClubPhoneSearchUrl(phoneDigits);
		}
		if (mode === 'phone-escorti' && phoneDigits) {
			return buildEscortiSearchUrl(phoneDigits);
		}
		if (mode === 'address-escorti' && item.adUrl) {
			return buildEscortiSearchUrl(normalizeEscortiAdUrl(item.adUrl) || item.adUrl);
		}
		return normalizeWatchUrl(item.profileUrls?.[0]) || '';
	}
	function getEscortAdStandardTextColor() {
		try {
			const sample=document.querySelector('.content-desc .tab-content, .content-desc, .content-info-col.-desc');
			const color=sample && typeof getComputedStyle === 'function'
				? getComputedStyle(sample).color
				:'';
			if (color && color !== 'rgb(255, 255, 255)' && color !== 'rgba(255, 255, 255, 1)') {
				return color;
			}
		} catch {}
		return 'rgba(255, 235, 247, .86)';
	}
	function getFavoriteSettings() {
		const settings=getSettings();
		return {
			profileResolutionMode:normalizeWatchProfileResolutionMode(settings.watchProfileResolutionMode),
			dataColumns:normalizeFavoritesDataColumns( settings.favoritesDataColumns ),
			categories:normalizeFavoriteCategories(settings.favoriteCategories), categoryColors:normalizeFavoriteCategoryColors(
				settings.favoriteCategoryColors, settings.favoriteCategories )
		};
	}
	function openFavoriteCategoriesEditor(onSaved=null) {
	const editorId='vm-favorite-categories-editor';
	if (document.getElementById(editorId)) return;
	const current=getSettings();
	const categories=normalizeFavoriteCategories(current.favoriteCategories);
	const colors=normalizeFavoriteCategoryColors(current.favoriteCategoryColors,categories);
	const draft=categories.map(label => ({
		original:label, label, color:colors[label] || '#c8c8c8'
	}));
	const editorOverlay= prepareHelperStyleApi( makeElement('div') );
	editorOverlay.id=editorId;
	const closeController=createDialogCloseController(editorOverlay);
	editorOverlay.classList.add('vm-ui-overlay');
	const editorBox=makeElement('div',"vm-ui-dialog");
	Object.assign(editorBox.style,{width:'min(520px, calc(100vw - 32px))'});
	const editorTitle=makeElement('div',"vm-ui-title",'Edytuj kategorie');
	const rowsHost=makeElement('div',"vm-ui-stack");
	const statusLine=makeElement('div',"vm-ui-help vm-ui-error");
	Object.assign(statusLine.style,{minHeight:'16px',marginTop:'6px'});
	const renderRows=() => {
		rowsHost.replaceChildren();
		draft.forEach((entry,index) => {
			const row=makeElement('div',"vm-ui-category-row");
			const color=makeElement('input');
			color.type='color';
			color.value=normalizeFavoriteCategoryColor(entry.color) || '#c8c8c8';
			color.title='Kolor kategorii';
			color.classList.add('vm-ui-field','vm-ui-color');
			color.addEventListener('input',() => {entry.color=color.value;});
			const label=makeElement('input');
			label.type='text';
			label.value=entry.label;
			label.maxLength=60;
			label.classList.add('vm-ui-field');
			Object.assign(label.style,{width:'100%'});
			label.addEventListener('input',() => {entry.label=label.value;});
			const remove=makeButton("vm-ui-button vm-ui-icon -danger",'×',{title:'Usuń kategorię'});
			remove.addEventListener('click',() => {
				draft.splice(index,1);
				renderRows();
			});
			row.append(color,label,remove);
			rowsHost.appendChild(row);
		});
		const addRow=makeElement('div',"vm-ui-actions");
		const addInput=makeElement('input',"vm-ui-field",null,{type:'text',placeholder:'Nowa kategoria',maxLength:60});
		Object.assign(addInput.style,{flex:'1 1 auto',minWidth:'0'});
		const add=makeButton("vm-ui-button -accent",'Dodaj');
		const addCategory=() => {
			const label=normalizeEscortAdText(addInput.value).slice(0,60);
			if (!label) return;
			if (draft.some(entry => normalizeText(entry.label) === normalizeText(label))) {
				statusLine.textContent='Taka kategoria już istnieje.';
				return;
			}
			draft.push({original:'',label,color:'#c8c8c8'});
			statusLine.textContent='';
			renderRows();
		};
		add.addEventListener('click',addCategory);
		addInput.addEventListener('keydown',event => {
			if (event.key === 'Enter') {event.preventDefault(); addCategory();}
		});
		addRow.append(addInput,add);
		rowsHost.appendChild(addRow);
	};
	renderRows();
	const actions=makeElement('div',"vm-ui-actions");
	const cancel=makeButton('','Anuluj');
	const save=makeButton('','Zapisz');
	for (const button of [cancel,save]) button.classList.add('vm-ui-button');
	save.classList.add('-primary');
	cancel.addEventListener('click',() => closeController.close('button'));
	save.addEventListener('click',() => {
		const cleaned=draft.map(entry => ({
			...entry, label:normalizeEscortAdText(entry.label).slice(0,60),
			color:normalizeFavoriteCategoryColor(entry.color) || '#c8c8c8'
		})).filter(entry => entry.label);
		const keys=cleaned.map(entry => entry.label.toLocaleLowerCase('pl-PL'));
		if (new Set(keys).size !== keys.length) {
			statusLine.textContent='Nazwy kategorii muszą być unikalne.';
			return;
		}
		const nextCategories=cleaned.map(entry => entry.label);
		const nextColors=Object.fromEntries(cleaned.map(entry => [entry.label,entry.color]));
		const renameMap=new Map(cleaned.filter(entry => entry.original).map(entry => [entry.original,entry.label]));
		const nextItems=getWatchedItems().map(item => {
			const assigned=normalizeFavoriteAssignedCategories(item.categories);
			if (!assigned.length) return item;
			const nextAssigned=assigned .map(value => renameMap.has(value) ? renameMap.get(value):value)
				.filter(value => !categories.includes(value) || nextCategories.includes(value));
			return normalizeWatchedItem({...item,categories:nextAssigned});
		});
		const removedNames=categories.filter(name=>!renameMap.has(name) && !nextCategories.includes(name));
		withTrackedUndo('Usunięto kategorie.',()=>{
			saveWatchedItems(nextItems);
			updateSettings({favoriteCategories:nextCategories,favoriteCategoryColors:nextColors});
		},()=>removedNames.length>0,{names:removedNames,colors});
		closeController.close('button');
		if (typeof onSaved === 'function') {
			try {
				onSaved({
					categories:nextCategories, colors:nextColors
				});
			} catch (error) {
				log( 'Błąd odświeżenia po edycji kategorii', error );
			}
		}
	});
	actions.append(cancel,save);
	editorBox.append(editorTitle,rowsHost,statusLine,actions);
	editorOverlay.appendChild(editorBox);
	document.body.appendChild(editorOverlay);
}
	function getFavoriteMatchesForCurrentEscortAd(adId=getAdIdFromUrl()) {
		if (!adId) return [];
		const profileIds=new Set(getHiddenResultProfileUrls(getListCache(adId)).map(getEscortiProfileId).filter(Boolean));
		return getWatchedItems().filter(item => getWatchedItemAdUrls(item).some(url => parseAdIdFromUrl(url) === adId) ||
			(item.sourceType !== 'escort-ad' && item.sourceType !== 'escort-search' &&
				item.profileUrls.some(url => profileIds.has(getEscortiProfileId(url)))) );
	}
	function getFavoriteMatchesForCurrentEscortiProfile() {
		const profileUrl=normalizeWatchUrl(`${location.origin}${location.pathname}`);
		const profileId=getEscortiProfileId(profileUrl);
		const watchKey=`profiles:${profileId || profileUrl}`;
		return getWatchedItems().filter(item => {
			if (item.sourceType === 'escort-ad') return false;
			if (item.watchKey && item.watchKey === watchKey) return true;
			return (item.profileUrls || []).some(url => {
				if (profileId) return getEscortiProfileId(url) === profileId;
				return normalizeWatchUrl(url) === profileUrl;
			});
		});
	}
	function saveFavoriteItemFromContext(context,patch={},strictScope=null) {
		const candidate=normalizeWatchedItem({
			id:'favorite-candidate', ...context, watchKey:getWatchKey(context), modes:patch.modes || {},
			favorite:patch.favorite !== false, note:patch.note || '', categories:patch.categories || []
		});
		const items=getWatchedItems();
		const index=items.findIndex(item => {
			if (strictScope === 'single' && item.sourceType !== 'escort-ad') return false;
			if (strictScope === 'person' && item.sourceType === 'escort-ad') return false;
			return context.id ? item.id === context.id:watchedItemsOverlap(item,candidate);
		});
		const modePatch=patch.modes || {};
		if (index >= 0) {
			const existing=items[index];
			const replacing=existing.resolutionMode !== candidate.resolutionMode;
			items[index]=normalizeWatchedItem({
				...existing, ...context, id:existing.id,
				watchKey:replacing ? candidate.watchKey:existing.watchKey || candidate.watchKey,
				displayName:existing.customDisplayName || context.displayName || existing.displayName,
				customDisplayName:existing.customDisplayName || null, imageUrl:context.imageUrl || existing.imageUrl,
				profileUrls:[...new Set([...(replacing || getEscortiWatchSearchValue(context) ? []:existing.profileUrls || []),...(context.profileUrls || [])])],
				resolvedAdUrls:[...new Set([...(replacing || context.sourceType === 'escort-search' ? []:existing.resolvedAdUrls || []),...(context.resolvedAdUrls || [])])],
				phoneDigits:context.phoneDigits || existing.phoneDigits, adUrl:context.adUrl || existing.adUrl,
				favorite:patch.favorite == null ? existing.favorite:!!patch.favorite,
				hidden:patch.hidden == null ? existing.hidden:!!patch.hidden,
				hiddenAt:patch.hidden === true && !existing.hidden ? Date.now():existing.hiddenAt,
				note:patch.note == null ? existing.note:String(patch.note || '').trim(), categories:patch.categories == null
					? existing.categories
					:normalizeFavoriteAssignedCategories(patch.categories), resolutionMode:normalizeStoredWatchProfileResolutionMode(
					context.resolutionMode || existing.resolutionMode || getFavoriteSettings().profileResolutionMode ), modes:{
					online: modePatch.online == null
							? existing.modes.online
							:!!modePatch.online, location: modePatch.location == null
							? existing.modes.location
							:!!modePatch.location, changes: modePatch.changes == null
							? existing.modes.changes
							:!!modePatch.changes, newAds: existing.sourceType !== 'escort-ad' && ( modePatch.newAds == null
								? existing.modes.newAds
								:!!modePatch.newAds )
				}, locationTarget:patch.locationTarget || existing.locationTarget, state:replacing ? null:existing.state,
				lastCheckedAt:replacing ? 0:existing.lastCheckedAt, lastFullCheckedAt:replacing ? 0:existing.lastFullCheckedAt,
				lastError:replacing ? null:existing.lastError, hiddenLastCheckedAt:replacing ? 0:existing.hiddenLastCheckedAt,
				hiddenLastError:replacing ? null:existing.hiddenLastError
			});
			if (replacing) saveWatchEvents(getWatchEvents().filter(event => event.itemId !== existing.id));
			saveWatchedItems(items);
			return items[index];
		}
		const created=normalizeWatchedItem({
			id:`${Date.now()}-${Math.random().toString(36).slice(2)}`, ...context, watchKey:getWatchKey(context),
			favorite:patch.favorite == null ? patch.hidden !== true:!!patch.favorite, note:String(patch.note || '').trim(),
			hidden:patch.hidden === true, hiddenAt:patch.hidden ? Date.now():0,
			categories:normalizeFavoriteAssignedCategories(patch.categories),
			resolutionMode:normalizeStoredWatchProfileResolutionMode(
				context.resolutionMode || getFavoriteSettings().profileResolutionMode ), modes:{
				online:!!modePatch.online, location:!!modePatch.location, changes:!!modePatch.changes, newAds:
					context.sourceType !== 'escort-ad' && !!modePatch.newAds
			}, locationTarget:patch.locationTarget || {type:'any',city:''}, createdAt:Date.now()
		});
		saveWatchedItems([...items,created]);
		return created;
	}
	function watchedItemHasNotesOrCategories(item) {
		return !!item?.note || normalizeFavoriteAssignedCategories(item?.categories).length > 0;
	}
	function clearAllFavorites() {
		return withTrackedUndo('Usunięto wszystkie Ulubione.',()=>{
			const stored=GM_getValue(WATCH_ITEMS_STORAGE_KEY,[]);
			if (!Array.isArray(stored)) return 0;
			const ids=new Set();
			const next=stored.map(raw => {
				const item=normalizeWatchedItem(raw);
				if (!item?.favorite) return raw;
				ids.add(item.id);
				// Keep even empty profiles across reloads and background data refreshes.
				return {...raw,favorite:false,preserveProfile:true,modes:{online:false,location:false,changes:false,newAds:false}};
			});
			if (!ids.size) return 0;
			writeHelperStorageValue(WATCH_ITEMS_STORAGE_KEY,next);
			const events=getWatchEvents();
			if (events.some(event => ids.has(event.itemId) && event.alertTriggered && !event.acknowledged)) {
				saveWatchEvents(events.map(event => ids.has(event.itemId) && event.alertTriggered && !event.acknowledged
					? {...event,acknowledged:true}:event));
			}
			refreshWatchPageButtons();
			try {document.getElementById(WATCH_SETTINGS_OVERLAY_ID)?._vmRender?.();} catch {}
			renderWatchNotificationBar();
			return ids.size;
		});
	}
	function clearAllFavoriteNotes() {
		return withTrackedUndo('Usunięto notatki.',() => {
			const items=getWatchedItems(),changed=items.filter(item => item.note).length;
			if (!changed) return 0;
			const next=items.map(item => normalizeWatchedItem({...item,note:''})).filter(item => item &&
				(item.preserveProfile || item.favorite || item.hidden || item.categories.length || watchedItemHasActiveAlerts(item)));
			writeHelperStorageValue(WATCH_ITEMS_STORAGE_KEY,next);
			refreshWatchPageButtons();
			renderWatchNotificationBar();
			document.getElementById(WATCH_SETTINGS_OVERLAY_ID)?._vmRender?.();
			return changed;
		});
	}
	function clearAllFavoriteAssignedCategories() {
		return withTrackedUndo('Usunięto przypisania kategorii.',()=>{
			const items=getWatchedItems();
			let changed=0;
			const nextItems=items.map(item => {
				if ( !normalizeFavoriteAssignedCategories( item?.categories ).length ) {
					return item;
				}
				changed += 1;
				return normalizeWatchedItem({
					...item, categories:[]
				});
			});
			if (changed) {
				saveWatchedItems(nextItems);
				document .getElementById( WATCH_SETTINGS_OVERLAY_ID ) ?._vmRender?.();
			}
			return changed;
		});
	}
	function removeFavoriteOnly(itemId) {
		return withTrackedUndo('Usunięto z Ulubionych.',()=>{
			const items=getWatchedItems();
			const index=items.findIndex(item => item.id === itemId);
			if (index < 0) return;
			const item=items[index];
			const keepForMetadata=item.preserveProfile || item.hidden || watchedItemHasNotesOrCategories(item);
			if (keepForMetadata) {
				items[index]=normalizeWatchedItem({
					...item, favorite:false, modes:{online:false,location:false,changes:false,newAds:false}
				});
			} else {
				items.splice(index,1);
				saveWatchEvents(getWatchEvents().filter(event => event.itemId !== itemId));
			}
			saveWatchedItems(items);
			renderWatchNotificationBar();
		});
	}
	function getLastFavoriteAlertCity() {
		try {
			const stored=GM_getValue(FAVORITE_LAST_ALERT_MODES_STORAGE_KEY,null);
			return normalizeEscortCity(stored?.locationCity || '');
		} catch {
			return '';
		}
	}
	function saveLastFavoriteAlertCity(city) {
		const locationCity=normalizeEscortCity(city);
		if (!locationCity) return;
		try {
			writeHelperStorageValue(FAVORITE_LAST_ALERT_MODES_STORAGE_KEY,{locationCity});
		} catch {}
	}
	function chooseFavoriteScope() {
		return chooseTrackedEntriesDialog(getFavoriteMatchesForCurrentEscortAd().filter(item => item.favorite));
	}
	function chooseTrackedEntriesDialog(items,hidden=false,isProfile=false) {
		if (isProfile && hidden && items.length <= 1) return Promise.resolve(editHiddenForCurrentPage());
		return new Promise(resolve => {
			const name=extractWatchPageName(document,items[0]?.displayName || (isProfile ? 'Profil':'Anons'));
			const {overlay,box}=createWatchDialogBase('vm-tracked-entries-overlay',hidden ? 'Ukryte':'Ulubione');
			const title=box.firstElementChild;
			const heading=makeElement('div','vm-ui-label',name);
			heading.style.marginBottom='10px';
			box.appendChild(heading);
			let result=null;
			const controller=createDialogCloseController(overlay,{onClose:() => resolve(result)});
			const action=(value,label,primary=false) => {
				const button=styleDialogActionButton(makeButton('',label),primary);
				button.addEventListener('click',() => {
					if (value.startsWith('remove:') || value.startsWith('restore:')) {
						const id=value.slice(value.indexOf(':') + 1);
						if (hidden) restoreHiddenItems([id]); else removeFavoriteOnly(id);
						items=items.filter(item => item.id !== id);
						refreshWatchPageButtons();
						if (!items.length && isProfile) controller.close('empty'); else render();
						return;
					}
					result=value;
					controller.close('choice');
				});
				return button;
			};
			const render=() => {
			box.replaceChildren(title,heading);
			const renderItem=item => {
				const row=makeElement('div','vm-ui-panel');
				Object.assign(row.style,{display:'flex',alignItems:'center',flexWrap:'wrap',gap:'8px',padding:'10px'});
				const source=watchItemSourceText(item);
				const label=makeElement('div','',item.sourceType === 'escort-ad' ? 'Ten anons':source.replace('Profil: ','Profil · ').replace('adres anonsu','adres').replace('wyszukiwarka ',''));
				Object.assign(label.style,{flex:'1 1 200px',overflowWrap:'anywhere'});
				label.title=trackedItemChoiceLabel(item);
				if (items.filter(other => watchItemSourceText(other) === source).length > 1) {
					const detail=makeElement('div','vm-ui-help',`${item.displayName} · ${item.phoneDigits || item.profileUrls?.map(getEscortiProfileId).filter(Boolean).join(', ') || item.id}`);
					label.appendChild(detail);
				}
				const actions=makeElement('div','vm-ui-row');
				const remove=action(`${hidden ? 'restore':'remove'}:${item.id}`,hidden ? 'Nie ukrywaj':'Usuń');
				remove.classList.add('-danger');
				remove.setAttribute('aria-label',`${hidden ? 'Nie ukrywaj':'Usuń z ulubionych'}: ${trackedItemChoiceLabel(item)}`);
				actions.appendChild(remove);
				row.append(label,actions);
				return row;
			};
			const sections=makeElement('div');
			Object.assign(sections.style,{display:'grid',gap:'12px',maxHeight:'60vh',overflowY:'auto'});
			const adActions=isProfile ? []:items.filter(item => item.sourceType === 'escort-ad').map(renderItem);
			if (!isProfile && !adActions.length) adActions.push(action('single',hidden ? '+ Ukryj ten anons':'+ Dodaj ten anons'));
			const profileActions=[];
			const profiles=items.filter(item => item.sourceType !== 'escort-ad');
			if (isProfile) {
				profileActions.push(...profiles.map(renderItem));
			} else {
				const available=new Set(getSelectableWatchProfileModes().map(([mode]) => mode));
				for (const [mode] of WATCH_PROFILE_RESOLUTION_OPTIONS) {
					const saved=profiles.filter(item => item.resolutionMode === mode);
					if (saved.length) {profileActions.push(...saved.map(renderItem)); continue;}
					if (!available.has(mode)) continue;
					const label=watchItemSourceText({resolutionMode:mode}).replace('Profil: ','').replace('adres anonsu','adres').replace('wyszukiwarka ','');
					profileActions.push(action(`person:${mode}`,`${hidden ? '+ Ukryj':'+ Dodaj'} · ${label}`));
				}
				profileActions.push(...profiles.filter(item => item.sourceType === 'escort-search').map(renderItem));
			}
			for (const [label,buttons] of [['Anons',adActions],['Profil',profileActions]]) {
				if (!buttons.length) continue;
				const section=makeElement('div','vm-ui-panel');
				Object.assign(section.style,{display:'grid',gap:'7px',padding:'10px'});
				section.append(makeElement('div','vm-ui-label',label),...buttons);
				sections.appendChild(section);
			}
			const close=createDialogCancelButton(controller);
			close.textContent='Zamknij';
			box.append(sections,close);
			};
			render();
		});
	}
	function getSelectableWatchProfileModes(allowedModes=null) {
		const configured=getFavoriteSettings().profileResolutionMode;
		const allowed=Array.isArray(allowedModes) && allowedModes.length ? new Set(allowedModes):null;
		return WATCH_PROFILE_RESOLUTION_OPTIONS.filter(([value]) => value !== 'ask' &&
			(configured === 'ask' || value === configured) && (!allowed || allowed.has(value)));
	}
	async function chooseFavoriteProfileResolutionMode(shownModes=[],allowedModes=null) {
		const choices=getSelectableWatchProfileModes(allowedModes).map(([value,label]) =>
			[value,shownModes.includes(value) ? `${label} - już w panelu`:label,shownModes.includes(value)]);
		if (choices.length === 1 && !choices[0][2]) return choices[0][0];
		return chooseWatchOptionDialog({
			id:'vm-garso-favorite-profile-resolution-overlay', title:'Jak ustalić profil osoby?',
			info:'Wybierz sposób znalezienia wszystkich anonsów tej osoby.', choices,
			primaryValue:choices.some(([value]) => value === 'address-escorti') ? 'address-escorti':choices[0]?.[0]
		});
	}
	function trackedItemChoiceLabel(item) {
		return `${item.displayName} - ${watchItemSourceText(item)}`;
	}
	function chooseWatchAlertOptions( existing=null, scopeLabel='', dialogOptions={} ) {
		return new Promise(resolve => {
			const locationOnly= dialogOptions.locationOnly === true;
			const rememberedAlertCity=getLastFavoriteAlertCity();
			const title=locationOnly
				? 'Alert lokalizacji'
				:(dialogOptions.scope === 'single' ? 'Alerty anonsu':'Alerty profilu');
			const {overlay,box}=createWatchDialogBase( 'vm-garso-favorite-options-overlay', title );
			if (!locationOnly) Object.assign(box.style,{maxWidth:'420px',padding:'16px'});
			if (locationOnly && scopeLabel) box.appendChild(makeElement('div','vm-ui-label',scopeLabel));
			const intro=makeElement('div',"vm-ui-help",dialogOptions.info || (locationOnly
					? 'Ustaw sposób obserwowania zmiany lokalizacji'
					:'Wybierz jakie alerty chcesz otrzymywać'));
			Object.assign(intro.style,{marginBottom:'12px'});
			const makeMode=(label,checked,tooltip='') => {
				const row=makeElement('label');
				if (tooltip) row.title=tooltip;
				row.classList.add('vm-ui-choice');
				const input=makeElement('input','',null,{type:'checkbox',checked:!!checked});
				input.style.accentColor='var(--vm-ui-accent, #f54da3)';
				const labelText=makeElement('span','',label);
				labelText.style.fontWeight=input.checked ? '700':'400';
				input.addEventListener('change',() => {
					labelText.style.fontWeight=input.checked ? '700':'400';
				});
				row.append(input,labelText);
				return {row,input,labelText};
			};
			const online=makeMode( 'Obserwuj online / offline', existing?.modes?.online,
				'Pokazuje na górnym pasku alert o zmianie stanu online/offline. Historia zmian w kolumnie „Zmiany” jest zapisywana niezależnie od tego checkboxa.'
			);
			const location=makeMode( 'Obserwuj zmianę lokalizacji', existing?.modes?.location,
				'Powiadamia o zmianie lokalizacji albo o pojawieniu się w wybranym mieście - zgodnie z ustawieniem poniżej.' );
			const changes=makeMode( 'Obserwuj zmiany danych anonsu', existing?.modes?.changes,
				'Powiadamia o zmianie nazwy anonsu, adresu, cennika, godzin dostępności, tagów/usług, opisu, numeru telefonu lub danych profilu. Zmiana stanu online/offline nie jest zmianą danych anonsu.'
			);
			const isProfileScope=existing
				? existing.sourceType !== 'escort-ad'
				:dialogOptions.scope === 'person' || /^profil$/i.test(String(scopeLabel || '').trim());
			const newAds=makeMode( 'Obserwuj pojawienie się nowego anonsu w profilu', isProfileScope && existing?.modes?.newAds,
				'Pokazuje na górnym pasku alert, gdy w obserwowanym profilu pojawi się anons o ID, którego wcześniej w tym profilu nie było. Historia nowych anonsów jest zapisywana niezależnie od tego checkboxa.'
			);
			if (!isProfileScope) {
				newAds.input.checked=false;
				newAds.input.disabled=true;
				newAds.row.style.opacity='.42';
				newAds.row.style.cursor='default';
				newAds.row.title=
					'Powiadamia o pojawieniu się nowego anonsu. Ten alert jest dostępny tylko dla całego profilu, nie dla pojedynczego anonsu.';
			}
			const locationControls=createWatchLocationTargetControls(
				existing?.modes?.location || existing?.locationTarget?.type === 'city'
					? existing.locationTarget
					:{type:rememberedAlertCity ? 'city':'any',city:rememberedAlertCity} );
			const syncLocationControls=() => {
				locationControls.setEnabled(location.input.checked);
			};
			location.input.addEventListener('change',syncLocationControls);
			syncLocationControls();
			const actions=makeElement('div',"vm-ui-actions");
			const right=makeElement('div',"vm-ui-row");
			const cancel=makeButton('','Anuluj');
			const save=makeButton('','Zapisz');
			for (const btn of [cancel,save]) {
				btn.classList.add('vm-ui-button');
			}
			save.classList.add('-primary');
			right.append(cancel,save);
			actions.appendChild(right);
			let dialogResult=null;
			const closeController=createDialogCloseController(overlay,{
				onClose:() => resolve(dialogResult)
			});
			const finish=value => {
				dialogResult=value;
				closeController.close('button');
			};
			cancel.addEventListener('click',() => finish(null));
			save.addEventListener('click',() => {
				if (save.disabled) return;
				let locationTarget= existing?.locationTarget || {type:'any',city:''};
				if (location.input.checked) {
					const selectedTarget=locationControls.read();
					if (!selectedTarget) {
						locationControls.input.focus();
						return;
					}
					locationTarget=selectedTarget;
				}
				const selectedModes={
					online:locationOnly
						? !!existing?.modes?.online
						:online.input.checked, location:location.input.checked, changes:locationOnly
						? !!existing?.modes?.changes
						:changes.input.checked, newAds: isProfileScope && ( locationOnly
								? !!existing?.modes?.newAds
								:newAds.input.checked )
				};
				if (selectedModes.location && locationTarget.type === 'city') saveLastFavoriteAlertCity(locationTarget.city);
				finish({
					modes:selectedModes, locationTarget
				});
			});
			if (locationOnly) {
				box.append( intro, location.row, locationControls.root, actions );
			} else {
				box.append( intro, online.row, location.row, locationControls.root, changes.row,
					...(isProfileScope ? [newAds.row]:[]), actions );
			}
			setTimeout(() => {
				if ( location.input.checked && !locationControls.anyInput.checked ) {
					locationControls.input.focus();
				}
			},0);
		});
	}
	function getCurrentEscortAdBaseData(adUrl,needPhone=false) {
		const adId=parseAdIdFromUrl(adUrl);
		if (!adId) return Promise.resolve(null);
		if (CURRENT_HOST === 'pl.escort.club' && getAdIdFromUrl() === adId) {
			const data=extractEscortClubAdData(document,adId,adUrl);
			if (!needPhone) return Promise.resolve(data);
			return resolveEscortPhoneForCurrentPage(data.phoneId).then(phone => {
				if (phone) Object.assign(data,phone);
				return data;
			});
		}
		return getEscortAdData(adId,adUrl,false,null,null,{needPhone}).then(result => result?.status === 'ok' ? result:null);
	}
	async function buildFavoriteContextFromEscortAd(adUrl,scope='single',resolutionMode=null) {
		const normalizedAdUrl=normalizeEscortiAdUrl(adUrl);
		const adId=parseAdIdFromUrl(normalizedAdUrl);
		if (!normalizedAdUrl || !adId) throw new Error('Nieprawidłowy adres anonsu Escort.club');
		if (scope === 'single') {
			const data=await getCurrentEscortAdBaseData(normalizedAdUrl);
			return {
				sourceType:'escort-ad', adUrl:normalizedAdUrl, profileUrls:[], phoneDigits:data?.phoneDigits || null,
				displayName:data?.title || `Anons ${adId}`,
				imageUrl:getAdIdFromUrl() === adId ? extractWatchPageImage(document,normalizedAdUrl):'', watchKey:`ad:${adId}`,
				resolutionMode:'single'
			};
		}
		const configured=getFavoriteSettings().profileResolutionMode;
		let mode=normalizeWatchProfileResolutionMode(configured === 'ask' ? resolutionMode || configured:configured);
		if (mode === 'ask') {
			mode=await chooseFavoriteProfileResolutionMode();
			if (!mode) return null;
		}
		if (mode === 'address-escorti') {
			const data=await getCurrentEscortAdBaseData(normalizedAdUrl);
			const context={sourceType:'escorti-profile',resolutionMode:mode,adUrl:normalizedAdUrl,profileUrls:[],phoneDigits:null,
				displayName:data?.title || `Anons ${adId}`,imageUrl:getAdIdFromUrl() === adId ? extractWatchPageImage(document,normalizedAdUrl):''};
			const result=await resolveWatchedProfileData(context);
			return {...context,profileUrls:result.profileUrls,resolvedAdUrls:result.adUrls,
				displayName:result.displayName,imageUrl:result.imageUrl,watchKey:getWatchKey(context)};
		}
		const data=await getCurrentEscortAdBaseData(normalizedAdUrl,true);
		const phoneDigits=getEscortAdPhoneDigits(data);
		if (!phoneDigits) throw new Error('Nie udało się odczytać numeru telefonu dla tego anonsu');
		if (mode === 'phone-escort-search') {
			return {
				sourceType:'phone', adUrl:normalizedAdUrl, profileUrls:[], resolvedAdUrls:[normalizedAdUrl], phoneDigits,
				displayName:data?.title || `Profil numeru ${phoneDigits}`,
				imageUrl:getAdIdFromUrl() === adId ? extractWatchPageImage(document,normalizedAdUrl):'',
				watchKey:`phone:${phoneDigits}`, resolutionMode:mode
			};
		}
		const found=await searchEscortiProfilesDirect(phoneDigits);
		const profileUrls=found.status === 'ok' ? found.profileUrls || []:[];
		const profileInfos=(await watchMapLimit(profileUrls,2,async profileUrl => {
			try {return await readEscortiProfileDirect(profileUrl);} catch {return null;}
		})).filter(Boolean);
		return {
			sourceType:profileUrls.length ? 'escorti-profile':'phone', adUrl:normalizedAdUrl, profileUrls,
			resolvedAdUrls:[...new Set(profileInfos.flatMap(info => info.adUrls || []))], phoneDigits,
			displayName:profileInfos[0]?.profileName || data?.title || `Profil numeru ${phoneDigits}`,
			imageUrl:profileInfos[0]?.imageUrl || (getAdIdFromUrl() === adId ? extractWatchPageImage(document,normalizedAdUrl):''),
			watchKey:profileUrls.length
				? `profiles:${profileUrls.map(getEscortiProfileId).filter(Boolean).sort().join(',')}`
				:`phone:${phoneDigits}`, resolutionMode:mode
		};
	}
	async function resolveWatchedProfileData(item,requestContext=null) {
		const operationContext=createRequestContext('watch.profile-resolution',requestContext);
		if (item.sourceType === 'escort-search') return resolveFavoriteSearch(item,null,null,operationContext);
		const mode=normalizeStoredWatchProfileResolutionMode(item.resolutionMode);
		if (mode === 'phone-escort-search' && item.phoneDigits) {
			const found=await fetchEscortClubPhoneSearchAds(item.phoneDigits,null,operationContext);
			return {
				profileUrls:item.profileUrls || [], adUrls:[...new Set([...(found.adUrls || []),...(item.resolvedAdUrls || [])])],
				displayName:item.displayName, imageUrl:item.imageUrl, profileInfos:[]
			};
		}
		let profileUrls=[...new Set(item.profileUrls || [])];
		const search=getEscortiWatchSearchValue(item);
		if (search) {
			const found=await searchEscortiProfilesDirect(search,null,operationContext);
			if (found.status !== 'ok') throw new Error('Nie udało się odczytać wyszukiwania Escorti - zachowuję poprzednie powiązania.');
			profileUrls=found.profileUrls || [];
		} else if (!profileUrls.length && item.phoneDigits && !item.escortiProfileOnly && !item.phoneProfileOnly) {
			const found=await searchEscortiProfilesDirect(item.phoneDigits,null,operationContext);
			profileUrls=found.status === 'ok' ? found.profileUrls || []:[];
		}
		const profileInfos=(await watchMapLimit(profileUrls,2,async profileUrl => {
			try {return await readEscortiProfileDirect(profileUrl,null,operationContext);} catch {return null;}
		})).filter(Boolean);
		if (profileInfos.length !== profileUrls.length) throw new Error('Nie udało się odczytać wszystkich profili Escorti - zachowuję poprzednie powiązania.');
		const adUrls=[...new Set([ ...profileInfos.flatMap(info => info.adUrls || [])
		].map(url => normalizeEscortiAdUrl(url)).filter(Boolean))];
		return {
			profileUrls, adUrls, displayName:profileInfos.map(info => info.profileName).find(Boolean) || item.displayName,
			imageUrl:profileInfos.map(info => info.imageUrl).find(Boolean) || item.imageUrl, profileInfos
		};
	}
	const WATCH_FULL_CHECK_INTERVAL_MS=60 * 60 * 1000;
	const WATCH_SCHEDULE_POLL_INTERVAL_MS=60000;
	function watchedItemHasActiveAlerts(item) {
		return !!( item?.modes?.online || item?.modes?.location || item?.modes?.changes || item?.modes?.newAds );
	}
	function getWatchedItemCheckTiming(item,settings) {
		const hasAlerts=watchedItemHasActiveAlerts(item);
		const eligible=!!(settings?.enabled && (item?.favorite || hasAlerts ||
			(item?.note && item.sourceType !== 'escort-ad')));
		const intervalMs=Math.max(1,Number(settings?.intervalMinutes) || 15) * 60000;
		const hourly=intervalMs < WATCH_FULL_CHECK_INTERVAL_MS && !hasAlerts;
		const expectedMs=hourly ? WATCH_FULL_CHECK_INTERVAL_MS:intervalMs;
		const lastCheckedAt=Number(item?.lastCheckedAt) || 0;
		const referenceAt=Number(hourly && item?.favorite ? item.lastFullCheckedAt:lastCheckedAt) || 0;
		return {eligible,expectedMs,referenceAt,dueAt:Math.max(referenceAt ? referenceAt + expectedMs:0,
			item?.lastError || item?.state?.partial ? lastCheckedAt + intervalMs:0)};
	}
	function shouldRunScheduledFullWatchCheck( item, settings, now=Date.now() ) {
		if (Number(settings?.intervalMinutes) >= 60) return true;
		const lastFullCheckedAt=Number(item?.lastFullCheckedAt) || 0;
		return !lastFullCheckedAt || now - lastFullCheckedAt >= WATCH_FULL_CHECK_INTERVAL_MS;
	}
	async function inspectWatchedItem( item, onAdProgress=null, {fullCheck=true,requestContext=null}={} ) {
		const operationContext=createRequestContext('watch.item',requestContext);
		let adUrls=[];
		let profileUrls=item.profileUrls || [];
		let displayName=item.displayName;
		let imageUrl=item.imageUrl;
		let profileImageUrl='';
		let profileInfos=[],verifiedAdResults=[];
		if (item.sourceType === 'escort-ad') {
			if (!item.adUrl) throw new Error('Brak adresu anonsu Escort.club');
			adUrls=[item.adUrl];
		} else {
			const profileData=await resolveWatchedProfileData(item,operationContext);
			profileUrls=profileData.profileUrls;
			adUrls=profileData.adUrls;
			displayName=profileData.displayName;
			profileImageUrl=profileData.imageUrl || '';
			profileInfos=profileData.profileInfos || [];
			verifiedAdResults=profileData.verifiedAdResults || [];
		}
		const profileNoteDiscoveryOnly=item.sourceType !== 'escort-ad' && !!item.note &&
			!item.favorite && !item.modes.online && !item.modes.location && !item.modes.changes && !item.modes.newAds;
		const newAdsOnlyCheck= !fullCheck && item.sourceType !== 'escort-ad' && item.modes.newAds && !item.modes.online &&
			!item.modes.location && !item.modes.changes;
		if (profileNoteDiscoveryOnly || newAdsOnlyCheck) {
			const previousState=item.state && typeof item.state === 'object' ? item.state:{};
			const state={
				...previousState, online:typeof previousState.online === 'boolean' ? previousState.online:null,
				activeCount:Number(previousState.activeCount) || 0, totalAds:adUrls.length,
				cities:Array.isArray(previousState.cities) ? previousState.cities:[],
				activeUrls:Array.isArray(previousState.activeUrls) ? previousState.activeUrls:[],
				profileDates:[...new Set(profileInfos.map(info => formatEscortiDate(info.creationDate)).filter(Boolean))],
				checkedAt:Date.now()
			};
			return {
				displayName, imageUrl:profileImageUrl || imageUrl, profileUrls, adUrls, activeResults:verifiedAdResults, profileInfos, state,
				historyFullCheckCompleted:false, fullCheckCompleted:false
			};
		}
		onAdProgress?.({
			checked:0, total:adUrls.length, activeCount:0, url:null, result:null
		});
		const needsDetails=fullCheck || item.modes.location;
		const activitySummary=await scanActiveEscortAds(adUrls,onAdProgress,true,{activityOnly:!needsDetails,headFirst:needsDetails,
			forceDetails:false,detailMinCheckedAt:needsDetails && Number(item.lastFullCheckedAt) > 0 ? Number(item.lastFullCheckedAt) + 1:0,
			verifiedResults:new Map(verifiedAdResults.map(result=>[result.url,result])),
			requestContext:createRequestContext(needsDetails ? 'watch.details':'watch.activity',operationContext)});
		const results=Array.isArray(activitySummary?.results)
			? activitySummary.results
			:[];
		const activeResults=results.filter(result => result?.active && !result.stale && !result.unknown && !result.error);
		let inspectedImageUrl= activeResults .map(result => result.imageUrl) .find(Boolean) || '';
		if ( fullCheck && item.sourceType !== 'escort-ad' && isWatchThumbnailKnownUnavailable(item.imageUrl) ) {
			const cachedAdImages=adUrls .map(url => parseAdIdFromUrl(url)) .filter(Boolean) .map(adId =>
					getStoredEscortAdData(adId)?.imageUrl || '' ) .filter(Boolean);
			const replacementImageUrl= await findNextAvailableWatchThumbnail( item.imageUrl, [ ...activeResults
							.map(result => result.imageUrl) .filter(Boolean), ...cachedAdImages, profileImageUrl ] );
			imageUrl=replacementImageUrl || '';
			if (replacementImageUrl) {
				inspectedImageUrl=replacementImageUrl;
			}
		}
		const unknownUrls=new Set(results.filter(result => result?.unknown).map(result => normalizeEscortiAdUrl(result.url)).filter(Boolean));
		const unknownCount=unknownUrls.size;
		const activeUrls=[...new Set([...activeResults.map(result => normalizeEscortiAdUrl(result.url)),
			...(item.state?.activeUrls || []).map(url => normalizeEscortiAdUrl(url)).filter(url => unknownUrls.has(url))].filter(Boolean))];
		const observedCities=[...new Set(activeResults.map(result => normalizeEscortCity(result.city)).filter(Boolean))];
		const activeWithoutCity=activeResults.some(result => !normalizeEscortCity(result.city));
		const locationPartial=unknownCount > 0 || activeWithoutCity;
		const previousCities=Array.isArray(item.state?.cities) ? item.state.cities.map(normalizeEscortCity).filter(Boolean):[];
		const cities=locationPartial ? previousCities:observedCities;
		const activeCount=activeUrls.length;
		const previousOnline=typeof item.state?.online === 'boolean' ? item.state.online:null;
		const online=adUrls.length === 0 ? null:(activeCount > 0 ? true:(unknownCount > 0 ? previousOnline:false));
		if ( !hasUsefulWatchThumbnail(item) && online === true && previousOnline !== true ) {
			imageUrl= inspectedImageUrl || ( isWatchThumbnailKnownUnavailable( profileImageUrl )
						? ''
						:profileImageUrl ) || imageUrl;
		}
		const state={
			online, activeCount, totalAds:adUrls.length, cities, activeUrls,
			adDetails:activeResults.filter(result => result?.adData).map(result => ({
				url:normalizeEscortiAdUrl(result.url) || result.url, adData:result.adData
			})), profileDates:[...new Set(profileInfos.map(info => formatEscortiDate(info.creationDate)).filter(Boolean))],
			unknownCount, partial:unknownCount > 0 || !!activitySummary?.detailsUnknownCount, locationPartial, checkedAt:Date.now()
		};
		registerEscortDataFieldsFromProfileState(state);
		return {
			displayName, imageUrl, profileUrls, adUrls, activeResults, profileInfos, state,
			historyFullCheckCompleted:!!fullCheck, fullCheckCompleted:!!fullCheck && !unknownCount && !activitySummary?.detailsUnknownCount
		};
	}
	function describeFavoriteVisitChanges( changes, {includeAddress=true}={} ) {
		const lines=[];
		if (changes.title) {
			lines.push( `Nazwa: ${
					formatEscortChangedValue( changes.title.previous )
				} → ${
					formatEscortChangedValue( changes.title.current )
				}` );
		}
		if (changes.image) {
			lines.push( 'Główne zdjęcie: zmieniono' );
		}
		if (includeAddress && changes.address) {
			lines.push( `Adres: ${
					formatEscortChangedValue( changes.address.previous )
				} → ${
					formatEscortChangedValue( changes.address.current )
				}` );
		}
		for (const change of changes.prices || []) {
			lines.push(`Cena ${getEscortPriceDurationLabel(change.durationKey)}: ${formatEscortChangedPrice(change.previous)} → ${formatEscortChangedPrice(change.current)}`);
		}
		for (const change of changes.availability || []) lines.push(`${change.day}: ${change.previous} → ${change.current}`);
		if (changes.tags) {
			if (changes.tags.added?.length) lines.push(`Dodane tagi: ${changes.tags.added.join(', ')}`);
			if (changes.tags.removed?.length) lines.push(`Usunięte tagi: ${changes.tags.removed.join(', ')}`);
		}
		if (changes.description) lines.push('Opis: zmieniono treść');
		if (changes.phone) lines.push(`Telefon: ${changes.phone.previous} → ${changes.phone.current}`);
		for (const change of changes.stats || []) lines.push(`${change.label}: ${formatEscortChangedValue(change.previous)} → ${formatEscortChangedValue(change.current)}`);
		return lines;
	}
	function getWatchHistoricalAdIdsBefore(cutoff,itemId='') {
		const before=Number(cutoff) || 0;
		const ownItemId=String(itemId || '');
		const ids=new Set();
		const addUrl=url => {
			const id=parseAdIdFromUrl(url);
			if (id) ids.add(String(id));
		};
		for (const event of getWatchEvents()) {
			if (ownItemId && String(event?.itemId || '') !== ownItemId) continue;
			if (before && Number(event?.timestamp) >= before) continue;
			addUrl(event?.url);
			for (const source of [event?.message,event?.description]) {
				for (const line of String(source || '').split(/\r?\n/)) {
					for (const match of line.matchAll(/\banons(?:u|\s+profilu)?\s*[:#-]?\s*(\d{3,7})\b/gi)) ids.add(match[1]);
					const list=line.match(/(?:włączone|wyłączone)\s+anonse\s*:\s*([\d,\s]+)/i);
					if (list) for (const match of list[1].matchAll(/\b(\d{3,7})\b/g)) ids.add(match[1]);
				}
			}
		}
		return ids;
	}
	function isActuallyNewFavoriteSearchAd(item,entry,historicalIds) {
		if (item?.sourceType !== 'escort-search') return true;
		const id=String(entry?.id || '');
		if (!id || historicalIds?.has(id)) return false;
		// Równe watchSnapshotCheckedAt i lastFullCheckedAt potwierdzają wcześniejszy wynik
		// tego wyszukiwania, także po utracie knownAdIds/resolvedAdUrls.
		const lastFullCheckedAt=Number(item.lastFullCheckedAt) || 0;
		if (lastFullCheckedAt) {
			const cached=getStoredEscortAdData(id);
			if (cached?.watchSnapshot && Number(cached.watchSnapshotCheckedAt) === lastFullCheckedAt) return false;
		}
		// Dane innych profili i wyszukiwań nie tłumią alertu tego wyszukiwania.
		return true;
	}
	function applyWatchedResult(item,result,notificationEvents=null) {
		const previous=item.state;
		const current={...result.state};
		current.lastKnownCities=current.cities?.length&&!current.locationPartial?current.cities:(previous?.lastKnownCities?.length?previous.lastKnownCities:previous?.cities||[]);
		current.escortiData=result.profileInfos?.length?compactEscortListResult(mergeEscortiProfileResults(result.profileInfos,result.profileInfos.length)):previous?.escortiData||null;
		if(current.online===false&&previous?.escortiData?.garsoTopicTitles?.length&&!current.escortiData?.garsoTopicTitles?.length&&
			JSON.stringify([...(previous.escortiData.profileIds||[])].sort())===JSON.stringify([...(current.escortiData?.profileIds||[])].sort()))
			current.escortiData={...current.escortiData,garsoTopicTitles:previous.escortiData.garsoTopicTitles,garsoTopics:previous.escortiData.garsoTopics};
		if(current.escortiData)registerEscortDataFieldsFromEscortiResult(expandEscortListCacheEntry(current.escortiData,item.resolutionMode));
		const emitEvent=(entry,type,message,url,description='',options={})=>createWatchEvent(entry,type,message,url,description,{...options,transitionAt:previous?.checkedAt,notificationEvents});
		const defaultEventUrl= result.activeResults?.[0]?.url || result.profileUrls?.[0] || item.adUrl;
		const contentChangeLabels=new Set();
		const contentChangeDescriptions=[];
		const addressChangeDescriptions=[];
		const previousKnownAdIds=new Set( [...(item.sourceType === 'escort-search' ? item.knownAdIds || []:[]),
				...(item.sourceType === 'escort-search' ? getWatchedItemHistoricalAdUrls(item).map(parseAdIdFromUrl):[]),
				...(item.resolvedAdUrls || []).map(parseAdIdFromUrl)].filter(Boolean).map(String) );
		const historicalSearchAdIds=item.sourceType === 'escort-search'
			? getWatchHistoricalAdIdsBefore(previous?.checkedAt || item.lastCheckedAt,item.id)
			:null;
		const newlyDiscoveredAds= item.sourceType === 'escort-ad'
				? []
				:(result.adUrls || []) .map(url => ({
						url:normalizeEscortiAdUrl(url) || url, id:parseAdIdFromUrl(url)
					})) .filter(entry => entry.id && !previousKnownAdIds.has(String(entry.id)) )
					.filter(entry => isActuallyNewFavoriteSearchAd(item,entry,historicalSearchAdIds));
		for (const detail of current.adDetails || []) {
			const adId=parseAdIdFromUrl(detail.url);
			if (!adId || !detail.adData) continue;
			const central=getStoredEscortAdData(adId);
			const previousSnapshot=central?.watchSnapshot || null;
			const currentSnapshot=createEscortVisitSnapshot( detail.adData, previousSnapshot );
			if ( previous?.checkedAt && previousSnapshot ) {
				const changes= compareEscortAdVisitData( previousSnapshot, currentSnapshot );
				const labels= changes.labels || [];
				for (const label of labels) {
					if (label === 'adres') continue;
					contentChangeLabels.add(label);
				}
				const contentLines= describeFavoriteVisitChanges( changes, {includeAddress:false} );
				if (contentLines.length) {
					contentChangeDescriptions.push( `Anons ${adId}:\n${
							contentLines.join('\n')
						}` );
				}
				if (changes.address) {
					addressChangeDescriptions.push( `Anons ${adId}:\nAdres: ${
							formatEscortChangedValue( changes.address.previous )
						} → ${
							formatEscortChangedValue( changes.address.current )
						}` );
				}
			}
			storeWatchedAdDataInCentralCache( adId, detail.adData, current.checkedAt, result.historyFullCheckCompleted
					? currentSnapshot
					:previousSnapshot );
		}
		const previousActiveIds=new Set( (previous?.activeUrls || []) .map(parseAdIdFromUrl) .filter(Boolean) .map(String) );
		const currentActiveIds=new Set( (current?.activeUrls || []) .map(parseAdIdFromUrl) .filter(Boolean) .map(String) );
		const enabledAds=[...currentActiveIds] .filter(id => !previousActiveIds.has(id) );
		const disabledAds=[...previousActiveIds] .filter(id => !currentActiveIds.has(id) );
		const updated=normalizeWatchedItem({
			...item, displayName:item.customDisplayName || result.displayName || item.displayName,
			customDisplayName:item.customDisplayName || null, imageUrl:result.imageUrl || item.imageUrl,
			profileUrls:result.profileUrls || item.profileUrls,
			...(item.sourceType === 'escort-search' ? {knownAdIds:[...previousKnownAdIds]}:{}), resolvedAdUrls:
				item.sourceType === 'escort-search'
					? (result.adUrls || item.resolvedAdUrls)
					:[...new Set([ ...getWatchedItemHistoricalAdUrls(item), ...(result.adUrls || []) ])], state:current,
			lastCheckedAt:current.checkedAt, lastError:null
		});
		if (previous?.checkedAt) {
			if ( item.sourceType !== 'escort-ad' && newlyDiscoveredAds.length ) {
				for (const entry of newlyDiscoveredAds) {
					const activeResult=result.activeResults?.find( active => String(parseAdIdFromUrl(active?.url)) === String(entry.id)
					);
					const city= normalizeEscortCity(activeResult?.city) || normalizeEscortCity( result.profileInfos
								?.map(info => info.currentCity) .find(Boolean) );
					emitEvent( updated, 'new-ad', `${updated.displayName} - nowy anons ${entry.id}${city ? ` • ${city}`:''}.`,
						entry.url || defaultEventUrl, [ `Nowy anons profilu: ${entry.id}`, city ? `Lokalizacja: ${city}`:''
						].filter(Boolean).join('\n'), {
							alertTriggered:!!item.modes.newAds, alertMode:'newAds'
						} );
				}
			}
			if (enabledAds.length) {
				emitEvent( updated, 'online', `${updated.displayName} - Włączone anonse: ${
						enabledAds.join(', ')
					}.`, current.activeUrls?.[0] || defaultEventUrl, `Włączone anonse: ${enabledAds.join(', ')}`, {
						alertTriggered:!!item.modes.online, alertMode:'online'
					} );
			}
			if (disabledAds.length) {
				emitEvent( updated, 'online', `${updated.displayName} - Wyłączone anonse: ${
						disabledAds.join(', ')
					}.`, result.profileUrls?.[0] || item.profileUrls?.[0] || defaultEventUrl,
					`Wyłączone anonse: ${disabledAds.join(', ')}`, {
						alertTriggered:false, alertMode:'online'
					} );
			}
			if ( previous.online === false && current.online === true && !enabledAds.length ) {
				emitEvent( updated, 'online', `${updated.displayName} pojawiła się online (${
						current.activeCount
					} aktywne).`, current.activeUrls?.[0] || defaultEventUrl, '', {
						alertTriggered:!!item.modes.online, alertMode:'online'
					} );
			} else if ( previous.online === true && current.online === false && !disabledAds.length ) {
				emitEvent( updated, 'online', `${updated.displayName} jest offline.`, result.profileUrls?.[0] ||
						item.profileUrls?.[0] || defaultEventUrl, '', {
						alertTriggered:false, alertMode:'online'
					} );
			}
			if ( !current.locationPartial && current.cities.length && watchCitiesKey(previous.cities) !==
					watchCitiesKey(current.cities) ) {
				const target= item.locationTarget || {type:'any',city:''};
				let locationAlertTriggered=false;
				let locationMessage= `${watchCitiesText(previous.cities)} → ${watchCitiesText(current.cities)}`;
				let locationUrl= current.activeUrls?.[0] || defaultEventUrl;
				if (item.modes.location) {
					if (target.type === 'city') {
						const targetKey= normalizeEscortCityKey(target.city);
						const previousMatch= (previous.cities || []).some( city => normalizeEscortCityKey(city) === targetKey );
						const currentMatch= current.cities.some( city => normalizeEscortCityKey(city) === targetKey );
						locationAlertTriggered= !previousMatch && currentMatch;
						if (locationAlertTriggered) {
							locationMessage= `${updated.displayName} pojawiła się w mieście ${target.city}.`;
							locationUrl= result.activeResults?.find( active => normalizeEscortCityKey(active.city) === targetKey )?.url ||
								locationUrl;
						}
					} else {
						locationAlertTriggered=true;
					}
				}
				emitEvent( updated, 'location', locationMessage, locationUrl, '', {
						alertTriggered: locationAlertTriggered, alertMode:'location'
					} );
			}
			if (addressChangeDescriptions.length) {
				emitEvent( updated, 'location', `${updated.displayName} - zmiana lokalizacji: adres.`,
					current.activeUrls?.[0] || defaultEventUrl, addressChangeDescriptions.join('\n\n'), {
						alertTriggered: !!item.modes.location && item.locationTarget?.type !== 'city', alertMode:'location'
					} );
			}
			if (contentChangeLabels.size) {
				const labels=[...contentChangeLabels];
				emitEvent( updated, 'changes', `${updated.displayName} - zmiany: ${labels.join(', ')}.`,
					current.activeUrls?.[0] || defaultEventUrl, contentChangeDescriptions.join('\n\n'), {
						alertTriggered:!!item.modes.changes, alertMode:'changes'
					} );
			}
		}
		return updated;
	}
	async function runWatchedChecks(options={}) {
		if (!getWatchSettings().enabled) return {status:'disabled',message:'Sprawdzanie wyłączone w ustawieniach.'};
		const manual=options.force === true && !options.scheduled;
		if (!manual && watchedCheckPromise) return watchedCheckPromise;
		let request=null;
		if (manual) {
			const items=new Map(getWatchedItems().map(item => [item.id,item]));
			request={at:Date.now(),items:options.itemIds ? new Map(options.itemIds.map(id => [String(id),items.get(String(id))])):items};
			watchManualRequests.add(request);
		}
		let lastMessage='';
		try {
			while (!watchCheckPageClosed && !request?.cancelled && getWatchSettings().enabled) {
				if (request) publishWatchManualRequests();
				const turn=getWatchManualTurn();
				if (!watchedCheckPromise && (!turn || manual && turn === WATCH_MANUAL_CHECK_PREFIX + WATCH_INSTANCE_ID)) {
					const reused=request ? getWatchedItems().filter(item => {
						const before=request.items.get(item.id);
						const field=options.hiddenOnly ? 'hiddenLastCheckedAt':item.sourceType !== 'escort-ad' && !item.favorite && !watchedItemHasActiveAlerts(item) ? 'lastCheckedAt':'lastFullCheckedAt';
						return before && sameWatchedDefinition(item,before) && !item[options.hiddenOnly ? 'hiddenLastError':'lastError'] &&
							Number(item[field]) >= request.at && Number(item[field]) > (Number(before[field]) || 0);
					}).map(item => item.id):[];
					const remaining=request ? [...request.items.keys()].filter(id => !reused.includes(id)):options.itemIds;
					let result;
					if (request && !remaining.length) result={status:reused.length ? 'completed':'empty',checked:0,failed:0,skipped:0};
					else {
						const task=performWatchedChecks({...options,itemIds:remaining});
						watchedCheckPromise=task;
						try {result=await task;} finally {if (watchedCheckPromise === task) watchedCheckPromise=null;}
					}
					if (reused.length && result.status === 'empty') Object.assign(result,{status:'completed',message:''});
					if (result.status !== 'busy' || !manual) return request ? {...result,checked:(result.checked || 0) + reused.length,total:request.items.size,
						...(reused.length && !remaining.length ? {message:`Odświeżono: ${reused.length}/${request.items.size}. Wykorzystano wyniki sprawdzania zakończonego podczas oczekiwania.`}:{})}:result;
				} else if (!manual) return watchedCheckPromise || {status:'busy',message:'Oczekuje ręczne odświeżenie zapisanych pozycji.'};
				const lock=GM_getValue(WATCH_CHECK_LOCK_KEY,null),progress=lock?.progress,queue=getEscortRequestQueueStatus();
				const message=queue.reloadRequired ? 'Odświeżenie oczekuje. Escort.club: BAN na IP — po odzyskaniu dostępu odśwież stronę.'
					:queue.blockedUntil > Date.now() ? `Odświeżenie oczekuje. Pobieranie wstrzymane do ${new Date(queue.blockedUntil).toLocaleTimeString('pl-PL')}.`
					:lock?.expiresAt > Date.now() ? `Odświeżenie oczekuje. Trwa ${lock.kind === 'scheduled' ? 'automatyczne':'ręczne'} sprawdzanie zapisanych pozycji${progress?.total ? `: ${progress.checked || 0}/${progress.total}`:''}${progress?.adTotal ? ` • dane ${progress.adChecked || 0}/${progress.adTotal}`:''}.`
						:'Odświeżenie zapisane w kolejce — rozpocznie się automatycznie.';
				if (message !== lastMessage) {lastMessage=message; options.onProgress?.({phase:'waiting',message});}
				await new Promise(resolve => setTimeout(resolve,1000));
			}
			return {status:'interrupted',message:watchCheckPageClosed || request?.cancelled ? 'Anulowano oczekujące odświeżenie po opuszczeniu strony.':'Sprawdzanie wyłączone w ustawieniach.'};
		} finally {if (request) {watchManualRequests.delete(request); publishWatchManualRequests();}}
	}
	async function performWatchedChecks({force=false,itemIds=null,onProgress=null,scheduled=false,hiddenOnly=false}={}) {
		const operationContext=createRequestContext(hiddenOnly ? 'watch.hidden':scheduled ? 'watch.scheduled':'watch.manual');
		const settings=getWatchSettings();
		if (!settings.enabled) {
			return {status:'disabled',message:'Sprawdzanie wyłączone w ustawieniach.'};
		}
		if (!(await acquireWatchCheckLock(scheduled ? 'scheduled':'manual'))) {
			return {status:'busy',message:'Trwa sprawdzanie zapisanych pozycji w innej karcie.'};
		}
		const notificationEvents=[];
		const watchLockHeartbeat=setInterval(() => {
			try {touchWatchCheckLock(); if (watchManualRequests.size) publishWatchManualRequests();} catch {}
		},WATCH_CHECK_LOCK_HEARTBEAT_MS);
		return (async () => {
			let items=getWatchedItems();
			const allowedIds=itemIds ? new Set(itemIds.map(String)):null;
			const now=Date.now();
			const dueBefore= now - settings.intervalMinutes * 60000;
			let scheduledFullCheckCompleted=false;
			const isWatchDue=item => {
				const timing=getWatchedItemCheckTiming(item,settings);
				if (hiddenOnly || !timing.eligible) return false;
				if (scheduled) return now >= timing.dueAt;
				if (force) return true;
				return !item.lastCheckedAt || item.lastCheckedAt <= dueBefore;
			};
			const isHiddenDue=item => item.hidden && item.sourceType !== 'escort-ad' &&
				((force && !scheduled) || now - item.hiddenLastCheckedAt >= HIDDEN_PROFILE_CHECK_INTERVAL_MS);
			const due=items.filter(item => (!allowedIds || allowedIds.has(item.id)) && (isWatchDue(item) || isHiddenDue(item)));
			const outcome={status:'completed',checked:0,failed:0,
				skipped:allowedIds ? allowedIds.size - due.length:0,total:allowedIds?.size ?? due.length};
			if (!due.length) Object.assign(outcome,{status:'empty',message:'Nie rozpoczęto sprawdzania - brak pozycji do sprawdzenia. Wybrane wpisy mogły zostać usunięte lub nie wymagają sprawdzania.'});
			if (due.length) {await persistentCacheInitialization; await ensureFullPersistentCache();}
			for (let index=0; index < due.length; index++) {
				if (scheduled && getWatchManualTurn()) {outcome.status='yielded'; break;}
				if (!getWatchSettings().enabled || !ownsWatchCheckLock()) {outcome.status='interrupted'; break;}
				touchWatchCheckLock();
				const candidate=getWatchedItems().find(item => item.id === due[index].id);
				if (!candidate || (!isWatchDue(candidate) && !isHiddenDue(candidate))) {outcome.skipped++; continue;}
				const discoveryOnly=!isWatchDue(candidate);
				const itemContext=createRequestContext(discoveryOnly ? 'watch.hidden-item':'watch.checked-item',operationContext);
				const reportProgress=adProgress => {
					const progress={
						checked:index, total:due.length, itemIndex:index + 1, itemName:candidate.displayName,
						adChecked:Number(adProgress?.checked) || 0, adTotal:Number(adProgress?.total) || 0,
						activeCount:Number(adProgress?.activeCount) || 0, phase:'checking'
					};
					touchWatchCheckLock(progress); onProgress?.(progress);
				};
				reportProgress({checked:0,total:0,activeCount:0});
				try {
					const result=discoveryOnly ? await resolveWatchedProfileData(candidate,itemContext):await inspectWatchedItem(candidate,reportProgress,{
						fullCheck:!scheduled || shouldRunScheduledFullWatchCheck(candidate,settings),requestContext:itemContext
					});
					if (!ownsWatchCheckLock()) {outcome.status='interrupted'; outcome.skipped++; break;}
					if (discoveryOnly) {
						updateWatchedItem(candidate.id,current => current.hidden && sameWatchedDefinition(current,candidate) ? {
							displayName:current.customDisplayName || result.displayName || current.displayName,
							imageUrl:result.imageUrl || current.imageUrl,
							profileUrls:result.profileUrls, resolvedAdUrls:result.adUrls,
							state:{...current.state,activeUrls:(current.state?.activeUrls || []).filter(url => result.adUrls.includes(url))},
							hiddenLastCheckedAt:Date.now(),hiddenLastError:null
						}:{});
					} else {
						if (result.fullCheckCompleted) {
							scheduledFullCheckCompleted=true;
						}
						const latest=getWatchedItems().find(entry => entry.id === candidate.id);
						if (!sameWatchedDefinition(latest,candidate)) {outcome.skipped++; continue;}
						touchWatchCheckLock();
						const updated=applyWatchedResult(latest,result,notificationEvents);
						updateWatchedItem( candidate.id, current => watchedResultUpdatePatch( current, updated, result.fullCheckCompleted
							) );
					}
				} catch (error) {
					if (!ownsWatchCheckLock()) {outcome.status='interrupted'; outcome.skipped++; break;}
					outcome.failed++;
					updateWatchedItem(candidate.id,current => sameWatchedDefinition(current,candidate) ? ({
						...(!discoveryOnly ? {lastCheckedAt:Date.now(),lastError:formatHelperDiagnosticError(error)}:{}),
						...(current.hidden ? {hiddenLastCheckedAt:Date.now(),hiddenLastError:formatHelperDiagnosticError(error)}:{})
					}):{});
				}
				outcome.checked++;
				items=getWatchedItems();
				renderWatchNotificationBar();
				document.getElementById(WATCH_SETTINGS_OVERLAY_ID)?._vmRender?.();
				const currentItem=items.find(item => item.id === candidate.id);
				const progress={
					checked:index + 1, total:due.length, itemIndex:index + 1, itemName:candidate.displayName,
					adChecked:Number(currentItem?.state?.totalAds) || 0, adTotal:Number(currentItem?.state?.totalAds) || 0,
					activeCount:Number(currentItem?.state?.activeCount) || 0, phase:'done'
				};
				touchWatchCheckLock(progress); onProgress?.(progress);
			}
			if (scheduled) {
				recordDiagnosticWatchScheduleRun( scheduledFullCheckCompleted );
			}
			return outcome;
		})().finally(async () => {
			clearInterval(watchLockHeartbeat); releaseWatchCheckLock();
			await showWatchSystemNotification(notificationEvents);
		});
	}
	function watchedResultUpdatePatch(current,updated,fullCheckCompleted=true) {
		return {
			displayName: current.customDisplayName || updated.displayName || current.displayName,
			imageUrl:updated.imageUrl || current.imageUrl, profileUrls:updated.profileUrls || current.profileUrls,
			resolvedAdUrls:updated.resolvedAdUrls || current.resolvedAdUrls,
			...(current.sourceType === 'escort-search' ? {knownAdIds:updated.knownAdIds}:{}), state:updated.state,
			lastCheckedAt:updated.lastCheckedAt, lastFullCheckedAt:fullCheckCompleted
				? updated.lastCheckedAt
				:current.lastFullCheckedAt, lastError:updated.lastError, ...(current.hidden && current.sourceType !== 'escort-ad'
				? {hiddenLastCheckedAt:updated.lastCheckedAt,hiddenLastError:null}:{})
		};
	}
	function sameWatchedDefinition(current,previous) {
		return !!current && current.resolutionMode === previous.resolutionMode &&
			!!current.escortiProfileOnly === !!previous.escortiProfileOnly &&
			getEscortiWatchSearchValue(current) === getEscortiWatchSearchValue(previous) &&
			(previous.sourceType !== 'escort-search' || current.watchKey === previous.watchKey);
	}
	async function refreshFavoriteItemData(item,reportError=false) {
		const operationContext=createRequestContext('watch.manual-refresh');
		try {
			const result=await inspectWatchedItem(item,null,{requestContext:operationContext});
			const latest= getWatchedItems().find(entry => entry.id === item.id);
			if (!sameWatchedDefinition(latest,item)) return latest;
			const updated=applyWatchedResult(latest,result);
			return updateWatchedItem( item.id, current => watchedResultUpdatePatch(current,updated,result.fullCheckCompleted) ) || latest;
		} catch (error) {
			if (reportError && sameWatchedDefinition(getWatchedItems().find(entry => entry.id === item.id),item)) {
				const saved=updateWatchedItem(item.id,{lastError:error?.message || 'Nie udało się sprawdzić profilu.'});
				if (saved) showWatchToast(`Profil zapisany. Sprawdzenie nie powiodło się: ${saved.lastError}`,true);
			}
			return getWatchedItems().find(entry => entry.id === item.id) || item;
		}
	}
	function getTrackedScopeItem(matches,scope) {
		const items=Array.isArray(matches) ? matches:[];
		return scope === 'single'
			? items.find(item => item.sourceType === 'escort-ad') || null
			:items.find(item => item.sourceType !== 'escort-ad') || null;
	}
	function trackedRemovalHint(kind) {
		const hints={
			profile:'Usunę cały wpis: notatkę, przypisane kategorie, Ulubione, ukrywanie, alerty, obserwowanie i historię powiadomień. Inne profile i osobne wpisy anonsów pozostaną.',
			favorite:'Usunę z Ulubionych i wyłączę wszystkie alerty oraz obserwowanie. Notatka, kategorie i ukrywanie pozostaną.',
			hidden:'Wyłączę ukrywanie tego wpisu. Ulubione, notatka i kategorie pozostaną. Anons nadal może być ukryty przez inny wpis.',
			notes:'Usunę notatkę i wszystkie przypisane kategorie. Ulubione i ukrywanie pozostaną. Nie usunę kategorii ze słownika ani danych innych wpisów.',
			category:'Usunę tylko przypisanie tej kategorii. Notatka, inne kategorie, Ulubione i ukrywanie pozostaną. Kategoria pozostanie w słowniku.'
		};
		return hints[kind] + (kind === 'profile' ? '':' Profil bez notatki, kategorii, Ulubionych i ukrywania zostanie usunięty wraz z obserwowaniem i historią powiadomień.');
	}
	let trackedUndoDepth=0;
	function restoreTrackedUndoValue(before,after,current) {
		const same=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
		if (same(before,after)) return current;
		if (same(current,after)) return copyWatchedValue(before);
		if ([before,after,current].every(value=>value && typeof value==='object' && !Array.isArray(value))) {
			const next={...current};
			for (const key of new Set([...Object.keys(before),...Object.keys(after)])) {
				const value=restoreTrackedUndoValue(before[key],after[key],current[key]);
				if (value===undefined) delete next[key]; else next[key]=value;
			}
			return next;
		}
		return current;
	}
	function withTrackedUndo(message,action,enabled=()=>true,categoryDictionary=null) {
		if (trackedUndoDepth || !enabled()) return action();
		const before=getWatchedItems(),eventsBefore=getWatchEvents();
		let result;
		trackedUndoDepth++;
		try {result=action();} finally {trackedUndoDepth--;}
		const after=new Map(getWatchedItems().map(item=>[item.id,item]));
		const changed=before.filter(item=>JSON.stringify(item)!==JSON.stringify(after.get(item.id)));
		if (!changed.length && !categoryDictionary?.names.length) return result;
		const eventIds=new Set(getWatchEvents().map(event=>event.id));
		const removedEvents=eventsBefore.filter(event=>!eventIds.has(event.id));
		showWatchToast(message,false,{id:'vm-tracked-undo',duration:10000,style:{bottom:'62px'},actionLabel:'Cofnij',onAction:()=>{
			const current=getWatchedItems(),restored=new Set();
			const indices=new Map(current.map((item,index)=>[item.id,index]));
			let restoredDefinitions=0;
			if (categoryDictionary) {
				const settings=getFavoriteSettings(),missing=categoryDictionary.names.filter(name=>!settings.categories.includes(name));
				if (missing.length) {
					updateSettings({favoriteCategories:[...settings.categories,...missing],favoriteCategoryColors:{...settings.categoryColors,
						...Object.fromEntries(missing.map(name=>[name,categoryDictionary.colors[name] || '#c8c8c8']))}});
					restoredDefinitions=missing.length;
				}
			}
			for (const original of changed) {
				const expected=after.get(original.id),index=indices.get(original.id) ?? -1;
				if (!expected) {
					if (index<0 && !current.some(item=>watchedItemsOverlap(item,original))) {current.push(original);restored.add(original.id);}
					continue;
				}
				if (index<0) continue;
				const latest=current[index],next=restoreTrackedUndoValue(original,expected,latest);
				// Przywróć usunięte kategorie, zachowując później dodane.
				const removed=original.categories.filter(category=>!expected.categories.includes(category) && (!categoryDictionary || categoryDictionary.names.includes(category)));
				next.categories=normalizeFavoriteAssignedCategories([...latest.categories,...removed]);
				if (JSON.stringify(next)!==JSON.stringify(latest)) {current[index]=next;restored.add(original.id);}
			}
			if (!restored.size && !restoredDefinitions) return showWatchToast('Wpis został później zmieniony. Nie nadpisano tych zmian.',false);
			saveWatchedItems(current);
			const events=getWatchEvents(),ids=new Set(events.map(event=>event.id));
			saveWatchEvents([...events,...removedEvents.filter(event=>restored.has(event.itemId)&&!ids.has(event.id))]);
			initLocalWatchSystem();renderWatchNotificationBar();refreshWatchPageButtons();
			showWatchToast('Przywrócono usunięte dane.');
		}});
		return result;
	}
	function changeTrackedItemStatus(itemId,action) {
		const item=getWatchedItems().find(entry => entry.id === itemId);
		if (!item) return;
		if (action === 'remove') {
			if (window.confirm(`Usunąć cały wpis „${item.displayName}”?\n\n${trackedRemovalHint('profile')}\n\nPrzez 10 sekund będzie dostępne „Cofnij”.`)) removeWatchedItem(itemId);
		} else if (action === 'favorite' && item.favorite) removeFavoriteOnly(itemId);
		else if (action === 'hidden' && item.hidden) restoreHiddenItems([itemId]);
		else if (action === 'favorite' || action === 'hidden') {
			const saved=updateWatchedItem(itemId,{[action]:true,
				...(action === 'hidden' ? {hiddenAt:Date.now(),hiddenLastCheckedAt:0,hiddenLastError:null}:{})});
			initLocalWatchSystem();
			if (saved) refreshFavoriteItemData(saved,true);
		}
	}
	function canRemoveEmptyTrackedItem(item) {
		return !!item && !item.preserveProfile && !item.hidden && !item.favorite && !watchedItemHasNotesOrCategories(item) &&
			(item.sourceType !== 'escort-ad' || !watchedItemHasActiveAlerts(item));
	}
	function editTrackedNoteDialog(title,value='') {
		return new Promise(resolve => {
			const previous=document.activeElement;
			const {overlay,box}=createWatchDialogBase('vm-tracked-note-editor',title);
			let result=null;
			box.setAttribute('role','dialog'); box.setAttribute('aria-modal','true'); box.setAttribute('aria-label',title);
			const field=makeElement('textarea','vm-ui-field vm-ui-textarea',null,{value,rows:7,maxLength:2500}); field.setAttribute('aria-label','Notatka');
			const controller=createDialogCloseController(overlay,{onClose:() => {
				if (previous?.isConnected) previous.focus({preventScroll:true});
				resolve(result);
			}});
			const actions=makeElement('div','vm-ui-actions');
			const cancel=makeButton('vm-ui-button','Anuluj'),save=makeButton('vm-ui-button -primary','Zapisz');
			cancel.addEventListener('click',() => controller.close('cancel'));
			const commit=() => {result=field.value.trim(); controller.close('save');};
			save.addEventListener('click',commit);
			field.addEventListener('keydown',event => {
				if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {stopUiEvent(event); commit();}
			});
			box.addEventListener('keydown',event => {
				if (event.key !== 'Tab') return;
				if (event.shiftKey && document.activeElement === field) {event.preventDefault(); save.focus();}
				else if (!event.shiftKey && document.activeElement === save) {event.preventDefault(); field.focus();}
			});
			actions.append(cancel,save); box.append(field,actions); field.focus();
		});
	}
	function ensureTrackedNoteCategoryStyles() {
		if (document.getElementById('vm-tracked-note-category-style')) return;
		appendHelperStyle('vm-tracked-note-category-style',` .vm-escort-favorite-note-scope { min-width:0; max-width:100%; padding:4px 7px; box-sizing:border-box; border:1px solid color-mix(in srgb,var(--vm-ui-accent,#f54da3) 40%,transparent); border-left:2px solid var(--vm-ui-accent,#f54da3); border-radius:5px; background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 7%,transparent); color:var(--vm-ui-panel-text,var(--vm-ui-text,var(--vm-tracked-text-fallback,#222))); }
.vm-escort-favorite-note-line { min-width:0; max-width:100%; line-height:1.22; white-space:normal; overflow-wrap:anywhere; }
.vm-escort-favorite-note-label { margin-right:5px; color:var(--vm-ui-panel-muted,var(--vm-ui-text-muted,var(--vm-tracked-text-fallback,#666))); font-size:8px; font-weight:400; white-space:nowrap; opacity:.72; }
.vm-escort-favorite-note-text { line-height:1.22; white-space:pre-wrap; overflow-wrap:anywhere; text-decoration:none; }
.vm-escort-inline-category-row { display:flex; flex-wrap:wrap; align-items:center; gap:4px; min-width:0; margin-top:3px; }
.vm-escort-inline-category-chip { display:inline-flex; align-items:center; gap:3px; min-width:0; min-height:22px; max-width:100%; padding:1px 4px 1px 7px; border-radius:999px; font-size:10px; font-weight:700; line-height:16px; box-sizing:border-box; white-space:normal; overflow-wrap:anywhere; color:var(--vm-ui-panel-text,var(--vm-ui-text,var(--vm-tracked-text-fallback,#222))); }
.vm-escort-inline-category-remove { flex:0 0 16px; width:16px; height:16px; padding:0; margin:0; border:0; border-radius:50%; background:transparent; color:var(--vm-ui-text-muted,#777); font-size:11px; font-weight:700; line-height:12px; text-align:center; cursor:pointer; opacity:.65; }
.vm-escort-inline-category-add { display:inline-flex; align-items:center; justify-content:center; width:18px; min-width:18px; height:18px; padding:0; margin:0; border:1px solid var(--vm-ui-border,#d4d4d8); border-radius:999px; background:var(--vm-ui-bg-alt,rgba(250,250,250,.82)); color:var(--vm-ui-text-muted,#777); font-size:13px; font-weight:700; line-height:16px; text-align:center; box-sizing:border-box; cursor:pointer; opacity:.30; }
.vm-escort-inline-alerts { flex:0 0 26px; margin-left:auto; width:26px; min-width:26px; height:26px; opacity:.8; }
.vm-escort-inline-alerts[data-vm-active="1"] { color:#382b08; background:#f4c542; border-color:#cda42e; opacity:1; }
.vm-escort-inline-alerts:hover, .vm-escort-inline-alerts:focus-visible { opacity:1; outline:1px solid currentColor; outline-offset:1px; }
.vm-escort-inline-alerts:disabled { cursor:wait; opacity:.45; }
.vm-escort-inline-alerts[data-vm-favorite-required="1"]:disabled { cursor:not-allowed; opacity:.30; }
.vm-tracked-box { display:grid; align-content:start; gap:6px; width:100%; max-width:520px; min-width:0; text-align:left; }
.vm-tracked-box[hidden], .vm-tracked-box [hidden] { display:none !important; }
.vm-tracked-list { display:grid; gap:8px; min-width:0; }
#vm-escort-favorite-note-row, #vm-escorti-profile-note-display { margin:8px 0 10px; }
#vm-escorti-profile-note-display { margin-inline:auto; }
/* Nagłówek i notatki obok pływającej galerii. */
.content-name:has(> #vm-escort-favorite-note-row) { display:flow-root; min-width:0; }
#vm-escort-favorite-note-row { clear:none !important; float:none !important; width:auto; max-width:100%; overflow-wrap:anywhere; }
.vm-tracked-entry, .vm-tracked-summary { --vm-ui-panel-text:var(--vm-ui-text,var(--vm-tracked-text-fallback,#222)); --vm-ui-panel-muted:var(--vm-ui-text-muted,var(--vm-tracked-text-fallback,#666)); display:flex; flex-direction:column; gap:5px; padding:8px 10px; font-size:11px; text-align:left; }
.vm-tracked-entry-heading { display:flex; align-items:baseline; gap:6px; min-width:0; }
.vm-tracked-entry-name { flex:0 1 auto; min-width:0; font-size:12px; font-weight:600; overflow-wrap:anywhere; }
.vm-tracked-entry-delete, .vm-tracked-profile-picker { display:inline-flex; align-items:center; gap:4px; flex:0 0 auto; padding:2px 0; border:0; background:transparent; color:var(--vm-ui-panel-muted,var(--vm-ui-text-muted,#888)); font-size:10px; font-weight:400; cursor:pointer; }
.vm-tracked-profile-picker { color:var(--vm-ui-accent,#f54da3); font-size:11px; }
.vm-tracked-profile-picker:hover { text-decoration:underline; }
.vm-tracked-profile-picker:disabled { opacity:.5; cursor:wait; }
.vm-tracked-entry-delete { margin-left:auto; align-self:flex-start; }
.vm-tracked-entry-delete svg { margin:0 !important; }
.vm-tracked-entry-delete:hover, .vm-tracked-entry-delete:focus-visible { color:var(--vm-ui-danger,#c62828); }
.vm-tracked-entry-heading [data-kind="rename"] { opacity:0; transition:opacity .12s ease; }
.vm-tracked-entry:hover [data-kind="rename"], .vm-tracked-entry:focus-within [data-kind="rename"] { opacity:1; }
@media (hover:none) { .vm-tracked-entry-heading [data-kind="rename"] { opacity:1; } }
.vm-tracked-entry-source { font-size:10px; line-height:1.3; white-space:normal; overflow-wrap:anywhere; color:var(--vm-ui-panel-muted,var(--vm-ui-text-muted,#888)); }
.vm-tracked-entry .vm-escort-favorite-note-label { display:none; }
.vm-tracked-entry .vm-escort-favorite-note-line { padding:3px 0; }
.vm-tracked-entry-action { flex:0 0 26px; width:26px; min-width:26px; height:26px; border-radius:5px; opacity:1; font-size:19px; font-weight:400; color:var(--vm-ui-accent,#f54da3); }
.vm-tracked-entry-action[data-kind="favorite"][data-vm-active="1"] { background:#f4c542; color:#000; border-color:#cda42e; }
.vm-tracked-entry-action[data-kind="hidden"][data-vm-active="1"] { background:#ff9b9b; color:#000; border-color:#db7777; }
.vm-tracked-entry-action:focus-visible, .vm-tracked-entry:focus { outline:2px solid var(--vm-ui-accent,#f54da3); outline-offset:2px; }
.vm-tracked-entry-action:disabled { opacity:.5; cursor:wait; }
.vm-tracked-summary.vm-escort-favorite-note-scope { position:relative; padding:2px 0; gap:5px; border:0; border-radius:0; background:transparent; box-shadow:none; color:var(--vm-ui-panel-text); }
.vm-tracked-entry.vm-escort-favorite-note-scope { width:100%; border:1px solid color-mix(in srgb,var(--vm-ui-accent,#f54da3) 55%,transparent); border-radius:7px; background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 5%,var(--vm-ui-bg,transparent)); }
.vm-tracked-summary-caption { display:flex; align-items:center; gap:8px; color:var(--vm-ui-panel-muted); font-size:11px; line-height:1.5; }
.vm-tracked-summary-caption::before { content:""; flex:0 0 6px; width:6px; height:6px; margin:0 2px 3px; border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor; transform:rotate(45deg); pointer-events:none; }
.vm-tracked-summary[aria-expanded="true"] > .vm-tracked-summary-caption::before { transform:rotate(225deg); margin-top:6px; }
.vm-tracked-category-add { width:auto; min-width:24px; height:24px; padding:0 7px; opacity:1; font-size:11px; }
.vm-tracked-summary:focus-visible, .vm-tracked-profile-picker:focus-visible { outline:2px solid var(--vm-ui-accent,#f54da3); outline-offset:2px; }
.vm-tracked-summary .vm-ui-help { color:var(--vm-ui-panel-muted); }
.vm-tracked-summary .vm-escort-favorite-note-text { display:block; white-space:pre-wrap; font-size:12px !important; line-height:1.45; }
.vm-tracked-summary .vm-escort-inline-alerts { margin-left:0; }
.vm-tracked-summary, .vm-tracked-summary .vm-tracked-entry-action { cursor:pointer; }
.vm-escort-inline-category-row:hover .vm-escort-inline-category-remove, .vm-escort-inline-category-row:hover .vm-escort-inline-category-add, .vm-escort-inline-category-row:focus-within .vm-escort-inline-category-remove, .vm-escort-inline-category-row:focus-within .vm-escort-inline-category-add { opacity:1; }
.vm-native-escort-action-hidden { display:none !important; }
`);
	}
	function createTrackedAlertsControl({alertItems=[],onEditAlerts=null,readOnly=false,alertsDisabled=false,labelText='Profil'}={}) {
		ensureTrackedNoteCategoryStyles();
		let bell=null;
		const activeAlerts=alertItems.filter(watchedItemHasActiveAlerts);
		if (onEditAlerts || (readOnly && activeAlerts.length)) {
			bell=readOnly ? makeElement('span'):makeButton();
			bell.className='vm-escort-inline-category-add vm-escort-inline-alerts';
			const active=activeAlerts.length > 0;
			bell.dataset.vmActive=active ? '1':'0';
			setControlTitle(bell,readOnly ? `Włączone alerty: ${activeAlerts.map(trackedItemChoiceLabel).join('; ')}`
				:`Alerty ${labelText === 'Anons' ? 'anonsu':'profilu'} - ${active ? 'włączone':'wyłączone'}. Kliknij, aby ustawić.`);
			if (readOnly) bell.setAttribute('role','img');
			else bell.setAttribute('aria-haspopup','dialog');
			bell.innerHTML='<svg aria-hidden="true" viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9M10 21h4"/></svg>';
			if (!readOnly && alertsDisabled) {
				bell.disabled=true;
				bell.dataset.vmFavoriteRequired='1';
				setControlTitle(bell,`Najpierw dodaj ${labelText === 'Anons' ? 'anons':'profil'} do Ulubionych, aby ustawić alerty.`);
			}
			if (!readOnly) bell.addEventListener('click',async event => {
				stopUiEvent(event);
				if (bell.disabled) return;
				bell.disabled=true;
				try {await onEditAlerts();}
				catch (error) {showWatchToast(`Nie udało się otworzyć lub zapisać alertów: ${error?.message || error}`,true);}
				finally {bell.disabled=false;}
			});
		}
		return bell;
	}
	function renderTrackedNoteCategoryBlock({
		host, labelText, readOnly=false, noteText='', categories=[], enabled=true,
		favoriteSettings, accent='#f54da3', fallbackText='#222', ownerKey='', alertItems=[], onEditAlerts=null,
		alertsDisabled=false, actions=[], onEditNote, onRemoveCategory, onAddCategory
	}) {
		ensureTrackedNoteCategoryStyles();
		prepareHelperStyleApi(host);
		host.classList.add('vm-escort-favorite-note-scope');
		host.style.setProperty('--vm-tracked-text-fallback',fallbackText);
		host.replaceChildren();
		const bell=createTrackedAlertsControl({alertItems,onEditAlerts,readOnly,alertsDisabled,labelText});
		const noteRow=makeElement('div','vm-escort-favorite-note-line');
		if (labelText) noteRow.appendChild(makeElement('span','vm-escort-favorite-note-label',`${labelText}:`));
		if (enabled && (!readOnly || noteText)) {
			if (!readOnly) {
				noteRow.tabIndex=0;
				noteRow.setAttribute('role','button');
				noteRow.title=noteText ? 'Kliknij tekst notatki, aby ją zmienić':'Dodaj notatkę';
				noteRow.style.cursor='pointer';
			}
			const value=makeElement('span','vm-escort-favorite-note-text',noteText || 'Dodaj notatkę');
			Object.assign(value.style,{
				color:noteText
					? 'var(--vm-ui-panel-text,var(--vm-ui-text,var(--vm-tracked-text-fallback,#222)))'
					:'var(--vm-ui-panel-muted,var(--vm-ui-text-muted,var(--vm-tracked-text-fallback,#666)))',
				fontSize:noteText ? '11px':'9px', fontWeight:noteText ? '500':'400', paddingBottom:noteText ? '0':'1px',
				opacity:noteText ? '1':'.70'
			});
			if (!noteText) value.style.setProperty('border-bottom','1px dotted currentColor','important');
			if (!readOnly) {
				const edit=async event => {
					stopUiEvent(event);
					await onEditNote?.();
				};
				noteRow.addEventListener('click',edit);
				noteRow.addEventListener('keydown',event => {
					if (event.key === 'Enter' || event.key === ' ') edit(event);
				});
			}
			noteRow.appendChild(value);
		}
		if (noteRow.childElementCount) host.appendChild(noteRow);
		if (!enabled && !bell && !actions.length) return host;
		const normalizedCategories=enabled ? normalizeFavoriteAssignedCategories(categories):[];
		const categoryRow=makeElement('div','vm-escort-inline-category-row',null,{title:`Kategorie ${String(labelText).toLowerCase()}`});
		for (const category of normalizedCategories) {
			const color=favoriteSettings.categoryColors[category] || accent;
			const chip=makeElement('span','vm-escort-inline-category-chip',null,{title:readOnly ? category:`${category} - kliknij ×, aby usunąć`});
			chip.style.border=`1px solid ${color}`;
			chip.style.background=favoriteCategoryTint(color,.10);
			chip.appendChild(makeElement('span','',category));
			if (!readOnly) {
				const remove=makeButton('vm-escort-inline-category-remove','×',{title:`Kategoria „${category}”. ${trackedRemovalHint('category')}`});
				remove.setAttribute('aria-label',`Usuń kategorię ${category}`);
				remove.addEventListener('click',event => {
					stopUiEvent(event);
					Promise.resolve(onRemoveCategory?.(category)).catch(error => log('Błąd usuwania kategorii',error));
				});
				chip.appendChild(remove);
			}
			categoryRow.appendChild(chip);
		}
		const available=(enabled ? favoriteSettings.categories:[]).filter(
			value => !normalizedCategories.includes(value) );
		if (!readOnly && available.length) {
			const add=makeButton('vm-escort-inline-category-add vm-tracked-category-add',normalizedCategories.length ? '+':'+ Kategoria',{title:'Dodaj kategorię'});
			add.setAttribute('aria-label','Dodaj kategorię');
			add.setAttribute('aria-haspopup','menu');
			add.addEventListener('click',event => {
				stopUiEvent(event);
				openEscortInlineCategoryPicker( add, available, favoriteSettings, value => onAddCategory?.(value), ownerKey );
			});
			categoryRow.appendChild(add);
		}
		if (bell) categoryRow.appendChild(bell);
		categoryRow.append(...actions);
		if (categoryRow.childElementCount) host.appendChild(categoryRow);
		return host;
	}
	function openEscortInlineCategoryPicker(anchor,availableCategories,settings,onSelect,ownerKey='') {
		const doc=anchor.ownerDocument,view=doc.defaultView || window;
		closeHelperDialog(doc.querySelector('.vm-favorites-category-picker'));
		const values=normalizeFavoriteCategories(availableCategories);
		if (!values.length) return;
		const picker=prepareHelperStyleApi(makeElement('div','vm-favorites-category-picker'));
		picker.dataset.vmFavoriteCategoryOwner=String(ownerKey || '');
		picker._vmAnchor=anchor; picker.setAttribute('role','menu'); picker.setAttribute('aria-label','Dodaj kategorię');
		anchor.setAttribute('aria-expanded','true');
		Object.assign(picker.style,{
			position:'fixed',zIndex:'2147483647',minWidth:'160px',maxWidth:'260px',maxHeight:'min(340px,75vh)',overflowY:'auto',
			padding:'4px',border:'1px solid var(--vm-ui-border,#cfcfd3)',borderRadius:'7px',background:'var(--vm-ui-bg,#fff)',
			boxShadow:'var(--vm-ui-shadow,0 5px 18px rgba(0,0,0,.22))',boxSizing:'border-box',pointerEvents:'auto'
		});
		const options=[];
		const controller=createDialogCloseController(picker,{closeOnBackdrop:false,onClose:reason => {
			anchor.setAttribute('aria-expanded','false');
			doc.removeEventListener('pointerdown',outside,true);
			doc.removeEventListener('keydown',keyboard,true);
			view.removeEventListener('resize',viewport,true); view.removeEventListener('scroll',viewport,true);
			if (['escape','choice','tab','viewport'].includes(reason) && anchor.isConnected) anchor.focus({preventScroll:true});
		}});
		const outside=event => {if (!picker.contains(event.target) && !anchor.contains(event.target)) controller.close('outside');};
		const viewport=event => {if (!picker.contains(event.target)) controller.close('viewport');};
		const keyboard=event => {
			if (!picker.contains(doc.activeElement)) return;
			if (event.key === 'Escape') {stopUiEvent(event); controller.close('escape'); return;}
			const index=options.indexOf(doc.activeElement);
			if (event.key === 'Tab') {controller.close('tab'); return;}
			const next=event.key === 'ArrowDown' ? (index + 1) % options.length:event.key === 'ArrowUp' ? (index + options.length - 1) % options.length:event.key === 'Home' ? 0:event.key === 'End' ? options.length - 1:null;
			if (next != null) {stopUiEvent(event); options[next].focus();}
		};
		for (const value of values) {
			const option=makeButton('vm-favorites-category-picker-option',value);
			option.setAttribute('role','menuitem');
			Object.assign(option.style,{display:'block',width:'100%',minHeight:'28px',padding:'4px 8px',border:'0',
				borderLeft:`4px solid ${settings.categoryColors[value] || '#b8b8bd'}`,borderRadius:'4px',background:'transparent',
				color:'var(--vm-ui-text,#333)',fontSize:'11px',textAlign:'left',cursor:'pointer',overflowWrap:'anywhere'});
			option.addEventListener('mouseenter',() => option.style.background='var(--vm-ui-bg-hover,#f4f4f6)');
			option.addEventListener('mouseleave',() => option.style.background='transparent');
			option.addEventListener('click',event => {
				stopUiEvent(event); controller.close('choice');
				Promise.resolve(onSelect?.(value)).catch(error => log('Błąd dodawania kategorii',error));
			});
			options.push(option); picker.appendChild(option);
		}
		doc.body.appendChild(picker);
		const a=anchor.getBoundingClientRect(),p=picker.getBoundingClientRect();
		picker.style.left=`${Math.round(Math.max(6,Math.min(a.right - p.width,view.innerWidth - p.width - 6)))}px`;
		picker.style.top=`${Math.round(Math.max(6,a.bottom + p.height + 3 <= view.innerHeight - 6 ? a.bottom + 3:a.top - p.height - 3))}px`;
		doc.addEventListener('pointerdown',outside,true); doc.addEventListener('keydown',keyboard,true);
		view.addEventListener('resize',viewport,true); view.addEventListener('scroll',viewport,true);
		options[0]?.focus({preventScroll:true});
	}
	function hideNativeEscortFavoriteAndNoteActions(enabled) {
		ensureTrackedNoteCategoryStyles();
		const scope=document.querySelector('.content-actions-col') || document;
		for (const element of scope.querySelectorAll('a,button')) {
			if (element.id?.startsWith('vm-')) continue;
			const label=normalizeText(element.textContent);
			const isFavoriteAction= /dodaj do ulubion|usuń z ulubion|ukryj w wynikach|ukryj anons/.test(label);
			const isNoteAction=/dodaj notat|notatka/.test(label);
			element.classList.toggle('vm-native-escort-action-hidden', enabled && (isFavoriteAction || isNoteAction) );
		}
	}
	function updateTrackedAlertModes(itemId,modes,locationTarget=null) {
		if (!getWatchSettings().enabled) return null;
		const current=getWatchedItems().find(item=>item.id===itemId);
		if (!current?.favorite) return null;
		const saved=updateWatchedItem(itemId,item=>({modes:{...item.modes,...modes},
			...(locationTarget ? {locationTarget}:{})}));
		initLocalWatchSystem();
		if (saved && watchedItemHasActiveAlerts(saved)) refreshFavoriteItemData(saved,true);
		return saved;
	}
	async function changeTrackedEntry(itemId,action,value=null,{getMatches=getWatchedItems,getContext=null,scope='single'}={}) {
		const read=() => {
			const matches=getMatches();
			const item=itemId ? matches.find(entry => entry.id === itemId):getTrackedScopeItem(matches,scope);
			if ((itemId || !getContext) && !item) throw new Error('Ten wpis nie jest już dostępny. Odśwież stronę.');
			return item;
		};
		try {
			const settings=getWatchSettings();
			if (!settings.enabled) return;
			let item=read();
			const label=item ? trackedItemChoiceLabel(item):scope === 'single' ? 'Ten anons':'Ten profil';
			if (action === 'rename') {
				const name=window.prompt('Nowa nazwa:',item.displayName);
				if (name?.trim()) {read(); renameWatchedItem(item.id,name);}
				return;
			}
			if (item && ['remove','favorite','hidden'].includes(action)) return changeTrackedItemStatus(item.id,action);
			let patch;
			if (action === 'note') {
				const note=await editTrackedNoteDialog(`Notatka - ${label}`,item?.note || '');
				if (note == null || (!item && !note.trim())) return;
				patch={note:note.trim()};
			} else if (action === 'clearNotes') {
				patch={note:'',categories:[]};
			} else if (action === 'alerts') {
				if (!item?.favorite) return;
				const options=await chooseWatchAlertOptions(item,label,{
					scope:item ? (item.sourceType === 'escort-ad' ? 'single':'person'):scope,
					info:`${label}. Alerty dotyczą wyłącznie tego wpisu.`
				});
				if (!options || !getWatchSettings().enabled || (!item && !watchedItemHasActiveAlerts(options))) return;
				patch={modes:options.modes,locationTarget:options.locationTarget};
			}
			item=read();
			if (action==='alerts') return updateTrackedAlertModes(item.id,patch.modes,patch.locationTarget);
			const update=current => action === 'favorite' ? {favorite:!current?.favorite}
					:action === 'hidden' ? {hidden:!current?.hidden,hiddenAt:current?.hidden ? 0:Date.now(),hiddenLastCheckedAt:0,hiddenLastError:null}
					:action === 'categories' ? {categories:normalizeFavoriteAssignedCategories(value(normalizeFavoriteAssignedCategories(current?.categories)))}
					:patch;
			return withTrackedUndo('Zmieniono notatkę lub kategorie.',()=>{
				const saved=item ? updateWatchedItem(item.id,update):saveFavoriteItemFromContext(getContext(),{favorite:false,...update(null)},scope);
				if (!saved) return;
				if (canRemoveEmptyTrackedItem(saved)) {removeWatchedItem(saved.id); return;}
				if (['favorite','hidden'].includes(action)) initLocalWatchSystem();
				if ((action === 'favorite' && saved.favorite) || (action === 'hidden' && saved.hidden)) refreshFavoriteItemData(saved,true);
			},()=>action==='clearNotes' || (action==='note' && !!item?.note && !patch.note) ||
				(action==='categories' && item?.categories?.some(category=>!update(item).categories.includes(category))));
		} catch (error) {showWatchToast(error?.message || 'Nie udało się zmienić wpisu.',true);}
	}
	function trackedEntryRenderKey(item) {
		return item ? [item.id,item.displayName,item.sourceType,item.resolutionMode,item.phoneDigits,item.adUrl,
			item.profileUrls,item.favorite,item.hidden,item.note,item.categories,item.modes,item.locationTarget,item.keepProfile,watchItemSourceUrl(item)]:null;
	}
	function renderTrackedEntriesSummary(summary,entries,expanded,{accent='#f54da3',fallbackText='#222'}={}) {
		const settings=getWatchSettings(),favoriteSettings=getFavoriteSettings(),actions=[];
		const signature=JSON.stringify([entries.map(trackedEntryRenderKey),settings.enabled,favoriteSettings.categoryColors,expanded,accent,fallbackText]);
		if (summary.dataset.vmSignature === signature) return;
		summary.dataset.vmSignature=signature;
		const hasData=settings.enabled && entries.some(item => item.note?.trim() || item.categories?.length ||
			hasFavoriteStar(item) || item.hidden || watchedItemHasActiveAlerts(item));
		if (expanded) summary.replaceChildren();
		else {
			if (settings.enabled) for (const kind of ['favorite','hidden']) {
				const active=entries.filter(item => kind === 'favorite' ? hasFavoriteStar(item):item.hidden);
				if (!active.length) continue;
				const icon=makeElement('span','vm-escort-inline-category-add vm-tracked-entry-action',kind === 'favorite' ? '★':'');
				icon.dataset.kind=kind; icon.dataset.vmActive='1';
				icon.title=`${kind === 'favorite' ? 'W Ulubionych':'Ukryty w wynikach Escort.club'}: ${active.map(trackedItemChoiceLabel).join('; ')}`;
				icon.setAttribute('role','img'); icon.setAttribute('aria-label',icon.title);
				if (kind === 'hidden') icon.appendChild(createHiddenIcon());
				actions.push(icon);
			}
			const notes=entries.filter(item => item.note?.trim());
			renderTrackedNoteCategoryBlock({
				host:summary,labelText:'',readOnly:true, noteText:notes.map(item => item.note.trim()).join('\n'),
				categories:entries.flatMap(item => item.categories || []),enabled:settings.enabled,
				favoriteSettings,accent,fallbackText,alertItems:settings.enabled ? entries:[],actions
			});
			summary.querySelector('.vm-escort-inline-category-row')?.prepend(...actions);
			for (const chip of summary.querySelectorAll('.vm-escort-inline-category-chip')) {
				chip.title=`${chip.textContent}: ${entries.filter(item => item.categories?.includes(chip.textContent)).map(trackedItemChoiceLabel).join('; ')}`;
			}
		}
		const fields=settings.enabled ? 'kategorię, notatkę':'';
		const count=entries.length > 1 ? ` · Wpisy: ${entries.length}`:'';
		const text=expanded ? `Zwiń szczegóły${count}`:hasData ? `Rozwiń szczegóły${count}`:`Rozwiń, aby dodać ${fields}…${count}`;
		summary.prepend(makeElement('span','vm-tracked-summary-caption',text));
		summary.setAttribute('aria-expanded',String(expanded));
		summary.title=expanded ? 'Kliknij, aby zwinąć szczegóły':'Kliknij, aby rozwinąć szczegóły';
	}
	function makeTrackedEntryAction(kind,text,title,onClick,active=false) {
		const button=makeButton('vm-escort-inline-category-add vm-tracked-entry-action',text);
		button.dataset.kind=kind;
		button.dataset.vmActive=active ? '1':'0';
		setControlTitle(button,title);
		if (['favorite','hidden'].includes(kind)) button.setAttribute('aria-pressed',String(active));
		if (kind === 'hidden') button.appendChild(createHiddenIcon(active));
		if (kind === 'remove') setTrashIcon(button);
		button.addEventListener('click',async event => {
			stopUiEvent(event);
			if (button.disabled) return;
			button.disabled=true;
			try {await onClick();}
			catch (error) {showWatchToast(error?.message || 'Nie udało się zmienić wpisu.',true);}
			finally {button.disabled=false;}
		});
		return button;
	}
	function renderTrackedEntriesEditor(list,items,changeEntry,{accent='#f54da3',fallbackText='#222',emptyAdOwnerKey='single'}={}) {
		const doc=list.ownerDocument,settings=getWatchSettings(),favoriteSettings=getFavoriteSettings();
		const previous=new Map([...list.children].map(row => [row.dataset.vmEntryId,row])),used=new Set();
		for (let index=0; index < items.length; index++) {
			const item=items[index];
			const single=!item || item.sourceType === 'escort-ad';
			const id=item?.id || null;
			const name=item?.displayName || 'Ten anons';
			const source=single ? 'Anons':watchItemSourceText(item);
			const key=id || 'single',old=previous.get(key);
			const signature=JSON.stringify([trackedEntryRenderKey(item),settings.enabled,favoriteSettings.categories,favoriteSettings.categoryColors,accent,fallbackText]);
			used.add(key);
			if (old?.dataset.vmSignature === signature) {
				if (list.children[index] !== old) list.insertBefore(old,list.children[index] || null);
				continue;
			}
			const picker=doc.querySelector('.vm-favorites-category-picker');
			const focused=old?.contains(doc.activeElement) ? doc.activeElement
				:picker?.contains(doc.activeElement) && old?.contains(picker._vmAnchor) ? picker._vmAnchor:null;
			const focusKind=focused?.dataset.kind,focusNote=focused?.classList.contains('vm-escort-favorite-note-line');
			if (old && picker?._vmAnchor && old.contains(picker._vmAnchor)) closeHelperDialog(picker);
			const block=makeElement('div','vm-tracked-entry');
			block.dataset.vmEntryId=key; block.dataset.vmSignature=signature;
			block.tabIndex=-1;
			const action=(kind,text,title,active=false) => makeTrackedEntryAction(kind,text,
				`${title}: ${single ? name:`${name} - ${source}`}`,() => changeEntry(id,kind),active);
			const actions=[];
			if (settings.enabled) {
				if (item?.sourceType !== 'escort-search') actions.push(action('favorite',item?.favorite ? '★':'☆',item?.favorite ? 'Usuń z Ulubionych':'Dodaj do Ulubionych',!!item?.favorite));
				else actions.push(makeFavoriteSearchEditButton(item));
				actions.push(action('hidden','',item?.hidden ? 'Nie ukrywaj':'Ukryj w wynikach',!!item?.hidden));
			}
			renderTrackedNoteCategoryBlock({
				host:block,labelText:single ? 'Anons':'Profil',noteText:item?.note || '',categories:item?.categories || [],
				enabled:settings.enabled,favoriteSettings,
				accent,fallbackText,ownerKey:id || emptyAdOwnerKey, alertItems:item ? [item]:[], alertsDisabled:!item?.favorite, actions,
				onEditAlerts:settings.enabled ? () => changeEntry(id,'alerts'):null, onEditNote:() => changeEntry(id,'note'),
				onRemoveCategory:category => changeEntry(id,'categories',values => values.filter(value => value !== category)),
				onAddCategory:category => changeEntry(id,'categories',values => [...values,category])
			});
			const heading=makeElement('div','vm-tracked-entry-heading');
			heading.appendChild(makeElement('div','vm-tracked-entry-name',name));
			if (id) {
				const rename=action('rename','✎',single ? 'Zmień nazwę anonsu':'Zmień nazwę profilu');
				rename.className='vm-ui-button vm-ui-icon vm-ui-compact vm-ui-table-icon';
				heading.appendChild(rename);
			}
			if (!single && id) {
				const remove=action('remove','',trackedRemovalHint('profile'));
				remove.className='vm-tracked-entry-delete';
				remove.appendChild(makeElement('span','','Usuń profil'));
				heading.appendChild(remove);
			}
			const sourceUrl=!single ? watchItemSourceUrl(item):'';
			const sourceLabel=makeElement(sourceUrl ? 'a':'div','vm-tracked-entry-source',source);
			if (sourceUrl) Object.assign(sourceLabel,{href:sourceUrl,target:'_blank',rel:'noopener noreferrer',title:'Otwórz źródło danych profilu'});
			block.prepend(sourceLabel);
			block.prepend(heading);
			if (old) old.replaceWith(block);
			else list.insertBefore(block,list.children[index] || null);
			if (list.children[index] !== block) list.insertBefore(block,list.children[index] || null);
			if (focused) {
				const focusTarget=focusKind ? block.querySelector(`[data-kind="${focusKind}"]`):focusNote ? block.querySelector('.vm-escort-favorite-note-line'):block.querySelector('.vm-tracked-category-add');
				(focusTarget || block).focus({preventScroll:true});
			}
		}
		for (const [key,row] of previous) if (!used.has(key)) {
			const picker=doc.querySelector('.vm-favorites-category-picker');
			if (picker?._vmAnchor && row.contains(picker._vmAnchor)) closeHelperDialog(picker);
			detachHelperNode(row);
		}
	}
	function createTrackedEntriesBox(changeEntry,options={}) {
		const root=prepareHelperStyleApi(makeElement('div','vm-tracked-box'));
		const summary=makeElement('div','vm-tracked-summary',null,{tabIndex:0}); summary.setAttribute('role','button');
		summary.setAttribute('aria-label','Rozwiń lub zwiń zapisane dane profili i anonsów');
		const panel=makeElement('div','vm-tracked-details vm-ui-stack'); panel.hidden=true;
		panel.setAttribute('role','region'); panel.setAttribute('aria-label','Notatki i kategorie');
		const list=makeElement('div','vm-tracked-list');
		panel.appendChild(list); root.append(summary,panel);
		let items=[];
		const render=entries => {
			if (entries) items=entries;
			renderTrackedEntriesSummary(summary,items.filter(Boolean),!panel.hidden,options);
			if (!panel.hidden) renderTrackedEntriesEditor(list,items,changeEntry,options);
		};
		const toggle=event => {
			if (event.type === 'keydown' && !['Enter',' '].includes(event.key)) return;
			stopUiEvent(event); panel.hidden=!panel.hidden; render();
		};
		summary.addEventListener('click',toggle); summary.addEventListener('keydown',toggle);
		return {root,panel,list,render};
	}
	function initEscortClubWatchButtons() {
		if (!getEscortAdIdFromPath(location.pathname)) return;
		const insert=() => {
			const settings=getWatchSettings();
			if (!settings.enabled) return true;
			hideNativeEscortFavoriteAndNoteActions(settings.enabled);
			const title=queryFirstEscortElement(['.content-info-col.-desc .content-name h1','.content-name.-desc-name h1','.content-name h1']);
			if (!title) return false;
			const existingRoot=document.getElementById('vm-escort-favorite-note-row');
			if (existingRoot) {
				if (title.nextElementSibling !== existingRoot) title.after(existingRoot);
				existingRoot._vmRender(); return true;
			}
			const change=(id,action,value) => changeTrackedEntry(id,action,value,{
				getMatches:getFavoriteMatchesForCurrentEscortAd,
				getContext:() => ({sourceType:'escort-ad',resolutionMode:'single',adUrl:location.href,
					displayName:extractWatchPageName(document,`Anons ${getAdIdFromUrl()}`),imageUrl:extractWatchPageImage(document,location.href)})
			});
			const {root,panel,list,render}=createTrackedEntriesBox(change,{
				accent:getEscortPagePinkColor(),fallbackText:getEscortAdStandardTextColor(),emptyAdOwnerKey:`single:${getAdIdFromUrl()}`
			});
			root.id='vm-escort-favorite-note-row'; root.classList.add('vm-ad-summary-box'); root.dataset.vmWatchWrapper='1';
			panel.id='vm-escort-tracked-panel'; list.id='vm-escort-favorite-note-list';
			const footer=makeElement('div','vm-ui-row');
			const add=makeButton('vm-tracked-profile-picker','+ Dodaj profil'); add.id='vm-escort-add-profile';
			add.title='Wybierz sposób wyszukania powiązanych anonsów, aby edytować ustawienia ich profilu.';
			const status=makeElement('div','vm-ui-help'); status.setAttribute('role','status');
			footer.append(add,status); panel.appendChild(footer);
			title.parentElement.insertBefore(root,title.nextSibling);
			const refresh=() => {
				if (!root.isConnected) return;
				const matches=getFavoriteMatchesForCurrentEscortAd();
				render([getTrackedScopeItem(matches,'single'),...matches.filter(item => item.sourceType !== 'escort-ad')]);
			};
			root._vmRender=refresh; watchPageButtonRefreshers.add(refresh);
			add.addEventListener('click',async event => {
				stopUiEvent(event);
				if (add.disabled) return;
				add.disabled=true;
				try {
					const mode=await chooseFavoriteProfileResolutionMode(getFavoriteMatchesForCurrentEscortAd().filter(item => item.sourceType !== 'escort-ad').map(item => item.resolutionMode));
					if (!mode) return;
					status.textContent='Wyszukiwanie profilu…';
					const context=await buildFavoriteContextFromEscortAd(location.href,'person',mode);
					if (!context || !root.isConnected || !getWatchSettings().enabled) return;
					const existing=getWatchedItems().find(item => item.sourceType !== 'escort-ad' && watchedItemsOverlap(item,context));
					const saved=saveFavoriteItemFromContext({...context,keepProfile:true,
						resolvedAdUrls:[...new Set([...(context.resolvedAdUrls || []),normalizeEscortiAdUrl(location.href)])]
					},{favorite:existing?.favorite === true},'person');
					status.textContent='';
					const target=[...list.children].find(row => row.dataset.vmEntryId === saved.id);
					if (!panel.hidden) {
						target?.focus({preventScroll:true});
						target?.scrollIntoView?.({block:'nearest',behavior:'smooth'});
					}
				} catch (error) {status.textContent=error?.message || 'Nie udało się dodać profilu.';}
				finally {add.disabled=false;}
			});
			refresh();
			return true;
		};
		if (insert()) return;
		let attempts=0;
		const timer=setInterval(() => {if (insert() || ++attempts >= 40) clearInterval(timer);},250);
	}
	function getEscortiUiAccentColor() {
		const sample=document.querySelector('[class*="text-primary-600"], [class*="text-primary-500"]');
		const color=sample ? getComputedStyle(sample).color:'';
		return color && color !== 'rgb(0, 0, 0)' ? color:'#c026d3';
	}
	function getEscortiProfileWatchHeading() {
		return ( document.querySelector('h3.text-2xl') || document.querySelector('main h1, main h2, main h3') ||
			document.querySelector('h1,h2,h3') );
	}
	function initEscortiProfileWatchButtons() {
		if (!/^\/escort\/\d+\/?$/i.test(location.pathname)) return;
		const insert=() => {
			const settings=getWatchSettings();
			if (!settings.enabled) return;
			const heading=getEscortiProfileWatchHeading();
			if (!heading) return;
			for (const element of document.querySelectorAll('a,button')) {
				if (element.closest('[data-vm-watch-wrapper="1"], .vm-helper-ui')) continue;
				const label=normalizeText(element.textContent);
				const hidden=/dodaj do ulubion|usuń z ulubion|obserwuj|dodaj notat|notatka/.test(label);
				element.classList.toggle('vm-native-escort-action-hidden',!!hidden);
			}
			let block=document.getElementById('vm-escorti-profile-note-display');
			if (!block) {
				const getMatches=() => getFavoriteMatchesForCurrentEscortiProfile().filter(item => item.sourceType !== 'escort-search');
				const getContext=() => ({...getCurrentEscortiWatchContextFast(),resolvedAdUrls:getEscortiAdUrls()});
				const change=(id,action,value) => changeTrackedEntry(id,action,value,{getMatches,getContext,scope:'person'});
				const {root,render}=createTrackedEntriesBox(change,{accent:getEscortiUiAccentColor(),fallbackText:'#4b5563',emptyAdOwnerKey:`escorti:${location.pathname}`});
				block=root; block.id='vm-escorti-profile-note-display'; block.classList.add('vm-ad-summary-box'); block.dataset.vmWatchWrapper='1';
				block._vmRender=() => {
					if (!block.isConnected) return;
					const settings=getWatchSettings(),matches=getMatches();
					block.hidden=!settings.enabled;
					render(matches.length ? matches:[getContext()]);
				};
				watchPageButtonRefreshers.add(block._vmRender);
			}
			if (heading.nextElementSibling !== block) heading.after(block);
			block._vmRender();
		};
		insert();
		const root=document.documentElement;
		if (!root.__vmEscortiProfileWatchObserver) {
			const schedule=createAnimationFrameScheduler(insert);
			const observer=new MutationObserver(() => {
				const heading=getEscortiProfileWatchHeading(),block=document.getElementById('vm-escorti-profile-note-display');
				if (heading && (!block?.isConnected || heading.nextElementSibling !== block)) schedule();
			});
			observer.observe(root,{childList:true,subtree:true});
			root.__vmEscortiProfileWatchObserver=observer;
		}
	}
	function getCachedEscortiResultsForFavoriteItem(item) {
		const urls=[ item?.adUrl, ...(item?.resolvedAdUrls || []), ...(item?.state?.activeUrls || []) ].filter(Boolean);
		const adIds=[...new Set(urls.map(parseAdIdFromUrl).filter(Boolean))];
		const results=[];
		const seen=new Set(),profileIds=new Set((item?.profileUrls||[]).map(getEscortiProfileId).filter(Boolean));
		const add=result=>{
			if(!result)return;
			if(profileIds.size&&(!result.profileIds?.length||result.profileIds.some(id=>!profileIds.has(id))))return;
			const signature=JSON.stringify({profiles:result.profileIds,ads:result.adIds,date:result.creationDate,city:result.currentCity,garso:result.garsoTopicTitles||result.garsoTopics});
			if(!seen.has(signature)){seen.add(signature);results.push(result);}
		};
		add(expandEscortListCacheEntry(item?.state?.escortiData,item?.resolutionMode));
		for (const adId of adIds) {
			try {
				const store=readPersistentCacheValue(listCacheKey(adId),null);
				for (const [mode,entry] of Object.entries(store?.modes || {})) {
					const expanded=expandEscortListCacheEntry(entry,mode);
					add(expanded);
				}
			} catch {}
		}
		return results;
	}
	function getFavoriteLocationCities(item,view=null) {
		const clean=values=>[...new Set((values||[]).map(normalizeEscortCity).filter(Boolean))];
		const current=clean(item?.state?.cities);if(current.length)return current;
		const last=clean(item?.state?.lastKnownCities);if(item?.state?.online!==true&&last.length)return last;
		const cached=clean((view?.details || getWatchedItemCachedAdDetails(item,true)).map(({adData})=>adData.location?.city));
		if(cached.length)return cached;if(last.length)return last;
		return clean((view?.escorti || getCachedEscortiResultsForFavoriteItem(item)).flatMap(result=>result.currentCity?[result.currentCity]:(result.profileSummaries||[]).map(profile=>profile.currentCity)));
	}
	function getFavoriteLastVisitedAt(item) {
		const urls=getWatchedItemAdUrls(item);
		let latest=0;
		for (const url of urls) {
			const adId=parseAdIdFromUrl(url);
			if (!adId) continue;
			const visitedAt=Number( (getStoredEscortAdData(adId) || getWatchedAdDataMirror(adId))?.lastVisitedAt ) || 0;
			if (visitedAt > latest) latest=visitedAt;
		}
		return latest || null;
	}
	function formatFavoriteColumnValue(item,field,view=null) {
		if (field === 'profile:cities') return watchCitiesText(view?.cities || getFavoriteLocationCities(item));
		if (field === 'profile:hidden') return isWatchedItemHidden(item) ? 'Tak':'Nie';
		if (field === 'profile:totalAds' && item.sourceType === 'escort-search') return String(getWatchedItemHistoricalAdUrls(item).length);
		const descriptor=getEscortDataFieldDescriptor(field);
		if (!descriptor) return '';
		if ( descriptor.source === 'ad' && descriptor.path?.[0] === 'lastVisitedAt' ) {
			const visitedAt=view ? view.lastVisited:getFavoriteLastVisitedAt(item);
			return visitedAt
				? formatEscortUniversalDataValue( descriptor, visitedAt, false )
				:'b/d';
		}
		if ( descriptor.source === 'escorti' && descriptor.path?.[0] === 'historicalCityCount' ) {
			const cities=getEscortiHistoricalCitiesFromResults(view?.escorti || getCachedEscortiResultsForFavoriteItem(item));
			return cities.length
				? String(cities.length)
				:'b/d';
		}
		if (descriptor.source === 'garso') {
			const stats=getCachedGarsoStatsForAds(view?.all || getWatchedItemAdUrls(item,false),item?.phoneDigits);
			return formatEscortUniversalDataValue(descriptor,getGarsoDataValue(stats,descriptor)) || 'b/d';
		}
		let values;
		if (descriptor.source === 'profile') {
			values=[getEscortDataValueByPath(item?.state || {},descriptor.path)];
		} else if (descriptor.source === 'escorti') {
			values=(view?.escorti || getCachedEscortiResultsForFavoriteItem(item))
				.map(result => getEscortDataValueByPath(result,descriptor.path));
		} else {
			values=(view?.details || getWatchedItemCachedAdDetails(item,true))
				.map(detail => getEscortDataFieldRawValue(detail.adData,descriptor));
		}
		const unique=[...new Set(values.map(value => formatEscortUniversalDataValue(descriptor,value,false)).filter(Boolean))];
		return unique.length ? unique.join(' • '):'b/d';
	}
	function favoriteDataSortValue(item,field,text=formatFavoriteColumnValue(item,field),view=null) {
		const descriptor=getEscortDataFieldDescriptor(field),kind=descriptor?.kind;
		text=String(Array.isArray(text)?text[0]??'':text??'').split(' • ')[0].trim();
		if(!text||/^(?:b\/d|brak danych|[?—-])$/i.test(text))return null;
		if(kind==='relative-time')return (view ? view.lastVisited:getFavoriteLastVisitedAt(item))||null;
		if(kind==='date'||kind==='datetime'){
			const date=text.match(/^(\d{2}\.\d{2}\.\d{4})(?:,?\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?/);
			const time=date?profileDateToTime(date[1])+((+date[2]||0)*3600+(+date[3]||0)*60+(+date[4]||0))*1000:Date.parse(text);
			return Number.isFinite(time)?time:null;
		}
		if(kind==='price'||kind==='number'||descriptor?.path?.[0]==='stats'){
			const match=text.replace(/\s+/g,'').replace(',','.').match(/-?\d+(?:\.\d+)?/);if(match)return Number(match[0]);
		}
		return normalizeText(text);
	}
	function compareFavoriteValues(a,b,direction=1) {
		if(a==null||b==null)return Number(a==null)-Number(b==null);
		return (typeof a==='number'&&typeof b==='number'?a-b:String(a).localeCompare(String(b),'pl-PL',{numeric:true,sensitivity:'base'}))*direction;
	}
	function getFavoriteColumnLabel(field) {
		return getEscortDataFieldDescriptor(field)?.label || 'Dane';
	}
	let watchImagePreview=null;
	let watchImagePreviewImg=null;
	function getQuickAdPreviewData(anchor,cachedData=null) {
		const url=normalizeEscortiAdUrl(anchor.href),id=parseAdIdFromUrl(url);
		if (!id) return null;
		const data=[getWatchedAdDataMirror(id),cachedData,getStoredEscortAdData(id)]
			.filter(Boolean).sort((a,b) => (Number(a.checkedAt) || 0) - (Number(b.checkedAt) || 0))
			.reduce((value,source) => mergeEscortDefinedObject(value,source),{});
		const tile=makeGarsoAdTileData(id,{...data,adUrl:url});
		const card=anchor.closest('.item-col.col, .vm-garso-visited-card');
		const text=selector => normalizeEscortAdText(card?.querySelector(selector)?.textContent);
		const img=card?.querySelector('img');
		tile.title=text('.item-name') || tile.title || anchor.textContent.trim() || `Anons ${id}`;
		tile.locationText=text('.item-stats').replace(/,\s*\d{1,3}\s*(?:y|lat|lata|rok|years?)\b.*$/i,'') || text('.vm-garso-visited-location') || tile.locationText;
		tile.priceText=tile.priceText || text('.vm-escort-tile-price, .vm-garso-visited-price');
		tile.profileStatsText=tile.profileStatsText || text('.vm-garso-visited-stats');
		tile.imageUrl=img?.currentSrc || img?.getAttribute('data-src') || img?.getAttribute('src') || tile.imageUrl;
		const profiles=new Set(getHiddenResultProfileUrls(getListCache(id)).map(getEscortiProfileId));
		const phone=normalizePhoneReputationPhone(getEscortAdPhoneDigits(data))?.local;
		const items=getWatchedItems().filter(item => getWatchedItemAdUrls(item).some(value => parseAdIdFromUrl(value) === id) ||
			(item.sourceType !== 'escort-ad' && item.sourceType !== 'escort-search' &&
				((item.profileUrls || []).some(value => profiles.has(getEscortiProfileId(value))) ||
					(phone && !item.escortiProfileOnly && !item.phoneProfileOnly && item.resolutionMode?.startsWith('phone-') && normalizePhoneReputationPhone(item.phoneDigits)?.local === phone))));
		const offlineOnly=!Object.keys(data).length && !getEscortCachedAdHost(id) && !tile.imageUrl && !tile.locationText && !tile.priceText && !tile.visitedAt &&
			!items.some(item => item.note?.trim() || item.categories?.length || hasFavoriteStar(item) || item.hidden) &&
			[...document.querySelectorAll('[data-vm-ad-status="offline"]')].some(marker => parseAdIdFromUrl(marker.dataset.adUrl) === id);
		return {...tile,offlineOnly,notes:[...new Set(items.map(item => item.note?.trim()).filter(Boolean))],
			categories:[...new Set(items.flatMap(item => item.categories || []))],
			favorite:items.some(hasFavoriteStar),hidden:items.some(item => item.hidden)};
	}
	function positionQuickAdPreview(popup,anchor) {
		popup.style.maxHeight='';
		const rect=(anchor.closest('.vm-garso-visited-card') || anchor).getBoundingClientRect();
		const box=popup.getBoundingClientRect(),margin=8,width=window.innerWidth,height=window.innerHeight;
		let left,top=Math.max(margin,Math.min(rect.top,height - box.height - margin));
		if (rect.right + margin + box.width <= width - margin) left=rect.right + margin;
		else if (rect.left - margin - box.width >= margin) left=rect.left - margin - box.width;
		else {
			const below=height - rect.bottom - margin * 2,above=rect.top - margin * 2;
			const useBelow=below >= box.height || (above < box.height && below >= above);
			const available=Math.min(box.height,useBelow ? below:above);
			if (available < 44) return false;
			popup.style.maxHeight=`${available}px`;
			top=useBelow ? rect.bottom + margin:rect.top - margin - available;
			left=Math.max(margin,Math.min(rect.left,width - box.width - margin));
		}
		popup.style.left=`${left}px`; popup.style.top=`${top}px`;
		return true;
	}
	function initQuickAdPreview() {
		const supported=/^(?:www\.)?(?:garsoniera\.com\.pl|escorti\.pl)$/i.test(location.hostname) || isEscortClubHost(location.hostname);
		if (!supported || window.top !== window.self || document.__vmQuickAdPreview) return;
		document.__vmQuickAdPreview=true;
		let anchor=null,popup=null,timer=null,closeTimer=null,revision=0;
		const cacheReads=new Map();
		const hide=() => {
			revision++;
			clearTimeout(timer); clearTimeout(closeTimer);
			if (anchor && popup) {
				const ids=(anchor.getAttribute('aria-describedby') || '').split(/\s+/).filter(id => id && id !== popup.id);
				if (ids.length) anchor.setAttribute('aria-describedby',ids.join(' ')); else anchor.removeAttribute('aria-describedby');
			}
			anchor=null;
			if (popup) popup.hidden=true;
		};
		const find=target => {
			if (SETTINGS.quickAdPreview === false || !target?.closest || target.closest('#vm-ad-quick-preview, [role="dialog"], .vm-ui-overlay')) return null;
			if (target.closest('button, input, select, textarea')) return null;
			const link=target.closest('a[href]');
			if (!link || !normalizeEscortiAdUrl(link.href)) return null;
			if (IS_ESCORT_CLUB) {
				try {
					if (isEscortClubHost(new URL(link.href,location.href).hostname)) return null;
				} catch {}
			}
			link.setAttribute('title','');
			for (const child of link.querySelectorAll('[title]')) child.removeAttribute('title');
			return link;
		};
		const render=value => {
			if (!anchor?.isConnected || SETTINGS.quickAdPreview === false) {hide(); return;}
			if (!value) {hide(); return;}
			if (!popup) {
				const style=makeElement('style');
				style.textContent=`#vm-ad-quick-preview{position:fixed;z-index:2147483647;box-sizing:border-box;width:420px;max-width:calc(100vw - 16px);max-height:min(80vh,560px);overflow:auto;padding:12px;border:1px solid var(--vm-ui-border,#ddd);border-radius:10px;background:var(--vm-ui-bg,#fff);color:var(--vm-ui-text,#222);box-shadow:var(--vm-ui-shadow,0 8px 28px #0003);font:13px/1.45 var(--vm-ui-font-family,Arial,sans-serif);overflow-wrap:anywhere;text-align:left}#vm-ad-quick-preview[hidden]{display:none!important}#vm-ad-quick-preview .vm-preview-head{display:flex;gap:12px}#vm-ad-quick-preview .vm-preview-photo{width:100px;height:135px;flex:none;object-fit:cover;border-radius:6px;background:var(--vm-ui-bg-hover,#eee);display:grid;place-items:center;color:var(--vm-ui-text-muted,#777)}#vm-ad-quick-preview .vm-preview-info{min-width:0;flex:1}#vm-ad-quick-preview strong{display:block;margin-bottom:6px;font-size:14px}#vm-ad-quick-preview .vm-preview-note{white-space:pre-wrap;margin-top:9px}#vm-ad-quick-preview .vm-preview-chips{display:flex;flex-wrap:wrap;gap:5px;margin-top:9px}#vm-ad-quick-preview .vm-preview-chip{padding:2px 7px;border:1px solid var(--vm-ui-border,#ccc);border-radius:12px}#vm-ad-quick-preview .vm-preview-status{margin-top:9px;color:var(--vm-ui-text-muted,#666)}`;
				document.head.appendChild(style);
				popup=prepareHelperStyleApi(makeElement('div')); popup.id='vm-ad-quick-preview'; popup.setAttribute('role','tooltip');
				popup.addEventListener('pointerenter',() => clearTimeout(closeTimer));
				popup.addEventListener('pointerleave',hide);
				document.body.appendChild(popup);
			}
			popup.replaceChildren();
			popup.style.width=value.offlineOnly ? 'auto':'';
			if (value.offlineOnly) popup.textContent='offline';
			else {
			const head=makeElement('div','vm-preview-head'),photo=makeElement('span','vm-preview-photo','Brak zdjęcia');
			if (value.imageUrl) {
				try {
					const url=new URL(value.imageUrl,location.href);
					if (/^https?:$/.test(url.protocol) || url.protocol === 'blob:' || /^data:image\//i.test(url.href)) {
						const img=makeElement('img','vm-preview-photo'); img.alt=''; img.referrerPolicy='no-referrer';
						img.addEventListener('error',() => img.replaceWith(photo)); img.src=url.href; head.appendChild(img);
					}
				} catch {}
			}
			if (!head.firstElementChild) head.appendChild(photo);
			const info=makeElement('div','vm-preview-info');
			info.append(makeElement('strong','',value.title),makeElement('div','',value.locationText || 'Brak danych o mieście'));
			const previewStats=createGarsoProfileStatsElement(value.profileStats,'vm-preview-stats');
			if (previewStats) info.appendChild(previewStats); else if (value.profileStatsText) info.appendChild(makeElement('div','vm-preview-stats',value.profileStatsText));
			info.appendChild(makeElement('div','',value.priceText || 'Brak zapisanych cen'));
			head.appendChild(info); popup.appendChild(head);
			popup.appendChild(makeElement('div','vm-preview-status',value.visitedAt ? `Ostatnio obejrzany: ${formatWatchDateTime(value.visitedAt)} (${formatEscortLastVisit(value.visitedAt)})`:'Brak zapisanego obejrzenia'));
			const chips=makeElement('div','vm-preview-chips'),colors=getFavoriteSettings().categoryColors;
			for (const category of value.categories) {const chip=makeElement('span','vm-preview-chip',category); chip.style.borderColor=colors[category] || '#ccc'; chips.appendChild(chip);}
			if (value.categories.length) popup.appendChild(chips);
			if (value.notes.length) popup.appendChild(makeElement('div','vm-preview-note',value.notes.join('\n')));
			const status=[value.favorite ? '★ W Ulubionych':'',value.hidden ? 'Ukryty w wyszukiwaniu':''].filter(Boolean).join(' · ');
			if (status) popup.appendChild(makeElement('div','vm-preview-status',status));
			}
			hideWatchImagePreview(); popup.hidden=false;
			const ids=new Set((anchor.getAttribute('aria-describedby') || '').split(/\s+/).filter(Boolean)); ids.add(popup.id); anchor.setAttribute('aria-describedby',[...ids].join(' '));
			if (!positionQuickAdPreview(popup,anchor)) hide();
		};
		const show=async () => {
			if (!anchor?.isConnected || SETTINGS.quickAdPreview === false) return;
			const link=anchor,currentRevision=revision,id=parseAdIdFromUrl(link.href);
			let cached=cacheReads.get(id);
			const value=getQuickAdPreviewData(link,cached?.data);
			render(value);
			if (anchor !== link || !value || cached?.data ||
				(value.imageUrl && value.locationText && value.priceText && (value.profileStats?.text || value.profileStatsText))) return;
			const host=getEscortCachedAdHost(id);
			if (!host) return;
			const message=makeElement('div','vm-preview-status','Odczytywanie danych z cache…');
			popup.appendChild(message);
			if (!positionQuickAdPreview(popup,link)) hide();
			try {
				if (!cached) {
					cached={};
					cached.promise=requestPersistentCacheBridgeAction('read-ad-data',{host,adIds:[id]}).then(response => {
						cached.data=response.ads?.find(entry => String(entry.adData?.adId) === id)?.adData;
						if (!cached.data) throw new Error('Brak danych anonsu w cache.');
						return cached.data;
					});
					cacheReads.set(id,cached);
				}
				const data=await cached.promise;
				if (anchor === link && revision === currentRevision) render(getQuickAdPreviewData(link,data));
			} catch {
				cacheReads.delete(id);
				if (anchor === link && revision === currentRevision && !popup.hidden) {
					message.textContent=`Aby odczytać pełny cache, otwórz kartę ${host} i najedź ponownie.`;
					if (!positionQuickAdPreview(popup,link)) hide();
				}
			}
		};
		const enter=event => {
			if (event.pointerType === 'touch' || popup?.contains(event.target)) return;
			const next=find(event.target);
			if (next === anchor) {clearTimeout(closeTimer); return;}
			hide();
			if (next) {anchor=next; timer=setTimeout(show,300);}
		};
		document.addEventListener('pointerover',enter);
		document.addEventListener('focusin',enter);
		for (const type of ['pointerout','focusout']) document.addEventListener(type,event => {
			if (!anchor || find(event.relatedTarget) === anchor || popup?.contains(event.relatedTarget)) return;
			clearTimeout(timer); clearTimeout(closeTimer); closeTimer=setTimeout(hide,120);
		});
		document.addEventListener('keydown',event => {if (event.key === 'Escape') hide();});
		document.addEventListener('pointerdown',event => {if (!popup?.contains(event.target)) hide();});
		document.addEventListener('scroll',event => {if (!popup?.contains(event.target)) hide();},true);
		window.addEventListener('resize',hide); window.addEventListener('blur',hide); window.addEventListener('pagehide',hide);
	}
	function ensureWatchImagePreview(container=document.body) {
		if (watchImagePreview?.isConnected) {
			if (container && watchImagePreview.parentElement !== container) container.appendChild(watchImagePreview);
			return watchImagePreview;
		}
		if (!document.getElementById('vm-watch-image-preview-style')) {
			appendHelperStyle('vm-watch-image-preview-style',`.vm-watch-image-preview { position:fixed; z-index:2147483647; display:none; width:220px; height:300px; padding:3px; border:1px solid var(--vm-ui-border-strong,rgba(0,0,0,.32)); border-radius:6px; background:var(--vm-ui-bg,#fff); box-shadow:var(--vm-ui-shadow,0 8px 24px rgba(0,0,0,.34)); box-sizing:border-box; pointer-events:none; }
.vm-watch-image-preview > img { display:block; width:100%; height:100%; object-fit:cover; border-radius:3px; }`);
		}
		watchImagePreview=prepareHelperStyleApi(makeElement('div','vm-watch-image-preview'));
		watchImagePreviewImg=makeElement('img');
		watchImagePreviewImg.alt='';
		watchImagePreviewImg.referrerPolicy='no-referrer';
		watchImagePreviewImg.addEventListener('error',() => {
			markWatchThumbnailUnavailable(watchImagePreviewImg.currentSrc || watchImagePreviewImg.src);
			hideWatchImagePreview();
		});
		watchImagePreview.appendChild(watchImagePreviewImg);
		(container || document.body).appendChild(watchImagePreview);
		return watchImagePreview;
	}
	function hideWatchImagePreview() {
		if (watchImagePreview) watchImagePreview.style.display='none';
	}
	function showWatchImagePreview(anchor,imageUrl,container=document.body) {
		if (!anchor || !imageUrl) return;
		const preview=ensureWatchImagePreview(container);
		watchImagePreviewImg.src=imageUrl;
		const rect=anchor.getBoundingClientRect(),margin=8,width=220,height=300;
		let left=rect.right + margin;
		if (left + width > window.innerWidth - margin) left=rect.left - width - margin;
		left=Math.max(margin,Math.min(left,window.innerWidth - width - margin));
		let top=rect.top + rect.height / 2 - height / 2;
		top=Math.max(margin,Math.min(top,window.innerHeight - height - margin));
		preview.style.left=`${Math.round(left)}px`;
		preview.style.top=`${Math.round(top)}px`;
		preview.style.display='block';
	}
	function prepareFavoriteTableStyleApi(overlay,box) {
		prepareHelperStyleApi(overlay);
		overlay.classList.add('vm-favorites-ui');
		overlay.dataset.vmFavoritesStyleApi='1';
		box.classList.add('vm-favorites-modal');
		if (document.getElementById('vm-favorites-style-api')) return;
		appendHelperStyle('vm-favorites-style-api',` .vm-favorites-ui { font-family:var(--vm-fav-font-family,var(--vm-ui-font-family,Arial,sans-serif)); }
.vm-favorites-ui .vm-favorites-checkbox { accent-color: var(--vm-fav-checkbox-accent, var(--vm-fav-accent, var(--vm-ui-accent, #f54998))); }
.vm-favorites-ui .vm-favorites-filter:focus { border-color: var(--vm-fav-accent, var(--vm-ui-accent, #f54998)) !important; box-shadow: var(--vm-fav-filter-focus-shadow, 0 0 0 2px rgba(245,73,152,.10)) !important; background: var(--vm-fav-filter-focus-bg, var(--vm-ui-input-bg, #fff)) !important; }
.vm-favorites-ui .vm-favorites-toolbar-button, .vm-favorites-ui .vm-favorites-icon-button, .vm-favorites-ui .vm-favorites-header-action { font-family: inherit; }
.vm-favorites-ui.vm-ui-overlay { padding:8px; }
.vm-favorites-ui .vm-ui-table-dialog { width:calc(100vw - 16px); height:calc(100vh - 16px); max-height:calc(100vh - 16px); display:flex; flex-direction:column; padding:0; overflow:hidden; }
.vm-favorites-ui .vm-favorites-toolbar { display: flex; align-items: center; gap: 7px; padding: 2px 10px; border-bottom: 1px solid var(--vm-fav-toolbar-border, var(--vm-ui-border, #e6e6e6)); flex: 0 0 auto; flex-wrap: wrap; }
.vm-favorites-ui .vm-favorites-toolbar-actions { display:flex; align-items:center; justify-content:flex-end; flex-wrap:wrap; gap:7px; min-width:0; max-width:100%; margin-left:auto; }
.vm-favorites-ui .vm-favorites-title { flex: 0 0 auto; margin: 0 8px 0 0; }
.vm-favorites-ui .vm-favorites-count { color:var(--vm-ui-text-muted,#666); font-size:.8em; font-weight:600; white-space:nowrap; font-variant-numeric:tabular-nums; }
.vm-favorites-ui .vm-favorites-manual-input { flex: 0 1 390px; min-width: 220px; border:1px solid var(--vm-ui-input-border,#bbb); border-radius:var(--vm-ui-radius,6px); background:var(--vm-ui-input-bg,#fff); color:var(--vm-ui-input-text,var(--vm-ui-text,#222)); box-sizing:border-box; }
.vm-favorites-ui .vm-favorites-add-button { white-space: nowrap; }
.vm-favorites-ui .vm-favorites-status { display: none; padding: 3px 10px; background: var(--vm-fav-status-bg, var(--vm-ui-bg-alt, #fff5f9)); color: var(--vm-fav-accent, var(--vm-ui-accent, #f54998)); font-size: 11px; font-weight: 700; flex: 0 0 auto; }
.vm-favorites-ui .vm-favorites-table-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain; overflow-anchor: none; padding: 0 10px 10px; box-sizing: border-box; }
.vm-favorites-ui .vm-favorites-table { position: relative; min-height: 100%; }
.vm-favorites-ui .vm-favorites-add-column-select { display: none; max-width: 160px; }
.vm-favorites-ui .vm-favorites-add-column, .vm-favorites-ui .vm-favorites-remove-all { white-space: nowrap; }
.vm-favorites-ui .vm-favorites-table-header { position: sticky; top: 0; z-index: 5; display: grid; grid-template-columns: var(--vm-fav-current-grid); gap: var(--vm-fav-grid-gap, 3px); align-items: stretch; padding: var(--vm-fav-header-padding, 2px 4px 3px); border: 1px solid var(--vm-fav-header-border, #d7d9df); border-radius: var(--vm-fav-header-radius, 8px 8px 0 0); background: var(--vm-fav-header-bg, linear-gradient(180deg, #f0f1f4 0%, #f7f7f9 100%)); box-shadow: var(--vm-fav-header-shadow, 0 3px 12px rgba(0,0,0,.055)); color: var(--vm-fav-header-text, #555); font-size: var(--vm-fav-header-font-size, 9px); font-weight: 700; backdrop-filter: blur(5px); }
.vm-favorites-ui .vm-favorites-sort-button { max-width: 100%; min-width: 0; padding: 0; border: 0; background: transparent; color: var(--vm-fav-header-text, #555); cursor: pointer; font: inherit; font-size: var(--vm-fav-header-font-size, 9px); font-weight: 800; text-align: left; white-space: normal; line-height: 1.08; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.vm-favorites-ui .vm-favorites-sort-button.-active { color: var(--vm-fav-accent, var(--vm-ui-accent, #f54998)); font-weight: 900; }
.vm-favorites-ui .vm-favorites-filter { width: 100%; min-width: 0; height: var(--vm-fav-filter-height, 20px); min-height: var(--vm-fav-filter-height, 20px); padding: 0 5px; border: 1px solid var(--vm-fav-filter-border,var(--vm-ui-input-border,#bbb)); border-radius: var(--vm-fav-filter-radius, 6px); outline: none; background: var(--vm-fav-filter-bg,var(--vm-ui-input-bg,#fff)); color: var(--vm-fav-filter-text,var(--vm-ui-input-text,#555)); font-size: var(--vm-fav-filter-font-size, 8.8px); box-sizing: border-box; transition: border-color .12s ease, box-shadow .12s ease, background-color .12s ease; }
.vm-favorites-ui select.vm-favorites-filter { padding: 0 15px 0 5px; }
.vm-favorites-ui .vm-favorites-filter-active { border-color: var(--vm-fav-accent, var(--vm-ui-accent, #f54998)) !important; background: var(--vm-fav-filter-active-bg, #fff5f9) !important; color: var(--vm-fav-filter-active-text,var(--vm-fav-accent,var(--vm-ui-accent,#f54998))) !important; font-weight:700; }
.vm-favorites-ui .vm-favorites-header-cell { min-width: 0; display: grid; grid-template-rows: var(--vm-fav-header-rows, var(--vm-fav-header-title-height, 24px)); gap: var(--vm-fav-header-gap, 0); align-content: center; padding: 0; }
.vm-favorites-ui .vm-favorites-header-cell > :first-child { margin-top: 2px; margin-bottom: 2px; }
.vm-favorites-ui .vm-favorites-header-group { display: flex; align-items: center; gap: 3px; min-width: 0; }
.vm-favorites-ui .vm-favorites-row { display: grid; grid-template-columns: var(--vm-fav-current-grid); gap: var(--vm-fav-grid-gap, 3px); align-items: center; min-height: var(--vm-fav-row-min-height, 64px); padding: var(--vm-fav-row-padding, 2px 4px); border-left: 1px solid var(--vm-fav-row-edge-border, #e3e4e7); border-right: 1px solid var(--vm-fav-row-edge-border, #e3e4e7); border-bottom: 1px solid var(--vm-fav-row-border, #e7e8ea); background: var(--vm-fav-row-current-bg, var(--vm-fav-row-bg, #fff)); box-shadow: var(--vm-fav-row-current-shadow, none); transition: background-color .11s ease, box-shadow .11s ease; }
.vm-favorites-ui .vm-favorites-row.-unavailable { background: var(--vm-fav-row-unavailable-bg, color-mix(in srgb, #888 18%, var(--vm-fav-row-bg, var(--vm-ui-bg, #fff)))) !important; }
.vm-favorites-ui .vm-favorites-row.-unavailable + .vm-favorites-row.-unavailable { border-top: 1px solid var(--vm-fav-section-separator, var(--vm-fav-row-edge-border, #c7c9ce)); }
.vm-favorites-ui .vm-favorites-row.-last { border-radius: var(--vm-fav-last-row-radius, 0 0 7px 7px); }
.vm-favorites-ui .vm-favorites-image-cell { width: 100%; height: 100%; min-height: var(--vm-fav-row-min-height, 64px); align-self: stretch; border-radius: 5px; background: var(--vm-ui-bg-alt, #f2f2f2); overflow: hidden; position: relative; display: flex; align-items: center; justify-content: center; color: var(--vm-ui-text-faint, #bbb); font-size: 9px; }
.vm-favorites-ui .vm-favorites-image-cell > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.vm-favorites-ui .vm-favorites-profile-cell { min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 1px; padding: 1px 0; }
.vm-favorites-ui .vm-favorites-profile-name-row { display: flex; align-items: center; gap: 3px; min-width: 0; margin-bottom: 4px; }
.vm-favorites-ui .vm-favorites-profile-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--vm-fav-accent, var(--vm-ui-accent, #f54998)); font-weight: 700; font-size: 13px; line-height: 1.15; text-decoration: none; }
.vm-favorites-ui .vm-favorites-note-cell { min-width: 0; display: flex; align-items: stretch; align-self: stretch; height: 100%; min-height: 0; overflow: hidden; }
.vm-favorites-ui .vm-favorites-category-cell { position: relative; z-index: 2; display: flex; align-items: flex-start; justify-content: flex-start; flex-wrap: nowrap; gap: 3px; min-width: 0; overflow: visible; }
.vm-favorites-ui .vm-favorites-category-chips { display: flex; flex: 1 1 auto; align-items: center; flex-wrap: wrap; gap: 2px; min-width: 0; }
.vm-favorites-ui .vm-favorites-actions-cell { display: flex; gap: 3px; align-items: center; justify-content: center; }
.vm-favorites-ui .vm-favorites-location-cell, .vm-favorites-ui .vm-favorites-data-cell { min-width: 0; color: var(--vm-ui-text, #444); font-size: 8.5px; line-height: 1.08; }
.vm-favorites-ui .vm-favorites-cell-text { display: block; min-width: 0; max-height: 4.4em; overflow: hidden; overflow-wrap: anywhere; }

.vm-favorites-ui .vm-ui-button { background:var(--vm-fav-button-bg,var(--vm-ui-bg,#fff)); }
.vm-favorites-ui .vm-ui-button.-accent { border-color:var(--vm-fav-accent,var(--vm-ui-accent,#f54da3)); color:var(--vm-fav-accent,var(--vm-ui-accent,#f54da3)); }
.vm-favorites-ui .vm-ui-button.-primary { border-color:var(--vm-fav-accent,var(--vm-ui-accent,#f54da3)); background:var(--vm-fav-accent,var(--vm-ui-accent,#f54da3)); color:var(--vm-fav-accent-contrast,var(--vm-ui-accent-contrast,#fff)); }
.vm-favorites-ui .vm-favorites-icon-button { border-color:var(--vm-fav-icon-border,var(--vm-ui-input-border,#bbb)); background:var(--vm-fav-icon-bg,var(--vm-ui-bg,#fff)); color:var(--vm-fav-icon-text,var(--vm-ui-text,#222)); }
.vm-favorites-ui .vm-favorites-change-history-button { opacity:.4; border-color:transparent; background:transparent; color:var(--vm-ui-text-muted,#888); }
.vm-favorites-ui .vm-favorites-changes-cell:hover .vm-favorites-change-history-button, .vm-favorites-ui .vm-favorites-change-history-button:focus-visible { opacity:1; border-color:var(--vm-fav-accent,var(--vm-ui-accent,#f54da3)); background:var(--vm-ui-bg-hover,#f5f5f5); color:var(--vm-fav-accent,var(--vm-ui-accent,#f54da3)); }
@media (hover:none) { .vm-favorites-ui .vm-favorites-change-history-button { opacity:1; } }
.vm-favorites-ui .vm-favorites-icon-button:hover:enabled { border-color:var(--vm-fav-icon-hover-border,var(--vm-ui-input-border,#bbb)); background:var(--vm-fav-icon-hover-bg,var(--vm-ui-bg-hover,#f5f5f5)); color:var(--vm-fav-accent,var(--vm-ui-accent,#f54da3)); }
.vm-favorites-ui .vm-favorites-close { background:var(--vm-fav-close-bg,var(--vm-ui-bg,#fff)); color:var(--vm-fav-close-text,var(--vm-ui-text,#222)); }
`);
	}
	function getWatchEventUrl(event,markRead=true) {
		if (!event?.url) return '';
		try {
			const url=new URL(event.url,location.href);
			if (!/^https?:$/.test(url.protocol)) return '';
			if (markRead && event.alertTriggered && event.id) {
				const parts=url.hash.slice(1).split('&').filter(part => part && !part.startsWith('vm_watch_read='));
				parts.push(`vm_watch_read=${encodeURIComponent(event.id)}`); url.hash=parts.join('&');
			}
			return url.href;
		} catch {return '';}
	}
	async function openWatchEvent(event,active=true) {
		const url=getWatchEventUrl(event);
		if (!url) return false;
		try {
			await GM_openInTab(url,{active,insert:true,setParent:true});
			if (event.alertTriggered) acknowledgeWatchEvent(event.id);
			return true;
		} catch (error) {log('Nie udało się otworzyć alertu',error); showWatchToast('Nie udało się otworzyć strony alertu.',true); return false;}
	}
	function makeWatchEventLink(event,text=event.message || '',markReadOnArrival=true) {
		const url=getWatchEventUrl(event,markReadOnArrival);
		const link=makeElement('a','',text,{href:url || '#',target:'_blank',rel:'noopener noreferrer',title:event.description || event.message || ''});
		if (url && markReadOnArrival && event.alertTriggered) {
			const read=click => {
				if (click.button > 1) return;
				setTimeout(() => {if (!click.defaultPrevented) acknowledgeWatchEvent(event.id);},0);
			};
			link.addEventListener('click',read); link.addEventListener('auxclick',read);
		}
		return link;
	}
	function getWatchEventTimelineLabel(event) {
		if (event?.type === 'online') return 'Stan online';
		if (event?.type === 'location') return 'Lokalizacja';
		if (event?.type === 'changes') return 'Zmiana treści';
		if (event?.type === 'new-ad') return 'Nowy anons';
		return 'Zmiana';
	}
	function clearWatchHistoryForItem(itemId) {
		const id=String(itemId || '');
		if (!id) return 0;
		const events=getWatchEvents(),remaining=events.filter(event=>event.itemId!==id);
		const removed=events.length-remaining.length;
		if (removed) {
			saveWatchEvents(remaining);
			refreshWatchPageButtons();
			try {document.getElementById(WATCH_SETTINGS_OVERLAY_ID)?._vmRender?.();} catch {}
			renderWatchNotificationBar();
		}
		return removed;
	}
	function openWatchChangesTimeline( item, itemEvents ) {
		const timelineAlertStyle={
							padding:'0', border:'0', borderRadius:'0', background:'transparent', color: 'var(--vm-ui-text-muted, #777)',
							fontSize:'8px', fontWeight:'400'
						};
		const events=( Array.isArray(itemEvents)
				? itemEvents
				:[] ) .filter(event=>String(event.itemId)===String(item?.id)).sort( (a,b) => Number(b.timestamp || 0) - Number(a.timestamp || 0) );
		const {overlay,box}= createWatchDialogBase( 'vm-favorites-changes-timeline-overlay', `Historia zmian - ${
					item?.displayName || 'profil/anons'
				}` );
		prepareFavoriteTableStyleApi( overlay, box );
		box.classList.add('vm-ui-timeline-dialog');
		Object.assign(box.style,{width: 'min(760px, calc(100vw - 32px))'});
		const title=box.querySelector('h2');
		if (title) {
			title.style.marginBottom='6px';
		}
		const summary=makeElement('div',"vm-ui-help",`${events.length} ${
				polishCountForm( events.length, 'zmiana', 'zmiany', 'zmian' )
			}`);
		Object.assign(summary.style,{marginBottom:'12px'});
		const timeline=makeElement( 'div', 'vm-favorites-changes-timeline' ),updates=new Map();
		Object.assign(timeline.style,{
			display:'grid', gap:'0'
		});
		if (!events.length) {
			const empty=makeElement( 'div', '', 'Brak zapisanych zmian.' );
			empty.style.color= 'var(--vm-ui-text-muted, #777)';
			timeline.appendChild(empty);
		}
		events.forEach((event,index) => {
			const row=makeElement( 'div', 'vm-favorites-changes-timeline-row' );
			Object.assign(row.style,{
				display:'grid', gridTemplateColumns: '108px 22px minmax(0,1fr)', gap:'8px', minWidth:'0'
			});
			const date=new Date( Number(event.timestamp || 0) );
			const time=makeElement( 'div', '', Number.isFinite(date.getTime())
					? date.toLocaleString( 'pl-PL', {
							day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit'
						} )
					:'' );
			Object.assign(time.style,{
				paddingTop:'5px', color: 'var(--vm-ui-text-muted, #777)', fontSize:'9.5px', textAlign:'right', whiteSpace:'nowrap'
			});
			const axis=makeElement('div');
			Object.assign(axis.style,{
				position:'relative', minHeight:'52px'
			});
			if (index < events.length - 1) {
				const vertical=makeElement('span');
				Object.assign(vertical.style,{
					position:'absolute', left:'10px', top:'16px', bottom:'-2px', width:'1px', background: 'var(--vm-ui-border, #ddd)'
				});
				axis.appendChild(vertical);
			}
			const dot=makeElement('span');
			Object.assign(dot.style,{
				position:'absolute',left:'5px',top:'7px',width:'11px',height:'11px',borderRadius:'50%',
				boxSizing:'border-box',border:'2px solid var(--vm-ui-bg, #fff)',boxShadow:'0 0 0 1px var(--vm-ui-border-strong, #bbb)'
			});
			axis.appendChild(dot);
			const content=makeElement('div',"vm-ui-panel");
			content.style.marginBottom='8px';
			const top=makeElement('div');
			Object.assign(top.style,{
				display:'flex', alignItems:'center', gap:'6px', flexWrap:'wrap', marginBottom:'3px'
			});
			const type=makeElement( 'span', '', getWatchEventTimelineLabel( event ) );
			Object.assign(type.style,{fontSize:'9px',fontWeight:'800'});
			top.appendChild(type);
			const timelineAlertLabel=event.alertTriggered ? makeElement('span'):null;
			const timelineAlertAccept=event.alertTriggered ? makeButton('vm-ui-button vm-ui-icon vm-ui-compact vm-ui-table-icon','✓'):null;
			if (timelineAlertAccept) {
				setControlTitle(timelineAlertAccept,'Zaakceptuj alert / oznacz jako odczytany');
				timelineAlertAccept.addEventListener('click',click => {stopUiEvent(click); acknowledgeWatchEvent(event.id);});
				top.append(timelineAlertLabel,timelineAlertAccept);
			}
			const message=makeWatchEventLink(event);
			Object.assign(message.style,{display:'block',textDecoration:'underline',textUnderlineOffset:'2px',fontSize:'10.5px',lineHeight:'1.3',overflowWrap:'anywhere'});
			const sync=current => {
				if (!current) {detachHelperNode(row); return;}
				event.acknowledged=current.acknowledged;
				const pending=!event.acknowledged && event.alertTriggered;
				dot.style.background=event.acknowledged ? 'var(--vm-ui-text-faint, #aaa)':pending ? 'var(--vm-ui-danger, #c62828)':'var(--vm-ui-accent, #f54da3)';
				message.style.color=event.acknowledged ? 'var(--vm-ui-text, #444)':pending ? 'var(--vm-ui-danger, #c62828)':'var(--vm-ui-accent, #f54da3)';
				type.style.color=pending ? 'var(--vm-ui-danger, #c62828)':'var(--vm-ui-text, #333)';
				content.style.border=pending ? '1px solid var(--vm-ui-danger, #c62828)':'1px solid var(--vm-ui-border, #ddd)';
				content.style.background=pending ? 'color-mix(in srgb, var(--vm-ui-danger, #c62828) 8%, var(--vm-ui-bg-alt, #fafafa))':'var(--vm-ui-bg-alt, #fafafa)';
				if (timelineAlertLabel) {
					timelineAlertLabel.textContent=pending ? 'alert':'alert odczytany';
					Object.assign(timelineAlertLabel.style,pending ? {padding:'1px 5px',border:'1px solid var(--vm-ui-danger, #c62828)',borderRadius:'999px',background:'color-mix(in srgb, var(--vm-ui-danger, #c62828) 8%, transparent)',color:'var(--vm-ui-danger, #c62828)',fontSize:'8px',fontWeight:'800'}:timelineAlertStyle);
					timelineAlertAccept.style.display=pending ? 'inline-flex':'none';
				}
			};
			updates.set(event.id,sync); sync(event);
			content.append(top,message);
			const description= String( event.description || '' ).trim();
			if (description) {
				const detail=makeElement( 'div', '', description );
				Object.assign(detail.style,{
					marginTop:'4px', paddingTop:'4px', borderTop: '1px solid var(--vm-ui-border, #e5e5e5)', color:
						'var(--vm-ui-text-muted, #666)', fontSize:'9.5px', lineHeight:'1.3', whiteSpace:'pre-wrap',
					overflowWrap:'anywhere'
				});
				content.appendChild(detail);
			}
			row.append( time, axis, content );
			timeline.appendChild(row);
		});
		overlay._vmRender=() => {
			const current=new Map(getWatchEvents().map(event => [event.id,event])); let count=0;
			for (const [id,sync] of updates) {sync(current.get(id)); if (current.has(id)) count++;}
			summary.textContent=`${count} ${polishCountForm(count,'zmiana','zmiany','zmian')}`;
		};
		const actions=makeElement('div',"vm-ui-actions");
		const clear=makeButton('vm-favorites-clear-history vm-ui-button -danger','Wyczyść historię');
		clear.disabled=!item?.id || !events.length;
		clear.style.marginRight='auto';
		setControlTitle(clear,'Usuń historię zmian tylko tego profilu/anonsu. Pozostałe dane i historia innych pozycji pozostaną.');
		const feedback=makeElement('div','vm-ui-help');feedback.setAttribute('role','status');
		clear.addEventListener('click',event=>{
			stopUiEvent(event);
			if(clear.disabled)return;
			if(!window.confirm(`Wyczyścić historię zmian „${item.displayName || 'profil/anons'}”?\n\n`+
				'Zostanie usunięta tylko historia tej pozycji, w tym jej oczekujące powiadomienia. Profil, notatki, kategorie i ustawienia alertów pozostaną bez zmian.\n\nTej operacji nie można cofnąć.'))return;
			try{
				const removed=clearWatchHistoryForItem(item.id);
				events.length=0;updates.clear();summary.textContent='0 zmian';
				timeline.replaceChildren(makeElement('div','vm-ui-help','Brak zapisanych zmian.'));
				clear.disabled=true;feedback.textContent=`Historia tej pozycji została wyczyszczona. Usunięte wpisy: ${removed}.`;
				close.focus({preventScroll:true});
			}catch(error){feedback.textContent='Nie udało się wyczyścić historii.';log('Błąd czyszczenia historii pozycji',error);}
		});
		const close=makeButton("vm-favorites-toolbar-button vm-ui-button",'Zamknij');
		const closeController=createDialogCloseController(overlay);
		closeController.bindButton(close);
		actions.append(clear,close);
		box.append( summary, timeline, feedback, actions );
	}
	function ensureHiddenItemsStyles() {
		if (document.getElementById('vm-hidden-items-style')) return;
		appendHelperStyle('vm-hidden-items-style',`
.vm-hidden-result { display:none !important; }
`);
	}
	function openWatchedSettingsModal(initialMode=false,forceTable=false) {
		if (deferUntilFullPersistentCacheReady(() => openWatchedSettingsModal(initialMode,forceTable))) return;
		const collectionMode=initialMode === true || initialMode === 'hidden';
		if (!collectionMode && !forceTable && getSettings().favoritesDefaultView === 'tiles') {
			const options=getFavoriteTilesOptions();openFavoriteTiles(options.fields,options.extra);return;
		}
		favoriteTilesOpenGeneration++;closeHelperDialog(document.getElementById('vm-favorite-tiles-test'));
		const titleText=collectionMode ? TRACKED_ITEMS_LABEL:'Ulubione';
		const tableStateKey=FAVORITE_TABLE_STATE_STORAGE_KEY + (collectionMode ? ':notes':'');
		const actionFilters=[['all','Wszystkie'],['favorite','Ulubione'],['not-favorite','Nie ulubione'],['hidden','Ukryte'],['not-hidden','Nie ukryte']];
		const isListItem=item => collectionMode ? item.hidden || watchedItemHasNotesOrCategories(item):item.favorite;
		closeHelperDialog(document.getElementById(WATCH_SETTINGS_OVERLAY_ID));
		const acknowledgeNonAlertUntilOpen= !collectionMode && getWatchSettings() .nonAlertChangeNewness ===
				'until-open';
		const nonAlertEventIdsNewAtOpen=new Set();
		if (acknowledgeNonAlertUntilOpen) {
			for (const event of getWatchEvents()) {
				if (!event.acknowledged && !event.alertTriggered) {
					nonAlertEventIdsNewAtOpen.add(event.id);
				}
			}
			if ( nonAlertEventIdsNewAtOpen.size && acknowledgeNonAlertWatchEvents() ) {
				renderWatchNotificationBar();
			}
		}
		const {overlay,box}=createWatchDialogBase(WATCH_SETTINGS_OVERLAY_ID,titleText);
		const closeController=createDialogCloseController(overlay,{
			onClose:() => {
				stopCheckProgress(); clearTimeout(checkStatusTimer);
				watchPageButtonRefreshers.delete(scheduleTableRender);tableRows.clear();
				for(const id of tableStorageListeners)try{GM_removeValueChangeListener(id);}catch {}
				if (acknowledgeNonAlertUntilOpen) {
					acknowledgeNonAlertWatchEvents();
					renderWatchNotificationBar();
				}
			}
		});
		prepareFavoriteTableStyleApi(overlay,box);
		box.classList.add('vm-ui-table-dialog');
		detachHelperNode(box.firstElementChild);
		const header=makeElement('div','vm-favorites-toolbar');
		const title=makeElement('h2',"vm-favorites-title vm-ui-title",titleText);
		const visibleCount=makeElement('span','vm-favorites-count');
		visibleCount.setAttribute('aria-live','polite');
		title.appendChild(visibleCount);
		const manualInput=makeElement('input',"vm-favorites-manual-input vm-ui-field vm-ui-compact",null,{type:'text',placeholder:'Adres profilu Escorti, anonsu Escort.club albo numer telefonu'});
		const addButton=makeButton("vm-favorites-toolbar-button vm-favorites-add-button vm-ui-button vm-ui-compact -accent",'Dodaj do ulubionych');
		const headerActions=makeElement('div','vm-favorites-toolbar-actions');
		const settingsGear=makeSettingsTabGearButton( 'watch', 'Ustawienia - Ulubione i zapisane anonse' );
		settingsGear.classList.add('vm-favorites-settings-gear');
		const close=makeButton("vm-favorites-close vm-ui-button vm-ui-icon vm-ui-compact",'×');
		setControlTitle(close,'Zamknij');
		closeController.bindButton(close);
		headerActions.append(settingsGear,close);
		header.append(title,manualInput,addButton,headerActions);
		const status=makeElement('div','vm-favorites-status');
		status.setAttribute('role','status');
		const body=makeElement('div','vm-favorites-table-scroll');
		let favoriteTableSavedScrollTop=0;
		let favoriteTableSavedScrollLeft=0;
		let favoriteTableScrollRenderGeneration=0;
		let suppressFavoriteTableScrollCapture=false;
		body.addEventListener('scroll',() => {
			if (suppressFavoriteTableScrollCapture) return;
			favoriteTableSavedScrollTop=body.scrollTop;
			favoriteTableSavedScrollLeft=body.scrollLeft;
		});
		const itemsHost=makeElement('div','vm-favorites-table');
		const tableRows=createFavoriteListRenderer(itemsHost);
		let nextTableNodes=[];
		body.append(itemsHost);
		box.append(header,status,body);
		const refresh=makeButton("vm-favorites-toolbar-button vm-favorites-refresh-all vm-ui-button vm-ui-compact -primary",'Sprawdź widoczne',
			{title:'Sprawdź tylko pozycje widoczne w tej tabeli po zastosowaniu filtrów.'});
		let visibleItemIds=[];
		const tileViewButton=makeFavoriteViewButton('tiles',()=>switchFavoriteView('tiles'));tileViewButton.classList.add('vm-favorites-toolbar-button');
		const headerAddColumnButton=makeButton("vm-favorites-toolbar-button vm-favorites-add-column vm-ui-button vm-ui-compact -accent",'Dodaj kolumnę');
		const headerAddColumnSelect=makeElement('select',"vm-favorites-add-column-select vm-ui-field vm-ui-compact");
		headerAddColumnSelect.style.display='none';
		const quickFilters=collectionMode ? [['hidden','Tylko ukryte'],['not-favorite','Tylko nieulubione']].map(([value,label]) => {
			const button=makeButton('vm-favorites-toolbar-button vm-favorites-quick-filter vm-ui-button vm-ui-compact',label);
			button.dataset.vmFavoritePreset=value;
			button.title='Filtruj tę listę; ponowne kliknięcie wyłącza ten filtr. Pozostałe filtry pozostają aktywne.';
			button.addEventListener('click',() => rerenderFavoriteFilter('actions',favoriteTableState.filters.actions === value ? 'all':value));
			headerActions.insertBefore(button,settingsGear);
			return button;
		}):[];
		const restoreHiddenButton=collectionMode ? makeButton('vm-favorites-toolbar-button vm-favorites-restore-hidden vm-ui-button vm-ui-compact','Odkryj widoczne'):null;
		headerActions.insertBefore(headerAddColumnButton,settingsGear);
		headerActions.insertBefore(headerAddColumnSelect,settingsGear);
		headerActions.insertBefore(refresh,settingsGear);
		if (!collectionMode) headerActions.insertBefore(tileViewButton,settingsGear);
		if (restoreHiddenButton) headerActions.insertBefore(restoreHiddenButton,settingsGear);
		const defaultFavoriteTableState={
			sortKey:'lp', sortDirection:'asc', filters:{
				profile:'', favorite:'all', note:'', category:'all', watch:'all', state:'all', location:'', data:{}, changes:'all'
			}
		};
		const savedFavoriteTableState=readFavoriteViewPreferences(tableStateKey);
		const favoriteTableState={
			...defaultFavoriteTableState,
			...(savedFavoriteTableState && typeof savedFavoriteTableState === 'object' ? savedFavoriteTableState:{}), filters:{
				...defaultFavoriteTableState.filters,
				...(savedFavoriteTableState?.filters && typeof savedFavoriteTableState.filters === 'object'
					? savedFavoriteTableState.filters
					:{}), data:savedFavoriteTableState?.filters?.data && typeof savedFavoriteTableState.filters.data === 'object'
					? {...savedFavoriteTableState.filters.data}
					:{}
			}
		};
		if (typeof favoriteTableState.sortKey !== 'string') favoriteTableState.sortKey=defaultFavoriteTableState.sortKey;
		if (collectionMode) {
			if (['favorite','watch','changes'].includes(favoriteTableState.sortKey)) favoriteTableState.sortKey='lp';
			for (const key of ['favorite','watch','changes']) favoriteTableState.filters[key]='all';
			if (!actionFilters.some(([key]) => key === favoriteTableState.filters.actions)) favoriteTableState.filters.actions='all';
			if (initialMode === 'hidden') favoriteTableState.filters.actions='hidden';
		}
		if (!['asc','desc'].includes(favoriteTableState.sortDirection)) favoriteTableState.sortDirection='asc';
		delete favoriteTableState.filters.duplicates;
		const persistFavoriteTableState=() => saveFavoriteViewPreferences(tableStateKey,{
			sortKey:favoriteTableState.sortKey, sortDirection:favoriteTableState.sortDirection, filters:{
				...favoriteTableState.filters, data:{...favoriteTableState.filters.data}
			}
		});
		const normalizeFavoriteFilterText=value => normalizeText(String(value || ''));
		let viewData;
		const favoriteProfileFilterText=item => normalizeFavoriteFilterText([ item.displayName, watchItemSourceText(item),
			...item.profileUrls, ...viewData(item).all ].filter(Boolean).join(' '));
		const favoriteNoteFilterText=item => normalizeFavoriteFilterText(item.note);
		const favoriteWatchSortValue=item => {
			const online=item.modes?.online ? 1:0;
			const locationMode=item.modes?.location ? 1:0;
			const changes=item.modes?.changes ? 1:0;
			const newAds=item.modes?.newAds ? 1:0;
			return ( (online + locationMode + changes + newAds) * 10 + online * 8 + locationMode * 4 + changes * 2 + newAds );
		};
		const getSortValue=(item,sortKey) => {
			if (sortKey === 'lp') return Number(item.createdAt) || 0;
			if (sortKey === 'favorite') return hasFavoriteStar(item) ? 1:0;
			if (sortKey === 'note') return favoriteNoteFilterText(item);
			if (sortKey === 'watch') return favoriteWatchSortValue(item);
			if (sortKey === 'state') return viewData(item).stateRank;
			if (sortKey === 'category') return normalizeFavoriteFilterText((item.categories || []).join(' '));
			if (sortKey === 'location') return normalizeFavoriteFilterText(watchCitiesText(viewData(item).cities));
			if (sortKey === 'changes') return viewData(item).latestChange;
			if (sortKey.startsWith('data:')) return viewData(item).sortValue(sortKey.slice(5));
			return normalizeFavoriteFilterText(item.displayName);
		};
		const filterAndSortFavoriteItems=(source,dataColumns) => {
			const filters=favoriteTableState.filters;
			const profileQuery=normalizeFavoriteFilterText(filters.profile);
			const noteQuery=normalizeFavoriteFilterText(filters.note);
			const locationQuery=normalizeFavoriteFilterText(filters.location);
			const filtered=source.filter(item => {
				if (collectionMode && ((filters.actions === 'favorite' && !item.favorite) ||
					(filters.actions === 'not-favorite' && item.favorite) || (filters.actions === 'hidden' && !item.hidden) ||
					(filters.actions === 'not-hidden' && item.hidden))) return false;
				const itemEvents=viewData(item).events;
				if (profileQuery && !favoriteProfileFilterText(item).includes(profileQuery)) return false;
				if (noteQuery && !favoriteNoteFilterText(item).includes(noteQuery)) return false;
				const itemCategories=normalizeFavoriteAssignedCategories(item.categories);
				if (filters.category === '__none__' && itemCategories.length) return false;
				if (filters.category !== 'all' && filters.category !== '__none__' && !itemCategories.includes(filters.category)) return false;
				if (filters.watch === 'online' && !item.modes?.online)
					return false;
				if (filters.watch === 'location' && !item.modes?.location)
					return false;
				if (filters.watch === 'changes' && !item.modes?.changes)
					return false;
				if (filters.watch === 'newAds' && !item.modes?.newAds)
					return false;
				if ( filters.watch === 'none' && ( item.modes?.online || item.modes?.location || item.modes?.changes ||
						item.modes?.newAds ) ) return false;
				if (filters.state === 'online' && item.state?.online !== true) return false;
				if (filters.state === 'online-open' && (item.state?.online !== true || viewData(item).availability !== true)) return false;
				if (filters.state === 'offline' && item.state?.online !== false) return false;
				if (filters.state === 'unknown' && (item.state?.online === true || item.state?.online === false)) return false;
				if (filters.state === 'open' && viewData(item).availability !== true) return false;
				if (filters.state === 'closed' && viewData(item).availability !== false) return false;
				if (locationQuery && !normalizeFavoriteFilterText(watchCitiesText(viewData(item).cities)).includes(locationQuery)) return false;
				for (const field of dataColumns) {
					const query=normalizeFavoriteFilterText(filters.data[field]);
					if (query && !normalizeFavoriteFilterText(viewData(item).column(field)).includes(query)) return false;
				}
				if (filters.changes === 'new' && !itemEvents.some(event => !event.acknowledged)) return false;
				if (filters.changes === 'any' && !itemEvents.length) return false;
				if (filters.changes === 'alert' && !itemEvents.some(event => !event.acknowledged && event.alertTriggered)) return false;
				if (filters.changes === 'none' && itemEvents.length) return false;
				return true;
			});
			const direction=favoriteTableState.sortDirection === 'desc' ? -1:1;
			return filtered.sort((first,second) => {
				const primary=compareFavoriteValues( getSortValue(first,favoriteTableState.sortKey),
					getSortValue(second,favoriteTableState.sortKey),direction );
				if (primary) return primary;
				return String(first.displayName || '').localeCompare(String(second.displayName || ''),'pl-PL',{
					numeric:true, sensitivity:'base'
				});
			});
		};
		const rerenderFavoriteFilter=(key,value,sourceControl=null,dataField=null) => {
			if (key === 'data' && dataField) favoriteTableState.filters.data[dataField]=value;
			else favoriteTableState.filters[key]=value;
			persistFavoriteTableState();
			const selectionStart=sourceControl?.selectionStart;
			const selectionEnd=sourceControl?.selectionEnd;
			render();
			if (!sourceControl || sourceControl.tagName !== 'INPUT') return;
			requestAnimationFrame(() => {
				const selector=dataField
					? `[data-vm-favorite-filter="data:${dataField}"]`
					:`[data-vm-favorite-filter="${key}"]`;
				const next=itemsHost.querySelector(selector);
				if (!(next instanceof HTMLInputElement)) return;
				next.focus();
				if (Number.isInteger(selectionStart) && Number.isInteger(selectionEnd)) {
					try {next.setSelectionRange(selectionStart,selectionEnd);} catch {}
				}
			});
		};
		const saveDataColumns=columns => {
			const normalized= normalizeFavoritesDataColumns(columns);
			updateSettings({
				favoritesDataColumns:normalized
			});
			return normalized;
		};
		const addFavoriteColumnSeparators=row => {
			const cells=[...row.children];
			cells.forEach((cell,index) => {
				cell.classList.add('vm-favorites-cell');
				cell.style.boxSizing='border-box';
				if (!index) return;
				if (index !== 1) {
					const isSectionEdge=index >= cells.length - 2;
					cell.style.alignSelf='stretch';
					cell.style.borderLeft=isSectionEdge
						? '1px solid var(--vm-fav-section-separator, rgba(70,70,70,.22))'
						:'1px solid var(--vm-fav-column-separator, rgba(70,70,70,.105))';
					cell.style.paddingLeft='var(--vm-fav-cell-padding-left, 4px)';
				}
				if (!cell.style.display && !cell.classList.contains('vm-favorites-header-cell')) {
					cell.style.display='flex';
					cell.style.flexDirection='column';
					cell.style.justifyContent='center';
				}
			});
		};
		const favoriteRowsToMeasure=[];
		const appendFavoriteTableRow=(row,dataColumns,categoryCell=null) => {
			const keys=['lp','profile','note','category','alert','state','location',
					...dataColumns.map(field => `data:${field}`),'changes','actions'];
			[...row.children].forEach((cell,index) => {
				cell.dataset.vmFavoriteColumn=keys[index];
				if ((collectionMode && ['alert','changes'].includes(keys[index])) || cell.style.display === 'none') detachHelperNode(cell);
			});
			addFavoriteColumnSeparators(row);
			nextTableNodes.push(row);
			if (!categoryCell) return;
			for (const cell of row.children) {
				if (cell === categoryCell) continue;
				Object.assign(cell.style,{minHeight:'0',maxHeight:'100%',overflow:'hidden'});
			}
			favoriteRowsToMeasure.push([row,categoryCell]);
		};
		const pendingChecks=new Set();
		const stopCheckProgress=attachWatchCheckProgress(status,() => pendingChecks.size > 0);
		let checkStatusTimer=null;
		const runListCheck=async ids => {
			if (pendingChecks.size || !ids.length) return;
			clearTimeout(checkStatusTimer);
			ids.forEach(id => pendingChecks.add(id));
			let progressText='Sprawdzanie…';
			let completedWithoutIssues=false;
			status.style.color='';
			render();
			showStatusText(status,progressText);
			try {
				const result=await runWatchedChecks({force:true,itemIds:ids, onProgress:progress => {
						progressText=progress.message || `Sprawdzanie ${progress.itemIndex || progress.checked || 0}/${progress.total || 0}: ${progress.itemName || ''}`;
						showStatusText(status,progressText);
					}
				});
				completedWithoutIssues=result.status === 'completed' && result.checked > 0 && !result.failed && !result.skipped;
				progressText=result.message || [
					result.status === 'interrupted' ? 'Sprawdzanie przerwane.'
						:result.checked ? 'Sprawdzanie zakończone.':'Nie sprawdzono żadnej pozycji.',
					`Sprawdzono: ${result.checked}/${result.total}.`,
					result.failed ? `Błędy: ${result.failed}. Najedź na przycisk „Sprawdź”, aby odczytać szczegóły.`:'',
					result.skipped ? `Pominięto: ${result.skipped} - wpisy zmienione, usunięte lub niewymagające sprawdzania.`:''
				].filter(Boolean).join(' ');
				if (result.failed || result.status === 'interrupted') status.style.color='var(--vm-ui-danger,#c62828)';
			} catch (error) {
				progressText=error?.message || 'Nie udało się sprawdzić pozycji.';
				status.style.color='var(--vm-ui-danger,#c62828)';
			} finally {
				pendingChecks.clear();
				showStatusText(status,progressText);
				render();
				if (completedWithoutIssues) checkStatusTimer=setTimeout(() => {status.style.display='none';},4000);
			}
		};
		const makeItemActions=item => {
			const actions=makeElement('div','vm-favorites-actions-cell');
			const check=makeButton('vm-favorites-icon-button vm-favorites-check-one vm-ui-button vm-ui-icon vm-ui-compact',pendingChecks.has(item.id) ? '…':'↻',{title:item.lastError ? `Sprawdź ponownie: ${item.lastError}`:'Sprawdź'});
			check.setAttribute('aria-label',`${check.title}: ${item.displayName}`);
			check.disabled=pendingChecks.size > 0;
			check.addEventListener('click',() => runListCheck([item.id]));
			actions.appendChild(check);
			Object.assign(actions.style,{display:'grid',gridTemplateColumns:'repeat(2,24px)',alignContent:'center'});
			for (const kind of ['hidden','favorite']) {
				if (kind === 'favorite' && item.sourceType === 'escort-search') continue;
				const button=makeButton('vm-favorites-icon-button vm-ui-button vm-ui-icon vm-ui-compact',kind === 'favorite' ? (hasFavoriteStar(item) ? '★':'☆'):'');
				if (kind === 'hidden') button.appendChild(createHiddenIcon(!!item.hidden));
				setControlTitle(button,kind === 'favorite' ? (hasFavoriteStar(item) ? trackedRemovalHint('favorite'):'Dodaj do Ulubionych'):item.hidden ? trackedRemovalHint('hidden'):'Ukryj w wynikach wyszukiwania. Ulubione, notatka, kategorie i alerty pozostaną.');
				button.setAttribute('aria-pressed',String(kind === 'favorite' ? hasFavoriteStar(item):!!item.hidden));
				if (kind === 'favorite' ? hasFavoriteStar(item):item.hidden) Object.assign(button.style,{background:kind === 'favorite' ? '#f4c542':'#ff9b9b',color:'#000'});
				button.addEventListener('click',() => {
					changeTrackedItemStatus(item.id,kind);
					render();
				});
				actions.appendChild(button);
			}
			const remove=makeButton('vm-favorites-icon-button vm-favorites-remove-one vm-ui-button vm-ui-icon vm-ui-compact');
			setTrashIcon(remove);
			remove.title=trackedRemovalHint(collectionMode ? 'notes':'profile');
			remove.disabled=collectionMode && !watchedItemHasNotesOrCategories(item);
			remove.setAttribute('aria-label',`${remove.title}: ${item.displayName}`);
			remove.addEventListener('click',async () => {
				if (!collectionMode) changeTrackedItemStatus(item.id,'remove');
				else {
					if (!window.confirm(`Usunąć notatkę i wszystkie przypisane kategorie dla „${item.displayName}”?`)) return;
					await changeTrackedEntry(item.id,'clearNotes');
				}
				render();
			});
			actions.insertBefore(remove,check.nextSibling);
			if (item.sourceType === 'escort-search') actions.appendChild(makeFavoriteSearchEditButton(item,true));
			return actions;
		};
		headerAddColumnButton.addEventListener('click',() => {
			if (headerAddColumnButton.disabled) return;
			headerAddColumnSelect.style.display=headerAddColumnSelect.style.display === 'none' ? 'inline-block':'none';
			if (headerAddColumnSelect.style.display !== 'none') {
				headerAddColumnSelect.focus();
				try {headerAddColumnSelect.showPicker?.();} catch {}
			}
		});
		headerAddColumnSelect.addEventListener('change',() => {
			const field=normalizeEscortDataFieldId(headerAddColumnSelect.value);
			if (!field || field === 'none') return;
			const currentColumns=getFavoriteSettings().dataColumns;
			saveDataColumns([...currentColumns,field]);
			headerAddColumnSelect.value='';
			headerAddColumnSelect.style.display='none';
			render();
		});
		restoreHiddenButton?.addEventListener('click',() => {
			const ids=getWatchedItems().filter(item => item.hidden && visibleItemIds.includes(item.id)).map(item => item.id);
			if (!ids.length || !window.confirm(`Odkryć widoczne pozycje (${ids.length})? Notatki, kategorie i Ulubione pozostaną bez zmian.`)) return;
			restoreHiddenItems(ids);
			render();
		});
		const render=() => {
			if(!overlay.isConnected)return;
			favoriteRowsToMeasure.length=0;
			const renderGeneration= ++favoriteTableScrollRenderGeneration;
			if (!suppressFavoriteTableScrollCapture) {
				favoriteTableSavedScrollTop=body.scrollTop;
				favoriteTableSavedScrollLeft=body.scrollLeft;
			}
			suppressFavoriteTableScrollCapture=true;
			for (const control of [manualInput,addButton]) control.style.display=collectionMode ? 'none':'';
			for (const button of quickFilters) {
				const active=favoriteTableState.filters.actions === button.dataset.vmFavoritePreset;
				button.setAttribute('aria-pressed',String(active));button.classList.toggle('-accent',active);
			}
			const allItems=getWatchedItems().filter(isListItem);
			const events=(collectionMode ? []:getWatchEvents()).map(event => nonAlertEventIdsNewAtOpen.has(event.id)
					? {...event,acknowledged:false}
					:event );
			viewData=createFavoriteViewData(events);
			const settings=getFavoriteSettings();
			const notesEnabled=getWatchSettings().enabled;
			const dataColumns=settings.dataColumns;
			if (!notesEnabled) {
				favoriteTableState.filters.note='';
				if (favoriteTableState.sortKey === 'note') {
					favoriteTableState.sortKey='lp';
					favoriteTableState.sortDirection='asc';
				}
			}
			if (favoriteTableState.sortKey.startsWith('data:') && !dataColumns.includes(favoriteTableState.sortKey.slice(5))) {
				favoriteTableState.sortKey='lp';
				favoriteTableState.sortDirection='asc';
			}
			for (const key of Object.keys(favoriteTableState.filters.data)) {
				if (!dataColumns.includes(key)) delete favoriteTableState.filters.data[key];
			}
			const items=filterAndSortFavoriteItems(allItems,dataColumns);
			visibleItemIds=items.map(item => item.id);
			const countText=` (${items.length}${items.length !== allItems.length ? '/' + allItems.length:''})`;
			if (visibleCount.textContent !== countText) visibleCount.textContent=countText;
			const countTitle=`Wyświetlane pozycje: ${items.length} z ${allItems.length}. Licznik uwzględnia filtry.`;
			if (visibleCount.title !== countTitle) setControlTitle(visibleCount,countTitle);
			refresh.disabled=pendingChecks.size > 0 || !items.length;
			nextTableNodes=[];
			itemsHost.dataset.vmStyleApi='1';
			const hasExtraDataColumns=dataColumns.length > 0;
			itemsHost.style.minWidth=`var(--vm-fav-table-min-width, ${(hasExtraDataColumns ? 1036:1078) + dataColumns.length * 92}px)`;
			const availableColumns=getEscortDataFieldOptions('none',true)
				.filter(([field]) => field !== 'none' && !dataColumns.includes(field));
			headerAddColumnSelect.replaceChildren();
			appendSelectOptions(headerAddColumnSelect,[['','wybierz…'],...availableColumns],'');
			headerAddColumnButton.disabled=!availableColumns.length || dataColumns.length >= FAVORITES_MAX_DATA_COLUMNS;
			headerAddColumnButton.style.opacity=headerAddColumnButton.disabled ? '.45':'1';
			headerAddColumnButton.style.cursor=headerAddColumnButton.disabled ? 'default':'pointer';
			if (headerAddColumnButton.disabled) headerAddColumnSelect.style.display='none';
			if (restoreHiddenButton) {
				restoreHiddenButton.style.display=favoriteTableState.filters.actions === 'hidden' ? '':'none';
				restoreHiddenButton.disabled=!items.some(item => item.hidden);
			}
			if (!allItems.length) {
				const empty=makeElement('div',"vm-ui-panel vm-ui-empty",collectionMode
						? 'Brak pozycji z notatką, kategorią lub oznaczeniem ukrycia.'
						:'Nie dodano jeszcze żadnego profilu ani anonsu do ulubionych.');
				tableRows.render([empty]);suppressFavoriteTableScrollCapture=false;
				return;
			}
			const dynamicColumns=dataColumns .map(() => 'var(--vm-fav-col-data, minmax(72px,92px))') .join(' ');
			const listGridColumns=[ 'var(--vm-fav-col-image, 48px)', 'var(--vm-fav-col-profile, minmax(100px,0.75fr))', notesEnabled
						? ( collectionMode ? 'var(--vm-fav-col-notes-note, minmax(260px,2fr))':hasExtraDataColumns
								? 'var(--vm-fav-col-note, 108px)'
								:'var(--vm-fav-col-note, 125px)' )
						:'', collectionMode ? 'var(--vm-fav-col-notes-category, 155px)':'var(--vm-fav-col-category, 135px)',
					collectionMode ? '':'var(--vm-fav-col-alert, 84px)', 'var(--vm-fav-col-state, 54px)',
					'var(--vm-fav-col-location, 81px)', dynamicColumns, collectionMode ? '':hasExtraDataColumns
						? 'var(--vm-fav-col-changes, minmax(225px,1.3fr))'
						:'var(--vm-fav-col-changes, minmax(250px,1.5fr))', collectionMode ? 'var(--vm-fav-col-notes-actions, 96px)':'var(--vm-fav-col-actions, 58px)' ].filter(Boolean).join(' ');
			const listHeader=makeElement('div','vm-favorites-table-header');
			listHeader.style.setProperty('--vm-fav-current-grid',listGridColumns);
			const makeSortableHeaderButton=(label,sortKey) => {
				const button=makeButton('vm-favorites-sort-button',label);
				button.dataset.vmFavoriteSort=sortKey;
				const active=favoriteTableState.sortKey === sortKey;
				if (active) button.textContent += favoriteTableState.sortDirection === 'asc' ? ' ↑':' ↓';
				button.title=active
					? `${label} - odwróć sortowanie`
					:`${label} - sortuj według tej kolumny`;
				button.classList.toggle('-active',active);
				button.addEventListener('click',() => {
					if (favoriteTableState.sortKey === sortKey) {
						favoriteTableState.sortDirection=favoriteTableState.sortDirection === 'asc' ? 'desc':'asc';
					} else {
						favoriteTableState.sortKey=sortKey;
						favoriteTableState.sortDirection=sortKey === 'changes' ? 'desc':'asc';
					}
					persistFavoriteTableState();
					render();
				});
				return button;
			};
			const styleModernFilter=(control,active) => {
				control.classList.add('vm-favorites-filter');
				control.classList.toggle('vm-favorites-filter-active',active);
				return control;
			};
			const makeTextFilter=(key,placeholder,dataField=null) => {
				const input=makeElement('input','',null,{type:'search',value:dataField ? (favoriteTableState.filters.data[dataField] || ''):(favoriteTableState.filters[key] || ''),placeholder});
				input.dataset.vmFavoriteFilter=dataField ? `data:${dataField}`:key;
				styleModernFilter(input,!!input.value.trim());
				input.addEventListener('input',() => rerenderFavoriteFilter(key,input.value,input,dataField));
				return input;
			};
			const makeSelectFilter=(key,options) => {
				const select=makeElement('select');
				appendSelectOptions(select,options,favoriteTableState.filters[key]);
				select.dataset.vmFavoriteFilter=key;
				styleModernFilter(select,select.value !== 'all');
				select.addEventListener('change',() => rerenderFavoriteFilter(key,select.value));
				return select;
			};
			const makeHeaderCell=(top,filter=null) => {
				const cell=makeElement('div','vm-favorites-header-cell');
				if (filter) {
					cell.style.setProperty('--vm-fav-header-rows','var(--vm-fav-header-title-height, 24px) var(--vm-fav-filter-height, 20px)');
					cell.style.setProperty('--vm-fav-header-gap','2px');
				}
				if (top) cell.appendChild(top);
				if (filter) cell.appendChild(filter);
				return cell;
			};
			const ordinalSort=makeSortableHeaderButton('#','lp');
			ordinalSort.title=favoriteTableState.sortKey === 'lp'
				? 'L.p. = liczba porządkowa. Sortowanie zgodnie z datą dodania do ulubionych. Kliknij, aby odwrócić kolejność.'
				:'L.p. = liczba porządkowa. Sortuj zgodnie z datą dodania do ulubionych.';
			if (collectionMode) ordinalSort.title='Sortuj zgodnie z datą zapisania pozycji';
			Object.assign(ordinalSort.style,{
				width:'100%', textAlign:'center', whiteSpace:'nowrap', fontSize:'11px'
			});
			listHeader.appendChild(makeHeaderCell(ordinalSort));
			listHeader.appendChild(makeHeaderCell( makeSortableHeaderButton('Profil','profile'),
				makeTextFilter('profile','Szukaj…') ));
			const noteHeader=makeHeaderCell( makeSortableHeaderButton('Notatka','note'), makeTextFilter('note','Szukaj…') );
			if (!notesEnabled) noteHeader.style.display='none';
			listHeader.appendChild(noteHeader);
			const categoryTop=makeElement('div','vm-favorites-header-group');
			const categorySort=makeSortableHeaderButton('Kategoria','category');
			categorySort.style.flex='0 1 auto';
			const editCategories=makeButton("vm-favorites-header-action vm-favorites-edit-categories vm-ui-button vm-ui-icon vm-ui-compact -accent vm-ui-table-icon",'✎',{title:'Edytuj kategorie'});
			editCategories.addEventListener('mouseenter',() => {editCategories.style.background='var(--vm-fav-header-action-hover-bg, #fff0f6)';});
			editCategories.addEventListener('mouseleave',() => {editCategories.style.background='transparent';});
			editCategories.addEventListener('click',event => {
				event.stopPropagation();
				openFavoriteCategoriesEditor(() => {
					render();
				});
			});
			categoryTop.append(categorySort,editCategories);
			listHeader.appendChild(makeHeaderCell(categoryTop,makeSelectFilter('category',[ ['all','Wszystkie'],
				['__none__','Bez kategorii'], ...settings.categories.map(value => [value,value]) ])));
			listHeader.appendChild(makeHeaderCell( makeSortableHeaderButton('Alert','watch'), makeSelectFilter('watch',[
					['all','Wszystkie'], ['online','Online'], ['location','Lokalizacja'], ['changes','Zmiany treści'],
					['newAds','Nowy anons'], ['none','Brak'] ]) ));
			listHeader.appendChild(makeHeaderCell( makeSortableHeaderButton('Stan','state'), makeSelectFilter('state',[
					['all','Wszystkie'], ['online','✓ online'], ['online-open','online i otwarte'], ['offline','× offline'],
					['unknown','? brak danych'], ['open','otwarte'], ['closed','zamknięte'] ]) ));
			listHeader.appendChild(makeHeaderCell( makeSortableHeaderButton('Lokalizacja','location'),
				makeTextFilter('location','Miasto…') ));
			for (const field of dataColumns) {
				const top=makeElement('div','vm-favorites-header-group');
				Object.assign(top.style,{display:'flex',alignItems:'flex-start',gap:'2px',minWidth:'0'});
				const sortButton=makeSortableHeaderButton(getFavoriteColumnLabel(field),`data:${field}`);
				sortButton.style.flex='1 1 auto';
				const removeColumn=makeButton("vm-favorites-header-action vm-favorites-remove-column vm-ui-button vm-ui-icon vm-ui-compact vm-ui-table-icon",'×',{title:'Usuń tę kolumnę'});
				removeColumn.addEventListener('mouseenter',() => {
					removeColumn.style.background='var(--vm-ui-bg-hover, #f1f2f4)';
					removeColumn.style.color='var(--vm-ui-text-muted, #666)';
				});
				removeColumn.addEventListener('mouseleave',() => {
					removeColumn.style.background='transparent';
					removeColumn.style.color='var(--vm-ui-text-faint, #aaa)';
				});
				removeColumn.addEventListener('click',event => {
					event.stopPropagation();
					saveDataColumns(dataColumns.filter(value => value !== field));
					delete favoriteTableState.filters.data[field];
					persistFavoriteTableState();
					render();
				});
				top.append(sortButton,removeColumn);
				listHeader.appendChild(makeHeaderCell(top,makeTextFilter('data','Filtr…',field)));
			}
			const changesTop=makeElement('div','vm-favorites-header-group vm-favorites-changes-header');
			Object.assign(changesTop.style,{
				display:'flex', alignItems:'center', gap:'3px', minWidth:'0', overflow:'hidden'
			});
			const changesSort=makeSortableHeaderButton( 'Zmiany', 'changes' );
			changesSort.style.flex='0 1 auto';
			const markAllNonAlertRead=makeButton("vm-favorites-header-action vm-favorites-mark-non-alert-read vm-ui-button vm-ui-compact -accent",'Odczytaj zmiany',{title:'Oznacz wszystkie nie-alertowe zmiany jako nie-nowe',disabled:!events.some(
					event => !event.acknowledged && !event.alertTriggered )});
			Object.assign(markAllNonAlertRead.style,{
				minHeight:'18px', padding:'0 4px', fontSize:'7.5px', lineHeight:'1', whiteSpace:'nowrap'
			});
			markAllNonAlertRead.addEventListener( 'click', event => {
					event.stopPropagation();
					if (markAllNonAlertRead.disabled) {
						return;
					}
					nonAlertEventIdsNewAtOpen.clear();
					acknowledgeNonAlertWatchEvents();
					renderWatchNotificationBar();
					render();
				} );
			const acceptAllAlertsAndChanges=makeButton("vm-favorites-header-action vm-favorites-accept-all vm-ui-button vm-ui-compact -danger",'Zaakceptuj wszystkie alerty',{title:'Zaakceptuj wszystkie alerty',disabled:!events.some(event =>
					!event.acknowledged && event.alertTriggered )});
			Object.assign(acceptAllAlertsAndChanges.style,{
				minHeight:'18px', padding:'0 4px', fontSize:'7.5px', lineHeight:'1', whiteSpace:'nowrap'
			});
			acceptAllAlertsAndChanges.addEventListener( 'click', event => {
					event.stopPropagation();
					if (!acceptAllAlertsAndChanges.disabled) {
						acknowledgeAllAlertWatchEvents();
					}
				} );
			changesTop.append( changesSort, markAllNonAlertRead, acceptAllAlertsAndChanges );
			listHeader.appendChild(makeHeaderCell(changesTop,makeSelectFilter('changes',[ ['all','Wszystkie'],
				['new','Tylko nowe'], ['any','Z historią'], ['alert','Z alertem'], ['none','Bez zmian'] ])));
			const actionsTitle=makeElement('span','vm-favorites-actions-title','Akcje');
			Object.assign(actionsTitle.style,{alignSelf:'center',color:'var(--vm-fav-header-text, #666)',fontSize:'var(--vm-fav-header-font-size, 9px)',fontWeight:'700'});
			const actionsFilter=collectionMode ? makeSelectFilter('actions',actionFilters):null;
			if (actionsFilter) actionsFilter.setAttribute('aria-label','Filtr Ulubionych i ukrytych');
			listHeader.appendChild(makeHeaderCell(actionsTitle,actionsFilter));
			appendFavoriteTableRow(listHeader,dataColumns);
			if (!items.length) {
				const emptyFiltered=makeElement('div',"vm-ui-panel vm-ui-empty",'Brak pozycji spełniających wybrane filtry.');
				nextTableNodes.push(emptyFiltered);tableRows.render(nextTableNodes);suppressFavoriteTableScrollCapture=false;
				return;
			}
			let availableStripeIndex=0;
			items.forEach((item,itemIndex) => {
				const availabilityNow=viewData(item).availability;
				const rowUnavailable=item.state?.online === false || availabilityNow === false;
				const stripeIndex=rowUnavailable ? 0:availableStripeIndex++;
				const itemEvents=viewData(item).events;
				const signature=JSON.stringify([item,itemEvents,itemIndex,stripeIndex,items.length,listGridColumns,settings,notesEnabled,pendingChecks.size>0,availabilityNow,viewData(item).allDetails,dataColumns.map(field=>viewData(item).column(field)),Math.floor(Date.now()/60000)]);
				const previous=tableRows.get(item.id,signature);
				if(previous){nextTableNodes.push(previous);favoriteRowsToMeasure.push([previous,previous.querySelector('.vm-favorites-category-cell')]);return;}
				const hasUnack=itemEvents.some(event => !event.acknowledged);
				const pendingAlertModes=new Set( itemEvents .filter(event => !event.acknowledged && event.alertTriggered )
						.map(event => event.alertMode || watchEventTypeToAlertMode(event.type) ) .filter(Boolean) );
				const card=makeElement('div','vm-favorites-row');
				card.dataset.vmFavoriteItemId=item.id;
				card.dataset.vmFavoriteHasNewChanges=hasUnack ? '1':'0';
				const cardBaseBackground=stripeIndex % 2
					? 'var(--vm-fav-row-alt-bg, #fbfbfc)'
					:'var(--vm-fav-row-bg, #fff)';
				card.style.setProperty('--vm-fav-current-grid',listGridColumns);
				card.style.setProperty( '--vm-fav-row-current-bg', pendingAlertModes.size
						? `color-mix(in srgb, var(--vm-ui-danger, #c62828) 6%, ${cardBaseBackground})`
						:cardBaseBackground );
				card.style.setProperty( '--vm-fav-row-current-shadow',
					pendingAlertModes.size ? 'inset 3px 0 0 var(--vm-ui-danger, #c62828)':'none' );
				card.classList.toggle('-last',itemIndex === items.length - 1);
				const imageBox=makeElement('div','vm-favorites-image-cell','-');
				const imageUrls=[...new Set([item.imageUrl,...viewData(item).allDetails.map(entry => entry.adData.imageUrl)]
					.map(normalizeWatchThumbnailUrl).filter(url => url && !isWatchThumbnailKnownUnavailable(url)))];
				if (imageUrls.length) {
					const img=makeElement('img','',null,{src:imageUrls.shift(),alt:'',loading:'lazy',referrerPolicy:'no-referrer'});
					img.addEventListener('error',() => {
						markWatchThumbnailUnavailable(img.src);
						hideWatchImagePreview();
						if (imageUrls.length) {img.src=imageUrls.shift(); return;}
						detachHelperNode(img);
						imageBox.title='Brak dostępnego zapisanego zdjęcia.';
					});
					imageBox.appendChild(img);
					imageBox.title='Najedź, aby powiększyć zdjęcie';
					imageBox.addEventListener('mouseenter',() => {
						if (img.parentElement === imageBox) showWatchImagePreview(imageBox,img.src,overlay);
					});
					imageBox.addEventListener('mouseleave',hideWatchImagePreview);
				}
				const details=makeElement('div','vm-favorites-profile-cell');
				const nameRow=makeElement('div','vm-favorites-profile-name-row');
				const name=makeElement('a','vm-favorites-profile-name',item.displayName,{href:(item.sourceType === 'escort-search' ? watchItemSourceUrl(item):item.state?.activeUrls?.[0] || item.profileUrls?.[0] || item.adUrl) || '#',target:'_blank',rel:'noopener noreferrer'});
				const rename=makeButton("vm-favorites-icon-button vm-favorites-rename vm-ui-button vm-ui-icon vm-ui-compact vm-ui-table-icon",'✎',{title:'Zmień nazwę'});
				Object.assign(rename.style,{opacity:'0',transition:'opacity .12s ease'});
				rename.addEventListener('click',async()=>{await changeTrackedEntry(item.id,'rename');render();});
				nameRow.append(name,rename);
				const sourceUrl=watchItemSourceUrl(item);
				const source=sourceUrl
					? makeElement('a','vm-favorites-profile-source',watchItemSourceText(item))
					:makeElement('div','vm-favorites-profile-source',watchItemSourceText(item));
				if (sourceUrl) {
					source.href=sourceUrl;
					source.target='_blank';
					source.rel='noopener noreferrer';
					source.title='Otwórz źródło danych profilu';
				}
				Object.assign(source.style,{
					display:'block', maxWidth:'100%', padding:'2px 0',
					color:sourceUrl ? 'var(--vm-fav-source-text, var(--vm-ui-text-muted, #6f6f74))':'var(--vm-fav-source-empty-text, var(--vm-ui-text-faint, #9a9a9a))',
					fontSize:'9px', fontWeight:sourceUrl ? '600':'400', lineHeight:'1.15', overflow:'hidden', textOverflow:'ellipsis',
					whiteSpace:'nowrap', textDecoration:sourceUrl ? 'underline':'none', textUnderlineOffset:'2px'
				});
				details.append(nameRow,source);
				const noteCell=makeElement('div','vm-favorites-note-cell');
				if (!notesEnabled) noteCell.style.display='none';
				const note=makeButton('vm-favorites-note',item.note || 'dodaj notatkę');
				note.dataset.vmFavoriteHasNote=item.note ? '1':'0';
				note.title=item.note ? `${item.note}

Kliknij, aby zmienić notatkę`:'Dodaj notatkę';
				Object.assign(note.style,{
					display:'-webkit-box', width:'100%', maxWidth:'100%', height:'100%', minHeight:'0', maxHeight:'100%',
					boxSizing:'border-box', margin:'0', padding:item.note ? '2px 4px':'0', border:'0',
					borderRadius:item.note ? '4px':'0',
					background:item.note ? 'var(--vm-fav-note-bg, rgba(244,67,143,.026))':'transparent',
					color:item.note ? 'var(--vm-fav-note-text, #666)':'var(--vm-fav-note-empty-text, #aaa)', cursor:'pointer',
					fontSize:'9px', lineHeight:'1.14', fontStyle:item.note ? 'normal':'italic', textDecoration:'none',
					textAlign:'left', whiteSpace:'normal', overflow:'hidden', overflowWrap:'anywhere', WebkitBoxOrient:'vertical',
					WebkitLineClamp:'4'
				});
				note.addEventListener('click',async()=>{await changeTrackedEntry(item.id,'note');render();});
				noteCell.appendChild(note);
				card.addEventListener('mouseenter',() => {rename.style.opacity='1';});
				card.addEventListener('mouseleave',() => {rename.style.opacity='0';});
				const categoryCell=makeElement('div','vm-favorites-category-cell');
				const chips=makeElement('div','vm-favorites-category-chips');
				const assignedCategories=fillFavoriteCategoryControls(chips,item,settings,async change => {
					await changeTrackedEntry(item.id,'categories',change);render();
				},{pointerDown:true});
				categoryCell.dataset.vmFavoriteCategory=assignedCategories.join('|');
				categoryCell.appendChild(chips);
				categoryCell.title=assignedCategories.length
					? assignedCategories.join(', ')
					:'Dodaj kategorię';
				const modes=makeElement('div','vm-favorites-alert-cell');
				Object.assign(modes.style,{
					display:'flex', flexDirection:'column', alignItems:'flex-start', gap:'2px', justifyContent:'center',
					minHeight:'58px', padding:'2px 0', boxSizing:'border-box', fontSize:'8px', lineHeight:'11px'
				});
				const makeModeCheck=( mode, label, {
						disabled=false, disabledTitle='', tooltip=''
					}={} ) => {
					const row=makeElement( 'label', 'vm-favorites-alert-option' );
					row.dataset.vmFavoriteAlert=mode;
					row.title=tooltip || label;
					Object.assign(row.style,{
						display:'flex', alignItems:'center', gap:'1px', minWidth:'0', height:'11px', margin:'0', padding:'0',
						whiteSpace:'nowrap', overflow:'visible'
					});
					const input=makeElement('input','vm-favorites-checkbox',null,{type:'checkbox',checked:!!item.modes[mode],disabled});
					if (disabled) {
						row.style.opacity='.35';
						row.style.cursor='default';
						row.title= disabledTitle || 'Ten alert nie jest dostępny dla tej pozycji.';
					}
					row.classList.toggle( 'vm-favorites-alert-option-active', input.checked );
					Object.assign(input.style,{
						flex:'0 0 11px', width:'11px', height:'11px', margin:'0', padding:'0',
						accentColor:'var(--vm-fav-checkbox-accent, var(--vm-fav-accent, var(--vm-ui-accent, #f54998)))'
					});
					const labelText=makeElement('span','',label);
					const hasUnreadAlert= pendingAlertModes.has(mode);
					Object.assign(labelText.style,{
						minWidth:'0', overflow:'visible', textOverflow:'clip', whiteSpace:'nowrap', lineHeight:'11px',
						color:hasUnreadAlert
							? 'var(--vm-fav-accent, var(--vm-ui-accent, #f54998))'
							:'var(--vm-ui-text-muted, #666)', fontWeight: input.checked || hasUnreadAlert
								? '800'
								:'400'
					});
					if (hasUnreadAlert) {
						row.classList.add( 'vm-favorites-alert-option-unread' );
						Object.assign(row.style,{
							height:'13px', padding:'0 3px', border:'1px solid var(--vm-ui-danger, #c62828)', borderRadius:'4px', background:
								'color-mix(in srgb, var(--vm-ui-danger, #c62828) 10%, transparent)', boxSizing:'border-box'
						});
						labelText.style.color= 'var(--vm-ui-danger, #c62828)';
						labelText.style.fontWeight='900';
						input.style.outline= '1px solid var(--vm-ui-danger, #c62828)';
						input.style.outlineOffset='0';
						row.title=`${row.title} • Niezaakceptowany alert`;
					}
					row.append(input,labelText);
					input.addEventListener('change',async () => {
						if (disabled) {
							input.checked=false;
							return;
						}
						if (mode === 'location') {
							input.checked=!!item.modes.location;
							row.classList.toggle( 'vm-favorites-alert-option-active', input.checked );
							labelText.style.fontWeight= input.checked ? '700':'400';
							const scopeLabel= item.sourceType === 'escort-ad'
									? 'Anons'
									:'Profil';
							const options=await chooseWatchAlertOptions( item, scopeLabel, {locationOnly:true} );
							if (!options) {
								render();
								return;
							}
							updateTrackedAlertModes(item.id,{location:options.modes.location},options.locationTarget);
						} else {
							updateTrackedAlertModes(item.id,{[mode]:input.checked});
						}
						render();
					});
					return row;
				};
				modes.append( makeModeCheck( 'online', 'online', {
							tooltip:
								'Śledzi stan online/offline. Powiadomienie pojawia się, gdy profil/anons wróci online po wcześniejszym stanie offline.'
						} ), makeModeCheck( 'location', item.locationTarget?.type === 'city'
							? `lok.→${item.locationTarget.city}`
							:'lokalizacja', {
							tooltip: item.locationTarget?.type === 'city'
									? `Pokazuje na górnym pasku alert, gdy profil/anons pojawi się w mieście ${item.locationTarget.city}. Historia lokalizacji jest zapisywana niezależnie od tego checkboxa. Kliknięcie checkboxa otwiera ustawienia lokalizacji.`
									:'Pokazuje na górnym pasku alert o każdej zmianie lokalizacji, w tym o zmianie adresu anonsu. Historia lokalizacji jest zapisywana niezależnie od tego checkboxa. Kliknięcie checkboxa otwiera ustawienia lokalizacji.'
						} ), makeModeCheck( 'changes', 'zmiany treści', {
							tooltip:
								'Pokazuje na górnym pasku alert o zmianie nazwy anonsu, głównego zdjęcia, cennika, godzin dostępności, tagów/usług, opisu, numeru telefonu lub danych profilu. Historia zmian jest zapisywana niezależnie od tego checkboxa. Zmiana adresu jest traktowana jako lokalizacja, nie zmiana treści.'
						} ), makeModeCheck( 'newAds', 'nowy anons', {
							disabled:item.sourceType === 'escort-ad', disabledTitle:
								'Alert „nowy anons” jest dostępny tylko dla profilu. Powiadamia, gdy w profilu pojawi się anons o ID, którego wcześniej w nim nie było.',
							tooltip:
								'Powiadamia, gdy w obserwowanym profilu pojawi się anons o ID, którego wcześniej w tym profilu nie było.'
						} ) );
				const stateCell=makeElement('div','vm-favorites-state-cell');
				const stateKind=item.state?.online === true ? 'online':(item.state?.online === false ? 'offline':'unknown');
				stateCell.dataset.vmFavoriteState=stateKind;
				Object.assign(stateCell.style,{textAlign:'center',lineHeight:'1.05',minWidth:'0'});
				const stateIcon=makeElement('div',`vm-favorites-state-icon vm-favorites-state-${stateKind}`,item.state?.online === true ? '✓':(item.state?.online === false ? '×':'?'));
				Object.assign(stateIcon.style,{
					color:item.state?.online === true ? 'var(--vm-ui-success, #16823a)':(item.state?.online === false ? 'var(--vm-ui-danger, #c62828)':'var(--vm-ui-text-muted, #777)'),
					fontSize:'13px', fontWeight:'900'
				});
				const stateTop=makeElement('div','vm-favorites-state-top');
				Object.assign(stateTop.style,{display:'flex',alignItems:'center',justifyContent:'center',gap:'4px',minHeight:'16px'});
				stateTop.appendChild(stateIcon);
				const totalAds=item.sourceType==='escort-search'?getWatchedItemHistoricalAdUrls(item,itemEvents).length:item.state?.totalAds || 0;
				const activeLine=makeElement('div','vm-favorites-state-detail',`aktywne ${item.state?.activeCount || 0}/${totalAds}`);
				if(item.sourceType==='escort-search')activeLine.title=`Łącznie ${totalAds} unikalnych anonsów znalezionych dotąd w tym wyszukiwaniu; aktywne według ostatniego sprawdzenia.`;
				Object.assign(activeLine.style,{marginTop:'1px',color:'var(--vm-ui-text-muted, #777)',fontSize:'8px',whiteSpace:'nowrap'});
				card.classList.toggle('-unavailable',rowUnavailable);
				const availabilityLine=makeElement('div','vm-favorites-state-availability');
				Object.assign(availabilityLine.style,{marginTop:'1px',color:'var(--vm-ui-text-muted, #777)',fontSize:'8px',lineHeight:'1',whiteSpace:'nowrap',minHeight:'8px'});
				if (availabilityNow != null) {
					const availabilityDot=makeElement('span','vm-favorites-availability-now','●');
					Object.assign(availabilityDot.style,{color:availabilityNow ? 'var(--vm-ui-success, #1aa34a)':'var(--vm-ui-danger, #d32f2f)',fontSize:'8px',fontWeight:'900',lineHeight:'1'});
					availabilityLine.append(availabilityDot,document.createTextNode(` ${availabilityNow ? 'otwarte':'zamknięte'}`));
					availabilityLine.title=viewData(item).availabilityTooltip || (availabilityNow ? 'Aktualnie czynne':'Aktualnie zamknięte');
				}
				stateCell.title=item.state?.online === true ? 'online':(item.state?.online === false ? 'offline':'brak danych');
				stateCell.append(stateTop,activeLine,availabilityLine);
				const locationValue=watchCitiesText(viewData(item).cities);
				const locationCell=makeElement('div','vm-favorites-location-cell');
				const locationTitle=locationValue!=='b/d'&&!item.state?.cities?.length?`Ostatnia znana lokalizacja: ${locationValue}`:locationValue;
				const locationText=makeElement('span',"vm-favorites-cell-text",locationValue,{title:locationTitle});
				locationCell.appendChild(locationText);
				const dataCells=dataColumns.map(field => {
					const value=viewData(item).column(field);
					const cell=makeElement('div','vm-favorites-data-cell');
					cell.dataset.vmFavoriteDataField=field;
					cell.title=value === 'b/d' ? '':value;
					cell.classList.add('vm-favorites-data-cell');
					const valueText=makeElement('span',"vm-favorites-cell-text",value);
					cell.appendChild(valueText);
					return cell;
				});
				const changesCell=makeElement( 'div', 'vm-favorites-changes-cell' );
				Object.assign(changesCell.style,{
					position:'relative', display:'grid', gridAutoRows:'minmax(0, 1fr)', gap:'1px', minWidth:'0', minHeight:'0',
					maxHeight:'100%', overflow:'hidden', color:'var(--vm-ui-text, #444)', fontSize:'8.5px', lineHeight:'1.1'
				});
				const visibleItemEvents= itemEvents.slice(0,4);
				if (!itemEvents.length) {
					const dash=makeElement('span','','-');
					dash.style.color='var(--vm-ui-text-faint, #aaa)';
					changesCell.appendChild(dash);
				}
				for (const event of visibleItemEvents) {
					const line=makeElement('div',event.acknowledged ? 'vm-favorites-change vm-favorites-change-acknowledged':'vm-favorites-change vm-favorites-change-new');
					line.dataset.vmFavoriteChangeState=event.acknowledged ? 'acknowledged':'new';
					Object.assign(line.style,{
						display:'flex', alignItems:'center', gap:'3px', minWidth:'0', minHeight:'0', overflow:'hidden', opacity:
							event.acknowledged
								? '.55'
								:'1'
					});
					if ( !event.acknowledged && event.alertTriggered ) {
						Object.assign(line.style,{
							padding:'0 3px', border: '1px solid var(--vm-ui-danger, #c62828)', borderRadius:'4px', background:
								'color-mix(in srgb, var(--vm-ui-danger, #c62828) 10%, transparent)', boxSizing:'border-box'
						});
					}
					const content=makeElement('div');
					Object.assign(content.style,{
						display:'flex', alignItems:'baseline', gap:'3px', minWidth:'0', flex:'1 1 auto', minHeight:'0',
						overflow:'hidden', whiteSpace:'nowrap'
					});
					const timestamp=makeElement( 'span', 'vm-favorites-change-time', formatWatchDateTime(event.timestamp) );
					Object.assign(timestamp.style,{
						flex:'0 0 auto', color:'var(--vm-ui-text-faint, #999)', fontSize:'7.5px', whiteSpace:'nowrap'
					});
					const separator=makeElement('span','','-');
					separator.style.color='var(--vm-ui-text-faint, #aaa)';
					const compact=String(event.message || '').replace( new RegExp( `^${String(item.displayName).replace(
								/[.*+?^${}()|[\]\\]/g, '\\$&' )}\\s*`, 'i' ), '' );
					const link=makeWatchEventLink(event,compact);
					link.className='vm-favorites-change-link';
					Object.assign(link.style,{
						minWidth:'0', flex:'1 1 auto', color:event.acknowledged
							? 'var(--vm-fav-change-ack-text, var(--vm-ui-text-muted, #707075))'
							:( event.alertTriggered
									? 'var(--vm-ui-danger, #c62828)'
									:'var(--vm-ui-accent, #f54da3)' ), textDecoration:event.description
							? 'underline dotted'
							:'none', textUnderlineOffset:'2px', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'
					});
					content.append(timestamp,separator,link);
					if ( !event.acknowledged && event.alertTriggered ) {
						const ok=makeButton("vm-ui-button vm-ui-icon vm-ui-compact -accent vm-ui-table-icon",'✓');
						Object.assign(ok.style,{opacity:'.18',transition:'opacity .1s ease'});
						line.addEventListener( 'mouseenter', () => {
								ok.style.opacity='1';
							} );
						line.addEventListener( 'mouseleave', () => {
								ok.style.opacity='.18';
							} );
						ok.title='Zaakceptuj alert';
						ok.addEventListener( 'click', () => acknowledgeWatchEvent( event.id ) );
						line.append( ok, content );
					} else {
						line.appendChild(content);
					}
					changesCell.appendChild(line);
				}
				const openTimeline=makeButton('vm-favorites-change-history-button',null,{title:`Pokaż wszystkie zapisane zmiany (${itemEvents.length})`});
				openTimeline.setAttribute( 'aria-label', openTimeline.title );
				openTimeline.innerHTML= '<svg viewBox="0 0 18 18" width="13" height="13" aria-hidden="true" focusable="false">' +
					'<path d="M3.5 3.5h5v1.5h-3.5v8h8v-3.5h1.5v5h-11z" fill="currentColor"/>' +
					'<path d="M9.5 2.5h6v6h-1.5v-3.45l-5.8 5.8-1.05-1.05 5.8-5.8h-3.45z" fill="currentColor"/>' + '</svg>';
				openTimeline.classList.add('vm-ui-button','vm-ui-icon','vm-ui-compact','-accent','vm-ui-table-icon');
				Object.assign(openTimeline.style,{position:'absolute',right:'1px',bottom:'1px',zIndex:'3'});
				openTimeline.addEventListener( 'click', event => {
						stopUiEvent(event);
						openWatchChangesTimeline( item, itemEvents );
					} );
				changesCell.appendChild( openTimeline );
				changesCell.style.paddingRight= '18px';
				const actions=makeItemActions(item);
				card.append(imageBox,details,noteCell,categoryCell,modes,stateCell,locationCell,...dataCells,changesCell,actions);
				tableRows.set(item.id,signature,card);
				appendFavoriteTableRow(card,dataColumns,categoryCell);
			});
			tableRows.render(nextTableNodes);
			favoriteRowsToMeasure.forEach(([row]) => {row.style.height='48px';});
			favoriteRowsToMeasure.map(([row,cell]) => [row,Math.max(48,Math.ceil(Math.max(cell.scrollHeight || 0,cell.offsetHeight || 0)) + 4)])
				.forEach(([row,height]) => {row.style.height=`${height}px`;});
			const restoreFavoriteTableScroll=() => {
				if ( renderGeneration !== favoriteTableScrollRenderGeneration ) {
					return;
				}
				if (!body.isConnected) {
					suppressFavoriteTableScrollCapture=false;
					return;
				}
				body.scrollTop= favoriteTableSavedScrollTop;
				body.scrollLeft= favoriteTableSavedScrollLeft;
			};
			queueMicrotask(restoreFavoriteTableScroll);
			requestAnimationFrame(() => {
				restoreFavoriteTableScroll();
				requestAnimationFrame(() => {
					restoreFavoriteTableScroll();
					if ( renderGeneration === favoriteTableScrollRenderGeneration ) {
						suppressFavoriteTableScrollCapture= false;
					}
				});
			});
		};
		const scheduleTableRender=createAnimationFrameScheduler(()=>{if(overlay.isConnected)render();});
		const tableStorageListeners=[];
		watchPageButtonRefreshers.add(scheduleTableRender);
		try{for(const key of [WATCH_EVENTS_STORAGE_KEY,WATCH_ITEMS_STORAGE_KEY,WATCH_AD_DATA_MIRROR_STORAGE_KEY,SETTINGS_STORAGE_KEY])tableStorageListeners.push(GM_addValueChangeListener(key,scheduleTableRender));}catch {}
		overlay._vmRender=scheduleTableRender;
		refresh.addEventListener('click',() => runListCheck(visibleItemIds.slice()));
		const addManual=async () => {
			addButton.disabled=true;
			status.style.display='block';
			status.style.color='var(--vm-ui-accent, #f54da3)';
			status.textContent='Dodawanie do ulubionych…';
			try {
				const added=await addWatchFromManualInput(manualInput.value);
				if (added?.item) {
					updateWatchedItem(added.item.id,{favorite:true});
					manualInput.value='';
					status.textContent='Dodano do ulubionych.';
				}
			} catch (error) {
				status.textContent=error?.message || 'Nie udało się dodać';
				status.style.color='var(--vm-ui-danger, #c62828)';
			} finally {
				addButton.disabled=false;
				render();
			}
		};
		addButton.addEventListener('click',addManual);
		manualInput.addEventListener('keydown',event => {if (event.key === 'Enter') addManual();});
		render();
	}
	function removeWatchPageUi() {
		document.querySelectorAll('[data-vm-watch-wrapper="1"]').forEach(element => detachHelperNode(element));
		for (const id of ['vm-escorti-watch-online','vm-escorti-watch-location','vm-escort-watch-online','vm-escort-watch-location','vm-escort-favorite-star','vm-escort-favorite-note-row','vm-escorti-profile-note-display']) detachHelperNode(document.getElementById(id));
		watchPageButtonRefreshers.clear();
		renderWatchNotificationBar();
	}
	function initLocalWatchSystem() {
		const host=location.hostname.toLowerCase();
		if (!IS_ESCORT_CLUB && ![ 'escorti.pl', 'www.escorti.pl', 'garsoniera.com.pl', 'www.garsoniera.com.pl'
		].includes(host)) return;
		const pageUrl=new URL(location.href);
		if (pageUrl.searchParams.has('vm_mode')) return;
		if (!watchStorageListenersInitialized) {
			watchStorageListenersInitialized=true;
			window.addEventListener('pagehide',() => {
				watchCheckPageClosed=true;
				for (const request of watchManualRequests) request.cancelled=true;
				watchManualRequests.clear(); publishWatchManualRequests(); releaseWatchCheckLock();
			});
			window.addEventListener('pageshow',() => {watchCheckPageClosed=false;});
			const items=getWatchedItems();
			if (items.some(item => item.sourceType !== 'escort-ad' && canRemoveEmptyTrackedItem(item))) saveWatchedItems(items.map(item => ({...item,keepProfile:false})));
			try {
				pruneWatchSystemNotificationClaims().catch(error=>log('Nie udało się posprzątać blokad powiadomień',error));
				GM_addValueChangeListener(WATCH_EVENTS_STORAGE_KEY,refreshWatchEventViews);
				GM_addValueChangeListener(WATCH_ITEMS_STORAGE_KEY,() => {
					refreshWatchPageButtons();
					initLocalWatchSystem();
					document.getElementById(WATCH_SETTINGS_OVERLAY_ID)?._vmRender?.();
				});
				GM_addValueChangeListener(SETTINGS_STORAGE_KEY,() => {
					setTimeout(() => {
						SETTINGS=getSettings();
						applyFavoriteSearchPageSettings();
						applyWatchFeatureState();
					},0);
				});
			} catch {}
		}
		const watchSettings=getWatchSettings();
		if (!watchSettings.enabled) removeWatchPageUi();
		if (!watchSettings.enabled) {
			if (watchedPollTimer) {
				clearInterval(watchedPollTimer);
				watchedPollTimer=null;
			}
			watchedPollIntervalMs=0;
			renderWatchNotificationBar();
			return;
		}
		renderWatchNotificationBar();
		if (!watchedInitialCheckScheduled) {
			watchedInitialCheckScheduled=true;
			setTimeout(() => runWatchedChecks({
				scheduled:true
			}).catch(error => log('Błąd ulubionych',error) ),3500);
		}
		const pollIntervalMs=WATCH_SCHEDULE_POLL_INTERVAL_MS;
		if (watchedPollTimer && watchedPollIntervalMs !== pollIntervalMs) {
			clearInterval(watchedPollTimer);
			watchedPollTimer=null;
		}
		if (!watchedPollTimer) {
			watchedPollIntervalMs=pollIntervalMs;
			watchedPollTimer=setInterval(() => {
				runWatchedChecks({
					scheduled:true
				}).catch(error => log('Błąd ulubionych',error));
			},pollIntervalMs);
		}
	}
	function applyWatchFeatureState() {
		if (window.top !== window.self) return;
		const host=location.hostname.toLowerCase();
		if (!IS_ESCORT_CLUB && ![ 'escorti.pl', 'www.escorti.pl', 'garsoniera.com.pl', 'www.garsoniera.com.pl'
		].includes(host)) return;
		initLocalWatchSystem();
		const watchSettings=getWatchSettings();
		if (isEscortClubHost(host) && !!getEscortAdIdFromPath(location.pathname)) {
			hideNativeEscortFavoriteAndNoteActions(watchSettings.enabled);
		}
		refreshHiddenItemsUi();
		if (!watchSettings.enabled) return;
		if (host === 'escorti.pl' || host === 'www.escorti.pl') {
			initEscortiProfileWatchButtons();
		} else if (!!getEscortAdIdFromPath(location.pathname)) {
			initEscortClubWatchButtons();
		}
	}
	function getEscortSavedSearchFilters() {
		try {
			const saved=GM_getValue(ESCORT_SAVED_FILTERS_STORAGE_KEY,[]);
			if (!Array.isArray(saved)) return [];
			return saved .filter(item => item && typeof item === 'object' && item.id && item.name && item.state) .map(item => ({
					id:String(item.id), name:String(item.name).trim().slice(0,80), savedAt:Number(item.savedAt) || 0, state:item.state
				})) .filter(item => item.name);
		} catch (error) {
			log('Błąd odczytu zapisanych filtrów Escort.club',error);
			return [];
		}
	}
	function setEscortSavedSearchFilters(filters) {
		try {
			writeHelperStorageValue(ESCORT_SAVED_FILTERS_STORAGE_KEY,Array.isArray(filters) ? filters:[]);
			return true;
		} catch (error) {
			log('Błąd zapisu filtrów Escort.club',error);
			return false;
		}
	}
	function captureEscortAdvancedFilterState() {
		const advanced=document.getElementById('advansed-search');
		if (!advanced) return null;
		const state={
			named:{}, ranges:{}, breasts:null
		};
		const groups=new Map();
		for (const control of advanced.querySelectorAll('input[name], select[name], textarea[name]')) {
			const name=String(control.name || '').trim();
			if (!name) continue;
			if (!groups.has(name)) groups.set(name,[]);
			groups.get(name).push(control);
		}
		for (const [name,controls] of groups) {
			const first=controls[0];
			const types=controls.map(control => String(control.type || '').toLowerCase());
			if (types.every(type => type === 'radio')) {
				const checked=controls.find(control => control.checked);
				state.named[name]={
					kind:'radio', value:checked ? String(checked.value ?? ''):null
				};
				continue;
			}
			if (types.every(type => type === 'checkbox')) {
				state.named[name]={
					kind:'checkbox', values:controls .filter(control => control.checked) .map(control => String(control.value ?? ''))
				};
				continue;
			}
			if (first instanceof HTMLSelectElement && first.multiple) {
				state.named[name]={
					kind:'multiple', values:[...first.selectedOptions].map(option => String(option.value))
				};
				continue;
			}
			state.named[name]={
				kind:'values', values:controls.map(control => String(control.value ?? ''))
			};
		}
		for (const key of ['price','age','weight','height']) {
			const from=advanced.querySelector(`input[data-${key}-from]`);
			const to=advanced.querySelector(`input[data-${key}-to]`);
			if (!from && !to) continue;
			state.ranges[key]={
				from:from ? String(from.value ?? ''):null, to:to ? String(to.value ?? ''):null
			};
		}
		const breastsInput=advanced.querySelector('#range_breasts_filter');
		if (breastsInput) {
			let from=breastsInput.getAttribute('data-from');
			let to=breastsInput.getAttribute('data-to');
			try {
				const jq=window.jQuery;
				const slider=jq ? jq(breastsInput).data('ionRangeSlider'):null;
				if (slider?.result) {
					from=slider.result.from;
					to=slider.result.to;
				}
			} catch {}
			state.breasts={
				value:String(breastsInput.value ?? ''), from:from == null ? null:String(from), to:to == null ? null:String(to)
			};
		}
		return state;
	}
	function dispatchEscortFilterControlEvents(control) {
		try {
			control.dispatchEvent(new Event('input',{bubbles:true}));
			control.dispatchEvent(new Event('change',{bubbles:true}));
		} catch {}
	}
	function refreshEscortFilterSelect(control) {
		if (!(control instanceof HTMLSelectElement)) return;
		try {
			const jq=window.jQuery;
			if (!jq) return;
			if (control.multiple && jq.fn?.select2 && jq(control).hasClass('select2-hidden-accessible')) {
				jq(control).trigger('change');
			}
			if (jq.fn?.selectpicker && jq(control).parent().hasClass('bootstrap-select')) {
				jq(control).selectpicker('refresh');
			}
		} catch (error) {
			log('Nie udało się odświeżyć kontrolki filtra Escort.club',error);
		}
	}
	function applyEscortAdvancedFilterState(state) {
		const advanced=document.getElementById('advansed-search');
		if (!advanced || !state || typeof state !== 'object') return false;
		const named=state.named && typeof state.named === 'object' ? state.named:{};
		for (const [name,stored] of Object.entries(named)) {
			const controls=[...advanced.querySelectorAll('[name]')] .filter(control => String(control.name || '') === name);
			if (!controls.length || !stored || typeof stored !== 'object') continue;
			if (stored.kind === 'radio') {
				for (const control of controls) {
					control.checked=stored.value != null && String(control.value) === String(stored.value);
					dispatchEscortFilterControlEvents(control);
				}
				continue;
			}
			if (stored.kind === 'checkbox') {
				const selected=new Set((stored.values || []).map(value => String(value)));
				for (const control of controls) {
					control.checked=selected.has(String(control.value ?? ''));
					dispatchEscortFilterControlEvents(control);
				}
				continue;
			}
			if (stored.kind === 'multiple') {
				const values=(stored.values || []).map(value => String(value));
				const selected=new Set(values);
				for (const control of controls) {
					if (!(control instanceof HTMLSelectElement)) continue;
					for (const option of control.options) {
						option.selected=selected.has(String(option.value));
					}
					dispatchEscortFilterControlEvents(control);
					refreshEscortFilterSelect(control);
				}
				continue;
			}
			const values=Array.isArray(stored.values) ? stored.values:[];
			controls.forEach((control,index) => {
				const value=values[index] ?? values[0] ?? '';
				control.value=String(value);
				dispatchEscortFilterControlEvents(control);
				refreshEscortFilterSelect(control);
			});
		}
		const ranges=state.ranges && typeof state.ranges === 'object' ? state.ranges:{};
		for (const key of ['price','age','weight','height']) {
			const stored=ranges[key];
			if (!stored) continue;
			const from=advanced.querySelector(`input[data-${key}-from]`);
			const to=advanced.querySelector(`input[data-${key}-to]`);
			if (from && stored.from != null) {
				from.value=String(stored.from);
				dispatchEscortFilterControlEvents(from);
			}
			if (to && stored.to != null) {
				to.value=String(stored.to);
				dispatchEscortFilterControlEvents(to);
			}
		}
		if (state.breasts) {
			const breastsInput=advanced.querySelector('#range_breasts_filter');
			if (breastsInput) {
				const from=Number(state.breasts.from);
				const to=Number(state.breasts.to);
				let sliderUpdated=false;
				try {
					const jq=window.jQuery;
					const slider=jq ? jq(breastsInput).data('ionRangeSlider'):null;
					if (slider && Number.isFinite(from) && Number.isFinite(to)) {
						slider.update({from,to});
						sliderUpdated=true;
					}
				} catch {}
				if (!sliderUpdated) {
					if (state.breasts.from != null) breastsInput.setAttribute('data-from',String(state.breasts.from));
					if (state.breasts.to != null) breastsInput.setAttribute('data-to',String(state.breasts.to));
				}
				if (state.breasts.value != null) {
					breastsInput.value=String(state.breasts.value);
				}
				dispatchEscortFilterControlEvents(breastsInput);
			}
		}
		for (const select of advanced.querySelectorAll('select')) {
			refreshEscortFilterSelect(select);
		}
		return true;
	}
	function submitEscortAdvancedFilters() {
		const advanced=document.getElementById('advansed-search');
		if (!advanced) return false;
		const form=advanced.closest('form');
		if (!form) return false;
		const submitter=[ ...advanced.querySelectorAll( 'button[type="submit"], input[type="submit"]' ) ].find(control => {
			const label=String( control.textContent || control.value || '' ) .replace(/\s+/g,' ') .trim();
			return !label || /^Szukaj$/i.test(label);
		}) || null;
		try {
			if (typeof form.requestSubmit === 'function') {
				if (submitter) form.requestSubmit(submitter);
				else form.requestSubmit();
			} else if (submitter) {
				submitter.click();
			} else {
				HTMLFormElement.prototype.submit.call(form);
			}
			return true;
		} catch (error) {
			log( 'Nie udało się uruchomić zapisanych filtrów Escort.club', error );
			return false;
		}
	}
	const ESCORT_NATIVE_FILTER_DEFAULTS=Object.freeze({
		filter_price_type:'', filter_price:'0;25000', filter_age:'18;100', filter_weight:'30;200', filter_height:'100;220',
		filter_breasts:'0;8', breasts_type:'', hair_colors:'', sexual_orientation:'', searchlang:'', zodiac_sign:'', q:''
	});
	function getEscortNativeFiltersFromUrl() {
		const defaults=ESCORT_NATIVE_FILTER_DEFAULTS;
		const labels={
			filter_price_type:'Typ ceny', filter_price:'Cena', filter_age:'Wiek', filter_weight:'Waga', filter_height:'Wzrost',
			filter_breasts:'Biust', breasts_type:'Typ biustu', hair_colors:'Kolor włosów', sexual_orientation:'Orientacja',
			searchlang:'Język', zodiac_sign:'Znak zodiaku', q:'Fraza'
		};
		const ignored=new Set(['province','district','page','sort','order']);
		const params=new URLSearchParams(location.search);
		const active=[];
		const normalizeParamBase=name => String(name || '') .replace(/\[\d+\]$|\[\]$/g,'') .toLowerCase();
		const controls=[ ...document.querySelectorAll( '#advansed-search select[name]' ) ];
		const findOptionLabel=(name,value) => {
			const base=normalizeParamBase(name);
			const select=controls.find(control => normalizeParamBase(control.name) === base );
			if (!select) return '';
			const option=[...select.options].find( candidate => String(candidate.value) === String(value) );
			const label=normalizeEscortAdText( option?.textContent );
			return label && !/^(dowoln|wszystk|wybierz)/i.test(label)
				? label
				:'';
		};
		const formatValue=value => {
			const shown=String(value ?? '').trim();
			if ( /^-?\d+(?:[.,]\d+)?;-?\d+(?:[.,]\d+)?$/.test( shown ) ) {
				return shown.replace(';','-');
			}
			return shown.replace(/;/g,', ');
		};
		const add=(key,label,value='') => {
			const shownValue=formatValue(value);
			active.push({
				key, label, value:shownValue, text:shownValue
					? `${label}: ${shownValue}`
					:label
			});
		};
		const pathParts= location.pathname.split('/').filter(Boolean);
		const adsIndex=pathParts.indexOf('anonse');
		const adType= adsIndex >= 0
				? decodeURIComponent( pathParts[adsIndex + 1] || '' )
				:'';
		if ( adType && adType.toLowerCase() !== 'towarzyskie' ) {
			add( 'ad_type', adType .replace(/-/g,' ') .replace( /^./, char => char.toUpperCase() ) );
		}
		for ( const [name,defaultValue]
			of Object.entries(defaults) ) {
			const value=params.get(name);
			if ( value == null || value === defaultValue ) {
				continue;
			}
			const label=labels[name];
			const optionLabel= findOptionLabel(name,value);
			if (optionLabel) {
				add(name,label,optionLabel);
			} else if ( /^\d+$/.test(String(value).trim()) && [ 'breasts_type', 'hair_colors', 'sexual_orientation',
					'searchlang', 'zodiac_sign' ].includes(name) ) {
				add(name,label);
			} else {
				add(name,label,value);
			}
		}
		const groupedDefs={
			sexual_interest:{
				label:'Tagi'
			}, sexual_interests:{
				label:'Tagi'
			}, nationality:{
				label:'Narodowość'
			}, nationalities:{
				label:'Narodowość'
			}, ethnicity:{
				label:'Etniczność'
			}, ethnicities:{
				label:'Etniczność'
			}
		};
		const grouped=new Map();
		for (const name of new Set([...params.keys()])) {
			const base=normalizeParamBase(name);
			const def=groupedDefs[base];
			if (!def) continue;
			const bucket= grouped.get(def.label) || {
					def, count:0, labels:[], unresolved:false
				};
			for (const value of params.getAll(name)) {
				if (!String(value).trim()) continue;
				bucket.count += 1;
				const optionLabel= findOptionLabel(name,value);
				if (optionLabel) {
					bucket.labels.push(optionLabel);
				} else {
					bucket.unresolved=true;
				}
			}
			grouped.set(def.label,bucket);
		}
		for (const [label,group] of grouped) {
			if (!group.count) continue;
			add( label.toLowerCase(), label, String(group.count) );
		}
		for (const name of new Set([...params.keys()])) {
			if ( name in defaults || ignored.has(name) ) {
				continue;
			}
			const base=normalizeParamBase(name);
			if (groupedDefs[base]) continue;
			const values= params.getAll(name) .map(value => String(value).trim() ) .filter(Boolean);
			if (!values.length) continue;
			const label=name .replace(/\[\d+\]$|\[\]$/g,'') .replace(/^filter_/,'') .replace(/_/g,' ') .replace( /^./,
					char => char.toUpperCase() );
			const optionLabels= values.map(value => findOptionLabel(name,value) );
			if ( optionLabels.every(Boolean) ) {
				add( name, label, [...new Set(optionLabels)].join(', ') );
			} else if ( values.every(value => /^\d+$/.test(value) ) ) {
				add(name,label);
			} else {
				add( name, label, values.join(', ') );
			}
		}
		return active.filter( (item,index,list) => list.findIndex( other => other.key === item.key && other.text === item.text
				) === index );
	}
	function buildEscortNativeFilterResetUrl() {
		const url=new URL(location.href);
		const province=url.searchParams.get('province');
		const district=url.searchParams.get('district');
		const parts=url.pathname.split('/').filter(Boolean);
		const adsIndex=parts.indexOf('anonse');
		if (adsIndex >= 0 && parts[adsIndex + 1]) {
			parts[adsIndex + 1]='towarzyskie';
			url.pathname=`/${parts.join('/')}/`;
		}
		url.search='';
		if (province != null) url.searchParams.set('province',province);
		if (district != null) url.searchParams.set('district',district);
		for (const [name,value] of Object.entries(ESCORT_NATIVE_FILTER_DEFAULTS)) {
			url.searchParams.set(name,value);
		}
		return url.href;
	}
	function initEscortNativeFilterResetFix() {
		if (!IS_ESCORT_CLUB) return;
		if (document.documentElement.dataset.vmNativeFilterResetFix === '1') return;
		document.documentElement.dataset.vmNativeFilterResetFix='1';
		document.addEventListener('click',event => {
			const control=event.target instanceof Element
				? event.target.closest('#advansed-search .search-fiters.-reset a, #advansed-search .search-fiters.-reset button, #advansed-search .search-fiters.-reset input')
				:null;
			if (!control) return;
			const label=String(control.textContent || control.value || '') .replace(/\s+/g,' ') .trim();
			if (!/^Resetuj$/i.test(label)) return;
			event.preventDefault();
			event.stopImmediatePropagation();
			location.assign(buildEscortNativeFilterResetUrl());
		},true);
	}
	function ensureEscortNativeFilterTheme() {
		appendHelperStyle('vm-native-filter-theme-style',`
#vm-native-filter-controls-group.vm-helper-ui { color:var(--vm-ui-text,#654426); }
#vm-native-filter-controls-group.vm-helper-ui.-active { border-color:var(--vm-ui-border,#d3b796) !important; background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 20%,var(--vm-ui-bg,#fffaf4)) !important; }
#vm-native-filter-controls-group .vm-native-filter-chip { border-color:var(--vm-ui-border-strong,var(--vm-ui-border,#c7a984)) !important; background:var(--vm-ui-bg,#fffaf4) !important; color:var(--vm-ui-text,#654426) !important; box-shadow:none; }
.vm-helper-ui.vm-native-filter-toggle, #vm-native-filter-reset-button { border-color:var(--vm-ui-accent,#f54da3) !important; background:var(--vm-ui-accent,#f54da3) !important; color:var(--vm-ui-accent-contrast,#211d22) !important; text-decoration:none; }
#vm-native-filter-reset-button { box-shadow:inset 2px 0 0 var(--vm-ui-bg,#411034) !important; }
.vm-helper-ui.vm-native-filter-toggle:is(:hover,:focus-visible), #vm-native-filter-reset-button:is(:hover,:focus-visible):not([aria-disabled="true"]) { background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 85%,var(--vm-ui-accent-contrast,#211d22)) !important; }
.vm-helper-ui.vm-native-filter-toggle:focus-visible, #vm-native-filter-reset-button:focus-visible { outline:2px solid var(--vm-ui-text,#fff); outline-offset:2px; }
#vm-native-filter-reset-button[aria-disabled="true"] { opacity:.45; cursor:default; }
`);
	}
	function getEscortNativeAdvancedFilterLinks() {
		return [...document.querySelectorAll('.search-links a, .mobile-trigger a')]
			.filter(link => /^Pokaż filtry(?: zaawansowane)?(?:\s*\(\d+\))?$/i.test(normalizeEscortAdText(link.textContent)));
	}
	function getEscortNativeDesktopFilterLink(links) {
		return links.find(link => !link.closest('.mobile-trigger') && link.offsetParent !== null)
			|| links.find(link => !link.closest('.mobile-trigger'));
	}
	function initEscortNativeAdvancedFilterCounter() {
		if (!IS_ESCORT_CLUB) return;
		if (document.documentElement.dataset.vmNativeFilterCounterInit === '1') return;
		document.documentElement.dataset.vmNativeFilterCounterInit='1';
		ensureEscortNativeFilterTheme();
		const update=() => {
			const active=getEscortNativeFiltersFromUrl();
			const summary=active.map(item => item.text);
			const links=getEscortNativeAdvancedFilterLinks();
			for (const link of links) {
				prepareHelperStyleApi(link).classList.add('vm-native-filter-toggle');
				link.textContent=`Pokaż filtry${active.length ? ` (${active.length})`:''}`;
				link.title=active.length ? `Aktywne filtry: ${summary.join(', ')}`:'Brak aktywnych filtrów';
			}
			const link=getEscortNativeDesktopFilterLink(links);
			if (!link) return;
			let resetButton=document.getElementById( 'vm-native-filter-reset-button' );
			if (!resetButton) {
				resetButton=link.cloneNode(false);
				resetButton.id='vm-native-filter-reset-button';
				resetButton.classList.remove('vm-native-filter-toggle');
				resetButton.classList.add('vm-native-filter-reset-button');
				resetButton.removeAttribute('onclick');
				resetButton.textContent='×';
				resetButton.setAttribute('role','button');
				resetButton.setAttribute('aria-label','Resetuj filtry');
				resetButton.addEventListener('click',event => {
					event.preventDefault();
					event.stopPropagation();
					if (!getEscortNativeFiltersFromUrl().length) return;
					location.assign(buildEscortNativeFilterResetUrl());
				});
			}
			resetButton.title='Resetuj filtry';
			resetButton.style.fontSize='22px';
			resetButton.classList.toggle('disabled',!active.length);
			resetButton.setAttribute('aria-disabled',String(!active.length));
			if ( resetButton.parentElement !== link.parentElement || resetButton.previousElementSibling !== link ) {
				link.insertAdjacentElement('afterend',resetButton);
			}
			link.style.borderTopRightRadius='0';
			link.style.borderBottomRightRadius='0';
			resetButton.style.borderTopLeftRadius='0';
			resetButton.style.borderBottomLeftRadius='0';
			link.style.borderRight='0';
			resetButton.style.borderLeft='0';
			let controlsGroup=document.getElementById( 'vm-native-filter-controls-group' );
			if (!controlsGroup) {
				controlsGroup=prepareHelperStyleApi(makeElement('span'));
				controlsGroup.id='vm-native-filter-controls-group';
			}
			const linkParent=link.parentElement;
			if (!linkParent) return;
			if (!controlsGroup.isConnected) {
				linkParent.insertBefore(controlsGroup,link);
			}
			let buttons=controlsGroup.querySelector('.vm-native-filter-buttons');
			if (!buttons) {buttons=makeElement('span','vm-native-filter-buttons'); controlsGroup.appendChild(buttons);}
			if (link.parentElement !== buttons || resetButton.parentElement !== buttons) buttons.append(link,resetButton);
			Object.assign(buttons.style,{display:'inline-flex',alignItems:'stretch',minWidth:'0',maxWidth:'100%'});
			Object.assign(link.style,{flex:'1 1 auto',minWidth:'0'});
			resetButton.style.flex='0 0 auto';
			Object.assign(controlsGroup.style,{
				display:'inline-flex', alignItems:'center', flex:'0 1 auto', flexWrap:'nowrap', gap:'0', minWidth:'0',
				maxWidth:'100%', boxSizing:'border-box', verticalAlign:'middle'
			});
			let info=document.getElementById('vm-native-filter-info');
			if (!info) {
				info=makeElement('span');
				info.id='vm-native-filter-info';
			}
			controlsGroup.classList.toggle('-active',!!active.length);
			info.replaceChildren();
			if (!active.length) {
				detachHelperNode(info);
				Object.assign(controlsGroup.style,{padding:'0',border:'0',borderRadius:'0',background:'transparent',boxShadow:'none'});
				return;
			}
			Object.assign(controlsGroup.style,{
				padding:'0 0 0 6px', border:'1px solid var(--vm-ui-border,#d3b796)', borderRadius:'9px',
				background:'var(--vm-ui-bg-alt,#f0e2d2)', boxShadow:'none'
			});
			Object.assign(info.style,{
				display:'inline-grid', gridAutoFlow:'column', gridTemplateRows:'repeat(2, 16px)', gridAutoColumns:'max-content',
				alignItems:'center', alignContent:'center', gap:'2px 4px', padding:'0 7px 0 0', margin:'0', verticalAlign:'middle',
				flex:'0 1 auto', minWidth:'0', maxWidth:'100%', overflowX:'auto', overflowY:'hidden', boxSizing:'border-box'
			});
			for (const item of active) {
				const chip=makeElement('span','vm-native-filter-chip',item.text);
				Object.assign(chip.style,{
					display:'inline-flex', alignItems:'center', height:'16px', padding:'0 6px', boxSizing:'border-box',
					border:'1px solid', borderRadius:'4px',
					font:'700 9px var(--vm-ui-font-family,Lato,Arial,sans-serif)', lineHeight:'1', whiteSpace:'nowrap'
				});
				chip.title=`Aktywny filtr: ${item.text}`;
				info.appendChild(chip);
			}
			info.title=`Aktywne filtry: ${summary.join(', ')}`;
			if ( info.parentElement !== controlsGroup || info.nextElementSibling !== buttons ) {
				controlsGroup.insertBefore(info,buttons);
			}
		};
		update();
		setTimeout(update,300);
	}
	function initEscortSavedSearchFilters() {
		if (!IS_ESCORT_CLUB) return;
		if (document.getElementById('vm-escort-saved-filters')) return;
		const advanced=document.getElementById('advansed-search');
		const footer=advanced?.querySelector('.search-fiters.-reset');
		if (!advanced || !footer) {
			if (!document.documentElement.dataset.vmSavedFiltersRetry) {
				document.documentElement.dataset.vmSavedFiltersRetry='1';
				let attempts=0;
				const timer=setInterval(() => {
					attempts++;
					if (document.getElementById('advansed-search')?.querySelector('.search-fiters.-reset')) {
						clearInterval(timer);
						delete document.documentElement.dataset.vmSavedFiltersRetry;
						initEscortSavedSearchFilters();
					} else if (attempts >= 40) {
						clearInterval(timer);
						delete document.documentElement.dataset.vmSavedFiltersRetry;
					}
				},250);
			}
			return;
		}
		const pagePinkColor=getEscortPagePinkColor();
		const styleId='vm-escort-saved-filters-style';
		if (!document.getElementById(styleId)) {
			appendHelperStyle(styleId,` #vm-escort-saved-filters { display: inline-flex; align-items: center; flex: 0 1 auto; flex-wrap: nowrap; gap: 7px; max-width: 100%; margin-right: auto; padding-right: 14px; padding-bottom: 2px; overflow-x: auto; font-family:var(--vm-ui-font-family,Lato,Arial,sans-serif); }
#vm-escort-saved-filters .vm-saved-filters-label { color: var(--vm-ui-text,#211d22); font-size: 12px; font-weight: 700; line-height: 32px; white-space: nowrap; }
#vm-escort-saved-filters .vm-saved-filter-select-wrap { --vm-saved-filter-select-width: ${ESCORT_SAVED_FILTER_SELECT_MIN_WIDTH}px; position: relative; width: var(--vm-saved-filter-select-width) !important; min-width: ${ESCORT_SAVED_FILTER_SELECT_MIN_WIDTH}px; max-width: ${ESCORT_SAVED_FILTER_SELECT_MAX_WIDTH}px; flex: 0 0 var(--vm-saved-filter-select-width) !important; height: 32px; margin: 0; transition: width .15s ease, flex-basis .15s ease; }
#vm-escort-saved-filters .vm-saved-filter-select-wrap::after { content: ''; position: absolute; top: 13px; right: 10px; width: 0; height: 0; border-left: 7px solid transparent; border-right: 7px solid transparent; border-top: 7px solid var(--vm-ui-accent,#ff4c99); pointer-events: none; }
#vm-escort-saved-filters-select { appearance: none !important; -webkit-appearance: none !important; -moz-appearance: none !important; box-sizing: border-box; width: 100% !important; min-width: 100% !important; max-width: 100% !important; height: 32px; min-height: 32px; margin: 0; padding: 0 30px 0 12px !important; border: 0 !important; border-bottom: 1px solid var(--vm-ui-accent,#ff4c99) !important; border-radius: 0 !important; outline: 0 !important; background: transparent !important; color: var(--vm-ui-text,#211d22); font-family:var(--vm-ui-font-family,Lato,Arial,sans-serif); font-size: 14px; line-height: 31px; text-overflow: ellipsis; cursor: pointer; box-shadow: none !important; }
#vm-escort-saved-filters-select:focus { border-bottom-color: var(--vm-ui-accent,#d90070) !important; }
#vm-escort-saved-filters .vm-saved-filter-action { box-sizing: border-box; display: inline-flex !important; align-items: center; justify-content: center; flex: 0 0 auto !important; width: auto !important; min-width: max-content !important; max-width: none !important; height: 32px; margin: 0; padding: 7px 12px !important; border-radius: 7px !important; font-family:var(--vm-ui-font-family,Lato,Arial,sans-serif); font-size: 12px; font-weight: 700; line-height: 16px; white-space: nowrap !important; text-decoration: none; cursor: pointer; transition: background-color .15s ease, border-color .15s ease, color .15s ease; }
#vm-escort-saved-filters .vm-saved-filter-delete { border: 1px solid var(--vm-ui-accent,${pagePinkColor}) !important; background: var(--vm-ui-bg,#fff) !important; color: var(--vm-ui-accent,${pagePinkColor}) !important; }
#vm-escort-saved-filters .vm-saved-filter-delete:hover:not(:disabled), #vm-escort-saved-filters .vm-saved-filter-delete:focus:not(:disabled) { background: var(--vm-ui-bg-hover,#fff1f8) !important; border-color: var(--vm-ui-accent,${pagePinkColor}) !important; color: var(--vm-ui-accent,${pagePinkColor}) !important; filter: brightness(.9); }
#vm-escort-saved-filters .vm-saved-filter-load:disabled, #vm-escort-saved-filters .vm-saved-filter-delete:disabled { border-color: var(--vm-ui-border,#d8d2d5) !important; background: var(--vm-ui-bg-alt,#f4f1f3) !important; color: var(--vm-ui-text-faint,#aaa3a7) !important; cursor: not-allowed; opacity: .72; }
#vm-escort-saved-filters .vm-saved-filters-status { color: var(--vm-ui-text-muted,#8d858a); font-size: 10px; line-height: 16px; white-space: nowrap; }
#vm-escort-saved-filters-quick { display: inline-flex; align-items: stretch; flex: 0 0 auto; margin-left: 10px; vertical-align: middle; }
#vm-escort-saved-filters-quick-select { appearance: auto; -webkit-appearance: auto; box-sizing: border-box; min-width: 180px; max-width: 280px; margin: 0; padding: 0 34px 0 15px; border: 2px solid var(--vm-ui-accent, ${pagePinkColor}); border-radius: 7px; background: var(--vm-ui-bg, #fff); color: var(--vm-ui-accent, ${pagePinkColor}); font-family:var(--vm-ui-font-family,Lato,Arial,sans-serif); font-size: 13px; font-weight: 800; line-height: 1; cursor: pointer; box-shadow: none; }
#vm-escort-saved-filters-quick-select:hover, #vm-escort-saved-filters-quick-select:focus { background: var( --vm-ui-bg-hover, color-mix( in srgb, var(--vm-ui-accent, ${pagePinkColor}) 7%, var(--vm-ui-bg, #fff) ) ); color: var(--vm-ui-accent, ${pagePinkColor}); outline: none; }
#vm-escort-saved-filters-quick-select:disabled { opacity: .58; cursor: default; }
@media (max-width: 767px) { #vm-escort-saved-filters-quick { display: none !important; }
#vm-escort-saved-filters { flex: 0 1 100%; width: 100%; margin-bottom: 10px; padding-right: 0; }
#vm-escort-saved-filters .vm-saved-filters-status { display: none; }
}
`);
		}
		const root=makeElement('div');
		root.id='vm-escort-saved-filters';
		const label=makeElement('span','sub-label vm-saved-filters-label','Zapisane filtry:');
		const select=makeElement('select');
		select.id='vm-escort-saved-filters-select';
		select.setAttribute('aria-label','Zapisane filtry wyszukiwania');
		const selectWrap=makeElement('div','vm-saved-filter-select-wrap');
		selectWrap.appendChild(select);
		const quickRoot=prepareHelperStyleApi( makeElement('span') );
		quickRoot.id='vm-escort-saved-filters-quick';
		const controlsShell=makeElement('div','vm-escort-results-controls-shell');
		const quickSelect=makeElement('select','',null,{id:'vm-escort-saved-filters-quick-select'});
		quickSelect.setAttribute( 'aria-label', 'Szybki wybór zapisanego zestawu filtrów' );
		quickSelect.title='Wczytaj zapisany zestaw filtrów';
		quickRoot.appendChild(quickSelect);
		function updateSavedFilterSelectWidth() {
			const selectedOption=select.options[select.selectedIndex];
			const text=String(selectedOption?.textContent || '').trim();
			const canvas=makeElement('canvas');
			const context=canvas.getContext('2d');
			if (context) {
				context.font='14px Lato, Arial, sans-serif';
			}
			const textWidth=context ? context.measureText(text).width:text.length * 8;
			const naturalWidth=Math.ceil(textWidth) + 48;
			const width=Math.max( ESCORT_SAVED_FILTER_SELECT_MIN_WIDTH,
				Math.min(ESCORT_SAVED_FILTER_SELECT_MAX_WIDTH,naturalWidth) );
			selectWrap.style.setProperty('--vm-saved-filter-select-width',`${width}px`);
			select.title=naturalWidth > ESCORT_SAVED_FILTER_SELECT_MAX_WIDTH ? text:'';
		}
		const loadBtn=makeButton('btn btn-pink vm-saved-filter-action vm-saved-filter-load','Wczytaj',{disabled:true});
		loadBtn.setAttribute('aria-disabled','true');
		loadBtn.title='Najpierw wybierz zapisany zestaw';
		const saveBtn=makeButton('btn btn-pink vm-saved-filter-action vm-saved-filter-save','Zapisz');
		const deleteBtn=makeButton("btn vm-saved-filter-action vm-saved-filter-delete",'Usuń',{disabled:true});
		deleteBtn.setAttribute('aria-disabled','true');
		deleteBtn.title='Najpierw wybierz zapisany zestaw';
		const status=makeElement('span','sub-label vm-saved-filters-status');
		status.setAttribute('aria-live','polite');
		function getSelectedId() {
			return String(select.value || '');
		}
		const getPresetById=id => getEscortSavedSearchFilters().find(item => item.id === String(id || '')) || null;
		const syncPresetSelection=id => {
			const value=String(id || '');
			select.value=value;
			quickSelect.value=value;
			updateSavedFilterSelectWidth();
			updatePresetActionState();
		};
		const fillPresetSelect=(target,filters,placeholderText,selectedId) => {
			target.replaceChildren();
			const placeholder=makeElement('option','',placeholderText,{value:''});
			target.appendChild(placeholder);
			for (const filter of filters) {
				const option=makeElement('option','',filter.name,{value:filter.id,selected:filter.id === selectedId});
				target.appendChild(option);
			}
		};
		function updatePresetActionState() {
			const enabled=!!getSelectedId();
			for (const [button,enabledTitle] of [ [loadBtn,'Wczytaj wybrany zapisany zestaw'],
				[deleteBtn,'Usuń wybrany zapisany zestaw'] ]) {
				button.disabled=!enabled;
				button.setAttribute('aria-disabled',String(!enabled));
				button.title=enabled ? enabledTitle:'Najpierw wybierz zapisany zestaw';
			}
		}
		function refreshList(selectedId='') {
			const filters=getEscortSavedSearchFilters();
			fillPresetSelect( select, filters, filters.length ? '- wybierz -':'- brak zapisanych -', selectedId );
			fillPresetSelect( quickSelect, filters, filters.length ? 'Zapisane filtry':'Brak zapisanych filtrów', selectedId );
			if ( selectedId && !filters.some(filter => filter.id === selectedId) ) {
				select.value='';
				quickSelect.value='';
			}
			quickSelect.disabled=!filters.length;
			quickSelect.title=filters.length
				? 'Wczytaj zapisany zestaw filtrów'
				:'Brak zapisanych zestawów filtrów';
			updateSavedFilterSelectWidth();
			updatePresetActionState();
		}
		select.addEventListener('change',() => {
			const id=getSelectedId();
			quickSelect.value=id;
			updateSavedFilterSelectWidth();
			updatePresetActionState();
			if (!id) {
				status.textContent='';
				return;
			}
			const preset=getPresetById(id);
			if (!preset) {
				status.textContent='nie znaleziono zestawu';
				return;
			}
			status.textContent=`wybrano „${preset.name}”`;
		});
		quickSelect.addEventListener('change',() => {
			const id=String(quickSelect.value || '');
			if (!id) return;
			const preset=getPresetById(id);
			if (!preset) {
				quickSelect.value='';
				return;
			}
			syncPresetSelection(id);
			if (applyEscortAdvancedFilterState(preset.state)) {
				status.textContent=`uruchamiam „${preset.name}”`;
				requestAnimationFrame(() => {
					setTimeout(() => {
						if (!submitEscortAdvancedFilters()) {
							status.textContent= 'nie udało się uruchomić filtrowania';
							quickSelect.value='';
							select.value='';
							updatePresetActionState();
						}
					},0);
				});
			} else {
				status.textContent='nie udało się wczytać';
				quickSelect.value='';
				select.value='';
				updatePresetActionState();
			}
		});
		loadBtn.addEventListener('click',event => {
			event.preventDefault();
			const id=getSelectedId();
			if (!id) return;
			const preset=getPresetById(id);
			if (!preset) {
				status.textContent='nie znaleziono zestawu';
				refreshList('');
				return;
			}
			if (applyEscortAdvancedFilterState(preset.state)) {
				quickSelect.value=id;
				status.textContent=`wczytano „${preset.name}”`;
			} else {
				status.textContent='nie udało się wczytać';
			}
		});
		saveBtn.addEventListener('click',event => {
			event.preventDefault();
			const currentId=getSelectedId();
			const currentPreset=getEscortSavedSearchFilters().find(item => item.id === currentId);
			const proposed=window.prompt( 'Podaj nazwę zestawu filtrów:', currentPreset?.name || '' );
			if (proposed == null) return;
			const name=proposed.replace(/\s+/g,' ').trim().slice(0,80);
			if (!name) {
				window.alert('Nazwa zestawu filtrów nie może być pusta.');
				return;
			}
			const state=captureEscortAdvancedFilterState();
			if (!state) {
				status.textContent='nie udało się odczytać filtrów';
				return;
			}
			const filters=getEscortSavedSearchFilters();
			const existing=filters.find(item => item.name.toLocaleLowerCase('pl') === name.toLocaleLowerCase('pl'));
			let id;
			if (existing) {
				if (!window.confirm(`Zestaw „${existing.name}” już istnieje. Nadpisać go?`)) return;
				existing.name=name;
				existing.state=state;
				existing.savedAt=Date.now();
				id=existing.id;
			} else {
				id=`preset_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`;
				filters.unshift({
					id, name, state, savedAt:Date.now()
				});
			}
			if (!setEscortSavedSearchFilters(filters)) {
				status.textContent='błąd zapisu';
				return;
			}
			refreshList(id);
			status.textContent=`zapisano „${name}”`;
		});
		deleteBtn.addEventListener('click',event => {
			event.preventDefault();
			const id=getSelectedId();
			if (!id) return;
			const filters=getEscortSavedSearchFilters();
			const preset=filters.find(item => item.id === id);
			if (!preset) return;
			if (!window.confirm(`Usunąć zapisany zestaw „${preset.name}”?`)) return;
			const next=filters.filter(item => item.id !== id);
			if (!setEscortSavedSearchFilters(next)) {
				status.textContent='błąd usuwania';
				return;
			}
			refreshList('');
			status.textContent=`usunięto „${preset.name}”`;
		});
		root.appendChild(label);
		root.appendChild(selectWrap);
		root.appendChild(loadBtn);
		root.appendChild(saveBtn);
		root.appendChild(deleteBtn);
		root.appendChild(status);
		footer.insertBefore(root,footer.firstChild);
		const placeQuickSelect=() => {
			const desktopLink=getEscortNativeDesktopFilterLink(getEscortNativeAdvancedFilterLinks());
			if (!desktopLink?.parentElement) return false;
			const searchLinks=desktopLink.closest('.search-links');
			const resultsLabel= searchLinks?.querySelector(':scope > .sub-label') ||
				document.querySelector('.vm-escort-results-count');
			const topSearch=document.getElementById('top-search');
			if (resultsLabel && topSearch?.parentElement) {
				markEscortResultCounter(resultsLabel);
				topSearch.style.paddingBottom='0';
			}
			const locationSearchBox= findEscortSearchLocationControls()?.searchBox;
			if (locationSearchBox) {
				const referenceStyle=getComputedStyle(locationSearchBox);
				Object.assign(controlsShell.style,{
					boxSizing:referenceStyle.boxSizing, width:'100%', maxWidth: locationSearchBox.style.maxWidth ||
						referenceStyle.maxWidth, margin:referenceStyle.margin, paddingTop:referenceStyle.paddingTop,
					paddingBottom:referenceStyle.paddingBottom, paddingRight:referenceStyle.paddingRight,
					paddingLeft:referenceStyle.paddingLeft, display:'flex', alignItems:'center', justifyContent:'space-between',
					flexWrap:'wrap', minWidth:'0'
				});
			}
			if (searchLinks?.parentElement) {
				if (searchLinks.parentElement !== controlsShell) {
					searchLinks.parentElement.insertBefore(controlsShell,searchLinks);
					controlsShell.appendChild(searchLinks);
				}
				Object.assign(searchLinks.style,{
					width:'100%', maxWidth:'none', margin:'0', padding:'0', flex:'1 1 auto', minWidth:'0'
				});
			}
			if (resultsLabel && searchLinks && controlsShell.contains(searchLinks)) {
				Object.assign(resultsLabel.style,{
					display:'block', flex:'0 1 auto', minWidth:'0', maxWidth:'100%', margin:'0', whiteSpace:'nowrap'
				});
				if ( resultsLabel.parentElement !== controlsShell || resultsLabel.nextElementSibling !== searchLinks ) {
					controlsShell.insertBefore(resultsLabel,searchLinks);
				}
			}
			const radiusLink=document.getElementById('vm-escort-radius-open')?.parentElement;
			if (radiusLink && controlsShell.parentElement) controlsShell.before(radiusLink);
			const controlsParent=desktopLink.parentElement;
			if (controlsParent && controlsParent !== searchLinks) {
				controlsParent.style.marginLeft='auto';
			} else {
				desktopLink.style.marginLeft='auto';
			}
			const resetButton=document.getElementById( 'vm-native-filter-reset-button' );
			const filterControlsGroup=desktopLink.closest('#vm-native-filter-controls-group');
			const quickAnchor= filterControlsGroup || ( resetButton?.parentElement === desktopLink.parentElement
					? resetButton
					:desktopLink );
			if ( quickRoot.parentElement !== quickAnchor.parentElement || quickRoot.previousElementSibling !== quickAnchor ) {
				quickAnchor.insertAdjacentElement('afterend',quickRoot);
			}
			const rect=desktopLink.getBoundingClientRect();
			if (rect.height > 0) {
				quickSelect.style.height= `${Math.round(rect.height)}px`;
				quickSelect.style.minHeight= `${Math.round(rect.height)}px`;
			}
			const computed=getComputedStyle(desktopLink);
			if (computed.borderRadius) {
				quickSelect.style.borderRadius= computed.borderRadius;
			}
			return true;
		};
		if (!placeQuickSelect()) {
			let quickAttempts=0;
			const quickTimer=setInterval(() => {
				quickAttempts++;
				if ( placeQuickSelect() || quickAttempts >= 40 ) {
					clearInterval(quickTimer);
				}
			},250);
		}
		refreshList('');
		document.fonts?.ready .then(() => {
				if (document.contains(select)) updateSavedFilterSelectWidth();
			}) .catch(() => {});
	}
	function initReverseImageUrlPrefill() {
		if (!/^(?:www\.)?(?:picdetective\.com|hivedetect\.ai)$/.test(location.hostname)) return false;
		if (window.top !== window.self) return true;
		const pageUrl=new URL(location.href);
		const imageUrl=new URLSearchParams(pageUrl.hash.slice(1)).get('vm_image_url') || pageUrl.searchParams.get('vm_image_url');
		try {
			const url=new URL(imageUrl);
			if (!/^https?:$/.test(url.protocol) || url.username || url.password || imageUrl.length > 8192) return true;
		} catch {return true;}
		let attempts=0,timer=null;
		const fill=() => {
			const input=[...document.querySelectorAll('input,textarea')].find(el =>
				!el.disabled && !el.readOnly && /^(?:text|url|search|textarea)$/.test(el.type) &&
				el.getClientRects().length && getComputedStyle(el).visibility === 'visible' &&
				(el.type === 'url' || /url/i.test([el.placeholder,el.name,el.id,el.getAttribute('aria-label')].join(' '))));
			if (!input || (input.value.trim() && input.value !== imageUrl)) return false;
			const proto=input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype:HTMLInputElement.prototype;
			Object.getOwnPropertyDescriptor(proto,'value').set.call(input,imageUrl);
			for (const name of ['input','change']) input.dispatchEvent(new Event(name,{bubbles:true}));
			input.focus();
			const clean=new URL(location.href),fragment=new URLSearchParams(clean.hash.slice(1));
			if (fragment.has('vm_image_url')) {fragment.delete('vm_image_url'); clean.hash=fragment.toString();}
			clean.searchParams.delete('vm_image_url');
			try {history.replaceState(history.state,document.title,clean.href);} catch {}
			return true;
		};
		if (!fill()) timer=setInterval(() => {
			if (fill() || ++attempts >= 240) clearInterval(timer);
		},250);
		return true;
	}
	function initEscortiSearchSettingsGear() {
		const url=new URL(location.href);
		if (url.pathname !== '/search' || url.searchParams.has('vm_mode')) {
			return;
		}
		const ensure=() => {
			if (document.getElementById('vm-escorti-search-settings-gear')) {
				return true;
			}
			const profilesTab=document.querySelector( 'button[data-tab="profiles"]' );
			const host= profilesTab?.parentElement || document.querySelector('.tabs, .tab-nav, main');
			if (!host) return false;
			const gear=makeSettingsTabGearButton( 'results', 'Ustawienia - Lista wyników' );
			gear.id='vm-escorti-search-settings-gear';
			Object.assign(gear.style,{
				width:'25px', height:'25px', marginLeft:'6px', verticalAlign:'middle'
			});
			if (profilesTab) {
				profilesTab.insertAdjacentElement('afterend',gear);
			} else {
				host.prepend(gear);
			}
			return true;
		};
		if (ensure()) return;
		let attempts=0;
		const timer=setInterval(() => {
			attempts++;
			if (ensure() || attempts >= 40) clearInterval(timer);
		},250);
	}
	function parseEscortiSearchSource(value) {
		const term=String(value || '').trim();
		const adUrl=normalizeEscortiAdUrl(term);
		if (adUrl) return {resolutionMode:'address-escorti',adUrl,phoneDigits:null};
		const phoneDigits=/^\+?[\d\s().-]+$/.test(term) && normalizeEscortCachedPhone(term);
		return phoneDigits ? {resolutionMode:'phone-escorti',phoneDigits,adUrl:null}:null;
	}
	function withEscortiSearchSource(href) {
		try {
			const page=new URL(location.href),url=new URL(href,page);
			if (page.pathname !== '/search' || page.searchParams.get('vm_mode') === 'check' || window.top !== window.self ||
					!/^https?:$/.test(url.protocol) || !/^(?:www\.)?escorti\.pl$/i.test(url.hostname) || !/^\/escort\/\d+\/?$/.test(url.pathname)) return href;
			const source=parseEscortiSearchSource(page.searchParams.get('search'));
			if (!source) return href;
			const hash=new URLSearchParams(url.hash.slice(1));
			hash.set('vm_escorti_source',source.phoneDigits || source.adUrl);
			url.hash=hash.toString();
			return url.href;
		} catch {return href;}
	}
	function initEscortiSearchSourceLinks() {
		if (window.top !== window.self || location.pathname !== '/search' ||
				new URL(location.href).searchParams.get('vm_mode') === 'check' || document.__vmEscortiSourceLinks) return;
		document.__vmEscortiSourceLinks=true;
		const update=() => {
			for (const link of document.querySelectorAll('a[href*="/escort/"]')) {
				const href=withEscortiSearchSource(link.href);
				if (href !== link.href) link.href=href;
			}
			syncEscortiWatchSearchResults();
		};
		update();
		new MutationObserver(createAnimationFrameScheduler(update)).observe(document.documentElement,{childList:true,subtree:true});
	}
	function syncEscortiWatchSearchResults() {
		const page=new URL(location.href),settings=getWatchSettings();
		if (page.pathname !== '/search' || page.searchParams.has('vm_mode') || !settings.enabled) return;
		const source=parseEscortiSearchSource(page.searchParams.get('search'));
		const search=getEscortiWatchSearchValue(source);
		if (!search) return;
		const profileUrls=getEscortiProfileUrls();
		if (!profileUrls.length || getDeclaredProfileCount() !== profileUrls.length) return;
		const ids=profileUrls.map(getEscortiProfileId).sort().join(','),items=getWatchedItems();
		let changed=false;
		for (const item of items) {
			if (getEscortiWatchSearchValue(item) !== search || item.profileUrls.map(getEscortiProfileId).sort().join(',') === ids) continue;
			item.profileUrls=profileUrls;
			changed=true;
		}
		if (changed) saveWatchedItems(items);
	}
	function handleEscortiPage() {
		const url=new URL(location.href);
		const mode=url.searchParams.get('vm_mode');
		const resultKey=url.searchParams.get('vm_key');
		const bootKey=url.searchParams.get('vm_boot');
		const mergeProfiles=url.searchParams.get('vm_merge') === '1';
		const stateKey=url.searchParams.get('vm_state');
		initEscortiSearchSourceLinks();
		if (mode === 'check' && bootKey) {
			writeHelperStorageValue(bootKey,{started:true,timestamp:Date.now()});
		}
		if (!mode) return;
		if (url.pathname === '/search') return processEscortiSearchPage(mode,resultKey,mergeProfiles);
		if (/^\/escort\/\d+\/?$/.test(url.pathname)) processEscortiProfilePage(mode,resultKey,mergeProfiles,stateKey);
	}
	function processEscortiSearchPage(mode,resultKey,mergeProfiles=false) {
		let attempts=0;
		const timer=setInterval(() => {
			attempts++;
			const profileUrls=getEscortiProfileUrls();
			const declaredProfileCount=getDeclaredProfileCount();
			if (declaredProfileCount != null && declaredProfileCount !== profileUrls.length) {
				if (attempts < 40) return;
				clearInterval(timer);
				if (mode === 'check') finishEscortiCheck(resultKey,{status:'error',message:'Nie udało się odczytać pełnej listy profili Escorti'});
				else removeVmParamsFromCurrentUrl();
				return;
			}
			if (profileUrls.length === 1 && (declaredProfileCount === 1 || (declaredProfileCount == null && attempts >= 4))) {
				clearInterval(timer);
				const profileUrl=new URL(profileUrls[0]);
				if (mode === 'check') {
					profileUrl.searchParams.set('vm_mode',mode);
					if (resultKey) profileUrl.searchParams.set('vm_key',resultKey);
					if (mergeProfiles) profileUrl.searchParams.set('vm_merge','1');
					location.replace(profileUrl.href);
				} else {
					removeVmParamsFromCurrentUrl();
					location.assign(withEscortiSearchSource(profileUrl.href));
				}
				return;
			}
			if (profileUrls.length > 1) {
				clearInterval(timer);
				if (mode === 'check' && mergeProfiles) {
					startMultiProfileFlow(resultKey,profileUrls);
					return;
				}
				if (mode === 'check') finishEscortiCheck(resultKey,{status:'ok',profiles:profileUrls.length});
				else removeVmParamsFromCurrentUrl();
				return;
			}
			if (declaredProfileCount > 0 && attempts < 40) return;
			if (declaredProfileCount === 0) {
				clearInterval(timer);
				if (mode === 'check') finishEscortiCheck(resultKey,{status:'ok',profiles:0});
				else removeVmParamsFromCurrentUrl();
				return;
			}
			if (attempts >= 40) {
				clearInterval(timer);
				if (mode === 'check') finishEscortiCheck(resultKey,{status:'timeout'});
				else removeVmParamsFromCurrentUrl();
			}
		},150);
	}
	function getEscortiProfileUrls(sourceDocument=document,baseUrl=location.href) {
		const unique=new Set();
		for (const link of sourceDocument.querySelectorAll('#tab-profiles a[href], a[href*="/escort/"]')) {
			try {
				const url=new URL(link.getAttribute('href'),baseUrl);
				if ((url.hostname === 'escorti.pl' || url.hostname === 'www.escorti.pl') && /^\/escort\/\d+\/?$/.test(url.pathname)) {
					url.search='';
					url.hash='';
					unique.add(url.href);
				}
			} catch {}
		}
		return [...unique];
	}
	function getDeclaredProfileCount(sourceDocument=document) {
		const tab=sourceDocument.querySelector('button[data-tab="profiles"]');
		if (!tab) return null;
		for (const span of tab.querySelectorAll('span')) {
			const text=(span.textContent || '').trim();
			if (/^\d+$/.test(text)) return Number(text);
		}
		return null;
	}
	function extractEscortiCurrentCity(sourceDocument=document) {
		const breadcrumbItems=[...sourceDocument.querySelectorAll( 'nav[aria-label="breadcrumb"] li' )];
		const last=breadcrumbItems.at(-1);
		return normalizeEscortCity(last?.textContent) || null;
	}
	function normalizeEscortiHistoryDate(value) {
		const text=normalizeEscortAdText(value);
		const match=text.match( /(\d{4}-\d{2}-\d{2}|\d{2}[.\-/]\d{2}[.\-/]\d{4})/ );
		return match ? formatEscortiDate(match[1]):null;
	}
	function extractEscortiCityHistory(sourceDocument=document) {
		const entries=new Map();
		const add=(dateValue,cityValue) => {
			const date=normalizeEscortiHistoryDate(dateValue);
			const city=normalizeEscortCity(cityValue) .replace(/^(?:miasto|lokalizacja)\s*:?\s*/i,'') .trim();
			if (!date || !city || /^brak$/i.test(city)) return;
			const key=`${date}|${normalizeEscortCityKey(city)}`;
			if (!entries.has(key)) entries.set(key,{date,city});
		};
		const locationHeadings=[...sourceDocument.querySelectorAll( 'h1, h2, h3, h4, h5, h6' )].filter(heading =>
			normalizeText(heading.textContent).startsWith('odwiedzane lokalizacje') );
		for (const heading of locationHeadings) {
			const section=heading.parentElement;
			if (!section) continue;
			for (const entry of section.querySelectorAll('div > span')) {
				const dateNode=[...entry.querySelectorAll('span')].find(node => normalizeEscortiHistoryDate(node.textContent) );
				if (!dateNode) continue;
				const cityNode=entry.cloneNode(true);
				cityNode.querySelectorAll('span').forEach(node => detachHelperNode(node));
				add(dateNode.textContent,cityNode.textContent);
			}
		}
		for (const table of sourceDocument.querySelectorAll('table')) {
			const headers=[...table.querySelectorAll('thead th')] .map(cell => normalizeText(cell.textContent));
			if (!headers.length) continue;
			const dateIndex=headers.findIndex(header =>
				/data|dzień|opublikowano|dodano|okres|pierwsz|ostatn|widzian|(^|\s)od(\s|$)/.test(header) );
			const cityIndex=headers.findIndex(header => /miasto|lokalizacj|miejscowość/.test(header) );
			if (dateIndex < 0 || cityIndex < 0) continue;
			for (const row of table.querySelectorAll('tbody tr')) {
				const cells=[...row.querySelectorAll(':scope > td')];
				if (!cells[dateIndex] || !cells[cityIndex]) continue;
				add(cells[dateIndex].textContent,cells[cityIndex].textContent);
			}
		}
		for (const element of sourceDocument.querySelectorAll( '[data-city][data-date], [data-miasto][data-data], ' +
			'[data-location][data-date], [data-city][data-created-at]' )) {
			add( element.getAttribute('data-date') || element.getAttribute('data-data') ||
					element.getAttribute('data-created-at'), element.getAttribute('data-city') ||
					element.getAttribute('data-miasto') || element.getAttribute('data-location') );
		}
		for (const row of sourceDocument.querySelectorAll('tr, li, article')) {
			const date=normalizeEscortiHistoryDate(row.textContent);
			if (!date) continue;
			const cityLink=[...row.querySelectorAll('a[href]')].find(anchor => {
				try {
					const url=new URL(anchor.getAttribute('href'),ESCORTI_BASE_URL);
					return /^\/escorts\/[^/]+\/?$/i.test(url.pathname) && !/\/(?:wszystkie|polska|poland)\/?$/i.test(url.pathname);
				} catch {
					return false;
				}
			});
			if (cityLink) add(date,cityLink.textContent);
		}
		return [...entries.values()].sort((a,b) => (profileDateToTime(a.date) || 0) - (profileDateToTime(b.date) || 0) ||
			a.city.localeCompare(b.city,'pl') );
	}
	function makeEscortiProfileResult(info) {
		return {
			status:'ok', profiles:1, profileUrl:info.profileUrl, profileUrls:[info.profileUrl], adLinks:info.adLinks, adLinksVersion:info.adLinksVersion,
			adUrls:info.adUrls, creationDate:info.creationDate, currentCity:info.currentCity, cityHistory:info.cityHistory,
			profileSummaries:[{
				profileUrl:info.profileUrl, profileName:info.profileName, creationDate:info.creationDate,
				currentCity:info.currentCity, adLinks:info.adLinks, cityHistory:info.cityHistory
			}], garsoTopics:info.garsoTopics, garsoTopicTitles:info.garsoTopicTitles
		};
	}
	function processEscortiProfilePage(mode,resultKey,mergeProfiles=false,stateKey=null) {
		let attempts=0;
		const timer=setInterval(() => {
			attempts++;
			const info=readEscortiProfileInfo();
			if (info && (info.adUrls.length > 0 || attempts >= 40)) {
				clearInterval(timer);
				const currentProfileUrl=`${location.origin}${location.pathname}`;
				info.profileUrl=currentProfileUrl;
				if (mode === 'check' && mergeProfiles && stateKey) {
					continueMultiProfileFlow(resultKey,stateKey,info,null);
					return;
				}
				if (mode === 'check') {
					finishEscortiCheck(resultKey,{
						...makeEscortiProfileResult(info), profileName:info.profileName, imageUrl:info.imageUrl
					});
				} else {
					removeVmParamsFromCurrentUrl();
				}
				return;
			}
			if (attempts >= 40) {
				clearInterval(timer);
				if (mode === 'check' && mergeProfiles && stateKey) {
					continueMultiProfileFlow(resultKey,stateKey,null,'Nie udało się odczytać danych profilu');
					return;
				}
				if (mode === 'check') finishEscortiCheck(resultKey,{status:'error',message:'Nie udało się odczytać danych profilu'});
			}
		},150);
	}
	function readEscortiProfileInfo(sourceDocument=document,pageUrl=location.href) {
		const bodyText=sourceDocument.body
			? (sourceDocument.body.innerText || sourceDocument.body.textContent || '')
			:'';
		const dateMatch=bodyText.match(/Data utworzenia profilu\s*:?\s*(\d{4}-\d{2}-\d{2}|\d{2}[.\-/]\d{2}[.\-/]\d{4})/i);
		const section=getEscortiAdLinkSection(sourceDocument);
		if (!dateMatch || !section) return null;
		const adUrls=getEscortiAdUrls(sourceDocument,pageUrl,section);
		const garsoTopicTitles=getEscortiGarsoTopicTitles(sourceDocument);
		return {
			creationDate:formatEscortiDate(dateMatch[1]), adUrls, adLinks:adUrls.length, adLinksVersion:1,
			profileName:extractWatchPageName(sourceDocument,'Profil Escorti'),
			imageUrl:extractWatchPageImage(sourceDocument,pageUrl), currentCity:extractEscortiCurrentCity(sourceDocument),
			cityHistory:extractEscortiCityHistory(sourceDocument), garsoTopicTitles, garsoTopics:garsoTopicTitles.length
		};
	}
	function normalizeEscortiAdUrl(href,baseUrl=location.href) {
		try {
			let raw=String(href || '').trim();
			if (!raw || /^[?#]/.test(raw)) return null;
			if (/^(?:(?:[a-z]{2}|www)\.)?escort\.club\//i.test(raw)) raw='https://' + raw;
			const relativeAd=/^\/?(?:anons|personal-ads)\/\d+\.html(?:[?#]|\/?$)/i.test(raw);
			const u=new URL(relativeAd ? '/' + raw.replace(/^\//,''):raw,relativeAd ? 'https://pl.escort.club/':baseUrl);
			if (!/^https?:$/.test(u.protocol) || !isEscortClubHost(u.hostname) || u.username || u.password) return null;
			const adId=getEscortAdIdFromPath(u.pathname);
			return adId ? `https://pl.escort.club/anons/${adId}.html`:null;
		} catch {
			return null;
		}
	}
	function getEscortiAdLinkSection(sourceDocument=document) {
		const ignored='.vm-helper-ui,.vm-favorites-ui,[data-vm-watch-wrapper],[id^="vm-"],.vm-escorti-ad-cache-badge,nav,aside,footer,script,template';
		const headings=[...sourceDocument.querySelectorAll('h1,h2,h3,h4,h5,h6')].filter(node => !node.closest(ignored));
		const index=headings.findIndex(node => /^linki do (?:anonsów|anonsow|ogłoszeń|ogloszen)\s*:?\s*(?:\(\d+\))?$/.test(normalizeText(node.textContent)));
		const heading=headings[index];
		if (!heading) return null;
		const end=headings[index+1],parent=heading.closest('section,article,.card,.panel') || heading.parentElement,range=sourceDocument.createRange();
		if (!end && (!parent || /^(?:BODY|HTML)$/.test(parent.tagName))) return null;
		range.setStartAfter(heading);
		if (end && parent.contains(end)) range.setEndBefore(end); else range.setEnd(parent,parent.childNodes.length);
		const elements=[...range.commonAncestorContainer.querySelectorAll('a[href],[data-href],[data-url],[data-link],[onclick]')]
			.filter(node => !node.closest(ignored) && !node.contains(heading) && !(end && node.contains(end)) && range.intersectsNode(node));
		return {heading,elements};
	}
	function getEscortiAdUrls(sourceDocument=document,baseUrl=location.href,section=getEscortiAdLinkSection(sourceDocument)) {
		const unique=new Set();
		const add=value => {const url=normalizeEscortiAdUrl(value,baseUrl); if (url) unique.add(url);};
		for (const node of section?.elements || []) {
			for (const name of ['href','data-href','data-url','data-link']) add(node.getAttribute(name));
			// Nie szukaj adresu anonsu wewnątrz obcego URL; onclick może zawierać bezpośredni cel.
			for (const match of (node.getAttribute('onclick') || '').replace(/\\\//g,'/').matchAll(/(['"])(.*?)\1/g)) add(match[2]);
		}
		return [...unique];
	}
	function findEscortiGarsoTable(sourceDocument=document) {
		const heading=[...sourceDocument.querySelectorAll('h1,h2,h3,h4,h5,h6')]
			.find(el => normalizeText(el.textContent).includes('znalezione dyskusje na forum garsoniera'));
		if (!heading) return null;
		let box=heading.parentElement;
		for (let level=0; level < 7 && box; level++,box=box.parentElement) {
			const table=box.querySelector('table');
			if (table) return table;
		}
		return null;
	}
	function getEscortiGarsoTopicTitles(sourceDocument=document) {
		const table=findEscortiGarsoTable(sourceDocument);
		if (!table) return [];
		const unique=new Map();
		for (const row of table.querySelectorAll('tbody > tr')) {
			const firstCell=row.querySelector('td');
			const title=(firstCell?.querySelector('a')?.textContent || firstCell?.textContent || '') .replace(/\s+/g,' ')
				.trim();
			if (!title) continue;
			const key=normalizeTopicTitleKey(title);
			if (key && !unique.has(key)) unique.set(key,title);
		}
		return [...unique.values()];
	}
	function startMultiProfileFlow(resultKey,profileUrls) {
		if (!resultKey || !profileUrls.length) {
			finishEscortiCheck(resultKey,{status:'error',message:'Brak profili do agregacji'});
			return;
		}
		const stateKey=`${resultKey}_multi`;
		writeHelperStorageValue(stateKey,{
			profileUrls:[...new Set(profileUrls)], index:0, results:[], errors:[]
		});
		openMultiProfileAtIndex(resultKey,stateKey,0);
	}
	function openMultiProfileAtIndex(resultKey,stateKey,index) {
		const state=GM_getValue(stateKey,null);
		if (!state || !Array.isArray(state.profileUrls) || !state.profileUrls[index]) {
			finishEscortiCheck(resultKey,{status:'error',message:'Błąd stanu agregacji profili'});
			return;
		}
		const u=new URL(state.profileUrls[index]);
		u.searchParams.set('vm_mode','check');
		u.searchParams.set('vm_key',resultKey);
		u.searchParams.set('vm_merge','1');
		u.searchParams.set('vm_state',stateKey);
		location.replace(u.href);
	}
	function continueMultiProfileFlow(resultKey,stateKey,info,errorMessage) {
		const state=GM_getValue(stateKey,null);
		if (!state || !Array.isArray(state.profileUrls)) {
			finishEscortiCheck(resultKey,{status:'error',message:'Utracono stan agregacji profili'});
			return;
		}
		if (info) state.results.push(info);
		if (errorMessage) state.errors.push({index:state.index,message:errorMessage});
		state.index += 1;
		writeHelperStorageValue(stateKey,state);
		if (state.index < state.profileUrls.length) {
			openMultiProfileAtIndex(resultKey,stateKey,state.index);
			return;
		}
		const merged=mergeEscortiProfileResults(state.results,state.profileUrls.length,state.errors.length,state.profileUrls);
		deleteHelperStorageValue(stateKey);
		finishEscortiCheck(resultKey,merged);
	}
	function normalizeEscortiCityHistory(entries) {
		return (Array.isArray(entries) ? entries:[]).map(entry => ({
			date:formatEscortiDate(entry?.date) || null, city:normalizeEscortCity(entry?.city) || null
		})).filter(entry => entry.date && entry.city);
	}
	function mergeEscortiProfileResults(results,totalProfiles,errorCount=0,allProfileUrls=[]) {
		if (!results.length) {
			return {
				status:'error', profiles:totalProfiles, message:'Nie udało się odczytać żadnego ze znalezionych profili'
			};
		}
		const adMap=new Map();
		const topicMap=new Map();
		let earliestDate=null;
		let earliestTime=null;
		const profileSummaries=[];
		for (const result of results) {
			for (const url of (result.adUrls || [])) {
				const normalized=normalizeEscortiAdUrl(url);
				if (!normalized) continue;
				const id=normalized.match(/\/anons\/(\d+)\.html$/i)?.[1] || normalized;
				adMap.set(id,normalized);
			}
			for (const title of (result.garsoTopicTitles || [])) {
				const key=normalizeTopicTitleKey(title);
				if (key && !topicMap.has(key)) topicMap.set(key,title);
			}
			const time=profileDateToTime(result.creationDate);
			if (time != null && (earliestTime == null || time < earliestTime)) {
				earliestTime=time;
				earliestDate=formatEscortiDate(result.creationDate);
			}
			profileSummaries.push({
				profileUrl:result.profileUrl || null, profileName:result.profileName || null,
				creationDate:result.creationDate || null, currentCity:result.currentCity || null,
				adLinks:Number.isFinite(Number(result.adLinks))
					? Number(result.adLinks)
					:null, cityHistory:normalizeEscortiCityHistory(result.cityHistory)
			});
		}
		const profileUrls=[...new Set( (allProfileUrls.length ? allProfileUrls:results.map(r => r.profileUrl))
				.filter(Boolean) .map(value => {
					try {
						const u=new URL(value,ESCORTI_BASE_URL);
						u.search='';
						u.hash='';
						return u.href;
					} catch {
						return null;
					}
				}) .filter(Boolean) )];
		return {
			status:'ok', profiles:totalProfiles, profileUrls, merged:totalProfiles > 1, partial:errorCount > 0,
			profileResultsRead:results.length, profileErrors:errorCount, adUrls:[...adMap.values()], adLinks:adMap.size, adLinksVersion:results.every(info => info.adLinksVersion === 1) ? 1:0,
			profileSummaries, cityHistory:profileSummaries.flatMap(profile => profile.cityHistory.map(entry => ({
					...entry, profileUrl:profile.profileUrl
				})) ), garsoTopicTitles:[...topicMap.values()], garsoTopics:topicMap.size, creationDate:earliestDate
		};
	}
	function finishEscortiCheck(resultKey,result) {
		if (resultKey) writeHelperStorageValue(resultKey,{...result,timestamp:Date.now()});
		if (window.top === window.self) setTimeout(() => {try {window.close();} catch {}},300);
	}
	function removeVmParamsFromCurrentUrl() {
		const url=new URL(location.href);
		['vm_mode','vm_key','vm_boot','vm_merge','vm_state'].forEach(k => url.searchParams.delete(k));
		history.replaceState(history.state,document.title,url.href);
	}
	async function fetchWatchDocumentDirect(url,cancelToken=null,options={}) {
		const response=await gmRequest({
			method:'GET', url, cancelToken, headers:{
				Accept:'text/html,application/xhtml+xml'
			}, ...options
		});
		const status=Number(response.status) || 0;
		const html=String(response.responseText || '');
		if ((status && (status < 200 || status >= 400)) || !html) {
			throw new Error(status ? `HTTP ${status}`:'Pusta odpowiedź serwera');
		}
		return {
			doc:new DOMParser().parseFromString(html,'text/html'), finalUrl:response.finalUrl || url
		};
	}
	async function searchEscortiProfilesDirect(searchValue,cancelToken=null,requestContext=null) {
		requestContext ||= createRequestContext('escorti.search');
		const searchUrl= `${ESCORTI_BASE_URL}search?search=${encodeURIComponent(searchValue)}`;
		const {doc,finalUrl}=await fetchWatchDocumentDirect( searchUrl, cancelToken, {requestContext} );
		const profileUrls=getEscortiProfileUrls(doc,finalUrl);
		const declaredProfileCount=getDeclaredProfileCount(doc);
		if (declaredProfileCount != null && declaredProfileCount !== profileUrls.length)
			throw new Error('Nie udało się odczytać pełnej listy profili Escorti');
		if (profileUrls.length > 0) {
			return {
				status:'ok', profiles:profileUrls.length, profileUrls
			};
		}
		if (declaredProfileCount === 0) {
			return {
				status:'ok', profiles:0, profileUrls:[]
			};
		}
		recordParserIssue( 'Escorti.pl - wyniki wyszukiwania',
			'Strona nie zawiera rozpoznanej listy profili ani jednoznacznego licznika zero.' );
		throw new Error('Nie udało się odczytać wyników wyszukiwania Escorti');
	}
	async function readEscortiProfileDirectUnshared(profileUrl,cancelToken=null,requestContext=null) {
		const {doc,finalUrl}=await fetchWatchDocumentDirect( profileUrl, cancelToken, {requestContext} );
		const info=readEscortiProfileInfo(doc,finalUrl);
		if (!info) {
			recordParserIssue( 'Escorti.pl - profil', 'Nie rozpoznano danych profilu w pobranym dokumencie.' );
			throw new Error('Nie udało się odczytać danych profilu Escorti');
		}
		const normalizedProfileUrl=new URL(finalUrl,ESCORTI_BASE_URL);
		normalizedProfileUrl.search='';
		normalizedProfileUrl.hash='';
		info.profileUrl=normalizedProfileUrl.href;
		return info;
	}
	const escortiRecentProfileData=new Map();
	let escortiRecentProfileEpoch=0;
	function clearEscortiRecentProfileData() {
		escortiRecentProfileData.clear();
		escortiRecentProfileEpoch++;
	}
	async function readEscortiProfileDirect(profileUrl,cancelToken=null,requestContext=null,reuseRecentProfileData=false) {
		cancelToken?.throwIfCancelled();
		requestContext ||= createRequestContext('escorti.profile');
		const key=normalizeProfileUrls({profileUrls:[profileUrl]})[0] || profileUrl;
		const ttlMs=Math.min(5 * 60 * 1000,getListCacheTtlMs());
		const cached=escortiRecentProfileData.get(key);
		if (reuseRecentProfileData && cached && Date.now() - cached.checkedAt < ttlMs) {
			return copyWatchedValue(cached.info);
		}
		escortiRecentProfileData.delete(key);
		const epoch=escortiRecentProfileEpoch;
		return runSharedHelperRequest('profile:' + key,async token => {
			const info=await readEscortiProfileDirectUnshared(key,token,requestContext);
			token?.throwIfCancelled();
			if (reuseRecentProfileData && epoch === escortiRecentProfileEpoch && ttlMs > 0) {
				const checkedAt=Date.now();
				for (const [id,entry] of escortiRecentProfileData) {
					if (checkedAt - entry.checkedAt >= ttlMs) escortiRecentProfileData.delete(id);
				}
				escortiRecentProfileData.set(key,{info:copyWatchedValue(info),checkedAt});
				if (escortiRecentProfileData.size > 512) escortiRecentProfileData.delete(escortiRecentProfileData.keys().next().value);
			}
			return info;
		},cancelToken);
	}
	async function checkEscortiDirect( searchValue, {mergeProfiles=false,cancelToken=null,requestContext=null,reuseRecentProfileData=false}={} ) {
		requestContext ||= createRequestContext('escorti.check');
		cancelToken?.throwIfCancelled();
		const searchResult=await searchEscortiProfilesDirect( searchValue, cancelToken, requestContext );
		if (searchResult.profiles === 0) return searchResult;
		if (mergeProfiles) {
			const results=[];
			const errors=[];
			for (const profileUrl of searchResult.profileUrls) {
				cancelToken?.throwIfCancelled();
				try {
					results.push(await readEscortiProfileDirect( profileUrl, cancelToken, requestContext, reuseRecentProfileData ));
				} catch (error) {
					if (isOperationCancelledError(error)) throw error;
					errors.push(error);
				}
			}
			return mergeEscortiProfileResults( results, searchResult.profiles, errors.length, searchResult.profileUrls );
		}
		if (searchResult.profiles === 1) {
			const info=await readEscortiProfileDirect( searchResult.profileUrls[0], cancelToken, requestContext, reuseRecentProfileData );
			return makeEscortiProfileResult(info);
		}
		return searchResult;
	}
	function checkEscortiBackground( searchValue, {
			timeoutMs=60000, mergeProfiles=false, preferDirect=false, cancelToken=null, requestContext=null, reuseRecentProfileData=false
		}={} ) {
		cancelToken?.throwIfCancelled();
		requestContext ||= createRequestContext('escorti.check');
		if (preferDirect) {
			return checkEscortiDirect(searchValue,{mergeProfiles,cancelToken,requestContext,reuseRecentProfileData}) .then(result => {
					if (result?.status === 'ok') return result;
					return checkEscortiBackground(searchValue,{
						timeoutMs, mergeProfiles, preferDirect:false, cancelToken, requestContext, reuseRecentProfileData
					});
				}) .catch(error => {
					if (isOperationCancelledError(error)) throw error;
					return checkEscortiBackground(searchValue,{
						timeoutMs, mergeProfiles, preferDirect:false, cancelToken, requestContext, reuseRecentProfileData
					});
				});
		}
		return new Promise(resolve => {
			const randomId=`${Date.now()}_${Math.random().toString(36).slice(2)}`;
			const resultKey=`vm_escorti_result_list_${randomId}`;
			const bootKey=`vm_escorti_boot_list_${randomId}`;
			const multiStateKey=`${resultKey}_multi`;
			deleteHelperStorageValue(resultKey);
			deleteHelperStorageValue(bootKey);
			deleteHelperStorageValue(multiStateKey);
			let finished=false;
			let iframeStarted=false,trace=null;
			let fallbackStarted=false;
			let iframe=null;
			let fallbackTimer=null;
			let totalTimer=null;
			let unsubscribeCancel=() => {};
			function cleanup() {
				if (fallbackTimer) clearTimeout(fallbackTimer);
				if (totalTimer) clearTimeout(totalTimer);
				unsubscribeCancel();
				try {detachHelperNode(iframe);} catch {}
				try {GM_removeValueChangeListener(resultListenerId);} catch {}
				try {GM_removeValueChangeListener(bootListenerId);} catch {}
				deleteHelperStorageValue(resultKey);
				deleteHelperStorageValue(bootKey);
				deleteHelperStorageValue(multiStateKey);
			}
			function finish(result) {
				if (finished) return;
				finished=true;
				trace?.(0,result?.status === 'ok' ? 'response':result?.status || 'error');
				cleanup();
				resolve(result);
			}
			const resultListenerId=GM_addValueChangeListener(resultKey,(_n,_o,newValue) => {
				if (!newValue || finished) return;
				finish(newValue);
			});
			const bootListenerId=GM_addValueChangeListener(bootKey,(_n,_o,newValue) => {
				if (!newValue || finished) return;
				iframeStarted=true;
				if (fallbackTimer) {
					clearTimeout(fallbackTimer);
					fallbackTimer=null;
				}
			});
			const mergeParam=mergeProfiles ? '&vm_merge=1':'';
			const iframeUrl=`${ESCORTI_BASE_URL}search?search=${encodeURIComponent(searchValue)}&vm_mode=check&vm_key=${encodeURIComponent(resultKey)}&vm_boot=${encodeURIComponent(bootKey)}${mergeParam}`;
			iframe=makeElement('iframe');
			iframe.src=iframeUrl;
			prepareHiddenHelperFrame(iframe);
			trace=recordDiagnosticRequest(iframeUrl,'IFRAME',requestContext,'iframe');
			document.body.appendChild(iframe);
			unsubscribeCancel=cancelToken?.onCancel(() => {
				finish({status:'cancelled'});
			}) || (() => {});
			fallbackTimer=setTimeout(() => {
				if (finished || iframeStarted || fallbackStarted) return;
				fallbackStarted=true;
				trace?.(0,'cancelled');
				try {detachHelperNode(iframe); iframe=null;} catch {}
				checkEscortiDirect(searchValue,{mergeProfiles,cancelToken,requestContext,reuseRecentProfileData}) .then(finish)
					.catch(error => finish(isOperationCancelledError(error)
						? {status:'cancelled'}
						:{
							status:'error', message:error?.message || 'Nie udało się odczytać Escorti'
						}));
			},4000);
			totalTimer=setTimeout(() => finish({status:'timeout'}),timeoutMs);
		});
	}
	function listCacheKey(adId) {
		return `vm_escorti_list_${adId}`;
	}
	function getEscortiProfileId(value) {
		try {
			const url=new URL(value,ESCORTI_BASE_URL);
			return url.pathname.match(/^\/escort\/(\d+)\/?$/i)?.[1] || null;
		} catch {
			return null;
		}
	}
	function buildEscortiProfileUrl(profileId) {
		return `${ESCORTI_BASE_URL}escort/${profileId}/`;
	}
	function normalizeProfileUrls(result) {
		const values=Array.isArray(result?.profileUrls)
			? result.profileUrls
			:(result?.profileUrl ? [result.profileUrl]:[]);
		const unique=new Set();
		for (const value of values) {
			try {
				const url=new URL(value,ESCORTI_BASE_URL);
				if ( !['escorti.pl','www.escorti.pl'].includes(url.hostname.toLowerCase()) ||
					!/^\/escort\/\d+\/?$/i.test(url.pathname) ) {
					continue;
				}
				url.protocol='https:';
				url.hostname='escorti.pl';
				url.pathname=`${url.pathname.replace(/\/+$/,'')}/`;
				url.search='';
				url.hash='';
				unique.add(url.href);
			} catch {}
		}
		return [...unique];
	}
	function getCachedEscortiAdUrlsForProfiles(profileUrls) {
		const wantedProfileIds=[...new Set( (Array.isArray(profileUrls) ? profileUrls:[]) .map(getEscortiProfileId)
				.filter(Boolean) )];
		if (!wantedProfileIds.length) return [];
		const index=getEscortiProfileAdIndexMemory();
		const adIds=new Set();
		for (const profileId of wantedProfileIds) {
			for (const adId of index.get(profileId) || []) adIds.add(adId);
		}
		return [...adIds].map(id => `https://pl.escort.club/anons/${id}.html`);
	}
	function normalizeEscortListProfileSummary(profile,compact=false) {
		const profileId=compact
			? getEscortiProfileId(profile?.profileUrl)
			:String(profile?.profileId || '').trim();
		if (!/^\d+$/.test(String(profileId || ''))) return null;
		const cityHistory=normalizeEscortiCityHistory(profile?.cityHistory);
		const common={
			profileName:normalizeEscortAdText(profile?.profileName) || null,
			creationDate:formatEscortiDate(profile?.creationDate) || null,
			currentCity:normalizeEscortCity(profile?.currentCity) || null, adLinks:Number.isFinite(Number(profile?.adLinks))
				? Number(profile.adLinks)
				:null, cityHistory
		};
		return compact
			? {profileId,...common}
			:{profileUrl:buildEscortiProfileUrl(profileId),...common};
	}
	function compactEscortListResult(result) {
		const profileUrls=Array.isArray(result?.profileUrls)
			? result.profileUrls
			:(result?.profileUrl ? [result.profileUrl]:[]);
		const profileIds=[...new Set(profileUrls.map(getEscortiProfileId).filter(Boolean))];
		const adIds=[...new Set( (Array.isArray(result?.adUrls) ? result.adUrls:[]) .map(parseAdIdFromUrl) .filter(Boolean)
		)];
		const rawProfileSummaries=Array.isArray(result?.profileSummaries)
			? result.profileSummaries
			:(profileIds.length === 1 ? [{
				profileUrl:profileUrls[0], profileName:result?.profileName || null, creationDate:result?.creationDate || null,
				currentCity:result?.currentCity || null, adLinks:result?.adLinks, cityHistory:result?.cityHistory || []
			}]:[]);
		const profileSummaries=rawProfileSummaries .map(profile => normalizeEscortListProfileSummary(profile,true))
			.filter(Boolean);
		return {
			profileIds, adIds, adLinksVersion:result?.adLinksVersion || 0, creationDate:result?.creationDate || null, profileSummaries,
			garsoTopicTitles:Array.isArray(result?.garsoTopicTitles) ? result.garsoTopicTitles:null,
			garsoTopics:Number.isFinite(result?.garsoTopics)
				? result.garsoTopics
				:null, partial:!!result?.partial, checkedAt:Date.now()
		};
	}
	function expandEscortListCacheEntry(entry,searchMode) {
		if (!entry || typeof entry !== 'object' || !entry.checkedAt) return null;
		if (searchMode !== 'phone-escort-search' && entry.adIds?.length && entry.adLinksVersion !== 1) return null;
		const profileIds=[...new Set( (Array.isArray(entry.profileIds) ? entry.profileIds:[])
				.map(value => String(value || '').trim()) .filter(value => /^\d+$/.test(value)) )];
		const adIds=[...new Set( (Array.isArray(entry.adIds) ? entry.adIds:[]) .map(value => String(value || '').trim())
				.filter(value => /^\d+$/.test(value)) )];
		const profileUrls=profileIds.map(buildEscortiProfileUrl);
		const adUrls=adIds.map(id => `https://pl.escort.club/anons/${id}.html`);
		const profileSummaries=(Array.isArray(entry.profileSummaries)
			? entry.profileSummaries
			:[]) .map(profile => normalizeEscortListProfileSummary(profile)) .filter(Boolean);
		return {
			status:'ok', searchMode, adLinksVersion:entry.adLinksVersion, profileIds, profileUrls, profiles:profileIds.length, merged:profileIds.length > 1,
			partial:!!entry.partial, adIds, adUrls, adLinks:adIds.length, creationDate:entry.creationDate || null,
			profileSummaries, currentCity:profileSummaries.length === 1
				? profileSummaries[0].currentCity
				:null, cityHistory:profileSummaries.flatMap(profile => profile.cityHistory.map(history => ({
					...history, profileUrl:profile.profileUrl
				})) ), garsoTopicTitles:Array.isArray(entry.garsoTopicTitles) ? entry.garsoTopicTitles:null,
			garsoTopics:Number.isFinite(entry.garsoTopics)
				? entry.garsoTopics
				:null, checkedAt:Number(entry.checkedAt)
		};
	}
	function getListCache(adId,searchMode=getEscortListSearchMode()) {
		try {
			const store=readPersistentCacheValue(listCacheKey(adId),null);
			return expandEscortListCacheEntry(store?.modes?.[searchMode],searchMode);
		} catch {
			return null;
		}
	}
	function setListCache(adId,result,searchMode=getEscortListSearchMode()) {
		if (!persistentCacheKeyIsLoaded(listCacheKey(adId))) {
			ensurePersistentCacheKeys([listCacheKey(adId)]).then(() => setListCache(adId,result,searchMode)).catch(error => log('Nie udało się scalić zapisu cache',error));
			return null;
		}
		if (result?.status !== 'ok') return;
		try {
			rememberHiddenResultAd(adId,result);
			const existing=readPersistentCacheValue(listCacheKey(adId),null);
			const modes=existing?.modes && typeof existing.modes === 'object'
				? {...existing.modes}
				:{};
			modes[searchMode]=compactEscortListResult(result);
			writePersistentCacheValue(listCacheKey(adId),{modes});
			registerEscortDataFieldsFromEscortiResult(result);
			invalidateEscortiProfileAdIndexMemory();
			if (String(getAdIdFromUrl()) === String(adId)) refreshHiddenItemsUi();
		} catch (e) {
			log('Nie udało się zapisać cache listy',e);
		}
	}
	function isListCacheFresh(cache) {
		return !!(cache && cache.checkedAt && Date.now() - cache.checkedAt < getListCacheTtlMs());
	}
	function parseAdIdFromUrl(url) {
		const canonical=normalizeEscortiAdUrl(url);
		return canonical ? getEscortAdIdFromPath(new URL(canonical).pathname):null;
	}
	function getRelatedAdsTileModeLabel(mode) {
		return {
			'address-escorti':'Escorti.pl wg adresu anonsu', 'phone-escorti':'Escorti.pl wg numeru telefonu',
			'phone-escort-search':'Escort.club wg numeru telefonu'
		}[normalizeEscortRelatedAdsTileLinkMode(mode)] || 'wybrana metoda';
	}
	function showRelatedAdsTileReport(report,error=false) {
		showWatchToast(report,error,{
			id:'vm-related-ads-tile-report',duration:error ? 6500:8000,
			style:{maxWidth:'420px',fontSize:'12px',lineHeight:'1.45',whiteSpace:'pre-line'}
		});
	}
	async function getTileRelatedAdUrls(adId,adUrl,mode) {
		const normalizedMode=normalizeEscortRelatedAdsTileLinkMode(mode);
		const requestContext=createRequestContext('tile.related');
		await ensureEscortPageCache([adId]);
		const cached=getListCache(adId,normalizedMode);
		if (isListCacheFresh(cached) && Array.isArray(cached.adUrls)) {
			return {
				urls:[...new Set(cached.adUrls.map(url => normalizeEscortiAdUrl(url) || url ).filter(Boolean))], source:'cache'
			};
		}
		let phone='';
		if (normalizedMode !== 'address-escorti') {
			let adData=getStoredEscortAdData(adId);
			phone=isEscortAdPhoneFresh(adData) ? getEscortAdPhoneDigits(adData):null;
			if (!phone) {
				adData=await getEscortAdData(adId,adUrl,false,null,requestContext);
				phone=isEscortAdPhoneFresh(adData) ? getEscortAdPhoneDigits(adData):null;
			}
			if (!phone) {
				throw new Error('Nie udało się odczytać numeru telefonu z tego anonsu.');
			}
		}
		if (normalizedMode === 'phone-escort-search') {
			const found=await fetchEscortClubPhoneSearchAds(phone,null,requestContext);
			const result={
				status:'ok', profileUrls:[], adUrls:found.adUrls || []
			};
			setListCache(adId,result,normalizedMode);
			return {
				urls:[...new Set(result.adUrls.map(url => normalizeEscortiAdUrl(url) || url ).filter(Boolean))],
				source:'wyszukiwanie'
			};
		}
		const searchValue=normalizedMode === 'phone-escorti' ? phone:adUrl;
		const result=await getOrStartListCheck( adId, searchValue, normalizedMode, null, requestContext );
		if (result?.status !== 'ok') {
			throw new Error(result?.message || 'Nie udało się znaleźć profilu tej osoby.');
		}
		return {
			urls:[...new Set((result.adUrls || []).map(url => normalizeEscortiAdUrl(url) || url ).filter(Boolean))],
			source:'wyszukiwanie'
		};
	}
	async function openAllActiveRelatedAdsFromTile(adId,adUrl,mode,onProgress=null) {
		const listResult=await getTileRelatedAdUrls(adId,adUrl,mode);
		const adUrls=listResult.urls;
		if (!adUrls.length) {
			return {
				found:0, active:0, opened:0, inactive:0, unknown:0, skippedByLimit:0, source:listResult.source
			};
		}
		if (onProgress) onProgress({phase:'activity',checked:0,total:adUrls.length});
		const summary=await scanActiveEscortAds( adUrls, progress => onProgress?.({phase:'activity',...progress}), false,
			{activityOnly:true,useActivityCache:true,cacheActivity:true} );
		const activeUrls=[...new Set( (summary?.results || []) .filter(result => result?.active === true)
				.map(result => normalizeEscortiAdUrl(result.url) || result.url) .filter(Boolean) )];
		const maxTabs=Math.max( 1, Math.floor(Number(SETTINGS.maxRelatedAdsTabs) || DEFAULT_SETTINGS.maxRelatedAdsTabs) );
		const toOpen=activeUrls.slice(0,maxTabs);
		for (const url of toOpen) {
			GM_openInTab(url,{active:false,insert:true});
		}
		return {
			found:adUrls.length, active:activeUrls.length, opened:toOpen.length, inactive:Math.max( 0,
				(summary?.totalChecked || 0) - activeUrls.length - (summary?.unknownCount || 0) ),
			unknown:summary?.unknownCount || 0, skippedByLimit:Math.max(0,activeUrls.length - toOpen.length),
			source:listResult.source
		};
	}
	function addRelatedAdsTileLinkButton(card,adId,adUrl) {
		let btn=card.querySelector('.vm-related-ads-open-button');
		if (btn) return btn;
		btn=makeElement('button');
		btn.type='button';
		btn.className='vm-related-ads-open-button';
		btn.textContent=SETTINGS.openAllActiveRelatedAdsFromTile ? 'Anonse ↗':'Profil ↗';
		const mode=normalizeEscortRelatedAdsTileLinkMode( SETTINGS.relatedAdsTileLinkMode );
		btn.title=SETTINGS.openAllActiveRelatedAdsFromTile
			? 'Otwórz wszystkie aktywne ogłoszenia tej osoby w nowych kartach'
			:(mode === 'address-escorti'
				? 'Otwórz profil tej osoby w Escorti.pl według adresu anonsu'
				:(mode === 'phone-escorti'
					? 'Otwórz profil tej osoby w Escorti.pl według numeru telefonu'
					:'Otwórz listę ogłoszeń tej osoby w wyszukiwarce Escort.club według numeru telefonu'));
		if (getComputedStyle(card).position === 'static') {
			card.style.position='relative';
		}
		Object.assign(btn.style,{
			position:'absolute', top:'4px', right:'2px', zIndex:'20', padding:'2px 5px',
			border:'1px solid rgba(255,255,255,.65)', borderRadius:'4px', background:'rgba(20,20,20,.72)', color:'#ffffff',
			fontSize:'9px', lineHeight:'1.2', fontWeight:'700', cursor:'pointer', boxShadow:'0 1px 3px rgba(0,0,0,.35)'
		});
		btn.addEventListener('click',async e => {
			e.preventDefault();
			e.stopPropagation();
			const selectedMode=normalizeEscortRelatedAdsTileLinkMode( SETTINGS.relatedAdsTileLinkMode );
			if (SETTINGS.openAllActiveRelatedAdsFromTile) {
				const previousText=btn.textContent;
				btn.disabled=true;
				btn.textContent='Szukam…';
				try {
					const report=await openAllActiveRelatedAdsFromTile( adId, adUrl, selectedMode, progress => {
							if (progress.phase === 'activity') {
								btn.textContent=progress.total
									? `${progress.checked}/${progress.total}`
									:'Sprawdzam…';
							}
						} );
					const lines=[
						`Znaleziono: ${formatPolishCount(report.found,'anons','anonse','anonsów')} • aktywne: ${report.active}`,
						`Metoda: ${getRelatedAdsTileModeLabel(selectedMode)} • lista: ${report.source === 'cache' ? 'z cache':'wyszukana teraz'}`,
						report.opened
							? `Otwieram: ${formatPolishCount(report.opened,'kartę','karty','kart')}`
							:'Nie otwarto żadnej karty' ];
					const omitted=[];
					if (report.skippedByLimit) {
						omitted.push(
							`${formatPolishCount(report.skippedByLimit,'aktywny anons','aktywne anonse','aktywnych anonsów')} pominięto przez limit`
						);
					}
					if (report.inactive) {
						omitted.push( `${formatPolishCount(report.inactive,'nieaktywny','nieaktywne','nieaktywnych')}` );
					}
					if (report.unknown) {
						omitted.push( `${formatPolishCount(report.unknown,'nierozpoznany','nierozpoznane','nierozpoznanych')}` );
					}
					if (omitted.length) lines.push(`Nie otwieram: ${omitted.join(' • ')}`);
					showRelatedAdsTileReport(lines.join('\n'),report.opened === 0);
				} catch (error) {
					log('Nie udało się otworzyć aktywnych ogłoszeń z kafelka',error);
					showRelatedAdsTileReport( error?.message || 'Nie udało się znaleźć aktywnych ogłoszeń tej osoby.', true );
				} finally {
					btn.disabled=false;
					btn.textContent=previousText;
				}
				return;
			}
			if (selectedMode === 'address-escorti') {
				GM_openInTab( buildEscortiSearchUrl(adUrl), {active:true,insert:true} );
				return;
			}
			const previousText=btn.textContent;
			btn.disabled=true;
			btn.textContent='…';
			try {
				let adData=getStoredEscortAdData(adId);
				let phone=isEscortAdPhoneFresh(adData) ? getEscortAdPhoneDigits(adData):null;
				if (!phone) {
					adData=await getEscortAdData(adId,adUrl,false);
					phone=isEscortAdPhoneFresh(adData) ? getEscortAdPhoneDigits(adData):null;
				}
				if (!phone) {
					window.alert('Nie udało się odczytać numeru telefonu z tego anonsu.');
					return;
				}
				const url=selectedMode === 'phone-escorti'
					? buildEscortiPhoneSearchUrl(phone)
					:buildEscortClubPhoneSearchUrl(phone);
				if (url) GM_openInTab(url,{active:true,insert:true});
			} catch (error) {
				log('Nie udało się otworzyć profilu z kafelka',error);
				window.alert('Nie udało się otworzyć profilu tej osoby.');
			} finally {
				btn.disabled=false;
				btn.textContent=previousText;
			}
		});
		card.appendChild(btn);
		return btn;
	}
	function getEscortProfileKeys(result) {
		if (!result || result.status !== 'ok' || result.profiles === 0) return [];
		const urls=Array.isArray(result.profileUrls)
			? result.profileUrls
			:(result.profileUrl ? [result.profileUrl]:[]);
		const keys=new Set();
		for (const value of urls) {
			try {
				const u=new URL(value,ESCORTI_BASE_URL);
				const m=u.pathname.match(/^\/escort\/(\d+)\/?$/i);
				if (m) keys.add(`escort:${m[1]}`);
				else keys.add(`${u.hostname.toLowerCase()}${u.pathname.replace(/\/$/,'')}`);
			} catch {}
		}
		return [...keys];
	}
	function buildEscortListCacheIndex(keys=null,index=null) {
		index ||= {
			byAdId:new Map(), freshProfileToAdIds:new Map(), adIdToFreshProfileKeys:new Map()
		};
		const listKeyPattern=/^vm_escorti_list_(\d+)$/i;
		const requiredSearchMode=getEscortListSearchMode();
		for (const key of keys || getEscortListCacheKeys()) {
			const match=key.match(listKeyPattern);
			if (!match) continue;
			const adId=match[1];
			try {
				const store=readPersistentCacheValue(key,null);
				const cache=expandEscortListCacheEntry( store?.modes?.[requiredSearchMode], requiredSearchMode );
				if (!cache) continue;
				const relatedAdIds=[...new Set([ adId, ...(Array.isArray(cache.adIds) ? cache.adIds:[])
				].map(String).filter(value => /^\d+$/.test(value)))];
				for (const relatedAdId of relatedAdIds) {
					const existing=index.byAdId.get(relatedAdId);
					if (!existing || Number(cache.checkedAt) > Number(existing.checkedAt || 0)) {
						index.byAdId.set(relatedAdId,cache);
					}
				}
				if (!isListCacheFresh(cache)) continue;
				const profileKeys=getEscortProfileKeys(cache);
				if (!profileKeys.length) continue;
				for (const relatedAdId of relatedAdIds) {
					const relatedKeys=index.adIdToFreshProfileKeys.get(relatedAdId) || new Set();
					for (const profileKey of profileKeys) {
						relatedKeys.add(profileKey);
						if (!index.freshProfileToAdIds.has(profileKey)) {
							index.freshProfileToAdIds.set(profileKey,new Set());
						}
						index.freshProfileToAdIds.get(profileKey).add(relatedAdId);
					}
					index.adIdToFreshProfileKeys.set(relatedAdId,relatedKeys);
				}
			} catch {}
		}
		return index;
	}
	function removeAdFromFreshCacheProfileIndex(index,adId) {
		const oldKeys=index.adIdToFreshProfileKeys.get(adId);
		if (!oldKeys) return;
		for (const profileKey of oldKeys) {
			const ids=index.freshProfileToAdIds.get(profileKey);
			if (!ids) continue;
			ids.delete(adId);
			if (!ids.size) {
				index.freshProfileToAdIds.delete(profileKey);
			}
		}
		index.adIdToFreshProfileKeys.delete(adId);
	}
	function updateEscortListCacheIndex(index,adId,result) {
		const relatedAdIds=[...new Set([ String(adId || ''), ...(Array.isArray(result?.adIds) ? result.adIds:[])
		].map(String).filter(value => /^\d+$/.test(value)))];
		for (const relatedAdId of relatedAdIds) {
			removeAdFromFreshCacheProfileIndex(index,relatedAdId);
		}
		if (!result || result.status !== 'ok') {
			return;
		}
		for (const relatedAdId of relatedAdIds) {
			index.byAdId.set(relatedAdId,result);
		}
		const profileKeys=getEscortProfileKeys(result);
		if (!profileKeys.length) return;
		for (const relatedAdId of relatedAdIds) {
			index.adIdToFreshProfileKeys.set(relatedAdId,new Set(profileKeys));
			for (const profileKey of profileKeys) {
				if (!index.freshProfileToAdIds.has(profileKey)) {
					index.freshProfileToAdIds.set(profileKey,new Set());
				}
				index.freshProfileToAdIds.get(profileKey).add(relatedAdId);
			}
		}
	}
	function normalizeEscortCachedPhone(value) {
		const digits=digitsOnly(value);
		if (digits.length < 8 || digits.length > 15) return null;
		if (digits.length > 7 && digits.length % 2 === 0) {
			const half=digits.length / 2;
			if (digits.slice(0,half) === digits.slice(half)) return null;
		}
		return digits;
	}
	function getEscortAdPhoneDigits(data) {
		for (const value of [data?.phoneDigits,data?.phone]) {
			const digits=normalizeEscortCachedPhone(value);
			if (digits) return digits;
		}
		return null;
	}
	function sanitizeEscortAdDataPhone(value) {
		if (!value || typeof value !== 'object') return value;
		const phone=getEscortAdPhoneDigits(value);
		const rawValues=[value.phoneDigits,value.phone] .map(candidate => String(candidate ?? '').trim()) .filter(Boolean);
		const malformedNumericPhone=rawValues.some(candidate => /^[+\d\s()./-]+$/.test(candidate) && digitsOnly(candidate) &&
			!normalizeEscortCachedPhone(candidate) );
		if (phone) {
			return normalizeEscortCachedPhone(value.phoneDigits) === phone
				? value
				:{...value,phoneDigits:phone};
		}
		return malformedNumericPhone
			? {
				...value, phone:null, phoneDigits:null, phoneFetchStatus:'invalid-cache'
			}
			:value;
	}
		function addEscortPhoneIndexEntry(index,adId,phone) {
		index.adIdToPhone.set(adId,phone);
		const ids=index.phoneToAdIds.get(phone) || new Set();
		ids.add(adId);
		index.phoneToAdIds.set(phone,ids);
	}
	function buildEscortAdPhoneCacheIndex(keys=null,index=null) {
		index ||= {
			phoneToAdIds:new Map(), adIdToPhone:new Map()
		};
		const adDataKeyPattern=/^vm_escort_ad_data_(\d+)$/i;
		for (const key of keys || getEscortAdDataCacheKeys()) {
			const match=key.match(adDataKeyPattern);
			if (!match) continue;
			const adId=match[1];
			try {
				const cache=readPersistentCacheValue(key,null);
				if ( !cache || cache.status !== 'ok' ) {
					continue;
				}
				const phone=isEscortAdPhoneFresh(cache) ? getEscortAdPhoneDigits(cache):null;
				if (!phone) continue;
				addEscortPhoneIndexEntry(index,adId,phone);
			} catch {}
		}
		return index;
	}
	function updateEscortAdPhoneCacheIndex(index,adId,adData) {
		const oldPhone=index.adIdToPhone.get(adId);
		if (oldPhone) {
			const oldIds=index.phoneToAdIds.get(oldPhone);
			oldIds?.delete(adId);
			if (oldIds && !oldIds.size) index.phoneToAdIds.delete(oldPhone);
			index.adIdToPhone.delete(adId);
		}
		const phone=adData?.status === 'ok' && isEscortAdPhoneFresh(adData)
			? getEscortAdPhoneDigits(adData)
			:null;
		if (!phone) return;
		addEscortPhoneIndexEntry(index,adId,phone);
	}
	function polishCountForm(value,one,few,many) {
		const n=Math.abs(Math.trunc(Number(value) || 0));
		if (n === 1) return one;
		const last=n % 10;
		const lastTwo=n % 100;
		return last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)
			? few
			:many;
	}
	function formatPolishCount(value,one,few,many) {
		const n=Math.max(0,Math.trunc(Number(value) || 0));
		return `${n} ${polishCountForm(n,one,few,many)}`;
	}
	function polishAdWord(n) {
		return polishCountForm(n,'anons','anonse','anonsów');
	}
	function getEscortListCardAgeAndCity(card) {
		const text=normalizeEscortAdText(card?.querySelector('.item-stats')?.textContent);
		const match=text.match(/^(.*?),\s*(\d{1,3})\s*y(?:\b|$)/i);
		return {
			city:match?.[1] ? normalizeText(match[1]):null, age:match?.[2] ? Number(match[2]):null
		};
	}
	function getEscortStatDifferenceLabels(stats) {
		return Array.isArray(stats) ? (stats.length > 2 ? ['wymiary']:stats):[];
	}
	function getEscortListGroupDataDifferences(members,cachedOnlyAdData=[]) {
		const cities=new Set();
		const ages=new Set();
		const statValues=new Map();
		const selectedPriceValues=new Set();
		const selectedPriceDuration=normalizeEscortPriceDuration( SETTINGS.searchResultPriceDuration );
		function collectAdData(adData,preferredCity=null,preferredAge=null) {
			const stats=adData?.stats && typeof adData.stats === 'object'
				? adData.stats
				:null;
			const city=normalizeText(preferredCity || adData?.location?.city);
			if (city) cities.add(city);
			let age=preferredAge;
			if (!Number.isFinite(age) && stats) {
				const ageEntry=Object.entries(stats).find(([label]) =>
					normalizeEscortChangeComparisonValue(label).replace(/:\s*$/,'') === 'wiek' );
				const ageMatch=normalizeEscortAdText(ageEntry?.[1]).match(/\d{1,3}/);
				age=ageMatch ? Number(ageMatch[0]):null;
			}
			if (Number.isFinite(age)) ages.add(age);
			if (!adData || adData.status !== 'ok') return;
			for (const [rawLabel,rawValue] of Object.entries(stats || {})) {
				const label=normalizeEscortChangeComparisonValue(rawLabel) .replace(/:\s*$/,'');
				if ( !label || label === 'wiek' || normalizeEscortClubPriceDurationKey(label) ) {
					continue;
				}
				const value=normalizeEscortChangeComparisonValue(rawValue);
				if (!value) continue;
				if (!statValues.has(label)) statValues.set(label,new Set());
				statValues.get(label).add(value);
			}
			const price=adData.prices?.[selectedPriceDuration];
			const hasAmount=price?.amount != null && Number.isFinite(Number(price.amount));
			const currency=normalizeEscortAdText(price?.currency).toUpperCase();
			selectedPriceValues.add( hasAmount
					? `${Number(price.amount)}|${currency}`
					:'__brak__' );
		}
		for (const member of members || []) {
			const cardData=getEscortListCardAgeAndCity(member?.card);
			const adData=member?.adData?.status === 'ok' ? member.adData:null;
			collectAdData(adData,cardData.city,cardData.age);
		}
		for (const adData of cachedOnlyAdData || []) {
			if (adData?.status === 'ok') collectAdData(adData);
		}
		const stats=[...statValues.entries()] .filter(([,values]) => values.size > 1) .map(([label]) => label);
		return {
			city:cities.size > 1, age:ages.size > 1, stats, price:selectedPriceValues.size > 1
		};
	}
	function getListAggregateLine(line) {
		if (!line?.parentElement) return null;
		let aggregateLine=line.parentElement.querySelector('.item-escorti-aggregate');
		if (aggregateLine) return aggregateLine;
		aggregateLine=makeElement('span');
		aggregateLine.className='item-escorti-aggregate';
		Object.assign(aggregateLine.style,{
			display:'none', width:'fit-content', maxWidth:'100%', marginTop:'2px', padding:'2px 4px',
			border:'1px solid rgba(255,255,255,.68)', borderRadius:'4px', background:'rgba(0,0,0,.18)', boxSizing:'border-box',
			fontSize:'9px', lineHeight:'1.15', fontWeight:'600', color:'#ffffff', whiteSpace:'normal',
			textShadow:'0 1px 2px rgba(0,0,0,.75)'
		});
		aggregateLine.addEventListener('click',e => {
			e.preventDefault();
			e.stopPropagation();
			if (typeof aggregateLine._vmToggleHandler === 'function') {
				aggregateLine._vmToggleHandler();
			}
		});
		line.insertAdjacentElement('afterend',aggregateLine);
		return aggregateLine;
	}
	function setListMergedInfo( line, activeCount, expanded=false, onToggle=null, cachedOnlyCount=0, dataDifferences=null
	) {
		if (!line) return;
		const count=Number(activeCount) || 0;
		const cacheCount=Math.max(0,Number(cachedOnlyCount) || 0);
		const listCount=Math.max(0,count - cacheCount);
		const aggregateLine=getListAggregateLine(line);
		if (!aggregateLine) return;
		aggregateLine._vmToggleHandler=typeof onToggle === 'function' ? onToggle:null;
		if (count > 1) {
			const arrow=onToggle ? ` ${expanded ? '▲':'▼'}`:'';
			const topLine=makeElement('span','',`Zagregowano ${listCount} ${polishAdWord(listCount)}${arrow}`);
			Object.assign(topLine.style,{
				display:'block', fontWeight:'700', whiteSpace:'nowrap'
			});
			aggregateLine.replaceChildren(topLine);
			if (cacheCount > 0) {
				const hiddenWord=polishCountForm(cacheCount,'niewidoczny','niewidoczne','niewidocznych');
				const sourceLine=makeElement('span','',`(+${cacheCount} ${hiddenWord} w tych wynikach)`);
				Object.assign(sourceLine.style,{
					display:'block', marginTop:'1px', fontSize:'8px', fontWeight:'500', whiteSpace:'nowrap', opacity:'.92'
				});
				aggregateLine.appendChild(sourceLine);
			}
			const differenceFields=[];
			if (dataDifferences?.age) differenceFields.push('wiek');
			if (dataDifferences?.city) differenceFields.push('miasto');
			differenceFields.push(...getEscortStatDifferenceLabels(dataDifferences?.stats));
			if (dataDifferences?.price) differenceFields.push('cena');
			if (differenceFields.length) {
				const warningLine=makeElement('span','',`⚠ Niezgodne: ${differenceFields.join(', ')}`);
				if (dataDifferences?.price) {
					warningLine.title= `Porównano cenę za ${getEscortPriceDurationLabel(SETTINGS.searchResultPriceDuration)}.`;
				}
				Object.assign(warningLine.style,{
					display:'block', width:'fit-content', maxWidth:'100%', marginTop:'2px', padding:'1px 3px', borderRadius:'3px',
					background:'#dc3545', color:'#fff', fontSize:'8px', fontWeight:'700', lineHeight:'1.2', whiteSpace:'nowrap',
					textShadow:'none'
				});
				aggregateLine.appendChild(warningLine);
			}
			aggregateLine.style.display='inline-block';
			aggregateLine.style.cursor=onToggle ? 'pointer':'default';
			aggregateLine.style.textDecoration='none';
			const toggleTitle=onToggle
				? (expanded ? 'Kliknij, aby ukryć scalone anonse':'Kliknij, aby pokazać scalone anonse')
				:'';
			const cacheTitle=cacheCount > 0
				? `${cacheCount} ${polishAdWord(cacheCount)} ${cacheCount === 1 ? 'nie jest widoczny':'nie są widoczne'} w tych wynikach i nie ma kafelków na obecnie załadowanej liście.`
				:'';
			const differencesTitle=differenceFields.length
				? `Zagregowane kafelki mają różne wartości: ${differenceFields.join(' i ')}.`
				:'';
			aggregateLine.title=[toggleTitle,cacheTitle,differencesTitle] .filter(Boolean) .join('\n');
		} else {
			aggregateLine.replaceChildren();
			aggregateLine.style.display='none';
			aggregateLine.style.cursor='default';
			aggregateLine.style.textDecoration='none';
			aggregateLine.title='';
		}
	}
	function renderListFavoriteStar(card,favorite) {
		let star=card.querySelector('.vm-list-favorite-star');
		if (!favorite) {detachHelperNode(star); return;}
		if (star) return;
		const name=card.querySelector('.item-name, .item-info');
		if (!name) return;
		star=makeElement('span','vm-list-favorite-star','★');
		star.title='W Ulubionych - anons lub jego profil';
		star.setAttribute('role','img');
		star.setAttribute('aria-label',star.title);
		Object.assign(star.style,{color:'#ffd54f',marginRight:'4px',textShadow:'0 1px 2px #000',fontSize:'24px',lineHeight:'1'});
		name.prepend(star);
	}
	function getListCardDataLine(card) {
		let line=card.querySelector('.item-escorti-stats');
		if (line) return line;
		const info=card.querySelector('.item-info');
		if (!info) return null;
		line=makeElement('span');
		line.className='item-escorti-stats';
		Object.assign(line.style,{
			display:'block', marginTop:'2px', fontSize:'9px', lineHeight:'1.15', fontWeight:'600', color:'#ffffff',
			whiteSpace:'nowrap', textShadow:'0 1px 2px rgba(0,0,0,.75)'
		});
		line.textContent='… ogł • od … • … opinii';
		info.appendChild(line);
		return line;
	}
	function getListCardPriceLine(card) {
		let line=card.querySelector('.vm-escort-tile-price');
		if (line) return line;
		if (getComputedStyle(card).position === 'static') {
			card.style.position='relative';
		}
		line=makeElement('span');
		line.className='vm-escort-tile-price';
		Object.assign(line.style,{
			display:'block', position:'absolute', top:SETTINGS.showRelatedAdsTileLink ? '22px':'4px', right:'2px', zIndex:'20',
			width:'fit-content', maxWidth:'100%', margin:'0', padding:'2px 5px', border:'1px solid rgba(255,255,255,.65)',
			borderRadius:'4px', background:'rgba(20,20,20,.72)', color:'#ffffff', fontSize:'9px', lineHeight:'1.2',
			fontWeight:'700', whiteSpace:'nowrap', textShadow:'0 1px 2px rgba(0,0,0,.55)', boxSizing:'border-box',
			boxShadow:'0 1px 3px rgba(0,0,0,.35)', pointerEvents:'none'
		});
		card.appendChild(line);
		return line;
	}
	function formatEscortTilePrice(price) {
		if ( !price || price.amount == null || price.amount === '' || !Number.isFinite(Number(price.amount)) ) {
			return '';
		}
		const amount=new Intl.NumberFormat('pl-PL',{
			minimumFractionDigits:0, maximumFractionDigits:2
		}).format(Number(price.amount));
		const currencyCode=String(price.currency || '').toUpperCase();
		const currency={
			PLN:'zł', EUR:'€', USD:'$', GBP:'£'
		}[currencyCode] || price.currency || '';
		return `${amount}${currency ? ` ${currency}`:''}`;
	}
	function formatEscortAggregatePrices(prices) {
		const groups=new Map(),unique=new Set();
		let count=0;
		for (const price of prices) {
			if (!['string','number'].includes(typeof price?.amount) || String(price.amount).trim() === '') continue;
			const formatted=formatEscortTilePrice(price);
			if (!formatted) continue;
			count++; unique.add(formatted);
			const currency=String(price.currency || '').trim().toUpperCase(),amount=Number(price.amount);
			const range=groups.get(currency);
			groups.set(currency,range ? [Math.min(range[0],amount),Math.max(range[1],amount)]:[amount,amount]);
		}
		const compact=[...groups].map(([currency,[min,max]]) => {
			const low=formatEscortTilePrice({amount:min,currency}),high=formatEscortTilePrice({amount:max,currency});
			return low === high ? high:`${formatEscortTilePrice({amount:min})}–${high}`;
		}).join(' • ');
		return {compact,full:[...unique].join(' • '),count};
	}
	function formatEscortAggregateField(descriptor,rawValues) {
		if (descriptor.kind === 'price') return formatEscortAggregatePrices(rawValues);
		const format=value => {try {return formatEscortUniversalDataValue(descriptor,value,false);} catch {return '';}};
		const present=value => value != null && (typeof value !== 'string' || value.trim() !== '') && (typeof value !== 'number' || Number.isFinite(value)) &&
			(typeof value !== 'object' || Object.keys(value).length > 0);
		const sources=rawValues.map(value => (Array.isArray(value) ? value.flat():[value]).filter(value => present(value) && !!format(value)));
		const values=sources.flat();
		const unique=[...new Set(values.map(format).filter(Boolean))];
		let compact=unique.join(' • ');
		const date=['date','datetime','relative-time'].includes(descriptor.kind);
		const identity=descriptor.path.some(key => /phone|telefon|(?:^|[_-])id$|Id$|Ids$/i.test(key));
		const stat=descriptor.path[0] === 'stats' && /^(?:wiek|age|wzrost|height|waga|weight|bmi|ocena|rating|score)$/i.test(descriptor.path[1] || '');
		if (values.length && (date || (!identity && (descriptor.kind === 'number' || stat)))) {
			const units=new Set();
			const ordered=values.map(value => {
				let number=NaN;
				if (date) {
					const text=String(value),parts=text.match(/^(\d{2})[.\/-](\d{2})[.\/-](\d{4})(?:,?\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
					number=typeof value === 'number' ? value:parts
						? Date.UTC(+parts[3],+parts[2] - 1,+parts[1],+parts[4] || 0,+parts[5] || 0,+parts[6] || 0):Date.parse(text);
				} else {
					const parsed=String(value).trim().match(/^([+-]?\d[\d\s]*(?:[.,]\d+)?)\s*([^\d]*)$/);
					if (parsed) {number=Number(parsed[1].replace(/\s/g,'').replace(',','.')); units.add(parsed[2].trim().toLowerCase());}
				}
				return {value,number};
			}).sort((a,b) => a.number - b.number);
			if (ordered.every(item => Number.isFinite(item.number)) && units.size <= 1) {
				const first=ordered[0],last=ordered[ordered.length - 1];
				const low=format(first.value),high=format(last.value),suffix=!date && low.match(/^[+-]?[\d\s.,]+(.*)$/)?.[1];
				compact=descriptor.kind === 'relative-time' || first.number === last.number || low === high ? high
					: `${suffix && high.toLowerCase().endsWith(suffix.toLowerCase()) ? low.slice(0,-suffix.length).trimEnd():low}–${high}`;
			}
		}
		return {compact:compact.length > 90 ? `${compact.slice(0,89)}…`:compact,full:unique.join(' • '),
			count:sources.filter(items => items.some(value => !!format(value))).length};
	}
	function getEscortAggregateAdData(member) {
		const data=member?.adData?.status === 'ok' ? member.adData:getStoredEscortAdData(member?.adId);
		return data?.status === 'ok' ? data:null;
	}
	function renderEscortListPrice(line,adData,durationValue,members=null) {
		if (!line) return;
		const aggregate=Array.isArray(members) && members.length > 1;
		Object.assign(line.style,{pointerEvents:aggregate ? 'auto':'none',overflow:aggregate ? 'hidden':'',textOverflow:aggregate ? 'ellipsis':''});
		const duration=normalizeEscortPriceDuration(durationValue);
		if (duration === 'hide') {
			line.style.display='none';
			line.textContent='';
			line.title='';
			return;
		}
		line.style.display='block';
		const durationLabel=getEscortPriceDurationLabel(duration);
		if (aggregate) {
			const data=members.map(getEscortAggregateAdData),value=formatEscortAggregatePrices(data.map(item => item?.prices?.[duration]));
			line.textContent=value.compact || (members.some(member => member?.adDataLoading) ? '…':data.every(Boolean) ? 'brak':'brak danych');
			line.style.background=value.compact ? getEscortPagePinkColor():'rgba(20,20,20,.72)';
			line.title=`Cena za ${durationLabel}\nDane dla ${value.count}/${members.length} anonsów\nWartości: ${value.full || 'b/d'}`;
			return;
		}
		const price=adData?.status === 'ok' && adData.prices
			? adData.prices[duration]
			:null;
		if (!adData) {
			line.textContent='…';
			line.style.background='rgba(20,20,20,.72)';
			line.title=`Trwa pobieranie ceny za ${durationLabel}`;
			return;
		}
		if (adData.status !== 'ok') {
			line.textContent='brak danych';
			line.style.background='rgba(120,35,45,.88)';
			line.title='Nie udało się odczytać cennika z pojedynczego anonsu';
			return;
		}
		const formattedPrice=formatEscortTilePrice(price);
		if (!formattedPrice) {
			line.textContent='brak';
			line.style.background='rgba(20,20,20,.72)';
			line.title=`W anonsie nie podano ceny za ${durationLabel}`;
			return;
		}
		line.textContent=formattedPrice;
		line.style.background=getEscortPagePinkColor();
		line.title=`Cena podana w anonsie za ${durationLabel}`;
	}
	function getListCardExtraDataLine(card) {
		let host=card.querySelector('.vm-escort-tile-extra-data-host');
		if (host) return host;
		if (getComputedStyle(card).position === 'static') card.style.position='relative';
		host=makeElement('div');
		host.className='vm-escort-tile-extra-data-host';
		const baseTop=SETTINGS.showRelatedAdsTileLink ? 22:4;
		const extraTop=baseTop + (isEscortSearchPriceDisplayed() ? 19:0);
		Object.assign(host.style,{
			display:'flex', position:'absolute', top:`${extraTop}px`, right:'2px', zIndex:'20', maxWidth:'78%', margin:'0',
			padding:'0', flexDirection:'column', alignItems:'flex-end', gap:'2px', boxSizing:'border-box', pointerEvents:'none'
		});
		card.appendChild(host);
		return host;
	}
	function updateEscortListActiveCounts(buckets,getCount) {
		for (const bucket of buckets) {
			const count=getCount(bucket);
			for (const member of bucket.members) {
				if (member.activeAdCount === count) continue;
				member.activeAdCount=count;
				renderEscortListExtraData(member.extraLine,member);
			}
		}
	}
	function getEscortTileProfileFieldValue(state,descriptor) {
		const key=descriptor?.path?.[0];
		if (!key) return null;
		if (key === 'activeCount') return state?.activeAdCount ?? null;
		if (key === 'totalAds') return state?.result?.adLinks != null && String(state.result.adLinks).trim() !== '' &&
			Number.isFinite(Number(state.result.adLinks)) ? Number(state.result.adLinks):null;
		if (key === 'profileDates') return state?.result?.creationDate ? [state.result.creationDate]:null;
		if (key === 'cities') return state?.adData?.location?.city ? [state.adData.location.city]:null;
		if (key === 'online') return state?.result ? true:null;
		if (key === 'checkedAt') return state?.result?.checkedAt || null;
		return getEscortDataValueByPath(state?.result || {},descriptor.path);
	}
	function getEscortTileFieldValue(state,descriptor) {
		if (!descriptor) return null;
		if (descriptor.source === 'garso') {
			const stats=getCachedGarsoStatsForAds([state?.adUrl || `https://pl.escort.club/anons/${state?.adId}.html`],getEscortAdPhoneDigits(state?.adData));
			return getGarsoDataValue(stats,descriptor);
		}
		if (descriptor.source === 'profile') {
			return getEscortTileProfileFieldValue( state, descriptor );
		}
		if (descriptor.source === 'escorti') {
			if ( descriptor.path?.[0] === 'historicalCityCount' ) {
				return getEscortiHistoricalCityCount( state?.result );
			}
			return getEscortDataValueByPath( state?.result || {}, descriptor.path );
		}
		const cachedAd= getStoredEscortAdData(state?.adId);
		if (descriptor.path?.[0] === 'lastVisitedAt') return cachedAd?.lastVisitedAt || null;
		return getEscortDataFieldRawValue(state?.adData?.status === 'ok' ? state.adData:(cachedAd || {}),descriptor);
	}
	function renderEscortListExtraData(host,state,fieldValues=getSearchResultExtraFields(),members=state?.aggregateMembers) {
		if (!host) return;
		const fields=normalizeSearchResultExtraFields(fieldValues);
		if (!fields.length) {
			host.replaceChildren();
			host.style.display='none';
			return;
		}
		host.style.display='flex';
		host.replaceChildren();
		for (const field of fields) {
			const descriptor=getEscortDataFieldDescriptor(field);
			if (!descriptor) continue;
			const aggregate=Array.isArray(members) && members.length > 1
				? descriptor.source === 'profile' && descriptor.path[0] === 'activeCount'
					? {compact:String(members.length),full:String(members.length),count:members.length}
					: formatEscortAggregateField(descriptor,members.map(member => getEscortTileFieldValue(member,descriptor))):null;
			const raw=aggregate ? null:getEscortTileFieldValue(state,descriptor);
			const compact=aggregate ? aggregate.compact:formatEscortUniversalDataValue(descriptor,raw,true);
			const full=aggregate ? `${descriptor.label}\nDane dla ${aggregate.count}/${members.length} anonsów\nWartości: ${aggregate.full || 'b/d'}`
				:formatEscortUniversalDataValue(descriptor,raw,false);
			const line=makeElement('span',"vm-escort-tile-extra-data",compact || 'b/d',{title:full || 'b/d'});
			Object.assign(line.style,{
				display:'block', width:'fit-content', maxWidth:'100%', margin:'0', padding:'2px 5px', overflow:'hidden',
				border:'1px solid rgba(255,255,255,.55)', borderRadius:'4px',
				background:compact ? 'rgba(20,20,20,.78)':'rgba(100,70,80,.72)', color:'#ffffff', fontSize:'9px', lineHeight:'1.2',
				fontWeight:'700', whiteSpace:'nowrap', textOverflow:'ellipsis', textShadow:'0 1px 2px rgba(0,0,0,.55)',
				boxSizing:'border-box', boxShadow:'0 1px 3px rgba(0,0,0,.35)', pointerEvents:'auto'
			});
			host.appendChild(line);
		}
		if (!host.childElementCount) host.style.display='none';
	}
	function renderListEscortiResult(line,result,stale=false) {
		if (!line) return;
		line.style.color='#ffffff';
		line.title=stale ? 'Dane z lokalnego cache - trwa odświeżanie w tle':'Dane z Escorti';
		if (result?.status === 'phone-missing') {
			line.textContent='Brak numeru';
			line.style.color='#ffcf9f';
			line.title='Nie można wyszukać w Escorti bez numeru telefonu';
			return;
		}
		if (!result || result.status === 'timeout' || result.status === 'error') {
			line.textContent='Błąd';
			line.style.color='#ffb3b3';
			line.title='Nie udało się pobrać danych z Escorti';
			return;
		}
		if (result.profiles === 0) {
			line.textContent='Nie znaleziono na Escorti';
			line.style.color='#b8dcff';
			line.title='Nie znaleziono profilu w Escorti';
			return;
		}
		if (result.profiles > 1 && !result.merged) {
			line.textContent='Kilka profili';
			line.title=`Znaleziono ${result.profiles} profile w Escorti`;
			return;
		}
		const ads=typeof result.adLinks === 'number' ? result.adLinks:'?';
		const date=result.creationDate ? formatShortDate(result.creationDate):'?';
		const opinions=typeof result.garsoTopics === 'number' ? result.garsoTopics:'?';
		const opinionsText=typeof opinions === 'number'
			? `${opinions} ${getGarsoTopicWord(opinions)}`
			:'? tematów';
		line.textContent=`${ads} ogł • od ${date} • ${opinionsText}`;
		if (result.merged && result.profiles > 1) {
			line.title=`Scalono ${result.profiles} profile Escorti; duplikaty anonsów i tematów policzono tylko raz${result.partial ? ' (część profili miała błąd odczytu)':''}`;
		}
	}
	async function getOrStartListCheck(adId,searchValue,searchMode=getEscortListSearchMode(),cancelToken=null,requestContext=null) {
		requestContext ||= createRequestContext('escorti.list');
		cancelToken?.throwIfCancelled();
		const normalized=searchMode === 'phone-escorti' ? normalizeEscortCachedPhone(searchValue) || '':String(searchValue || '').trim().toLowerCase();
		if (searchMode === 'phone-escorti' && !normalized) return {status:'phone-missing'};
		await ensurePersistentCacheKeys([listCacheKey(adId)]);
		cancelToken?.throwIfCancelled();
		const requestKey=`list:${searchMode}:${normalized}`;
		const result=await runSharedHelperRequest(requestKey,token =>
			checkEscortiBackground(searchValue,{timeoutMs:60000,mergeProfiles:true,preferDirect:true,cancelToken:token,requestContext,reuseRecentProfileData:true}),cancelToken);
		cancelToken?.throwIfCancelled();
		if (result?.status === 'ok') setListCache(adId,result,searchMode);
		return result;
	}
	function normalizeEscortListRouteKey(value) {
		try {
			const url=new URL(value,location.href);
			url.hash='';
			url.pathname=url.pathname
				.replace(/\/(?:page|strona)\d+\.html\/?$/i,'/')
				.replace(/\/(?:page|strona)-\d+\/?$/i,'/')
				.replace(/\/(?:page|strona)\/\d+\/?$/i,'/');
			for (const key of ['page','p','strona']) {
				url.searchParams.delete(key);
			}
			const sorted=[...url.searchParams.entries()]
				.sort(([ak,av],[bk,bv]) => ak.localeCompare(bk) || av.localeCompare(bv));
			url.search='';
			for (const [key,value] of sorted) {
				url.searchParams.append(key,value);
			}
			return `${url.hostname.toLowerCase()}${url.pathname}${url.search}`;
		} catch {
			return null;
		}
	}
	function getEscortListPageNumber(value) {
		try {
			const url=new URL(value,location.href);
			const pathPatterns=[ /\/(?:page|strona)(\d+)\.html\/?$/i, /\/(?:page|strona)-(\d+)\/?$/i,
				/\/(?:page|strona)\/(\d+)\/?$/i ];
			for (const pattern of pathPatterns) {
				const match=url.pathname.match(pattern);
				if (match) return Number(match[1]);
			}
			for (const key of ['page','p','strona']) {
				const valueFromQuery=url.searchParams.get(key);
				if (/^\d+$/.test(valueFromQuery || '')) {
					return Number(valueFromQuery);
				}
			}
			return 1;
		} catch {
			return 1;
		}
	}
	function getEscortPaginationLinkPageNumber(link,href,currentPage) {
		const parsed=getEscortListPageNumber(href);
		if (parsed > 1) return parsed;
		const text=normalizeEscortAdText(link?.textContent);
		if (/^\d+$/.test(text)) return Number(text);
		const hint=normalizeEscortAdText([ link?.getAttribute?.('aria-label'), link?.getAttribute?.('title'), text ]
			.filter(Boolean).join(' ')).toLocaleLowerCase('pl-PL');
		if (/następ|nastep|dalej|next|›|»/.test(hint)) return currentPage + 1;
		return parsed;
	}
	function findNextEscortListPageUrl(doc,baseUrl) {
		if (!doc) return null;
		const currentPage=getEscortListPageNumber(baseUrl);
		const routeKey=normalizeEscortListRouteKey(baseUrl);
		const normalizeCandidate=(link,relaxed=false) => {
			const href=link?.getAttribute?.('href');
			if (!href) return null;
			try {
				const url=new URL(href,baseUrl);
				if (!isEscortClubHost(url.hostname)) return null;
				const page=getEscortPaginationLinkPageNumber(link,url.href,currentPage);
				if (!Number.isFinite(page) || page <= currentPage) return null;
				const sameRoute=normalizeEscortListRouteKey(url.href) === routeKey;
				if (!sameRoute && !relaxed) return null;
				return {href:url.href,page};
			} catch {
				return null;
			}
		};
		const directNextSelectors=[ 'link[rel="next"][href]', 'a[rel="next"][href]', '.pagination .next a[href]',
			'.pagination a.next[href]', '.pager .next a[href]', '.pager a.next[href]', 'a[aria-label*="następ" i][href]',
			'a[title*="następ" i][href]', 'a[aria-label*="next" i][href]', 'a[title*="next" i][href]' ];
		const isPaginationElement=element => !!element && (
			element.matches?.('link[rel="next"], a[rel="next"]') || !!element.closest?.('.pagination, .pager, .pages, .ads-nav') );
		for (const selector of directNextSelectors) {
			const el=doc.querySelector(selector);
			const candidate=normalizeCandidate(el,isPaginationElement(el));
			if (candidate) return candidate.href;
		}
		const paginationLinks=[ ...doc.querySelectorAll(
				'.pagination a[href], .pager a[href], .pages a[href], .ads-nav a[href], a[href*="page"][href], a[href*="strona"][href]' ) ];
		const textualNext=paginationLinks.find(a => {
			const text=(a.textContent || '') .replace(/\s+/g,' ') .trim() .toLowerCase();
			return /^(następna|następny|dalej|next|›|»|>)$/.test(text);
		});
		const textualCandidate=normalizeCandidate(textualNext,isPaginationElement(textualNext));
		if (textualCandidate) return textualCandidate.href;
		const numbered=paginationLinks .map(a => normalizeCandidate(a,isPaginationElement(a))) .filter(Boolean)
			.filter(item => item.page > currentPage) .sort((a,b) => a.page - b.page);
		return numbered[0]?.href || null;
	}
	function normalizeEscortSectionHeading(value) {
		return normalizeText(value) .normalize('NFKD') .replace(/\p{M}/gu,'');
	}
	function isEscortMainResultsHeading(value) {
		return /anonse erotyczne|^escort in\b/i.test(normalizeEscortSectionHeading(value));
	}
	function isEscortSearchPath(pathname=location.pathname) {
		return !getEscortAdIdFromPath(pathname) && /^\/(?:szukaj|search|anonse|personal-ads)(?:\/|$)/i.test(String(pathname || ''));
	}
	function getEscortSelectOptionData(select) {
		if (!(select instanceof HTMLSelectElement)) return [];
		return [...select.options].map(option => ({
			value:String(option.value ?? ''), label:String(option.textContent || '').replace(/\s+/g,' ').trim()
		})).filter(option => option.label);
	}
	function findEscortSearchLocationControls(doc=document) {
		const searchBox=[...doc.querySelectorAll('.search-box')].find(box => ['country','province','city']
			.every(key => box.querySelector(`select[name="${key}"]`)) && box.querySelector('.btn-search[type="submit"]'));
		const searchButton=searchBox?.querySelector('.btn-search[type="submit"]');
		if (!searchButton || !searchBox) return null;
		return {
			searchBox, searchButton, submitItem:searchButton.closest('.form-item.-submit'),
			...Object.fromEntries(['country','province','city','district'].map(key => [key,searchBox.querySelector(`select[name="${key}"]`)]))
		};
	}
	function findEscortLocationOption(select,value,label='') {
		if (!(select instanceof HTMLSelectElement)) return null;
		const wantedValue=String(value ?? '');
		const wantedLabel=String(label || '').replace(/\s+/g,' ').trim() .toLocaleLowerCase('pl-PL');
		return [...select.options].find(option => wantedValue && String(option.value) === wantedValue
		) || [...select.options].find(option => wantedLabel && String(option.textContent || '') .replace(/\s+/g,' ') .trim()
				.toLocaleLowerCase('pl-PL') === wantedLabel ) || null;
	}
	function getEscortLocationSelectSignature(select) {
		return getEscortSelectOptionData(select) .map(option => `${option.value}:${option.label}`) .join('|');
	}
	function setEscortLocationControl(select,value,label='',strongChange=false) {
		const option=findEscortLocationOption(select,value,label);
		if (!option) return false;
		select.disabled=false;
		const changed=String(select.value ?? '') !== String(option.value ?? '');
		select.value=option.value;
		if (changed) {
			dispatchEscortFilterControlEvents(select);
			if (strongChange) {
				try {
					const jq=window.jQuery;
					if (jq) jq(select).trigger('change');
				} catch {}
			}
		}
		refreshEscortFilterSelect(select);
		return true;
	}
	function clearEscortLocationControl(select) {
		const empty=[...(select?.options || [])].find(option => !String(option.value || ''));
		if (!empty || String(select.value || '') === String(empty.value || '')) return false;
		select.value=empty.value;
		dispatchEscortFilterControlEvents(select);
		refreshEscortFilterSelect(select);
		return true;
	}
	function waitForEscortLocationOptions(controlName,predicate,timeoutMs=7000) {
		return new Promise((resolve,reject) => {
			const startedAt=Date.now();
			const check=() => {
				const controls=findEscortSearchLocationControls();
				const select=controls?.[controlName];
				if (select && predicate(select,controls)) {
					clearInterval(timer);
					resolve(controls);
					return;
				}
				if (Date.now() - startedAt >= timeoutMs) {
					clearInterval(timer);
					reject(new Error( controlName === 'district'
							? 'Escort.club nie udostępnił listy dzielnic.'
							:(controlName === 'city'
								? 'Escort.club nie udostępnił listy miast.'
								:'Escort.club nie udostępnił listy województw.') ));
				}
			};
			const timer=setInterval(check,100);
			check();
		});
	}
	async function loadEscortLocationOptions(locationData,options={}) {
		const selected=normalizeDefaultSearchLocation(locationData);
		const forceCountryChange=options?.forceCountryChange === true;
		let controls=findEscortSearchLocationControls();
		if (!controls?.country || !controls?.province || !controls?.city) {
			throw new Error( 'Kontrolki lokalizacji są dostępne na stronie wyszukiwania Escort.club.' );
		}
		if (options.useKnownIds && !selected.districtValue && !selected.districtLabel && ESCORT_RADIUS_CITIES.some(city =>
			String(city[4]) === selected.cityValue && String(ESCORT_RADIUS_PROVINCE_IDS[city[1]]) === selected.provinceValue)) {
			// Formularz zostanie od razu wysłany; zdarzenia change uruchomiłyby ponowne pobieranie list.
			for (const key of ['country','province','city','district']) {
				const control=controls[key],value=selected[`${key}Value`];
				if (!control) continue;
				if (![...control.options].some(option => option.value === value)) {
					control.appendChild(makeElement('option','',selected[`${key}Label`] || '',{value}));
				}
				control.value=value;
				control.disabled=false;
				refreshEscortFilterSelect(control);
			}
			return controls;
		}
		const previousCountry=String(controls.country.value || '');
		const provinceSignature=getEscortLocationSelectSignature(controls.province);
		if (!setEscortLocationControl( controls.country, selected.countryValue, selected.countryLabel, forceCountryChange )) {
			throw new Error('Nie znaleziono wybranego państwa w wyszukiwarce Escort.club.');
		}
		if (previousCountry !== String(controls.country.value || '')) {
			try {
				controls=await waitForEscortLocationOptions( 'province', select =>
						getEscortLocationSelectSignature(select) !== provinceSignature, 3500 );
			} catch {
				controls=findEscortSearchLocationControls() || controls;
			}
		}
		if (!selected.provinceValue && !selected.provinceLabel) return controls;
		if (!findEscortLocationOption( controls.province, selected.provinceValue, selected.provinceLabel )) {
			if (previousCountry === String(controls.country.value || '')) {
				dispatchEscortFilterControlEvents(controls.country);
				refreshEscortFilterSelect(controls.country);
			}
			controls=await waitForEscortLocationOptions( 'province', select => !!findEscortLocationOption( select,
					selected.provinceValue, selected.provinceLabel ), 5000 );
		}
		const previousProvince=String(controls.province.value || '');
		const citySignature=getEscortLocationSelectSignature(controls.city);
		if (!setEscortLocationControl( controls.province, selected.provinceValue, selected.provinceLabel )) {
			throw new Error('Nie znaleziono wybranego województwa w wyszukiwarce Escort.club.');
		}
		const cityAlreadyAvailable=selected.cityValue || selected.cityLabel
			? !!findEscortLocationOption( controls.city, selected.cityValue, selected.cityLabel )
			:getEscortSelectOptionData(controls.city).some(option => option.value);
		if (!cityAlreadyAvailable || previousProvince !== String(controls.province.value || '')) {
			if ( !cityAlreadyAvailable && previousProvince === String(controls.province.value || '') ) {
				dispatchEscortFilterControlEvents(controls.province);
				refreshEscortFilterSelect(controls.province);
			}
			controls=await waitForEscortLocationOptions( 'city', select => {
					if (selected.cityValue || selected.cityLabel) {
						return !!findEscortLocationOption( select, selected.cityValue, selected.cityLabel );
					}
					return getEscortLocationSelectSignature(select) !== citySignature &&
						getEscortSelectOptionData(select).some(option => option.value);
				} );
		}
		if (selected.cityValue || selected.cityLabel) {
			const previousCity=String(controls.city.value || '');
			const districtSignature=getEscortLocationSelectSignature(controls.district);
			if (!setEscortLocationControl( controls.city, selected.cityValue, selected.cityLabel )) {
				throw new Error('Nie znaleziono wybranego miasta w wyszukiwarce Escort.club.');
			}
			controls=findEscortSearchLocationControls() || controls;
			if (selected.districtValue || selected.districtLabel) {
				if (!controls.district) {
					throw new Error('Escort.club nie udostępnił pola dzielnicy.');
				}
				const districtAlreadyAvailable=!!findEscortLocationOption( controls.district, selected.districtValue,
					selected.districtLabel );
				if ( !districtAlreadyAvailable || previousCity !== String(controls.city.value || '') ) {
					if ( !districtAlreadyAvailable && previousCity === String(controls.city.value || '') ) {
						dispatchEscortFilterControlEvents(controls.city);
						refreshEscortFilterSelect(controls.city);
					}
					controls=await waitForEscortLocationOptions( 'district', select => !!findEscortLocationOption( select,
							selected.districtValue, selected.districtLabel ) || (
							getEscortLocationSelectSignature(select) !== districtSignature &&
							getEscortSelectOptionData(select).some(option => option.value) ) );
				}
				if (!setEscortLocationControl( controls.district, selected.districtValue, selected.districtLabel )) {
					throw new Error('Nie znaleziono wybranej dzielnicy w wyszukiwarce Escort.club.');
				}
			} else if (controls.district) {
				if (previousCity !== String(controls.city.value || '')) {
					try {
						controls=await waitForEscortLocationOptions( 'district', select =>
								getEscortLocationSelectSignature(select) !== districtSignature, 2500 );
					} catch {
						controls=findEscortSearchLocationControls() || controls;
					}
				}
				clearEscortLocationControl(controls.district);
			}
		} else {
			if (clearEscortLocationControl(controls.city)) controls=findEscortSearchLocationControls() || controls;
			clearEscortLocationControl(controls.district);
		}
		return controls;
	}
	function resolveEscortLocationLabels(value,doc=document) {
		const selected=normalizeDefaultSearchLocation(value),controls=findEscortSearchLocationControls(doc);
		const saved=(SETTINGS.savedLocationGroups || []).flat().concat(SETTINGS.searchLocations || [])
			.find(item => getEscortLocationKey(item) === getEscortLocationKey(selected));
		let sameParent=String(controls?.country?.value || '') === selected.countryValue;
		for (const key of ['province','city','district']) {
			const id=selected[key + 'Value'],label=key + 'Label';
			selected[label]=[selected[label],saved?.[label],sameParent ? findEscortLocationOption(controls?.[key],id)?.textContent?.trim():'']
				.find(text => text && text !== id) || '';
			sameParent &&= String(controls?.[key]?.value || '') === id;
		}
		const province=ESCORT_RADIUS_PROVINCE_IDS.findIndex(id => String(id) === selected.provinceValue);
		if (province >= 0) {
			selected.provinceLabel ||= ESCORT_RADIUS_PROVINCES[province].replace(/^./,letter => letter.toLocaleUpperCase('pl-PL'));
			selected.cityLabel ||= ESCORT_RADIUS_CITIES.find(city => city[1] === province && String(city[4]) === selected.cityValue)?.[0] || '';
		}
		return selected;
	}
	function getSavedLocationButtonLabel(locationData) {
		const selected=normalizeDefaultSearchLocation(locationData);
		const province=selected.provinceLabel || selected.provinceValue || '';
		const city=selected.cityLabel || selected.cityValue || '';
		const district=selected.districtLabel || selected.districtValue || '';
		if (city && district) return `${city}, ${district}`;
		return city || province || 'Lokalizacja';
	}
	function getEscortFullLocationLabel(locationData,separator=' › ') {
		const selected=normalizeDefaultSearchLocation(locationData);
		return ['province','city','district'].map(key => selected[key + 'Label'] || selected[key + 'Value'])
			.filter(Boolean).join(separator) || 'Lokalizacja';
	}
	function getEscortLocationGroupLabel(locations) {
		return normalizeSearchLocations(locations) .map(getSavedLocationButtonLabel) .join(' + ');
	}
	function getEscortLocationKey(locationData) {
		const location=normalizeDefaultSearchLocation(locationData);
		return ['country','province','city','district'].map(key => location[key + 'Value'] || location[key + 'Label'])
			.join('|').toLocaleLowerCase('pl-PL');
	}
	function isUsefulEscortLocation(locationData) {
		const location=normalizeDefaultSearchLocation(locationData);
		return !!( location.provinceValue || location.provinceLabel || location.cityValue || location.cityLabel );
	}
	function readEscortLocationFromControls(controls=findEscortSearchLocationControls()) {
		if (!controls?.province || !controls?.city) return null;
		const fields={};
		for (const key of ['province','city','district']) {
			const select=controls[key];
			if (!(select instanceof HTMLSelectElement) || !select.value) continue;
			fields[key + 'Value']=select.value;
			fields[key + 'Label']=select.selectedOptions?.[0]?.textContent;
		}
		const locationData=normalizeDefaultSearchLocation(fields);
		return isUsefulEscortLocation(locationData) ? locationData:null;
	}
	function getEscortActiveMultiLocations() {
		if (SETTINGS.multiLocationEnabled === false) return [];
		try {
			return normalizeSearchLocations(JSON.parse(sessionStorage.getItem(ESCORT_MULTI_LOCATION_SESSION_KEY)));
		} catch {
			return [];
		}
	}
	function saveEscortActiveMultiLocations(locations,resetResultCounts=true) {
		const normalized=normalizeSearchLocations(locations);
		try {
			if (normalized.length) {
				sessionStorage.setItem( ESCORT_MULTI_LOCATION_SESSION_KEY, JSON.stringify(normalized) );
			} else {
				sessionStorage.removeItem(ESCORT_MULTI_LOCATION_SESSION_KEY);
			}
		} catch {}
		if (resetResultCounts) clearEscortMultiLocationResultCounts();
		return normalized;
	}
	function addEscortActiveMultiLocation(locationData) {
		if (!isUsefulEscortLocation(locationData)) return getEscortActiveMultiLocations();
		const current=getEscortActiveMultiLocations();
		const key=getEscortLocationKey(locationData);
		if (!current.some(item => getEscortLocationKey(item) === key)) {
			if (current.length >= ESCORT_MULTI_LOCATION_MAX) {
				showWatchToast(`Możesz wybrać maksymalnie ${ESCORT_MULTI_LOCATION_MAX} lokalizacji. Najpierw usuń jedną z listy.`,true);
				return null;
			}
			current.push(normalizeDefaultSearchLocation(locationData));
		}
		return saveEscortActiveMultiLocations(current);
	}
	function removeEscortActiveMultiLocation(locationData) {
		const key=getEscortLocationKey(locationData);
		return saveEscortActiveMultiLocations(
			getEscortActiveMultiLocations().filter(item => getEscortLocationKey(item) !== key) );
	}
	function getEscortSavedLocationGroups() {
		return normalizeSavedLocationGroups( SETTINGS.savedLocationGroups );
	}
	function getEscortLocationGroupKey(locations) {
		return normalizeSearchLocations(locations) .map(getEscortLocationKey) .sort() .join('||');
	}
	function saveEscortSavedLocationGroups(groups) {
		const normalized=normalizeSavedLocationGroups(groups);
		updateSettings({
			searchLocations:[], savedLocationGroups:normalized
		});
		return normalized;
	}
	function addEscortSavedLocationGroup(locations) {
		const normalizedGroup=normalizeSearchLocations(locations);
		if (!normalizedGroup.length) return getEscortSavedLocationGroups();
		const current=getEscortSavedLocationGroups();
		const key=getEscortLocationGroupKey(normalizedGroup);
		if (!current.some(group => getEscortLocationGroupKey(group) === key)) {
			current.push(normalizedGroup);
		}
		return saveEscortSavedLocationGroups(current);
	}
	function removeEscortSavedLocationGroup(locations) {
		const key=getEscortLocationGroupKey(locations);
		return saveEscortSavedLocationGroups( getEscortSavedLocationGroups().filter(group =>
				getEscortLocationGroupKey(group) !== key ) );
	}
	function getEscortMultiLocationsForCurrentPage() {
		const currentLocation=readEscortLocationFromControls();
		const active=getEscortActiveMultiLocations();
		if (active.length > 1) {
			if (!currentLocation) return active;
			const currentKey=getEscortLocationKey(currentLocation);
			if (!active.some(item => getEscortLocationKey(item) === currentKey)) {
				return active;
			}
			return [ currentLocation, ...active.filter(item => getEscortLocationKey(item) !== currentKey) ];
		}
		return currentLocation ? [currentLocation]:active;
	}
	function polishLocationWord(count) {
		return polishCountForm(count,'miasto','miasta','miast');
	}
	function refreshEscortMultiLocationUi() {
		for (const refresh of escortMultiLocationUiRefreshers) {
			try {refresh();} catch {}
		}
	}
	function clearEscortMultiLocationResultCounts() {
		escortMultiLocationResultCounts.clear();
		escortMultiLocationPageCounts.clear();
		refreshEscortMultiLocationUi();
	}
	function setEscortMultiLocationResultCount(locationData,count,doc=document) {
		const key=getEscortLocationKey(locationData);
		if (!key) return;
		const resolved=resolveEscortLocationLabels(locationData,doc),active=getEscortActiveMultiLocations();
		if (active.some(item => getEscortLocationKey(item) === key && JSON.stringify(item) !== JSON.stringify(resolved))) {
			saveEscortActiveMultiLocations(active.map(item => getEscortLocationKey(item) === key ? resolved:item),false);
		}
		Object.assign(locationData,resolved);
		const normalizedCount=Math.max(0,Number(count) || 0);
		escortMultiLocationResultCounts.set(key,normalizedCount);
		escortMultiLocationPageCounts.set(key,getEscortResultsPageCount(doc));
		refreshEscortMultiLocationUi();
	}
	function getEscortMultiLocationResultCount(locationData) {
		const key=getEscortLocationKey(locationData);
		return escortMultiLocationResultCounts.has(key)
			? escortMultiLocationResultCounts.get(key)
			:null;
	}
	function getEscortMultiLocationKnownTotal(locations) {
		const group=normalizeSearchLocations(locations);
		if (group.length <= 1) return null;
		const counts=group.map(locationData => getEscortMultiLocationResultCount(locationData) );
		if ( counts.length !== group.length || counts.some(value => value == null || !Number.isFinite(Number(value)) ||
				Number(value) < 0 ) ) {
			return null;
		}
		return counts.reduce( (sum,value) => sum + Number(value), 0 );
	}
	function getEscortResultsPageCount(doc=document) {
		const counters=[...doc.querySelectorAll('.pagination, .pager, .pages, .ads-nav')];
		let pages=1;
		for (const counter of counters) {
			const total=(counter.textContent || '').match(/\b\d+\s*(?:z|\/)\s*(\d+)\b/i);
			if (total) pages=Math.max(pages,Number(total[1]));
			for (const link of counter.querySelectorAll('a[href]')) {
				const url=new URL(link.getAttribute('href'),location.href);
				if (isEscortClubHost(url.hostname) && /\/(?:anonse|search)\//.test(url.pathname)) pages=Math.max(pages,getEscortListPageNumber(url.href));
			}
		}
		return pages;
	}
	function formatEscortResultPages(text,pages) {
		return `${text} (${pages} ${polishCountForm(pages,'strona','strony','stron')})`;
	}
	function markEscortResultCounter(element) {
		if (!element) return;
		element.classList.add('vm-escort-results-count');
		element.dataset.vmOriginalText ??= element.textContent;
		element.textContent=formatEscortResultPages(element.dataset.vmOriginalText,getEscortResultsPageCount());
	}
	function setEscortMultiResultCountersVisible(visible) {
		for (const element of document.querySelectorAll( '.search-links .sub-label, .ads-nav p, .vm-escort-results-count' )) {
			const textValue=normalizeEscortAdText( element.textContent );
			const isResultCounter= /^(?:Lista\s+wyników?|Wyświetlane|Załadowano)\b/i .test(textValue);
			if (!isResultCounter) continue;
			if (visible) {
				if ( element.dataset.vmMultiResultCounterHidden === '1' ) {
					element.style.removeProperty('display');
					delete element.dataset.vmMultiResultCounterHidden;
				}
			} else {
				element.dataset.vmMultiResultCounterHidden='1';
				element.style.setProperty( 'display', 'none', 'important' );
			}
		}
	}
	function setEscortMultiNoResultsMessageVisible(visible) {
		const section=getEscortMainResultsSection(document);
		const root=section || document;
		const matches=[...root.querySelectorAll('*')].filter(element =>
			/^(?:brak\s+(?:anons(?:ów|ow)|ogłoszeń|ogloszen)|no\s+(?:ads|adverts|advertisements)(?:\s+found)?)[.!]?$/i.test(
				normalizeEscortAdText(element.textContent) ) );
		const matchingSet=new Set(matches);
		const outerMatches=matches.filter(element => {
			let parent=element.parentElement;
			while (parent && parent !== root) {
				if (matchingSet.has(parent)) return false;
				parent=parent.parentElement;
			}
			return true;
		});
		for (const element of outerMatches) {
			if (!visible) {
				if (element.dataset.vmMultiNoResultsHidden !== '1') {
					element.dataset.vmMultiNoResultsHidden='1';
					element.dataset.vmMultiNoResultsDisplay= element.style.getPropertyValue('display') || '';
					element.dataset.vmMultiNoResultsDisplayPriority= element.style.getPropertyPriority('display') || '';
				}
				element.style.setProperty('display','none','important');
			} else if (element.dataset.vmMultiNoResultsHidden === '1') {
				const display=element.dataset.vmMultiNoResultsDisplay || '';
				const priority=element.dataset.vmMultiNoResultsDisplayPriority || '';
				if (display) element.style.setProperty('display',display,priority);
				else element.style.removeProperty('display');
				delete element.dataset.vmMultiNoResultsHidden;
				delete element.dataset.vmMultiNoResultsDisplay;
				delete element.dataset.vmMultiNoResultsDisplayPriority;
			}
		}
	}
	function ensureEscortMultiLocationUiStyle() {
		if (document.getElementById('vm-escort-multi-location-style')) return;
		appendHelperStyle('vm-escort-multi-location-style',`
.search-box form, .search-box .row { flex-wrap:wrap; }
#${ESCORT_MULTI_LOCATION_UI_ID} .vm-multi-submit-button.-end { display:block; min-height:40px; border:2px solid var(--vm-ui-accent,#f54da3) !important; border-radius:6px !important; background:var(--vm-ui-bg,#fff) !important; color:var(--vm-ui-text,#332c31) !important; -webkit-text-fill-color:currentColor !important; text-shadow:none !important; box-shadow:none !important; font-size:12px !important; line-height:1.05 !important; white-space:normal !important; overflow:hidden !important; }
.vm-multi-end-inline { width:150px !important; min-width:150px !important; flex:0 0 150px; }
#${ESCORT_MULTI_LOCATION_UI_ID} .vm-multi-submit-button.-end:hover { background:var(--vm-ui-bg-hover,color-mix(in srgb,var(--vm-ui-accent,#f54da3) 7%,transparent)) !important; }
.vm-radius-link-row { margin:2px auto 7px; text-align:right; }
#vm-escort-radius-open { width:auto; min-height:0; margin:0; padding:0; border:0; background:none; box-shadow:none; color:var(--vm-ui-text-muted,#756b72); font:400 12px/1.4 var(--vm-ui-font-family,Lato,Arial,sans-serif); text-decoration:underline; text-underline-offset:2px; cursor:pointer; }
#vm-escort-radius-open:hover, #vm-escort-radius-open:focus-visible { color:var(--vm-ui-accent,#f54da3); }
.vm-multi-panel { box-sizing:border-box; width:100%; max-width:var(--vm-multi-max-width,1120px); font:11px/1.35 var(--vm-ui-font-family,Lato,Arial,sans-serif); }
#${ESCORT_MULTI_LOCATION_UI_ID}.vm-multi-panel { margin:8px auto 3px; }
[data-vm-saved-locations-ui="1"].vm-multi-panel { margin:0 auto 8px; }
.vm-multi-section { flex-direction:column; align-items:stretch; gap:7px; padding:10px 12px; border-radius:5px; }
.vm-multi-section.-active { display:none; border:1px solid var(--vm-ui-accent,#f54da3); background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 7%,transparent); color:var(--vm-ui-text,#332c31); }
.vm-multi-section.-saved { margin-top:7px; border:1px solid var(--vm-ui-border,#c8d0d7); background:var(--vm-ui-bg-alt,#f5f7f8); color:var(--vm-ui-text,#3f4850); }
.vm-multi-top-row { display:flex; align-items:center; justify-content:space-between; gap:10px; }
.vm-multi-top-row.-active, .vm-multi-top-row.-saved { flex-wrap:wrap; }
.vm-multi-label { font-size:11px; letter-spacing:.03em; }
.vm-multi-label.-active { color:var(--vm-ui-accent,#f54da3); }
.vm-multi-label.-saved { color:var(--vm-ui-text-muted,#52606b); }
.vm-multi-active-box { display:grid; grid-template-columns:repeat(auto-fit,minmax(185px,1fr)); align-items:stretch; gap:7px; }
.vm-multi-saved-box { display:flex; align-items:center; flex-wrap:wrap; gap:6px; }
.vm-multi-save-current { padding:6px 10px; border:1px solid var(--vm-ui-accent,#f54da3); border-radius:5px; background:var(--vm-ui-accent,#f54da3); color:var(--vm-ui-accent-contrast,#fff); cursor:pointer; font:800 10px var(--vm-ui-font-family,Lato,Arial,sans-serif); box-shadow:var(--vm-ui-shadow,0 1px 2px rgba(0,0,0,.12)); }
.vm-multi-active-card { position:relative; display:flex; flex-direction:column; justify-content:center; min-width:0; min-height:48px; padding:7px 31px 7px 10px; border:1px solid var(--vm-ui-accent,#f54da3); border-radius:4px; background:var(--vm-ui-bg,#fff); box-shadow:var(--vm-ui-shadow,0 1px 2px rgba(0,0,0,.05)); }
.vm-multi-active-text { color:var(--vm-ui-text,#2f292d); font-size:12px; font-weight:700; line-height:1.25; overflow-wrap:anywhere; }
.vm-multi-count { margin-top:3px; color:var(--vm-ui-accent,#f54da3); font-size:10px; font-weight:700; }
.vm-multi-count.-pending { color:var(--vm-ui-text-faint,#91878d); }
.vm-multi-active-remove { position:absolute; top:50%; right:8px; transform:translateY(-50%); width:20px; height:20px; padding:0; border:1px solid var(--vm-ui-accent,#f54da3); border-radius:50%; background:var(--vm-ui-bg,#fff); color:var(--vm-ui-accent,#f54da3); cursor:pointer; font:800 14px/17px var(--vm-ui-font-family,Arial,sans-serif); }
.vm-multi-saved-chip { display:inline-flex; align-items:center; gap:6px; padding:6px 7px 6px 10px; border:1px solid var(--vm-ui-border-strong,#aeb8c1); border-radius:4px; background:var(--vm-ui-bg,#fff); white-space:normal; box-shadow:var(--vm-ui-shadow,0 1px 1px rgba(0,0,0,.04)); }
.vm-multi-saved-action { padding:0; border:0; background:transparent; color:var(--vm-ui-text,#35424d); cursor:pointer; font:700 11px var(--vm-ui-font-family,Lato,Arial,sans-serif); white-space:normal; text-align:left; }
.vm-multi-saved-remove { width:18px; height:18px; flex:0 0 18px; padding:0; border:0; border-radius:50%; background:var(--vm-ui-bg-hover,#eef1f3); color:var(--vm-ui-text-muted,#707b84); cursor:pointer; font:800 12px/16px var(--vm-ui-font-family,Arial,sans-serif); }
`);
	}
	function normalizeRadiusCityName(value) {
		return String(value || '').toLowerCase().replace(/\s*\(\d+\)\s*$/,'').normalize('NFD')
			.replace(/[\u0300-\u036f]/g,'').replace(/ł/g,'l').replace(/[^a-z0-9]/g,'');
	}
	function escortRadiusDistanceKm(center,city) {
		const rad=Math.PI / 180,dLat=(city[2] - center.lat) * rad,dLng=(city[3] - center.lng) * rad;
		const a=Math.sin(dLat / 2) ** 2 + Math.cos(center.lat * rad) * Math.cos(city[2] * rad) * Math.sin(dLng / 2) ** 2;
		return 12742.0176 * Math.asin(Math.sqrt(Math.min(1,Math.max(0,a))));
	}
	function getEscortRadiusCities(center,radiusKm) {
		if (![center?.lat,center?.lng,radiusKm].every(Number.isFinite) || Math.abs(center.lat) > 90 || Math.abs(center.lng) > 180 || radiusKm < 1 || radiusKm > 200) return [];
		return ESCORT_RADIUS_CITIES.map(city => ({city,distance:escortRadiusDistanceKm(center,city)}))
			.filter(entry => entry.distance <= radiusKm).sort((a,b) => a.distance - b.distance || a.city[0].localeCompare(b.city[0],'pl'));
	}
	function resolveEscortRadiusCities(cities) {
		return cities.map(city => normalizeDefaultSearchLocation({
			provinceValue:ESCORT_RADIUS_PROVINCE_IDS[city[1]],provinceLabel:ESCORT_RADIUS_PROVINCES[city[1]],
			cityValue:city[4],cityLabel:city[0]
		}));
	}
	async function openEscortRadiusMap(onApply) {
		const L=await loadEscortRadiusLeaflet();
		if (document.getElementById('vm-escort-radius-overlay')) return;
		if (typeof L === 'undefined') throw new Error('Nie udało się załadować mapy. Zaktualizuj skrypt i odśwież stronę.');
		if (!document.getElementById('vm-escort-radius-style')) {
			appendHelperStyle('vm-escort-radius-style',GM_getResourceText('escortRadiusLeafletCss') + `
#vm-escort-radius-overlay .vm-ui-dialog { width:min(980px,calc(100vw - 24px)); padding:12px; }
#vm-escort-radius-overlay .vm-ui-title { font-size:18px; margin:0 0 8px; }
#vm-escort-radius-overlay .vm-radius-toolbar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:7px; }
#vm-escort-radius-overlay .vm-radius-toolbar label { margin:0; }
#vm-escort-radius-overlay .vm-radius-toolbar input[type=number] { width:78px; margin-left:6px; }
#vm-escort-radius-overlay .vm-radius-slider { flex:1 1 180px; min-width:120px; max-width:420px; height:20px; margin:0; padding:0; border:0; background:transparent; accent-color:var(--vm-ui-accent,#f54da3); cursor:pointer; }
#vm-escort-radius-map { height:clamp(220px,46vh,440px); width:100%; border:1px solid var(--vm-ui-border,#bbb); border-radius:6px; background:#ddd; color:#222; }
#vm-escort-radius-map .leaflet-control-attribution { color:#333; }
#vm-escort-radius-map .vm-radius-pin { background:none; border:0; font-size:30px; line-height:36px; text-align:center; }
#vm-escort-radius-overlay .vm-radius-cities { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:3px 32px; max-height:150px; overflow:auto; margin-top:6px; }
#vm-escort-radius-overlay .vm-radius-city { display:grid; grid-template-columns:auto minmax(0,1fr) 64px; align-items:center; gap:6px; margin:0; font-size:12px; cursor:pointer; }
#vm-escort-radius-overlay .vm-radius-city > span { min-width:0; overflow-wrap:anywhere; }
#vm-escort-radius-overlay .vm-radius-city small { text-align:right; color:var(--vm-ui-text-muted,#666); white-space:nowrap; font-variant-numeric:tabular-nums; }
#vm-escort-radius-overlay .vm-radius-status { margin:5px 0; font-size:12px; min-height:1.4em; }
#vm-escort-radius-overlay .vm-radius-status:empty { display:none; }
#vm-escort-radius-overlay .vm-ui-actions { margin-top:8px; padding-top:8px; }
`);
		}
		const native=readEscortLocationFromControls();
		const nativeCity=ESCORT_RADIUS_CITIES.find(city => normalizeRadiusCityName(city[0]) === normalizeRadiusCityName(native?.cityLabel) &&
			(!native?.provinceLabel || normalizeRadiusCityName(ESCORT_RADIUS_PROVINCES[city[1]]) === normalizeRadiusCityName(native.provinceLabel)));
		let center=nativeCity ? {lat:nativeCity[2],lng:nativeCity[3]}:{lat:52,lng:19};
		let hasCenter=!!nativeCity,revision=0,closed=false,applying=false,map=null,selected=new Set(),matches=[];
		const {overlay,box}=createWatchDialogBase('vm-escort-radius-overlay','Miasta w promieniu');
		const controller=createDialogCloseController(overlay,{closeOnBackdrop:false,closeOnEscape:false,
			onClose:() => {closed=true; map?.remove();}});
		const toolbar=makeElement('div','vm-radius-toolbar');
		const radiusLabel=makeElement('label','','Promień:');
		const radius=makeElement('input','vm-ui-field',null,{type:'number',min:'1',max:'200',step:'1',value:'20',required:true});
		radius.setAttribute('aria-label','Promień wyszukiwania w kilometrach');
		radiusLabel.append(radius,makeElement('span','',' km'));
		const slider=makeElement('input','vm-radius-slider');
		Object.assign(slider,{type:'range',min:radius.min,max:radius.max,step:radius.step,value:radius.value});
		slider.setAttribute('aria-label','Promień wyszukiwania w kilometrach - suwak');
		toolbar.append(radiusLabel,slider);
		const mapHost=makeElement('div'); mapHost.id='vm-escort-radius-map';
		const help=makeElement('div','vm-ui-help','Kliknij mapę lub przeciągnij pineskę. Promień jest liczony w linii prostej do środka miasta.');
		const count=makeElement('div','vm-radius-status'); count.setAttribute('role','status');
		const list=makeElement('div','vm-radius-cities');
		const status=makeElement('div','vm-radius-status'); status.setAttribute('role','status');
		const actions=makeElement('div','vm-ui-actions');
		const cancel=makeButton('vm-ui-button','Anuluj'),apply=makeButton('vm-ui-button -primary','Zapisz');
		controller.bindButton(cancel); actions.append(cancel,apply);
		box.append(toolbar,mapHost,help,count,list,status,actions);
		try {map=L.map(mapHost,{scrollWheelZoom:true}).setView(center,nativeCity ? 9:6);}
		catch (error) {controller.close('error'); throw error;}
		L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:18,
			attribution:'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> · <a href="https://www.geonames.org/" title="Współrzędne miast, zaokrąglone do 4 miejsc">GeoNames</a> (<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>)'}).addTo(map)
			.on('tileerror',() => {if (!closed) status.textContent='Nie udało się wczytać podkładu mapy. Sprawdź połączenie z internetem.';});
		const accent=getEscortPagePinkColor();
		const marker=L.marker(center,{draggable:true,title:'Centrum promienia - przeciągnij pineskę',
			icon:L.divIcon({className:'vm-radius-pin',html:'📍',iconSize:[32,38],iconAnchor:[16,36]})}).addTo(map);
		const circle=L.circle(center,{radius:Number(radius.value) * 1000,color:accent,weight:2,fillOpacity:.10,interactive:false}).addTo(map);
		const dots=L.layerGroup().addTo(map);
		const updateCount=() => {
			count.textContent=`W promieniu: ${matches.length} ${polishLocationWord(matches.length)}`;
			apply.disabled=applying || !hasCenter || !selected.size || selected.size > ESCORT_MULTI_LOCATION_MAX || !radius.checkValidity();
		};
		const render=(fit=false) => {
			if (closed || applying) return;
			matches=hasCenter ? getEscortRadiusCities(center,Number(radius.value)):[];
			selected=new Set(matches.slice(0,ESCORT_MULTI_LOCATION_MAX).map(entry => entry.city));
			list.replaceChildren(); dots.clearLayers();
			for (const {city,distance} of matches) {
				const row=makeElement('label','vm-radius-city'),check=makeElement('input'); check.type='checkbox'; check.checked=selected.has(city);
				row.title=ESCORT_RADIUS_PROVINCES[city[1]];
				check.addEventListener('change',() => {
					if (applying) return;
					if (check.checked && selected.size >= ESCORT_MULTI_LOCATION_MAX) {
						check.checked=false; status.textContent=`Możesz wybrać maksymalnie ${ESCORT_MULTI_LOCATION_MAX} miast. Najpierw odznacz inne.`; return;
					}
					if (check.checked) selected.add(city); else selected.delete(city);
					status.textContent=''; updateCount();
				});
				row.append(check,makeElement('span','',city[0]),makeElement('small','',`${distance.toLocaleString('pl-PL',{maximumFractionDigits:1})} km`));
				list.appendChild(row);
				L.circleMarker([city[2],city[3]],{radius:4,color:accent,fillOpacity:.8,interactive:false}).addTo(dots);
			}
			updateCount();
			status.textContent=!radius.checkValidity() ? 'Podaj promień od 1 do 200 km.':matches.length > ESCORT_MULTI_LOCATION_MAX
				? `Domyślnie wybrano ${ESCORT_MULTI_LOCATION_MAX} najbliższych miast. Możesz zmienić zaznaczenie.`:hasCenter && !matches.length ? 'Brak miast z bazy w wybranym promieniu.':'';
			marker.setOpacity(hasCenter ? 1:0);
			const showCircle=hasCenter && radius.checkValidity();
			circle.setStyle({opacity:showCircle ? 1:0,fillOpacity:showCircle ? .10:0});
			if (showCircle) {
				circle.setLatLng(center).setRadius(Number(radius.value) * 1000);
				if (fit) map.fitBounds(circle.getBounds(),{padding:[18,18],maxZoom:14});
			}
		};
		const setCenter=point => {
			if (closed || applying) return;
			center={lat:point.lat,lng:((point.lng + 180) % 360 + 360) % 360 - 180}; hasCenter=true; revision++;
			marker.setLatLng(center); render(true);
		};
		map.on('click',event => setCenter(event.latlng));
		marker.on('dragstart',() => revision++).on('dragend',() => setCenter(marker.getLatLng()));
		radius.addEventListener('input',() => {if (radius.checkValidity()) slider.value=radius.value; render(true);});
		slider.addEventListener('input',() => {radius.value=slider.value; render(true);});
		apply.addEventListener('click',async () => {
			if (apply.disabled) return;
			applying=true; apply.disabled=true; radius.disabled=slider.disabled=true;
			for (const input of list.querySelectorAll('input')) input.disabled=true;
			map.dragging.disable(); marker.dragging.disable(); revision++;
			const chosen=[...selected];
			status.textContent='Zapisywanie wybranych miast…';
			try {
				const locations=resolveEscortRadiusCities(chosen);
				if (closed) return;
				cancel.disabled=true;
				if (await onApply(locations) === false) {status.textContent=''; return;}
				controller.close('apply');
			} catch (error) {
				if (!closed) status.textContent=error?.message || 'Nie udało się wybrać miast.';
			} finally {
				if (!closed) {
					applying=false;
					radius.disabled=slider.disabled=false; cancel.disabled=false;
					for (const input of list.querySelectorAll('input')) input.disabled=false;
					map.dragging.enable(); marker.dragging.enable(); updateCount();
				}
			}
		});
		render(!!nativeCity);
		if (!navigator.geolocation) {status.textContent='Przeglądarka nie udostępnia geolokalizacji. Wskaż pineskę na mapie.'; return;}
		const started=revision; status.textContent='Ustalanie lokalizacji…';
		try {navigator.geolocation.getCurrentPosition(position => {
			if (!closed && revision === started) setCenter({lat:position.coords.latitude,lng:position.coords.longitude});
		},error => {
			if (!closed && revision === started) status.textContent=error.code === 1
				? 'Brak zgody na lokalizację. Wskaż centrum na mapie lub pozostaw miasto z wyszukiwarki.'
				:'Nie udało się ustalić lokalizacji. Możesz wskazać pineskę na mapie.';
		},{enableHighAccuracy:true,timeout:10000,maximumAge:60000});}
		catch {status.textContent='Geolokalizacja jest niedostępna. Wskaż pineskę na mapie.';}
	}
	async function chooseEscortRadiusLocations(locations,currentLocations,hasSearch) {
		let mode='replace';
		if (hasSearch) mode=await chooseWatchOptionDialog({
			id:'vm-escort-radius-mode-overlay',title:'Jak zastosować wybrane miasta?',
			info:'Zastąpić lokalizacje bieżącego wyszukiwania czy dodać wybrane miasta do multiwyszukiwania?',
			choices:[['replace','Zastąp bieżące wyszukiwanie'],['append','Dodaj miasta do multiwyszukiwania']],
			primaryValue:'append'
		});
		if (!mode) return null;
		const merged=[...new Map((mode === 'append' ? [...currentLocations,...locations]:locations)
			.map(item => [getEscortLocationKey(item),item])).values()];
		if (merged.length > ESCORT_MULTI_LOCATION_MAX) throw new Error(
			`Łącznie wybrano ${merged.length} lokalizacji. Limit to ${ESCORT_MULTI_LOCATION_MAX}. Odznacz część miast lub zastąp bieżące wyszukiwanie.`
		);
		return merged;
	}
	function initEscortMultiLocationControls() {
		if (!IS_ESCORT_CLUB) return;
		ensureEscortMultiLocationUiStyle();
		if (document.getElementById(ESCORT_MULTI_LOCATION_UI_ID)) return;
		if ( SETTINGS.multiLocationEnabled === false && SETTINGS.saveSearchLocationsEnabled === false ) return;
		const insert=() => {
			if (document.getElementById(ESCORT_MULTI_LOCATION_UI_ID)) return true;
			const controls=findEscortSearchLocationControls();
			if (!controls?.searchBox || !controls?.submitItem || !controls?.searchButton) return false;
			if ( SETTINGS.saveSearchLocationsEnabled !== false && location.pathname === '/' ) {
				const searchTitle=[...document.querySelectorAll('h1,h2,h3,h4')]
					.find(element => normalizeText(element.textContent) === 'wyszukaj ogłoszenia');
				if (searchTitle) {
					let ancestor=controls.searchBox.parentElement;
					let compacted=false;
					for (let depth=0; ancestor && ancestor !== document.body && depth < 7; depth++) {
						if (ancestor.contains(searchTitle)) {
							const computed=getComputedStyle(ancestor);
							const paddingTop=parseFloat(computed.paddingTop) || 0;
							if (paddingTop >= 45) {
								ancestor.style.setProperty( 'padding-top', `${Math.max(20,paddingTop - 65)}px`, 'important' );
								compacted=true;
								break;
							}
						}
						ancestor=ancestor.parentElement;
					}
					if (!compacted) {
						searchTitle.style.setProperty('margin-top','-55px','important');
					}
				}
				controls.searchBox.style.removeProperty('margin-top');
			}
			const form=controls.searchBox.closest('form');
			const hasInitialSearch=isEscortSearchPath() || location.pathname === '/searchgate.php' || !!favoriteSearchEditSession;
			const initialSearchLocation=hasInitialSearch ? readEscortLocationFromControls(controls):null;
			let multiSubmitInProgress=false;
			const nativeSearchButtonLabel= String(controls.searchButton.textContent || 'Szukaj').trim() || 'Szukaj';
			const nativeSearchButtonTitle=controls.searchButton.getAttribute('title') || '';
			const submitNativeSearch=() => {
				multiSubmitInProgress=true;
				if (favoriteSearchEditSession) {
					favoriteSearchEditSession.currentLocation=readEscortLocationFromControls();
					persistFavoriteSearchSession();
				}
				if (form?.requestSubmit) {
					form.requestSubmit(controls.searchButton);
				} else {
					controls.searchButton.click();
				}
			};
			controls.searchButton.addEventListener('click',() => {
				if (multiSubmitInProgress || favoriteSearchEditSession) return;
				saveEscortActiveMultiLocations([]);
			},{capture:true});
			form?.addEventListener('submit',event => {
				if (multiSubmitInProgress) {
					multiSubmitInProgress=false;
					return;
				}
				if (event.submitter === controls.searchButton) {
					if (favoriteSearchEditSession) updateFavoriteSearchEditLocation();
					else saveEscortActiveMultiLocations([]);
				}
			},{capture:true});
			let radiusLink=null;
			let multiSubmitItem=null;
			let endMultiButton=null;
			if (SETTINGS.multiLocationEnabled !== false) {
				multiSubmitItem=controls.submitItem.cloneNode(false);
				multiSubmitItem.removeAttribute('id');
				const multiSearchButton=controls.searchButton.cloneNode(false);
				multiSearchButton.type='button';
				multiSearchButton.removeAttribute('id');
				multiSearchButton.innerHTML='Dodaj do<br>multiwyboru';
				multiSearchButton.style.cssText += ';font-size:10px!important;line-height:1.05!important;white-space:normal!important;overflow:hidden!important;';
				multiSearchButton.title= 'Dodaje wybraną lokalizację do multiwyboru i od razu uruchamia wyszukiwanie.';
				multiSearchButton.addEventListener('click',() => {
					const latestControls=findEscortSearchLocationControls() || controls;
					const locationData=readEscortLocationFromControls(latestControls);
					if (!locationData) {
						showWatchToast('Najpierw wybierz województwo lub miasto.',true);
						return;
					}
					if (!addEscortActiveMultiLocation(locationData)) return;
					render();
					submitNativeSearch();
				});
				multiSubmitItem.appendChild(multiSearchButton);
				controls.submitItem.insertAdjacentElement('afterend',multiSubmitItem);
				if (CURRENT_HOST === 'pl.escort.club') {
					radiusLink=prepareHelperStyleApi(makeElement('div','vm-multi-panel vm-radius-link-row'));
					const mapButton=makeButton('','Wybierz miasta w promieniu',{id:'vm-escort-radius-open',title:'Wybierz centrum na mapie i wyszukaj miasta w zadanym promieniu.'});
					mapButton.addEventListener('click',async () => {
						try {await openEscortRadiusMap(async locations => {
							const active=getEscortActiveMultiLocations();
							const previous=favoriteSearchEditSession?.currentLocation || initialSearchLocation;
							const chosen=await chooseEscortRadiusLocations(locations,
								active.length ? active:previous ? [previous]:[],hasInitialSearch || active.length > 0);
							if (!chosen) return false;
							await loadEscortLocationOptions(chosen[0],{useKnownIds:true});
							saveEscortActiveMultiLocations(chosen);
							render();
							submitNativeSearch();
						});} catch (error) {showWatchToast(error.message,true);}
					});
					radiusLink.appendChild(mapButton);
				}
				endMultiButton=makeButton('vm-ui-button vm-multi-submit-button -end');
				endMultiButton.innerHTML='Zakończ tryb<br>multiwyboru';
				endMultiButton.title= 'Usuwa aktywny zestaw lokalizacji i wraca do zwykłego wyszukiwania jednej lokalizacji.';
			}
			const panel=prepareHelperStyleApi( makeElement('div') );
			panel.id=ESCORT_MULTI_LOCATION_UI_ID;
			const searchBoxComputed=getComputedStyle(controls.searchBox);
			const searchBoxMaxWidth=controls.searchBox.style.maxWidth || searchBoxComputed.maxWidth || '1120px';
			panel.classList.add('vm-multi-panel');
			panel.style.setProperty('--vm-multi-max-width',searchBoxMaxWidth === 'none' ? '1120px':searchBoxMaxWidth);
			const activeSection=makeElement('div','vm-multi-section -active');
			const activeTopRow=makeElement('div','vm-multi-top-row -active');
			const activeLabel=makeElement('strong','vm-multi-label -active','MULTIWYBÓR LOKALIZACJI:');
			activeTopRow.append(activeLabel);
			const activeBox=makeElement('div','vm-multi-active-box');
			activeSection.append(activeTopRow,activeBox);
			if (endMultiButton) {
				endMultiButton.classList.add('vm-multi-end-inline');
				activeTopRow.appendChild(endMultiButton);
			}
			panel.appendChild(activeSection);
			const savedPanel=prepareHelperStyleApi( makeElement('div') );
			savedPanel.dataset.vmSavedLocationsUi='1';
			savedPanel.classList.add('vm-multi-panel');
			savedPanel.style.setProperty('--vm-multi-max-width',searchBoxMaxWidth === 'none' ? '1120px':searchBoxMaxWidth);
			const savedSection=makeElement('div','vm-multi-section -saved');
			savedSection.style.display=SETTINGS.saveSearchLocationsEnabled === false ? 'none':'flex';
			const savedTopRow=makeElement('div','vm-multi-top-row -saved');
			const savedLabel=makeElement('strong','vm-multi-label -saved','ZAPISANE LOKALIZACJE:');
			const saveCurrent=makeButton('vm-multi-save-current','★ Zapisz bieżącą lokalizację');
			savedTopRow.append(savedLabel,saveCurrent);
			const savedBox=makeElement('div','vm-multi-saved-box');
			savedSection.append(savedTopRow,savedBox);
			savedPanel.appendChild(savedSection);
			const makeActiveChip=locationData => {
				const card=makeElement('div','vm-multi-active-card');
				const text=makeElement('div','vm-multi-active-text',getEscortFullLocationLabel(locationData));
				const count=getEscortMultiLocationResultCount(locationData);
				const countLine=makeElement('div','',count == null
						? '…'
						:`${count} ${polishAdWord(count)}`,{className:`vm-multi-count${count == null ? ' -pending':''}`});
				const remove=makeButton('','×',{title:'Usuń z multiwyszukiwania i odśwież wyniki'});
				remove.className='vm-multi-active-remove';
				remove.addEventListener('click',async () => {
					const remaining=removeEscortActiveMultiLocation(locationData);
					render();
					if (!remaining.length) {
						location.reload();
						return;
					}
					try {
						const latestControls=findEscortSearchLocationControls() || controls;
						const currentLocation=readEscortLocationFromControls(latestControls);
						const currentKey=currentLocation
							? getEscortLocationKey(currentLocation)
							:'';
						const target=remaining.find(item => getEscortLocationKey(item) === currentKey ) || remaining[0];
						if (currentKey && getEscortLocationKey(target) === currentKey) {
							location.reload();
						} else {
							await loadEscortLocationOptions(target);
							submitNativeSearch();
						}
					} catch (error) {
						showWatchToast( error?.message || 'Nie udało się odświeżyć wyników multiwyszukiwania.', true );
					}
				});
				card.append(text,countLine,remove);
				return card;
			};
			const loadSavedGroup=async locations => {
				const group=normalizeSearchLocations(locations);
				if (!group.length) return;
				try {
					if (group.length > 1) {
						saveEscortActiveMultiLocations(group);
						await loadEscortLocationOptions(group[0]);
						render();
						submitNativeSearch();
						return;
					}
					await loadEscortLocationOptions(group[0]);
					render();
					showWatchToast('Wstawiono zapisaną lokalizację do pól wyszukiwania.');
				} catch (error) {
					showWatchToast( error?.message || 'Nie udało się wczytać zapisanej lokalizacji.', true );
				}
			};
			const makeSavedGroupChip=locations => {
				const group=normalizeSearchLocations(locations);
				const chip=makeElement('span','vm-multi-saved-chip');
				const action=makeButton('',getEscortLocationGroupLabel(group),{title:group.length > 1
					? 'Uruchom zapisane multiwyszukiwanie'
					:'Wstaw zapisaną lokalizację do pól wyszukiwania'});
				action.className='vm-multi-saved-action';
				action.addEventListener('click',() => loadSavedGroup(group));
				const remove=makeButton('','×',{title:'Usuń zapisaną lokalizację'});
				remove.className='vm-multi-saved-remove';
				remove.addEventListener('click',event => {
					event.stopPropagation();
					removeEscortSavedLocationGroup(group);
					render();
				});
				chip.append(action,remove);
				return chip;
			};
			const render=() => {
				const active=getEscortActiveMultiLocations();
				activeBox.replaceChildren();
				activeSection.style.display= SETTINGS.multiLocationEnabled !== false && active.length ? 'flex':'none';
				if (active.length) {
					activeLabel.textContent=`MULTIWYBÓR LOKALIZACJI (${active.length}):`;
					active.forEach(locationData => activeBox.appendChild(makeActiveChip(locationData)) );
				}
				savedBox.replaceChildren();
				if (SETTINGS.saveSearchLocationsEnabled !== false) {
					const groups=getEscortSavedLocationGroups();
					if (!groups.length) {
						const empty=makeElement('span','','brak zapisanych lokalizacji');
						empty.style.color='var(--vm-ui-text-muted, #7d8790)';
						savedBox.appendChild(empty);
					} else {
						groups.forEach(group => savedBox.appendChild(makeSavedGroupChip(group)) );
					}
				}
				saveCurrent.textContent=active.length
					? '★ Zapisz bieżącą multilokalizację'
					:'★ Zapisz bieżącą lokalizację';
				saveCurrent.title=active.length
					? `Zapisz zestaw: ${getEscortLocationGroupLabel(active)}`
					:'Zapisz lokalizację wybraną w polach Escort.club';
				const soloSearchMode= SETTINGS.multiLocationEnabled !== false && active.length > 0;
				controls.submitItem.style.display=soloSearchMode ? 'none':'';
				controls.searchButton.textContent=nativeSearchButtonLabel;
				controls.searchButton.style.removeProperty('white-space');
				controls.searchButton.style.removeProperty('line-height');
				if (nativeSearchButtonTitle) {
					controls.searchButton.title=nativeSearchButtonTitle;
				} else {
					controls.searchButton.removeAttribute('title');
				}
				panel.style.display=activeSection.style.display !== 'none'
					? 'block'
					:'none';
				savedPanel.style.display=savedSection.style.display !== 'none'
					? 'block'
					:'none';
			};
			endMultiButton?.addEventListener('click',() => {
				saveEscortActiveMultiLocations([]);
				render();
				location.reload();
			});
			saveCurrent.addEventListener('click',() => {
				const active=getEscortActiveMultiLocations();
				if (active.length) {
					addEscortSavedLocationGroup(active);
					render();
					showWatchToast('Zapisano bieżącą multilokalizację.');
					return;
				}
				const latestControls=findEscortSearchLocationControls() || controls;
				const locationData=readEscortLocationFromControls(latestControls);
				if (!locationData) {
					showWatchToast('Najpierw wybierz województwo lub miasto.',true);
					return;
				}
				addEscortSavedLocationGroup([locationData]);
				render();
				showWatchToast('Zapisano lokalizację.');
			});
			escortMultiLocationUiRefreshers.add(render);
			controls.searchBox.insertAdjacentElement('beforebegin',savedPanel);
			controls.searchBox.insertAdjacentElement('afterend',panel);
			if (radiusLink) {
				radiusLink.style.setProperty('--vm-multi-max-width',searchBoxMaxWidth === 'none' ? '1120px':searchBoxMaxWidth);
				const filters=document.getElementById('vm-escort-saved-filters-quick')?.closest('.vm-escort-results-controls-shell');
				(filters || panel).insertAdjacentElement(filters ? 'beforebegin':'afterend',radiusLink);
			}
			render();
			return true;
		};
		if (insert()) return;
		let attempts=0;
		const timer=setInterval(() => {
			attempts++;
			if (insert() || attempts >= 40) clearInterval(timer);
		},250);
	}
	function getEscortMainResultsSection(doc=document) {
		const sections=[...doc.querySelectorAll('section.content-sec.-index')];
		return sections.find(section => {
			const heading=section.querySelector('h1');
			return isEscortMainResultsHeading(heading?.textContent || '');
		}) || (IS_ESCORT_CLUB && CURRENT_HOST !== 'pl.escort.club' && !getEscortAdIdFromPath()
			? sections.find(section => section.querySelector('h1') && section.querySelector('.item-col.col')):null) || null;
	}
	function isEscortResultsPage(doc=document,pageUrl=location.href) {
		let pathname=location.pathname;
		try {
			pathname=new URL(pageUrl,location.href).pathname;
		} catch {}
		if (getEscortAdIdFromPath(pathname)) return false;
		if (isEscortSearchPath(pathname)) return true;
		return !!getEscortMainResultsSection(doc);
	}
	function getEscortSectionTitleCandidates(doc=document) {
		const selectors=[ 'h1','h2','h3','h4','h5','h6', '.title','.heading','.section-title','.section-heading',
			'.block-title','.box-title','.content-title','.content-heading', '.list-title','.ads-title',
			'[class*="section-title"]','[class*="section-heading"]', '[class*="block-title"]','[class*="content-title"]',
			'strong','b','p','span','div' ];
		const out=[];
		for (const el of doc.querySelectorAll(selectors.join(','))) {
			if ( el.closest( [ '.vm-helper-ui', '.vm-favorites-ui', '[data-vm-style-api="2"]', '[data-vm-favorites-style-api]',
						'[data-vm-watch-wrapper="1"]' ].join(', ') ) ) {
				continue;
			}
			const raw=(el.textContent || '').replace(/\s+/g,' ').trim();
			if (!raw || raw.length > 90) continue;
			const normalized=normalizeEscortSectionHeading(raw);
			if ( !/^(?:polecane|wyroznione|popularne miasta)(?:\s|$)/i.test(normalized) &&
				!isEscortMainResultsHeading(normalized) ) {
				continue;
			}
			const hasMatchingChild=[...el.children].some(child => {
				const childText=(child.textContent || '').replace(/\s+/g,' ').trim();
				if (!childText || childText.length > 90) return false;
				const childNormalized=normalizeEscortSectionHeading(childText);
				return /^(?:polecane|wyroznione|popularne miasta)(?:\s|$)/i.test(childNormalized) ||
					isEscortMainResultsHeading(childNormalized);
			});
			if (hasMatchingChild) continue;
			out.push(el);
		}
		return out;
	}
	function findEscortSectionLabels(pattern,doc=document) {
		return getEscortSectionTitleCandidates(doc) .filter(el => pattern.test(
				normalizeEscortSectionHeading(el.textContent || '') ));
	}
	function isElementAfter(reference,el) {
		return !!( reference && el && reference !== el &&
			(reference.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING) );
	}
	function isElementBefore(reference,el) {
		return !!( reference && el && reference !== el &&
			(reference.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING) );
	}
	function getEscortSectionBoundary(label,doc=document) {
		const labels=getEscortSectionTitleCandidates(doc);
		const currentIndex=labels.indexOf(label);
		if (currentIndex >= 0) {
			return labels.slice(currentIndex + 1)[0] || null;
		}
		return labels.find(candidate => isElementAfter(label,candidate)) || null;
	}
	function hideEscortSectionByHeading({
		headingPattern, targetSelector, targetMapper=el => el, hiddenDatasetKey='vmEscortSectionHidden'
	}) {
		const labels=findEscortSectionLabels(headingPattern,document);
		for (const label of labels) {
			if ( label.closest( '.vm-helper-ui, .vm-favorites-ui, [data-vm-style-api="2"], ' +
					'[data-vm-favorites-style-api], [data-vm-watch-wrapper="1"]' ) ) {
				continue;
			}
			if (label.closest(`[data-${hiddenDatasetKey.replace(/[A-Z]/g,m => '-' + m.toLowerCase())}="1"]`)) {
				continue;
			}
			const nextBoundary=getEscortSectionBoundary(label,document);
			const isInsideRange=el => {
				if (!isElementAfter(label,el)) return false;
				if (!nextBoundary) return true;
				return isElementBefore(nextBoundary,el);
			};
			const targetElements=targetSelector
				? [...document.querySelectorAll(targetSelector)] .filter(isInsideRange) .map(targetMapper) .filter(Boolean)
				:[];
			let wrapper=label.parentElement;
			let hidden=false;
			while ( wrapper && wrapper !== document.body && wrapper !== document.documentElement ) {
				const containsTarget=targetElements.length > 0 && targetElements.some(el => wrapper.contains(el));
				const containsBoundary=nextBoundary
					? wrapper.contains(nextBoundary)
					:false;
				if (containsTarget && !containsBoundary) {
					wrapper.style.setProperty('display','none','important');
					wrapper.dataset[hiddenDatasetKey]='1';
					hidden=true;
					break;
				}
				wrapper=wrapper.parentElement;
			}
			if (hidden) continue;
			label.style.setProperty('display','none','important');
			label.dataset[hiddenDatasetKey]='1';
			for (const el of targetElements) {
				el.style.setProperty('display','none','important');
				el.dataset[hiddenDatasetKey]='1';
			}
		}
	}
	function applyEscortSectionVisibility() {
		trimFavoriteSearchEditPage();
		for (const searchBox of document.querySelectorAll('.search-box')) {
			for (let element=searchBox; element; element=element.parentElement) element.classList.add('vm-escort-search-background');
		}
		const isResultsPage=isEscortResultsPage();
		const isSingleAdPage=!!getEscortAdIdFromPath(location.pathname);
		if (isSingleAdPage && SETTINGS.hideSingleAdActions) {
			for (const actions of document.querySelectorAll('.content-actions-col')) {
				actions.style.setProperty('display','none','important');
				actions.dataset.vmSingleAdActionsHidden='1';
			}
		}
		if (isResultsPage && SETTINGS.hideRecommendedSearchSection) {
			for (const section of document.querySelectorAll('section.dates-sec')) {
				const title=normalizeEscortSectionHeading( section.querySelector('h1,h2,h3,h4,h5,h6')?.textContent || '' );
				if (/^(?:polecane|recommended escorts)(?:\s|$)/i.test(title)) {
					section.style.setProperty('display','none','important');
					section.dataset.vmRecommendedSectionHidden='1';
				}
			}
		}
		if (isSingleAdPage && SETTINGS.hideRecommendedAdSection) {
			for (const section of document.querySelectorAll('section.dates-sec')) {
				const title=normalizeEscortSectionHeading( section.querySelector('h1,h2,h3,h4,h5,h6')?.textContent || '' );
				if (/^(?:polecane|recommended escorts)(?:\s|$)/i.test(title)) {
					section.style.setProperty('display','none','important');
					section.dataset.vmRecommendedAdSectionHidden='1';
				}
			}
			hideEscortSectionByHeading({
				headingPattern:/^(?:polecane|recommended escorts)(?:\s|$)/i,
				targetSelector:'a:is([href*="/anons/"],[href*="/personal-ads/"])',
				targetMapper:link => link.closest('.item-col, .col') || link, hiddenDatasetKey:'vmRecommendedAdSectionHidden'
			});
		}
		if (isSingleAdPage && SETTINGS.hideSingleAdContactButtons) {
			for (const contactBox of document.querySelectorAll('.content-contact')) {
				const containsTargetButton=contactBox.querySelector( 'a[href*="action=tipTokens"], a[href*="action=sendMessage"]' );
				if (!containsTargetButton) continue;
				contactBox.style.setProperty('display','none','important');
				contactBox.dataset.vmSingleAdContactButtonsHidden='1';
			}
		}
		if (isResultsPage && SETTINGS.hideFeaturedSearchSection) {
			for (const section of document.querySelectorAll('section.vip-sec')) {
				const title=normalizeEscortSectionHeading( section.querySelector('h1,h2,h3,h4,h5,h6')?.textContent || '' );
				if (/^(?:wyroznione|escorts promoted)(?:\s|$)/i.test(title)) {
					section.style.setProperty('display','none','important');
					section.dataset.vmFeaturedSectionHidden='1';
				}
			}
		}
		if ( SETTINGS.hidePopularCitiesSection && (isResultsPage || isSingleAdPage) ) {
			const cityList=document.querySelector('#cityList');
			const section=cityList?.closest('section.search-sec');
			if (section) {
				section.style.setProperty('display','none','important');
				section.dataset.vmPopularCitiesSectionHidden='1';
			} else {
				hideEscortSectionByHeading({
					headingPattern:/^(?:popularne miasta|popular cities)(?:\s|$)/i, targetSelector:'a[href*="/anonse/"]',
					targetMapper:link => link, hiddenDatasetKey:'vmPopularCitiesSectionHidden'
				});
			}
		}
	}
	function initEscortSectionVisibility() {
		const style=makeElement('style');
		style.textContent='.vm-escort-search-background,.vm-escort-search-background::before,.vm-escort-search-background::after { background-image:none !important; }';
		document.head.appendChild(style);
		applyEscortSectionVisibility();
		if (document.readyState === 'loading') {
			document.addEventListener( 'DOMContentLoaded', applyEscortSectionVisibility, {once:true} );
		}
		window.addEventListener('load',applyEscortSectionVisibility,{once:true});
		if ( favoriteSearchEditSession || SETTINGS.hideRecommendedSearchSection || SETTINGS.hideRecommendedAdSection ||
			SETTINGS.hideSingleAdContactButtons || SETTINGS.hideSingleAdActions || SETTINGS.hideFeaturedSearchSection ||
			SETTINGS.hidePopularCitiesSection ) {
			let updateScheduled=false;
			const observer=new MutationObserver(() => {
				if (updateScheduled) return;
				updateScheduled=true;
				requestAnimationFrame(() => {
					updateScheduled=false;
					applyEscortSectionVisibility();
				});
			});
			observer.observe(document.documentElement,{
				childList:true, subtree:true
			});
		}
	}
	function hideEscortMainResultsTitle(listContext=null) {
		const heading=listContext?.heading || findEscortMainResultsHeading();
		const titleCol=heading?.closest('.title-col.col, .title-col');
		if (!titleCol) return;
		titleCol.style.display='none';
		titleCol.dataset.vmEscortMainTitleHidden='1';
	}
	function findEscortResultsBreadcrumb(heading=findEscortMainResultsHeading()) {
		if (!heading) return null;
		const isBeforeHeading=element => !!( element && element !== heading &&
			(element.compareDocumentPosition(heading) & Node.DOCUMENT_POSITION_FOLLOWING) );
		const preferred=[...document.querySelectorAll(
			'.breadcrumb, .breadcrumbs, [class*="breadcrumb"], [class*="breadcrumbs"]' )].filter(element =>
			isBeforeHeading(element) && /escort\.club/i.test(element.textContent || '') &&
			/anonse erotyczne/i.test(element.textContent || '') );
		let container=preferred.at(-1) || null;
		if (!container) {
			const escortLinks=[...document.querySelectorAll('a')].filter(link =>
				normalizeEscortAdText(link.textContent).toLocaleLowerCase('pl-PL') === 'escort.club' && isBeforeHeading(link) );
			for (const link of escortLinks.reverse()) {
				let parent=link.parentElement;
				for (let depth=0; parent && depth < 4; depth++,parent=parent.parentElement) {
					const text=normalizeEscortAdText(parent.textContent);
					if (/anonse erotyczne/i.test(text) && text.length < 240) {
						container=parent;
						break;
					}
				}
				if (container) break;
			}
		}
		return container;
	}
	function updateEscortMultiResultsBreadcrumb( locations, forcedTotal=null ) {
		const group=normalizeSearchLocations(locations);
		const resultCounter=document.querySelector( '.vm-escort-results-count, .search-links > .sub-label' );
		if (group.length <= 1) {
			setEscortMultiResultCountersVisible(true);
			if (resultCounter?.dataset.vmOriginalText != null) {
				resultCounter.textContent=
					formatEscortResultPages(resultCounter.dataset.vmOriginalText,getEscortResultsPageCount());
			}
			return;
		}
		const forced=forcedTotal == null || forcedTotal === ''
			? NaN:Number(forcedTotal);
		const total= Number.isFinite(forced) && forced >= 0
				? forced
				:getEscortMultiLocationKnownTotal(group);
		if (resultCounter && total != null) {
			markEscortResultCounter(resultCounter);
			const counts=group.map(value => escortMultiLocationPageCounts.get(getEscortLocationKey(value)));
			const text=`Lista wyników: ${total} sex ${polishCountForm(total,'ogłoszenie','ogłoszenia','ogłoszeń')}`;
			resultCounter.textContent=counts.every(Number.isFinite) ? formatEscortResultPages(text,counts.reduce((sum,n) => sum + n,0)):text;
		}
		const newLabel=`Anonse erotyczne ${getEscortLocationGroupLabel(group)}`;
		setEscortMultiResultCountersVisible(false);
		if (resultCounter && total != null) {
			resultCounter.style.removeProperty('display');
			delete resultCounter.dataset.vmMultiResultCounterHidden;
		}
		const container=findEscortResultsBreadcrumb();
		if (!container) return;
		const targetChild=[...container.querySelectorAll('a, span, li, p')] .reverse() .find(element =>
				/anonse erotyczne/i.test(element.textContent || '') && normalizeEscortAdText(element.textContent).length < 180 );
		if (targetChild) {
			targetChild.textContent=newLabel;
			return;
		}
		const walker=document.createTreeWalker(container,NodeFilter.SHOW_TEXT);
		let node=walker.nextNode();
		while (node) {
			if (/anonse erotyczne/i.test(node.nodeValue || '')) {
				node.nodeValue=String(node.nodeValue || '').replace( /Anonse erotyczne.*$/i, newLabel );
				return;
			}
			node=walker.nextNode();
		}
	}
	function findEscortMainResultsHeading(doc=document) {
		const section=getEscortMainResultsSection(doc);
		if (section) return section.querySelector('h1');
		return getEscortSectionTitleCandidates(doc) .find(el => isEscortMainResultsHeading(el.textContent || '')) || null;
	}
	function getEscortClubListContext(doc=document,pageUrl=location.href) {
		const cardSelector='.item-col.col > a:is([href*="/anons/"],[href*="/personal-ads/"])';
		let pagePath=location.pathname;
		try {pagePath=new URL(pageUrl,location.href).pathname;} catch {}
		if (getEscortAdIdFromPath(pagePath)) return null;
		const mainSection=getEscortMainResultsSection(doc);
		if (mainSection) {
			return {
				anchors:[...mainSection.querySelectorAll(cardSelector)], adsNav:mainSection.querySelector('.ads-nav'),
				heading:mainSection.querySelector('h1')
			};
		}
		if (isEscortSearchPath(pagePath)) {
			const heading=findEscortMainResultsHeading(doc);
			if (!heading) return null;
			const labels=getEscortSectionTitleCandidates(doc);
			const headingIndex=labels.indexOf(heading);
			const nextBoundary=headingIndex >= 0
				? labels.slice(headingIndex + 1)[0] || null
				:labels.find(el => isElementAfter(heading,el)) || null;
			const isInsideTargetSection=el => {
				if (!isElementAfter(heading,el)) return false;
				if (!nextBoundary) return true;
				return isElementBefore(nextBoundary,el);
			};
			return {
				anchors:[...doc.querySelectorAll(cardSelector)].filter(isInsideTargetSection),
				adsNav:[...doc.querySelectorAll('.ads-nav')].find(isInsideTargetSection) || null, heading
			};
		}
		const root=doc.querySelector('section.content-sec.-index') || doc;
		return {
			anchors:[...root.querySelectorAll(cardSelector)], adsNav:root.querySelector('.ads-nav'),
			heading:root.querySelector('h1')
		};
	}
	function waitForEscortClubListContext(timeoutMs=10000) {
		return new Promise(resolve => {
			let observer=null,deadline=null,tick=null,poll=null,done=false;
			const finish=context => {
				if (done) return; done=true;
				observer?.disconnect(); clearTimeout(deadline); clearTimeout(tick); clearInterval(poll);
				document.removeEventListener('DOMContentLoaded',schedule);
				resolve(context);
			};
			const check=() => {
				tick=null;
				const context=getEscortClubListContext();
				if (context && (context.anchors?.length || getEscortActiveMultiLocations().length > 1 ||
					(document.readyState !== 'loading' && getEscortListTotalResultCount(document,context) === 0))) finish(context);
			};
			const schedule=() => {if (!done && !tick) tick=setTimeout(check,50);};
			applyEscortSectionVisibility();
			check(); if (done) return;
			if (typeof MutationObserver === 'function') {
				observer=new MutationObserver(schedule);
				observer.observe(document.body || document.documentElement,{childList:true,subtree:true});
			} else poll=setInterval(check,500);
			document.addEventListener('DOMContentLoaded',schedule);
			deadline=setTimeout(() => finish(getEscortClubListContext()),Math.max(0,timeoutMs));
		});
	}
	function getEscortListCardContainer(anchors) {
		const cards=anchors .map(anchor => anchor.closest('.item-col.col')) .filter(Boolean);
		if (!cards.length) return null;
		const firstParent=cards[0].parentElement;
		if (!firstParent) return null;
		return firstParent;
	}
	function escortAdDataCacheKey(adId) {
		return `vm_escort_ad_data_${adId}`;
	}
	function normalizeEscortAdText(value) {
		return String(value || '').replace(/\s+/g,' ').trim();
	}
	function normalizeEscortAdMultilineText(value) {
		return String(value || '') .replace(/\r\n?/g,'\n') .replace(/[^\S\r\n]+/g,' ') .replace(/ *\n */g,'\n')
			.replace(/\n{3,}/g,'\n\n') .trim();
	}
	function extractEscortClubDescription(doc) {
		const box=doc?.querySelector('.content-desc');
		if (!box) return null;
		let source=box.querySelector( '.tab-content .tab-pane.show.active, ' + '.tab-content .tab-pane.active, ' +
			'.tab-content .tab-pane' );
		if (!source) {
			source=box.cloneNode(true);
			source.querySelectorAll('.label, .nav, script, style').forEach(el => detachHelperNode(el));
		} else {
			source=source.cloneNode(true);
			source.querySelectorAll('script, style').forEach(el => detachHelperNode(el));
		}
		source.querySelectorAll('br').forEach(br => br.replaceWith('\n'));
		return normalizeEscortAdMultilineText(source.textContent) || null;
	}
	function readEscortPhoneFromDocument(doc) {
		for (const link of doc.querySelectorAll('[data-show-phone][data-phone-id], .adsPhone a[href]')) {
			const href=String(link.getAttribute('href') || '').trim();
			const candidates=[ /^tel:/i.test(href) ? href.replace(/^tel:/i,''):'', link.getAttribute('data-phone'),
				link.textContent ];
			for (const value of candidates) {
				const digits=normalizeEscortCachedPhone(value);
				if (!digits) continue;
				return {
					phone:formatEscortPhoneForDisplay(value), phoneDigits:digits, phoneFetchStatus:'page', phoneCheckedAt:Date.now()
				};
			}
		}
		return null;
	}
	function extractEscortClubPhoneId(doc) {
		const phoneLink=doc?.querySelector( '[data-show-phone][data-phone-id], [data-phone-id]' );
		const phoneId=String(phoneLink?.getAttribute('data-phone-id') || '').trim();
		return /^\d+$/.test(phoneId) ? phoneId:null;
	}
	function extractEscortClubAgencyOrSalonPhone(doc) {
		if (!doc) return false;
		const contactContainers=new Set();
		const phoneLink=doc.querySelector( '.adsPhone [data-show-phone], .adsPhone a[href^="tel:"], ' +
			'[data-show-phone][data-phone-id]' );
		const closestContactContainer=phoneLink?.closest('.content-contact');
		if (closestContactContainer) contactContainers.add(closestContactContainer);
		for (const phoneContainer of doc.querySelectorAll('.adsPhone')) {
			const contactContainer=phoneContainer.closest('.content-contact');
			contactContainers.add(contactContainer || phoneContainer);
		}
		return [...contactContainers].some(container => /\(\s*numer\s+telefonu\b[^)]*\)/i.test(
				normalizeEscortAdText(container.textContent) ) );
	}
	function parseEscortClubPhoneResponse(response) {
		const raw=response?.responseText || '';
		let payload=response?.response;
		if (!payload || typeof payload !== 'object') {
			try {
				payload=JSON.parse(raw);
			} catch {
				throw new Error(`Nieprawidłowa odpowiedź numeru: ${raw.slice(0,120) || 'pusta'}`);
			}
		}
		const phone=normalizeEscortAdText(payload?.phone);
		const phoneDigits=normalizeEscortCachedPhone(phone);
		if (!phone || !phoneDigits) {
			throw new Error(payload?.error || payload?.message || 'Odpowiedź nie zawiera numeru');
		}
		return {phone,phoneDigits};
	}
	async function fetchEscortClubPhone(phoneId,cancelToken=null,requestContext=null) {
		if (!/^\d+$/.test(String(phoneId || ''))) {
			throw new Error('Brak prawidłowego data-phone-id');
		}
		const response=await gmRequest({
			method:'POST', url:ESCORT_PHONE_ENDPOINT, cancelToken, timeout:ESCORT_AD_DATA_FETCH_TIMEOUT_MS,
			requestContext:{...requestContext,...createRequestContext(requestContext?.source || 'escort-phone',requestContext)}, headers:{
				'Accept':'application/json, text/javascript, */*; q=0.01',
				'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With':'XMLHttpRequest'
			}, data:new URLSearchParams({id:String(phoneId)}).toString()
		});
		if (response.status && (response.status < 200 || response.status >= 400)) {
			throw new Error(`HTTP ${response.status}`);
		}
		return parseEscortClubPhoneResponse(response);
	}
	function extractEscortClubAvailability(doc) {
		if (!doc) return {};
		const box=[...doc.querySelectorAll('.sub-info-box')]
			.find(candidate => /^(?:godziny dostępności|availability|working hours|working time)$/i.test(normalizeText(
				candidate.querySelector('.label')?.textContent )));
		if (!box) return {};
		const availability={};
		for (const row of box.querySelectorAll('.sub-info-items .sub-info-elem')) {
			const day=normalizeEscortAdText(row.querySelector('.sub-label')?.textContent) .replace(/:\s*$/,'');
			const hours=normalizeEscortAdText(row.querySelector('.desc')?.textContent);
			if (day && hours) availability[day]=hours;
		}
		return availability;
	}
	function normalizeEscortAvailabilityToken(value) {
		const token=normalizeEscortAdText(value) .toLocaleLowerCase('pl-PL') .normalize('NFD')
			.replace(/[\u0300-\u036f]/g,'') .replace(/ł/g,'l') .replace(/:\s*$/,'') .trim();
		return ({monday:'poniedzialek',tuesday:'wtorek',wednesday:'sroda',thursday:'czwartek',friday:'piatek',saturday:'sobota',sunday:'niedziela'})[token] || token;
	}
	function getEscortCurrentWeekdayToken(now=new Date()) {
		try {
			return normalizeEscortAvailabilityToken( new Intl.DateTimeFormat('pl-PL',{weekday:'long'}).format(now) );
		} catch {
			return ['niedziela','poniedzialek','wtorek','sroda','czwartek','piatek','sobota'][now.getDay()];
		}
	}
	function getEscortAvailabilityNow(availability,now=new Date()) {
		if (!availability || typeof availability !== 'object') return null;
		const wantedDay=getEscortCurrentWeekdayToken(now);
		const entry=Object.entries(availability).find(([day]) => normalizeEscortAvailabilityToken(day) === wantedDay );
		if (!entry) return null;
		const hoursText=String(entry[1] || '');
		const normalized=normalizeEscortAvailabilityToken(hoursText);
		if (!normalized) return null;
		if (/niedostepn|not available|unavailable|closed/.test(normalized)) return false;
		if (/^(?:caly czas|cala dobe|all day|all the time|24\s*h)$/.test(normalized)) return true;
		const nowMinutes=now.getHours() * 60 + now.getMinutes();
		const ranges=[...hoursText.matchAll( /(\d{1,2})[.:](\d{2})\s*(?:do|to|[-–\u2014])\s*(\d{1,2})[.:](\d{2})/gi
		)].map(match => ({
			start:Number(match[1]) * 60 + Number(match[2]), end:Number(match[3]) * 60 + Number(match[4]),
			valid:Number(match[1]) < 24 && Number(match[3]) < 24 && Number(match[2]) < 60 && Number(match[4]) < 60
		})).filter(range => range.valid);
		if (!ranges.length) return null;
		return ranges.some(({start,end}) => {
			if (start === end) return true;
			if (start < end) return nowMinutes >= start && nowMinutes <= end;
			return nowMinutes >= start || nowMinutes <= end;
		});
	}
	function initEscortClubCurrentAvailabilityDot() {
		if (!getEscortAdIdFromPath(location.pathname)) return;
		if (!document.getElementById('vm-escort-current-availability-style')) {
			appendHelperStyle('vm-escort-current-availability-style',`
                .vm-escort-current-availability-open::before,
                .vm-escort-current-availability-closed::before,
                .vm-escort-location-current-availability-open::after,
                .vm-escort-location-current-availability-closed::after {
                    content: '●';
                    display: inline-block;
                    font-size: 19px;
                    line-height: 1;
                    vertical-align: -2px;
                }
                .vm-escort-current-availability-open::before,
                .vm-escort-current-availability-closed::before { margin-right: 6px; }
                .vm-escort-location-current-availability-open::after,
                .vm-escort-location-current-availability-closed::after { margin-left: 6px; }
                .vm-escort-current-availability-open::before,
                .vm-escort-location-current-availability-open::after { color: #1aa34a; }
                .vm-escort-current-availability-closed::before,
                .vm-escort-location-current-availability-closed::after { color: #d32f2f; }
            `);
		}
		const apply=() => {
			const box=findEscortSubInfoBox('godziny dostępności');
			if (!box) return false;
			const status=getEscortAvailabilityNow(extractEscortClubAvailability(document));
			const wantedDay=getEscortCurrentWeekdayToken();
			for (const row of box.querySelectorAll('.sub-info-elem')) {
				const label=row.querySelector('.sub-label');
				if (!label) continue;
				label.classList.remove( 'vm-escort-current-availability-open', 'vm-escort-current-availability-closed' );
				label.removeAttribute('data-vm-current-availability');
				if (normalizeEscortAvailabilityToken(label.textContent) !== wantedDay || status == null) continue;
				label.classList.add(status
					? 'vm-escort-current-availability-open'
					:'vm-escort-current-availability-closed');
				label.dataset.vmCurrentAvailability=status ? 'open':'closed';
				label.title=status ? 'Aktualnie czynne':'Aktualnie zamknięte';
			}
			const locationLabel=document.querySelector(
				'.content-info-col.-desc .content-name > .content-location .sub-label, .content-name > .content-location .sub-label'
			);
			if (locationLabel) {
				locationLabel.classList.remove( 'vm-escort-location-current-availability-open',
					'vm-escort-location-current-availability-closed' );
				if (status != null) {
					locationLabel.classList.add(status
						? 'vm-escort-location-current-availability-open'
						:'vm-escort-location-current-availability-closed');
					locationLabel.title=status ? 'Aktualnie czynne':'Aktualnie zamknięte';
				}
			}
			return true;
		};
		apply();
		let attempts=0;
		const readyTimer=setInterval(() => {
			if (apply() || ++attempts >= 40) clearInterval(readyTimer);
		},250);
		setInterval(apply,60000);
	}
	function getFavoriteAvailabilityNow(item,now=new Date(),details=getWatchedItemCachedAdDetails(item,true)) {
		if (!details.length) return null;
		const statuses=details.map(detail => getEscortAvailabilityNow(detail.adData?.availability,now) );
		if (statuses.some(status => status === true)) return true;
		if (statuses.length && statuses.every(status => status === false)) return false;
		return null;
	}
	function getFavoriteAvailabilityTodayTooltip(item,now=new Date(),details=getWatchedItemCachedAdDetails(item,true)) {
		const wantedDay=getEscortCurrentWeekdayToken(now);
		const values=[...new Set( details.map(detail => {
				const availability=detail.adData?.availability;
				if (!availability || typeof availability !== 'object') return '';
				const entry=Object.entries(availability).find(([day]) => normalizeEscortAvailabilityToken(day) === wantedDay );
				return normalizeEscortAdText(entry?.[1]);
			}).filter(Boolean) )];
		if (!values.length) return '';
		return `Dzisiaj: ${values.join(' • ')}`;
	}
	function applyEscortClubTagHighlights(targetDocument=document) {
		if (targetDocument === document && !getEscortAdIdFromPath(location.pathname)) return false;
		if (SETTINGS.showTagHighlights === false) return false;
		const groups=normalizeTagHighlightGroups(SETTINGS.tagHighlightGroups);
		const fallback=groups.at(-1);
		const tagMap=new Map();
		for (const group of groups.slice(0,-1)) {
			for (const tag of group.tags) {
				tagMap.set(normalizeEscortChangeComparisonValue(tag),group);
			}
		}
		const elements=[...targetDocument.querySelectorAll('.sub-info-box .tags-box .tag')];
		if (!elements.length) return false;
		for (const element of elements) {
			const label=normalizeEscortAdText(element.textContent);
			const group=tagMap.get(normalizeEscortChangeComparisonValue(label)) || fallback;
			const color=normalizeFavoriteCategoryColor(group?.color) || DEFAULT_TAG_HIGHLIGHT_GROUPS.at(-1).color;
			element.dataset.vmTagHighlightGroup=group?.id || 'other';
			element.style.setProperty('background-color',color,'important');
			element.style.setProperty('border-color',color,'important');
			element.style.setProperty('color','#fff','important');
			element.style.setProperty('box-shadow',`inset 0 0 0 1px ${color}`,'important');
			element.title=`Grupa: ${group?.name || 'pozostałe'}`;
		}
		return true;
	}
	function ensureEscortClubTagsSettingsGear() {
		if (!getEscortAdIdFromPath(location.pathname)) {
			return false;
		}
		if (document.getElementById('vm-escort-tags-settings-gear')) {
			return true;
		}
		const box=findEscortSubInfoBox('tagi');
		const label=box?.querySelector(':scope > .label, .label');
		if (!label) return false;
		const gear=makeSettingsTabGearButton( 'ad', 'Ustawienia - Pojedynczy anons' );
		gear.id='vm-escort-tags-settings-gear';
		Object.assign(gear.style,{
			width:'18px', height:'18px', marginLeft:'5px', border:'0', borderRadius:'4px', background:'transparent',
			color:'currentColor', verticalAlign:'middle'
		});
		const svg=gear.querySelector('svg');
		if (svg) {
			svg.setAttribute('width','13');
			svg.setAttribute('height','13');
		}
		label.style.display='flex';
		label.style.alignItems='center';
		label.style.gap='0';
		label.appendChild(gear);
		return true;
	}
	function initEscortClubTagHighlighting() {
		if (SETTINGS.showTagHighlights === false) {
			ensureEscortClubTagsSettingsGear();
			return;
		}
		const apply=() => {
			const highlighted=applyEscortClubTagHighlights();
			ensureEscortClubTagsSettingsGear();
			return highlighted;
		};
		apply();
		let attempts=0;
		const timer=setInterval(() => {
			if (apply() || ++attempts >= 40) clearInterval(timer);
		},250);
	}
	function extractEscortClubTags(doc) {
		if (!doc) return [];
		const box=[...doc.querySelectorAll('.sub-info-box')] .find(candidate => normalizeText(
				candidate.querySelector('.label')?.textContent ) === 'tagi');
		if (!box) return [];
		const tags=[];
		const seen=new Set();
		for (const element of box.querySelectorAll('.tags-box .tag')) {
			const tag=normalizeEscortAdText(element.textContent);
			const key=tag.toLocaleLowerCase('pl-PL');
			if (!tag || seen.has(key)) continue;
			seen.add(key);
			tags.push(tag);
		}
		return tags;
	}
	function extractEscortClubLocation(doc) {
		const location={
			country:null, province:null, city:null, district:null, area:null, addressText:null, addressParts:[]
		};
		if (!doc) return location;
		const locationBox=[...doc.querySelectorAll('.content-name .content-location, .content-info-col .content-location')]
			.find(box => box.querySelector('.sub-label'));
		const label=locationBox?.querySelector('.sub-label') || null;
		if (label) {
			location.addressText=normalizeEscortAdText(label.textContent) || null;
			for (const link of label.querySelectorAll('a[href]')) {
				const text=normalizeEscortAdText(link.textContent);
				if (!text) continue;
				try {
					const url=new URL(link.getAttribute('href'),'https://pl.escort.club/');
					const path=url.pathname;
					if (/^\/(?:anonse\/towarzyskie|personal-ads)\/poland\/?$/i.test(path)) {
						location.country=text;
						continue;
					}
					if (/^\/(?:szukaj|search)\/?$/i.test(path) && url.searchParams.has('province')) {
						location.province=text;
						continue;
					}
					if (/^\/(?:anonse\/towarzyskie|personal-ads)\/[^/]+\/?$/i.test(path)) {
						if (url.searchParams.has('district')) {
							location.district=text;
						} else if (!/^poland$/i.test(path.split('/').filter(Boolean).pop() || '')) {
							location.city=text;
						}
					}
				} catch {}
			}
		}
		const mapLink=doc.querySelector( '.content-location #showMap[adres], .content-location .content-map[adres]' );
		if (mapLink) {
			location.addressParts=String(mapLink.getAttribute('adres') || '') .split(',')
				.map(part => normalizeEscortAdText(part)) .filter(Boolean);
		}
		location.city=location.city || extractEscortClubCity(doc);
		if (location.addressParts.length) {
			if (!location.province) location.province=location.addressParts[0] || null;
			if (!location.city) location.city=location.addressParts[1] || null;
			if (!location.district && location.addressParts.length >= 4) {
				location.district=location.addressParts[2] || null;
			}
			const areaIndex=location.district ? 3:2;
			if (location.addressParts.length > areaIndex) {
				location.area=location.addressParts.slice(areaIndex).join(', ') || null;
			}
		}
		return location;
	}
	function extractEscortClubProfileStats(doc) {
		const stats={};
		if (!doc) return stats;
		for (const elem of doc.querySelectorAll('.content-hours .stats-box .stat-elem')) {
			const label=normalizeEscortAdText(elem.querySelector('.sub-label')?.textContent) .replace(/:\s*$/,'');
			const value=normalizeEscortAdText(elem.querySelector('.sub-desc')?.textContent);
			if (!label) continue;
			stats[label]=value || null;
		}
		return stats;
	}
	function extractEscortClubStructuredAdData(doc) {
		if (!doc) return {};
		for (const script of doc.querySelectorAll('script[type="application/ld+json"]')) {
			try {
				const parsed=JSON.parse(script.textContent || 'null');
				const queue=Array.isArray(parsed) ? [...parsed]:[parsed];
				while (queue.length) {
					const item=queue.shift();
					if (!item || typeof item !== 'object') continue;
					if (Array.isArray(item['@graph'])) queue.push(...item['@graph']);
					const types=Array.isArray(item['@type']) ? item['@type']:[item['@type']];
					if (!types.some(type => String(type || '').toLowerCase() === 'classifiedad')) {
						continue;
					}
					return {
						datePosted:item.datePosted || null,
						structuredCity:item.location?.address?.addressLocality || item.location?.name || null
					};
				}
			} catch {}
		}
		return {};
	}
	function normalizeEscortClubPriceDurationKey(value) {
		const normalized=normalizeEscortAdText(value) .toLowerCase() .replace(/,/g,'.') .replace(/:\s*$/,'');
		if (!normalized) return null;
		if (/\bnoc\b|ca(?:ł|l)[aą]?\s+noc/.test(normalized)) return 'night';
		if (/\bp[oó]ł\s*(?:godz|godzin)/.test(normalized)) return '30';
		const minutesMatch=normalized.match(/(\d+(?:\.\d+)?)\s*(?:min|minut)/);
		if (minutesMatch) {
			const minutes=Math.round(Number(minutesMatch[1]));
			return Number.isFinite(minutes) && minutes > 0 ? String(minutes):null;
		}
		const hoursMatch=normalized.match(/(\d+(?:\.\d+)?)\s*(?:h\b|godz|godzin)/);
		if (hoursMatch) {
			const minutes=Math.round(Number(hoursMatch[1]) * 60);
			return Number.isFinite(minutes) && minutes > 0 ? String(minutes):null;
		}
		if (/^(?:jedna\s+)?godzina$/.test(normalized)) return '60';
		return null;
	}
	function parseEscortClubPrice(value) {
		const raw=normalizeEscortAdText(value).replace(/\u00a0/g,' ');
		if (!raw) return null;
		const match=raw.match(/(\d[\d\s]*(?:[.,]\d+)?)\s*(PLN|zł|EUR|€|USD|\$|GBP|£)?/i);
		if (!match) return null;
		const amount=Number(match[1].replace(/\s/g,'').replace(',','.'));
		if (!Number.isFinite(amount)) return null;
		const currencyToken=String(match[2] || '').toUpperCase();
		const currency=currencyToken === 'ZŁ' ? 'PLN'
			:currencyToken === '€' ? 'EUR'
				:currencyToken === '$' ? 'USD'
					:currencyToken === '£' ? 'GBP'
						:currencyToken || null;
		return {amount,currency};
	}
	function extractEscortClubPrices(doc) {
		const prices={};
		if (!doc) return prices;
		for (const elem of doc.querySelectorAll( '.contant-prices .stat-elem, .content-prices .stat-elem' )) {
			const rawLabel=normalizeEscortAdText( elem.querySelector('.sub-label')?.textContent ).replace(/:\s*$/,'');
			const durationKey=normalizeEscortClubPriceDurationKey(rawLabel);
			const parsedPrice=parseEscortClubPrice( elem.querySelector('.sub-desc')?.textContent );
			if (!durationKey) continue;
			prices[durationKey]=parsedPrice || {
				amount:null, currency:null
			};
		}
		return prices;
	}
	function formatEscortClubDatePosted(value) {
		const raw=String(value || '').trim();
		if (!raw) return '';
		const match=raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
		if (!match) return raw;
		const date=new Date(Number(match[1]),Number(match[2]) - 1,Number(match[3]));
		if ( Number.isNaN(date.getTime()) || date.getFullYear() !== Number(match[1]) ||
			date.getMonth() !== Number(match[2]) - 1 || date.getDate() !== Number(match[3]) ) {
			return raw;
		}
		try {
			return new Intl.DateTimeFormat('pl-PL',{
				day:'numeric', month:'long', year:'numeric'
			}).format(date);
		} catch {
			return raw;
		}
	}
	function initEscortClubDatePostedDisplay() {
		if (!getEscortAdIdFromPath(location.pathname)) return;
		if (isEscortCachedArchiveRuntime()) return;
		const insertDate=() => {
			if (document.getElementById('vm-escort-date-posted')) return true;
			const profileHeading=document.querySelector( '.content-info-col.-desc > .content-name.-desc-name > h1, ' +
				'.content-info-col.-desc > .content-name > h1' );
			if (!profileHeading) return false;
			profileHeading.style.display='block';
			const profileColumn=profileHeading.closest('.content-info-col.-desc');
			const profileWrapper=profileColumn?.closest('.content-info-wrapper.-left');
			const galleryColumn=profileWrapper?.querySelector('.content-gallery-col');
			if (profileWrapper) profileWrapper.style.alignItems='flex-start';
			if (profileColumn) {
				profileColumn.style.alignSelf='flex-start';
				profileColumn.style.verticalAlign='top';
			}
			if (galleryColumn) {
				galleryColumn.style.alignSelf='flex-start';
				galleryColumn.style.verticalAlign='top';
			}
			if (profileWrapper && profileColumn && galleryColumn && window.matchMedia?.('(min-width: 768px)').matches) {
				const halvePx=(element,property,cssProperty) => {
					const value=parseFloat(getComputedStyle(element)[property]);
					if (!Number.isFinite(value) || value <= 0) return;
					element.style.setProperty(cssProperty,`${Math.max(0,value / 2)}px`);
				};
				halvePx(profileWrapper,'columnGap','column-gap');
				halvePx(galleryColumn,'paddingRight','padding-right');
				halvePx(profileColumn,'paddingLeft','padding-left');
			}
			const datePosted=formatEscortClubDatePosted( extractEscortClubStructuredAdData(document).datePosted );
			if (!datePosted) return true;
			const row=makeElement('span');
			row.id='vm-escort-date-posted';
			row.setAttribute('aria-label',`Data publikacji anonsu: ${datePosted}`);
			Object.assign(row.style,{
				display:'block', boxSizing:'border-box', position:'static', float:'none', clear:'both', width:'100%',
				margin:'0 0 18px', padding:'4px 0', background:'transparent', color:getEscortPagePinkColor(), fontSize:'14px',
				lineHeight:'1.35', textAlign:'left', whiteSpace:'normal'
			});
			const publishedLine=makeElement('span');
			Object.assign(publishedLine.style,{
				display:'flex', position:'static', float:'none', alignItems:'baseline', gap:'6px', whiteSpace:'nowrap'
			});
			const label=makeElement('span','','Opublikowano:');
			const date=makeElement('strong','',datePosted);
			date.style.color=getEscortPagePinkColor();
			publishedLine.append(label,date);
			row.appendChild(publishedLine);
			const pendingLastVisit=document.getElementById(ESCORT_LAST_VISIT_ID);
			if (pendingLastVisit) row.appendChild(pendingLastVisit);
			const visitSummary=document.getElementById(ESCORT_VISIT_SUMMARY_ID);
			if (visitSummary) {
				row.style.marginBottom='5px';
				visitSummary.insertAdjacentElement('beforebegin',row);
			} else {
				profileHeading.insertAdjacentElement('afterbegin',row);
			}
			return true;
		};
		if (insertDate()) return;
		if (document.readyState === 'loading') {
			document.addEventListener('DOMContentLoaded',insertDate,{once:true});
		} else {
			window.addEventListener('load',insertDate,{once:true});
		}
	}
	function extractEscortClubTitle(doc) {
		const heading=doc?.querySelector( '.content-info-col .content-name h1, .content-name.-desc-name h1' );
		if (!heading) return null;
		const source=heading.cloneNode(true);
		source.querySelectorAll( '#vm-escort-date-posted, #vm-escort-visit-summary, #vm-escort-last-visited'
		).forEach(element => detachHelperNode(element));
		return normalizeEscortAdText(source.textContent) || null;
	}
	function extractEscortClubAdData(doc,adId,adUrl) {
		const structured=extractEscortClubStructuredAdData(doc);
		const location=extractEscortClubLocation(doc);
		if (!location.city && structured.structuredCity) {
			location.city=normalizeEscortAdText(structured.structuredCity) || null;
		}
		return {
			adId:String(adId), adUrl, phoneId:extractEscortClubPhoneId(doc), ...readEscortPhoneFromDocument(doc),
			isAgencyOrSalonPhone:extractEscortClubAgencyOrSalonPhone(doc), title:extractEscortClubTitle(doc),
			imageUrl:extractWatchPageImage(doc,adUrl), description:extractEscortClubDescription(doc),
			availability:extractEscortClubAvailability(doc), tags:extractEscortClubTags(doc),
			prices:extractEscortClubPrices(doc), datePosted:structured.datePosted || null, location,
			stats:extractEscortClubProfileStats(doc)
		};
	}
	function normalizeEscortChangeComparisonValue(value) {
		return normalizeEscortAdText(value) .normalize('NFKC') .toLocaleLowerCase('pl-PL');
	}
	function getEscortObjectEntryByNormalizedKey(object,key) {
		const wanted=normalizeEscortChangeComparisonValue(key).replace(/:\s*$/,'');
		return Object.entries(object || {}).find(([candidate]) =>
			normalizeEscortChangeComparisonValue(candidate).replace(/:\s*$/,'') === wanted ) || null;
	}
	function formatEscortPhoneForDisplay(value) {
		const raw=normalizeEscortAdText(value).replace(/^tel:\s*/i,'');
		const digits=normalizeEscortCachedPhone(raw);
		if (!digits) return raw;
		if (digits.length === 9) {
			return '+48 ' + digits.replace(/(\d{3})(\d{3})(\d{3})/,'$1 $2 $3');
		}
		return raw.startsWith('+') ? raw:`+${digits}`;
	}
	function getEscortComparableAddress(data) {
		if (!data || typeof data !== 'object') return null;
		if (Object.prototype.hasOwnProperty.call(data,'address')) {
			return normalizeEscortAdText(data.address) || null;
		}
		const location=data.location || {};
		const addressText=normalizeEscortAdText(location.addressText);
		if (addressText) return addressText;
		const addressParts=Array.isArray(location.addressParts)
			? location.addressParts.map(normalizeEscortAdText).filter(Boolean)
			:[];
		if (addressParts.length) return addressParts.join(', ');
		return [ location.country, location.province, location.city, location.district, location.area
		].map(normalizeEscortAdText).filter(Boolean).join(', ') || null;
	}
	function normalizeEscortImageComparisonValue( value ) {
		const raw=String(value || '').trim();
		if (!raw) return '';
		try {
			const url=new URL( raw, location.href );
			url.search='';
			url.hash='';
			return `${url.hostname.toLowerCase()}${
				url.pathname
			}`;
		} catch {
			return raw .replace(/[?#].*$/,'') .trim() .toLocaleLowerCase('pl-PL');
		}
	}
	function createEscortVisitSnapshot(data,previousSnapshot=null) {
		const currentPhoneDigits=getEscortAdPhoneDigits(data);
		const previousPhoneDigits=getEscortAdPhoneDigits(previousSnapshot);
		const hasCurrentPhone=!!currentPhoneDigits;
		return {
			title: normalizeEscortAdText( data?.title ) || null, imageUrl: String(data?.imageUrl || '').trim() || String(
					previousSnapshot?.imageUrl || '' ).trim() || null, address:getEscortComparableAddress(data), phone:hasCurrentPhone
				? (data.phone || formatEscortPhoneForDisplay(currentPhoneDigits))
				:(previousPhoneDigits ? previousSnapshot.phone:null), phoneDigits:hasCurrentPhone
				? currentPhoneDigits
				:(previousPhoneDigits || null), description:data?.description || null,
			availability:{...(data?.availability || {})}, prices:Object.fromEntries(Object.entries(data?.prices || {}).map(
				([key,value]) => [key,value && typeof value === 'object' ? {...value}:value] )), stats:{...(data?.stats || {})},
			tags:Array.isArray(data?.tags) ? [...data.tags]:[]
		};
	}
	function readEscortPhoneFromCurrentPage() {
		return readEscortPhoneFromDocument(document);
	}
	async function resolveEscortPhoneForCurrentPage(phoneId) {
		for (let attempt=0; attempt < 9; attempt++) {
			const fromPage=readEscortPhoneFromCurrentPage();
			if (fromPage) return fromPage;
			await new Promise(resolve => setTimeout(resolve,200));
		}
		if (!phoneId) return null;
		const adId=getEscortAdIdFromPath(location.pathname);
		const cached=[escortAdDataMemoryCache.get(String(adId)),getStoredEscortAdData(adId)]
			.find(data => String(data?.phoneId || '') === String(phoneId) && isEscortAdPhoneFresh(data));
		if (cached) return {phone:cached.phone,phoneDigits:getEscortAdPhoneDigits(cached),phoneFetchStatus:'cached',
			phoneCheckedAt:getEscortAdPhoneCheckedAt(cached)};
		try {
			const fetched=await fetchEscortClubPhone(phoneId);
			return {
				...fetched, phoneFetchStatus:'ok', phoneCheckedAt:Date.now()
			};
		} catch (error) {
			log('Nie udało się odświeżyć numeru podczas zapisu wizyty',error);
			return null;
		}
	}
	function escortPricesEquivalent(previous,current) {
		if (!previous || !current) return false;
		const previousHasAmount=previous.amount != null && Number.isFinite(Number(previous.amount));
		const currentHasAmount=current.amount != null && Number.isFinite(Number(current.amount));
		if (previousHasAmount !== currentHasAmount) return false;
		if ( previousHasAmount && Number(previous.amount) !== Number(current.amount) ) {
			return false;
		}
		const previousCurrency=normalizeEscortAdText(previous.currency).toUpperCase();
		const currentCurrency=normalizeEscortAdText(current.currency).toUpperCase();
		return previousCurrency === currentCurrency;
	}
	function formatEscortChangedPrice(price) {
		return formatEscortTilePrice(price) || 'brak';
	}
	function compareEscortAdVisitData(previous,current) {
		const changes={
			title:null, image:null, address:null, prices:[], availability:[], tags:null, description:null, phone:null, stats:[],
			labels:[]
		};
		if (!previous || !current) return changes;
		const previousTitle=normalizeEscortAdText(previous.title);
		const currentTitle=normalizeEscortAdText(current.title);
		if ( (previousTitle || currentTitle) && normalizeEscortChangeComparisonValue(previousTitle) !==
				normalizeEscortChangeComparisonValue(currentTitle) ) {
			changes.title={
				previous:previousTitle || null, current:currentTitle || null
			};
			changes.labels.push('nazwę anonsu');
		}
		const previousImage= normalizeEscortImageComparisonValue( previous.imageUrl );
		const currentImage= normalizeEscortImageComparisonValue( current.imageUrl );
		if ( previousImage && currentImage && previousImage !== currentImage ) {
			changes.image={
				previous: String(previous.imageUrl || ''), current: String(current.imageUrl || '')
			};
			changes.labels.push( 'główne zdjęcie' );
		}
		const previousAddress=getEscortComparableAddress(previous);
		const currentAddress=getEscortComparableAddress(current);
		if ( (previousAddress || currentAddress) && normalizeEscortChangeComparisonValue(previousAddress) !==
				normalizeEscortChangeComparisonValue(currentAddress) ) {
			changes.address={
				previous:previousAddress, current:currentAddress
			};
			changes.labels.push('adres');
		}
		const previousPrices=previous.prices || {};
		const currentPrices=current.prices || {};
		const priceDurationKeys=new Set([ ...Object.keys(previousPrices), ...Object.keys(currentPrices) ]);
		for (const durationKey of priceDurationKeys) {
			const previousHasLine=Object.prototype.hasOwnProperty.call( previousPrices, durationKey );
			const currentHasLine=Object.prototype.hasOwnProperty.call( currentPrices, durationKey );
			const previousPrice=previousHasLine ? previousPrices[durationKey]:null;
			const currentPrice=currentHasLine ? currentPrices[durationKey]:null;
			if ( previousHasLine && currentHasLine && escortPricesEquivalent(previousPrice,currentPrice) ) {
				continue;
			}
			changes.prices.push({
				durationKey, previous:previousPrice, current:currentPrice, type:!previousHasLine
					? 'added'
					:(!currentHasLine ? 'removed':'changed')
			});
		}
		if (changes.prices.length) {
			changes.labels.push( changes.prices.some(change => change.type !== 'changed')
					? 'cennik'
					:'cenę' );
		}
		for (const [day,currentHours] of Object.entries(current.availability || {})) {
			const previousEntry=getEscortObjectEntryByNormalizedKey(previous.availability,day);
			const previousHours=previousEntry?.[1];
			if ( !previousHours || normalizeEscortChangeComparisonValue(previousHours) ===
					normalizeEscortChangeComparisonValue(currentHours) ) {
				continue;
			}
			changes.availability.push({
				day, previous:previousHours, current:currentHours
			});
		}
		if (changes.availability.length) changes.labels.push('godziny dostępności');
		if (Array.isArray(previous.tags) && Array.isArray(current.tags)) {
			const previousTags=new Map(previous.tags.map(tag => [ normalizeEscortChangeComparisonValue(tag),
				normalizeEscortAdText(tag) ]).filter(([key]) => key));
			const currentTags=new Map(current.tags.map(tag => [ normalizeEscortChangeComparisonValue(tag),
				normalizeEscortAdText(tag) ]).filter(([key]) => key));
			const added=[...currentTags] .filter(([key]) => !previousTags.has(key)) .map(([,tag]) => tag);
			const removed=[...previousTags] .filter(([key]) => !currentTags.has(key)) .map(([,tag]) => tag);
			if (added.length || removed.length) {
				changes.tags={added,removed};
				changes.labels.push('tagi');
			}
		}
		if ( previous.description && current.description && normalizeEscortChangeComparisonValue(previous.description) !==
				normalizeEscortChangeComparisonValue(current.description) ) {
			changes.description={
				previous:previous.description, current:current.description
			};
			changes.labels.push('opis');
		}
		const previousPhoneDigits=getEscortAdPhoneDigits(previous);
		const currentPhoneDigits=getEscortAdPhoneDigits(current);
		if ( previousPhoneDigits && currentPhoneDigits && previousPhoneDigits !== currentPhoneDigits ) {
			changes.phone={
				previous:previous.phone || previousPhoneDigits, current:current.phone || currentPhoneDigits
			};
			changes.labels.push('numer telefonu');
		}
		for (const [currentLabel,currentValue] of Object.entries(current.stats || {})) {
			const previousEntry=getEscortObjectEntryByNormalizedKey( previous.stats, currentLabel );
			if (!previousEntry) continue;
			const previousValue=previousEntry[1];
			if ( normalizeEscortChangeComparisonValue(previousValue) === normalizeEscortChangeComparisonValue(currentValue) ) {
				continue;
			}
			changes.stats.push({
				label:currentLabel, previous:previousValue, current:currentValue
			});
		}
		if (changes.stats.length) {
			const onlyBreastChanged=changes.stats.length === 1 &&
				normalizeEscortChangeComparisonValue(changes.stats[0].label) === 'biust';
			changes.labels.push(onlyBreastChanged ? 'rozmiar biustu':'dane profilu');
		}
		return changes;
	}
	function ensureEscortVisitChangeStyles() {
		if (document.getElementById(ESCORT_CHANGE_STYLE_ID)) return;
		const changeRedColor='#b42318',changeBg='rgba(180, 35, 24, .13)',changeText='#7a1c12';
		appendHelperStyle(ESCORT_CHANGE_STYLE_ID,[ '.vm-escort-changed-fragment {',
			'  padding: 0 4px !important;', '  border-radius: 2px !important;',
			'  background: ' + changeBg + ' !important;', '  box-shadow: inset 3px 0 0 ' + changeRedColor + ' !important;',
			'  color: ' + changeText + ' !important;', '  text-shadow: none !important;', '}', '.vm-escort-change-note {',
			'  box-sizing: border-box;', '  margin: 4px 0 8px;', '  padding: 5px 8px;', '  border-left: 3px solid ' + changeRedColor + ';',
			'  border-radius: 0 4px 4px 0;', '  background: ' + changeBg + ';', '  color: ' + changeText + ';',
			'  text-shadow: none;', '  font: 700 11px/1.35 Lato, Arial, sans-serif;', '}', '.vm-escort-description-change {',
			'  margin: 10px 0 0;', '  padding: 8px 10px 10px;', '  border: 1px solid ' + changeRedColor + ';',
			'  border-radius: 6px;', '  background: ' + changeBg + ';', '  color: ' + changeText + ';',
			'  font: 700 11px/1.35 Lato, Arial, sans-serif;', '}', '.vm-escort-description-change summary {',
			'  cursor: pointer;', '  color: ' + changeText + ';', '  font-weight: 700;', '}',
			'.vm-escort-description-current-changed {', '  padding-left: 8px;', '  box-shadow: inset 3px 0 0 ' + changeRedColor + ';', '}',
			'.vm-escort-description-previous {', '  max-height: 260px;', '  margin-top: 9px;', '  padding: 12px;', '  overflow: auto;', '  border-radius: 5px;',
			'  background: rgba(255, 255, 255, .98);', '  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08);', '  color: #2f2530;',
			'  font-weight: 400;', '  line-height: 1.5;', '  white-space: pre-wrap;', '}' ].join('\n'));
	}
	function addEscortPreviousValueNote(element,text) {
		if (!element || element.dataset.vmEscortChangeMarked === '1') return;
		element.dataset.vmEscortChangeMarked='1';
		element.classList.add('vm-escort-changed-fragment');
		const note=makeElement('div','vm-escort-change-note',text);
		element.insertAdjacentElement('afterend',note);
	}
	function addEscortPriceSectionChangeNote(priceBox,text) {
		if (!priceBox) {
			priceBox=document.getElementById('vm-escort-removed-price-section');
			if (!priceBox) {
				const info=document.querySelector( '.content-info-col.-info .content-info, .content-info-col .content-info' );
				if (!info) return;
				priceBox=makeElement('div');
				priceBox.id='vm-escort-removed-price-section';
				priceBox.style.marginTop='12px';
				const removedLabel=makeElement('div','label','Zmiany w cenniku');
				priceBox.appendChild(removedLabel);
				info.appendChild(priceBox);
			}
		}
		const label=priceBox.querySelector(':scope > .label, .label');
		if (label) label.classList.add('vm-escort-changed-fragment');
		let notes=priceBox.querySelector('.vm-escort-price-section-changes');
		if (!notes) {
			notes=makeElement('div');
			notes.className='vm-escort-price-section-changes';
			if (label) label.insertAdjacentElement('afterend',notes);
			else priceBox.prepend(notes);
		}
		const note=makeElement('div','vm-escort-change-note',text);
		notes.appendChild(note);
	}
	function formatEscortChangedValue(value) {
		return normalizeEscortAdText(value) || 'brak';
	}
	function renderEscortTitleChange(change) {
		if (!change) return;
		const heading=document.querySelector( '.content-info-col.-desc > .content-name.-desc-name > h1, ' +
			'.content-info-col.-desc > .content-name > h1' );
		if (!heading) return;
		const currentKey=normalizeEscortChangeComparisonValue(change.current);
		const titleNode=currentKey
			? [...heading.childNodes].find(node => node.nodeType === Node.TEXT_NODE &&
				normalizeEscortChangeComparisonValue(node.textContent) === currentKey )
			:null;
		if (titleNode) {
			const markedTitle=makeElement('span','vm-escort-changed-fragment');
			markedTitle.style.marginRight='6px';
			markedTitle.textContent=formatEscortChangedValue(change.current);
			titleNode.replaceWith(markedTitle);
		}
		if (!document.getElementById('vm-escort-title-change-note')) {
			const note=makeElement('div');
			note.id='vm-escort-title-change-note';
			note.className='vm-escort-change-note';
			note.textContent= 'Nazwa zmieniona: ' + formatEscortChangedValue(change.previous) + ' → ' +
				formatEscortChangedValue(change.current);
			heading.insertAdjacentElement('afterend',note);
		}
	}
	function renderEscortAddressChange(change) {
		if (!change) return;
		const locationBox=document.querySelector( '.content-info-col.-desc .content-name > .content-location' );
		addEscortPreviousValueNote( locationBox, 'Adres zmieniony: ' + formatEscortChangedValue(change.previous) + ' → ' +
				formatEscortChangedValue(change.current) );
	}
	function findEscortSubInfoBox(labelText) {
		return [...document.querySelectorAll('.sub-info-box')] .find(candidate => normalizeText(
				candidate.querySelector('.label')?.textContent ) === labelText) || null;
	}
	function renderEscortTagChanges(change) {
		if (!change) return;
		let box=findEscortSubInfoBox('tagi');
		if (!box) {
			const column=document.querySelector('.anons-info-sec .sub-info-col');
			if (!column) return;
			box=makeElement('div');
			box.className='sub-info-box -tags';
			const label=makeElement('div','label','Tagi');
			box.appendChild(label);
			column.appendChild(box);
		}
		const label=box.querySelector(':scope > .label, .label');
		if (label) label.classList.add('vm-escort-changed-fragment');
		let notes=box.querySelector('.vm-escort-tag-changes');
		if (!notes) {
			notes=makeElement('div');
			notes.className='vm-escort-tag-changes';
			notes.style.marginTop='8px';
			box.appendChild(notes);
		}
		const appendNote=text => {
			const note=makeElement('div','vm-escort-change-note',text);
			notes.appendChild(note);
		};
		if (change.added?.length) {
			appendNote('Dodano tagi: ' + change.added.join(', '));
		}
		if (change.removed?.length) {
			appendNote('Usunięto tagi: ' + change.removed.join(', '));
		}
	}
	function findEscortStatRow(label) {
		const wanted=normalizeEscortChangeComparisonValue(label).replace(/:\s*$/,'');
		return [...document.querySelectorAll('.stats-box .stat-elem')] .find(row => normalizeEscortChangeComparisonValue(
					row.querySelector('.sub-label')?.textContent ).replace(/:\s*$/,'') === wanted ) || null;
	}
	function renderEscortVisitChanges(changes) {
		ensureEscortVisitChangeStyles();
		renderEscortTitleChange( changes.title );
		renderEscortAddressChange( changes.address );
		if (changes.image) {
			const gallery= document.querySelector( '.content-gallery-col, .galleryContainer' );
			if ( gallery && !gallery.querySelector( '.vm-escort-main-image-change' ) ) {
				const note=makeElement( 'div', 'vm-escort-main-image-change', 'Główne zdjęcie zmieniono od poprzedniej wizyty.' );
				Object.assign(note.style,{
					margin:'6px 0', padding:'5px 8px', borderLeft: '3px solid var(--vm-ui-danger, #ff4d4f)', background:
						'color-mix(in srgb, var(--vm-ui-danger, #ff4d4f) 12%, transparent)', color: 'var(--vm-ui-danger, #ff4d4f)', font:
						'700 10px/1.35 Lato, Arial, sans-serif'
				});
				gallery.appendChild(note);
			}
		}
		const priceBox=document.querySelector('.contant-prices, .content-prices');
		for (const change of changes.prices) {
			const row=[...document.querySelectorAll( '.contant-prices .stat-elem, .content-prices .stat-elem'
			)].find(candidate => normalizeEscortClubPriceDurationKey( candidate.querySelector('.sub-label')?.textContent
				) === change.durationKey );
			const durationLabel=getEscortPriceDurationLabel(change.durationKey);
			const prefix=change.type === 'added'
				? 'Dodano pozycję cennika'
				:(change.type === 'removed'
					? 'Usunięto pozycję cennika'
					:'Cena zmieniona');
			const message= prefix + ' (' + durationLabel + '): ' + formatEscortChangedPrice(change.previous) + ' → ' +
				formatEscortChangedPrice(change.current);
			if (row) addEscortPreviousValueNote(row,message);
			else addEscortPriceSectionChangeNote(priceBox,message);
		}
		const availabilityBox=findEscortSubInfoBox('godziny dostępności');
		for (const change of changes.availability) {
			const row=[...(availabilityBox?.querySelectorAll('.sub-info-elem') || [])] .find(candidate =>
					normalizeEscortChangeComparisonValue( candidate.querySelector('.sub-label')?.textContent ).replace(/:\s*$/,'') ===
					normalizeEscortChangeComparisonValue(change.day).replace(/:\s*$/,'') );
			addEscortPreviousValueNote( row, 'Godziny zmienione: ' + change.previous + ' → ' + change.current );
		}
		renderEscortTagChanges(changes.tags);
		if (changes.description) {
			const description=document.querySelector('.content-desc');
			if (description && !description.querySelector('.vm-escort-description-change')) {
				const currentText=description.querySelector('.tab-content') || description;
				currentText.classList.add('vm-escort-description-current-changed');
				const details=makeElement('details','vm-escort-description-change');
				const summary=makeElement('summary','','Opis zmieniony - pokaż poprzednią wersję');
				const previous=makeElement('div','vm-escort-description-previous',changes.description.previous);
				details.append(summary,previous);
				description.appendChild(details);
			}
		}
		if (changes.phone) {
			for (const phoneBox of document.querySelectorAll('.adsPhone')) {
				addEscortPreviousValueNote( phoneBox, 'Numer telefonu zmieniony: ' +
						formatEscortPhoneForDisplay(changes.phone.previous) + ' → ' + formatEscortPhoneForDisplay(changes.phone.current)
				);
			}
		}
		for (const change of changes.stats) {
			const isBreast= normalizeEscortChangeComparisonValue(change.label) === 'biust';
			addEscortPreviousValueNote( findEscortStatRow(change.label), (isBreast
					? 'Rozmiar biustu zmieniony: '
					:'Zmiana „' + change.label + '”: ') + formatEscortChangedValue(change.previous) + ' → ' +
					formatEscortChangedValue(change.current) );
		}
	}
	function polishElapsedValue(value,one,few,many) {
		return `${formatPolishCount(value,one,few,many)} temu`;
	}
	function formatEscortLastVisit(timestamp) {
		const elapsedMs=Date.now() - Number(timestamp);
		if (!Number.isFinite(elapsedMs) || elapsedMs < 0) return '';
		const minutes=Math.floor(elapsedMs / 60000);
		if (minutes < 1) return 'przed chwilą';
		if (minutes < 60) return polishElapsedValue(minutes,'minutę','minuty','minut');
		const hours=Math.floor(elapsedMs / 3600000);
		if (hours < 24) return polishElapsedValue(hours,'godzinę','godziny','godzin');
		const days=Math.floor(elapsedMs / 86400000);
		if (days < 31) return polishElapsedValue(days,'dzień','dni','dni');
		const months=Math.floor(days / 30.4375);
		if (months < 12) return polishElapsedValue(months,'miesiąc','miesiące','miesięcy');
		const years=Math.floor(months / 12);
		return polishElapsedValue(years,'rok','lata','lat');
	}
	function renderEscortVisitSummary(previous,changes,isFirstVisit=false) {
		if ((!previous && !isFirstVisit) || document.getElementById(ESCORT_LAST_VISIT_ID)) return;
		const profileHeading=document.querySelector( '.content-info-col.-desc > .content-name.-desc-name > h1, ' +
			'.content-info-col.-desc > .content-name > h1' );
		if (!profileHeading) return;
		profileHeading.style.display='block';
		const hasChanges=!isFirstVisit && changes.labels.length > 0;
		const pagePinkColor=getEscortPagePinkColor();
		const changeRedColor='#b42318',changeBg='rgba(180, 35, 24, .13)',changeText='#7a1c12';
		const visitLine=makeElement('span','',null,{id:ESCORT_LAST_VISIT_ID});
		Object.assign(visitLine.style,{
			display:'block', position:'static', float:'none', marginTop:'3px', color:pagePinkColor,
			font:'400 11px/1.35 Lato, Arial, sans-serif', opacity:'.72'
		});
		if (isFirstVisit) {
			visitLine.textContent='Wyświetlasz pierwszy raz';
		} else if (previous.lastVisitedAt) {
			visitLine.appendChild(document.createTextNode('Ostatnio odwiedzono: '));
			const relative=makeElement('strong','',formatEscortLastVisit(previous.lastVisitedAt));
			relative.style.fontWeight='600';
			relative.title=new Intl.DateTimeFormat('pl-PL',{
				dateStyle:'medium', timeStyle:'short'
			}).format(new Date(previous.lastVisitedAt));
			visitLine.appendChild(relative);
		} else {
			visitLine.textContent='Porównano z wcześniejszym zapisem w cache.';
		}
		const dateRow=document.getElementById('vm-escort-date-posted');
		if (dateRow) {
			dateRow.appendChild(visitLine);
			dateRow.style.marginBottom=hasChanges ? '5px':'18px';
		} else {
			Object.assign(visitLine.style,{
				boxSizing:'border-box', margin:hasChanges ? '0 0 5px':'0 0 18px', padding:'4px 10px',
				borderLeft:'3px solid ' + pagePinkColor
			});
			profileHeading.insertAdjacentElement('afterbegin',visitLine);
		}
		if (!hasChanges || document.getElementById(ESCORT_VISIT_SUMMARY_ID)) return;
		const row=makeElement('span');
		row.id=ESCORT_VISIT_SUMMARY_ID;
		Object.assign(row.style,{
			display:'block', boxSizing:'border-box', position:'static', float:'none', clear:'both', width:'100%',
			margin:'0 0 18px', padding:'0', border:'0', background:'transparent'
		});
		const changedLine=makeElement('span');
		Object.assign(changedLine.style,{
			display:'inline-block', position:'static', float:'none', padding:'6px 10px', borderRadius:'0 4px 4px 0',
			borderLeft:'3px solid ' + changeRedColor, background:changeBg, color:changeText, textShadow:'none',
			font:'700 11px/1.35 Lato, Arial, sans-serif'
		});
		changedLine.appendChild(document.createTextNode('Zmieniono: '));
		const values=makeElement('strong','',changes.labels.join(', '));
		changedLine.appendChild(values);
		row.appendChild(changedLine);
		if (dateRow) dateRow.insertAdjacentElement('afterend',row);
		else visitLine.insertAdjacentElement('afterend',row);
	}
	function mergeEscortDefinedObject(previous,current) {
		const merged={...(previous || {})};
		for (const [key,value] of Object.entries(current || {})) {
			if (value == null || value === '') continue;
			if (Array.isArray(value) && value.length === 0) continue;
			merged[key]=value;
		}
		return merged;
	}
	function mergeEscortAdVisitData(previous,current,visitedAt) {
		const old=previous && typeof previous === 'object' ? previous:{};
		const currentHasPhone=!!getEscortAdPhoneDigits(current);
		const previousPhone=current.phoneId && String(current.phoneId) !== String(old.phoneId || '') ? {}:old;
		const merged={
			...old, ...current, title:current.title || old.title || null, imageUrl:current.imageUrl || old.imageUrl || null,
			description:current.description || old.description || null, availability:{
				...(old.availability || {}), ...(current.availability || {})
			}, tags:Array.isArray(current.tags)
				? [...current.tags]
				:[...(old.tags || [])], prices:{...(current.prices || {})},
			datePosted:current.datePosted || old.datePosted || null,
			location:mergeEscortDefinedObject(old.location,current.location), stats:Object.keys(current.stats || {}).length
				? {...current.stats}
				:{...(old.stats || {})}, phoneId:current.phoneId || old.phoneId || null,
			phone:currentHasPhone ? current.phone:(previousPhone.phone || null),
			phoneDigits:currentHasPhone ? current.phoneDigits:(previousPhone.phoneDigits || null), phoneFetchStatus:currentHasPhone
				? current.phoneFetchStatus
				:(previousPhone.phoneFetchStatus || null), phoneCheckedAt:currentHasPhone
				? current.phoneCheckedAt
				:(previousPhone.phoneCheckedAt || null), visitSnapshot:createEscortVisitSnapshot(current,old.visitSnapshot),
			fetchTransport:'current-page', pageCheckedAt:visitedAt, lastVisitedAt:visitedAt
		};
		return merged;
	}
	function initEscortClubVisitChangeTracking() {
		if (isEscortCachedArchiveRuntime()) return;
		if (!getEscortAdIdFromPath(location.pathname)) return;
		if (document.documentElement.dataset.vmEscortVisitTracking === '1') return;
		document.documentElement.dataset.vmEscortVisitTracking='1';
		(async () => {
			let ready=false;
			for (let attempt=0; attempt < 40; attempt++) {
				ready= document.readyState !== 'loading' && !!document.querySelector(
						'.content-info-col .content-name h1, .content-name.-desc-name h1' );
				if (ready) break;
				await new Promise(resolve => setTimeout(resolve,250));
			}
			if (!ready) return;
			const adId=getAdIdFromUrl();
			if (!adId) return;
			const adUrl='https://pl.escort.club/anons/' + adId + '.html';
			const stored=getStoredEscortAdData(adId);
			const previousSnapshot=stored?.visitSnapshot || null;
			const current=CURRENT_HOST === 'pl.escort.club'
				? extractEscortClubAdData(document,adId,adUrl)
				:await getEscortAdData(adId,adUrl,true);
			if (!current || (CURRENT_HOST !== 'pl.escort.club' && current.status !== 'ok')) return;
			const phone=await resolveEscortPhoneForCurrentPage(current.phoneId);
			if (phone) Object.assign(current,phone);
			const changes=compareEscortAdVisitData(previousSnapshot,current);
			const previousVisitInfo=(previousSnapshot || stored?.lastVisitedAt)
				? {
					...(previousSnapshot || {}), lastVisitedAt:stored?.lastVisitedAt || null
				}
				:null;
			renderEscortVisitSummary( previousVisitInfo, changes, !previousVisitInfo );
			if (previousSnapshot) {
				renderEscortVisitChanges( changes );
			}
			const visitedAt=Date.now();
			const merged=mergeEscortAdVisitData(stored,current,visitedAt);
			const saved=setPersistentEscortAdData(adId,merged);
			if (saved) {
				escortAdDataMemoryCache.set(String(adId),saved);
				storeWatchedAdDataMirror(adId,saved);
			}
		})().catch(error => {
			log('Błąd porównania danych pojedynczego anonsu',error);
		});
	}
	function isValidEscortAdTemplateCache(value,doc=document) {
		if (![1,2].includes(value?.schema) || !Number.isFinite(value.savedAt) || value.savedAt <= 0 || value.savedAt > Date.now() ||
			typeof value.html !== 'string' || typeof value.css !== 'string' || !value.css.trim() ||
			new TextEncoder().encode(JSON.stringify(value)).length > 100000 ||
			/[<>\\]|url\s*\(|@import|image-set\s*\(|expression\s*\(|(?:^|[;{])\s*(?:behavior|content|src)\s*:/i.test(value.css)) return false;
		const holder=doc.createElement('template');
		holder.innerHTML=value.html;
		const root=holder.content;
		const allowedTags=new Set(['MAIN','SECTION','DIV','H1','SPAN','A']);
		const labels=new Set(['Godziny dostępności','Tagi','Parametry','Ceny','O mnie','Więcej o mnie','Dodatkowe informacje']);
		for (const element of root.querySelectorAll('*')) {
			if (!allowedTags.has(element.tagName) || [...element.attributes].some(attribute =>
				attribute.name !== 'class' || !/^[a-zA-Z0-9 _-]*$/.test(attribute.value))) return false;
			for (const child of element.childNodes) if (child.nodeType === 3 && child.textContent.trim() &&
				(!element.classList.contains('label') || !labels.has(child.textContent.trim()))) return false;
		}
		if ([...root.childNodes].some(node => node.nodeType === 3 && node.textContent.trim())) return false;
		return ['main .content-info-col .content-name h1','.content-location','.galleryContainer',
			'.content-desc .tab-content','.content-hours .stats-box','.content-prices .stats-box','.adsPhone a',
			'.sub-info-items','.tags-box',...(value.schema === 2 ? ['.content-side-col .content-sidebar-card',
				'.content-extra .sub-info-items','.content-extra .tags-box']:[])]
			.every(selector => !!root.querySelector(selector)) && [...root.querySelectorAll('.sub-info-box .label')]
			.some(label => label.textContent === 'Godziny dostępności') && [...root.querySelectorAll('.sub-info-box .label')]
			.some(label => label.textContent === 'Tagi');
	}
	function createEscortAdTemplateCache(savedAt,appearance='') {
		const html=`<main class="vm-cached-ad-layout">
<section class="content-sec">
	<div class="content-info-wrapper -left">
		<div class="content-gallery-col"><div class="galleryContainer"></div></div>
		<div class="content-info-col -desc">
			<div class="content-name -desc-name"><h1></h1><div class="content-location"></div></div>
			<div class="content-desc"><div class="label">O mnie</div><div class="tab-content"></div></div>
		</div>
		<div class="content-side-col content-info-col -info">
			<div class="content-sidebar-card">
				<div class="content-contact"><div class="adsPhone"><a></a></div></div>
				<div class="content-hours"><div class="label">Więcej o mnie</div><div class="stats-box"></div></div>
			</div>
			<div class="content-prices contant-prices"><div class="label">Ceny</div><div class="stats-box"></div></div>
		</div>
	</div>
	<section class="content-extra">
		<div class="label content-extra-title">Dodatkowe informacje</div>
		<div class="content-extra-grid">
			<div class="sub-info-box"><div class="label">Godziny dostępności</div><div class="sub-info-items"></div></div>
			<div class="sub-info-box"><div class="label">Tagi</div><div class="tags-box"></div></div>
		</div>
	</section>
</section></main>`;
		const css=`
* { box-sizing: border-box; }
body { margin: 0; padding: 18px; color: #333; background-color: #fff; font: 14px/1.5 Arial,sans-serif; overflow-wrap: anywhere; }
${appearance}
main { max-width: 1280px; margin: 0 auto; }
h1 { margin: 0 0 14px; font-size: 32px; line-height: 1.2; font-weight: 400; }
.content-info-wrapper { display: grid; grid-template-areas: "gallery info side"; grid-template-columns: minmax(0,.9fr) minmax(0,1.15fr) minmax(280px,1fr); align-items: start; gap: 32px; }
.content-gallery-col,.content-info-col,.content-side-col { min-width: 0; }
.content-gallery-col { grid-area: gallery; }
.content-info-col.-desc { grid-area: info; }
.content-side-col { grid-area: side; }
.content-info-wrapper:not(:has(.content-gallery-col)) { grid-template-areas: "info side"; grid-template-columns: minmax(0,1.15fr) minmax(280px,1fr); }
.galleryContainer { max-width: 340px; margin: 0 auto; }
.galleryContainer img { display: block; width: 100%; height: auto; border-radius: 8px; }
.content-location { margin: 20px 0; font-size: 17px; }
.content-desc { margin-top: 34px; }
.content-desc .tab-content { white-space: pre-wrap; }
.content-sidebar-card { border: 1px solid rgba(128,128,128,.25); border-radius: 9px; }
.content-contact { padding: 18px 20px; border-bottom: 1px solid rgba(128,128,128,.25); }
.adsPhone { font-size: 20px; }
.adsPhone a { color: inherit; text-decoration: none; }
.content-hours { padding: 18px 20px; }
.content-prices { padding: 20px; }
.label { margin: 0 0 12px; font-weight: 600; }
.stats-box,.sub-info-items { display: grid; gap: 10px; }
.stat-elem,.sub-info-elem { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 16px; }
.stat-elem .sub-label,.sub-info-elem .sub-label { flex: 0 0 auto; max-width: 52%; }
.stat-elem .sub-desc,.sub-info-elem .desc { min-width: 0; text-align: right; }
.content-extra { margin-top: 48px; }
.content-extra-title { margin-bottom: 22px; font-size: 24px; font-weight: 400; }
.content-extra-grid { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1.4fr); gap: 40px; }
.content-extra .sub-info-items { max-width: 380px; }
.tags-box { display: flex; flex-wrap: wrap; gap: 8px; }
.tag { display: inline-block; max-width: 100%; padding: 6px 14px; border-radius: 999px; color: #6d2244; background-color: #ffe0ed; font-size: 12px; line-height: 1.4; text-align: center; }
@media (max-width: 960px) {
	.content-info-wrapper { grid-template-areas: "gallery info" "side side"; grid-template-columns: minmax(0,1fr) minmax(0,1.3fr); gap: 24px; }
	.content-info-wrapper:not(:has(.content-gallery-col)) { grid-template-areas: "info" "side"; grid-template-columns: minmax(0,1fr); }
	.content-side-col { display: grid; grid-template-columns: minmax(0,1.3fr) minmax(0,1fr); align-items: start; gap: 24px; }
}
@media (max-width: 640px) {
	body { padding: 12px; }
	.content-info-wrapper,.content-side-col,.content-extra-grid { grid-template-columns: minmax(0,1fr); gap: 22px; }
	.content-info-wrapper { grid-template-areas: "gallery" "info" "side"; }
	.content-desc { margin-top: 24px; }
	.content-extra { margin-top: 32px; }
	.content-extra .sub-info-items { max-width: none; }
}
`;
		return {schema:2,savedAt,html,css};
	}
	function readEscortAdTemplateCache() {
		try {
			const value=GM_getValue(ESCORT_AD_TEMPLATE_CACHE_KEY,null);
			if (!isValidEscortAdTemplateCache(value)) return null;
			if (value.schema === 2 && value.html === createEscortAdTemplateCache(value.savedAt).html) return value;
			// Stały szablon zachowuje motyw i datę.
			const appearance=[...value.css.matchAll(/(?:^|[}\n])\s*(body|h1)\s*\{([^{}]*)\}/g)]
				.map(([,selector,declarations]) => {
					const kept=declarations.split(';').filter(rule => /^\s*(?:color|background-color|font-family|font-size|font-weight|font-style|line-height|letter-spacing)\s*:/.test(rule));
					return kept.length ? `${selector}{${kept.join(';')}}`:'';
				}).join('\n');
			const updated=createEscortAdTemplateCache(value.savedAt,appearance);
			try {writeHelperStorageValue(ESCORT_AD_TEMPLATE_CACHE_KEY,updated);} catch {}
			return updated;
		} catch {return null;}
	}
	function saveEscortAdTemplateFromPage(doc=document) {
		if (!doc?.body || !doc.defaultView || isEscortCachedArchiveRuntime()) return false;
		try {
			const page=new URL(doc.URL || location.href),heading=doc.querySelector('.content-info-col .content-name h1, .content-name.-desc-name h1');
			if (!isEscortClubHost(page.hostname) || !getEscortAdIdFromPath(page.pathname) ||
				new URLSearchParams(page.hash.slice(1)).has('vm_cached_template') ||
				doc.documentElement.dataset.vmCachedArchiveReady || !heading?.textContent.trim() ||
				!doc.querySelector('.content-desc') || !doc.querySelector('.content-hours .stats-box, .contant-prices .stats-box, .content-prices .stats-box')) return false;
			const properties=['color','background-color','font-family','font-size','font-weight','font-style','line-height','letter-spacing'];
			const rules=[];
			const transparent=value => !value || value === 'transparent' || /^(?:rgba\([^)]*,\s*0(?:\.0+)?\)|[a-z]+\([^)]*\/\s*0(?:\.0+)?%?\))$/i.test(value);
			for (const [selector,source] of [['body',doc.body],['h1',heading]]) {
				const computed=doc.defaultView.getComputedStyle(source),values=[];
				for (const property of properties) {
					let value=computed.getPropertyValue(property).trim();
					if (selector === 'body' && property === 'background-color' && transparent(value)) {
						value=doc.defaultView.getComputedStyle(doc.documentElement).getPropertyValue(property).trim();
						if (transparent(value)) value='#fff';
					}
					if (value && value.length < 240 && !/[<>\\{};@]|url\s*\(|image-set\s*\(|expression\s*\(/i.test(value)) values.push(`${property}:${value}`);
				}
				if (values.length) rules.push(`${selector}{${values.join(';')}}`);
			}
			const value=createEscortAdTemplateCache(Date.now(),rules.join('\n'));
			if (!isValidEscortAdTemplateCache(value,doc)) return false;
			const previous=readEscortAdTemplateCache();
			if (previous?.html !== value.html || previous?.css !== value.css) writeHelperStorageValue(ESCORT_AD_TEMPLATE_CACHE_KEY,value);
			return true;
		} catch (error) {
			log('Nie udało się zapisać szablonu anonsu',error);
			return false;
		}
	}
	function getStoredEscortAdData(adId) {
		try {
			const value=readPersistentCacheValue(escortAdDataCacheKey(adId),null);
			if (!value || typeof value !== 'object' || value.status !== 'ok' || !value.checkedAt) {
				return null;
			}
			return sanitizeEscortAdDataPhone(value);
		} catch {
			return null;
		}
	}
	function formatEscortCachedArchiveDate(timestamp) {
		const value=Number(timestamp);
		if (!Number.isFinite(value) || value <= 0) return '';
		try {
			return formatServerMediaDate(new Date(value));
		} catch {
			return '';
		}
	}
	function isEscortCachedArchiveRuntime() {
		if (escortCachedArchiveRuntimeOverride) return true;
		const raw=String(location.hash || '').replace(/^#/,'');
		if (!raw) return false;
		try {
			return new URLSearchParams(raw).get(ESCORT_CACHED_AD_ARCHIVE_VIEW_HASH_KEY) === '1';
		} catch {
			return false;
		}
	}
	function getEscortCachedArchiveMarkerFromHash() {
		const raw=String(location.hash || '').replace(/^#/,'');
		if (!raw) return null;
		try {
			const params=new URLSearchParams(raw);
			const adId=params.get(ESCORT_CACHED_AD_HASH_KEY);
			return /^\d+$/.test(adId || '') ? adId:null;
		} catch {
			return null;
		}
	}
	function addEscortCachedArchiveMarkerToUrl(value) {
		try {
			const url=new URL(value,location.href);
			if (!isEscortClubHost(url.hostname)) return value;
			const adId=parseAdIdFromUrl(url.href);
			if (!adId || !getEscortAdIdFromPath(url.pathname)) return value;
			const parts=url.hash.slice(1).split('&').filter(part => part && !part.startsWith(`${ESCORT_CACHED_AD_HASH_KEY}=`) );
			parts.push(`${ESCORT_CACHED_AD_HASH_KEY}=${encodeURIComponent(adId)}`);
			url.hash=parts.join('&');
			return url.href;
		} catch {
			return value;
		}
	}
	function rememberEscortCachedAdNavigation(adId) {
		if (!adId) return;
		try {
			sessionStorage.setItem( ESCORT_CACHED_AD_SESSION_KEY, JSON.stringify({adId:String(adId),at:Date.now()}) );
		} catch {}
	}
	function readRecentEscortCachedAdNavigation() {
		try {
			const value=JSON.parse(sessionStorage.getItem(ESCORT_CACHED_AD_SESSION_KEY) || 'null');
			if (!value || !/^\d+$/.test(String(value.adId || ''))) return null;
			if (Date.now() - Number(value.at || 0) > 15000) return null;
			return String(value.adId);
		} catch {
			return null;
		}
	}
	function clearEscortCachedAdNavigationMarker() {
		try {sessionStorage.removeItem(ESCORT_CACHED_AD_SESSION_KEY);} catch {}
		if (!getEscortCachedArchiveMarkerFromHash()) return;
		try {
			history.replaceState( history.state, '', `${location.pathname}${location.search}` );
		} catch {}
	}
	function installEscortCachedAdNavigationMarker() {
		if (document.documentElement.dataset.vmCachedAdNavigationMarker === '1') return;
		document.documentElement.dataset.vmCachedAdNavigationMarker='1';
		const prepareAnchor=event => {
			const anchor=event.target?.closest?.('a[href]');
			if (!anchor) return;
			let url;
			try {url=new URL(anchor.href,location.href);} catch {return;}
			const adId=parseAdIdFromUrl(url.href);
			if ( !isEscortClubHost(url.hostname) || !adId || !getEscortAdIdFromPath(url.pathname) ) return;
			anchor.href=addEscortCachedArchiveMarkerToUrl(url.href);
			if ( event.type === 'click' && event.button === 0 &&
				!event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey &&
				String(anchor.target || '').toLowerCase() !== '_blank' ) {
				rememberEscortCachedAdNavigation(adId);
			}
		};
		document.addEventListener('pointerdown',prepareAnchor,true);
		document.addEventListener('click',prepareAnchor,true);
		document.addEventListener('auxclick',prepareAnchor,true);
	}
	function formatEscortCachedArchiveDuration(key) {
		if (key === 'night') return 'Cała noc';
		const minutes=Number(key);
		if (!Number.isFinite(minutes) || minutes <= 0) return String(key || 'Cena');
		if (minutes < 60) return `${minutes} min`;
		return `${String(minutes / 60).replace('.',',')} h`;
	}
	function replaceEscortTemplateRows(container,entries,rowClass,valueClass) {
		if (!container) return;
		container.replaceChildren();
		for (const [label,value] of entries || []) {
			if (value == null || String(value).trim() === '') continue;
			const row=container.ownerDocument.createElement('div');
			row.className=rowClass;
			const labelNode=container.ownerDocument.createElement('span');
			labelNode.className='sub-label';
			labelNode.textContent=label;
			const valueNode=container.ownerDocument.createElement('span');
			valueNode.className=valueClass;
			valueNode.textContent=String(value);
			row.append(labelNode,valueNode);
			container.appendChild(row);
		}
	}
	function getEscortCachedArchiveImageUrl(adId,cached) {
		const cachedUrl=String(cached?.imageUrl || '').trim();
		if (cachedUrl) return cachedUrl;
		const targetId=String(adId);
		const watched=getWatchedItems().find(item => parseAdIdFromUrl(item.adUrl) === targetId &&
			String(item.imageUrl || '').trim() );
		if (watched?.imageUrl) return String(watched.imageUrl).trim();
		const profileWithResolvedAd=getWatchedItems().find(item => item.sourceType === 'escorti-profile' &&
			Array.isArray(item.resolvedAdUrls) && item.resolvedAdUrls.some(url => parseAdIdFromUrl(url) === targetId) &&
			String(item.imageUrl || '').trim() );
		return String(profileWithResolvedAd?.imageUrl || '').trim();
	}
	function getEscortArchiveLocationText(cached) {
		const missing=value => /^(?:brak|brak danych|b[./]?d[.]?)$/i.test( normalizeEscortAdText(value) );
		const cleanParts=value => String(value || '') .split(',') .map(normalizeEscortAdText)
			.filter(part => part && !missing(part));
		const location=cached?.location || {};
		const addressTextParts=cleanParts(location.addressText);
		if (addressTextParts.length) return addressTextParts.join(', ');
		const addressParts=Array.isArray(location.addressParts)
			? location.addressParts .map(normalizeEscortAdText) .filter(part => part && !missing(part))
			:[];
		if (addressParts.length) return addressParts.join(', ');
		const parts=[ location.country, location.province, location.city, location.district, location.area
		].map(normalizeEscortAdText).filter(part => part && !missing(part));
		if (parts.length) return parts.join(', ');
		const fallback=cleanParts(cached?.address);
		return fallback.length ? fallback.join(', '):'Brak zapisanej lokalizacji';
	}
	function replaceEscortArchivePublishedDate(doc,heading,cached) {
		detachHelperNode(doc.getElementById('vm-cached-ad-published'));
		const published=formatEscortClubDatePosted(cached.datePosted);
		const visitedAt=Number(cached.lastVisitedAt) || 0;
		const visitedDate=formatEscortCachedArchiveDate(visitedAt);
		const visitedAge=visitedAt ? formatEscortLastVisit(visitedAt):'';
		if (!published && !visitedDate && !visitedAge) return;
		const row=doc.createElement('div');
		row.id='vm-cached-ad-published';
		row.style.cssText= `margin:0 0 14px;padding:4px 0;` + `color:${getEscortPagePinkColor()};font-size:14px`;
		if (published) {
			const publishedLine=doc.createElement('div');
			publishedLine.textContent=`Opublikowano: ${published}`;
			row.appendChild(publishedLine);
		}
		if (visitedDate || visitedAge) {
			const visitValue=[ visitedDate, visitedAge ? `(${visitedAge})`:'' ].filter(Boolean).join(' ');
			const visitedLine=doc.createElement('div');
			visitedLine.textContent=`Ostatnio odwiedzono: ${visitValue}`;
			visitedLine.style.marginTop=published ? '2px':'0';
			row.appendChild(visitedLine);
		}
		heading.insertAdjacentElement('afterend',row);
	}
	function renderEscortArchiveFallbackHelperUi(doc,adId,cached) {
		const heading=doc.querySelector('.content-info-col .content-name h1, .content-name.-desc-name h1');
		const nameBox=heading?.closest('.content-name'),locationBox=nameBox?.querySelector('.content-location');
		const previous=doc.getElementById('vm-escort-favorite-note-row');
		previous?._vmDispose?.(); detachHelperNode(previous);
		const settings=getWatchSettings();
		if (!heading || !locationBox || !settings.enabled) return;
		const id=String(adId),storedUrl=normalizeEscortiAdUrl(cached.adUrl || '');
		const adUrl=parseAdIdFromUrl(storedUrl) === id ? storedUrl:`https://pl.escort.club/anons/${id}.html`;
		const getMatches=() => getFavoriteMatchesForCurrentEscortAd(id);
		let disposed=false;
		const change=async (itemId,action,value) => {
			if (disposed || !root.isConnected) return;
			try {
				await changeTrackedEntry(itemId,action,value,{getMatches,getContext:() => ({
					sourceType:'escort-ad',resolutionMode:'single',adUrl,displayName:cached.title || `Anons ${id}`,
					imageUrl:getEscortCachedArchiveImageUrl(id,cached),phoneDigits:getEscortAdPhoneDigits(cached) || ''
				})});
			} finally {refresh();}
		};
		const {root,panel,list,render}=createTrackedEntriesBox(change,{
			accent:getEscortPagePinkColor(),fallbackText:doc.defaultView.getComputedStyle(heading).color || '#222',
			emptyAdOwnerKey:`single:${id}`
		});
		root.id='vm-escort-favorite-note-row'; root.classList.add('vm-ad-summary-box'); root.dataset.vmWatchWrapper='1';
		panel.id='vm-escort-tracked-panel'; list.id='vm-escort-favorite-note-list';
		const dispose=() => {
			disposed=true; watchPageButtonRefreshers.delete(refresh);
			const picker=doc.querySelector('.vm-favorites-category-picker');
			if (picker?._vmAnchor && root.contains(picker._vmAnchor)) closeHelperDialog(picker,'archive-close');
		};
		const refresh=() => {
			if (disposed || !root.isConnected) {dispose(); return;}
			const current=getWatchSettings(); root.hidden=!current.enabled;
			if (root.hidden) return;
			const matches=getMatches();
			render([getTrackedScopeItem(matches,'single'),...matches.filter(item => item.sourceType !== 'escort-ad')]);
		};
		root._vmRender=refresh; root._vmDispose=dispose;
		nameBox.insertBefore(root,locationBox);
		watchPageButtonRefreshers.add(refresh); refresh();
		for (const styleId of ['vm-tracked-note-category-style','vm-escort-helper-style-api']) cloneEscortHelperStyleIntoDocument(doc,styleId);
	}
	function applyEscortArchiveTheme(doc) {
		copyHelperThemeVariablesIntoDocument(doc,document);
		const root=doc.documentElement.style,view=doc.defaultView,heading=doc.querySelector('h1');
		const bodyStyle=view.getComputedStyle(doc.body);
		const background=root.getPropertyValue('--vm-ui-bg').trim() || bodyStyle.backgroundColor;
		let text=root.getPropertyValue('--vm-ui-text').trim(),headingText=text;
		if (!text) {
			const canvas=doc.createElement('canvas'); canvas.width=canvas.height=1;
			const context=canvas.getContext('2d',{willReadFrequently:true});
			const luminance=color => {
				context.fillStyle='#fff'; context.fillRect(0,0,1,1);
				context.fillStyle=color; context.fillRect(0,0,1,1);
				return [...context.getImageData(0,0,1,1).data].slice(0,3).reduce((sum,value,index) => {
					const channel=value/255;
					return sum+(channel<=.04045 ? channel/12.92:((channel+.055)/1.055)**2.4)*[.2126,.7152,.0722][index];
				},0);
			};
			const bg=luminance(background),contrast=color => {
				const fg=luminance(color); return (Math.max(bg,fg)+.05)/(Math.min(bg,fg)+.05);
			};
			headingText=heading ? view.getComputedStyle(heading).color:bodyStyle.color;
			text=[bodyStyle.color,headingText,'#fff','#000'].find(color => contrast(color)>=4.5);
			if (contrast(headingText)<4.5) headingText=text;
		}
		for (const [name,value] of Object.entries({bg:background,text,'text-muted':text,
			'panel-bg':background,'panel-text':text,'panel-muted':text,'input-bg':background,'input-text':text}))
			if (!root.getPropertyValue(`--vm-ui-${name}`).trim()) root.setProperty(`--vm-ui-${name}`,value);
		doc.body.style.color='var(--vm-ui-text)'; doc.body.style.backgroundColor='var(--vm-ui-bg)';
		if (heading) heading.style.color=headingText;
	}
	function cloneEscortHelperStyleIntoDocument( targetDocument, styleId ) {
		if (!targetDocument) return;
		copyHelperThemeVariablesIntoDocument( targetDocument, document );
		if ( targetDocument.getElementById(styleId) ) return;
		const source= document.getElementById(styleId);
		if (!source) return;
		const clone=targetDocument.createElement('style');
		clone.id=styleId;
		clone.textContent=source.textContent;
		targetDocument.head?.appendChild(clone);
	}
	async function renderEscortArchiveFullFallbackUi( doc, adId, cached ) {
		if (!doc?.body) return false;
		try {await ensureEscortPageCache([String(adId)],getEscortAdPhoneDigits(cached) || '');}
		catch (error) {log('Nie udało się odczytać części danych panelu z cache',error);}
		if (!doc.defaultView?.frameElement?.isConnected) return false;
		copyHelperThemeVariablesIntoDocument( doc, document );
		renderEscortArchiveFallbackHelperUi( doc, adId, cached );
		const adUrl= `https://pl.escort.club/anons/${adId}.html`;
		const phone=getEscortAdPhoneDigits(cached) || '';
		let researchPanel=null;
		let sidePanel=null;
		let sideToggle=null;
		const previousOverride= escortCachedArchiveRuntimeOverride;
		try {
			escortCachedArchiveRuntimeOverride=true;
			ensureEscortSummarySidePanelStyle();
			researchPanel= createEscortResearchPanel( phone, adUrl, {archiveData:cached,archiveDocument:doc} );
			sidePanel= document.getElementById( ESCORT_SUMMARY_SIDE_PANEL_ID );
			sideToggle= document.getElementById( 'vm-escort-summary-side-toggle' );
		} catch (error) {
			log( 'Nie udało się zbudować pełnego interfejsu archiwalnego z cache', error );
		} finally {
			escortCachedArchiveRuntimeOverride= previousOverride;
		}
		document.body.classList.remove( ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS, ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS );
		if (researchPanel) {
			detachHelperNode(researchPanel);
			doc.adoptNode(researchPanel);
			placeEscortResearchPanelAboveActionButtons( researchPanel, null, doc, {retry:false} );
		}
		ensureGarsoSummaryCardsStyle();
		for (const styleId of [ESCORT_SUMMARY_SIDE_PANEL_STYLE_ID,'vm-escort-helper-style-api',
			'vm-garso-summary-cards-style',ESCORT_COMPACT_SUMMARY_ID + '-style']) cloneEscortHelperStyleIntoDocument(doc,styleId);
		if ( SETTINGS.showSummarySidePanel && sidePanel && sideToggle ) {
			detachHelperNode(sidePanel);
			detachHelperNode(sideToggle);
			doc.adoptNode(sidePanel);
			doc.adoptNode(sideToggle);
			doc.body.append( sidePanel, sideToggle );
			bindEscortSummarySidePanelControls( doc, sidePanel, sideToggle );
		} else {
			detachHelperNode(sidePanel);
			detachHelperNode(sideToggle);
		}
		applyEscortClubTagHighlights(doc);
		return !!( researchPanel || sidePanel );
	}
	function populateEscortArchiveTemplate(doc,adId,cached,closeView) {
		const title=normalizeEscortAdText(cached.title) || `Anons ${adId}`;
		const locationText=getEscortArchiveLocationText(cached);
		const savedDate=formatEscortCachedArchiveDate( cached.pageCheckedAt || cached.checkedAt || cached.lastVisitedAt );
		doc.title=`${title} – zapisana wersja – Escort.club`;
		const heading=doc.querySelector( '.content-info-col .content-name h1, .content-name.-desc-name h1' );
		if (!heading) return false;
		heading.textContent=title;
		replaceEscortArchivePublishedDate(doc,heading,cached);
		const locationLabel=doc.createElement('span');
		locationLabel.className='sub-label vm-cached-archive-location';
		locationLabel.textContent=locationText;
		doc.querySelector('.content-location').replaceChildren(locationLabel);
		const galleryColumn=doc.querySelector('.content-gallery-col');
		const gallery=doc.querySelector('.galleryContainer');
		const archiveImageUrl=getEscortCachedArchiveImageUrl(adId,cached);
		if (galleryColumn && gallery) {
			if (archiveImageUrl) {
				gallery.replaceChildren();
				const link=doc.createElement('a');
				link.className='simple-zoom-image';
				link.href=archiveImageUrl;
				link.target='_blank';
				link.rel='noopener';
				const image=doc.createElement('img');
				image.src=archiveImageUrl;
				image.alt=title;
				image.style.cssText='display:block;width:100%;height:auto';
				image.addEventListener('error',() => detachHelperNode(galleryColumn),{once:true});
				link.appendChild(image);
				gallery.appendChild(link);
			} else {
				detachHelperNode(galleryColumn);
			}
		}
		doc.querySelector('.content-desc .tab-content').textContent=normalizeEscortAdText(cached.description)
			? String(cached.description):'Brak opisu w cache.';
		replaceEscortTemplateRows( doc.querySelector('.sub-info-items'),
			Object.entries(cached.availability || {}).map(([day,value]) => [ `${day.replace(/:\s*$/,'')}:`, value ]),
			'sub-info-elem', 'desc' );
		const tagsContainer=doc.querySelector('.tags-box');
		if (tagsContainer) {
			tagsContainer.replaceChildren();
			for (const value of cached.tags || []) {
				const tag=normalizeEscortAdText(value);
				if (!tag) continue;
				const node=doc.createElement('span');
				node.className='tag';
				node.textContent=tag;
				tagsContainer.appendChild(node);
			}
		}
		const statsContainer=doc.querySelector('.content-hours .stats-box');
		replaceEscortTemplateRows( statsContainer, Object.entries(cached.stats || {}).map(([label,value]) => [
				`${label.replace(/:\s*$/,'')}:`, value ]), 'stat-elem', 'sub-desc' );
		const priceContainer=doc.querySelector( '.contant-prices .stats-box, .content-prices .stats-box' );
		replaceEscortTemplateRows( priceContainer, Object.entries(cached.prices || {}).map(([key,price]) => [
				`${formatEscortCachedArchiveDuration(key)}:`, formatEscortTilePrice(price) || 'brak ceny' ]), 'stat-elem',
			'sub-desc' );
		const cachedPhoneDigits=getEscortAdPhoneDigits(cached);
		const phoneLink=doc.querySelector('.adsPhone a');
		if (cachedPhoneDigits) {
			phoneLink.href=`tel:${cachedPhoneDigits}`;
			phoneLink.textContent=formatEscortPhoneForDisplay(cachedPhoneDigits);
		} else {
			detachHelperNode(phoneLink.closest('.content-contact'));
		}
		const main=doc.querySelector('main') || doc.body;
		const notice=doc.createElement('div');
		notice.id='vm-cached-ad-template-notice';
		notice.style.cssText= `margin:0 auto 22px;padding:12px 16px;max-width:1280px;` +
			`border:1px solid var(--vm-ui-border,rgba(128,128,128,.3));border-left:4px solid ${getEscortPagePinkColor()};border-radius:7px;` +
			`background:var(--vm-ui-bg,#fff);color:var(--vm-ui-text,#332c30);font-size:14px;font-weight:600`;
		notice.appendChild(doc.createTextNode(
			`Zapisana wersja anonsu${savedDate ? ` z ${savedDate}`:''} • dane z pamięci podręcznej` ));
		const close=doc.createElement('button');
		close.type='button';
		close.textContent='Przejdź na stronę główną';
		close.style.cssText= `margin-left:14px;border:0;background:transparent;` +
			`color:${getEscortPagePinkColor()};font-size:14px;font-weight:800;cursor:pointer`;
		close.addEventListener('click',() => {
			try {
				doc.defaultView.top.location.href=ESCORT_CLUB_ORIGIN + '/';
			} catch {
				closeView();
			}
		});
		notice.appendChild(close);
		const originalAdUrl= normalizeEscortiAdUrl(cached.adUrl || '') ||
			(adId ? `https://pl.escort.club/anons/${adId}.html`:'');
		if (originalAdUrl) {
			const linkLine=doc.createElement('div');
			linkLine.style.cssText= 'margin-top:6px;font-size:13px;font-weight:600;line-height:1.35';
			linkLine.appendChild(doc.createTextNode('Link anonsu: '));
			const link=doc.createElement('a');
			link.id='vm-cached-ad-original-link';
			link.href=originalAdUrl;
			link.target='_blank';
			link.rel='noopener noreferrer';
			link.textContent=originalAdUrl;
			link.style.cssText= `color:${getEscortPagePinkColor()};text-decoration:underline;` + `overflow-wrap:anywhere`;
			linkLine.appendChild(link);
			notice.appendChild(linkLine);
		}
		detachHelperNode(doc.getElementById('vm-cached-ad-template-notice'));
		main.insertBefore(notice,main.firstChild);
		return true;
	}
	function renderEscortCachedAdSnapshot(adId,cached) {
		const template=readEscortAdTemplateCache();
		if (!template) {
			window.alert('Brak zapisanego szablonu anonsu. Gdy strona będzie działać, otwórz dowolny aktywny anons — szablon zapisze się automatycznie.');
			return;
		}
		const previousView=document.getElementById('vm-cached-ad-view');
		if (previousView?._vmClose) previousView._vmClose();
		else detachHelperNode(previousView);
		const banner=document.getElementById('vm-cached-ad-unavailable-banner');
		const button=banner?.querySelector('button'),label=button?.textContent,previousBannerDisplay=banner?.style.display;
		if (button) {button.disabled=true; button.textContent='Otwieranie zapisanej wersji…';}
		const iframe=makeElement('iframe');
		iframe.id='vm-cached-ad-view'; iframe.title='Zapisana wersja anonsu';
		iframe.setAttribute('sandbox','allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation');
		Object.assign(iframe.style,{
			position:'fixed',inset:'0',width:'100%',height:'100%',border:'0',
			zIndex:'2147483646',background:'#fff',visibility:'hidden'
		});
		const previousOverflow=document.documentElement.style.overflow;
		document.documentElement.style.overflow='hidden';
		let closed=false,ready=false,timer=null;
		const restore=() => {if (button) {button.disabled=false; button.textContent=label;}};
		const closeView=() => {
			if (closed) return;
			closed=true; clearTimeout(timer);
			try {iframe.contentDocument?.getElementById('vm-escort-favorite-note-row')?._vmDispose?.();} catch {}
			document.documentElement.style.overflow=previousOverflow;
			detachHelperNode(iframe); if (banner) banner.style.display=previousBannerDisplay; restore();
		};
		iframe._vmClose=closeView;
		const fail=error => {
			if (closed || ready) return;
			closeView(); log('Nie udało się otworzyć szablonu z cache',error);
			window.alert('Nie udało się otworzyć zapisanego szablonu. Dane anonsu pozostały w cache.');
		};
		iframe.addEventListener('load',async () => {
			if (closed || ready) return;
			try {
				const doc=iframe.contentDocument;
				if (!doc?.querySelector('main')) return;
				doc.documentElement.dataset.vmCachedArchiveAdId=String(adId);
				applyEscortArchiveTheme(doc);
				if (!populateEscortArchiveTemplate(doc,adId,cached,closeView)) throw new Error('Niekompletny szablon');
				const close=doc.createElement('button');
				close.type='button'; close.textContent='Zamknij';
				close.style.cssText='float:right;margin:0 0 8px 12px;padding:7px 12px;cursor:pointer;border:1px solid var(--vm-ui-border,#bbb);border-radius:5px;background:var(--vm-ui-bg-alt,var(--vm-ui-bg,#f5f5f5));color:var(--vm-ui-text,#222)';
				close.addEventListener('click',closeView);
				(doc.getElementById('vm-cached-ad-template-notice') || doc.body).prepend(close);
				doc.documentElement.dataset.vmCachedArchiveReady='1';
				ready=true; clearTimeout(timer); restore(); if (banner) banner.style.display='none';
				iframe.style.visibility='visible';
				try {await renderEscortArchiveFullFallbackUi(doc,adId,cached);}
				catch (error) {if (!closed) log('Nie udało się uzupełnić panelu archiwalnego z cache',error);}
			} catch (error) {fail(error);}
		});
		iframe.addEventListener('error',fail,{once:true});
		iframe.srcdoc='<!doctype html><html lang="pl"><head><meta charset="utf-8">' +
			'<meta name="viewport" content="width=device-width,initial-scale=1">' +
			'<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; img-src https: data:; style-src \'unsafe-inline\'; base-uri \'none\'; form-action \'none\'">' +
			'<style>' + template.css + '</style></head><body>' + template.html + '</body></html>';
		timer=setTimeout(() => fail(new Error('Przekroczono czas otwierania lokalnego szablonu')),10000);
		document.body.appendChild(iframe);
	}
	function renderEscortCachedAdUnavailableBanner(adId,cached) {
		if (document.getElementById('vm-cached-ad-unavailable-banner')) return;
		const savedAt=Number(cached.pageCheckedAt || cached.checkedAt || cached.lastVisitedAt || 0);
		const savedDate=formatEscortCachedArchiveDate(savedAt);
		const banner=prepareHelperStyleApi(makeElement('div'));
		banner.id='vm-cached-ad-unavailable-banner';
		Object.assign(banner.style,{
			position:'fixed', inset:'22px', zIndex:'2147483645', display:'flex', flexDirection:'column', alignItems:'center',
			justifyContent:'center', boxSizing:'border-box', padding:'32px', border:'1px solid var(--vm-ui-border,#ddd)',
			borderLeft:'8px solid var(--vm-ui-accent,#f54da3)', borderRadius:'12px',
			background:'var(--vm-ui-bg,rgba(255,255,255,.985))', color:'var(--vm-ui-text,#332c30)',
			boxShadow:'var(--vm-ui-shadow,0 12px 46px rgba(0,0,0,.34))', fontFamily:'Lato, Arial, sans-serif',
			textAlign:'center'
		});
		const title=makeElement('div','','Anons jest obecnie niedostępny');
		Object.assign(title.style,{
			fontSize:'30px', fontWeight:'900', marginBottom:'18px'
		});
		const actions=makeElement('div');
		Object.assign(actions.style,{
			display:'flex', flexWrap:'wrap', alignItems:'center', justifyContent:'center', gap:'12px'
		});
		const button=makeButton('vm-ui-button -primary',`Pokaż zapisaną wersję${savedDate ? ` z ${savedDate}`:''}`);
		const homeButton=makeButton('vm-ui-button -accent','Przejdź do strony głównej');
		for (const control of [button,homeButton]) {
			Object.assign(control.style,{padding:'12px 18px',borderRadius:'7px',fontSize:'15px',fontWeight:'900'});
		}
		button.addEventListener('click',() => renderEscortCachedAdSnapshot(adId,cached));
		homeButton.addEventListener('click',() => {
			location.href=ESCORT_CLUB_ORIGIN + '/';
		});
		actions.append(button,homeButton);
		banner.append(title,actions);
		document.body.appendChild(banner);
	}
	async function handleEscortCachedAdArchivePage() {
		if (!IS_ESCORT_CLUB) return false;
		const markerAdId=getEscortCachedArchiveMarkerFromHash();
		const exactAdId=getEscortAdIdFromPath();
		const redirectedAdId=location.pathname === '/'
			? (markerAdId || readRecentEscortCachedAdNavigation())
			:null;
		const candidateAdId=exactAdId || redirectedAdId;
		if (!candidateAdId) return false;
		if (exactAdId) {
			const hasAdContent=!!document.querySelector( '.content-info-col .content-name h1, .content-name.-desc-name h1' );
			if (hasAdContent) {
				clearEscortCachedAdNavigationMarker();
				return false;
			}
			await new Promise(resolve => setTimeout(resolve,500));
			if (document.querySelector( '.content-info-col .content-name h1, .content-name.-desc-name h1' )) {
				clearEscortCachedAdNavigationMarker();
				return false;
			}
		}
		let cached=null;
		try {
			cached=getStoredEscortAdData(candidateAdId);
		} catch (error) {
			log( `Błąd odczytu cache archiwalnego anonsu ${candidateAdId}`, error );
		}
		if (!cached) {
			const cacheHost=getEscortCachedAdHost(candidateAdId);
			if (cacheHost && cacheHost !== CURRENT_HOST) {
				const banner=prepareHelperStyleApi(makeElement('div','vm-ui-panel'));
				const link=makeElement('a','vm-ui-button',`Pokaż zapisaną wersję z cache (${cacheHost})`,{href:`https://${cacheHost}${getEscortAdPath(candidateAdId,cacheHost)}#${ESCORT_CACHED_AD_HASH_KEY}=${candidateAdId}`});
				banner.appendChild(link);
				document.body.prepend(banner);
			}
			clearEscortCachedAdNavigationMarker();
			return false;
		}
		clearEscortCachedAdNavigationMarker();
		renderEscortCachedAdUnavailableBanner(candidateAdId,cached);
		return true;
	}
	function getPersistentEscortAdData(adId) {
		const value=getStoredEscortAdData(adId);
		const age=Date.now() - Number(value?.checkedAt);
		return value?.status === 'ok' && Number(value.checkedAt) > 0 && age >= 0 && age < getListCacheTtlMs() ? value:null;
	}
	function setPersistentEscortAdData(adId,data,{preserveCheckedAt=false}={}) {
		if (!persistentCacheKeyIsLoaded(escortAdDataCacheKey(adId))) {
			ensurePersistentCacheKeys([escortAdDataCacheKey(adId)]).then(() => setPersistentEscortAdData(adId,data,{preserveCheckedAt})).catch(error => log('Nie udało się scalić zapisu cache',error));
			return null;
		}
		if (!data) return null;
		try {
			const existing=getStoredEscortAdData(adId);
			const cacheId=String(adId);
			const pendingActivity=pendingEscortiActivityCacheWrites.get(cacheId) || {};
			const escortiActivity={
				...(data?.escortiActivity || {}), ...(existing?.escortiActivity || {}), ...pendingActivity
			};
			const value={
				status:'ok', ...(existing?.lastVisitedAt
					? {lastVisitedAt:existing.lastVisitedAt}
					:{}), ...(existing?.pageCheckedAt
					? {pageCheckedAt:existing.pageCheckedAt}
					:{}), ...(existing?.visitSnapshot
					? {visitSnapshot:existing.visitSnapshot}
					:{}), ...(existing?.watchSnapshot
					? {watchSnapshot:existing.watchSnapshot}
					:{}), ...(existing?.watchSnapshotCheckedAt
					? {watchSnapshotCheckedAt:existing.watchSnapshotCheckedAt}
					:{}), ...data, oldestPhotoDate:data.oldestPhotoDate || existing?.oldestPhotoDate || null,
				imageUrl:data.imageUrl || existing?.imageUrl || null, ...(Object.keys(escortiActivity).length
					? {escortiActivity}
					:{}), checkedAt:preserveCheckedAt ? data.checkedAt:Date.now()
			};
			writePersistentCacheValue(escortAdDataCacheKey(adId),value);
			registerEscortDataFieldsFromAdData(value);
			pendingEscortiActivityCacheWrites.delete(cacheId);
			return value;
		} catch (error) {
			log(`Nie udało się zapisać danych Escort.club dla anonsu ${adId}`,error);
			return null;
		}
	}
	const escortAdPhoneInflight=new Map();
	function getEscortAdPhoneCheckedAt(data) {
		return Number(Object.prototype.hasOwnProperty.call(data || {},'phoneCheckedAt') ? data.phoneCheckedAt:data?.checkedAt) || 0;
	}
	function isEscortAdPhoneFresh(data,now=Date.now()) {
		const checkedAt=getEscortAdPhoneCheckedAt(data);
		return !['error','missing-id','invalid-cache'].includes(data?.phoneFetchStatus) && !!getEscortAdPhoneDigits(data) && checkedAt > 0 && checkedAt <= now && now - checkedAt < getListCacheTtlMs();
	}
	function hasRecentEscortPhoneAttempt(data,now=Date.now()) {
		const attemptedAt=Number(data?.phoneAttemptedAt) || 0;
		const ttl=data?.phoneFetchStatus === 'error' ? Math.min(getListCacheTtlMs(),5 * 60 * 1000):getListCacheTtlMs();
		return ['error','missing-id'].includes(data?.phoneFetchStatus) && attemptedAt > 0 && attemptedAt <= now && now - attemptedAt < ttl;
	}
	function preserveEscortAdPhoneData(data,previous) {
		if (getEscortAdPhoneDigits(data)) return data;
		if (String(data.phoneId || '') !== String(previous?.phoneId || '')) previous=null;
		return {...data,phone:previous?.phone || null,phoneDigits:getEscortAdPhoneDigits(previous),
			phoneFetchStatus:previous?.phoneFetchStatus === 'page' ? 'cached':previous?.phoneFetchStatus || 'not-requested',phoneCheckedAt:getEscortAdPhoneCheckedAt(previous),
			phoneAttemptedAt:Number(previous?.phoneAttemptedAt) || 0};
	}
	function joinEscortAdDataOperation(operations,key,run,cancelToken=null,requestContext=null) {
		cancelToken?.throwIfCancelled();
		let operation=operations.get(key);
		if (!operation || operation.token.cancelled) {
			operation={token:createOperationCancelToken('Dane anonsu Escort.club'),waiters:new Set(),promise:null};
			operations.set(key,operation);
			operation.promise=Promise.resolve().then(() => {
				operation.token.throwIfCancelled();
				return run(operation.token);
			}).finally(() => {if (operations.get(key) === operation) operations.delete(key);});
		}
		else recordDiagnosticCacheDecision(requestContext,'joined');
		const waiter={};
		operation.waiters.add(waiter);
		return new Promise((resolve,reject) => {
			let settled=false,off=() => {};
			const finish=(value,error=null) => {
				if (settled) return;
				settled=true; off(); operation.waiters.delete(waiter);
				if (error) reject(error); else resolve(value);
			};
			off=cancelToken?.onCancel(reason => {
				finish(null,createOperationCancelledError(reason));
				if (!operation.waiters.size) operation.token.cancel(reason);
			}) || (() => {});
			operation.promise.then(value => finish(value),error => finish(null,error));
		});
	}
	function completeEscortAdPhone(id,data,forceRefresh,cancelToken,requestContext,followedIdChange=false) {
		if (!forceRefresh && (isEscortAdPhoneFresh(data) || hasRecentEscortPhoneAttempt(data))) return Promise.resolve(data);
		// Świeży numer ze strony nie wymaga POST.
		if (data.phoneFetchStatus === 'page' && isEscortAdPhoneFresh(data) && forceRefresh) return Promise.resolve(data);
		requestContext={...requestContext,cacheKind:'phone',cacheReason:forceRefresh ? 'forced':getEscortAdPhoneDigits(data) ? 'expired':'incomplete'};
		return joinEscortAdDataOperation(escortAdPhoneInflight,`${id}:${data.phoneId || ''}`,async token => {
			const current=escortAdDataMemoryCache.get(id) || data;
			if (String(current.phoneId || '') !== String(data.phoneId || '')) return current;
			if (!forceRefresh && (isEscortAdPhoneFresh(current) || hasRecentEscortPhoneAttempt(current))) return current;
			let phone=null,status='missing-id';
			if (data.phoneId) {
				try {
					phone=await fetchEscortClubPhone(data.phoneId,token,requestContext);
					status='ok';
				} catch (error) {
					if (isOperationCancelledError(error)) throw error;
					if (isEscortRequestBlockedError(error)) return current;
					status='error'; log(`Błąd pobierania numeru dla anonsu ${id}`,error);
				}
			}
			token.throwIfCancelled();
			const latest=escortAdDataMemoryCache.get(id) || data;
			if (String(latest.phoneId || '') !== String(data.phoneId || '')) return latest;
			const updated={...latest,...phone,phoneFetchStatus:status,phoneAttemptedAt:Date.now(),
				phoneCheckedAt:phone ? Date.now():getEscortAdPhoneCheckedAt(latest)};
			const saved=setPersistentEscortAdData(id,updated,{preserveCheckedAt:true}) || updated;
			escortAdDataMemoryCache.set(id,saved);
			return saved;
		},cancelToken,requestContext).then(result => !followedIdChange && String(result.phoneId || '') !== String(data.phoneId || '')
			? completeEscortAdPhone(id,result,forceRefresh,cancelToken,requestContext,true):result);
	}
	function getEscortAdData(adId,adUrl,forceRefresh=false,cancelToken=null,requestContext=null,{needPhone=true,maxAgeMs=getListCacheTtlMs(),minCheckedAt=0,beforeDispatch=null,freshnessRetry=false}={}) {
		cancelToken?.throwIfCancelled();
		const id=String(adId || ''),requestedAt=Date.now();
		minCheckedAt=Math.min(requestedAt,Math.max(0,Number(minCheckedAt) || 0));
		requestContext ||= createRequestContext('escort.ad-data');
		if (!persistentCacheKeyIsLoaded(escortAdDataCacheKey(id))) return ensurePersistentCacheKeys([escortAdDataCacheKey(id)])
			.then(() => getEscortAdData(adId,adUrl,forceRefresh,cancelToken,requestContext,{needPhone,maxAgeMs,minCheckedAt,beforeDispatch,freshnessRetry}));
		adUrl=normalizeEscortiAdUrl(adUrl);
		if (!id || !adUrl || parseAdIdFromUrl(adUrl) !== id) return Promise.resolve({status:'error',data:null});
		const activity=typeof readSharedEscortActivity === 'function' ? readSharedEscortActivity(adUrl):null;
		if (activity && !activity.active) return Promise.resolve({status:'offline',activity,data:null});
		const finish=promise => promise.then(data => {
			cancelToken?.throwIfCancelled();
			if (data?.status === 'ok' && (Number(data.checkedAt) < minCheckedAt ||
				((forceRefresh || Date.now() - Number(data.checkedAt) >= Math.max(0,Number(maxAgeMs) || 0)) && Number(data.checkedAt) < requestedAt))) {
				if (freshnessRetry) return {status:'error',data:null};
				return getEscortAdData(id,adUrl,true,cancelToken,requestContext,
					{needPhone,maxAgeMs,minCheckedAt,beforeDispatch,freshnessRetry:true});
			}
			if (!needPhone || data?.status !== 'ok') return data;
			return completeEscortAdPhone(id,data,forceRefresh,cancelToken,requestContext).then(result =>
				isEscortAdPhoneFresh(result) ? result:{...result,phone:null,phoneDigits:null});
		}).catch(error => {
			if (isOperationCancelledError(error)) return {status:'cancelled',data:null};
			throw error;
		});
		const allowedAge=Math.min(getListCacheTtlMs(),Math.max(0,Number(maxAgeMs) || 0));
		const readCached=() => {
			const memory=sanitizeEscortAdDataPhone(escortAdDataMemoryCache.get(id));
			const persistent=getPersistentEscortAdData(id),age=Date.now() - Number(memory?.checkedAt);
			const cached=memory?.status === 'ok' && Number(memory.checkedAt) > 0 && age >= 0 && age < getListCacheTtlMs() &&
				(!persistent || Number(memory.checkedAt) >= Number(persistent.checkedAt)) ? memory:persistent;
			return {memory,cached:cached && cached.checkedAt >= minCheckedAt && Date.now() - cached.checkedAt < allowedAge ? cached:null};
		};
		const {memory,cached}=readCached();
		if (!forceRefresh && cached) {
			escortAdDataMemoryCache.set(id,cached);
			return finish(Promise.resolve(cached));
		}
		const stored=getStoredEscortAdData(id);
		requestContext={...requestContext,cacheKind:'details',cacheReason:forceRefresh && !freshnessRetry ? 'forced':stored || memory ? 'expired':'missing'};
		const reuseCached=() => {
			const skipped=beforeDispatch?.();
			if (skipped) return skipped;
			const latest=!forceRefresh && readCached().cached;
			if (!latest) return null;
			return {outcome:'cached',value:{sharedAdData:latest}};
		};
		return finish(joinEscortAdDataOperation(escortAdDataInflight,id,token => new Promise(resolve => {
			enqueueEscortAdDataJob(async () => {
				try {
					token.throwIfCancelled();
					const result=reuseCached()?.value || await fetchEscortClubAdDocument(adUrl,token,requestContext,reuseCached,
						{minCheckedAt:forceRefresh ? requestedAt:Math.max(minCheckedAt,Date.now() - allowedAge)});
					token.throwIfCancelled();
					if (result?.sharedAdData) {
						const saved=setPersistentEscortAdData(id,result.sharedAdData,{preserveCheckedAt:true}) || result.sharedAdData;
						escortAdDataMemoryCache.set(id,saved); resolve(saved); return;
					}
					if (result?.sharedActivityRetry) {resolve(result); return;}
					if (result?.sharedActivityResult) {
						resolve({status:result.sharedActivityResult.unknown ? 'error':result.sharedActivityResult.active ? 'activity':'offline',activity:result.sharedActivityResult,data:null}); return;
					}
					if (!result?.ok) {resolve({status:'error',data:null}); return;}
					const finalUrl=new URL(result.finalUrl || adUrl,adUrl),finalAdId=parseAdIdFromUrl(finalUrl.href);
					if (!isEscortClubHost(finalUrl.hostname) || !getEscortAdIdFromPath(finalUrl.pathname) || finalAdId !== id) {
						resolve({status:'error',data:null}); return;
					}
					const extracted=extractEscortClubAdData(result.doc,id,finalUrl.href);
					const previous=escortAdDataMemoryCache.get(id) || memory || stored;
					const data={...preserveEscortAdPhoneData(extracted,previous),fetchTransport:result.transport || null};
					const parsed={status:'ok',...data,checkedAt:Date.now()};
					const saved=setPersistentEscortAdData(id,parsed,{preserveCheckedAt:true}) || parsed;
					if (typeof saveSharedEscortActivity === 'function') saveSharedEscortActivity(adUrl,{active:true},parsed.checkedAt);
					escortAdDataMemoryCache.set(id,saved); resolve(saved);
				} catch (error) {
					if (isOperationCancelledError(error)) {resolve({status:'cancelled',data:null}); return;}
					log(`Błąd pobierania danych Escort.club dla anonsu ${id}`,error);
					resolve({status:'error',data:null});
				}
			});
		}),cancelToken,requestContext));
	}
	function prepareImportedEscortCard(card,baseUrl=location.href) {
		const makeAbsolute=value => {
			if (!value) return null;
			try {
				return new URL(value,baseUrl).href;
			} catch {
				return value;
			}
		};
		for (const img of card.querySelectorAll('img')) {
			const lazySrc= img.getAttribute('data-src') || img.getAttribute('data-lazy-src') ||
				img.getAttribute('data-original');
			if (!img.hasAttribute('loading')) img.loading='lazy';
			img.decoding='async';
			if (lazySrc) {
				img.setAttribute('src',makeAbsolute(lazySrc));
			} else if (img.getAttribute('src')) {
				img.setAttribute('src',makeAbsolute(img.getAttribute('src')));
			}
			const lazySrcset= img.getAttribute('data-srcset') || img.getAttribute('data-lazy-srcset');
			if (lazySrcset) {
				img.setAttribute('srcset',lazySrcset);
			}
		}
		for (const source of card.querySelectorAll('source')) {
			const lazySrcset= source.getAttribute('data-srcset') || source.getAttribute('data-lazy-srcset');
			if (lazySrcset) {
				source.setAttribute('srcset',lazySrcset);
			}
		}
		return card;
	}
	function wrapEscortListSelect(select,width) {
		const styleId='vm-escort-list-select-style';
		if (!document.getElementById(styleId)) {
			appendHelperStyle(styleId,`
.vm-escort-list-select-wrap { position:relative; display:inline-flex; flex:0 0 auto; height:26px; vertical-align:middle; }
.vm-escort-list-select-wrap::after { content:''; position:absolute; top:11px; right:3px; border-left:3px solid transparent; border-right:3px solid transparent; border-top:4px solid var(--vm-ui-accent,${getEscortPagePinkColor()}); pointer-events:none; }
:is(#vm-escort-custom-filters-control,#vm-escort-list-pages-control) .vm-escort-list-select-wrap > select { appearance:none !important; box-sizing:border-box; width:100% !important; min-width:0 !important; max-width:none !important; height:26px !important; min-height:26px !important; margin:0 !important; padding:0 10px 0 2px !important; border:1px solid var(--vm-ui-accent,${getEscortPagePinkColor()}) !important; border-radius:5px !important; background:var(--vm-ui-bg,#fff) !important; color:var(--vm-ui-text,#211d22) !important; font:400 11px var(--vm-ui-font-family,Lato,Arial,sans-serif) !important; box-shadow:none !important; cursor:pointer; }
.vm-escort-list-select-wrap > select:focus-visible { outline:2px solid var(--vm-ui-accent,#f54da3); outline-offset:2px; }
.vm-escort-list-select-wrap > select:disabled { cursor:wait; opacity:.65; }
`);
		}
		const wrap=makeElement('span','vm-escort-list-select-wrap');
		wrap.style.width=`${width}px`;
		wrap.appendChild(select);
		return wrap;
	}
	function addEscortListPagesControl(listContext) {
		const existing=document.getElementById('vm-escort-list-pages-control');
		if (existing) {
			const progress=document.getElementById('vm-escort-list-pages-progress');
			const progressText=progress?.querySelector('.vm-list-pages-progress-text');
			const cancelButton=progress?.querySelector('.vm-list-pages-cancel');
			return {
				row:existing, select:existing.querySelector('select'), status:existing.querySelector('.vm-list-pages-status'),
				progress, setProgress(text,onCancel=null) {
					const value=String(text || '');
					if (progressText) progressText.textContent=value;
					if (progress) progress.hidden=!value;
					if (cancelButton) {
						cancelButton.hidden=typeof onCancel !== 'function';
						cancelButton.onclick=typeof onCancel === 'function'
							? onCancel
							:null;
					}
				}
			};
		}
		if (!listContext) return null;
		const targetContainer=getEscortListCardContainer(listContext.anchors || []);
		const earlyTitleCol=listContext.heading?.closest('.title-col');
		const earlyTitleRow=earlyTitleCol?.parentElement;
		if (!targetContainer?.parentElement && !(earlyTitleCol && earlyTitleRow?.classList.contains('row'))) {
			return null;
		}
		const pagePinkColor=getEscortPagePinkColor();
		const listPagesStyleId='vm-list-pages-select-style';
		if (!document.getElementById(listPagesStyleId)) {
			appendHelperStyle(listPagesStyleId,` #vm-escort-list-pages-control .vm-list-pages-selection { display: inline-flex; align-items: center; flex: 0 0 auto; gap: 2px; box-sizing: border-box; height: 34px; margin: 0; padding: 0 5px 0 8px; border: 1px solid var(--vm-ui-accent,${pagePinkColor}); border-radius: 7px; color: var(--vm-ui-text,#211d22); white-space: nowrap; }
#vm-favorite-search-button { align-self:center; height:auto !important; min-height:28px; padding:4px 9px; margin:0 0 0 12px; vertical-align:middle; white-space:nowrap; font:700 11px/13px var(--vm-ui-font-family,Lato,Arial,sans-serif); box-sizing:border-box; }
.vm-favorite-search-slot { display:inline-flex; align-items:center; list-style:none; }
.vm-favorite-search-slot::before, .vm-favorite-search-slot::after { content:none !important; }
`);
		}
		const row=makeElement('div',"header-filter col",null,{id:'vm-escort-list-pages-control'});
		Object.assign(row.style,{
			display:'flex', alignItems:'center', justifyContent:'flex-end', flex:'0 0 auto', marginLeft:'auto',
			paddingLeft:'12px'
		});
		const inner=makeElement('div','filter-box vm-list-pages-inner');
		Object.assign(inner.style,{
			display:'inline-flex', alignItems:'center', flexWrap:'nowrap', justifyContent:'flex-end', gap:'2px',
			fontFamily:'Lato,Arial,sans-serif', lineHeight:'1.2'
		});
		const label=makeElement('span','sub-label','Pokaż');
		Object.assign(label.style,{
			color:'var(--vm-ui-text,#211d22)', fontSize:'12px', fontWeight:'400', whiteSpace:'nowrap'
		});
		const selectionBox=makeElement('div','vm-list-pages-selection');
		const select=makeElement('select','vm-list-pages-select');
		select.setAttribute('aria-label','Pokaż na jednej liście');
		const selectWrap=wrapEscortListSelect(select,59.5);
		const multiLocationCount=getEscortMultiLocationsForCurrentPage().length;
		const locationMultiplier=makeElement('span','vm-list-pages-location-multiplier',multiLocationCount > 1
			? `× ${multiLocationCount} ${polishLocationWord(multiLocationCount)}`
			:'');
		Object.assign(locationMultiplier.style,{
			color:'var(--vm-ui-text-muted,#746b70)', fontSize:'11px', fontWeight:'700', whiteSpace:'nowrap'
		});
		const selectedValue=normalizeListPagesToShow(LIST_PAGES_TO_SHOW);
		appendSelectOptions(select,LIST_PAGE_OPTIONS,selectedValue);
		const status=makeElement('span','vm-list-pages-status');
		Object.assign(status.style,{
			color:'var(--vm-ui-text-faint,#9a9297)', fontFamily:'Lato,Arial,sans-serif', fontSize:'10px', fontWeight:'400',
			whiteSpace:'nowrap'
		});
		const progress=makeElement('div');
		progress.id='vm-escort-list-pages-progress';
		progress.setAttribute('aria-live','polite');
		progress.hidden=true;
		const progressText=makeElement('span','vm-list-pages-progress-text');
		const cancelButton=makeButton('vm-list-pages-cancel','Przerwij',{hidden:true});
		progress.append(progressText,cancelButton);
		const setProgress=(text,onCancel=null) => {
			const value=String(text || '');
			progressText.textContent=value;
			progress.hidden=!value;
			cancelButton.hidden=typeof onCancel !== 'function';
			cancelButton.onclick=typeof onCancel === 'function'
				? onCancel
				:null;
		};
		select.addEventListener('change',() => {
			const newValue=normalizeListPagesToShow(select.value);
			const oldValue=normalizeListPagesToShow(LIST_PAGES_TO_SHOW);
			if (String(newValue) === String(oldValue)) {
				return;
			}
			select.disabled=true;
			status.textContent='';
			setProgress('Ładowanie stron...');
			if (!requestOneTimeListPagesReload(newValue)) {
				select.disabled=false;
				setProgress('');
				status.textContent='Nie udało się przeładować listy.';
				select.value=String(oldValue);
				return;
			}
			location.reload();
		});
		selectionBox.appendChild(label);
		selectionBox.appendChild(selectWrap);
		if (multiLocationCount > 1) selectionBox.appendChild(locationMultiplier);
		inner.appendChild(selectionBox);
		inner.appendChild(status);
		row.appendChild(inner);
		const titleCol=listContext.heading?.closest('.title-col');
		const titleRow=titleCol?.parentElement;
		if (titleCol && titleRow?.classList.contains('row')) {
			titleCol.insertAdjacentElement('afterend',row);
			titleRow.insertAdjacentElement('afterend',progress);
		} else {
			Object.assign(row.style,{
				width:'100%', justifyContent:'flex-start', margin:'0 0 10px', paddingLeft:'0'
			});
			targetContainer.parentElement.insertBefore(row,targetContainer);
			row.insertAdjacentElement('afterend',progress);
		}
		return {
			row, select, status, progress, setProgress
		};
	}
	function getEscortListSortField(field) {
		const duration=normalizeEscortPriceDuration(SETTINGS.searchResultPriceDuration);
		return {price:`ad:prices:${duration === 'hide' ? '60':duration}`,age:'ad:stats:wiek',
			published:'ad:datePosted',visited:'ad:lastVisitedAt',default:'none'}[field] || field;
	}
	function createEscortListSortControl(onChange) {
		const key='vm_escort_list_sort_v1';
		const saved=readFavoriteViewPreferences(key);
		let config={field:saved.field || 'default',direction:saved.direction === 'desc' ? 'desc':'asc'};
		const node=makeElement('div','vm-search-price-control');
		const select=makeElement('select','vm-search-extra-select');
		const direction=makeButton('vm-search-sort-direction');
		select.setAttribute('aria-label','Sortuj wyniki według');
		node.title='Sortuje wszystkie wczytane wyniki. Brakujące dane trafiają na koniec. Grupy pozostają razem.';
		node.append(document.createTextNode('Sortuj:'),wrapEscortListSelect(select,84),direction);
		function sync() {
			select.value=config.field;
			select.title=select.selectedOptions?.[0]?.textContent || '';
			const descending=config.direction === 'desc';
			direction.textContent=descending ? '↓':'↑';
			direction.title=descending ? 'Malejąco — zmień na rosnąco':'Rosnąco — zmień na malejąco';
			direction.setAttribute('aria-label',direction.title);
			direction.disabled=config.field === 'default';
		}
		function commit(nextDirection) {
			const next={field:select.value,direction:nextDirection};
			try {
				saveFavoriteViewPreferences(key,next);
				config=next;
			} catch (error) {
				log('Nie udało się zapisać sortowania wyników',error);
				sync();
				return;
			}
			sync();
			onChange?.();
		}
		function refresh() {
			const duration=getEscortListSortField('price').split(':').pop();
			const standardFields=['ad:location:city','ad:title','escorti:adLinks','escorti:creationDate','escorti:garsoTopics'];
			const options=[['default','Domyślna kolejność'],['price',`Cena (${getEscortPriceDurationLabel(duration)})`],
				['age','Wiek'],['published',getEscortDataFieldDescriptor('ad:datePosted').label],['visited','Ostatnia wizyta']];
			const used=new Set(options.map(([field]) => getEscortListSortField(field)));
			for (const field of [...standardFields,...getSearchResultExtraFields()]) {
				if (used.has(field)) continue;
				used.add(field);
				options.push([field,field === 'ad:location:city' ? 'Miasto':getEscortDataFieldDescriptor(field)?.label || field]);
			}
			if (!options.some(([field]) => field === config.field)) {
				config.field=options.find(([field]) => getEscortListSortField(field) === getEscortListSortField(config.field))?.[0] || 'default';
				try {saveFavoriteViewPreferences(key,config);} catch (error) {log('Nie udało się zapisać sortowania wyników',error);}
			}
			select.replaceChildren();
			appendSelectOptions(select,options,config.field);
			sync();
		}
		select.addEventListener('change',() => commit(config.direction));
		direction.addEventListener('click',() => commit(config.direction === 'desc' ? 'asc':'desc'));
		refresh();
		return {node,refresh,getConfig:() => ({...config})};
	}
	function getEscortListSortValue(state,field) {
		if (field === 'default') return null;
		if (field === 'age') {
			const age=getEscortListCardAgeAndCity(state.card).age;
			if (age != null) return age;
		}
		const dataField=getEscortListSortField(field);
		const descriptor=getEscortDataFieldDescriptor(dataField);
		if (!descriptor) return null;
		let raw=getEscortTileFieldValue(state,descriptor);
		if (dataField === 'ad:location:city') raw=getEscortListCardAgeAndCity(state.card).city || raw;
		if (dataField === 'ad:title') raw=normalizeEscortAdText(state.card?.querySelector('.item-name')?.textContent) || raw;
		if (raw == null || raw === '' || (Array.isArray(raw) && !raw.length)) return null;
		if (typeof raw === 'number') return Number.isFinite(raw) ? raw:null;
		if (typeof raw === 'boolean') return Number(raw);
		return favoriteDataSortValue(null,dataField,formatEscortUniversalDataValue(descriptor,raw,false),{lastVisited:raw});
	}
	function applyEscortResultSort(states,config={field:'default',direction:'asc'},getGroupAdIds=null) {
		const parents=new Map(),direction=config.direction === 'desc' ? -1:1,dataField=getEscortListSortField(config.field);
		for (const state of states) {
			const parent=state.card?.parentNode;
			if (!parent) continue;
			if (!parents.has(parent)) parents.set(parent,new Map());
			const groups=parents.get(parent),key=state.group || state;
			if (!groups.has(key)) groups.set(key,[]);
			groups.get(key).push(state);
		}
		for (const [parent,groups] of parents) {
			const buckets=[...groups.values()].map(members => {
				members.sort((a,b) => a.index-b.index);
				const canonical=members.find(member => !member.hiddenInResults) || members[0];
				let value=null;
				if (dataField === 'ad:lastVisitedAt') {
					value=getBucketLastVisitedAt({members},canonical.group ? getGroupAdIds?.(canonical.group):undefined);
				} else if (config.field !== 'default') {
					const visible=(canonical.aggregateMembers || members).filter(member => !member.hiddenInResults);
					for (const member of visible) {
						const next=getEscortListSortValue(member,config.field);
						if (compareFavoriteValues(next,value,direction) < 0) value=next;
					}
					if (dataField === 'profile:activeCount' && canonical.aggregateMembers?.length > 1) value=visible.length;
				}
				return {members,index:members[0].index,value};
			});
			buckets.sort((a,b) => compareFavoriteValues(a.value,b.value,direction) || a.index-b.index);
			const cards=buckets.flatMap(bucket => bucket.members.map(member => member.card));
			const tracked=new Set(cards);
			let cursor=[...parent.childNodes].find(node => tracked.has(node)) || null;
			// Keep loader anchors and other site nodes attached while moving cards.
			for (const card of cards) {
				while (cursor && !tracked.has(cursor)) cursor=cursor.nextSibling;
				if (card === cursor) cursor=cursor.nextSibling;
				else parent.insertBefore(card,cursor);
			}
		}
	}
	function addEscortCustomFiltersControl(listContext,onChange,onPriceDurationChange,onExtraFieldChange,onSortChange) {
		const existing=document.getElementById('vm-escort-custom-filters-control');
		if (existing) return null;
		const heading=listContext?.heading;
		const titleCol=heading?.closest('.title-col');
		const titleRow=titleCol?.parentElement;
		if (!heading || !titleCol || !titleRow) return null;
		const pagePinkColor=getEscortPagePinkColor();
		const styleId='vm-escort-custom-filters-style';
		if (!document.getElementById(styleId)) {
			appendHelperStyle(styleId,` #vm-escort-custom-filters-control { display: flex; flex-wrap: wrap; align-items: center; flex: 0 1 auto; gap: 7px; margin-left: 0; padding-left: 10px; }
#vm-escort-custom-filters-trigger { display: inline-flex !important; align-items: center; justify-content: center; flex: 0 0 auto !important; width: auto !important; min-width: max-content !important; max-width: none !important; height: 32px; margin: 0; padding: 7px 12px !important; border: 1px solid var(--vm-ui-accent, #f54da3) !important; background: var(--vm-ui-accent, #f54da3) !important; border-radius: 7px !important; color: var(--vm-ui-accent-contrast, #fff) !important; font-family:var(--vm-ui-font-family,Lato,Arial,sans-serif); font-size: 12px; font-weight: 700; line-height: 16px; white-space: nowrap !important; cursor: pointer; }
#vm-escort-custom-filters-trigger * { color: inherit !important; fill: currentColor !important; stroke: currentColor !important; }
#vm-escort-custom-filters-trigger:disabled { cursor: not-allowed !important; opacity: .55; }
#vm-escort-custom-filters-control .vm-search-sort-direction { flex:0 0 28px; width:28px; height:26px; margin:0; padding:0; border:1px solid var(--vm-ui-accent, ${pagePinkColor}); border-radius:5px; background:var(--vm-ui-bg, #fff); color:var(--vm-ui-accent, ${pagePinkColor}); font:700 18px/24px var(--vm-ui-font-family,Lato,Arial,sans-serif); cursor:pointer; }
#vm-escort-custom-filters-control .vm-search-sort-direction:disabled { opacity:.4; cursor:default; }
#vm-escort-custom-filters-control .vm-search-price-control { display: inline-flex; align-items: center; flex: 0 0 auto; gap: 6px; box-sizing: border-box; height: 34px; margin: 0 0 0 3px; padding: 0 5px 0 8px; border: 1px solid var(--vm-ui-accent, ${pagePinkColor}); border-radius: 7px; color: var(--vm-ui-text, #211d22); font:400 12px var(--vm-ui-font-family,Lato,Arial,sans-serif); white-space: nowrap; }
#vm-escort-custom-filters-control .vm-search-extra-control { display: inline-flex; align-items: center; flex: 0 1 auto; gap: 4px; box-sizing: border-box; min-height: 34px; margin: 0; padding: 3px 6px 3px 8px; border: 1px solid var(--vm-ui-accent, ${pagePinkColor}); border-radius: 7px; color: var(--vm-ui-text, #211d22); font:400 12px var(--vm-ui-font-family,Lato,Arial,sans-serif); white-space: nowrap; }
#vm-escort-custom-filters-control .vm-search-extra-add, #vm-escort-custom-filters-control .vm-search-extra-chip { box-sizing: border-box; height: 26px; margin: 0; border: 1px solid var(--vm-ui-border, rgba(0,0,0,.14)); border-radius: 5px; background: var(--vm-ui-bg, #fff); color: var(--vm-ui-text, #211d22); font:600 10px var(--vm-ui-font-family,Lato,Arial,sans-serif); }
#vm-escort-custom-filters-control .vm-search-extra-add { padding: 2px 7px; border-color: var(--vm-ui-accent, ${pagePinkColor}); color: var(--vm-ui-accent, ${pagePinkColor}); cursor: pointer; }
#vm-escort-custom-filters-control .vm-search-extra-chip { display: inline-flex; align-items: center; gap: 3px; flex: 0 0 auto; max-width: 180px; padding: 1px 3px 1px 6px; }
#vm-escort-custom-filters-control .vm-search-extra-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#vm-escort-custom-filters-control .vm-search-extra-chip button { flex: 0 0 auto; padding: 0 2px; border: 0; background: transparent; color: var(--vm-ui-text-muted, #777); font-size: 13px; line-height: 1; cursor: pointer; }
#vm-custom-filter-pages-hint { box-sizing:border-box; width:100%; margin:4px 0 8px; padding:7px 10px; border-left:3px solid var(--vm-ui-accent, #f54da3); border-radius:4px; background:var(--vm-ui-bg-alt, #fcfafb); color:var(--vm-ui-text, #211d22); font:400 12px/1.4 var(--vm-ui-font-family,Lato,Arial,sans-serif); }
#vm-custom-filter-pages-hint[hidden] { display:none !important; }
#vm-escort-custom-filters-panel { box-sizing: border-box; width: 100%; margin: 0 0 10px; padding: 5px 8px; border-top: 1px solid var(--vm-ui-accent, #ff4c99); background: var(--vm-ui-bg, #fff); box-shadow: var(--vm-ui-shadow, 0 3px 8px rgba(33, 29, 34, .08)); font-family:var(--vm-ui-font-family,Lato,Arial,sans-serif); }
#vm-escort-custom-filters-panel[hidden] { display: none !important; }
#vm-escort-custom-filters-panel[data-busy="true"] { opacity: .58; }
#vm-escort-custom-filters-panel .vm-custom-filter-intro { margin: 0 0 3px; padding: 0; color: var(--vm-ui-accent, ${pagePinkColor}); font-size: 11px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }
#vm-escort-custom-filters-panel .vm-custom-filter-intro strong { font-weight: 700; }
#vm-escort-custom-filters-panel .vm-custom-filter-intro.-show-only { margin-top: 5px; padding-top: 4px; border-top: 1px solid var(--vm-ui-accent, #ff4c99); font-weight: 700; }
#vm-escort-custom-filters-panel .vm-custom-filter-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 5px; align-items: start; }
#vm-escort-custom-filters-panel .vm-custom-filter-group { min-width: 0; padding: 3px 6px 4px; border: 1px solid var(--vm-ui-border, #eee6eb); border-radius: 7px; background: var(--vm-ui-bg-alt, #fcfafb); }
#vm-escort-custom-filters-panel .vm-custom-filter-group-title { margin: 0 0 2px; color: var(--vm-ui-accent, ${pagePinkColor}); font-size: 10px; font-weight: 700; line-height: 15px; }
#vm-escort-custom-filters-panel .vm-custom-filter-group-body { display: grid; gap: 0; }
#vm-escort-custom-filters-panel .vm-custom-filter-row { display: flex; align-items: center; min-width: 0; min-height: 22px; height: auto; margin: 0; padding: 0; line-height: 1.2; flex-wrap: wrap; gap: 4px; color: var(--vm-ui-text, #211d22); font-size: 11px; white-space: normal; }
#vm-escort-custom-filters-panel .vm-custom-filter-check { flex: 0 0 auto; width: 14px; height: 14px; margin: 0 1px 0 0; accent-color: var(--vm-ui-accent, ${pagePinkColor}); cursor: pointer; }
#vm-escort-custom-filters-panel .vm-custom-filter-number { box-sizing: border-box; width: 48px; height: 20px; padding: 0 3px; border: 0; border-bottom: 1px solid var(--vm-ui-accent, #ff4c99); border-radius: 0; outline: 0; background: transparent; color: var(--vm-ui-text, #211d22); font:700 11px var(--vm-ui-font-family,Lato,Arial,sans-serif); text-align: center; }
#vm-escort-custom-filters-panel .vm-custom-filter-unit, #vm-escort-custom-filters-panel .vm-custom-filter-day, #vm-escort-custom-filters-panel .vm-custom-filter-time { box-sizing: border-box; width: 72px; height: 20px; padding: 0 3px; border: 0; border-bottom: 1px solid var(--vm-ui-accent, #ff4c99); border-radius: 0; outline: 0; background: transparent; color: var(--vm-ui-text, #211d22); font:11px var(--vm-ui-font-family,Lato,Arial,sans-serif); cursor: pointer; }
#vm-escort-custom-filters-panel .vm-custom-filter-day { width: 98px; }
#vm-escort-custom-filters-panel .vm-custom-filter-time { width: 66px; font-weight: 700; text-align: center; }
#vm-escort-custom-filters-panel .vm-custom-filter-text { box-sizing: border-box; width: min(190px, 100%); height: 20px; padding: 0 3px; border: 0; border-bottom: 1px solid var(--vm-ui-accent, #ff4c99); border-radius: 0; outline: 0; background: transparent; color: var(--vm-ui-text, #211d22); font:11px var(--vm-ui-font-family,Lato,Arial,sans-serif); }
#vm-escort-custom-filters-panel .vm-custom-filter-category { box-sizing: border-box; min-width: 0; max-width: 140px; height: 20px; margin: 0; padding: 0 3px; font: inherit; }
#vm-escort-custom-filters-panel .vm-custom-filter-number:disabled, #vm-escort-custom-filters-panel .vm-custom-filter-unit:disabled, #vm-escort-custom-filters-panel .vm-custom-filter-day:disabled, #vm-escort-custom-filters-panel .vm-custom-filter-time:disabled, #vm-escort-custom-filters-panel .vm-custom-filter-text:disabled { border-bottom-color: var(--vm-ui-border, #d8d2d5); color: var(--vm-ui-text-faint, #aaa3a7); cursor: not-allowed; opacity: .7; }
#vm-escort-custom-filters-panel .vm-custom-filter-footer { display: flex; align-items: center; gap: 10px; margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--vm-ui-border, #eee9ec); }
#vm-escort-custom-filters-panel .vm-custom-filter-clear { padding: 0; border: 0; background: transparent; color: var(--vm-ui-accent, ${pagePinkColor}); font:700 11px var(--vm-ui-font-family,Lato,Arial,sans-serif); cursor: pointer; }
#vm-escort-custom-filters-panel .vm-custom-filter-status { color: var(--vm-ui-text-muted, #8d858a); font-size: 10px; }
@media (max-width: 767px) { #vm-escort-custom-filters-control { flex-wrap: wrap; margin-left: 0; padding: 6px 0 0; }
#vm-escort-custom-filters-panel .vm-custom-filter-grid { grid-template-columns: 1fr; }
#vm-escort-custom-filters-panel .vm-custom-filter-row { min-height: 22px; height: auto; flex-wrap: wrap; white-space: normal; }
}
`);
		}
		let config=getEscortCustomFilters();
		let filtersBusy=false;
		let listIncomplete=false;
		const control=prepareHelperStyleApi( makeElement('div') );
		control.id='vm-escort-custom-filters-control';
		control.className='header-filter col';
		const trigger=makeElement('button');
		trigger.id='vm-escort-custom-filters-trigger';
		trigger.type='button';
		trigger.className='btn btn-pink';
		let priceDurationSelect=null;
		let priceControl=null;
		if (SETTINGS.showPricesInSearchResults) {
			priceControl=makeElement('label');
			priceControl.className='vm-search-price-control';
			priceControl.appendChild(document.createTextNode('Cena za'));
			priceDurationSelect=makeElement('select');
			priceDurationSelect.className='vm-search-price-select';
			priceDurationSelect.setAttribute('aria-label','Wyświetl cenę za wybrany czas');
			const selectedDuration=normalizeEscortPriceDuration( SETTINGS.searchResultPriceDuration );
			const compactLabels={hide:'—',15:'15′',30:'30′',60:'1h',120:'2h',night:'noc'};
			appendSelectOptions(priceDurationSelect,ESCORT_PRICE_DISPLAY_OPTIONS.map(([value]) => [value,compactLabels[value]]),selectedDuration);
			priceDurationSelect.title=getEscortPriceDurationLabel(selectedDuration);
			priceControl.appendChild(wrapEscortListSelect(priceDurationSelect,32.4));
		}
		const sortControl=createEscortListSortControl(onSortChange);
		const extraFieldControl=makeElement('div',"vm-search-extra-control");
		extraFieldControl.appendChild(document.createTextNode('Wyświetl dodatkowo:'));
		const extraFieldAddButton=makeButton('vm-search-extra-add','Dodaj');
		const extraFieldSelect=makeElement('select',"vm-search-extra-select");
		const extraFieldSelectWrap=wrapEscortListSelect(extraFieldSelect,168);
		extraFieldSelectWrap.style.display='none';
		extraFieldSelect.setAttribute('aria-label','Dodaj dodatkową informację na kafelkach');
		extraFieldSelect.title='Opcje są tworzone automatycznie z danych, które Escort Helper już odczytał lub ma w cache. Dodanie pola nie uruchamia dodatkowego pobierania.';
		const extraFieldChips=makeElement('div');
		extraFieldChips.style.display='contents';
		const saveSearchResultExtraFields=fields => {
			const previousFields=getSearchResultExtraFields();
			const normalizedFields=normalizeSearchResultExtraFields(fields);
			try {
				updateSettings({
					searchResultExtraFields:normalizedFields
				});
				refreshExtraFieldOptions();
				if (typeof onExtraFieldChange === 'function') onExtraFieldChange(normalizedFields);
			} catch (error) {
				SETTINGS.searchResultExtraFields=previousFields;
				log('Nie udało się zapisać dodatkowych informacji na kafelkach',error);
			}
		};
		const refreshExtraFieldOptions=() => {
			sortControl.refresh();
			const selectedFields=getSearchResultExtraFields();
			const available=getEscortDataFieldOptions('none')
				.filter(([field]) => field !== 'none' && !selectedFields.includes(field));
			extraFieldSelect.replaceChildren();
			appendSelectOptions(extraFieldSelect,[['','wybierz…'],...available],'');
			extraFieldAddButton.disabled=!available.length || selectedFields.length >= SEARCH_RESULT_MAX_EXTRA_FIELDS;
			extraFieldAddButton.style.opacity=extraFieldAddButton.disabled ? '.45':'1';
			extraFieldAddButton.style.cursor=extraFieldAddButton.disabled ? 'default':'pointer';
			if (extraFieldAddButton.disabled) extraFieldSelectWrap.style.display='none';
			extraFieldChips.replaceChildren();
			for (const field of selectedFields) {
				const descriptor=getEscortDataFieldDescriptor(field);
				const chip=makeElement('span','vm-search-extra-chip');
				const text=makeElement('span','',descriptor?.label || field,{title:descriptor?.label || field});
				const remove=makeButton('','×',{title:'Usuń tę informację z kafelków'});
				remove.addEventListener('click',() => {
					saveSearchResultExtraFields(getSearchResultExtraFields().filter(value => value !== field));
				});
				chip.append(text,remove);
				extraFieldChips.appendChild(chip);
			}
		};
		extraFieldAddButton.addEventListener('click',() => {
			if (extraFieldAddButton.disabled) return;
			extraFieldSelectWrap.style.display=extraFieldSelectWrap.style.display === 'none' ? 'inline-block':'none';
			if (extraFieldSelectWrap.style.display !== 'none') extraFieldSelect.focus();
		});
		extraFieldSelect.addEventListener('change',() => {
			const field=normalizeEscortDataFieldId(extraFieldSelect.value);
			if (!field || field === 'none') return;
			saveSearchResultExtraFields([...getSearchResultExtraFields(),field]);
			extraFieldSelect.value='';
			extraFieldSelectWrap.style.display='none';
		});
		refreshExtraFieldOptions();
		window.addEventListener('vmEscortDataCatalogChanged',refreshExtraFieldOptions);
		extraFieldControl.append(extraFieldAddButton,extraFieldSelectWrap,extraFieldChips);
		const loadAllHint=prepareHelperStyleApi(makeElement('div'));
		loadAllHint.id='vm-custom-filter-pages-hint';
		loadAllHint.setAttribute('role','status');
		loadAllHint.textContent='Filtry niestandardowe działają tylko na wczytanych anonsach. Warto wczytać wszystkie strony: „Pokaż” → „Wszystkie”.';
		loadAllHint.hidden=true;
		const progress=makeElement('div','vm-custom-filter-progress');
		progress.setAttribute('aria-live','polite');
		progress.hidden=true;
		const progressText=makeElement('span','vm-custom-filter-progress-text');
		const progressCancelButton=makeButton('vm-custom-filter-cancel','Przerwij',{hidden:true});
		let progressBusy=false;
		progress.append(progressText,progressCancelButton);
		const panel=makeElement('div');
		panel.id='vm-escort-custom-filters-panel';
		panel.hidden=!config.expanded;
		const intro=makeElement('p','vm-custom-filter-intro','Ukrywaj');
		const grid=makeElement('div','vm-custom-filter-grid');
		const showOnlyIntro=makeElement('p','vm-custom-filter-intro -show-only','Pokazuj tylko');
		const showOnlyGrid=makeElement('div','vm-custom-filter-grid');
		const status=makeElement('span','vm-custom-filter-status');
		status.setAttribute('aria-live','polite');
		const controls={};
		const dependentControls=[];
		function makeFilterGroup(title,targetGrid) {
			const group=makeElement('section','vm-custom-filter-group');
			const heading=makeElement('div','vm-custom-filter-group-title',title);
			const body=makeElement('div','vm-custom-filter-group-body');
			group.append(heading,body);
			targetGrid.appendChild(group);
			return body;
		}
		function makeFilterControl(key,tag,className,label,attributes={}) {
			const control=makeElement(tag,'vm-custom-filter-' + className,null,attributes);
			control.setAttribute('aria-label',label);
			controls[key]=control;
			return control;
		}
		function makeFilterRow(key,text,fields,target,title='',tag='div') {
			const row=makeElement(tag,'vm-custom-filter-row');
			if (title) row.title=title;
			const checkbox=makeElement('input','vm-custom-filter-check',null,{type:'checkbox',checked:!!config[key]});
			controls[key]=checkbox;
			row.append(checkbox,document.createTextNode(text),...fields);
			for (const field of fields) {
				if (field?.matches?.('input,select')) dependentControls.push([checkbox,field]);
			}
			target.appendChild(row);
		}
		function makeFilterSelect(key,className,label,options,value=config[key]) {
			const select=makeFilterControl(key,'select',className,label);
			appendSelectOptions(select,options,value);
			return select;
		}
		function makeCategoryFilterRow(checkKey,categoryKey,target) {
			const categories=normalizeFavoriteCategories(SETTINGS.favoriteCategories);
			const current=normalizeCustomFilterCategory(config[categoryKey]);
			const options=[['','Dowolna'],...categories.map(category => [category,category])];
			if (current && !categories.some(category => category.toLocaleLowerCase('pl-PL') === current.toLocaleLowerCase('pl-PL'))) {
				options.push([current,current]);
			}
			makeFilterRow(checkKey,'Ma przypisaną kategorię:',[
				makeFilterSelect(categoryKey,'category','Wybierz kategorię',options,current)
			],target,'Uwzględnia kategorie przypisane zarówno do konkretnego anonsu, jak i do powiązanego profilu.');
		}
		function makeFilterNumberInput(key,label) {
			const [normalize,,min=0,max=100000]=ESCORT_CUSTOM_FILTER_FIELDS[key];
			const decimal=normalize === normalizeCustomFilterNumber;
			const input=makeFilterControl(key,'input','number',label,{
				type:'number',min:String(min),max:String(max),step:decimal ? '0.1':'1',inputMode:decimal ? 'decimal':'numeric',value:String(config[key])
			});
			input.dataset.filterKey=key;
			return input;
		}
		const filterGroups={
			hp:makeFilterGroup('Garsoniera i profil Escorti.pl',grid), ha:makeFilterGroup('Anonse i agregacja',grid),
			hv:makeFilterGroup('Odwiedziny, notatki i kategorie',grid), hd:makeFilterGroup('Dostępność',grid),
			sp:makeFilterGroup('Garsoniera i profil Escorti.pl',showOnlyGrid),
			sa:makeFilterGroup('Anonse i agregacja',showOnlyGrid),
			sv:makeFilterGroup('Odwiedziny, notatki i kategorie',showOnlyGrid), sd:makeFilterGroup('Dostępność',showOnlyGrid)
		};
		const rowBuilders={
			s:(target,[key,text,title='']) => makeFilterRow(key,text,[],target,title,'label'),
			n:(target,[key,text,numberKey,suffix='',title='']) => makeFilterRow(key,text,[
				makeFilterNumberInput(numberKey,`${text} ${suffix}`),document.createTextNode(suffix)
			],target,title),
			a:(target,[key,text,numberKey,unitKey]) => makeFilterRow(key,text,[
				makeFilterNumberInput(numberKey,text),
				makeFilterSelect(unitKey,'unit',`${text} - jednostka wieku`,ESCORT_CUSTOM_FILTER_AGE_UNIT_OPTIONS)
			],target),
			b:(target,[key,minKey,maxKey]) => makeFilterRow(key,'Tylko z deklarowanym BMI: od ',[
				makeFilterNumberInput(minKey,'BMI od'),document.createTextNode(' do '),makeFilterNumberInput(maxKey,'BMI do')
			],target,'BMI jest liczone w locie z deklarowanych w anonsie pól Waga i Wzrost. BMI nie jest zapisywane w cache.'),
			c:(target,[key,categoryKey]) => makeCategoryFilterRow(key,categoryKey,target),
			v:(target,[key,text,dayKey,timeKey]) => makeFilterRow(key,text,[
				makeFilterSelect(dayKey,'day',`${text} - dzień tygodnia`,ESCORT_CUSTOM_FILTER_WEEKDAYS),
				makeFilterControl(timeKey,'input','time',`${text} - godzina`,{type:'time',step:'60',value:normalizeCustomFilterTime(config[timeKey])})
			],target),
			d:(target,[key,text,queryKey,placeholder='wpisz tekst']) => {
				const input=makeFilterControl(queryKey,'input','text',text,{type:'search',value:config[queryKey] || '',placeholder,maxLength:200});
				input.dataset.filterKey=queryKey;
				makeFilterRow(key,text,[input],target);
			}
		};
		const filterLayout={
			hp:[ ['s','hideWithoutOpinions','Bez opinii na Garsonierze'],
				['n','hideProfileAdsOver','Anonse profilu na Escorti.pl: więcej niż','profileAdsLimit','','Łączna liczba anonsów znaleziona w historii profilu Escorti.pl.'],
				['n','hideLocationChangesOver','Liczba zmian lokalizacji więcej niż:','locationChangesLimit','','Liczba kolejnych zmian miasta w chronologicznej historii profilu Escorti.pl. Domyślny limit: 1.'],
				['a','hideYoungerThan','Profil młodszy niż','youngerValue','youngerUnit'],
				['a','hideOlderThan','Profil starszy niż','olderValue','olderUnit'] ], ha:[
				['n','hideActiveAdsOver','Zagregowane anonse: więcej niż','activeAdsLimit'],
				['s','hideAgencyPhone','Z numerem telefonu agencji/salonu'],
				['d','hideCity','Miasto:','hideCityQuery','wpisz miasto'],
				['n','hideTagCountOver','Liczba usług / tagów: więcej niż','tagCountLimit','','Domyślnie używa limitu z ustawień głównych; tutaj można ustawić inną wartość dla filtrowania.'],
				['a','hideAdYoungerThan','Anons młodszy niż','adYoungerValue','adYoungerUnit'],
				['a','hideAdOlderThan','Anons starszy niż','adOlderValue','adOlderUnit'],
				['d','hideDescriptionMatch','Opis zawiera:','hideDescriptionQuery'] ], hv:[
				['s','hideVisited','Odwiedzone'],['s','hideUnvisited','Nieodwiedzone'],
				['a','hideVisitedOlderThan','Anons odwiedzony dawniej niż','hideVisitedOlderValue','hideVisitedOlderUnit'],
				['a','hideVisitedRecentThan','Anons odwiedzony w ciągu ostatnich','hideVisitedRecentValue','hideVisitedRecentUnit'],
				['s','hideHasNote','Ma notatkę','Uwzględnia notatkę przypisaną do anonsu lub powiązanego profilu.'],
				['c','hideHasCategory','hideCategory'] ], hd:[ ['s','hideAlwaysAvailable','Dostępne „cały czas”'],
				['v','hideUnavailableAt','Niedostępne:','hideUnavailableDay','hideUnavailableTime'],
				['v','hideAvailableAt','Dostępne:','hideAvailableDay','hideAvailableTime'] ], sp:[
				['s','showOnlyWithoutOpinions','Bez opinii na Garsonierze (tryb sapera)'],
				['s','showOnlySingleProfileAd','Profil Escorti.pl z 1 anonsem'] ], sa:[
				['s','showOnlyAgencyPhone','Z numerem telefonu agencji/salonu'],
				['b','showOnlyBmi','showOnlyBmiMin','showOnlyBmiMax'],
				['d','showOnlyDescriptionMatch','Opis zawiera:','showOnlyDescriptionQuery'] ], sv:[
				['a','showOnlyVisitedOlderThan','Anons odwiedzony dawniej niż','showOnlyVisitedOlderValue','showOnlyVisitedOlderUnit'],
				['a','showOnlyVisitedRecentThan','Anons odwiedzony w ciągu ostatnich','showOnlyVisitedRecentValue','showOnlyVisitedRecentUnit'],
				['s','showOnlyHasNote','Ma notatkę','Uwzględnia notatkę przypisaną do anonsu lub powiązanego profilu.'],
				['c','showOnlyHasCategory','showOnlyCategory'] ], sd:[
				['v','showOnlyUnavailableAt','Niedostępne:','showOnlyUnavailableDay','showOnlyUnavailableTime'],
				['v','showOnlyAvailableAt','Dostępne:','showOnlyAvailableDay','showOnlyAvailableTime'] ]
		};
		for (const [groupKey,rows] of Object.entries(filterLayout)) {
			for (const [type,...args] of rows) {
				rowBuilders[type](filterGroups[groupKey],args);
			}
		}
		const footer=makeElement('div','vm-custom-filter-footer');
		const clearBtn=makeButton('vm-custom-filter-clear','Wyłącz wszystkie');
		footer.append(clearBtn,status);
		panel.append(intro,grid,showOnlyIntro,showOnlyGrid,footer);
		control.append(trigger,sortControl.node);
		if (priceControl) control.appendChild(priceControl);
		control.appendChild(extraFieldControl);
		function readConfig() {
			return Object.fromEntries(Object.entries(ESCORT_CUSTOM_FILTER_FIELDS).map(([key,[normalize]]) => [
				key,key === 'expanded' ? !panel.hidden:normalizeEscortCustomFilterField(
					key,normalize === Boolean ? controls[key].checked:controls[key].value,SETTINGS.alarmingTagCountLimit ) ]));
		}
		function updateTrigger() {
			const activeCount=getActiveEscortCustomFilterCount(config);
			loadAllHint.hidden=!listIncomplete || !activeCount;
			const arrow=panel.hidden ? '▼':'▲';
			trigger.textContent=`Filtry niestandardowe${activeCount ? ` (${activeCount})`:''} ${arrow}`;
			trigger.setAttribute('aria-expanded',String(!panel.hidden));
			trigger.setAttribute('aria-controls',panel.id);
		}
		function updateDisabledStates() {
			for (const checkbox of panel.querySelectorAll('.vm-custom-filter-check')) {
				checkbox.disabled=filtersBusy;
			}
			for (const [checkbox,dependent] of dependentControls) {
				dependent.disabled=filtersBusy || (!checkbox.checked && !dependent.matches('.vm-custom-filter-category'));
			}
			clearBtn.disabled=filtersBusy;
		}
		function setFiltersBusy(value) {
			if (filtersBusy === !!value) return;
			filtersBusy=!!value;
			trigger.disabled=filtersBusy;
			trigger.setAttribute('aria-disabled',String(filtersBusy));
			panel.dataset.busy=String(filtersBusy);
			updateDisabledStates();
		}
		function commit() {
			config=saveEscortCustomFilters(readConfig());
			for (const input of panel.querySelectorAll('.vm-custom-filter-number')) {
				input.value=String(config[input.dataset.filterKey]);
			}
			updateDisabledStates();
			updateTrigger();
			if (typeof onChange === 'function') onChange(config);
		}
		for (const control of panel.querySelectorAll('.vm-custom-filter-check,.vm-custom-filter-number,.vm-custom-filter-unit,.vm-custom-filter-day')) {
			control.addEventListener('change',commit);
		}
		for (const category of panel.querySelectorAll('.vm-custom-filter-category')) {
			category.addEventListener('change',() => {
				category.closest('.vm-custom-filter-row').querySelector('.vm-custom-filter-check').checked=true;
				commit();
			});
		}
		for (const time of panel.querySelectorAll('.vm-custom-filter-time')) {
			time.addEventListener('change',() => {
				time.value=normalizeCustomFilterTime(time.value);
				commit();
			});
		}
		let descriptionCommitTimer=null;
		for (const input of panel.querySelectorAll('.vm-custom-filter-text')) {
			input.addEventListener('input',() => {
				clearTimeout(descriptionCommitTimer);
				descriptionCommitTimer=setTimeout(commit,180);
			});
			input.addEventListener('change',commit);
		}
		trigger.addEventListener('click',() => {
			if (filtersBusy) return;
			panel.hidden=!panel.hidden;
			config=saveEscortCustomFilters(readConfig());
			updateTrigger();
		});
		priceDurationSelect?.addEventListener('change',() => {
			const previousDuration=SETTINGS.searchResultPriceDuration;
			const duration=normalizeEscortPriceDuration(priceDurationSelect.value);
			try {
				updateSettings({
					searchResultPriceDuration:duration
				});
				priceDurationSelect.title=getEscortPriceDurationLabel(duration);
				sortControl.refresh();
				if (typeof onPriceDurationChange === 'function') {
					onPriceDurationChange(duration);
				}
			} catch (error) {
				priceDurationSelect.value=previousDuration;
				priceDurationSelect.title=getEscortPriceDurationLabel(previousDuration);
				log('Nie udało się zapisać czasu ceny na kafelkach',error);
			}
		});
		clearBtn.addEventListener('click',() => {
			for (const checkbox of panel.querySelectorAll('.vm-custom-filter-check')) {
				checkbox.checked=false;
			}
			commit();
		});
		updateDisabledStates();
		updateTrigger();
		if (favoriteSearchEditSession) favoriteSearchEditReadFilters=readConfig;
		titleCol.insertAdjacentElement('afterend',control);
		titleRow.insertAdjacentElement('afterend',panel);
		panel.insertAdjacentElement('afterend',progress);
		titleRow.insertAdjacentElement('afterend',loadAllHint);
		return {
			getSortConfig:sortControl.getConfig,
			setListIncomplete:value => {listIncomplete=!!value; updateTrigger();}, getConfig:() => ({...config}),
			setStatus:text => {status.textContent=text || '';}, setProgress:(text,busy=!!text) => {
				const value=text || '';
				const nextBusy=!!value && !!busy;
				if (progressText.textContent === value && progressBusy === nextBusy) return;
				progressText.textContent=value;
				progress.hidden=!value;
				progressBusy=nextBusy;
				progressCancelButton.hidden=!progressBusy || typeof progressCancelButton.onclick !== 'function';
				setFiltersBusy(progressBusy);
			}, setCancelHandler:handler => {
				progressCancelButton.hidden=!progressBusy || typeof handler !== 'function';
				progressCancelButton.onclick=typeof handler === 'function'
					? handler
					:null;
			}
		};
	}
	function getEscortListTotalResultCount(doc=document,context=null) {
		if (!doc) return null;
		const parseCount=value => {
			const text=String(value || '').replace(/\u00a0/g,' ');
			const patterns=[ /wyświetlane[\s\S]{0,160}?spośród\s+(\d[\d\s.]*)/i,
				/wyswietlane[\s\S]{0,160}?sposrod\s+(\d[\d\s.]*)/i, /displayed[\s\S]{0,160}?\b(?:from|of)\s+(\d[\d\s.,]*)/i,
				/(?:lista\s+wynik(?:ów|ow)|result\s+list)\s*:\s*(\d[\d\s.,]*)/i ];
			for (const pattern of patterns) {
				const match=text.match(pattern);
				if (!match) continue;
				const count=Number(match[1].replace(/[\s.,]/g,''));
				if (Number.isFinite(count) && count >= 0) return count;
			}
			return null;
		};
		const resolvedContext=context || getEscortClubListContext(doc,location.href);
		const navText=resolvedContext?.adsNav?.textContent || '';
		const fromNav=parseCount(navText);
		if (fromNav != null) return fromNav;
		for (const element of doc.querySelectorAll('p, .search-box + *, .search-box ~ *')) {
			const text=element.textContent || '';
			if (!/lista\s+wynik|wyświetlane|wyswietlane|result\s+list|displayed/i.test(text)) continue;
			const count=parseCount(text);
			if (count != null) return count;
		}
		const fromBody=parseCount(doc.body?.textContent || '');
		if (fromBody != null) return fromBody;
		const emptyResultsRoot=getEscortMainResultsSection(doc) || doc;
		for (const element of emptyResultsRoot.querySelectorAll('*')) {
			if (/^(?:brak\s+(?:anons(?:ów|ow)|ogłoszeń|ogloszen)|no\s+(?:ads|adverts|advertisements)(?:\s+found)?)[.!]?$/i.test(
				normalizeEscortAdText(element.textContent))) return 0;
		}
		return null;
	}
	function buildEscortSearchRequestForLocation(locationData) {
		const controls=findEscortSearchLocationControls();
		const form=controls?.searchBox?.closest('form');
		if (!form) throw new Error('Nie znaleziono formularza wyszukiwania Escort.club.');
		const data=new FormData(form);
		const selected=normalizeDefaultSearchLocation(locationData);
		const setField=(name,value) => {
			if (data.has(name) || form.querySelector(`[name="${name}"]`)) {
				data.set(name,String(value || ''));
			}
		};
		setField('country',selected.countryValue || '33');
		setField('province',selected.provinceValue);
		setField('city',selected.cityValue);
		setField('district',selected.districtValue);
		for (const name of ['page','p']) data.delete(name);
		if (controls.searchButton?.name) {
			data.set(controls.searchButton.name,controls.searchButton.value || '');
		}
		const method=String(form.method || 'GET').toUpperCase();
		const action=new URL(form.action || location.href,location.href);
		const options={
			method, credentials:'include', redirect:'follow', cache:'no-store'
		};
		if (method === 'GET') {
			const params=new URLSearchParams();
			for (const [key,value] of data.entries()) {
				if (typeof value === 'string') params.append(key,value);
			}
			action.search=params.toString();
		} else if ((form.enctype || '').toLowerCase().includes('multipart/form-data')) {
			options.body=data;
		} else {
			const params=new URLSearchParams();
			for (const [key,value] of data.entries()) {
				if (typeof value === 'string') params.append(key,value);
			}
			options.body=params;
			options.headers={'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'};
		}
		return {url:action.href,options};
	}
	function stripEscortListPageFromUrl(value) {
		const url=new URL(value,location.href);
		url.pathname=url.pathname
			.replace(/\/(?:page|strona)\d+\.html\/?$/i,'/')
			.replace(/\/(?:page|strona)-\d+\/?$/i,'/')
			.replace(/\/(?:page|strona)\/\d+\/?$/i,'/');
		for (const key of ['page','p','strona']) url.searchParams.delete(key);
		return url;
	}
	function buildEscortListPageUrlFromTemplate(baseUrl,pageNumber,templateUrl=location.href) {
		const page=Math.max(1,Number(pageNumber) || 1);
		const base=stripEscortListPageFromUrl(baseUrl);
		if (page <= 1) return base.href;
		const template=new URL(templateUrl,location.href);
		const pathname=template.pathname;
		const rootPath=base.pathname.replace(/\/$/,'');
		const compact=pathname.match(/\/(page|strona)\d+\.html\/?$/i);
		if (compact) {
			base.pathname=`${rootPath}/${compact[1]}${page}.html`;
			return base.href;
		}
		const dashed=pathname.match(/\/(page|strona)-\d+\/?$/i);
		if (dashed) {
			base.pathname=`${rootPath}/${dashed[1]}-${page}/`;
			return base.href;
		}
		const nested=pathname.match(/\/(page|strona)\/\d+\/?$/i);
		if (nested) {
			base.pathname=`${rootPath}/${nested[1]}/${page}/`;
			return base.href;
		}
		for (const key of ['page','p','strona']) {
			if (!template.searchParams.has(key)) continue;
			base.searchParams.set(key,String(page));
			return base.href;
		}
		return null;
	}
	async function fetchEscortListPage(url,options={},cancelToken=null,requestContext=null) {
		while (true) {
			try {
				requestContext ||= createRequestContext('results.pagination');
				const controller=new AbortController();
				let releaseSlot=() => {},timer=null,response=null,trace=null,outcome='error';
				const observeResponse=(result,complete=true) => releaseSlot.observeResponse
					? releaseSlot.observeResponse(result,complete):observeEscortRequestResponse(url,result);
				const unsubscribe=cancelToken?.onCancel(() => controller.abort()) || (() => {});
				try {
					cancelToken?.throwIfCancelled();
					releaseSlot=await acquireEscortRequestSlot(url,cancelToken,false,options.method,requestContext);
					cancelToken?.throwIfCancelled();
					releaseSlot.beforeDispatch?.();
					timer=setTimeout(() => controller.abort(),ESCORT_AD_DATA_FETCH_TIMEOUT_MS);
					trace=recordDiagnosticRequest(url,options.method || 'GET',requestContext,'fetch');
					const pendingResponse=fetch(url,{
						credentials:'include', redirect:'follow', cache:'no-store', ...options, signal:controller.signal
					});
					releaseSlot.markStarted?.();
					response=await pendingResponse;
					trace?.(response.status,'headers',response);
					outcome='response';
					recordDiagnosticResponse(url,response.status);
					let html='',blockedError=null;
					try {observeResponse({status:response.status,headers:response.headers,finalUrl:response.url},false);}
					catch (error) {blockedError=error;}
					try {html=await response.text();}
					catch (error) {
						if (blockedError) throw blockedError;
						if (response.ok) throw error;
					}
					if (blockedError) throw refineEscortRequestBlockedError(url,{responseText:html},blockedError);
					observeResponse({status:response.status,headers:response.headers,responseText:html,finalUrl:response.url});
					if (!response.ok) throw new Error(`HTTP ${response.status}`);
					const fetchedUrl=response.url || url;
					return {
						doc:new DOMParser().parseFromString(html,'text/html'), url:fetchedUrl
					};
				} catch (error) {
					outcome=cancelToken?.cancelled ? 'cancelled':controller.signal.aborted ? 'timeout':response?.status >= 400 ? 'response':'error';
					throw error;
				} finally {
					trace?.(response?.status,outcome,response);
					clearTimeout(timer);
					unsubscribe();
					releaseSlot();
				}
			} catch (error) {
				if (cancelToken?.cancelled && !isOperationCancelledError(error)) throw createOperationCancelledError(cancelToken.reason);
				if (error?.retryable && (error.status === 503 || error.code === 'ESCORT_QUEUE_RESCHEDULE') && !cancelToken?.cancelled) continue;
				throw error;
			}
		}
	}
	async function loadAdditionalEscortListPages(initialContext,cancelToken=null,onProgress=null,onLocationPage=null,onCardsAppended=null) {
		const requested=normalizeListPagesToShow(LIST_PAGES_TO_SHOW);
		const currentLocation=readEscortLocationFromControls();
		const multiLocations=getEscortMultiLocationsForCurrentPage();
		// Bez lokalizacji w formularzu zachowaj paginację bieżącej listy: użyj jedynej
		// aktywnej lokalizacji lub technicznego ID, którego nie wysyłamy do formularza.
		const currentPageFallbackLocation=currentLocation || (multiLocations.length === 1 ? multiLocations[0]:null) ||
			normalizeDefaultSearchLocation({cityValue:'__vm_current_list__',cityLabel:'Bieżąca lista'});
		const locations=multiLocations.length
			? multiLocations
			:[currentPageFallbackLocation];
		const locationCount=Math.max(1,locations.length);
		const requestContext=createRequestContext('results.pagination');
		const locationAdIds=new Map();
		const locationTotalAds=new Map();
		const rememberLocationTotal=(locationData,doc,context=null) => {
			if (!locationData || !doc) return null;
			const key=getEscortLocationKey(locationData);
			if (!key) return null;
			const total=getEscortListTotalResultCount(doc,context);
			if (total == null) return null;
			locationTotalAds.set(key,total);
			setEscortMultiLocationResultCount(locationData,total,doc);
			return total;
		};
		const rememberLocationAds=(locationData,context) => {
			if (!locationData || !context?.anchors) return 0;
			const key=getEscortLocationKey(locationData);
			if (!key) return 0;
			if (!locationAdIds.has(key)) locationAdIds.set(key,new Set());
			const ids=locationAdIds.get(key);
			for (const anchor of context.anchors) {
				const adId=parseAdIdFromUrl(anchor.getAttribute('href'));
				if (adId) ids.add(adId);
			}
			return ids.size;
		};
		const selectedLocationKeys=new Set( locations.map(getEscortLocationKey).filter(Boolean) );
		const currentLocationKey=getEscortLocationKey(currentPageFallbackLocation);
		const initialPageBelongsToSelection=!!currentLocationKey && selectedLocationKeys.has(currentLocationKey);
		const foreignInitialContainer=locationCount > 1 && !initialPageBelongsToSelection
			? getEscortListCardContainer(initialContext?.anchors || [])
			:null;
		if (locationCount > 1 && !initialPageBelongsToSelection) {
			const foreignCards=new Set( (initialContext?.anchors || []) .map(anchor => anchor.closest('.item-col.col'))
					.filter(Boolean) );
			for (const card of foreignCards) detachHelperNode(card);
		} else if (currentPageFallbackLocation) {
			rememberLocationTotal(currentPageFallbackLocation,document,initialContext);
			if (initialContext?.anchors?.length) {
				rememberLocationAds(currentPageFallbackLocation,initialContext);
			}
		}
		if ( (requested === 1 && locationCount <= 1) || (!initialContext?.anchors?.length && locationCount <= 1) ) {
			return {
				loadedPages:1,
				allPagesLoaded:getEscortListPageNumber(location.href) === 1 && !findNextEscortListPageUrl(document,location.href),
				addedCards:0, duplicateCards:0, locationCount:1, cancelled:false, locationAdCounts:Object.fromEntries(
					[...locationAdIds].map(([key,ids]) => [key,ids.size]) ), locationTotalAds:Object.fromEntries(locationTotalAds)
			};
		}
		const initialAnchors=initialPageBelongsToSelection
			? (initialContext.anchors || [])
			:[];
		let targetContainer=getEscortListCardContainer(initialAnchors) || foreignInitialContainer;
		const ensureTargetContainer=fetchedContext => {
			if (targetContainer?.isConnected) return targetContainer;
			if (!fetchedContext?.anchors?.length) return null;
			const sourceContainer=getEscortListCardContainer(fetchedContext.anchors);
			if (!sourceContainer) return null;
			const importedContainer=document.importNode(sourceContainer,false);
			const mainSection=getEscortMainResultsSection(document);
			const adsNav=initialContext?.adsNav;
			if (adsNav?.parentElement) {
				adsNav.insertAdjacentElement('beforebegin',importedContainer);
			} else if (mainSection) {
				mainSection.appendChild(importedContainer);
			} else {
				return null;
			}
			targetContainer=importedContainer;
			return targetContainer;
		};
		const knownAdIds=new Set( initialAnchors .map(anchor => parseAdIdFromUrl(anchor.getAttribute('href')))
				.filter(Boolean) );
		const startPage=getEscortListPageNumber(location.href);
		const maxPagesPerLocation=requested === 'all'
			? Number.POSITIVE_INFINITY
			:Number(requested);
		const finitePlannedPages=requested === 'all'
			? null
			:maxPagesPerLocation * locationCount;
		const currentKey=initialPageBelongsToSelection
			? currentLocationKey
			:'';
		let loadedPages=initialPageBelongsToSelection ? 1:0;
		let addedCards=0;
		let duplicateCards=0;
		let reachedEnd=false;
		let stoppedByError=false;
		let cancelled=false;
		const locationStats=new Map();
		const completedLocations=new Set();
		const locationEnds=new Map();
		const ensureLocationEnds=destination => {
			if (locationEnds.size) return;
			const ordered=[ ...locations.filter(item => getEscortLocationKey(item) === currentKey),
				...locations.filter(item => getEscortLocationKey(item) !== currentKey) ];
			for (const item of ordered) {
				const end=document.createComment('vm-location-end');
				destination.appendChild(end);
				locationEnds.set(getEscortLocationKey(item),end);
			}
		};
		const report=(locationData,locationPages,phase='loading') => {
			if (typeof onProgress !== 'function') return;
			const key=getEscortLocationKey(locationData);
			try {
				onProgress({
					loadedPages, totalPages:finitePlannedPages, locationCount,
					locationLabel:getSavedLocationButtonLabel(locationData), locationPages, pagesPerLocation:requested,
					locationAds:locationAdIds.get(key)?.size || 0, phase, addedCards, duplicateCards
				});
			} catch {}
		};
		const appendContextCards=async (fetchedContext,fetchedUrl,locationData) => {
			if (!fetchedContext?.anchors?.length) return 0;
			const destination=ensureTargetContainer(fetchedContext);
			if (!destination) {
				log('Nie znaleziono miejsca do wstawienia kafelków z pozostałych lokalizacji');
				return 0;
			}
			rememberLocationAds(locationData,fetchedContext);
			ensureLocationEnds(destination);
			const end=locationEnds.get(getEscortLocationKey(locationData));
			let fragment=document.createDocumentFragment();
			let imported=0;
			let importedSinceYield=0;
			const importedCards=[];
			try {
			for (const anchor of fetchedContext.anchors) {
				cancelToken?.throwIfCancelled();
				const href=anchor.getAttribute('href');
				const adId=parseAdIdFromUrl(href);
				const sourceCard=anchor.closest('.item-col.col');
				if (!adId || !sourceCard) continue;
				if (knownAdIds.has(adId)) {
					duplicateCards++;
					continue;
				}
				const importedCard=prepareImportedEscortCard( document.importNode(sourceCard,true), fetchedUrl );
				fragment.appendChild(importedCard);
				importedCards.push(importedCard);
				knownAdIds.add(adId);
				addedCards++;
				imported++;
				importedSinceYield++;
				if (importedSinceYield >= 8) {
					importedSinceYield=0;
					destination.insertBefore(fragment,end);
					fragment=document.createDocumentFragment();
					await yieldHelperWork();
				}
			}
			} finally {
				destination.insertBefore(fragment,end);
				if (importedCards.length) await onCardsAppended?.(importedCards);
			}
			return imported;
		};
		const loadLocation=async locationData => {
			const locationKey=getEscortLocationKey(locationData);
			const isCurrent=!!currentKey && locationKey === currentKey;
			let currentDoc;
			let currentUrl;
			let locationPages=0;
			try {
				cancelToken?.throwIfCancelled();
				if (isCurrent) {
					currentDoc=document;
					currentUrl=location.href;
					locationPages=1;
					rememberLocationTotal(locationData,currentDoc,initialContext);
					rememberLocationAds(locationData,initialContext);
					locationStats.set(locationKey,locationPages);
					report(locationData,locationPages);
				} else {
					const request=buildEscortSearchRequestForLocation(locationData);
					let first=await fetchEscortListPage(request.url,request.options,cancelToken,requestContext);
					onLocationPage?.(locationData,first.doc,first.url,getEscortClubListContext(first.doc,first.url));
					if (startPage > 1) {
						const directPageUrl=buildEscortListPageUrlFromTemplate( first.url, startPage, location.href );
						if (directPageUrl) {
							first=await fetchEscortListPage(directPageUrl,{method:'GET'},cancelToken,requestContext);
						} else {
							let page=1;
							while (page < startPage) {
								const next=findNextEscortListPageUrl(first.doc,first.url);
								if (!next) break;
								first=await fetchEscortListPage(next,{method:'GET'},cancelToken,requestContext);
								page++;
							}
						}
					}
					currentDoc=first.doc;
					currentUrl=first.url;
					const firstContext=getEscortClubListContext(currentDoc,currentUrl);
					rememberLocationTotal(locationData,currentDoc,firstContext);
					await appendContextCards(firstContext,currentUrl,locationData);
					locationPages=1;
					loadedPages++;
					locationStats.set(locationKey,locationPages);
					report(locationData,locationPages);
					if (!firstContext?.anchors?.length) {
						if (startPage === 1 && locationTotalAds.get(locationKey) === 0) completedLocations.add(locationKey);
						return;
					}
				}
				while (locationPages < maxPagesPerLocation) {
					cancelToken?.throwIfCancelled();
					const nextUrl=findNextEscortListPageUrl(currentDoc,currentUrl);
					if (!nextUrl) {
						reachedEnd=true;
						break;
					}
					const next=await fetchEscortListPage(nextUrl,{method:'GET'},cancelToken,requestContext);
					const fetchedContext=getEscortClubListContext(next.doc,next.url);
					if (!fetchedContext?.anchors?.length) {
						reachedEnd=true;
						break;
					}
					await appendContextCards(fetchedContext,next.url,locationData);
					currentDoc=next.doc;
					currentUrl=next.url;
					locationPages++;
					loadedPages++;
					locationStats.set(locationKey,locationPages);
					report(locationData,locationPages);
					if (locationPages >= 500) {
						stoppedByError=true;
						log('Przerwano doładowywanie po 500 stronach jednej lokalizacji.');
						break;
					}
					await yieldHelperWork();
				}
				if (startPage === 1 && !findNextEscortListPageUrl(currentDoc,currentUrl)) completedLocations.add(locationKey);
			} catch (error) {
				if (isOperationCancelledError(error) || cancelToken?.cancelled || error?.name === 'AbortError') {
					cancelled=true;
					return;
				}
				stoppedByError=true;
				log(`Nie udało się pobrać listy dla lokalizacji ${getSavedLocationButtonLabel(locationData)}`,error);
			}
		};
		const currentFirst=locations.find(item => getEscortLocationKey(item) === currentKey);
		const orderedLocations=[currentFirst,...locations.filter(item => getEscortLocationKey(item) !== currentKey)].filter(Boolean);
		await watchMapLimit(orderedLocations,3,loadLocation);
		for (const end of locationEnds.values()) detachHelperNode(end);
		if (cancelToken?.cancelled) cancelled=true;
		refreshEscortMultiLocationUi();
		return {
			loadedPages, allPagesLoaded:!cancelled && !stoppedByError && completedLocations.size === locationCount, addedCards,
			duplicateCards, locationCount, reachedEnd, stoppedByError, cancelled,
			locationStats:Object.fromEntries(locationStats), locationAdCounts:Object.fromEntries(
				[...locationAdIds].map(([key,ids]) => [key,ids.size]) ), locationTotalAds:Object.fromEntries(locationTotalAds)
		};
	}
	function showEscortListInitializationError(error) {
		ensureSettingsUiStyles();
		const existing=document.getElementById('vm-escort-list-init-error');
		if (existing) return;
		const message=makeElement('div',"vm-escort-list-init-error",'Nie udało się uruchomić dodatków skryptu na tej liście anonsów. Odśwież stronę; jeśli problem się powtórzy, sprawdź konsolę przeglądarki.',{id:'vm-escort-list-init-error'});
		if (error?.message) message.title=String(error.message);
		const heading=findEscortMainResultsHeading();
		const host=heading?.parentElement || document.querySelector('main') || document.body;
		if (heading?.parentElement) heading.insertAdjacentElement('afterend',message);
		else host?.prepend(message);
	}
	function initHiddenResultCards() {
		if (document.documentElement.dataset.vmHiddenResults) return;
		document.documentElement.dataset.vmHiddenResults='1';
		ensureHiddenItemsStyles();
		const refresh=() => {
			for (const anchor of getEscortClubListContext()?.anchors || []) {
				const id=parseAdIdFromUrl(anchor.href);
				const card=anchor.closest('.item-col.col');
				if (card && !card.dataset.vmHiddenManaged) card.classList.toggle('vm-hidden-result',getHiddenAdMatches(id,getHiddenResultProfileUrls(getListCache(id))).length > 0);
			}
		};
		hiddenUiRefreshers.add(refresh);
		const schedule=createAnimationFrameScheduler(refresh);
		new MutationObserver(mutations => {
			if (mutations.some(mutation => [...mutation.addedNodes].some(node => node.nodeType === 1 &&
				(node.closest('.item-col:not([data-vm-hidden-managed])') || node.querySelector('.item-col:not([data-vm-hidden-managed])'))))) schedule();
		}).observe(document.documentElement,{childList:true,subtree:true});
		refresh();
	}
	function getActiveEscortCustomFilterCount(filters) {
		return Object.entries(DEFAULT_ESCORT_CUSTOM_FILTERS).filter(([key,fallback]) =>
			typeof fallback === 'boolean' && key !== 'expanded' && filters[key] &&
			(!['hideCity','hideDescriptionMatch','showOnlyDescriptionMatch'].includes(key) ||
				normalizeCustomFilterQuery(filters[key.replace(/Match$/,'') + 'Query']))).length;
	}
	function getCustomFilterDataNeeds(filters) {
		const source=filters || {};
		return {
			escorti:!!( source.hideWithoutOpinions || source.hideProfileAdsOver || source.hideLocationChangesOver ||
				source.hideYoungerThan || source.hideOlderThan || source.hideHasNote || source.hideHasCategory ||
				source.showOnlyHasNote || source.showOnlyHasCategory || source.showOnlyWithoutOpinions ||
				source.showOnlySingleProfileAd ), escortAd:!!( source.hideAlwaysAvailable || source.hideAdYoungerThan ||
				source.hideAdOlderThan || source.hideTagCountOver || source.hideAgencyPhone || source.hideUnavailableAt ||
				source.hideAvailableAt || ( source.hideDescriptionMatch && normalizeCustomFilterQuery(source.hideDescriptionQuery)
				) || source.showOnlyUnavailableAt || source.showOnlyAvailableAt || source.showOnlyAgencyPhone ||
				source.showOnlyBmi || ( source.showOnlyDescriptionMatch &&
					normalizeCustomFilterQuery(source.showOnlyDescriptionQuery) ) ), agencyPhone:!!(
				source.hideAgencyPhone || source.showOnlyAgencyPhone )
		};
	}
	function customFilterAgeToDays(value,unit) {
		const multipliers={
			days:1, weeks:7, months:30.4375, years:365.25
		};
		return Math.max(1,Number(value) || 1) * (multipliers[unit] || 1);
	}
	function normalizeCustomFilterWeekdayToken(value) {
		return normalizeText(value) .replace(/:\s*$/,'') .normalize('NFD') .replace(/[\u0300-\u036f]/g,'')
			.replace(/ł/g,'l');
	}
	function normalizeCustomFilterSearchText(value) {
		return normalizeText(value) .normalize('NFD') .replace(/[\u0300-\u036f]/g,'') .replace(/ł/g,'l');
	}
	function customFilterDescriptionContains(description,query) {
		const needle=normalizeCustomFilterSearchText(query);
		return !!needle && normalizeCustomFilterSearchText(description).includes(needle);
	}
	function customFilterCityMatches(city,query) {
		const needle=normalizeCustomFilterSearchText(query);
		return !!needle && normalizeCustomFilterSearchText(city) === needle;
	}
	function customFilterTimeToMinutes(value) {
		const normalized=normalizeCustomFilterTime(value,'');
		if (!normalized) return null;
		const [hours,minutes]=normalized.split(':').map(Number);
		return hours * 60 + minutes;
	}
	function customFilterIsAlwaysAvailable(value) {
		const normalized=normalizeCustomFilterWeekdayToken(value);
		return normalized === 'caly czas' || normalized === 'cala dobe' || normalized === '24h' || normalized === '24 h';
	}
	function customFilterIsExplicitlyUnavailable(value) {
		return normalizeCustomFilterWeekdayToken(value).includes('niedostepn');
	}
	function customFilterHasAlwaysAvailable(availability) {
		return !!availability && typeof availability === 'object' &&
			Object.values(availability).some(customFilterIsAlwaysAvailable);
	}
	function customFilterGetDayAvailability(availability,weekday) {
		if (!availability || typeof availability !== 'object') return null;
		const label=ESCORT_CUSTOM_FILTER_WEEKDAYS .find(([value]) => value === normalizeCustomFilterWeekday(weekday))?.[1];
		const wanted=normalizeCustomFilterWeekdayToken(label);
		for (const [day,hours] of Object.entries(availability)) {
			if (normalizeCustomFilterWeekdayToken(day) === wanted) return String(hours || '');
		}
		return null;
	}
	function customFilterGetAvailabilityAt(availability,weekday,time) {
		const hoursText=customFilterGetDayAvailability(availability,weekday);
		if (!hoursText) return null;
		if (customFilterIsAlwaysAvailable(hoursText)) return true;
		if (customFilterIsExplicitlyUnavailable(hoursText)) return false;
		const selectedMinutes=customFilterTimeToMinutes(time);
		if (selectedMinutes == null) return null;
		const ranges=[...hoursText.matchAll( /(\d{1,2})[.:](\d{2})\s*(?:do|to|[-–\u2014])\s*(\d{1,2})[.:](\d{2})/gi )];
		if (!ranges.length) return null;
		return ranges.some(match => {
			const start=Number(match[1]) * 60 + Number(match[2]);
			const end=Number(match[3]) * 60 + Number(match[4]);
			if (start === end) return true;
			if (start < end) return selectedMinutes >= start && selectedMinutes <= end;
			return selectedMinutes >= start || selectedMinutes <= end;
		});
	}
	function customFilterIsUnavailableAt(availability,weekday,time) {
		const available=customFilterGetAvailabilityAt(availability,weekday,time);
		return available == null ? null:!available;
	}
	function buildCustomFilterWatchMetadataIndex() {
		const byAdId=new Map();
		const byProfileId=new Map();
		const add=( map, key, item ) => {
			const normalizedKey= String(key || '').trim();
			if (!normalizedKey) return;
			if (!map.has(normalizedKey)) {
				map.set( normalizedKey, new Set() );
			}
			map.get(normalizedKey).add(item);
		};
		for (const item of getWatchedItems()) {
			const categories= normalizeFavoriteAssignedCategories( item.categories );
			if (!item.favorite && !item.note && !categories.length) {
				continue;
			}
			for (const url of
				getWatchedItemAdUrls( item, false )) {
				const adId= parseAdIdFromUrl(url);
				if (adId) {
					add( byAdId, adId, item );
				}
			}
			for (const url of
				item.profileUrls || []) {
				const profileId= getEscortiProfileId(url);
				if (profileId) {
					add( byProfileId, profileId, item );
				}
			}
			const watchProfiles= String(item.watchKey || '') .match(/^profiles:(.+)$/i)?.[1] ?.split(',')
					.map(value => value.trim()) .filter(value => /^\d+$/.test(value)) || [];
			for (const profileId of
				watchProfiles) {
				add( byProfileId, profileId, item );
			}
		}
		return {
			byAdId, byProfileId
		};
	}
	function getBucketWatchMetadata( bucket, index ) {
		const matches=new Set();
		const collect=( map, key ) => {
			const values= map.get(String(key || ''));
			if (!values) return;
			for (const item of values) {
				matches.add(item);
			}
		};
		for (const member of bucket.members) {
			if (member.adId) {
				collect( index.byAdId, member.adId );
			}
			const result=member.result;
			const profileUrls=[ ...(Array.isArray( result?.profileUrls )
					? result.profileUrls
					:[]), result?.profileUrl, ...(Array.isArray( result?.profileSummaries )
					? result.profileSummaries .map(profile => profile?.profileUrl )
					:[]) ].filter(Boolean);
			for (const url of profileUrls) {
				const profileId= getEscortiProfileId(url);
				if (profileId) {
					collect( index.byProfileId, profileId );
				}
			}
		}
		const categories=new Map();
		let hasNote=false;
		for (const item of matches) {
			if (String(item.note || '').trim()) {
				hasNote=true;
			}
			for (const category of
				normalizeFavoriteAssignedCategories( item.categories )) {
				const key= category.toLocaleLowerCase( 'pl-PL' );
				if (!categories.has(key)) {
					categories.set( key, category );
				}
			}
		}
		return {
			hasNote, favorite:[...matches].some(hasFavoriteStar), categories: [...categories.values()]
		};
	}
	function customFilterCategoryMatches( categories, wanted ) {
		const list= normalizeFavoriteAssignedCategories( categories );
		if (!list.length) return false;
		const target= normalizeCustomFilterCategory( wanted );
		if (!target) return true;
		const targetKey= target.toLocaleLowerCase( 'pl-PL' );
		return list.some( category => category.toLocaleLowerCase( 'pl-PL' ) === targetKey );
	}
	function countEscortiProfileLocationChanges( profile ) {
		if (!profile || typeof profile !== 'object') {
			return null;
		}
		const history=( Array.isArray(profile.cityHistory)
				? profile.cityHistory
				:[] ) .map(entry => ({
				city: normalizeEscortCity( entry?.city ), time: profileDateToTime( entry?.date )
			})) .filter(entry => entry.city) .sort((a,b) => (a.time || 0) - (b.time || 0) );
		const cities= history.map(entry => entry.city);
		const currentCity= normalizeEscortCity( profile.currentCity );
		if ( currentCity && ( !cities.length || normalizeEscortCityKey( cities[cities.length - 1] ) !==
				normalizeEscortCityKey( currentCity ) ) ) {
			cities.push(currentCity);
		}
		if (!cities.length) return 0;
		let changes=0;
		let previousKey='';
		for (const city of cities) {
			const key= normalizeEscortCityKey(city);
			if (!key) continue;
			if ( previousKey && key !== previousKey ) {
				changes++;
			}
			previousKey=key;
		}
		return changes;
	}
	function getEscortiResultLocationChangeCount( result ) {
		if ( !result || result.status !== 'ok' ) {
			return null;
		}
		if (Number(result.profiles) === 0) {
			return 0;
		}
		const profiles= getEscortiProfileSummariesForResearch( result );
		if (!profiles.length) {
			return null;
		}
		let total=0;
		let found=false;
		for (const profile of profiles) {
			const count= countEscortiProfileLocationChanges( profile );
			if (count == null) continue;
			total += count;
			found=true;
		}
		return found
			? total
			:null;
	}
	function collectBucketMemberRecords(bucket,extractor) {
		const records=[];
		let unresolved=false;
		for (const member of bucket.members) {
			const extracted=member.adData?.status === 'ok' ? extractor(member.adData) || {}:{unresolved:true};
			if (extracted.unresolved) unresolved=true;
			if (extracted.hasValue) records.push(extracted.value);
		}
		return {records,unresolved};
	}
	function getBucketAvailabilityRecords(bucket) {
		return collectBucketMemberRecords(bucket,data => ({hasValue:true,
			value:data.availability && typeof data.availability === 'object' ? data.availability:{}}));
	}
	function getBucketDescriptionRecords(bucket) {
		return collectBucketMemberRecords(bucket,data => ({hasValue:true,value:String(data.description || '')}));
	}
	function getBucketAdAgeRecords(bucket) {
		const now=Date.now();
		return collectBucketMemberRecords(bucket,data => {
			const datePostedTime=profileDateToTime(data.datePosted);
			return datePostedTime == null ? {unresolved:true}
				:{hasValue:true,value:Math.max(0,(now - datePostedTime) / (24 * 60 * 60 * 1000))};
		});
	}
	function getBucketTagCountRecords(bucket) {
		return collectBucketMemberRecords(bucket,data => Array.isArray(data.tags)
			? {hasValue:true,value:data.tags.length}:{unresolved:true});
	}
	function getBucketLastVisitedAt(bucket,adIds=[]) {
		let latest=0;
		const members=[...bucket.members].filter(member => !member.hiddenInResults);
		const ids=new Set([...members.map(member => member.adId),...adIds]);
		const visits=members.map(member => member.adData?.lastVisitedAt);
		for (const id of ids) visits.push(getStoredEscortAdData(id)?.lastVisitedAt);
		for (const value of visits) {
			const visitedAt=Number(value);
			if (Number.isFinite(visitedAt) && visitedAt > latest) latest=visitedAt;
		}
		return latest || null;
	}
	function getBucketBmiRecords(bucket) {
		return collectBucketMemberRecords(bucket,data => {
			const bmi=getDeclaredEscortBmi(data);
			return bmi == null ? {}:{hasValue:true,value:bmi};
		});
	}
	function getBucketAgencyPhoneRecords(bucket) {
		return collectBucketMemberRecords(bucket,data => typeof data.isAgencyOrSalonPhone === 'boolean'
			? {hasValue:true,value:data.isAgencyOrSalonPhone}:{unresolved:true});
	}
	function resolveBucketRecordMatch(bucketRecords,predicate) {
		let unresolved=bucketRecords.unresolved;
		for (const record of bucketRecords.records) {
			const result=predicate(record);
			if (result === true) return true;
			if (result == null) unresolved=true;
		}
		return unresolved ? null:false;
	}
	function getEscortBucketActiveAdCount(bucket,{phoneBasedAggregation,escortClubPhoneSearchAggregation,phoneCacheIndex,cacheIndex,phoneSearchAggregationIndex,getGroupAggregateAdIds}) {
		const ids=bucket.group
			? getGroupAggregateAdIds(bucket.group)
			:new Set(bucket.members.map(member => member.adId).filter(Boolean));
		for (const member of bucket.members) {
			const aggregationKeys=phoneBasedAggregation
				? (getEscortAdPhoneDigits(member.adData) ? [`phone:${getEscortAdPhoneDigits(member.adData)}`]:[])
				:getEscortProfileKeys(member.result);
			for (const aggregationKey of aggregationKeys) {
				const cachedIds=phoneBasedAggregation
					? phoneCacheIndex.phoneToAdIds.get( aggregationKey.replace(/^phone:/,'') )
					:cacheIndex.freshProfileToAdIds.get(aggregationKey);
				for (const adId of cachedIds || []) ids.add(adId);
				if (escortClubPhoneSearchAggregation) {
					const searchedIds=phoneSearchAggregationIndex.get( aggregationKey.replace(/^phone:/,'') )?.adIds;
					for (const adId of searchedIds || []) ids.add(adId);
				}
			}
		}
		return ids.size || null;
	}
	function getBucketCustomFilterMetrics( bucket, filters, watchMetadataIndex, activeAdCount, relatedAdIds ) {
		const results=bucket.members .map(member => member.result) .filter(result => result && result.status === 'ok');
		let opinions=null;
		let profileAds=null;
		let locationChanges=null;
		let creationTime=null;
		for (const result of results) {
			const opinionCount=typeof result.garsoTopics === 'number'
				? result.garsoTopics
				:(result.profiles === 0 ? 0:null);
			if (opinionCount != null) {
				opinions=opinions == null ? opinionCount:Math.max(opinions,opinionCount);
			}
			const adCount=typeof result.adLinks === 'number'
				? result.adLinks
				:(result.profiles === 0 ? 0:null);
			if (adCount != null) {
				profileAds=profileAds == null ? adCount:Math.max(profileAds,adCount);
			}
			const resultLocationChanges= getEscortiResultLocationChangeCount( result );
			if ( resultLocationChanges != null ) {
				locationChanges= locationChanges == null
						? resultLocationChanges
						:Math.max( locationChanges, resultLocationChanges );
			}
			const time=profileDateToTime(result.creationDate);
			if (time != null && (creationTime == null || time < creationTime)) {
				creationTime=time;
			}
		}
		const availability=getBucketAvailabilityRecords(bucket);
		const descriptions=getBucketDescriptionRecords(bucket);
		const adAges=getBucketAdAgeRecords(bucket);
		const tagCounts=getBucketTagCountRecords(bucket);
		const agencyPhones= getBucketAgencyPhoneRecords(bucket);
		const bmiValues= getBucketBmiRecords(bucket);
		const lastVisitedAt= getBucketLastVisitedAt(bucket,relatedAdIds);
		const watchMetadata= getBucketWatchMetadata( bucket, watchMetadataIndex );
		const visitAgeDays=lastVisitedAt
			? Math.max(0,(Date.now() - lastVisitedAt) / (24 * 60 * 60 * 1000))
			:null;
		return {
			opinions, profileAds, locationChanges, activeAds:activeAdCount, hasNote: watchMetadata.hasNote, hasCategoryHide:
				customFilterCategoryMatches( watchMetadata.categories, filters.hideCategory ), hasCategoryShowOnly:
				customFilterCategoryMatches( watchMetadata.categories, filters.showOnlyCategory ), visited:!!lastVisitedAt,
			visitAgeDays, tagCountOver:resolveBucketRecordMatch( tagCounts,
				value => value > normalizeCustomFilterInteger(filters.tagCountLimit,SETTINGS.alarmingTagCountLimit,0,10000) ),
			cityMatchesHide:bucket.members.some(member => customFilterCityMatches(
					getEscortListCardAgeAndCity(member?.card).city, filters.hideCityQuery ) ), agencyPhone:resolveBucketRecordMatch(
				agencyPhones, value => value === true ), bmiInRange:resolveBucketRecordMatch( bmiValues, value => {
					const first= normalizeCustomFilterNumber( filters.showOnlyBmiMin, 0, 0, 100 );
					const second= normalizeCustomFilterNumber( filters.showOnlyBmiMax, 100, 0, 100 );
					const min= Math.min(first,second);
					const max= Math.max(first,second);
					return value >= min && value <= max;
				} ), alwaysAvailable:resolveBucketRecordMatch( availability, customFilterHasAlwaysAvailable ),
			unavailableAtHideTime:resolveBucketRecordMatch( availability, value => customFilterIsUnavailableAt( value,
					filters.hideUnavailableDay, filters.hideUnavailableTime ) ),
			availableAtHideAvailableTime:resolveBucketRecordMatch( availability, value => customFilterGetAvailabilityAt( value,
					filters.hideAvailableDay, filters.hideAvailableTime ) ), unavailableAtShowOnlyTime:resolveBucketRecordMatch(
				availability, value => customFilterIsUnavailableAt( value, filters.showOnlyUnavailableDay,
					filters.showOnlyUnavailableTime ) ), availableAtShowOnlyTime:resolveBucketRecordMatch( availability,
				value => customFilterGetAvailabilityAt( value, filters.showOnlyAvailableDay, filters.showOnlyAvailableTime ) ),
			descriptionMatchesHide:resolveBucketRecordMatch( descriptions, value => customFilterDescriptionContains( value,
					filters.hideDescriptionQuery ) ), descriptionMatchesShowOnly:resolveBucketRecordMatch( descriptions,
				value => customFilterDescriptionContains( value, filters.showOnlyDescriptionQuery ) ),
			adYoungerThan:resolveBucketRecordMatch( adAges, value => value < customFilterAgeToDays( filters.adYoungerValue,
					filters.adYoungerUnit ) ), adOlderThan:resolveBucketRecordMatch( adAges, value => value > customFilterAgeToDays(
					filters.adOlderValue, filters.adOlderUnit ) ), ageDays:creationTime == null
				? null
				:Math.max(0,(Date.now() - creationTime) / (24 * 60 * 60 * 1000))
		};
	}
	function evaluateEscortCustomFilters(metrics,filters) {
	let shouldHide=false;
	let unresolved=false;
	const test=(enabled,value,predicate) => {
		if (!enabled) return;
		if (value == null) {
			unresolved=true;
			return;
		}
		if (predicate(value)) shouldHide=true;
	};
	test(filters.hideWithoutOpinions,metrics.opinions,value => value === 0);
	test( filters.hideProfileAdsOver, metrics.profileAds,
		value => value > normalizeCustomFilterInteger(filters.profileAdsLimit,10) );
	test( filters.hideLocationChangesOver, metrics.locationChanges, value => value > normalizeCustomFilterInteger(
				filters.locationChangesLimit, 1, 0, 100000 ) );
	test( filters.hideActiveAdsOver, metrics.activeAds,
		value => value > normalizeCustomFilterInteger(filters.activeAdsLimit,1) );
	test( filters.hideAgencyPhone, metrics.agencyPhone, value => value === true );
	test( filters.hideCity && !!normalizeCustomFilterQuery(filters.hideCityQuery), metrics.cityMatchesHide,
		value => value === true );
	if (filters.hideVisited && metrics.visited) shouldHide=true;
	if (filters.hideUnvisited && !metrics.visited) shouldHide=true;
	if (filters.hideVisitedOlderThan && metrics.visited && metrics.visitAgeDays > customFilterAgeToDays(filters.hideVisitedOlderValue,filters.hideVisitedOlderUnit)) shouldHide=true;
	if (filters.hideVisitedRecentThan && metrics.visited && metrics.visitAgeDays < customFilterAgeToDays(filters.hideVisitedRecentValue,filters.hideVisitedRecentUnit)) shouldHide=true;
	if ( filters.hideHasNote && metrics.hasNote ) {
		shouldHide=true;
	}
	test( filters.hideHasCategory, metrics.hasCategoryHide, value => value === true );
	test(filters.hideTagCountOver,metrics.tagCountOver,value => value === true);
	test( filters.hideYoungerThan, metrics.ageDays,
		value => value < customFilterAgeToDays(filters.youngerValue,filters.youngerUnit) );
	test( filters.hideOlderThan, metrics.ageDays,
		value => value > customFilterAgeToDays(filters.olderValue,filters.olderUnit) );
	test( filters.hideAdYoungerThan, metrics.adYoungerThan, value => value === true );
	test( filters.hideAdOlderThan, metrics.adOlderThan, value => value === true );
	test( filters.hideAlwaysAvailable, metrics.alwaysAvailable, value => value === true );
	test( filters.hideUnavailableAt, metrics.unavailableAtHideTime, value => value === true );
	test( filters.hideAvailableAt, metrics.availableAtHideAvailableTime, value => value === true );
	test( filters.hideDescriptionMatch && !!normalizeCustomFilterQuery(filters.hideDescriptionQuery),
		metrics.descriptionMatchesHide, value => value === true );
	test( filters.showOnlyWithoutOpinions, metrics.opinions, value => value !== 0 );
	test( filters.showOnlySingleProfileAd, metrics.profileAds, value => value !== 1 );
	test( filters.showOnlyAgencyPhone, metrics.agencyPhone, value => value === false );
	test( filters.showOnlyBmi, metrics.bmiInRange, value => value === false );
	if ( filters.showOnlyHasNote && !metrics.hasNote ) {
		shouldHide=true;
	}
	test( filters.showOnlyHasCategory, metrics.hasCategoryShowOnly, value => value === false );
	if ( filters.showOnlyVisitedOlderThan && ( !metrics.visited || metrics.visitAgeDays <= customFilterAgeToDays(
					filters.showOnlyVisitedOlderValue, filters.showOnlyVisitedOlderUnit ) ) ) shouldHide=true;
	if (filters.showOnlyVisitedRecentThan && (!metrics.visited || metrics.visitAgeDays >= customFilterAgeToDays(filters.showOnlyVisitedRecentValue,filters.showOnlyVisitedRecentUnit))) shouldHide=true;
	test( filters.showOnlyUnavailableAt, metrics.unavailableAtShowOnlyTime, value => value === false );
	test( filters.showOnlyAvailableAt, metrics.availableAtShowOnlyTime, value => value === false );
	test( filters.showOnlyDescriptionMatch && !!normalizeCustomFilterQuery(filters.showOnlyDescriptionQuery),
		metrics.descriptionMatchesShowOnly, value => value === false );
		return {shouldHide,unresolved};
	}
	function normalizeFavoriteSearch(value,onError=null) {
		if (!value || !Array.isArray(value.requests) || !value.requests.length) return null;
		try {
			const requests=value.requests.map(request => {
				const url=stripEscortListPageFromUrl(request.url);
				if (url.protocol !== 'https:' || !isEscortClubHost(url.hostname) || url.username || url.password ||
					!(isEscortSearchPath(url.pathname) || url.pathname.startsWith('/anonse/') || url.pathname === '/searchgate.php' || url.pathname === '/')) throw new Error(`Nieobsługiwany adres formularza: ${url.origin}${url.pathname}`);
				url.hash='';
				url.searchParams.sort();
				const method=(request.method || request.options?.method) === 'POST' ? 'POST':'GET';
				const body=new URLSearchParams(method === 'POST' ? (request.body ?? request.options?.body) || '':'');
				body.delete('page'); body.delete('p'); body.sort();
				return {url:url.href,method,body:body.toString()};
			}).sort((a,b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
			return {
				requests:requests.filter((request,index) => !index || JSON.stringify(request) !== JSON.stringify(requests[index - 1])),
				filters:normalizeEscortCustomFilters(value.filters),
				listSearchMode:value.listSearchMode === 'phone-escorti' ? 'phone-escorti':'address-escorti', merge:!!value.merge,
				aggregationMode:normalizeEscortAggregationMode(value.aggregationMode),
				locations:normalizeSearchLocations(value.locations)
			};
		} catch (error) {onError?.(error); return null;}
	}
	function getFavoriteSearchSession() {return favoriteSearchEditSession || favoriteSearchViewSession;}
	function persistFavoriteSearchSession() {
		const session=getFavoriteSearchSession();
		if (!session) return;
		if (favoriteSearchEditSession && window.top !== window.self && JSON.parse(sessionStorage.getItem(FAVORITE_SEARCH_EDIT_KEY) || 'null')?.frameToken !== session.frameToken) return;
		sessionStorage.setItem(favoriteSearchEditSession ? FAVORITE_SEARCH_EDIT_KEY:FAVORITE_SEARCH_VIEW_KEY,JSON.stringify(session));
	}
	function applyFavoriteSearchPageSettings() {
		const search=getFavoriteSearchSession()?.search;
		if (search) Object.assign(SETTINGS,{mergeEscortAds:search.merge,escortAggregationMode:search.aggregationMode,
			...(search.requests.length > 1 ? {multiLocationEnabled:true}:{})});
	}
	function getFavoriteSearchViewUrl(item) {
		const request=item.search?.requests?.[0];
		if (!request) return '';
		const url=new URL(request.method === 'POST' ? '/':request.url,request.url);
		url.hash=new URLSearchParams({[FAVORITE_SEARCH_VIEW_KEY]:item.id}).toString();
		return url.href;
	}
	function initFavoriteSearchView() {
		if (!IS_ESCORT_CLUB || window.top !== window.self) return false;
		const url=new URL(location.href),hash=new URLSearchParams(url.hash.slice(1)),itemId=hash.get(FAVORITE_SEARCH_VIEW_KEY);
		try {
			if (itemId) sessionStorage.removeItem(FAVORITE_SEARCH_EDIT_KEY);
			else if (url.searchParams.has(FAVORITE_SEARCH_EDIT_PARAM) || sessionStorage.getItem(FAVORITE_SEARCH_EDIT_KEY)) return false;
			const session=itemId ? {search:getWatchedItems().find(item => item.id === itemId && item.sourceType === 'escort-search')?.search}
				:JSON.parse(sessionStorage.getItem(FAVORITE_SEARCH_VIEW_KEY) || 'null');
			if (!session) return false;
			if (!itemId && !isEscortSearchPath(url.pathname) && url.pathname !== '/searchgate.php' &&
				!(url.pathname === '/' && url.search) && !session.pending) {
				if (url.pathname === '/') sessionStorage.removeItem(FAVORITE_SEARCH_VIEW_KEY);
				return false;
			}
			session.search=normalizeFavoriteSearch(session.search);
			if (!session.search) throw new Error('Nie znaleziono zapisanego wyszukiwania. Otwórz je ponownie z Ulubionych.');
			favoriteSearchViewSession=session;
			applyFavoriteSearchPageSettings();
			if (itemId) {
				saveEscortActiveMultiLocations(getFavoriteSearchEditLocations(session.search));
				hash.delete(FAVORITE_SEARCH_VIEW_KEY); url.hash=hash.toString(); history.replaceState(history.state,'',url.href);
			}
			session.pending=!!itemId && session.search.requests[0].method === 'POST';
			persistFavoriteSearchSession();
			if (session.pending) {navigateFavoriteSearchRequest(session.search.requests[0]); return true;}
		} catch (error) {
			favoriteSearchViewSession=null; sessionStorage.removeItem(FAVORITE_SEARCH_VIEW_KEY);
			showWatchToast(error?.message || 'Nie udało się otworzyć wyszukiwania.',true);
		}
		return false;
	}
	function makeFavoriteSearchEditButton(item,compact=false) {
		const button=makeButton((compact
			? 'vm-favorites-icon-button vm-ui-button vm-ui-icon vm-ui-compact'
			:'vm-escort-inline-category-add vm-tracked-entry-action') + ' vm-favorite-search-edit-button','✎',{title:`Edytuj wyszukiwanie: ${item.displayName}`});
		button.setAttribute('aria-label',button.title);
		button.addEventListener('click',event => {
			stopUiEvent(event);
			try {openFavoriteSearchEdit(item.id);}
			catch (error) {showWatchToast(error?.message || 'Nie udało się otworzyć edycji.',true);}
		});
		return button;
	}
	function openFavoriteSearchEdit(itemId) {
		const item=getWatchedItems().find(entry => entry.id === itemId && entry.sourceType === 'escort-search');
		const search=normalizeFavoriteSearch(item?.search);
		if (!search) throw new Error('Nie znaleziono zapisanego wyszukiwania.');
		const now=Date.now(),prefix=FAVORITE_SEARCH_EDIT_KEY + ':';
		for (const key of GM_listValues().filter(key => key.startsWith(prefix))) {
			if (!(GM_getValue(key,null)?.expiresAt > now)) deleteHelperStorageValue(key);
		}
		const token=`${now}-${Math.random().toString(36).slice(2)}`,key=prefix + token;
		writeHelperStorageValue(key,{itemId,search,baseline:favoriteSearchKey(search),closeKey:key,expiresAt:now + 600000});
		const url=new URL('/',search.requests[0].url);
		url.searchParams.set(FAVORITE_SEARCH_EDIT_PARAM,token);
		let tab,listener;
		const cleanup=() => {if (listener != null) {GM_removeValueChangeListener(listener); listener=null;} deleteHelperStorageValue(key);};
		try {
			listener=GM_addValueChangeListener(key,(_key,_old,value) => {
				if (value?.finished) {try {tab?.close?.();} finally {cleanup();}}
			});
			tab=GM_openInTab(url.href,{active:true,insert:true,setParent:true});
			tab.onclose=cleanup;
		} catch (error) {cleanup(); throw error;}
	}
	function navigateFavoriteSearchRequest(request,frame) {
		if (request.method !== 'POST') {if (frame) frame.src=request.url; else location.replace(request.url); return;}
		const form=makeElement('form','',null,{method:'POST',action:request.url,target:frame?.name || '_self',hidden:true});
		for (const [name,value] of new URLSearchParams(request.body)) {
			const input=makeElement('input','',null,{type:'hidden',name,value}); form.appendChild(input);
		}
		document.body.appendChild(form);
		HTMLFormElement.prototype.submit.call(form);
		detachHelperNode(form);
	}
	function getFavoriteSearchEditLocations(search) {
		if (search.requests.length < 2) return [];
		return normalizeSearchLocations(search.requests.map(request => {
			const params=new URLSearchParams(new URL(request.url).search);
			for (const [key,value] of new URLSearchParams(request.body)) params.set(key,value);
			const selected={countryValue:params.get('country') || '33',provinceValue:params.get('province') || '',
				cityValue:params.get('city') || '',districtValue:params.get('district') || ''};
			const saved=search.locations?.find(value => getEscortLocationKey(value) === getEscortLocationKey(selected));
			return resolveEscortLocationLabels({...saved,...selected});
		}));
	}
	function updateFavoriteSearchEditLocation(doc=null) {
		if (!favoriteSearchEditSession) return;
		const active=getEscortActiveMultiLocations();
		if (active.length < 2) return;
		const current=readEscortLocationFromControls(doc ? findEscortSearchLocationControls(doc):undefined);
		if (!current) {if (!doc) saveEscortActiveMultiLocations([]); return;}
		const previous=doc ? current:favoriteSearchEditSession.currentLocation;
		if (!previous || (!doc && getEscortLocationKey(previous) === getEscortLocationKey(current))) return;
		saveEscortActiveMultiLocations(active.map(value =>
			getEscortLocationKey(value) === getEscortLocationKey(previous) ? current:value),!doc);
		if (doc) refreshEscortMultiLocationUi();
		else {favoriteSearchEditSession.currentLocation=current; persistFavoriteSearchSession();}
	}
	function captureFavoriteSearchEdit() {
		updateFavoriteSearchEditLocation();
		const locations=getEscortMultiLocationsForCurrentPage();
		const requests=(locations.length ? locations:[readEscortLocationFromControls() || {}]).map(buildEscortSearchRequestForLocation);
		const search=normalizeFavoriteSearch({...favoriteSearchEditSession.search,requests,locations,
			filters:favoriteSearchEditReadFilters ? favoriteSearchEditReadFilters():getEscortCustomFilters()});
		if (!search) throw new Error('Nie udało się odczytać parametrów formularza wyszukiwania.');
		return search;
	}
	function getFavoriteSearchLocationScope(request,doc,search=null) {
		const url=new URL(request.url),params=new URLSearchParams(url.search);
		if (request.method === 'POST') for (const [key,value] of new URLSearchParams(request.body)) params.set(key,value);
		const controls=findEscortSearchLocationControls(doc),scope={};
		const selected=Object.fromEntries(['country','province','city','district'].map(key => [key + 'Value',params.get(key) || '']));
		const saved=search?.locations?.find(value => getEscortLocationKey(value) === getEscortLocationKey(selected));
		const labels=resolveEscortLocationLabels({...saved,...selected},doc);
		for (const key of ['country','province','city','district']) {
			const value=params.get(key);
			if (!value || value === '0') continue;
			const option=findEscortLocationOption(controls?.[key] || doc.querySelector(`select[name="${key}"]`),value)?.textContent?.trim();
			const label=option && option !== value ? option:((params.get('country') || '33') === '33' ? labels[key + 'Label']:'');
			scope[key]=key === 'country' && value === '33' ? 'polska':label ? normalizeEscortCityKey(label):null;
			if (!scope[key]) throw new Error('Nie odczytano lokalizacji wyszukiwania. Otwórz „Edytuj” i zapisz wyszukiwanie ponownie.');
		}
		const cityPath=url.pathname.match(/^\/(?:anonse\/[^/]+|personal-ads)\/([^/]+)\/?$/i)?.[1];
		if (cityPath && !('city' in scope)) {
			if (cityPath.toLowerCase() === 'poland') {if (!('country' in scope)) scope.country='polska';}
			else {try {scope.cityPath=decodeURIComponent(cityPath);} catch {scope.cityPath=null;}}
		}
		return scope;
	}
	function matchesFavoriteSearchLocation(adData,scope) {
		const location=adData?.location;
		const slug=value=>normalizeEscortCityKey(value).replace(/ł/g,'l').replace(/[^\p{L}\p{N}]+/gu,'-').replace(/^-|-$/g,'');
		return !!location && Object.entries(scope).every(([key,value])=>value && (key === 'cityPath'
			? slug(location.city) === slug(value):normalizeEscortCityKey(location[key]) === value));
	}
	async function* iterateFavoriteSearchPages(search,cancelToken=null,onPage=null,requestContext=null) {
		let page=0;
		for (const request of search.requests) {
			let url=request.url,slots=0,locationScope=null;
			const seen=new Set();
			while (url) {
				cancelToken?.throwIfCancelled();
				if (seen.has(url)) throw new Error('Nie udało się odczytać całego wyszukiwania. Zawęź wyniki filtrami i spróbuj ponownie.');
				seen.add(url);
				page++; onPage?.(page);
				const pageContext=createRequestContext('watch.search-pages',requestContext);
				const {doc,finalUrl}=await fetchWatchDocumentDirect(url,cancelToken,{
					...(seen.size === 1 ? {method:request.method,...(request.method === 'POST' ? {data:request.body,
						headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'}}:{})}:{}),
					requestContext:pageContext
				});
				if (!isEscortClubHost(new URL(finalUrl).hostname)) throw new Error('Wyszukiwanie przekierowało poza Escort.club.');
				const context=getEscortClubListContext(doc,finalUrl),total=getEscortListTotalResultCount(doc,context);
				if (!context?.anchors?.length && total !== 0) throw new Error('Nie rozpoznano wyników wyszukiwania. Sprawdź stronę Escort.club i spróbuj ponownie.');
				slots += context?.anchors?.length || 0;
				url=findNextEscortListPageUrl(doc,finalUrl);
				if (!url && total != null && slots < total) throw new Error('Odczytano tylko część wyników. Zawęź wyszukiwanie lub spróbuj ponownie.');
				locationScope ||= getFavoriteSearchLocationScope(request,doc,search);
				yield {context,finalUrl,page,locationScope};
			}
		}
		cancelToken?.throwIfCancelled();
	}
	function commitFavoriteSearchEdit(session,search) {
		const items=getWatchedItems(),index=items.findIndex(item => item.id === session.itemId && item.sourceType === 'escort-search');
		if (index < 0) throw new Error('Ten profil został usunięty. Edycja nie została zapisana.');
		const current=items[index],key=favoriteSearchKey(search);
		if (favoriteSearchKey(normalizeFavoriteSearch(current.search)) !== session.baseline)
			throw new Error('Wyszukiwanie zmieniono w innej karcie. Anuluj i otwórz edycję ponownie.');
		if (items.some(item => item.id !== current.id && item.sourceType === 'escort-search' && favoriteSearchKey(normalizeFavoriteSearch(item.search)) === key))
			throw new Error('Takie wyszukiwanie jest już zapisane jako inny profil.');
		if (key === session.baseline) return updateWatchedItem(current.id,{search}) || current;
		items[index]=normalizeWatchedItem({...current,search,watchKey:key,resolvedAdUrls:[],state:null,
			lastCheckedAt:0,lastFullCheckedAt:0,lastError:null,hiddenLastCheckedAt:0,hiddenLastError:null});
		saveWatchedItems(items);
		return items[index];
	}
	function finishFavoriteSearchEdit() {
		const session=favoriteSearchEditSession;
		if (!session) return;
		favoriteSearchEditCancelToken?.cancel('Zakończono edycję wyszukiwania');
		sessionStorage.removeItem(FAVORITE_SEARCH_EDIT_KEY);
		favoriteSearchEditSession=null; favoriteSearchEditReadFilters=null;
		detachHelperNode(document.getElementById('vm-favorite-search-edit-frame'));
		sessionStorage.removeItem(ESCORT_MULTI_LOCATION_SESSION_KEY);
		try {if (session.closeKey) writeHelperStorageValue(session.closeKey,{finished:true,expiresAt:Date.now() + 60000});}
		finally {window.close();}
	}
	function trimFavoriteSearchEditPage() {
		if (!favoriteSearchEditSession) return;
		const searchBox=findEscortSearchLocationControls()?.searchBox;
		const results=getEscortMainResultsSection() || getEscortClubListContext()?.heading?.closest('section');
		const keep='#vm-favorite-search-edit-bar, #advansed-search, .vm-ui-overlay, .vm-multi-panel, .select2-container, .select2-dropdown, .bs-container';
		const hideOutside=(boundary,direction) => {
			for (let node=boundary; node && node !== document.body; node=node.parentElement) {
				for (let sibling=node[direction]; sibling; sibling=sibling[direction]) {
					if (!sibling.matches('script, style, link') && !sibling.matches(keep) && !sibling.querySelector(keep)) sibling.classList.add('vm-search-edit-outside');
				}
			}
		};
		hideOutside(searchBox,'previousElementSibling');
		hideOutside(results,'nextElementSibling');
	}
	function mountFavoriteSearchEditBar() {
		const session=favoriteSearchEditSession,origin=location.origin;
		const item=getWatchedItems().find(entry => entry.id === session.itemId);
		const shell=prepareHelperStyleApi(makeElement('div'));
		shell.id='vm-favorite-search-edit-shell';
		const bar=makeElement('div');
		bar.id='vm-favorite-search-edit-bar'; bar.setAttribute('role','region');
		bar.setAttribute('aria-label','Edycja wyszukiwania jako profilu');
		const title=makeElement('strong','',`Edytujesz wyszukiwanie zapisane jako profil: ${item?.displayName || ''}`);
		const save=makeButton('vm-ui-button -primary','Zapisz wyszukiwanie');
		const cancel=makeButton('vm-ui-button','Anuluj');
		const status=makeElement('span'); status.setAttribute('role','status');
		const frame=makeElement('iframe');
		frame.id='vm-favorite-search-edit-frame'; frame.name=FAVORITE_SEARCH_EDIT_KEY + ':' + session.frameToken;
		frame.title='Wyszukiwarka - edycja profilu';
		const style=makeElement('style');
		style.textContent=`
html.vm-search-edit-shell, html.vm-search-edit-shell body { overflow:hidden !important; }
html.vm-search-edit-shell body > :not(#vm-favorite-search-edit-shell):not(#vm-garso-watch-toast):not(style):not(script) { display:none !important; }
#vm-favorite-search-edit-shell { position:fixed; inset:0; z-index:2147483647; display:flex; flex-direction:column; padding:7px; box-sizing:border-box; background:var(--vm-ui-accent,${getEscortPagePinkColor()}); color:var(--vm-ui-accent-contrast,#fff); font:700 16px/1.2 var(--vm-ui-font-family,Arial,sans-serif); }
#vm-favorite-search-edit-bar { display:flex; flex:0 0 auto; flex-wrap:wrap; align-items:center; gap:6px 10px; padding:4px 10px 10px; }
#vm-favorite-search-edit-bar strong { flex:1 1 320px; font:inherit; }
#vm-favorite-search-edit-bar .vm-ui-button { flex:0 0 auto; min-height:30px; padding:4px 10px; border-color:var(--vm-ui-accent-contrast,#fff); background:transparent; color:inherit; font:inherit; }
#vm-favorite-search-edit-bar .vm-ui-button.-primary { background:var(--vm-ui-accent-contrast,#fff); color:var(--vm-ui-accent,${getEscortPagePinkColor()}); }
#vm-favorite-search-edit-bar .vm-ui-button:disabled { opacity:.55; cursor:wait; }
#vm-favorite-search-edit-bar .vm-ui-button:focus-visible { outline-color:var(--vm-ui-accent-contrast,#fff); }
#vm-favorite-search-edit-bar [role=status] { flex:1 1 100%; font-size:12px; }
#vm-favorite-search-edit-bar [role=status]:empty { display:none; }
#vm-favorite-search-edit-frame { display:block; flex:1 1 0; min-height:0; width:100%; border:0; background:var(--vm-ui-bg,#fff); }
`;
		document.head.appendChild(style); bar.append(title,save,cancel,status); shell.append(bar,frame);
		document.documentElement.classList.add('vm-search-edit-shell');
		let ready=false,waiting=false,busy=false,pageId='',requestId=0,timer;
		const setBusy=value => {busy=value; frame.inert=value; save.disabled=value || !ready; if (!value) status.textContent='';};
		const loading=() => {
			favoriteSearchEditCancelToken?.cancel('Wyszukiwarka jest przeładowywana');
			ready=false; waiting=false; setBusy(false); frame.setAttribute('aria-busy','true');
			clearTimeout(timer);
		};
		const receive=async event => {
			const data=event.data;
			if (favoriteSearchEditSession !== session || event.origin !== origin || event.source !== frame.contentWindow ||
					data?.type !== FAVORITE_SEARCH_EDIT_KEY || data.token !== session.frameToken) return;
			if (data.state === 'loading') {loading(); return;}
			if (data.state === 'ready' || data.state === 'error') {
				if (busy) return;
				clearTimeout(timer); pageId=data.pageId; ready=data.state === 'ready'; setBusy(false);
				frame.setAttribute('aria-busy','false');
				if (!ready) showWatchToast(data.error || 'Nie udało się przygotować wyszukiwarki.',true);
				return;
			}
			if (data.state !== 'capture' || !waiting || data.pageId !== pageId || data.requestId !== requestId) return;
			waiting=false; clearTimeout(timer);
			const token=favoriteSearchEditCancelToken=createOperationCancelToken('Zapis wyszukiwania');
			try {
				if (data.error) throw new Error(data.error);
				const search=normalizeFavoriteSearch(data.search);
				if (!search) throw new Error('Nie udało się odczytać parametrów wyszukiwania.');
				token.throwIfCancelled();
				if (favoriteSearchEditSession !== session) return;
				const saved=commitFavoriteSearchEdit(session,search);
				cancel.disabled=true; status.textContent='Wyszukiwanie zapisane. Aktualizowanie anonsów profilu…';
				await refreshFavoriteItemData(saved,true);
				finishFavoriteSearchEdit();
			} catch (error) {
				if (favoriteSearchEditSession === session && !isOperationCancelledError(error)) {
					showWatchToast(error?.message || 'Nie udało się zapisać wyszukiwania.',true);
					setBusy(false); cancel.disabled=false;
				}
			}
		};
		window.addEventListener('message',receive);
		cancel.addEventListener('click',() => {clearTimeout(timer); finishFavoriteSearchEdit();});
		save.addEventListener('click',() => {
			if (save.disabled || favoriteSearchEditSession !== session) return;
			setBusy(true); waiting=true;
			frame.contentWindow.postMessage({type:FAVORITE_SEARCH_EDIT_KEY,token:session.frameToken,state:'capture',pageId,requestId:++requestId},origin);
			timer=setTimeout(() => {
				waiting=false; setBusy(false); showWatchToast('Wyszukiwarka nie odpowiada. Poczekaj na załadowanie wyników i spróbuj zapisać ponownie.',true);
			},10000);
		});
		frame.addEventListener('load',() => {
			try {
				if (frame.contentWindow.location.href === 'about:blank') return;
				if (frame.contentWindow.location.origin !== origin) throw new Error();
				if (ready && frame.contentDocument?.documentElement.dataset.vmSearchEditPage !== pageId) loading();
			} catch {
				loading(); showWatchToast('Wyszukiwarka przeszła na inną domenę lub ramka została zablokowana. Anuluj edycję i otwórz ją ponownie.',true);
			}
		});
		window.addEventListener('pagehide',() => {
			clearTimeout(timer); favoriteSearchEditCancelToken?.cancel('Zamknięto kartę edycji');
			window.removeEventListener('message',receive);
		});
		window.addEventListener('pageshow',event => {if (event.persisted) location.reload();});
		loading(); document.body.appendChild(shell);
		navigateFavoriteSearchRequest(session.search.requests[0],frame);
	}
	function initFavoriteSearchEditFrame() {
		const session=favoriteSearchEditSession,origin=location.origin,pageId=crypto.randomUUID();
		document.documentElement.dataset.vmSearchEditPage=pageId;
		let active=true;
		favoriteSearchEditFrameNotify=(state,details={}) => {
			if (active) window.parent.postMessage({...details,type:FAVORITE_SEARCH_EDIT_KEY,token:session.frameToken,pageId,state},origin);
		};
		favoriteSearchEditFrameNotify('loading');
		const style=makeElement('style');
		style.textContent=`
html.vm-favorite-search-edit .item-col.col, html.vm-favorite-search-edit .item-col.col * { cursor:not-allowed !important; }
html.vm-favorite-search-edit :is(.vm-search-edit-outside, #vm-favorite-search-button, #${HELPER_MENU_ID}, #${GLOBAL_QUICK_SEARCH_ID}, #${WATCH_BAR_ID}, #${WATCH_BAR_SPACER_ID}) { display:none !important; }
`;
		document.head.appendChild(style); document.documentElement.classList.add('vm-favorite-search-edit');
		trimFavoriteSearchEditPage();
		const blockCard=event => {
			if (event.target.closest?.('.item-col.col')) {event.preventDefault(); event.stopImmediatePropagation();}
		};
		for (const type of ['click','auxclick','contextmenu']) window.addEventListener(type,blockCard,true);
		window.addEventListener('message',event => {
			const data=event.data;
			if (!active || event.origin !== origin || event.source !== window.parent || data?.type !== FAVORITE_SEARCH_EDIT_KEY ||
					data.token !== session.frameToken || data.pageId !== pageId || data.state !== 'capture') return;
			try {
				const search=captureFavoriteSearchEdit(),limitError=getFavoriteSearchLimitError();
				if (limitError) throw new Error(limitError);
				session.search=search; persistFavoriteSearchSession();
				favoriteSearchEditFrameNotify('capture',{requestId:data.requestId,search});
			} catch (error) {favoriteSearchEditFrameNotify('capture',{requestId:data.requestId,error:error.message});}
		});
		window.addEventListener('pagehide',() => {
			favoriteSearchEditFrameNotify('loading'); active=false;
			try {
				if (JSON.parse(sessionStorage.getItem(FAVORITE_SEARCH_EDIT_KEY) || 'null')?.frameToken !== session.frameToken) return;
				if (favoriteSearchEditReadFilters) session.search=captureFavoriteSearchEdit();
				persistFavoriteSearchSession();
			} catch {}
		});
		window.addEventListener('pageshow',event => {if (event.persisted) location.reload();});
	}
	function initFavoriteSearchEdit() {
		if (!IS_ESCORT_CLUB) return false;
		const url=new URL(location.href),token=url.searchParams.get(FAVORITE_SEARCH_EDIT_PARAM);
		const child=window.top !== window.self;
		if (child && !window.name.startsWith(FAVORITE_SEARCH_EDIT_KEY + ':')) return false;
		try {
			const fresh=!child && token && token !== 'shell';
			const key=fresh ? FAVORITE_SEARCH_EDIT_KEY + ':' + token:FAVORITE_SEARCH_EDIT_KEY;
			const session=fresh ? GM_getValue(key,null):JSON.parse(sessionStorage.getItem(key) || 'null');
			if (fresh) deleteHelperStorageValue(key);
			if (!child && !token && (!session || getEscortAdIdFromPath(url.pathname))) return false;
			if (!session?.itemId || !normalizeFavoriteSearch(session.search) || (fresh && !(session.expiresAt > Date.now())))
				throw new Error('Brak aktualnej sesji edycji. Otwórz edycję ponownie z profilu.');
			if (child && (window.name !== FAVORITE_SEARCH_EDIT_KEY + ':' + session.frameToken ||
					window.parent.location.origin !== url.origin || new URL(window.parent.location.href).searchParams.get(FAVORITE_SEARCH_EDIT_PARAM) !== 'shell')) return false;
			favoriteSearchEditSession=session;
			applyFavoriteSearchPageSettings();
			if (child) {
				session.currentLocation=readEscortLocationFromControls();
				initFavoriteSearchEditFrame();
				return false;
			}
			session.frameToken ||= crypto.randomUUID();
			persistFavoriteSearchSession();
			if (fresh) saveEscortActiveMultiLocations(getFavoriteSearchEditLocations(session.search));
			url.searchParams.set(FAVORITE_SEARCH_EDIT_PARAM,'shell');
			history.replaceState(history.state,'',url.href);
			mountFavoriteSearchEditBar();
			return true;
		} catch (error) {
			favoriteSearchEditSession=null;
			if (!child) sessionStorage.removeItem(FAVORITE_SEARCH_EDIT_KEY);
			else try {
				if (window.parent.location.origin === url.origin && new URL(window.parent.location.href).searchParams.get(FAVORITE_SEARCH_EDIT_PARAM) === 'shell')
					window.parent.postMessage({type:FAVORITE_SEARCH_EDIT_KEY,token:window.name.slice(FAVORITE_SEARCH_EDIT_KEY.length + 1),
						state:'error',error:error?.message || 'Nie udało się uruchomić edycji.'},url.origin);
			} catch {}
			showWatchToast(error?.message || 'Nie udało się uruchomić edycji.',true);
			return child;
		}
		return false;
	}
	function favoriteSearchKey(search) {
		if (!search) return 'search:null';
		const criteria={...search}; delete criteria.locations;
		return `search:${JSON.stringify(criteria)}`;
	}
	function getFavoriteSearchBuckets(states,search) {
		const buckets=new Set();
		const byKey=new Map();
		const phoneBased=['exact-phone','phone-escort-search'].includes(search.aggregationMode);
		for (const member of states) {
			const phone=getEscortAdPhoneDigits(member.adData);
			const keys=!search.merge ? []:phoneBased ? (phone ? [`phone:${phone}`]:[]):getEscortProfileKeys(member.result);
			const found=[...new Set(keys.map(key => byKey.get(key)).filter(Boolean))];
			const bucket=found[0] || {members:[],keys:new Set()};
			for (const other of found.slice(1)) {
				bucket.members.push(...other.members);
				for (const key of other.keys) {bucket.keys.add(key); byKey.set(key,bucket);}
				buckets.delete(other);
			}
			bucket.members.push(member);
			for (const key of keys) {bucket.keys.add(key); byKey.set(key,bucket);}
			buckets.add(bucket);
		}
		return [...buckets];
	}
	async function resolveFavoriteSearch(item,cancelToken=null,onProgress=null,requestContext=null) {
		const operationContext=createRequestContext('watch.saved-search',requestContext);
		await persistentCacheInitialization;
		await ensureFullPersistentCache();
		cancelToken?.throwIfCancelled();
		const search=normalizeFavoriteSearch(item.search);
		if (!search) throw new Error('Brak prawidłowej definicji wyszukiwania. Zapisz wyszukiwanie ponownie.');
		const states=new Map(),known=new Set(getWatchedItemHistoricalAdUrls(item).map(parseAdIdFromUrl));
		const filters=search.filters;
		const filtered=Object.entries(filters).some(([key,value]) => /^(hide|showOnly)/.test(key) && value === true);
		const phoneBased=['exact-phone','phone-escort-search'].includes(search.aggregationMode);
		const needs=getCustomFilterDataNeeds(filters);
		const needsEscorti=needs.escorti || (((search.merge && filtered) || filters.hideActiveAdsOver) && !phoneBased) ||
			getWatchedItems().some(entry => entry.id !== item.id && entry.hidden && entry.profileUrls.length);
		const needsAggregationPhone=(search.merge && filtered && phoneBased) || (filters.hideActiveAdsOver && phoneBased);
		const needsEscortiPhone=needsEscorti && search.listSearchMode === 'phone-escorti';
		const needsAd=needs.escortAd || needsAggregationPhone || needsEscortiPhone;
		const excluded=state => getHiddenAdMatches(state.adId,getHiddenResultProfileUrls(state.result)).some(entry => entry.id !== item.id);
		for await (const {context,finalUrl,page,locationScope} of iterateFavoriteSearchPages(search,cancelToken,page => {
			onProgress?.(`Sprawdzanie wyszukiwania · strona ${page}…`);
			touchWatchCheckLock();
		},operationContext)) {
			const newStates=[];
			for (const anchor of context?.anchors || []) {
				const adUrl=normalizeEscortiAdUrl(anchor.getAttribute('href'),finalUrl);
				const adId=parseAdIdFromUrl(adUrl);
				if (!adId) continue;
				if (states.has(adId)) {states.get(adId).locationScopes.push(locationScope);continue;}
				const state={adId,adUrl,locationScopes:[locationScope],newCandidate:!known.has(adId),card:anchor.closest('.item-col.col'),result:getListCache(adId,search.listSearchMode),adData:getStoredEscortAdData(adId)};
				states.set(adId,state);
				if (!excluded(state)) newStates.push(state);
			}
			await watchMapLimit(newStates,2,async state => {
				cancelToken?.throwIfCancelled();
				if (state.newCandidate || needsAd || needsEscorti) onProgress?.(`Sprawdzanie filtrów · anons ${state.adId} · strona ${page}…`);
				const cardContext=createRequestContext('watch.search-ad',operationContext);
				const needPhone=!!(needsAggregationPhone || (needsEscortiPhone && !isListCacheFresh(state.result)));
				const needAd=needs.escortAd || needPhone;
				if (needAd) state.adData=await getEscortAdData(state.adId,state.adUrl,state.newCandidate,cancelToken,cardContext,{needPhone});
				if (state.newCandidate) {
					state.verified=needAd ? {url:state.adUrl,active:state.adData?.status === 'ok',adData:state.adData,
						city:state.adData?.location?.city,imageUrl:state.adData?.imageUrl}
						:await inspectEscortClubAd(state.adUrl,cancelToken,true,cardContext);
					cancelToken?.throwIfCancelled();
					if (!state.verified.active || !state.verified.adData) return;
					state.adData=state.verified.adData;
				}
				if (needsEscorti && !isListCacheFresh(state.result)) {
					const query=search.listSearchMode === 'phone-escorti' ? getEscortAdPhoneDigits(state.adData):state.adUrl;
					if (!query) throw new Error('Brak numeru telefonu potrzebnego do sprawdzenia filtrów. Spróbuj ponownie po odczytaniu danych anonsu.');
					state.result=await getOrStartListCheck(state.adId,query,search.listSearchMode,cancelToken,cardContext);
				}
				if (needsEscorti && state.result?.status !== 'ok') throw new Error('Nie udało się sprawdzić danych Escorti.pl potrzebnych do filtrowania. Spróbuj ponownie.');
			});
		}
		// Odrzucony kandydat pozostaje nieznany; wróci przy zwykłym sprawdzeniu.
		const candidates=[...states.values()].filter(state => !excluded(state) && (!state.newCandidate ||
			(state.verified?.active && state.locationScopes.some(scope=>matchesFavoriteSearchLocation(state.verified.adData,scope)))));
		const watchMetadata=buildCustomFilterWatchMetadataIndex();
		const cacheIndex=buildEscortListCacheIndex();
		const phoneIndex=phoneBased ? buildEscortAdPhoneCacheIndex():null;
		const phoneResults=new Map();
		const accepted=[];
		for (const bucket of getFavoriteSearchBuckets(candidates,search)) {
			cancelToken?.throwIfCancelled();
			if (filters.hideActiveAdsOver && search.aggregationMode === 'phone-escort-search') {
				for (const member of bucket.members) {
					const phone=getEscortAdPhoneDigits(member.adData);
					if (phone && !phoneResults.has(phone)) {
						const found=await fetchEscortClubPhoneSearchAds(phone,cancelToken,
							createRequestContext('watch.search-phone',operationContext));
						phoneResults.set(phone,{adIds:new Set(found.adUrls.map(parseAdIdFromUrl).filter(Boolean))});
					}
				}
			}
			const activeAdCount=getEscortBucketActiveAdCount(bucket,{phoneBasedAggregation:phoneBased,escortClubPhoneSearchAggregation:search.aggregationMode === 'phone-escort-search',phoneCacheIndex:phoneIndex,cacheIndex,phoneSearchAggregationIndex:phoneResults});
			const metrics=getBucketCustomFilterMetrics(bucket,filters,watchMetadata,activeAdCount);
			const {shouldHide,unresolved}=evaluateEscortCustomFilters(metrics,filters);
			if (shouldHide) continue;
			if (unresolved) throw new Error('Brakuje danych do sprawdzenia filtrów niestandardowych. Poczekaj na dane lub zawęź wyszukiwanie i spróbuj ponownie.');
			accepted.push(...bucket.members);
		}
		onProgress?.(`Pasujące anonse: ${accepted.length} · sprawdzono wszystkie strony.`);
		return {profileUrls:[],adUrls:accepted.map(state => state.adUrl),displayName:item.displayName,imageUrl:item.imageUrl || '',profileInfos:[],
			verifiedAdResults:accepted.map(state=>state.verified).filter(Boolean)};
	}
	function getFavoriteSearchLimitError(locations=getEscortMultiLocationsForCurrentPage()) {
		const total=locations.length > 1 ? getEscortMultiLocationKnownTotal(locations):getEscortListTotalResultCount();
		if (total == null) return 'Nie odczytano jeszcze liczby ogłoszeń dla wszystkich wybranych lokalizacji. Poczekaj na załadowanie liczników wyników lub odśwież stronę.';
		return total > FAVORITE_SEARCH_MAX_ADS
			? `Za dużo wyników: ${total} ogłoszeń. Limit zapisu wyszukiwania to ${FAVORITE_SEARCH_MAX_ADS} ogłoszeń łącznie dla wybranych lokalizacji. Zawęź wyszukiwanie filtrami serwisu.`:'';
	}
	function addFavoriteSearchButton(listContext,row) {
		if (!row || document.getElementById('vm-favorite-search-button')) return;
		const locations=getEscortMultiLocationsForCurrentPage();
		const multi=locations.length > 1;
		updateFavoriteSearchEditLocation(document);
		const requests=multi ? locations.map(buildEscortSearchRequestForLocation):[{url:location.href}];
		const makeSearch=() => normalizeFavoriteSearch({requests,locations,filters:getEscortCustomFilters(),listSearchMode:getEscortListSearchMode(),merge:SETTINGS.mergeEscortAds,aggregationMode:SETTINGS.escortAggregationMode},error => showWatchToast(`Nie udało się zapisać parametrów wyszukiwania. ${error?.message || error}`,true));
		const button=makeButton('vm-ui-button','★ Dodaj wyszukiwanie jako profil');
		button.id='vm-favorite-search-button';
		button.title=`Zapisuje wyszukiwanie z najwyżej ${FAVORITE_SEARCH_MAX_ADS} ogłoszeniami łącznie dla wybranych lokalizacji, po uwzględnieniu filtrów serwisu.`;
		const refresh=() => {button.style.display=getWatchSettings().enabled ? '':'none';};
		hiddenUiRefreshers.add(refresh); refresh();
		button.addEventListener('click',async () => {
			if (!getWatchSettings().enabled || button.disabled) return;
			if (document.getElementById('vm-favorite-search-overlay')) return;
			const limitError=getFavoriteSearchLimitError(locations);
			if (limitError) {showWatchToast(limitError,true); return;}
			const search=makeSearch();
			if (!search) return;
			button.disabled=true;
			try {
				const watchKey=favoriteSearchKey(search);
				const existing=getWatchedItems().find(item => item.watchKey === watchKey);
				if (existing?.favorite) {
					showWatchToast('Ten wpis jest już w Ulubionych.');
					return;
				}
				const {overlay,box}=createWatchDialogBase('vm-favorite-search-overlay','Zapisz wyszukiwanie jako profil');
				const controller=createDialogCloseController(overlay);
				const label=makeElement('label','vm-ui-stack','Nazwa profilu');
				const name=makeElement('input','vm-ui-field',null,{maxLength:120,value:`Wyszukiwanie · ${normalizeEscortAdText(listContext.heading?.textContent || document.title).slice(0,100)}`});
				label.appendChild(name);
				const info=makeElement('p','vm-ui-help','Zapamiętam lokalizacje i filtry, także niestandardowe. Po zapisaniu sprawdzę anonse w tle; ukryte pominę. Zmiana filtrów na stronie nie zmieni tego profilu.');
				const status=makeElement('p','vm-ui-help',`Lokalizacje: ${multi ? locations.length:1}`);
				status.setAttribute('role','status');
				const save=styleDialogActionButton(makeButton('','Zapisz profil'),true);
				const cancel=makeButton('vm-ui-button','Anuluj');
				controller.bindButton(cancel);
				const actions=makeElement('div','vm-ui-actions');
				actions.append(cancel,save);
				box.append(label,info,status,actions);
				save.addEventListener('click',() => {
					if (save.disabled || !overlay.isConnected) return;
					const displayName=normalizeEscortAdText(name.value);
					if (!displayName) {name.focus(); status.textContent='Wpisz nazwę profilu.'; return;}
					if (!getWatchSettings().enabled) return;
					save.disabled=true;
					const item=saveFavoriteItemFromContext({sourceType:'escort-search',resolutionMode:'search-escort',watchKey,search,displayName,resolvedAdUrls:existing?.resolvedAdUrls || []},{favorite:true},'person');
					controller.close('save');
					initLocalWatchSystem();
					showWatchToast('Profil zapisany. Anonse są sprawdzane w tle. Alerty włączysz w Ulubionych.');
					refreshFavoriteItemData(item,true).finally(() => refreshWatchPageButtons());
				});
			} finally {button.disabled=false;}
		});
		const breadcrumb=findEscortResultsBreadcrumb(listContext.heading);
		if (breadcrumb) {
			const container=breadcrumb.querySelector('ol,ul') || breadcrumb;
			const slot=container.matches('ol,ul') ? makeElement('li','vm-favorite-search-slot'):container;
			if (slot !== container) container.appendChild(slot);
			slot.appendChild(button);
		} else row.prepend(button);
	}
	async function initEscortClubListPage() {
		initHiddenResultCards();
		const shouldWaitForList= isEscortSearchPath() || location.pathname.startsWith('/anonse/');
		let listContext=shouldWaitForList
			? await waitForEscortClubListContext(10000)
			:getEscortClubListContext();
		applyEscortSectionVisibility();
		if (!listContext) {
			recordParserIssue( 'Escort.club - lista wyników', 'Nie znaleziono głównej sekcji listy anonsów.' );
			log('Nie znaleziono sekcji "Anonse erotyczne ..." na stronie wyszukiwania Escort.club');
			return;
		}
		hideEscortMainResultsTitle(listContext);
		markEscortResultCounter(document.querySelector('.vm-escort-results-count, .search-links > .sub-label'));
		updateEscortMultiResultsBreadcrumb(getEscortMultiLocationsForCurrentPage());
		const initialLocations=getEscortMultiLocationsForCurrentPage();
		const activeMultiLocations=getEscortActiveMultiLocations();
		const currentLocation=readEscortLocationFromControls();
		if (activeMultiLocations.length && currentLocation) {
			const currentTotal=getEscortListTotalResultCount(document,listContext);
			if (currentTotal != null) {
				setEscortMultiLocationResultCount(currentLocation,currentTotal);
			}
		}
		const initialMultiLocationCount=Math.max( 1, initialLocations.length );
		if (!listContext.anchors.length && initialMultiLocationCount <= 1 && !favoriteSearchEditSession &&
			getEscortListTotalResultCount(document,listContext) !== 0) {
			recordParserIssue( 'Escort.club - kafelki', 'Sekcja listy istnieje, ale nie znaleziono linków kafelków anonsów.' );
			log('Nie znaleziono kafelków anonsów Escort.club w analizowanej sekcji');
			return;
		}
		const listPagesControl=addEscortListPagesControl(listContext);
		try {addFavoriteSearchButton(listContext,listPagesControl?.row);}
		catch (error) {log('Nie udało się przygotować zapisu wyszukiwania',error);}
		let customFilterCallbacksReady=false;
		const customFilterController=addEscortCustomFiltersControl( listContext, config => {
				if (customFilterCallbacksReady) {
					ensureListDataNeeds();
					applyCustomFilters(config);
				}
			}, duration => {
				if (customFilterCallbacksReady) {
					renderAllEscortListPrices(duration);
					ensureListDataNeeds();
				}
			}, field => {
				if (customFilterCallbacksReady) {
					renderAllEscortListExtraData(field);
					ensureListDataNeeds();
				}
			}, () => {
				if (customFilterCallbacksReady) ensureListDataNeeds();
			} );
		const requestedPages=normalizeListPagesToShow(LIST_PAGES_TO_SHOW);
		const activeLocationCount=initialMultiLocationCount;
		const listRequestContext=createRequestContext('results');
		const needsAdditionalListLoading=requestedPages !== 1 || activeLocationCount > 1;
		const listPagesCancelToken=needsAdditionalListLoading
			? createOperationCancelToken('Ładowanie wielu stron/lokalizacji Escort.club')
			:null;
		if (listPagesControl && needsAdditionalListLoading) {
			if (listPagesControl.status) listPagesControl.status.textContent='';
			listPagesControl.setProgress?.( activeLocationCount > 1
					? `Ładowanie: 1/${requestedPages === 'all' ? '?':Number(requestedPages) * activeLocationCount} stron • ${activeLocationCount} ${polishLocationWord(activeLocationCount)}`
					:'Ładowanie stron...', () => listPagesCancelToken?.cancel('Przerwano ładowanie stron/lokalizacji') );
			if (listPagesControl.select) {
				listPagesControl.select.disabled=true;
			}
		}
		let loadResult={loadedPages:1,locationCount:initialMultiLocationCount,allPagesLoaded:false};
		const {adsNav}=listContext;
		const anchors=[];
		const initialPageSelected=initialLocations.length <= 1 || initialLocations.some(item =>
			getEscortLocationKey(item) === getEscortLocationKey(currentLocation));
		const aggregationMode=normalizeEscortAggregationMode( SETTINGS.escortAggregationMode );
		const phoneBasedAggregation= aggregationMode === 'exact-phone' || aggregationMode === 'phone-escort-search';
		const escortClubPhoneSearchAggregation= aggregationMode === 'phone-escort-search';
		const listSearchMode=getEscortListSearchMode();
		async function buildIndexInBatches(builder,keys) {
			let index=builder([]);
			for (let start=0; start < keys.length; start += 24) {
				index=builder(keys.slice(start,start + 24),index);
				if (start + 24 < keys.length) await yieldHelperWork();
			}
			return index;
		}
		await ensureEscortPageCache(listContext.anchors.map(anchor => parseAdIdFromUrl(anchor.href)).filter(Boolean));
		const cacheIndex=await buildIndexInBatches(buildEscortListCacheIndex,getEscortListCacheKeys());
		const indexedListKeys=new Set(getEscortListCacheKeys());
		const phoneCacheIndex=phoneBasedAggregation
			? await buildIndexInBatches(buildEscortAdPhoneCacheIndex,getEscortAdDataCacheKeys())
			:{phoneToAdIds:new Map(),adIdToPhone:new Map()};
		const phoneSearchAggregationIndex=new Map();
		const cardStates=new Map();
		const aggregationKeyToGroup=new Map();
		let nextGroupId=1;
		let listInitComplete=false;
		const dirtyGroups=new Set();
		const dirtyStates=new Set();
		let watchMetadataIndex=null;
		let listRefreshPending=false;
		let listRefreshFrame=null,listRefreshTimer=null,listRefreshRunning=false;
		let cacheChangeTimer=null,cacheChangeRunning=false,cacheChangePending=false;
		let listProgressTimer=null;
		function deferListView(state=null) {
			if (!document.hidden) return false;
			if (state) dirtyStates.add(state);
			listRefreshPending=true;
			return true;
		}
		function scheduleListProgress() {
			if (listInitComplete && !deferListView() && !listProgressTimer) listProgressTimer=setTimeout(updateListDataProgress,100);
		}
		function clearListViewTimers() {
			cancelAnimationFrame(listRefreshFrame); clearTimeout(listRefreshTimer); clearTimeout(listProgressTimer);
			listRefreshFrame=listRefreshTimer=listProgressTimer=null;
		}
		function flushListView() {
			clearListViewTimers();
			if (!listRefreshPending || deferListView() || cacheChangePending || cacheChangeRunning) return;
			listRefreshRunning=true;
			try {
				for (const state of dirtyStates) {
					renderState(state);
					renderEscortListPrice(state.priceLine,state.adData,SETTINGS.searchResultPriceDuration,state.aggregateMembers);
					applyListMerge(state);
				}
				dirtyStates.clear();
				applyCustomFilters();
				listRefreshPending=false;
			} finally {listRefreshRunning=false;}
		}
		function scheduleListRefresh() {
			if (!listInitComplete) return;
			listRefreshPending=true;
			if (document.hidden || listRefreshRunning || listRefreshFrame !== null) return;
			listRefreshFrame=requestAnimationFrame(flushListView);
			listRefreshTimer=setTimeout(flushListView,100);
		}
		const listDataCancelToken=createOperationCancelToken( 'Masowe pobieranie danych na liście anonsów' );
		let massDataCancelled=false;
		customFilterController?.setCancelHandler(() => {
			if (massDataCancelled) return;
			massDataCancelled=true;
			listDataCancelToken.cancel('Przerwano masowe pobieranie danych');
			for (const state of cardStates.values()) {
				state.dataCancelled=true;
				state.checkDone=true;
				state.adDataRetryScheduled=false;
			}
			customFilterController.setStatus( 'Pobieranie przerwane. Odśwież stronę, aby wznowić.' );
			applyCustomFilters(customFilterController.getConfig());
			updatePageAggregateSummary();
		});
		const originalSummaryLine=adsNav
			? [...adsNav.querySelectorAll('p')].find(p => /(?:Wyświetlane|Lista\s+wyników?)/i.test(p.textContent || '') )
			:null;
		const originalSummaryText=(originalSummaryLine?.textContent || '') .replace(/\s+/g,' ') .trim();
		const totalAdsMatch=originalSummaryText.match(/spośród\s+(\d[\d\s.]*)/i);
		const totalAds=totalAdsMatch
			? Number(totalAdsMatch[1].replace(/[\s.]/g,''))
			:null;
		function polishPageWord(n) {
			return polishCountForm(n,'strona','strony','stron');
		}
		function repeatedAdsPhrase(n) {
			return polishCountForm( n, 'powtarzający się anons', 'powtarzające się anonse', 'powtarzających się anonsów' );
		}
		function updateLoadedSummaryLine() {
			if (!originalSummaryLine) return;
			const loadedCount=anchors.length;
			const requested=normalizeListPagesToShow(LIST_PAGES_TO_SHOW);
			const allPagesLoaded=requested === 'all' && loadResult.allPagesLoaded;
			const pagesText=allPagesLoaded
				? 'wszystkie strony'
				:`${loadResult.loadedPages} ${polishPageWord(loadResult.loadedPages)}`;
			const multiLocations=getEscortMultiLocationsForCurrentPage();
			const multiLocation=multiLocations.length > 1 || Number(loadResult.locationCount) > 1;
			if (multiLocation) {
				const totalsByKey=loadResult.locationTotalAds || {};
				const expectedLocations=Math.max( 1, Number(loadResult.locationCount) || multiLocations.length || 1 );
				let totals=Object.values(totalsByKey) .map(value => Number(value))
					.filter(value => Number.isFinite(value) && value >= 0);
				if (totals.length !== expectedLocations) {
					const activeLocations=getEscortActiveMultiLocations();
					const fallbackTotals=activeLocations.map(locationData => {
						const value=Number(getEscortMultiLocationResultCount(locationData));
						return Number.isFinite(value) && value >= 0 ? value:null;
					});
					if ( fallbackTotals.length === expectedLocations && fallbackTotals.every(value => value != null) ) {
						totals=fallbackTotals;
					}
				}
				const allTotalsKnown=totals.length === expectedLocations;
				const summedTotal=totals.reduce((sum,value) => sum + value,0);
				updateEscortMultiResultsBreadcrumb( multiLocations.length > 1
						? multiLocations
						:getEscortMultiLocationsForCurrentPage(), allTotalsKnown ? summedTotal:null );
				setEscortMultiResultCountersVisible(false);
				return;
			}
			setEscortMultiResultCountersVisible(true);
			originalSummaryLine.innerHTML=Number.isFinite(totalAds) && totalAds > 0
				? `Załadowano <strong>${loadedCount}</strong> z <strong>${totalAds}</strong> ${totalAds === 1 ? 'anonsu':'anonsów'} • ${pagesText}.`
				:`Załadowano <strong>${loadedCount}</strong> ${polishAdWord(loadedCount)} • ${pagesText}.`;
			originalSummaryLine.title=
				'Licznik obejmuje kafelki rzeczywiście załadowane na tej liście przez Escort.club i skrypt.';
		}
		let aggregateSummaryLine=adsNav?.querySelector('.vm-escorti-page-aggregate-summary') || null;
		if (!aggregateSummaryLine && originalSummaryLine) {
			aggregateSummaryLine=makeElement('p');
			aggregateSummaryLine.className='vm-escorti-page-aggregate-summary';
			Object.assign(aggregateSummaryLine.style,{marginTop:'2px'});
			originalSummaryLine.insertAdjacentElement('afterend',aggregateSummaryLine);
		}
		customFilterController?.setListIncomplete(needsAdditionalListLoading);
		function getListDataNeeds() {
			const filters=customFilterController?.getConfig() || {};
			const sortField=customFilterController?.getSortConfig().field;
			const fields=[...getSearchResultExtraFields()];
			if (sortField?.startsWith('escorti:')) fields.push(sortField);
			const extraFields=fields.map(getEscortDataFieldDescriptor).filter(Boolean);
			const options={showPrice:isEscortSearchPriceDisplayed(),showEscorti:SETTINGS.showEscortiTileData,
				mergeAds:SETTINGS.mergeEscortAds,phoneBasedAggregation,escortClubPhoneSearchAggregation};
			return {...getEscortListDataRequirements(filters,sortField,extraFields,options),sortField,extraFields};
		}
		function updateListDataProgress(states=cardStates.values()) {
			clearTimeout(listProgressTimer);
			listProgressTimer=null;
			if (!listInitComplete || deferListView()) return;
			const progress=getEscortListDataProgress(states,getListDataNeeds(),phoneSearchAggregationIndex);
			const queue=getEscortRequestQueueStatus();
			customFilterController?.setProgress(formatEscortListDataProgress(progress,queue,massDataCancelled),
				progress.pending > 0 && !massDataCancelled);
		}
		function updatePageAggregateSummary() {
			if (!listInitComplete || deferListView()) return;
			const allStates=[...cardStates.values()];
			const states=allStates.filter(state => !state.hiddenInResults);
			const hiddenText=allStates.length !== states.length
				? ` • ukryte z Twojej listy: <strong>${allStates.length - states.length}</strong>`:'';
			const pendingCount=states.filter(state => !state.dataCancelled && ( !state.checkDone || ( SETTINGS.mergeEscortAds &&
					phoneBasedAggregation && ( !state.adData || state.adDataLoading || ( escortClubPhoneSearchAggregation &&
							isEscortClubPhoneSearchAggregationPending(state) ) ) )) ).length;
			updateListDataProgress(states);
			if (massDataCancelled) {
				customFilterController?.setStatus( 'Pobieranie przerwane. Odśwież stronę, aby wznowić.' );
			}
			if (!aggregateSummaryLine) return;
			if (!SETTINGS.mergeEscortAds) {
				aggregateSummaryLine.innerHTML= 'Agregacja wyłączona' + hiddenText +
					(pendingCount > 0 ? ' • trwa sprawdzanie…':'.');
				return;
			}
			const repeatedCount=states.filter(state => state.mergedHidden).length;
			const afterAggregation=states.length - repeatedCount;
			aggregateSummaryLine.innerHTML=
				`Po agregacji: <strong>${afterAggregation}</strong> ${polishAdWord(afterAggregation)} ` +
				`• ukryto <strong>${repeatedCount}</strong> ${repeatedAdsPhrase(repeatedCount)}` + hiddenText +
				(pendingCount > 0 ? ' • trwa sprawdzanie…':'.');
			aggregateSummaryLine.title=
				'Ukryte powtarzające się anonse to kafelki połączone przez agregację z innym kafelkiem na liście.';
		}
		function ensureMergedCardHighlightStyles() {
			if (document.getElementById('vm-escorti-merged-card-styles')) return;
			appendHelperStyle('vm-escorti-merged-card-styles',`
                .vm-escorti-merged-expanded-card {
                    position: relative !important;
                }
                .vm-escorti-merged-expanded-card::after {
                    content: '';
                    position: absolute;
                    inset: 0;
                    box-sizing: border-box;
                    border: 4px solid #00d8f0;
                    background: rgba(0, 216, 240, .10);
                    box-shadow:
                        inset 0 0 0 1px rgba(255, 255, 255, .20),
                        0 0 8px rgba(0, 216, 240, .35);
                    pointer-events: none;
                    z-index: 15;
                }
            `);
		}
		function clearMergedCardHighlight(state) {
			state.card.classList.remove('vm-escorti-merged-expanded-card');
		}
		function highlightMergedCard(state) {
			ensureMergedCardHighlightStyles();
			state.card.classList.add('vm-escorti-merged-expanded-card');
		}
		function restoreCard(state) {
			clearMergedCardHighlight(state);
			state.mergedHidden=false;
			state.card.style.display=state.customHidden ? 'none':state.originalDisplay;
		}
		function hideMergedCard(state) {
			clearMergedCardHighlight(state);
			state.card.style.display='none';
			state.mergedHidden=true;
		}
		function showMergedCard(state) {
			state.card.style.display=state.customHidden ? 'none':state.originalDisplay;
			if (state.customHidden) clearMergedCardHighlight(state);
			else highlightMergedCard(state);
			state.mergedHidden=true;
		}
		function renderState(state) {
			if (state.result) registerEscortDataFieldsFromEscortiResult(state.result);
			if (deferListView(state)) return;
			if (state.result) renderListEscortiResult(state.line,state.result,!!state.stale);
			renderEscortListExtraData(state.extraLine,state,getSearchResultExtraFields());
		}
		function getCanonical(group) {
			const members=[...group.members].filter(member => !member.hiddenInResults);
			return members.reduce( (best,item) => (!best || item.index < best.index ? item:best), null );
		}
		function getStateAggregationKeys(state) {
			if (phoneBasedAggregation) {
				const phone=getStatePhoneForAggregation(state);
				return phone ? [`phone:${phone}`]:[];
			}
			return getEscortProfileKeys(state?.result);
		}
		function getGroupAggregateAdIds(group) {
			const adIds=new Set( [...group.members] .filter(member => !member.hiddenInResults) .map(member => member.adId)
					.filter(Boolean) );
			for (const aggregationKey of group.aggregationKeys) {
				const cachedIds=phoneBasedAggregation
					? phoneCacheIndex.phoneToAdIds.get( aggregationKey.replace(/^phone:/,'') )
					:cacheIndex.freshProfileToAdIds.get(aggregationKey);
				for (const adId of cachedIds || []) {
					adIds.add(adId);
				}
				if (escortClubPhoneSearchAggregation) {
					const searchedIds=phoneSearchAggregationIndex.get( aggregationKey.replace(/^phone:/,'') )?.adIds;
					for (const adId of searchedIds || []) adIds.add(adId);
				}
			}
			return new Set([...adIds].filter(id => !getHiddenAdMatches(id,getHiddenResultProfileUrls(cacheIndex.byAdId.get(id))).length));
		}
		function refreshGroupDisplay(group) {
			if (!group) return;
			if (deferListView()) {dirtyGroups.add(group); return;}
			const previousMembers=new Set(group.members || []);
			if (group.canonical) previousMembers.add(group.canonical);
			for (const member of previousMembers) delete member.aggregateMembers;
			if (!group.members) group.members=new Set();
			for (const member of group.members) {
				if (!member.hiddenInResults) continue;
				clearMergedCardHighlight(member);
				member.mergedHidden=false;
				setListMergedInfo(member.line,0);
			}
			const members=[...group.members].filter(member => !member.hiddenInResults).sort((a,b) => a.index - b.index);
			const canonical=SETTINGS.mergeEscortAds ? getCanonical(group):null;
			group.canonical=canonical;
			if (!canonical) {
				for (const member of previousMembers) {
					if (!SETTINGS.mergeEscortAds) restoreCard(member);
					setListMergedInfo(member.line,0);
					renderEscortListPrice(member.priceLine,member.adData,SETTINGS.searchResultPriceDuration,member.aggregateMembers);
					renderEscortListExtraData(member.extraLine,member,getSearchResultExtraFields());
				}
				return;
			}
			for (const member of members) {
				member.group=group;
				if (member === canonical) {
					restoreCard(member);
					if (group.expanded && members.length > 1) {
						highlightMergedCard(member);
					}
				} else if (group.expanded) {
					showMergedCard(member);
				} else {
					hideMergedCard(member);
				}
			}
			if (group.expanded) {
				let insertAfter=canonical.card;
				for (const member of members) {
					if (member === canonical) continue;
					insertAfter.insertAdjacentElement('afterend',member.card);
					insertAfter=member.card;
				}
			}
			const aggregateAdIds=new Set([...getGroupAggregateAdIds(group)].map(adId => String(adId || '')).filter(Boolean));
			const aggregateCount=aggregateAdIds.size;
			const aggregateMembers=new Map();
			for (const member of members) {
				const adId=String(member.adId || '');
				if (adId && aggregateAdIds.has(adId) && !aggregateMembers.has(adId)) aggregateMembers.set(adId,member);
			}
			const cachedOnlyCount=Math.max(0,aggregateCount - aggregateMembers.size),cachedOnlyAdData=[];
			for (const adId of aggregateAdIds) {
				if (aggregateMembers.has(adId)) continue;
				const adData=getStoredEscortAdData(adId);
				aggregateMembers.set(adId,{adId,adUrl:`https://pl.escort.club/anons/${adId}.html`,adData,
					result:cacheIndex.byAdId.get(adId) || null,activeAdCount:aggregateCount});
				if (adData?.status === 'ok') cachedOnlyAdData.push(adData);
			}
			canonical.aggregateMembers=[...aggregateMembers.values()];
			const dataDifferences=getEscortListGroupDataDifferences( members, cachedOnlyAdData );
			setListMergedInfo( canonical.line, aggregateCount, !!group.expanded, members.length > 1
					? () => {
						group.expanded=!group.expanded;
						refreshGroupDisplay(group);
						updatePageAggregateSummary();
					}
					:null, cachedOnlyCount, dataDifferences );
			for (const member of previousMembers) {
				renderEscortListPrice(member.priceLine,member.adData,SETTINGS.searchResultPriceDuration,member.aggregateMembers);
				if (member === canonical) renderState(member);
				else {
					setListMergedInfo(member.line,0);
					renderEscortListExtraData(member.extraLine,member,getSearchResultExtraFields());
				}
			}
			if (aggregateCount > 1 && canonical.line) {
				const additionalSource=escortClubPhoneSearchAggregation
					? 'z wyszukiwarki Escort.club lub aktualnego lokalnego cache; bez tworzenia kafelków'
					:'z aktualnego lokalnego cache; bez tworzenia kafelków';
				canonical.line.title= `${canonical.line.title || 'Dane z Escorti'}; ` +
					`na tej stronie: ${members.length}, w liczniku agregacji: ${aggregateCount}` + (cachedOnlyCount > 0
						? ` (dodatkowo ${cachedOnlyCount} ${additionalSource})`
						:'');
			}
		}
		function applyListMerge(state) {
			if (!SETTINGS.mergeEscortAds || !state) return;
			if (deferListView(state)) return;
			if ( !phoneBasedAggregation && (!state.result || state.result.status !== 'ok' || state.result.profiles === 0) ) {
				return;
			}
			const aggregationKeys=getStateAggregationKeys(state);
			if (!aggregationKeys.length) return;
			const touchedGroups=[...new Set( aggregationKeys .map(key => aggregationKeyToGroup.get(key)) .filter(Boolean) )];
			let group;
			if (!touchedGroups.length) {
				group={
					id:nextGroupId++, members:new Set(), aggregationKeys:new Set(), expanded:false
				};
			} else {
				group=touchedGroups[0];
				for (const other of touchedGroups.slice(1)) {
					if (other === group) continue;
					dirtyGroups.delete(other);
					group.expanded=!!(group.expanded || other.expanded);
					for (const member of other.members) group.members.add(member);
					for (const key of other.aggregationKeys) group.aggregationKeys.add(key);
				}
			}
			group.members.add(state);
			for (const key of aggregationKeys) group.aggregationKeys.add(key);
			for (const key of group.aggregationKeys) aggregationKeyToGroup.set(key,group);
			for (const member of group.members) member.group=group;
			dirtyGroups.add(group);
			scheduleListRefresh();
		}
		function getCustomFilterBuckets() {
			const buckets=[];
			const seenGroups=new Set();
			for (const state of cardStates.values()) {
				if (state.hiddenInResults) continue;
				if (state.group) {
					if (seenGroups.has(state.group)) continue;
					seenGroups.add(state.group);
					buckets.push({group:state.group,members:[...state.group.members].filter(member => !member.hiddenInResults)});
				} else {
					buckets.push({group:null,members:[state]});
				}
			}
			return buckets;
		}
		function getBucketActiveAdCount(bucket) {
			return getEscortBucketActiveAdCount(bucket,{phoneBasedAggregation,escortClubPhoneSearchAggregation,phoneCacheIndex,cacheIndex,phoneSearchAggregationIndex,getGroupAggregateAdIds});
		}
		function getStatePhoneForAggregation(state) {
			return isEscortAdPhoneFresh(state?.adData) ? getEscortAdPhoneDigits(state.adData):'';
		}
		function isEscortClubPhoneSearchAggregationPending(state) {
			if (!escortClubPhoneSearchAggregation) return false;
			if (!state?.adData || state.adDataLoading) return true;
			const phone=getStatePhoneForAggregation(state);
			if (!phone) return false;
			const entry=phoneSearchAggregationIndex.get(phone);
			return !entry || !!entry.loading;
		}
		function ensureEscortClubPhoneSearchAggregation(state) {
			if ( !escortClubPhoneSearchAggregation || !state || massDataCancelled || listDataCancelToken.cancelled || !getListDataNeeds().phoneSearch ) return null;
			const phone=getStatePhoneForAggregation(state);
			if (!phone) {
				state.phoneSearchAggregationLoading=false;
				return null;
			}
			const existing=phoneSearchAggregationIndex.get(phone);
			if (existing) {
				state.phoneSearchAggregationLoading=!!existing.loading;
				return existing.promise;
			}
			const entry={
				adIds:new Set(), loading:true, error:null, promise:null
			};
			phoneSearchAggregationIndex.set(phone,entry);
			for (const member of cardStates.values()) {
				if (getStatePhoneForAggregation(member) === phone) {
					member.phoneSearchAggregationLoading=true;
				}
			}
			updatePageAggregateSummary();
			const requestContext=createRequestContext('results.phone-search',listRequestContext);
			entry.promise=(async () => {
				const search=await fetchEscortClubPhoneSearchAds( phone, listDataCancelToken, requestContext );
				const searchItems=search.adUrls .map(url => ({
						url, adId:parseAdIdFromUrl(url)
					})) .filter(item => item.adId);
				for (const item of searchItems) entry.adIds.add(item.adId);
				await watchMapLimit(searchItems,4,async item => {
					try {
						listDataCancelToken.throwIfCancelled();
						const result=await getEscortAdData( item.adId, item.url, false, listDataCancelToken, requestContext, {needPhone:getListDataNeeds().phone} );
						if (result?.status === 'ok') {
							updateEscortAdPhoneCacheIndex( phoneCacheIndex, item.adId, result );
						} else entry.incomplete=true;
						return result;
					} catch (error) {
						if (isOperationCancelledError(error)) throw error;
						entry.incomplete=true;
						log( `Nie udało się pobrać danych anonsu ${item.adId} podczas agregowania przez wyszukiwarkę Escort.club`, error
						);
						return {status:'error'};
					}
				});
			})() .catch(error => {
					if (isOperationCancelledError(error) || listDataCancelToken.cancelled) {
						entry.error='cancelled';
						return;
					}
					entry.error=formatHelperDiagnosticError(error);
					log( `Błąd agregowania przez wyszukiwarkę Escort.club dla numeru ${phone}`, error );
				}) .finally(() => {
					entry.loading=false;
					for (const member of cardStates.values()) {
						if (getStatePhoneForAggregation(member) !== phone) continue;
						member.phoneSearchAggregationLoading=false;
						if (member.group) dirtyGroups.add(member.group);
					}
					scheduleListRefresh();
				});
			return entry.promise;
		}
		function ensureEscortAdDataForState(state,forceRefresh=false) {
			if (state?.hiddenInResults) return;
			if (!state || massDataCancelled || listDataCancelToken.cancelled) {
				if (state) state.dataCancelled=true;
				return;
			}
			if (state.adDataLoading) return;
			const needPhone=getListDataNeeds().phone;
			const previousAdData=!forceRefresh && needPhone && state.adDataPrefetchStarted && state.adData?.status === 'ok' ? state.adData:null;
			if (forceRefresh) {
				if (state.agencyPhoneRefreshStarted || state.agencyPhoneRefreshDone) return;
				state.agencyPhoneRefreshStarted=true;
			} else {
				if (state.adDataPrefetchStarted && !(needPhone && !state.adDataPhoneRequested && !isEscortAdPhoneFresh(state.adData))) return;
				state.adDataPrefetchStarted=true;
			}
			if (needPhone) state.adDataPhoneRequested=true;
			state.adDataAttempts=(state.adDataAttempts || 0) + 1;
			const requestContext=createRequestContext('results.ad-data',listRequestContext);
			state.adDataLoading=true;
			getEscortAdData( state.adId, state.adUrl, forceRefresh, listDataCancelToken, requestContext, {needPhone} ) .then(result => {
					if (result?.status === 'cancelled') {
						state.dataCancelled=true;
						state.adDataCancelled=true;
						return;
					}
					state.adData=result?.status === 'ok'
						? result
						:previousAdData || {status:'error'};
				}) .catch(error => {
					if (isOperationCancelledError(error)) {
						state.dataCancelled=true;
						state.adDataCancelled=true;
						return;
					}
					state.adData=previousAdData || {status:'error'};
				}) .finally(() => {
					state.adDataLoading=false;
					state.adDataFinished=true;
					if (forceRefresh) {state.agencyPhoneRefreshStarted=false; state.agencyPhoneRefreshDone=true;}
					updateEscortAdPhoneCacheIndex( phoneCacheIndex, state.adId, state.adData );
					if (state.group) dirtyGroups.add(state.group);
					if (phoneBasedAggregation) {
						applyListMerge(state);
						if (escortClubPhoneSearchAggregation) {
							ensureEscortClubPhoneSearchAggregation(state);
						}
					}
					if (!deferListView(state)) {
						renderEscortListPrice( state.priceLine, state.adData, SETTINGS.searchResultPriceDuration, state.aggregateMembers );
						renderEscortListExtraData( state.extraLine, state, getSearchResultExtraFields() );
					}
					if ( !forceRefresh && !massDataCancelled && !listDataCancelToken.cancelled && state.adData?.status !== 'ok' &&
						state.adDataAttempts < 2 ) {
						state.adDataRetryScheduled=true;
						setTimeout(() => {
							state.adDataRetryScheduled=false;
							state.adDataPrefetchStarted=false;
							ensureEscortAdDataForState(state);
						},1200);
					}
					if (state.adData?.status === 'ok' && getListDataNeeds().phone && !state.adDataPhoneRequested) ensureEscortAdDataForState(state);
					if (!forceRefresh && state.adData?.status === 'ok' && getListDataNeeds().agencyPhone &&
						typeof state.adData.isAgencyOrSalonPhone !== 'boolean') ensureEscortAdDataForState(state,true);
					scheduleListRefresh();
				});
		}
		function renderAllEscortListPrices() {
			if (!SETTINGS.showPricesInSearchResults) return;
			const baseTop=SETTINGS.showRelatedAdsTileLink ? 22:4;
			const extraTop=baseTop + (isEscortSearchPriceDisplayed() ? 19:0);
			for (const state of cardStates.values()) {
				renderEscortListPrice( state.priceLine, state.adData, SETTINGS.searchResultPriceDuration, state.aggregateMembers );
				if (state.priceLine) state.priceLine.style.top=`${baseTop}px`;
				if (state.extraLine) state.extraLine.style.top=`${extraTop}px`;
			}
			const groups=new Set( [...cardStates.values()].map(state => state.group).filter(Boolean) );
			for (const group of groups) refreshGroupDisplay(group);
		}
		function renderAllEscortListExtraData(fieldValues=getSearchResultExtraFields()) {
			const fields=normalizeSearchResultExtraFields(fieldValues);
			for (const state of cardStates.values()) {
				if (!fields.length) {
					detachHelperNode(state.extraLine);
					state.extraLine=null;
					continue;
				}
				if (!state.extraLine) state.extraLine=getListCardExtraDataLine(state.card);
				renderEscortListExtraData(state.extraLine,state,fields);
			}
		}
		function applyCustomFilters(config=customFilterController?.getConfig()) {
			if (deferListView()) return;
			watchMetadataIndex ||= buildCustomFilterWatchMetadataIndex();
			const favoritesEnabled=getWatchSettings().enabled;
			for (const state of cardStates.values()) {
				const hidden=getHiddenAdMatches(state.adId,getHiddenResultProfileUrls(state.result)).length > 0;
				state.card.classList.toggle('vm-hidden-result',hidden);
				const favorite=favoritesEnabled && getBucketWatchMetadata({members:[state]},watchMetadataIndex).favorite;
				if (state.favorite !== favorite) {
					state.favorite=favorite;
					renderListFavoriteStar(state.card,favorite);
				}
				if (state.hiddenInResults !== hidden) {
					state.hiddenInResults=hidden;
					if (state.group) dirtyGroups.add(state.group);
					else restoreCard(state);
				}
			}
			if (getSearchResultExtraFields().includes('profile:activeCount')) {
				updateEscortListActiveCounts(getCustomFilterBuckets(),getBucketActiveAdCount);
			}
			const filters=config || getEscortCustomFilters();
			const activeFilterCount=getActiveEscortCustomFilterCount(filters);
			if (!activeFilterCount) {
				for (const state of cardStates.values()) {
					if (!state.customHidden) continue;
					state.customHidden=false;
					if (state.group) dirtyGroups.add(state.group);
					else restoreCard(state);
				}
				for (const group of dirtyGroups) refreshGroupDisplay(group);
				dirtyGroups.clear();
				customFilterController?.setStatus(massDataCancelled
					? 'Pobieranie przerwane. Odśwież stronę, aby wznowić.':'');
				applyEscortResultSort(cardStates.values(),customFilterController?.getSortConfig(),getGroupAggregateAdIds);
				updatePageAggregateSummary();
				return;
			}
			const needsEscortAdData=getCustomFilterDataNeeds(filters).escortAd;
			let hiddenCount=0;
			let pendingCount=0;
			let failedDataCount=0;
			const needsVisitData=filters.hideVisited || filters.hideUnvisited || filters.hideVisitedOlderThan ||
				filters.hideVisitedRecentThan || filters.showOnlyVisitedOlderThan || filters.showOnlyVisitedRecentThan;
			for (const bucket of getCustomFilterBuckets()) {
				const metrics= getBucketCustomFilterMetrics( bucket, filters, watchMetadataIndex, getBucketActiveAdCount(bucket),
					needsVisitData && bucket.group ? getGroupAggregateAdIds(bucket.group):undefined );
				const {shouldHide,unresolved}=evaluateEscortCustomFilters(metrics,filters);
				const visibilityChanged=bucket.members.some(member => member.customHidden !== shouldHide);
				for (const member of bucket.members) member.customHidden=shouldHide;
				if (bucket.group && (visibilityChanged || dirtyGroups.has(bucket.group))) {
					refreshGroupDisplay(bucket.group);
				} else if (!bucket.group && visibilityChanged && bucket.members[0]) {
					restoreCard(bucket.members[0]);
				}
				if (shouldHide) {
					hiddenCount++;
				} else if (unresolved) {
					const failed=needsEscortAdData && bucket.members.some(member => member.adData?.status === 'error' &&
						!member.adDataLoading && !member.adDataRetryScheduled && (member.adDataAttempts || 0) >= 2 );
					if (failed) failedDataCount++;
					else pendingCount++;
				}
			}
			dirtyGroups.clear();
			if (customFilterController) {
				if (massDataCancelled) {
					customFilterController.setStatus( 'Pobieranie przerwane. Odśwież stronę, aby wznowić.' );
				} else if (!activeFilterCount) {
					customFilterController.setStatus('');
				} else {
					customFilterController.setStatus( `Ukryto: ${hiddenCount}` +
						(pendingCount ? ` • oczekuje na dane: ${pendingCount}`:'') +
						(failedDataCount ? ` • brak danych: ${failedDataCount}`:'') );
				}
			}
			applyEscortResultSort(cardStates.values(),customFilterController?.getSortConfig(),getGroupAggregateAdIds);
			updatePageAggregateSummary();
		}
		function scheduleListNetworkCheck(state) {
			if ( !state || state.hiddenInResults || state.checkDone || state.checkScheduled || massDataCancelled ||
				listDataCancelToken.cancelled ) {
				if (state && (massDataCancelled || listDataCancelToken.cancelled)) {
					state.dataCancelled=true;
					state.checkDone=true;
				}
				return;
			}
			state.checkScheduled=true;
			const requestContext=createRequestContext('results.escorti',listRequestContext);
			if (!state.result && !deferListView(state)) state.line.textContent='… ogł • od … • … opinii';
			enqueueListJob(async () => {
				try {
					listDataCancelToken.throwIfCancelled();
					const newlyResolvedCache=cacheIndex.byAdId.get(state.adId);
					if (isListCacheFresh(newlyResolvedCache)) {
						state.result=newlyResolvedCache;
						state.stale=false;
						renderState(state);
						applyListMerge(state);
						return;
					}
					let searchValue=state.adUrl;
					if (listSearchMode === 'phone-escorti') {
						state.escortiPhoneLoading=true;
						state.adDataPhoneRequested=true;
						const adDataResult=await getEscortAdData( state.adId, state.adUrl, false, listDataCancelToken,
							createRequestContext('results.escorti-phone',requestContext), {needPhone:true} );
						state.escortiPhoneLoading=false;
						listDataCancelToken.throwIfCancelled();
						state.adData=adDataResult?.status === 'ok'
							? adDataResult
							:{status:'error'};
						updateEscortAdPhoneCacheIndex( phoneCacheIndex, state.adId, state.adData );
						if (!deferListView(state)) renderEscortListPrice( state.priceLine, state.adData, SETTINGS.searchResultPriceDuration, state.aggregateMembers );
						searchValue=isEscortAdPhoneFresh(state.adData) ? getEscortAdPhoneDigits(state.adData):'';
						if (!searchValue) {
							state.result={status:'phone-missing'};
							state.stale=false;
							renderState(state);
							return;
						}
					}
					const result=await getOrStartListCheck( state.adId, searchValue, listSearchMode, listDataCancelToken, requestContext );
					listDataCancelToken.throwIfCancelled();
					state.result=result;
					state.stale=false;
					updateEscortListCacheIndex(cacheIndex,state.adId,result);
					renderState(state);
					applyListMerge(state);
				} catch (e) {
					if (isOperationCancelledError(e) || listDataCancelToken.cancelled) {
						state.dataCancelled=true;
						state.result=state.result || {status:'cancelled'};
						return;
					}
					log(`Błąd Escorti dla anonsu ${state.adId}`,e);
					state.result={status:'error'};
					state.stale=false;
					renderState(state);
				} finally {
					state.escortiPhoneLoading=false;
					state.checkDone=true;
					if (state.group) dirtyGroups.add(state.group);
					scheduleListRefresh();
				}
			});
		}
		async function initializeListCards(newAnchors) {
			await ensureEscortPageCache(newAnchors.map(anchor => parseAdIdFromUrl(anchor.href)).filter(Boolean));
			await buildIndexInBatches((keys,index) => buildEscortListCacheIndex(keys,index || cacheIndex),getEscortListCacheKeys().filter(key => !indexedListKeys.has(key)));
			for (const key of getEscortListCacheKeys()) indexedListKeys.add(key);
			const newStates=[];
			let batchStarted=performance.now(),batchCount=0;
			for (const anchor of newAnchors) {
			const adUrl=new URL(anchor.getAttribute('href'),location.href).href;
			const adId=parseAdIdFromUrl(adUrl);
			const card=anchor.closest('.item-col.col');
			if (!adId || !card || cardStates.has(adId)) continue;
			const index=anchors.length;
			anchors.push(anchor);
			if (SETTINGS.showRelatedAdsTileLink) {
				addRelatedAdsTileLinkButton(card,adId,adUrl);
			} else {
				detachHelperNode(card.querySelector('.vm-related-ads-open-button'));
			}
			const line=getListCardDataLine(card);
			if (line) {
				line.style.display=SETTINGS.showEscortiTileData
					? 'block'
					:'none';
			}
			const priceLine=SETTINGS.showPricesInSearchResults
				? getListCardPriceLine(card)
				:null;
			const extraLine=getSearchResultExtraFields().length
				? getListCardExtraDataLine(card)
				:null;
			const state={
				adId, adUrl, index, anchor, card, line, priceLine, extraLine, originalDisplay:card.style.display, result:null,
				stale:false, group:null, mergedHidden:false, customHidden:false,
				hiddenInResults:getHiddenAdMatches(adId,getHiddenResultProfileUrls(cacheIndex.byAdId.get(adId))).length > 0,
				adData:getStoredEscortAdData(adId), adDataPrefetchStarted:false, adDataPhoneRequested:false, adDataAttempts:0, adDataRetryScheduled:false,
				adDataLoading:false, phoneSearchAggregationLoading:false, checkDone:false, checkScheduled:false,
				dataCancelled:false
			};
			cardStates.set(adId,state);
			newStates.push(state);
			card.dataset.vmHiddenManaged='1';
			card.classList.toggle('vm-hidden-result',state.hiddenInResults);
			if (!deferListView(state)) {
				renderEscortListPrice( state.priceLine, state.adData, SETTINGS.searchResultPriceDuration, state.aggregateMembers );
				renderEscortListExtraData( state.extraLine, state, getSearchResultExtraFields() );
			}
			const cache=cacheIndex.byAdId.get(adId) || null;
			if (cache) {
				state.result=cache;
				state.stale=!isListCacheFresh(cache);
				renderState(state);
				if (!state.stale) {
					applyListMerge(state);
					state.checkDone=true;
				}
			}
			batchCount++;
			if (performance.now() - batchStarted >= 8 || batchCount >= 24) {
				batchCount=0;
				await yieldHelperWork();
				batchStarted=performance.now();
			}
			}
			return newStates;
		}
		await initializeListCards(initialPageSelected ? listContext.anchors:[]);
		customFilterCallbacksReady=true;
		listInitComplete=true;
		const unsubscribeQueueProgress=subscribeEscortRequestQueueStatus(scheduleListProgress);
		window.addEventListener('pagehide',event => {
			listRefreshPending=true;
			if (cacheChangeTimer) cacheChangePending=true;
			clearListViewTimers();
			clearTimeout(cacheChangeTimer); cacheChangeTimer=null;
			if (!event.persisted) {
				unsubscribeQueueProgress();
				document.removeEventListener('visibilitychange',resumeListView);
				window.removeEventListener('pageshow',resumeListView);
			}
		});
		const refreshListAfterCacheChange=async () => {
			clearTimeout(cacheChangeTimer);
			cacheChangeTimer=null;
			if (document.hidden || cacheChangeRunning) {cacheChangePending=true; return;}
			cacheChangePending=false;
			cacheChangeRunning=true;
			try {
				await ensureEscortPageCache([...cardStates.keys()]);
				if (document.hidden) {cacheChangePending=true; return;}
				const rebuilt=await buildIndexInBatches(buildEscortListCacheIndex,getEscortListCacheKeys());
				if (document.hidden) {cacheChangePending=true; return;}
				Object.assign(cacheIndex,rebuilt);
				const expandedKeys=new Set([...aggregationKeyToGroup].filter(([,group]) => group.expanded).map(([key]) => key));
				aggregationKeyToGroup.clear(); dirtyGroups.clear();
				const fields=getSearchResultExtraFields();
				// Przebudowa grup musi być synchroniczna: odświeżenie w połowie pokazywało częściowe liczniki.
				for (const state of cardStates.values()) {
					state.adData=getStoredEscortAdData(state.adId);
					state.result=cacheIndex.byAdId.get(state.adId) || null;
					state.stale=!isListCacheFresh(state.result);
					state.group=null; delete state.aggregateMembers;
					if (state.result) renderState(state);
					else if (state.line) state.line.textContent='';
					if (phoneBasedAggregation) updateEscortAdPhoneCacheIndex(phoneCacheIndex,state.adId,state.adData);
				}
				for (const state of cardStates.values()) {
					applyListMerge(state);
					if (state.group && [...state.group.aggregationKeys].some(key => expandedKeys.has(key))) state.group.expanded=true;
					if (state.group) continue;
					restoreCard(state); setListMergedInfo(state.line,0);
					renderEscortListPrice(state.priceLine,state.adData,SETTINGS.searchResultPriceDuration);
					renderEscortListExtraData(state.extraLine,state,fields);
				}
				applyCustomFilters();
				dirtyStates.clear();
				listRefreshPending=false;
				clearListViewTimers();
			} catch (error) {log('Nie udało się odświeżyć listy po zmianie cache',error);}
			finally {
				cacheChangeRunning=false;
				if (!document.hidden) {
					if (cacheChangePending) cacheChangeTimer=setTimeout(refreshListAfterCacheChange,100);
					else if (listRefreshPending) scheduleListRefresh();
				}
			}
		};
		document.addEventListener('vm-helper-cache-change',event => {
			if (!(event.detail?.keys || []).some(key => /^(?:vm_escort_ad_data_|vm_escorti_list_)\d+$/.test(key))) return;
			if (document.hidden || cacheChangeRunning) {cacheChangePending=true; return;}
			if (!cacheChangeTimer) cacheChangeTimer=setTimeout(refreshListAfterCacheChange,100);
		});
		function resumeListView() {
			if (document.hidden) {listRefreshPending=true; clearListViewTimers(); return;}
			if (cacheChangePending) void refreshListAfterCacheChange();
			else if (listRefreshPending) scheduleListRefresh();
		}
		document.addEventListener('visibilitychange',resumeListView);
		window.addEventListener('pageshow',resumeListView);
		hiddenUiRefreshers.add(() => {
			watchMetadataIndex=null;
			applyCustomFilters();
			ensureListDataNeeds();
		});
		applyCustomFilters(customFilterController?.getConfig());
		if (typeof requestIdleCallback === 'function') requestIdleCallback(() => ensureListDataNeeds(),{timeout:250});
		else setTimeout(ensureListDataNeeds,32);
		loadResult=await loadAdditionalEscortListPages( listContext, listPagesCancelToken, progressInfo => {
				if (!listPagesControl || !needsAdditionalListLoading) return;
				const totalText=progressInfo.totalPages
					? `${progressInfo.loadedPages}/${progressInfo.totalPages}`
					:`${progressInfo.loadedPages}`;
				const perLocationText=progressInfo.pagesPerLocation === 'all'
					? `${progressInfo.locationPages} str.`
					:`${progressInfo.locationPages}/${progressInfo.pagesPerLocation}`;
				listPagesControl.setProgress?.( `Ładowanie: ${totalText} stron • ${progressInfo.locationLabel} ${perLocationText}`,
					() => listPagesCancelToken?.cancel('Przerwano ładowanie stron/lokalizacji') );
			}, (_location,doc) => updateFavoriteSearchEditLocation(doc), async cards => {
				const newStates=await initializeListCards(cards.flatMap(card => [...card.querySelectorAll(':scope > a:is([href*="/anons/"],[href*="/personal-ads/"])')]));
				applyCustomFilters();
				ensureListDataNeeds(newStates);
			} );
		listPagesControl?.setProgress?.('');
		customFilterController?.setListIncomplete(!loadResult.allPagesLoaded);
		if (listPagesControl?.status) {
			if (loadResult.cancelled) {
				listPagesControl.status.textContent=
					`przerwano po ${loadResult.loadedPages} ${polishPageWord(loadResult.loadedPages)}`;
			} else {
				listPagesControl.status.textContent='';
			}
			if (listPagesControl.select) {
				listPagesControl.select.disabled=false;
			}
		}
		listContext=getEscortClubListContext();
		hideEscortMainResultsTitle(listContext);
		const breadcrumbMultiLocations= getEscortMultiLocationsForCurrentPage();
		const breadcrumbTotals=Object.values( loadResult.locationTotalAds || {} ) .map(value => Number(value))
			.filter(value => Number.isFinite(value) && value >= 0 );
		const breadcrumbExpectedLocations=Math.max( 1, Number(loadResult.locationCount) || breadcrumbMultiLocations.length ||
				1 );
		const breadcrumbTotal= breadcrumbMultiLocations.length > 1 && breadcrumbTotals.length === breadcrumbExpectedLocations
				? breadcrumbTotals.reduce( (sum,value) => sum + value, 0 )
				:null;
		updateEscortMultiResultsBreadcrumb( breadcrumbMultiLocations, breadcrumbTotal );
		const multiSelectionActive=activeMultiLocations.length > 0;
		const currentPageTotal=getEscortListTotalResultCount( document, listContext );
		const hasMultiResults=breadcrumbTotal != null
			? breadcrumbTotal > 0
			:activeMultiLocations.length === 1 && currentPageTotal != null
				? currentPageTotal > 0
				:!!listContext?.anchors?.length;
		if (multiSelectionActive) {
			setEscortMultiNoResultsMessageVisible(!hasMultiResults);
		}
		if (!listContext?.anchors?.length) {
			if ((breadcrumbTotal ?? currentPageTotal) !== 0) {
				recordParserIssue( 'Escort.club - lista po doładowaniu', 'Po doładowaniu stron nie znaleziono kafelków anonsów.' );
				log('Po doładowaniu nie znaleziono kafelków anonsów Escort.club');
			}
			return;
		}
		updateLoadedSummaryLine();
		applyCustomFilters();
		function ensureListDataNeeds(states=cardStates.values()) {
			const {escortAd:needsAd,escorti:needsEscorti,agencyPhone}=getListDataNeeds();
			for (const state of states) {
				const hidden=getHiddenAdMatches(state.adId,getHiddenResultProfileUrls(state.result)).length > 0;
				if (state.hiddenInResults !== hidden) {
					state.hiddenInResults=hidden;
					if (state.group) dirtyGroups.add(state.group);
				}
				if (state.hiddenInResults) continue;
				if (needsAd) ensureEscortAdDataForState(state,!!(state.adDataPrefetchStarted && agencyPhone && state.adData?.status === 'ok' &&
					typeof state.adData.isAgencyOrSalonPhone !== 'boolean'));
				if (needsEscorti) {
					if (state.checkSkipped) {state.checkSkipped=false; state.checkDone=false;}
					scheduleListNetworkCheck(state);
				} else if (!state.checkScheduled && !state.checkDone) {
					state.checkSkipped=true;
					state.checkDone=true;
				}
			}
			scheduleListRefresh();
		}
	}
	const IMAGE_TOOLS_ID='vm-gallery-image-tools';
	function getEscortListDataRequirements(filters,sortField,extraFields,{showPrice,showEscorti,mergeAds,phoneBasedAggregation,escortClubPhoneSearchAggregation}) {
		const filterNeeds=getCustomFilterDataNeeds(filters);
		const phoneAggregation=phoneBasedAggregation && (mergeAds || extraFields.some(field => field.id === 'profile:activeCount'));
		return {
			escorti:!!(showEscorti || filterNeeds.escorti || (mergeAds && !phoneBasedAggregation) ||
				extraFields.some(field => field.source === 'escorti' || field.source === 'profile')),
			escortAd:!!(filterNeeds.escortAd || showPrice || ['price','published'].includes(sortField) || phoneAggregation ||
				extraFields.some(field => !['escorti','profile','garso'].includes(field.source) || field.path?.[0] === 'cities')),
			agencyPhone:filterNeeds.agencyPhone,
			phone:!!(phoneAggregation || extraFields.some(field => field.source === 'ad' && field.path?.[0] === 'phone')),
			phoneSearch:!!(phoneAggregation && escortClubPhoneSearchAggregation)
		};
	}
	function getEscortListDataProgress(states,needs,phoneSearchIndex=new Map()) {
		const progress={total:0,completed:0,pending:0,failed:0,cancelled:0,escortPending:0};
		for (const state of states) {
			if (state.hiddenInResults) continue;
			progress.total++;
			const adBusy=!!(state.adDataLoading || state.adDataRetryScheduled || state.agencyPhoneRefreshStarted);
			const escortiBusy=!!(state.checkScheduled && !state.checkDone);
			const requiresAd=needs.escortAd || adBusy;
			const requiresEscorti=needs.escorti || escortiBusy;
			const entry=phoneSearchIndex.get(getEscortAdPhoneDigits(state.adData));
			const adPending=requiresAd && (adBusy || (!state.adDataPrefetchStarted && !state.adDataFinished) ||
				(needs.agencyPhone && state.adData?.status === 'ok' && typeof state.adData.isAgencyOrSalonPhone !== 'boolean' && !state.agencyPhoneRefreshDone) ||
				(needs.phone && state.adData?.status === 'ok' && !isEscortAdPhoneFresh(state.adData) && !state.adDataPhoneRequested));
			const escortiPending=requiresEscorti && (escortiBusy || (!state.checkDone && !(state.result?.status === 'ok' && !state.stale)));
			const phonePending=!!state.phoneSearchAggregationLoading || !!(needs.phoneSearch && entry?.loading);
			const adMissing=requiresAd && (state.adDataCancelled || state.adData?.status !== 'ok' ||
				(needs.agencyPhone && typeof state.adData.isAgencyOrSalonPhone !== 'boolean') ||
				(needs.phone && !isEscortAdPhoneFresh(state.adData)));
			const missing=adMissing || (requiresEscorti && (state.result?.status !== 'ok' || (state.dataCancelled && state.stale))) ||
				(needs.phoneSearch && (!entry || entry.error || entry.incomplete));
			if (!adPending && !escortiPending && !phonePending && !missing) progress.completed++;
			else if (state.dataCancelled) progress.cancelled++;
			else if (adPending || escortiPending || phonePending) {
				progress.pending++;
				if (adPending || phonePending || state.escortiPhoneLoading) progress.escortPending++;
			} else progress.failed++;
		}
		if (!needs.escortAd && !needs.escorti && !progress.pending) progress.total=progress.completed=progress.failed=progress.cancelled=0;
		return progress;
	}
	function formatEscortListDataProgress(progress,queue={},cancelled=false) {
		if (!progress.total || progress.completed === progress.total) return '';
		const pause=progress.escortPending > 0 && (queue.reloadRequired || queue.blockedUntil > Date.now());
		const label=cancelled ? 'Uzupełnianie przerwane':pause ? 'Uzupełnianie danych wstrzymane':
			progress.pending ? 'Uzupełnianie danych':'Uzupełnianie zakończone z brakami';
		return `${label}: ${progress.completed}/${progress.total} kafelków` +
			(progress.pending ? ` • oczekuje: ${progress.pending}`:'') +
			(progress.failed ? ` • brak danych: ${progress.failed}`:'') +
			(progress.cancelled ? ` • przerwano: ${progress.cancelled}`:'') +
			(pause ? queue.reloadRequired ? ' • HTTP 403 — odśwież stronę':` • pauza Escort.club do ${new Date(queue.blockedUntil).toLocaleTimeString('pl-PL')}`:'');
	}
	const IMAGE_SERVER_DATE_ID='vm-gallery-image-server-date';
	const LIGHTBOX_IMAGE_URL_ID='vm-lightbox-image-url';
	const mediaServerDateCache=new Map();
	function isEscortGalleryPhotoUrl(value) {
		if (!value) return false;
		try {
			const url=new URL(value,location.href);
			return /(^|\.)escort\.club$/i.test(url.hostname)
				&& /\/galleries\//i.test(url.pathname)
				&& /\.(?:jpe?g|png|webp|avif)(?:$|[?#])/i.test(url.href);
		} catch {return false;}
	}
	function isEscortGalleryVideoUrl(value) {
		if (!value) return false;
		try {
			const url=new URL(value,location.href);
			return /(^|\.)escort\.club$/i.test(url.hostname)
				&& /\/videos\//i.test(url.pathname)
				&& /\.(?:mp4|webm|m4v|mov)(?:$|[?#])/i.test(url.href);
		} catch {return false;}
	}
	function buildConfiguredImageSearchUrl(urlPrefix,imageUrl) {
		return `${urlPrefix}${encodeURIComponent(imageUrl)}`;
	}
	function getEnabledImageSearchEngines() {
		return normalizeImageSearchEngines(SETTINGS.imageSearchEngines) .filter(engine => engine.enabled !== false);
	}
	function getActiveGallerySlide() {
		const slider=document.querySelector('.content-gallery-col .galleryContainer #lightSlider');
		if (!slider) return null;
		const active=slider.querySelector(':scope > li.lslide.active, :scope > li.active');
		if (active) return active;
		const wrapper=slider.closest('.lSSlideWrapper');
		if (!wrapper) return null;
		const wr=wrapper.getBoundingClientRect();
		let best=null,bestArea=0;
		for (const li of slider.querySelectorAll(':scope > li')) {
			const r=li.getBoundingClientRect();
			const w=Math.max(0,Math.min(r.right,wr.right) - Math.max(r.left,wr.left));
			const h=Math.max(0,Math.min(r.bottom,wr.bottom) - Math.max(r.top,wr.top));
			if (w * h > bestArea) {bestArea=w * h; best=li;}
		}
		return best;
	}
	function getActiveGalleryMedia() {
		const slide=getActiveGallerySlide();
		if (!slide) return null;
		const anchor=slide.querySelector('a.simple-zoom-image');
		if (!anchor) return null;
		const isVideo=anchor.classList.contains('video-layer');
		const images=[...anchor.querySelectorAll('img')];
		const img=isVideo
			? images.find(candidate => /\/thumbs\//i.test(candidate.currentSrc || candidate.src || ''))
				|| images.at(-1)
			:images[0];
		if (!img) return null;
		let mediaUrl=anchor.href || '';
		if (isVideo) {
			if (!isEscortGalleryVideoUrl(mediaUrl)) return null;
		} else {
			if (!isEscortGalleryPhotoUrl(mediaUrl)) mediaUrl=img.currentSrc || img.src || '';
			if (!isEscortGalleryPhotoUrl(mediaUrl)) return null;
		}
		const r=img.getBoundingClientRect();
		if (r.width < 100 || r.height < 100) return null;
		return {
			slide, img, mediaUrl, mediaType:isVideo ? 'video':'photo'
		};
	}
	function getActiveGalleryPhoto(activeMedia=getActiveGalleryMedia()) {
		if (!activeMedia || activeMedia.mediaType !== 'photo') return null;
		return {
			...activeMedia, imageUrl:activeMedia.mediaUrl
		};
	}
	function createImageToolButton(engine) {
		const btn=makeButton('',`${engine.name} ↗`,{title:`Otwórz ${engine.name} z adresem aktualnego zdjęcia`});
		Object.assign(btn.style,{
			display:'block', width:'58px', maxWidth:'58px', padding:'3px',
			border:'1px solid var(--vm-ui-panel-border, rgba(255,255,255,.75))', borderRadius:'4px',
			background:'var(--vm-ui-panel-bg, rgba(20,20,20,.82))', color:'var(--vm-ui-panel-text, #fff)', fontSize:'9px',
			lineHeight:'1.15', fontWeight:'700', fontFamily:'var(--vm-ui-font-family, Arial, sans-serif)', textAlign:'center',
			cursor:'pointer', boxShadow:'var(--vm-ui-shadow, 0 1px 5px rgba(0,0,0,.45))', overflow:'hidden',
			textOverflow:'ellipsis', whiteSpace:'nowrap',
			transition:'border-color .12s ease, background .12s ease, color .12s ease'
		});
		btn.addEventListener('mouseenter',() => {
			btn.style.borderColor= 'var(--vm-ui-accent, #f54da3)';
			btn.style.background= 'var(--vm-ui-panel-bg-alt, var(--vm-ui-panel-bg, rgba(20,20,20,.82)))';
		});
		btn.addEventListener('mouseleave',() => {
			btn.style.borderColor= 'var(--vm-ui-panel-border, rgba(255,255,255,.75))';
			btn.style.background= 'var(--vm-ui-panel-bg, rgba(20,20,20,.82))';
		});
		const open=event => {
			event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
			const imageUrl=btn.closest(`#${IMAGE_TOOLS_ID}`)?.dataset.imageUrl;
			if (imageUrl && engine.urlPrefix) GM_openInTab(buildConfiguredImageSearchUrl(engine.urlPrefix,imageUrl),{active:true,insert:true});
		};
		btn.addEventListener('mousedown',e => {e.preventDefault(); e.stopPropagation();},true);
		btn.addEventListener('click',open,true);
		return btn;
	}
	function getOrCreateImageTools() {
		let tools=document.getElementById(IMAGE_TOOLS_ID);
		if (tools) return tools;
		tools=prepareHelperStyleApi( makeElement('div') );
		tools.id=IMAGE_TOOLS_ID;
		Object.assign(tools.style,{
			position:'absolute',display:'none',zIndex:'1000',flexDirection:'column',
			alignItems:'stretch',gap:'3px',pointerEvents:'auto'
		});
		for (const engine of getEnabledImageSearchEngines()) tools.appendChild(createImageToolButton(engine));
		document.body.appendChild(tools);
		return tools;
	}
	function parseResponseHeader(headers,name) {
		const safeName=String(name || '').replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
		const match=String(headers || '').match(new RegExp(`^${safeName}:\\s*(.+)$`,'im'));
		return match ? match[1].trim():null;
	}
	function formatServerMediaDate(date) {
		return new Intl.DateTimeFormat('pl-PL',{
			day:'2-digit', month:'2-digit', year:'numeric'
		}).format(date);
	}
	function formatMinimumMediaAge(date) {
		const ms=Math.max(0,Date.now() - date.getTime());
		const days=Math.floor(ms / 86400000);
		if (days < 1) return '<1 dnia';
		if (days < 31) return `${days} dni`;
		const months=Math.floor(days / 30.4375);
		if (months < 12) return `${months} mies.`;
		const years=Math.floor(months / 12);
		const restMonths=months % 12;
		return restMonths ? `${years} r. ${restMonths} mies.`:`${years} r.`;
	}
	function getMediaServerDate(mediaUrl) {
		if (mediaServerDateCache.has(mediaUrl)) return mediaServerDateCache.get(mediaUrl);
		const promise=gmRequest({
			method:'HEAD', url:mediaUrl, timeout:12000
		}).then(response => {
			const value=parseResponseHeader(response.responseHeaders,'Last-Modified');
			if (!value) return null;
			const timestamp=Date.parse(value);
			return Number.isFinite(timestamp) ? new Date(timestamp):null;
		}).catch(error => {
			log('Nie udało się odczytać daty pliku galerii z serwera',error);
			return null;
		});
		mediaServerDateCache.set(mediaUrl,promise);
		return promise;
	}
	function getOrCreateImageServerDateLabel() {
		let label=document.getElementById(IMAGE_SERVER_DATE_ID);
		if (label) return label;
		label=prepareHelperStyleApi( makeElement('div') );
		label.id=IMAGE_SERVER_DATE_ID;
		Object.assign(label.style,{
			position:'absolute', display:'none', zIndex:'999', padding:'3px 6px',
			border:'1px solid var(--vm-ui-panel-border, rgba(255,255,255,.35))', borderRadius:'4px',
			background:'var(--vm-ui-panel-bg, rgba(20,20,20,.72))', color:'var(--vm-ui-panel-text, #fff)', fontSize:'9px',
			lineHeight:'1.15', fontWeight:'600', fontFamily:'var(--vm-ui-font-family, Arial, sans-serif)', whiteSpace:'nowrap',
			pointerEvents:'auto', cursor:'help', boxShadow:'var(--vm-ui-shadow, 0 1px 4px rgba(0,0,0,.3))'
		});
		document.body.appendChild(label);
		return label;
	}
	async function updateMediaServerDateLabel(active) {
		const label=getOrCreateImageServerDateLabel();
		if (!active) {
			label.style.display='none';
			label.dataset.mediaUrl='';
			label.dataset.loaded='';
			return;
		}
		const {slide,img,mediaUrl,mediaType}=active;
		if (label.parentElement !== slide) slide.appendChild(label);
		if (getComputedStyle(slide).position === 'static') slide.style.position='relative';
		const sameLoadedMedia=label.dataset.mediaUrl === mediaUrl && label.dataset.loaded === '1';
		label.style.display='block';
		const positionLabel=() => {
			const sr=slide.getBoundingClientRect();
			const ir=img.getBoundingClientRect();
			label.style.left=`${Math.round(Math.max(4,ir.left - sr.left + 8))}px`;
			label.style.top=`${Math.round(Math.max(4,ir.top - sr.top + 8))}px`;
		};
		if (sameLoadedMedia) {
			positionLabel();
			return;
		}
		label.dataset.mediaUrl=mediaUrl;
		label.dataset.loaded='0';
		label.textContent='data: …';
		label.title=`Odczytuję nagłówek Last-Modified z serwera ${mediaType === 'video' ? 'filmu':'obrazu'}.`;
		positionLabel();
		const serverDate=await getMediaServerDate(mediaUrl);
		if (label.dataset.mediaUrl !== mediaUrl) return;
		label.dataset.loaded='1';
		if (!serverDate) {
			label.textContent='data serwera: brak';
			label.title=`Serwer nie zwrócił nagłówka Last-Modified dla tego ${mediaType === 'video' ? 'filmu':'zdjęcia'}.`;
			positionLabel();
			return;
		}
		label.textContent=`${formatServerMediaDate(serverDate)} (${formatMinimumMediaAge(serverDate)})`;
		label.title= `Data pochodzi z nagłówka HTTP Last-Modified serwera ${mediaType === 'video' ? 'filmu':'obrazu'}.\n` +
			`Wiek w nawiasie jest wiekiem minimalnym: materiał mógł powstać wcześniej niż wskazuje data pliku na serwerze.\n` +
			`Last-Modified nie jest datą wykonania materiału.`;
		positionLabel();
	}
	function updateGalleryImageTools() {
		const tools=getOrCreateImageTools();
		const activeMedia=getActiveGalleryMedia();
		const active=getActiveGalleryPhoto(activeMedia);
		updateMediaServerDateLabel(activeMedia);
		if (!active || !SETTINGS.showImageSearchButtons || !getEnabledImageSearchEngines().length) {
			tools.style.display='none';
			tools.dataset.imageUrl='';
			return;
		}
		const {slide,img,imageUrl}=active;
		if (tools.parentElement !== slide) slide.appendChild(tools);
		if (getComputedStyle(slide).position === 'static') slide.style.position='relative';
		tools.dataset.imageUrl=imageUrl;
		tools.style.display='flex';
		const sr=slide.getBoundingClientRect();
		const ir=img.getBoundingClientRect();
		const tr=tools.getBoundingClientRect();
		tools.style.left=`${Math.round(Math.max(4,ir.right - sr.left - tr.width - 4))}px`;
		tools.style.top=`${Math.round(Math.max(4,ir.top - sr.top + 8))}px`;
	}
	function updateLightboxImageUrl() {
		const lightbox=document.querySelector('#lightbox');
		const existing=document.getElementById(LIGHTBOX_IMAGE_URL_ID);
		if (!lightbox) return;
		if (getComputedStyle(lightbox).display === 'none') {
			if (existing) existing.style.display='none';
			return;
		}
		const img=lightbox.querySelector('.lb-image');
		const dataContainer=lightbox.querySelector('.lb-dataContainer');
		if (!img || !dataContainer) return;
		const imageUrl=img.currentSrc || img.src || '';
		if (!/^https:\/\/static\.escort\.club\//i.test(imageUrl)) {
			if (existing) existing.style.display='none';
			return;
		}
		let box=existing;
		if (!box) {
			box=prepareHelperStyleApi( makeElement('div') );
			box.id=LIGHTBOX_IMAGE_URL_ID;
			Object.assign(box.style,{
				display:'block',clear:'both',width:'100%',boxSizing:'border-box',padding:'7px 4px 5px',
				fontFamily:'Arial,sans-serif',fontSize:'11px',lineHeight:'1.25',textAlign:'left',wordBreak:'break-all'
			});
			const link=makeElement('a','',null,{target:'_blank',rel:'noopener noreferrer'});
			Object.assign(link.style,{color:'var(--vm-ui-accent, #f54da3)',textDecoration:'underline',cursor:'pointer'});
			link.addEventListener('click',event => {
				const href=link.href;
				if (!href) return;
				stopUiEvent(event);
				GM_openInTab(href,{active:true,insert:true});
			});
			box.appendChild(link);
			dataContainer.appendChild(box);
		} else if (box.parentElement !== dataContainer) {
			dataContainer.appendChild(box);
		}
		const link=box.querySelector('a');
		box.style.display='block';
		link.href=imageUrl;
		link.textContent=imageUrl;
		link.title='Otwórz samo zdjęcie w nowej karcie';
	}
	function initEscortGalleryWatcher() {
		if (!getEscortAdIdFromPath(location.pathname)) return;
		if (!document.getElementById('vm-gallery-navigation-contrast')) {
			appendHelperStyle('vm-gallery-navigation-contrast',`
.content-gallery-col .lSAction > a:not(.disabled), #lightbox .lSAction > a:not(.disabled),
#lightbox .lb-nav a.lb-prev:not(.disabled), #lightbox .lb-nav a.lb-next:not(.disabled) {
  filter:drop-shadow(0 0 1px #000) drop-shadow(0 0 3px #000) !important; opacity:1 !important;
}`);
		}
		const gallery=document.querySelector('.content-gallery-col');
		const initialPhoneContainer=getEscortPhoneLink()?.closest('.adsPhone');
		if (!gallery && !initialPhoneContainer) return;
		let observedLightbox=null;
		let observedPhoneContainer=null;
		let observedTargetsBound=false;
		const scheduleGalleryUpdate=createAnimationFrameScheduler(updateGalleryImageTools);
		const scheduleLightboxUpdate=createAnimationFrameScheduler(updateLightboxImageUrl);
		const isOwnScriptElement=target => {
			const element=target?.nodeType === Node.ELEMENT_NODE
				? target
				:target?.parentElement;
			return !!element?.closest( `#${IMAGE_TOOLS_ID}, #${IMAGE_SERVER_DATE_ID}, ` +
				`#${LIGHTBOX_IMAGE_URL_ID}, .vm-escort-copy-phone` );
		};
		const isOnlyOwnScriptMutation=mutation => {
			if (isOwnScriptElement(mutation.target)) return true;
			if (mutation.type !== 'childList') return false;
			const changedNodes=[ ...mutation.addedNodes, ...mutation.removedNodes ];
			return changedNodes.length > 0 && changedNodes.every(isOwnScriptElement);
		};
		const observer=new MutationObserver(mutations => {
			let galleryChanged=false;
			let lightboxChanged=false;
			let phoneChanged=false;
			for (const mutation of mutations) {
				if (isOnlyOwnScriptMutation(mutation)) continue;
				if ( observedPhoneContainer && ( mutation.target === observedPhoneContainer ||
						observedPhoneContainer.contains(mutation.target) ) ) {
					phoneChanged=true;
				} else if ( observedLightbox && (mutation.target === observedLightbox || observedLightbox.contains(mutation.target))
				) {
					lightboxChanged=true;
				} else {
					galleryChanged=true;
				}
			}
			if (galleryChanged) scheduleGalleryUpdate();
			if (lightboxChanged) scheduleLightboxUpdate();
			if (phoneChanged) checkAndAddButtons();
		});
		const refreshObservedTargets=() => {
			const lightbox=document.querySelector('#lightbox');
			const phoneContainer=getEscortPhoneLink()?.closest('.adsPhone') || null;
			if ( observedTargetsBound && lightbox === observedLightbox && phoneContainer === observedPhoneContainer ) return;
			observer.disconnect();
			if (gallery) {
				observer.observe(gallery,{
					subtree:true, childList:true, attributes:true, attributeFilter:['class','src','srcset','href']
				});
			}
			observedLightbox=lightbox;
			if (observedLightbox) {
				observer.observe(observedLightbox,{
					subtree:true, childList:true, attributes:true, attributeFilter:['class','src','style']
				});
				scheduleLightboxUpdate();
			}
			observedPhoneContainer=phoneContainer;
			if (observedPhoneContainer) {
				observer.observe(observedPhoneContainer,{
					subtree:true, childList:true, characterData:true, attributes:true,
					attributeFilter:['href','data-phone-id','data-show-phone']
				});
			}
			observedTargetsBound=true;
		};
		if (gallery) {
			gallery.addEventListener('load',scheduleGalleryUpdate,true);
			gallery.addEventListener('transitionend',scheduleGalleryUpdate,true);
			gallery.addEventListener('click',event => {
				scheduleGalleryUpdate();
				const galleryAnchor=event.target.closest?.('a.simple-zoom-image');
				if (galleryAnchor) {
					requestAnimationFrame(refreshObservedTargets);
				}
			},true);
		}
		window.addEventListener('resize',scheduleGalleryUpdate,{passive:true});
		refreshObservedTargets();
		if (gallery) scheduleGalleryUpdate();
	}
	function getAdIdFromUrl() {
		return getEscortAdIdFromPath();
	}
	function buildGarsoAdLinkSearchTerm(adId) {
		const normalizedAdId=String(adId || '').match(/^\d+$/)?.[0];
		if (!normalizedAdId) return '';
		return `"escort.club / anons / ${normalizedAdId}.html"`;
	}
	function normalizeGarsoSearchTerm(term) {
		const raw=String(term || '').trim();
		const candidate=raw.replace(/^["“”„]+|["“”„]+$/g,'');
		if (!/^(?:https?:\/\/)?(?:(?:[a-z]{2}|www)\.)?escort\.club\/(?:anons|personal-ads)\/\d+\.html(?:[?#]|\/?$)/i.test(candidate)) return raw;
		const url=normalizeEscortiAdUrl(/^https?:\/\//i.test(candidate) ? candidate:`https://${candidate}`);
		return url ? buildGarsoAdLinkSearchTerm(parseAdIdFromUrl(url)):raw;
	}
	function buildGarsoPhoneMaskSearchTerm(searchTerm) {
		const digits=normalizeEscortCachedPhone(searchTerm);
		return digits?.length === 9 ? `"${digits.slice(0,3)}-${digits.slice(3,6)}-xxx"`:'';
	}
	function getGarsoSearchTerms(term,searchMode='') {
		if (searchMode !== 'phone') return [term];
		const phone=formatEscortPhoneForClipboard(term,'local-hyphens');
		return phone ? [`"${phone}"`,buildGarsoPhoneMaskSearchTerm(phone)].filter(Boolean):[];
	}
	function getGarsoSearchQuery(term,searchMode='') {
		const terms=(Array.isArray(term) ? term:getGarsoSearchTerms(term,searchMode)).filter(Boolean);
		if (searchMode === 'phone') terms.reverse();
		return {term:terms.join(' '),andorType:terms.length > 1 ? 'or':'and'};
	}
	function queryFirstEscortElement(selectors,root=document) {
		for (const selector of selectors) {
			const element=root.querySelector(selector);
			if (element) return element;
		}
		return null;
	}
	function getEscortPhoneLink() {
		return queryFirstEscortElement([ '.content-info-col.-info .adsPhone a[href^="tel:"]',
			'.content-info-col.-info .adsPhone a[data-show-phone][data-phone-id]', '.content-info-col.-info a[href^="tel:"]',
			'.content-info-col.-info a[data-show-phone][data-phone-id]', '.content-info-col.-info .adsPhone a[href]',
			'.adsPhone a[href^="tel:"]', '.content-info .adsPhone a[data-show-phone][data-phone-id]',
			'.adsPhone a[data-show-phone][data-phone-id]', '[data-show-phone][data-phone-id]', '.adsPhone a[href]' ]);
	}
	function getEscortButtonsContainer(phoneLink=getEscortPhoneLink()) {
		const phoneContact=phoneLink?.closest('.content-contact');
		if (phoneContact?.closest('.content-info-col.-info')) return phoneContact;
		const desktopInfo=document.querySelector('.content-info-col.-info');
		if (desktopInfo) {
			const desktopPhone=queryFirstEscortElement([ '.adsPhone a[data-show-phone][data-phone-id]',
				'.adsPhone a[href^="tel:"]', '.adsPhone a[href]' ],desktopInfo);
			return desktopPhone?.closest('.content-contact')
				|| desktopInfo.querySelector('.content-info')
				|| desktopInfo;
		}
		if (phoneContact) return phoneContact;
		return queryFirstEscortElement([ '.content-info .content-contact', '.content-info-col .content-info',
			'.content-info-col' ]);
	}
	function getEscortTipMessageButtonsContainer(scope=document) {
		return [...scope.querySelectorAll('.content-contact')].find(container =>
			container.querySelector('a[href*="action=tipTokens"]') && container.querySelector('a[href*="action=sendMessage"]')
		) || null;
	}
	function placeEscortResearchPanelAboveActionButtons( panel, fallbackContainer=null, scopeDocument=document,
		{retry=true}={} ) {
		if (!panel || !scopeDocument) return false;
		const getPlacement=() => {
			const infoColumn= scopeDocument.querySelector( '.content-info-col.-info' ) || fallbackContainer?.closest?.(
					'.content-info-col.-info' ) || scopeDocument.querySelector( '.content-info-col' );
			if (!infoColumn) return null;
			const actionButtons= getEscortTipMessageButtonsContainer( infoColumn );
			const moreAboutSection= infoColumn.querySelector( '.content-hours' );
			const reference= actionButtons || moreAboutSection;
			if (reference?.parentNode) {
				return {
					parent:reference.parentNode, reference
				};
			}
			return {
				parent: infoColumn.querySelector( '.content-info' ) || infoColumn, reference:null
			};
		};
		const place=() => {
			const placement=getPlacement();
			if (!placement?.parent) return false;
			if ( panel.parentNode !== placement.parent || panel.nextSibling !== placement.reference ) {
				placement.parent.insertBefore( panel, placement.reference );
			}
			return true;
		};
		if (place()) return true;
		if (!retry) return false;
		let attempts=0;
		const timer=setInterval(() => {
			attempts++;
			if ( place() || attempts >= 40 ) {
				clearInterval(timer);
			}
		},250);
		return false;
	}
	function getFormattedPhoneNumber(phoneEl=getEscortPhoneLink()) {
		if (!phoneEl) return null;
		const href=String(phoneEl.getAttribute('href') || '').trim();
		const candidates=[ /^tel:/i.test(href) ? href.replace(/^tel:/i,''):'', phoneEl.getAttribute('data-phone'),
			phoneEl.textContent ];
		for (const value of candidates) {
			const digits=normalizeEscortCachedPhone(value);
			if (!digits) continue;
			return digits.length === 9
				? digits.replace(/(\d{3})(\d{3})(\d{3})/,'$1-$2-$3')
				:`+${digits}`;
		}
		return null;
	}
	function formatEscortPhoneForClipboard( value, format=SETTINGS.phoneClipboardFormat ) {
		const digits=normalizeEscortCachedPhone(value);
		if (!digits) return null;
		if (digits.length !== 9) return `+${digits}`;
		const groups=digits.match(/\d{3}/g);
		if (!groups) return digits;
		return normalizePhoneClipboardFormat(format) === 'international-spaces'
			? `+48 ${groups.join(' ')}`
			:groups.join('-');
	}
	function copyEscortPhoneToClipboard(value) {
		try {
			if (typeof GM_setClipboard === 'function') {
				GM_setClipboard(value,'text');
				return Promise.resolve();
			}
		} catch {}
		if (navigator.clipboard?.writeText) {
			return navigator.clipboard.writeText(value);
		}
		return new Promise((resolve,reject) => {
			const textarea=makeElement('textarea','',null,{value});
			textarea.setAttribute('readonly','');
			Object.assign(textarea.style,{
				position:'fixed', left:'-9999px', top:'0'
			});
			document.body.appendChild(textarea);
			textarea.select();
			try {
				if (!document.execCommand('copy')) throw new Error('copy failed');
				resolve();
			} catch (error) {
				reject(error);
			} finally {
				detachHelperNode(textarea);
			}
		});
	}
	function renderEscortPhoneCopyButton(phone) {
		if (SETTINGS.showPhoneCopyButton === false) {
			for (const button of document.querySelectorAll( '.vm-escort-copy-phone' )) {
				detachHelperNode(button);
			}
			return;
		}
		const digits=normalizeEscortCachedPhone(phone);
		if (!digits) return;
		const formatted=formatEscortPhoneForClipboard(digits);
		if (!formatted) return;
		for (const phoneBox of document.querySelectorAll('.adsPhone')) {
			const phoneLink=phoneBox.querySelector( '[data-show-phone], a[href^="tel:"]' );
			if (!phoneLink) continue;
			const phoneContact=phoneBox.closest('.content-contact') || phoneBox;
			for (const node of [...phoneContact.childNodes]) {
				if ( node !== phoneLink && node.nodeType === Node.TEXT_NODE && /^\s*Telefon\s*:\s*$/i.test(node.textContent || '')
				) {
					node.textContent='';
				}
			}
			for (const label of phoneContact.querySelectorAll( '.label, strong, b, label, span' )) {
				if ( label !== phoneLink && !label.contains(phoneLink) && /^\s*Telefon\s*:\s*$/i.test(label.textContent || '') ) {
					label.style.display='none';
				}
			}
			let copyButton=phoneBox.querySelector('.vm-escort-copy-phone');
			if (!copyButton) {
				copyButton=makeElement('button');
				copyButton.type='button';
				copyButton.className='vm-escort-copy-phone';
				copyButton.textContent='Kopiuj';
				Object.assign(copyButton.style,{
					display:'inline-flex', alignItems:'center', justifyContent:'center', marginLeft:'8px', padding:'4px 7px',
					border:`1px solid ${getEscortPagePinkColor()}`, borderRadius:'6px', background:'transparent',
					color:getEscortPagePinkColor(), fontSize:'10px', fontWeight:'600', lineHeight:'1', whiteSpace:'nowrap',
					verticalAlign:'middle', cursor:'pointer'
				});
				copyButton.addEventListener('click',event => {
					stopUiEvent(event);
					const value=copyButton.dataset.copyValue;
					if (!value) return;
					copyEscortPhoneToClipboard(value).then(() => {
						copyButton.textContent='Skopiowano';
						clearTimeout(copyButton._vmRestoreTimer);
						copyButton._vmRestoreTimer=setTimeout(() => {
							copyButton.textContent='Kopiuj';
						},1200);
					}).catch(() => {
						copyButton.textContent='błąd kopiowania';
					});
				});
			}
			copyButton.dataset.copyValue=formatted;
			copyButton.title=`Skopiuj ${formatted}`;
			if (phoneLink.nextElementSibling !== copyButton) {
				phoneLink.insertAdjacentElement('afterend',copyButton);
			}
		}
	}
	function normalizeGarsoCacheSearchTerm(searchMode,searchTerm) {
		if (searchMode === 'phone') {
			return normalizeEscortCachedPhone(searchTerm) || '';
		}
		if (searchMode === 'address') {
			const match=String(searchTerm || '').match(/(?:anons|personal-ads)\s*\/\s*(\d+)\.html/i);
			if (match) return match[1];
		}
		return normalizeText(searchTerm);
	}
	function garsoSearchCountCacheKey(searchMode,searchTerm) {
		if (searchMode !== 'phone' && searchMode !== 'address') return null;
		const normalizedTerm=normalizeGarsoCacheSearchTerm(searchMode,searchTerm);
		if (!normalizedTerm) return null;
		const versionSuffix=['phone','address'].includes(searchMode)
			? `_v${GARSO_PHONE_SEARCH_CACHE_VERSION}`
			:'';
		return 'vm_garso_search_count_' + `${searchMode}${versionSuffix}_${encodeURIComponent(normalizedTerm)}`;
	}
	function getGarsoSearchCountCache(searchMode,searchTerm) {
		const key=garsoSearchCountCacheKey(searchMode,searchTerm);
		if (!key) return null;
		try {
			const value=readPersistentCacheValue(key,null);
			const count=Number(value?.count);
			const checkedAt=Number(value?.checkedAt);
			const topicLinksCheckedAt=Number(value?.topicLinksCheckedAt);
			const topicLinks=Array.isArray(value?.topicLinks)
				? mergeUniqueGarsoTopics(value.topicLinks)
				:[];
			const phoneSearchVariant=['full','masked','both'].includes(value?.phoneSearchVariant)
				? value.phoneSearchVariant:null;
			const variantsKnown=searchMode !== 'phone' || !!phoneSearchVariant;
			const topicLinksFresh=variantsKnown && Number.isFinite(topicLinksCheckedAt) && topicLinksCheckedAt > 0 &&
				Date.now() - topicLinksCheckedAt < GARSO_TOPIC_LINKS_CACHE_TTL_MS;
			if (!Number.isFinite(count) || count < 0 || !Number.isFinite(checkedAt) || checkedAt <= 0) {
				return null;
			}
			return {
				count, checkedAt, phoneSearchVariant, topicLinks:topicLinksFresh ? topicLinks:[],
				topicLinksCheckedAt:topicLinksFresh ? topicLinksCheckedAt:null, topicLinksFresh,
				fresh:variantsKnown && Date.now() - checkedAt < getGarsoCacheTtlMs()
			};
		} catch (error) {
			log('Błąd odczytu wyniku Garsoniery z cache',error);
			return null;
		}
	}
	function setGarsoSearchCountCache( searchMode, searchTerm, count, {
			phoneSearchVariant=null, topicLinks, topicLinksCheckedAt=null
		}={} ) {
		const key=garsoSearchCountCacheKey(searchMode,searchTerm);
		const normalizedCount=Number(count);
		if (!key || !Number.isFinite(normalizedCount) || normalizedCount < 0) return null;
		try {
			const checkedAt=Date.now();
			const previous=readPersistentCacheValue(key,null);
			const storedTopicLinks=Array.isArray(topicLinks)
				? mergeUniqueGarsoTopics(topicLinks)
				:Array.isArray(previous?.topicLinks)
					? previous.topicLinks
					:null;
			const storedTopicLinksCheckedAt=Array.isArray(topicLinks)
				? Number(topicLinksCheckedAt) || checkedAt
				:Number(previous?.topicLinksCheckedAt) || null;
			const topicLinksData=Array.isArray(storedTopicLinks) && storedTopicLinksCheckedAt > 0
				? {
					topicLinks:storedTopicLinks, topicLinksCheckedAt:storedTopicLinksCheckedAt
				}
				:{};
			writePersistentCacheValue(key,{
				count:normalizedCount, checkedAt,
				...(searchMode === 'phone' && ['full','masked','both'].includes(phoneSearchVariant)
					? {phoneSearchVariant}:{}), ...topicLinksData
			});
			return checkedAt;
		} catch (error) {
			log('Błąd zapisu wyniku Garsoniery do cache',error);
			return null;
		}
	}
	function getGarsoDataFields() {
		return [['mean','średnia ogólna'],['median','mediana'],['last5Mean','średnia ostatnich 5 recenzji'],
			['reviewCount','liczba ocenionych recenzji'],...CATEGORY_DEFS.map(def => [def.key,def.label])];
	}
	function getGarsoDataValue(stats,descriptor) {
		const key=descriptor.path[0];
		return stats?.categories?.[key]?.mean ?? stats?.[key] ?? null;
	}
	function getCachedGarsoStatsForAds(urls,fallbackPhone='') {
		const candidates=new Map();
		const add=(mode,term) => {
			if (!term) return;
			const key=garsoSummaryCacheKey(mode,term);
			if (!candidates.has(key)) candidates.set(key,getGarsoSummaryCache(mode,term));
		};
		add('phone',fallbackPhone);
		for (const url of urls || []) {
			const id=parseAdIdFromUrl(url);
			if (!id) continue;
			const phone=getEscortAdPhoneDigits(getStoredEscortAdData(id) || getWatchedAdDataMirror(id)) || fallbackPhone;
			add('address',url);
			add('phone',phone);
			add('combined',buildCombinedGarsoSummaryTerm(phone,url));
		}
		return [...candidates.values()].filter(Boolean).sort((a,b) =>
			(Number(b.result.stats.reviewCount) || 0) - (Number(a.result.stats.reviewCount) || 0) || b.checkedAt - a.checkedAt
		)[0]?.result.stats || null;
	}
	function garsoSummaryCacheKey(searchMode,searchTerm) {
		if (!['phone','address','combined'].includes(searchMode)) return null;
		const normalizedTerm=normalizeGarsoCacheSearchTerm(searchMode,searchTerm);
		if (!normalizedTerm) return null;
		const versionSuffix=['phone','combined'].includes(searchMode)
			? `_v${GARSO_PHONE_SEARCH_CACHE_VERSION}`
			:'';
		return 'vm_garso_summary_' + `${searchMode}${versionSuffix}_${encodeURIComponent(normalizedTerm)}`;
	}
	function getGarsoTopicsSignature(topics) {
		return (topics || []) .filter(topic => topic?.url) .map(topic => normalizeTopicUrl(topic.url)) .filter(Boolean)
			.sort() .join('|');
	}
	function getGarsoTopicDeduplicationKey(topicUrl) {
		if (!topicUrl) return '';
		const normalizedUrl=normalizeTopicUrl(topicUrl);
		if (!normalizedUrl) return '';
		try {
			const url=new URL(normalizedUrl,GARSO_BASE_URL);
			const pathId=url.pathname.match(/\/topic\/(\d+)(?:[-/]|$)/i)?.[1];
			const queryId=url.searchParams.get('showtopic');
			const topicId=pathId || (/^\d+$/.test(queryId || '') ? queryId:'');
			if (topicId) return `topic:${topicId}`;
			return `url:${url.href.replace(/\/$/,'')}`;
		} catch {
			return `url:${normalizedUrl.replace(/\/$/,'')}`;
		}
	}
	function mergeUniqueGarsoTopics(...topicGroups) {
		const topicsByKey=new Map();
		for (const topic of topicGroups.flat()) {
			if (!topic?.url) continue;
			const url=normalizeTopicUrl(topic.url);
			if (!url) continue;
			const deduplicationKey=getGarsoTopicDeduplicationKey(url);
			if (!deduplicationKey) continue;
			const current=topicsByKey.get(deduplicationKey);
			const candidate={
				title:normalizeEscortAdText(topic?.title), subtitle:normalizeEscortAdText(topic?.subtitle), url
			};
			if (!current) {
				topicsByKey.set(deduplicationKey,candidate);
				continue;
			}
			if (candidate.title.length > current.title.length) {
				current.title=candidate.title;
			}
			if (candidate.subtitle.length > current.subtitle.length) {
				current.subtitle=candidate.subtitle;
			}
		}
		return [...topicsByKey.values()];
	}
	function buildCombinedGarsoSummaryTerm(phoneTerm,addressTerm) {
		const phoneKey=normalizeGarsoCacheSearchTerm('phone',phoneTerm);
		const addressKey=normalizeGarsoCacheSearchTerm('address',addressTerm);
		return `${phoneKey || 'bez-numeru'}|${addressKey || 'bez-anonsu'}`;
	}
	function compactGarsoSummaryResult(result) {
		const sourceStats=result?.stats && typeof result.stats === 'object'
			? result.stats
			:{};
		const {reviews:_unusedReviews,...stats}=sourceStats;
		const topics=(Array.isArray(result?.topics) ? result.topics:[]) .map(topic => ({
				title:normalizeEscortAdText(topic?.title), subtitle:normalizeEscortAdText(topic?.subtitle),
				url:topic?.url ? normalizeTopicUrl(topic.url):''
			})) .filter(topic => topic.url);
		const topicInfo=(Array.isArray(result?.topicInfo) ? result.topicInfo:[]) .map(info => ({
				title:normalizeEscortAdText(info?.title), subtitle:normalizeEscortAdText(info?.subtitle),
				url:info?.url ? normalizeTopicUrl(info.url):'', maxPage:Number.isFinite(Number(info?.maxPage))
					? Number(info.maxPage)
					:null, pagesRead:(Array.isArray(info?.pagesRead) ? info.pagesRead:[]) .map(Number)
					.filter(page => Number.isFinite(page) && page > 0), posts:Math.max(0,Number(info?.posts) || 0),
				error:info?.error ? String(info.error):null
			}));
		const excludedTopics=(Array.isArray(result?.excludedTopics)
			? result.excludedTopics
			:[]) .map(topic => ({
				title:normalizeEscortAdText(topic?.title), url:topic?.url ? normalizeTopicUrl(topic.url):''
			})) .filter(topic => topic.url);
		const screenedTopicCount=Number.isFinite(Number(result?.screenedTopicCount))
			? Math.max(0,Number(result.screenedTopicCount))
			:topicInfo.length;
		const excludedTopicCount=Math.max( excludedTopics.length, Number.isFinite(Number(result?.excludedTopicCount))
				? Math.max(0,Number(result.excludedTopicCount))
				:0 );
		const failedTopicCount=Number.isFinite(Number(result?.failedTopicCount))
			? Math.max(0,Number(result.failedTopicCount))
			:0;
		return {
			searchTerm:String(result?.searchTerm || ''), sourceCheckedAt:Number(result?.sourceCheckedAt) || null,
			sourceCheckAttemptAt:Number(result?.sourceCheckAttemptAt) || null,
			unverifiedTopicCount:Math.max(0,Number(result?.unverifiedTopicCount) || 0), topics, topicInfo, excludedTopics,
			screenedTopicCount, excludedTopicCount, failedTopicCount, postCount:Number.isFinite(Number(result?.postCount))
				? Math.max(0,Number(result.postCount))
				:Math.max(0,Number(result?.allPosts?.length) || 0), stats
		};
	}
	function getGarsoAnalysisPages(maxPage) {
		return Array(Math.min(maxPage,30)).fill().map((_,index) => maxPage <= 30 || index < 10 ? index + 1:maxPage - 29 + index);
	}
	function hasGarsoAnalysisPages({maxPage,pagesRead}={}) {
		if (!Number.isSafeInteger(maxPage) || maxPage < 1 || !Array.isArray(pagesRead)) return false;
		if (pagesRead.length === maxPage) return pagesRead.every((page,index) => page === index + 1);
		const selected=getGarsoAnalysisPages(maxPage);
		return pagesRead.length === selected.length && pagesRead.every((page,index) => page === selected[index]);
	}
	function isCompleteGarsoSummary(result,expectedTopics=[]) {
		const info=Array.isArray(result?.topicInfo) ? result.topicInfo:[];
		const expectedCount=mergeUniqueGarsoTopics(expectedTopics).length;
		const screenedCount=Number(result?.screenedTopicCount);
		const failedCount=Number(result?.failedTopicCount);
		return Number.isFinite(screenedCount) && screenedCount >= expectedCount &&
			(!Number.isFinite(failedCount) || failedCount === 0) &&
			info.every(topic => !topic?.error && hasGarsoAnalysisPages(topic));
	}
	function isGarsoTopicCountCheckRecent(timestamp) {
		const age=Date.now() - Number(timestamp);
		return Number(timestamp) > 0 && age >= 0 && age < GARSO_TOPIC_COUNT_CHECK_INTERVAL_MS;
	}
	function getGarsoSummaryCache(searchMode,searchTerm) {
		const key=garsoSummaryCacheKey(searchMode,searchTerm);
		if (!key) return null;
		try {
			const value=readPersistentCacheValue(key,null);
			if ( Number(value?.parserVersion) !== GARSO_SUMMARY_PARSER_VERSION ) {
				return null;
			}
			const checkedAt=Number(value?.checkedAt);
			if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
			if (!value?.summary?.stats || !Array.isArray(value.summary.topicInfo)) {
				return null;
			}
			const result=compactGarsoSummaryResult(value.summary);
			const recentCheck=isGarsoTopicCountCheckRecent(result.sourceCheckAttemptAt) &&
				value.topicCacheRevision === GM_getValue(GARSO_TOPIC_REVISION_KEY,'');
			return {
				checkedAt, recentCheck, fresh:recentCheck && !result.unverifiedTopicCount,
				topicSignature:String(value.topicSignature || getGarsoTopicsSignature(result.topics)), result
			};
		} catch (error) {
			log('Błąd odczytu analizy Garso z cache',error);
			return null;
		}
	}
	function setGarsoSummaryCache(searchMode,searchTerm,topics,result) {
		const key=garsoSummaryCacheKey(searchMode,searchTerm);
		if (!key || !result?.stats) return;
		try {
			const checkedAt=Number(result.sourceCheckedAt) || Date.now();
			const summary=compactGarsoSummaryResult(result);
			writePersistentCacheValue(key,{
				checkedAt, parserVersion:GARSO_SUMMARY_PARSER_VERSION,
				topicCacheRevision:GM_getValue(GARSO_TOPIC_REVISION_KEY,''),
				topicSignature:getGarsoTopicsSignature(topics || summary.topics), summary
			});
			return checkedAt;
		} catch (error) {
			log('Błąd zapisu analizy Garso do cache',error);
			return null;
		}
	}
	function deleteGarsoSummaryCache(searchMode,searchTerm) {
		const key=garsoSummaryCacheKey(searchMode,searchTerm);
		if (key) deletePersistentCacheValue(key);
	}
	function garsoExtendedCacheKey(adId) {
		const normalizedAdId=String(adId || '').match(/^\d+$/)?.[0];
		return normalizedAdId
			? `vm_garso_extended_${normalizedAdId}`
			:null;
	}
	function normalizeGarsoExtendedSearchMode(value) {
		return ['phone','address','combined'].includes(value)
			? value
			:'address';
	}
	function getGarsoExtendedCache(adId) {
		const key=garsoExtendedCacheKey(adId);
		if (!key) return null;
		try {
			const value=readPersistentCacheValue(key,null);
			if (!value || typeof value !== 'object' || Number(value.phoneScanVersion) !== GARSO_EXTENDED_PHONE_SCAN_VERSION) return null;
			const checkedAt=Number(value?.checkedAt);
			if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
			const searchMode=normalizeGarsoExtendedSearchMode( value?.searchMode );
			const adUrls=[...new Set( (Array.isArray(value?.adUrls) ? value.adUrls:[]) .map(url => normalizeEscortiAdUrl(url))
					.filter(Boolean) )];
			const results=(Array.isArray(value?.results) ? value.results:[])
				.filter(result => result?.searchTerm && Number(result?.count) > 0) .map(result => ({
					status:'ok', count:Number(result.count), searchTerm:String(result.searchTerm),andorType:result.andorType === 'or' ? 'or':'and',
					adUrl:normalizeEscortiAdUrl(result.adUrl) || null, signature:String(result.signature || '')
				}));
			return {
				checkedAt, fresh:Date.now() - checkedAt < getGarsoCacheTtlMs(), adUrls, results, searchMode,
				phoneTermsKey:String(value.phoneTermsKey || ''), phoneScanVersion:Number(value.phoneScanVersion) || 0
			};
		} catch (error) {
			log('Błąd odczytu rozszerzonego wyniku Garso z cache',error);
			return null;
		}
	}
	function setGarsoExtendedCache( adId, adUrls, results, searchMode='address', phoneTermsKey='' ) {
		const key=garsoExtendedCacheKey(adId);
		if (!key) return;
		const mode=normalizeGarsoExtendedSearchMode(searchMode);
		const normalizedAdUrls=[...new Set( (adUrls || []).map(url => normalizeEscortiAdUrl(url)).filter(Boolean) )];
		const normalizedResults=(results || []) .filter(result => result?.status === 'ok' && Number(result?.count) > 0)
			.map(result => ({
				count:Number(result.count), searchTerm:String(result.searchTerm || ''),andorType:result.andorType === 'or' ? 'or':'and',
				adUrl:normalizeEscortiAdUrl(result.adUrl) || null, signature:getGarsoExtendedResultSignature(result)
			})) .filter(result => result.searchTerm);
		try {
			const checkedAt=Date.now();
			writePersistentCacheValue(key,{
				checkedAt, adUrls:normalizedAdUrls, searchMode:mode, phoneTermsKey,
				phoneScanVersion:GARSO_EXTENDED_PHONE_SCAN_VERSION, results:normalizedResults
			});
		} catch (error) {
			log('Błąd zapisu rozszerzonego wyniku Garso do cache',error);
		}
	}
	function createGarsoHttpError(status) {
		const error=new Error(`Garso: HTTP ${status}`);
		error.status=Number(status) || 0;
		if ([401,403].includes(error.status)) garsoAuthPromise=null;
		return error;
	}
	function getGarsoAuth(cancelToken=null,immediate=false) {
		if (!immediate && garsoAuthPromise) {
			return garsoAuthPromise.then(auth => {
				cancelToken?.throwIfCancelled();
				return auth;
			});
		}
		const requestPromise=immediate
			? gmRequest({
				method:'GET', url:GARSO_BASE_URL, cancelToken
			})
			:garsoTopicRequest({
				method:'GET', url:GARSO_BASE_URL, cancelToken
			});
		const authPromise=requestPromise.then(response => {
			if (response.status && (response.status < 200 || response.status >= 400)) {
				throw createGarsoHttpError(response.status);
			}
			const auth=parseGarsoAuth(response.responseText || '');
			if (!auth) {
				recordParserIssue( 'Garso - dane sesji', 'Nie znaleziono secure_hash lub session_id.' );
				throw new Error('Brak danych sesji Garso');
			}
			return auth;
		});
		if (immediate) return authPromise;
		garsoAuthPromise=authPromise;
		garsoAuthPromise.catch(() => {if (garsoAuthPromise === authPromise) garsoAuthPromise=null;});
		return garsoAuthPromise;
	}
	function isGarsoAntifloodResponse(html) {
		const doc=new DOMParser().parseFromString(String(html || ''),'text/html');
		const text=normalizeText(doc.body?.innerText || html);
		return /anty\s*-?\s*flood|anti\s*-?\s*flood|flood control/.test(text)
			|| /(?:odczekaj|poczekaj|odczekać|zaczekaj).{0,80}(?:sekund|sek\.)/.test(text);
	}
	function normalizeGarsoResultPageUrl(value) {
		try {
			const url=new URL(value || '',GARSO_BASE_URL);
			if (!['garsoniera.com.pl','www.garsoniera.com.pl'].includes( url.hostname.toLowerCase() )) return null;
			return url.href;
		} catch {
			return null;
		}
	}
	function garsoResultFromCache(cached) {
		return {status:'ok',found:cached.count>0,count:cached.count,topics:cached.topicLinks,resultUrl:null,
			phoneSearchVariant:cached.phoneSearchVariant,topicLinksComplete:true,topicLinksCheckedAt:cached.topicLinksCheckedAt};
	}
	function cacheGarsoSearchResult(mode,term,result) {
		const job=garsoAutoSearchJobs.get(garsoSearchCountCacheKey(mode,term));
		if (job && job.result===result) return job.checkedAt;
		return result?.status==='ok' ? setGarsoSearchCountCache(mode,term,result.count ?? 0,{
			phoneSearchVariant:result.phoneSearchVariant,topicLinks:result.topicLinksComplete ? result.topics:undefined,
			topicLinksCheckedAt:result.topicLinksCheckedAt}):null;
	}
	async function checkGarsoTerm( term, collectTopics=true, cancelToken=null, searchMode='', forceRefresh=false,
		onProgress=null ) {
		term=normalizeGarsoSearchTerm(term);
		try {
			cancelToken?.throwIfCancelled();
			const key=garsoSearchCountCacheKey(searchMode,term),prepared=garsoAutoSearchJobs.get(key);
			if (prepared) {
				if (forceRefresh || (prepared.completedAt && Date.now()-prepared.completedAt>=GARSO_TOPIC_LINKS_CACHE_TTL_MS)) {
					garsoAutoSearchJobs.delete(key); prepared.cancelToken.cancel('Ponowne sprawdzenie');
				}
				else {
					const unsubscribe=cancelToken?.onCancel(reason=>prepared.cancelToken.cancel(reason));
					try {const result=await prepared.promise; cancelToken?.throwIfCancelled(); onProgress?.(result); return result;}
					finally {unsubscribe?.();}
				}
			}
			const cachedTopicList=collectTopics && !forceRefresh
				? getGarsoSearchCountCache(searchMode,term)
				:null;
			if (cachedTopicList?.topicLinksFresh) return garsoResultFromCache(cachedTopicList);
			const auth=await getGarsoAuth();
			cancelToken?.throwIfCancelled();
			const query=getGarsoSearchQuery(term,searchMode);
			if (!query.term) return {status:'error',count:null,topics:[],resultUrl:null};
			const targetUrl=`${GARSO_BASE_URL}index.php?app=core&module=search&do=search&fromMainBar=1&s=${encodeURIComponent(auth.sessionId)}`;
			const params=new URLSearchParams({search_term:query.term,andor_type:query.andorType,
				search_app:'forums',secure_hash:auth.secureHash,submit:'Szukaj'});
			const response=await garsoRequest({
				method:'POST',url:targetUrl,cancelToken,headers:{'Content-Type':'application/x-www-form-urlencoded'},data:params.toString()
			});
			if (response.status && (response.status < 200 || response.status >= 400)) throw createGarsoHttpError(response.status);
			const html=response.responseText || '';
			if (isGarsoAntifloodResponse(html)) return {status:'antiflood',count:null,topics:[],resultUrl:null};
			const result=analyzeGarsoResults(html);
			result.resultUrl=normalizeGarsoResultPageUrl(response.finalUrl);
			if (searchMode === 'phone') result.phoneSearchVariant='both';
			const publishResult=() => {
				result.topicLinksComplete=result.status === 'ok' &&
					(Number(result.count) === 0 || result.topics.length >= Number(result.count));
				onProgress?.({...result,searchTerm:query.term});
			};
			publishResult();
			if (collectTopics && result.status === 'ok' && result.count > result.topics.length && result.topics.length > 0) {
				result.topics=await collectAllGarsoSearchTopics(html,result,cancelToken,
					topics => {result.topics=topics; publishResult();});
				publishResult();
			}
			return result;
		} catch (error) {
			if (isOperationCancelledError(error)) throw error;
			if (error?.code === 'GARSO_ANTIFLOOD') {
				return {
					status:'antiflood', count:null, topics:[], resultUrl:null
				};
			}
			const httpStatus=Number(error?.status) || 0;
			if ([401,403].includes(httpStatus)) garsoAuthPromise=null;
			if (!error?.garsoLogged) {
				log('Błąd sprawdzania Garso:',error);
				try {error.garsoLogged=true;} catch {}
			}
			return {status:'error',count:null,topics:[],resultUrl:null,httpStatus,
				errorMessage:String(error?.message || 'Nie udało się sprawdzić Garso'),errorLogged:true};
		}
	}
	function normalizeTopicUrl(href) {
		if (!href) return '';
		try {
			const u=new URL(href,GARSO_BASE_URL);
			if (!/^https?:$/.test(u.protocol) || !/^(?:www\.)?garsoniera\.com\.pl$/i.test(u.hostname) || u.username || u.password) return '';
			u.searchParams.delete('hl');
			u.searchParams.delete('findpost');
			u.searchParams.delete('view');
			u.searchParams.delete('p');
			u.hash='';
			return u.href;
		} catch {return '';}
	}
	function getDisplayableGarsoTopicSubtitle(value) {
		const subtitle=normalizeEscortAdText(value);
		return subtitle && !subtitle.includes('?') ? subtitle:'';
	}
	function extractGarsoTopicSubtitle(doc) {
		const subtitle=doc.querySelector( 'h1.ipsType_pagetitle > span.desc[style*="font-size"], ' +
			'h1[itemprop="name"] > span.desc[style*="font-size"]' );
		return normalizeEscortAdText(subtitle?.textContent);
	}
	function extractSearchTopicSubtitle(anchor) {
		const heading=anchor.closest('h1,h2,h3,h4,h5,h6');
		const subtitle=heading?.querySelector('span.desc[style*="font-size"]');
		return normalizeEscortAdText(subtitle?.textContent);
	}
	function extractSearchTopics(doc) {
		const out=[];
		const seen=new Set();
		for (const a of doc.querySelectorAll( '#forum_table a[href*="/topic/"], ' + '#forum_table a[href*="showtopic"], ' +
			'#forum_table a[title="Pokaż wyniki"]' )) {
			const url=normalizeTopicUrl(a.getAttribute('href'));
			if (!url || seen.has(url)) continue;
			seen.add(url);
			out.push({
				title:normalizeEscortAdText(a.textContent), subtitle:extractSearchTopicSubtitle(a), url
			});
		}
		return out;
	}
	function analyzeGarsoResults(html) {
		if (!html) {
			recordParserIssue( 'Garso - wyniki wyszukiwania', 'Otrzymano pusty dokument wyników.' );
			return {status:'unknown',count:null,topics:[]};
		}
		const doc=new DOMParser().parseFromString(html,'text/html');
		const topics=extractSearchTopics(doc);
		for (const d of doc.querySelectorAll('.ipsType_pagedesc')) {
			const text=(d.textContent || '').replace(/\s+/g,' ').trim();
			const m=text.match(/zwróciła\s+następującą\s+liczbę\s+wyników\s*:\s*(\d+)/i);
			if (m) {
				const count=Number(m[1]);
				return {status:'ok',found:count > 0,count,topics};
			}
		}
		const rows=[...doc.querySelectorAll('#forum_table tr._recordRow')];
		if (rows.length) return {status:'ok',found:true,count:rows.length,topics};
		const scripts=html.match(/ipb\.global\.searchResults\s*\[\s*\d+\s*\]\s*=/g);
		if (scripts?.length) return {status:'ok',found:true,count:scripts.length,topics};
		const bodyText=normalizeText(doc.body ? doc.body.innerText:html);
		const noResultPhrases=[ 'nie znaleziono wyników','nie znaleziono żadnych wyników','brak wyników',
			'brak rezultatów','wyszukiwanie nie zwróciło żadnych wyników', 'twoje wyszukiwanie nie zwróciło żadnych wyników' ];
		if (noResultPhrases.some(p => bodyText.includes(p))) return {status:'ok',found:false,count:0,topics:[]};
		recordParserIssue( 'Garso - wyniki wyszukiwania',
			`Nie rozpoznano licznika wyników; znalezione linki tematów: ${topics.length}.` );
		return {status:'unknown',count:null,topics};
	}
	async function collectAllGarsoSearchTopics( firstHtml, result, cancelToken=null, onProgress=null ) {
		const firstDoc=parseHtml(firstHtml);
		const seen=new Map(result.topics.map(t => [t.url,t]));
		const pageLinks=[...firstDoc.querySelectorAll('.pagination a[href], ul.pagination a[href], .pages a[href]')]
			.map(a => ({page:parsePageNumber(a),href:a.getAttribute('href')})) .filter(x => x.page && x.page > 1 && x.href);
		if (!pageLinks.length) return [...seen.values()];
		const template=pageLinks[0];
		const perPage=result.topics.length;
		const maxPage=Math.ceil(result.count / perPage);
		for (let p=2; p <= maxPage; p++) {
			cancelToken?.throwIfCancelled();
			let url=null;
			const direct=pageLinks.find(x => x.page === p);
			if (direct) {
				try {url=new URL(direct.href,GARSO_BASE_URL).href;} catch {}
			}
			if (!url) {
				let templateAbs=null;
				try {templateAbs=new URL(template.href,GARSO_BASE_URL).href;} catch {}
				if (templateAbs) url=constructPageUrl(templateAbs,p,template.page);
			}
			if (!url) break;
			try {
				const html=await gmGetText( url, cancelToken, {searchRequest:true} );
				for (const t of extractSearchTopics(parseHtml(html))) seen.set(t.url,t);
				onProgress?.([...seen.values()]);
			} catch (e) {
				if (isOperationCancelledError(e)) throw e;
				log(`Nie udało się pobrać strony ${p} wyników Garso`,e);
			}
		}
		return [...seen.values()];
	}
	function getGarsoTopicWord(number) {
		return polishCountForm(number,'temat','tematy','tematów');
	}
	function setTwoLineButton(btn,title,result) {
		btn._vmResponsiveValueObserver?.disconnect();
		btn._vmResponsiveValueObserver=null;
		btn.replaceChildren();
		const titleLine=makeElement('span','',title);
		Object.assign(titleLine.style,{
			display:'block', fontWeight:'700', lineHeight:'1.2'
		});
		const resultLine=makeElement('span','',result);
		Object.assign(resultLine.style,{
			display:'block', marginTop:'2px', fontWeight:'600', fontSize:'0.92em', lineHeight:'1.2'
		});
		if (btn.classList.contains('vm-research-panel-row')) {
			titleLine.className='vm-research-panel-label';
			resultLine.className='vm-research-panel-value';
			Object.assign(titleLine.style,{
				minWidth:'0', marginRight:'12px', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', textAlign:'left'
			});
			Object.assign(resultLine.style,{
				flex:'0 0 auto', marginTop:'0', color:{
					stale:'#9ca3af', checking:'var(--vm-ui-panel-muted, #d8c8d9)', found:'var(--vm-ui-success, #62cf7b)', empty:'#80b9ee',
					error:'var(--vm-ui-danger, #ff6262)'
				}[btn.dataset.vmResultStatus] || 'var(--vm-ui-panel-muted, #d8c8d9)', textAlign:'right', whiteSpace:'nowrap'
			});
			const arrow=makeElement('span','','›');
			arrow.setAttribute('aria-hidden','true');
			Object.assign(arrow.style,{
				flex:'0 0 auto', marginLeft:'9px', color:getEscortPagePinkColor(), fontSize:'20px', fontWeight:'400',
				lineHeight:'1'
			});
			btn.append(titleLine,resultLine,arrow);
			const fullResult=String(result ?? '');
			const compactResult=fullResult.replace(
				/^(\d+) (?:anons|anonse|anonsów|ogłoszenia) \((\d+) (?:aktywne|aktywnych)\)$/, '$1 ogł. ($2 akt.)' );
			if (compactResult !== fullResult) {
				const renderResponsiveResult=() => {
					resultLine.textContent=fullResult;
					const labelIsClipped= titleLine.scrollWidth > titleLine.clientWidth + 1;
					const rowOverflows=btn.scrollWidth > btn.clientWidth + 1;
					if (labelIsClipped || rowOverflows) {
						resultLine.textContent=compactResult;
					}
				};
				if (typeof ResizeObserver === 'function') {
					btn._vmResponsiveValueObserver=new ResizeObserver( renderResponsiveResult );
					btn._vmResponsiveValueObserver.observe(btn);
				}
				requestAnimationFrame(renderResponsiveResult);
			}
			return;
		}
		btn.appendChild(titleLine);
		btn.appendChild(resultLine);
	}
	function renderGarsoButton(btn) {
		const displayTerm=btn.dataset.garsoDisplayTerm || '';
		const status=btn.dataset.garsoStatus || 'checking';
		const denied=[401,403].includes(Number(btn.dataset.garsoHttpStatus));
		let statusText='sprawdzam...';
		if (status === 'result') {
			const count=Number(btn.dataset.garsoCount || 0);
			statusText=`${count} ${getGarsoTopicWord(count)}`;
			setButtonColor(btn,count > 0 ? 'found':'empty');
		} else if (status === 'refreshing') {
			const count=Number(btn.dataset.garsoCount || 0);
			statusText=`${count} ${getGarsoTopicWord(count)} • odświeżanie…`;
			setButtonColor(btn,'checking');
		} else if (status === 'unknown') {
			statusText='?'; setButtonColor(btn,'checking');
		} else if (status === 'error') {
			statusText=denied ? 'zaloguj się':'błąd'; setButtonColor(btn,'error');
		} else if (status === 'manual') {
			statusText='kliknij'; setButtonColor(btn,'checking');
		} else setButtonColor(btn,'checking');
		if (btn.dataset.garsoSummaryState === 'ready') {
			const reviewCount=Math.max( 0, Number(btn.dataset.garsoReviewCount) || 0 );
			if (reviewCount > 0) {
				statusText += btn.classList.contains('vm-research-panel-row')
					? ' • są oceny'
					:' • oceny';
			}
		}
		else if (['loading','refreshing'].includes(btn.dataset.garsoSummaryState)) {
			statusText += ' • sprawdzanie';
		}
		if (btn.dataset.garsoCacheState === 'stale') {
			statusText += ' • nie odświeżono';
			btn.title='Pokazuję wcześniejszą liczbę tematów, ponieważ odświeżenie nie powiodło się.';
		} else if (btn.dataset.garsoCacheState === 'fresh') {
			btn.title='Liczba tematów pochodzi z aktualnego cache Garsoniery.';
		} else {
			btn.removeAttribute('title');
		}
		if (btn.dataset.garsoErrorMessage) {
			const hint=denied
				? `Garso odmówiło dostępu (HTTP ${btn.dataset.garsoHttpStatus}). Otwórz Garsonierę w zwykłej karcie i zaloguj się, następnie ponów sprawdzanie.`
				:btn.dataset.garsoErrorMessage;
			btn.title=[btn.title,hint].filter(Boolean).join(' ');
		}
		const title=btn.classList.contains('vm-research-panel-row')
			? `Garso - ${displayTerm}`
			:`Szukaj Garso: ${displayTerm}`;
		setTwoLineButton(btn,title,statusText);
	}
	async function updateGarsoStatus( btn, searchTerm, searchMode, forceRefresh=false, analyzeContent=true,
		prepareContentSummary=true, cancelToken=null ) {
		cancelToken?.throwIfCancelled();
		await ensurePersistentCacheKeys([garsoSearchCountCacheKey(searchMode,searchTerm),garsoSummaryCacheKey(searchMode,searchTerm)]);
		cancelToken?.throwIfCancelled();
		const cached=getGarsoSearchCountCache(searchMode,searchTerm);
		delete btn.dataset.garsoHttpStatus;
		delete btn.dataset.garsoErrorMessage;
		if (searchMode === 'phone' && cached) {
			btn.dataset.garsoPhoneSearchVariant=cached.phoneSearchVariant || 'both';
		}
		const shouldAnalyzeContent=analyzeContent !== false;
		const shouldPrepareContentSummary= shouldAnalyzeContent && prepareContentSummary !== false;
		if (!forceRefresh && cached?.fresh && cached?.topicLinksFresh) {
			btn.dataset.garsoStatus='result';
			btn.dataset.garsoCount=String(cached.count);
			btn.dataset.garsoCacheState='fresh';
			btn.dataset.garsoCountCheckedAt=String(cached.checkedAt);
			if (!btn.dataset.garsoSummaryState) {
				const restored=restoreGarsoSummaryFromCache( btn, searchTerm, searchMode );
				if (!restored && Number(cached.count) === 0) {
					setGarsoSummaryState(btn,'empty','',{
						checkedAt:cached.checkedAt, cacheState:'fresh'
					});
				} else if ( !restored && !shouldAnalyzeContent && Number(cached.count) > 0 ) {
					setGarsoSummaryState(btn,'count-only','',{
						checkedAt:cached.checkedAt, cacheState:'fresh'
					});
				}
			}
			renderGarsoButton(btn);
			emitGarsoCountState(btn,'ready',cached.count,true);
			const summaryState=btn.dataset.garsoSummaryState || '';
			const summaryCacheState=btn.dataset.garsoSummaryCacheState || '';
			const needsContentRefresh= shouldPrepareContentSummary && Number(cached.count) > 0 && ( summaryState !== 'ready' ||
					['stale','partial'].includes(summaryCacheState) );
			if (!needsContentRefresh) return garsoResultFromCache(cached);
		}
		if (cached) {
			btn.dataset.garsoStatus='refreshing';
			btn.dataset.garsoCount=String(cached.count);
			btn.dataset.garsoCacheState='refreshing';
			btn.dataset.garsoCountCheckedAt=String(cached.checkedAt);
		} else {
			btn.dataset.garsoStatus='checking';
			delete btn.dataset.garsoCacheState;
			delete btn.dataset.garsoCountCheckedAt;
		}
		renderGarsoButton(btn);
		const result=await checkGarsoTerm( searchTerm, shouldAnalyzeContent, cancelToken, searchMode, forceRefresh );
		if (['error','unknown','antiflood'].includes(result.status)) {
			if (result.errorMessage) btn.dataset.garsoErrorMessage=result.errorMessage;
			if (result.httpStatus) btn.dataset.garsoHttpStatus=String(result.httpStatus);
			if (searchMode === 'phone') btn.dataset.garsoPhoneSearchVariant='both';
			if (cached) {
				btn.dataset.garsoStatus='result';
				btn.dataset.garsoCount=String(cached.count);
				btn.dataset.garsoCacheState='stale';
			} else {
				btn.dataset.garsoStatus=result.status === 'antiflood'
					? 'unknown'
					:result.status;
				delete btn.dataset.garsoCacheState;
			}
		}
		else {
			btn.dataset.garsoStatus='result';
			btn.dataset.garsoCount=String(result.count ?? 0);
			delete btn.dataset.garsoCacheState;
			const countCheckedAt=cacheGarsoSearchResult(searchMode,searchTerm,result);
			if (searchMode === 'phone') {
				btn.dataset.garsoPhoneSearchVariant=result.phoneSearchVariant || 'both';
			}
			if (countCheckedAt) {
				btn.dataset.garsoCountCheckedAt=String(countCheckedAt);
			}
		}
		renderGarsoButton(btn);
		emitGarsoCountState( btn, result.status === 'ok' ? 'ready':(cached ? 'stale':'error'),
			result.status === 'ok' ? (result.count ?? 0):(cached?.count ?? null), result.status !== 'ok' && !!cached );
		if ( shouldPrepareContentSummary && result.status === 'ok' && result.count > 0 && result.topics?.length ) {
			prepareGarsoSummary( btn, searchTerm, result.topics, forceRefresh, cancelToken );
		} else if ( !shouldPrepareContentSummary && result.status === 'ok' && result.count > 0 &&
			btn.dataset.garsoSummaryState !== 'ready' ) {
			setGarsoSummaryState(btn,'count-only','',{
				checkedAt:Number(btn.dataset.garsoCountCheckedAt) || Date.now(), cacheState:'fresh'
			});
		} else if (result.status === 'ok' && Number(result.count) === 0) {
			deleteGarsoSummaryCache(searchMode,searchTerm);
			setGarsoSummaryState(btn,'empty','');
		}
		return result;
	}
	async function startSearchProcess(term,btnElement) {
		const title=btnElement.classList.contains('vm-research-panel-row')
			? `Garso - ${btnElement.dataset.garsoDisplayTerm || ''}`
			:`Szukaj Garso: ${btnElement.dataset.garsoDisplayTerm || ''}`;
		setTwoLineButton(btnElement,title,'Autoryzacja...');
		setButtonColor(btnElement,'checking');
		btnElement.disabled=true;
		try {
			const auth=await getGarsoAuth();
			await submitRealForm(term,auth.secureHash,auth.sessionId,btnElement);
		} catch {
			setTwoLineButton(btnElement,title,'Błąd sesji');
			setButtonColor(btnElement,'error');
			btnElement.disabled=false;
			setTimeout(() => renderGarsoButton(btnElement),2000);
		}
	}
	async function submitGarsoSearchFormToNewTab( term, hash, sessionId, {active=true,andorType='and'}={} ) {
		term=normalizeGarsoSearchTerm(term);
		const token=`${Date.now()}-${Math.random().toString(36).slice(2)}`;
		const storageKey=`${GARSO_OPEN_BRIDGE_KEY_PREFIX}${token}`;
		const bridgeUrl=new URL( 'index.php?app=core&module=search&search_in=forums', GARSO_BASE_URL );
		bridgeUrl.hash=new URLSearchParams({[GARSO_OPEN_BRIDGE_PARAM]:token}).toString();
		try {
			await GM.setValue(storageKey,{term,andorType:andorType === 'or' ? 'or':'and',secureHash:hash,sessionId,createdAt:Date.now()});
			noteGarsoNativeSearch();
			GM_openInTab(bridgeUrl.href,{
				active, insert:true, setParent:true
			});
		} catch (error) {
			deleteHelperStorageValue(storageKey);
			throw error;
		}
		setTimeout(() => deleteHelperStorageValue(storageKey),5 * 60 * 1000);
	}
	async function submitRealForm(term,hash,sessionId,btnElement) {
		const title=btnElement.classList.contains('vm-research-panel-row')
			? `Garso - ${btnElement.dataset.garsoDisplayTerm || ''}`
			:`Szukaj Garso: ${btnElement.dataset.garsoDisplayTerm || ''}`;
		setTwoLineButton(btnElement,title,'Otwieranie...');
		setButtonColor(btnElement,'checking');
		const query=getGarsoSearchQuery(term,btnElement.dataset.garsoSearchMode);
		if (query.term) await submitGarsoSearchFormToNewTab(query.term,hash,sessionId,{active:true,andorType:query.andorType});
		setTimeout(() => {
			btnElement.disabled=false;
			renderGarsoButton(btnElement);
		},800);
	}
	function parsePageNumber(a) {
		const text=(a.textContent || '').trim();
		if (/^\d+$/.test(text)) return Number(text);
		const href=a.getAttribute('href') || '';
		let m=href.match(/\/page-(\d+)\/?/i);
		if (m) return Number(m[1]);
		m=href.match(/[?&]page=(\d+)/i);
		if (m) return Number(m[1]);
		return null;
	}
	function getPaginationMap(doc,baseUrl) {
		const map=new Map([[1,baseUrl]]);
		let baseTopicId=null;
		try {baseTopicId=getGarsoTopicPageContext(baseUrl)?.id || null;} catch {}
		for (const a of doc.querySelectorAll('.pagination a[href], ul.pagination a[href], .pages a[href], a[href*="page-"]')) {
			const n=parsePageNumber(a);
			if (!n || n < 1) continue;
			try {
				const url=new URL(a.getAttribute('href'),baseUrl);
				const candidateTopicId=getGarsoTopicPageContext(url.href)?.id || null;
				if (baseTopicId && candidateTopicId !== baseTopicId) continue;
				map.set(n,url.href);
			} catch {}
		}
		return map;
	}
	function getMaxPage(doc,map) {
		let maxPage=Math.max(...map.keys());
		const text=[...doc.querySelectorAll('.pagination,.pages')].map(el => {
			const clone=el.cloneNode(true);
			clone.querySelectorAll('*').forEach(child => child.append(' '));
			return clone.textContent || '';
		}).join(' ');
		const m=text.match(/(?:strona|page)\s*\d+\s*(?:z|of)\s*(\d+)/i);
		if (m) maxPage=Math.max(maxPage,Number(m[1]));
		return Number.isFinite(maxPage) && maxPage > 0 ? maxPage:1;
	}
	function constructPageUrl(templateUrl,pageNo,knownPageNo=null) {
		try {
			const u=new URL(templateUrl);
			if (/\/page-\d+\/?/i.test(u.pathname)) {
				u.pathname=u.pathname.replace(/\/page-\d+\/?/i,`/page-${pageNo}/`);
				return u.href;
			}
			if (u.searchParams.has('page')) {
				u.searchParams.set('page',String(pageNo));
				return u.href;
			}
			if (u.searchParams.has('st') && knownPageNo && knownPageNo > 1) {
				const current=Number(u.searchParams.get('st'));
				const perPage=current / (knownPageNo - 1);
				if (Number.isFinite(perPage) && perPage > 0) {
					u.searchParams.set('st',String(Math.round((pageNo - 1) * perPage)));
					return u.href;
				}
			}
		} catch {}
		return null;
	}
	function parseHtml(html) {return new DOMParser().parseFromString(html,'text/html');}
	function resolveTopicPageUrl(map,baseUrl,pageNo,maxPage) {
		if (pageNo === 1) return baseUrl;
		if (map.has(pageNo)) return map.get(pageNo);
		const knownPages=[...map.keys()] .filter(page => page > 1)
			.sort((a,b) => Math.abs(a - pageNo) - Math.abs(b - pageNo));
		for (const knownPage of knownPages) {
			const url=constructPageUrl(map.get(knownPage),pageNo,knownPage);
			if (url) return url;
		}
		return constructPageUrl(map.get(maxPage) || baseUrl,pageNo,maxPage);
	}
	function extractGarsoTopicTitle(doc,fallback='') {
		const heading=doc?.querySelector( 'h1[itemprop="name"], h1.ipsType_pagetitle, #content h1' );
		if (!heading) return normalizeEscortAdText(fallback);
		const clone=heading.cloneNode(true);
		clone.querySelectorAll('span.desc, .desc, .vm-garso-inline-tools').forEach(element => detachHelperNode(element));
		return normalizeEscortAdText(clone.textContent) || normalizeEscortAdText(fallback);
	}
	function extractFirstGarsoPostLine(doc) {
		if (!doc) return '';
		const firstBlock=doc.querySelector('.post_block, [id^="post_id_"]');
		if (!firstBlock) return '';
		const content=getGarsoPostContent(firstBlock);
		return cleanPostElement(content) .split('\n') .map(line => line.trim()) .find(Boolean) || '';
	}
	function getGarsoTopicMatchPhones(doc,title,context) {
		const fullPhones=text => findGarsoPhoneTokens(text).filter(match => !/xxx$/i.test(match.value));
		const titlePhones=fullPhones(title);
		if (titlePhones.length || context?.page !== 1) return titlePhones;
		return fullPhones(extractFirstGarsoPostLine(doc));
	}
	const GARSO_REFERENCE_URL_RE=/(?<![\p{L}\p{N}_.@/-])(?:https?:\/\/|www\.|(?:[a-z]{2}\.)?escort\.club\/)[^\s<>"'“”„]+/giu;
	function readGarsoMatchSource(sources) {
		const urls=new Set(),otherUrls=new Set(),texts=[]; let hasLinks=false;
		const urlPattern=GARSO_REFERENCE_URL_RE;
		const addUrl=value => {
			try {
				const url=new URL(/^(?:www\.|(?:[a-z]{2}\.)?escort\.club\/)/i.test(value) ? `https://${value}`:value,GARSO_BASE_URL);
				if (!/^https?:$/.test(url.protocol)) return;
				hasLinks=true;
				if (isEscortClubHost(url.hostname)) {
					if (getEscortAdIdFromPath(url.pathname)) urls.add(normalizeEscortiAdUrl(url.href));
				} else if (!isGarsoRequestUrl(url.href)) otherUrls.add(url.href);
			} catch {}
		};
		for (const source of sources.filter(Boolean)) {
			const copy=cloneGarsoPostContent(source);
			for (const anchor of copy.querySelectorAll('a[href]')) {
				addUrl(anchor.getAttribute('href'));
				anchor.textContent=anchor.textContent.replace(urlPattern,' ');
			}
			copy.querySelectorAll('br,p,div,li').forEach(element => element.append(' '));
			const body=copy.textContent || '';
			for (const match of body.matchAll(urlPattern)) addUrl(match[0].replace(/[.,;:!?)\]}]+$/,''));
			texts.push(body.replace(urlPattern,' '));
		}
		return {adUrls:[...urls].filter(Boolean),otherUrls:[...otherUrls],text:texts.join(' '),hasLinks};
	}
	let garsoCurrentTopicSourcePromise=null;
	function getGarsoCurrentTopicSource() {
		if (garsoCurrentTopicSourcePromise) return garsoCurrentTopicSourcePromise;
		garsoCurrentTopicSourcePromise=(async () => {
			const context=getGarsoTopicPageContext();
			if (!context) return null;
			const title=extractGarsoTopicTitle(document);
			const titlePhones=getGarsoTopicMatchPhones(null,title,{page:2});
			const key='vm_garso_topic_sources',now=Date.now();
			let stored={};
			try {stored=JSON.parse(sessionStorage.getItem(key) || '{}');} catch {}
			if (!stored || typeof stored !== 'object' || Array.isArray(stored)) stored={};
			const saved=stored[context.id];
			if (context.page !== 1 && saved?.at > now - GARSO_TOPIC_COUNT_CHECK_INTERVAL_MS && Array.isArray(saved.phones) && Array.isArray(saved.adUrls)) {
				return {...saved,phones:titlePhones.length ? titlePhones:saved.phones,eligible:titlePhones.length > 0 || saved.eligible,title,firstDoc:null};
			}
			let firstDoc=context.page === 1 ? document:null;
			if (!firstDoc && !titlePhones.length) {
				const token=createOperationCancelToken('Garso - pierwszy post tematu');
				const cancel=() => token.cancel('Opuszczono temat');
				window.addEventListener('pagehide',cancel);
				try {
					const response=await garsoTopicRequest({method:'GET',url:context.baseUrl,timeout:20000,cancelToken:token});
					token.throwIfCancelled();
					const actual=getGarsoTopicPageContext(response.finalUrl || context.baseUrl);
					if (response.status < 200 || response.status >= 400 || actual?.id !== context.id || actual.page !== 1) throw new Error('Nie udało się odczytać pierwszego posta tematu.');
					firstDoc=parseHtml(response.responseText || '');
					if (!firstDoc.querySelector('.post_block,[id^="post_id_"]')) throw new Error('Pierwszy post jest niedostępny.');
				} finally {window.removeEventListener('pagehide',cancel);}
			}
			const firstBlock=firstDoc?.querySelector('.post_block,[id^="post_id_"]');
			const first=firstBlock && getGarsoPostContent(firstBlock);
			const result={
				at:now,title, phones:titlePhones.length ? titlePhones:getGarsoTopicMatchPhones(firstDoc,title,{page:1}),
				adUrls:readGarsoMatchSource([first]).adUrls,
				eligible:firstDoc ? getGarsoSummaryTopicEligibility({url:context.baseUrl,title},firstDoc).eligible:titlePhones.length > 0
			};
			if (firstDoc) {
				stored[context.id]=result;
				stored=Object.fromEntries(Object.entries(stored).filter(([,v]) => v?.at > now - GARSO_TOPIC_COUNT_CHECK_INTERVAL_MS).sort((a,b) => b[1].at - a[1].at).slice(0,100));
				try {sessionStorage.setItem(key,JSON.stringify(stored));} catch {}
			}
			return {...result,firstDoc};
		})().catch(error => {garsoCurrentTopicSourcePromise=null; throw error;});
		return garsoCurrentTopicSourcePromise;
	}
	function extractGarsoTopicForumNames(doc) {
		if (!doc) return [];
		const names=new Set();
		const selectors=[ '.breadcrumb a[href*="/forum/"]', '.breadcrumb [itemprop="title"]',
			'#breadcrumb a[href*="/forum/"]', '#breadcrumb [itemprop="title"]', 'ol.breadcrumb a[href*="/forum/"]',
			'ol.breadcrumb [itemprop="title"]', '#secondary_navigation .breadcrumb a[href*="/forum/"]',
			'#secondary_navigation .breadcrumb [itemprop="title"]', '.ipsBreadcrumb a[href*="/forum/"]',
			'.ipsBreadcrumb [itemprop="name"]' ].join(', ');
		for (const element of doc.querySelectorAll(selectors)) {
			const name=normalizeEscortAdText(element.textContent);
			if (name) names.add(name);
		}
		return [...names];
	}
	function getGarsoSummaryTopicEligibility(topic,firstDoc) {
		const title=extractGarsoTopicTitle(firstDoc,topic?.title);
		return {
			eligible:findGarsoPhoneTokens(title).length > 0 ||
				findGarsoPhoneTokens(extractFirstGarsoPostLine(firstDoc)).some(match => !/xxx$/i.test(match.value)) ||
				getGarsoTopicPageContext(topic?.url)?.hasPhone === true ||
				extractGarsoTopicForumNames(firstDoc).some(name => /(?:czatodajki|cichodajki)/i.test(name)), title
		};
	}
	function getGarsoPostBlocks(doc=document) {
		const blocks=[...doc.querySelectorAll('.post_block')];
		return blocks.length ? blocks:[...doc.querySelectorAll('[id^="post_id_"]')];
	}
	function cloneGarsoPostContent(content) {
		const copy=content.cloneNode(true);
		copy.querySelectorAll('.post_controls,.post_footer,.post_header,.posted_info,.post_date,.author_info,.post_username,blockquote,.ipsBlockquote,.citation,.quote,.bbc_quote,.signature,.signature_content,.edit,script,style,noscript,.vm-helper-ui,.vm-garso-tracked-topics,.vm-garso-inline-tools,.vm-garso-parser-feedback,[id^="vm-"],[hidden],input,textarea,button').forEach(el => detachHelperNode(el));
		return copy;
	}
	function readGarsoPost(block,topicTitle,pageNo,pageUrl) {
		const content=getGarsoPostContent(block),date=block.querySelector('time[datetime]') || block.querySelector('.post_date abbr[title],.posted_info abbr[title]') || block.querySelector('.post_date,.posted_info,time');
		return {topicTitle,pageNo,pageUrl,postId:block.id || '',
			author:normalizeEscortAdText(block.querySelector('.post_username,.author_info .name,[itemprop="name"]')?.textContent),
			date:normalizeEscortAdText(date?.getAttribute('datetime') || date?.getAttribute('title') || date?.textContent),
			text:cleanPostElement(content),references:getGarsoPostReferences(content)};
	}
	function getGarsoPostContent(block) {
		return block.querySelector('.post.entry-content,.post_body .post') || block.querySelector('.post_body') || block;
	}
	function cleanPostElement(postEl) {
		const clone=cloneGarsoPostContent(postEl);
		clone.querySelectorAll('br').forEach(br => br.replaceWith('\n'));
		clone.querySelectorAll('p,li').forEach(el => el.append('\n'));
		return (clone.textContent || '') .replace(/\r/g,'') .replace(/[^\S\n]+/g,' ') .replace(/\n\s*\n+/g,'\n') .trim();
	}
	function extractPostsFromPage(html,topicTitle,pageNo,pageUrl='') {
		const blocks=getGarsoPostBlocks(typeof html === 'string' ? parseHtml(html):html),posts=[];
		if (!blocks.length) recordParserIssue('Garso - posty tematu',`Brak postów na stronie ${pageNo || '?'}.`);
		for (const block of blocks) {
			const post=readGarsoPost(block,topicTitle,pageNo,pageUrl),{text,references}=post;
			if (text.length >= 15 || references.phones.length || references.adUrls.length || references.otherUrls.length || Object.keys(extractRatingsFromPost(text,post)).length) posts.push(post);
		}
		return posts;
	}
	const garsoParserRegexCache=new Map();
	function garsoParserUnicodeRegex(regex) {
		const key=regex.flags + ':' + regex.source;
		if (!garsoParserRegexCache.has(key)) {
			// JS \b nie traktuje polskich liter jako znaków słowa.
			const boundary=String.raw`(?:(?<![\p{L}\p{N}_])(?=[\p{L}\p{N}_])|(?<=[\p{L}\p{N}_])(?![\p{L}\p{N}_]))`;
			if (garsoParserRegexCache.size >= 160) garsoParserRegexCache.clear();
			garsoParserRegexCache.set(key,new RegExp(regex.source.replace(/\\b/g,boundary),regex.flags.includes('u') ? regex.flags:regex.flags + 'u'));
		}
		return garsoParserRegexCache.get(key);
	}
	function garsoParserTest(regex,text) {
		const rx=garsoParserUnicodeRegex(regex); rx.lastIndex=0;
		return rx.test(text);
	}
	// Zakres jest wyznaczany tylko z jawnych nagłówków/przejść; bez zgadywania tożsamości.
	function getGarsoPostScopes(text) {
		const aliases=getGarsoPostScopes.aliases ||= new Set(CATEGORY_DEFS.flatMap(def => [...def.patterns,...(def.shortPatterns || [])]));
		const memo=getGarsoPostScopes.memo ||= new Map();
		if (memo.has(text)) return memo.get(text);
		const scopes=[{start:0,end:text.length,subject:''}];
		const skip=/^(?:Witam|Cześć|Hej|Oceny|Cyfry|Podsumowanie|Rekomendacja|Polecam|Pozdrawiam|Plusy|Minusy|Ogólnie|Numer|Telefon|Zdjęcia|Fotki|Załączniki|Cytuj|Zgłoś|Edytowano|Dzięki|Dziekuje|Dziękuję)$/iu;
		for (const line of text.matchAll(/[^\n]+/g)) {
			const value=line[0].trim();
			let name=value.match(/^([\p{Lu}][\p{Ll}]{2,24})\s*:?$/u)?.[1];
			name ||= value.match(/^Tym\s+razem\s*[-–—:]\s*([\p{Lu}][\p{Ll}]{2,24})\s*$/u)?.[1];
			name ||= value.match(/^U\s+(?:pani\s+)?([\p{Lu}][\p{Ll}]{2,24})\s+(?:również|rowniez|także|takze)(?!\p{L})/u)?.[1];
			name ||= value.match(/^Po\s+(?:ostatniej|pierwszej)\s+wizycie\s+u\s+([\p{Lu}][\p{Ll}]{2,24})\b/u)?.[1];
			if (!name || skip.test(name) || aliases.has(name.toLocaleLowerCase('pl-PL'))) continue;
			const previous=scopes.at(-1);
			if (previous.subject === name) continue;
			previous.end=line.index;
			scopes.push({start:line.index,end:text.length,subject:name});
		}
		const result=scopes.filter(scope => scope.end > scope.start);
		if (text.length <= 30000) {
			if (memo.size >= 64) memo.delete(memo.keys().next().value);
			memo.set(text,result);
		}
		return result;
	}
	function getGarsoRatingTitleAliases(text,title) {
		const standard=getCategoryRatingRegexes();
		// Alias imienia wymaga tabeli ocen oraz zgodności z tytułem, nie słownika imion.
		const categories=new Set([...text.matchAll(standard.labels)].filter(match =>
			/^[\s:–—-]*\d+(?:[.,]\d+)?\s*\/\s*(?:5|10)(?!\d)/u.test(text.slice(match.index + match[0].length)))
			.map(match => standard.aliases.get(match[0].replace(/\s+/g,'').toLowerCase())));
		if (categories.size < 2 || getGarsoPostScopes(text).filter(scope => scope.subject).length > 1 ||
			/\b(?:salon|masaż[euy]|masaze|agencja|pytamy|panny|dziewczyny)\b|\s(?:i|&)\s/iu.test(title)) return [];
		const words=new Set((title.match(/\p{L}{3,24}/gu) || []).map(word => word.toLocaleLowerCase('pl-PL')));
		const result=[];
		for (const line of text.split('\n')) {
			const match=line.trim().match(/^([\p{Lu}][\p{Ll}]{2,24})\s*(?::|[-–—])?\s*(?=\d+(?:[.,]\d+)?\s*\/|$)/u);
			if (match && words.has(match[1].toLocaleLowerCase('pl-PL')) && !standard.aliases.has(match[1].toLowerCase())) result.push(match[1]);
		}
		return [...new Set(result)];
	}
	function getGarsoMentionContext(text,span) {
		const left=text.slice(0,span.start),right=text.slice(span.end);
		const start=Math.max(left.lastIndexOf('\n'),left.lastIndexOf('.'),left.lastIndexOf('!'),left.lastIndexOf('?')) + 1;
		const boundary=right.search(/[.!?\n]/u),end=boundary < 0 ? text.length:span.end + boundary;
		return {start,end,before:text.slice(Math.max(start,span.start - 240),span.start),
			after:text.slice(span.end,Math.min(end,span.end + 240)),sentence:text.slice(start,Math.min(end,start + 900)),
			near:text.slice(Math.max(0,span.start - 240),Math.min(text.length,span.end + 240)),
			line:text.slice(left.lastIndexOf('\n') + 1,right.indexOf('\n') < 0 ? text.length:span.end + right.indexOf('\n'))};
	}
	function classifyGarsoMention(text,key,span,tone) {
		const c=getGarsoMentionContext(text,span),matched=text.slice(span.start,span.end);
		const contrast=[...c.before.matchAll(/(?:,\s*|\s+)(?:ale|jednak|natomiast|a\s+okazało\s+się|a\s+okazalo\s+sie)\s+/giu)].at(-1);
		if (contrast) c.before=c.before.slice(contrast.index + contrast[0].length);
		const local=c.before + matched + c.after;
		if (garsoParserTest(/\b(?:gdyby|byłby|byłaby|byłoby|byłyby|bylby|bylaby|byloby|bylyby|było\s+by)\b/iu,c.before + matched)) return 'warunek niezrealizowany';
		if (garsoParserTest(/\b(?:nie\s+(?:wiem|jestem\s+pewien)|(?:daj|dawaj|pyt|prosz|wiecie|wiesz|ciekaw)\p{L}*|chciałbym\s+wiedzieć)[^.!?\n]*\bczy\b/iu,c.before + matched)) return 'pytanie lub niepewność';
		if (garsoParserTest(/\b(?:boję\s+się|boje\s+sie|bałem\s+się|balem\s+sie|obaw\p{L}*|ryzyko|żeby|zeby|aby)\b[^.!?\n]{0,100}$/iu,c.before) &&
			! garsoParserTest(/\bbez\s+obaw\b/iu,c.before)) return 'obawa lub cel, nie stwierdzenie';
		if (key === 'rekomendacja') {
			if (garsoParserTest(/^polece(?:ni\p{L}*|ń)$/iu,matched) && !garsoParserTest(/\b(?:do|wart\p{L}*|godn\p{L}*|moja|moje|moją|daję|daje)\s*$/iu,c.before)) return 'polecenie nie jest własną rekomendacją';
			if (garsoParserTest(/\b(?:prośb\p{L}*|prosb\p{L}*|prosz\p{L}*)\s+o\s*$/iu,c.before) ||
				garsoParserTest(/\b(?:koledzy|inni|wszyscy|ktoś|ktos|kolega)\s*$/iu,c.before) || garsoParserTest(/^polec(?:ił[ao]?|il[ao]?|ali|ano|ona|ony|one)$/iu,matched)) return 'prośba, instrukcja albo cudze polecenie';
			if (tone === 'positive' && garsoParserTest(/^(?:\s+\p{L}+){0,2}\s+(?:dzwonić|dzwonic|telefonować|telefonowac|parking|zaparkować|zaparkowac|lekturę|lekture)\b/iu,c.after)) return 'polecenie czynności';
		}
		if (key === 'GFE') {
			if (garsoParserTest(/https?:\/\/\S+\s*>?\s*[^\n.!?]{0,60}\bgfe\b/iu,c.line) || garsoParserTest(/(?:\bu|\bo)\s+[\p{Lu}][\p{Ll}]{2,24}\s*$/u,c.before) && garsoParserTest(/\b(?:ostatnio|wcześniej|wczesniej|kiedyś|kiedys)\b/iu,c.sentence)) return 'nazwa anonsu lub inna osoba';
			if (garsoParserTest(/\b(?:ceni[ea]|cenę|cene|cenie|ceną|cena)[^.!?\n]{0,65}(?:przewyższ\p{L}*|niż|niz|jak)\s*$/iu,c.before)) return 'porównanie z inną usługą';
			if (garsoParserTest(/\b(?:na\s+)?gfe\s+(?:i\s+tak\s+)?nie\s+licz[eę]\b|\b(?:licz[eę]|liczyłem|liczylem|oczekuj[eę]|oczekiwałem|oczekiwalem)\s+(?:na\s+)?(?:pełne\s+)?gfe\b/iu,local)) return 'oczekiwanie autora';
		}
		if (['wtopa','oszustwo','scam','podmianka'].includes(key)) {
			if (garsoParserTest(/\b(?:panny|kobiety|inne|inni)\b[^.!?\n]{0,40}\bopisywane\s+jako\b/iu,local)) return 'porównanie z innymi osobami';
			if (key === 'wtopa') {
				if (garsoParserTest(/\bpo\s+zaliczeniu\s*$/iu,c.before) && garsoParserTest(/^\s+w\s+(?:mojej|innej|swojej)\s+okolicy/iu,c.after)) return 'wcześniejsze doświadczenia w innym miejscu';
				if (garsoParserTest(/\b(?:dość|dosc|dosyć|dosyc|kilku|paru|wielu|poprzednich|wcześniejszych|wczesniejszych)\s*$/iu,c.before) ||
					garsoParserTest(/^\s+(?:trochę\s+|troche\s+)?z\s+mojej\s+strony\b/iu,c.after) || garsoParserTest(/(?<!\p{L})[łl]atwo\s+zaliczy[ćc]\s*$/iu,c.before)) return 'inne doświadczenia albo pomyłka klienta';
				if (garsoParserTest(/\b(?:po)?my[śs]la[łl]em\b[^.!?\n]{0,50}$/iu,c.before) && garsoParserTest(/\b(?:myliłem\s+się|mylilem\s+sie|udało\s+się|udalo\s+sie|jednak\s+było\s+dobrze)\b/iu,c.near)) return 'odwołane pierwsze wrażenie';
			}
		}
		if (key === 'zdjęcia') {
			if (garsoParserTest(/\b(?:nie\s+jestem\s+pewien|nie\s+uważam|nie\s+uwazam|możliwość|mozliwosc|może\s+być|moze\s+byc|może\s+by[łl]y)\b/iu,local)) return 'niepewność dotycząca zdjęć';
			if (garsoParserTest(/\bwiek\b/iu,c.sentence) && garsoParserTest(/\b(?:czy\s+prawdziwy|wywnioskować|wywnioskowac)\b/iu,local)) return 'ocena wieku, nie zdjęć';
			if (tone === 'positive' && garsoParserTest(/\b(?:upewniłem|upewnilem|zapewniono|zapewnia\p{L}*|obieca\p{L}*)\b[^.!?\n]{0,90}$/iu,c.before)) return 'zapewnienie przed wizytą';
			if (tone === 'negative' && garsoParserTest(/\b(?:wystylizowan\p{L}*|umalowan\p{L}*|uczesan\p{L}*)\b/iu,matched)) return 'różnica stylizacji';
			if (tone === 'positive' && garsoParserTest(/\bta\s+sama\s+osoba\s*\(przynajmniej\s+na\s+zdj/iu,matched)) return 'porównanie ofert, nie wyglądu';
			if (tone === 'positive' && garsoParserTest(/\b(?:te\s+same|takie\s+same)\s+zdjęcia\b[^.!?\n]{0,90}\b(?:imi|ofert|anons|profil)/iu,local)) return 'porównanie ofert, nie wyglądu';
		}
		return '';
	}

	const RATING_NUMBER_SOURCE='\\d+(?:[.,]\\d+)?';
	let categoryRatingRegexCache=null;
	function escapeRatingAlias(pattern) {
		return pattern.trim().split(/\s+/).map(part => part.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('\\s*');
	}
	function getCategoryRatingRegexes() {
		if (categoryRatingRegexCache) return categoryRatingRegexCache;
		const aliases=new Map();
		for (const def of CATEGORY_DEFS) {
			for (const alias of [...def.patterns,...(def.shortPatterns || [])]) aliases.set(alias.replace(/\s+/g,'').toLowerCase(),def.key);
		}
		const patterns=CATEGORY_DEFS.flatMap(def => [...def.patterns,...(def.shortPatterns || [])]).sort((a,b) => b.length - a.length).map(escapeRatingAlias);
		return categoryRatingRegexCache={
			aliases, labels:new RegExp(`(?<![\\p{L}\\p{N}])(?:${patterns.join('|')})(?!\\p{L})`,'giu'),
			numbers:new RegExp(`(?<!\\d)(?<!\\d[.,])(${RATING_NUMBER_SOURCE})(?:\\s*[-–\\u2014]\\s*(${RATING_NUMBER_SOURCE}))?\\s*[+−-]?\\s*(?:(?:/|\\bna\\b|:(?=\\s*(?:5|10)(?!\\d)))\\s*(${RATING_NUMBER_SOURCE}))?(?!\\d|[.,]\\d)`,'giu')
		};
	}
	function extractRatingsFromPost(text,context={}) {
		const ratings={};
		text=String(text || '');
		const base=getCategoryRatingRegexes(),aliases=new Map(base.aliases),names=getGarsoRatingTitleAliases(text,String(context?.topicTitle || ''));
		for (const name of names) aliases.set(name.toLowerCase(),'girl');
		const labelRegex=names.length ? new RegExp(`${base.labels.source}|(?<![\\p{L}\\p{N}])(?:${names.map(escapeRatingAlias).join('|')})(?!\\p{L})`,'giu'):base.labels;
		const numberRegex=base.numbers;
		const allowColonScale=[...text.matchAll(base.labels)].filter(match => /^[\s:–—-]*\d+(?:[.,]\d+)?\s*\/\s*(?:5|10)(?!\d)/u.test(text.slice(match.index + match[0].length))).length >= 2;
		let section=null;
		for (const raw of text.matchAll(/[^\n]+/g)) {
			const normalized=raw[0].replace(/[\t\r\u00a0]/g,' ').replace(/^(?:\s*\d{1,2}[.)]|\s*[IVX]+[.)])\s*/i,value => ' '.repeat(value.length));
			if (!normalized.trim()) continue;
			const ownLabels=[...normalized.matchAll(labelRegex)];
			const heading=ownLabels.length > 0 && /^[\s:.–—/+&,\-]*(?:(?:i|oraz)[\s:.–—/+&,\-]*)*$/iu.test(normalized.replace(labelRegex,''));
			if (heading) {section={label:normalized.trim().replace(/[\s:.–—-]+$/u,''),start:raw.index + ownLabels[0].index}; continue;}
			// Nie przenosimy kontekstu przez nieznany nagłówek/osobę ani stopkę forum.
			if (/^\s*(?:[\p{Lu}][\p{L}]{2,24}\s*:?|[-–—]{3,}|Lubię to|Dodaj odpowiedź|Do góry|Zgłoś)\s*$/u.test(normalized)) section=null;
			const ownRating=ownLabels.some(match => /^[\s:=(\[–—-]*\d/.test(normalized.slice(match.index + match[0].length)));
			if (ownRating) section=null;
			const localHeading=ownLabels.find(match => !normalized.slice(0,match.index).trim());
			const reverseRating=ownLabels.some(match => /^\s*\d+(?:[.,]\d+)?\s*\/\s*(?:5|10)\s*[-–—:]*\s*$/u.test(normalized.slice(0,match.index)));
			if (localHeading || reverseRating) section=null;
			const prefix=section && !ownRating ? `${section.label}: `:'';
			if (localHeading && /^\s*[:.,–—-]/u.test(normalized.slice(localHeading.index + localHeading[0].length)))
				section={label:localHeading[0],start:raw.index + localHeading.index};
			const line=prefix + normalized;
			const labels=[...line.matchAll(labelRegex)].map(match => ({
				alias:match[0].toLowerCase(),key:aliases.get(match[0].replace(/\s+/g,'').toLowerCase()),start:match.index,end:match.index + match[0].length
			}));
			if (!labels.length) continue;

			let lastRatingEnd=-1;
			for (const match of line.matchAll(numberRegex)) {
				const start=match.index;
				const end=start + match[0].trimEnd().length;
				const scale=match[3] == null ? 10:Number(match[3].replace(',','.'));
				const first=Number(match[1].replace(',','.'));
				const second=match[2] == null ? first:Number(match[2].replace(',','.'));
				const explicit=match[3] != null;
				if (match[0].includes(':') && (!allowColonScale || /^\s*(?:rano|wieczorem|godz|jutro)\b/iu.test(line.slice(end)))) continue;
				const numericContext=getGarsoMentionContext(line,{start,end});
				const contrast=[...numericContext.before.matchAll(/(?:,\s*|\s+)(?:ale|natomiast|a\s+tak(?:\s+to)?)\s+/giu)].at(-1);
				if (contrast) numericContext.before=numericContext.before.slice(contrast.index + contrast[0].length);
				const rhetorical=/byłbym\s+[^.!?]{0,20}niesprawiedliwy[^.!?]{0,30}gdyby\s+nie\s+było\s*$/iu.test(numericContext.before);
				if (!rhetorical && /(?:\b(?:gdyby|byłby|byłaby|byłoby|bylby|bylaby|byloby)|\b(?:było|bylo)\s+by\b)/iu.test(numericContext.before) ||
					/^\s*(?:\?|gdyby\b)/iu.test(line.slice(end))) {continue;}
				if (!explicit && (/^\s*(?:%|:\s*\d|w\s+\d|do\s+\d|na\s+\d)/iu.test(line.slice(end)) ||
					/\d\s*(?:w|:|do|na)\s*$/iu.test(line.slice(0,start)))) {continue;}
				if (![5,10].includes(scale) || /^\s*\/\s*\d/.test(line.slice(end,end + 12)) || !explicit && /^(?:\s*(?:zł|pln|cm|kg|lat|lata|razy|osoby|osób|osob|pokoje|pokoi|telefony|telefonów|pietro|piętro|x|minut\p{L}*|min|godzin\p{L}*|godz|dni|dzień|dzien|h)\b)/iu.test(line.slice(end))) continue;
				const previousRatingEnd=lastRatingEnd;
				if (explicit && first <= scale && second <= scale) lastRatingEnd=end;
				const candidates=labels.filter(label => {
					if (label.end > start || label.start < previousRatingEnd) return false;
					const prefix=line.slice(0,label.start);
					const gap=line.slice(label.end,start);
					if (/^[\s:=–—-]*(?:nie\s+(?:było|bylo|oceniam)|bez\s+oceny)/iu.test(gap)) return false;
					const heading=label === labels[0] && label.start < 25 && !/\d/.test(prefix);
					const clause=/(?:^|[.!?;,–\u2014-])\s*$/.test(prefix);
					const direct=/^[\s:=(\[–\u2014-]*(?:(?:oceniam(?:\s+na)?|daj[eę]|mocne|solidne)\s*)?$/iu.test(gap);
					const previous=labels.findLast(other => other.end <= label.start);
					const followsLabel=previous && label.start - previous.end < 25 && !/[.!?;\d]/.test(line.slice(previous.end,label.start));
					if (!(heading || clause || (direct && followsLabel))) return false;
					if (!explicit) {
						const nextLabel=labels.find(item => item.start > start);
						const remainder=line.slice(end,nextLabel?.start);
						return direct && !/\d\s*(?:[+−-]?\s*)\/\s*\d/.test(remainder);
					}
					return heading || /^\s*[:=–\u2014-]/.test(gap) ? gap.length <= 800:gap.length <= 40 && (label.key !== 'girl' || gap.length <= 24) && !/[.!?](?!\s*\(?$)/.test(gap);
				});
				const nearest=labels.findLast(item => item.end <= start);
				const closerDirect=nearest && /^[\s:=(\[–\u2014-]*$/u.test(line.slice(nearest.end,start));
				const inherited=nearest?.alias === 'ocena' && candidates.find(item => item.key !== 'summary');
				const candidate=inherited || candidates.at(-1);
				const label=closerDirect && !inherited && !candidates.includes(nearest) && nearest.key !== candidate?.key ? null:candidate;
				let selected=label ? [label]:[];
				if (!selected.length && explicit) {
					const reverse=labels.find(item => item.start >= end && /^[\s–\u2014:-]*$/.test(line.slice(end,item.start)));
					if (reverse) {
						selected=[reverse];
						for (const item of labels.filter(item => item.start >= reverse.end)) {
							if (!/^\s*(?:[/+&,]|i|oraz)\s*$/iu.test(line.slice(selected.at(-1).end,item.start))) break;
							selected.push(item);
						}
					}
				}
				if (!selected.length) {continue;}
				const address=explicit && first > scale && second <= scale && selected[0].key === 'location' && /\p{L}{3,}\s+$/u.test(line.slice(selected[0].end,start)) && /\s[-–\u2014]\s/.test(match[0]);
				if ((!address && first > scale) || second > scale) {continue;}
				const value=((address ? second:(first + second) / 2) * 10) / scale;
				const before=labels.filter(item => item.end <= selected[0].start).reverse();
				for (const item of before) {
					if (!/^\s*(?:[/+&,]|i|oraz|and)?\s*$/i.test(line.slice(item.end,selected[0].start))) break;
					selected.unshift(item);
				}
				for (const item of labels.filter(item => item.start >= selected.at(-1).end && item.end <= start)) {
					if (!/^\s*(?:[/+&,]|i|oraz|and)?\s*$/i.test(line.slice(selected.at(-1).end,item.start))) break;
					selected.push(item);
				}
				if (selected.some(item => item.key !== 'summary')) selected=selected.filter(item => item.key !== 'summary');
				for (const key of new Set(selected.map(item => item.key))) {
					(ratings[key] ||= []).push(value);

				}
				lastRatingEnd=Math.max(end,...selected.map(item => item.end));
			}
		}
		return Object.fromEntries(Object.entries(ratings).map(([key,values]) => [key,avg(values)]));
	}
	function regexSpans(text,regex) {
		const flags=regex.flags.includes('g') ? regex.flags:`${regex.flags}g`;
		const rx=garsoParserUnicodeRegex(new RegExp(regex.source,flags));
		return [...String(text || '').matchAll(rx)].map(match => ({
			start:match.index, end:match.index + match[0].length
		}));
	}
	function spansOverlap(a,b) {
		return a.start < b.end && b.start < a.end;
	}
	function buildGarsoMentionEvidence(post,spans,scopes=getGarsoPostScopes(String(post?.text || '')),category='') {
		const sourceText=String(post?.text || '');
		const first=(Array.isArray(spans) ? spans:[])[0] || {
			start:0, end:Math.min(sourceText.length,1)
		};
		const scope=scopes.find(item => item.start <= first.start && item.end > first.start) || {start:0,end:sourceText.length,subject:''};
		const start=Math.max(scope.start,Number(first.start) - 105);
		const end=Math.min(scope.end,Number(first.end) + 145);
		const context=getGarsoMentionContext(sourceText,first);
		const visit=context.sentence.match(/\b(?:po|podczas|na)\s+(?:pierwszej|drugiej|ostatniej|poprzedniej)\s+wizy(?:cie|ty)/iu)?.[0] || '';
		const fragment=sourceText.slice(first.start,first.end);
		const aspect=category !== 'zdjęcia' ? '':/nieaktualn|sprzed\s+lat/iu.test(fragment) ? 'aktualność':/chud|grub|wyższ|niższ|starsz|młodsz/iu.test(fragment) ? 'wygląd':'zgodność zdjęć';
		const snippet=sourceText.slice(start,end).replace(/\s+/g,' ').trim();
		return {
			topicTitle:normalizeEscortAdText(post?.topicTitle) || 'Temat Garso',
			url:post?.pageUrl ? normalizeTopicUrl(post.pageUrl):'', author:normalizeEscortAdText(post?.author),
			date:normalizeEscortAdText(post?.date), postId:String(post?.postId || ''), start:first.start, end:first.end,
			scope:scope.subject ? `Sekcja: ${scope.subject}`:'Post w temacie; osoba nieustalona', visit, aspect,
			snippet:`${start > 0 ? '…':''}${snippet}${end < sourceText.length ? '…':''}`, match:sourceText.slice(
				Math.max(0,Number(first.start)), Math.max(0,Number(first.end)) ).replace(/\s+/g,' ').trim().slice(0,120)
		};
	}
	function createExplicitMentionAnalyzer() {
		const {rules,extraPositive,extraNegative,extraIgnored}=createExplicitMentionAnalyzer.rules ||= (() => {
		const agencyWord=String.raw`(?:agenc(?:j|yjn)\p{L}*|agentur\p{L}*)`;
		const fraudWord=String.raw`(?:oszust\p{L}*|oszuś\p{L}*)`;
		const scamWord=String.raw`(?:scam\p{L}*|skam\p{L}*)`;
		const swapWord=String.raw`(?:podmian\p{L}*|podmien\p{L}*)`;
		const photoWord=String.raw`(?:zdj[eę](?:ci\p{L}*|ć|c(?!\p{L}))|fot\p{L}*)`;
		const wordToken=source => `(?<!\\p{L})(?:${source})(?!\\p{L})`;
		const agencyToken=wordToken(agencyWord);
		const fraudToken=wordToken(fraudWord);
		const scamToken=wordToken(scamWord);
		const swapToken=wordToken(swapWord);
		const photoToken=wordToken(photoWord);
		const gfeNegative=/(?:(?<!\p{L})(?:nie\s+(?:jest|był[oa]?|byl[oa]?|b[eę]dzie|ma|czuć|czuc|do[śs]wiadcz\p{L}*)(?:\s+to)?|bez|zero|brak\p{L}*|zabrak\p{L}*|[żz]adnego)(?:\s+[\p{L}\p{N}%]+){0,6}\s+gfe\b|\bnie\s+(?:(?:serwis|klimat)\s+)?gfe\b|\bgfe\s*(?:[-–\u2014:,]\s*)?0\s*%|\bnie\s+(?:uświadczysz|uraczysz)(?:\s+\p{L}+){0,8}\s+gfe\b|\bgfe\b[\s:–\u2014-]+(?:(?:to|tu|tutaj|u\s+niej|też|tez|raczej|zdecydowanie|absolutnie)\s+){0,4}nie\s+(?:jest|był[oa]?|byl[oa]?|ma|uświadczysz|uraczysz|doświadcz\p{L}*|doswiadcz\p{L}*|tam)\b|\bgfe\b[^.!?\n]{0,80}\b(?:to\s+)?(?:absolutnie\s+)?nie\s+(?:ten|taki)\s+adres\b|\bdalek\p{L}*\s+(?:(?:temu|tutaj|tu|jej|mu|było|bylo)\s+)?(?:od|do)\s+gfe\b|\bdo\s+gfe\s+(?:to\s+)?daleko\b|\bgfe\s+(?:bym\s+)?nie\s+liczył\p{L}*|\bgfe\s+(?:to\s+)?nic\s+z\s+tego)/giu;
		const gfeIgnored=/(?:\bnie\s+do\s+ko[nń]ca\s+gfe\b|\bnie\s+oczek\p{L}*(?:\s+\p{L}+){0,3}\s+gfe\b|\b(?:deklarowan\p{L}*|szukam|oczekuj[eę]|preferuj[eę]|byłoby|było\s+by|byloby|bylo\s+by)(?:\s+\p{L}+){0,3}\s+gfe\b)/giu;
		const wtopaNegative=/(?:\b(?:bez|brak|zero)\s+(?:[żz]adnej\s+)?wtop\p{L}*|\bbez\s+obaw\s+o\s+(?:\p{L}+\s+){0,2}wtop\p{L}*|\bani[\s,;:–\u2014-]+(?:to\s+)?(?:[żz]adna\s+)?wtop\p{L}*|\bnie\s*(?:(?:był[oa]?|byl[oa]?|jest|ma|b[eę]dzie|czułem|czulem)\s+)?(?:to\s+)?(?:mowy\s+o\s+|[żz]adnej\s+)?wtop\p{L}*|\bwtop\p{L}*(?:\s+\p{L}+){0,3}\s+nie\s*(?:był[oa]?|byl[oa]?|jest|ma|b[eę]dzie|powinno\s+by[cć])(?!\p{L})|\bnie\s+(?:traktuj\p{L}*|okre[śs]li[łl]?\p{L}*)(?:\s+\p{L}+){0,3}\s+(?:jak|jako)\s+wtop\p{L}*)/giu;
		const cooperativeNegative=/(?:\b(?:nie\s+jest(?:\s+to)?|to\s+nie\s+jest)\b(?:\s+\p{L}+){0,4}\s+sp[oó]łdzieln\p{L}*|\b(?:bez|brak)\s+sp[oó]łdzieln\p{L}*|\bsp[oó]łdzieln\p{L}*(?:\s+\p{L}+){0,3}\s+nie\s+jest\b)/giu;
		const agencyNegative=new RegExp(
			String.raw`(?:\bnie\s+${agencyToken}|\b(?:nie\s+jest(?:\s+to)?|to\s+nie\s+jest)\b(?:\s+\p{L}+){0,4}\s+${agencyToken}|\b(?:bez|brak)\s+${agencyToken}|${agencyToken}(?:\s+\p{L}+){0,3}\s+nie\s+jest\b)`,
			'giu' );
		const privateNegative=/(?:\bnie\s+(?:jest|działa|dziala|pracuje|przyjmuje)(?:\s+to)?(?:\s+\p{L}+){0,3}\s+prywatnie\b|\bprywatnie(?:\s+\p{L}+){0,2}\s+nie\s+(?:jest|działa|dziala|pracuje|przyjmuje)\b)/giu;
		const makeFraudNegativeRegex=token => new RegExp(
			String.raw`(?:\b(?:nie\s+(?:jest|ma|są|sa|była|byla|było|bylo|były|byly)(?:\s+to)?|to\s+nie\s+(?:jest|są|sa))(?:\s+\p{L}+){0,4}\s+${token}|\b(?:to\s+)?nie\s+${token}|\b(?:bez|brak|zero)\s+(?:[żz]adn\p{L}*\s+)?${token}|(?<!\p{L})[żz]adn\p{L}*\s+${token}|\bani[\s,;:–\\u2014-]+(?:to\s+)?${token}|${token}(?:\s+\p{L}+){0,3}\s+nie\s+(?:jest|są|sa)\b|\bnigdy\s+nie\s+przejechałem\s+si[eę]\s+na\s+${token})`,
			'giu' );
		const fraudNegative=makeFraudNegativeRegex(fraudToken);
		const scamNegative=makeFraudNegativeRegex(scamToken);
		const swapNegative=makeFraudNegativeRegex(swapToken);
		const wtopaIgnored=/(?:\b(?:iloś(?:ć|ci)|dużo|wiele|mnóstwo|sporo)\s+(?:różnych\s+)?wtop\p{L}*(?:\s+w\s+\p{L}+)?|\bwtop\p{L}*\s+si[eę]\s+w\s+tłum\p{L}*|\b(?:boj[eę]\s+si[eę]|bałem\s+si[eę]|balem\s+sie|(?<!bez\s)obaw\p{L}*|ryzyko)(?:\s+\p{L}+){0,3}\s+wtop\p{L}*)/giu;
		const riskIgnored=new RegExp(
			String.raw`\b(?:o\s+ile|[żz]eby|aby|boj[eę]\s+si[eę]|obawiam\s+si[eę])(?:\s+\p{L}+){0,5}\s+(?:${fraudToken}|${scamToken}|${swapToken})`,
			'giu' );
		const photoConsistent=new RegExp(
			String.raw`(?:\bprzypomina\s+(?:t[eę]\s+)?(?:lask\p{L}*|dziewczyn\p{L}*|pann\p{L}*)\s+ze?\s+(?:\p{L}+\s+){0,4}${photoToken}|${photoToken}(?:(?!\bnie\s+(?:(?:są|sa|jest|były|byly)\s+)?(?:jej|zgodn\p{L}*|aktualn\p{L}*|autentyczn\p{L}*|prawdziw\p{L}*)\b)[^.!?;\n]){0,35}\b(?:zgodn\p{L}*|aktualn\p{L}*|autentyczn\p{L}*|prawdziw\p{L}*)\b|${photoToken}(?:(?!\bnie\s+(?:są|sa|jest|były|byly)\s+jej\b)[^.!?;\n]){0,18}\b(?:są|sa|to)\s+jej\b|(?<!nie\s)\b(?:są|sa|to)\s+jej\b[^.!?;\n]{0,18}${photoToken}|\b(?:zgodn\p{L}*|ta\s+sama)\b[^.!?;\n]{0,25}\b(?:na\s+)?${photoToken}|(?<!nie\s)\b(?:wygl[aą]d\p{L}*|jest)\b[^.!?;\n]{0,18}\bjak\s+na\s+${photoToken}|\b(?:dziewczyn|kobiet|pann)\p{L}*\b[^.!?;\n]{0,16}\b(?:jest\s+)?(?:ta\s+sama\s+)?(?:co|ze)\s+(?:na\s+)?${photoToken}|${photoToken}[^.!?;\n]{0,30}\bnie\s+(?:kłami\p{L}*|klami\p{L}*)\b)`,
			'giu' );
		const photoInconsistent=new RegExp(
			String.raw`(?:(?<!\p{L})[żz]adna\s+ze\s+${photoToken}|${photoToken}\s+(?:\p{L}+\s+){0,2}kradzione\s+z\s+internetu|${photoToken}\s+na\s+E\.\s+(?:\p{L}+\s+){0,2}nieaktualn\p{L}*|\b(?:dziewczyn|kobiet|pann)\p{L}*\s+ze\s+${photoToken}\s+nie\s+jest\b|\bnie\s+(?:jest|był[oa]?|byl[oa]?)\s+(?:to\s+)?(?:na\s+\d+%\s+)?(?:dziewczyn|kobiet|pann)\p{L}*\s+ze\s+${photoToken}|\bnie\s+podobn\p{L}*\s+do\s+(?:dziewczyn|kobiet|pann)\p{L}*\s+ze\s+${photoToken}|\bniezgodn\p{L}*[^.!?;\n]{0,35}${photoToken}|${photoToken}[^.!?;\n]{0,35}\b(?:niezgodn\p{L}*|fałszyw\p{L}*|falszyw\p{L}*|fake|nieaktualn\p{L}*|przerob\p{L}*)\b|\b(?:fałszyw\p{L}*|falszyw\p{L}*|fake|nieaktualn\p{L}*|przerob\p{L}*)\b[^.!?;\n]{0,25}${photoToken}|${photoToken}[^.!?;\n]{0,24}\bnie\s+(?:(?:są|sa|jest|były|byly)\s+)?(?:jej|zgodn\p{L}*|aktualn\p{L}*|autentyczn\p{L}*|prawdziw\p{L}*)\b|\bnie\s+(?:są|sa|jest|były|byly)\s+jej\b[^.!?;\n]{0,18}${photoToken}|\bnie\s+jej\b[^.!?;\n]{0,18}${photoToken}|\bnie\s+wygl[aą]d\p{L}*[^.!?;\n]{0,35}(?:jak|ze)\s+(?:na\s+)?${photoToken}|\b(?:inn\p{L}*|starsz\p{L}*|młodsz\p{L}*|mlodsz\p{L}*|grubsz\p{L}*|chudsz\p{L}*|niższ\p{L}*|nizsz\p{L}*|wyższ\p{L}*|wyzsz\p{L}*|ładniejsz\p{L}*|ladniejsz\p{L}*)\s+niż\s+(?:na\s+)?${photoToken}|\bnie\s+(?:jest\s+)?(?:tak\s+)?\p{L}+(?:\s+\p{L}+){0,2}\s+jak\s+(?:na\s+)?${photoToken}|\b(?:pann|dziewczyn|kobiet)\p{L}*\b[^.!?;\n]{0,25}\bnie\s+wygl[aą]d\p{L}*[^.!?;\n]{0,20}\bjak\s+(?:na|w)\s+ogłoszen\p{L}*|\bnie\s+wygl[aą]d\p{L}*[^.!?;\n]{0,20}\bjak\s+żadn\p{L}*\s+z\s+(?:dziewczyn|kobiet|osób|osob)\p{L}*\s+przedstawion\p{L}*|\binn\p{L}*\s+(?:dziewczyn|kobiet|pann)\p{L}*\s+ni[żz]\s+(?:ta\s+)?(?:ze|z|na)\s+${photoToken}|\bnie\s+(?:ma\s+|ta\s+)?(?:dziewczyn|kobiet|pann)\p{L}*[^.!?;\n]{0,12}\b(?:ze|z)\s+${photoToken}|${photoToken}(?:(?!\bnie\s+(?:kłami\p{L}*|klami\p{L}*)\b)[^.!?;\n]){0,30}\b(?:kłami\p{L}*|klami\p{L}*)\b)`,
			'giu' );
		const photoAiGenerated=new RegExp(
			String.raw`(?:${photoToken}(?:(?!\bnie\b)[^.!?;\n]){0,55}\b(?:wygenerowan\p{L}*|generowan\p{L}*)\b[^.!?;\n]{0,40}\b(?:ai|sztuczn\p{L}*\s+inteligenc\p{L}*)\b|\b(?:wygenerowan\p{L}*|generowan\p{L}*)\b[^.!?;\n]{0,40}\b(?:ai|sztuczn\p{L}*\s+inteligenc\p{L}*)\b[^.!?;\n]{0,55}${photoToken})`,
			'giu' );
		const photoNegative=new RegExp( `(?:${photoInconsistent.source}|${photoAiGenerated.source})`, 'giu' );
		const photoIgnored=new RegExp(
			String.raw`(?:\bczy\s+(?:\p{L}+\s+){0,5}${photoToken}|${photoToken}\s+(?:(?:wcale|absolutnie)\s+)?nie\s+(?:(?:są|sa|były|byly)\s+)?(?:przerob\p{L}*|retuszowan\p{L}*)|\bnie\s+(?:(?:są|sa)\s+)?(?:przerob\p{L}*|retuszowan\p{L}*)\s+${photoToken}|\b(?:do|o)\s+zgodno[śs]ci\s+${photoToken})`,
			'giu' );
		const recommendationWord=String.raw`polec(?:a\p{L}*|i(?:ć|ł\p{L}*|li\p{L}*)|e(?:ni\p{L}*|ń)|on\p{L}*)`;
		const recommendationToken=wordToken(recommendationWord);
		const recommendationIgnored=new RegExp(
			String.raw`(?:${recommendationToken}\s+(?:raczej\s+)?(?:zostaw\p{L}*|zapark\p{L}*|parkow\p{L}*|lektur\p{L}*|artykuł\p{L}*|forum|czyta\p{L}*|przeczyta\p{L}*|wej[śs]\p{L}*|zajrze\p{L}*|link\p{L}*|inn(?:e|ą|a|ego|ej|ych)(?!\p{L}))|\b(?:wydaj\p{L}*|daj\p{L}*)\s+poleceni\p{L}*|\bnie\s+odradz\p{L}*|\bnie\s+polecam\s*,?\s*(?:ani|i)\s+nie\s+odradz\p{L}*|\b(?:z|zgodnie\s+z)\s+poleceni\p{L}*|\b(?:kto[śs]|kt[oó]ry[śs]|kogo)\s+(?:mi\s+)?${recommendationToken})`,
			'giu' );
		const niceAtmosphere=/(?:\bmił\p{L}*(?:\s+\p{L}+){0,2}\s+atmosfer\p{L}*|\batmosfer\p{L}*(?:\s+\p{L}+){0,3}\s+mił\p{L}*)/giu;
		const niceAtmosphereNegative=/(?:\bniemił\p{L}*(?:\s+\p{L}+){0,2}\s+atmosfer\p{L}*|\bnie\s+(?:(?:ma|jest|był\p{L}*)\s+)?mił\p{L}*(?:\s+\p{L}+){0,2}\s+atmosfer\p{L}*|\b(?:bez|brak)\s+(?:\p{L}+\s+){0,2}mił\p{L}*(?:\s+\p{L}+){0,2}\s+atmosfer\p{L}*|\batmosfer\p{L}*(?:\s+\p{L}+){0,3}\s+nie\s+(?:jest|był\p{L}*)\s+mił\p{L}*)/giu;
		const extraNegative={
			rekomendacja:/\bnie\s+(?:jest\s+)?(?:do|wart\p{L}*|godn\p{L}*)\s+poleceni\p{L}*/giu,
			GFE:/(?:\b0\s*%?\s*gfe\b|\bnie\s+(?:mamy|miałem|mialem|miały|mialy|były|byly)\b(?:\s+\p{L}+){0,4}\s+gfe\b|\bgfe\b[\s:–—-]+(?:(?:to|raczej|też|tez|już|juz|tam|tego)\s+){0,6}nie\s+(?:u[śs]wiadcz\p{L}*|znajdz\p{L}*|jej\s+klimat\p{L}*)|\bzabrak\p{L}*\s+tej\s+magii[.!]\s*Tego\s+klimatu\s+gfe\b)/giu,
			wtopa:/(?:\bnie\s+(?:jest|była|byla|było|bylo)\s+(?:to\s+)?(?:jakaś|jakas|żadna|zadna)\s+wtop\p{L}*|\bnie\s+przytrafi\p{L}*\s+(?:mi\s+)?si[eę]\s+(?:jeszcze\s+)?wtop\p{L}*|\bwtop\p{L}*\s+(?:(?:raczej|też|tez|tutaj|tu)\s+){0,3}(?:brak|nie\s+zalicz\p{L}*))/giu,
			'zdjęcia':/(?:\bnie\s+ma\s+(?:tam\s+|tutaj\s+)?(?:kobiet|dziewczyn|panien)\p{L}*\s+ze?\s+zdj[eę](?:ci\p{L}*|ć|c)|\b(?:dziewczyn|kobiet|pann)\p{L}*\s+ze\s+zdj[eę](?:ci\p{L}*|ć|c)\s+(?:tam\s+|tutaj\s+)?nie\s+ma\b|\bnie\s+(?:ta|taka)\s+(?:pann|dziewczyn|kobiet)\p{L}*\s+co\s+na\s+zdj[eę]ci\p{L}*|\bz\s+dziewczyn\p{L}*\s+ze\s+zdj[eę](?:ci\p{L}*|ć|c)\s+ma\s+wsp[oó]ln\p{L}*\s+tylko\s+\p{L}+|\b(?:zdj[eę](?:ci\p{L}*|ć|c)|foty)\s+(?:ze\s+strony\s+)?pokazuj\p{L}*\s+inn\p{L}*\s+osob\p{L}*|\ba\s+na\s+miejscu\s*\.{0,3}\s*panna\s+(?:kompletnie\s+)?niepodobn\p{L}*)/giu,
			atmosfera:/(?:\b(?:niemił\p{L}*|niemil\p{L}*|nieprzyjazn\p{L}*|nieprzyjemn\p{L}*|fataln\p{L}*|kiepsk\p{L}*|sztywn\p{L}*)\s+(?:\p{L}+\s+){0,2}atmosfer\p{L}*|\batmosfer\p{L}*\s+(?:(?:jest|była|byla|bardzo|raczej)\s+){0,3}(?:niemił\p{L}*|niemil\p{L}*|nieprzyjazn\p{L}*|nieprzyjemn\p{L}*|fataln\p{L}*|kiepsk\p{L}*|sztywn\p{L}*)|\bnie\s+(?:jest|ma|była|byla|było|bylo)\s+(?:to\s+)?(?:jak\p{L}*\s+)?(?:super\s+|świetn\p{L}*\s+|swietn\p{L}*\s+|fajn\p{L}*\s+|przyjemn\p{L}*\s+)?atmosfer\p{L}*|\bbrak\p{L}*\s+(?:(?:jakiejkolwiek|dobrej|miłej|milej)\s+)?(?:atmosfer\p{L}*|klimatu))/giu
		};
		const extraPositive={
			'zdjęcia':/\bzdj[eę]cia\b[^.!?;\n]{0,45}\b(?:należą\s+do\s+niej|naleza\s+do\s+niej|nie\s+odbiegają\s+od\s+rzeczywistości)/giu,
			atmosfera:/(?:\b(?:mił\p{L}*|mil\p{L}*|świetn\p{L}*|swietn\p{L}*|super|fajn\p{L}*|przyjemn\p{L}*|przyjazn\p{L}*|wspaniał\p{L}*|wspanial\p{L}*)\s+(?:\p{L}+\s+){0,2}atmosfer\p{L}*|\batmosfer\p{L}*\s+(?:(?:jest|była|byla|bardzo|naprawdę|naprawde|wręcz|wrecz)\s+){0,3}(?:mił\p{L}*|mil\p{L}*|świetn\p{L}*|swietn\p{L}*|super|fajn\p{L}*|przyjemn\p{L}*|przyjazn\p{L}*))/giu
		};
		const extraIgnored={
			rekomendacja:/\bnie\s+(?:polec\p{L}*\s*,?\s*(?:ani|i)\s+nie\s+odradz\p{L}*|odradz\p{L}*\s*,?\s*(?:ani|i)\s+nie\s+polec\p{L}*)/giu
		};

		const rules=[ ['GFE','normal',/\bgfe\b/giu,gfeNegative,gfeIgnored],
			['wtopa','normal',/\bwtop\p{L}*/giu,wtopaNegative,wtopaIgnored],
			['obsuwa','normal',/\bobsuw\p{L}*/giu,/(?:\b(?:bez|brak)\s+(?:żadnej\s+)?obsuw\p{L}*|\bnie\s+było\s+(?:żadnej\s+)?obsuw\p{L}*|\bobsuw\p{L}*\s+(?:nie\s+było|brak))/giu,null],
			['spółdzielnia','normal',/\bsp[oó]łdzieln\p{L}*/giu,cooperativeNegative,null],
			['agencja','normal',new RegExp(agencyToken,'giu'),agencyNegative,null],
			['atmosfera','normal',niceAtmosphere,niceAtmosphereNegative,null],
			['prywatnie','normal',/(?:\b(?:przyjmuje|pracuje|działa|dziala|zaprasza\p{L}*|wyje[żz]d[żz]a)(?:\s+\p{L}+){0,5}\s+prywatnie\b|\bprywatnie\s+(?:przyjmuje|pracuje|działa|dziala|na\s+masa[żz]\p{L}*)\b|^prywatnie[.!]?\s*$)/gimu,privateNegative,null],
			['oszustwo','normal',new RegExp(fraudToken,'giu'),fraudNegative,riskIgnored],
			['scam','normal',new RegExp(scamToken,'giu'),scamNegative,riskIgnored],
			['podmianka','normal',new RegExp(swapToken,'giu'),swapNegative,riskIgnored],
			['zdjęcia','photo',photoConsistent,photoNegative,photoIgnored], [ 'rekomendacja', 'normal',
				new RegExp(recommendationToken,'giu'),
				new RegExp(String.raw`(?:\bnie\s+(?:(?:mog\p{L}*|chc\p{L}*|b[eę]d\p{L}*)\s+(?:\p{L}+\s+){0,3})?${recommendationToken}|${recommendationToken}\s+nie\s+mog\p{L}*|\bodradz\p{L}*|\bbez\s+polecenia\b|\bpolecam\s+(?:to\s+)?(?:na\s+ni[aą]\s+)?uwa[żz]a\p{L}*)`,'giu'),
				recommendationIgnored ] ];
			return {rules,extraPositive,extraNegative,extraIgnored};
		})();
		const stats={};
		const createBucket=() => ({
			positive:0, negative:0, positiveEvidence:[], negativeEvidence:[]
		});
		for (const [key] of rules) stats[key]=createBucket();
		function add(post) {
			const text=String(post?.text || '');
			const gfePraise=regexSpans(text,/(?:\bnigdy\s+takiego\s+gfe\s+nie\s+do[śs]wiadczy[łl]em\b|\b(?:nigdy|jeszcze)\s+nie\s+(?:miałem|mialem|do[śs]wiadczy[łl]em)\s+takiego\s+gfe\b)/giu);
			const scopes=getGarsoPostScopes(text);
			const answers=regexSpans(text,/\bczy\s+polecam\s*(?:\?|[-–—:])\s*(?:tak|oczywiście|oczywiscie|jak\s+najbardziej|nie)(?!\p{L})/giu);
			const questions=regexSpans(text,/[^.!?\n]*\?|https?:\/\/[^\s<>]+/giu).filter(question => !answers.some(answer => spansOverlap(question,answer)));
			for (const [key,,positiveRegex,negativeRegex,ignoredRegex] of rules) {
				const bucket=stats[key];
				const ignoredSpans=[...questions,...(ignoredRegex ? regexSpans(text,ignoredRegex):[]),...(extraIgnored[key] ? regexSpans(text,extraIgnored[key]):[])];
				const accept=(span,tone) => {
					const acrossScopes=!scopes.some(scope => scope.start <= span.start && scope.end >= span.end);
					const reason=acrossScopes ? 'różne sekcje osób':ignoredSpans.some(ignored => spansOverlap(span,ignored)) ? 'pytanie lub wyrażenie wyłączone':classifyGarsoMention(text,key,span,tone);
					return !reason;
				};
				const negativeSpans=[...regexSpans(text,negativeRegex),...(extraNegative[key] ? regexSpans(text,extraNegative[key]):[]),
					...(key === 'rekomendacja' ? answers.filter(span => /\bnie\s*$/iu.test(text.slice(span.start,span.end))):[])]
					.filter(span => accept(span,'negative') && (key !== 'GFE' || !gfePraise.some(praise => spansOverlap(span,praise))))
					.sort((a,b) => a.start - b.start || b.end - a.end);
				if (negativeSpans.length) {
					bucket.negative++;
					bucket.negativeEvidence.push( buildGarsoMentionEvidence(post,negativeSpans,scopes,key) );
				}
				const positiveSpansRaw=[...regexSpans(text,positiveRegex),...(extraPositive[key] ? regexSpans(text,extraPositive[key]):[]),
					...(key === 'rekomendacja' ? answers.filter(span => !/\bnie\s*$/iu.test(text.slice(span.start,span.end))):[])];
				const positiveSpans=positiveSpansRaw.filter(mention => !negativeSpans.some(negated => spansOverlap(mention,negated)) && accept(mention,'positive'))
					.sort((a,b) => a.start - b.start || b.end - a.end);
				if (positiveSpans.length) {
					bucket.positive++;
					bucket.positiveEvidence.push( buildGarsoMentionEvidence(post,positiveSpans,scopes,key) );
				}
			}
		}
		return {
			add, result:() => stats
		};
	}
	function parseGarsoPostDate(value) {
		const text=String(value || '') .toLocaleLowerCase('pl-PL') .replace(/\u00a0/g,' ') .replace(/\s+/g,' ') .trim();
		if (!text) return null;
		const relativeDate=new Date();
		if (/\b(?:dzisiaj|today)\b/.test(text)) {
			return Date.UTC( relativeDate.getFullYear(), relativeDate.getMonth(), relativeDate.getDate() );
		}
		if (/\b(?:wczoraj|yesterday)\b/.test(text)) {
			relativeDate.setDate(relativeDate.getDate() - 1);
			return Date.UTC( relativeDate.getFullYear(), relativeDate.getMonth(), relativeDate.getDate() );
		}
		let match=text.match(/\b(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})\b/);
		if (match) {
			const time=Date.UTC(Number(match[1]),Number(match[2]) - 1,Number(match[3]));
			return Number.isFinite(time) ? time:null;
		}
		match=text.match(/\b(\d{1,2})[.\-/](\d{1,2})[.\-/](\d{4})\b/);
		if (match) {
			const time=Date.UTC(Number(match[3]),Number(match[2]) - 1,Number(match[1]));
			return Number.isFinite(time) ? time:null;
		}
		const polishMonths={
			styczeń:0,stycznia:0, luty:1,lutego:1, marzec:2,marca:2, kwiecień:3,kwietnia:3, maj:4,maja:4,
			czerwiec:5,czerwca:5, lipiec:6,lipca:6, sierpień:7,sierpnia:7, wrzesień:8,września:8,wrzesnia:8,
			październik:9,października:9,pazdziernika:9, listopad:10,listopada:10, grudzień:11,grudnia:11
		};
		match=text.match(
			/\b(\d{1,2})\s+(styczeń|stycznia|luty|lutego|marzec|marca|kwiecień|kwietnia|maj|maja|czerwiec|czerwca|lipiec|lipca|sierpień|sierpnia|wrzesień|września|wrzesnia|październik|października|pazdziernika|listopad|listopada|grudzień|grudnia)\s+(\d{4})\b/
		);
		if (!match) return null;
		const time=Date.UTC(Number(match[3]),polishMonths[match[2]],Number(match[1]));
		return Number.isFinite(time) ? time:null;
	}
	function formatIsoDateFromTimestamp(timestamp) {
		const time=Number(timestamp);
		return Number.isFinite(time) ? new Date(time).toISOString().slice(0,10):null;
	}
	function detectOralRatingVariants(text) {
		const value=String(text || '');
		return {
			fbg:/(?:\b(?:fbg|fgb)\b|\bfrancuz\p{L}*.{0,30}\bbez\s+gum\p{L}*)/iu.test(value),
			fwg:/(?:\bfwg\b|\bfrancuz\p{L}*.{0,30}\b(?:w|z)\s+gum\p{L}*)/iu.test(value)
		};
	}
	async function buildRatingStats(posts,cachedMentions=null,cancelToken=null) {
		const reviews=[];
		const categoryValues=Object.fromEntries(CATEGORY_DEFS.map(d => [d.key,[]]));
		const oralRatingVariants={fbg:false,fwg:false};
		const mentionAnalyzer=cachedMentions ? null:createExplicitMentionAnalyzer();
		let chunkStarted=performance.now();
		for (let index=0; index < posts.length; index++) {
			cancelToken?.throwIfCancelled();
			if (performance.now() - chunkStarted >= 8) {
				await yieldHelperWork();
				cancelToken?.throwIfCancelled();
				chunkStarted=performance.now();
			}
			const post=posts[index];
			mentionAnalyzer?.add(post);
			const ratings=post.ratingValues || extractRatingsFromPost(post.text,post);
			const componentValues=CATEGORY_DEFS .filter(def => !def.overall && ratings[def.key] != null)
				.map(def => ratings[def.key]);
			const vals=componentValues.length
				? componentValues
				:(ratings.summary == null ? []:[ratings.summary]);
			if (!Object.keys(ratings).length || !vals.length) continue;
			for (const [k,v] of Object.entries(ratings)) categoryValues[k].push(v);
			if (ratings.oral != null) {
				const variants=post.oralVariants || detectOralRatingVariants(post.text);
				oralRatingVariants.fbg ||= variants.fbg;
				oralRatingVariants.fwg ||= variants.fwg;
			}
			reviews.push({index,mean:avg(vals),ratings,post});
		}
		cancelToken?.throwIfCancelled();
		const reviewMeans=reviews.map(r => r.mean);
		const datedReviews=reviews .map(review => ({
				time:parseGarsoPostDate(review.post?.date), url:review.post?.pageUrl
					? normalizeTopicUrl(review.post.pageUrl)
					:null, mean:review.mean, index:review.index
			})) .filter(item => Number.isFinite(item.time)) .sort((a,b) => a.time - b.time || a.index - b.index);
		const chronologicalLastReviews=datedReviews.length === reviews.length
			? datedReviews.slice(-5)
			:reviews.slice(-5).map(review => ({mean:review.mean}));
		const last5=chronologicalLastReviews.map(review => review.mean);
		const datedPosts=posts .map(post => ({
				time:parseGarsoPostDate(post?.date), url:post?.pageUrl ? normalizeTopicUrl(post.pageUrl):null
			})) .filter(item => Number.isFinite(item.time)) .sort((a,b) => a.time - b.time);
		const reviewDateSource=reviews.length
			? (datedReviews.length ? datedReviews:datedPosts)
			:[];
		const categories={};
		const oralCategoryLabel=oralRatingVariants.fbg !== oralRatingVariants.fwg
			? (oralRatingVariants.fbg ? 'FBG':'FWG')
			:'Francuz';
		for (const def of CATEGORY_DEFS) {
			const values=categoryValues[def.key];
			categories[def.key]={
				key:def.key, label:def.key === 'oral' ? oralCategoryLabel:def.label, mean:round1(avg(values)), count:values.length,
				min:values.length ? round1(Math.min(...values)):null, max:values.length ? round1(Math.max(...values)):null
			};
		}
		return {
			reviews, reviewCount:reviews.length, mean:round1(avg(reviewMeans)), median:round1(median(reviewMeans)),
			min:reviewMeans.length ? round1(Math.min(...reviewMeans)):null,
			max:reviewMeans.length ? round1(Math.max(...reviewMeans)):null, last5Mean:round1(avg(last5)),
			last5ReviewCount:last5.length, firstReviewDate:formatIsoDateFromTimestamp(reviewDateSource[0]?.time),
			lastReviewDate:formatIsoDateFromTimestamp(reviewDateSource.at(-1)?.time),
			firstReviewUrl:reviewDateSource[0]?.url || null, lastReviewUrl:reviewDateSource.at(-1)?.url || null, categories,
			postDateBounds:[datedPosts[0],datedPosts.at(-1)].filter(Boolean),
			mentions:cachedMentions || mentionAnalyzer.result()
		};
	}
	function getGarsoRatingCategoryLabel(category) {
		const label=String(category?.label || '').trim();
		const normalizedLabel=label.toLocaleLowerCase('pl-PL');
		if (normalizedLabel === 'francuz / oral') return 'Francuz';
		if (normalizedLabel === 'wygląd dziewczyny') return 'Wygląd';
		if (normalizedLabel === 'akcja / seks') return 'Akcja';
		return label;
	}
	function getGarsoRatingBarColor(mean) {
		const score=Number(mean);
		if (mean == null || !Number.isFinite(score)) {
			return 'color-mix(in srgb, var(--vm-ui-panel-text, #fff) 16%, transparent)';
		}
		if (score < 5) return 'var(--vm-ui-danger, #e04b4b)';
		if (score >= 9) return 'var(--vm-ui-success, #4ac46b)';
		const hue=score <= 7
			? ((score - 5) / 2) * 50
			:50 + ((score - 7) / 2) * 72;
		return `hsl(${Math.round(hue)}, 78%, 52%)`;
	}
	function emitGarsoCountState(btn,state,count=null,fromCache=false) {
		if (!btn) return;
		btn.dispatchEvent(new CustomEvent('vm-garso-count-state',{
			detail:{
				state, count:count != null && Number.isFinite(Number(count)) ? Number(count):null, fromCache:!!fromCache
			}
		}));
	}
	function setGarsoSummaryState( btn, state, html, {checkedAt=null,cacheState=null}={} ) {
		if (!btn) return;
		btn.dataset.garsoSummaryState=state;
		btn.dataset.garsoSummaryHtml=html || '';
		if (Number.isFinite(Number(checkedAt)) && Number(checkedAt) > 0) {
			btn.dataset.garsoSummaryCheckedAt=String(Number(checkedAt));
		} else {
			delete btn.dataset.garsoSummaryCheckedAt;
		}
		if (cacheState) btn.dataset.garsoSummaryCacheState=cacheState;
		else delete btn.dataset.garsoSummaryCacheState;
		renderGarsoButton(btn);
		btn.dispatchEvent(new CustomEvent('vm-garso-summary-state',{
			detail:{
				state, html:btn.dataset.garsoSummaryHtml, checkedAt:Number(btn.dataset.garsoSummaryCheckedAt) || null,
				cacheState:btn.dataset.garsoSummaryCacheState || null, firstReviewDate:btn.dataset.garsoFirstReviewDate || null,
				lastReviewDate:btn.dataset.garsoLastReviewDate || null, firstReviewUrl:btn.dataset.garsoFirstReviewUrl || null,
				lastReviewUrl:btn.dataset.garsoLastReviewUrl || null, result:btn._vmGarsoSummaryResult || null
			}
		}));
	}
	function restoreGarsoSummaryFromCache(btn,searchTerm,searchMode) {
		const cached=getGarsoSummaryCache(searchMode,searchTerm);
		if (!cached) return null;
		applySummaryToButton(btn,cached.result,{
			checkedAt:cached.checkedAt, cacheState:cached.fresh ? 'fresh':'stale'
		});
		return cached;
	}
	async function prepareGarsoSummary( btn, searchTerm, topics, forceRefresh=false, operationCancelToken=null ) {
		const searchMode=btn.dataset.garsoSearchMode || '';
		const key=getGarsoTopicsSignature(topics) + ':' + GM_getValue(GARSO_TOPIC_REVISION_KEY,'');
		const persistent=getGarsoSummaryCache(searchMode,searchTerm);
		const sameTopics=persistent && persistent.topicSignature === getGarsoTopicsSignature(topics);
		if (!forceRefresh && persistent?.recentCheck && sameTopics) {
			applySummaryToButton(btn,persistent.result,{
				checkedAt:persistent.checkedAt, cacheState:persistent.fresh ? 'fresh':'stale'
			});
			return persistent.result;
		}
		const progressOptions=sameTopics ? {checkedAt:persistent.checkedAt,cacheState:'refreshing'}:{};
		const renderPending=() => setGarsoSummaryState(btn,sameTopics ? 'refreshing':'loading',
			sameTopics ? summaryToHtml(persistent.result):'<b>Garso - recenzje</b><br>Pobieram treść tematów…',progressOptions);
		renderPending();
		const pending=!forceRefresh && summaryCache.get(key);
		const cancelToken=pending?.cancelToken || operationCancelToken || createOperationCancelToken('Analiza Garso');
		btn._vmGarsoSummaryCancelToken=cancelToken;
		renderPending();
		const renderProgress=progress => {
			if (!progress || !['page','topic-done','stats'].includes(progress.phase)) {
				return;
			}
			let line='Pobieram treść tematów…';
			if (progress.phase === 'page') {
				line=`Temat ${progress.topicIndex + 1}/${progress.topicTotal}` +
					` • ${progress.message || `strona ${progress.page}/${progress.pageTotal}`}`;
			} else if (progress.phase === 'topic-done') {
				line=`Pobrano tematy: ${progress.completedTopics}/${progress.topicTotal}`;
			} else if (progress.phase === 'stats') {
				line=`Obliczam statystyki z ${progress.postCount} postów…`;
			}
			setGarsoSummaryState( btn, sameTopics ? 'refreshing':'loading', `<b>Garso - recenzje</b><br>${escapeHtml(line)}`,
				progressOptions );
		};
		const promise=pending || buildGarsoSummary(
			searchTerm,topics,cancelToken,{forceRefresh,onProgress:renderProgress}).then(compactGarsoSummaryResult);
		promise.cancelToken=cancelToken;
		summaryCache.set(key,promise);
		try {
			const result=await promise;
			const complete=isCompleteGarsoSummary(result,topics);
			if (!complete && summaryCache.get(key) === promise) {
				summaryCache.delete(key);
			}
			const persistedAt=complete
				? setGarsoSummaryCache(searchMode,searchTerm,topics,result)
				:null;
			if (!complete && sameTopics) {
				applySummaryToButton(btn,persistent.result,{
					checkedAt:persistent.checkedAt, cacheState:'stale'
				});
				return persistent.result;
			}
			const checkedAt=persistedAt || Date.now();
			applySummaryToButton(btn,result,{
				checkedAt,
				cacheState:result.unverifiedTopicCount ? 'stale':(persistedAt ? 'fresh':(complete ? 'session':'partial'))
			});
			return result;
		} catch (e) {
			if (summaryCache.get(key) === promise) summaryCache.delete(key);
			if (isOperationCancelledError(e)) {
				if (sameTopics) {
					btn._vmGarsoSummaryResult=persistent.result;
					setGarsoSummaryState( btn, 'cancelled', summaryToHtml(persistent.result), {
							checkedAt:persistent.checkedAt, cacheState:'stale'
						} );
					return persistent.result;
				}
				setGarsoSummaryState( btn, 'cancelled', '<b>Garso - recenzje</b><br>Analiza została przerwana.' );
				return null;
			}
			log('Błąd podsumowania Garso:',e);
			if (sameTopics) {
				applySummaryToButton(btn,persistent.result,{
					checkedAt:persistent.checkedAt, cacheState:'stale'
				});
			} else {
				setGarsoSummaryState( btn, 'error',
					`<b>Garso - recenzje</b><br>Nie udało się pobrać postów: ${escapeHtml(e.message || e)}` );
			}
			return null;
		} finally {
			if (summaryCache.get(key) === promise) summaryCache.delete(key);
			if (btn._vmGarsoSummaryCancelToken === cancelToken) {
				btn._vmGarsoSummaryCancelToken=null;
			}
		}
	}
	async function buildGarsoSummary(searchTerm,topics,cancelToken=null,{forceRefresh=false,onProgress=null}={}) {
		topics=mergeUniqueGarsoTopics(topics);
		cancelToken ||= createOperationCancelToken('Analiza Garso');
		const client=createGarsoTopicCacheClient(cancelToken);
		let completedTopics=0;
		try {
			const results=await watchMapLimit(topics,GARSO_TOPIC_FETCH_CONCURRENT,async (topic,topicIndex) => {
				try {
					cancelToken.throwIfCancelled();
					const value=await client.get(topic,{forceRefresh,onProgress:(page,pageTotal,message,fromCache) =>
						onProgress?.({phase:'page',topicIndex,topicTotal:topics.length,page,pageTotal,message,fromCache})});
					cancelToken.throwIfCancelled();
					if (value?.eligible !== false && !validGarsoTopicAnalysis(value,getGarsoTopicPageContext(topic.url)?.id)) throw new Error('Niepełna analiza tematu.');
					return {topic,value};
				} catch (error) {
					if (cancelToken.cancelled || isOperationCancelledError(error)) throw error;
					log(`Nie udało się odczytać tematu Garso: ${topic.url}`,error);
					return {topic,error};
				} finally {
					onProgress?.({phase:'topic-done',completedTopics:++completedTopics,topicTotal:topics.length,topicIndex});
				}
			});
			cancelToken.throwIfCancelled();
			const ratingPosts=[],topicInfo=[],analyzedTopics=[],excludedTopics=[],mentions={};
			let postCount=0,failedTopicCount=0,unverifiedTopicCount=0,sourceCheckedAt=Date.now(),sourceCheckAttemptAt=Date.now();
			for (const {topic,value,error} of results) {
				if (error) {failedTopicCount++; continue;}
				if (value.eligible === false) {excludedTopics.push({...topic,title:value.title || topic.title}); continue;}
				const summary=value.summary;
				sourceCheckedAt=Math.min(sourceCheckedAt,value.checkedAt);
				sourceCheckAttemptAt=Math.min(sourceCheckAttemptAt,Number(value.postCountCheckedAt) || 0);
				if (value.postCountUnverified) unverifiedTopicCount++;
				topicInfo.push(...summary.topicInfo); analyzedTopics.push(...summary.topics); postCount += summary.postCount;
				for (const post of value.ratingPosts) ratingPosts.push({...post,pageUrl:post.pageUrl || summary.topics[0].url});
				for (const bound of summary.stats.postDateBounds || []) ratingPosts.push({date:formatIsoDateFromTimestamp(bound.time),pageUrl:bound.url,ratingValues:{}});
				for (const [key,bucket] of Object.entries(summary.stats.mentions || {})) {
					const combined=mentions[key] ||= {positive:0,negative:0,positiveEvidence:[],negativeEvidence:[]};
					for (const tone of ['positive','negative']) {
						combined[tone] += Number(bucket[tone]) || 0;
						combined[tone + 'Evidence'].push(...(bucket[tone + 'Evidence'] || []));
					}
				}
			}
			onProgress?.({phase:'stats',postCount});
			const stats=await buildRatingStats(ratingPosts,mentions,cancelToken);
			return {searchTerm,topics:analyzedTopics,topicInfo,excludedTopics,postCount,sourceCheckedAt,sourceCheckAttemptAt,unverifiedTopicCount,
				screenedTopicCount:topics.length - failedTopicCount,excludedTopicCount:excludedTopics.length,failedTopicCount,stats};
		} finally {client.close();}
	}
	function getGarsoMentionEntries(resultOrStats,includeZero=false) {
		const stats=resultOrStats?.stats || resultOrStats;
		const mentions=stats?.mentions;
		if (!mentions || typeof mentions !== 'object') return [];
		const entries=[['GFE','GFE','brak GFE'],['wtopa','wtopa','bez wtopy',true],
			['obsuwa','obsuwa','bez obsuwy',true],['spółdzielnia','spółdzielnia','nie spółdzielnia',true],['agencja','agentura','nie agentura',true],
			['atmosfera','miła atmosfera','niemiła atmosfera'],['prywatnie','prywatnie','nie prywatnie'],
			['oszustwo','oszustka','nie oszustka',true],['scam','scam','brak scamu',true],
			['podmianka','podmianka','brak podmianki',true],['zdjęcia','zdjęcia zgodne','zdjęcia niezgodne'],
			['rekomendacja','polecam','nie polecam']].flatMap(([key,positive,negative,invert]) =>
			['positive','negative'].map((kind,index) => [index ? negative:positive,mentions[key]?.[kind],
				(index === 0) !== !!invert ? 'positive':'negative',mentions[key]?.[kind + 'Evidence']])).map(([label,count,tone,evidence]) => ({
			label, count:Math.max(0,Number(count) || 0), tone, evidence:(Array.isArray(evidence) ? evidence:[]).map(item => ({
				topicTitle:normalizeEscortAdText(item?.topicTitle) || 'Temat Garso',
				url:item?.url ? normalizeTopicUrl(item.url):'', author:normalizeEscortAdText(item?.author),
				date:normalizeEscortAdText(item?.date), snippet:String(item?.snippet || '').slice(0,360),
				match:String(item?.match || '').slice(0,120), scope:String(item?.scope || '').slice(0,100),
				visit:String(item?.visit || '').slice(0,100), aspect:String(item?.aspect || '').slice(0,40)
			}))
		}));
		return includeZero
			? entries
			:entries.filter(entry => entry.count > 0);
	}
	function getGarsoMentionToneStyle(tone) {
		if (tone === 'positive') {
			return {
				color:'var(--vm-ui-success, #9aefad)', border:'color-mix(in srgb, var(--vm-ui-success, #62cf7b) 65%, transparent)',
				background:'color-mix(in srgb, var(--vm-ui-success, #62cf7b) 14%, transparent)'
			};
		}
		if (tone === 'negative') {
			return {
				color:'var(--vm-ui-danger, #ff9a9a)', border:'color-mix(in srgb, var(--vm-ui-danger, #ff6262) 72%, transparent)',
				background:'color-mix(in srgb, var(--vm-ui-danger, #dc3545) 16%, transparent)'
			};
		}
		return {
			color:'var(--vm-ui-panel-text, #fff)', border:'color-mix(in srgb, var(--vm-ui-accent, #f54da3) 55%, transparent)',
			background:'color-mix(in srgb, var(--vm-ui-accent, #f54da3) 10%, transparent)'
		};
	}
	function getGarsoReviewWarning(result) {
		const popularMentions=getMostFrequentGarsoMentions(result);
		const negativePopularMentions=popularMentions.filter( item => item.tone === 'negative' );
		const rawMean=result?.stats?.mean;
		const mean=Number(rawMean);
		const lowMean=rawMean != null && Number.isFinite(mean) && mean < 6;
		const reasons=[];
		if (negativePopularMentions.length) {
			reasons.push( `Najpopularniejsze negatywne wzmianki: ${negativePopularMentions
					.map(item => `${item.label} (${item.count})`) .join(', ')}.` );
		}
		if (lowMean) reasons.push(`Średnia ocena: ${rawMean}/10.`);
		return {
			show:reasons.length > 0, title:reasons.join('\n'), lowMean, negativePopularMentions
		};
	}
	function ensureGarsoSummaryCardsStyle() {
		if (document.getElementById('vm-garso-summary-cards-style')) return;
		appendHelperStyle('vm-garso-summary-cards-style',`
@grating-card{min-width:0;padding:7px 8px;border:1px solid color-mix(in srgb,var(--vm-ui-panel-text,#fff) 16%,transparent);border-radius:7px;background:color-mix(in srgb,var(--vm-ui-panel-text,#fff) 3.5%,transparent)}
@grating-head{display:flex;justify-content:space-between;gap:8px}
@grating-value{color:var(--vm-ui-panel-text,#fff);font-weight:800}
@grating-bar{height:4px;margin:6px 0 4px;border-radius:999px;background:color-mix(in srgb,var(--vm-ui-panel-text,#fff) 12%,transparent);overflow:hidden}
@grating-bar>div{height:100%}
@grating-detail{color:var(--vm-ui-panel-muted,#cbb8ce);font-size:10px}
@gmetric,@gextreme{padding:8px;border:1px solid color-mix(in srgb,var(--vm-ui-accent,#f54da3) 32%,transparent);border-radius:8px;background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 7%,transparent);text-align:center}
@gmetric-value{font-size:18px;font-weight:900;line-height:1.05;color:var(--vm-ui-panel-text,#fff)}
@gmetric-label{margin-top:3px;color:var(--vm-ui-panel-muted,#cbb8ce);font-size:10px;line-height:1.15}
@gmetric-detail{margin-top:2px;color:var(--vm-ui-panel-muted,#9f8ca3);font-size:9px}
@gtrend{margin-left:5px;font-size:17px;font-weight:900}
@gmention-chip{display:inline-block;margin:3px 3px 0 0;padding:3px 7px;border:1px solid;border-radius:999px;font:inherit;cursor:pointer}
@gextreme-label{font-size:10px;font-weight:900;line-height:1.15}
@gextreme-name{margin-top:4px;color:var(--vm-ui-panel-text,#fff);font-size:15px;font-weight:900;line-height:1.15}
@gextreme-value{margin-top:3px;font-size:10px;font-weight:900;line-height:1.1}
@gsummary-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}
@gsummary-info{margin-top:7px;padding:7px 8px;border-radius:7px;background:color-mix(in srgb,var(--vm-ui-panel-text,#fff) 3.5%,transparent);color:var(--vm-ui-panel-muted,#d8c8d9)}
@gsummary-info>b{color:var(--vm-ui-panel-text,#fff)}
@gsummary-heading{margin-top:9px;font-weight:800;color:var(--vm-ui-accent,#f54da3)}
@gsummary-section{margin-top:9px;padding:8px 9px;border:1px solid color-mix(in srgb,var(--vm-ui-panel-text,#fff) 14%,transparent);border-radius:7px}
@gsummary-section.-topics{background:color-mix(in srgb,var(--vm-ui-panel-text,#fff) 2.5%,transparent)}
@gsummary-section.-subtitles{margin-top:8px;border-color:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 34%,transparent);background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 6%,transparent)}
@gsummary-section.-excluded{margin-top:8px;border-color:color-mix(in srgb,var(--vm-ui-warning,#ffb45c) 58%,transparent);background:color-mix(in srgb,var(--vm-ui-warning,#ffb45c) 7%,transparent)}
@gsummary-section.-topics>b,@gsummary-section.-subtitles>b{color:var(--vm-ui-accent,#f54da3)}
@gsummary-topic{display:block;margin-top:5px;color:var(--vm-ui-panel-text,#fff);text-decoration:none}
@gsummary-topic>span:first-child{color:var(--vm-ui-accent,#f54da3)}
@gsummary-topic>small{display:block;margin-left:12px;color:var(--vm-ui-panel-muted,#cbb8ce);font-size:10px}
@gsummary-topic.-excluded,@gsummary-section.-excluded>b{color:var(--vm-ui-warning,#ffd2e8)}
@gsummary-topic.-excluded>span:first-child{color:var(--vm-ui-warning,#ffb45c)}
@gsummary-subtitle{margin-top:6px}
@gsummary-subtitle>div{color:var(--vm-ui-panel-text,#fff);font-weight:800;line-height:1.25}
@gsummary-subtitle>small{display:block;margin-top:2px;color:var(--vm-ui-panel-muted,#cbb8ce);font-size:10px;line-height:1.2}
`.replaceAll('@g','.vm-garso-'));
	}
	function summaryToHtml(result,{topicView=false}={}) {
		if (!result.topics?.length && !result.topicInfo?.length && !result.excludedTopics?.length) return '<div class="vm-garso-summary-preview">Brak tematów.</div>';
		ensureGarsoSummaryCardsStyle();
		const s=result.stats;
		const scope=result.topicInfo?.some(topic => topic.pagesRead?.length < topic.maxPage)
			? '<div class="vm-garso-summary-info">Długie tematy: pierwsze 10 i ostatnie 20 stron.</div>':'';
		const categoryCards=(topicView && CATEGORY_DEFS.every(d => s.categories?.[d.key]?.mean == null) ? []:CATEGORY_DEFS).map(d => {
			const c=s.categories[d.key],percentage=c.mean == null ? 0:Math.max(0,Math.min(100,Number(c.mean) * 10));
			return `<div class="vm-garso-rating-card"><div class="vm-garso-rating-head"><b>${escapeHtml(getGarsoRatingCategoryLabel(c))}</b>
<span class="vm-garso-rating-value"${c.mean == null ? ' style="color:var(--vm-ui-panel-muted,#cbb8ce)"':''}>${c.mean == null ? '-':`${c.mean}/10`}</span></div>
<div class="vm-garso-rating-bar"><div style="width:${percentage}%;background:${getGarsoRatingBarColor(c.mean)}"></div></div>
<div class="vm-garso-rating-detail">${c.mean == null ? 'brak ocen':`${c.count} ocen · zakres ${c.min}-${c.max}`}</div></div>`;
		}).join('');
		const mentions=getGarsoMentionEntries(s).sort((a,b) => b.count - a.count || a.label.localeCompare(b.label,'pl-PL'));
		const mentionChipsHtml=mentions.map((item,index) => {
			const tone=getGarsoMentionToneStyle(item.tone);
			return `<button type="button" class="vm-garso-mention-chip" data-vm-garso-mention-index="${index}" title="Wzmianki z analizowanych stron; mogą dotyczyć różnych osób i wizyt. Pokaż źródła." style="border-color:${tone.border};background:${tone.background};color:${tone.color}">${escapeHtml(item.label)} <b>${item.count}</b></button>`;
		}).join(' ');
		const metricCard=(label,value,detail='') => `<div class="vm-garso-metric"><div class="vm-garso-metric-value">${value}</div>
<div class="vm-garso-metric-label">${label}</div>${detail ? `<div class="vm-garso-metric-detail">${detail}</div>`:''}</div>`;
		const overall=Number(s.mean),recent=Number(s.last5Mean),comparable=s.mean != null && s.last5Mean != null && Number.isFinite(overall) && Number.isFinite(recent);
		const trendColor=comparable && recent !== overall ? (recent > overall ? 'var(--vm-ui-success,#67df85)':'var(--vm-ui-danger,#ff7777)'):'var(--vm-ui-panel-text,#fff)';
		let overallMean=s.mean == null ? '-':`${s.mean}/10`;
		if (comparable && Number(s.last5ReviewCount) >= 5 && Number(s.reviewCount) > 5 && recent !== overall) {
			const title=escapeHtml(`Ostatnie 5 recenzji: ${s.last5Mean}/10; ${recent > overall ? 'wzrost':'spadek'} o ${round1(Math.abs(recent - overall))} względem całości.`);
			overallMean+=`<span class="vm-garso-trend" title="${title}" aria-label="${title}" style="color:${trendColor}">${recent > overall ? '↑':'↓'}</span>`;
		}
		if (topicView) return `<div class="vm-garso-topic-overall">${metricCard('średnia ogólna',overallMean,`${s.reviewCount} recenzji z ocenami`)}</div>
${scope}<div class="vm-garso-topic-grid">${categoryCards}</div>${mentionChipsHtml ? `<div class="vm-garso-topic-mentions">${mentionChipsHtml}</div>`:''}`;
		const ranked=CATEGORY_DEFS.filter(d => !d.overall).map(d => s.categories?.[d.key]).filter(c => c?.count > 0 && Number.isFinite(c.mean)).sort((a,b) => b.mean - a.mean);
		const getPostWord=count => polishCountForm(count,'post','posty','postów'),postCount=Math.max(0,Number(result.postCount) || 0);
		const excluded=Array.isArray(result.excludedTopics) ? result.excludedTopics:[];
		const topicLines=(topics,skipped=false) => topics.map(t => `<a class="vm-garso-summary-topic${skipped ? ' -excluded':''}" href="${escapeHtml(normalizeTopicUrl(t.url))}" target="_blank" rel="noopener noreferrer"><span>›</span> ${escapeHtml(t.title)}${skipped ? '':`<small>${t.posts} ${getPostWord(t.posts)}</small>`}</a>`).join('');
		const subtitleLines=result.topicInfo.map(t => ({...t,subtitle:getDisplayableGarsoTopicSubtitle(t.subtitle)})).filter(t => t.subtitle).map(t =>
			`<div class="vm-garso-summary-subtitle"><div>${escapeHtml(t.subtitle)}</div><small>${escapeHtml(t.title)}</small></div>`).join('');
		const extremeCard=(label,c,high) => {
			const color=high ? 'var(--vm-ui-success,#9aefad)':'var(--vm-ui-danger,#ffaaaa)';
			const border=`color-mix(in srgb,var(--vm-ui-${high ? 'success,#62cf7b':'danger,#ff6262'}) ${high ? 62:68}%,transparent)`;
			const bg=`color-mix(in srgb,var(--vm-ui-${high ? 'success,#62cf7b':'danger,#dc3545'}) ${high ? 13:14}%,transparent)`;
			return `<div class="vm-garso-extreme" style="border-color:${border};background:${bg};color:${color}"><div class="vm-garso-extreme-label">${label}</div><div class="vm-garso-extreme-name">${c ? escapeHtml(getGarsoRatingCategoryLabel(c)):'brak ocen'}</div><div class="vm-garso-extreme-value">${c ? `${c.mean}/10`:'-'}</div></div>`;
		};
		const section=(kind,label,content)=>`<div class="vm-garso-summary-section ${kind}"><b>${label}</b>${content}</div>`;
		const lastLabel=Number(s.last5ReviewCount) === 1 ? 'ostatnia ocena':`ostatnie ${Math.max(0,Number(s.last5ReviewCount) || 0)}`;
		return `<div class="vm-garso-summary-preview">${scope}
${result.unverifiedTopicCount ? `<div role="status" style="margin-bottom:7px;color:var(--vm-ui-warning,#b46b12)">Nie potwierdzono aktualności ${result.unverifiedTopicCount} tematów. Zachowano poprzednie wyniki; odśwież ręcznie w razie potrzeby.</div>`:''}
<div class="vm-garso-summary-grid">${metricCard('recenzje z ocenami',s.reviewCount)}${metricCard('średnia ogólna',overallMean)}${extremeCard('najwyżej',ranked[0],true)}${extremeCard('najniżej',ranked.at(-1),false)}</div>
<div class="vm-garso-summary-info">Analiza: <b>${result.topicInfo.length} ${getGarsoTopicWord(result.topicInfo.length)} · ${postCount} ${getPostWord(postCount)}</b> · zakres: <b>${s.reviewCount ? `${s.min}-${s.max}/10`:'-'}</b> · ${lastLabel}: <b style="color:${trendColor}">${s.last5Mean == null ? '-':`${s.last5Mean}/10`}</b></div></div>
${subtitleLines ? section('-subtitles','Dopiski z Garso',subtitleLines):''}
<div class="vm-garso-summary-heading">Oceny cząstkowe</div><div class="vm-garso-summary-grid" style="margin-top:6px">${categoryCards}</div>
${mentionChipsHtml ? section('','Jawne wzmianki w analizowanych stronach',`<div style="margin-top:3px">${mentionChipsHtml}</div>`):''}
${section('-topics','Analizowane tematy',topicLines(result.topicInfo) || '<div style="margin-top:5px;color:var(--vm-ui-panel-muted,#cbb8ce)">Brak tematów spełniających kryteria analizy.</div>')}
${excluded.length ? section('-excluded',`Pominięte w statystyce (${excluded.length})`,`<div style="margin-top:3px;color:var(--vm-ui-panel-muted,#d8c8d9);font-size:10px">Tytuły mogą zawierać polecenia lub ostrzeżenia - kliknij, aby otworzyć temat.</div>${topicLines(excluded,true)}`):''}`;
	}
	function applySummaryToButton( btn, result, {checkedAt=null,cacheState=null}={} ) {
		btn._vmGarsoSummaryResult=result || null;
		const firstReviewDate=result?.stats?.firstReviewDate || '';
		const lastReviewDate=result?.stats?.lastReviewDate || '';
		btn.dataset.garsoReviewCount=String( Math.max(0,Number(result?.stats?.reviewCount) || 0) );
		const firstReviewUrl=result?.stats?.firstReviewUrl || result?.topics?.[0]?.url || '';
		const lastReviewUrl=result?.stats?.lastReviewUrl || result?.topics?.at(-1)?.url || firstReviewUrl;
		if (firstReviewDate) btn.dataset.garsoFirstReviewDate=firstReviewDate;
		else delete btn.dataset.garsoFirstReviewDate;
		if (lastReviewDate) btn.dataset.garsoLastReviewDate=lastReviewDate;
		else delete btn.dataset.garsoLastReviewDate;
		if (firstReviewUrl) btn.dataset.garsoFirstReviewUrl=firstReviewUrl;
		else delete btn.dataset.garsoFirstReviewUrl;
		if (lastReviewUrl) btn.dataset.garsoLastReviewUrl=lastReviewUrl;
		else delete btn.dataset.garsoLastReviewUrl;
		setGarsoSummaryState( btn, 'ready', summaryToHtml(result), {checkedAt,cacheState} );
	}
	function normalizeEscortCity(value) {
		return String(value || '') .replace(/\s+/g,' ') .replace(/^[\s,;|\-–\u2014]+|[\s,;|\-–\u2014]+$/g,'') .trim();
	}
	function normalizeEscortCityKey(value) {
		return normalizeEscortCity(value) .normalize('NFKD') .replace(/\p{M}/gu,'') .toLocaleLowerCase('pl-PL');
	}
	function extractEscortClubCity(doc) {
		if (!doc) return null;
		const locationLinks=[...doc.querySelectorAll('.content-location a[href]')];
		for (const link of locationLinks) {
			try {
				const url=new URL(link.getAttribute('href'),'https://pl.escort.club/');
				if (!/^\/(?:anonse\/towarzyskie|personal-ads)\/[^/]+\/?$/i.test(url.pathname)) continue;
				if (/\/poland\/?$/i.test(url.pathname) || url.searchParams.has('district')) continue;
				const city=normalizeEscortCity(link.textContent);
				if (city) return city;
			} catch {}
		}
		return null;
	}
	function buildEscortClubAdDocumentResult(html,status,finalUrl,transport) {
		const text=String(html || '');
		const numericStatus=Number(status) || 0;
		return {
			ok:!!text && (numericStatus === 0 || (numericStatus >= 200 && numericStatus < 400)), status:numericStatus, finalUrl,
			transport, doc:new DOMParser().parseFromString(text,'text/html')
		};
	}
	function isUsableEscortClubAdDocument(result,requestedUrl) {
		if (!result?.ok || !result.doc) return false;
		try {
			const requestedId=parseAdIdFromUrl(requestedUrl);
			const finalUrl=new URL(result.finalUrl || requestedUrl,requestedUrl);
			if ( requestedId && isEscortClubHost(finalUrl.hostname) && finalUrl.pathname === '/' ) {
				return true;
			}
		} catch {}
		const requestedId=parseAdIdFromUrl(requestedUrl);
		const finalId=parseAdIdFromUrl(result.finalUrl || requestedUrl);
		if (requestedId && finalId && requestedId !== finalId) return false;
		const title=result.doc.querySelector( '.content-name h1, .content-name.-desc-name h1' );
		const bodyText=normalizeEscortAdText(result.doc.body?.textContent);
		return !!title && !/^site unavailable\b/i.test(bodyText);
	}
	async function fetchEscortClubAdDocumentViaPage(url,cancelToken=null,requestContext=null,beforeDispatch=null) {
		if (typeof unsafeWindow === 'undefined' || typeof unsafeWindow.fetch !== 'function') {
			throw new Error('Brak dostępu do fetch strony');
		}
		const AbortControllerClass=unsafeWindow.AbortController || AbortController;
		const controller=new AbortControllerClass();
		let timer=null,response=null,trace=null,outcome='error';
		let releaseSlot=() => {};
		const unsubscribeCancel=cancelToken?.onCancel(() => controller.abort()) || (() => {});
		try {
			cancelToken?.throwIfCancelled();
			releaseSlot=await acquireEscortRequestSlot(url,cancelToken,false,'GET',requestContext);
			cancelToken?.throwIfCancelled();
			const skipped=beforeDispatch?.();
			if (skipped) {
				return skipped.value;
			}
			releaseSlot.beforeDispatch?.();
			timer=setTimeout(() => controller.abort(),ESCORT_AD_DATA_FETCH_TIMEOUT_MS);
			trace=recordDiagnosticRequest(url,'GET',requestContext,'fetch');
			const pendingResponse=unsafeWindow.fetch(url,{
				method:'GET', credentials:'include', redirect:'follow', cache:'no-store', signal:controller.signal, headers:{
					Accept:'text/html,application/xhtml+xml'
				}
			});
			releaseSlot.markStarted?.();
			response=await pendingResponse;
			trace?.(response.status,'headers',response);
			outcome='response';
			recordDiagnosticResponse(url,response.status);
			let html,blockedError=null;
			try {
				const info={status:response.status,headers:response.headers,finalUrl:response.url};
				if (releaseSlot.observeResponse) releaseSlot.observeResponse(info,false); else observeEscortRequestResponse(url,info);
			}
			catch (error) {blockedError=error;}
			try {html=await response.text();}
			catch (error) {
				if (blockedError) throw blockedError;
				throw error;
			}
			if (blockedError) throw refineEscortRequestBlockedError(url,{responseText:html},blockedError);
			const info={status:response.status,headers:response.headers,responseText:html,finalUrl:response.url};
			if (releaseSlot.observeResponse) releaseSlot.observeResponse(info); else observeEscortRequestResponse(url,info);
			return buildEscortClubAdDocumentResult(html,response.status,response.url || url,'page-fetch');
		} catch (error) {
			outcome=cancelToken?.cancelled ? 'cancelled':controller.signal.aborted ? 'timeout':response?.status >= 400 ? 'response':'error';
			throw error;
		} finally {
			trace?.(response?.status,outcome,response);
			clearTimeout(timer);
			unsubscribeCancel();
			releaseSlot();
		}
	}
	async function fetchEscortClubAdDocumentViaGm(url,cancelToken=null,requestContext=null,beforeDispatch=null) {
		const response=await gmRequest({
			method:'GET', url, cancelToken, requestContext, beforeDispatch, timeout:ESCORT_AD_DATA_FETCH_TIMEOUT_MS, responseType:'text',
			overrideMimeType:'text/html; charset=utf-8', anonymous:false, headers:{
				Accept:'text/html,application/xhtml+xml', Referer:location.href
			}
		});
		if (response.sharedActivityResult || response.sharedActivityRetry || response.sharedAdData) return response;
		return buildEscortClubAdDocumentResult(response.responseText || response.response || '',response.status,
			response.finalUrl || url,'gm-xhr');
	}
	async function fetchEscortClubAdDocumentViaIframe(url,cancelToken=null,requestContext=null,beforeDispatch=null) {
		let releaseSlot=() => {};
		try {
			cancelToken?.throwIfCancelled();
			releaseSlot=await acquireEscortRequestSlot(url,cancelToken,false,'GET',requestContext);
			cancelToken?.throwIfCancelled();
			const skipped=beforeDispatch?.();
			if (skipped) {
				releaseSlot();
				return skipped.value;
			}
		} catch (error) {
			releaseSlot();
			throw error;
		}
		return new Promise((resolve,reject) => {
			const iframe=makeElement('iframe');
			let finished=false,trace=null;
			let timer=null;
			let unsubscribeCancel=() => {};
			const cleanup=() => {
				if (timer) clearTimeout(timer);
				unsubscribeCancel();
				try {detachHelperNode(iframe);} catch {}
			};
			const finish=(callback,value,outcome,response) => {
				if (finished) return;
				finished=true;
				trace?.(0,outcome || (callback === resolve ? 'response':isOperationCancelledError(value) ? 'cancelled':'error'),response);
				cleanup();
				callback(value);
			};
			iframe.setAttribute('sandbox','allow-same-origin');
			prepareHiddenHelperFrame(iframe);
			iframe.addEventListener('load',() => {
				if (finished) return;
				let html='',finalUrl=url;
				try {
					const sourceDoc=iframe.contentDocument;
					html=sourceDoc?.documentElement?.outerHTML || '';
					finalUrl=iframe.contentWindow?.location?.href || url;
					const status=html ? 200:0;
					const info={status:0,responseText:html,finalUrl};
					if (releaseSlot.observeResponse) releaseSlot.observeResponse(info); else observeEscortRequestResponse(url,info);
					const result=buildEscortClubAdDocumentResult(html,status,finalUrl,'iframe');
					recordDiagnosticResponse(url,0);
					finish(resolve,result,'response',{finalUrl});
				} catch (error) {finish(reject,error);}
			},{once:true});
			iframe.addEventListener('error',() => {
				const error=new Error('Błąd nawigacji iframe');
				recordDiagnosticRequestFailure(url,error);
				finish(reject,error);
			},{once:true});
			timer=setTimeout(() => finish(reject,new Error('Przekroczono czas iframe'),'timeout'),ESCORT_AD_DATA_FETCH_TIMEOUT_MS);
			unsubscribeCancel=cancelToken?.onCancel(reason => {
				finish(reject,createOperationCancelledError(reason));
			}) || (() => {});
			if (finished) return;
			try {
				releaseSlot.beforeDispatch?.();
				trace=recordDiagnosticRequest(url,'IFRAME',requestContext,'iframe');
				iframe.src=url;
				document.body.appendChild(iframe);
				releaseSlot.markStarted?.();
			} catch (error) {finish(reject,error);}
		}).finally(() => releaseSlot());
	}
	async function fetchEscortClubAdDocumentUnshared(url,cancelToken=null,requestContext=null,beforeDispatch=null) {
		requestContext ||= createRequestContext('escort-ad-document');
		const errors=[];
		const transports=new URL(url,location.href).origin === location.origin
			? [ ['page-fetch',fetchEscortClubAdDocumentViaPage], ['gm-xhr',fetchEscortClubAdDocumentViaGm],
				['iframe',fetchEscortClubAdDocumentViaIframe] ]
			:[ ['gm-xhr',fetchEscortClubAdDocumentViaGm], ['page-fetch',fetchEscortClubAdDocumentViaPage],
				['iframe',fetchEscortClubAdDocumentViaIframe] ];
		for (const [name,loader] of transports) {
			try {
				cancelToken?.throwIfCancelled();
				let result;
				while (true) {
					try {result=await loader(url,cancelToken,requestContext,beforeDispatch); break;}
					catch (error) {
						if (error?.code !== 'ESCORT_QUEUE_RESCHEDULE' && (error?.status !== 503 || error?.retryable !== true)) throw error;
						cancelToken?.throwIfCancelled();
					}
				}
				if (result.sharedActivityResult || result.sharedActivityRetry || result.sharedAdData) return result;
				const usable=isUsableEscortClubAdDocument(result,url);
				if (usable || [404,410].includes(result?.status)) return result;
				errors.push(`${name}: nieprawidłowa odpowiedź (HTTP ${result?.status || 0})`);
			} catch (error) {
				const cancelled=isOperationCancelledError(error) || cancelToken?.cancelled;
				if (cancelled) throw createOperationCancelledError(cancelToken?.reason || error?.message);
				if (isEscortRequestBlockedError(error)) throw error;
				errors.push(`${name}: ${formatHelperDiagnosticError(error)}`);
			}
		}
		throw new Error(errors.join(' | '));
	}
	const ESCORT_SHARED_AD_REQUEST_PREFIX='vm_escort_ad_request_v1_';
	const ESCORT_SHARED_AD_RESULT_PREFIX='vm_escort_ad_result_v1_';
	const ESCORT_SHARED_AD_CONSUMER_PREFIX='vm_escort_ad_consumer_v1_';
	const ESCORT_SHARED_AD_LEASE_MS=90000;
	const sharedEscortAdRequests=new Map();
	let sharedEscortAdPruneAt=0;
	function isSharedEscortAdLeaseLive(value,now=Date.now()) {
		return typeof value?.owner === 'string' && !!value.owner && Number.isFinite(value.expiresAt) &&
			value.expiresAt > now && value.expiresAt <= now + ESCORT_SHARED_AD_LEASE_MS;
	}
	function readSharedEscortAdResult(id,minCheckedAt=0) {
		try {
			const value=GM_getValue(ESCORT_SHARED_AD_RESULT_PREFIX + id,null),now=Date.now();
			if (!value || !(value.expiresAt > now && value.expiresAt <= now + 30000) ||
				!(value.checkedAt >= minCheckedAt && value.checkedAt <= now)) return null;
			return value.value?.sharedAdData || value.value?.sharedActivityResult ? value.value:null;
		} catch {return null;}
	}
	function pruneSharedEscortAdRequests() {
		if (sharedEscortAdPruneAt > Date.now()) return;
		sharedEscortAdPruneAt=Date.now() + 30000;
		try {
			for (const key of GM_listValues()) {
				if (![ESCORT_SHARED_AD_REQUEST_PREFIX,ESCORT_SHARED_AD_RESULT_PREFIX,ESCORT_SHARED_AD_CONSUMER_PREFIX]
					.some(prefix => key.startsWith(prefix))) continue;
				const value=GM_getValue(key,null);
				if (!(value?.expiresAt > Date.now())) deleteHelperStorageValue(key);
			}
		} catch {}
	}
	function shareFetchedEscortAdDocument(url,result) {
		if (result.sharedAdData || result.sharedActivityResult || result.sharedActivityRetry) return result;
		const id=parseAdIdFromUrl(url),checkedAt=Date.now();
		let finalUrl;
		try {finalUrl=new URL(result.finalUrl || url,url);} catch {return result;}
		if (!id || !isEscortClubHost(finalUrl.hostname)) return result;
		if ([404,410].includes(result.status) || (result.ok && finalUrl.pathname === '/')) {
			const activity={url,active:false,city:null,status:result.status,checkedAt,source:'document'};
			saveSharedEscortActivity(url,activity,checkedAt);
			return {sharedActivityResult:activity};
		}
		if (!result.ok || parseAdIdFromUrl(finalUrl.href) !== id || !getEscortAdIdFromPath(finalUrl.pathname) ||
			!isUsableEscortClubAdDocument(result,url)) return result;
		const previous=escortAdDataMemoryCache.get(id) || getStoredEscortAdData(id);
		const extracted=extractEscortClubAdData(result.doc,id,finalUrl.href);
		const data={status:'ok',...preserveEscortAdPhoneData(extracted,previous),checkedAt,fetchTransport:result.transport || null};
		saveSharedEscortActivity(url,{active:true},checkedAt);
		return {sharedAdData:data};
	}
	function fetchEscortClubAdDocument(url,cancelToken=null,requestContext=null,beforeDispatch=null,{minCheckedAt=Date.now()}={}) {
		const id=parseAdIdFromUrl(url);
		if (!id) return fetchEscortClubAdDocumentUnshared(url,cancelToken,requestContext,beforeDispatch);
		return joinEscortAdDataOperation(sharedEscortAdRequests,id,
			token => fetchSharedEscortAdDocument(url,token,requestContext,beforeDispatch,minCheckedAt),cancelToken,requestContext);
	}
	async function fetchSharedEscortAdDocument(url,cancelToken,requestContext,beforeDispatch,minCheckedAt=Date.now()) {
		const id=parseAdIdFromUrl(url),requestedAt=Math.max(0,Number(minCheckedAt) || 0),epoch=sharedEscortActivityCacheEpoch;
		const key=ESCORT_SHARED_AD_REQUEST_PREFIX + id,resultKey=ESCORT_SHARED_AD_RESULT_PREFIX + id;
		const owner=`${Date.now()}-${Math.random().toString(36).slice(2)}`,consumerPrefix=ESCORT_SHARED_AD_CONSUMER_PREFIX + id + ':';
		const consumerKey=consumerPrefix + owner,token=createOperationCancelToken('Wspólne pobieranie anonsu');
		let timer=null,owns=false,disposed=false;
		const peers=() => {
			try {return GM_listValues().some(name => name.startsWith(consumerPrefix) && name !== consumerKey &&
				isSharedEscortAdLeaseLive(GM_getValue(name,null)));} catch {return false;}
		};
		const renew=() => {
			if (disposed) return;
			try {
				if (!cancelToken?.cancelled) writeHelperStorageValue(consumerKey,{owner,expiresAt:Date.now() + ESCORT_SHARED_AD_LEASE_MS});
				else if (!peers()) token.cancel(cancelToken.reason);
				const lease=GM_getValue(key,null);
				if (lease?.owner === owner && isSharedEscortAdLeaseLive(lease))
					writeHelperStorageValue(key,{owner,expiresAt:Date.now() + ESCORT_SHARED_AD_LEASE_MS});
			} catch {}
			clearTimeout(timer); timer=setTimeout(renew,10000);
		};
		const off=cancelToken?.onCancel(reason => {
			try {deleteHelperStorageValue(consumerKey);} catch {}
			if (!owns || !peers()) token.cancel(reason);
		}) || (() => {});
		const pagehide=() => token.cancel('Zamknięto stronę');
		if (typeof window !== 'undefined') window.addEventListener('pagehide',pagehide,{once:true});
		pruneSharedEscortAdRequests(); renew();
		try {
			while (true) {
				token.throwIfCancelled();
				if (epoch !== sharedEscortActivityCacheEpoch) throw createOperationCancelledError('Wyczyszczono cache');
				const shared=readSharedEscortAdResult(id,requestedAt);
				if (shared) {recordDiagnosticCacheDecision(requestContext,'joined'); return shared;}
				let lease;
				try {lease=GM_getValue(key,null);} catch {return await fetchEscortClubAdDocumentUnshared(url,token,requestContext,beforeDispatch);}
				if (isSharedEscortAdLeaseLive(lease) && lease.owner !== owner) {
					await waitWithCancellation(250,token); continue;
				}
				writeHelperStorageValue(key,{owner,expiresAt:Date.now() + ESCORT_SHARED_AD_LEASE_MS});
				await waitWithCancellation(40,token);
				if (GM_getValue(key,null)?.owner !== owner) continue;
				owns=true;
				const before=() => {
					token.throwIfCancelled();
					if (epoch !== sharedEscortActivityCacheEpoch) throw createOperationCancelledError('Wyczyszczono cache');
					const latest=readSharedEscortAdResult(id,requestedAt);
					if (latest) return {outcome:'cached',value:latest};
					const current=GM_getValue(key,null);
					if (current?.owner !== owner || !isSharedEscortAdLeaseLive(current))
						return {outcome:'superseded',value:{sharedActivityRetry:true}};
					renew();
					return beforeDispatch?.();
				};
				const fetched=await fetchEscortClubAdDocumentUnshared(url,token,requestContext,before);
				if (fetched.sharedActivityRetry) {owns=false; continue;}
				if (epoch !== sharedEscortActivityCacheEpoch || GM_getValue(key,null)?.owner !== owner)
					throw createOperationCancelledError('Zmieniono właściciela pobierania');
				const result=shareFetchedEscortAdDocument(url,fetched);
				if (result.sharedAdData || result.sharedActivityResult) writeHelperStorageValue(resultKey,
					{checkedAt:result.sharedAdData?.checkedAt || result.sharedActivityResult?.checkedAt,expiresAt:Date.now() + 30000,value:result});
				return result;
			}
		} finally {
			disposed=true; clearTimeout(timer); off();
			if (typeof window !== 'undefined') window.removeEventListener?.('pagehide',pagehide);
			try {
				deleteHelperStorageValue(consumerKey);
				if (GM_getValue(key,null)?.owner === owner) deleteHelperStorageValue(key);
			} catch {}
		}
	}
	async function inspectEscortClubAd(url,cancelToken=null,forceRefresh=false,requestContext=null,beforeDispatch=null,{minCheckedAt=0}={}) {
		requestContext ||= createRequestContext('escort-ad-inspection');
		cancelToken?.throwIfCancelled();
		url=normalizeEscortiAdUrl(url) || url;
		const id=parseAdIdFromUrl(url);
		const currentId=IS_ESCORT_CLUB && new URL(url,location.href).origin === location.origin && !!getEscortAdIdFromPath(location.pathname)
			? getAdIdFromUrl():null;
		if (!forceRefresh && !minCheckedAt && id && currentId === id &&
			isUsableEscortClubAdDocument({ok:true,doc:document,finalUrl:url},url)) {
			const adData=extractEscortClubAdData(document,id,url);
			const navigation=typeof performance === 'object' ? performance.getEntriesByType?.('navigation')?.[0]:null;
			const checkedAt=typeof performance === 'object' && Number(performance.timeOrigin) > 0 && Number(navigation?.responseEnd) > 0 &&
				(!navigation.responseStatus || (navigation.responseStatus >= 200 && navigation.responseStatus < 400))
				? performance.timeOrigin + navigation.responseEnd:0;
			if (checkedAt > 0) saveSharedEscortActivity(url,{active:true},checkedAt);
			return {url,active:true,city:adData.location?.city || extractEscortClubCity(document),
				imageUrl:extractWatchPageImage(document,url),adData,source:'current',checkedAt};
		}
		const activity=readSharedEscortActivity(url);
		if (activity && !activity.active) return activity;
		try {
			const skipped=beforeDispatch?.(true);
			if (skipped?.value?.sharedActivityResult || skipped?.value?.sharedActivityRetry)
				return skipped.value.sharedActivityResult || skipped.value;
			const data=await getEscortAdData(id,url,forceRefresh,cancelToken,requestContext,
				{needPhone:false,maxAgeMs:ESCORT_SHARED_ACTIVITY_CACHE_TTL_MS,minCheckedAt,beforeDispatch:beforeDispatch ? () => beforeDispatch(true):null});
			cancelToken?.throwIfCancelled();
			if (data?.activity) return data.activity;
			if (data?.sharedActivityRetry) return data;
			if (data?.status !== 'ok') return {url,active:false,city:null,unknown:true,detailsUnknown:true};
			return {url,active:true,city:data.location?.city || null,imageUrl:data.imageUrl || '',adData:data,
				checkedAt:data.checkedAt,source:'document'};
		} catch (error) {
			if (isOperationCancelledError(error) || cancelToken?.cancelled) throw createOperationCancelledError(cancelToken?.reason || error?.message);
			return {url,active:false,city:null,error:formatHelperDiagnosticError(error),unknown:true};
		}
	}
	async function inspectEscortClubAdActivityOnly(url,cancelToken=null,requestContext=null,{strict=false,forceRefresh=false,useSharedCache=true,beforeDispatch=null}={}) {
		requestContext ||= createRequestContext('escort-ad-activity');
		cancelToken?.throwIfCancelled();
		if (useSharedCache) {
			return inspectSharedEscortActivity(url,cancelToken,requestContext,{strict});
		}
		if (GARSO_MENU_ONLY_MODE && !strict) {
			return inspectEscortClubAd(url,cancelToken,forceRefresh,requestContext,beforeDispatch);
		}
		const requestedId=parseAdIdFromUrl(url);
		const currentId=IS_ESCORT_CLUB && new URL(url,location.href).origin === location.origin && !!getEscortAdIdFromPath(location.pathname)
			? getAdIdFromUrl():null;
		if (!forceRefresh && requestedId && currentId && requestedId === currentId &&
			isUsableEscortClubAdDocument({ok:true,doc:document,finalUrl:url},url)) {
			if (strict) return inspectEscortClubAd(url,cancelToken,false,requestContext,beforeDispatch);
			return {
				url, active:true, city:null, imageUrl:'', source:'current-head'
			};
		}
		try {
			const response=await gmRequest({
				method:'HEAD', url, cancelToken, requestContext, beforeDispatch, timeout:ESCORT_AD_DATA_FETCH_TIMEOUT_MS, anonymous:false, headers:{
					Accept:'text/html,application/xhtml+xml', Referer:location.href
				}
			});
			if (response.sharedActivityResult || response.sharedActivityRetry) return response.sharedActivityResult || response;
			const status=Number(response?.status) || 0;
			const finalUrl=new URL(response?.finalUrl || url,url);
			const finalId=parseAdIdFromUrl(finalUrl.href);
			const exactAdPath=!!getEscortAdIdFromPath(finalUrl.pathname);
			if (isEscortClubHost(finalUrl.hostname) && (status === 404 || status === 410)) {
				return {url,active:false,city:null,status,source:'head'};
			}
			if (status === 405 || status === 501) {
				return inspectEscortClubAd(url,cancelToken,forceRefresh,requestContext,beforeDispatch);
			}
			if (status < 200 || status >= 400) {
				return {url,active:false,city:null,status,unknown:true,source:'head'};
			}
			if ( isEscortClubHost(finalUrl.hostname) && exactAdPath && requestedId && finalId === requestedId ) {
				return {
					url, active:true, city:null, imageUrl:'', status, source:'head'
				};
			}
			if ( isEscortClubHost(finalUrl.hostname) && finalUrl.pathname === '/' ) {
				return {
					url, active:false, city:null, status, source:'head'
				};
			}
			if (strict) return {url,active:false,city:null,status,unknown:true,source:'head'};
		} catch (error) {
			if (isOperationCancelledError(error) || cancelToken?.cancelled) throw createOperationCancelledError(cancelToken?.reason || error?.message);
			if (strict || isEscortRequestBlockedError(error)) {
				return {url,active:false,city:null,status:Number(error.status) || 0,unknown:true,source:'head',
					error:formatHelperDiagnosticError(error)};
			}
		}
		return inspectEscortClubAd(url,cancelToken,forceRefresh,requestContext,beforeDispatch);
	}
	const ESCORT_SHARED_ACTIVITY_CACHE_PREFIX='vm_escort_activity_v1_';
	const ESCORT_SHARED_ACTIVITY_CACHE_TTL_MS=30 * 60 * 1000;
	const sharedEscortActivityRequests=new Map();
	const sharedEscortActivityResultTimes=new WeakMap();
	let sharedEscortActivityPruneAt=0,sharedEscortActivityCacheEpoch=0;
	const ESCORT_SHARED_ACTIVITY_PENDING_PREFIX='vm_escort_activity_pending_v1_';
	const ESCORT_SHARED_ACTIVITY_LEASE_MS=90000;
	const sharedEscortActivityOwners=new Map();
	let sharedEscortActivityPageHooked=false,sharedEscortActivityLeasePruneAt=0;
	function isSharedEscortActivityLeaseLive(value,now=Date.now()) {
		return typeof value?.owner === 'string' && !!value.owner && Number.isFinite(value.expiresAt) &&
			value.expiresAt > now && value.expiresAt <= now + ESCORT_SHARED_ACTIVITY_LEASE_MS;
	}
	function releaseSharedEscortActivityOwner(job) {
		clearTimeout(job.timer);
		sharedEscortActivityOwners.delete(job.owner);
		try {
			if (GM_getValue(job.key,null)?.owner === job.owner) deleteHelperStorageValue(job.key);
		} catch {}
	}
	function waitForSharedEscortActivity(key,delayMs,cancelToken) {
		cancelToken.throwIfCancelled();
		return new Promise((resolve,reject) => {
			let settled=false,timer=null,unsubscribe=() => {};
			const listeners=[];
			const finish=error => {
				if (settled) return;
				settled=true; clearTimeout(timer); unsubscribe();
				for (const id of listeners) {try {GM_removeValueChangeListener(id);} catch {}}
				if (error) reject(error); else resolve();
			};
			try {
				if (typeof GM_addValueChangeListener === 'function' && typeof GM_removeValueChangeListener === 'function') {
					listeners.push(GM_addValueChangeListener(key,() => finish()));
					listeners.push(GM_addValueChangeListener(ESCORT_SHARED_ACTIVITY_CACHE_PREFIX + key.slice(ESCORT_SHARED_ACTIVITY_PENDING_PREFIX.length),() => finish()));
				}
			} catch {}
			timer=setTimeout(() => finish(),Math.max(1,Math.min(2000,delayMs)));
			unsubscribe=cancelToken.onCancel(reason => finish(createOperationCancelledError(reason)));
		});
	}
	async function inspectSharedEscortActivity(url,cancelToken,requestContext,options) {
		const inspect=(token,beforeDispatch=null) => inspectEscortClubAdActivityOnly(url,token,requestContext,{...options,useSharedCache:false,beforeDispatch});
		const id=parseAdIdFromUrl(url);
		if (!id) return inspect(cancelToken);
		const token=createOperationCancelToken('Wspólne sprawdzanie dostępności');
		const unsubscribe=cancelToken?.onCancel(reason => token.cancel(reason)) || (() => {});
		const job={key:ESCORT_SHARED_ACTIVITY_PENDING_PREFIX + id,owner:`${Date.now()}-${Math.random().toString(36).slice(2)}`,
			token,timer:null};
		const epoch=sharedEscortActivityCacheEpoch;
		sharedEscortActivityOwners.set(job.owner,job);
		if (!sharedEscortActivityPageHooked && typeof window !== 'undefined') {
			sharedEscortActivityPageHooked=true;
			window.addEventListener('pagehide',() => {
				for (const pending of [...sharedEscortActivityOwners.values()]) {
					pending.token.cancel('Zamknięto stronę');
					releaseSharedEscortActivityOwner(pending);
				}
			});
		}
		const remember=result => {
			if (result && typeof result === 'object') sharedEscortActivityResultTimes.set(result,{
				at:result.checkedAt || Date.now(),epoch:-1,managed:true});
			return result;
		};
		const skipped=(result,outcome='cached') => ({value:{sharedActivityResult:result},outcome});
		const cleared=() => remember({url,active:false,unknown:true,source:'activity-cache-cleared'});
		const renew=() => {
			try {
				const current=GM_getValue(job.key,null);
				if (current?.owner !== job.owner || !isSharedEscortActivityLeaseLive(current) || epoch !== sharedEscortActivityCacheEpoch) return;
				writeHelperStorageValue(job.key,{owner:job.owner,expiresAt:Date.now() + ESCORT_SHARED_ACTIVITY_LEASE_MS});
				job.timer=setTimeout(renew,30000);
			} catch {}
		};
		try {
			try {
				if (sharedEscortActivityLeasePruneAt <= Date.now()) {
					sharedEscortActivityLeasePruneAt=Date.now() + 300000;
					for (const key of GM_listValues()) if (key.startsWith(ESCORT_SHARED_ACTIVITY_PENDING_PREFIX) &&
						!isSharedEscortActivityLeaseLive(GM_getValue(key,null))) deleteHelperStorageValue(key);
				}
			} catch {}
			while (true) {
				token.throwIfCancelled();
				if (epoch !== sharedEscortActivityCacheEpoch) return cleared();
				const previous=readSharedEscortActivity(url,true),cached=previous && !previous.stale ? previous:null;
				requestContext={...requestContext,cacheKind:'activity',cacheReason:previous ? 'expired':'missing'};
				if (!cached && typeof ESCORT_SHARED_AD_REQUEST_PREFIX !== 'undefined') {
					let detailLease=null;
					try {detailLease=GM_getValue(ESCORT_SHARED_AD_REQUEST_PREFIX + id,null);} catch {}
					if (isSharedEscortAdLeaseLive(detailLease)) {
						await waitWithCancellation(250,token); continue;
					}
				}
				if (cached) { return remember(cached);}
				let current;
				try {
					current=GM_getValue(job.key,null);
					if (!isSharedEscortActivityLeaseLive(current) || current.owner === job.owner) {
						writeHelperStorageValue(job.key,{owner:job.owner,expiresAt:Date.now() + ESCORT_SHARED_ACTIVITY_LEASE_MS});
					} else {
						await waitForSharedEscortActivity(job.key,current.expiresAt - Date.now(),token);
						continue;
					}
				} catch (error) {
					if (isOperationCancelledError(error)) throw error;
					return await inspect(token);
				}
				await waitWithCancellation(40,token);
				try {current=GM_getValue(job.key,null);} catch {return await inspect(token);}
				if (!current) return cleared();
				if (current.owner !== job.owner) continue;
				clearTimeout(job.timer); renew();
				const beforeDispatch=(details=false) => {
					token.throwIfCancelled();
					if (epoch !== sharedEscortActivityCacheEpoch) return skipped(cleared(),'superseded');
					const hit=readSharedEscortActivity(url);
					if (!details && !hit && typeof ESCORT_SHARED_AD_REQUEST_PREFIX !== 'undefined' &&
						isSharedEscortAdLeaseLive(GM_getValue(ESCORT_SHARED_AD_REQUEST_PREFIX + id,null)))
						return {value:{sharedActivityRetry:true},outcome:'superseded'};
					if (hit) { return skipped(remember(hit));}
					let lease;
					try {lease=GM_getValue(job.key,null);} catch {return;}
					if (!lease) return skipped(cleared(),'superseded');
					if (lease.owner !== job.owner || !isSharedEscortActivityLeaseLive(lease)) {
						return {value:{sharedActivityRetry:true},outcome:'superseded'};
					}
					clearTimeout(job.timer); renew();
				};
				const result=await inspect(token,beforeDispatch);
				clearTimeout(job.timer);
				if (result.sharedActivityRetry) continue;
				let owns=false;
				try {owns=GM_getValue(job.key,null)?.owner === job.owner && epoch === sharedEscortActivityCacheEpoch;} catch {}
				remember(result);
				if (owns && result.source !== 'activity-cache' && (!/^current/.test(result.source || '') || result.checkedAt > 0))
					saveSharedEscortActivity(url,result,result.checkedAt || Date.now());
				return result;
			}
		} finally {
			unsubscribe();
			releaseSharedEscortActivityOwner(job);
		}
	}
	function isSharedEscortActivityEntryValid(value,now=Date.now()) {
		return Array.isArray(value) && value.length === 2 && Number.isFinite(value[0]) && value[0] > 0 &&
			value[0] <= now && (value[1] === 0 || value[1] === 1);
	}
	function isSharedEscortActivityEntryFresh(value,now=Date.now()) {
		return isSharedEscortActivityEntryValid(value,now) && now - value[0] < ESCORT_SHARED_ACTIVITY_CACHE_TTL_MS;
	}
	function pruneSharedEscortActivityCache(force=false) {
		const now=Date.now();
		if ((!force && sharedEscortActivityPruneAt > now)) return;
		sharedEscortActivityPruneAt=now + 5 * 60 * 1000;
		try {
			for (const key of GM_listValues()) if (key.startsWith(ESCORT_SHARED_ACTIVITY_CACHE_PREFIX) &&
				!isSharedEscortActivityEntryValid(GM_getValue(key,null),now)) deleteHelperStorageValue(key);
		} catch {}
	}
	function clearSharedEscortActivityCache() {
		sharedEscortActivityCacheEpoch++;
		sharedEscortActivityRequests.clear(); sharedEscortActivityPruneAt=0;
		if (typeof sharedEscortAdRequests !== 'undefined') {
			for (const job of sharedEscortAdRequests.values()) job.token.cancel('Wyczyszczono cache');
			sharedEscortAdRequests.clear(); sharedEscortAdPruneAt=0;
		}
		let removed=0;
		try {
			for (const key of GM_listValues()) {
				if (key.startsWith(ESCORT_SHARED_ACTIVITY_CACHE_PREFIX)) {deleteHelperStorageValue(key); removed++;}
				else if (['vm_escort_activity_pending_v1_','vm_escort_ad_request_v1_','vm_escort_ad_result_v1_',
					'vm_escort_ad_consumer_v1_'].some(prefix => key.startsWith(prefix))) deleteHelperStorageValue(key);
			}
		} catch {}
		return removed;
	}
	function readSharedEscortActivity(url,allowStale=false) {
		const id=parseAdIdFromUrl(url);
		if (!id) return null;
		pruneSharedEscortActivityCache();
		try {
			const key=ESCORT_SHARED_ACTIVITY_CACHE_PREFIX + id,value=GM_getValue(key,null);
			if (!isSharedEscortActivityEntryValid(value)) {
				if (value !== null) deleteHelperStorageValue(key);
				return null;
			}
			const stale=!isSharedEscortActivityEntryFresh(value);
			if (stale && !allowStale) return null;
			return {url:normalizeEscortiAdUrl(url),active:value[1] === 1,city:null,imageUrl:'',source:'activity-cache',checkedAt:value[0],stale};
		} catch {return null;}
	}
	function saveSharedEscortActivity(url,result,checkedAt=Date.now()) {
		if (!result || result.stale || result.unknown || result.error || typeof result.active !== 'boolean') return;
		const id=parseAdIdFromUrl(url),value=[checkedAt,result.active ? 1:0];
		if (!id || !isSharedEscortActivityEntryValid(value)) return;
		pruneSharedEscortActivityCache();
		try {
			const key=ESCORT_SHARED_ACTIVITY_CACHE_PREFIX + id,previous=GM_getValue(key,null);
			if (isSharedEscortActivityEntryValid(previous) && (previous[0] > checkedAt ||
				(previous[0] === checkedAt && previous[1] === value[1]))) return;
			writeHelperStorageValue(key,value);
		} catch {}
	}
	function shareEscortActivityResult(url,result) {
		const check=result && typeof result === 'object' ? sharedEscortActivityResultTimes.get(result):null;
		if (check?.epoch === sharedEscortActivityCacheEpoch) saveSharedEscortActivity(url,result,check.at);
	}
	function activeAdsCacheKey(adUrls) {
		return [...new Set((adUrls || []).map(url => normalizeEscortiAdUrl(url) || url).filter(Boolean))] .sort() .join('|');
	}
	function escortiActivityAdIdsKey(adUrls) {
		return [...new Set( (adUrls || []) .map(parseAdIdFromUrl) .filter(Boolean) )] .sort((a,b) => Number(a) - Number(b))
			.join(',');
	}
	function summarizeActiveAdResults(results) {
		const active=results.filter(item => item.active);
		const unknown=results.filter(item => item.unknown).length;
		const knownCities=active.map(item => normalizeEscortCity(item.city)).filter(Boolean);
		const uniqueCities=[...new Set( knownCities.map(city => normalizeEscortCityKey(city))
		)].map(key => knownCities.find(city => normalizeEscortCityKey(city) === key));
		const allActiveCitiesKnown=active.length > 0 && knownCities.length === active.length;
		let cityText='';
		if (active.length > 0) {
			if (allActiveCitiesKnown && uniqueCities.length === 1) cityText=uniqueCities[0];
			else if (uniqueCities.length > 1) cityText='różne miasta';
			else cityText='miasto ?';
		}
		return {
			totalChecked:results.length, activeCount:active.length, unknownCount:unknown, staleCount:results.filter(item => item.stale).length, cities:uniqueCities,
			allActiveCitiesKnown, cityText, results
		};
	}
	function compactEscortiActivitySummary(adUrls,summary) {
		return {
			adIdsKey:escortiActivityAdIdsKey(adUrls), totalChecked:Number(summary?.totalChecked) || 0,
			activeCount:Number(summary?.activeCount) || 0, unknownCount:Number(summary?.unknownCount) || 0,
			cities:Array.isArray(summary?.cities) ? [...summary.cities]:[],
			allActiveCitiesKnown:!!summary?.allActiveCitiesKnown, cityText:String(summary?.cityText || ''), checkedAt:Date.now()
		};
	}
	function getCachedEscortiActivitySummary(adId,searchMode,adUrls=null) {
		if (!adId || !searchMode) return null;
		const stored=getStoredEscortAdData(adId);
		const summary=stored?.escortiActivity?.[searchMode];
		if (!summary || typeof summary !== 'object') return null;
		if ( Array.isArray(adUrls) && summary.adIdsKey !== escortiActivityAdIdsKey(adUrls) ) {
			return null;
		}
		if ( !Number.isFinite(Number(summary.totalChecked)) || !Number.isFinite(Number(summary.activeCount)) ||
			!Number.isFinite(Number(summary.unknownCount)) ) {
			return null;
		}
		return {
			...summary, totalChecked:Number(summary.totalChecked), activeCount:Number(summary.activeCount),
			unknownCount:Number(summary.unknownCount), cities:Array.isArray(summary.cities) ? summary.cities:[],
			allActiveCitiesKnown:!!summary.allActiveCitiesKnown, cityText:String(summary.cityText || '')
		};
	}
	function getLatestCachedEscortiActivitySummary(adId,searchModes) {
		return (Array.isArray(searchModes) ? searchModes:[searchModes])
			.map(mode => getCachedEscortiActivitySummary(adId,mode)) .filter(Boolean)
			.sort((a,b) => Number(b.checkedAt || 0) - Number(a.checkedAt || 0))[0] || null;
	}
	function getEscortiAdUrlsFromActivitySummary(summary) {
		return [...new Set( String(summary?.adIdsKey || '') .split(',') .map(value => value.trim())
				.filter(value => /^\d+$/.test(value)) .map(id => `https://pl.escort.club/anons/${id}.html`) )];
	}
	function recoverEscortiResultAdUrls(result,adId,searchMode) {
		if (result?.status !== 'ok' || Number(result?.profiles) === 0) return [];
		const normalizeUrls=values => [...new Set( (Array.isArray(values) ? values:[]) .map(url => normalizeEscortiAdUrl(url))
				.filter(Boolean) )];
		const directUrls=normalizeUrls([ ...(Array.isArray(result.adUrls) ? result.adUrls:[]),
			...(Array.isArray(result.adIds) ? result.adIds:[]) .map(id => /^\d+$/.test(String(id || '').trim())
					? `https://pl.escort.club/anons/${String(id).trim()}.html`
					:null) ]);
		const activitySummary=getCachedEscortiActivitySummary( adId, searchMode );
		const activityUrls=normalizeUrls( getEscortiAdUrlsFromActivitySummary(activitySummary) );
		const profileCacheUrls=normalizeUrls( getCachedEscortiAdUrlsForProfiles(normalizeProfileUrls(result)) );
		const combinedUrls=normalizeUrls([ ...directUrls, ...activityUrls, ...profileCacheUrls ]);
		const reportedCount=Number(result.adLinks);
		const profileSummaries=Array.isArray(result.profileSummaries)
			? result.profileSummaries
			:[];
		const singleProfileCount=profileSummaries.length === 1
			? Number(profileSummaries[0]?.adLinks)
			:NaN;
		const activityCount=Number(activitySummary?.totalChecked);
		const expectedCount=Number.isFinite(reportedCount) && reportedCount > 0
			? reportedCount
			:(Number.isFinite(singleProfileCount) && singleProfileCount > 0
				? singleProfileCount
				:(Number.isFinite(activityCount) && activityCount > 0
					? activityCount
					:reportedCount));
		if (Number.isFinite(expectedCount) && expectedCount >= 0) {
			const exactMatch=[ directUrls, activityUrls, profileCacheUrls, combinedUrls
			].find(urls => urls.length === expectedCount);
			if (exactMatch) return exactMatch;
			return directUrls;
		}
		return directUrls.length
			? directUrls
			:(activityUrls.length ? activityUrls:profileCacheUrls);
	}
	function hydrateEscortiResultAdUrls(result,adId,searchMode) {
		if (result?.status !== 'ok') return result;
		const adUrls=recoverEscortiResultAdUrls(result,adId,searchMode);
		if (!adUrls.length) return result;
		const currentUrls=(Array.isArray(result.adUrls) ? result.adUrls:[]) .map(url => normalizeEscortiAdUrl(url))
			.filter(Boolean);
		const unchanged=currentUrls.length === adUrls.length && currentUrls.every((url,index) => url === adUrls[index]);
		if (unchanged) return result;
		return {
			...result, adIds:adUrls.map(parseAdIdFromUrl).filter(Boolean), adUrls, adLinks:adUrls.length
		};
	}
	function saveEscortiActivitySummary(adId,searchModes,adUrls,summary) {
		if (!adId || !summary) return;
		if (!persistentCacheKeyIsLoaded(escortAdDataCacheKey(adId))) {
			ensurePersistentCacheKeys([escortAdDataCacheKey(adId)]).then(() => saveEscortiActivitySummary(adId,searchModes,adUrls,summary)).catch(error => log('Nie udało się zapisać aktywności w cache',error));
			return;
		}
		const modes=[...new Set( (Array.isArray(searchModes) ? searchModes:[searchModes]).filter(Boolean) )];
		if (!modes.length) return;
		const entry=compactEscortiActivitySummary(adUrls,summary);
		const activityPatch=Object.fromEntries( modes.map(mode => [mode,{...entry,cities:[...entry.cities]}]) );
		const cacheId=String(adId);
		const stored=getStoredEscortAdData(adId);
		if (!stored) {
			pendingEscortiActivityCacheWrites.set(cacheId,{
				...(pendingEscortiActivityCacheWrites.get(cacheId) || {}), ...activityPatch
			});
			return;
		}
		try {
			const escortiActivity={
				...(stored.escortiActivity || {}), ...activityPatch
			};
			const value={...stored,escortiActivity};
			writePersistentCacheValue(escortAdDataCacheKey(adId),value);
			const memoryValue=escortAdDataMemoryCache.get(cacheId);
			if (memoryValue) {
				escortAdDataMemoryCache.set(cacheId,{
					...memoryValue, escortiActivity
				});
			}
		} catch (error) {
			log(`Nie udało się zapisać aktywności Escorti dla anonsu ${adId}`,error);
		}
	}
	async function scanEscortAdsHeadFirst(adUrls,onProgress,forceRefresh,options) {
		const requestContext=options.requestContext || createRequestContext('active-ad-scan');
		const urls=[...new Set((adUrls || []).map(url => normalizeEscortiAdUrl(url)).filter(Boolean))];
		const results=new Array(urls.length),previews=new Map();
		let next=0,checked=0;
		const notify=(index=null,result=null) => {
			const displayed=urls.map((_,i) => results[i] || previews.get(i)).filter(Boolean);
			const summary=summarizeActiveAdResults(displayed);
			try {
				onProgress?.({phase:'details',checked,total:urls.length,activeCount:summary.activeCount,
					summary,url:index == null ? null:urls[index],result});
			} catch {}
		};
		for (let index=0;index < urls.length;index++) {
			const cached=readSharedEscortActivity(urls[index],true);
			if (cached) {
				if (!cached.stale && !cached.active) {results[index]=cached; checked++;}
				else previews.set(index,cached);
			}
		}
		notify();
		results.forEach((result,index) => {if (result) notify(index,result);});
		const worker=async () => {
			while (next < urls.length) {
				const index=next++,url=urls[index];
				if (results[index]) continue;
				let result=options.verifiedResults?.get(url);
				if (!result?.adData) result=await inspectEscortClubAd(url,null,options.forceDetails ?? forceRefresh,
					requestContext,null,{minCheckedAt:options.detailMinCheckedAt || 0});
				if ((result.unknown || result.error) && previews.has(index))
					result={...previews.get(index),unknown:true,refreshFailed:true,detailsUnknown:true};
				results[index]=result; checked++; notify(index,result);
			}
		};
		await Promise.all(Array(Math.min(getEscortActivityDetailConcurrency(),urls.length)).fill().map(worker));
		const summary=summarizeActiveAdResults(results.filter(Boolean));
		const detailsUnknownCount=results.filter(result => result?.detailsUnknown || (result?.active && !result.adData)).length;
		return {...summary,detailsUnknownCount};
	}
	function scanActiveEscortAds( adUrls, onProgress=null, forceRefresh=false, options={} ) {
		if (options.headFirst) return scanEscortAdsHeadFirst(adUrls,onProgress,forceRefresh,options);
		const activityOnly=options?.activityOnly === true,useActivityCache=activityOnly && options.cacheActivity !== false;
		const requestContext=options.requestContext || createRequestContext('active-ad-scan');
		const urls=[...new Set( (adUrls || []) .map(url => normalizeEscortiAdUrl(url) || url) .filter(Boolean) )];
		const baseKey=activeAdsCacheKey(urls);
		const key=baseKey && !useActivityCache ? `${activityOnly ? 'activity:':'details:'}${baseKey}`:'';
		if (!forceRefresh && key && escortActiveAdsSummaryCache.has(key)) {
			const cached=escortActiveAdsSummaryCache.get(key);
			return options.cacheActivity ? cached.then(summary => {
				for (const result of summary.results || []) shareEscortActivityResult(result?.url,result);
				return summary;
			}):cached;
		}
		if (forceRefresh && key) escortActiveAdsSummaryCache.delete(key);
		const promise=(async () => {
			const results=new Array(urls.length),previews=new Map();
			let nextIndex=0;
			let completed=0;
			const finish=(index,result,preview=false) => {
				const url=urls[index];
				if (!preview) {
					if ((result?.unknown || result?.error) && previews.has(index)) result={...previews.get(index),unknown:true,refreshFailed:true};
					results[index]=result;
					if (options.cacheActivity || useActivityCache) shareEscortActivityResult(url,result);
					completed++;
				}
				const displayed=urls.map((_,i) => results[i] || previews.get(i)).filter(Boolean);
				try {
					onProgress?.({checked:completed,total:urls.length,
						activeCount:displayed.filter(item => item.active).length,url,result,preview,
						...(options.headFirstActivity ? {summary:summarizeActiveAdResults(displayed)}:{})});
				} catch {}
			};
			// Cache przed uruchomieniem kolejki.
			if (useActivityCache) urls.forEach((url,index) => {
				const cached=!options.verifiedResults?.get(url) && readSharedEscortActivity(url,true);
				if (!cached) return;
				if (cached.stale) previews.set(index,cached);
				finish(index,cached,cached.stale);
			});
			async function worker() {
				while (true) {
					const index=nextIndex++;
					if (index >= urls.length) return;
					if (results[index]) continue;
					const url=urls[index];
					const checkCache=activityOnly
						? escortActiveAdProbeCache
						:escortActiveAdCheckCache;
					const cached=useActivityCache ? readSharedEscortActivity(url):null;
					let checkPromise=options.verifiedResults?.get(url) || cached || (useActivityCache
						? sharedEscortActivityRequests.get(url):forceRefresh ? null:checkCache.get(url));
					if (!checkPromise) {
						const epoch=sharedEscortActivityCacheEpoch;
						checkPromise=(activityOnly ? inspectEscortClubAdActivityOnly(url,null,requestContext,
							{strict:!!options.headFirstActivity,forceRefresh,useSharedCache:useActivityCache})
							:inspectEscortClubAd(url,null,false,requestContext)).then(result => {
							if (result && typeof result === 'object' && !sharedEscortActivityResultTimes.get(result)?.managed) {
								sharedEscortActivityResultTimes.set(result,{at:result.checkedAt || Date.now(),epoch});
							}
							return result;
						});
						if (useActivityCache) {
							sharedEscortActivityRequests.set(url,checkPromise);
							const forget=() => {if (sharedEscortActivityRequests.get(url) === checkPromise) sharedEscortActivityRequests.delete(url);};
							checkPromise.then(forget,forget);
						} else checkCache.set(url,checkPromise);
					}
					const result=await checkPromise;
					finish(index,result);
				}
			}
			const workerCount=Math.min( activityOnly && !GARSO_MENU_ONLY_MODE
					? ESCORT_ACTIVE_PROBE_CONCURRENT
					:getEscortActivityDetailConcurrency(), Math.max(1,urls.length) );
			const workers=[];
			for (let workerIndex=0; workerIndex < workerCount; workerIndex++) {
				workers.push(worker());
			}
			await Promise.all(workers);
			return summarizeActiveAdResults(results.filter(Boolean));
		})();
		if (key) escortActiveAdsSummaryCache.set(key,promise);
		promise.catch(() => {
			if (key) escortActiveAdsSummaryCache.delete(key);
		});
		return promise;
	}
	function buildActiveAdsTitle(baseTitle,detailResult,summary) {
		const lines=[baseTitle];
		if (typeof detailResult?.adLinks === 'number') {
			lines.push(`Anonse znalezione w profilu Escorti: ${detailResult.adLinks}.`);
		}
		lines.push(`Aktywne na Escort.club: ${summary.activeCount} z ${summary.totalChecked}.`);
		if (summary.activeCount > 0) {
			if (summary.allActiveCitiesKnown && summary.cities.length === 1) {
				lines.push(`Wszystkie aktywne anonse są z miasta: ${summary.cities[0]}.`);
			} else if (summary.cities.length > 1) {
				lines.push(`Aktywne anonse są z różnych miast: ${summary.cities.join(', ')}.`);
			} else {
				lines.push('Nie udało się jednoznacznie ustalić miasta wszystkich aktywnych anonsów.');
			}
		}
		return lines.join('\n');
	}
	async function getEscortiDetailForSingleAd( adId, searchValue, searchMode, forceRefresh=false ) {
		await ensureEscortPageCache([adId]);
		const rawCached=getListCache(adId,searchMode);
		const cached=hydrateEscortiResultAdUrls( rawCached, adId, searchMode );
		if (cached && cached !== rawCached) {
			setListCache(adId,cached,searchMode);
		}
		const cacheHasProfileSummary=cached?.profiles === 0 || ( Array.isArray(cached?.profileSummaries) &&
			cached.profileSummaries.length > 0 );
		if ( !forceRefresh && isListCacheFresh(cached) && cacheHasProfileSummary ) {
			return {result:cached,source:'cache'};
		}
		const refreshed=await checkEscortiBackground(searchValue,{
			timeoutMs:60000, mergeProfiles:true, preferDirect:true
		});
		if (refreshed?.status === 'ok') {
			const hydratedRefreshed=hydrateEscortiResultAdUrls( refreshed, adId, searchMode );
			setListCache(adId,hydratedRefreshed,searchMode);
			return {
				result:getListCache(adId,searchMode) || hydratedRefreshed, source:'network'
			};
		}
		if (cached?.status === 'ok') {
			return {result:cached,source:'stale-cache'};
		}
		return {result:refreshed,source:'network'};
	}
	function createGarsoButton(displayTerm,searchTerm,searchMode) {
		const btn=makeElement('button','vm-research-panel-row');
		btn.dataset.garsoDisplayTerm=displayTerm;
		btn.dataset.garsoSearchMode=searchMode;
		const cached=getGarsoSearchCountCache(searchMode,searchTerm);
		if (cached) btn.dataset.garsoCountCheckedAt=String(cached.checkedAt);
		if (searchMode === 'phone' && cached) {
			btn.dataset.garsoPhoneSearchVariant=cached.phoneSearchVariant || 'both';
		}
		if (cached?.fresh) {
			btn.dataset.garsoStatus='result';
			btn.dataset.garsoCount=String(cached.count);
			btn.dataset.garsoCacheState='fresh';
		} else {
			btn.dataset.garsoStatus=SETTINGS.autoGarsoCheck ? 'checking':'manual';
		}
		renderGarsoButton(btn);
		btn.addEventListener('click',async e => {
			e.preventDefault();
			e.stopPropagation();
			if (btn.dataset.garsoStatus === 'manual') {
				btn.disabled=true;
				try {await updateGarsoStatus(btn,searchTerm,searchMode,false,false);}
				finally {btn.disabled=false;}
			}
			startSearchProcess(searchTerm,btn);
		});
		return btn;
	}
	function formatResearchPanelList(values,maxItems=5) {
		const clean=[...new Set((values || []).filter(Boolean))];
		if (clean.length <= maxItems) return clean.join(', ');
		return `${clean.slice(0,maxItems).join(', ')} (+${clean.length - maxItems})`;
	}
	function formatResearchPriceDuration(durationKey) {
		if (durationKey === 'night') return 'cała noc';
		const minutes=Number(durationKey);
		if (!Number.isFinite(minutes) || minutes <= 0) return String(durationKey);
		if (minutes < 60) return `${minutes} min`;
		const hours=minutes / 60;
		return `${String(hours).replace('.',',')} h`;
	}
	function normalizeEscortDescriptionComparisonValue(value) {
		return String(value || '').replace(/\s+/gu,'');
	}
	function getEscortActiveAdsDescriptionComparison(summary) {
		const active=(summary?.results || []) .filter(item => item?.active);
		if (active.length < 2) {
			return {
				activeCount:active.length, comparableCount:0, missingCount:active.length, same:null
			};
		}
		const descriptions=active.map(item => ({
			item, raw:String(item.adData?.description || ''), normalized:normalizeEscortDescriptionComparisonValue(
				item.adData?.description )
		}));
		const comparable=descriptions.filter(entry => entry.normalized);
		const missingCount=descriptions.length - comparable.length;
		const uniqueDescriptions=[...new Set( comparable.map(entry => entry.normalized) )];
		return {
			activeCount:active.length, comparableCount:comparable.length, missingCount,
			same:uniqueDescriptions.length > 1 ? false:
				missingCount === 0 && comparable.length >= 2 && !(summary?.unknownCount > 0) ? true:null
		};
	}
	function getEscortActiveAdsConsistencyWarnings(summary,statFields=[]) {
		const active=(summary?.results || []) .filter(item => item?.active && item.adData)
			.map(item => ({item,data:item.adData}));
		if (active.length < 2) return [];
		const warnings=[];
		const missing='__brak__';
		const displayValue=value => value === missing ? 'brak':value;
		const uniqueComparedValues=values => [...new Set(values)];
		const cities=uniqueComparedValues(active.map(({item,data}) => {
			const city=normalizeEscortCity(data.location?.city || item.city);
			return city || missing;
		}));
		if (cities.length > 1) {
			warnings.push(`Różne miasta: ${formatResearchPanelList(cities.map(displayValue))}`);
		}
		const ages=uniqueComparedValues(active.map(({data}) => {
			const entry=getEscortObjectEntryByNormalizedKey(data.stats,'Wiek');
			const match=normalizeEscortAdText(entry?.[1]).match(/\d{1,3}/);
			return match ? match[0]:missing;
		}));
		if (ages.length > 1) {
			warnings.push(`Różny wiek: ${formatResearchPanelList(ages.map(displayValue))}`);
		}
		const statLabels=new Map();
		for (const {data} of active) {
			for (const rawLabel of Object.keys(data.stats || {})) {
				const key=normalizeEscortChangeComparisonValue(rawLabel).replace(/:\s*$/,'');
				if (!key || key === 'wiek' || normalizeEscortClubPriceDurationKey(key)) continue;
				if (!statLabels.has(key)) statLabels.set(key,normalizeEscortAdText(rawLabel));
			}
		}
		const differentStats=[];
		for (const [key,label] of statLabels) {
			const rawValues=active.map(({data}) => {
				const entry=Object.entries(data.stats || {}).find(([candidate]) =>
					normalizeEscortChangeComparisonValue(candidate).replace(/:\s*$/,'') === key );
				return entry ? (normalizeEscortAdText(entry[1]) || 'brak'):'brak';
			});
			const comparedValues=rawValues.map(value => normalizeEscortChangeComparisonValue(value) || missing );
			if (uniqueComparedValues(comparedValues).length > 1) {
				statFields.push(key);
				differentStats.push( `${label} (${formatResearchPanelList(rawValues)})` );
			}
		}
		if (differentStats.length) {
			warnings.push(`Różne parametry: ${formatResearchPanelList(differentStats)}`);
		}
		const priceDurationKeys=new Set( active.flatMap(({data}) => Object.keys(data.prices || {})) );
		const differentPrices=[];
		for (const durationKey of priceDurationKeys) {
			const comparedValues=[];
			const displayedValues=[];
			for (const {data} of active) {
				const prices=data.prices || {};
				if (!Object.prototype.hasOwnProperty.call(prices,durationKey)) {
					comparedValues.push('__brak_pozycji__');
					displayedValues.push('brak pozycji');
					continue;
				}
				const price=prices[durationKey];
				const amount=price?.amount != null && Number.isFinite(Number(price.amount))
					? String(Number(price.amount))
					:'__brak_ceny__';
				const currency=normalizeEscortAdText(price?.currency).toUpperCase();
				comparedValues.push(`${amount}|${currency}`);
				displayedValues.push(formatEscortTilePrice(price) || 'brak ceny');
			}
			if (uniqueComparedValues(comparedValues).length > 1) {
				differentPrices.push( `${formatResearchPriceDuration(durationKey)} ` +
					`(${formatResearchPanelList(displayedValues)})` );
			}
		}
		if (differentPrices.length) {
			warnings.push(`Różne ceny: ${formatResearchPanelList(differentPrices)}`);
		}
		const availabilityDays=new Map();
		for (const {data} of active) {
			for (const day of Object.keys(data.availability || {})) {
				const key=normalizeEscortChangeComparisonValue(day).replace(/:\s*$/,'');
				if (key && !availabilityDays.has(key)) {
					availabilityDays.set(key,normalizeEscortAdText(day).replace(/:\s*$/,''));
				}
			}
		}
		const differentAvailability=[];
		for (const [dayKey,dayLabel] of availabilityDays) {
			const values=active.map(({data}) => {
				const entry=Object.entries(data.availability || {}).find(([day]) =>
					normalizeEscortChangeComparisonValue(day).replace(/:\s*$/,'') === dayKey );
				return entry
					? (normalizeEscortChangeComparisonValue(entry[1]) || missing)
					:missing;
			});
			if (uniqueComparedValues(values).length > 1) {
				differentAvailability.push(dayLabel);
			}
		}
		if (differentAvailability.length) {
			warnings.push( `Różne godziny przyjmowania: ${formatResearchPanelList(differentAvailability)}` );
		}
		const tagSets=active.map(({data}) => new Map( (Array.isArray(data.tags) ? data.tags:[]).map(tag => [
				normalizeEscortChangeComparisonValue(tag), normalizeEscortAdText(tag) ]) ));
		const tagSignatures=tagSets.map(tags => [...tags.keys()].sort().join('|'));
		if (uniqueComparedValues(tagSignatures).length > 1) {
			const tagCounts=new Map();
			const tagLabels=new Map();
			for (const tags of tagSets) {
				for (const [key,label] of tags) {
					tagCounts.set(key,(tagCounts.get(key) || 0) + 1);
					if (!tagLabels.has(key)) tagLabels.set(key,label);
				}
			}
			const differingTags=[...tagCounts] .filter(([,count]) => count !== tagSets.length)
				.map(([key]) => tagLabels.get(key));
			warnings.push( `Różne tagi${differingTags.length ? `: ${formatResearchPanelList(differingTags)}`:''}` );
		}
		const descriptionComparison=getEscortActiveAdsDescriptionComparison(summary);
		if (descriptionComparison.same === false) {
			warnings.push('Różny opis');
		}
		return warnings;
	}
	function buildEscortClubPhoneSearchUrl(phone) {
		if (!normalizeEscortCachedPhone(phone)) return '';
		const query=normalizeEscortAdText(phone) .replace(/^\+/,'') .replace(/[^\d]+/g,' ') .trim();
		const params=new URLSearchParams({province:'',q:query});
		return `https://pl.escort.club/anonse/towarzyskie/poland/?${params}`;
	}
	function buildEscortiSearchUrl(value) {
		const params=new URLSearchParams({search:String(value || ''),vm_mode:'open'});
		return `${ESCORTI_BASE_URL}search?${params}`;
	}
	function buildEscortiPhoneSearchUrl(phone) {
		const digits=normalizeEscortCachedPhone(phone);
		return digits
			? buildEscortiSearchUrl(digits)
			:'';
	}
	function normalizeGlobalQuickSearchPhrase(value) {
		let phrase=String(value || '').trim();
		if (!phrase) return '';
		phrase=phrase .replace(/^[\s"“”„]+|[\s"“”„]+$/g,'') .replace(/["“”„]+/g,' ') .replace(/\s+/g,' ') .trim();
		return phrase;
	}
	function quoteGlobalQuickSearchPhrase(value) {
		const phrase=normalizeGlobalQuickSearchPhrase(value);
		return phrase ? `"${phrase}"`:'';
	}
	function parseGlobalQuickSearchValue(value) {
		const raw=String(value || '').trim();
		if (!raw) return null;
		const adMatch=raw.match(/(?<![\w.@/-])(?:https?:\/\/)?(?:(?:[a-z]{2}|www)\.)?escort\.club\/(?:anons|personal-ads)\/(\d+)\.html\b/i)
			|| raw.match(/\b(?:anons|personal-ads)\s*\/\s*(\d+)\.html\b/i);
		if (adMatch) {
			const adId=adMatch[1];
			return {
				type:'ad', adId, adUrl:`https://pl.escort.club/anons/${adId}.html`
			};
		}
		const phone=normalizeEscortCachedPhone(raw);
		if (phone) return {type:'phone',phone};
		const phrase=normalizeGlobalQuickSearchPhrase(raw);
		return phrase ? {type:'phrase',phrase}:null;
	}
	function buildGlobalQuickSearchUrl(target,parsed) {
		if (!parsed) return '';
		if (target === 'escort-club') {
			if (parsed.type === 'ad') return parsed.adUrl;
			if (parsed.type === 'phone') return buildEscortClubPhoneSearchUrl(parsed.phone);
			const phrase=quoteGlobalQuickSearchPhrase(parsed.phrase);
			if (!phrase) return '';
			const params=new URLSearchParams({province:'',q:phrase});
			return `https://pl.escort.club/anonse/towarzyskie/poland/?${params}`;
		}
		if (target === 'escorti') {
			if (parsed.type === 'ad') {
				return buildEscortiSearchUrl(parsed.adUrl);
			}
			if (parsed.type === 'phone') return buildEscortiPhoneSearchUrl(parsed.phone);
			const phrase=normalizeGlobalQuickSearchPhrase(parsed.phrase);
			return phrase
				? buildEscortiSearchUrl(phrase)
				:'';
		}
		return '';
	}
	async function openGlobalQuickSearch(target,parsed,openMode='new-tab') {
		if (!parsed) return;
		if (target === 'garso') {
			const garsoPhone=parsed.type === 'phone'
				? (formatEscortPhoneForClipboard(parsed.phone,'local-hyphens') || parsed.phone)
				:'';
			const searchTerm=parsed.type === 'ad'
				? buildGarsoAdLinkSearchTerm(parsed.adId)
				:parsed.type === 'phone'
					? `"${garsoPhone}"`
					:quoteGlobalQuickSearchPhrase(parsed.phrase);
			if (!searchTerm) throw new Error('Nie udało się przygotować wyszukiwania Garsoniery.');
			const auth=await getGarsoAuth(null,true);
			await submitGarsoSearchFormToNewTab( searchTerm, auth.secureHash, auth.sessionId, {active:true} );
			return;
		}
		const url=buildGlobalQuickSearchUrl(target,parsed);
		if (!url) throw new Error('Nie udało się przygotować adresu wyszukiwania.');
		if (openMode === 'same-tab') location.assign(url);
		else GM_openInTab(url,{active:true,insert:true});
	}
	function initGlobalQuickSearch(forceShow=false) {
		if (window.top !== window.self) {
			detachHelperNode(document.getElementById(GLOBAL_QUICK_SEARCH_ID));
			return;
		}
		const hostName=location.hostname.toLowerCase();
		const supportedHosts=new Set([ 'escorti.pl', 'www.escorti.pl', 'garsoniera.com.pl', 'www.garsoniera.com.pl' ]);
		if (!IS_ESCORT_CLUB && !supportedHosts.has(hostName)) return;
		const mount=() => {
			if (!document.body) return false;
			detachHelperNode(document.getElementById(GLOBAL_QUICK_SEARCH_ID));
			if (!SETTINGS.showTopPhoneSearch && !forceShow) return true;
			const root=prepareHelperStyleApi( makeElement('div','vm-global-quick-search') );
			root.id=GLOBAL_QUICK_SEARCH_ID;
			Object.assign(root.style,{
				position:'fixed', right:'16px', bottom:'16px', zIndex:'2147483000', display:'flex', flexDirection:'row-reverse',
				alignItems:'flex-end', gap:'0', fontFamily:'var(--vm-ui-font-family, Lato, Arial, sans-serif)',
				transition:'right .22s ease', pointerEvents:'none'
			});
			const trigger=makeElement('button','vm-global-quick-search-trigger',null,{type:'button',title:'Szukaj numeru telefonu, linku Escort.club lub nazwy / frazy'});
			trigger.setAttribute('aria-label',trigger.title);
			trigger.innerHTML='<svg viewBox="0 0 24 24" width="22" height="22" focusable="false" aria-hidden="true"><path fill="currentColor" d="M9.5 3a6.5 6.5 0 1 0 3.96 11.65L19.81 21 21 19.81l-6.35-6.35A6.5 6.5 0 0 0 9.5 3Zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Z"/></svg>';
			Object.assign(trigger.style,{
				width:'46px', height:'46px', padding:'0',
				border:'1px solid color-mix(in srgb, var(--vm-ui-accent-contrast, #fff) 32%, var(--vm-ui-border, #bbb))',
				borderRadius:'50%', display:'inline-flex', alignItems:'center', justifyContent:'center',
				background:'var(--vm-ui-accent, #f54da3)', color:'var(--vm-ui-accent-contrast, #fff)',
				boxShadow:'var(--vm-ui-shadow, 0 4px 16px rgba(0,0,0,.28))', cursor:'pointer', boxSizing:'border-box',
				pointerEvents:'auto'
			});
			const panel=makeElement('div','vm-global-quick-search-panel');
			Object.assign(panel.style,{
				width:'350px', maxWidth:'calc(100vw - 78px)', marginRight:'8px', padding:'8px',
				border:'1px solid color-mix(in srgb, var(--vm-ui-accent, #f54da3) 42%, var(--vm-ui-border, #bbb))',
				borderRadius:'8px', background:'var(--vm-ui-panel-bg, #1f1821)',
				boxShadow:'var(--vm-ui-shadow, 0 5px 22px rgba(0,0,0,.32))', boxSizing:'border-box', opacity:'0',
				visibility:'hidden', pointerEvents:'none', transform:'translateX(7px)',
				transition:'opacity .14s ease, transform .14s ease, visibility .14s ease'
			});
			const form=makeElement('form','vm-global-quick-search-form');
			Object.assign(form.style,{
				display:'flex', flexDirection:'column', gap:'6px', margin:'0'
			});
			const input=makeElement('input','vm-global-quick-search-input',null,{type:'text',inputMode:'search',autocomplete:'off',placeholder:'Numer telefonu, link Escort.club lub nazwa / fraza'});
			input.title=input.placeholder;
			input.classList.add('vm-ui-field');
			Object.assign(input.style,{width:'100%'});
			input.addEventListener('input',() => input.setCustomValidity(''));
			const defaultTarget=normalizeTopPhoneSearchEnterTarget( SETTINGS.topPhoneSearchEnterTarget );
			const openMode=normalizeTopPhoneSearchOpenMode( SETTINGS.topPhoneSearchOpenMode );
			const buttons=makeElement('div','vm-global-quick-search-buttons');
			Object.assign(buttons.style,{
				display:'grid', gridTemplateColumns:'repeat(3, minmax(0, 1fr))', gap:'5px'
			});
			const setBusy=busy => {
				input.disabled=busy;
				buttons.querySelectorAll('button').forEach(button => {
					button.disabled=busy;
					button.style.opacity=busy ? '.6':'1';
				});
			};
			const getParsed=() => {
				const parsed=parseGlobalQuickSearchValue(input.value);
				if (parsed) {
					input.setCustomValidity('');
					return parsed;
				}
				input.setCustomValidity('Wpisz numer telefonu, link do anonsu Escort.club albo nazwę / dowolną frazę.');
				input.reportValidity();
				input.focus();
				return null;
			};
			const runSearch=async target => {
				if (input.disabled) return;
				const parsed=getParsed();
				if (!parsed) return;
				setBusy(true);
				try {
					await openGlobalQuickSearch(target,parsed,openMode);
				} catch (error) {
					log('Błąd podręcznej wyszukiwarki',error);
					input.setCustomValidity(error?.message || 'Nie udało się otworzyć wyszukiwania.');
					input.reportValidity();
				} finally {
					setBusy(false);
				}
			};
			const buttonDefs=[ ['garso','Garso'], ['escort-club','Escort.club'], ['escorti','Escorti.pl'] ];
			for (const [target,label] of buttonDefs) {
				const button=makeElement('button','vm-global-quick-search-button',label);
				button.type='button';
				button.dataset.target=target;
				button.title=target === 'garso'
					? 'Szukaj na Garsonierze'
					:`Szukaj na ${label}`;
				const active=target === defaultTarget;
				button.classList.add('vm-ui-button');
				button.classList.toggle('-primary',active);
				button.addEventListener('click',() => runSearch(target));
				buttons.appendChild(button);
			}
			form.addEventListener('submit',event => {
				event.preventDefault();
				runSearch(defaultTarget);
			});
			form.append(input,buttons);
			panel.appendChild(form);
			root.append(trigger,panel);
			let forcedOpen=false;
			const setOpen=open => {
				panel.style.transitionProperty=open ? 'opacity, transform':'opacity, transform, visibility';
				root.style.pointerEvents=open ? 'auto':'none';
				panel.style.opacity=open ? '1':'0';
				panel.style.visibility=open ? 'visible':'hidden';
				panel.style.pointerEvents=open ? 'auto':'none';
				panel.style.transform=open ? 'translateX(0)':'translateX(7px)';
			};
			const syncHoverState=() => setOpen( forcedOpen || root.matches(':hover') || root.contains(document.activeElement) );
			const focusQuickSearch=() => {
				setOpen(true);
				input.focus();
				input.setSelectionRange(input.value.length,input.value.length);
			};
			root._vmPrefill=value => {
				if (input.disabled) return;
				input.value=String(value || '');
				input.setCustomValidity('');
				forcedOpen=true;
				focusQuickSearch();
			};
			const isEditableTarget=target => target?.matches?.('input, textarea, select, [contenteditable="true"]');
			document.addEventListener('keydown',event => {
				if (input.disabled || document.activeElement === input || !root.matches(':hover')) return;
				if (isEditableTarget(document.activeElement)) return;
				const copyShortcut= (event.ctrlKey || event.metaKey) && String(event.key).toLowerCase() === 'c';
				const typedCharacter= event.key?.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
				if (!copyShortcut && !typedCharacter) return;
				if (copyShortcut) {
					setTimeout(focusQuickSearch,0);
					return;
				}
				event.preventDefault();
				focusQuickSearch();
				input.value += event.key;
				input.dispatchEvent(new Event('input',{bubbles:true}));
			},true);
			trigger.addEventListener('mouseenter',() => {
				focusQuickSearch();
			});
			root.addEventListener('mouseleave',() => setTimeout(syncHoverState,40));
			root.addEventListener('focusin',event => {
				if (event.target === trigger || panel.contains(event.target)) setOpen(true);
			});
			root.addEventListener('focusout',() => setTimeout(syncHoverState,40));
			trigger.addEventListener('click',() => {
				forcedOpen=!forcedOpen;
				setOpen(forcedOpen);
				if (forcedOpen) setTimeout(() => input.focus(),0);
			});
			document.addEventListener('pointerdown',event => {
				if (!forcedOpen || root.contains(event.target)) return;
				forcedOpen=false;
				setOpen(false);
			},true);
			document.body.appendChild(root);
			return true;
		};
		if (document.body) mount();
		else document.addEventListener('DOMContentLoaded',mount,{once:true});
	}
	function findGarsoPhoneTokens(text) {
		const source=String(text || '');
		const tokens=[];
		for (const match of source.matchAll(/(?:(?:\+48|0048)[ \t\u00a0-]*)?\d{3}[ \t\u00a0-]*\d{3}[ \t\u00a0-]*(?:\d{3}|xxx)/giu)) {
			const start=match.index;
			const end=start + match[0].length;
			const beforeChar=start > 0 ? source[start - 1]:'';
			const afterChar=end < source.length ? source[end]:'';
			const beforeText=source.slice(Math.max(0,start - 12),start);
			const hasNormalBoundary=!beforeChar || !/[\p{L}\p{N}_+]/u.test(beforeChar);
			const hasPhonePrefix=/(?:^|[^\p{L}\p{N}_])tel(?:efon)?[.:\s-]?$/iu.test(beforeText);
			if ((!hasNormalBoundary && !hasPhonePrefix) || (afterChar && /[\p{L}\p{N}_]/u.test(afterChar))) continue;
			tokens.push({start,end,value:match[0].toLowerCase()});
		}
		return tokens;
	}
	function getGarsoTopicPageContext(value=location.href) {
		try {
			const url=new URL(value,GARSO_BASE_URL);
			if (!/^https?:$/.test(url.protocol) || !/^(?:www\.)?garsoniera\.com\.pl$/i.test(url.hostname) || url.username || url.password) return null;
			const match=url.pathname.match(/^\/forum\/topic\/(\d+)(?:-([^/]+))?(?:\/page-(\d+))?\/?$/i);
			const queryId=/^\/forum\/index\.php$/i.test(url.pathname) && url.searchParams.get('showtopic');
			if (!match && !/^\d+$/.test(queryId || '')) return null;
			const offset=Number(url.searchParams.get('st') || 0);
			const page=match?.[3] || url.searchParams.get('page') || (offset > 0 ? null:1);
			if (page != null && (!Number.isSafeInteger(Number(page)) || Number(page) < 1)) return null;
			const id=match?.[1] || queryId;
			const baseUrl=match ? `${url.origin}/forum/topic/${id}${match[2] ? '-' + match[2]:''}/`:`${url.origin}/forum/index.php?showtopic=${id}`;
			return {id,page:page == null ? null:Number(page),baseUrl,url:url.href,
				hasPhone:findGarsoPhoneTokens(decodeURIComponent(match?.[2] || '')).length > 0};
		} catch {return null;}
	}
	function getGarsoTopicPostCount(doc) {
		for (const element of doc.querySelectorAll('h2,h3,.maintitle,.ipsType_sectionTitle')) {
			if (element.closest('.post_block,[id^="post_id_"],.author_info,.vm-helper-ui,[id^="vm-"]')) continue;
			const match=element.textContent.trim().match(/^(\d+(?:[\s.,]\d{3})*)\s+(odpowiedź|odpowiedzi|repl(?:y|ies)|post(?:y|ów|s)?)\s+(?:w\s+tym\s+temacie|to\s+this\s+topic)\b/iu);
			if (!match) continue;
			const count=Number(match[1].replace(/[^\d]/g,'')) + (/^(?:odpowied|repl)/i.test(match[2]) ? 1:0);
			if (Number.isSafeInteger(count) && count > 0) return count;
		}
		return null;
	}
	async function readGarsoTopicSummaryPage(doc,context) {
		const blocks=getGarsoPostBlocks(doc);
		if (!blocks.length) throw new Error('Nie znaleziono postów. Sprawdź, czy temat jest dostępny i czy jesteś zalogowany.');
		const title=extractGarsoTopicTitle(doc,'Temat Garso');
		const active=doc.querySelector('.pagination .active, .pages .active, .pagination [aria-current="page"]');
		const currentNumber=active?.textContent.trim();
		if (context.page == null && /^\d+$/.test(currentNumber || '')) context.page=Number(currentNumber);
		if (!context.page || (/^\d+$/.test(currentNumber || '') && Number(currentNumber) !== context.page)) throw new Error('Forum zwróciło inną stronę tematu niż żądana.');
		const url=context.url || (context.page === 1 ? context.baseUrl:`${context.baseUrl}page-${context.page}/`);
		const posts=extractPostsFromPage(doc,title,context.page,url);
		const pageMap=getPaginationMap(doc,context.baseUrl);
		const maxPage=Math.max(context.page,getMaxPage(doc,pageMap));
		const ids=blocks.map(block => block.id).filter(Boolean);
		const bytes=new TextEncoder().encode(JSON.stringify([ids,posts.map(post => [post.author,post.date,post.text,post.references])]));
		const hash=await crypto.subtle.digest('SHA-256',bytes);
		const stamp=[...new Uint8Array(hash)].map(value => value.toString(16).padStart(2,'0')).join('');
		return {page:context.page,posts,ids,stamp,pageMap,maxPage,title,topicPostCount:getGarsoTopicPostCount(doc),subtitle:extractGarsoTopicSubtitle(doc),
			eligible:getGarsoSummaryTopicEligibility({url:context.baseUrl,title},doc).eligible};
	}
	async function collectGarsoFullTopic(context,seed,cancelToken,onProgress,includeReferences=false) {
		const pages=new Map(),pageMap=new Map(seed.pageMap),seenIds=new Set(),seenPosts=new Set();
		let maxPage=seed.maxPage,selected=getGarsoAnalysisPages(maxPage);
		for (let index=0; index < selected.length; index++) {
			const page=selected[index];
			cancelToken.throwIfCancelled();
			onProgress(index,selected.length,`Czytanie strony ${page}/${maxPage}…`);
			let data;
			if (page === context.page) data=seed;
			else {
				const url=resolveTopicPageUrl(pageMap,context.baseUrl,page,maxPage) ||
					(page === 1 ? context.baseUrl:`${context.baseUrl}page-${page}/`);
				const requested=getGarsoTopicPageContext(url);
				if (!requested || requested.id !== context.id || (requested.page != null && requested.page !== page)) throw new Error(`Nieprawidłowy adres strony ${page}.`);
				data=await loadGarsoSummaryPage(url,page,context.id,cancelToken);
			}
			cancelToken.throwIfCancelled();
			if (page === 1 && !data.eligible && !includeReferences) return {eligible:false,title:data.title,subtitle:data.subtitle};
			if (data.ids.length && data.ids.every(id => seenIds.has(id))) throw new Error(`Strona ${page} powtarza już odczytane posty. Analiza jest niepełna.`);
			for (const id of data.ids) seenIds.add(id);
			const uniquePosts=data.posts.filter(post => {
				const key=post.postId || `${page}:${post.author}:${post.date}:${post.text}`;
				if (seenPosts.has(key)) return false;
				seenPosts.add(key); return true;
			});
			pages.set(page,{...data,posts:uniquePosts});
			for (const [number,url] of data.pageMap) pageMap.set(number,url);
			if (page === 1) {maxPage=data.maxPage; selected=getGarsoAnalysisPages(maxPage);}
			onProgress(index + 1,selected.length,`Przeczytano ${index + 1}/${selected.length} wybranych stron`);
			await waitWithCancellation(0,cancelToken);
		}
		cancelToken.throwIfCancelled();
		onProgress(pages.size,pages.size,'Obliczanie ocen…');
		const first=pages.get(1),posts=[...pages.values()].flatMap(value => value.posts);
		const topic={url:context.baseUrl,title:first.title,subtitle:first.subtitle};
		const stats=await buildRatingStats(posts,null,cancelToken);
		const ratingPosts=stats.reviews.map(({post,index,ratings}) => ({postId:post.postId || `${context.id}:${index}`,
			date:formatIsoDateFromTimestamp(parseGarsoPostDate(post.date) ?? undefined) || '',pageUrl:post.pageUrl,
			ratingValues:ratings,oralVariants:ratings.oral != null ? detectOralRatingVariants(post.text):null}));
		const summary=compactGarsoSummaryResult({topics:[topic],allPosts:posts,
			topicInfo:[{...topic,maxPage,pagesRead:[...pages.keys()],posts:posts.length}],
			screenedTopicCount:1,excludedTopicCount:0,failedTopicCount:0,stats});
		return {schema:2,topicId:context.id,parserVersion:GARSO_SUMMARY_PARSER_VERSION,checkedAt:Date.now(),maxPage,ratingPosts,
			eligible:first.eligible,references:mergeGarsoTopicReferences(posts.map(post => post.references)),
			topicPostCount:first.topicPostCount ?? (pages.size === maxPage ? seenIds.size || null:null),postCountCheckedAt:Date.now(),postCountUnverified:first.topicPostCount == null,
			pageStamps:Object.fromEntries([...pages].map(([page,data]) => [page,data.stamp])),summary};
	}
	function getGarsoTopicCacheOrigin() {
		const key='vm_garso_topic_cache_origin',saved=GM_getValue(key,'');
		if (/^https:\/\/(?:www\.)?garsoniera\.com\.pl$/.test(saved)) return saved;
		const origin=GARSO_MENU_ONLY_MODE ? location.origin:new URL(GARSO_BASE_URL).origin;
		writeHelperStorageValue(key,origin);
		return origin;
	}
	function validGarsoTopicAnalysis(value,id) {
		const topic=value?.summary?.topicInfo?.[0],pages=topic?.pagesRead;
		return value?.schema === 2 && value.topicId === String(id) && value.parserVersion === GARSO_SUMMARY_PARSER_VERSION &&
			Number.isFinite(value.checkedAt) && value.checkedAt > 0 && topic?.maxPage === value.maxPage && hasGarsoAnalysisPages(topic) &&
			Array.isArray(value.ratingPosts) && value.ratingPosts.length === value.summary?.stats?.reviewCount &&
			value.summary.failedTopicCount === 0 && value.pageStamps && Object.keys(value.pageStamps).length === pages.length &&
			pages.every(page => typeof value.pageStamps[page] === 'string');
	}
	async function accessGarsoTopicAnalysis(id,value=null,metadataOnly=false) {
		if (!GARSO_MENU_ONLY_MODE || window.top !== window.self) throw new Error('Cache tematów wymaga karty Garsoniery.');
		const prefix='vm_garso_summary_topic_',key=prefix + id,db=await openPersistentCacheDb();
		try {
			if (!value) return await new Promise((resolve,reject) => {
				const transaction=db.transaction(PERSISTENT_CACHE_STORE_NAME,'readonly');
				const request=transaction.objectStore(PERSISTENT_CACHE_STORE_NAME).get(key);
				request.onsuccess=() => resolve(request.result?.value || null);
				request.onerror=() => reject(request.error); transaction.onabort=() => reject(transaction.error);
			});
			if (!validGarsoTopicAnalysis(value,id)) throw new Error('Niepełna analiza - zapis pominięty.');
			await putPersistentCacheRecords(db,[{key,value,updatedAt:value.checkedAt,sizeBytes:getPersistentCacheEntrySizeBytes(key,value)}]);
			if (metadataOnly) return value;
			writeHelperStorageValue(GARSO_TOPIC_REVISION_KEY,`${Date.now()}-${Math.random()}`);
			const rows=await new Promise((resolve,reject) => {
				const request=db.transaction(PERSISTENT_CACHE_STORE_NAME,'readonly').objectStore(PERSISTENT_CACHE_STORE_NAME)
					.getAll(IDBKeyRange.bound(prefix,prefix + '\uffff'));
				request.onsuccess=() => resolve(request.result); request.onerror=() => reject(request.error);
			});
			const expired=rows.sort((a,b) => b.updatedAt - a.updatedAt).slice(200);
			if (expired.length) {
				const transaction=db.transaction(PERSISTENT_CACHE_STORE_NAME,'readwrite');
				for (const row of expired) transaction.objectStore(PERSISTENT_CACHE_STORE_NAME).delete(row.key);
				await waitForIndexedDbTransaction(transaction);
			}
			return value;
		} finally {db.close();}
	}
	async function loadGarsoSummaryPage(url,page,id,cancelToken) {
		const response=await garsoTopicRequest({method:'GET',url,timeout:20000,cancelToken});
		cancelToken.throwIfCancelled();
		const context=getGarsoTopicPageContext(response.finalUrl || url);
		if (response.status < 200 || response.status >= 400 || !context || context.id !== id || (context.page != null && context.page !== page)) {
			throw new Error(`Nie udało się odczytać strony ${page} (HTTP ${response.status || '?'} lub przekierowanie).`);
		}
		context.page=page;
		return readGarsoTopicSummaryPage(parseHtml(response.responseText || ''),context);
	}
	async function getLocalGarsoTopicAnalysis(topic,cancelToken,{forceRefresh=false,onProgress=() => {},seed=null,observed=null,includeReferences=false}={}) {
		const context=getGarsoTopicPageContext(topic.url);
		if (!context) throw new Error('Nieprawidłowy adres tematu Garso.');
		const abort=new AbortController(),unsubscribe=cancelToken.onCancel(() => abort.abort());
		const work=async () => {
			cancelToken.throwIfCancelled();
			let saved=null,storageError='';
			try {saved=await accessGarsoTopicAnalysis(context.id);}
			catch {storageError=GARSO_MENU_ONLY_MODE ? 'IndexedDB niedostępne - bez zapisu':'Brak otwartej karty Garsoniery - bez zapisu w cache tematów';}
			let check=observed || (seed && {page:seed.page || context.page,postCount:seed.topicPostCount,stamp:seed.stamp});
			const hasSaved=validGarsoTopicAnalysis(saved,context.id);
			const reuse=async (unverified,error='',updateCheck=true) => {
				if (updateCheck) {
					saved={...saved,postCountCheckedAt:Date.now(),postCountUnverified:unverified,postCountCheckError:error};
					try {await accessGarsoTopicAnalysis(context.id,saved,true);}
					catch {storageError='Nie zapisano czasu sprawdzenia licznika w IndexedDB';}
				}
				onProgress(saved.summary.topicInfo[0].pagesRead.length,saved.summary.topicInfo[0].pagesRead.length,unverified
					? 'Cache - nie potwierdzono aktualności licznika postów':'Cache - bez zmian w sprawdzonych danych tematu',true);
				return {...saved,fromCache:true,persisted:true,storageError,postCountUnverified:unverified};
			};
			if (!forceRefresh && hasSaved && (!includeReferences || Array.isArray(saved.references?.otherUrls)) && Date.now() - saved.checkedAt < getGarsoCacheTtlMs()) {
				if (!check) {
					if (isGarsoTopicCountCheckRecent(saved.postCountCheckedAt)) return reuse(!!saved.postCountUnverified,'',false);
					context.page=1;
					onProgress(0,1,'Sprawdzanie liczby postów - pierwsza strona tematu…');
					try {seed=await loadGarsoSummaryPage(context.baseUrl,1,context.id,cancelToken);}
					catch (error) {
						if (cancelToken.cancelled || isOperationCancelledError(error)) throw error;
						return reuse(true,String(error?.message || error));
					}
					check={page:1,postCount:seed.topicPostCount,stamp:seed.stamp};
				}
				const hasCount=Number.isSafeInteger(check.postCount) && check.postCount > 0;
				const hasSavedCount=Number.isSafeInteger(saved.topicPostCount) && saved.topicPostCount > 0;
				const changedPage=[[check.page,check.stamp],[seed?.page || check.page,seed?.stamp]]
					.some(([page,stamp]) => stamp && saved.pageStamps[page] && saved.pageStamps[page] !== stamp);
				const changedPageCount=seed && seed.maxPage !== saved.maxPage;
				if (!changedPage && !changedPageCount) {
					if (!hasCount) return reuse(true,'Nie odczytano licznika postów');
					if (hasSavedCount && check.postCount === saved.topicPostCount) return reuse(false);
				}
			}
			if (seed) context.page=seed.page || check?.page || context.page;
			if (!seed || forceRefresh) {
				context.page=1;
				onProgress(0,1,'Czytanie pierwszej strony tematu…');
				seed=await loadGarsoSummaryPage(context.baseUrl,1,context.id,cancelToken);
			}
			const value=await collectGarsoFullTopic(context,seed,cancelToken,onProgress,includeReferences);
			cancelToken.throwIfCancelled();
			if (value.eligible === false && !value.summary) return value;
			let persisted=false;
			if (!storageError) {
				try {await accessGarsoTopicAnalysis(context.id,value); persisted=true;}
				catch {storageError='Nie udało się zapisać analizy w IndexedDB';}
			}
			return {...value,fromCache:false,persisted,storageError};
		};
		try {
			if (navigator.locks?.request) return await navigator.locks.request(`vm-garso-topic-summary:${context.id}`,{signal:abort.signal},work);
			return await work();
		} finally {unsubscribe();}
	}
	function pruneGarsoTopicBridge() {
		const now=Date.now(),prefix=GARSO_TOPIC_BRIDGE_PREFIX;
		for (const key of GM_listValues().filter(key => key.startsWith(prefix))) {
			const value=GM_getValue(key,null);
			if (key.startsWith(prefix + 'session:')) {if (!(value?.expiresAt > now)) deleteHelperStorageValue(key);}
			else if (key.startsWith(prefix + 'request:') || key.startsWith(prefix + 'reply:')) {
				if (!(GM_getValue(prefix + 'session:' + value?.session,null)?.expiresAt > now)) deleteHelperStorageValue(key);
			}
		}
	}
	function initGarsoTopicCacheBridge() {
		pruneGarsoTopicBridge();
		if (typeof GM_addValueChangeListener === 'function') GM_addValueChangeListener(GARSO_TOPIC_REVISION_KEY,() => summaryCache.clear());
		if (!GARSO_MENU_ONLY_MODE || window.top !== window.self) return false;
		const prefix=GARSO_TOPIC_BRIDGE_PREFIX,worker=GARSO_TOPIC_WORKER_ID,processing=new Set();
		if (worker) {
			const session=GM_getValue(prefix + 'session:' + worker,null);
			if (session?.expiresAt > Date.now() && session.origin !== location.origin) {
				session.origin=location.origin; writeHelperStorageValue(prefix + 'session:' + worker,session);
				writeHelperStorageValue('vm_garso_topic_cache_origin',location.origin);
			}
		} else getGarsoTopicCacheOrigin();
		const serve=async id => {
			if (!id || processing.has(id)) return;
			const requestKey=prefix + 'request:' + id,replyKey=prefix + 'reply:' + id;
			const request=GM_getValue(requestKey,null),sessionKey=prefix + 'session:' + request?.session;
			const session=GM_getValue(sessionKey,null);
			if (!request || request.protocol !== 1 || !(session?.expiresAt > Date.now()) || session.origin !== location.origin ||
				(worker && request.session !== worker) || !getGarsoTopicPageContext(request.topic?.url)) return;
			processing.add(id);
			const work=async () => {
				if (!GM_getValue(requestKey,null) || GM_getValue(replyKey,null)?.done) return;
				const token=createOperationCancelToken('Garso - wspólna analiza tematu');
				const alive=() => GM_getValue(requestKey,null) && GM_getValue(sessionKey,null)?.expiresAt > Date.now();
				const publish=data => {if (alive()) writeHelperStorageValue(replyKey,{session:request.session,at:Date.now(),...data});};
				const pulse=setInterval(() => {if (!alive()) token.cancel('Zakończono odczyt w karcie źródłowej'); else publish({working:true});},15000);
				let listener=null;
				if (typeof GM_addValueChangeListener === 'function') listener=GM_addValueChangeListener(requestKey,(_n,_o,value) => {if (!value) token.cancel();});
				const unload=() => token.cancel('Zamknięto kartę obsługującą cache');
				window.addEventListener('pagehide',unload);
				try {
					publish({working:true});
					const url=new URL(request.topic.url); url.host=location.host; url.protocol=location.protocol;
					const value=await getLocalGarsoTopicAnalysis({...request.topic,url:url.href},token,{
						forceRefresh:request.forceRefresh === true,observed:request.observed || null,includeReferences:request.includeReferences === true,
						onProgress:(done,total,message,fromCache) => publish({working:true,progress:{done,total,message,fromCache:!!fromCache}})
					});
					token.throwIfCancelled(); publish({done:true,value});
				} catch (error) {publish({done:true,error:String(error?.message || error),retryable:isOperationCancelledError(error)});}
				finally {
					clearInterval(pulse); window.removeEventListener('pagehide',unload);
					if (listener != null) GM_removeValueChangeListener(listener);
					setTimeout(() => {if (!GM_getValue(requestKey,null)) deleteHelperStorageValue(replyKey);},1000);
				}
			};
			try {
				if (navigator.locks?.request) await navigator.locks.request(`vm-garso-topic-bridge:${id}`,{ifAvailable:true},lock => lock ? work():undefined);
				else {
					const claim=GM_getValue(replyKey,null);
					if (!claim) {
						writeHelperStorageValue(replyKey,{session:request.session,at:Date.now(),owner:GARSO_SEARCH_INSTANCE_ID});
						await waitWithCancellation(80);
						if (GM_getValue(replyKey,null)?.owner === GARSO_SEARCH_INSTANCE_ID) await work();
					}
				}
			} finally {processing.delete(id);}
		};
		const dispatch=id => {void serve(id).catch(error => log('Błąd komunikacji cache tematów Garso',error));};
		if (typeof GM_addValueChangeListener === 'function') GM_addValueChangeListener(prefix + 'signal',(_n,_o,id) => dispatch(id));
		for (const key of GM_listValues().filter(key => key.startsWith(prefix + 'request:'))) dispatch(key.slice((prefix + 'request:').length));
		if (worker) {
			const timer=setInterval(() => {
				if (!(GM_getValue(prefix + 'session:' + worker,null)?.expiresAt > Date.now())) {clearInterval(timer); pruneGarsoTopicBridge(); window.close();}
			},15000);
		}
		return !!worker;
	}
	function createGarsoTopicCacheClient(cancelToken) {
		pruneGarsoTopicBridge();
		const prefix=GARSO_TOPIC_BRIDGE_PREFIX,sessionId=`${Date.now()}-${Math.random().toString(36).slice(2)}`;
		const sessionKey=prefix + 'session:' + sessionId,pending=new Map();
		let sequence=0,closed=false,pulse=null,unsubscribe=() => {};
		const touch=() => {
			if (closed) return;
			const origin=GM_getValue(sessionKey,null)?.origin || getGarsoTopicCacheOrigin();
			writeHelperStorageValue(sessionKey,{origin,expiresAt:Date.now() + 120000});
		};
		const close=() => {
			if (closed) return;
			closed=true; clearInterval(pulse); unsubscribe(); window.removeEventListener('pagehide',close);
			for (const stop of [...pending.values()]) stop(createOperationCancelledError('Zakończono odczyt tematów'));
			deleteHelperStorageValue(sessionKey);
		};
		window.addEventListener('pagehide',close);
		unsubscribe=cancelToken.onCancel(close);
		return {
			close,
			async get(topic,{forceRefresh=false,onProgress=() => {},seed=null,observed=null,includeReferences=false}={}) {
				cancelToken.throwIfCancelled();
				if (!getGarsoTopicPageContext(topic?.url)) throw new Error('Nieprawidłowy adres tematu Garso.');
				if (closed) throw createOperationCancelledError('Zamknięto odczyt tematów');
				if (GARSO_MENU_ONLY_MODE && location.origin === getGarsoTopicCacheOrigin()) {
					return getLocalGarsoTopicAnalysis(topic,cancelToken,{forceRefresh,onProgress,seed,observed,includeReferences});
				}
				if (!pulse) {touch(); pulse=setInterval(touch,15000);}
				const id=sessionId + ':' + (++sequence),requestKey=prefix + 'request:' + id,replyKey=prefix + 'reply:' + id;
				return new Promise((resolve,reject) => {
					let listener=null,timer=null,settled=false,lastAt=Date.now(),lastReply=0,localToken=null;
					const startedAt=Date.now();
					const finish=(error,value) => {
						if (settled) return;
						settled=true; clearInterval(timer); pending.delete(id);
						localToken?.cancel('Zakończono odczyt tematów');
						if (listener != null) GM_removeValueChangeListener(listener);
						deleteHelperStorageValue(requestKey); deleteHelperStorageValue(replyKey);
						if (error) reject(error); else resolve(value);
					};
					const check=() => {
						if (settled || localToken) return;
						try {
							const reply=GM_getValue(replyKey,null);
							if (reply?.done && !reply.retryable) {finish(reply.error ? new Error(reply.error):null,reply.value); return;}
							if (reply?.at > lastReply) {
								lastReply=reply.at; lastAt=Date.now();
								if (reply.progress) {const p=reply.progress; onProgress(p.done,p.total,p.message,p.fromCache);}
							}
							if (reply?.retryable || (!lastReply && Date.now() - startedAt >= 1500) || (lastReply && Date.now() - lastAt > 120000)) {
								localToken=createOperationCancelToken('Garso - odczyt bez karty forum');
								clearInterval(timer);
								if (listener != null) {GM_removeValueChangeListener(listener); listener=null;}
								deleteHelperStorageValue(requestKey); deleteHelperStorageValue(replyKey);
								getLocalGarsoTopicAnalysis(topic,localToken,{forceRefresh,onProgress,seed,observed,includeReferences})
									.then(value => finish(null,value),error => finish(error));
							}
						} catch (error) {finish(error);}
					};
					pending.set(id,finish);
					try {
						if (typeof GM_addValueChangeListener === 'function') listener=GM_addValueChangeListener(replyKey,check);
						writeHelperStorageValue(requestKey,{protocol:1,session:sessionId,topic,forceRefresh,observed,includeReferences});
						writeHelperStorageValue(prefix + 'signal',id);
						timer=setInterval(check,250); check();
					} catch (error) {finish(error);}
				});
			}
		};
	}
	function getGarsoPostReferences(content) {
		const source=readGarsoMatchSource([content]);
		const phones=[...new Set(findGarsoPhoneTokens(source.text).map(({value}) =>
			value.replace(/^(?:\+48|0048)/,'').replace(/[\s-]/g,'')))];
		return {phones,adUrls:source.adUrls,otherUrls:source.otherUrls,hasLinks:source.hasLinks};
	}
	function mergeGarsoTopicReferences(groups) {
		return Object.fromEntries(['phones','adUrls','otherUrls'].map(key =>
			[key,[...new Set(groups.flatMap(group => group?.[key] || []))]]));
	}
	function createGarsoTopicTools(context) {
		const first=document.querySelector('.post_block,[id^="post_id_"]');
		if (!first) return null;
		const panel=prepareHelperStyleApi(makeElement('details','vm-ui-panel'));
		panel.id='vm-garso-topic-tools'; panel.hidden=true;
		const title=makeElement('summary');
		const history=makeElement('div','vm-topic-history');
		const row=prepareHelperStyleApi(makeElement('label','',' Posty:'));
		row.id='vm-garso-topic-filter';
		let bar=[...document.querySelectorAll('h2,h3,.maintitle,.ipsType_sectionTitle')].find(element =>
			!element.closest('.post_block,[id^="post_id_"],.vm-helper-ui') &&
			/^(?:\d[\d\s.,]*\s+(?:odpowied|repl|post)|brak odpowiedzi|no replies)/i.test(element.textContent.trim()));
		if (!bar) {bar=makeElement('div','maintitle'); first.before(bar);}
		bar.classList.add('vm-topic-filter-bar'); bar.appendChild(row);
		const style=makeElement('style');
		style.textContent=`
.vm-topic-filter-bar { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:6px 16px; }
#vm-garso-topic-filter { display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:7px; margin-left:auto; color:inherit; font:12px/1.35 var(--vm-ui-font-family,Arial,sans-serif); }
#vm-garso-topic-filter select { width:auto; max-width:100%; min-height:28px; padding:3px 7px; border:1px solid var(--vm-ui-input-border,#8b8b9b); border-radius:5px; background:var(--vm-ui-input-bg,#fff); color:var(--vm-ui-input-text,#222); font:inherit; cursor:pointer; }
#vm-garso-topic-filter .vm-topic-filter-count { white-space:nowrap; opacity:.85; font-size:11px; }
@t { margin:8px 0 12px; padding:9px 12px; font:12px/1.4 var(--vm-ui-font-family,Arial,sans-serif); }
@t[hidden], .vm-garso-post-filtered { display:none !important; }
@t > summary { cursor:pointer; font-weight:600; }
@t > summary:focus-visible { outline:2px solid var(--vm-ui-accent,#f54da3); outline-offset:3px; }
@t .vm-topic-history { display:grid; grid-template-columns:minmax(130px,.6fr) repeat(2,minmax(0,1fr)); gap:8px 20px; margin-top:8px; max-height:min(50vh,420px); overflow:auto; overscroll-behavior:contain; overflow-wrap:anywhere; }
@t section, @t li > a { min-width:0; max-width:100%; }
@media (max-width:700px) { @t .vm-topic-history { grid-template-columns:1fr; } }
@t a { color:var(--vm-ui-accent,#b52d77); text-underline-offset:2px; }
@t a:focus-visible { outline:2px solid currentColor; outline-offset:2px; }
@t section > button { margin:3px 0 3px 8px; }
@t ul { list-style:none; margin:5px 0 0; padding:0; }
@t li { display:flex; flex-wrap:wrap; align-items:center; gap:7px; margin:0; padding:3px 0; }
@t .vm-topic-link-status { display:inline-flex; align-items:center; gap:4px; font-size:15px; font-weight:700; }
`.replaceAll('@t','#vm-garso-topic-tools');
		const select=makeElement('select');
		select.setAttribute('aria-label','Filtr postów w temacie');
		appendSelectOptions(select,[['all','Wszystkie'],['ratings','Tylko posty z ocenami'],['contacts','Tylko posty z telefonami lub linkami']]);
		const key=`vm_garso_post_filter_${context.id}`;
		let mode='all';
		try {mode=sessionStorage.getItem(key) || 'all';} catch {}
		select.value=['ratings','contacts'].includes(mode) ? mode:'all';
		const status=makeElement('span','vm-topic-filter-count'); status.setAttribute('role','status');
		row.append(select,status); panel.append(title,history); document.head.appendChild(style); first.before(panel);
		let fullHistory=null,current=[],timer=null,historyKey='',openAllButton=null;
		const showStatuses=SETTINGS.garsoCheckEscortLinks !== false;
		const statusByUrl=new Map();
		const refreshOpenAllButton=() => updateOpenAvailableEscortAdsButton(openAllButton,statusByUrl,
			[...history.querySelectorAll('.vm-topic-link-status')].some(marker => !marker.dataset.vmStatusReady));
		const checkLinks=async () => {
			if (!showStatuses || !panel.open || panel.hidden) return;
			const pending=new Map();
			for (const marker of history.querySelectorAll('.vm-topic-link-status')) if (!marker.dataset.vmRequested) {
				marker.dataset.vmRequested='1'; pending.set(marker.dataset.adUrl,marker);
			}
			if (!pending.size) return;
			const show=(url,result) => {
				const marker=pending.get(normalizeEscortiAdUrl(url));
				if (marker?.isConnected) {
					statusByUrl.set(marker.dataset.adUrl,result || null);
					renderEscortAdLinkStatusMarker(marker,result);
					refreshOpenAllButton();
				}
			};
			try {
				const summary=await scanActiveEscortAds([...pending.keys()],progress => show(progress.url,progress.result),false,{activityOnly:true,useActivityCache:true,cacheActivity:true});
				const results=new Map((summary?.results || []).map(result => [normalizeEscortiAdUrl(result.url),result]));
				for (const url of pending.keys()) show(url,results.get(url));
			} catch {
				for (const [url,marker] of pending) if (!marker.dataset.vmStatusReady) show(url,null);
			}
		};
		const renderHistory=references => {
			references=mergeGarsoTopicReferences([references]);
			title.textContent='Zobacz listę telefonów i linków z tematu';title.title=fullHistory?'Cały temat':'Bieżąca strona';
			panel.hidden=!Object.values(references).some(values => values.length);
			const signature=JSON.stringify(references);
			if (signature === historyKey) return;
			historyKey=signature; history.replaceChildren(); statusByUrl.clear(); openAllButton=null;
			for (const [key,label] of [['phones','Numery telefonów'],['adUrls','Linki Escort.club'],['otherUrls','Linki do innych serwisów']]) {
				const values=references[key],column=makeElement('section'),list=makeElement('ul');
				column.append(makeElement('strong','',`${label} (${values.length})`),list);
				if (key === 'adUrls' && values.length && showStatuses) {
					openAllButton=makeButton('vm-ui-button vm-ui-compact');
					openAllButton.addEventListener('click',event => openAvailableEscortAds(statusByUrl,event));
					column.insertBefore(openAllButton,list);
				}
				if (!values.length) list.appendChild(makeElement('li','','Brak'));
				for (const value of values) {
					const item=makeElement('li');
					if (key === 'phones') item.textContent=value.replace(/^(\d{3})(\d{3})(\d{3}|xxx)$/,'$1-$2-$3');
					else {
						const link=makeElement('a','',value);
						link.style.overflowWrap='anywhere'; link.style.minWidth='0';
						Object.assign(link,{href:value,target:'_blank',rel:'noopener noreferrer',title:value});
						item.appendChild(link);
						if (key === 'adUrls' && showStatuses) {
							const marker=makeElement('span','vm-topic-link-status','…');
							marker.dataset.adUrl=value; marker.title='Sprawdzanie aktywności anonsu';
							item.appendChild(marker);
						}
					}
					if (SETTINGS.showTopPhoneSearch !== false && SETTINGS.garsoShowSearchIcons !== false) item.appendChild(makeGarsoInlineSearchButton(value));
					list.appendChild(item);
				}
				history.appendChild(column);
			}
			refreshOpenAllButton();
			void checkLinks();
		};
		panel.addEventListener('toggle',() => {void checkLinks();});
		const applyFilter=() => {
			let visible=0;
			for (const {block,references,rated} of current) {
				const hide=select.value === 'ratings' ? !rated:select.value === 'contacts' && !references.phones.length && !references.hasLinks;
				block.classList.toggle('vm-garso-post-filtered',hide); if (!hide) visible++;
			}
			status.textContent=`${visible} / ${current.length}`;
			setControlTitle(status,`Widoczne ${visible} z ${current.length} postów na tej stronie.`);
		};
		const parsedPosts=new WeakMap(),dirtyPosts=new WeakSet();
		const scan=() => {
			current=getGarsoPostBlocks().map(block => {
				let value=parsedPosts.get(block);
				if (!value || dirtyPosts.has(block)) {
					const content=getGarsoPostContent(block),text=cleanPostElement(content);
					value={block,text,references:getGarsoPostReferences(content),
						rated:value?.text === text ? value.rated:Object.keys(extractRatingsFromPost(text,{topicTitle:extractGarsoTopicTitle(document)})).length > 0};
					parsedPosts.set(block,value); dirtyPosts.delete(block);
				}
				return value;
			});
			applyFilter();
			renderHistory(mergeGarsoTopicReferences([fullHistory,...current.map(post => post.references)]));
		};
		select.addEventListener('change',() => {
			try {sessionStorage.setItem(key,select.value);} catch {}
			applyFilter();
		});
		const own=node => (node.nodeType === 1 ? node:node.parentElement)?.closest('.vm-helper-ui,.vm-garso-inline-tools,.vm-garso-parser-feedback');
		const observer=new MutationObserver(mutations => {
			let changed=false;
			for (const m of mutations) {
				if (own(m.target)) continue;
				const node=m.target.nodeType === 1 ? m.target:m.target.parentElement;
				const post=node?.closest('.post_block,[id^="post_id_"]');
				const relevant=m.type !== 'childList' ? !!post:[...m.addedNodes,...m.removedNodes].some(n => !own(n) &&
					(post || (n.nodeType === 1 && (n.matches('.post_block,[id^="post_id_"]') || n.querySelector('.post_block,[id^="post_id_"]')))));
				if (relevant) {changed=true; if (post) dirtyPosts.add(post);}
			}
			if (changed && timer === null) timer=setTimeout(() => {timer=null; scan();},120);
		});
		observer.observe(document.getElementById('content') || document.body,{childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:['href']});
		window.addEventListener('pagehide',event => {if (!event.persisted) {observer.disconnect(); clearTimeout(timer);}});
		scan();
		return {render(value) {fullHistory=value.references; renderHistory(mergeGarsoTopicReferences([fullHistory,...current.map(post => post.references)]));}};
	}
	async function initGarsoTopicSummary() {
		if (!GARSO_MENU_ONLY_MODE || window.top !== window.self || (SETTINGS.garsoShowTopicSummary === false && SETTINGS.garsoShowTopicTools === false)) return;
		const context=getGarsoTopicPageContext();
		if (!context || document.getElementById('vm-garso-topic-summary')) return;
		const tools=SETTINGS.garsoShowTopicTools !== false ? createGarsoTopicTools(context):null;
		let cached=null;
		try {cached=await accessGarsoTopicAnalysis(context.id);} catch {}
		if (!validGarsoTopicAnalysis(cached,context.id)) cached=null;
		const source=cached?.eligible && context.page !== 1 ? {eligible:true,firstDoc:null}:await getGarsoCurrentTopicSource();
		if (!source?.eligible && !tools) return;
		const showRatings=source?.eligible && SETTINGS.garsoShowTopicSummary !== false;
		const heading=document.querySelector('h1[itemprop="name"], h1.ipsType_pagetitle, #content h1');
		if (!heading || !document.querySelector('.post_block, [id^="post_id_"]')) return;
		const panel=prepareHelperStyleApi(makeElement('section'));
		panel.id='vm-garso-topic-summary';
		panel.setAttribute('aria-label','Analiza tematu: do 10 pierwszych i 20 ostatnich stron');
		const style=makeElement('style');
		style.textContent=`
@s { --vm-ui-panel-text:var(--vm-ui-text,#262226); --vm-ui-panel-muted:var(--vm-ui-text-muted,#666); box-sizing:border-box; margin:12px 0; padding:10px; border:1px solid var(--vm-ui-border,#cec4c6); border-radius:7px; background:var(--vm-ui-bg,#fff); color:var(--vm-ui-panel-text); font:12px/1.35 var(--vm-ui-font-family,Arial,sans-serif); }
@s[hidden], @s [hidden], @s .vm-garso-topic-status:empty { display:none !important; }
@s .vm-garso-topic-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:6px; }
@s .vm-garso-topic-grid > div { min-width:0; }
@s .vm-garso-topic-header { display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-bottom:9px; }
@s .vm-garso-topic-overall { min-width:160px; max-width:100%; }
@s .vm-garso-topic-header > .vm-garso-topic-status { min-width:160px; }
@s .vm-garso-topic-header > button { margin-left:auto; }
@s .vm-garso-topic-mentions { margin-top:7px; }
@s .vm-garso-mention-chip { color:var(--vm-ui-panel-text) !important; }
@s .vm-garso-topic-status { flex:1 1 230px; color:var(--vm-ui-text-muted,#666); font-size:11px; }
@s progress { display:block; width:100%; height:5px; margin:8px 0 0; accent-color:var(--vm-ui-accent,#b52d77); }
`.replaceAll('@s','#vm-garso-topic-summary');
		const content=makeElement('div'),overview=makeElement('div'),headerRow=makeElement('div','vm-garso-topic-header');
		const status=makeElement('div','vm-garso-topic-status','Przygotowanie analizy…');
		status.setAttribute('role','status');
		const refresh=makeButton('vm-ui-button vm-ui-compact','Odśwież',{title:'Ponownie przeczytaj do 10 pierwszych i 20 ostatnich stron'});
		const progress=makeElement('progress');
		progress.setAttribute('aria-label','Postęp czytania wybranych stron');
		progress.hidden=true;
		overview.hidden=content.hidden=!showRatings;
		headerRow.append(overview,status,refresh); panel.append(style,headerRow,progress,content);
		let header=heading.closest('.ipsPageHeader, .ipsBox_withphoto, .topic_header, .topic-header') || heading.parentElement;
		if (!header || header === document.body || header.id === 'content' || header.querySelector('.post_block, [id^="post_id_"]')) header=heading;
		header.after(panel);
		let token=null,latest=null;
		const render=value => {
			latest=value;
			tools?.render(value);
			if (showRatings) {
				content.innerHTML=summaryToHtml(value.summary,{topicView:true});
				const metric=content.querySelector('.vm-garso-topic-overall');
				if (metric) overview.replaceChildren(metric);
			}
			panel._vmSummary=value.summary;
			panel._vmReferences=value.references;
			window.dispatchEvent(new Event('vm-garso-topic-summary-updated'));
		};
		const update=(done,total,message) => {
			progress.max=Math.max(1,total); progress.value=done; status.textContent=message;
		};
		const run=async (forceRefresh=false) => {
			if (token) return;
			token=createOperationCancelToken('Garso - analiza bieżącego tematu');
			const cancelToken=token,client=createGarsoTopicCacheClient(cancelToken);
			refresh.textContent='Przerwij'; refresh.title='Przerwij czytanie tematu';
			progress.hidden=false; panel.hidden=false; panel.setAttribute('aria-busy','true');
			try {
				status.textContent='Przygotowanie analizy / oczekiwanie na analizę w innej karcie…';
				const observedSeed=await readGarsoTopicSummaryPage(document,context);
				const seed=!forceRefresh && context.page !== 1 && source.firstDoc
					? await readGarsoTopicSummaryPage(source.firstDoc,{...context,page:1,url:context.baseUrl}):observedSeed;
				const value=await client.get({url:context.url},{forceRefresh,seed,onProgress:update,includeReferences:!!tools,
					observed:{page:context.page,postCount:observedSeed.topicPostCount,stamp:observedSeed.stamp}});
				cancelToken.throwIfCancelled();
				if (!validGarsoTopicAnalysis(value,context.id) || (tools && !value.references)) throw new Error('Brak kompletnej analizy tematu.');
				render(value);
				status.textContent=[value.storageError,value.postCountUnverified?'Aktualność niepotwierdzona':''].filter(Boolean).join(' • ');
				status.title=`Analiza: ${formatWatchDateTime(value.checkedAt)}`;
			} catch (error) {
				status.textContent=(cancelToken.cancelled || isOperationCancelledError(error)
					? 'Przerwano analizę.':`Analiza niepełna: ${error?.message || error}.`) +
					` Nie zapisano nowego wyniku.${latest ? ' Widoczne są poprzednie wyniki.':''}`;
			} finally {
				client.close(); token=null; progress.hidden=true; panel.setAttribute('aria-busy','false');
				panel.hidden=!showRatings && !status.textContent.trim();
				refresh.disabled=false; refresh.textContent='Odśwież'; refresh.title='Ponownie przeczytaj do 10 pierwszych i 20 ostatnich stron';
			}
		};
		refresh.addEventListener('click',event => {
			stopUiEvent(event);
			if (token) {token.cancel(); refresh.disabled=true; refresh.textContent='Przerywam…';}
			else void run(true);
		});
		content.addEventListener('click',event => {
			const chip=event.target.closest('.vm-garso-mention-chip');
			if (!chip || !content.contains(chip)) return;
			const entries=getGarsoMentionEntries(latest?.summary).sort((a,b) => b.count - a.count || a.label.localeCompare(b.label,'pl-PL'));
			const entry=entries[Number(chip.dataset.vmGarsoMentionIndex)];
			if (entry) {stopUiEvent(event); openGarsoMentionEvidenceDialog(entry);}
		});
		window.addEventListener('pagehide',() => token?.cancel('Opuszczono temat'));
		if (cached) {render(cached); status.textContent='Sprawdzanie aktualności…'; await yieldHelperWork();}
		await run();
	}
	function readEscortAdCacheBundle(adId) {
		const stored=getStoredEscortAdData(adId);
		if (!stored) return null;
		const adData=makeWatchedAdDataMirrorValue(stored);
		adData.adId=String(adId);
		adData.imageUrl=getEscortCachedArchiveImageUrl(adId,stored);
		const phone=getEscortAdPhoneDigits(stored),url=stored.adUrl || `https://pl.escort.club/anons/${adId}.html`;
		const facts=getEscortArchiveResearchFactsFromCache(adId,url,phone);
		return {adData,facts};
	}
	function getGarsoAdTileProfileStats(value) {
		const age=getFavoriteTileStat(value,'wiek','lat'),height=getFavoriteTileStat(value,'wzrost','cm'),weight=getFavoriteTileStat(value,'waga','kg'),breasts=getFavoriteTileStat(value,'biust');
		return {age,height,weight,breasts,text:[age,height,weight,breasts ? `biust ${breasts}`:''].filter(Boolean).join(' • ')};
	}
	function createGarsoProfileStatsElement(profileStats,className='vm-garso-visited-stats') {
		return profileStats?.text ? makeElement('span',className,profileStats.text):null;
	}
	function makeGarsoAdTileData(adId,value) {
		const labels=[];
		for (const [duration] of ESCORT_PRICE_DURATION_OPTIONS) {
			const price=formatEscortTilePrice(value.prices?.[duration]);
			if (price) labels.push(`${getEscortPriceDurationLabel(duration)}: ${price}`);
			if (labels.length >= 4) break;
		}
		const profileStats=getGarsoAdTileProfileStats(value);
		return {
			adId:String(adId),url:normalizeEscortiAdUrl(value.adUrl) || `https://pl.escort.club/anons/${adId}.html`,
			title:normalizeEscortAdText(value.title),imageUrl:String(value.imageUrl || '').trim(),
			phoneDigits:normalizePhoneReputationPhone(getEscortAdPhoneDigits(value))?.local || '',
			locationText:normalizeEscortCity(value.location?.city) || '',priceText:labels.join(' • '),
			profileStats,profileStatsText:profileStats.text,visitedAt:Number(value.lastVisitedAt) || 0
		};
	}
	async function findGarsoEscortiFallback(phone,cancelToken) {
		const search=await searchEscortiProfilesDirect(phone,cancelToken),candidates=new Map();
		for (const profileUrl of search.profileUrls || []) {
			cancelToken.throwIfCancelled();
			const profile=await readEscortiProfileDirect(profileUrl,cancelToken);
			for (const url of profile.adUrls || []) {
				const id=parseAdIdFromUrl(url);
				if (id) candidates.set(String(id),{url,profile});
			}
		}
		let unknown=false;
		for (const id of [...candidates.keys()].sort((a,b) => Number(b) - Number(a))) {
			cancelToken.throwIfCancelled();
			const {url,profile}=candidates.get(id),cacheHost=getEscortCachedAdHost(id);
			const activity=cacheHost ? null:await inspectEscortClubAdActivityOnly(url,cancelToken);
			if (activity) escortActiveAdProbeCache.set(normalizeEscortiAdUrl(url),Promise.resolve(activity));
			unknown ||= !!activity?.unknown;
			if (!cacheHost && !activity?.active) continue;
			const tile=makeGarsoAdTileData(id,{
				title:profile.profileName,imageUrl:profile.imageUrl,
				...getWatchedAdDataMirror(id),...activity?.adData,adUrl:url,phoneDigits:phone
			});
			if (cacheHost) {
				tile.url=addEscortCachedArchiveMarkerToUrl(`https://${cacheHost}${getEscortAdPath(id,cacheHost)}`);
				tile.cached=true;
			}
			return tile;
		}
		if (unknown) throw new Error('Nie udało się potwierdzić aktywności anonsów z Escorti.pl.');
		return null;
	}
	async function findGarsoPhoneAdTiles(phones,cancelToken) {
		const found=new Map();
		for (const phone of phones) {
			let matched=false;
			const search={requests:[{url:buildEscortClubPhoneSearchUrl(phone),method:'GET'}]};
			for await (const {context,finalUrl} of iterateFavoriteSearchPages(search,cancelToken)) {
				for (const anchor of context?.anchors || []) {
					const adUrl=normalizeEscortiAdUrl(anchor.getAttribute('href'),finalUrl),id=parseAdIdFromUrl(adUrl);
					if (!id) continue;
					matched=true;
					if (found.has(String(id))) continue;
					escortActiveAdProbeCache.set(adUrl,Promise.resolve({url:adUrl,active:true}));
					const card=anchor.closest('.item-col.col') || anchor,img=card.querySelector('img');
					const tile=makeGarsoAdTileData(id,{...getWatchedAdDataMirror(id),adUrl,phoneDigits:phone});
					const title=normalizeEscortAdText(card.querySelector('.item-name')?.textContent);
					if (title) tile.title=title;
					const stats=normalizeEscortAdText(card.querySelector('.item-stats')?.textContent);
					if (stats) tile.locationText=stats.replace(/,\s*\d{1,3}\s*(?:y|lat|lata|rok|years?)\b.*$/i,'');
					const src=img?.getAttribute('data-src') || img?.getAttribute('data-lazy-src') || img?.getAttribute('data-original') || img?.getAttribute('src');
					if (src) {try {tile.imageUrl=new URL(src,finalUrl).href;} catch {}}
					found.set(String(id),tile);
				}
			}
			if (!matched) {
				const tile=await findGarsoEscortiFallback(phone,cancelToken);
				if (tile) found.set(tile.adId,tile);
			}
		}
		cancelToken.throwIfCancelled();
		return [...found.values()];
	}
	async function findGarsoLinkedAdTiles(urls,cancelToken,onTile) {
		const seen=new Set();
		for (const value of urls) {
			cancelToken.throwIfCancelled();
			const url=normalizeEscortiAdUrl(value),id=parseAdIdFromUrl(url);
			if (!id || seen.has(id)) continue;
			seen.add(id);
			const cacheHost=getEscortCachedAdHost(id);
			let activity=null;
			if (!cacheHost) {
				let request=escortActiveAdProbeCache.get(url);
				if (!request) {
					request=inspectEscortClubAdActivityOnly(url,cancelToken);
					escortActiveAdProbeCache.set(url,request);
				}
				try {activity=await request;}
				catch {
					if (escortActiveAdProbeCache.get(url) === request) escortActiveAdProbeCache.delete(url);
					cancelToken.throwIfCancelled(); continue;
				}
				cancelToken.throwIfCancelled();
				if (!activity?.active) continue;
			}
			const tile=makeGarsoAdTileData(id,{...mergeEscortDefinedObject(getWatchedAdDataMirror(id),activity?.adData),adUrl:url});
			if (cacheHost) {
				tile.url=addEscortCachedArchiveMarkerToUrl(`https://${cacheHost}${getEscortAdPath(id,cacheHost)}`);
				tile.cached=true;
			}
			onTile(tile);
		}
	}
	function renderGarsoVisitedAds(host,ads,label='Odwiedzone anonse') {
		host.hidden=!ads.length;
		const signature=JSON.stringify([label,ads]);
		if (host._vmSignature === signature) return;
		host._vmSignature=signature;
		const restoreMoreFocus=document.activeElement === host.querySelector('.vm-garso-visited-more');
		host.replaceChildren(makeElement('span','vm-garso-visited-label',`${label} (${ads.length}):`));
		const grid=makeElement('div','vm-garso-visited-grid');
		for (const ad of ads) {
			const card=makeElement('div','vm-garso-visited-card');
			const placeholder=makeElement('span','vm-garso-visited-thumb','—',{title:'Brak zdjęcia'}),thumb=ad.imageUrl ? makeElement('img','vm-garso-visited-thumb',null,{alt:'',loading:'lazy',decoding:'async',referrerPolicy:'no-referrer'}):placeholder;
			if (ad.imageUrl) {
				thumb.src=ad.imageUrl; thumb.title='Powiększ zdjęcie';
				thumb.addEventListener('error',() => {hideWatchImagePreview(); thumb.replaceWith(placeholder);},{once:true});
				thumb.addEventListener('mouseenter',() => showWatchImagePreview(thumb,thumb.currentSrc || thumb.src));
				thumb.addEventListener('mouseleave',hideWatchImagePreview);
			}
			const body=makeElement('div','vm-garso-visited-body');
			const row=makeElement('div','vm-garso-visited-title-row');
			const link=makeElement('a','',null,{title:ad.title ? `${ad.title} [${ad.adId}]`:`Anons ${ad.adId}`});
			link.append(makeElement('span','vm-garso-visited-title-text',ad.title || 'Anons'),
				makeElement('span','vm-garso-visited-title-id',` [${ad.adId}]`));
			Object.assign(link,{href:ad.url,target:'_blank',rel:'noopener noreferrer'});
			row.appendChild(link); body.appendChild(row);
			if (ad.locationText) body.appendChild(makeElement('span','vm-garso-visited-location',ad.locationText));
			if (ad.profileStatsText) body.appendChild(makeElement('span','vm-garso-visited-stats',ad.profileStatsText));
			if (ad.priceText) body.appendChild(makeElement('span','vm-garso-visited-price',ad.priceText));
			const visitedWhen=makeElement('span','vm-garso-visited-when',ad.visitedAt ? `Ostatnio obejrzany: ${formatWatchDateTime(ad.visitedAt)} (${formatEscortLastVisit(ad.visitedAt)})`:'Jeszcze nieobejrzany');
			card.append(thumb,body,visitedWhen); grid.appendChild(card);
		}
		host.appendChild(grid);
		if (ads.length > 3) {
			const more=makeButton('vm-tracked-profile-picker vm-garso-visited-more');
			const update=() => {
				const expanded=host.dataset.vmExpanded === '1';
				for (let index=0; index < grid.children.length; index++) grid.children[index].hidden=!expanded && index >= 3;
				more.textContent=expanded ? 'Pokaż mniej':`Pokaż pozostałe (${ads.length - 3})`;
				more.setAttribute('aria-expanded',String(expanded));
			};
			more.addEventListener('click',event => {stopUiEvent(event); host.dataset.vmExpanded=host.dataset.vmExpanded === '1' ? '0':'1'; update();});
			host.appendChild(more); update();
			if (restoreMoreFocus) more.focus({preventScroll:true});
		}
	}
	function createGarsoTrackedBox(topicView) {
		const box=makeElement('div','vm-garso-tracked-topics' + (topicView ? ' vm-garso-topic-helper-card':''));
		box.setAttribute('aria-label','Zapisane dane Escort Helper');
		const notice=makeElement('div','vm-ui-help');
		notice.hidden=true; notice.style.gridColumn='1 / -1'; notice.setAttribute('role','status'); box.appendChild(notice);
		const visited=makeElement('div','vm-garso-visited-ads'); visited.hidden=true;
		const {root:profile,panel,render}=createTrackedEntriesBox(changeTrackedEntry,{accent:'#b52d77'});
		const footer=makeElement('div','vm-ui-row');
		const add=makeButton('vm-tracked-profile-picker','+ Dodaj profil'),status=makeElement('div','vm-ui-help');
		status.setAttribute('role','status'); add.title='Dodaj profil na podstawie numeru telefonu powiązanego z tematem.';
		footer.append(add,status); panel.appendChild(footer);
		const remaining=makeElement('div','vm-garso-unvisited-ads');
		const otherTiles=makeElement('div','vm-garso-visited-ads');
		const details=makeElement('div','vm-garso-profile-column');
		const compact=topicView && SETTINGS.showCompactSummaryAboveDescription !== false ? createEscortCompactSummaryBox(false):null;
		const caption=makeElement('a','vm-garso-visited-label vm-helper-ui');
		if (compact) {
			Object.assign(caption,{target:'_blank',rel:'noopener noreferrer'});
			details.append(caption,compact.box);
		}
		details.appendChild(profile);
		remaining.append(otherTiles); box.append(visited,remaining,details);
		let fetched=[],searchToken=null,requestedKey='',completedKey='',compactSignature='';
		let topicAds=[],linksToken=null,linksKey='',linksFrame=0;
		const cache=new Map(),cacheToken=createOperationCancelToken('Garso - dane zapisanych anonsów');
		const updateSummary=() => {if (box.isConnected && box._vmState) box._vmUpdate(box._vmState);};
		if (topicView) window.addEventListener('vm-garso-topic-summary-updated',updateSummary);
		box._vmCancel=() => {
			searchToken?.cancel('Opuszczono temat'); linksToken?.cancel('Opuszczono temat'); cacheToken.cancel('Opuszczono temat');
			cancelAnimationFrame(linksFrame);
			compact?.popularMentionsResizeObserver?.disconnect();
			window.removeEventListener('vm-garso-topic-summary-updated',updateSummary);
		};
		const loadCachedAds=ads => {
			const byHost=new Map();
			for (const ad of ads) {
				const id=String(ad.adId),host=getEscortCachedAdHost(id);
				if (!host || cache.has(id)) continue;
				cache.set(id,null);
				if (!byHost.has(host)) byHost.set(host,[]);
				byHost.get(host).push(id);
			}
			for (const [host,adIds] of byHost) {
				void requestPersistentCacheBridgeAction('read-ad-data',{host,adIds,cancelToken:cacheToken}) .then(response => {
						cacheToken.throwIfCancelled();
						for (const value of response.ads || []) {
							const id=String(value.adData?.adId);
							if (adIds.includes(id)) cache.set(id,value);
						}
						updateSummary();
					}).catch(error => {
						for (const id of adIds) if (cache.get(id) === null) cache.delete(id);
						if (!cacheToken.cancelled) log('Nie udało się odczytać danych anonsów z cache',error);
					});
			}
		};
		const enrich=ad => {
			const data=cache.get(String(ad.adId))?.adData;
			if (!data) return ad;
			const saved=makeGarsoAdTileData(ad.adId,data);
			const result={...ad,visitedAt:Math.max(ad.visitedAt || 0,saved.visitedAt)};
			for (const key of ['title','imageUrl','locationText','priceText','profileStats','profileStatsText']) {
				result[key]=(ad.cached && saved[key]) || ad[key] || saved[key];
			}
			if (!result.profileStats?.text && saved.profileStats?.text) result.profileStats=saved.profileStats;
			return result;
		};
		const searchInBackground=async (phones,key) => {
			const token=createOperationCancelToken('Garso - nieodwiedzone anonse');
			searchToken=token; requestedKey=key;
			try {
				const results=await findGarsoPhoneAdTiles(phones,token);
				token.throwIfCancelled();
				if (!box.isConnected || JSON.stringify([...box._vmState.phones].sort()) !== key) return;
				fetched=results; completedKey=key;
				box._vmUpdate(box._vmState);
			} catch (error) {
				if (!token.cancelled) log('Nie udało się wyszukać anonsów tematu Garso',error);
			} finally {
				if (searchToken === token) searchToken=null;
			}
		};
		add.addEventListener('click',async event => {
			stopUiEvent(event);
			if (add.disabled || !box._vmState) return;
			add.disabled=true;
			try {
				const state=box._vmState;
				const phoneDigits=state.phones.length === 1 ? state.phones[0]:await chooseWatchOptionDialog({
					id:'vm-garso-profile-phone',title:'Wybierz numer profilu',info:'Temat zawiera kilka numerów telefonu.',
					choices:state.phones.map(phone => [phone,formatEscortPhoneForClipboard(phone,'local-hyphens')])
				});
				if (!phoneDigits) return;
				const shownModes=state.items.filter(item => item.sourceType !== 'escort-ad' && normalizePhoneReputationPhone(item.phoneDigits)?.local === phoneDigits).map(item => item.resolutionMode);
				const mode=await chooseFavoriteProfileResolutionMode(shownModes,['phone-escorti','phone-escort-search']);
				if (!mode) return;
				status.textContent='Wyszukiwanie profilu…';
				const ads=state.ads.filter(ad => ad.phoneDigits === phoneDigits),seed=ads[0];
				let context={
					id:'garso-profile-preview',sourceType:'phone',profileUrls:[],resolvedAdUrls:ads.map(ad => ad.url),
					adUrl:seed?.url || null,phoneDigits,displayName:extractGarsoTopicTitle(document,`Profil numeru ${phoneDigits}`),
					imageUrl:seed?.imageUrl || '',watchKey:`phone:${phoneDigits}`,resolutionMode:mode,
					favorite:false,hidden:false,note:'',categories:[],modes:{},keepProfile:true
				};
				const resolved=await resolveWatchedProfileData(normalizeWatchedItem(context));
				if (!box.isConnected || !box._vmState.phones.includes(phoneDigits) || !getWatchSettings().enabled) return;
				const resolvedUrls=[...new Set([...ads.map(ad => ad.url),...(resolved.adUrls || [])])];
				const profileIds=(resolved.profileUrls || []).map(getEscortiProfileId).filter(Boolean).sort((a,b) => Number(a) - Number(b));
				context={...context, sourceType:mode === 'phone-escorti' && profileIds.length ? 'escorti-profile':'phone',
					profileUrls:resolved.profileUrls || [],resolvedAdUrls:resolvedUrls,adUrl:seed?.url || resolvedUrls[0] || null,
					displayName:resolved.displayName || context.displayName,imageUrl:resolved.imageUrl || context.imageUrl,
					watchKey:profileIds.length ? `profiles:${profileIds.join(',')}`:`phone:${phoneDigits}`
				};
				delete context.id;
				const existing=getWatchedItems().find(item => item.sourceType !== 'escort-ad' && watchedItemsOverlap(item,context));
				saveFavoriteItemFromContext(context,{favorite:existing?.favorite === true},'person');
				status.textContent='';
			} catch (error) {status.textContent=error?.message || 'Nie udało się dodać profilu.';}
			finally {add.disabled=false;}
		});
		box._vmUpdate=state => {
			box._vmState=state;
			profile.hidden=!state.showProfiles || (!state.items.length && !state.canAdd);
			footer.hidden=!state.canAdd;
			if (!profile.hidden) render(state.items);
			const key=JSON.stringify([...state.phones].sort());
			if (topicView && state.phones.length && requestedKey !== key) {
				searchToken?.cancel('Zmieniono numer tematu'); fetched=[]; completedKey='';
				void searchInBackground([...state.phones],key);
			}
			const candidates=completedKey === key ? fetched:(state.otherAds || []);
			const visits=new Map(state.ads.map(ad => [String(ad.adId),enrich(ad)])),others=new Map();
			const addTile=(candidate,fromTopic=false) => {
				const ad=enrich(candidate),id=String(ad.adId);
				if (visits.has(id) || (!fromTopic && !state.phones.includes(ad.phoneDigits))) return;
				const visitedAt=Math.max(ad.visitedAt || 0,Number(getWatchedAdDataMirror(id)?.lastVisitedAt) || 0);
				if (visitedAt) visits.set(id,{...ad,visitedAt});
				else others.set(id,ad);
			};
			for (const candidate of candidates) addTile(candidate);
			const fromTopic=topicView && !visits.size && !others.size && (!state.phones.length || completedKey === key);
			const urls=fromTopic && document.getElementById('vm-garso-topic-summary')?._vmReferences?.adUrls;
			const nextLinksKey=Array.isArray(urls) ? JSON.stringify([key,urls]):'';
			if (nextLinksKey !== linksKey) {
				linksToken?.cancel('Zmieniono źródło kafelków'); linksToken=null; topicAds=[]; linksKey=nextLinksKey;
				if (urls?.length) {
					const token=linksToken=createOperationCancelToken('Garso - anonse z linków w temacie');
					void findGarsoLinkedAdTiles(urls,token,ad => {
						if (!token.cancelled && box.isConnected && linksToken === token) {
							topicAds.push(ad);
							if (!linksFrame) linksFrame=requestAnimationFrame(() => {linksFrame=0; updateSummary();});
						}
					}).catch(error => {if (!token.cancelled) log('Nie udało się sprawdzić linków tematu Garso',error);});
				}
			}
			if (fromTopic) for (const ad of topicAds) addTile(ad,true);
			if (topicView) loadCachedAds([...visits.values(),...others.values()]);
			notice.hidden=!fromTopic || (!visits.size && !others.size);
			if (!notice.hidden) notice.replaceChildren(
				state.phones.length ? 'Nie znaleziono ogłoszeń online z tym numerem telefonu. ' : 'Nie udało się ustalić numeru telefonu. ',
				makeElement(state.phones.length ? 'span':'strong','','Wyświetlane są anonse podane w postach'),
				' - dostępne online lub z cache.');
			const visitAds=[...visits.values()].sort((a,b) => b.visitedAt - a.visitedAt || Number(b.adId) - Number(a.adId));
			renderGarsoVisitedAds(visited,visitAds);
			remaining.hidden=!topicView || !others.size;
			if (!remaining.hidden) renderGarsoVisitedAds(otherTiles,[...others.values()],fromTopic ? 'Nieodwiedzone anonse z tematu':'Nieodwiedzone anonse z tym numerem telefonu');
			if (compact) {
				const seed=visitAds[0] || [...others.values()].sort((a,b) => Number(b.adId) - Number(a.adId))[0];
				const saved=seed && cache.get(String(seed.adId));
				const topicSummary=document.getElementById('vm-garso-topic-summary')?._vmSummary;
				caption.hidden=compact.box.hidden=!seed && !topicSummary;
				const facts={...saved?.facts};
				if (topicSummary) facts.garsoPhoneSummaryResult=facts.garsoAddressSummaryResult=topicSummary;
				const data=saved?.adData || (seed && getWatchedAdDataMirror(seed.adId));
				if (!facts.photoRange && data?.oldestPhotoDate) facts.photoRange={oldest:data.oldestPhotoDate};
				const signature=JSON.stringify([seed?.adId,seed?.url,facts,data?.tags,data?.checkedAt]);
				if (!compact.box.hidden && signature !== compactSignature) {
					compactSignature=signature;
					caption.textContent=`Skrócone podsumowanie${seed ? ` [${seed.adId}]`:''}`;
					if (seed) caption.href=seed.url; else caption.removeAttribute('href');
					caption.title=data?.checkedAt ? `Dane anonsu z cache: ${formatWatchDateTime(data.checkedAt)}`:'';
					renderEscortCompactSummary(compact,facts,data?.tags?.length || 0,'Brak zapisanych danych do podsumowania');
				}
			}
			details.hidden=profile.hidden && (!compact || compact.box.hidden);
			box.hidden=visited.hidden && remaining.hidden && details.hidden;
		};
		return box;
	}
	let garsoTrackedIndex=null;
	function getGarsoTrackedIndex() {
		const mirror=readWatchedAdDataMirror();
		if (watchedItemsReadCache && garsoTrackedIndex?.itemsCache === watchedItemsReadCache && garsoTrackedIndex.mirror === mirror) return garsoTrackedIndex;
		const byPhone=new Map(),byAd=new Map();
		const add=(map,key,item) => {if (key) {if (!map.has(key)) map.set(key,new Set()); map.get(key).add(item);}};
		const phoneKey=value => normalizePhoneReputationPhone(value)?.local || '';
		for (const item of getWatchedItems()) {
			add(byPhone,phoneKey(item.phoneDigits),item);
			for (const url of getWatchedItemAdUrls(item)) {
				const id=parseAdIdFromUrl(url); add(byAd,id,item);
				if (id) add(byPhone,phoneKey(getEscortAdPhoneDigits(mirror[id])),item);
			}
		}
		return garsoTrackedIndex={itemsCache:watchedItemsReadCache,mirror,byPhone,byAd};
	}
	async function initGarsoTrackedTopics() {
		if (!GARSO_MENU_ONLY_MODE || window.top !== window.self || getSettings().garsoShowTopicHelperData === false ||
			document.getElementById('vm-garso-tracked-topics-style')) return;
		ensureTrackedNoteCategoryStyles();
		const root=document.getElementById('content') || document.body;
		if (!root) return;
		const className='vm-garso-tracked-topics',context=getGarsoTopicPageContext();
		const ignored='.vm-helper-ui,[id^="vm-"],.vm-garso-inline-tools,.' + className;
		const postContent='.post_block,[id^="post_id_"],.post_wrap,.post_body,.post,.ipsComment,[data-role="commentContent"]';
		appendHelperStyle('vm-garso-tracked-topics-style',`
.${className} { --vm-ui-panel-text:var(--vm-ui-text,#222); --vm-ui-panel-muted:var(--vm-ui-text-muted,#666); display:block; max-width:100%; margin:5px 0; font:11px/1.3 var(--vm-ui-font-family,Arial,sans-serif); text-align:left; }
.${className} [hidden], .${className}[hidden] { display:none !important; }
.${className}.vm-garso-topic-helper-card { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr)); align-items:start; width:100%; max-width:100%; gap:12px; box-sizing:border-box; margin:12px 0; padding:10px; border:1px solid var(--vm-ui-border,#cec4c6); border-radius:7px; background:var(--vm-ui-bg,#fff); color:var(--vm-ui-panel-text); }
.vm-garso-profile-column { min-width:0; }
.vm-garso-profile-column > #vm-escort-compact-summary { margin:0 0 8px; }
.vm-garso-profile-column > @vlabel { display:block; margin-bottom:6px; }
.vm-garso-unvisited-ads { display:grid; align-content:start; gap:8px; min-width:0; }
@vads { display:flex; flex-direction:column; align-items:flex-start; gap:6px; min-width:0; color:var(--vm-ui-panel-muted); font:11px/1.3 var(--vm-ui-font-family,Arial,sans-serif); text-align:left; }
@vlabel { font-weight:700; color:var(--vm-ui-panel-text); }
@vgrid { display:flex; flex-wrap:wrap; align-items:flex-start; gap:7px; width:100%; }
@vcard { flex:0 1 100%; width:100%; max-width:100%; display:grid; grid-template-columns:54px minmax(0,1fr); gap:7px; min-height:68px; padding:5px; box-sizing:border-box; border:1px solid var(--vm-ui-border,#d8d8dc); border-radius:6px; background:var(--vm-ui-bg-alt,#fafafa); }
@vthumb { display:grid; place-items:center; width:54px; height:68px; border-radius:4px; object-fit:cover; background:var(--vm-ui-bg-hover,#eee); }
@vbody { min-width:0; display:flex; flex-direction:column; align-items:flex-start; gap:4px; }
@vtitle-row { display:flex; align-items:center; flex-wrap:nowrap; gap:2px; min-width:0; width:100%; overflow:hidden; }
@vtitle-row > a { display:flex; align-items:center; gap:3px; flex:0 1 auto; min-width:0; overflow:hidden; white-space:nowrap; overflow-wrap:normal; }
@vtitle-text { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@vtitle-id { flex:0 0 auto; white-space:nowrap; }
@vtitle-row > .vm-garso-inline-tools { flex:0 0 auto; margin-left:1px; }
@vcard a { color:var(--vm-ui-accent,#b52d77); font-weight:700; text-decoration:none; overflow-wrap:anywhere; }
@vcard a:hover { text-decoration:underline; }
@vlocation, @vstats, @vprice, @vwhen { color:var(--vm-ui-panel-muted); font-size:10px; }
@vwhen { grid-column:1 / -1; grid-row:2; justify-self:start; min-width:0; text-align:left; }
`.replaceAll('@v','.vm-garso-visited-'));
		let topicSource=null;
		if (context) {
			try {topicSource=await getGarsoCurrentTopicSource();}
			catch (error) {
				log('Nie udało się odczytać danych pierwszego posta Garso',error);
				topicSource={phones:getGarsoTopicMatchPhones(document,extractGarsoTopicTitle(document),context),adUrls:[]};
			}
		}
		const phoneKey=value => normalizePhoneReputationPhone(value)?.local || '';
		const boxes=new Map();
		let sourceCache=new WeakMap();
		const render=() => {
			const used=new Set(),settings=getWatchSettings(),show=getSettings().garsoShowTopicHelperData !== false;
			const showProfiles=show && settings.enabled,showVisited=show && !!context;
			const visibleItem=item => settings.enabled && (hasFavoriteStar(item) || item.hidden || item.categories.length || watchedItemHasActiveAlerts(item) || item.note);
			if (showProfiles || showVisited) {
				const {mirror,byPhone,byAd}=getGarsoTrackedIndex();
				const decorate=(target,sources,topicView=false) => {
					if (used.has(target)) return;
					let data=topicView ? topicSource:sourceCache.get(target);
					if (!data) {data=readGarsoMatchSource(sources); sourceCache.set(target,data);}
					const adUrls=data?.adUrls || [],matches=new Set();
					const tokens=topicView ? data?.phones || []:findGarsoPhoneTokens(data?.text || '');
					const phones=[...new Set(tokens.filter(t => !/xxx$/i.test(t.value)).map(t => phoneKey(t.value)).filter(Boolean))];
					const addMatches=(map,key) => {for (const item of map.get(key) || []) matches.add(item);};
					for (const adUrl of adUrls) addMatches(byAd,parseAdIdFromUrl(adUrl));
					for (const phone of phones) addMatches(byPhone,phone);
					if (!phones.length && topicView) {
						for (const item of matches) {const p=phoneKey(item.phoneDigits); if (p && !phones.includes(p)) phones.push(p);}
						for (const adUrl of adUrls) {const p=phoneKey(getEscortAdPhoneDigits(mirror[parseAdIdFromUrl(adUrl)])); if (p && !phones.includes(p)) phones.push(p);}
					}
					const visited=new Map(),otherAds=new Map();
					const remember=(adId,value) => {
						if (!adId) return;
						const tile=makeGarsoAdTileData(adId,value);
						if (tile.visitedAt) visited.set(String(adId),tile);
						else if (phones.includes(tile.phoneDigits)) otherAds.set(String(adId),tile);
					};
					if (showVisited && topicView) {
						const linked=new Set(adUrls.map(parseAdIdFromUrl).filter(Boolean)),events=getWatchEvents();
						for (const item of matches) if (item.sourceType !== 'escort-search') {
							for (const adUrl of getWatchedItemVisitedAdUrls(item,mirror,events)) linked.add(parseAdIdFromUrl(adUrl));
						}
						for (const [id,value] of Object.entries(mirror)) {
							if (linked.has(id) || (value?.status === 'ok' && phones.includes(phoneKey(getEscortAdPhoneDigits(value))))) remember(id,value);
						}
					}
					const items=[...matches].filter(item => (topicView && item.keepProfile && item.sourceType !== 'escort-ad') || visibleItem(item));
					const canAdd=showProfiles && topicView && phones.length > 0;
					if ((!showProfiles || (!items.length && !canAdd)) && !visited.size && !otherAds.size && !(showVisited && topicView)) return;
					used.add(target);
					let box=boxes.get(target);
					if (!box?.isConnected) {
						box=createGarsoTrackedBox(topicView);
						if (topicView) target.after(box); else target.appendChild(box);
						boxes.set(target,box);
					}
					box._vmUpdate({items,phones,canAdd,showProfiles,otherAds:[...otherAds.values()],ads:[...visited.values()].sort((a,b) => b.visitedAt - a.visitedAt || Number(b.adId) - Number(a.adId))});
				};
				if (context) {
					const heading=root.querySelector('h1[itemprop="name"],h1.ipsType_pagetitle,h1');
					if (heading && root.querySelector('.post_block,[id^="post_id_"]')) {
						let header=heading.closest('.ipsPageHeader,.ipsBox_withphoto,.topic_header,.topic-header') || heading;
						if (header.querySelector(postContent)) header=heading;
						decorate(header,[],true);
					}
				} else for (const anchor of root.querySelectorAll('#forum_table a[href],.topic_list a[href],#search_results h3 a[href],#search_results h4 a[href],#search_results .ipsDataItem_title a[href]')) {
					if (anchor.closest(ignored) || !getGarsoTopicPageContext(anchor.href)) continue;
					if (!anchor.matches('.topic_title,[id^="tid_"]') && !anchor.closest('h3,h4,.ipsDataItem_title')) continue;
					const row=anchor.closest('tr,.ipsDataItem,li.search_topic,.search_topic');
					if (!row || row.closest(postContent) || row.querySelector(postContent)) continue;
					const target=anchor.closest('td') || row;
					decorate(target,[target]);
				}
			}
			for (const [target,box] of boxes) if (!used.has(target)) {
				const picker=document.querySelector('.vm-favorites-category-picker');
				if (picker?._vmAnchor && box.contains(picker._vmAnchor)) closeHelperDialog(picker);
				box._vmCancel?.(); detachHelperNode(box); boxes.delete(target);
			}
		};
		let timer=null;
		const schedule=() => {if (timer === null) timer=setTimeout(() => {timer=null; try {render();} catch (error) {log('Błąd oznaczeń tematów Garso',error);}},120);};
		const own=node => !!(node.nodeType === 1 ? node:node.parentElement)?.closest(ignored);
		const listSelector='#forum_table,.topic_list,#search_results';
		const observer=new MutationObserver(mutations => {
			let changed=false;
			for (const m of mutations) {
				if (own(m.target) || (m.type === 'childList' && ![...m.addedNodes,...m.removedNodes].some(node => !own(node)))) continue;
				const node=m.target.nodeType === 1 ? m.target:m.target.parentElement;
				if (context) {
					if (node?.closest('h1') || (m.type === 'childList' && [...m.addedNodes,...m.removedNodes].some(n => n.nodeType === 1 &&
						(n.matches('h1,.post_block,[id^="post_id_"]') || n.querySelector('h1,.post_block,[id^="post_id_"]'))))) changed=true;
				} else if (node?.closest(listSelector)) {
					const row=node.closest('tr,.ipsDataItem,li.search_topic,.search_topic');
					if (row) {sourceCache.delete(row); for (const cell of row.querySelectorAll('td')) sourceCache.delete(cell);}
					else sourceCache=new WeakMap();
					changed=true;
				} else if (m.type === 'childList' && [...m.addedNodes,...m.removedNodes].some(n => n.nodeType === 1 &&
					(n.matches(listSelector) || n.querySelector(listSelector)))) {sourceCache=new WeakMap(); changed=true;}
			}
			if (changed) schedule();
		});
		observer.observe(root,{childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:['href']});
		hiddenUiRefreshers.add(schedule);
		let listener=null;
		if (typeof GM_addValueChangeListener === 'function') listener=GM_addValueChangeListener(WATCH_AD_DATA_MIRROR_STORAGE_KEY,schedule);
		window.addEventListener('pagehide',event => {for (const box of boxes.values()) box._vmCancel?.(); if (event.persisted) return; observer.disconnect(); clearTimeout(timer); hiddenUiRefreshers.delete(schedule); if (listener != null) GM_removeValueChangeListener(listener);});
		render();
	}
	function ensureInlineQuickSearchButtonStyle() {
		if (document.getElementById('vm-inline-quick-search-style')) return;
		appendHelperStyle('vm-inline-quick-search-style',`
.vm-garso-inline-search { display:inline-flex; align-items:center; justify-content:center; vertical-align:middle; box-sizing:border-box; width:19px; height:19px; min-width:19px; min-height:19px; margin:0; padding:1px; border:1px solid transparent; border-radius:3px; background:transparent; color:var(--vm-ui-text-muted,#777); cursor:pointer; line-height:1; box-shadow:none; flex:0 0 19px; }
.vm-garso-inline-search:hover, .vm-garso-inline-search:focus-visible { border-color:currentColor; background:color-mix(in srgb,currentColor 9%,transparent); }
.vm-garso-inline-search svg { width:14px; height:14px; pointer-events:none; }
`);
	}
	function makeGarsoInlineSearchButton(value) {
		ensureInlineQuickSearchButtonStyle();
		const button=makeButton('vm-garso-inline-search',null,{title:`Wstaw ${value} do podręcznej wyszukiwarki`});
		button.setAttribute('aria-label',button.title);
		button.innerHTML='<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/></svg>';
		button.addEventListener('click',event => {
			stopUiEvent(event);
			try {
				if (!document.getElementById(GLOBAL_QUICK_SEARCH_ID)) initGlobalQuickSearch(true);
				document.getElementById(GLOBAL_QUICK_SEARCH_ID)?._vmPrefill(value);
			} catch (error) {
				log('Błąd otwierania podręcznej wyszukiwarki z treści Garsoniery',error);
				showWatchToast('Nie udało się otworzyć podręcznej wyszukiwarki. Spróbuj ponownie.',true);
			}
		});
		return button;
	}
	function initGarsoInlineTools() {
		if (!GARSO_MENU_ONLY_MODE || window.top !== window.self || !/^\/forum(?:\/|$)/i.test(location.pathname)) return;
		if (!document.body || document.getElementById('vm-garso-inline-tools-style')) return;
		const showStatuses=SETTINGS.garsoCheckEscortLinks !== false,showSearch=SETTINGS.showTopPhoneSearch !== false && SETTINGS.garsoShowSearchIcons !== false;
		const toolsClass='vm-garso-inline-tools';
		const ignoredSelector='script,style,noscript,template,textarea,input,select,button,pre,code,svg,math,iframe,' +
			'[contenteditable]:not([contenteditable="false"]),[role="textbox"],[hidden],[aria-hidden="true"],' +
			'.cke,.cke_editable,.ipsEditor,.vm-helper-ui,[id^="vm-"],.' + toolsClass;
		const blockTags=new Set('BODY DIV P H1 H2 H3 H4 H5 H6 LI UL OL DL DT DD TD TH TR TABLE SECTION ARTICLE HEADER FOOTER MAIN NAV ASIDE BLOCKQUOTE FIGURE FIGCAPTION FORM FIELDSET BR HR'.split(' '));
		const statusByUrl=new Map();
		const queuedUrls=new Set();
		const pendingRoots=new Set();
		let scanTimer=null,checking=false;
		const style=makeElement('style');
		style.id='vm-garso-inline-tools-style';
		style.textContent=`
:is(.post_block,[id^="post_id_"]) > :is(h3,.post_header),
:is(.post_block,[id^="post_id_"]) > .post_wrap > :is(h3,.post_header) { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.${toolsClass} { display:inline-flex; align-items:center; gap:3px; margin:0 3px; vertical-align:baseline; white-space:nowrap; font:700 13px/1 var(--vm-ui-font-family,Arial,sans-serif); }
.${toolsClass} .vm-garso-ad-online-status { display:inline-flex; align-items:center; gap:3px; font-size:16px; font-weight:900; }
`;
		document.head.appendChild(style);
		if (!showStatuses && !showSearch) return;
		const isIgnored=node => !!(node?.nodeType === 1 ? node:node?.parentElement)?.closest(ignoredSelector);
		const nextTools=node => {
			let next=node.nextSibling;
			while (next?.nodeType === 3 && !next.textContent.trim()) next=next.nextSibling;
			return next?.classList?.contains(toolsClass) ? next:null;
		};
		const adUrlFromLink=value => {
			try {
				const url=new URL(value,location.href);
				if (!/^https?:$/.test(url.protocol) || !isEscortClubHost(url.hostname) ||
					!getEscortAdIdFromPath(url.pathname)) return null;
				return normalizeEscortiAdUrl(url.href);
			} catch {return null;}
		};
		const findTokens=text => {
			const tokens=[],urlRanges=[];
			for (const match of text.matchAll(GARSO_REFERENCE_URL_RE)) {
				urlRanges.push([match.index,match.index + match[0].length]);
				const value=match[0].replace(/[.,;:!?\)\]}]+$/,'');
				const adUrl=adUrlFromLink(/^https?:\/\//i.test(value) ? value:`https://${value}`);
				if (adUrl) tokens.push({start:match.index,end:match.index + value.length,value:/^https?:\/\//i.test(value) ? value:`https://${value}`,adUrl});
			}
			if (!showSearch) return tokens;
			for (const match of findGarsoPhoneTokens(text)) {
				if (urlRanges.some(([from,to]) => match.start < to && match.end > from)) continue;
				tokens.push({...match,adUrl:null});
			}
			return tokens.sort((a,b) => a.start - b.start);
		};
		const showStatus=(url,result) => {
			const entry=statusByUrl.get(url);
			if (!entry) return;
			entry.result=result || null;
			for (const marker of entry.markers) {
				if (!marker.isConnected) {entry.markers.delete(marker); continue;}
				renderEscortAdLinkStatusMarker(marker,entry.result);
			}
		};
		const checkStatuses=async () => {
			if (checking) return;
			checking=true;
			try {
				while (queuedUrls.size) {
					const urls=[...queuedUrls].filter(url => statusByUrl.get(url).result === undefined &&
						[...statusByUrl.get(url).markers].some(marker => marker.isConnected));
					queuedUrls.clear();
					if (!urls.length) break;
					try {
						const summary=await scanActiveEscortAds(urls,progress => {
							const url=normalizeEscortiAdUrl(progress?.result?.url);
							if (url) showStatus(url,progress.result);
						},false,{activityOnly:true,useActivityCache:true,cacheActivity:true});
						const results=new Map((summary?.results || []).map(result => [normalizeEscortiAdUrl(result.url),result]));
						for (const url of urls) if (statusByUrl.get(url).result === undefined) showStatus(url,results.get(url));
					} catch {
						for (const url of urls) if (statusByUrl.get(url).result === undefined) showStatus(url,null);
					}
				}
			} finally {checking=false;}
		};
		const createTools=token => {
			const tools=makeElement('span',toolsClass);
			tools.dataset.vmQuery=token.value;
			if (showStatuses && token.adUrl) {
				const marker=makeElement('span','vm-garso-ad-online-status','…');
				marker.dataset.adUrl=token.adUrl;
				setControlTitle(marker,'Sprawdzanie aktywności anonsu');
				marker.style.color='#9ca3af';
				tools.appendChild(marker);
				let entry=statusByUrl.get(token.adUrl);
				if (!entry) {entry={markers:new Set(),result:undefined}; statusByUrl.set(token.adUrl,entry);}
				entry.markers.add(marker);
				if (entry.result !== undefined) renderEscortAdLinkStatusMarker(marker,entry.result);
				else queuedUrls.add(token.adUrl);
			}
			if (!showSearch) return tools;
			tools.appendChild(makeGarsoInlineSearchButton(token.value));
			return tools;
		};
		const scanRoot=root => {
			const oldTools=new Set(root.querySelectorAll('.' + toolsClass));
			const used=new Set();
			let parts=[],text='';
			const flushText=() => {
				for (const token of findTokens(text).reverse()) {
					const part=parts.find(item => token.end > item.start && token.end <= item.end);
					if (!part) continue;
					const offset=token.end - part.start;
					let tools=offset === part.node.length ? nextTools(part.node):null;
					if (!tools || tools.dataset.vmQuery !== token.value) {
						tools=createTools(token);
						const range=document.createRange();
						range.setStart(part.node,offset);
						range.collapse(true);
						range.insertNode(tools);
					}
					used.add(tools);
				}
				parts=[]; text='';
			};
			const visit=node => {
				if (node.nodeType === 3) {
					parts.push({node,start:text.length,end:text.length + node.length});
					text += node.data;
					return;
				}
				if (node.nodeType !== 1) return;
				if (node.matches(ignoredSelector)) {flushText(); return;}
				if (node.tagName === 'A') {
					flushText();
					const adUrl=adUrlFromLink(node.getAttribute('href'));
					const tokens=adUrl ? [{value:new URL(node.getAttribute('href'),location.href).href,adUrl}]:findTokens(node.textContent).filter(token => !token.adUrl);
					const seen=new Set();
					let previous=node;
					for (const token of tokens) {
						if (seen.has(token.value)) continue;
						seen.add(token.value);
						let tools=nextTools(previous);
						if (!tools || tools.dataset.vmQuery !== token.value) {
							tools=createTools(token);
							previous.after(tools);
							const marker=tools.querySelector('.vm-garso-ad-online-status'),result=statusByUrl.get(token.adUrl)?.result;
							if (marker && result !== undefined) renderEscortAdLinkStatusMarker(marker,result);
						}
						used.add(tools);
						previous=tools;
					}
					return;
				}
				const block=blockTags.has(node.tagName);
				const inlineBoundary=node.matches?.('h1.ipsType_pagetitle > span.desc, h1[itemprop="name"] > span.desc');
				if (block || inlineBoundary) flushText();
				for (const child of [...node.childNodes]) visit(child);
				if (block || inlineBoundary) flushText();
			};
			visit(root);
			flushText();
			for (const tools of oldTools) if (!used.has(tools)) detachHelperNode(tools);
		};
		const queueRoot=node => {
			let root=node?.nodeType === 1 ? node:node?.parentElement;
			if (!root || isIgnored(root)) return;
			while (root !== document.body && root.parentElement && !blockTags.has(root.tagName)) root=root.parentElement;
			pendingRoots.add(root);
			if (scanTimer === null) scanTimer=setTimeout(flushRoots,120);
		};
		const observer=new MutationObserver(mutations => {
			for (const mutation of mutations) {
				if (isIgnored(mutation.target)) continue;
				if (mutation.type === 'childList' && ![...mutation.addedNodes,...mutation.removedNodes].some(node => !isIgnored(node))) continue;
				queueRoot(mutation.target);
			}
		});
		const observe=() => observer.observe(document.body,{
			childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:['href','hidden','aria-hidden']
		});
		function flushRoots() {
			scanTimer=null;
			const roots=[...pendingRoots].filter(root => root.isConnected && ![...pendingRoots].some(other => other !== root && other.contains(root)));
			pendingRoots.clear();
			observer.disconnect();
			try {for (const root of roots) scanRoot(root);}
			catch (error) {log('Błąd oznaczania telefonów i linków na Garsonierze',error);}
			finally {observe();}
			for (const entry of statusByUrl.values()) for (const marker of entry.markers) if (!marker.isConnected) entry.markers.delete(marker);
			void checkStatuses();
		}
		pendingRoots.add(document.body);
		flushRoots();
	}
	function getEscortClubSearchAdUrls(doc,pageUrl) {
		const context=getEscortClubListContext(doc,pageUrl);
		const anchors=context?.anchors || [];
		return [...new Set( anchors .map(anchor => normalizeEscortiAdUrl(anchor.getAttribute('href'),pageUrl))
				.filter(Boolean) )];
	}
	async function fetchEscortClubPhoneSearchAds(phone,cancelToken=null,requestContext=null) {
		requestContext ||= createRequestContext('escort.phone-search');
		const firstUrl=buildEscortClubPhoneSearchUrl(phone);
		const found=new Set();
		const seenPages=new Set();
		let pageUrl=firstUrl;
		while (pageUrl && !seenPages.has(pageUrl) && seenPages.size < 100) {
			cancelToken?.throwIfCancelled();
			seenPages.add(pageUrl);
			const {doc,finalUrl}=await fetchWatchDocumentDirect(pageUrl,cancelToken,{requestContext});
			for (const url of getEscortClubSearchAdUrls(doc,finalUrl)) found.add(url);
			pageUrl=findNextEscortListPageUrl(doc,finalUrl);
		}
		return {
			searchUrl:firstUrl, adUrls:[...found]
		};
	}
	function createResearchPanelResultRow(label,searchUrl) {
		const row=makeButton('vm-research-panel-row');
		row.dataset.searchUrl=searchUrl;
		setButtonColor(row,'checking');
		setTwoLineButton(row,label,'sprawdzam…');
		row.addEventListener('click',event => {
			stopUiEvent(event);
			if (!row.dataset.searchUrl) return;
			GM_openInTab(row.dataset.searchUrl,{active:true,insert:true});
		});
		return row;
	}
	function getGarsoExtendedResultSignature(result) {
		if (result?.signature) return String(result.signature);
		const topicUrls=[...new Set( (result?.topics || []) .map(topic => normalizeTopicUrl(topic?.url)) .filter(Boolean)
		)].sort();
		if (topicUrls.length && result?.topicLinksComplete !== false) return `topics:${topicUrls.join('|')}`;
		return `fallback:${String(result?.searchTerm || '')}|count:${
			Number(result?.count) || 0
		}|url:${normalizeGarsoResultPageUrl(result?.resultUrl) || ''}`;
	}
	function getGarsoExtendedPhoneTarget(phoneDigits,trailDepth=0) {
		const query=getGarsoSearchQuery(phoneDigits,'phone');
		return {adUrl:null,searchTerm:query.term,andorType:query.andorType,phoneDigits,phoneSearchKind:'standard',trailDepth};
	}
	function getCachedGarsoExtendedResults(targets,previous=[]) {
		const results=new Map(previous.map(result => [result.searchTerm,result])),counts=new Map();
		for (const target of targets) {
			if (results.has(target.searchTerm)) continue;
			const mode=target.adUrl ? 'address':'phone',term=target.phoneDigits || target.searchTerm;
			const key=`${mode}:${term}`;
			if (!counts.has(key)) counts.set(key,getGarsoSearchCountCache(mode,term));
			const cached=counts.get(key);
			if (!(cached?.count > 0)) continue;
			if (mode === 'phone' && cached.phoneSearchVariant !== 'both') continue;
			results.set(target.searchTerm,{
				...target,status:'ok',count:cached.count,
				topics:cached.topicLinks
			});
		}
		return [...results.values()];
	}
	function getUniqueGarsoResultPages(results) {
		const seenResults=new Set();
		const uniqueResults=[];
		for (const item of results || []) {
			if (item?.status !== 'ok' || Number(item.count) <= 0) continue;
			const signature=getGarsoExtendedResultSignature(item);
			if (seenResults.has(signature)) continue;
			seenResults.add(signature);
			uniqueResults.push(item);
		}
		return uniqueResults;
	}
	function formatGarsoUniquePageCount(count) {
		return formatPolishCount(count,'unikalna strona','unikalne strony','unikalnych stron');
	}
	async function openUniqueGarsoResultPages(results) {
		const uniqueResults=getUniqueGarsoResultPages(results);
		if (!uniqueResults.length) return 0;
		const auth=await getGarsoAuth();
		for (const result of uniqueResults) {
			await submitGarsoSearchFormToNewTab(result.searchTerm,auth.secureHash,auth.sessionId,{active:false,andorType:result.andorType});
		}
		return uniqueResults.length;
	}
	function summarizeActiveAdsSubset(summary,adUrls) {
		const wanted=new Set( (adUrls || []).map(url => normalizeEscortiAdUrl(url) || url) );
		return summarizeActiveAdResults( (summary?.results || []).filter(item =>
				wanted.has(normalizeEscortiAdUrl(item.url) || item.url) ) );
	}
	function normalizedEscortiResultSet(result,type) {
		const values=type === 'profiles'
			? normalizeProfileUrls(result)
			:(Array.isArray(result?.adUrls) ? result.adUrls:[]);
		return [...new Set(values.map(value => type === 'profiles'
				? (getEscortiProfileId(value) || value)
				:(parseAdIdFromUrl(value) || value) ).filter(Boolean))].sort();
	}
	function areSameEscortiResults(first,second) {
		if (first?.status !== 'ok' || second?.status !== 'ok') return false;
		return ['profiles','ads'].every(type => {
			const a=normalizedEscortiResultSet(first,type);
			const b=normalizedEscortiResultSet(second,type);
			return a.length === b.length && a.every((value,index) => value === b[index]);
		});
	}
	function activeEscortAdsWord(count) {
		return polishCountForm(count,'aktywny','aktywne','aktywnych');
	}
	function getCompactSummaryAggregationSource(facts) {
		const mode=normalizeEscortCompactSummaryMode( SETTINGS.compactSummaryComparisonMode );
		const sources={
			'address-escorti':{
				mode, label:'adres anonsu → Escorti.pl', description:
					'Biorę aktywne anonse Escort.club przypisane do profili znalezionych w Escorti.pl po adresie bieżącego anonsu.',
				result:facts?.escortiAddressResult, summary:facts?.escortiAddressSummary, loading:!!facts?.escortiLoading
			}, 'phone-escorti':{
				mode, label:'nr tel. → Escorti.pl', description:
					'Biorę aktywne anonse Escort.club przypisane do profili znalezionych w Escorti.pl po numerze telefonu bieżącego anonsu.',
				result:facts?.escortiPhoneResult, summary:facts?.escortiPhoneSummary, loading:!!facts?.escortiLoading
			}, 'exact-phone':{
				mode, label:'nr tel. → wyszukiwarka Escort.club', description:
					'Biorę aktualnie dostępne anonse zwrócone przez wyszukiwarkę Escort.club po numerze telefonu bieżącego anonsu.',
				result:null, summary:facts?.escortPhoneSummary, loading:!!facts?.escortPhoneLoading
			}
		};
		return sources[mode] || sources['address-escorti'];
	}
	function buildCompactSummaryAggregationTooltip(source,maxAds=10) {
		const summary=source?.summary;
		const lines=[ `Sposób pozyskania anonsów: ${source?.label || 'brak danych'}.`, source?.description || ''
		].filter(Boolean);
		const activeResults=(summary?.results || []).filter(item => item?.active);
		const activeCount=Number(summary?.activeCount) || activeResults.length;
		lines.push(`Do porównania używam tylko aktywnych anonsów (${activeCount}).`);
		if (!activeResults.length) {
			if (activeCount > 0) {
				lines.push( '', 'Nazwy profili/anonsów nie są dostępne w skróconych danych zapisanych w cache.' );
			}
			return lines.join('\n');
		}
		const descriptions=activeResults.map(item => {
			const adId=parseAdIdFromUrl(item.url);
			const name=normalizeEscortAdText(item.adData?.title)
				|| (adId ? `Anons nr ${adId}`:'Anons bez nazwy');
			const city=normalizeEscortCity(item.adData?.location?.city || item.city);
			return `${name}${city ? ` - ${city}`:''}${adId ? ` (nr ${adId})`:''}`;
		});
		lines.push( '', 'Nazwy aktywnych profili/anonsów uwzględnionych w porównaniu:' );
		lines.push(...descriptions.slice(0,maxAds).map(value => `• ${value}`));
		if (descriptions.length > maxAds) {
			lines.push(`• …oraz ${descriptions.length - maxAds} kolejnych`);
		}
		return lines.join('\n');
	}
	function formatEscortiAdsAndActivity(result,summary) {
		const total=Number.isFinite(Number(result?.adLinks))
			? Number(result.adLinks)
			:Number(summary?.totalChecked) || 0;
		const active=Number(summary?.activeCount) || 0;
		return `${total} ${polishAdWord(total)} (${active} ${activeEscortAdsWord(active)})`;
	}
	function formatResearchDurationDays(days) {
		const safeDays=Math.max(0,Math.round(Number(days) || 0));
		if (safeDays < 31) return `${safeDays} dni`;
		const months=Math.floor(safeDays / 30.4375);
		if (months < 12) return `${months} mies.`;
		const years=Math.floor(months / 12);
		const restMonths=months % 12;
		return restMonths ? `${years} r. ${restMonths} mies.`:`${years} r.`;
	}
	function formatResearchDateAge(value) {
		const normalizedValue=String(value || '').match(/^\d{4}-\d{2}-\d{2}/)?.[0]
			|| value;
		const timestamp=profileDateToTime(normalizedValue);
		const date=formatEscortiDate(normalizedValue);
		if (!date) return 'brak';
		if (timestamp == null) return date;
		const days=Math.max(0,(Date.now() - timestamp) / 86400000);
		return `${date} (${formatResearchDurationDays(days)})`;
	}
	function getEscortiProfileSummariesForResearch(result) {
		if (Array.isArray(result?.profileSummaries) && result.profileSummaries.length) {
			return result.profileSummaries;
		}
		if (result?.status !== 'ok' || !result.profiles) return [];
		return [{
			profileUrl:result.profileUrl || result.profileUrls?.[0] || null, profileName:result.profileName || null,
			creationDate:result.creationDate || null, currentCity:result.currentCity || null, adLinks:result.adLinks,
			cityHistory:Array.isArray(result.cityHistory) ? result.cityHistory:[]
		}];
	}
	function getEscortGalleryPhotoUrls(sourceDocument=document) {
		const urls=new Set();
		for (const anchor of sourceDocument.querySelectorAll(
			'.content-gallery-col .galleryContainer a.simple-zoom-image:not(.video-layer)' )) {
			const candidates=[ anchor.getAttribute('href'), anchor.querySelector('img')?.getAttribute('data-src'),
				anchor.querySelector('img')?.getAttribute('src') ];
			const found=candidates.find(isEscortGalleryPhotoUrl);
			if (found) urls.add(new URL(found,location.href).href);
		}
		return [...urls];
	}
	async function getEscortGalleryPhotoDateRange(sourceDocument=document) {
		const urls=getEscortGalleryPhotoUrls(sourceDocument);
		if (!urls.length) {
			return {
				total:0, dated:0, oldest:null, newest:null, dates:[], items:[]
			};
		}
		const items=(await watchMapLimit(urls,4,async url => ({
			url, date:await getMediaServerDate(url)
		}))) .filter(item => item.date instanceof Date && Number.isFinite(item.date.getTime()) )
			.sort((a,b) => a.date.getTime() - b.date.getTime());
		const dates=items.map(item => item.date);
		return {
			total:urls.length, dated:dates.length, oldest:dates[0] || null, newest:dates.at(-1) || null, dates, items
		};
	}
	const ESCORT_RESEARCH_TIMELINE_CATEGORIES={
		profile:{label:'profile Escorti',color:'#55c7ff'}, ad:{label:'anonse',color:'#ffb45c'},
		garso:{label:'recenzje Garso',color:'#75db91'}, photo:{label:'zdjęcia',color:'#f54da3'},
		location:{label:'lokalizacje Escorti',color:'#b99cff'}
	};
	function getEscortTimelineReadableTextColor(baseColor) {
		return `color-mix(in srgb, ${baseColor} 65%, var(--vm-ui-panel-text, #fff))`;
	}
	function parseEscortResearchTimelineDate(value) {
		if (value instanceof Date && Number.isFinite(value.getTime())) {
			return new Date(value.getTime());
		}
		if (Number.isFinite(Number(value)) && Number(value) > 0) {
			const date=new Date(Number(value));
			return Number.isFinite(date.getTime()) ? date:null;
		}
		const normalizedValue=String(value || '').match(/^\d{4}-\d{2}-\d{2}/)?.[0]
			|| formatEscortiDate(value);
		const timestamp=profileDateToTime(normalizedValue);
		if (timestamp != null) return new Date(timestamp);
		const fallback=Date.parse(String(value || ''));
		return Number.isFinite(fallback) ? new Date(fallback):null;
	}
	function getGarsoSummaryReviewDates(state) {
		const data=state?.dataset;
		return {first:data?.garsoFirstReviewDate || null,last:data?.garsoLastReviewDate || null,
			firstUrl:data?.garsoFirstReviewUrl || null,lastUrl:data?.garsoLastReviewUrl || null};
	}
	function getEscortArchiveActivitySummary(adId,mode,result) {
		const urls=Array.isArray(result?.adUrls) ? result.adUrls:[];
		const previous=getCachedEscortiActivitySummary(adId,mode,result ? urls:null);
		const results=urls.map(url => {
			const data=getStoredEscortAdData(parseAdIdFromUrl(url)),activity=readSharedEscortActivity(url,true);
			const pageCheckedAt=Number(data?.pageCheckedAt) || 0,newerPage=pageCheckedAt > Number(activity?.checkedAt || 0);
			const checkedAt=newerPage ? pageCheckedAt:activity?.checkedAt || Number(data?.checkedAt) || 0;
			return {url,active:newerPage || (activity ? activity.active:!!checkedAt),unknown:!checkedAt,
				checkedAt,stale:!checkedAt || Date.now() - checkedAt >= ESCORT_SHARED_ACTIVITY_CACHE_TTL_MS,
				city:data?.location?.city || null,adData:data || null};
		});
		const summary=summarizeActiveAdResults(results),times=results.map(item => item.checkedAt).filter(Boolean);
		if (previous && (!times.length || (previous.checkedAt >= Math.max(...times) &&
			(previous.activeCount !== summary.activeCount || previous.unknownCount !== summary.unknownCount)))) return {...previous,results:[],
			stale:!previous.checkedAt || Date.now() - previous.checkedAt >= ESCORT_SHARED_ACTIVITY_CACHE_TTL_MS};
		if (!result && !results.length) return null;
		return {...summary,checkedAt:times.length ? Math.min(...times):0,stale:summary.staleCount > 0};
	}
	function renderEscortArchiveResearchRow(row,label,result,summary,availability=true) {
		const total=result?.adLinks != null ? Number(result.adLinks):Number(summary?.totalChecked);
		const known=Number.isFinite(total) && total >= 0;
		const active=Number(summary?.activeCount) || 0,unknown=Number(summary?.unknownCount) || 0;
		const complete=summary && Number(summary.totalChecked) === total && !unknown;
		const checkedAt=Number(availability ? summary?.checkedAt || result?.checkedAt:result?.checkedAt) || 0;
		const stale=!checkedAt || Date.now() - checkedAt >= ESCORT_SHARED_ACTIVITY_CACHE_TTL_MS || summary?.stale;
		let value=known ? `${total} ${polishAdWord(total)}`:'brak zapisanego wyniku';
		if (known && availability && total > 0) value=complete
			? formatEscortiAdsAndActivity(result,summary)
			:value + (active ? ` • online: ≥${active}`:' • online: b/d');
		setButtonColor(row,!known || stale || (availability && total > 0 && !complete) ? 'stale':total > 0 ? 'found':'empty');
		setTwoLineButton(row,label,value);
		row.title=`Wynik zapisany w cache${checkedAt ? `: ${formatEscortCachedArchiveDate(checkedAt)}`:''}.` +
			(availability && known && !complete && total > 0 ? ' Brak pełnego zapisanego wyniku dostępności.':'');
	}
	function getEscortArchiveResearchFactsFromCache( adId, adUrl, phone, garsoCombinedSummaryState, cachedGarsoPhone,
		cachedGarsoAddress, archiveData=null ) {
		const stored=archiveData || getStoredEscortAdData(adId) || {};
		const phoneResult=getListCache(adId,'phone-escorti');
		const addressResult=getListCache(adId,'address-escorti');
		const escortPhoneResult=getListCache(adId,'phone-escort-search');
		const normalizedPhone=normalizePhoneReputationPhone(phone);
		const garsoSummary= (!['phone','address'].includes(garsoCombinedSummaryState?.dataset?.garsoSearchMode)
			? garsoCombinedSummaryState?._vmGarsoSummaryResult:null) ||
			getGarsoSummaryCache('combined',buildCombinedGarsoSummaryTerm(phone,adUrl))?.result || null;
		const reviewDates=getGarsoSummaryReviewDates(garsoCombinedSummaryState);
		return {
			garsoPhoneCount:cachedGarsoPhone?.count ?? null, garsoAddressCount:cachedGarsoAddress?.count ?? null,
			escortiPhoneResult:phoneResult || null, escortiAddressResult:addressResult || null,
			escortiPhoneSummary:getEscortArchiveActivitySummary(adId,'phone-escorti',phoneResult),
			escortiAddressSummary:getEscortArchiveActivitySummary(adId,'address-escorti',addressResult),
			escortPhoneResult,escortPhoneSummary:getEscortArchiveActivitySummary(adId,'phone-escort-search',escortPhoneResult),
			sameEscortiResult: phoneResult && addressResult
					? areSameEscortiResults(phoneResult,addressResult)
					:null, escortiWarnings:[], escortiLoading:false, escortPhoneLoading:false,
			photoRange:stored.oldestPhotoDate ? {oldest:stored.oldestPhotoDate}:null, photoDatesLoading:false,
			garsoPhoneSummaryResult:garsoSummary || getGarsoSummaryCache('phone',phone)?.result || null,
			garsoAddressSummaryResult:garsoSummary || getGarsoSummaryCache('address',adUrl)?.result || null, datePosted:stored.datePosted || null,
			currentAdUrl:adUrl, phoneReputationResult: SETTINGS.showPhoneReputationLinks && normalizedPhone
					? getCachedPhoneReputation(normalizedPhone)
					:null, garsoPhoneReviewDates:{...reviewDates}, garsoAddressReviewDates:{...reviewDates}, savedAt:null
		};
	}
	function collectEscortResearchTimelineEvents(facts) {
		const events=[];
		const seen=new Set();
		const add=( value, label, category, identity='', url='', source='' ) => {
			const date=parseEscortResearchTimelineDate(value);
			if (!date || !ESCORT_RESEARCH_TIMELINE_CATEGORIES[category]) return;
			const key=`${category}|${identity || label}|${date.toISOString().slice(0,10)}`;
			if (seen.has(key)) return;
			seen.add(key);
			events.push({
				date, label, category, url:String(url || ''), source:String(source || '')
			});
		};
		add( facts?.datePosted, isEscortCachedArchiveRuntime()
				? 'Publikacja bieżącego anonsu Escort.club'
				:'Włączenie bieżącego anonsu Escort.club', 'ad', `current:${facts?.currentAdUrl || location.pathname}`,
			facts?.currentAdUrl || location.href );
		const resultPairs=[ ['adres',facts?.escortiAddressResult], ['telefon',facts?.escortiPhoneResult] ];
		for (const [,result] of resultPairs) {
			for (const profile of getEscortiProfileSummariesForResearch(result)) {
				const profileIdentity=profile.profileUrl || profile.profileName || 'profil';
				add( profile.creationDate, `Założenie profilu Escorti${profile.profileName ? `: ${profile.profileName}`:''}`,
					'profile', profileIdentity, profile.profileUrl );
				for (const entry of Array.isArray(profile.cityHistory) ? profile.cityHistory:[]) {
					add( entry?.date, `Lokalizacja Escorti${entry?.city ? `: ${entry.city}`:''}`, 'location',
						`${profileIdentity}:${entry?.city || ''}`, profile.profileUrl );
				}
			}
		}
		const activeResultSources=[ {
				source:'escorti', label:'Dodanie aktywnego anonsu znalezionego przez Escorti.pl', results:[
					...(facts?.escortiAddressSummary?.results || []), ...(facts?.escortiPhoneSummary?.results || []) ]
			}, {
				source:'escort-phone', label:'Dodanie aktywnego anonsu Escort.club z tym samym nr tel.',
				results:facts?.escortPhoneSummary?.results || []
			} ];
		for (const activeSource of activeResultSources) {
			for (const item of activeSource.results) {
				if (!item?.active || !item.adData?.datePosted) continue;
				const adId=parseAdIdFromUrl(item.url);
				add( item.adData.datePosted, `${activeSource.label}${adId ? ` nr ${adId}`:''}`, 'ad',
					`${activeSource.source}:${item.url || adId || item.adData.datePosted}`, item.url, activeSource.source );
			}
		}
		const reviewSources=[ ['nr tel. + adres anonsu', facts?.garsoPhoneReviewDates || facts?.garsoAddressReviewDates] ];
		for (const [source,dates] of reviewSources) {
			add( dates?.first, `Pierwsza recenzja Garso (${source})`, 'garso', `first:${source}`, dates?.firstUrl );
			add( dates?.last, `Ostatnia recenzja Garso (${source})`, 'garso', `last:${source}`, dates?.lastUrl );
		}
		const photoItems=Array.isArray(facts?.photoRange?.items)
			? facts.photoRange.items
			:(facts?.photoRange?.dates || []).map(date => ({date,url:''}));
		for (let index=0;index<photoItems.length;index++) {
			const item=photoItems[index];
			add( item?.date, 'Zdjęcie – data pliku na serwerze', 'photo', `photo:${index}`, item?.url );
		}
		return events.sort((a,b) => a.date.getTime() - b.date.getTime());
	}
	function makeEscortSummarySectionCollapsible( section, header, content, storageSuffix, defaultExpanded=true,
		persistState=true ) {
		if (!section || !header || header.dataset.vmCollapsibleReady === '1') return null;
		const contentNodes=(Array.isArray(content) ? content:[content]).filter(Boolean);
		if (!contentNodes.length) return null;
		header.dataset.vmCollapsibleReady='1';
		const storageKey=`vm_escort_summary_section_expanded_${storageSuffix}`;
		let expanded=persistState
			? !!GM_getValue(storageKey,defaultExpanded)
			:!!defaultExpanded;
		const originalMarginBottom=header.style.marginBottom || '';
		const toggleHint=makeElement('span','vm-escort-summary-collapse-hint');
		toggleHint.setAttribute('aria-hidden','true');
		const hintText=makeElement('span');
		const arrow=makeElement('span');
		arrow.setAttribute('aria-hidden','true');
		toggleHint.append(hintText,arrow);
		header.appendChild(toggleHint);
		header.classList.add('vm-escort-summary-collapsible-header');
		header.setAttribute('role','button');
		header.setAttribute('tabindex','0');
		const apply=() => {
			for (const node of contentNodes) node.hidden=!expanded;
			hintText.textContent=expanded ? 'Zwiń':'Rozwiń';
			arrow.textContent=expanded ? '▾':'▸';
			section.classList.toggle( 'vm-escort-summary-section-collapsed', !expanded );
			header.style.marginBottom=expanded ? originalMarginBottom:'0';
			header.setAttribute('aria-expanded',String(expanded));
			header.title=expanded ? 'Kliknij, aby zwinąć sekcję':'Kliknij, aby rozwinąć sekcję';
		};
		const setExpanded=nextExpanded => {
			expanded=!!nextExpanded;
			if (persistState) writeHelperStorageValue(storageKey,expanded);
			apply();
		};
		const toggle=() => setExpanded(!expanded);
		header.addEventListener('click',event => {
			if (event.target.closest('a, button, input, select, textarea')) return;
			toggle();
		});
		header.addEventListener('keydown',event => {
			if (event.target !== header || (event.key !== 'Enter' && event.key !== ' ')) return;
			event.preventDefault();
			toggle();
		});
		apply();
		return {
			setExpanded, isExpanded:() => expanded
		};
	}
	function makeEscortTimelineElementClickable(element,url,titleText) {
		let targetUrl=null;
		try {
			const parsed=new URL(String(url || ''),location.href);
			if (['http:','https:'].includes(parsed.protocol)) targetUrl=parsed.href;
		} catch {}
		if (!element || !targetUrl) return false;
		element.setAttribute('role','link');
		element.setAttribute('tabindex','0');
		element.title=titleText || 'Otwórz stronę źródłową';
		element.classList.add('vm-summary-source-link');
		element.addEventListener('mouseenter',() => {
			element.style.filter='brightness(1.12)';
		});
		element.addEventListener('mouseleave',() => {
			element.style.filter='';
		});
		const open=event => {
			stopUiEvent(event);
			GM_openInTab(targetUrl,{
				active:true, insert:true, setParent:true
			});
		};
		element.addEventListener('click',open);
		element.addEventListener('keydown',event => {
			if (event.key !== 'Enter' && event.key !== ' ') return;
			open(event);
		});
		return true;
	}
	function formatEscortResearchTimelineAge(date) {
		const days=Math.max(0,(Date.now() - date.getTime()) / 86400000);
		return `${formatResearchDurationDays(days)} temu`;
	}
	function createEscortResearchDateTimeline(facts) {
		const box=makeElement('section','vm-escort-summary-integrated-section');
		const title=makeElement('div','vm-summary-timeline-title');
		const titleText=makeElement('span','','Oś czasu i zgodność dat');
		const titleWarning=makeElement('span','vm-summary-timeline-title-warning','⚠︎',{hidden:true});
		titleWarning.setAttribute('aria-label','Ostrzeżenia na osi czasu');
		title.append(titleText,titleWarning);
		box.appendChild(title);
		const timelineHeaderWarnings=[];
		const addTimelineHeaderWarning=(message,color,priority) => {
			timelineHeaderWarnings.push({message,color,priority});
			const strongest=[...timelineHeaderWarnings] .sort((a,b) => b.priority - a.priority)[0];
			titleWarning.hidden=false;
			titleWarning.style.color=strongest.color;
			titleWarning.title=timelineHeaderWarnings .map(item => item.message) .join('\n');
		};
		const events=collectEscortResearchTimelineEvents(facts);
		const photos=events.filter(event => event.category === 'photo');
		const profiles=events.filter(event => event.category === 'profile');
		const currentAds=events.filter(event => event.label === 'Włączenie bieżącego anonsu Escort.club' ||
			event.label === 'Publikacja bieżącego anonsu Escort.club' );
		const escortiActiveAds=events.filter(event => event.source === 'escorti');
		const escortPhoneActiveAds=events.filter(event => event.source === 'escort-phone' );
		const garsoReviews=events.filter(event => event.category === 'garso');
		const locations=events.filter(event => event.category === 'location');
		const oldestPhoto=photos[0] || null;
		const newestPhoto=photos.at(-1) || null;
		const oldestProfile=profiles[0] || null;
		const newestProfile=profiles.at(-1) || null;
		const currentAd=currentAds[0] || null;
		const keyDates=[ {
				label:'Najstarsze zdjęcie', event:oldestPhoto, color:ESCORT_RESEARCH_TIMELINE_CATEGORIES.photo.color
			}, {
				label:'Najstarszy profil', event:oldestProfile, color:ESCORT_RESEARCH_TIMELINE_CATEGORIES.profile.color
			}, {
				label:'Ten anons', event:currentAd, color:ESCORT_RESEARCH_TIMELINE_CATEGORIES.ad.color
			} ];
		const keyGrid=makeElement('div','vm-summary-key-dates');
		for (const item of keyDates) {
			const card=makeElement('div','vm-summary-key-date-card');
			card.style.border= `1px solid ${item.event ? item.color:'rgba(255,255,255,.14)'}`;
			const label=makeElement('div','vm-summary-key-date-label',item.label);
			label.style.color=item.event
				? getEscortTimelineReadableTextColor(item.color)
				:'var(--vm-ui-panel-muted, #a997ae)';
			const date=makeElement('div','vm-summary-key-date-value');
			date.textContent=item.event
				? formatServerMediaDate(item.event.date)
				:(facts?.escortiLoading || facts?.photoDatesLoading ? '…':'brak');
			const age=makeElement('div','vm-summary-key-date-age',item.event ? formatEscortResearchTimelineAge(item.event.date):'');
			card.append(label,date,age);
			makeEscortTimelineElementClickable( card, item.event?.url, `Otwórz źródło: ${item.label}` );
			keyGrid.appendChild(card);
		}
		box.appendChild(keyGrid);
		const threeYearsAgo=new Date();
		threeYearsAgo.setFullYear(threeYearsAgo.getFullYear() - 3);
		if (newestPhoto && newestPhoto.date.getTime() < threeYearsAgo.getTime()) {
			addTimelineHeaderWarning( 'Wszystkie zdjęcia mają daty plików starsze niż 3 lata.', '#ff8b8b', 2 );
			const oldPhotosWarning=makeElement('div','vm-summary-old-photos-warning',`⚠ Wszystkie zdjęcia mają daty plików starsze niż 3 lata. ` +
				`Najnowsze: ${formatServerMediaDate(newestPhoto.date)} ` +
				`(${formatEscortResearchTimelineAge(newestPhoto.date)}).`);
			makeEscortTimelineElementClickable( oldPhotosWarning, newestPhoto.url, 'Otwórz najnowsze zdjęcie' );
			box.appendChild(oldPhotosWarning);
		}
		if (oldestPhoto && oldestProfile) {
			const differenceDays= (oldestProfile.date.getTime() - oldestPhoto.date.getTime()) / 86400000;
			const comparison=makeElement('div');
			const mismatch=differenceDays > 30;
			comparison.className= `vm-summary-date-comparison ${mismatch ? '-mismatch':'-ok'}`;
			if (mismatch) {
				addTimelineHeaderWarning( 'Data najstarszego zdjęcia jest wcześniejsza niż założenie najstarszego profilu Escorti.',
					'#ffb08e', 1 );
			}
			comparison.textContent=differenceDays > 0
				? `${mismatch ? '⚠ ':''}Najstarsze zdjęcie ma datę pliku o ${formatResearchDurationDays(differenceDays)} wcześniejszą niż założenie najstarszego profilu Escorti.`
				:'Daty plików zdjęć nie są wcześniejsze niż data założenia najstarszego profilu Escorti.';
			makeEscortTimelineElementClickable( comparison, oldestPhoto.url, 'Otwórz najstarsze zdjęcie użyte w porównaniu' );
			box.appendChild(comparison);
		}
		const timelineItems=[];
		const timelineSeen=new Set();
		const addTimelineItem=(event,label,detail='') => {
			if (!event) return;
			const key=`${label}|${event.date.toISOString().slice(0,10)}`;
			if (timelineSeen.has(key)) return;
			timelineSeen.add(key);
			timelineItems.push({...event,label,detail});
		};
		if (oldestPhoto) {
			addTimelineItem( oldestPhoto, photos.length === 1 ? 'Zdjęcie – data pliku':'Najstarsze zdjęcie – data pliku',
				`${photos.length} ${photos.length === 1 ? 'zdjęcie z datą':'zdjęć z datami'}` );
		}
		if (newestPhoto && newestPhoto.date.getTime() !== oldestPhoto?.date.getTime()) {
			addTimelineItem(newestPhoto,'Najnowsze zdjęcie – data pliku');
		}
		if (oldestProfile) {
			addTimelineItem( oldestProfile,
				profiles.length > 1 ? 'Założenie najstarszego profilu Escorti':'Założenie profilu Escorti',
				profiles.length > 1 ? `${profiles.length} znalezione profile`:'' );
		}
		if (newestProfile && newestProfile.date.getTime() !== oldestProfile?.date.getTime()) {
			addTimelineItem(newestProfile,'Założenie najnowszego profilu Escorti');
		}
		addTimelineItem( currentAd, isEscortCachedArchiveRuntime()
				? 'Publikacja bieżącego anonsu Escort.club'
				:'Włączenie bieżącego anonsu Escort.club' );
		addTimelineItem( escortiActiveAds[0], 'Najstarszy aktywny anons z Escorti.pl' );
		addTimelineItem( escortPhoneActiveAds[0], 'Najstarszy aktywny anons Escort.club z tym samym nr tel.' );
		addTimelineItem(garsoReviews[0],'Pierwsza recenzja Garso');
		if (garsoReviews.at(-1)?.date.getTime() !== garsoReviews[0]?.date.getTime()) {
			addTimelineItem(garsoReviews.at(-1),'Ostatnia recenzja Garso');
		}
		addTimelineItem(locations[0],'Pierwsza zapisana lokalizacja Escorti');
		if (locations.at(-1)?.date.getTime() !== locations[0]?.date.getTime()) {
			addTimelineItem(locations.at(-1),'Ostatnia zapisana lokalizacja Escorti');
		}
		timelineItems.sort((a,b) => a.date.getTime() - b.date.getTime());
		const timelineDays=[];
		const timelineDaysByDate=new Map();
		for (const item of timelineItems) {
			const dayKey=[ item.date.getFullYear(), String(item.date.getMonth() + 1).padStart(2,'0'),
				String(item.date.getDate()).padStart(2,'0') ].join('-');
			let day=timelineDaysByDate.get(dayKey);
			if (!day) {
				day={date:item.date,items:[]};
				timelineDaysByDate.set(dayKey,day);
				timelineDays.push(day);
			}
			day.items.push(item);
		}
		if (timelineDays.length) {
			const timeline=makeElement('div','vm-summary-timeline');
			const line=makeElement('div','vm-summary-timeline-line');
			timeline.appendChild(line);
			for (const day of timelineDays) {
				const row=makeElement('div','vm-summary-timeline-row');
				const markerColors=[...new Set(day.items.map(item =>
					ESCORT_RESEARCH_TIMELINE_CATEGORIES[item.category]?.color || '#fff' ))];
				const markerBackground=markerColors.length === 1
					? markerColors[0]
					:`conic-gradient(${markerColors.map((color,index) => {
						const start=Math.round(index / markerColors.length * 100);
						const end=Math.round((index + 1) / markerColors.length * 100);
						return `${color} ${start}% ${end}%`;
					}).join(', ')})`;
				const marker=makeElement('span','vm-summary-timeline-marker');
				marker.style.background=markerBackground;
				const dateLine=makeElement('div');
				const date=makeElement('strong','vm-summary-timeline-date',formatServerMediaDate(day.date));
				const age=makeElement('span','vm-summary-timeline-age',` • ${formatEscortResearchTimelineAge(day.date)}`);
				dateLine.append(date,age);
				const events=makeElement('div','vm-summary-timeline-events');
				for (const item of day.items) {
					const event=makeElement('div','vm-summary-timeline-event');
					const label=makeElement('div','vm-summary-timeline-label',`• ${item.label}`);
					const categoryColor= ESCORT_RESEARCH_TIMELINE_CATEGORIES[item.category]?.color || '#fff';
					label.style.color= getEscortTimelineReadableTextColor(categoryColor);
					event.appendChild(label);
					if (item.detail) {
						const detail=makeElement('div','vm-summary-timeline-detail',item.detail);
						event.appendChild(detail);
					}
					makeEscortTimelineElementClickable( event, item.url, `Otwórz źródło: ${item.label}` );
					events.appendChild(event);
				}
				row.append(marker,dateLine,events);
				timeline.appendChild(row);
			}
			box.appendChild(timeline);
		}
		const note=makeElement('div','vm-summary-timeline-note');
		const loadingParts=[];
		if (facts?.escortiLoading) loadingParts.push('Escorti i aktywne anonse');
		if (facts?.escortPhoneLoading) {
			loadingParts.push('Escort.club po nr tel.');
		}
		if (facts?.photoDatesLoading) loadingParts.push('zdjęcia');
		note.textContent= (loadingParts.length ? `Trwa uzupełnianie: ${loadingParts.join(', ')}. `:'') +
			'Daty zdjęć pochodzą z nagłówka Last-Modified. Nie są pewną datą wykonania zdjęcia, ale pokazują, od kiedy dany plik istniał na serwerze.';
		box.appendChild(note);
		makeEscortSummarySectionCollapsible( box, title, [...box.children].filter(child => child !== title &&
				!child.matches('.vm-summary-key-dates, .vm-summary-old-photos-warning, .vm-summary-date-comparison')), 'timeline',
			true );
		return box;
	}
	const ESCORT_SUMMARY_SIDE_PANEL_ID='vm-escort-summary-side-panel';
	const ESCORT_SUMMARY_SIDE_PANEL_STYLE_ID='vm-escort-summary-side-panel-style';
	const ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS='vm-escort-summary-side-panel-open';
	const ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS='vm-escort-summary-side-panel-resizing';
	const ESCORT_SUMMARY_SIDE_PANEL_WIDTH_KEY='vm_escort_summary_side_panel_width';
	const ESCORT_SUMMARY_SIDE_PANEL_PINNED_KEY='vm_escort_summary_side_panel_pinned';
	const ESCORT_SUMMARY_SIDE_PANEL_PREVIOUS_DEFAULT_WIDTH=400;
	const ESCORT_SUMMARY_SIDE_PANEL_DEFAULT_WIDTH=340;
	const ESCORT_SUMMARY_SIDE_PANEL_MIN_WIDTH=300;
	function getEscortSummarySidePanelMaxWidth( view=window ) {
		const viewportWidth= Number(view?.innerWidth) || window.innerWidth;
		return Math.max( ESCORT_SUMMARY_SIDE_PANEL_MIN_WIDTH, Math.min( 800, viewportWidth - 260 ) );
	}
	function normalizeEscortSummarySidePanelWidth( value, view=window ) {
		const width=Number(value);
		const fallback= ESCORT_SUMMARY_SIDE_PANEL_DEFAULT_WIDTH;
		return Math.round( Math.min( getEscortSummarySidePanelMaxWidth( view ), Math.max( ESCORT_SUMMARY_SIDE_PANEL_MIN_WIDTH,
					Number.isFinite(width)
						? width
						:fallback ) ) );
	}
	const escortSummarySidePanelBindings= new WeakMap();
	function bindEscortSummarySidePanelControls( doc, panel, toggle, {
			onOpen=null
		}={} ) {
		if (!doc || !panel || !toggle) {
			return null;
		}
		const previous= escortSummarySidePanelBindings.get( panel );
		previous?.dispose?.();
		const cleanup=[];
		const listen=( target, type, handler, options=false ) => {
			if (!target) return;
			target.addEventListener( type, handler, options );
			cleanup.push(() => {
				try {
					target.removeEventListener( type, handler, options );
				} catch {}
			});
		};
		const root=doc.documentElement;
		const body=doc.body;
		const view= doc.defaultView || window;
		const pin= panel.querySelector( '.vm-summary-panel-pin' );
		const pinIcon= panel.querySelector( '.vm-summary-panel-pin-icon' );
		const resizeHandle= panel.querySelector( '.vm-escort-summary-side-resize-handle' );
		const storedPanelWidth=Number( GM_getValue( ESCORT_SUMMARY_SIDE_PANEL_WIDTH_KEY,
				ESCORT_SUMMARY_SIDE_PANEL_DEFAULT_WIDTH ) );
		const initialPanelWidth= storedPanelWidth === ESCORT_SUMMARY_SIDE_PANEL_PREVIOUS_DEFAULT_WIDTH
				? ESCORT_SUMMARY_SIDE_PANEL_DEFAULT_WIDTH
				:storedPanelWidth;
		let panelWidth= normalizeEscortSummarySidePanelWidth( initialPanelWidth, view );
		if ( storedPanelWidth === ESCORT_SUMMARY_SIDE_PANEL_PREVIOUS_DEFAULT_WIDTH ) {
			writeHelperStorageValue( ESCORT_SUMMARY_SIDE_PANEL_WIDTH_KEY, panelWidth );
		}
		let pinned= !!GM_getValue( ESCORT_SUMMARY_SIDE_PANEL_PINNED_KEY, true );
		let opened=pinned;
		let resizing=false;
		const applyWidth=value => {
			panelWidth= normalizeEscortSummarySidePanelWidth( value, view );
			root.style.setProperty( '--vm-escort-summary-panel-width', `${panelWidth}px` );
			return panelWidth;
		};
		const renderPin=() => {
			if (!pin) return;
			pin.setAttribute( 'aria-pressed', String(pinned) );
			pin.dataset.vmPinned= pinned ? '1':'0';
			setControlTitle(pin,pinned
				? 'Panel przypięty - będzie otwierany automatycznie'
				:'Przypnij panel, aby otwierał się automatycznie');
			if (pinIcon) {
				pinIcon.style.transform= pinned
						? 'rotate(0deg)'
						:'rotate(-45deg)';
			}
		};
		const setOpen=value => {
			opened=!!value;
			body.classList.toggle( ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS, opened );
			toggle.textContent= opened
					? 'Zwiń ›'
					:'‹ Podsumowanie';
			toggle.title= opened
					? 'Zwiń podsumowanie'
					:'Pokaż podsumowanie';
			if ( opened && typeof onOpen === 'function' ) {
				onOpen();
			}
		};
		listen( toggle, 'click', event => {
				event.preventDefault();
				setOpen(!opened);
			} );
		listen( pin, 'click', event => {
				event.preventDefault();
				pinned=!pinned;
				writeHelperStorageValue( ESCORT_SUMMARY_SIDE_PANEL_PINNED_KEY, pinned );
				renderPin();
				if (pinned) {
					setOpen(true);
				}
			} );
		listen( resizeHandle, 'pointerdown', event => {
				if (event.button !== 0) return;
				event.preventDefault();
				resizing=true;
				body.classList.add( ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS );
				try {
					resizeHandle.setPointerCapture( event.pointerId );
				} catch {}
			} );
		listen( resizeHandle, 'pointermove', event => {
				if (!resizing) return;
				applyWidth( ( Number(view.innerWidth) || window.innerWidth ) - event.clientX );
			} );
		const finishResize=event => {
			if (!resizing) return;
			resizing=false;
			body.classList.remove( ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS );
			try {
				if ( event?.pointerId != null && resizeHandle ?.hasPointerCapture?.( event.pointerId ) ) {
					resizeHandle .releasePointerCapture( event.pointerId );
				}
			} catch {}
			writeHelperStorageValue( ESCORT_SUMMARY_SIDE_PANEL_WIDTH_KEY, panelWidth );
		};
		listen( resizeHandle, 'pointerup', finishResize );
		listen( resizeHandle, 'pointercancel', finishResize );
		listen( resizeHandle, 'dblclick', event => {
				event.preventDefault();
				applyWidth( ESCORT_SUMMARY_SIDE_PANEL_DEFAULT_WIDTH );
				writeHelperStorageValue( ESCORT_SUMMARY_SIDE_PANEL_WIDTH_KEY, panelWidth );
			} );
		applyWidth(panelWidth);
		renderPin();
		setOpen(pinned);
		const binding={
			dispose() {
				while (cleanup.length) {
					cleanup.pop()?.();
				}
				body.classList.remove( ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS );
			}, setOpen, applyWidth, isOpen:() => opened
		};
		escortSummarySidePanelBindings.set( panel, binding );
		return binding;
	}
	function ensureEscortSummarySidePanelStyle() {
		if (document.getElementById(ESCORT_SUMMARY_SIDE_PANEL_STYLE_ID)) return;
		const sidePanel=`#${ESCORT_SUMMARY_SIDE_PANEL_ID}`;
		const accent=`var(--vm-ui-accent, ${getEscortPagePinkColor()})`;
		const panelText=`var(--vm-ui-panel-text, #fff)`;
		const panelMuted=`var(--vm-ui-panel-muted, #cbb8ce)`;
		const panelBackground=`var(--vm-ui-panel-bg, #2a0833)`;
		const danger=`var(--vm-ui-danger, #ff7777)`;
		const success=`var(--vm-ui-success, #62cf7b)`;
		const mixAccent=`var(--vm-ui-accent, #f54da3)`;
		const mix=(color,percent) => `color-mix(in srgb, ${color} ${percent}%, transparent)`;
		appendHelperStyle(ESCORT_SUMMARY_SIDE_PANEL_STYLE_ID,` :root { --vm-escort-summary-panel-width: ${ESCORT_SUMMARY_SIDE_PANEL_DEFAULT_WIDTH}px; --vm-escort-summary-panel-top-offset: 0px; --vm-escort-summary-panel-effective-width: min( var(--vm-escort-summary-panel-width), 92vw ); }
body { transition: width .22s ease; }
body.${ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS} { width: calc(100% - var(--vm-escort-summary-panel-effective-width)) !important; max-width: calc(100% - var(--vm-escort-summary-panel-effective-width)) !important; }
body.${ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS}, body.${ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS} ${sidePanel}, body.${ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS} #vm-escort-summary-side-toggle { transition: none !important; user-select: none !important; }
${sidePanel} { position: fixed; z-index: 2147483000; top: var(--vm-escort-summary-panel-top-offset); right: 0; width: var(--vm-escort-summary-panel-effective-width); height: calc(100vh - var(--vm-escort-summary-panel-top-offset)); height: calc(100dvh - var(--vm-escort-summary-panel-top-offset)); box-sizing: border-box; display: flex; flex-direction: column; transform: translateX(100%); transition: transform .22s ease; background: ${panelBackground}; color: ${panelText}; border-left: 2px solid ${accent}; box-shadow: -10px 0 30px rgba(0,0,0,.35); font-family: var(--vm-ui-font-family, Arial, sans-serif); }
body.${ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS} ${sidePanel} { transform: translateX(0); }
#vm-escort-summary-side-toggle { position: fixed; z-index: 2147483001; top: 50%; right: 0; transform: translateY(-50%); padding: 11px 7px; border: 1px solid ${accent}; border-right: 0; border-radius: 8px 0 0 8px; background: ${accent}; color: var(--vm-ui-accent-contrast,#fff); font-size: 12px; font-weight: 800; line-height: 1.15; cursor: pointer; box-shadow: -3px 2px 12px rgba(0,0,0,.28); transition: right .22s ease; writing-mode: vertical-rl; }
body.${ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS} #vm-escort-summary-side-toggle { right: var(--vm-escort-summary-panel-effective-width); }
body.${ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS} #${GLOBAL_QUICK_SEARCH_ID} { right: calc(var(--vm-escort-summary-panel-effective-width) + 16px) !important; }
.vm-escort-summary-side-resize-handle { position: absolute; z-index: 3; top: 0; bottom: 0; left: -6px; width: 12px; cursor: ew-resize; touch-action: none; }
.vm-escort-summary-side-resize-handle::after { content: ''; position: absolute; top: 0; bottom: 0; left: 5px; width: 2px; background: transparent; transition: background .15s ease; }
.vm-escort-summary-side-resize-handle:hover::after, body.${ESCORT_SUMMARY_SIDE_PANEL_RESIZING_CLASS} .vm-escort-summary-side-resize-handle::after { background: ${accent}; }
${sidePanel} .vm-summary-panel-header { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 10px 11px; border-bottom: 1px solid ${accent}; background: color-mix(in srgb, ${panelBackground} 84%, #000); }
${sidePanel} .vm-summary-panel-title { flex: 1 1 auto; min-width: 0; color: ${accent}; font-size: 16px; font-weight: 800; }
${sidePanel} .vm-summary-panel-pin { width: 30px; height: 30px; padding: 0; border: 1px solid ${mix(panelText,25)}; border-radius: 50%; background: ${mix(panelText,7)}; color: ${panelText}; cursor: pointer; transition: background .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease; }
${sidePanel} .vm-summary-panel-pin[aria-pressed="true"] { border-color: ${panelText}; background: ${panelText}; color: ${panelBackground}; box-shadow: 0 0 0 2px color-mix( in srgb, ${panelText} 22%, transparent ); }
${sidePanel} .vm-summary-panel-pin-icon { display: block; width: 17px; height: 17px; margin: auto; fill: currentColor; pointer-events: none; transition: transform .16s ease; }
.vm-escort-summary-side-content { min-height: 0; flex: 1 1 auto; overflow-y: auto; padding: 10px; box-sizing: border-box; scrollbar-width: thin; }
.vm-escort-summary-side-card { margin-bottom: 10px; border: 2px solid ${mix(mixAccent,58)}; border-radius: 7px; background: var(--vm-ui-panel-bg-alt, ${mix(panelText,3.5)}); box-shadow: 0 3px 12px rgba(0,0,0,.2); overflow: hidden; }
${sidePanel} .vm-escort-summary-integrated-section { border-width: 2px !important; border-color: ${mix(mixAccent,58)} !important; box-shadow: 0 3px 12px rgba(0,0,0,.2); }
${sidePanel} .vm-escort-summary-side-card > .vm-escort-summary-collapsible-header { border: 0 !important; border-bottom: 1px solid ${mix(mixAccent,48)} !important; border-radius: 0 !important; }
${sidePanel} .vm-escort-summary-side-card.vm-escort-summary-section-collapsed > .vm-escort-summary-collapsible-header { border-bottom: 0 !important; }
.vm-escort-summary-side-card-head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 2px 8px; padding: 8px 10px 7px; border-bottom: 1px solid var(--vm-ui-panel-border, ${mix(panelText,13)}); background: var(--vm-ui-panel-bg-alt, ${mix(panelText,3.5)}); }
${sidePanel} [hidden], #vm-escort-research-panel [hidden] { display: none !important; }
.vm-summary-section-preview { padding: 8px 10px; font-size: 12px; line-height: 1.4; color: ${panelText}; overflow-wrap: anywhere; }
.vm-summary-section-preview.-warning { color: var(--vm-ui-warning, #ffcf5a); }
.vm-escort-summary-side-card-body { padding: 9px 10px 10px; font-size: 12px; line-height: 1.38; }
${sidePanel} .vm-escort-summary-integrated-section { box-sizing: border-box; margin: 0 0 10px; padding: 9px; border: 1px solid var(--vm-ui-panel-border, ${mix(panelText,18)}); border-radius: 6px; background: var(--vm-ui-panel-bg-alt, ${mix(panelText,2.5)}); color: ${panelText}; font-size: 11px; line-height: 1.3; }
${sidePanel} .vm-summary-timeline-title { margin-bottom: 8px; font-size: 12px; font-weight: 800; }
${sidePanel} .vm-summary-timeline-title-warning { margin-left: 5px; font-size: 13px; font-weight: 900; line-height: 1; }
${sidePanel} .vm-summary-key-dates { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 5px; margin-bottom: 8px; }
${sidePanel} .vm-summary-key-date-card { min-width: 0; padding: 6px 5px; border-radius: 5px; background: ${panelBackground}; text-align: center; }
${sidePanel} .vm-summary-key-date-label { min-height: 23px; font-size: 9px; font-weight: 700; line-height: 1.2; }
${sidePanel} .vm-summary-key-date-value { margin-top: 2px; font-size: 11px; font-weight: 800; white-space: nowrap; }
${sidePanel} .vm-summary-key-date-age { margin-top: 2px; color: ${panelMuted}; font-size: 8px; white-space: nowrap; }
${sidePanel} .vm-summary-old-photos-warning, ${sidePanel} .vm-summary-date-comparison { padding: 7px 8px; border-radius: 5px; font-size: 10px; line-height: 1.3; }
${sidePanel} .vm-summary-old-photos-warning { margin-bottom: 8px; border: 1px solid ${mix(danger,78)}; background: ${mix(danger,14)}; color: ${danger}; font-weight: 800; }
${sidePanel} .vm-summary-date-comparison { margin-bottom: 9px; }
${sidePanel} .vm-summary-date-comparison.-mismatch { border: 1px solid color-mix(in srgb, var(--vm-ui-warning, #ffcf5a) 80%, transparent); background: color-mix(in srgb, var(--vm-ui-warning, #ffcf5a) 13%, transparent); color: var(--vm-ui-warning, #ffcf5a); font-weight: 700; }
${sidePanel} .vm-summary-date-comparison.-ok { border: 1px solid ${mix(success,55)}; background: ${mix(success,9)}; color: ${success}; font-weight: 600; }
${sidePanel} .vm-summary-source-link { cursor: pointer; transition: background-color .15s ease, filter .15s ease; }
${sidePanel} .vm-summary-timeline { position: relative; margin: 2px 0 0 5px; padding-left: 17px; }
${sidePanel} .vm-summary-timeline-line { position: absolute; top: 7px; bottom: 9px; left: 5px; width: 2px; background: ${mix(panelText,20)}; }
${sidePanel} .vm-summary-timeline-row { position: relative; padding-bottom: 10px; }
${sidePanel} .vm-summary-timeline-marker { position: absolute; top: 4px; left: -16px; width: 9px; height: 9px; box-sizing: border-box; border: 2px solid ${panelBackground}; border-radius: 50%; }
${sidePanel} .vm-summary-timeline-date { font-size: 10px; }
${sidePanel} .vm-summary-timeline-age { color: var(--vm-ui-panel-muted, #bca9c1); font-size: 9px; }
${sidePanel} .vm-summary-timeline-events { display: grid; gap: 2px; margin-top: 2px; }
${sidePanel} .vm-summary-timeline-event { min-width: 0; padding: 1px 3px; border-radius: 3px; }
${sidePanel} .vm-summary-timeline-label { font-size: 10px; font-weight: 700; }
${sidePanel} .vm-summary-timeline-detail { margin: 1px 0 0 9px; color: var(--vm-ui-panel-muted, #a997ae); font-size: 9px; }
${sidePanel} .vm-summary-timeline-note { margin-top: 3px; padding-top: 6px; border-top: 1px solid ${mix(panelText,12)}; color: var(--vm-ui-panel-muted, #a997ae); font-size: 9px; line-height: 1.3; }
${sidePanel} .vm-summary-other-cities-framed { box-sizing: border-box; margin-top: 0; padding: 8px 9px; border: 1px solid ${mix(panelText,14)}; border-radius: 7px; background: ${mix(panelText,2.5)}; color: ${panelText}; }
${sidePanel} .vm-escort-summary-other-label { margin-bottom: 6px; color: ${panelText}; font-size: 11px; font-weight: 700; line-height: 1.25; }
${sidePanel} .vm-escort-summary-other-city { display: inline-flex; align-items: center; padding: 2px 6px; border: 1px solid ${mix(panelText,18)}; border-radius: 999px; background: ${mix(panelText,4)}; color: ${panelText}; font-size: 10px; font-weight: 600; line-height: 1.2; }
${sidePanel} .vm-garso-side-card > .vm-escort-summary-collapsible-header { padding-right: 9px !important; }
${sidePanel} .vm-garso-card-status-full { flex: 0 0 100%; width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; margin-top: 2px; padding-right: 0; white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.25; }
.vm-garso-card-title { display: flex; flex: 0 0 100%; align-items: center; gap: 6px; min-width: 0; box-sizing: border-box; padding-right: 94px; color: ${accent}; font-size: 13px; font-weight: 800; }
.vm-garso-card-title-warning { flex: 0 0 auto; color: ${danger}; font-size: 14px; font-weight: 900; line-height: 1; }
.vm-garso-card-meta { margin-top: 3px; color: ${panelMuted}; font-size: 10px; line-height: 1.25; }
${sidePanel} .vm-escort-summary-collapsible-header, #vm-escort-research-panel .vm-escort-summary-collapsible-header { position: relative; min-height: 34px; box-sizing: border-box; padding: 8px 82px 8px 9px !important; border: 1px solid ${mix(mixAccent,36)}; border-radius: 6px; background: ${mix(mixAccent,9)} !important; color: ${accent}; cursor: pointer; user-select: none; transition: background .15s ease, border-color .15s ease; }
${sidePanel} .vm-escort-summary-collapsible-header:hover, #vm-escort-research-panel .vm-escort-summary-collapsible-header:hover { border-color: ${mix(mixAccent,72)}; background: ${mix(mixAccent,17)} !important; }
${sidePanel} .vm-escort-summary-collapsible-header:focus-visible, #vm-escort-research-panel .vm-escort-summary-collapsible-header:focus-visible { outline: 2px solid ${accent}; outline-offset: 2px; }
${sidePanel} .vm-escort-summary-collapse-hint, #vm-escort-research-panel .vm-escort-summary-collapse-hint { position: absolute; top: 50%; right: 7px; transform: translateY(-50%); display: inline-flex; align-items: center; gap: 4px; padding: 3px 6px; border: 1px solid ${mix(mixAccent,55)}; border-radius: 999px; background: color-mix(in srgb, ${panelBackground} 80%, #000); color: color-mix(in srgb, ${mixAccent} 42%, ${panelText}); font-size: 9px; font-weight: 800; line-height: 1; pointer-events: none; }
${sidePanel} .vm-escort-summary-section-collapsed > .vm-escort-summary-collapsible-header, #vm-escort-research-panel .vm-escort-summary-section-collapsed > .vm-escort-summary-collapsible-header { background: ${mix(mixAccent,14)} !important; }
${sidePanel} .vm-escort-summary-integrated-section { overflow: hidden; }
${sidePanel} .vm-escort-summary-integrated-section > .vm-escort-summary-collapsible-header { margin: -9px -9px 8px !important; border: 0 !important; border-bottom: 1px solid ${mix(mixAccent,36)} !important; border-radius: 0 !important; }
${sidePanel} .vm-escort-summary-integrated-section.vm-escort-summary-section-collapsed > .vm-escort-summary-collapsible-header { margin: -9px -9px 8px !important; border-bottom: 0 !important; }
${sidePanel} .vm-escort-summary-inconsistencies { border: 2px solid ${mix(mixAccent,58)}; border-radius: 7px; background: var(--vm-ui-panel-bg-alt, ${mix(panelText,3.5)}); box-shadow: 0 3px 12px rgba(0,0,0,.2); overflow: hidden; }
${sidePanel} .vm-escort-summary-inconsistencies > .vm-escort-summary-collapsible-header { margin: 0 !important; border: 0 !important; border-bottom: 1px solid ${mix(mixAccent,48)} !important; border-radius: 0 !important; color: ${accent} !important; }
${sidePanel} .vm-escort-summary-inconsistencies.vm-escort-summary-section-collapsed > .vm-escort-summary-collapsible-header { border-bottom: 0 !important; }
${sidePanel} .vm-escort-summary-inconsistencies > .vm-escort-summary-inconsistencies-content { border: 0 !important; border-radius: 0 !important; background: transparent !important; box-shadow: none !important; }
#vm-escort-research-panel { width: 100%; box-sizing: border-box; margin-top: 12px; overflow: hidden; border: 1px solid ${accent}; border-radius: 8px; background: var(--vm-ui-panel-bg-alt, ${mix(panelText,2.5)}); color: ${panelText}; }
#vm-escort-research-panel > .vm-research-panel-main-header { display: flex; align-items: center; gap: 8px; padding: 10px 11px 8px; color: ${accent}; font-size: 14px; font-weight: 700; line-height: 1.2; }
#vm-escort-research-panel > .vm-research-panel-row, #vm-phone-reputation-links .vm-research-panel-row { display: flex; align-items: center; width: 100%; min-height: 42px; margin: 0; padding: 9px 11px; border: 0; border-bottom: 1px solid var(--vm-ui-panel-border, rgba(255,255,255,.1)); border-radius: 0; background: transparent; color: ${panelText}; cursor: pointer; font-size: 13px; font-weight: 600; box-sizing: border-box; transition: background-color .15s ease; }
#vm-escort-research-panel > .vm-research-panel-row:hover, #vm-phone-reputation-links .vm-research-panel-row:hover { background: var(--vm-ui-panel-bg-alt, rgba(255,255,255,.055)); }
#vm-escort-research-panel .vm-research-panel-main-title { min-width: 0; flex: 1 1 auto; }
#vm-escort-research-panel .vm-research-panel-refresh { flex: 0 0 auto; width: 28px; height: 28px; padding: 0; border: 1px solid ${accent}; border-radius: 50%; background: transparent; color: ${accent}; font-size: 20px; font-weight: 700; line-height: 24px; cursor: pointer; }
${sidePanel} .vm-escort-summary-inconsistencies-title { display: flex; align-items: center; gap: 0; margin-bottom: 6px; font-weight: 800; }
${sidePanel} .vm-escort-summary-inconsistencies { margin: 0 0 10px; color: ${panelText}; font-size: 12px; font-weight: 400; line-height: 1.35; text-align: left; }
${sidePanel} .vm-inconsistencies-status-badge { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; font-weight: 800; line-height: 1; }
${sidePanel} .vm-escort-summary-status-alert .vm-inconsistencies-status-badge { display: inline; margin-left: 5px; padding: 0; border: 0; border-radius: 0; background: transparent; color: ${danger}; box-shadow: none; font-size: 13px; font-weight: 900; }
${sidePanel} .vm-escort-summary-status-ok .vm-inconsistencies-status-badge { margin-left: 6px; padding: 3px 7px; border: 1px solid ${mix(success,72)}; border-radius: 999px; background: ${mix(success,12)}; color: ${success}; box-shadow: none; font-size: 10px; }
${sidePanel} .vm-inconsistencies-empty { padding: 8px 9px; border: 1px solid ${mix(success,42)}; border-radius: 6px; background: ${mix(success,8)}; color: ${success}; font-size: 11px; font-weight: 800; }
${sidePanel} .vm-inconsistencies-source-card { overflow: hidden; border: 1px solid ${mix(danger,35)}; border-radius: 7px; background: ${mix(danger,4.5)}; }
${sidePanel} .vm-inconsistencies-source-head { display: flex; align-items: center; gap: 7px; padding: 7px 8px; border-bottom: 1px solid ${mix(danger,22)}; background: ${mix(danger,6)}; }
${sidePanel} .vm-inconsistencies-source-marker { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: 1px solid ${mix(danger,55)}; border-radius: 50%; background: ${mix(danger,14)}; color: ${danger}; font-size: 13px; font-weight: 900; line-height: 1; }
${sidePanel} .vm-inconsistencies-source-title { display: block; color: ${danger}; font-weight: 900; line-height: 1.2; }
${sidePanel} .vm-inconsistencies-source-subtitle { display: block; margin-top: 2px; color: ${panelMuted}; font-size: 9px; font-weight: 600; line-height: 1.2; }
${sidePanel} .vm-inconsistencies-source-count { margin-left: auto; padding: 2px 6px; border: 1px solid ${mix(danger,45)}; border-radius: 999px; color: ${danger}; font-size: 9px; font-weight: 800; white-space: nowrap; }
${sidePanel} .vm-inconsistencies-warning-list { display: grid; gap: 6px; padding: 7px; }
${sidePanel} .vm-inconsistencies-warning-row { display: grid; grid-template-columns: 24px minmax(0,1fr); align-items: start; gap: 7px; padding: 6px 7px; border-left: 3px solid ${mix(danger,78)}; border-radius: 5px; background: var(--vm-ui-panel-bg-alt, ${mix(panelText,3.5)}); }
${sidePanel} .vm-inconsistencies-warning-icon { display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 22px; border-radius: 5px; background: ${mix(danger,13)}; color: ${danger}; font-size: 10px; font-weight: 900; line-height: 1; }
${sidePanel} .vm-inconsistencies-warning-label { display: block; color: ${danger}; font-weight: 900; line-height: 1.2; }
${sidePanel} .vm-inconsistencies-warning-detail { display: block; margin-top: 2px; color: ${panelText}; font-size: 10px; font-weight: 400; line-height: 1.25; }
${sidePanel} .vm-garso-main-head { padding-right: 108px !important; }
${sidePanel} .vm-garso-main-title { box-sizing: border-box; overflow: hidden; padding-right: 0; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
${sidePanel} .vm-garso-main-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#vm-escort-research-panel .vm-research-panel-footer { padding: 8px 10px 10px; }
${sidePanel} .vm-escort-summary-inconsistencies-content { display: grid; gap: 7px; padding: 8px 9px; border: 0; border-radius: 0; background: transparent; color: ${panelText}; }
${sidePanel} .vm-garso-download-button { position: absolute; z-index: 1; top: 16px; right: 72px; transform: translateY(-50%); display: inline-flex; align-items: center; justify-content: center; width: 21px; height: 21px; margin: 0; padding: 0; border: 1px solid ${accent}; border-radius: 50%; background: color-mix(in srgb, ${panelBackground} 80%, #000); color: color-mix(in srgb, ${mixAccent} 42%, ${panelText}); font-size: 14px; font-weight: 800; line-height: 1; cursor: pointer; }
#vm-phone-reputation-links { overflow: hidden; margin-bottom: 7px; border: 1px solid var(--vm-ui-panel-border, ${mix(panelText,16)}); border-radius: 7px; background: var(--vm-ui-panel-bg-alt, ${mix(panelText,2.5)}); }
#vm-phone-reputation-links > .vm-phone-reputation-head { display: flex; align-items: center; gap: 7px; min-height: 34px; padding: 8px 82px 8px 9px !important; color: ${accent}; font-size: 11px; font-weight: 800; }
#vm-escort-research-panel .vm-research-section-title { color:var(--vm-ui-accent,#f54da3); font-size:12px; font-weight:800; }
#vm-phone-reputation-links .vm-phone-reputation-title { min-width: 0; flex: 0 1 auto; }
#vm-phone-reputation-links .vm-phone-reputation-badge { flex: 0 0 auto; padding: 2px 6px; border-radius: 999px; font-size: 9px; font-weight: 900; line-height: 1.1; white-space: nowrap; }
#vm-phone-reputation-links .vm-phone-reputation-badge.-negative { border: 1px solid ${mix(danger,70)}; background: ${mix(danger,14)}; color: ${danger}; }
#vm-phone-reputation-links .vm-phone-reputation-badge.-positive { border: 1px solid ${mix(success,70)}; background: ${mix(success,13)}; color: ${success}; }
#vm-phone-reputation-links .vm-phone-reputation-body { overflow: hidden; border-top: 1px solid ${mix(panelText,10)}; }
#vm-phone-reputation-links .vm-phone-reputation-rows > .vm-research-panel-row:last-child { border-bottom: 0 !important; }
.vm-escort-summary-other-city-list { display: flex; flex-wrap: wrap; gap: 4px; }
#vm-escort-research-panel .vm-escort-summary-collapse-hint { padding:3px 6px !important; font:700 9px/1.15 var(--vm-ui-font-family,Lato,Arial,sans-serif) !important; }
#vm-garso-extended-panel { overflow: hidden; border: 1px solid var(--vm-ui-panel-border, ${mix(panelText,16)}); border-radius: 7px; background: var(--vm-ui-panel-bg-alt, ${mix(panelText,2.5)}); }
#vm-garso-extended-panel > .vm-garso-extended-head { display: flex; align-items: center; gap: 7px; color: ${accent}; font-size: 12px; font-weight: 800; }
#vm-garso-extended-panel .vm-garso-extended-status-badge { flex: 0 0 auto; padding: 2px 5px; border: 1px solid ${mix(success,70)}; border-radius: 999px; background: ${mix(success,14)}; color: ${success}; font-size: 10px; line-height: 1; }
#vm-garso-extended-panel .vm-garso-extended-body { display:grid; gap:8px; padding:10px; font:400 12px/1.4 var(--vm-ui-font-family,Lato,Arial,sans-serif); }
#vm-garso-extended-panel .vm-garso-extended-message { margin:0; color:var(--vm-ui-panel-muted,var(--vm-ui-text-muted,#d8c8d9)); text-align:left; overflow-wrap:anywhere; }
#vm-garso-extended-panel .vm-garso-extended-recommendation { display:block; width:calc(100% - 14px); margin:7px 7px 0; padding:7px 9px; box-sizing:border-box; border:1px solid color-mix(in srgb,var(--vm-ui-warning,#e2b84b) 75%,transparent); border-radius:6px; background:color-mix(in srgb,var(--vm-ui-warning,#e2b84b) 9%,var(--vm-ui-panel-bg-alt,transparent)); color:var(--vm-ui-panel-text,var(--vm-ui-text,#f2f2f2)); font:inherit; text-align:left; cursor:pointer; }
#vm-garso-extended-panel .vm-garso-extended-recommendation strong { display:block; color:var(--vm-ui-warning,#e2b84b); font-size:10px; }
#vm-garso-extended-panel .vm-garso-extended-recommendation span { display:block; margin-top:2px; color:var(--vm-ui-panel-muted,var(--vm-ui-text-muted,#d8c8d9)); font-size:9px; line-height:1.3; }
#vm-garso-extended-panel .vm-garso-extended-controls { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }
#vm-garso-extended-panel .vm-garso-extended-progress { display:block; width:100%; height:5px; margin:0; accent-color:var(--vm-ui-accent,#f54da3); }
#vm-garso-extended-panel [hidden] { display:none !important; }
#vm-garso-extended-panel .vm-garso-extended-action { display:flex; align-items:center; justify-content:center; gap:6px; box-sizing:border-box; width:100%; min-width:0; min-height:34px; margin:0; padding:6px 8px; border:1px solid var(--vm-ui-panel-border,var(--vm-ui-border,#806383)); border-radius:5px; background:transparent; color:var(--vm-ui-panel-text,var(--vm-ui-text,#f2f2f2)); font:700 12px/1.25 var(--vm-ui-font-family,Lato,Arial,sans-serif); cursor:pointer; }
#vm-garso-extended-panel .vm-garso-extended-action.-search { border-color:var(--vm-ui-accent,#f54da3); background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 12%,transparent); }
#vm-garso-extended-panel .vm-garso-extended-action.-cancel { border-color:var(--vm-ui-danger,#dc2626); color:var(--vm-ui-danger,#dc2626); }
#vm-garso-extended-panel .vm-garso-extended-action:hover:enabled { background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 20%,transparent); }
#vm-garso-extended-panel .vm-garso-extended-action:focus-visible { outline:2px solid var(--vm-ui-accent,#f54da3); outline-offset:2px; }
#vm-garso-extended-panel .vm-garso-extended-action svg { flex:0 0 14px; width:14px; height:14px; }
#vm-garso-extended-panel .vm-garso-extended-count { padding:1px 5px; border-radius:4px; background:color-mix(in srgb,var(--vm-ui-accent,#f54da3) 15%,transparent); font-size:10px; font-variant-numeric:tabular-nums; }
${sidePanel} .vm-garso-mention-chip:hover, ${sidePanel} .vm-garso-mention-chip:focus-visible { filter: brightness(1.18); outline: 1px solid ${mix(panelText,72)}; outline-offset: 1px; }
.vm-helper-ui .vm-garso-evidence-dialog { width:min(760px,calc(100vw - 32px)); display:flex; flex-direction:column; overflow:hidden; }
.vm-garso-evidence-content { min-height:0; overflow-y:auto; }
.vm-garso-evidence-title { min-width:0; flex:1; }
.vm-garso-evidence-intro { margin:0 0 8px; }
.vm-garso-evidence-topic { margin-top:8px; }
.vm-garso-evidence-topic-title { display:block; padding-bottom:8px; color:var(--vm-ui-accent,#f54da3); font-size:12px; font-weight:700; text-decoration:none; }
.vm-garso-evidence-snippet { padding:8px 0; border-top:1px solid var(--vm-ui-border,#ddd); }
.vm-garso-evidence-meta { margin-bottom:4px; color:var(--vm-ui-text-muted,#666); font-size:var(--vm-ui-small-font-size,11px); }
.vm-garso-evidence-text { color:var(--vm-ui-text,#222); font-size:12px; line-height:1.4; }

.vm-garso-evidence-text mark { padding: 0 2px; border-radius: 2px; background: var(--vm-ui-warning, #ffcf5a); color: #351523; font-weight: 800; }
.vm-garso-cancel-button { position: absolute; right: 7px; bottom: 3px; display: none; align-items: center; margin: 0; padding: 3px 7px; border: 1px solid ${danger}; border-radius: 999px; background: color-mix(in srgb, ${danger} 12%, ${panelBackground}); color: ${danger}; font:700 9px/1 var(--vm-ui-font-family,Lato,Arial,sans-serif); cursor: pointer; }
.vm-garso-cancel-button.-visible { display: inline-flex; }
@media (max-width: 1100px) { body.${ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS} { width: 100% !important; max-width: 100% !important; }
}
`);
	}
	function appendGarsoEvidenceText(container,snippet,matchText) {
		const text=String(snippet || '');
		const match=String(matchText || '');
		const index=match
			? text.toLocaleLowerCase('pl-PL').indexOf( match.toLocaleLowerCase('pl-PL') )
			:-1;
		if (index < 0) {
			container.textContent=text;
			return;
		}
		container.append( document.createTextNode(text.slice(0,index)), Object.assign(makeElement('mark'),{
				textContent:text.slice(index,index + match.length)
			}), document.createTextNode(text.slice(index + match.length)) );
	}
	function openGarsoMentionEvidenceDialog(entry) {
		closeHelperDialog(document.querySelector('.vm-garso-evidence-overlay'));
		ensureEscortSummarySidePanelStyle();
		const overlay=prepareHelperStyleApi( makeElement('div',"vm-garso-evidence-overlay vm-ui-overlay") );
		const dialog=makeElement('section',"vm-garso-evidence-dialog vm-ui-dialog");
		dialog.setAttribute('role','dialog');
		dialog.setAttribute('aria-modal','true');
		const header=makeElement('div',"vm-garso-evidence-header vm-ui-header");
		const title=makeElement('div',"vm-garso-evidence-title vm-ui-title",`Dlaczego zliczono: ${entry?.label || 'wzmiankę'} (${entry?.count || 0})`);
		const close=makeButton("vm-garso-evidence-close vm-ui-button vm-ui-icon",'×',{title:'Zamknij'});
		header.append(title,close);
		const content=makeElement('div','vm-garso-evidence-content');
		const intro=makeElement('p',"vm-garso-evidence-intro vm-ui-help",'Wzmianki dotyczą całych tematów i mogą opisywać różne osoby lub wizyty. Każdy fragment odpowiada jednemu postowi w liczniku. Zgodność zdjęć obejmuje tożsamość, aktualność i wygląd; szczegół wynika z cytatu. Kliknij tytuł, aby otworzyć temat Garso.');
		content.appendChild(intro);
		const evidence=Array.isArray(entry?.evidence) ? entry.evidence:[];
		if (!evidence.length) {
			const empty=makeElement('div',"vm-garso-evidence-intro vm-ui-help",'Brak zapisanych fragmentów. Odśwież analizę Garso, aby utworzyć uzasadnienia.');
			content.appendChild(empty);
		} else {
			const groups=new Map();
			for (const item of evidence) {
				const key=item.url || item.topicTitle || 'temat';
				if (!groups.has(key)) groups.set(key,[]);
				groups.get(key).push(item);
			}
			for (const items of groups.values()) {
				const first=items[0];
				const topic=makeElement('article',"vm-garso-evidence-topic vm-ui-panel");
				const topicTitle=first.url
					? makeElement('a')
					:makeElement('div');
				topicTitle.className='vm-garso-evidence-topic-title';
				topicTitle.textContent=first.topicTitle || 'Temat Garso';
				if (first.url) {
					topicTitle.href=first.url;
					topicTitle.target='_blank';
					topicTitle.rel='noopener noreferrer';
				}
				topic.appendChild(topicTitle);
				for (const item of items) {
					const row=makeElement('div','vm-garso-evidence-snippet');
					const meta=makeElement('div','vm-garso-evidence-meta',[item.author,item.date,item.scope,item.visit,item.aspect] .filter(Boolean)
						.join(' • ') || 'post Garso');
					const fragment=makeElement('div','vm-garso-evidence-text');
					appendGarsoEvidenceText(fragment,item.snippet,item.match);
					row.append(meta,fragment);
					topic.appendChild(row);
				}
				content.appendChild(topic);
			}
		}
		const closeController=createDialogCloseController(overlay);
		closeController.bindButton(close);
		dialog.append(header,content);
		overlay.appendChild(dialog);
		document.body.appendChild(overlay);
		close.focus();
	}
	function formatGarsoCacheTimestamp(timestamp) {
		const time=Number(timestamp);
		if (!Number.isFinite(time) || time <= 0) return 'brak zapisanego cache';
		const date=new Date(time).toLocaleString('pl-PL',{
			day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit'
		});
		const age=formatEscortLastVisit(time);
		return age ? `${date} • ${age}`:date;
	}
	function removeEscortSummarySidePanel() {
		detachHelperNode(document.getElementById(ESCORT_SUMMARY_SIDE_PANEL_ID));
		detachHelperNode(document.getElementById('vm-escort-summary-side-toggle'));
		document.body.classList.remove(ESCORT_SUMMARY_SIDE_PANEL_OPEN_CLASS);
	}
	function createEscortSummarySidePanel() {
		removeEscortSummarySidePanel();
		ensureEscortSummarySidePanelStyle();
		if (!SETTINGS.showSummarySidePanel) {
			return {
				panel:null, content:makeElement('div'), onOpen() {}
			};
		}
		const panel= prepareHelperStyleApi( makeElement('aside') );
		panel.id= ESCORT_SUMMARY_SIDE_PANEL_ID;
		panel.setAttribute( 'aria-label', 'Podsumowanie anonsu' );
		const resizeHandle=
			makeElement('div','vm-escort-summary-side-resize-handle',null,{title:'Przeciągnij, aby zmienić szerokość panelu. Kliknij dwukrotnie, aby przywrócić domyślną.'});
		resizeHandle.setAttribute( 'role', 'separator' );
		resizeHandle.setAttribute( 'aria-orientation', 'vertical' );
		const header= makeElement( 'div', 'vm-summary-panel-header' );
		const title= makeElement( 'div', 'vm-summary-panel-title', 'Podsumowanie' );
		const pin= makeElement('button','vm-summary-panel-pin',null,{type:'button'});
		const pinIcon= document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
		pinIcon.classList.add( 'vm-summary-panel-pin-icon' );
		pinIcon.setAttribute( 'viewBox', '0 0 24 24' );
		pinIcon.setAttribute( 'aria-hidden', 'true' );
		const pinPath= document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
		pinPath.setAttribute( 'd', 'M9 2.5h6l-.8 5.3 3.3 3.3v1.7H13l-1 8.7-1-8.7H6.5v-1.7l3.3-3.3L9 2.5z' );
		pinIcon.appendChild(pinPath);
		pin.appendChild(pinIcon);
		header.append(title,pin);
		const content= makeElement( 'div', 'vm-escort-summary-side-content' );
		panel.append( resizeHandle, header, content );
		const toggle= makeElement('button');
		toggle.id= 'vm-escort-summary-side-toggle';
		toggle.type='button';
		toggle.textContent= 'Podsumowanie';
		toggle.title= 'Pokaż lub ukryj podsumowanie';
		const openCallbacks=new Set();
		document.body.append( panel, toggle );
		bindEscortSummarySidePanelControls( document, panel, toggle, {
				onOpen() {
					openCallbacks.forEach( callback => callback() );
				}
			} );
		return {
			panel, content, onOpen(callback) {
				if ( typeof callback === 'function' ) {
					openCallbacks.add(callback);
				}
			}
		};
	}
	function createEscortSummarySideCard( sidePanel, titleText, storageSuffix, defaultExpanded=true, sectionClass='' ) {
		const section=makeElement( 'section', `vm-escort-summary-side-card${sectionClass ? ` ${sectionClass}`:''}` );
		const head=makeElement('div','vm-escort-summary-side-card-head');
		const title=makeElement('div','vm-garso-card-title',titleText);
		const body=makeElement('div','vm-escort-summary-side-card-body');
		head.appendChild(title);
		section.append(head,body);
		sidePanel.content.appendChild(section);
		const collapsible=makeEscortSummarySectionCollapsible( section,head,body,storageSuffix,defaultExpanded );
		return {section,head,title,body,collapsible};
	}
	function createGarsoSideSummaryCard(sidePanel,btn,titleText) {
		const card=makeElement('section','vm-escort-summary-side-card vm-garso-side-card');
		const head=makeElement('div','vm-escort-summary-side-card-head');
		const title=makeElement('div','vm-garso-card-title');
		const titleLabel=makeElement('span','',titleText);
		const titleWarning=makeElement('span','vm-garso-card-title-warning','⚠︎',{hidden:true});
		title.append(titleLabel,titleWarning);
		const meta=makeElement('div','vm-garso-card-meta vm-garso-card-status-full');
		head.append(title,meta);
		const body=makeElement('div','vm-escort-summary-side-card-body');
		const bodyContent=makeElement('div');
		const cancelButton=makeButton('vm-garso-cancel-button','Przerwij');
		cancelButton.addEventListener('click',event => {
			stopUiEvent(event);
			const token=btn._vmGarsoSummaryCancelToken;
			if (!token || token.cancelled) return;
			cancelButton.disabled=true;
			cancelButton.textContent='Przerywam…';
			token.cancel('Pełna analiza Garso przerwana przez użytkownika');
		});
		bodyContent.addEventListener('click',event => {
			const chip=event.target.closest('.vm-garso-mention-chip');
			if (!chip || !bodyContent.contains(chip)) return;
			stopUiEvent(event);
			const entries=getGarsoMentionEntries(btn._vmGarsoSummaryResult);
			entries.sort((a,b) => b.count - a.count || a.label.localeCompare(b.label,'pl-PL') );
			const index=Number(chip.dataset.vmGarsoMentionIndex);
			if (Number.isInteger(index) && entries[index]) {
				openGarsoMentionEvidenceDialog(entries[index]);
			}
		});
		const preview=makeElement('div','vm-escort-summary-side-card-body vm-summary-preview',null,{hidden:true});
		body.append(bodyContent);
		head.appendChild(cancelButton);
		card.append(head,preview,body);
		sidePanel.content.appendChild(card);
		const collapsible=makeEscortSummarySectionCollapsible( card, head, body,
			`garso-${btn.dataset.garsoSearchMode || normalizeText(titleText).replace(/\s+/g,'-')}`, true );
		const render=detail => {
			const state=detail?.state || btn.dataset.garsoSummaryState || '';
			const html=detail?.html ?? btn.dataset.garsoSummaryHtml ?? '';
			const result=detail?.result || btn._vmGarsoSummaryResult || null;
			const summaryCheckedAt=Number( detail?.checkedAt ?? btn.dataset.garsoSummaryCheckedAt ) || null;
			const countCheckedAt=Number(btn.dataset.garsoCountCheckedAt) || null;
			const checkedAt=summaryCheckedAt || countCheckedAt;
			const cacheState=detail?.cacheState || btn.dataset.garsoSummaryCacheState || '';
			preview.replaceChildren();
			if (state === 'empty') bodyContent.innerHTML=summaryToHtml({});
			else if (html || result?.stats) bodyContent.innerHTML=html || summaryToHtml(result);
			else if (state === 'count-only') {
				bodyContent.textContent=
					'Sprawdzono tylko, czy są tematy. Aby przeanalizować tematy, kliknij przycisk odświeżania w nagłówku sekcji.';
			}
			else if (state === 'loading') bodyContent.textContent='Obliczanie statystyk…';
			else if (state === 'cancelled') bodyContent.textContent='Analiza została przerwana.';
			else if (state === 'error') bodyContent.textContent='Nie udało się przygotować analizy.';
			else bodyContent.textContent='Analiza pojawi się po sprawdzeniu Garsoniery.';
			const summaryPreview=bodyContent.querySelector('.vm-garso-summary-preview');
			if (summaryPreview) preview.appendChild(summaryPreview);
			preview.hidden=!summaryPreview;
			const cancellationAvailable= ['loading','refreshing'].includes(state) && btn._vmGarsoSummaryCancelToken &&
				!btn._vmGarsoSummaryCancelToken.cancelled;
			cancelButton.classList.toggle('-visible',!!cancellationAvailable);
			cancelButton.disabled=false;
			cancelButton.textContent='Przerwij';
			const reviewWarning=getGarsoReviewWarning(result);
			titleWarning.hidden=!reviewWarning.show;
			titleWarning.setAttribute( 'aria-hidden', String(!reviewWarning.show) );
			titleWarning.title=reviewWarning.title;
			if (checkedAt) {
				const prefix=state === 'count-only'
					? 'Sprawdzenie obecności tematów'
					:(!summaryCheckedAt
					? 'Liczba tematów z cache'
					:(['session','partial'].includes(cacheState)
						? 'Analiza bieżąca'
						:'Cache analizy'));
				let suffix='';
				if (cacheState === 'refreshing') suffix=' • trwa odświeżanie';
				else if (cacheState === 'stale') suffix=result?.unverifiedTopicCount
					? ' • nie potwierdzono aktualności':' • wymaga sprawdzenia aktualności';
				else if (cacheState === 'partial') suffix=' • analiza niepełna, nie zapisano';
				else if (!summaryCheckedAt) suffix=' • brak cache analizy';
				if (summaryCheckedAt && result?.sourceCheckAttemptAt && !result.unverifiedTopicCount) suffix += ` • licznik postów sprawdzono: ${formatGarsoCacheTimestamp(result.sourceCheckAttemptAt)}`;
				meta.textContent=`${prefix}: ${formatGarsoCacheTimestamp(checkedAt)}${suffix}`;
			} else if (state === 'loading') {
				meta.textContent='Brak cache • trwa analiza';
			} else {
				meta.textContent='Brak zapisanego cache';
			}
			meta.title=meta.textContent;
		};
		btn.addEventListener('vm-garso-summary-state',event => render(event.detail));
		btn.addEventListener('vm-garso-count-state',() => render());
		sidePanel.onOpen(() => render());
		render();
		return {
			card, head, title, titleLabel, titleWarning, meta, body, bodyContent, collapsible
		};
	}
	const ESCORT_COMPACT_SUMMARY_ID='vm-escort-compact-summary';
	function findEscortCompactSummaryPlacement() {
		const map=document.querySelector( '.content-info-col.-desc .content-location > #map' );
		const locationBox=map?.parentElement;
		if (!locationBox?.classList.contains('content-location')) return null;
		return locationBox;
	}
	function createEscortCompactSummaryBox(mountOnAd=true) {
		const styleId=ESCORT_COMPACT_SUMMARY_ID + '-style';
		if (!document.getElementById(styleId)) {
			appendHelperStyle(styleId,`
#${ESCORT_COMPACT_SUMMARY_ID} { display: block; clear: both; flex: 0 0 100%; align-self: stretch; width: 100%; max-width: 100%; box-sizing: border-box; margin: 8px 0 12px;  }
#${ESCORT_COMPACT_SUMMARY_ID} > .vm-escort-compact-summary-content { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 7px; }
#${ESCORT_COMPACT_SUMMARY_ID} .vm-compact-summary-chip { display: inline-block; max-width: 100%; box-sizing: border-box; overflow: hidden; padding: 3px 6px; border: 1px solid color-mix(in srgb, var(--vm-ui-panel-text, #fff) 20%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--vm-ui-panel-text, #fff) 5.5%, transparent); color: var(--vm-ui-panel-text, #fff); text-overflow: ellipsis; white-space: nowrap; }
#${ESCORT_COMPACT_SUMMARY_ID} .vm-compact-summary-chip.-ok { border-color: color-mix(in srgb, var(--vm-ui-success, #62cf7b) 65%, transparent); background: color-mix(in srgb, var(--vm-ui-success, #62cf7b) 14%, transparent); color: var(--vm-ui-success, #62cf7b); }
#${ESCORT_COMPACT_SUMMARY_ID} .vm-compact-summary-chip.-alert { border-color: color-mix(in srgb, var(--vm-ui-danger, #ff7777) 72%, transparent); background: color-mix(in srgb, var(--vm-ui-danger, #ff7777) 16%, transparent); color: var(--vm-ui-danger, #ff7777); }
`);
		}
		detachHelperNode(document.getElementById(ESCORT_COMPACT_SUMMARY_ID));
		const box=makeElement('section');
		box.id=ESCORT_COMPACT_SUMMARY_ID;
		prepareHelperStyleApi(box).classList.add('vm-ad-summary-box');
		box.setAttribute('aria-label','Skrócone podsumowanie anonsu');
		const content=makeElement('div','vm-escort-compact-summary-content');
		box.appendChild(content);
		if (!mountOnAd) return {box,content};
		const placeBox=() => {
			const parent=findEscortCompactSummaryPlacement();
			if (!parent) return false;
			parent.style.setProperty('flex-wrap','wrap','important');
			if (box.parentNode !== parent || box.nextSibling) parent.appendChild(box);
			return true;
		};
		if (!placeBox()) {
			let attempts=0;
			const timer=setInterval(() => {
				attempts++;
				if (placeBox() || attempts >= 40) clearInterval(timer);
			},250);
		}
		return {box,content};
	}
	function renderEscortCompactSummary(compactSummaryUi,researchFacts,currentTagCount,emptyText='Tylko jedno ogłoszenie') {
		if (!compactSummaryUi) return;
		const {content}=compactSummaryUi;
		content.replaceChildren();
		const addChip=(text,status='normal',title='') => {
			const chip=makeElement('span','vm-compact-summary-chip');
			if (status === 'ok' || status === 'alert') {
				chip.classList.add(`-${status}`);
			}
			chip.textContent=text;
			if (title) chip.title=title;
			content.appendChild(chip);
			return chip;
		};
		const escortiResults=[ researchFacts.escortiPhoneResult, researchFacts.escortiAddressResult
		].filter(result => result?.status === 'ok');
		const profileDates=escortiResults .flatMap(getEscortiProfileSummariesForResearch)
			.map(profile => formatEscortiDate(profile?.creationDate)) .filter(Boolean) .sort((a,b) =>
				(profileDateToTime(a) || Infinity) - (profileDateToTime(b) || Infinity) );
		if (profileDates.length) {
			addChip( `Pierwszy profil: ${formatResearchDateAge(profileDates[0])}`, 'normal',
				'Najstarsza znaleziona data utworzenia profilu na Escorti.pl powiązanego z tym anonsem lub numerem telefonu. Wiek w nawiasie jest liczony do dzisiaj.'
			);
		}
		const oldestPhoto=parseEscortResearchTimelineDate(researchFacts.photoRange?.oldest);
		if (oldestPhoto) addChip( `Najstarsze zdjęcie: ${formatResearchDateAge(formatServerMediaDate(oldestPhoto))}`,
			'normal','Najstarsza odczytana data pliku zdjęcia z galerii (Last-Modified). Nie jest to potwierdzona data wykonania zdjęcia.'
		);
		const overlaps=getEscortiCityHistoryOverlaps([ researchFacts.escortiAddressResult,researchFacts.escortiPhoneResult ]);
		if (overlaps.length) addChip( 'W tym samym czasie w więcej niż jednej lokalizacji','alert',
			'Historia Escorti.pl zawiera różne miasta przy tej samej dacie:\n' +
			[...new Set(overlaps.map(day => `${day.date}: ${[...day.cities.values()].join(', ')}`))].join('\n') );
		const alarmingTagCountLimit=normalizeCustomFilterInteger(SETTINGS.alarmingTagCountLimit,69,0,10000);
		if (currentTagCount > alarmingTagCountLimit) {
			addChip( `Uwaga: dużo usług / tagów (${currentTagCount})`, 'alert',
				`Liczba oferowanych usług / tagów (${currentTagCount}) przekracza ustawiony limit ${alarmingTagCountLimit}.` );
		}
		const phoneReputationReport=getPhoneReputationReport( researchFacts.phoneReputationResult );
		if (phoneReputationReport.sentiment === 'negative') {
			addChip( 'Nr tel.: opinia negatywna', 'alert', 'W sprawdzaniu numeru telefonu znaleziono negatywną opinię.' );
		} else if (phoneReputationReport.sentiment === 'positive') {
			addChip( 'Nr tel.: opinia pozytywna', 'ok', 'W sprawdzaniu numeru telefonu znaleziono pozytywną opinię.' );
		}
		const aggregationSource=getCompactSummaryAggregationSource(researchFacts);
		const comparisonSummary=aggregationSource.summary || null;
		const aggregationTooltip=buildCompactSummaryAggregationTooltip( aggregationSource );
		const relatedAdsCount=Math.max( 0, Number(aggregationSource.result?.adLinks) || 0,
			Number(comparisonSummary?.totalChecked) || 0 );
		if (relatedAdsCount > 1) {
			if (aggregationSource.loading) {
				addChip( 'Rozbieżności: sprawdzanie…', 'normal', `Trwa sprawdzanie rozbieżności.\n\n${aggregationTooltip}` );
			} else {
				const warnings=[...new Set( getEscortActiveAdsConsistencyWarnings(comparisonSummary)
				)].filter(warning => warning !== 'Różny opis');
				const comparableAds=(comparisonSummary?.results || []) .filter(item => item?.active && item.adData) .length;
				const activeAds=Math.max(Number(comparisonSummary?.activeCount) || 0,
					(comparisonSummary?.results || []).filter(item => item?.active).length);
				if (warnings.length) {
					addChip( `Rozbieżności (${comparableAds} ogł.): tak`, 'alert', 'Wykryte rozbieżności:\n' +
						warnings.map(warning => `• ${warning}`).join('\n') + `\n\n${aggregationTooltip}` );
				} else if (comparableAds < activeAds || comparisonSummary?.unknownCount > 0) {
					addChip('Rozbieżności: niepełne dane','normal',
						`Dane porównawcze: ${comparableAds} z ${activeAds} potwierdzonych aktywnych anonsów.` +
						(comparisonSummary?.unknownCount > 0 ? ` Anonse o nieustalonej dostępności: ${comparisonSummary.unknownCount}.`:'') +
						`\n\n${aggregationTooltip}`);
				} else if (comparableAds >= 2) {
					addChip( `Rozbieżności (${comparableAds} ogł.): nie`, 'ok', `Wykryte rozbieżności: brak.\n\n${aggregationTooltip}`
					);
				} else if (comparableAds === 1) {
					addChip( 'Tylko jeden aktywny anons', 'normal',
						'Znaleziono tylko jeden aktywny anons, więc nie ma drugiego aktywnego anonsu do porównania.' +
						`\n\n${aggregationTooltip}` );
				} else {
					addChip( `Rozbieżności (${comparableAds} ogł.): brak danych porównawczych`, 'normal',
						'Nie wykryto rozbieżności, ponieważ nie znaleziono aktywnych anonsów z pełnymi danymi możliwymi do porównania.' +
						`\n\n${aggregationTooltip}` );
				}
			}
		}
		if (!aggregationSource.loading) {
			const descriptionComparison= getEscortActiveAdsDescriptionComparison(comparisonSummary);
			if (descriptionComparison.same === true) {
				addChip( `Opis (${descriptionComparison.comparableCount} ogł.): taki sam`, 'ok',
					'Porównano pełną treść opisu aktywnych anonsów. Przy porównaniu pomijane są wszystkie białe znaki: spacje, tabulatory i przejścia do nowej linii.' +
					`\n\n${aggregationTooltip}` );
			}
		}
		const simultaneousDifferentCitiesSummary= Number(comparisonSummary?.activeCount) >= 2 &&
			Array.isArray(comparisonSummary?.cities) && comparisonSummary.cities.length >= 2
				? comparisonSummary
				:null;
		if ( simultaneousDifferentCitiesSummary && !aggregationSource.loading ) {
			const activeCount=Number( simultaneousDifferentCitiesSummary.activeCount ) || 0;
			const cities=simultaneousDifferentCitiesSummary.cities .map(normalizeEscortCity) .filter(Boolean);
			addChip( `Różne lokalizacje jednocześnie: ` + `${formatResearchPanelList(cities,4)} ` +
				`(${activeCount} ${activeEscortAdsWord(activeCount)} ` + `${polishAdWord(activeCount)})`, 'alert',
				`${aggregationTooltip}\n\nRównocześnie aktywne anonse z wybranego źródła wskazują różne lokalizacje.` );
		}
		const garsoResults=[...new Set([ researchFacts.garsoPhoneSummaryResult, researchFacts.garsoAddressSummaryResult
		].filter(result => result?.stats))];
		const bestGarsoResult=garsoResults.sort((a,b) => (Number(b.stats?.reviewCount) || 0) -
				(Number(a.stats?.reviewCount) || 0) || (Number(b.stats?.mean != null) - Number(a.stats?.mean != null)) ||
			(Number(b.topicInfo?.length) || 0) - (Number(a.topicInfo?.length) || 0) )[0] || null;
		if (bestGarsoResult?.stats?.mean != null) {
			const reviewCount=Math.max( 0, Number(bestGarsoResult.stats.reviewCount) || 0 );
			addChip( `Średnia Garso: ${bestGarsoResult.stats.mean}/10`, 'normal', reviewCount
					? `Średnia z ${reviewCount} ocenionych recenzji.`
					:'' );
		}
		const garsoMentionCandidates=garsoResults .flatMap(getMostFrequentGarsoMentions);
		const maximumMentionCount=garsoMentionCandidates.reduce( (maximum,item) => Math.max(maximum,item.count), 0 );
		const popularMentions=[...new Map( garsoMentionCandidates .filter(item => item.count === maximumMentionCount)
				.map(item => [item.label,item]) ).values()];
		if (popularMentions.length && maximumMentionCount > 0) {
			const allLabels=popularMentions.map(item => item.label);
			const popularMentionChip=addChip( '', 'normal', `Najwyższa liczba wystąpień (${maximumMentionCount}):\n` +
					allLabels.map(label => `• ${label}`).join('\n') );
			popularMentionChip.dataset.vmGarsoPopularMentions='1';
			const renderPopularMentions=visibleCount => {
				const visibleMentions=popularMentions.slice(0,visibleCount);
				const nodes=[document.createTextNode('Najczęściej: ')];
				visibleMentions.forEach((item,index) => {
					if (index) nodes.push(document.createTextNode(', '));
					const label=makeElement('span','',item.label);
					label.style.color=getGarsoMentionToneStyle( item.tone ).color;
					label.style.fontWeight='800';
					nodes.push(label);
				});
				nodes.push(document.createTextNode(` (${maximumMentionCount})`));
				popularMentionChip.replaceChildren(...nodes);
			};
			const fitPopularMentions=() => {
				if (!popularMentionChip.isConnected) return;
				const availableWidth=Math.floor( content.getBoundingClientRect().width );
				if (!availableWidth) return;
				for ( let visibleCount=allLabels.length;
					visibleCount >= 1;
					visibleCount-- ) {
					renderPopularMentions(visibleCount);
					if ( visibleCount === 1 || popularMentionChip.scrollWidth <= availableWidth + 1 ) {
						break;
					}
				}
			};
			compactSummaryUi.fitPopularGarsoMentions=fitPopularMentions;
			requestAnimationFrame(fitPopularMentions);
			if ( !compactSummaryUi.popularMentionsResizeObserver && typeof ResizeObserver === 'function' ) {
				compactSummaryUi.popularMentionsResizeObserver= new ResizeObserver(() => {
						compactSummaryUi.fitPopularGarsoMentions?.();
					});
				compactSummaryUi.popularMentionsResizeObserver.observe(content);
			}
		}
		const subtitles=[...new Set( garsoResults.flatMap(result => (result.topicInfo || [])
					.map(topic => getDisplayableGarsoTopicSubtitle(topic?.subtitle)) .filter(Boolean) ) )];
		if (subtitles.length) {
			const firstSubtitle=subtitles[0].length > 100
				? `${subtitles[0].slice(0,97).trim()}…`
				:subtitles[0];
			addChip( `Dopisek z Garso: ${firstSubtitle}${subtitles.length > 1
					? ` (+${subtitles.length - 1})`
					:''}`, 'normal', subtitles.join('\n') );
		}
		if (!content.childElementCount) {
			addChip( researchFacts.escortiLoading
					? 'Pobieranie skróconego podsumowania…'
					:emptyText );
		}
	}
	function getMostFrequentGarsoMentions(result) {
		const candidates=getGarsoMentionEntries(result,true);
		const maximumCount=candidates.reduce( (maximum,item) => Math.max(maximum,item.count), 0 );
		return maximumCount > 0
			? candidates.filter(item => item.count === maximumCount)
			:[];
	}
	const PHONE_REPUTATION_CACHE_PREFIX='vm_phone_reputation_';
	const PHONE_REPUTATION_PARSER_VERSION=2;
	const phoneReputationInFlight=new Map();
	function getPhoneReputationServices() {
		return normalizePhoneReputationServices(SETTINGS.phoneReputationServices)
			.filter(service => service.enabled !== false);
	}
	function getAutoPhoneReputationSources() {
		return getPhoneReputationServices().filter(service => PHONE_REPUTATION_AUTO_SOURCE_IDS.has(service.id) );
	}
	function buildPhoneReputationQuery(phone) {
		if (!phone?.digits) return '';
		const local=phone.local || phone.digits;
		const variants=phone.supported
			? [ `"${local}"`, `"${local.replace(/(\d{3})(\d{3})(\d{3})/,'$1 $2 $3')}"`,
				`"${local.replace(/(\d{3})(\d{3})(\d{3})/,'$1-$2-$3')}"`,
				`"+48 ${local.replace(/(\d{3})(\d{3})(\d{3})/,'$1 $2 $3')}"`, `"+48${local}"` ]
			:[`"+${phone.digits}"`,`"${phone.digits}"`];
		return variants.join(' OR ');
	}
	function buildPhoneReputationServiceUrl(service,phone) {
		if (!service?.urlTemplate || !phone?.digits) return '';
		const local=phone.local || phone.digits;
		const template=String(service.urlTemplate);
		const hasToken=/\{(?:phone|phone48|query)\}/.test(template);
		const replaced=template .replaceAll('{phone}',encodeURIComponent(local))
			.replaceAll('{phone48}',encodeURIComponent(phone.e164Digits || phone.digits))
			.replaceAll('{query}',encodeURIComponent(buildPhoneReputationQuery(phone)));
		return hasToken ? replaced:`${replaced}${encodeURIComponent(local)}`;
	}
	function getPhoneReputationSourceSignature() {
		return `${PHONE_REPUTATION_PARSER_VERSION}|` + getAutoPhoneReputationSources()
			.map(source => `${source.id}:${source.urlTemplate}`) .sort() .join('|');
	}
	function normalizePhoneReputationPhone(value) {
		let digits=digitsOnly(value);
		if (!digits) return null;
		if (digits.startsWith('0048') && digits.length === 13) digits=digits.slice(2);
		if (digits.length === 9) digits=`48${digits}`;
		if (!/^48\d{9}$/.test(digits)) {
			return {
				supported:false, digits, local:null, e164Digits:digits, display:String(value || '').trim() || `+${digits}`
			};
		}
		const local=digits.slice(2);
		return {
			supported:true, digits, local, e164Digits:digits,
			display:`+48 ${local.replace(/(\d{3})(\d{3})(\d{3})/,'$1 $2 $3')}`
		};
	}
	function phoneReputationCacheKey(phone) {
		return `${PHONE_REPUTATION_CACHE_PREFIX}${phone?.digits || ''}`;
	}
	function getCachedPhoneReputation(phone) {
		if (!phone?.digits) return null;
		const value=readPersistentCacheValue(phoneReputationCacheKey(phone),null);
		if (!value || typeof value !== 'object' || !value.checkedAt) return null;
		if (String(value.sourceSignature || '') !== getPhoneReputationSourceSignature()) return null;
		return {
			...value, fresh:Date.now() - Number(value.checkedAt) < getListCacheTtlMs()
		};
	}
	function setCachedPhoneReputation(phone,result) {
		if (!phone?.digits || !result) return;
		writePersistentCacheValue(phoneReputationCacheKey(phone),result);
	}
	function normalizePhoneReputationPageText(doc) {
		return String(doc?.body?.textContent || doc?.documentElement?.textContent || '') .replace(/\u00a0/g,' ')
			.replace(/\s+/g,' ') .trim();
	}
	function firstPhoneReputationInteger(text,patterns) {
		for (const pattern of patterns) {
			const match=text.match(pattern);
			if (!match) continue;
			const value=Number(String(match[1] || '').replace(/[^\d]/g,''));
			if (Number.isFinite(value)) return value;
		}
		return null;
	}
	function firstPhoneReputationText(text,patterns) {
		for (const pattern of patterns) {
			const match=text.match(pattern);
			const value=normalizeEscortAdText(match?.[1]);
			if (value) return value;
		}
		return null;
	}
	function parsePhoneReputationSource(sourceId,html,phone,finalUrl='') {
		const doc=parseHtml(html);
		const text=normalizePhoneReputationPageText(doc);
		const compactDigits=digitsOnly(text);
		const containsPhone=!!( phone?.local && ( text.includes(phone.local) || compactDigits.includes(phone.local) ||
				compactDigits.includes(phone.e164Digits) ) );
		const base={
			status:containsPhone ? 'ok':'unknown', comments:null, ratings:null, searches:null, reputation:null, risk:null,
			category:null, finalUrl:finalUrl || null
		};
		if (sourceId === 'ktoto') {
			base.comments=firstPhoneReputationInteger(text,[ /Liczba komentarzy:\s*([\d.,]+(?:\s\d{3})*)/i,
				/komentarz(?:y|e|ów)?\s*:\s*([\d.,]+(?:\s\d{3})*)/i ]);
			base.ratings=firstPhoneReputationInteger(text,[ /Opinia większości:[^(]{0,80}\(\s*(\d+)\s*\)/i,
				/Liczba ocen:\s*(\d+)/i ]);
			base.reputation=firstPhoneReputationText(text,[
				/Opinia większości:\s*([A-Za-zĄĆĘŁŃÓŚŹŻąćęłńóśźż -]+?)(?:\s*\(\d+\)|\s+Liczba ocen:)/i ]);
		} else if (sourceId === 'numerytelefonu') {
			base.comments=firstPhoneReputationInteger(text,[
				/Numer telefonu\s+\d+\s+skomentowano\s+(\d+(?:[ .,]\d{3})*)\s+raz(?:y)?\b/i ]);
			base.ratings=firstPhoneReputationInteger(text,[ /Głosowa(?:ł[oa]?|li)\s+(\d+(?:[ .,]\d{3})*)\s+użytkownik/i ]);
			base.reputation=firstPhoneReputationText(text,[ /Ten numer jest\s+([^.!?]{1,60})[.!?]/i ]);
		} else if (sourceId === 'sprawdzenienumeru') {
			base.comments=firstPhoneReputationInteger(text,[ /(?:Opinie użytkowników|Opublikowane wpisy)\s*\(([\d\s.,]+)\)/i,
				/\bOpinie\s+([\d\s.,]+)\b/i, /na podstawie\s+([\d\s.,]+)\s+(?:opinii|opublikowanych wpisów)/i ]);
			base.searches=firstPhoneReputationInteger(text,[ /\bWyszukania\s+([\d\s.,]+)\b/i,
				/był sprawdzany na naszej stronie\s+([\d\s.,]+)\s+raz/i ]);
			base.risk=firstPhoneReputationInteger(text,[ /Wskaźnik ryzyka(?: tego numeru)? wynosi\s*(\d{1,3})\s*\/\s*100/i,
				/\b(\d{1,3})\s*\/\s*100\b/i ]);
			base.category=firstPhoneReputationText(text,[ /Najczęściej przypisywana kategoria to:\s*([^.!?]{1,80})[.!?]/i,
				/Użytkownicy klasyfikują połączenia z tego numeru jako:\s*([^.!?]{1,80})[.!?]/i ]);
		}
		if (containsPhone && base.comments == null) base.comments=0;
		return base;
	}
	async function fetchPhoneReputationSource(source,phone) {
		const url=buildPhoneReputationServiceUrl(source,phone);
		try {
			const response=await gmRequest({
				method:'GET', url, headers:{
					Accept:'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
					'Accept-Language':'pl-PL,pl;q=0.9,en;q=0.6'
				}
			});
			const statusCode=Number(response?.status) || 0;
			if (statusCode < 200 || statusCode >= 400) {
				throw new Error(`HTTP ${statusCode || '?'}`);
			}
			const parsed=parsePhoneReputationSource( source.id, String(response?.responseText || ''), phone,
				response?.finalUrl || url );
			return {
				id:source.id, name:source.name, url, ...parsed
			};
		} catch (error) {
			recordDiagnosticError( `Sprawdzanie numeru - ${source.name}`, error );
			return {
				id:source.id, name:source.name, url, status:'error', comments:null, ratings:null, searches:null, reputation:null,
				risk:null, category:null, error:formatHelperDiagnosticError(error)
			};
		}
	}
	async function getPhoneReputationResult(phoneValue,forceRefresh=false) {
		const phone=normalizePhoneReputationPhone(phoneValue);
		const sourcesToCheck=getAutoPhoneReputationSources();
		if (!phone) {
			return {
				status:'unsupported',checkedAt:Date.now(),phone:null,
				sourceSignature:getPhoneReputationSourceSignature(),sources:[]
			};
		}
		if (!phone.supported) {
			return {
				status:'unsupported',checkedAt:Date.now(),phone, sourceSignature:getPhoneReputationSourceSignature(),
				sources:sourcesToCheck.map(source => ({
					id:source.id, name:source.name, url:buildPhoneReputationServiceUrl(source,phone), status:'unsupported'
				}))
			};
		}
		await ensurePersistentCacheKeys([phoneReputationCacheKey(phone)]);
		if (!forceRefresh) {
			const cached=getCachedPhoneReputation(phone);
			if (cached?.fresh) return {...cached,fromCache:true};
		}
		if (!sourcesToCheck.length) {
			return {
				sourceSignature:getPhoneReputationSourceSignature(), status:'empty',checkedAt:Date.now(),phone,sources:[]
			};
		}
		const inFlightKey=`${phone.digits}|${getPhoneReputationSourceSignature()}`;
		if (!forceRefresh && phoneReputationInFlight.has(inFlightKey)) {
			return phoneReputationInFlight.get(inFlightKey);
		}
		const promise=(async () => {
			const sources=await Promise.all( sourcesToCheck.map(source => fetchPhoneReputationSource(source,phone)) );
			const result={
				sourceSignature:getPhoneReputationSourceSignature(), status:sources.some(source => source.status === 'ok')
					? 'ok'
					:(sources.some(source => source.status === 'unknown') ? 'partial':'error'), checkedAt:Date.now(), phone, sources
			};
			setCachedPhoneReputation(phone,result);
			return result;
		})();
		phoneReputationInFlight.set(inFlightKey,promise);
		promise.finally(() => {
			if (phoneReputationInFlight.get(inFlightKey) === promise) {
				phoneReputationInFlight.delete(inFlightKey);
			}
		});
		return promise;
	}
	function formatPhoneReputationRowSummary(source) {
		if (source?.status === 'unsupported') return 'numer spoza PL';
		if (source?.status === 'error') return 'błąd pobierania';
		if (source?.status === 'unknown') return 'nie udało się odczytać wyniku';
		const comments=Math.max(0,Number(source?.comments) || 0);
		const ratings=Math.max(0,Number(source?.ratings) || 0);
		if (!comments && !ratings) return 'brak opinii';
		const parts=[];
		const sentiment=getPhoneReputationSourceSentiment(source);
		const label=sentiment === 'negative' ? 'Negatywny':(sentiment === 'positive' ? 'Pozytywny':normalizeEscortAdText(source?.reputation || source?.category));
		if (label) parts.push(label);
		if (comments) parts.push(formatPolishCount(comments,'komentarz','komentarze','komentarzy'));
		if (ratings) parts.push(formatPolishCount(ratings,'ocena','oceny','ocen'));
		return parts.join(' • ');
	}
	function getPhoneReputationSourceSentiment(source) {
		if (source?.status !== 'ok') return null;
		const text=`${source.reputation || ''} ${source.category || ''}`;
		const negativePattern=/negatyw|niebezpie|oszust|spam|wyłudz|naciąg|podejrz|phishing|scam|fraud/i;
		const positivePattern=/pozytyw|dobr(?:y|a|e)|super|bezpiecz|zaufan|wiarygod|polecan/i;
		if ((Number(source.risk) || 0) >= 50 || negativePattern.test(text)) return 'negative';
		if (positivePattern.test(text)) return 'positive';
		return null;
	}
	function getPhoneReputationReport(result) {
		const sources=Array.isArray(result?.sources) ? result.sources:[];
		const details=sources.map(source => `${source.name || source.id}: ${formatPhoneReputationRowSummary(source)}` );
		const negative=sources.filter(source => getPhoneReputationSourceSentiment(source) === 'negative');
		const positive=sources.filter(source => getPhoneReputationSourceSentiment(source) === 'positive');
		const sentiment=negative.length ? 'negative':(positive.length ? 'positive':null);
		return {
			sentiment, title:details.join('\n'), badgeText:sentiment === 'negative'
				? 'ocena negatywna'
				:(sentiment === 'positive' ? 'ocena pozytywna':'')
		};
	}
	function createPhoneReputationLinksSection( phoneValue, onResult=null, cacheOnly=false ) {
		const phone=normalizePhoneReputationPhone(phoneValue);
		const section=makeElement('section','vm-phone-reputation-links',null,{id:'vm-phone-reputation-links'});
		const head=makeElement('div','vm-phone-reputation-head');
		const title=makeElement('span','vm-phone-reputation-title vm-research-section-title','Sprawdzenie nr. tel.');
		const badge=makeElement('span','vm-phone-reputation-badge',null,{hidden:true});
		head.append(title,badge);
		const body=makeElement('div','vm-phone-reputation-body');
		const rows=makeElement('div','vm-phone-reputation-rows');
		body.appendChild(rows);
		section.append(head,body);
		makeEscortSummarySectionCollapsible( section,head,body,'phone-reputation-links',false,true );
		const configuredServices=getPhoneReputationServices();
		const rowById=new Map();
		for (const service of configuredServices) {
			const row=createResearchPanelResultRow( service.name, buildPhoneReputationServiceUrl(service,phone) );
			if (PHONE_REPUTATION_AUTO_SOURCE_IDS.has(service.id)) {
				setTwoLineButton( row, service.name, cacheOnly ? 'brak wyniku w cache':SETTINGS.autoPhoneReputationSearch
						? 'sprawdzanie / cache…'
						:'kliknij, aby sprawdzić' );
				setButtonColor( row, cacheOnly ? 'stale':SETTINGS.autoPhoneReputationSearch ? 'checking':'empty' );
			} else {
				setTwoLineButton( row, service.name, service.id === 'google'
						? 'wyszukaj ręcznie'
						:'kliknij, aby sprawdzić' );
				setButtonColor(row,'empty');
			}
			rowById.set(service.id,row);
			rows.appendChild(row);
		}
		const applyResult=result => {
			const report=getPhoneReputationReport(result);
			badge.hidden=!report.badgeText;
			badge.textContent=report.badgeText;
			badge.className=`vm-phone-reputation-badge${report.sentiment ? ` -${report.sentiment}`:''}`;
			head.title=report.title || 'Kliknij, aby rozwinąć sekcję';
			for (const source of result?.sources || []) {
				const row=rowById.get(source.id);
				if (!row) continue;
				const configuredName=configuredServices.find( service => service.id === source.id )?.name;
				setTwoLineButton( row, configuredName || source.name || source.id, formatPhoneReputationRowSummary(source) );
				const sentiment=getPhoneReputationSourceSentiment(source);
				setButtonColor( row, source.status === 'error' ? 'error':((Number(source.comments) || 0) > 0 ? 'found':'empty') );
				const value=row.querySelector('.vm-research-panel-value');
				if (value && sentiment === 'negative') value.style.color='var(--vm-ui-danger,#ff8b8b)';
				if (value && sentiment === 'positive') value.style.color='var(--vm-ui-success,#7ee497)';
				if (source.error) row.title=source.error;
			}
			onResult?.(result);
		};
		const cached=phone ? getCachedPhoneReputation(phone):null;
		if (cached) applyResult({...cached,fromCache:cached.fresh});
		const refresh=async (forceRefresh=false) => {
			if (!SETTINGS.autoPhoneReputationSearch) {
				if (!cached) onResult?.(null);
				return cached;
			}
			for (const service of getAutoPhoneReputationSources()) {
				const row=rowById.get(service.id);
				if (!row) continue;
				setTwoLineButton(row,service.name,forceRefresh ? 'odświeżanie…':'sprawdzanie…');
				setButtonColor(row,'checking');
			}
			const result=await getPhoneReputationResult(phoneValue,forceRefresh);
			applyResult(result);
			return result;
		};
		if ( !cacheOnly && SETTINGS.autoPhoneReputationSearch && !cached?.fresh ) {
			refresh(false).catch(error => {
				log('Błąd sprawdzania numeru telefonu',error);
			});
		} else if (cached) {
			onResult?.(cached);
		}
		return {section,refresh,applyResult};
	}
	function createEscortResearchPanel(phone,adUrl,{archiveData=null,archiveDocument=null}={}) {
		const archiveRuntime=!!archiveDocument || isEscortCachedArchiveRuntime();
		const setResearchControlDisabled=(control,disabled,opacity='.55') => {
			if ('disabled' in control) control.disabled=disabled;
			control.style.opacity=disabled ? opacity:'1';
			control.style.cursor=disabled ? 'default':'pointer';
		};
		const setResearchRefreshState=(button,busy,title) => {
			button.disabled=busy;
			button.textContent=busy ? '⟳':'↻';
			setControlTitle(button,title);
			button.style.opacity=busy ? '.72':'1';
		};
		const sidePanel=createEscortSummarySidePanel();
		const panel=prepareHelperStyleApi(makeElement('section'));
		panel.id='vm-escort-research-panel';
		panel.setAttribute('aria-label','Linki do powiązanych wyników');
		const header=makeElement('div','vm-research-panel-main-header');
		const headerTitle=makeElement('div','vm-research-panel-main-title','Linki');
		const refreshButton=makeButton('vm-research-panel-refresh','↻',{title:'Pobierz ponownie dane z sekcji „Linki” (Garsoniera: tylko sprawdzenie, czy są tematy; sprawdzanie numeru: jeśli włączone automatyczne wyszukiwanie)'});
		refreshButton.setAttribute('aria-label',refreshButton.title);
		header.append(headerTitle,refreshButton);
		panel.appendChild(header);
		const normalizedPhone=normalizeEscortCachedPhone(phone) || '';
		phone=normalizedPhone ? phone:'';
		const phoneSearch=normalizedPhone;
		const adId=parseAdIdFromUrl(adUrl);
		const garsoPhoneTerm=getGarsoSearchTerms(phone,'phone')[0] || '';
		const garsoAddressTerm=buildGarsoAdLinkSearchTerm(adId);
		const garsoCombinedSummaryTerm=buildCombinedGarsoSummaryTerm( garsoPhoneTerm, garsoAddressTerm );
		const garsoPhoneRow=createGarsoButton('nr tel.',garsoPhoneTerm,'phone');
		const garsoAddressRow=createGarsoButton('adres anonsu',garsoAddressTerm,'address');
		const garsoCombinedSummaryState=makeElement('button');
		garsoCombinedSummaryState.dataset.garsoDisplayTerm='nr tel. + adres anonsu';
		garsoCombinedSummaryState.dataset.garsoSearchMode='combined';
		garsoCombinedSummaryState.dataset.garsoStatus='manual';
		const cachedCombinedGarsoSummary=restoreGarsoSummaryFromCache( garsoCombinedSummaryState, garsoCombinedSummaryTerm,
			'combined' );
		if (archiveRuntime && !cachedCombinedGarsoSummary) {
			const saved=[['phone',garsoPhoneTerm,'nr tel.'],['address',garsoAddressTerm,'adres anonsu']]
				.map(([mode,term,label]) => ({mode,term,label,cached:getGarsoSummaryCache(mode,term)}))
				.filter(item => item.cached).sort((a,b) => b.cached.checkedAt - a.cached.checkedAt)[0];
			if (saved) {
				garsoCombinedSummaryState.dataset.garsoSearchMode=saved.mode;
				garsoCombinedSummaryState.dataset.garsoDisplayTerm=saved.label;
				restoreGarsoSummaryFromCache(garsoCombinedSummaryState,saved.term,saved.mode);
			}
		}
		panel.append(garsoPhoneRow,garsoAddressRow);
		const cachedGarsoPhone=getGarsoSearchCountCache('phone',garsoPhoneTerm);
		const cachedGarsoAddress=getGarsoSearchCountCache('address',garsoAddressTerm);
		const researchFacts=archiveRuntime
			? getEscortArchiveResearchFactsFromCache( adId, adUrl, phone, garsoCombinedSummaryState, cachedGarsoPhone,
				cachedGarsoAddress, archiveData )
			:{
				garsoPhoneCount:cachedGarsoPhone?.fresh ? cachedGarsoPhone.count:null,
				garsoAddressCount:cachedGarsoAddress?.fresh ? cachedGarsoAddress.count:null, escortiPhoneResult:null,
				escortiAddressResult:null, escortiPhoneSummary:null, escortiAddressSummary:null, escortPhoneSummary:null,
				sameEscortiResult:null, escortiWarnings:[], escortiLoading:true, escortPhoneLoading:true, photoRange:null,
				photoDatesLoading:true, garsoPhoneSummaryResult: garsoCombinedSummaryState._vmGarsoSummaryResult || null,
				garsoAddressSummaryResult: garsoCombinedSummaryState._vmGarsoSummaryResult || null, datePosted:
					extractEscortClubStructuredAdData(document).datePosted || null, currentAdUrl:adUrl, phoneReputationResult:(() => {
					if (!SETTINGS.showPhoneReputationLinks) return null;
					const normalizedPhone=normalizePhoneReputationPhone(phone);
					return normalizedPhone
						? getCachedPhoneReputation(normalizedPhone)
						:null;
				})(), garsoPhoneReviewDates:getGarsoSummaryReviewDates(garsoCombinedSummaryState),
				garsoAddressReviewDates:getGarsoSummaryReviewDates(garsoCombinedSummaryState)
			};
		const compactSummaryUi=SETTINGS.showCompactSummaryAboveDescription
			? createEscortCompactSummaryBox(!archiveDocument)
			:null;
		if (compactSummaryUi && archiveDocument) archiveDocument.querySelector('.content-info-col.-desc .content-location')?.appendChild(compactSummaryUi.box);
		const photoHistogramHost=makeElement('div');
		sidePanel.content.appendChild(photoHistogramHost);
		const renderCompactResearchSummary=() => {
			if (compactSummaryUi) renderEscortCompactSummary(compactSummaryUi,researchFacts,
				archiveData ? (archiveData.tags || []).length:extractEscortClubTags(document).length,
				archiveRuntime ? 'Brak zapisanych danych do podsumowania':'Tylko jedno ogłoszenie');
		};
		let updateGarsoExtendedRecommendation=() => {},updateGarsoExtendedButton=() => {};
		let renderOtherCities=() => {};
		const renderResearchFacts=() => {
			try {
				photoHistogramHost.replaceChildren( createEscortResearchDateTimeline( researchFacts ) );
			} catch (error) {
				log( 'Nie udało się wyrenderować osi czasu w panelu bocznym', error );
				photoHistogramHost.replaceChildren();
			}
			try {
				renderCompactResearchSummary();
			} catch (error) {
				log( 'Nie udało się wyrenderować skróconego podsumowania', error );
			}
			try {
				renderOtherCities();
			} catch (error) {
				log( 'Nie udało się wyrenderować sekcji Inne', error );
			}
			try {
				updateGarsoExtendedButton();
			} catch (error) {
				log('Nie udało się zaktualizować podpowiedzi Garso',error);
			}
		};
		const bindGarsoCountForFacts=(row,key) => {
			row.addEventListener('vm-garso-count-state',event => {
				researchFacts[key]=Number.isFinite(event.detail?.count)
					? Number(event.detail.count)
					:null;
				renderResearchFacts();
			});
		};
		bindGarsoCountForFacts(garsoPhoneRow,'garsoPhoneCount');
		bindGarsoCountForFacts(garsoAddressRow,'garsoAddressCount');
		if (archiveRuntime) {
			for (const [row,cached] of [ [garsoPhoneRow,cachedGarsoPhone], [garsoAddressRow,cachedGarsoAddress] ]) {
				if (!cached) continue;
				row.dataset.garsoStatus='result';
				row.dataset.garsoCount=String(cached.count);
				row.dataset.garsoCountCheckedAt=String(cached.checkedAt);
				row.dataset.garsoCacheState=cached.fresh ? 'fresh':'stale';
				renderGarsoButton(row);
			}
		}
		garsoCombinedSummaryState.addEventListener( 'vm-garso-summary-state', event => {
				const reviewDates={
					first:event.detail?.firstReviewDate || null, last:event.detail?.lastReviewDate || null,
					firstUrl:event.detail?.firstReviewUrl || null, lastUrl:event.detail?.lastReviewUrl || null
				};
				researchFacts.garsoPhoneReviewDates={...reviewDates};
				researchFacts.garsoAddressReviewDates={...reviewDates};
				if (event.detail?.result) {
					researchFacts.garsoPhoneSummaryResult=event.detail.result;
					researchFacts.garsoAddressSummaryResult=event.detail.result;
				}
				renderResearchFacts();
			} );
		renderResearchFacts();
		const escortiPhoneUrl= buildEscortiSearchUrl(phoneSearch);
		const escortiAddressUrl= buildEscortiSearchUrl(adUrl);
		const escortClubPhoneUrl=buildEscortClubPhoneSearchUrl(phone);
		const escortiAddressRow=createResearchPanelResultRow( 'Escorti.pl', escortiAddressUrl );
		const escortiPhoneRow=createResearchPanelResultRow( 'Escorti.pl - nr tel.', escortiPhoneUrl );
		escortiPhoneRow.style.display='none';
		const escortPhoneRow=createResearchPanelResultRow( 'Escort - nr tel.', escortClubPhoneUrl );
		escortPhoneRow.title= 'Aktualnie znalezione na Escort.club anonse z tym samym numerem telefonu.';
		panel.append(escortiAddressRow,escortiPhoneRow,escortPhoneRow);
		const warningBox=makeElement('section','vm-escort-summary-inconsistencies vm-escort-summary-status-ok');
		const warningTitle=makeElement('div','vm-escort-summary-inconsistencies-title');
		const warningTitleText=makeElement('span','','Niezgodności');
		const warningStatusBadge=makeElement('span','vm-inconsistencies-status-badge');
		warningTitle.append(warningTitleText,warningStatusBadge);
		const warningContent=makeElement('div','vm-escort-summary-inconsistencies-content');
		const warningPreview=makeElement('div','vm-summary-section-preview');
		warningBox.append(warningTitle,warningPreview,warningContent);
		makeEscortSummarySectionCollapsible( warningBox, warningTitle, warningContent, 'inconsistencies', true );
		const garsoSummaryCard=createGarsoSideSummaryCard( sidePanel, garsoCombinedSummaryState, 'Garso - recenzje' );
		const {
			section:otherSection, body:otherBody
		}=createEscortSummarySideCard( sidePanel, 'Inne', 'other', false, 'vm-escort-summary-other' );
		const otherPreview=makeElement('div','vm-summary-section-preview');
		otherSection.insertBefore(otherPreview,otherBody);
		const otherCities=makeElement( 'div', 'vm-escort-summary-other-cities vm-summary-other-cities-framed' );
		otherBody.appendChild(otherCities);
		renderOtherCities=() => {
			const cities= getEscortiHistoricalCityStats([ researchFacts.escortiPhoneResult, researchFacts.escortiAddressResult
				]);
			const summaries=[researchFacts.escortiAddressSummary,
				researchFacts.escortiPhoneSummary,researchFacts.escortPhoneSummary].filter(Boolean);
			const activeCities=[...new Map(summaries.flatMap(summary => summary.cities || [])
				.map(normalizeEscortCity).filter(Boolean) .map(city => [normalizeEscortCityKey(city),city])).values()];
			const loading=researchFacts.escortiLoading || researchFacts.escortPhoneLoading;
			const overlaps=getEscortiCityHistoryOverlaps([ researchFacts.escortiAddressResult,researchFacts.escortiPhoneResult
			]);
			const activeCitiesText=`W aktywnych anonsach: ${activeCities.join(', ') ||
				(loading ? 'sprawdzam…':summaries.length ? 'brak ustalonych miast':'brak danych')}`;
			otherPreview.classList.toggle('-warning',overlaps.length > 0);
			otherPreview.textContent=overlaps.length
				? `⚠ W tym samym czasie w więcej niż jednej lokalizacji · ${activeCitiesText}`
				:activeCitiesText;
			otherCities.replaceChildren();
			for (const day of overlaps) {
				const line=makeElement('div','vm-escort-summary-other-label',
					`⚠ Ta sama data przy różnych miastach: ${day.date} - ${[...day.cities.values()].join(', ')}`);
				makeEscortTimelineElementClickable(line,day.url,'Otwórz historię lokalizacji w profilu Escorti');
				otherCities.appendChild(line);
			}
			const heading=makeElement( 'div', 'vm-escort-summary-other-label', `Miasta w historii Escorti.pl (${
					cities.length
				}):` );
			otherCities.appendChild(heading);
			if (!cities.length) {
				otherCities.appendChild( makeElement( 'div', '', researchFacts.escortiLoading
							? 'Pobieranie danych Escorti.pl…'
							:'Brak znalezionych miast w danych Escorti.pl.' ) );
				return;
			}
			const list=makeElement('div','vm-escort-summary-other-city-list');
			for (const item of cities) {
				const chip=makeElement('span','vm-escort-summary-other-city',`${item.city} (${item.count})`,{title:item.count > 1
						? `Znalezione wystąpienia tego miasta w historii Escorti.pl: ${item.count}`
						:'Miasto znalezione w historii Escorti.pl'});
				list.appendChild(chip);
			}
			otherCities.appendChild(list);
		};
		renderOtherCities();
		garsoSummaryCard.head.classList.add('vm-garso-main-head');
		garsoSummaryCard.title.classList.add('vm-garso-main-title');
		garsoSummaryCard.meta.classList.add('vm-garso-main-meta');
		const garsoCollapseHint=garsoSummaryCard.head.querySelector( '.vm-escort-summary-collapse-hint' );
		if (garsoCollapseHint) garsoCollapseHint.style.top='16px';
		const garsoDownloadButton=makeButton('vm-garso-download-button','↻',{title:'Odśwież i przeanalizuj treść tematów Garsoniery dla numeru telefonu i adresu anonsu.'});
		garsoDownloadButton.setAttribute('aria-label',garsoDownloadButton.title);
		garsoSummaryCard.head.appendChild(garsoDownloadButton);
		sidePanel.content.prepend(warningBox);
		const footer=makeElement('div','vm-research-panel-footer');
		const phoneReputationLinksUi=SETTINGS.showPhoneReputationLinks
			? createPhoneReputationLinksSection( phone, result => {
					researchFacts.phoneReputationResult=result;
					renderCompactResearchSummary();
				}, archiveRuntime )
			:null;
		if (phoneReputationLinksUi) footer.appendChild(phoneReputationLinksUi.section);
		const garsoExtendedPanel=makeElement('section','',null,{id:'vm-garso-extended-panel'});
		const garsoExtendedHead=makeElement('div','vm-garso-extended-head');
		const garsoExtendedTitle=makeElement('span','vm-research-section-title','Garso - szukanie rozszerzone');
		const garsoExtendedTitleHelp='Od razu pokazuje zapisane wyniki, także ze zwykłych wyszukiwań powiązanych numerów i adresów. „Szukaj” sprawdza Garsonierę na żywo. Wyszukuje na Garso linki anonsów z Escorti.pl, pełny numer 123-456-789 i zapis 123-456-xxx (dosłowne iksy), a także historyczne numery zapisane w cache powiązanych anonsów Escort.club. Jeśli standardowe warianty danego numeru nic nie znajdą, próbuje jeszcze zapisu 123 456 789. W aktywnych anonsach odczytuje aktualny telefon i sprawdza go tak samo. Dodatkowo w pierwszym poście tematów znalezionych w tym kroku szuka innego pełnego numeru lub linku Escort.club i sprawdza go jeden poziom dalej. Nie analizuje już pierwszych postów tematów znalezionych tym dodatkowym krokiem. Nie powtarza zapytań. Przycisk „Otwórz wyniki” otwiera unikalne strony wyników w nowych kartach.';
		garsoExtendedTitle.title=garsoExtendedTitleHelp;
		garsoExtendedTitle.style.flex='1 1 auto';
		garsoExtendedTitle.style.minWidth='0';
		const garsoExtendedStatusBadge=makeElement('span','vm-garso-extended-status-badge','✓',{hidden:true});
		garsoExtendedHead.append( garsoExtendedTitle, garsoExtendedStatusBadge );
		const garsoExtendedRecommendation=makeButton('vm-garso-extended-recommendation',null,{hidden:true});
		const garsoExtendedBody=makeElement('div','vm-garso-extended-body');
		const garsoExtendedControls=makeElement('div','vm-garso-extended-controls');
		const garsoExtendedButton=makeButton('vm-garso-extended-action -search');
		garsoExtendedButton.innerHTML='<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2"><circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/></svg>';
		const garsoExtendedButtonLabel=makeElement('span','','Szukaj');
		garsoExtendedButton.appendChild(garsoExtendedButtonLabel);
		const garsoExtendedOpenButton=makeButton('vm-garso-extended-action');
		garsoExtendedOpenButton.innerHTML='<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 3h7v7M21 3 10 14M10 3H3v18h18v-7"/></svg>';
		const garsoExtendedOpenLabel=makeElement('span','','Otwórz wyniki');
		const garsoExtendedOpenCount=makeElement('span','vm-garso-extended-count');
		garsoExtendedOpenButton.append(garsoExtendedOpenLabel,garsoExtendedOpenCount);
		const garsoExtendedProgress=makeElement('progress','vm-garso-extended-progress');
		garsoExtendedProgress.setAttribute('aria-label','Postęp szukania rozszerzonego');
		const garsoExtendedMessage=makeElement('div','vm-garso-extended-message');
		garsoExtendedMessage.setAttribute('role','status');
		garsoExtendedControls.append(garsoExtendedButton,garsoExtendedOpenButton);
		garsoExtendedBody.append(garsoExtendedControls,garsoExtendedProgress,garsoExtendedMessage);
		garsoExtendedPanel.append(garsoExtendedHead,garsoExtendedRecommendation,garsoExtendedBody);
		footer.appendChild(garsoExtendedPanel);
		panel.appendChild(footer);
		const garsoExtendedCollapsible=makeEscortSummarySectionCollapsible( garsoExtendedPanel, garsoExtendedHead,
			garsoExtendedBody, 'garso-extended', false, false );
		garsoExtendedRecommendation.addEventListener('click',event => {stopUiEvent(event); garsoExtendedCollapsible?.setExpanded(true);});
		let escortiRefreshRun=0;
		let escortPhoneRefreshRun=0;
		let escortPhoneWarningSummary=archiveRuntime ? researchFacts.escortPhoneSummary:null;
		let escortiAllAdUrls=[];
		let escortiPhoneAdUrls=getListCache(adId,'phone-escorti')?.adUrls || [];
		let escortiAddressAdUrls=getListCache(adId,'address-escorti')?.adUrls || [];
		let escortiAdUrlSourcesReady=archiveRuntime;
		let garsoExtendedResults=null;
		let garsoExtendedCheckRunning=false;
		let garsoExtendedCancelToken=null;
		let garsoExtendedOpenRunning=false;
		let garsoExtendedCheckCompleted=false;
		const getGarsoExtendedAdUrls=() => {
			return [...new Set( [adUrl,...escortiPhoneAdUrls,...escortiAddressAdUrls].map(url => {
					const normalized=normalizeEscortiAdUrl(url);
					if (normalized) return normalized;
					const linkedAdId=parseAdIdFromUrl(url);
					return linkedAdId
						? `https://pl.escort.club/anons/${linkedAdId}.html`
						:null;
				}).filter(Boolean) )];
		};
		const getGarsoExtendedPhoneDigits=() => {
			const phones=new Set([normalizeEscortCachedPhone(phone)].filter(Boolean));
			for (const adUrl of escortiAllAdUrls) {
				const linkedAdId=parseAdIdFromUrl(adUrl);
				if (!linkedAdId) continue;
				const data=getStoredEscortAdData(linkedAdId) || getWatchedAdDataMirror(linkedAdId);
				for (const source of [data,data?.visitSnapshot,data?.watchSnapshot]) {
					const digits=getEscortAdPhoneDigits(source);
					if (digits) phones.add(digits);
				}
			}
			return [...phones];
		};
		const getGarsoExtendedPhoneTargets=() => getGarsoExtendedPhoneDigits().map(digits => getGarsoExtendedPhoneTarget(digits));
		const getGarsoExtendedPhoneTermsKey=() => getGarsoExtendedPhoneTargets().map(target => target.searchTerm).sort().join('|');
		const getGarsoExtendedSearchTargets=() => [ ...getGarsoExtendedPhoneTargets(),
			...escortiAllAdUrls.map(adUrl => ({adUrl,searchTerm:buildGarsoAdLinkSearchTerm(parseAdIdFromUrl(adUrl)) || adUrl}))
		];
		const readGarsoFirstPostTrails=async (topicUrl,cancelToken) => {
			const context=getGarsoTopicPageContext(topicUrl);
			if (!context) return {phones:[],adUrls:[]};
			const response=await garsoTopicRequest({method:'GET',url:context.baseUrl,timeout:20000,cancelToken});
			cancelToken?.throwIfCancelled();
			const finalContext=getGarsoTopicPageContext(response.finalUrl || context.baseUrl);
			if (response.status < 200 || response.status >= 400 || finalContext?.id !== context.id) throw new Error(`Garso: nie udało się odczytać pierwszego posta tematu ${context.id}`);
			const doc=parseHtml(response.responseText || '');
			const block=doc.querySelector('.post_block, [id^="post_id_"]');
			if (!block) return {phones:[],adUrls:[]};
			const content=getGarsoPostContent(block);
			const text=cleanPostElement(content);
			const phones=[...new Set(findGarsoPhoneTokens(text).map(token => normalizePhoneReputationPhone(token.value)?.local).filter(Boolean))];
			const adUrls=new Set();
			const addAdUrl=value => {
				try {
					const url=new URL(value,context.baseUrl);
					const normalized=normalizeEscortiAdUrl(url.href);
					if (normalized && parseAdIdFromUrl(normalized)) adUrls.add(normalized);
				} catch {}
			};
			for (const link of content.querySelectorAll('a[href]')) {
				if (link.closest('blockquote,.ipsBlockquote,.citation,.quote,.bbc_quote,.signature')) continue;
				addAdUrl(link.getAttribute('href'));
			}
			for (const match of text.matchAll(/(?:https?:\/\/|www\.|(?:[a-z]{2}\.)?escort\.club\/)[^\s<>"'“”„]+/giu)) {
				addAdUrl(/^https?:\/\//i.test(match[0]) ? match[0]:`https://${match[0]}`);
			}
			return {phones,adUrls:[...adUrls]};
		};
		let garsoExtendedStatusText='';
		let garsoExtendedPhoneScanChecked=false;
		let garsoExtendedCheckedAdUrls=[];
		let hasCachedGarsoExtendedInfo=false;
		let garsoExtendedCachedPhoneTermsKey='';
		let garsoExtendedCachedResults=[];
		const applyGarsoExtendedCache=cached => {
			const matchesMode=cached?.searchMode === 'combined';
			hasCachedGarsoExtendedInfo=!!(matchesMode && cached?.checkedAt);
			garsoExtendedCachedPhoneTermsKey=String(cached?.phoneTermsKey || '');
			garsoExtendedPhoneScanChecked=false;
			garsoExtendedCheckedAdUrls=cached?.adUrls || [];
			garsoExtendedCachedResults=matchesMode
				? (cached?.results || [])
				:[];
			return matchesMode;
		};
		const syncGarsoExtendedAdUrls=() => {
			if (!escortiAdUrlSourcesReady) return;
			escortiAllAdUrls=getGarsoExtendedAdUrls();
			garsoExtendedPhoneScanChecked=hasCachedGarsoExtendedInfo && garsoExtendedCachedPhoneTermsKey === getGarsoExtendedPhoneTermsKey();
		};
		const cachedGarsoExtended=getGarsoExtendedCache(adId);
		applyGarsoExtendedCache(cachedGarsoExtended);
		escortiAllAdUrls=[...new Set([ ...getGarsoExtendedAdUrls(),...(cachedGarsoExtended?.adUrls || []) ].filter(Boolean))];
		garsoExtendedPhoneScanChecked=hasCachedGarsoExtendedInfo && garsoExtendedCachedPhoneTermsKey === getGarsoExtendedPhoneTermsKey();
		const getGarsoExtendedResults=() => garsoExtendedResults ??
			getCachedGarsoExtendedResults(getGarsoExtendedSearchTargets(),garsoExtendedCachedResults);
		updateGarsoExtendedButton=(statusText=null) => {
			if (statusText !== null) garsoExtendedStatusText=statusText;
			const count=escortiAllAdUrls.length;
			const pageCount=getUniqueGarsoResultPages(getGarsoExtendedResults()).length;
			const canSearch=escortiAdUrlSourcesReady && (count > 0 || getGarsoExtendedPhoneTargets().length > 0);
			const completed=(garsoExtendedResults === null
				? hasCachedGarsoExtendedInfo && garsoExtendedPhoneScanChecked:garsoExtendedCheckCompleted) &&
				activeAdsCacheKey(garsoExtendedCheckedAdUrls) === activeAdsCacheKey(escortiAllAdUrls);
			garsoExtendedStatusBadge.hidden=!completed || garsoExtendedCheckRunning ||
				(garsoExtendedResults === null && !cachedGarsoExtended?.fresh);
			garsoExtendedStatusBadge.title=`Szukanie zakończone • ${formatGarsoUniquePageCount(pageCount)}`;
			setResearchControlDisabled(garsoExtendedButton,!garsoExtendedCheckRunning && (!canSearch || garsoExtendedOpenRunning));
			garsoExtendedButton.classList.toggle('-cancel',garsoExtendedCheckRunning);
			garsoExtendedButtonLabel.textContent=garsoExtendedCheckRunning ? 'Przerwij':'Szukaj';
			garsoExtendedButton.title=garsoExtendedCheckRunning ? 'Przerwij szukanie'
				:!escortiAdUrlSourcesReady ? 'Czekam na listę Escorti.pl'
					:!canSearch ? 'Brak dodatkowych anonsów i numeru telefonu':'Wyszukaj na żywo na Garso, ponownie sprawdzając numery i adresy';
			setResearchControlDisabled(garsoExtendedOpenButton,!pageCount || garsoExtendedCheckRunning || garsoExtendedOpenRunning);
			garsoExtendedOpenLabel.textContent=garsoExtendedOpenRunning ? 'Otwieranie…':'Otwórz wyniki';
			garsoExtendedOpenCount.textContent=String(pageCount);
			garsoExtendedOpenButton.title=pageCount
				? `Otwórz ${formatPolishCount(pageCount,'kartę','karty','kart')} z wynikami Garso (bez powtarzających się stron)`
				:'Brak znalezionych stron do otwarcia';
			garsoExtendedProgress.hidden=!garsoExtendedCheckRunning;
			garsoExtendedMessage.textContent=garsoExtendedResults === null && pageCount
				? `Zapisane wyniki: ${formatGarsoUniquePageCount(pageCount)}. „Szukaj” sprawdzi Garsonierę na żywo.`
				:garsoExtendedStatusText || (!escortiAdUrlSourcesReady ? 'Czekam na listę Escorti.pl…'
					:canSearch && garsoExtendedResults === null ? 'Brak wyników w cache. Kliknij „Szukaj”, aby sprawdzić Garsonierę.'
						:canSearch ? '':'Brak dodatkowych anonsów i numeru telefonu do sprawdzenia.');
			updateGarsoExtendedRecommendation();
		};
		updateGarsoExtendedRecommendation=() => {
			const escortiAdCount=escortiAdUrlSourcesReady ? escortiAllAdUrls.length:0;
			const currentScopeCompleted=(garsoExtendedResults === null
				? hasCachedGarsoExtendedInfo && garsoExtendedPhoneScanChecked
				:garsoExtendedCheckCompleted) && activeAdsCacheKey(garsoExtendedCheckedAdUrls) === activeAdsCacheKey(escortiAllAdUrls);
			const show=!archiveRuntime && researchFacts.garsoPhoneCount === 0 && researchFacts.garsoAddressCount === 0 &&
				escortiAdCount > 1 && !currentScopeCompleted && !garsoExtendedCheckRunning && !getGarsoExtendedResults().length;
			garsoExtendedTitle.textContent='Garso - szukanie rozszerzone';
			garsoExtendedTitle.title=garsoExtendedTitleHelp;
			garsoExtendedRecommendation.hidden=!show;
			if (show) garsoExtendedRecommendation.innerHTML=`<strong>💡 Warto sprawdzić szerzej</strong><span>Zwykłe wyszukiwanie po numerze i adresie nic nie znalazło, ale Escorti.pl wskazuje ${escortiAdCount} ${polishAdWord(escortiAdCount)}. Mogę sprawdzić te linki, odszukać inne numery telefonów oraz sprawdzić alternatywne formatowanie numerów.</span>`;
		};
		updateGarsoExtendedRecommendation();
		updateGarsoExtendedButton();
		const runGarsoExtendedAction=async () => {
			if (garsoExtendedCheckRunning) {
				garsoExtendedCancelToken?.cancel('Przerwano szukanie rozszerzone Garso');
				updateGarsoExtendedButton('Przerywanie…');
				return;
			}
			if (garsoExtendedButton.disabled || garsoExtendedOpenRunning) return;
			const targets=getGarsoExtendedSearchTargets().map(target => ({...target,trailDepth:0}));
			const adUrls=[...escortiAllAdUrls];
			const searchedPhones=new Set(getGarsoExtendedPhoneDigits());
			const phoneDepth=new Map([...searchedPhones].map(digits => [digits,0]));
			const searchedTerms=new Set(targets.map(target => target.searchTerm));
			const phoneStandardChecks=new Map();
			const fallbackPhonesQueued=new Set();
			const firstLevelTopicUrls=new Set();
			let trailQueued=false;
			let total=targets.length;
			garsoExtendedCheckRunning=true;
			garsoExtendedCancelToken=createOperationCancelToken('Garso - szukanie rozszerzone');
			refreshButton.disabled=true;
			refreshButton.style.opacity='.55';
			garsoExtendedResults=[...getGarsoExtendedResults()];
			const freshResults=new Map();
			const keepResult=(result,target) => {
				if (result?.status !== 'ok') return;
				garsoExtendedResults=garsoExtendedResults.filter(item => item.searchTerm !== target.searchTerm);
				if (Number(result.count) > 0) {
					const entry={...result,...target};
					garsoExtendedResults.push(entry);
					freshResults.set(target.searchTerm,entry);
				} else {
					freshResults.delete(target.searchTerm);
				}
			};
			garsoExtendedCheckCompleted=false;
			garsoExtendedProgress.max=Math.max(1,total);
			garsoExtendedProgress.value=0;
			let failed=0,phoneErrors=0,trailErrors=0,newPhones=0,newLinks=0,checked=0;
			let finalStatus='';
			const addPhoneTargets=(digits,depth=0,insertAt=null) => {
				digits=normalizeEscortCachedPhone(digits);
				if (!/^\d{9}$/.test(digits || '')) return 0;
				const wasNew=!searchedPhones.has(digits);
				if (wasNew) {searchedPhones.add(digits); newPhones++;}
				if (!phoneDepth.has(digits) || depth < phoneDepth.get(digits)) phoneDepth.set(digits,depth);
				const next=getGarsoExtendedPhoneTarget(digits,depth);
				if (searchedTerms.has(next.searchTerm)) return 0;
				searchedTerms.add(next.searchTerm);
				if (insertAt == null) targets.push(next); else targets.splice(insertAt,0,next);
				return 1;
			};
			const addAdTarget=(adUrl,depth=1) => {
				const normalized=normalizeEscortiAdUrl(adUrl);
				const linkedAdId=parseAdIdFromUrl(normalized);
				const searchTerm=linkedAdId ? buildGarsoAdLinkSearchTerm(linkedAdId):'';
				if (!normalized || !searchTerm || searchedTerms.has(searchTerm)) return false;
				searchedTerms.add(searchTerm);
				targets.push({adUrl:normalized,searchTerm,trailDepth:depth});
				return true;
			};
			const queuePhoneFallbacks=() => {
				let added=0;
				for (const digits of searchedPhones) {
					if (fallbackPhonesQueued.has(digits)) continue;
					const state=phoneStandardChecks.get(digits);
					if (!state || !state.checked) continue;
					fallbackPhonesQueued.add(digits);
					if (state.found || state.error) continue;
					const spaced=`"${digits.replace(/(\d{3})(\d{3})(\d{3})/,'$1 $2 $3')}"`;
					if (searchedTerms.has(spaced)) continue;
					searchedTerms.add(spaced);
					targets.push({adUrl:null,searchTerm:spaced,phoneDigits:digits,phoneSearchKind:'spaces-fallback',trailDepth:phoneDepth.get(digits) || 0});
					added++;
				}
				return added;
			};
			const queueFirstPostTrails=async () => {
				if (trailQueued) return 0;
				trailQueued=true;
				let added=0,scanned=0;
				const topicUrls=[...firstLevelTopicUrls];
				for (const topicUrl of topicUrls) {
					garsoExtendedCancelToken.throwIfCancelled();
					scanned++;
					updateGarsoExtendedButton(`Sprawdzam pierwszy post ${scanned}/${topicUrls.length} • szukam innych numerów i linków`);
					try {
						const trails=await readGarsoFirstPostTrails(topicUrl,garsoExtendedCancelToken);
						for (const digits of trails.phones) added += addPhoneTargets(digits,1);
						for (const trailAdUrl of trails.adUrls) {
							if (addAdTarget(trailAdUrl,1)) {added++; newLinks++;}
							const linkedAdId=parseAdIdFromUrl(trailAdUrl);
							const data=linkedAdId ? (getStoredEscortAdData(linkedAdId) || getWatchedAdDataMirror(linkedAdId)):null;
							for (const source of [data,data?.visitSnapshot,data?.watchSnapshot]) added += addPhoneTargets(getEscortAdPhoneDigits(source),1);
						}
					} catch (error) {
						if (isOperationCancelledError(error) || garsoExtendedCancelToken.cancelled) throw error;
						trailErrors++;
						log(`Garso rozszerzone - pierwszy post ${topicUrl}`,error);
					}
				}
				total=targets.length;
				garsoExtendedProgress.max=Math.max(1,total);
				return added;
			};
			try {
				for (let index=0; index < targets.length; index++) {
					const target=targets[index];
					garsoExtendedCancelToken.throwIfCancelled();
					const label=target.adUrl ? `anons ${parseAdIdFromUrl(target.adUrl)}`:`telefon ${target.searchTerm.replaceAll('"','')}`;
					updateGarsoExtendedButton(`Sprawdzanie ${checked + 1}/${total} • ${label}`);
					const phoneSearch=target.phoneSearchKind === 'standard';
					const result=await checkGarsoTerm(phoneSearch ? target.phoneDigits:target.searchTerm,true,garsoExtendedCancelToken,phoneSearch ? 'phone':'',true,partial => {
						keepResult(partial,target);
						updateGarsoExtendedButton();
					});
					keepResult(result,target);
					garsoExtendedCancelToken.throwIfCancelled();
					if (result?.status === 'ok' && Number(result.count) > 0) {
						if (!target.trailDepth) for (const topic of result.topics || []) {
							const topicContext=getGarsoTopicPageContext(topic?.url);
							if (topicContext?.baseUrl) firstLevelTopicUrls.add(topicContext.baseUrl);
						}
					}
					if (result?.status !== 'ok' || result.topicLinksComplete === false) failed++;
					if (target.phoneDigits && target.phoneSearchKind === 'standard') {
						const state=phoneStandardChecks.get(target.phoneDigits) || {checked:0,found:false,error:false};
						state.checked++;
						state.found ||= result?.status === 'ok' && Number(result.count) > 0;
						state.error ||= result?.status !== 'ok';
						phoneStandardChecks.set(target.phoneDigits,state);
					}
					if (target.adUrl) {
						try {
							const linkedAdId=parseAdIdFromUrl(target.adUrl);
							updateGarsoExtendedButton(`Sprawdzanie ${checked + 1}/${total} • odczyt telefonu anonsu ${linkedAdId}`);
							const activity=await inspectEscortClubAdActivityOnly(target.adUrl,garsoExtendedCancelToken);
							garsoExtendedCancelToken.throwIfCancelled();
							if (activity.unknown) throw new Error(activity.error || 'Nie udało się sprawdzić aktywności');
							if (activity.active) {
								const data=await getEscortAdData(linkedAdId,target.adUrl,true,garsoExtendedCancelToken);
								garsoExtendedCancelToken.throwIfCancelled();
								const digits=data?.status === 'ok' ? getEscortAdPhoneDigits(data):null;
								if (!digits) throw new Error('Nie udało się odczytać aktualnego numeru');
								addPhoneTargets(digits,target.trailDepth || 0,index + 1);
								total=targets.length;
								garsoExtendedProgress.max=Math.max(1,total);
							}
						} catch (error) {
							if (isOperationCancelledError(error) || garsoExtendedCancelToken.cancelled) throw error;
							phoneErrors++;
							log(`Garso rozszerzone - odczyt telefonu anonsu ${parseAdIdFromUrl(target.adUrl)}`,error);
						}
					}
					checked++;
					garsoExtendedProgress.value=checked;
					if (index === targets.length - 1) {
						if (queuePhoneFallbacks()) {
							total=targets.length;
							garsoExtendedProgress.max=Math.max(1,total);
						} else if (!trailQueued) {
							await queueFirstPostTrails();
						}
					}
				}
				garsoExtendedCheckCompleted=failed === 0 && phoneErrors === 0 && trailErrors === 0;
				if (garsoExtendedCheckCompleted) {
					garsoExtendedResults=[...freshResults.values()];
					setGarsoExtendedCache(adId,adUrls,garsoExtendedResults,'combined',getGarsoExtendedPhoneTermsKey());
					garsoExtendedCachedResults=garsoExtendedResults.map(result => ({...result,signature:getGarsoExtendedResultSignature(result)}));
					hasCachedGarsoExtendedInfo=true;
					garsoExtendedPhoneScanChecked=true;
					garsoExtendedCheckedAdUrls=adUrls;
				}
				finalStatus=(failed || phoneErrors || trailErrors ? `Sprawdzono ${total - failed}/${total}`:`Zakończono ${total}/${total}`) +
					` • Znalezione nowe numery telefonów: ${newPhones}` + (newLinks ? ` • nowe linki: ${newLinks}`:'') +
					(failed ? ` • błędy Garso: ${failed}`:'') + (phoneErrors ? ` • nieodczytane telefony: ${phoneErrors}`:'') + (trailErrors ? ` • nieodczytane pierwsze posty: ${trailErrors}`:'');
			} catch (error) {
				if (isOperationCancelledError(error) || garsoExtendedCancelToken.cancelled) {
					finalStatus=`Przerwano • sprawdzono ${checked}/${total}`;
				} else {
					log('Błąd szukania rozszerzonego Garso',error);
					finalStatus=`Błąd • sprawdzono ${checked}/${total}`;
				}
			} finally {
				garsoExtendedCheckRunning=false;
				garsoExtendedCancelToken=null;
				refreshButton.disabled=false;
				refreshButton.style.opacity='1';
				updateGarsoExtendedButton(`${finalStatus} • ${formatGarsoUniquePageCount(getUniqueGarsoResultPages(garsoExtendedResults).length)}`);
			}
		};
		garsoExtendedButton.addEventListener('click',event => {
			stopUiEvent(event);
			void runGarsoExtendedAction();
		});
		garsoExtendedOpenButton.addEventListener('click',async event => {
			stopUiEvent(event);
			if (garsoExtendedOpenButton.disabled) return;
			garsoExtendedOpenRunning=true;
			updateGarsoExtendedButton('Otwieram strony wyników…');
			let status='';
			try {
				const opened=await openUniqueGarsoResultPages(getGarsoExtendedResults());
				status=`Otwarto ${formatPolishCount(opened,'kartę','karty','kart')} z wynikami.`;
			} catch (error) {
				log('Nie udało się otworzyć wyników rozszerzonego Garso',error);
				status='Nie udało się otworzyć wyników. Spróbuj ponownie.';
			} finally {
				garsoExtendedOpenRunning=false;
				updateGarsoExtendedButton(status);
			}
		});
		const renderWarnings=() => {
			const sections=getResearchConsistencySections(researchFacts);
			const hasWarnings=sections.length > 0;
			warningBox.classList.toggle( 'vm-escort-summary-status-alert', hasWarnings );
			warningBox.classList.toggle( 'vm-escort-summary-status-ok', !hasWarnings );
			const labels=[...new Set(sections.flatMap(section => section.warnings)
				.filter(warning => !warning.startsWith('Różne parametry:'))
				.map(warning => warning.split(':')[0].replace(/^Różn[ey] /,'').replace(/^tagi$/,'usługi/tagi')))];
			labels.push(...getEscortStatDifferenceLabels([...new Set(sections.flatMap(section => section.statFields))]));
			const comparable=[researchFacts.escortiAddressSummary, researchFacts.escortiPhoneSummary,escortPhoneWarningSummary]
				.some(summary => (summary?.results || []).filter(item => item.active && item.adData).length >= 2);
			warningPreview.textContent=hasWarnings
				? `⚠ Znaleziono różnice w powiązanych anonsach: ${labels.join(', ')}.`
				:comparable ? 'Nie znaleziono różnic w porównanych anonsach.'
					:archiveRuntime ? 'Brak wystarczających danych w cache do porównania.':'Tylko jedno ogłoszenie';
			warningPreview.classList.toggle('-warning',hasWarnings);
			warningStatusBadge.textContent=hasWarnings ? '⚠︎':comparable ? '✓ brak':'';
			warningStatusBadge.hidden=!warningStatusBadge.textContent;
			warningStatusBadge.title=warningPreview.textContent;
			warningContent.replaceChildren();
			if (!sections.length) {
				const emptyLine=makeElement('div','vm-inconsistencies-empty',warningPreview.textContent);
				warningContent.appendChild(emptyLine);
				return;
			}
			const getWarningIcon=label => {
				if (/miast/i.test(label)) return '📍';
				if (/wiek/i.test(label)) return 'W';
				if (/parametr/i.test(label)) return '≠';
				if (/cen/i.test(label)) return 'zł';
				if (/godzin/i.test(label)) return '◷';
				if (/tag/i.test(label)) return '#';
				return '!';
			};
			const getDifferenceWord=count => count === 1
				? 'różnica'
				:(count >= 2 && count <= 4 ? 'różnice':'różnic');
			sections.forEach(section => {
				const sourceCard=makeElement('section','vm-inconsistencies-source-card');
				const sourceHead=makeElement('div','vm-inconsistencies-source-head');
				const sourceMarker=makeElement('span','vm-inconsistencies-source-marker','!');
				const sourceCopy=makeElement('span');
				sourceCopy.style.minWidth='0';
				const sourceTitle=makeElement('span','vm-inconsistencies-source-title',section.title);
				const sourceSubtitle=makeElement('span','vm-inconsistencies-source-subtitle',section.subtitle);
				sourceCopy.append(sourceTitle,sourceSubtitle);
				const sourceCount=makeElement('span','vm-inconsistencies-source-count',`${section.warnings.length} ${getDifferenceWord(section.warnings.length)}`);
				sourceHead.append( sourceMarker, sourceCopy, sourceCount );
				sourceCard.appendChild(sourceHead);
				const warningList=makeElement('div','vm-inconsistencies-warning-list');
				for (const warning of section.warnings) {
					const line=makeElement('div','vm-inconsistencies-warning-row');
					const separator=warning.indexOf(':');
					const label=separator >= 0
						? warning.slice(0,separator).trim()
						:warning.trim();
					const detail=separator >= 0
						? warning.slice(separator + 1).trim()
						:'';
					const icon=makeElement('span','vm-inconsistencies-warning-icon',getWarningIcon(label));
					const copy=makeElement('span');
					const labelLine=makeElement('span','vm-inconsistencies-warning-label',label);
					copy.appendChild(labelLine);
					if (detail) {
						const detailLine=makeElement('span','vm-inconsistencies-warning-detail',detail);
						copy.appendChild(detailLine);
					}
					line.append(icon,copy);
					warningList.appendChild(line);
				}
				sourceCard.appendChild(warningList);
				warningContent.appendChild(sourceCard);
			});
		};
		renderWarnings();
		const renderArchiveCachedResearchRows=() => {
			if (!archiveRuntime) return;
			const phoneResult=researchFacts.escortiPhoneResult;
			const addressResult=researchFacts.escortiAddressResult;
			const sameResult=researchFacts.sameEscortiResult === true;
			const phoneSummary=researchFacts.escortiPhoneSummary;
			const addressSummary=researchFacts.escortiAddressSummary;
			escortiAddressRow.style.display='flex';
			escortiPhoneRow.style.display=(phoneResult || phoneSummary) && !sameResult ? 'flex':'none';
			renderEscortArchiveResearchRow(escortiAddressRow,sameResult ? 'Escorti.pl':'Escorti.pl - adres anonsu',addressResult,addressSummary);
			renderEscortArchiveResearchRow(escortiPhoneRow,'Escorti.pl - nr tel.',phoneResult,phoneSummary);
			renderEscortArchiveResearchRow(escortPhoneRow,'Escort - nr tel.',researchFacts.escortPhoneResult,researchFacts.escortPhoneSummary,false);
		};
		const refreshEscorti=async (forceRefresh=false) => {
			const run=++escortiRefreshRun;
			const requestContext=createRequestContext('research-escorti');
			researchFacts.escortiLoading=true;
			renderResearchFacts();
			updateGarsoExtendedButton( garsoExtendedResults === null ? 'Pobieram listę Escorti.pl…':null );
			const cachedActivity=getLatestCachedEscortiActivitySummary( adId, ['phone-escorti','address-escorti'] );
			escortiPhoneRow.style.display='none';
			escortiAddressRow.style.display='flex';
			setButtonColor(escortiAddressRow,cachedActivity ? 'stale':'checking');
			setTwoLineButton( escortiAddressRow, 'Escorti.pl', cachedActivity
					? `${cachedActivity.activeCount} aktywnych • odświeżanie…`
					:'szukam powiązań…' );
			try {
				const [phoneData,addressData]=await Promise.all([ getEscortiDetailForSingleAd( adId, phoneSearch, 'phone-escorti',
						forceRefresh ), getEscortiDetailForSingleAd( adId, adUrl, 'address-escorti', forceRefresh ) ]);
				if (run !== escortiRefreshRun) return;
				const phoneResult=phoneData.result;
				const addressResult=addressData.result;
				const sameResult=areSameEscortiResults(phoneResult,addressResult);
				const phoneAdUrls=phoneResult?.status === 'ok'
					? [...new Set(phoneResult.adUrls || [])]
					:[];
				const addressAdUrls=addressResult?.status === 'ok'
					? [...new Set(addressResult.adUrls || [])]
					:[];
				escortiPhoneAdUrls=phoneAdUrls;
				escortiAddressAdUrls=addressAdUrls;
				escortiAdUrlSourcesReady=true;
				syncGarsoExtendedAdUrls();
				const adUrls=[...new Set( [...phoneAdUrls,...addressAdUrls] )];
				updateGarsoExtendedButton(garsoExtendedResults === null ? '':null);
				researchFacts.escortiPhoneResult=phoneResult;
				researchFacts.escortiAddressResult=addressResult;
				researchFacts.sameEscortiResult=sameResult;
				researchFacts.escortiPhoneSummary= getCachedEscortiActivitySummary( adId, 'phone-escorti', phoneAdUrls );
				researchFacts.escortiAddressSummary= getCachedEscortiActivitySummary( adId, 'address-escorti', addressAdUrls );
				renderResearchFacts();
				const activityRows=[{row:escortiAddressRow,title:sameResult ? 'Escorti.pl':'Escorti.pl - adres anonsu',
					result:addressResult,urls:addressAdUrls,summary:researchFacts.escortiAddressSummary}];
				if (!sameResult) activityRows.push({row:escortiPhoneRow,title:'Escorti.pl - nr tel.',
					result:phoneResult,urls:phoneAdUrls,summary:researchFacts.escortiPhoneSummary});
				escortiAddressRow.style.display='flex';
				escortiPhoneRow.style.display=sameResult ? 'none':'flex';
				for (const entry of activityRows) {
					setTwoLineButton(entry.row,entry.title,entry.summary
						? formatEscortiAdsAndActivity(entry.result,entry.summary):'sprawdzam dostępność…');
					setButtonColor(entry.row,entry.summary ? 'stale':'checking');
				}
				if (!adUrls.length) {
					const emptySummary=summarizeActiveAdResults([]);
					researchFacts.escortiPhoneSummary=emptySummary;
					researchFacts.escortiAddressSummary=emptySummary;
					setButtonColor(escortiAddressRow,'empty');
					setTwoLineButton( escortiAddressRow, sameResult ? 'Escorti.pl':'Escorti.pl - adres anonsu',
						formatEscortiAdsAndActivity(addressResult,emptySummary) );
					if (!sameResult) {
						setButtonColor(escortiPhoneRow,'empty');
						setTwoLineButton( escortiPhoneRow, 'Escorti.pl - nr tel.', formatEscortiAdsAndActivity(phoneResult,emptySummary)
						);
					}
					researchFacts.escortiWarnings=[];
					renderWarnings();
					renderResearchFacts();
					return;
				}
				const detailedUrls=new Set();
				const summary=await scanActiveEscortAds( adUrls, progress => {
						if (run !== escortiRefreshRun) return;
						if (progress.summary) {
							researchFacts.escortiPhoneSummary=summarizeActiveAdsSubset(progress.summary,phoneAdUrls);
							researchFacts.escortiAddressSummary=summarizeActiveAdsSubset(progress.summary,addressAdUrls);
							activityRows[0].summary=researchFacts.escortiAddressSummary;
							if (!sameResult) activityRows[1].summary=researchFacts.escortiPhoneSummary;
							renderResearchFacts();
						}
						if (activityRows.some(entry => entry.summary)) {
							if (progress.phase === 'details' && progress.url) detailedUrls.add(normalizeEscortiAdUrl(progress.url) || progress.url);
							for (const entry of activityRows) {
								if (!entry.summary) continue;
								const checked=entry.urls.filter(url => detailedUrls.has(normalizeEscortiAdUrl(url) || url)).length;
								const detailsTotal=entry.urls.length;
								const pending=progress.phase !== 'details'
									? (progress.checked < progress.total ? ` • dostępność ${progress.checked}/${progress.total}…`:'')
									:checked < detailsTotal ? ` • dane ${checked}/${detailsTotal}`:'';
								setTwoLineButton(entry.row,entry.title,formatEscortiAdsAndActivity(entry.result,entry.summary) + pending);
								setButtonColor(entry.row,entry.summary.staleCount || entry.summary.unknownCount ||
									entry.summary.totalChecked < Number(entry.result?.adLinks) ? 'stale':entry.summary.activeCount > 0 ? 'found':'empty');
							}
							return;
						}
						const progressText=`${progress.phase === 'details' ? 'dane':'dostępność'} ${progress.checked}/${progress.total}…`;
						setTwoLineButton( escortiAddressRow, sameResult ? 'Escorti.pl':'Escorti.pl - adres anonsu', progressText );
						if (!sameResult) {
							setTwoLineButton( escortiPhoneRow, 'Escorti.pl - nr tel.', progressText );
						}
					}, forceRefresh, {headFirst:true,cacheActivity:true,requestContext} );
				if (run !== escortiRefreshRun) return;
				const phoneSummary=summarizeActiveAdsSubset(summary,phoneAdUrls);
				const addressSummary=summarizeActiveAdsSubset(summary,addressAdUrls);
				saveEscortiActivitySummary( adId, ['phone-escorti'], phoneAdUrls, phoneSummary );
				saveEscortiActivitySummary( adId, ['address-escorti'], addressAdUrls, addressSummary );
				researchFacts.escortiPhoneSummary=phoneSummary;
				researchFacts.escortiAddressSummary=addressSummary;
				activityRows[0].summary=addressSummary;
				if (!sameResult) activityRows[1].summary=phoneSummary;
				for (const entry of activityRows) {
					setButtonColor(entry.row,entry.summary.staleCount ? 'stale':entry.summary.activeCount > 0 ? 'found':'empty');
					setTwoLineButton(entry.row,entry.title,formatEscortiAdsAndActivity(entry.result,entry.summary));
					const title=sameResult ? 'Wyszukiwanie Escorti.pl po adresie anonsu i nr tel. zwróciło ten sam wynik.'
						:entry.row === escortiAddressRow ? 'Escorti.pl po adresie anonsu.':'Escorti.pl po nr tel.';
					entry.row.title=buildActiveAdsTitle(title,entry.result,entry.summary);
				}
				researchFacts.escortiWarnings= getEscortActiveAdsConsistencyWarnings(summary);
				renderWarnings();
				renderResearchFacts();
			} catch (error) {
				if (run !== escortiRefreshRun) return;
				updateGarsoExtendedButton('nie udało się pobrać listy Escorti');
				log('Błąd odświeżania panelu powiązanych wyników',error);
				if (cachedActivity) {
					setButtonColor(escortiAddressRow,'stale');
					setTwoLineButton( escortiAddressRow, 'Escorti.pl', `${cachedActivity.activeCount} aktywnych` );
				} else {
					setButtonColor(escortiAddressRow,'error');
					setTwoLineButton(escortiAddressRow,'Escorti.pl','błąd');
				}
				renderResearchFacts();
			} finally {
				if (run === escortiRefreshRun) {
					researchFacts.escortiLoading=false;
					renderResearchFacts();
				}
			}
		};
		const refreshEscortPhone=async (forceRefresh=false) => {
			const run=++escortPhoneRefreshRun;
			researchFacts.escortPhoneLoading=true;
			renderResearchFacts();
			setButtonColor(escortPhoneRow,'checking');
			setTwoLineButton(escortPhoneRow,'Escort - nr tel.','wyszukiwanie…');
			try {
				const search=await fetchEscortClubPhoneSearchAds(phone);
				if (run !== escortPhoneRefreshRun) return;
				setListCache(adId,{status:'ok',profileUrls:[],adUrls:search.adUrls},'phone-escort-search');
				escortPhoneRow.dataset.searchUrl=search.searchUrl;
				const total=search.adUrls.length;
				let completed=0;
				const dataResults=await Promise.all(search.adUrls.map(async url => {
					const result=await getEscortAdData( parseAdIdFromUrl(url), url, forceRefresh, null, null, {needPhone:false} );
					completed++;
					if (run === escortPhoneRefreshRun && completed < total) {
						setTwoLineButton( escortPhoneRow, 'Escort - nr tel.',
							`${total} ${polishAdWord(total)} • dane ${completed}/${total}…` );
					}
					return {url,result};
				}));
				if (run !== escortPhoneRefreshRun) return;
				const results=dataResults .filter(item => item.result?.status === 'ok') .map(item => ({
						url:item.url, active:true, city:item.result.location?.city || null, adData:item.result
				}));
				escortPhoneWarningSummary=summarizeActiveAdResults(results);
				researchFacts.escortPhoneSummary=escortPhoneWarningSummary;
				renderWarnings();
				renderResearchFacts();
				setButtonColor(escortPhoneRow,total > 0 ? 'found':'empty');
				setTwoLineButton( escortPhoneRow, 'Escort - nr tel.', `${total} ${polishAdWord(total)}` );
				escortPhoneRow.title= `Aktualnie znalezione na Escort.club anonse z tym samym numerem telefonu: ${total}.`;
			} catch (error) {
				if (run !== escortPhoneRefreshRun) return;
				log('Błąd wyszukiwania numeru telefonu na Escort.club',error);
				setButtonColor(escortPhoneRow,'error');
				setTwoLineButton(escortPhoneRow,'Escort - nr tel.','błąd');
			} finally {
				if (run === escortPhoneRefreshRun) {
					researchFacts.escortPhoneLoading=false;
					renderResearchFacts();
				}
			}
		};
		const refreshPhotoDateRange=async () => {
			researchFacts.photoDatesLoading=true;
			renderResearchFacts();
			try {
				researchFacts.photoRange=await getEscortGalleryPhotoDateRange(document);
				const stored=getStoredEscortAdData(adId),oldestPhotoDate=researchFacts.photoRange?.oldest?.getTime();
				if (stored && oldestPhotoDate) {
					const saved={...stored,oldestPhotoDate};
					writePersistentCacheValue(escortAdDataCacheKey(adId),saved);
					escortAdDataMemoryCache.set(String(adId),saved);
					storeWatchedAdDataMirror(adId,saved);
				}
			} catch (error) {
				researchFacts.photoRange=null;
				log('Nie udało się przygotować zakresu dat zdjęć',error);
			} finally {
				researchFacts.photoDatesLoading=false;
				renderResearchFacts();
			}
		};
		const refreshAll=async () => {
			if (refreshButton.disabled) return;
			setResearchRefreshState(refreshButton,true,'Pobieranie danych…');
			try {
				await Promise.allSettled([ updateGarsoStatus( garsoPhoneRow, garsoPhoneTerm, 'phone', true, true, false ),
					updateGarsoStatus( garsoAddressRow, garsoAddressTerm, 'address', true, true, false ), (async () => {
						await refreshEscorti(true);
						await refreshEscortPhone(true);
					})(), refreshPhotoDateRange(), phoneReputationLinksUi?.refresh(true) ]);
			} finally {
				setResearchRefreshState( refreshButton, false,
					'Pobierz ponownie dane z sekcji „Linki” (Garsoniera: tylko sprawdzenie, czy są tematy; sprawdzanie numeru: jeśli włączone automatyczne wyszukiwanie)'
				);
			}
		};
		const loadGarsoTopicsForContent=(row,term,mode,cancelToken,forceRefresh=false) =>
			updateGarsoStatus(row,term,mode,forceRefresh,true,false,cancelToken);
		const refreshGarsoContent=async (forceRefresh=true) => {
			if (garsoDownloadButton.disabled) return;
			const operationCancelToken=createOperationCancelToken( 'Pełna analiza Garso' );
			garsoCombinedSummaryState._vmGarsoSummaryCancelToken= operationCancelToken;
			if (forceRefresh) {
				garsoSummaryCard.collapsible?.setExpanded(true);
			}
			setResearchRefreshState( garsoDownloadButton, true, 'Pobieranie i analizowanie tematów Garsoniery…' );
			const previousResult=garsoCombinedSummaryState._vmGarsoSummaryResult || null;
			const previousCheckedAt=Number( garsoCombinedSummaryState.dataset.garsoSummaryCheckedAt ) || null;
			let failureMessage='';
			if (previousResult) {
				setGarsoSummaryState( garsoCombinedSummaryState, 'refreshing', summaryToHtml(previousResult), {
						checkedAt:previousCheckedAt, cacheState:'refreshing'
					} );
			} else {
				setGarsoSummaryState( garsoCombinedSummaryState, 'loading', '<b>Garso</b><br>Łączenie list tematów…' );
			}
			try {
				const [phoneResult,addressResult]=await Promise.all([ loadGarsoTopicsForContent( garsoPhoneRow, garsoPhoneTerm,
						'phone', operationCancelToken, forceRefresh ), loadGarsoTopicsForContent( garsoAddressRow, garsoAddressTerm,
						'address', operationCancelToken, forceRefresh ) ]);
				const failed=[phoneResult,addressResult].filter(result => result?.status !== 'ok');
				if (failed.length) {
					const messages=[...new Set(failed.map(result => result?.errorMessage).filter(Boolean))];
					const error=new Error(messages.join(' | ') || 'Nie udało się pobrać obu list tematów Garso');
					error.garsoAlreadyLogged=failed.every(result => result?.errorLogged);
					throw error;
				}
				const combinedTopics=mergeUniqueGarsoTopics( phoneResult.topics || [], addressResult.topics || [] );
				await prepareGarsoSummary( garsoCombinedSummaryState, garsoCombinedSummaryTerm, combinedTopics, forceRefresh,
					operationCancelToken );
				if (forceRefresh) {
					garsoSummaryCard.collapsible?.setExpanded(true);
				}
			} catch (error) {
				if (isOperationCancelledError(error)) {
					garsoCombinedSummaryState._vmGarsoSummaryResult= previousResult;
					setGarsoSummaryState( garsoCombinedSummaryState, 'cancelled', previousResult ? summaryToHtml(previousResult):'', {
							checkedAt:previousCheckedAt, cacheState:previousResult ? 'stale':null
						} );
				} else {
					failureMessage=String(error?.message || 'Nie udało się przygotować analizy Garso');
					if (!error?.garsoAlreadyLogged) log('Błąd wspólnej analizy Garso',error);
					if (previousResult) {
						applySummaryToButton( garsoCombinedSummaryState, previousResult, {
								checkedAt:previousCheckedAt, cacheState:'stale'
							} );
					} else {
						setGarsoSummaryState( garsoCombinedSummaryState, 'error', '' );
					}
				}
			} finally {
				if ( garsoCombinedSummaryState._vmGarsoSummaryCancelToken === operationCancelToken ) {
					garsoCombinedSummaryState._vmGarsoSummaryCancelToken=null;
				}
				setResearchRefreshState( garsoDownloadButton, false,
					failureMessage || 'Odśwież i przeanalizuj treść tematów Garsoniery dla numeru telefonu i adresu anonsu.' );
			}
		};
		const initializeGarsoChecks=() => {
			if (!SETTINGS.autoGarsoCheck) return;
			const analyzeContent=normalizeAutoGarsoAnalysisMode( SETTINGS.autoGarsoAnalysisMode ) === 'content';
			if (analyzeContent && !cachedCombinedGarsoSummary?.recentCheck) {
				refreshGarsoContent(false);
				return;
			}
			Promise.allSettled([ updateGarsoStatus( garsoPhoneRow, garsoPhoneTerm, 'phone', false, true, false ),
				updateGarsoStatus( garsoAddressRow, garsoAddressTerm, 'address', false, true, false ) ]).then(() => {
				if (garsoCombinedSummaryState.dataset.garsoSummaryState) return;
				if ( garsoPhoneRow.dataset.garsoStatus !== 'result' || garsoAddressRow.dataset.garsoStatus !== 'result' ) return;
				const checkedAt=Math.max( Number(garsoPhoneRow.dataset.garsoCountCheckedAt) || 0,
					Number(garsoAddressRow.dataset.garsoCountCheckedAt) || 0 );
				setGarsoSummaryState( garsoCombinedSummaryState, 'count-only', '', {
						checkedAt:checkedAt || Date.now(), cacheState:'fresh'
					} );
			});
		};
		refreshButton.addEventListener('click',event => {
			stopUiEvent(event);
			refreshAll();
		});
		garsoDownloadButton.addEventListener('click',event => {
			stopUiEvent(event);
			refreshGarsoContent(true);
		});
		garsoDownloadButton.addEventListener('keydown',event => {
			event.stopPropagation();
		});
		if (archiveRuntime) {
			renderArchiveCachedResearchRows();
			renderWarnings();
			renderResearchFacts();
			refreshButton.disabled=true;
			setControlTitle(refreshButton,'Widok archiwalny korzysta z zapisanego podsumowania z cache.');
			refreshButton.style.opacity='.45';
			garsoDownloadButton.disabled=true;
			setControlTitle(garsoDownloadButton,'Widok archiwalny korzysta z zapisanej analizy Garso z cache.');
			garsoDownloadButton.style.opacity='.45';
		} else {
			setTimeout(() => {
				if (!panel.isConnected) return;
				initializeGarsoChecks();
				refreshEscorti(false);
				refreshEscortPhone(false);
				refreshPhotoDateRange();
			},50);
		}
		return panel;
	}
	function initEscortGarsoAutoChecks() {
		const adId=getEscortAdIdFromPath();
		if (!IS_ESCORT_CLUB || window.top!==window.self || !adId || !SETTINGS.autoGarsoCheck || isEscortCachedArchiveRuntime()) return;
		const start=(mode,term)=>{
			const key=garsoSearchCountCacheKey(mode,term);
			if (!term || !key || garsoAutoSearchJobs.has(key)) return;
			const keys=[garsoSearchCountCacheKey(mode,term),garsoSummaryCacheKey(mode,term)].filter(Boolean);
			if (keys.some(key => !persistentCacheKeyIsLoaded(key))) {ensurePersistentCacheKeys(keys).then(() => start(mode,term)).catch(error => log('Nie udało się odczytać cache Garso',error)); return;}
			const cached=getGarsoSearchCountCache(mode,term);
			if (cached?.fresh && cached.topicLinksFresh) return;
			const job={cancelToken:createOperationCancelToken('Automatyczne sprawdzanie Garso')};
			job.promise=checkGarsoTerm(term,true,job.cancelToken,mode).then(result=>{
				job.cancelToken.throwIfCancelled(); job.checkedAt=cacheGarsoSearchResult(mode,term,result);
				job.result=result; job.completedAt=Date.now(); return result;
			});
			garsoAutoSearchJobs.set(key,job);
			job.promise.catch(()=>{if(garsoAutoSearchJobs.get(key)===job)garsoAutoSearchJobs.delete(key);});
		};
		start('address',buildGarsoAdLinkSearchTerm(adId));
		let observer=null;
		const checkPhone=()=>{
			if (!SETTINGS.autoGarsoCheck) {observer?.disconnect(); return true;}
			const phone=getFormattedPhoneNumber();
			if (!phone) return false;
			start('phone',getGarsoSearchTerms(phone,'phone')[0]); observer?.disconnect(); return true;
		};
		if (!checkPhone()) {
			observer=new MutationObserver(checkPhone);
			observer.observe(document.body || document.documentElement,{subtree:true,childList:true,characterData:true,attributes:true,attributeFilter:['href','data-phone']});
		}
		window.addEventListener('pagehide',event=>{
			if(event.persisted)return; observer?.disconnect();
			for(const job of garsoAutoSearchJobs.values())job.cancelToken.cancel('Karta została zamknięta.');
		});
	}
	const buttonsAdded={panel:false};
	function ensureEscortResearchPanel(phone,adUrl,container) {
		if (buttonsAdded.panel || !adUrl) return;
		const id=parseAdIdFromUrl(adUrl);
		if (persistentCacheUsesIndexedDb && getPersistentGarsoKeys(id,phone).some(key => !persistentCacheKeyIsLoaded(key))) {
			if (!buttonsAdded.loading) {buttonsAdded.loading=true; ensureEscortPageCache([id],phone).then(() => {buttonsAdded.loading=false; checkAndAddButtons();}).catch(error => {buttonsAdded.loading=false; log('Nie udało się odczytać danych panelu',error);});}
			return;
		}
		const researchPanel=createEscortResearchPanel(phone,adUrl);
		placeEscortResearchPanelAboveActionButtons(researchPanel,container);
		buttonsAdded.panel=true;
	}
	function checkAndAddButtons() {
		if (!SETTINGS.showSummarySidePanel) {
			removeEscortSummarySidePanel();
		}
		if (!SETTINGS.showCompactSummaryAboveDescription) {
			detachHelperNode(document.getElementById( ESCORT_COMPACT_SUMMARY_ID ));
		}
		const phoneLink=getEscortPhoneLink();
		const container= getEscortButtonsContainer(phoneLink);
		if (!container) return;
		const adId=getAdIdFromUrl();
		const adUrl=adId
			? `https://pl.escort.club/anons/${adId}.html`
			:null;
		const archiveRuntime= isEscortCachedArchiveRuntime();
		if (!archiveRuntime) {
			const phone= getFormattedPhoneNumber(phoneLink);
			if (phone) {
				renderEscortPhoneCopyButton(phone);
				ensureEscortResearchPanel(phone,adUrl,container);
			} else if ( phoneLink && phoneLink.getAttribute('href') === '#' && !phoneLink.dataset.scriptClicked ) {
				phoneLink.click();
				phoneLink.dataset.scriptClicked= 'true';
			}
			return;
		}
		const archiveData= adId
				? getStoredEscortAdData(adId)
				:null;
		const phone= getFormattedPhoneNumber(phoneLink) || normalizeEscortCachedPhone( archiveData?.phone ) || '';
		if (phone) {
			renderEscortPhoneCopyButton(phone);
		}
		ensureEscortResearchPanel(phone,adUrl,container);
	}
	const persistentReadBridgeTabs=new Map();
	function persistentCacheReadTabKey(host) {return 'vm_helper_cache_read_tab_' + host;}
	function findPendingPersistentCacheReadToken(host) {
		for (const key of GM_listValues()) {
			if (!key.startsWith(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX)) continue;
			const request=GM_getValue(key,null);
			if (request?.action !== 'read-ad-data' || request.host !== host ||
				!(Date.now() - Number(request.createdAt) < 15000)) continue;
			const token=key.slice(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX.length);
			const response=GM_getValue(persistentCacheBridgeResponseKey(token),null);
			if (response?.protocol !== 1 || response.working) return token;
		}
		return '';
	}
	function closePersistentCacheReadTab(host,state) {
		clearInterval(state.timer);
		state.timer=null;
		try {state.tab?.close?.();} catch {}
		const key=persistentCacheReadTabKey(host),ownerKey=persistentCacheReaderKey(host);
		if (GM_getValue(key,null)?.token === state.token) deleteHelperStorageValue(key);
		const owner=GM_getValue(ownerKey,null);
		if (owner?.auxiliary && owner.token === state.token) deleteHelperStorageValue(ownerKey);
		state.tab=null;
		state.token='';
	}
	async function ensurePersistentCacheReadTab(host,token,unresponsiveSession='') {
		let state=persistentReadBridgeTabs.get(host);
		if (!state) {
			state={opening:null,tab:null,timer:null,token:''};
			persistentReadBridgeTabs.set(host,state);
		}
		if (state.tab?.closed) closePersistentCacheReadTab(host,state);
		const ready=() => {const value=getReadyPersistentCacheReader(host); return value && value.session !== unresponsiveSession;};
		if (ready()) return;
		if (state.opening) return state.opening;
		if (state.tab) return;
		const current=GM_getValue(persistentCacheReadTabKey(host),null);
		if (current?.token && Date.now() - current.at < 45000) return;
		state.opening=(async () => {
			const key=persistentCacheReadTabKey(host);
			// Dzierżawa obejmuje też uruchamianie karty przed zgłoszeniem gotowości.
			writeHelperStorageValue(key,{token,at:Date.now()});
			await new Promise(resolve => setTimeout(resolve,60));
			if (GM_getValue(key,null)?.token !== token) return;
			if (ready()) {deleteHelperStorageValue(key); return;}
			const pending=findPendingPersistentCacheReadToken(host);
			if (!pending) {deleteHelperStorageValue(key); return;}
			token=pending;
			state.token=token;
			writeHelperStorageValue(key,{token,at:Date.now()});
			try {
				state.tab=GM_openInTab(`https://${host}/?${PERSISTENT_CACHE_BRIDGE_PARAM}=${encodeURIComponent(token)}`,{active:false,insert:true,setParent:true});
				if (!state.tab) throw new Error('Nie ma gotowej karty obsługującej cache. Otwórz Escort.club i ponów odczyt.');
				const tab=state.tab,openedAt=Date.now();
				let idleSince=0;
				state.timer=setInterval(() => {
					if (state.tab !== tab) return;
					const now=Date.now(),pending=findPendingPersistentCacheReadToken(host);
					if (pending) idleSince=0;
					else if (!idleSince) idleSince=now;
					if (tab.closed || (idleSince && now - idleSince >= 2000) ||
						(now - openedAt >= 20000 && !getReadyPersistentCacheReader(host))) {
						closePersistentCacheReadTab(host,state);
						return;
					}
					const lease=GM_getValue(key,null);
					if (pending && lease?.token === token && now - lease.at >= 10000) writeHelperStorageValue(key,{token,at:now});
				},1000);
			} catch (error) {
				closePersistentCacheReadTab(host,state);
				throw error;
			}
		})();
		try {await state.opening;} finally {state.opening=null;}
	}

	const persistentReadBridgeJobs=new Map();
	let persistentCacheReaderInstalled=false,persistentCacheReaderBusy=0;
	function persistentCacheReaderKey(host) {return PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX + 'reader_' + host;}
	function getReadyPersistentCacheReader(host) {
		const value=GM_getValue(persistentCacheReaderKey(host),null);
		return value?.protocol === 1 && value.host === host && value.ready && Date.now() - value.at < 15000 ? value:null;
	}
	async function servePersistentCacheAdRequest(token,allowOwnerChange=false) {
		const key=persistentCacheBridgeRequestKey(token),replyKey=persistentCacheBridgeResponseKey(token);
		const request=GM_getValue(key,null),reply=GM_getValue(replyKey,null);
		if (request?.action !== 'read-ad-data' || request.host !== location.hostname ||
			Date.now() - Number(request.createdAt) >= 25000 || (reply?.protocol === 1 && !reply.working)) return;
		if (reply?.working && reply.session === GARSO_SEARCH_INSTANCE_ID) return;
		if (!allowOwnerChange && request.owner && request.owner !== GARSO_SEARCH_INSTANCE_ID) return;
		if (!allowOwnerChange && !request.owner && getReadyPersistentCacheReader(location.hostname)?.session !== GARSO_SEARCH_INSTANCE_ID) return;
		let response; persistentCacheReaderBusy++;
		writeHelperStorageValue(replyKey,{protocol:1,working:true,session:GARSO_SEARCH_INSTANCE_ID,at:Date.now()});
		try {
			const ids=[...new Set(request.adIds || [])].map(String).filter(id => /^\d+$/.test(id));
			await persistentCacheDbOperationChain;
			await ensurePersistentCacheKeys(ids.flatMap(id => [escortAdDataCacheKey(id),listCacheKey(id)]),{refresh:true});
			await ensureEscortPageCache(ids);
			response={protocol:1,session:GARSO_SEARCH_INSTANCE_ID,ok:true,ads:ids.map(readEscortAdCacheBundle).filter(Boolean),completedAt:Date.now()};
		} catch (error) {response={protocol:1,session:GARSO_SEARCH_INSTANCE_ID,ok:false,error:error?.message || String(error)};}
		finally {persistentCacheReaderBusy--;}
		if (GM_getValue(key,null)) writeHelperStorageValue(replyKey,response);
	}

	function requestPersistentCacheBridgeDirect(action,options={}) {
		if (IS_ESCORT_CLUB) return Promise.reject(new Error('Bridge cache nie jest potrzebny na Escort.club.'));
		if (options.cancelToken?.cancelled) return Promise.reject(createOperationCancelledError(options.cancelToken.reason));
		const host=String(options.host || 'pl.escort.club').toLowerCase();
		if (!isEscortClubHost(host)) return Promise.reject(new Error('Nieprawidłowa domena cache.'));
		const token=`${Date.now()}-${Math.random().toString(36).slice(2)}`;
		const requestKey=persistentCacheBridgeRequestKey(token),responseKey=persistentCacheBridgeResponseKey(token);
		const owner=action === 'read-ad-data' ? getReadyPersistentCacheReader(host):null;
		const payload={protocol:1,action,host,owner:owner?.session || '',createdAt:Date.now(),
			garsoTtlMs:Number(options.garsoTtlMs) || 0,escortTtlMs:Number(options.escortTtlMs) || 0,
			adIds:[...new Set((options.adIds || []).map(String).filter(id => /^\d+$/.test(id)))]};
		writeHelperStorageValue(requestKey,payload); deleteHelperStorageValue(responseKey);
		return new Promise((resolve,reject) => {
			let settled=false,listenerId=null,bridgeTab=null,pollTimer=null,timeoutTimer=null,fallbackTimer=null,unsubscribe=null,workingAt=0,fallbackStarted=false;
			const cleanup=() => {
				unsubscribe?.();
				if (listenerId != null && typeof GM_removeValueChangeListener === 'function') GM_removeValueChangeListener(listenerId);
				clearInterval(pollTimer); clearTimeout(timeoutTimer); clearTimeout(fallbackTimer);
				try {bridgeTab?.close?.();} catch {}
				deleteHelperStorageValue(requestKey); deleteHelperStorageValue(responseKey);
			};
			const finish=(response,error=null) => {
				if (settled) return;
				settled=true; cleanup();
				if (error) reject(error);
				else if (response?.ok) resolve(response);
				else reject(new Error(response?.error || 'Nie udało się odczytać danych z cache Escort.club.'));
			};
			const onResponse=response => {
				if (!response || (action === 'read-ad-data' && response.protocol !== 1)) return;
				if (response.working) {workingAt=Number(response.at) || Date.now(); return;}
				finish(response);
			};
			const openFallback=() => {
				if (settled || bridgeTab || fallbackStarted) return;
				fallbackStarted=true;
				try {
					payload.owner=''; writeHelperStorageValue(requestKey,payload);
					if (action === 'read-ad-data') {
						void ensurePersistentCacheReadTab(host,token,owner?.session).then(() => {
							if (!settled) writeHelperStorageValue(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX + 'signal',token);
						},error => finish(null,error));
						return;
					}
					bridgeTab=GM_openInTab(`https://${host}/?${PERSISTENT_CACHE_BRIDGE_PARAM}=${encodeURIComponent(token)}`,{active:false,insert:true,setParent:true});
					if (!bridgeTab) finish(null,new Error('Nie ma gotowej karty obsługującej cache. Otwórz Escort.club i ponów odczyt.'));
				} catch (error) {finish(null,error);}
			};
			try {
				if (typeof GM_addValueChangeListener === 'function') listenerId=GM_addValueChangeListener(responseKey,(_key,_old,value) => onResponse(value));
				pollTimer=setInterval(() => {
					onResponse(GM_getValue(responseKey,null));
					if (!settled && action === 'read-ad-data' && !workingAt && getReadyPersistentCacheReader(host))
						writeHelperStorageValue(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX + 'signal',token);
					if (!settled && owner && !bridgeTab && workingAt && Date.now() - workingAt > 3000 &&
						getReadyPersistentCacheReader(host)?.session !== owner.session) openFallback();
				},250);
				timeoutTimer=setTimeout(() => finish(null,new Error(action === 'read-ad-data'
					? 'Nie udało się uruchomić odbiorcy cache Escort.club. Otwórz stronę Escort.club i ponów odczyt.'
					: 'Przekroczono czas oczekiwania na operację na cache Escort.club.')),action === 'read-ad-data' ? 15000:20000);
				unsubscribe=options.cancelToken?.onCancel(reason => finish(null,createOperationCancelledError(reason)));
				if (settled) return;
				if (action === 'read-ad-data') {
					writeHelperStorageValue(PERSISTENT_CACHE_BRIDGE_REQUEST_PREFIX + 'signal',token);
					fallbackTimer=setTimeout(() => {if (!workingAt) openFallback();},owner ? 1000:0);
				} else openFallback();
				onResponse(GM_getValue(responseKey,null));
			} catch (error) {finish(null,error);}
		});
	}
	function formatHelperDiagnosticError(error) {
		if (error == null) return '';
		if (typeof error !== 'object') return String(error);
		const parts=[];
		if (error.name && error.name !== 'Error') parts.push(String(error.name));
		if (error.message) parts.push(String(error.message));
		if (error.type) parts.push('type=' + String(error.type));
		if (Number.isFinite(Number(error.status))) parts.push('status=' + Number(error.status));
		if (error.statusText) parts.push(String(error.statusText));
		if (error.readyState != null) parts.push('readyState=' + String(error.readyState));
		const address=error.finalUrl || error.url;
		if (address) {try {const url=new URL(address,location.href); parts.push(url.origin + url.pathname);} catch {}}
		if (error.timeout != null) parts.push('timeout=' + String(error.timeout));
		if (!parts.length) parts.push('Błąd bez komunikatu (' + Object.keys(error).slice(0,12).join(', ') + ')');
		return parts.join(' | ').slice(0,1200);
	}

	const sharedHelperRequests=new Map();
	let watchButtonsRefreshScheduled=false;
	let escortLeafletPromise=null;
	function runSharedHelperRequest(key,work,cancelToken=null) {
		if (cancelToken?.cancelled) return Promise.reject(createOperationCancelledError(cancelToken.reason));
		let job=sharedHelperRequests.get(key);
		if (!job) {
			job={token:createOperationCancelToken(key),subscribers:new Set()};
			sharedHelperRequests.set(key,job);
			Promise.resolve().then(() => {job.token.throwIfCancelled(); return work(job.token);}).then(
				value => {if (sharedHelperRequests.get(key) === job) sharedHelperRequests.delete(key); for (const sub of job.subscribers) {sub.off?.(); sub.resolve(copyWatchedValue(value));}},
				error => {if (sharedHelperRequests.get(key) === job) sharedHelperRequests.delete(key); for (const sub of job.subscribers) {sub.off?.(); sub.reject(error);}}
			).finally(() => {job.subscribers.clear(); if (sharedHelperRequests.get(key) === job) sharedHelperRequests.delete(key);});
		}
		return new Promise((resolve,reject) => {
			const sub={resolve,reject}; job.subscribers.add(sub);
			sub.off=cancelToken?.onCancel(reason => {
				job.subscribers.delete(sub); reject(createOperationCancelledError(reason));
				if (!job.subscribers.size) {
					if (sharedHelperRequests.get(key) === job) sharedHelperRequests.delete(key);
					job.token.cancel(reason);
				}
			});
		});
	}

	function loadEscortRadiusLeaflet() {
		if (!escortLeafletPromise) escortLeafletPromise=Promise.resolve().then(() => {
			const source=GM_getResourceText('escortRadiusLeafletJs');
			if (!source) throw new Error('Brak zasobu mapy. Zainstaluj ponownie pełną wersję skryptu.');
			const exports={};
			new Function('exports','module',source)(exports,{exports});
			if (typeof exports.map !== 'function') throw new Error('Nie udało się uruchomić mapy.');
			return exports;
		}).catch(error => {escortLeafletPromise=null; throw error;});
		return escortLeafletPromise;
	}

	initLiveDiagnosticsReports();
	probeEscortCooldownOnStartup().catch(error => log('Nie udało się sprawdzić dostępu do Escort.club',error));
	let earlyPageUi=false;
	try {
		const pageParams=new URL(location.href).searchParams;
		earlyPageUi=IS_ESCORT_CLUB && window.top === window.self &&
			!getEscortCachedArchiveMarkerFromHash() &&
			!(location.pathname === '/' && readRecentEscortCachedAdNavigation()) &&
			!sessionStorage.getItem(FAVORITE_SEARCH_EDIT_KEY) &&
			(!getEscortAdIdFromPath() || !!document.querySelector('.content-info-col .content-name h1, .content-name.-desc-name h1')) &&
			![PERSISTENT_CACHE_BRIDGE_PARAM,FAVORITE_SEARCH_EDIT_PARAM]
				.some(param => pageParams.has(param));
	} catch {}
	if (earlyPageUi) {
		try {
			initHelperToolbar();
			initEscortSectionVisibility();
		} catch (error) {
			earlyPageUi=false;
			log('Nie udało się wcześniej uruchomić interfejsu',error);
		}
	}
	const cacheBridgeStartupTimer=IS_ESCORT_CLUB && new URL(location.href).searchParams.has(PERSISTENT_CACHE_BRIDGE_PARAM)
		? setTimeout(() => {try {window.close();} catch {}},20000):null;
	await persistentCacheInitialization;
	clearRemovedDiagnosticStorage();
	if (IS_ESCORT_CLUB && await handlePersistentCacheBridgePage(true)) {
		clearTimeout(cacheBridgeStartupTimer);
		return;
	}
	clearTimeout(cacheBridgeStartupTimer);
	initPersistentCacheChanges();
	try {await rebuildWatchedAdDataMirrorFromCentralCache();}
	catch (error) {log('Nie udało się odbudować pomocniczego mirrora Ulubionych',error);}
	initEscortAdCacheReader();
	acknowledgeWatchEventOnArrival();
	try {saveEscortAdTemplateFromPage();} catch (error) {log('Nie udało się zapisać szablonu anonsu',error);}
	installEscortCachedAdNavigationMarker();
	if (await handleEscortCachedAdArchivePage()) return;
	if (initFavoriteSearchView()) return;
	if (initFavoriteSearchEdit()) return;
	try {initEscortGarsoAutoChecks();} catch(error) {log('Nie udało się rozpocząć sprawdzania Garso',error);}
	if (IS_ESCORT_CLUB && location.pathname === '/' && !getFavoriteSearchSession()) {
		const pageUrl=new URL(location.href);
		if (!pageUrl.searchParams.has(PERSISTENT_CACHE_BRIDGE_PARAM)) {
			try {
				sessionStorage.removeItem(ESCORT_MULTI_LOCATION_SESSION_KEY);
			} catch {}
		}
	}
	if (initGarsoTopicCacheBridge()) return;
	maybeOpenSettingsOnFirstRun();
	initHelperToolbar();
	try {
		applyWatchFeatureState();
	} catch (error) {
		log( 'Nie udało się uruchomić systemu Ulubionych/alertów', error );
	}
	try {
		initLiveDiagnosticsPanel();
		initQuickAdPreview();
		initGlobalQuickSearch();
	} catch (error) {
		log( 'Nie udało się uruchomić szybkiego wyszukiwania', error );
	}
	if (['1','table'].includes(getFavoritePageMode())) {
		try {if(getFavoritePageMode()==='table')openWatchedSettingsModal(false,true);else await renderFavoriteTilesView();}
		catch (error) {log('Nie udało się otworzyć Ulubionych',error); showWatchToast('Nie udało się otworzyć Ulubionych.',true);}
		const url=new URL(location.href);for(const key of [FAVORITE_TILES_PARAM,FAVORITE_TILES_FIELDS_PARAM,FAVORITE_TILES_EXTRA_PARAM])url.searchParams.delete(key);
		history.replaceState(history.state,'',url.href);
	}
	if (GARSO_MENU_ONLY_MODE) {
		try {initGarsoInlineTools();}
		catch (error) {log('Nie udało się uruchomić skrótów Garsoniery',error);}
		initGarsoTopicSummary().catch(error => log('Nie udało się uruchomić podsumowania tematu Garso',error));
		initGarsoTrackedTopics().catch(error => log('Nie udało się pokazać zapisanych danych przy tematach Garso',error));
	}
	if ( location.hostname === 'escorti.pl' || location.hostname === 'www.escorti.pl' ) {
		initEscortiProfileAdLinkStatuses();
		initEscortiSearchSettingsGear();
		handleEscortiPage();
		return;
	}
	if (IS_ESCORT_CLUB) {
		const isSingleEscortAd=!!getEscortAdIdFromPath(location.pathname);
		if (!earlyPageUi) initEscortSectionVisibility();
		if (!isSingleEscortAd) {
			initEscortMultiLocationControls();
			initEscortNativeAdvancedFilterCounter();
			initEscortSavedSearchFilters();
			initEscortNativeFilterResetFix();
		}
		if (isSingleEscortAd) {
			for (const [label,initializer] of [ ['data publikacji anonsu',initEscortClubDatePostedDisplay],
				['stan dostępności anonsu',initEscortClubCurrentAvailabilityDot],
				['kolorowanie tagów',initEscortClubTagHighlighting],
				['porównanie z poprzednią wizytą',initEscortClubVisitChangeTracking] ]) {
				try {
					initializer();
				} catch (error) {
					log( `Nie udało się uruchomić: ${label}`, error );
				}
			}
		}
	}
	if ( IS_ESCORT_CLUB &&
		(getFavoriteSearchSession() || location.pathname.startsWith('/anonse/') || isEscortSearchPath() ||
			(CURRENT_HOST !== 'pl.escort.club' && location.pathname !== '/' && !getEscortAdIdFromPath() && !!getEscortClubListContext(document,location.href)?.anchors?.length))
	) {
		try {
			await initEscortClubListPage();
			if (favoriteSearchEditFrameNotify) {
				trimFavoriteSearchEditPage();
				favoriteSearchEditFrameNotify(favoriteSearchEditReadFilters && findEscortSearchLocationControls() ? 'ready':'error',
					{error:'Nie udało się odczytać formularza lub wyników. Poczekaj na wyszukiwarkę albo anuluj edycję.'});
			}
		} catch (error) {
			log('Nie udało się uruchomić funkcji strony wyników Escort.club',error);
			showEscortListInitializationError(error);
			favoriteSearchEditFrameNotify?.('error',{error:error?.message || 'Nie udało się przygotować wyszukiwarki.'});
		}
		return;
	}
	if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded',syncGarsoNativeSearchCooldown,{once:true});
	else syncGarsoNativeSearchCooldown();
	if (!GARSO_MENU_ONLY_MODE) {
		try {
			initEscortGalleryWatcher();
		} catch (error) {
			log( 'Nie udało się uruchomić obserwatora galerii', error );
		}
		try {
			checkAndAddButtons();
		} catch (error) {
			recordDiagnosticError( 'Escort.club - panel Linki/podsumowania', error );
			console.error( '[GARSO-ESCORTI] Nie udało się utworzyć panelu Linki/podsumowania', error );
		}
	}
})();