// ==UserScript==
// @name Platesmania Lookup Toolbox
// @version 1.13
// @description Shows lookup buttons on Platesmania upload pages.
// @match https://platesmania.com/*/add*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @connect opendata.rdw.nl
// @connect motonet.fi
// @license MIT
// @namespace https://greasyfork.org/users/976031
// ==/UserScript==
(function () {
'use strict';
// --- Settings & Storage ---
// View size can be Large or Compact (formerly "Small").
// We support a default and per-country overrides, plus per-country hidden sites.
const SIZE_DEFAULT_KEY = 'lookup_button_size_default';
const SIZE_BY_COUNTRY_KEY = 'lookup_button_size_by_country'; // JSON object { code: 'Large'|'Compact' }
const HIDDEN_SITES_BY_COUNTRY_KEY = 'lookup_hidden_sites_by_country'; // JSON object { code: [siteName, ...] }
const DISABLE_GOOGLE_IMAGES_KEY = 'lookup_disable_google_images';
const DISABLE_AUTOGESPOT_KEY = 'lookup_disable_autogespot';
function getCurrentCountryCode() {
try {
const href = window.location.href || '';
const m = href.match(/platesmania\.com\/([a-z]{2,4})\/add/i);
return m ? m[1].toLowerCase() : '';
} catch {
return '';
}
}
function readJsonFromStorage(key, fallback) {
try {
if (typeof GM_getValue === 'function') {
const raw = GM_getValue(key, '');
if (!raw) return fallback;
try { return JSON.parse(raw); } catch {}
} else if (typeof localStorage !== 'undefined') {
const raw = localStorage.getItem(key);
if (!raw) return fallback;
try { return JSON.parse(raw); } catch {}
}
} catch {}
return fallback;
}
function writeJsonToStorage(key, obj) {
const raw = JSON.stringify(obj || {});
try {
if (typeof GM_setValue === 'function') GM_setValue(key, raw);
else if (typeof localStorage !== 'undefined') localStorage.setItem(key, raw);
} catch {}
}
function readStringFromStorage(key, fallback) {
try {
if (typeof GM_getValue === 'function') return GM_getValue(key, fallback);
if (typeof localStorage !== 'undefined') return localStorage.getItem(key) || fallback;
} catch {}
return fallback;
}
function writeStringToStorage(key, val) {
try {
if (typeof GM_setValue === 'function') GM_setValue(key, val);
else if (typeof localStorage !== 'undefined') localStorage.setItem(key, val);
} catch {}
}
function migrateLegacySizeDefault() {
// Migrate from old global key 'lookup_button_size_mode' if present
const LEGACY_KEY = 'lookup_button_size_mode';
const legacy = readStringFromStorage(LEGACY_KEY, '');
if (legacy) {
const normalized = legacy === 'Small' ? 'Compact' : legacy;
writeStringToStorage(SIZE_DEFAULT_KEY, normalized);
try { if (typeof GM_setValue === 'function') GM_setValue(LEGACY_KEY, ''); else if (localStorage) localStorage.removeItem(LEGACY_KEY); } catch {}
}
}
function getDefaultSizeMode() {
migrateLegacySizeDefault();
const val = readStringFromStorage(SIZE_DEFAULT_KEY, 'Large');
return val === 'Small' ? 'Compact' : (val || 'Large');
}
function setDefaultSizeMode(mode) {
const normalized = mode === 'Small' ? 'Compact' : mode;
writeStringToStorage(SIZE_DEFAULT_KEY, normalized);
}
function getSizeModeForCountry(code) {
const byCountry = readJsonFromStorage(SIZE_BY_COUNTRY_KEY, {});
const val = byCountry[code];
if (val) return val === 'Small' ? 'Compact' : val;
return getDefaultSizeMode();
}
function setSizeModeForCountry(code, mode) {
const normalized = mode === 'Small' ? 'Compact' : mode;
const byCountry = readJsonFromStorage(SIZE_BY_COUNTRY_KEY, {});
byCountry[code] = normalized;
writeJsonToStorage(SIZE_BY_COUNTRY_KEY, byCountry);
}
function clearAllCountrySizeOverrides() {
writeJsonToStorage(SIZE_BY_COUNTRY_KEY, {});
}
function isCompactMode() {
const code = getCurrentCountryCode();
const mode = code ? getSizeModeForCountry(code) : getDefaultSizeMode();
return mode === 'Compact';
}
function getHiddenSitesForCountry(code) {
const data = readJsonFromStorage(HIDDEN_SITES_BY_COUNTRY_KEY, {});
const arr = Array.isArray(data[code]) ? data[code] : [];
return new Set(arr);
}
function setHiddenSitesForCountry(code, hiddenArray) {
const data = readJsonFromStorage(HIDDEN_SITES_BY_COUNTRY_KEY, {});
data[code] = Array.from(new Set(hiddenArray || []));
writeJsonToStorage(HIDDEN_SITES_BY_COUNTRY_KEY, data);
}
function isSiteEnabledForCountry(code, siteName) {
const hidden = getHiddenSitesForCountry(code);
return !hidden.has(siteName);
}
function isGoogleImagesGloballyEnabled() {
const v = readStringFromStorage(DISABLE_GOOGLE_IMAGES_KEY, '0');
return String(v) !== '1';
}
function setGoogleImagesGloballyEnabled(enabled) {
writeStringToStorage(DISABLE_GOOGLE_IMAGES_KEY, enabled ? '0' : '1');
}
function isAutogespotGloballyEnabled() {
const v = readStringFromStorage(DISABLE_AUTOGESPOT_KEY, '0');
return String(v) !== '1';
}
function setAutogespotGloballyEnabled(enabled) {
writeStringToStorage(DISABLE_AUTOGESPOT_KEY, enabled ? '0' : '1');
}
// Favicon cache so we don't keep refetching once resolved
const faviconCache = {};
function getFaviconConfigForUrl(urlStr) {
try {
const u = new URL(urlStr);
const key = u.hostname;
const cached = faviconCache[key];
if (cached) {
return { key, candidates: [cached] };
}
const s2 = `https://www.google.com/s2/favicons?sz=64&domain=${u.hostname}`;
const ico = `${u.origin}/favicon.ico`;
return { key, candidates: [ico, s2] };
} catch {
return { key: '', candidates: [] };
}
}
const lookupSites = {
nl: [
{ name: 'Finnik', base: 'https://finnik.nl/kenteken/' },
{ name: 'Autoweek', base: 'https://www.autoweek.nl/kentekencheck/' },
{ name: 'voertuig.net', base: 'https://voertuig.net/kenteken/' },
{ name: 'Centraal Beheer', base: 'https://centraalbeheer.finnik.nl/kenteken/' },
{ name: 'Kentekencheck.info', base: 'https://www.kentekencheck.info/kenteken/' },
{ name: 'Kentekencheck.nu', base: 'https://www.kentekencheck.nu/kenteken/' },
{ name: 'Qenteken', base: 'https://www.qenteken.nl/kentekencheck/' },
{ name: 'RDW (Site)', base: 'https://www.rdwdata.nl/kenteken/' },
],
se: [
{ name: 'car.info', base: 'https://www.car.info/?s=' },
{ name: 'biluppgifter.se', base: 'https://biluppgifter.se/fordon/' },
{ name: 'transportstyrelsen', base: 'https://fordon-fu-regnr.transportstyrelsen.se/?ts-regnr-sok=' },
],
ua: [
{ name: 'carplates.app', base: 'https://ua.carplates.app/en/number/' },
{ name: 'baza-gai.com.ua', base: 'https://baza-gai.com.ua/nomer/' },
{ name: 'auto-inform.com.ua', base: 'https://auto-inform.com.ua/search/' },
],
uk: [
{ name: 'checkcardetails', base: 'https://www.checkcardetails.co.uk/cardetails/' },
{ name: 'totalcarcheck', base: 'https://totalcarcheck.co.uk/FreeCheck?regno=' },
{ name: 'checkhistory', base: 'https://checkhistory.uk/vehicle/' },
{ name: 'carcheck', base: 'https://www.carcheck.co.uk/sendnudes/' },
{ name: 'carhistorycheck', base: 'https://carhistorycheck.co.uk/confirm-vehicle/?vrm=' },
],
dk: [
{ name: 'digitalservicebog.dk', base: 'https://app.digitalservicebog.dk/search?country=dk&Registration=' },
{ name: 'esyn.dk', base: 'https://findsynsrapport.esyn.dk/result?registration=' },
],
no: [{ name: 'vegvesen.no', base: 'https://www.vegvesen.no/en/vehicles/buy-and-sell/vehicle-information/check-vehicle-information/?registreringsnummer=' },
{ name: 'regnr.info', base: 'https://regnr.info/' }
],
fr: [
{ name: 'immatriculation-auto.info', base: 'https://immatriculation-auto.info/vehicle/' },
{ name: 'carter-cash.com', base: 'https://www.carter-cash.com/pieces-auto/?plate=', needsHyphen: true },
],
es: [
{ name: 'carter-cash.es', base: 'https://www.carter-cash.es/piezas-auto/?plate=' },
],
fi: [
{ name: 'Biltema', base: 'https://www.biltema.fi/sv-fi/rekosok-bil/' },
{ name: 'Motonet (in new tab)', base: 'https://www.motonet.fi/api/vehicleInfo/registrationNumber/FI?locale=fi®istrationNumber=' },
],
cz: [{ name: 'uniqa.cz', base: 'https://www.uniqa.cz/online/pojisteni-vozidla/#ecvId=' }],
sk: [{ name: 'overenie.digital', base: 'https://overenie.digital/over/sk/ecv/' },
{ name: 'stkonline', base: 'https://www.stkonline.sk/spz/' }
],
ie: [{ name: 'cartell.ie', base: 'https://www.cartell.ie/ssl/servlet/beginStarLookup?registration=' },
{ name: 'motorcheck.ie', base: 'https://www.motorcheck.ie/free-car-check/?vrm=' }
],
is: [{ name: 'island.is', base: 'https://island.is/uppfletting-i-oekutaekjaskra?vq=' }],
it: [{ name: 'carter-cash.it', base: 'https://www.carter-cash.it/ricambi-auto/?plate=' },]
};
const url = window.location.href;
const isPage = (code) => url.includes(`platesmania.com/${code}/add`);
const supportedCodes = ['nl','ua','no','dk','fr','uk','fi','pl','lt','cz','se','es', 'sk', 'us', 'ie', 'is', 'it'];
// Platesmania US: mapping of <option value> in #drop_1 to 2-letter state codes
const usStateValueToCode = {
'7502': 'AL',
'7501': 'AK',
'7504': 'AZ',
'7503': 'AR',
'7505': 'CA',
'7506': 'CO',
'7507': 'CT',
'7508': 'DE',
'7551': 'DC',
'7509': 'FL',
'7510': 'GA',
'7511': 'HI',
'7513': 'ID',
'7514': 'IL',
'7515': 'IN',
'7512': 'IA',
'7516': 'KS',
'7517': 'KY',
'7518': 'LA',
'7521': 'ME',
'7520': 'MD',
'7519': 'MA',
'7522': 'MI',
'7523': 'MN',
'7525': 'MS',
'7524': 'MO',
'7526': 'MT',
'7529': 'NE',
'7533': 'NV',
'7530': 'NH',
'7531': 'NJ',
'7532': 'NM',
'7534': 'NY',
'7527': 'NC',
'7528': 'ND',
'7535': 'OH',
'7536': 'OK',
'7537': 'OR',
'7538': 'PA',
'7539': 'RI',
'7540': 'SC',
'7541': 'SD',
'7542': 'TN',
'7543': 'TX',
'7544': 'UT',
'7546': 'VT',
'7545': 'VA',
'7547': 'WA',
'7549': 'WV',
'7548': 'WI',
'7550': 'WY',
};
function getUSStateCode() {
const sel = document.getElementById('drop_1');
if (!sel) return '';
const val = sel.value;
return usStateValueToCode[val] || '';
}
// --- Helpers ---
function selectedText(id) {
const el = document.getElementById(id);
if (!el || !el.options || el.selectedIndex < 0) return '';
return el.options[el.selectedIndex].text;
}
function areFieldsFilled() {
if (isPage('nl')) return document.getElementById('nomer').value !== '';
if (isPage('ua')) {
const region = document.getElementById('region1').value;
const digits = document.getElementById('digit1').value;
return region !== '' && digits !== '';
}
if (isPage('no') || isPage('dk') || isPage('se')) return document.getElementById('let').value !== '' && document.getElementById('digit').value !== '';
if (isPage('fr')) {
const b1 = document.getElementById('b1').value;
const d2 = document.getElementById('digit2').value;
const b2 = document.getElementById('b2').value;
return b1 !== '' && d2 !== '' && b2 !== '';
}
if (isPage('es')) {
const ctype = document.getElementById('ctype')?.value;
if (!ctype) return false;
if (ctype === '1') {
return document.getElementById('digit1').value !== '' && document.getElementById('let').value !== '';
}
if (ctype === '2') {
return selectedText('dip') !== '' && selectedText('region') !== '' && document.getElementById('digit1').value !== '';
}
if (ctype === '3') {
return selectedText('region') !== '' && document.getElementById('digit1').value !== '' && document.getElementById('let').value !== '';
}
if (ctype === '4') {
return selectedText('region') !== '' && document.getElementById('digit2').value !== '' && document.getElementById('let').value !== '';
}
if (ctype === '5') {
return selectedText('region') !== '' && document.getElementById('digit1').value !== '' && document.getElementById('let').value !== '';
}
if (ctype === '7') {
return selectedText('region') !== '' && document.getElementById('digit2').value !== '';
}
return false;
}
if (isPage('de') || isPage('ch')) return document.getElementById('digit').value !== '';
if (isPage('us')) {
const state = getUSStateCode();
const plate = document.getElementById('nomer').value;
return !!state && plate !== '';
}
if (isPage('pl') || isPage('uk')) {
const nomerpl = document.getElementById('nomerpl')?.value || '';
const dip = document.getElementById('dip')?.value || '';
return nomerpl !== '' || dip !== '';
}
if (isPage('fi')) return document.getElementById('digit').value !== '';
if (isPage('lt')) return document.getElementById('digit2').value !== '';
if (isPage('cz')) {
const d1 = document.getElementById('digit1').value;
const d2 = document.getElementById('digit2').value;
const d3 = document.getElementById('digit3').value;
const nomer = document.getElementById('nomer').value;
return d1 !== '' || d2 !== '' || d3 !== '' || nomer !== '';
}
if (isPage('sk')) {
const digit = document.getElementById('digit').value;
const nomerpl = document.getElementById('nomerpl').value;
const police = document.getElementById('police').value;
return digit !== '' || nomerpl !== '' || police !== '';
}
if (isPage('ie')) {
const digit2 = document.getElementById('digit2').value;
return digit2;
}
if (isPage('is')) {
const nomer = document.getElementById('nomer').value;
const b1 = document.getElementById('b1').value;
return nomer !== '' || b1 !== '';
}
if (isPage('it')) {
const c = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
const txt = (id) => (document.getElementById(id)?.options?.[document.getElementById(id).selectedIndex]?.text || '');
switch (c) {
case '1': return q('b1') !== '' && q('digit1') !== '' && q('b2') !== '';
case '2': return q('b1') !== '' && q('digit2') !== '';
case '3': return q('b1') !== '' && q('digit2') !== '';
case '4': return q('b1') !== '' && q('digit1') !== '' && q('b2') !== '';
case '5': return txt('region1') !== '' && q('nomerpl1') !== '';
case '6': return txt('region1') !== '' && q('nomerpl1') !== '';
case '7': return txt('region1') !== '' && q('nomerpl1') !== '';
case '8': return false; // disabled
case '9': return txt('dipreg') !== '' && q('digit2') !== '' && q('b2') !== '';
case '10': return q('b1') !== '' && q('digit2') !== '';
default: return false;
}
}
return false;
}
function buildPlateForCurrentPage() {
if (isPage('nl')) return document.getElementById('nomer').value;
if (isPage('ua')) {
const region = document.getElementById('region1').value;
const digits = document.getElementById('digit1').value;
const b1 = document.getElementById('b1').value;
const b2 = document.getElementById('b2').value;
return `${region}${digits}${b1}${b2}`;
}
if (isPage('no') || isPage('dk') || isPage('se')) {
const letField = document.getElementById('let').value;
const digitField = document.getElementById('digit').value;
return `${letField}${digitField}`;
}
if (isPage('fr')) {
const b1 = document.getElementById('b1').value;
const digit2 = document.getElementById('digit2').value;
const b2 = document.getElementById('b2').value;
return `${b1}${digit2}${b2}`;
}
if (isPage('us')) {
const raw = document.getElementById('nomer').value || '';
return raw.replace(/\s+/g, '');
}
if (isPage('es')) {
const ctype = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
if (ctype === '1') {
return q('digit1') + q('let');
}
if (ctype === '2') {
return selectedText('dip') + selectedText('region') + q('digit1');
}
if (ctype === '3') {
return selectedText('region') + q('digit1') + q('let');
}
if (ctype === '4') {
return selectedText('region') + q('digit2') + q('let');
}
if (ctype === '5') {
return selectedText('region') + q('digit1') + q('let');
}
if (ctype === '7') {
return selectedText('region') + q('digit2');
}
return '';
}
if (isPage('fi')) {
const letField = document.getElementById('let1').value;
const digitField = document.getElementById('digit').value;
return `${letField}-${digitField}`;
}
if (isPage('uk')) {
return (document.getElementById('nomerpl')?.value || document.getElementById('nomer')?.value || '');
}
if (isPage('pl')) {
const ctype = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
switch (ctype) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '9':
case '10':
case '11':
return selectedText('region') + q('nomerpl');
case '6':
case '7':
case '8':
return selectedText('region') + q('b1') + q('nomerpl');
case '12':
return selectedText('dip') + selectedText('region') + q('digit');
default:
return '';
}
}
if (isPage('lt')) {
const b1 = document.getElementById('b1');
const b2 = document.getElementById('b2');
const b3 = document.getElementById('b3');
const d1 = document.getElementById('digit1');
const d2 = document.getElementById('digit2');
const d3 = document.getElementById('digit3');
const vanity = document.getElementById('nomer');
const ctype = document.getElementById('ctype').value;
if (ctype === '1') return b1.value + b2.value + b3.value + d2.value;
if (ctype === '2') return d1.value + b1.value + b2.value;
if (ctype === '3') return b1.value + b2.value + d2.value;
if (ctype === '4') return d1.value + b1.value + b2.value + b3.value;
if (['5','6','7','9'].includes(ctype)) return vanity.value;
if (ctype === '8') return d3.value + b1.value + b2.value;
return '';
}
if (isPage('cz')) {
const q = (id) => document.getElementById(id)?.value || '';
const category = q('ctype');
const regionField = document.getElementById('region');
const selectedRegionText = regionField.options[regionField.selectedIndex].text;
const b1 = q('b1'), b2 = q('b2'), b3 = q('b3');
const d1 = q('digit1'), d2 = q('digit2'), d3 = q('digit3');
const nomer = q('nomer'), el = q('el');
switch (category) {
case '1': return `${b1}${selectedRegionText}${b2}${d1}`;
case '2': return `${b1}${selectedRegionText}${d1}`;
case '4': return `${selectedRegionText}${b2}${d1}`;
case '5': return `${selectedRegionText}${b2}${d1}`;
case '6': return `${selectedRegionText}${b2}${d1}`;
case '7': return `${selectedRegionText}${b2}${d2}`;
case '8': return `${selectedRegionText}${b2}${d2}`;
case '9': return `${selectedRegionText}${b2}${d2}`;
case '10': return `${selectedRegionText}${b2}${d2}`;
case '11': return nomer;
case '3': return nomer;
case '12': return `${el}${b3}${d3}`;
case '13': return `${d1}${d3}`;
default: return '';
}
}
if (isPage('sk')) {
const ctype = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
switch (ctype) {
case '1':
case '2':
case '12':
return selectedText('region') + q('digit') + q('let2');
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
return selectedText('region') + q('let1') + q('digit');
case '9':
return q('let2') + q('nomerpl');
case '10':
return q('let1') + q('police');
case '11':
return q('digit') + q('police');
case '13':
return selectedText('dip') + q('police');
default:
return '';
}
}
if (isPage('ie')) {
const ctype = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
switch (ctype) {
case '1':
return q('digit1') + selectedText('region') + q('digit2');
case '2':
case '3':
return q('let') + q('digit2')
default:
return '';
}
}
if (isPage('is')) {
const ctype = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
switch (ctype) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '9':
return q('nomer').replace(/\s+/g, '');
case '8':
return selectedText('b1') + selectedText('b2') + selectedText('b3') + selectedText('b4') + selectedText('b5') + selectedText('b6');
case '10':
return selectedText('region') + q('nomer').replace(/\s+/g, '');
default:
return '';
}
}
if (isPage('it')) {
const c = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
const txt = (id) => {
const el = document.getElementById(id);
if (!el || el.selectedIndex < 0 || !el.options) return '';
return el.options[el.selectedIndex].text || '';
};
const joinClean = (...parts) => parts.join('').replace(/[\s-]+/g, '');
switch (c) {
case '1': return joinClean(q('b1'), q('digit1'), q('b2'));
case '2': return joinClean(q('b1'), q('digit2'));
case '3': return joinClean(q('b1'), q('digit2'));
case '4': return joinClean(q('b1'), q('digit1'), q('b2'));
case '5': return joinClean(txt('region1'), q('nomerpl1'));
case '6': return joinClean(txt('region1'), q('nomerpl1'));
case '7': return joinClean(txt('region1'), q('nomerpl1'));
case '8': return ''; // disabled
case '9': {
const dip = txt('dipreg').replace(/\s*\(.*?\)\s*/g, '');
return joinClean(dip, q('digit2'), q('b2'));
}
case '10': return joinClean(q('b1'), q('digit2'));
default: return '';
}
}
return '';
}
// For Google Images button we historically insert spaces between chunks on some countries.
function buildPlateForSearchDisplay() {
if (isPage('nl')) return document.getElementById('nomer').value;
if (isPage('ua')) {
const region = document.getElementById('region1').value;
const digits = document.getElementById('digit1').value;
const b1 = document.getElementById('b1').value;
const b2 = document.getElementById('b2').value;
return `${region} ${digits} ${b1}${b2}`;
}
if (isPage('no') || isPage('dk') || isPage('se')) {
return document.getElementById('let').value + ' ' + document.getElementById('digit').value;
}
if (isPage('fr')) {
const b1 = document.getElementById('b1').value;
const d2 = document.getElementById('digit2').value;
const b2 = document.getElementById('b2').value;
return `${b1} ${d2} ${b2}`;
}
if (isPage('us')) {
return (document.getElementById('nomer').value || '').replace(/\s+/g, '');
}
if (isPage('es')) {
return buildPlateForCurrentPage();
}
if (isPage('de')) {
const regionFieldBase = document.getElementById('region');
const regionField = regionFieldBase.options[regionFieldBase.selectedIndex].text;
const letField = document.getElementById('b1').value;
const letField2 = document.getElementById('b2').value;
const digitField = document.getElementById('digit').value;
return `${regionField} ${letField} ${digitField}${letField2}`;
}
if (isPage('ch')) {
const regionField = document.getElementById('region').value;
const digitField = document.getElementById('digit').value;
return `${regionField} ${digitField}`;
}
if (isPage('fi')) {
return document.getElementById('let1').value + '-' + document.getElementById('digit').value;
}
if (isPage('pl')) {
const regionField = document.getElementById('region');
const selectedRegionText = regionField.options[regionField.selectedIndex].text;
const digitField = document.getElementById('nomerpl').value;
return selectedRegionText + ' ' + digitField;
}
if (isPage('uk')) {
return (document.getElementById('nomerpl')?.value || document.getElementById('nomer')?.value || '');
}
if (isPage('lt') || isPage('it') || isPage('cz') || isPage('sk')) {
return buildPlateForCurrentPage();
}
if (isPage('ie')) {
const ctype = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
switch (ctype) {
case '1':
return q('digit1') + '-' + selectedText('region') + '-' + q('digit2');
case '2':
case '3':
return q('let') + q('digit2')
default:
return '';
}
}
if (isPage('is')) {
const ctype = document.getElementById('ctype')?.value;
const q = (id) => document.getElementById(id)?.value || '';
switch (ctype) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '9':
return q('nomer');
case '8':
return selectedText('b1') + selectedText('b2') + selectedText('b3') + selectedText('b4') + selectedText('b5') + selectedText('b6');
case '10':
return selectedText('region') + ' ' + q('nomer').replace(/\s+/g, '');
default:
return '';
}
}
return '';
}
function frWithHyphens(s) {
return s.replace(/^([A-Z]{2})(\d{3})([A-Z]{2})$/i, '$1-$2-$3');
}
function onlyYearFromDate(s) {
if (!s) return '';
const m = String(s).match(/(\d{4})/);
return m ? m[1] : '';
}
function showFIWindow(info) {
const existing = document.getElementById('fiFloatWin');
if (existing) existing.remove();
const wrap = document.createElement('div');
wrap.id = 'fiFloatWin';
wrap.style.position = 'fixed';
wrap.style.top = '80px';
wrap.style.right = '40px';
wrap.style.zIndex = '99999';
wrap.style.background = '#fff';
wrap.style.border = '1px solid #ccc';
wrap.style.borderRadius = '6px';
wrap.style.boxShadow = '0 6px 18px rgba(0,0,0,0.18)';
wrap.style.minWidth = '260px';
wrap.style.maxWidth = '420px';
wrap.style.fontFamily = 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif';
const header = document.createElement('div');
header.style.padding = '10px 12px';
header.style.borderBottom = '1px solid #eee';
header.style.display = 'flex';
header.style.alignItems = 'center';
header.style.justifyContent = 'space-between';
header.style.background = '#f8f9fa';
const makeModel = `${info.manufacturerName || ''} ${info.model || ''}`.trim().replace(/\s+/g, ' ');
const makeModelSpanHTML =
`<span style="border-bottom: 1px dotted;" onclick="$('#markamodtype').val('${escAttr(makeModel)}').autocomplete('search', '${escAttr(makeModel)}'); return false;">${escHtml(makeModel)}</span>`;
const y = onlyYearFromDate(info.registrationDate);
const title = document.createElement('div');
title.innerHTML = `${makeModelSpanHTML}${y ? ` (${escHtml(y)})` : ''}`;
title.style.fontWeight = '600';
const close = document.createElement('div');
close.textContent = '×';
close.title = 'Close';
close.style.cursor = 'pointer';
close.style.fontSize = '18px';
close.style.lineHeight = '18px';
close.style.marginLeft = '10px';
close.onclick = () => wrap.remove();
header.appendChild(title);
header.appendChild(close);
const body = document.createElement('div');
body.style.padding = '10px 12px';
body.style.fontSize = '14px';
const rows = [
['Manufacturer', info.manufacturerName],
['Model', info.model],
['Type', info.type],
['VIN', info.VIN],
['Registration date', info.registrationDate],
['Fuel', info.fuel],
['Power', (info.powerKw || info.powerHp) ? `${info.powerKw ?? ''}${info.powerKw ? ' kW' : ''}${(info.powerKw && info.powerHp) ? ' / ' : ''}${info.powerHp ?? ''}${info.powerHp ? ' hp' : ''}` : ''],
].filter(([, v]) => v);
for (const [label, value] of rows) {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.margin = '6px 0';
const text = document.createElement('div');
text.textContent = `${label}: ${value}`;
text.style.flex = '1 1 auto';
const img = document.createElement('img');
img.src = 'https://i.imgur.com/RjmoRpu.png';
img.alt = 'Copy';
img.title = 'Copy';
img.style.height = '1em';
img.style.cursor = 'pointer';
img.style.marginLeft = '8px';
img.onclick = () => copyToClipboard(String(value));
row.appendChild(text);
row.appendChild(img);
body.appendChild(row);
}
wrap.appendChild(header);
wrap.appendChild(body);
document.body.appendChild(wrap);
makeDraggable(wrap, header);
}
async function fetchMotonetData(fiPlateRaw) {
const plate = String(fiPlateRaw || '').trim().toUpperCase();
if (!plate) throw new Error('Empty plate');
const url = `https://www.motonet.fi/api/vehicleInfo/registrationNumber/FI?locale=fi®istrationNumber=${encodeURIComponent(plate)}`;
const data = await httpGet(url).catch(() => ({}));
return data && typeof data === 'object' ? data : {};
}
// --- Utils for RDW API + Floating Window ---
function httpGet(url) {
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest === 'function') {
GM_xmlhttpRequest({
method: 'GET',
url,
headers: { 'Accept': 'application/json' },
onload: (res) => {
try { resolve(JSON.parse(res.responseText)); }
catch (e) { reject(e); }
},
onerror: (e) => reject(e),
});
} else {
fetch(url).then(r => r.json()).then(resolve).catch(reject);
}
});
}
function onlyYear(yyyymmdd) {
if (!yyyymmdd || typeof yyyymmdd !== 'string') return 'unknown';
if (/^\d{8}$/.test(yyyymmdd)) return yyyymmdd.slice(0,4);
return 'unknown';
}
function copyToClipboard(text) {
const doFallback = () => {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); } catch {}
document.body.removeChild(ta);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(doFallback);
} else {
doFallback();
}
}
function escHtml(s) {
return String(s).replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
}
function escAttr(s) {
return String(s).replace(/['"]/g, (c) => ({ "'": "\\'", '"': '\\"' }[c]));
}
function makeDraggable(win, handle) {
let ox=0, oy=0, dragging=false;
handle.style.cursor = 'move';
handle.addEventListener('mousedown', (e) => {
dragging = true;
ox = e.clientX - win.offsetLeft;
oy = e.clientY - win.offsetTop;
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
e.preventDefault();
});
function move(e) {
if (!dragging) return;
win.style.left = (e.clientX - ox) + 'px';
win.style.top = (e.clientY - oy) + 'px';
}
function up() {
dragging = false;
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
}
}
function showRDWWindow(info) {
const existing = document.getElementById('rdwFloatWin');
if (existing) existing.remove();
const wrap = document.createElement('div');
wrap.id = 'rdwFloatWin';
wrap.style.position = 'fixed';
wrap.style.top = '80px';
wrap.style.right = '40px';
wrap.style.zIndex = '99999';
wrap.style.background = '#fff';
wrap.style.border = '1px solid #ccc';
wrap.style.borderRadius = '6px';
wrap.style.boxShadow = '0 6px 18px rgba(0,0,0,0.18)';
wrap.style.minWidth = '260px';
wrap.style.maxWidth = '380px';
wrap.style.fontFamily = 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif';
const header = document.createElement('div');
header.style.padding = '10px 12px';
header.style.borderBottom = '1px solid #eee';
header.style.display = 'flex';
header.style.alignItems = 'center';
header.style.justifyContent = 'space-between';
header.style.background = '#f8f9fa';
const makeModel = `${info.make} ${info.model}`.trim().replace(/\s+/g,' ');
const makeModelSpanHTML =
`<span style="border-bottom: 1px dotted;" onclick="$('#markamodtype').val('${escAttr(makeModel)}').autocomplete('search', '${escAttr(makeModel)}'); return false;">${escHtml(makeModel)}</span>`;
const y = info.yFirst && info.yFirst !== 'unknown' ? ` (${escHtml(info.yFirst)})` : '';
const title = document.createElement('div');
title.innerHTML = `${makeModelSpanHTML}${y}`;
title.style.fontWeight = '600';
const close = document.createElement('div');
close.textContent = '×';
close.title = 'Close';
close.style.cursor = 'pointer';
close.style.fontSize = '18px';
close.style.lineHeight = '18px';
close.style.marginLeft = '10px';
close.onclick = () => wrap.remove();
header.appendChild(title);
header.appendChild(close);
const body = document.createElement('div');
body.style.padding = '10px 12px';
body.style.fontSize = '14px';
const rows = [
['Make', info.make],
['Model', info.model],
['Variant', info.variant],
['First registration', info.yFirst],
['First registration in NL', info.yNL],
['Fuel', info.fuel],
['Doors', info.doors],
['Color', info.color],
];
for (const [label, value] of rows) {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.margin = '6px 0';
const text = document.createElement('div');
text.textContent = `${label}: ${value}`;
text.style.flex = '1 1 auto';
const img = document.createElement('img');
img.src = 'https://i.imgur.com/RjmoRpu.png';
img.alt = 'Copy';
img.title = 'Copy';
img.style.height = '1em';
img.style.cursor = 'pointer';
img.style.marginLeft = '8px';
img.onclick = () => copyToClipboard(String(value));
row.appendChild(text);
row.appendChild(img);
body.appendChild(row);
}
wrap.appendChild(header);
wrap.appendChild(body);
document.body.appendChild(wrap);
makeDraggable(wrap, header);
}
async function fetchRDWData(kentekenRaw) {
const plate = String(kentekenRaw || '').trim().replace(/[\s-]+/g, '').toUpperCase();
if (!plate) throw new Error('Empty plate');
const baseUrl = 'https://opendata.rdw.nl/resource/m9d7-ebf2.json';
const fuelUrl = 'https://opendata.rdw.nl/resource/8ys7-d773.json';
const qBase = `${baseUrl}?kenteken=${encodeURIComponent(plate)}`;
const qFuel = `${fuelUrl}?kenteken=${encodeURIComponent(plate)}`;
const [base, fuel] = await Promise.all([
httpGet(qBase).catch(() => []),
httpGet(qFuel).catch(() => []),
]);
const primary = Array.isArray(base) && base.length ? base[0] : {};
const fuelTypes = (Array.isArray(fuel) ? fuel : [])
.map(x => x.brandstof_omschrijving || x.brandstof_omschrijving_ || x.brandstof || '')
.filter(Boolean);
const info = {
make: primary.merk || 'unknown',
model: primary.handelsbenaming || 'unknown',
variant: primary.variant || 'unknown',
yFirst: onlyYear(primary.datum_eerste_toelating),
yNL: onlyYear(primary.datum_eerste_tenaamstelling_in_nederland),
fuel: fuelTypes.length ? fuelTypes.join('/') : 'unknown',
doors: primary.aantal_deuren || 'unknown',
color: primary.eerste_kleur || 'unknown',
};
return info;
}
// --- UI Button Factory (supports Large/Small modes) ---
function makeBtn(label, disabled, onclick, iconUrl) {
const btn = document.createElement('button');
btn.disabled = !!disabled;
const compact = isCompactMode();
if (compact) {
btn.title = label;
btn.style.marginBottom = '0';
btn.style.width = '35px';
btn.style.height = '35px';
btn.style.backgroundColor = disabled ? '#95a5a6' : '#3498db';
btn.style.color = '#ffffff';
btn.style.border = 'none';
btn.style.cursor = disabled ? 'default' : 'pointer';
btn.style.borderRadius = '50%';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.padding = '0';
btn.style.gap = '0';
btn.style.position = 'relative';
if (iconUrl) {
const img = document.createElement('img');
const cfg = (iconUrl && iconUrl.candidates) ? iconUrl : { key: '', candidates: (Array.isArray(iconUrl) ? iconUrl.slice() : [iconUrl]) };
const candidates = cfg.candidates.slice();
const tryNext = () => {
if (!candidates.length) { img.remove(); return; }
img.src = candidates.shift();
};
img.alt = '';
img.style.width = '29px';
img.style.height = '29px';
img.style.display = 'block';
img.style.objectFit = 'contain';
img.style.transition = 'opacity 120ms ease-in-out';
img.onerror = tryNext;
img.onload = () => {
if (cfg.key) {
faviconCache[cfg.key] = img.src;
}
};
btn.appendChild(img);
tryNext();
// Add a hidden label that shows on hover
const labelSpan = document.createElement('span');
labelSpan.innerText = label;
labelSpan.style.position = 'absolute';
labelSpan.style.top = '4px';
labelSpan.style.bottom = '4px';
labelSpan.style.left = '4px';
labelSpan.style.right = '4px';
labelSpan.style.display = 'flex';
labelSpan.style.alignItems = 'center';
labelSpan.style.justifyContent = 'center';
labelSpan.style.textAlign = 'center';
labelSpan.style.fontSize = '12px';
labelSpan.style.lineHeight = '1.05';
labelSpan.style.whiteSpace = 'normal';
labelSpan.style.overflow = 'hidden';
labelSpan.style.wordBreak = 'break-word';
labelSpan.style.overflowWrap = 'anywhere';
labelSpan.style.opacity = '0';
labelSpan.style.pointerEvents = 'none';
labelSpan.style.transition = 'opacity 120ms ease-in-out';
btn.appendChild(labelSpan);
// Hover behavior: expand, hide icon, show text
btn.addEventListener('mouseenter', () => {
img.style.opacity = '0';
labelSpan.style.opacity = '1';
// Auto-fit text size to keep it inside 35x35 (with 4px insets)
let size = 12; // start larger for readability
const minSize = 7;
labelSpan.style.fontSize = size + 'px';
// Iterate down until it fits or we hit min
// Guard against excessive loops
for (let i = 0; i < 20; i++) {
const fits = labelSpan.scrollWidth <= labelSpan.clientWidth && labelSpan.scrollHeight <= labelSpan.clientHeight;
if (fits || size <= minSize) break;
size -= 0.5;
labelSpan.style.fontSize = size + 'px';
}
});
btn.addEventListener('mouseleave', () => {
img.style.opacity = '1';
labelSpan.style.opacity = '0';
});
}
} else {
btn.style.marginBottom = '0';
btn.style.width = '100%';
btn.style.backgroundColor = disabled ? '#95a5a6' : '#3498db';
btn.style.color = '#ffffff';
btn.style.border = 'none';
btn.style.cursor = disabled ? 'default' : 'pointer';
btn.style.height = '23px';
btn.style.borderRadius = '4px';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.gap = '6px';
btn.style.padding = '0 8px';
if (iconUrl) {
const img = document.createElement('img');
const cfg = (iconUrl && iconUrl.candidates) ? iconUrl : { key: '', candidates: (Array.isArray(iconUrl) ? iconUrl.slice() : [iconUrl]) };
const candidates = cfg.candidates.slice();
const tryNext = () => {
if (!candidates.length) { img.remove(); return; }
img.src = candidates.shift();
};
img.onerror = tryNext;
img.onload = () => {
if (cfg.key) {
faviconCache[cfg.key] = img.src;
}
};
img.alt = '';
img.style.maxHeight = (23 - 2) + 'px';
img.style.display = 'inline-block';
img.style.verticalAlign = 'middle';
btn.appendChild(img);
tryNext();
}
const span = document.createElement('span');
span.innerText = label;
btn.appendChild(span);
}
if (onclick && !disabled) btn.onclick = onclick;
return btn;
}
// --- Buttons (Lookup / Google Images / Autogespot) ---
function createOrUpdateLookupButtons() {
const host = document.getElementById('zoomimgid');
if (!host) return;
// Always ensure a grid container exists (even if there are no site lookups)
let container = document.getElementById('lookupButtonsContainer');
if (!container) {
container = document.createElement('div');
container.id = 'lookupButtonsContainer';
container.style.display = 'grid';
container.style.gap = '6px';
host.parentNode.insertBefore(container, host);
} else {
container.innerHTML = '';
}
// Apply grid layout based on size mode
if (isCompactMode()) {
container.style.gridTemplateColumns = 'repeat(auto-fill, 35px)';
container.style.justifyContent = 'center';
container.style.justifyItems = 'center';
} else {
container.style.gridTemplateColumns = '1fr';
container.style.justifyItems = '';
}
const fieldsOk = areFieldsFilled();
const code = getCurrentCountryCode();
let sites = lookupSites[code] || [];
// Filter hidden sites for current country
if (code) {
const enabledSites = [];
for (const s of sites) {
if (isSiteEnabledForCountry(code, s.name)) enabledSites.push(s);
}
sites = enabledSites;
}
// Settings button (replaces inline radios)
let existingControls = document.getElementById('lookupSettingsControls');
if (existingControls) existingControls.remove();
const controls = document.createElement('div');
controls.id = 'lookupSettingsControls';
controls.style.display = 'flex';
controls.style.alignItems = 'center';
controls.style.justifyContent = 'center';
controls.style.margin = '4px 0 6px 0';
const settingsBtn = document.createElement('button');
settingsBtn.textContent = 'Settings';
settingsBtn.style.width = "100%";
settingsBtn.style.height = "100%";
settingsBtn.style.flex = "1";
settingsBtn.style.border = 'none';
settingsBtn.style.borderRadius = '4px';
settingsBtn.style.background = '#6c757d';
settingsBtn.style.color = '#fff';
settingsBtn.style.cursor = 'pointer';
settingsBtn.onclick = openSettingsPanel;
controls.appendChild(settingsBtn);
container.parentNode.insertBefore(controls, container);
// Countries with special flows
const specialPL = isPage('pl');
const specialLT = isPage('lt');
const specialUS = isPage('us');
if (specialPL) {
const favicon = isCompactMode() ? getFaviconConfigForUrl('https://moj.gov.pl/') : null;
const btn = makeBtn('Lookup', !fieldsOk, () => {
const plateNumber = buildPlateForCurrentPage();
const targetUrl =
`https://moj.gov.pl/nforms/engine/ng/index?nfWidReset=true&xFormsAppName=NormaEuro&xFormsOrigin=EXTERNAL&plateNumber=${encodeURIComponent(plateNumber)}#/search`;
window.open(targetUrl, '_blank');
}, isCompactMode() ? favicon : null);
container.appendChild(btn);
// keep going; Google/Autogespot will also append into this same container
} else if (specialLT) {
const favicon = isCompactMode() ? getFaviconConfigForUrl('https://www.cab.lt/') : null;
const btn = makeBtn('Lookup', !fieldsOk, () => {
const plateNumber = buildPlateForCurrentPage();
let form = document.createElement('form');
form.action = 'https://www.cab.lt/draustumo-patikra/';
form.method = 'POST';
form.target = '_blank';
let inputCountry = document.createElement('input');
inputCountry.type = 'hidden'; inputCountry.name = 'country'; inputCountry.value = 'LT';
let inputPlate = document.createElement('input');
inputPlate.type = 'hidden'; inputPlate.name = 'plate'; inputPlate.value = plateNumber;
form.appendChild(inputCountry); form.appendChild(inputPlate);
document.body.appendChild(form); form.submit(); document.body.removeChild(form);
}, isCompactMode() ? favicon : null);
container.appendChild(btn);
} else if (specialUS) {
const favClearVin = isCompactMode() ? getFaviconConfigForUrl('https://www.clearvin.com/') : null;
const btn = makeBtn('ClearVin', !fieldsOk, () => {
const plateRaw = document.getElementById('nomer').value || '';
const plate = plateRaw.replace(/\s+/g, '');
const state = getUSStateCode();
if (!plate || !state) return;
const targetUrl = `https://www.clearvin.com/en/payment/prepare/${encodeURIComponent(plate)}:${state}/`;
window.open(targetUrl, '_blank');
}, isCompactMode() ? favClearVin : null);
container.appendChild(btn);
const favFaxVin = isCompactMode() ? getFaviconConfigForUrl('https://www.faxvin.com/') : null;
const btn2 = makeBtn('FaxVin', !fieldsOk, () => {
const plateRaw = document.getElementById('nomer').value || '';
const plate = plateRaw.replace(/\s+/g, '');
const state = getUSStateCode();
if (!plate || !state) return;
const targetUrl = `https://www.faxvin.com/license-plate-lookup/result?plate=${encodeURIComponent(plate)}&state=${state}/`;
window.open(targetUrl, '_blank');
}, isCompactMode() ? favFaxVin : null);
container.appendChild(btn2);
const favTagNap = isCompactMode() ? getFaviconConfigForUrl('https://tagnap.com/') : null;
const btn3 = makeBtn('TagNap', !fieldsOk, () => {
const plateRaw = document.getElementById('nomer').value || '';
const plate = plateRaw.replace(/\s+/g, '');
const state = getUSStateCode();
if (!plate || !state) return;
const targetUrl = `https://tagnap.com/plates/${encodeURIComponent(plate)}-${state}/`;
window.open(targetUrl, '_blank');
}, isCompactMode() ? favTagNap : null);
container.appendChild(btn3);
const favInfoTracer = isCompactMode() ? getFaviconConfigForUrl('https://infotracer.com/') : null;
const btn4 = makeBtn('InfoTracer', !fieldsOk, () => {
const plateRaw = document.getElementById('nomer').value || '';
const plate = plateRaw.replace(/\s+/g, '');
const state = getUSStateCode();
if (!plate || !state) return;
const targetUrl = `https://infotracer.com/loading/?type=plate-lookup&s=rw&page=results&mercSubId=plate&state=${state}&tid=tagnap&plate=${encodeURIComponent(plate)}`;
window.open(targetUrl, '_blank');
}, isCompactMode() ? favInfoTracer : null);
container.appendChild(btn4);
}
// Standard flow: add site buttons if present
if (sites.length === 1) {
const only = sites[0];
const favicon = isCompactMode() ? getFaviconConfigForUrl(only.base) : null;
const btn = makeBtn('Lookup', !fieldsOk, () => {
const raw = buildPlateForCurrentPage();
if (!raw) return;
const finalPlate = (isPage('fr') && only.needsHyphen) ? frWithHyphens(raw) : raw;
window.open(only.base + finalPlate, '_blank');
}, favicon);
container.appendChild(btn);
} else if (sites.length > 1) {
for (const site of sites) {
const favicon = isCompactMode() ? getFaviconConfigForUrl(site.base) : null;
const btn = makeBtn(site.name, !fieldsOk, () => {
const raw = buildPlateForCurrentPage();
if (!raw) return;
const finalPlate = (isPage('fr') && site.needsHyphen) ? frWithHyphens(raw) : raw;
window.open(site.base + finalPlate, '_blank');
}, favicon);
container.appendChild(btn);
}
}
// --- NL: RDW API lookup button that opens floating window ---
if (isPage('nl')) {
const rdwIcon = isCompactMode() ? getFaviconConfigForUrl('https://www.rdw.nl/') : null;
const rdwBtn = makeBtn('RDW (API)', !fieldsOk, async () => {
const plate = buildPlateForCurrentPage();
if (!plate) return;
rdwBtn.disabled = true;
rdwBtn.style.opacity = '0.7';
try {
const info = await fetchRDWData(plate);
showRDWWindow(info);
} catch (e) {
showRDWWindow({
make: 'unknown',
model: 'unknown',
variant: 'unknown',
yFirst: 'unknown',
yNL: 'unknown',
fuel: 'unknown',
doors: 'unknown',
color: 'unknown',
});
} finally {
rdwBtn.disabled = false;
rdwBtn.style.opacity = '1';
}
}, rdwIcon);
container.appendChild(rdwBtn);
}
// --- FI: Motonet API lookup button that opens floating window ---
if (isPage('fi')) {
const fiIcon = isCompactMode() ? getFaviconConfigForUrl('https://www.motonet.fi/') : null;
const fiBtn = makeBtn('Motonet (Direct API)', !fieldsOk, async () => {
const plate = buildPlateForCurrentPage(); // returns "AAA-123"
if (!plate) return;
fiBtn.disabled = true;
fiBtn.style.opacity = '0.7';
try {
const info = await fetchMotonetData(plate);
showFIWindow(info && Object.keys(info).length ? info : {});
} catch (e) {
showFIWindow({});
} finally {
fiBtn.disabled = false;
fiBtn.style.opacity = '1';
}
}, fiIcon);
container.appendChild(fiBtn);
}
}
function createOrUpdateGoogleImagesButton() {
const host = document.getElementById('zoomimgid');
if (!host) return;
// Respect global disable
if (!isGoogleImagesGloballyEnabled()) {
const existing = document.getElementById('googleImagesButton');
if (existing) existing.remove();
return;
}
let googleBtn = document.getElementById('googleImagesButton');
if (!googleBtn) {
googleBtn = makeBtn('Google Images', true, null, 'https://i.imgur.com/5x00UaD.png');
googleBtn.id = 'googleImagesButton';
const container = document.getElementById('lookupButtonsContainer');
if (container) {
container.appendChild(googleBtn);
} else {
host.parentNode.insertBefore(googleBtn, host); // fallback (shouldn’t happen now)
}
}
const fieldsOk = areFieldsFilled();
googleBtn.disabled = !fieldsOk;
googleBtn.onclick = !fieldsOk ? null : function () {
const plateNumber = buildPlateForSearchDisplay();
if (!plateNumber) return;
window.open('https://www.google.com/search?tbm=isch&q="' + plateNumber + '"', '_blank');
};
}
function createOrUpdateAutogespotButton() {
const host = document.getElementById('zoomimgid');
if (!host) return;
// Respect global disable
if (!isAutogespotGloballyEnabled()) {
const existing = document.getElementById('autogespotButton');
if (existing) existing.remove();
return;
}
let agBtn = document.getElementById('autogespotButton');
if (!agBtn) {
agBtn = makeBtn('Autogespot', true, null, 'https://i.imgur.com/X8HxriW.png');
agBtn.id = 'autogespotButton';
const container = document.getElementById('lookupButtonsContainer');
if (container) {
container.appendChild(agBtn);
} else {
host.parentNode.insertBefore(agBtn, host); // fallback (shouldn’t happen now)
}
}
const fieldsOk = areFieldsFilled();
agBtn.disabled = !fieldsOk;
agBtn.style.marginBottom = '6px';
agBtn.onclick = !fieldsOk ? null : function () {
let plateCompact = buildPlateForCurrentPage();
if (!plateCompact) {
const display = buildPlateForSearchDisplay();
plateCompact = (display || '').replace(/\s+/g, '');
}
if (!plateCompact) return;
const target = `https://www.autogespot.com/spots?licenseplate=${encodeURIComponent(plateCompact)}`;
window.open(target, '_blank');
};
}
// --- Settings Panel UI ---
function openSettingsPanel() {
const existing = document.getElementById('lookupSettingsPanel');
if (existing) existing.remove();
const code = getCurrentCountryCode();
const wrapper = document.createElement('div');
wrapper.id = 'lookupSettingsPanel';
wrapper.style.position = 'fixed';
wrapper.style.top = '80px';
wrapper.style.right = '40px';
wrapper.style.zIndex = '99999';
wrapper.style.background = '#fff';
wrapper.style.border = '1px solid #ccc';
wrapper.style.borderRadius = '6px';
wrapper.style.boxShadow = '0 6px 18px rgba(0,0,0,0.18)';
wrapper.style.minWidth = '280px';
wrapper.style.maxWidth = '380px';
wrapper.style.fontFamily = 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif';
const header = document.createElement('div');
header.style.padding = '10px 12px';
header.style.borderBottom = '1px solid #eee';
header.style.display = 'flex';
header.style.alignItems = 'center';
header.style.justifyContent = 'space-between';
header.style.background = '#f8f9fa';
const title = document.createElement('div');
title.textContent = 'Lookup Toolbox Settings';
title.style.fontWeight = '600';
const close = document.createElement('div');
close.textContent = '×';
close.title = 'Close';
close.style.cursor = 'pointer';
close.style.fontSize = '18px';
close.style.lineHeight = '18px';
close.style.marginLeft = '10px';
close.onclick = () => wrapper.remove();
header.appendChild(title);
header.appendChild(close);
const body = document.createElement('div');
body.style.padding = '10px 12px';
body.style.fontSize = '14px';
body.style.maxHeight = '60vh';
body.style.overflow = 'auto';
// View size section
const viewSection = document.createElement('div');
const currentMode = code ? getSizeModeForCountry(code) : getDefaultSizeMode();
const viewTitle = document.createElement('div');
viewTitle.textContent = 'View size';
viewTitle.style.fontWeight = '600';
viewTitle.style.marginBottom = '6px';
const radiosWrap = document.createElement('div');
radiosWrap.style.display = 'flex';
radiosWrap.style.gap = '12px';
const makeRadio = (label, value) => {
const lab = document.createElement('label');
lab.style.display = 'inline-flex'; lab.style.alignItems = 'center'; lab.style.gap = '6px';
const input = document.createElement('input');
input.type = 'radio'; input.name = 'ltb-view-size'; input.value = value;
input.checked = currentMode === value;
const span = document.createElement('span'); span.textContent = label;
lab.appendChild(input); lab.appendChild(span);
return lab;
};
radiosWrap.appendChild(makeRadio('Large', 'Large'));
radiosWrap.appendChild(makeRadio('Compact', 'Compact'));
const applyWrap = document.createElement('div');
applyWrap.style.display = 'flex';
applyWrap.style.gap = '10px';
applyWrap.style.marginTop = '8px';
const btnApplyCountry = document.createElement('button');
btnApplyCountry.textContent = `Apply to ${code || 'XX'}`;
btnApplyCountry.style.padding = '4px 8px';
btnApplyCountry.style.border = 'none';
btnApplyCountry.style.borderRadius = '4px';
btnApplyCountry.style.background = '#3498db';
btnApplyCountry.style.color = '#fff';
btnApplyCountry.style.cursor = 'pointer';
btnApplyCountry.onclick = () => {
const val = (body.querySelector('input[name="ltb-view-size"]:checked') || {}).value || 'Large';
if (code) setSizeModeForCountry(code, val);
render();
};
const btnApplyAll = document.createElement('button');
btnApplyAll.style.padding = '4px 8px';
btnApplyAll.style.border = 'none';
btnApplyAll.style.borderRadius = '4px';
btnApplyAll.style.background = '#dc3545';
btnApplyAll.style.color = '#fff';
btnApplyAll.style.cursor = 'pointer';
btnApplyAll.style.display = 'flex';
btnApplyAll.style.flexDirection = 'column';
btnApplyAll.style.alignItems = 'center';
// main label
const label = document.createElement('span');
label.textContent = 'Apply to all countries';
// smaller warning
const warning = document.createElement('span');
warning.textContent = '(will overwrite all custom settings!)';
warning.style.fontSize = '0.8em';
warning.style.opacity = '0.8';
btnApplyAll.appendChild(label);
btnApplyAll.appendChild(warning);
btnApplyAll.onclick = () => {
const val = (body.querySelector('input[name="ltb-view-size"]:checked') || {}).value || 'Large';
setDefaultSizeMode(val);
clearAllCountrySizeOverrides();
render();
};
applyWrap.appendChild(btnApplyCountry);
applyWrap.appendChild(btnApplyAll);
viewSection.appendChild(viewTitle);
viewSection.appendChild(radiosWrap);
viewSection.appendChild(applyWrap);
// Lookup sites section (per current country)
const sitesSection = document.createElement('div');
sitesSection.style.marginTop = '14px';
const sitesTitle = document.createElement('div');
sitesTitle.textContent = 'Lookup sites (uncheck to hide)';
sitesTitle.style.fontWeight = '600';
sitesTitle.style.marginBottom = '6px';
const sitesWrap = document.createElement('div');
sitesWrap.style.display = 'grid';
sitesWrap.style.gridTemplateColumns = '1fr';
sitesWrap.style.gap = '6px';
const allSites = (lookupSites && code && lookupSites[code]) ? lookupSites[code] : [];
const hiddenSet = code ? getHiddenSitesForCountry(code) : new Set();
for (const site of allSites) {
const lab = document.createElement('label');
lab.style.display = 'flex'; lab.style.alignItems = 'center'; lab.style.gap = '8px';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = !hiddenSet.has(site.name);
cb.addEventListener('change', () => {
const newHidden = getHiddenSitesForCountry(code);
if (cb.checked) {
newHidden.delete(site.name);
} else {
newHidden.add(site.name);
}
setHiddenSitesForCountry(code, Array.from(newHidden));
render();
});
const span = document.createElement('span'); span.textContent = site.name;
lab.appendChild(cb); lab.appendChild(span);
sitesWrap.appendChild(lab);
}
sitesSection.appendChild(sitesTitle);
sitesSection.appendChild(sitesWrap);
// Global toggles section
const globalSection = document.createElement('div');
globalSection.style.marginTop = '14px';
const globalTitle = document.createElement('div');
globalTitle.textContent = 'Other options';
globalTitle.style.fontWeight = '600';
globalTitle.style.marginBottom = '6px';
const globalWrap = document.createElement('div');
globalWrap.style.display = 'grid';
globalWrap.style.gridTemplateColumns = '1fr';
globalWrap.style.gap = '6px';
const mkToggle = (label, isEnabled, onChange) => {
const lab = document.createElement('label');
lab.style.display = 'flex';
lab.style.alignItems = 'center';
lab.style.gap = '8px';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = isEnabled();
cb.addEventListener('change', () => {
onChange(cb.checked);
render();
});
const span = document.createElement('span');
span.textContent = label + ' (global)';
lab.appendChild(cb); lab.appendChild(span);
return lab;
};
globalWrap.appendChild(mkToggle('Show Google Images', isGoogleImagesGloballyEnabled, (checked) => setGoogleImagesGloballyEnabled(checked)));
globalWrap.appendChild(mkToggle('Show Autogespot', isAutogespotGloballyEnabled, (checked) => setAutogespotGloballyEnabled(checked)));
globalSection.appendChild(globalTitle);
globalSection.appendChild(globalWrap);
body.appendChild(viewSection);
body.appendChild(sitesSection);
body.appendChild(globalSection);
wrapper.appendChild(header);
wrapper.appendChild(body);
document.body.appendChild(wrapper);
try { makeDraggable(wrapper, header); } catch {}
}
// --- Initial render + live updates ---
function render() {
createOrUpdateLookupButtons();
createOrUpdateGoogleImagesButton();
createOrUpdateAutogespotButton();
}
render();
setInterval(render, 1000);
})();