Recon+

Unlocks Paywalls in recon

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Advertisement:

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

Advertisement:

// ==UserScript==
// @name         Recon+
// @namespace    https://update.greasyfork.org/scripts/582087/Recon%2B.user.js
// @version      2.2
// @match        https://www.recon.com/*
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @connect      basemaps.cartocdn.com
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://unpkg.com/[email protected]/dist/leaflet.js
// @resource     LEAFLET_CSS https://unpkg.com/[email protected]/dist/leaflet.css
// @run-at       document-start
// @description Unlocks Paywalls in recon
// ==/UserScript==

(function(){
// src/state.ts
var win = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
var origXHR = win.XMLHttpRequest;
var profileDetailsCache = new Map;
var profileVersionsMap = new Map;
var profileMediaMap = new Map;
var state = {
  lastBlockedRequest: null,
  lastProfileDetailPageData: null,
  authHeaders: {}
};
var interestMap = {
  1: "Recon Men",
  2: "Skinheads",
  4: "Leather",
  5: "Rubber",
  6: "Sports Gear",
  7: "Military",
  8: "Hoods & Masks",
  9: "Muscle",
  10: "Punks",
  13: "Bikers",
  15: "Bondage",
  18: "Suits",
  19: "Fisting",
  20: "Masters & Slaves",
  27: "Boots",
  30: "Tattoos & Piercings",
  31: "Bears",
  34: "Fighting",
  36: "Feet",
  37: "Pups & Handlers",
  38: "Smokers",
  39: "Gunge",
  40: "Trackies",
  41: "Underwear",
  42: "Chastity",
  43: "Watersports",
  44: "Impact Play",
  45: "Electro",
  46: "Sneakers & Socks",
  47: "ADBL"
};

// src/toast.ts
function showToast(message, type = "success") {
  let container = document.getElementById("custom-toast-container");
  if (!container) {
    container = document.createElement("div");
    container.id = "custom-toast-container";
    container.style.cssText = `position: fixed; top: 20px; right: 20px; z-index: 99999; display: flex; flex-direction: column; gap: 10px; pointer-events: none;`;
    document.body.appendChild(container);
  }
  const toast = document.createElement("div");
  toast.textContent = message;
  toast.style.cssText = `background: ${type === "error" ? "#cf2a2a" : "#2acf65"}; color: #fff; padding: 12px 24px; border-radius: 4px; font-family: sans-serif; font-weight: bold; font-size: 14px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); opacity: 0; transform: translateY(-20px); transition: all 0.3s ease;`;
  container.appendChild(toast);
  setTimeout(() => {
    toast.style.opacity = "1";
    toast.style.transform = "translateY(0)";
  }, 10);
  setTimeout(() => {
    toast.style.opacity = "0";
    toast.style.transform = "translateY(-20px)";
    setTimeout(() => toast.remove(), 300);
  }, 3500);
}
function showProgressIndicator(title) {
  let container = document.getElementById("custom-toast-container");
  if (!container) {
    container = document.createElement("div");
    container.id = "custom-toast-container";
    container.style.cssText = `position: fixed; top: 20px; right: 20px; z-index: 99999; display: flex; flex-direction: column; gap: 10px; pointer-events: none;`;
    document.body.appendChild(container);
  }
  const card = document.createElement("div");
  card.style.cssText = `
        background: rgba(20, 20, 22, 0.95);
        backdrop-filter: blur(10px);
        -webkit-backdrop-filter: blur(10px);
        color: #fff;
        padding: 16px;
        border-radius: 8px;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        font-size: 14px;
        box-shadow: 0 10px 25px rgba(0,0,0,0.5);
        border: 1px solid rgba(255,255,255,0.1);
        width: 300px;
        pointer-events: auto;
        opacity: 0;
        transform: translateY(-20px);
        transition: all 0.3s ease;
        display: flex;
        flex-direction: column;
        gap: 10px;
    `;
  const header = document.createElement("div");
  header.style.cssText = `display: flex; justify-content: space-between; align-items: center; font-weight: bold;`;
  const titleEl = document.createElement("span");
  titleEl.textContent = title;
  const pctEl = document.createElement("span");
  pctEl.style.cssText = `color: #a855f7; font-variant-numeric: tabular-nums; font-weight: bold;`;
  pctEl.textContent = "0%";
  header.appendChild(titleEl);
  header.appendChild(pctEl);
  const msgEl = document.createElement("div");
  msgEl.style.cssText = `font-size: 12px; color: rgba(255,255,255,0.7); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;`;
  msgEl.textContent = "Initializing...";
  const barTrack = document.createElement("div");
  barTrack.style.cssText = `background: rgba(255,255,255,0.1); height: 6px; border-radius: 3px; overflow: hidden;`;
  const barFill = document.createElement("div");
  barFill.style.cssText = `width: 0%; height: 100%; background: linear-gradient(90deg, #6366f1, #a855f7); transition: width 0.2s ease; border-radius: 3px;`;
  barTrack.appendChild(barFill);
  card.appendChild(header);
  card.appendChild(msgEl);
  card.appendChild(barTrack);
  container.appendChild(card);
  setTimeout(() => {
    card.style.opacity = "1";
    card.style.transform = "translateY(0)";
  }, 10);
  return {
    update(percent, message) {
      const safePercent = Math.min(100, Math.max(0, percent));
      pctEl.textContent = `${Math.round(safePercent)}%`;
      msgEl.textContent = message;
      barFill.style.width = `${safePercent}%`;
      if (safePercent >= 100) {
        barFill.style.background = "linear-gradient(90deg, #2ec4b6, #2acf65)";
        pctEl.style.color = "#2acf65";
      }
    },
    close(delay = 1000) {
      setTimeout(() => {
        card.style.opacity = "0";
        card.style.transform = "translateY(-20px)";
        setTimeout(() => card.remove(), 300);
      }, delay);
    }
  };
}

// src/utils.ts
async function fetchImageAsBlob(url) {
  try {
    const res = await win.fetch(url);
    if (res.ok)
      return await res.blob();
  } catch (e) {
    console.warn(`[Script] Standard fetch failed for ${url}, trying GM_xmlhttpRequest:`, e);
  }
  if (typeof GM_xmlhttpRequest !== "undefined") {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url,
        responseType: "blob",
        onload: function(response) {
          if (response.status >= 200 && response.status < 300) {
            resolve(response.response);
          } else {
            reject(new Error(`GM_xmlhttpRequest failed with status ${response.status}`));
          }
        },
        onerror: function(err) {
          reject(err);
        }
      });
    });
  }
  throw new Error(`Cannot fetch ${url} without CORS or GM_xmlhttpRequest`);
}
function getJSZip() {
  if (typeof JSZip !== "undefined")
    return Promise.resolve(JSZip);
  if (win.JSZip)
    return Promise.resolve(win.JSZip);
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js";
    script.onload = () => resolve(window.JSZip || win.JSZip);
    script.onerror = () => reject(new Error("Failed to load JSZip"));
    document.head.appendChild(script);
  });
}
async function exportProfileData() {
  const progress = showProgressIndicator("Exporting Profile");
  let JSZipLib;
  try {
    progress.update(5, "Loading JSZip library...");
    JSZipLib = await getJSZip();
  } catch (e) {
    console.error(e);
    showToast("Failed to load JSZip library.", "error");
    progress.close(0);
    return;
  }
  progress.update(10, "Analyzing profile data...");
  const data = state.lastProfileDetailPageData;
  const profileId = data ? data.id : (win.location.pathname.match(/\/(?:members|profiles)\/([a-f0-9-]{36}|[a-zA-Z0-9_-]+)/i) || [])[1] || "unknown";
  let displayName = "";
  if (data) {
    displayName = data.displayName || data.username || data.name || "";
  }
  if (!displayName) {
    const h1 = document.querySelector("h1, .profile-header h1, t101-profile-header h1, .member-info h1, .profile-info-container h1");
    if (h1 && h1.innerText.trim()) {
      displayName = h1.innerText.trim();
    } else if (document.title) {
      displayName = document.title.replace(/\s*-\s*Recon\s*$/i, "").trim();
    }
  }
  if (!displayName) {
    displayName = "Profile";
  }
  const photoUrls = new Set;
  document.querySelectorAll(".photo-tile").forEach((t) => {
    const htmlEl = t;
    const bgImg = htmlEl.style.backgroundImage;
    if (bgImg) {
      const url = bgImg.replace(/^url\(["']?/, "").replace(/["']?\)$/, "").split("?")[0];
      if (url)
        photoUrls.add(url);
    }
  });
  document.querySelectorAll(".profile-avatar").forEach((t) => {
    const htmlEl = t;
    const bgImg = htmlEl.style.backgroundImage;
    if (bgImg) {
      const url = bgImg.replace(/^url\(["']?/, "").replace(/["']?\)$/, "").split("?")[0];
      if (url)
        photoUrls.add(url);
    }
  });
  if (data) {
    const findUrls = (obj) => {
      if (typeof obj === "string") {
        if (obj.startsWith("http") && (obj.includes("/photos/") || obj.includes("/images/") || obj.match(/\.(jpg|jpeg|png|webp|gif)/i))) {
          photoUrls.add(obj.split("?")[0]);
        }
      } else if (typeof obj === "object" && obj !== null) {
        for (const key in obj) {
          if (Object.prototype.hasOwnProperty.call(obj, key)) {
            findUrls(obj[key]);
          }
        }
      }
    };
    findUrls(data);
  }
  const urlsArray = Array.from(photoUrls);
  if (urlsArray.length === 0) {
    showToast("No photos found to export.", "error");
    progress.close(0);
    return;
  }
  progress.update(15, `Found ${urlsArray.length} photos. Starting download...`);
  const zip = new JSZipLib;
  let infoText = "";
  infoText += `=== RECON PROFILE EXPORT ===
`;
  infoText += `Export Date: ${new Date().toLocaleString()}
`;
  infoText += `Profile URL: ${win.location.href}

`;
  const interestMap2 = {
    1: "Recon Men",
    2: "Skinheads",
    4: "Leather",
    5: "Rubber",
    6: "Sports Gear",
    7: "Military",
    8: "Hoods & Masks",
    9: "Muscle",
    10: "Punks",
    13: "Bikers",
    15: "Bondage",
    18: "Suits",
    19: "Fisting",
    20: "Masters & Slaves",
    27: "Boots",
    30: "Tattoos & Piercings",
    31: "Bears",
    34: "Fighting",
    36: "Feet",
    37: "Pups & Handlers",
    38: "Smokers",
    39: "Gunge",
    40: "Trackies",
    41: "Underwear",
    42: "Chastity",
    43: "Watersports",
    44: "Impact Play",
    45: "Electro",
    46: "Sneakers & Socks",
    47: "ADBL"
  };
  if (data) {
    infoText += `Display Name: ${data.displayName || "N/A"}
`;
    infoText += `Username: ${data.username || "N/A"}
`;
    infoText += `Profile ID: ${data.id || "N/A"}
`;
    if (data.location) {
      infoText += `Location: ID ${data.location.locationId || "N/A"}
`;
    }
    if (data.lastUpdatedDate) {
      infoText += `Last Updated: ${data.lastUpdatedDate}
`;
    }
    if (data.interests && data.interests.length > 0) {
      infoText += `Interests: ${data.interests.map((id) => interestMap2[id] || `Tag ${id}`).join(", ")}
`;
    }
    infoText += `
--- PROFILE INFO (RAW KEY-VALUES) ---
`;
    for (const [key, val] of Object.entries(data)) {
      if (typeof val !== "object" && val !== null) {
        infoText += `${key}: ${val}
`;
      }
    }
  }
  const bioSection = document.querySelector(".text-container, .bio-container, .member-bio");
  if (bioSection) {
    infoText += `
--- PROFILE BIO ---
`;
    infoText += bioSection.innerText.trim() + `
`;
  }
  const infoSections = document.querySelectorAll(".details-container, .stats-container, .profile-details");
  if (infoSections.length > 0) {
    infoText += `
--- PROFILE DETAILS ---
`;
    infoSections.forEach((section) => {
      const htmlEl = section;
      infoText += htmlEl.innerText.trim() + `

`;
    });
  }
  zip.file("profile_info.txt", infoText);
  if (data) {
    zip.file("profile_data.json", JSON.stringify(data, null, 4));
  }
  let successCount = 0;
  let completedCount = 0;
  const photosFolder = zip.folder("photos");
  const downloadPromises = urlsArray.map(async (url, idx) => {
    try {
      const blob = await fetchImageAsBlob(url);
      const ext = url.split(".").pop()?.split(/[?#]/)[0] || "jpg";
      photosFolder.file(`photo_${idx + 1}.${ext}`, blob);
      successCount++;
    } catch (err) {
      console.error(`[Script] Failed to download photo from ${url}:`, err);
    } finally {
      completedCount++;
      const percent = 15 + Math.round(completedCount / urlsArray.length * 65);
      progress.update(percent, `Downloading photos (${completedCount}/${urlsArray.length})...`);
    }
  });
  await Promise.all(downloadPromises);
  if (successCount === 0) {
    showToast("Failed to download any profile photos.", "error");
    progress.close(0);
    return;
  }
  progress.update(80, `Zipping ${successCount} photos...`);
  try {
    const content = await zip.generateAsync({ type: "blob" }, (metadata) => {
      const percent = 80 + Math.round(metadata.percent / 100 * 15);
      progress.update(percent, `Zipping files (${Math.round(metadata.percent)}%)...`);
    });
    progress.update(95, "Saving export file...");
    const blobUrl = URL.createObjectURL(content);
    const a = document.createElement("a");
    const safeName = displayName?.replace(/[^a-z0-9_-]/gi, "_").toLowerCase();
    a.href = blobUrl;
    a.download = `${safeName}_${profileId}_export.zip`;
    a.click();
    URL.revokeObjectURL(blobUrl);
    progress.update(100, `Complete! Saved ${successCount}/${urlsArray.length} photos.`);
    progress.close(2000);
  } catch (err) {
    console.error("[Script] Error creating zip file:", err);
    showToast("Error generating zip archive.", "error");
    progress.close(0);
  }
}
function extractPhotoUrls(obj) {
  const urlsMap = new Map;
  const findUrls = (val) => {
    if (!val)
      return;
    if (typeof val === "object") {
      if (typeof val.url === "string" && val.url) {
        let url = val.url;
        if (url.startsWith("//")) {
          url = "https:" + url;
        }
        const baseUrl = url.split("?")[0];
        if (!urlsMap.has(baseUrl) || url.includes("size=")) {
          urlsMap.set(baseUrl, url);
        }
      }
      for (const key in val) {
        if (Object.prototype.hasOwnProperty.call(val, key)) {
          findUrls(val[key]);
        }
      }
    } else if (typeof val === "string") {
      if (val.startsWith("http") && (val.includes("/profiles/") || val.includes("/photos/") || val.includes("/images/") || val.includes("/files/") || val.includes("t101api.com") || val.match(/\.(jpg|jpeg|png|webp|gif)/i))) {
        const baseUrl = val.split("?")[0];
        if (!urlsMap.has(baseUrl) || val.includes("size=")) {
          urlsMap.set(baseUrl, val);
        }
      }
    }
  };
  findUrls(obj);
  return Array.from(urlsMap.values());
}

// src/locate.ts
function degToRad(d) {
  return d * Math.PI / 180;
}
function radToDeg(r) {
  return r * 180 / Math.PI;
}
function offsetLatLon(lat, lon, northMetres, eastMetres) {
  const R = 6371000;
  const dLat = northMetres / R;
  const dLon = eastMetres / (R * Math.cos(degToRad(lat)));
  return { lat: lat + radToDeg(dLat), lon: lon + radToDeg(dLon) };
}
function haversine(lat1, lon1, lat2, lon2) {
  const R = 6371000;
  const dLat = degToRad(lat2 - lat1);
  const dLon = degToRad(lon2 - lon1);
  const a = Math.sin(dLat / 2) ** 2 + Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) * Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
function bearingTo(lat1, lon1, lat2, lon2) {
  const φ1 = degToRad(lat1), φ2 = degToRad(lat2);
  const Δ_ = degToRad(lon2 - lon1);
  const y = Math.sin(Δ_) * Math.cos(φ2);
  const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δ_);
  return Math.atan2(y, x);
}
function destinationPoint(lat, lon, bearingRad, distMetres) {
  const R = 6371000;
  const d = distMetres / R;
  const φ1 = degToRad(lat), λ1 = degToRad(lon);
  const φ2 = Math.asin(Math.sin(φ1) * Math.cos(d) + Math.cos(φ1) * Math.sin(d) * Math.cos(bearingRad));
  const λ2 = λ1 + Math.atan2(Math.sin(bearingRad) * Math.sin(d) * Math.cos(φ1), Math.cos(d) - Math.sin(φ1) * Math.sin(φ2));
  return { lat: radToDeg(φ2), lon: radToDeg(λ2) };
}
async function searchFromPoint(lat, lon, targetProfileId, myProfileId, age) {
  const ageMin = age ? String(age) : "18";
  const ageMax = age ? String(age) : "99";
  const params = new URLSearchParams({
    activeWithinMinutes: "525600",
    ageMax,
    ageMin,
    culture: "en",
    isExplore: "true",
    latitude: lat.toFixed(6),
    longitude: lon.toFixed(6),
    myProfileId,
    radiusMetres: "40225",
    sortProperty: "distance"
  });
  const url = `https://www.recon.com/api/profileSearch/profiles?${params.toString()}`;
  try {
    const res = await win.fetch(url, { headers: state.authHeaders });
    if (!res.ok) {
      console.warn(`[Recon+ Locate] fetch failed with status ${res.status}`);
      return null;
    }
    const json = await res.json();
    if (!json?.data)
      return null;
    const match = json.data.find((p) => p.profileId === targetProfileId);
    if (!match)
      return null;
    return { profileId: match.profileId, distanceMetres: match.distanceMetres };
  } catch (e) {
    console.warn("[Recon+ Locate] fetch error", e);
    return null;
  }
}
function getMyProfileId() {
  const navLinks = Array.from(document.querySelectorAll("a[href]"));
  for (const a of navLinks) {
    const m = a.href.match(/\/profiles\/([a-z0-9-]{36})\/?/i);
    if (m && !a.href.includes("cruise") && !a.href.includes("bookmarks") && !a.href.includes("connections")) {
      if (a.querySelector(".profile-avatar") || a.classList.contains("nav-item-right-container") || a.classList.contains("mobile-nav-cell")) {
        if (a.closest("t101-nav-bar, t101-mobile-nav-bar")) {
          return m[1];
        }
      }
    }
  }
  for (const a of navLinks) {
    const m = a.href.match(/\/profiles\/([a-z0-9-]{36})\/?$/i);
    if (m && a.closest("t101-nav-bar, t101-mobile-nav-bar"))
      return m[1];
  }
  return null;
}
function getTargetProfileId() {
  const urlMatch = win.location.pathname.match(/\/profiles\/([a-f0-9-]{36})/i);
  if (urlMatch)
    return urlMatch[1];
  if (state.lastProfileDetailPageData?.id) {
    return state.lastProfileDetailPageData.id;
  }
  for (const [id, data] of profileDetailsCache) {
    if (data.isDetailed)
      return id;
  }
  return null;
}
function getTargetAge() {
  const data = state.lastProfileDetailPageData;
  if (data && typeof data.age === "number" && data.age > 0) {
    return data.age;
  }
  const ageEl = document.querySelector('.age-value, .age, [class*="age-value"], [class*="age_value"]');
  if (ageEl && ageEl.textContent) {
    const m = ageEl.textContent.match(/(\d+)/);
    if (m)
      return parseInt(m[1], 10);
  }
  const text = document.body.innerText;
  const match = text.match(/Age\s*:\s*(\d+)/i) || text.match(/(\d+)\s*years?\s*old/i);
  if (match)
    return parseInt(match[1], 10);
  return null;
}
function getUserPosition() {
  return new Promise((resolve, reject) => {
    if (!navigator.geolocation) {
      reject(new Error("Geolocation not supported in this browser"));
      return;
    }
    navigator.geolocation.getCurrentPosition((pos) => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude }), (err) => reject(new Error(`Geolocation denied: ${err.message}`)), { enableHighAccuracy: false, timeout: 12000, maximumAge: 120000 });
  });
}
function getProfileDistanceMetres() {
  const d = state.lastProfileDetailPageData?.distanceMetres;
  if (typeof d === "number" && d > 0)
    return d;
  const distEl = document.querySelector(".distance-value");
  const text = distEl ? distEl.textContent : document.body.innerText;
  const kmMatch = text.match(/(\d[\d,]*\.?\d*)\s*km\b/i);
  if (kmMatch) {
    const v = parseFloat(kmMatch[1].replace(/,/g, "")) * 1000;
    if (v > 0)
      return v;
  }
  const miMatch = text.match(/(\d[\d,]*\.?\d*)\s*mi(?:le)?s?\b/i);
  if (miMatch) {
    const v = parseFloat(miMatch[1].replace(/,/g, "")) * 1609.34;
    if (v > 0)
      return v;
  }
  const ftMatch = text.match(/(\d[\d,]*\.?\d*)\s*ft\b/i);
  if (ftMatch) {
    const v = parseFloat(ftMatch[1].replace(/,/g, "")) * 0.3048;
    if (v > 0)
      return v;
  }
  const mMatch = text.match(/(\d[\d,]+)\s*m\b(?!\s*[il])/i);
  if (mMatch) {
    const v = parseFloat(mMatch[1].replace(/,/g, ""));
    if (v > 0)
      return v;
  }
  return null;
}
var PROBE_OFFSET_METRES = 5000;
async function triangulate(targetProfileId, myProfileId, centerLat, centerLon, onProgress) {
  const probeOffsets = [
    { name: "origin", north: 0, east: 0 },
    { name: "North", north: PROBE_OFFSET_METRES, east: 0 },
    { name: "South", north: -PROBE_OFFSET_METRES, east: 0 },
    { name: "East", north: 0, east: PROBE_OFFSET_METRES },
    { name: "West", north: 0, east: -PROBE_OFFSET_METRES }
  ];
  const targetAge = getTargetAge();
  let probes = [];
  const runScan = async (ageFilter) => {
    probes = [];
    for (let i = 0;i < probeOffsets.length; i++) {
      const off = probeOffsets[i];
      const { lat, lon } = offsetLatLon(centerLat, centerLon, off.north, off.east);
      const filterLabel = ageFilter ? `age ${ageFilter}` : "broad search";
      onProgress(`Scanning ${i + 1}/${probeOffsets.length} (${off.name}, ${filterLabel})…`);
      const hit = await searchFromPoint(lat, lon, targetProfileId, myProfileId, ageFilter);
      if (hit) {
        probes.push({ lat, lon, isHit: true, distanceMetres: hit.distanceMetres });
      } else {
        probes.push({ lat, lon, isHit: false, distanceMetres: 0 });
      }
      await new Promise((r) => setTimeout(r, 600));
    }
  };
  await runScan(targetAge);
  let hits = probes.filter((p) => p.isHit);
  if (hits.length === 0 && targetAge !== null) {
    onProgress("No hits with age filter. Retrying with broad age search…");
    await runScan(null);
    hits = probes.filter((p) => p.isHit);
  }
  if (hits.length === 0) {
    onProgress("Profile not found in any probe search.");
    return null;
  }
  let targetDist = getProfileDistanceMetres();
  if (!targetDist || targetDist <= 0) {
    const firstHit = hits[0];
    const distFromCenterToProbe = haversine(centerLat, centerLon, firstHit.lat, firstHit.lon);
    targetDist = firstHit.distanceMetres + distFromCenterToProbe;
  }
  onProgress("Resolving location using positive and negative constraints…");
  let bestLat = centerLat;
  let bestLon = centerLon;
  let minPenalty = Infinity;
  for (let deg = 0;deg < 360; deg++) {
    const bearingRad = degToRad(deg);
    const candidate = destinationPoint(centerLat, centerLon, bearingRad, targetDist);
    let penalty = 0;
    for (const probe of probes) {
      const distToCandidate = haversine(probe.lat, probe.lon, candidate.lat, candidate.lon);
      if (probe.isHit) {
        penalty += Math.abs(distToCandidate - probe.distanceMetres);
      } else {
        const maxRadius = 40225;
        if (distToCandidate < maxRadius - 1000) {
          penalty += (maxRadius - 1000 - distToCandidate) * 5;
        }
      }
    }
    if (penalty < minPenalty) {
      minPenalty = penalty;
      bestLat = candidate.lat;
      bestLon = candidate.lon;
    }
  }
  const radiusMetres = Math.max(200, Math.min(1e4, minPenalty));
  return { lat: bestLat, lon: bestLon, radiusMetres };
}
function ensureLeafletCss() {
  if (document.querySelector("#recon-leaflet-css"))
    return;
  try {
    const gmGet = typeof GM_getResourceText !== "undefined" ? GM_getResourceText : win.GM_getResourceText;
    const gmAdd = typeof GM_addStyle !== "undefined" ? GM_addStyle : win.GM_addStyle;
    if (gmGet && gmAdd) {
      const css = gmGet("LEAFLET_CSS");
      if (css) {
        const style = document.createElement("style");
        style.id = "recon-leaflet-css";
        style.textContent = css;
        document.head.appendChild(style);
        return;
      }
    }
  } catch (_) {}
  const link = document.createElement("link");
  link.id = "recon-leaflet-css";
  link.rel = "stylesheet";
  link.href = "https://unpkg.com/[email protected]/dist/leaflet.css";
  document.head.appendChild(link);
}
function waitForLeaflet(timeoutMs = 8000) {
  const check = () => typeof L !== "undefined" && L.map ? L : win.L && win.L.map ? win.L : null;
  const immediate = check();
  if (immediate)
    return Promise.resolve(immediate);
  return new Promise((resolve, reject) => {
    const start = Date.now();
    const poll = setInterval(() => {
      const leaflet = check();
      if (leaflet) {
        clearInterval(poll);
        resolve(leaflet);
      } else if (Date.now() - start > timeoutMs) {
        clearInterval(poll);
        reject(new Error("Leaflet not available — please reinstall the script to refresh @require cache."));
      }
    }, 150);
  });
}
function renderDualMap(container, L2, primary, secondary) {
  ensureLeafletCss();
  if (container._leafletMap) {
    try {
      container._leafletMap.remove();
    } catch (_) {}
  }
  container.innerHTML = "";
  container.style.cssText = `
        width: 100%;
        height: 360px;
        border-radius: 12px;
        overflow: hidden;
        margin-top: 16px;
        position: relative;
        box-shadow: 0 4px 24px rgba(0,0,0,0.5);
        border: 1px solid rgba(255,255,255,0.08);
    `;
  const MAP_HEIGHT = 360;
  const mapDiv = document.createElement("div");
  mapDiv.style.cssText = `width:100%;height:${MAP_HEIGHT}px;`;
  container.appendChild(mapDiv);
  const zoomForRadius = (r) => {
    if (r < 300)
      return 15;
    if (r < 800)
      return 14;
    if (r < 2000)
      return 13;
    if (r < 5000)
      return 12;
    if (r < 15000)
      return 11;
    if (r < 40000)
      return 10;
    return 8;
  };
  const map = L2.map(mapDiv, {
    zoomControl: true,
    attributionControl: true,
    preferCanvas: false
  });
  container._leafletMap = map;
  const CspTileLayer = L2.TileLayer.extend({
    createTile: function(coords, done) {
      const tile = document.createElement("img");
      tile.alt = "";
      tile.setAttribute("role", "presentation");
      const url = this.getTileUrl(coords);
      const gmXhr = typeof GM_xmlhttpRequest !== "undefined" ? GM_xmlhttpRequest : win.GM_xmlhttpRequest;
      if (gmXhr) {
        gmXhr({
          method: "GET",
          url,
          responseType: "blob",
          onload: function(response) {
            if (response.status === 200 && response.response) {
              const blobUrl = URL.createObjectURL(response.response);
              tile.onload = () => {
                done(null, tile);
                URL.revokeObjectURL(blobUrl);
              };
              tile.onerror = () => {
                done(new Error("Tile failed"), tile);
                URL.revokeObjectURL(blobUrl);
              };
              tile.src = blobUrl;
            } else {
              tile.src = url;
              L2.DomEvent.on(tile, "load", L2.Util.bind(this._tileOnLoad, this, done, tile));
              L2.DomEvent.on(tile, "error", L2.Util.bind(this._tileOnError, this, done, tile));
            }
          }.bind(this),
          onerror: function() {
            tile.src = url;
            L2.DomEvent.on(tile, "load", L2.Util.bind(this._tileOnLoad, this, done, tile));
            L2.DomEvent.on(tile, "error", L2.Util.bind(this._tileOnError, this, done, tile));
          }.bind(this)
        });
      } else {
        tile.src = url;
        L2.DomEvent.on(tile, "load", L2.Util.bind(this._tileOnLoad, this, done, tile));
        L2.DomEvent.on(tile, "error", L2.Util.bind(this._tileOnError, this, done, tile));
      }
      return tile;
    }
  });
  new CspTileLayer("https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", {
    attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/">CARTO</a>',
    subdomains: "abcd",
    maxZoom: 19
  }).addTo(map);
  const addLayer = (layer) => {
    if (!layer.pinOnly) {
      L2.circle([layer.lat, layer.lon], {
        radius: layer.radiusMetres,
        color: layer.color,
        fillColor: layer.color,
        fillOpacity: layer.ringOnly ? 0.06 : 0.18,
        weight: 2,
        opacity: layer.ringOnly ? 0.5 : 0.8,
        dashArray: layer.ringOnly ? "6 4" : undefined
      }).addTo(map);
    }
    const size = layer.ringOnly && !layer.pinOnly ? 10 : 14;
    const icon = L2.divIcon({
      className: "",
      html: `<div style="width:${size}px;height:${size}px;background:${layer.color};border-radius:50%;border:2px solid #fff;box-shadow:0 0 8px ${layer.color}bb;"></div>`,
      iconSize: [size, size],
      iconAnchor: [size / 2, size / 2]
    });
    L2.marker([layer.lat, layer.lon], { icon }).addTo(map);
  };
  if (secondary)
    addLayer(secondary);
  addLayer(primary);
  let lineDistanceMetres = null;
  if (secondary) {
    lineDistanceMetres = haversine(primary.lat, primary.lon, secondary.lat, secondary.lon);
    L2.polyline([[primary.lat, primary.lon], [secondary.lat, secondary.lon]], { color: "#ffffff", weight: 1.5, opacity: 0.6, dashArray: "5 6" }).addTo(map);
    const midLat = (primary.lat + secondary.lat) / 2;
    const midLon = (primary.lon + secondary.lon) / 2;
    const distLabel = lineDistanceMetres >= 1000 ? `${(lineDistanceMetres / 1000).toFixed(2)} km` : `${Math.round(lineDistanceMetres)} m`;
    const labelIcon = L2.divIcon({
      className: "",
      html: `<div style="background:rgba(14,14,20,0.88);color:#fff;font-size:10px;font-family:system-ui,sans-serif;font-weight:600;padding:2px 7px;border-radius:6px;border:1px solid rgba(255,255,255,0.2);white-space:nowrap;pointer-events:none;">${distLabel}</div>`,
      iconAnchor: [30, 10],
      iconSize: [60, 20]
    });
    L2.marker([midLat, midLon], { icon: labelIcon, interactive: false }).addTo(map);
  }
  const boundsFromLayer = (layer) => {
    const R = 6371000;
    const r = Math.max(layer.radiusMetres, 200);
    const dLat = r / R * (180 / Math.PI);
    const dLon = r / (R * Math.cos(layer.lat * Math.PI / 180)) * (180 / Math.PI);
    return L2.latLngBounds([layer.lat - dLat, layer.lon - dLon], [layer.lat + dLat, layer.lon + dLon]);
  };
  let bounds = boundsFromLayer(primary);
  if (secondary)
    bounds = bounds.extend(boundsFromLayer(secondary));
  const legendRows = [
    `<div style="display:flex;align-items:center;gap:6px;">
            <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${primary.color};"></span>
            <span style="color:${primary.color};font-weight:700;">${primary.label}</span>
         </div>
         <div style="color:#aaa;font-size:11px;margin-left:16px;">${primary.subtitle}</div>`
  ];
  if (secondary) {
    legendRows.push(`<div style="display:flex;align-items:center;gap:6px;margin-top:6px;">
                <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${secondary.color};border:2px solid rgba(255,255,255,0.4);"></span>
                <span style="color:${secondary.color};font-weight:700;">${secondary.label}</span>
             </div>
             <div style="color:#aaa;font-size:11px;margin-left:16px;">${secondary.subtitle}</div>`);
  }
  if (lineDistanceMetres !== null) {
    const distLabel = lineDistanceMetres >= 1000 ? `${(lineDistanceMetres / 1000).toFixed(2)} km` : `${Math.round(lineDistanceMetres)} m`;
    legendRows.push(`<div style="margin-top:8px;padding-top:7px;border-top:1px solid rgba(255,255,255,0.12);display:flex;align-items:center;gap:6px;">
                <span style="color:#fff;font-size:11px;">\uD83D\uDCCF Distance between pins:</span>
                <span style="color:#fff;font-weight:700;font-size:12px;">${distLabel}</span>
             </div>`);
  }
  const overlay = document.createElement("div");
  overlay.style.cssText = `
        position: absolute;
        top: 10px; left: 10px;
        z-index: 1000;
        background: rgba(14,14,20,0.93);
        backdrop-filter: blur(10px);
        color: #fff;
        font-family: system-ui, sans-serif;
        font-size: 12px;
        padding: 10px 14px;
        border-radius: 10px;
        border: 1px solid rgba(255,255,255,0.12);
        pointer-events: none;
        line-height: 1.6;
        min-width: 180px;
    `;
  overlay.innerHTML = legendRows.join("");
  container.appendChild(overlay);
  const doSetView = () => {
    map.invalidateSize({ animate: false });
    map.fitBounds(bounds, { padding: [24, 24], animate: false, maxZoom: zoomForRadius(primary.radiusMetres) });
  };
  setTimeout(doSetView, 50);
  setTimeout(doSetView, 300);
}
function renderLocateMap(container, L2, lat, lon, radiusMetres, label = "\uD83D\uDCCD Estimated Location", subtitle = `±${Math.round(radiusMetres)}m accuracy`, color = "#e32222") {
  renderDualMap(container, L2, { lat, lon, radiusMetres, color, label, subtitle });
}
function injectDistanceMap() {
  if (!win.location.pathname.match(/\/profiles\/[^/]+/i))
    return;
  if (document.getElementById("recon-locate-map"))
    return;
  const anchor = document.querySelector("t101-profile-extra-info") || document.querySelector(".right-wrapper > t101-profile-extra-info") || document.querySelector(".laptop-background") || document.querySelector(".right-wrapper");
  if (!anchor)
    return;
  const distMetres = getProfileDistanceMetres();
  if (!distMetres || distMetres <= 0)
    return;
  const mapContainer = document.createElement("div");
  mapContainer.id = "recon-locate-map";
  mapContainer.style.cssText = `
        width: 100%;
        margin-top: 20px;
        border-radius: 12px;
        overflow: hidden;
        position: relative;
        box-shadow: 0 4px 24px rgba(0,0,0,0.5);
        border: 1px solid rgba(255,255,255,0.08);
    `;
  anchor.after(mapContainer);
  Promise.all([waitForLeaflet(), getUserPosition()]).then(([L2, pos]) => {
    renderLocateMap(mapContainer, L2, pos.lat, pos.lon, distMetres, "\uD83D\uDCCD Approximate Area", `Profile is within ${Math.round(distMetres)}m of your position`, "#3b82f6");
  }).catch((err) => {
    console.warn("[Recon+ Map] Could not render distance map:", err?.message || err);
    mapContainer.remove();
  });
}
function injectLocateButton() {
  injectDistanceMap();
  if (!win.location.pathname.match(/\/profiles\/[^/]+/i))
    return;
  if (document.getElementById("recon-locate-btn"))
    return;
  const exportBtn = document.getElementById("custom-export-profile-btn");
  if (!exportBtn)
    return;
  const btn = document.createElement("button");
  btn.id = "recon-locate-btn";
  btn.textContent = "\uD83D\uDD3A Triangulate";
  btn.title = "Probe from multiple points to narrow down the actual location";
  btn.style.cssText = `background-color: #1a2a1a; color: #4ade80; border: 1px solid #4ade8055; padding: 8px 16px; margin-left: 8px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 14px; transition: background 0.2s;`;
  btn.onmouseover = () => btn.style.backgroundColor = "#243324";
  btn.onmouseout = () => btn.style.backgroundColor = "#1a2a1a";
  exportBtn.parentNode.insertBefore(btn, exportBtn.nextSibling);
  btn.addEventListener("click", async () => {
    btn.disabled = true;
    btn.textContent = "⏳ Triangulating…";
    const setStatus = (msg) => {
      btn.textContent = `⏳ ${msg}`;
    };
    try {
      const [L2, pos] = await Promise.all([waitForLeaflet(), getUserPosition()]);
      const targetId = getTargetProfileId();
      const myId = getMyProfileId();
      if (!targetId) {
        showToast("Could not determine target profile ID.", "error");
        btn.disabled = false;
        btn.textContent = "\uD83D\uDD3A Triangulate";
        return;
      }
      if (!myId) {
        showToast("Could not determine your profile ID. Make sure you are logged in.", "error");
        btn.disabled = false;
        btn.textContent = "\uD83D\uDD3A Triangulate";
        return;
      }
      const result = await triangulate(targetId, myId, pos.lat, pos.lon, setStatus);
      const dist = getProfileDistanceMetres();
      document.getElementById("recon-locate-map")?.remove();
      const mapContainer = document.createElement("div");
      mapContainer.id = "recon-locate-map";
      mapContainer.style.cssText = `
                width: 100%;
                margin-top: 20px;
                border-radius: 12px;
                overflow: hidden;
                position: relative;
                box-shadow: 0 4px 24px rgba(0,0,0,0.5);
                border: 1px solid rgba(255,255,255,0.08);
            `;
      const anchor = document.querySelector("t101-profile-extra-info") || document.querySelector(".right-wrapper > t101-profile-extra-info") || document.querySelector(".laptop-background") || document.querySelector(".right-wrapper");
      if (anchor)
        anchor.after(mapContainer);
      if (!dist) {
        showToast("Distance not found on page — cannot plot location.", "error");
        mapContainer.remove();
      } else if (result) {
        const bearing = bearingTo(pos.lat, pos.lon, result.lat, result.lon);
        const targetPoint = destinationPoint(pos.lat, pos.lon, bearing, dist);
        const userLayer = {
          lat: pos.lat,
          lon: pos.lon,
          radiusMetres: dist,
          color: "#3b82f6",
          label: "\uD83D\uDCCD Your Position",
          subtitle: `~${Math.round(dist)}m reported distance`,
          ringOnly: true
        };
        const targetLayer = {
          lat: targetPoint.lat,
          lon: targetPoint.lon,
          radiusMetres: 0,
          color: "#f97316",
          label: "\uD83D\uDCCC Estimated Location",
          subtitle: `On ring edge · bearing ${Math.round(radToDeg(bearing + 2 * Math.PI) % 360)}°`,
          pinOnly: true
        };
        renderDualMap(mapContainer, L2, targetLayer, userLayer);
        showToast(`Location estimated on ring edge — ${Math.round(dist)}m from you`, "success");
      } else {
        renderDualMap(mapContainer, L2, {
          lat: pos.lat,
          lon: pos.lon,
          radiusMetres: dist,
          color: "#f59e0b",
          label: "\uD83D\uDCCD Distance Ring",
          subtitle: `Profile within ~${Math.round(dist)}m`,
          ringOnly: false
        });
        showToast("Triangulation inconclusive — showing distance ring only.", "warning");
      }
      mapContainer.scrollIntoView({ behavior: "smooth", block: "nearest" });
    } catch (e) {
      showToast(`Error: ${e?.message}`, "error");
    }
    btn.disabled = false;
    btn.textContent = "\uD83D\uDD3A Triangulate";
  });
}

// src/lightbox.ts
function setupPhotoViewer() {
  const tiles = document.querySelectorAll(".photo-tile:not([data-lightbox-ready])");
  tiles.forEach((tile) => {
    const htmlTile = tile;
    htmlTile.setAttribute("data-lightbox-ready", "true");
    htmlTile.style.pointerEvents = "auto";
    htmlTile.style.cursor = "pointer";
    htmlTile.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      const container = htmlTile.closest(".member-photos, t101-profile-photos, .gallery-grid, .photo-grid") || document.body;
      const allTiles = Array.from(container.querySelectorAll(".photo-tile"));
      const urls = allTiles.map((t) => {
        const htmlEl = t;
        const bgImg2 = htmlEl.style.backgroundImage;
        return bgImg2 ? bgImg2.replace(/^url\(["']?/, "").replace(/["']?\)$/, "").split("?")[0] : null;
      }).filter((b) => Boolean(b));
      const bgImg = htmlTile.style.backgroundImage;
      if (!bgImg)
        return;
      let currentUrl = bgImg.replace(/^url\(["']?/, "").replace(/["']?\)$/, "").split("?")[0];
      let currentIndex = urls.indexOf(currentUrl);
      if (currentIndex === -1) {
        currentIndex = 0;
        urls.unshift(currentUrl);
      }
      createFullscreenLightbox(urls, currentIndex);
    });
  });
}
function createFullscreenLightbox(urls, startIndex) {
  if (document.getElementById("custom-photo-lightbox"))
    return;
  document.documentElement.style.setProperty("overflow", "hidden", "important");
  document.body.style.setProperty("overflow", "hidden", "important");
  let currentIndex = startIndex;
  const overlay = document.createElement("div");
  overlay.id = "custom-photo-lightbox";
  overlay.style.cssText = `position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.95); z-index: 100000; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.25s ease;`;
  const img = document.createElement("img");
  img.src = urls[currentIndex];
  img.style.cssText = `max-width: 90%; max-height: 90%; object-fit: contain; box-shadow: 0 0 20px rgba(0,0,0,0.8); border-radius: 4px; user-select: none; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);`;
  const closeBtn = document.createElement("div");
  closeBtn.innerHTML = "&#x2715;";
  closeBtn.style.cssText = `position: absolute; top: 20px; right: 25px; color: #ffffff; font-size: 35px; cursor: pointer; user-select: none; z-index: 100002; transition: transform 0.1s;`;
  function updateImage(index) {
    currentIndex = (index + urls.length) % urls.length;
    img.style.transform = "scale(0.97)";
    img.style.opacity = "0";
    setTimeout(() => {
      img.src = urls[currentIndex];
      img.onload = () => {
        img.style.transform = "scale(1)";
        img.style.opacity = "1";
      };
    }, 150);
  }
  if (urls.length > 1) {
    const leftBtn = document.createElement("div");
    leftBtn.innerHTML = "&#x276E;";
    leftBtn.style.cssText = `position: absolute; left: 30px; top: 50%; transform: translateY(-50%); width: 55px; height: 55px; background: rgba(255,255,255,0.08); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; border-radius: 50%; color: #fff; font-size: 24px; cursor: pointer; user-select: none; transition: all 0.25s ease; border: 1px solid rgba(255,255,255,0.15); z-index: 100001;`;
    leftBtn.onmouseover = () => {
      leftBtn.style.background = "rgba(255,255,255,0.2)";
      leftBtn.style.transform = "translateY(-50%) scale(1.08)";
    };
    leftBtn.onmouseout = () => {
      leftBtn.style.background = "rgba(255,255,255,0.08)";
      leftBtn.style.transform = "translateY(-50%) scale(1)";
    };
    leftBtn.addEventListener("click", (e) => {
      e.stopPropagation();
      updateImage(currentIndex - 1);
    });
    const rightBtn = document.createElement("div");
    rightBtn.innerHTML = "&#x276F;";
    rightBtn.style.cssText = `position: absolute; right: 30px; top: 50%; transform: translateY(-50%); width: 55px; height: 55px; background: rgba(255,255,255,0.08); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; border-radius: 50%; color: #fff; font-size: 24px; cursor: pointer; user-select: none; transition: all 0.25s ease; border: 1px solid rgba(255,255,255,0.15); z-index: 100001;`;
    rightBtn.onmouseover = () => {
      rightBtn.style.background = "rgba(255,255,255,0.2)";
      rightBtn.style.transform = "translateY(-50%) scale(1.08)";
    };
    rightBtn.onmouseout = () => {
      rightBtn.style.background = "rgba(255,255,255,0.08)";
      rightBtn.style.transform = "translateY(-50%) scale(1)";
    };
    rightBtn.addEventListener("click", (e) => {
      e.stopPropagation();
      updateImage(currentIndex + 1);
    });
    overlay.appendChild(leftBtn);
    overlay.appendChild(rightBtn);
  }
  const handleKeyDown = (e) => {
    if (e.key === "ArrowLeft" && urls.length > 1) {
      updateImage(currentIndex - 1);
    } else if (e.key === "ArrowRight" && urls.length > 1) {
      updateImage(currentIndex + 1);
    } else if (e.key === "Escape") {
      closeLightbox();
    }
  };
  const closeLightbox = () => {
    win.removeEventListener("keydown", handleKeyDown);
    overlay.style.opacity = "0";
    setTimeout(() => {
      overlay.remove();
      document.documentElement.style.removeProperty("overflow");
      document.body.style.removeProperty("overflow");
    }, 250);
  };
  win.addEventListener("keydown", handleKeyDown);
  closeBtn.addEventListener("click", closeLightbox);
  overlay.addEventListener("click", (e) => {
    if (e.target === overlay)
      closeLightbox();
  });
  overlay.appendChild(img);
  overlay.appendChild(closeBtn);
  document.body.appendChild(overlay);
  setTimeout(() => overlay.style.opacity = "1", 10);
}

// src/dom.ts
function paintProfileData(profileId) {
  const card = document.getElementById(profileId);
  if (!card || card.dataset.reconInjected)
    return;
  const data = profileDetailsCache.get(profileId);
  const textContainer = card.querySelector(".text-container");
  if (!data || !textContainer)
    return;
  card.dataset.reconInjected = "true";
  if (data.location && data.location.locationId) {
    let locDiv = document.createElement("div");
    locDiv.className = "custom-injected-location";
    locDiv.style.cssText = `font-family: 'Raleway', sans-serif; font-size: 13px; font-weight: 700; color: #e32222; margin: 4px 0; text-align: left;`;
    locDiv.innerHTML = `\uD83D\uDCCD Loading...`;
    const nameNode = textContainer.querySelector(".name");
    if (nameNode)
      nameNode.after(locDiv);
    else
      textContainer.prepend(locDiv);
    const rawLocId = data.location.locationId;
    if (isNaN(Number(rawLocId))) {
      locDiv.innerHTML = `\uD83D\uDCCD ${rawLocId}`;
    } else {
      getLocationFromIndexedDB(rawLocId).then((cachedLoc) => {
        if (cachedLoc) {
          const locName = cachedLoc.name || cachedLoc.longName || rawLocId;
          locDiv.innerHTML = `\uD83D\uDCCD ${locName}`;
        } else {
          let fetchUrl = data.location.locationUrl || `https://www.recon.com/api/location/locations/${rawLocId}`;
          if (!fetchUrl.includes("culture=")) {
            fetchUrl += (fetchUrl.includes("?") ? "&" : "?") + "culture=en";
          }
          win.fetch(fetchUrl, {
            headers: {
              ...state.authHeaders
            }
          }).then((res) => res.json()).then((locData) => {
            const name = locData.shortLocationName || locData.name || locData.longLocationName;
            if (name) {
              const longName = locData.longLocationName || locData.longName || name;
              locDiv.innerHTML = `\uD83D\uDCCD ${name}`;
              saveLocationToIndexedDB(rawLocId, fetchUrl, name, longName);
            } else {
              locDiv.innerHTML = `\uD83D\uDCCD Loc ID: ${rawLocId}`;
            }
          }).catch(() => {
            locDiv.innerHTML = `\uD83D\uDCCD Loc ID: ${rawLocId}`;
          });
        }
      });
    }
  }
  if (data.interests && data.interests.length > 0) {
    let intDiv = document.createElement("div");
    intDiv.className = "custom-injected-interests";
    intDiv.style.cssText = `display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; justify-content: flex-start;`;
    data.interests.forEach((item) => {
      let id = item;
      let text = "";
      if (item && typeof item === "object") {
        id = item.interestId || item.id || item.interest;
        text = item.name || item.text || item.label;
      }
      let span = document.createElement("span");
      span.style.cssText = `background-color: rgba(255, 255, 255, 0.2); color: #fff; padding: 3px 8px; border-radius: 10px; font-size: 11px; font-weight: bold;`;
      span.textContent = text || interestMap[id] || `Tag: ${id}`;
      intDiv.appendChild(span);
    });
    textContainer.appendChild(intDiv);
  }
}
function paintProfileDetailPage(data) {
  if (!data || !data.lastUpdatedDate)
    return;
  let container = document.querySelector(".profile-header, t101-profile-header, .member-info, .profile-info-container");
  if (!container) {
    container = document.querySelector("h1");
  }
  if (!container) {
    const cruiseBtn = document.querySelector('a[href*="/cruise"], button:has(.fa-ship), [class*="cruise"], button:has(t101-icon-cruise), button:has(t101-icon-cruise-icon), t101-icon-cruise, t101-icon-cruise-icon, button:has([class*="cruise"]), button:has([id*="cruise"])');
    if (cruiseBtn) {
      container = cruiseBtn.parentElement;
    }
  }
  if (!container)
    return;
  if (document.getElementById("custom-profile-last-updated"))
    return;
  const dateDiv = document.createElement("div");
  dateDiv.id = "custom-profile-last-updated";
  dateDiv.style.cssText = `
        font-family: 'Raleway', sans-serif;
        font-size: 14px;
        font-weight: 700;
        color: #adadad;
        margin: 10px 0;
        display: inline-flex;
        align-items: center;
        gap: 6px;
    `;
  try {
    const dateObj = new Date(data.lastUpdatedDate);
    const formattedDate = dateObj.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
    dateDiv.innerHTML = `\uD83D\uDCC5 Profile Updated: <span style="color: #fff; font-weight: 800;">${formattedDate}</span>`;
  } catch (e) {
    dateDiv.innerHTML = `\uD83D\uDCC5 Profile Updated: <span style="color: #fff; font-weight: 800;">${data.lastUpdatedDate}</span>`;
  }
  if (container.tagName === "H1") {
    container.after(dateDiv);
  } else {
    container.appendChild(dateDiv);
  }
}
function injectViewedButton() {
  const cruiseBtn = document.querySelector('a[href*="/cruise"], button:has(.fa-ship), [class*="cruise"], button:has(t101-icon-cruise), button:has(t101-icon-cruise-icon), t101-icon-cruise, t101-icon-cruise-icon, button:has([class*="cruise"]), button:has([id*="cruise"])');
  if (cruiseBtn) {
    const targetEl = cruiseBtn.closest("button") || cruiseBtn.closest("a") || cruiseBtn;
    let viewBtn = document.getElementById("manual-view-btn");
    if (!viewBtn) {
      viewBtn = document.createElement("button");
      viewBtn.id = "manual-view-btn";
      viewBtn.textContent = "Show As Viewed";
      viewBtn.style.cssText = `background-color: #2b2b2b; color: #ffffff; border: 1px solid #444; padding: 8px 16px; margin-left: 8px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 14px; transition: background 0.2s;`;
      viewBtn.onmouseover = () => viewBtn.style.backgroundColor = "#3d3d3d";
      viewBtn.onmouseout = () => viewBtn.style.backgroundColor = "#2b2b2b";
      viewBtn.addEventListener("click", () => {
        if (!state.lastBlockedRequest)
          return showToast("No profile visit tracker captured yet.", "error");
        const xhr = new origXHR;
        xhr._isManualTrigger = true;
        xhr.open(state.lastBlockedRequest.method, state.lastBlockedRequest.url);
        if (state.lastBlockedRequest.headers) {
          for (const [key, value] of Object.entries(state.lastBlockedRequest.headers)) {
            xhr.setRequestHeader(key, value);
          }
        }
        xhr.onload = function() {
          if (this.status >= 200 && this.status < 300) {
            showToast("Profile marked as viewed successfully!");
          } else {
            let errorMsg = `Failed to contact server: Status ${this.status}`;
            try {
              const errData = JSON.parse(this.responseText);
              if (errData && errData.title) {
                errorMsg = errData.title;
              }
            } catch (e) {}
            showToast(errorMsg, "error");
          }
        };
        xhr.onerror = () => showToast("Failed to contact server.", "error");
        xhr.send(state.lastBlockedRequest.body);
      });
      targetEl.parentNode.insertBefore(viewBtn, targetEl.nextSibling);
    }
    if (!document.getElementById("custom-export-profile-btn")) {
      const exportBtn = document.createElement("button");
      exportBtn.id = "custom-export-profile-btn";
      exportBtn.textContent = "Export Profile";
      exportBtn.style.cssText = `background-color: #2b2b2b; color: #ffffff; border: 1px solid #444; padding: 8px 16px; margin-left: 8px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 14px; transition: background 0.2s;`;
      exportBtn.onmouseover = () => exportBtn.style.backgroundColor = "#3d3d3d";
      exportBtn.onmouseout = () => exportBtn.style.backgroundColor = "#2b2b2b";
      exportBtn.addEventListener("click", exportProfileData);
      viewBtn.parentNode.insertBefore(exportBtn, viewBtn.nextSibling);
    }
    injectLocateButton();
  }
}
function paintPremiumGallery(profileId) {
  const photos = profileMediaMap.get(profileId);
  if (!photos || photos.length === 0)
    return;
  if (document.getElementById("custom-premium-gallery"))
    return;
  let feed = document.querySelector("t101-profile-member-feed, .newsfeed");
  let photoGrid = document.querySelector("t101-profile-latest-photos-panel, .latest-photos-block, .photo-tile-container, t101-profile-photos, .member-photos, .gallery-grid, .photo-grid");
  let parent = feed ? feed.parentElement : photoGrid ? photoGrid.parentElement : null;
  if (!parent) {
    parent = document.querySelector(".profile-header, t101-profile-header, .member-info, .profile-info-container, t101-profile-main, .profile-main-container");
  }
  if (!parent)
    return;
  const galleryDiv = document.createElement("div");
  galleryDiv.id = "custom-premium-gallery";
  galleryDiv.style.cssText = `
        margin: 24px 0;
        padding: 20px;
        background: rgba(255, 255, 255, 0.03);
        backdrop-filter: blur(12px);
        -webkit-backdrop-filter: blur(12px);
        border: 1px solid rgba(255, 255, 255, 0.08);
        border-radius: 12px;
        font-family: 'Raleway', sans-serif;
        clear: both;
    `;
  const titleDiv = document.createElement("div");
  titleDiv.style.cssText = `
        display: flex;
        align-items: center;
        gap: 8px;
        margin-bottom: 16px;
    `;
  titleDiv.innerHTML = `
        <span style="font-size: 20px;">\uD83D\uDC51</span>
        <h3 style="margin: 0; font-family: 'Barlow', sans-serif; font-size: 20px; font-weight: 800; background: linear-gradient(90deg, #ff3333, #ff7b00); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-transform: uppercase; letter-spacing: 0.5px;">Recon+ Premium Photos</h3>
        <span style="background: rgba(255, 51, 51, 0.15); border: 1px solid rgba(255, 51, 51, 0.3); color: #ff3333; font-size: 10px; font-weight: 800; padding: 2px 6px; border-radius: 4px; text-transform: uppercase; margin-left: auto;">Unlocked</span>
    `;
  galleryDiv.appendChild(titleDiv);
  const grid = document.createElement("div");
  grid.style.cssText = `
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
        gap: 12px;
        margin-top: 16px;
    `;
  photos.forEach((photoUrl, index) => {
    let previewUrl = photoUrl;
    if (previewUrl.includes("size=")) {
      previewUrl = previewUrl.replace(/size=\d+/, "size=102");
    } else {
      previewUrl += (previewUrl.includes("?") ? "&" : "?") + "size=102";
    }
    if (!previewUrl.includes("deviceTypeId=")) {
      previewUrl += "&deviceTypeId=1";
    }
    const tile = document.createElement("div");
    tile.className = "photo-tile";
    tile.style.cssText = `
            aspect-ratio: 1;
            background-image: url('${previewUrl}');
            background-size: cover;
            background-position: center;
            border-radius: 8px;
            cursor: pointer;
            border: 1px solid rgba(255, 255, 255, 0.05);
            transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.25s, border-color 0.25s;
        `;
    tile.onmouseover = () => {
      tile.style.transform = "scale(1.05)";
      tile.style.boxShadow = "0 8px 16px rgba(0, 0, 0, 0.4)";
      tile.style.borderColor = "rgba(255, 255, 255, 0.2)";
    };
    tile.onmouseout = () => {
      tile.style.transform = "scale(1)";
      tile.style.boxShadow = "none";
      tile.style.borderColor = "rgba(255, 255, 255, 0.05)";
    };
    tile.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      const highResUrls = photos.map((u) => u.split("?")[0]);
      createFullscreenLightbox(highResUrls, index);
    });
    grid.appendChild(tile);
  });
  galleryDiv.appendChild(grid);
  if (feed) {
    feed.parentNode.insertBefore(galleryDiv, feed);
  } else if (photoGrid) {
    photoGrid.after(galleryDiv);
  } else {
    parent.appendChild(galleryDiv);
  }
}

// src/db.ts
function getLocationFromIndexedDB(locationId) {
  return new Promise((resolve) => {
    try {
      const request = win.indexedDB.open("RECON_DB");
      request.onsuccess = function(event) {
        const db = event.target.result;
        if (!db.objectStoreNames.contains("App_Locations")) {
          db.close();
          resolve(null);
          return;
        }
        const transaction = db.transaction("App_Locations", "readonly");
        const store = transaction.objectStore("App_Locations");
        function checkVal(val) {
          if (val && val.data) {
            if (val.expiry) {
              const expiryDate = new Date(val.expiry);
              if (expiryDate > new Date) {
                return val.data;
              }
            } else {
              return val.data;
            }
          }
          return null;
        }
        const getRequest = store.get(Number(locationId));
        getRequest.onsuccess = function() {
          let result = checkVal(getRequest.result);
          if (result) {
            db.close();
            resolve(result);
          } else {
            const getRequestStr = store.get(String(locationId));
            getRequestStr.onsuccess = function() {
              db.close();
              resolve(checkVal(getRequestStr.result));
            };
            getRequestStr.onerror = function() {
              db.close();
              resolve(null);
            };
          }
        };
        getRequest.onerror = function() {
          db.close();
          resolve(null);
        };
      };
      request.onerror = function() {
        resolve(null);
      };
    } catch (e) {
      resolve(null);
    }
  });
}
function saveLocationToIndexedDB(locationId, locationUrl, name, longName) {
  try {
    const request = win.indexedDB.open("RECON_DB");
    request.onsuccess = function(event) {
      const db = event.target.result;
      if (!db.objectStoreNames.contains("App_Locations")) {
        db.close();
        return;
      }
      const transaction = db.transaction("App_Locations", "readwrite");
      const store = transaction.objectStore("App_Locations");
      const expiryDate = new Date;
      expiryDate.setFullYear(expiryDate.getFullYear() + 1);
      const value = {
        data: {
          locationId: Number(locationId),
          locationUrl,
          longName,
          name
        },
        expiry: expiryDate.toString()
      };
      store.put(value, Number(locationId));
      transaction.oncomplete = function() {
        db.close();
      };
    };
  } catch (e) {
    console.error("[Script] Error saving location to IndexedDB", e);
  }
}
function getFromStore(storeName, profileId) {
  return new Promise((resolve) => {
    try {
      const request = win.indexedDB.open("RECON_DB");
      request.onsuccess = function(event) {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(storeName)) {
          db.close();
          resolve(null);
          return;
        }
        const transaction = db.transaction(storeName, "readonly");
        const store = transaction.objectStore(storeName);
        function checkVal(val) {
          if (val) {
            if (val.data) {
              if (val.expiry) {
                const expiryDate = new Date(val.expiry);
                if (expiryDate > new Date) {
                  return val.data;
                }
              } else {
                return val.data;
              }
            }
            return val;
          }
          return null;
        }
        const dbKey = /^\d+$/.test(String(profileId)) ? Number(profileId) : String(profileId);
        const getRequest = store.get(dbKey);
        getRequest.onsuccess = function() {
          let result = checkVal(getRequest.result);
          if (result) {
            db.close();
            resolve(result);
          } else {
            const fallbackKey = typeof dbKey === "number" ? String(dbKey) : isNaN(Number(dbKey)) ? null : Number(dbKey);
            if (fallbackKey !== null) {
              const getRequestFallback = store.get(fallbackKey);
              getRequestFallback.onsuccess = function() {
                db.close();
                resolve(checkVal(getRequestFallback.result));
              };
              getRequestFallback.onerror = function() {
                db.close();
                resolve(null);
              };
            } else {
              db.close();
              resolve(null);
            }
          }
        };
        getRequest.onerror = function() {
          db.close();
          resolve(null);
        };
      };
      request.onerror = function() {
        resolve(null);
      };
    } catch (e) {
      resolve(null);
    }
  });
}
async function getProfileFromIndexedDB(profileId) {
  try {
    const [profileCache, profileDetail] = await Promise.all([
      getFromStore("Profile_Cache", profileId),
      getFromStore("Profile_Detail_Cache", profileId)
    ]);
    if (!profileCache && !profileDetail) {
      return null;
    }
    return {
      ...profileCache || {},
      ...profileDetail || {}
    };
  } catch (e) {
    console.error("[Script] Error getting profile from IndexedDB", e);
    return null;
  }
}
function saveProfileToIndexedDB(profileId, data) {
  try {
    const request = win.indexedDB.open("RECON_DB");
    request.onsuccess = function(event) {
      const db = event.target.result;
      if (!db.objectStoreNames.contains("Profile_Cache")) {
        db.close();
        return;
      }
      const transaction = db.transaction("Profile_Cache", "readwrite");
      const store = transaction.objectStore("Profile_Cache");
      const dbKey = /^\d+$/.test(String(profileId)) ? Number(profileId) : String(profileId);
      const getRequest = store.get(dbKey);
      getRequest.onsuccess = function() {
        let existingData = null;
        const val = getRequest.result;
        if (val) {
          existingData = val.data ? val.data : val;
        }
        const mergedData = existingData ? { ...existingData, ...data } : data;
        const expiryDate = new Date;
        expiryDate.setFullYear(expiryDate.getFullYear() + 1);
        const value = {
          data: mergedData,
          expiry: expiryDate.toString()
        };
        store.put(value, dbKey);
      };
      transaction.oncomplete = function() {
        db.close();
      };
    };
  } catch (e) {
    console.error("[Script] Error saving profile to IndexedDB", e);
  }
}
function checkAndPaintProfileFromIndexedDB() {
  const match = win.location.pathname.match(/\/(?:members|profiles)\/([a-f0-9-]{36}|[a-zA-Z0-9_-]+)/i);
  if (match) {
    const profileId = match[1];
    const uuidOrNumRegex = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|\d+)$/i;
    if (profileId && uuidOrNumRegex.test(profileId)) {
      getProfileFromIndexedDB(profileId).then((cachedProfile) => {
        if (cachedProfile) {
          state.lastProfileDetailPageData = cachedProfile;
          paintProfileDetailPage(cachedProfile);
        }
      });
    }
  }
}

// src/xhr.ts
function setupXHRInterceptor() {
  win.XMLHttpRequest = function() {
    const xhr = new origXHR;
    const origOpen = xhr.open;
    const origSend = xhr.send;
    const origSetHeader = xhr.setRequestHeader;
    xhr._headers = {};
    xhr.setRequestHeader = function(header, value) {
      if (this._headers) {
        this._headers[header] = value;
      }
      return origSetHeader.apply(this, arguments);
    };
    xhr.open = function(method, url) {
      if (typeof url === "string" && url.includes("/api/profileSearch/profiles")) {
        try {
          const saved = win.localStorage.getItem("recon_advanced_filters");
          if (saved) {
            const filters = JSON.parse(saved);
            const isRelative = !url.startsWith("http:") && !url.startsWith("https:") && !url.startsWith("//");
            const urlObj = new URL(url, win.location.origin || "https://www.recon.com");
            const bodyTypeMap = {
              athletic: 1,
              average: 2,
              large: 3,
              muscled: 4,
              slim: 5
            };
            const bodyHairMap = {
              average: 1,
              hairy: 2,
              none: 3,
              shaved: 4,
              some: 5
            };
            const ethnicityMap = {
              asian: 1,
              black: 2,
              hispanic: 3,
              latino: 4,
              "middle eastern": 5,
              "mixed race": 6,
              "south asian": 7,
              white: 8,
              other: 9
            };
            const getActiveMinutes = (activeWithin) => {
              switch (activeWithin) {
                case "now":
                  return 10;
                case "hour":
                  return 60;
                case "day":
                  return 1440;
                case "week":
                  return 10080;
                case "any":
                  return 525600;
                default:
                  return 43200;
              }
            };
            if (filters.activeWithin) {
              urlObj.searchParams.set("activeWithinMinutes", String(getActiveMinutes(filters.activeWithin)));
            }
            if (filters.onlyPhotos) {
              urlObj.searchParams.set("hasPhoto", "true");
              urlObj.searchParams.set("hasPhotos", "true");
              urlObj.searchParams.set("photo", "true");
            } else {
              urlObj.searchParams.delete("hasPhoto");
              urlObj.searchParams.delete("hasPhotos");
              urlObj.searchParams.delete("photo");
            }
            if (filters.onlyNew) {
              urlObj.searchParams.set("isNew", "true");
              urlObj.searchParams.set("new", "true");
            } else {
              urlObj.searchParams.delete("isNew");
              urlObj.searchParams.delete("new");
            }
            if (filters.onlyPremium) {
              urlObj.searchParams.set("isPremium", "true");
              urlObj.searchParams.set("premium", "true");
            } else {
              urlObj.searchParams.delete("isPremium");
              urlObj.searchParams.delete("premium");
            }
            if (filters.heightMin) {
              urlObj.searchParams.set("heightMin", String(filters.heightMin));
              urlObj.searchParams.set("minHeight", String(filters.heightMin));
            } else {
              urlObj.searchParams.delete("heightMin");
              urlObj.searchParams.delete("minHeight");
            }
            if (filters.heightMax) {
              urlObj.searchParams.set("heightMax", String(filters.heightMax));
              urlObj.searchParams.set("maxHeight", String(filters.heightMax));
            } else {
              urlObj.searchParams.delete("heightMax");
              urlObj.searchParams.delete("maxHeight");
            }
            if (filters.bodyTypes && filters.bodyTypes.length > 0) {
              const ids = filters.bodyTypes.map((t) => bodyTypeMap[t.toLowerCase()]).filter(Boolean).join(",");
              urlObj.searchParams.set("bodyTypeIds", ids);
            } else {
              urlObj.searchParams.set("bodyTypeIds", "");
            }
            if (filters.bodyHairs && filters.bodyHairs.length > 0) {
              const ids = filters.bodyHairs.map((h) => bodyHairMap[h.toLowerCase()]).filter(Boolean).join(",");
              urlObj.searchParams.set("bodyHairIds", ids);
            } else {
              urlObj.searchParams.set("bodyHairIds", "");
            }
            if (filters.ethnicities && filters.ethnicities.length > 0) {
              const ids = filters.ethnicities.map((e) => ethnicityMap[e.toLowerCase()]).filter(Boolean).join(",");
              urlObj.searchParams.set("ethnicityIds", ids);
            } else {
              urlObj.searchParams.set("ethnicityIds", "");
            }
            url = isRelative ? urlObj.pathname + urlObj.search + urlObj.hash : urlObj.toString();
            console.log(`[Recon+] Intercepted Search request, overwrote params: ${url}`);
          }
        } catch (e) {
          console.error("[Recon+] Error patching search parameters:", e);
        }
      }
      this._customUrl = url;
      if (typeof url === "string" && url.includes("/visitedProfiles/")) {
        if (!this._isManualTrigger) {
          this.send = function(body) {
            state.lastBlockedRequest = {
              url,
              method,
              headers: this._headers || {},
              body
            };
            Object.defineProperty(this, "readyState", { value: 4 });
            Object.defineProperty(this, "status", { value: 200 });
            Object.defineProperty(this, "responseText", { value: '{"success":true}' });
            if (this.onreadystatechange)
              this.onreadystatechange();
            if (this.onload)
              this.onload();
          };
          return origOpen.apply(this, arguments);
        }
      }
      return origOpen.apply(this, arguments);
    };
    xhr.send = function(body) {
      if (this._headers) {
        const authKeys = ["authorization", "x-xsrf-token", "client-id", "x-client-id"];
        for (const key of Object.keys(this._headers)) {
          if (authKeys.includes(key.toLowerCase())) {
            state.authHeaders[key] = this._headers[key];
          }
        }
      }
      this.addEventListener("readystatechange", function() {
        if (this.readyState === 4 && this._customUrl) {
          if (this._customUrl.includes("/api/profile/profiles/")) {
            const match = this._customUrl.match(/\/api\/profile\/profiles\/([a-f0-9-]{36})(?:\?|$)/);
            if (match) {
              try {
                const data = JSON.parse(this.responseText);
                data.isPremium = true;
                data.premium = true;
                data.membershipType = "Premium";
                data.membership = { type: "Premium", status: "Active" };
                const modifiedText = JSON.stringify(data);
                Object.defineProperty(this, "responseText", {
                  get: function() {
                    return modifiedText;
                  },
                  configurable: true
                });
                Object.defineProperty(this, "response", {
                  get: function() {
                    return modifiedText;
                  },
                  configurable: true
                });
                console.log("[Recon+] Mocked profile summary response to Premium.");
              } catch (e) {}
            }
          }
        }
      });
      this.addEventListener("load", function() {
        if (this._customUrl) {
          if (this._customUrl.includes("/api/profileSearch/profiles")) {
            console.log(`[Recon+] Intercepted search URL: ${this._customUrl}`);
            try {
              const data = JSON.parse(this.responseText);
              console.log(`[Recon+] Search returned profiles:`, data?.data?.length || 0);
              if (data && Array.isArray(data.data)) {
                data.data.forEach((p) => {
                  if (p.profileId && p.profileUrl) {
                    const match = p.profileUrl.match(/\/profiles\/([^/]+)\/(\d+)/);
                    if (match) {
                      const uuid = match[1];
                      const ver = parseInt(match[2], 10);
                      profileVersionsMap.set(uuid, ver);
                      console.log(`[Recon+] Mapped version: ${uuid} -> ${ver}`);
                    } else {
                      console.log(`[Recon+] Could not parse version from profileUrl: ${p.profileUrl}`);
                    }
                  }
                });
              }
            } catch (e) {
              console.error("[Script] Error parsing profileSearch data", e);
            }
          } else if (this._customUrl.includes("/api/media/profiles/") && this._customUrl.includes("/fileProperties")) {
            console.log(`[Recon+] Intercepted fileProperties URL: ${this._customUrl}`);
            try {
              const data = JSON.parse(this.responseText);
              const match = this._customUrl.match(/\/api\/media\/profiles\/([^/]+)\/fileProperties/);
              if (match && match[1]) {
                const profileId = match[1];
                const urls = extractPhotoUrls(data);
                console.log(`[Recon+] Captured ${urls.length} media files for profile: ${profileId}`);
                profileMediaMap.set(profileId, urls);
                setTimeout(() => paintPremiumGallery(profileId), 50);
              }
            } catch (e) {
              console.error("[Script] Error parsing fileProperties data", e);
            }
          } else if (this._customUrl.includes("/api/profile/profiles/")) {
            console.log(`[Recon+] Intercepted profile URL: ${this._customUrl}`);
            try {
              const data = JSON.parse(this.responseText);
              if (data && data.id) {
                if (this._customUrl.includes("/detail/")) {
                  console.log(`[Recon+] Intercepted profile detail: ${data.id}, version: ${data.version}`);
                  state.lastProfileDetailPageData = data;
                  const existing = document.getElementById("custom-profile-last-updated");
                  if (existing)
                    existing.remove();
                  setTimeout(() => paintProfileDetailPage(data), 50);
                  data.isDetailed = true;
                  const cachedSummary = profileDetailsCache.get(data.id);
                  const merged = cachedSummary ? { ...cachedSummary, ...data } : data;
                  profileDetailsCache.set(data.id, merged);
                  saveProfileToIndexedDB(data.id, merged);
                } else {
                  console.log(`[Recon+] Intercepted profile summary: ${data.id}`);
                  profileDetailsCache.set(data.id, data);
                  setTimeout(() => paintProfileData(data.id), 50);
                }
              }
            } catch (e) {
              console.error("[Script] Error parsing profile data", e);
            }
          }
        }
      });
      return origSend.apply(this, arguments);
    };
    return xhr;
  };
}

// src/styles.ts
function injectStyles() {
  const style = document.createElement("style");
  style.innerHTML = `
        t101-profile-list-filter { display: none !important; }
        .cdk-overlay-container:has(t101-paywall), t101-paywall { display: none !important; opacity: 0 !important; pointer-events: none !important; }
        html.cdk-global-scrollblock, body { position: static !important; overflow-y: auto !important; overflow: auto !important; width: auto !important; }
        .photo-tile { pointer-events: auto !important; cursor: pointer !important; }
        .text-container .interests-container { display: none !important; }
        .text-container { height: auto !important; max-height: none !important; padding-bottom: 15px !important; }

        /* Recon+ Custom Filter Panel */
        #recon-filter-panel {
            background-color: #0b0b0d !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            border-radius: 0px !important;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6) !important;
            padding: 16px !important;
            max-height: 70vh !important;
            display: flex !important;
            flex-direction: column !important;
            box-sizing: border-box !important;
        }
        .recon-filter-scroll-container {
            max-height: 55vh !important;
            overflow-y: auto !important;
            overflow-x: hidden !important;
            padding-right: 6px !important;
            margin: 12px 0 !important;
            display: block !important; /* Block layout ensures scrolling */
            box-sizing: border-box !important;
        }
        .recon-filter-scroll-container::-webkit-scrollbar {
            width: 6px !important;
        }
        .recon-filter-scroll-container::-webkit-scrollbar-track {
            background: rgba(0, 0, 0, 0.2) !important;
            border-radius: 3px !important;
        }
        .recon-filter-scroll-container::-webkit-scrollbar-thumb {
            background: rgba(255, 255, 255, 0.12) !important;
            border-radius: 3px !important;
        }
        .recon-filter-scroll-container::-webkit-scrollbar-thumb:hover {
            background: rgba(255, 51, 51, 0.5) !important;
        }
        .recon-apply-btn {
            background: #ff3333 !important;
            color: #fff !important;
            border: none !important;
            border-radius: 6px !important;
            padding: 10px 16px !important;
            font-size: 13px !important;
            font-weight: 800 !important;
            cursor: pointer !important;
            font-family: 'Barlow', sans-serif !important;
            text-transform: uppercase !important;
            letter-spacing: 0.5px !important;
            transition: all 0.2s !important;
            width: 100% !important;
            margin-bottom: 8px !important;
            box-sizing: border-box !important;
            display: block !important;
            flex-shrink: 0 !important;
        }
        .recon-apply-btn:hover {
            background: #e62e2e !important;
            box-shadow: 0 4px 12px rgba(255, 51, 51, 0.3) !important;
        }
        .recon-apply-btn:active {
            transform: scale(0.98) !important;
        }
        .recon-filter-section {
            display: flex !important;
            flex-direction: column !important;
            gap: 8px !important;
            padding-bottom: 12px !important;
            border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important;
            margin-bottom: 12px !important;
            box-sizing: border-box !important;
        }
        .recon-filter-section:last-child {
            border-bottom: none !important;
            margin-bottom: 0 !important;
            padding-bottom: 0 !important;
        }
        .recon-filter-section-title {
            font-size: 13px !important;
            font-weight: 800 !important;
            color: #adadad !important;
            text-transform: uppercase !important;
            letter-spacing: 0.5px !important;
            font-family: 'Barlow', sans-serif !important;
            margin-bottom: 4px !important;
        }
        .recon-filter-row {
            display: flex !important;
            gap: 10px !important;
            align-items: center !important;
            width: 100% !important;
            box-sizing: border-box !important;
        }
        .recon-filter-row > * {
            flex: 1 1 0% !important;
            min-width: 0 !important;
        }
        .recon-chip-container {
            display: flex !important;
            flex-wrap: wrap !important;
            gap: 6px !important;
            margin-top: 4px !important;
        }
        .recon-filter-chip {
            background: rgba(255, 255, 255, 0.06) !important;
            color: #adadad !important;
            border: 1px solid rgba(255, 255, 255, 0.08) !important;
            padding: 5px 10px !important;
            border-radius: 20px !important;
            font-size: 12px !important;
            font-weight: 700 !important;
            cursor: pointer !important;
            user-select: none !important;
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
            font-family: 'Raleway', sans-serif !important;
            text-align: center !important;
        }
        .recon-filter-chip:hover {
            background: rgba(255, 255, 255, 0.12) !important;
            color: #fff !important;
            border-color: rgba(255, 255, 255, 0.2) !important;
        }
        .recon-filter-chip.selected {
            background: #ff3333 !important;
            color: #fff !important;
            border-color: #ff3333 !important;
            box-shadow: 0 2px 8px rgba(255, 51, 51, 0.4) !important;
        }
        .recon-filter-input {
            width: 100% !important;
            background: rgba(0, 0, 0, 0.3) !important;
            border: 1px solid rgba(255, 255, 255, 0.1) !important;
            border-radius: 6px !important;
            color: #fff !important;
            padding: 8px 12px !important;
            font-size: 13px !important;
            outline: none !important;
            box-sizing: border-box !important;
            font-family: 'Raleway', sans-serif !important;
            transition: border-color 0.2s, background 0.2s !important;
        }
        .recon-filter-input:focus {
            border-color: #ff3333 !important;
            background: rgba(0, 0, 0, 0.4) !important;
        }
        .recon-filter-select {
            cursor: pointer;
            appearance: none;
            background-image: url("data:image/svg+xml;utf8,<svg fill='white' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>");
            background-repeat: no-repeat;
            background-position: right 8px center;
            background-size: 16px;
            padding-right: 28px !important;
        }
    `;
  if (document.head) {
    document.head.appendChild(style);
  } else {
    const docObserver = new MutationObserver(() => {
      if (document.head) {
        document.head.appendChild(style);
        docObserver.disconnect();
      }
    });
    docObserver.observe(document.documentElement, { childList: true });
  }
}

// src/filter.ts
var advancedFilterState = {
  keyword: "",
  activeWithin: "any",
  onlyPhotos: false,
  onlyNew: false,
  onlyPremium: false,
  heightMin: 0,
  heightMax: 0,
  bodyTypes: [],
  bodyHairs: [],
  ethnicities: []
};
var savedFilters = win.localStorage.getItem("recon_advanced_filters");
if (savedFilters) {
  try {
    const parsed = JSON.parse(savedFilters);
    Object.assign(advancedFilterState, parsed);
  } catch (e) {
    console.error("[Recon+] Error loading saved filters", e);
  }
}
var filterState = {
  fetchQueue: [],
  activeFetches: 0,
  queueTotal: 0,
  queueCompleted: 0
};
var maxConcurrentFetches = 2;
var fetchDelayMs = 250;
function getActiveCards() {
  const uuidOrNumRegex = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|\d+)$/i;
  return Array.from(document.querySelectorAll("[id]")).filter((el) => uuidOrNumRegex.test(el.id) && (el.querySelector(".text-container") !== null || el.querySelector(".photo-tile") !== null || el.classList.contains("photo-tile")));
}
function containsKeyword(obj, keyword) {
  if (!obj)
    return false;
  if (typeof obj === "string") {
    const val = obj.toLowerCase();
    if (val.startsWith("http") || val.includes("/photos/") || val.includes("/images/")) {
      return false;
    }
    return val.includes(keyword);
  }
  if (typeof obj === "object") {
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        const lowerKey = key.toLowerCase();
        if (lowerKey.endsWith("id") || lowerKey.includes("url") || lowerKey.includes("image") || lowerKey.includes("photo") || lowerKey.includes("date") || lowerKey.includes("expiry") || lowerKey === "lat" || lowerKey === "lon" || lowerKey === "latitude" || lowerKey === "longitude" || lowerKey.includes("distance") || lowerKey.includes("status") || lowerKey.includes("culture")) {
          continue;
        }
        if (containsKeyword(obj[key], keyword)) {
          return true;
        }
      }
    }
  }
  return false;
}
async function applyFilterToCard(cardEl, stateObj) {
  const profileId = cardEl.id;
  if (!stateObj.keyword) {
    cardEl.style.removeProperty("display");
    cardEl.style.removeProperty("flex-grow");
    cardEl.style.removeProperty("flex-shrink");
    cardEl.style.removeProperty("max-width");
    return;
  }
  let data = profileDetailsCache.get(profileId);
  if (!data) {
    data = await getProfileFromIndexedDB(profileId) || undefined;
    if (data) {
      profileDetailsCache.set(profileId, data);
    }
  }
  const isDetailed = data && (data.isDetailed || typeof data.longText === "string");
  if (isDetailed && data) {
    if (containsKeyword(data, stateObj.keyword)) {
      cardEl.style.removeProperty("display");
      cardEl.style.setProperty("flex-grow", "0", "important");
      cardEl.style.setProperty("flex-shrink", "0", "important");
      cardEl.style.setProperty("max-width", "220px", "important");
    } else {
      cardEl.style.setProperty("display", "none", "important");
    }
  } else {
    cardEl.style.removeProperty("display");
    cardEl.style.setProperty("flex-grow", "0", "important");
    cardEl.style.setProperty("flex-shrink", "0", "important");
    cardEl.style.setProperty("max-width", "220px", "important");
    enqueueProfileFetch(profileId);
  }
}
function enqueueProfileFetch(profileId) {
  if (filterState.fetchQueue.includes(profileId))
    return;
  filterState.fetchQueue.push(profileId);
  filterState.queueTotal = filterState.fetchQueue.length + filterState.queueCompleted;
  updateFilterStatusUI();
  processFetchQueue();
}
function getProfilePathFromCard(cardEl) {
  const profileId = cardEl.id;
  const links = Array.from(cardEl.querySelectorAll("a"));
  for (const link of links) {
    const href = link.getAttribute("href") || "";
    const match = href.match(/\/members?\/([^?#]+)/);
    if (match) {
      return match[1].replace(/\/$/, "");
    }
  }
  return profileId;
}
function processFetchQueue() {
  if (filterState.fetchQueue.length === 0 || filterState.activeFetches >= maxConcurrentFetches) {
    return;
  }
  if (Object.keys(state.authHeaders).length === 0) {
    setTimeout(processFetchQueue, 1000);
    return;
  }
  const profileId = filterState.fetchQueue.shift();
  if (!profileId)
    return;
  filterState.activeFetches++;
  updateFilterStatusUI();
  const card = document.getElementById(profileId);
  fetchProfileDetail(profileId, card).then((data) => {
    filterState.activeFetches--;
    filterState.queueCompleted++;
    updateFilterStatusUI();
    if (data) {
      data.isDetailed = true;
      const cachedSummary = profileDetailsCache.get(profileId);
      const merged = cachedSummary ? { ...cachedSummary, ...data } : data;
      profileDetailsCache.set(profileId, merged);
      saveProfileToIndexedDB(profileId, merged);
      if (card) {
        applyFilterToCard(card, advancedFilterState);
        updateFilterStatusUI();
      }
    }
    setTimeout(processFetchQueue, fetchDelayMs);
  }).catch((err) => {
    console.error(`[Recon+] Error fetching profile ${profileId}:`, err);
    filterState.activeFetches--;
    filterState.queueCompleted++;
    updateFilterStatusUI();
    setTimeout(processFetchQueue, fetchDelayMs);
  });
  if (filterState.activeFetches < maxConcurrentFetches && filterState.fetchQueue.length > 0) {
    processFetchQueue();
  }
}
async function fetchProfileDetail(profileId, cardEl) {
  let data = profileDetailsCache.get(profileId);
  if (data && (data.isDetailed || typeof data.longText === "string")) {
    return data;
  }
  data = await getProfileFromIndexedDB(profileId) || undefined;
  if (data && (data.isDetailed || typeof data.longText === "string")) {
    profileDetailsCache.set(profileId, data);
    return data;
  }
  const path = cardEl ? getProfilePathFromCard(cardEl) : profileId;
  const parts = path.split("/");
  const uuid = parts[0];
  let version = parts[1] || null;
  if (!version) {
    version = profileVersionsMap.get(uuid) || null;
  }
  if (!version) {
    const summary = profileDetailsCache.get(uuid);
    if (summary) {
      if (summary.version) {
        version = summary.version;
      } else if (summary.profileUrl) {
        const match = summary.profileUrl.match(/\/profiles\/([^/]+)\/(\d+)/);
        if (match) {
          version = match[2];
        }
      }
    }
  }
  if (!version) {
    version = 1;
  }
  const targetUrl = `/api/profile/profiles/${uuid}/detail/${version}?culture=en`;
  const res = await win.fetch(targetUrl, {
    headers: {
      ...state.authHeaders
    }
  });
  if (!res.ok) {
    throw new Error(`Fetch status: ${res.status}`);
  }
  const json = await res.json();
  return json;
}
function triggerFilterChange() {
  win.localStorage.setItem("recon_advanced_filters", JSON.stringify(advancedFilterState));
  filterState.fetchQueue = [];
  filterState.queueTotal = 0;
  filterState.queueCompleted = 0;
  const cards = getActiveCards();
  cards.forEach((card) => {
    applyFilterToCard(card, advancedFilterState);
  });
  processFetchQueue();
}
function updateFilterStatusUI() {
  const statusEl = document.getElementById("recon-filter-status");
  const badgeEl = document.getElementById("recon-filter-count");
  const loadMoreEl = document.getElementById("recon-filter-load-more");
  if (!statusEl)
    return;
  const cards = getActiveCards();
  const total = cards.length;
  const visible = cards.filter((c) => c.style.display !== "none").length;
  if (loadMoreEl) {
    const hasActiveFilter = advancedFilterState.keyword || advancedFilterState.activeWithin !== "any" || advancedFilterState.onlyPhotos || advancedFilterState.onlyNew || advancedFilterState.onlyPremium || advancedFilterState.heightMin || advancedFilterState.heightMax || advancedFilterState.bodyTypes.length > 0 || advancedFilterState.bodyHairs.length > 0 || advancedFilterState.ethnicities.length > 0;
    loadMoreEl.style.display = hasActiveFilter ? "block" : "none";
  }
  if (Object.keys(state.authHeaders).length === 0) {
    statusEl.innerHTML = `<span>Waiting for authentication...</span>`;
    if (badgeEl)
      badgeEl.style.display = "none";
    return;
  }
  if (badgeEl) {
    badgeEl.textContent = `${visible}/${total}`;
    badgeEl.style.display = "block";
  }
  if (filterState.fetchQueue.length > 0 || filterState.activeFetches > 0) {
    const pct = filterState.queueTotal > 0 ? Math.round(filterState.queueCompleted / filterState.queueTotal * 100) : 0;
    statusEl.innerHTML = `<span>Scanning profiles...</span><span style="color: #ff3333; font-weight: bold;">${pct}% (${filterState.queueCompleted}/${filterState.queueTotal})</span>`;
  } else {
    statusEl.innerHTML = `<span>Filters active</span><span style="color: #ff3333; font-weight: bold;">Matched: ${visible}</span>`;
  }
}
function updatePanelVisibility() {
  const panel = document.getElementById("recon-filter-panel");
  if (!panel)
    return;
  const hasCards = getActiveCards().length > 0;
  panel.style.display = hasCards ? "flex" : "none";
}
function triggerLoadMore() {
  const loadMoreBtn = document.getElementById("recon-filter-load-more");
  if (loadMoreBtn) {
    loadMoreBtn.disabled = true;
    loadMoreBtn.textContent = "Loading...";
  }
  const cards = getActiveCards();
  cards.forEach((card) => {
    card.style.removeProperty("display");
    card.style.removeProperty("flex-grow");
    card.style.removeProperty("flex-shrink");
    card.style.removeProperty("max-width");
  });
  win.scrollTo({ top: document.body.scrollHeight, behavior: "instant" });
  setTimeout(() => {
    triggerFilterChange();
    if (loadMoreBtn) {
      loadMoreBtn.disabled = false;
      loadMoreBtn.textContent = "Load More";
    }
    win.scrollTo({ top: 0, behavior: "smooth" });
  }, 1200);
}
function injectFilterPanel() {
  const myTypeFilter = document.querySelector("t101-profile-my-type-filter");
  if (!myTypeFilter)
    return;
  let panel = document.getElementById("recon-filter-panel");
  if (panel) {
    if (panel.parentNode !== myTypeFilter.parentNode) {
      myTypeFilter.parentNode.insertBefore(panel, myTypeFilter.nextSibling);
    }
    return;
  }
  panel = document.createElement("div");
  panel.id = "recon-filter-panel";
  panel.style.cssText = `
        position: relative;
        width: 100%;
        box-sizing: border-box;
        color: #fff;
        display: none;
        flex-direction: column;
    `;
  const titleDiv = document.createElement("div");
  titleDiv.style.cssText = `
        display: flex;
        justify-content: space-between;
        align-items: center;
        user-select: none;
        margin-bottom: 8px;
    `;
  const titleH3 = document.createElement("h3");
  titleH3.textContent = "Recon+ Filter";
  titleH3.style.cssText = `
        font: 20px/25px Barlow, sans-serif;
        font-weight: 700;
        color: #fff;
        margin: 0;
    `;
  const badgeSpan = document.createElement("span");
  badgeSpan.id = "recon-filter-count";
  badgeSpan.style.cssText = `
        display: none;
        background: #ff3333;
        color: #fff;
        padding: 2px 8px;
        border-radius: 12px;
        font-size: 11px;
        font-weight: 800;
        font-family: 'Barlow', sans-serif;
    `;
  titleDiv.appendChild(titleH3);
  titleDiv.appendChild(badgeSpan);
  panel.appendChild(titleDiv);
  const scrollContainer = document.createElement("div");
  scrollContainer.className = "recon-filter-scroll-container";
  const searchSection = document.createElement("div");
  searchSection.className = "recon-filter-section";
  const searchWrapper = document.createElement("div");
  searchWrapper.style.cssText = `position: relative; display: flex; align-items: center; width: 100%;`;
  const input = document.createElement("input");
  input.type = "text";
  input.placeholder = "Search bio keywords...";
  input.className = "recon-filter-input";
  input.style.width = "100%";
  input.value = advancedFilterState.keyword;
  const clearBtn = document.createElement("span");
  clearBtn.innerHTML = "&#x2715;";
  clearBtn.style.cssText = `
        position: absolute; right: 12px; cursor: pointer; color: rgba(255, 255, 255, 0.4);
        font-size: 14px; display: ${input.value ? "block" : "none"}; user-select: none;
    `;
  clearBtn.onclick = () => {
    input.value = "";
    clearBtn.style.display = "none";
    advancedFilterState.keyword = "";
    triggerFilterChange();
  };
  input.addEventListener("input", () => {
    clearBtn.style.display = input.value ? "block" : "none";
    advancedFilterState.keyword = input.value.trim().toLowerCase();
    triggerFilterChange();
  });
  searchWrapper.appendChild(input);
  searchWrapper.appendChild(clearBtn);
  searchSection.appendChild(searchWrapper);
  const infoText = document.createElement("div");
  infoText.style.cssText = `
        font-size: 11px;
        color: #adadad;
        font-family: 'Raleway', sans-serif;
        margin-top: 6px;
        line-height: 1.3;
    `;
  infoText.textContent = "Search runs client-side (requires profile loading time)";
  searchSection.appendChild(infoText);
  scrollContainer.appendChild(searchSection);
  const onlineSection = document.createElement("div");
  onlineSection.className = "recon-filter-section";
  const onlineTitle = document.createElement("div");
  onlineTitle.className = "recon-filter-section-title";
  onlineTitle.textContent = "Active Status";
  onlineSection.appendChild(onlineTitle);
  const onlineSelect = document.createElement("select");
  onlineSelect.className = "recon-filter-input recon-filter-select";
  onlineSelect.style.width = "100%";
  const activeOpts = [
    { val: "any", label: "Anytime" },
    { val: "now", label: "Online Now (last 10m)" },
    { val: "hour", label: "Past Hour" },
    { val: "day", label: "Past 24 Hours" },
    { val: "week", label: "Past Week" }
  ];
  activeOpts.forEach((opt) => {
    const option = document.createElement("option");
    option.value = opt.val;
    option.textContent = opt.label;
    option.style.backgroundColor = "#0b0b0d";
    if (advancedFilterState.activeWithin === opt.val) {
      option.selected = true;
    }
    onlineSelect.appendChild(option);
  });
  onlineSelect.onchange = () => {
    advancedFilterState.activeWithin = onlineSelect.value;
  };
  onlineSection.appendChild(onlineSelect);
  scrollContainer.appendChild(onlineSection);
  const togglesSection = document.createElement("div");
  togglesSection.className = "recon-filter-section";
  const togglesTitle = document.createElement("div");
  togglesTitle.className = "recon-filter-section-title";
  togglesTitle.textContent = "Only Show";
  togglesSection.appendChild(togglesTitle);
  const toggleWrapper = document.createElement("div");
  toggleWrapper.style.cssText = `display: flex; flex-direction: column; gap: 8px;`;
  const createCheckbox = (labelTxt, key) => {
    const row = document.createElement("label");
    row.style.cssText = `display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 13px; font-family: 'Raleway', sans-serif; color: #adadad;`;
    const chk = document.createElement("input");
    chk.type = "checkbox";
    chk.checked = advancedFilterState[key];
    chk.style.cssText = `accent-color: #ff3333; width: 16px; height: 16px; cursor: pointer;`;
    chk.onchange = () => {
      advancedFilterState[key] = chk.checked;
    };
    const span = document.createElement("span");
    span.textContent = labelTxt;
    row.appendChild(chk);
    row.appendChild(span);
    return row;
  };
  toggleWrapper.appendChild(createCheckbox("Guys with Photos", "onlyPhotos"));
  toggleWrapper.appendChild(createCheckbox("New Guys", "onlyNew"));
  toggleWrapper.appendChild(createCheckbox("Premium Members", "onlyPremium"));
  togglesSection.appendChild(toggleWrapper);
  scrollContainer.appendChild(togglesSection);
  const heightSection = document.createElement("div");
  heightSection.className = "recon-filter-section";
  const heightTitle = document.createElement("div");
  heightTitle.className = "recon-filter-section-title";
  heightTitle.textContent = "Height (cm)";
  heightSection.appendChild(heightTitle);
  const heightRow = document.createElement("div");
  heightRow.className = "recon-filter-row";
  const minHeightInput = document.createElement("input");
  minHeightInput.type = "number";
  minHeightInput.placeholder = "Min height";
  minHeightInput.className = "recon-filter-input";
  minHeightInput.value = advancedFilterState.heightMin ? String(advancedFilterState.heightMin) : "";
  minHeightInput.addEventListener("input", () => {
    advancedFilterState.heightMin = Number(minHeightInput.value) || 0;
  });
  const maxHeightInput = document.createElement("input");
  maxHeightInput.type = "number";
  maxHeightInput.placeholder = "Max height";
  maxHeightInput.className = "recon-filter-input";
  maxHeightInput.value = advancedFilterState.heightMax ? String(advancedFilterState.heightMax) : "";
  maxHeightInput.addEventListener("input", () => {
    advancedFilterState.heightMax = Number(maxHeightInput.value) || 0;
  });
  heightRow.appendChild(minHeightInput);
  heightRow.appendChild(maxHeightInput);
  heightSection.appendChild(heightRow);
  scrollContainer.appendChild(heightSection);
  const createChipSetSection = (titleTxt, items, key) => {
    const section = document.createElement("div");
    section.className = "recon-filter-section";
    const title = document.createElement("div");
    title.className = "recon-filter-section-title";
    title.textContent = titleTxt;
    section.appendChild(title);
    const container = document.createElement("div");
    container.className = "recon-chip-container";
    items.forEach((item) => {
      const chip = document.createElement("span");
      chip.className = "recon-filter-chip";
      chip.textContent = item;
      const isSelected = advancedFilterState[key].includes(item);
      if (isSelected)
        chip.classList.add("selected");
      chip.onclick = () => {
        const arr = advancedFilterState[key];
        const idx = arr.indexOf(item);
        if (idx > -1) {
          arr.splice(idx, 1);
          chip.classList.remove("selected");
        } else {
          arr.push(item);
          chip.classList.add("selected");
        }
      };
      container.appendChild(chip);
    });
    section.appendChild(container);
    return section;
  };
  scrollContainer.appendChild(createChipSetSection("Body Type", ["Athletic", "Average", "Large", "Muscled", "Slim"], "bodyTypes"));
  scrollContainer.appendChild(createChipSetSection("Body Hair", ["Average", "Hairy", "None", "Shaved", "Some"], "bodyHairs"));
  scrollContainer.appendChild(createChipSetSection("Ethnicity", ["Asian", "Black", "Hispanic", "Latino", "Middle Eastern", "Mixed Race", "South Asian", "White", "Other"], "ethnicities"));
  panel.appendChild(scrollContainer);
  const applyBtn = document.createElement("button");
  applyBtn.className = "recon-apply-btn";
  applyBtn.textContent = "Apply Filters";
  applyBtn.onclick = () => {
    const oldSaved = win.localStorage.getItem("recon_advanced_filters");
    let needsReload = false;
    if (oldSaved) {
      try {
        const old = JSON.parse(oldSaved);
        if (old.activeWithin !== advancedFilterState.activeWithin || old.onlyPhotos !== advancedFilterState.onlyPhotos || old.onlyNew !== advancedFilterState.onlyNew || old.onlyPremium !== advancedFilterState.onlyPremium || old.heightMin !== advancedFilterState.heightMin || old.heightMax !== advancedFilterState.heightMax || JSON.stringify(old.bodyTypes) !== JSON.stringify(advancedFilterState.bodyTypes) || JSON.stringify(old.bodyHairs) !== JSON.stringify(advancedFilterState.bodyHairs) || JSON.stringify(old.ethnicities) !== JSON.stringify(advancedFilterState.ethnicities)) {
          needsReload = true;
        }
      } catch (e) {
        needsReload = true;
      }
    } else {
      needsReload = true;
    }
    win.localStorage.setItem("recon_advanced_filters", JSON.stringify(advancedFilterState));
    if (needsReload) {
      win.location.reload();
    } else {
      triggerFilterChange();
    }
  };
  panel.appendChild(applyBtn);
  const statusDiv = document.createElement("div");
  statusDiv.id = "recon-filter-status";
  statusDiv.style.cssText = `
        font-size: 12px;
        color: #adadad;
        font-family: 'Raleway', sans-serif;
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 100%;
        margin-top: 10px;
    `;
  statusDiv.innerHTML = `<span>Status: Ready</span>`;
  const loadMoreBtn = document.createElement("button");
  loadMoreBtn.id = "recon-filter-load-more";
  loadMoreBtn.textContent = "Load More";
  loadMoreBtn.style.cssText = `
        background: rgba(255, 51, 51, 0.15);
        color: #ff3333;
        border: 1px solid rgba(255, 51, 51, 0.3);
        border-radius: 4px;
        padding: 4px 10px;
        font-size: 11px;
        font-weight: 800;
        cursor: pointer;
        text-transform: uppercase;
        font-family: 'Barlow', sans-serif;
        display: none;
        transition: all 0.2s;
    `;
  loadMoreBtn.onmouseover = () => {
    loadMoreBtn.style.background = "#ff3333";
    loadMoreBtn.style.color = "#fff";
  };
  loadMoreBtn.onmouseout = () => {
    loadMoreBtn.style.background = "rgba(255, 51, 51, 0.15)";
    loadMoreBtn.style.color = "#ff3333";
  };
  loadMoreBtn.onclick = triggerLoadMore;
  statusDiv.appendChild(loadMoreBtn);
  panel.appendChild(statusDiv);
  myTypeFilter.parentNode.insertBefore(panel, myTypeFilter.nextSibling);
  triggerFilterChange();
}

// src/index.ts
(function() {
  injectStyles();
  setupXHRInterceptor();
  checkAndPaintProfileFromIndexedDB();
  const observer = new MutationObserver(() => {
    injectViewedButton();
    setupPhotoViewer();
    if (state.lastProfileDetailPageData) {
      paintProfileDetailPage(state.lastProfileDetailPageData);
    }
    const match = win.location.pathname.match(/\/(?:members|profiles)\/([a-f0-9-]{36}|[a-zA-Z0-9_-]+)/i);
    if (match && match[1]) {
      paintPremiumGallery(match[1]);
    }
    profileDetailsCache.forEach((data, profileId) => {
      const card = document.getElementById(profileId);
      if (card && !card.dataset.reconInjected)
        paintProfileData(profileId);
    });
    injectFilterPanel();
    updatePanelVisibility();
    injectDistanceMap();
    const cards = getActiveCards();
    cards.forEach((card) => {
      if (card.dataset.reconLastFilterKeyword !== advancedFilterState.keyword) {
        card.dataset.reconLastFilterKeyword = advancedFilterState.keyword;
        applyFilterToCard(card, advancedFilterState);
      }
    });
  });
  observer.observe(document.documentElement, { childList: true, subtree: true });
  if (state.lastProfileDetailPageData) {
    paintProfileDetailPage(state.lastProfileDetailPageData);
  }
  const initMatch = win.location.pathname.match(/\/(?:members|profiles)\/([a-f0-9-]{36}|[a-zA-Z0-9_-]+)/i);
  if (initMatch && initMatch[1]) {
    paintPremiumGallery(initMatch[1]);
  }
})();

})();