☰

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.

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

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

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

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

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

// ==UserScript==
// @name         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 );
		}
	}
})();