WME Bookmarks

Bookmark, share your favourite places

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name            WME Bookmarks
// @description     Bookmark, share your favourite places
// @version         2025.12.13.001
// @icon            data:image/png;base64,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
// @include         https://www.waze.com/*/editor*
// @include         https://beta.waze.com/*
// @exclude         https://www.waze.com/user/*
// @exclude         https://www.waze.com/*/user/*
// @namespace       https://greasyfork.org/fr/scripts/4515-wme-bookmarks
// @connect         waze-france.fr
// @grant           GM_xmlhttpRequest
// @author          Sebiseba
// @copyright       Sebiseba 2014-2023
// @require         https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// ==/UserScript==

/* global $ */
/* global WazeWrap */
/* global I18n */

(function() {
    //'use strict';
    const scriptName = GM_info.script.name;
    var BKMversion = GM_info.script.version, noSynch, link={}, bookmarks_Layer=null, bookmarkList=[], countries=[], checkData=true, debug = 0;
    var lang, tset, text1, text2, text3; // Language variables
    var BKMusername, BKMcountryActive; // DOM variables
    var di = 'data:image/png;base64,';
    const BKM_UPDATE_NOTES = `<b>NEW:</b><br> <b>FIXES:</b><br> - Update for new WME<br><br>`;
    const layerMapping = [
        { Idx: 1, id: "group_issues_tracker" },
        { Idx: 11, id: "group_places" }, { Idx: 12, id: "item_venues" }, { Idx: 35, id: "item_venues_labels" }, { Idx: 13, id: "item_residential_places" }, { Idx: 14, id: "item_parking_places" }, { Idx: 36, id: "item_venues_parking_labels" }, { Idx: 45, id: "item_natural_features" }, { Idx: 46, id: "item_venues_natural_features_labels" }, { Idx: 47, id: "item_my_saved_places" },
        { Idx: 15, id: "group_road" }, { Idx: 16, id: "item_road" }, { Idx: 38, id: "item_paths" }, { Idx: 17, id: "item_junction_boxes" }, { Idx: 18, id: "item_closures" }, { Idx: 31, id: "item_house_numbers" },
        { Idx: 64, id: "group_permanent_hazards" }, { Idx: 65, id: "item_permanent_hazard_camera" }, { Idx: 69, id: "item_permanent_hazard_railroad_crossing" }, { Idx: 72, id: "item_permanent_hazard_school_zone" }, { Idx: 66, id: "item_permanent_hazard_dangerous_curve" }, { Idx: 67, id: "item_permanent_hazard_dangerous_intersection" }, { Idx: 68, id: "item_permanent_hazard_dangerous_merge" }, { Idx: 70, id: "item_permanent_hazard_speed_bump" }, { Idx: 71, id: "item_permanent_hazard_toll_booth" },
        { Idx: 20, id: "group_display" }, { Idx: 21, id: "item_satellite_imagery" }, { Idx: 22, id: "item_area_managers" }, { Idx: 23, id: "item_gps_points" }, { Idx: 25, id: "item_editable_areas" }, { Idx: 43, id: "item_online_editors" }, { Idx: 29, id: "item_disallowed_turns" }, { Idx: 26, id: "item_map_comments" }, { Idx: 27, id: "group_cities" }, { Idx: 28, id: "item_city_names" }, { Idx: 32, id: "group_restricted_driving_areas" }, { Idx: 33, id: "item_restricted_driving_areas_names" },
        { Idx: 48, id: "item_group_imagery" }, { Idx: 54, id: "item_ee_merged_collection_by_latest_no_candid" }, { Idx: 56, id: "item_ee_merged_collection_by_quality_no_candid" }, { Idx: 58, id: "item_ee_satellite_pleiades_ortho_rgb" }, { Idx: 61, id: "item_ee_satellite_worldview2_ortho_rgb" }, { Idx: 62, id: "item_ee_satellite_worldview3_ortho_rgb" }, { Idx: 57, id: "item_ee_satellite_geoeye1_ortho_rgb" }, { Idx: 60, id: "item_ee_satellite_skysat_ortho_rgb" }, { Idx: 59, id: "item_ee_satellite_pneo_ortho_rgb" }
    ];

    // ***********
    // ** START **
    // ***********

    let wmeSDK;
    // Ensure SDK_INITIALIZED is available
    if (unsafeWindow.SDK_INITIALIZED) {
        unsafeWindow.SDK_INITIALIZED.then(bootstrap).catch((err) => {
            console.error(`${scriptName}: SDK initialization failed`, err);
        });
    }
    else {
        console.warn(`${scriptName}: SDK_INITIALIZED is undefined`);
    }
    function bootstrap() {
        wmeSDK = unsafeWindow.getWmeSdk({
            scriptId: scriptName.replaceAll(' ', ''),
            scriptName: scriptName,
        });

        // Use Promise.all to check readiness of all dependencies
        Promise.all([isWmeReady(), isWazeWrapReady()])
            .then(() => {
            console.log(`${scriptName}: All dependencies are ready.`);
            BKMinit();
            console.log(`${scriptName}: Initialized`);
        })
            .catch((error) => {
            console.error(`${scriptName}: Error during bootstrap -`, error);
        });
    }
    function isWmeReady() {
        return new Promise((resolve, reject) => {
            if (wmeSDK && wmeSDK.State.isReady() && wmeSDK.Sidebar && wmeSDK.LayerSwitcher && wmeSDK.Shortcuts && wmeSDK.Events) {
                console.log(`${scriptName}: WME is already ready.`);
                resolve();
            } else {
                wmeSDK.Events.once({ eventName: 'wme-ready' })
                    .then(() => {
                    if (wmeSDK.Sidebar && wmeSDK.LayerSwitcher && wmeSDK.Shortcuts && wmeSDK.Events) {
                        console.log(`${scriptName}: WME is fully ready now.`);
                        resolve();
                    } else {
                        reject(`${scriptName}: Some SDK components are not loaded.`);
                    }
                })
                    .catch((error) => {
                    console.error(`${scriptName}: Error while waiting for WME to be ready:`, error);
                    reject(error);
                });
            }
        });
    }
    function isWazeWrapReady() {
        return new Promise((resolve, reject) => {
            const maxTries = 1000;
            const checkInterval = 500;

            (function check(tries = 0) {
                if (unsafeWindow.WazeWrap && unsafeWindow.WazeWrap.Ready) {
                    console.log(`${scriptName}: WazeWrap is successfully loaded.`);
                    resolve();
                } else if (tries < maxTries) {
                    setTimeout(() => check(++tries), checkInterval);
                } else {
                    reject(`${scriptName}: WazeWrap took too long to load.`);
                }
            })();
        });
    }

    // ********************
    // **  CHECK SERVER  **
    // ********************

    const timeoutMs = 3000;
    const maxRetries = 4;
    function updateIcon($icon, state) {
        switch(state) {
            case 'ok':
                $('#pingAttempt').html('');
                $('#bkmServer').css("opacity","1");
                if (noSynch != 1) {
                    $('#transmit').show();
                    $('#notransmit').hide();
                    $('#noStable').hide();
                    $('#noSynch').hide();
                }
                $icon.html('<img src="https://cdn.pixabay.com/animation/2023/03/20/02/45/02-45-27-186_256.gif" style="height:16px;"/>');
                setTimeout(function() {
                    $icon.html('✔️').attr('title', 'Serveur OK');
                }, 1000);
                if ($('#chkSynchro').prop('checked')) { getBookmarks(''); }
                break;
            case 'instable':
                $icon.html('✖️').attr('title', 'Serveur instable');
                if (noSynch != 1) {
                    $('#transmit').hide();
                    $('#notransmit').hide();
                    $('#noStable').show();
                    $('#noSynch').hide();
                }
                break;
            case 'indisponible':
                $icon.html('❌').attr('title', 'Serveur indisponible');
                $('#pingAttempt').html(' Echec');
                if ($('#chkSynchro').prop('checked')) { $('#chkSynchro').prop('checked', false); }
                $('#bkmServer').css("opacity","0.3");
                if (noSynch != 1) {
                    $('#transmit').hide();
                    $('#notransmit').show();
                    $('#noStable').hide();
                    $('#noSynch').hide();
                }
                break;
        }
    }
    function pingServer(attempt = 1) {
        const $icon = $('#iconSynchro');
        const BKMusername = wmeSDK.State.getUserInfo()?.userName;
        GM.xmlHttpRequest({
            method: 'GET',
            url: `https://waze-france.fr/script/ping.htm`,
            timeout: timeoutMs,
            onload: function(response) {
                if (response.status === 200) {
                    updateIcon($icon, 'ok');
                } else {
                    console.warn(`Réponse HTTP inattendue: ${response.status}`);
                    handleRetry(attempt, $icon);
                }
            },
            onerror: function(err) {
                console.warn('Erreur réseau', err);
                handleRetry(attempt, $icon);
            },
            ontimeout: function() {
                console.warn('Timeout serveur');
                handleRetry(attempt, $icon);
            }
        });
    }
    function handleRetry(attempt, $icon) {
        if (attempt < maxRetries) {
            console.log(`${scriptName}: Nouvelle tentative dans 10s (tentative ${attempt + 1})`);
            updateIcon($icon, 'instable');
            setTimeout(() => pingServer(attempt + 1), 10000);
            $('#pingAttempt').html(' Tentative n°'+attempt);
        } else {
            console.error(`${scriptName}: Serveur considéré comme indisponible après ${maxRetries} tentatives`);
            updateIcon($icon, 'indisponible');

        }
    }

    // ************
    // **  MISC  **
    // ************

    function isJsonString(str) {
        try { JSON.parse(str); }
        catch (e) { return false; }
        return true;
    }
    function diffJSON(obj1, obj2, path = '') {
        const differences = {};
        const keys = new Set([...Object.keys(obj1 || {}), ...Object.keys(obj2 || {})]);

        keys.forEach(key => {
            const fullPath = path ? `${path}.${key}` : key;
            const val1 = obj1 ? obj1[key] : undefined;
            const val2 = obj2 ? obj2[key] : undefined;

            if (typeof val1 === 'object' && val1 !== null && typeof val2 === 'object' && val2 !== null) {
                const nested = diffJSON(val1, val2, fullPath);
                if (Object.keys(nested).length > 0) {
                    Object.assign(differences, nested);
                }
            } else if (val1 !== val2) {
                differences[fullPath] = { from: val1, to: val2 };
            }
        });
        return differences;
    }
    function BKMtableCountries() {
        var waitFordivHistoContent = setInterval(function() {
            if ($('#divHistoContent').length && wmeSDK.DataModel.Cities.getTopCity()) {
                $('#histoButtonDiv').show();
                $('#histoPrev').css('color', 'black');
                link={}; getLink(wmeSDK.Map.getPermalink({ includeLayers: true }));
                const d=new Date();
                const options = { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }; //hour12: true
                let histoDate;
                if ($('#chkHistoDate').prop('checked')) {
                    histoDate = new Date().toLocaleString("en-US", options);
                } else {
                    histoDate = new Date().toLocaleString("fr-FR", options);
                }
                const encodedBookmark = {
                    country: BKMcountryActive,
                    name: getName(),
                    perma: link,
                    hour: histoDate
                };
                const jsonData = JSON.parse(localStorage.WMEHistoric);
                const result = jsonData.filter(item => item.perma.lon === link.lon && item.perma.lat === link.lat);
                if (result.length === 0) { // No double
                    $('#bkmHisto').append(makeLiShort(encodedBookmark,"Histo"));
                    jsonData.push(encodedBookmark);
                    if (jsonData.length > JSON.parse(localStorage.WMEBookmarksSettings).histolength) {
                        jsonData.shift();
                    }
                    localStorage.setItem('WMEHistoric', JSON.stringify(jsonData));
                    $('#bkmHisto').empty();
                    jsonData.slice().reverse().forEach(item => {
                        $('#bkmHisto').append(makeLiShort(item,"Histo"));
                    });
                }
                clearInterval(waitFordivHistoContent);
            }
        }, 200);

        const currentTopCountry = wmeSDK.DataModel.Countries.getTopCountry()?.name;
        const selectedCountry = $('#selectCountry').val();
        try {
            const a = JSON.parse(localStorage.WMEBookmarks);
            for (const item of a) {
                const pays = item.country;
                if (pays && pays.length > 0 && !countries.includes(pays)) {
                    countries.push(pays);
                }
            }
            if (!selectedCountry || selectedCountry === null) {
                if (debug) console.log(`${scriptName}: Main Countries listed`, countries);
                select(countries, 'selectCountry');
            }
        } catch (err) {
            console.error(`${scriptName}: Erreur parsing WMEBookmarks :`, err);
        }

        if (selectedCountry && currentTopCountry && selectedCountry !== currentTopCountry) {
            if (debug) console.log(`${scriptName}: Country changed :`, selectedCountry, currentTopCountry);

            const jsonData = JSON.parse(localStorage.WMEBookmarks);
            $('#bkmList').empty();

            jsonData.forEach(item => {
                if (item.country === selectedCountry) {
                    $('#bkmList').append(makeLi(item));
                }
            });
        }
    }
    function select(cArray, selectlist) {
        const BKMcountryActive = wmeSDK.DataModel.Countries.getTopCountry()?.name;
        const $CSelect = $('#' + selectlist);
        $CSelect.empty();
        for (const c of cArray) {
            const $option = $('<option></option>')
            .val(c)
            .text(c);
            if (c === BKMcountryActive) {
                $option.prop('selected', true);
            }
            $CSelect.append($option);
        }

        //If country not listed
        if (!cArray.includes(BKMcountryActive)) {
            const $missingOption = $('<option></option>')
            .val(BKMcountryActive)
            .text(BKMcountryActive)
            .prop('selected', true);
            $CSelect.append($missingOption);
        }
        giveResults("");
    }
    function giveResults(source) {
        $('#bkmList').empty();
        const jsonData = JSON.parse(localStorage.WMEBookmarks);

        if (source === "BKMSearch") {
            const query = $('#BKMSearch').val();
            const nq = normalizeText(query);

            if (query.length > 1) {
                const results = jsonData.filter(item => {
                    const ctry = item.country ? normalizeText(item.country) : "";
                    const name = item.name ? normalizeText(item.name) : "";
                    const comm = item.comm ? normalizeText(item.comm) : "";

                    return ctry.includes(nq) ||
                        name.includes(nq) ||
                        comm.includes(nq);
                });

                if (results.length !== 0) {
                    results.forEach(item => $('#bkmList').append(makeLi(item)));
                }
            }
        } else {
            jsonData
                .filter(item => item.country === $('#selectCountry').val())
                .sort((a, b) => {
                const sortA = Number(a.sort);
                const sortB = Number(b.sort);

                if (isNaN(sortA) && isNaN(sortB)) return 0;
                if (isNaN(sortA)) return 1;
                if (isNaN(sortB)) return -1;
                return sortA - sortB;
            })
                .forEach(item => $('#bkmList').append(makeLi(item)));
        }
        updateLayer();
    }
    function normalizeText(str) {
        return str
            .normalize("NFD")
            .replace(/[\u0300-\u036f]/g, "")
            .toLowerCase();
    }
    function normalizeSort(sortBy = "sort") {
        const topCountry = wmeSDK.DataModel.Countries.getTopCountry().name;
        const data = JSON.parse(localStorage.WMEBookmarks || "[]");
        const filtered = data.filter(item => item.country === topCountry);

        switch(sortBy) {
            case "name-asc":
                filtered.sort((a, b) => a.name.localeCompare(b.name, 'fr', { sensitivity: 'base' }));
                break;
            case "name-desc":
                filtered.sort((a, b) => b.name.localeCompare(a.name, 'fr', { sensitivity: 'base' }));
                break;
            case "sort":
            default:
                filtered.sort((a, b) => Number(a.sort) - Number(b.sort));
        }
        filtered.forEach((item, index) => {
            item.sort = index.toString();
        });
        const newData = data.map(item => {
            if (item.country === topCountry) {
                return filtered.find(f => f.name === item.name && f.perma.lon === item.perma.lon && f.perma.lat === item.perma.lat);
            } else {
                return item;
            }
        });
        localStorage.WMEBookmarks = JSON.stringify(newData);
        giveResults();
        BKMpostBookmarks();
    }
    function makeLi(item) {
        const liColor = item.color ? item.color : 'rgb(255, 255, 255)';
        const commText = item.comm ? item.comm.replace('\n','<br>') : ' ';
        const reminderIcon = item.reminder ? `<span class="bkm-btn bkm-reminder-icon">⏰</span>` : '';
        const shareIcon = item.share ? `<span class="bkm-btn bkm-share-icon">🔗</span>` : '';
        const $li = $(`<li class="bkm-item"
                data-reminder="${item.reminder || ''}"
                data-lonlat="${item.perma.lon}*${item.perma.lat}"
                data-zoom="${item.perma.zoom || ''}"
                data-layers="${item.perma.layers || ''}"
                data-share="${item.share || ''}"
                data-color="${liColor}"
                data-type_obj="${item.perma.type_obj || ''}"
                data-id_obj="${item.perma.id_obj || ''}">
                <span class="bkm-name">${item.name}</span>
                <span class="bkm-comment">${commText}</span>
                <div class="bkm-actions">${shareIcon}${reminderIcon}${checkObjIcon(item.perma.type_obj)}
                    <button class="bkm-btn bkm-editBtn" title="${lang[16]}">✏️</button>
                    <button class="bkm-btn bkm-deleteBtn" title="${lang[11]}">🗑️</button>
                    <button class="bkm-btn bkm-relocBtn" title="${lang[12]}">📌</button>
                </div></li> `)
        .attr('draggable', 'true')
        .css('background-color', liColor);
        return $li;
    }
    function makeLiShort(item,source) {
        let detail = (source === "Histo") ? item.hour : item.owner;
        const $li = $(`<li class="bkm-item" style="display:flex; justify-content:space-between; align-items:center; flex-direction:row;"
                data-lonlat="${item.perma.lon}*${item.perma.lat}"
                data-zoom="${item.perma.zoom}"
                data-layers="${item.perma.layers || ''}"
                data-type_obj="${item.perma.type_obj || ''}"
                data-id_obj="${item.perma.id_obj || ''}">
            <div class="bkm-name" style="text-align:left;">${item.name}</div>
            <div style="font-size:10px; text-align:right;">${detail}</div></li> `)
        .attr('draggable', 'false')
        .css('background-color', 'rgb(255, 255, 255)');
        return $li;
    }
    function makeListSortable() {
        const list = document.getElementById('bkmList');
        let draggedItem = null;

        list.addEventListener('dragstart', (e) => {
            const li = e.target.closest('li');
            if (!li) return;
            draggedItem = li;
            li.style.opacity = 0.5;
        });

        list.addEventListener('dragend', (e) => {
            const li = e.target.closest('li');
            if (!li) return;
            li.style.opacity = '';
            draggedItem = null;
            logListOrder();
        });

        list.addEventListener('dragover', (e) => {
            e.preventDefault();
            const li = e.target.closest('li');
            if (!li || li === draggedItem) return;

            const rect = li.getBoundingClientRect();
            const next = (e.clientY - rect.top) / (rect.bottom - rect.top) > 0.5;
            list.insertBefore(draggedItem, next ? li.nextSibling : li);
        });
    }
    function logListOrder() {
        const listItems = document.querySelectorAll('#bkmList li');
        const jsonData = JSON.parse(localStorage.WMEBookmarks);

        listItems.forEach((li, index) => {
            const name = $(li).find('.bkm-name').text().trim();
            const comm = $(li).find('.bkm-comment').text().trim();

            const item = jsonData.find(obj => obj.name === name && obj.comm === comm);
            if (item) {
                item.sort = index.toString();
            } else {
                console.warn(`${scriptName}: No matches found for : name="${name}", comm="${comm}"`);
            }
        });
        localStorage.WMEBookmarks = JSON.stringify(jsonData);
        if ($('#chkSynchro').prop('checked')) {
            initBookmarks();
            setTimeout(BKMpostBookmarks, 500);
        }
    }
    function openEditForm($li){
        $('#divSelectCountry').hide();
        const currentName = $li.find('.bkm-name').text();
        const currentCoord = $li.data('lonlat');
        const currentComm = $li.find('.bkm-comment').html().replace(/<br\s*\/?>/gi, '\n');
        const currentShare = $li.data('share') || '';
        const currentReminder = $li.data('reminder') || '';
        const currentObjet = $li.data('type_obj') || '';
        const currentColor = $li.data('color') || $li.css('background-color');
        const $form = $(`
            <div class="bkm-edit-form-container" data-type_obj="${currentObjet}">
                <label>${lang[6]}:<input type="text" id="tmnEditName" value="${currentName}"></label>
                <label>${lang[17]}:</label>
                <div id="tmnColorPicker">
                    <div class="bkm-color-swatch" data-color="rgb(255, 255, 255)" style="background:rgb(255,255,255);"></div>
                    <div class="bkm-color-swatch" data-color="rgb(255, 255, 204)" style="background:rgb(255,255,204);"></div>
                    <div class="bkm-color-swatch" data-color="rgb(255, 204, 204)" style="background:rgb(255,204,204);"></div>
                    <div class="bkm-color-swatch" data-color="rgb(204, 204, 204)" style="background:rgb(204,204,204);"></div>
                    <div class="bkm-color-swatch" data-color="rgb(204, 255, 255)" style="background:rgb(204,255,255);"></div>
                    <div class="bkm-color-swatch" data-color="rgb(204, 204, 255)" style="background:rgb(204,204,255);"></div>
                    <div class="bkm-color-swatch" data-color="rgb(204, 255, 204)" style="background:rgb(204,255,204);"></div>
                    <div class="bkm-color-swatch" data-color="rgb(255, 204, 255)" style="background:rgb(255,204,255);"></div>
                </div>
                <label>${lang[14]}:<input type="datetime-local" id="tmnEditDate" value="${currentReminder}"/></label>
                <label>${lang[13]}:<textarea id="tmnEditComm" style="resize:vertical; min-height:100px;">${currentComm}</textarea></label>
                <label>${lang[2]}:<input type="text" id="tmnEditShare" value="${currentShare}"/></label>
                <div style="text-align:center;">
                    <button id="tmnEditCancel" style="margin:0 5px;">${lang[9]}</button>
                    <button id="tmnEditSave" style="margin:0 5px;">${lang[8]}</button>
                </div>
            </div>`);
        $form.find(`.bkm-color-swatch[data-color="${currentColor}"]`).addClass("selected");
        $('#BKMedit').empty().append($form);

        const $colorPicker = $form.find('#tmnColorPicker');
        const $inputName = $form.find('#tmnEditName');
        const $inputComm = $form.find('#tmnEditComm');
        const $inputDate = $form.find('#tmnEditDate');
        const $inputShare = $form.find('#tmnEditShare');
        const $btnSave = $form.find('#tmnEditSave');
        const $btnCancel = $form.find('#tmnEditCancel');

        let selectedColor = currentColor;

        $colorPicker.on('click', '.bkm-color-swatch', function() {
            $colorPicker.find('.bkm-color-swatch').removeClass('selected');
            $(this).addClass('selected');
            selectedColor = $(this).data('color');
        });
        $btnSave.on('click', function() {
            const newName = $inputName.val().trim();
            let newComm = $inputComm.val();
            newComm = newComm.replace(/\r?\n/g, '<br />');
            const newReminder = $inputDate.val();
            const newShare = $inputShare.val().trim();

            if (newName) { $li.find('.bkm-name').text(newName); }
            $li.find('.bkm-share').val(newShare);

            $li.css('background-color', selectedColor)
                .data('color', selectedColor)
                .attr('data-color', selectedColor)
                .data('share', newShare)
                .attr('data-share', newShare)
                .data('reminder', newReminder)
                .attr('data-reminder', newReminder);

            const $actions = $li.find('.bkm-actions');
            $actions.find('.bkm-share-icon').remove();
            $actions.find('.bkm-reminder-icon').remove();
            if (currentObjet) { $actions.prepend(checkObjIcon(currentObjet)); }
            if (newShare) { $actions.prepend('<span class="bkm-btn bkm-share-icon">🔗</span>'); }
            if (newReminder) { $actions.prepend('<span class="bkm-btn bkm-reminder-icon">⏰</span>'); }

            const [lon, lat] = currentCoord.split('*');
            const jsonData=JSON.parse(localStorage.WMEBookmarks);
            const bkmToChange = jsonData.find(item => item.perma.lon === lon && item.perma.lat === lat);
            selectedColor = (selectedColor === "rgb(255, 255, 255)") ? "" : selectedColor;
            if (bkmToChange) {
                bkmToChange.name = newName;
                bkmToChange.comm = newComm;
                bkmToChange.share = newShare;
                bkmToChange.reminder = newReminder;
                bkmToChange.color = selectedColor;
                localStorage.setItem('WMEBookmarks', JSON.stringify(jsonData));

                if ($('#chkSynchro').prop('checked')) {
                    if (debug) console.log(`${scriptName}: UPDATE: `, BKMusername, bkmToChange);
                    BKMupdateBookmarks('UPDATE',BKMusername, bkmToChange);
                }
            }
            $('#BKMedit').empty();
            $('#divSelectCountry').show();
            giveResults();
        });
        $btnCancel.on('click', function() {
            $('#BKMedit').empty();
            $('#divSelectCountry').show();
        });
    }
    function BKMconvertPermalink(data) { //Redo permalink
        if (data) {
            var l=data.split("|"), link = {};
            link.env=l[5];
            link.lat=l[1];
            link.lon=l[0];
            link.zoom=l[2];
            link.layers=l[3];
            if (l[4]) {
                switch (l[4].substring(0, 1)) {
                    case 's': link.segments = l[4].substring(2); break;
                    case 'n': link.nodes = l[4].substring(2); break;
                    case 'v': link.venues = l[4].substring(2); break;
                }
            }
            Object.keys(link).sort();
            return link;
        }
    }
    function getLink(pl) {
        const url = new URL(pl);
        const params = url.searchParams;
        const simpleKeys = { env: "env", lat: "lat", lon: "lon", zoomLevel: "zoom", s: "layers" };
        const objectKeys = [ "segments", "nodes", "venues", "bigJunctions", "mapProblems", "mapUpdateRequests", "permanentHazards", "mapComments", "editSuggestion", "segmentSuggestion" ];
        for (const key in simpleKeys) {
            if (params.has(key)) link[simpleKeys[key]] = params.get(key);
        }
        for (const objKey of objectKeys) {
            if (params.has(objKey)) {
                link.type_obj = objKey;
                link.id_obj = params.get(objKey);
                break;
            }
        }
    }
    function getName() {
        const city = wmeSDK.DataModel.Cities.getTopCity();
        if (city) {
            return city.name;
        } else {
            return lang[15];
        }
    }
    function checkObjIcon(typeobj) {
        let ObjIcon, withObj;
        switch (typeobj) {
            case "nodes":
                ObjIcon = di+"iVBORw0KGgoAAAANSUhEUgAAAGUAAABBCAYAAADIQWrvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAZ/SURBVHja7J3BTxpZHMffzBBCabuYTXdDMBJbSRQlCKi4IgeyMU2xrXuVxNimB63CTatmNxya0PQP8GSk0dihLZiGqKjZIDHYVDzYupBaZkLY8eJ2m23STbvdelg7e6HJNmvrGxhm3gAv+d0I83t85vfem9/8fl8wlmVBZaA1ZFJzeGlpyZhKpYw0TddTFNXw4sUL9evXr79+9+6dEgAATp48+XdVVdWfGo3mt4aGBqq+vp42Go2py5cvp6QyRwz1SMlmszKSJPtWV1ed29vbrYeHhzgAoJbj1+wRBPGhtbV12+l0rvb19ZF1dXX/IDtplmWRtEAgYOvs7AxgGMYAAFg+DcMwprOzMxAIBGwozh05h/x+f5dOp4sCAHiHcYQxOp0u6vf7uypQjrCtrS21xWJ5KBCM/8GxWCwPt7a21BUoOfN4PKM4jmdFgPGJ4Tie9Xg8o2UNhaZpucFgWBIpOj4bNQaDYYmmaXnZQVlbW9OpVKokQjA+saqqqp21tTVd2UAJhUJWuVxOowrko8nlcjoUCllLHkooFLISBMHn/sF8wQr+foIgskKDERRILBY7x0OEMFqtNn7lypWb09PTXfF4XHvUteLxuHZ6errr6tWrN7VabbwQSHK5nI7FYudKDgpN0/IC9hBGqVTuDgwM/Li5uanJ5/qbm5uawcHBCaVSuZsPIJVKlRRq8xcMSu6UldddOjIy4uHTl5GREU8+EWswGJZKBsrw8PBYHncnY7fb76bTaUUxfEqn0wq73X6Xo1+M2+0elTyUeDyu5Zq/wjCMGR8fvy7EDTM+Pn6di384jmcTiYRa0lC4LlsEQWSFzkXNzMw4uJwIzWZzWLJQ/H5/F5flAcfxLEmSdjGenUiStHMAw9y5c+d7SUKpq6uLcVmvJycnL4mZ9pmcnLwEexPpdLqo5KCQJGnnECVMb2/vbRSSo729vbch/WaK9T6maJNrb28PwkZJdXX1Y5TeZ1RXVz+G8bu9vT0oGSi7u7tKLlEiRn7pSxYMBq2w0fLs2bNTkoDi9XqvwUZJW1vbPIqvZNva2uZh/Pd6vdckAcVqtcIuXUw4HDahCCUcDptgosVqtQYlAQX2aInaXpLP3kIQRJbv6+J8V8dEIhFDrgzo2OFyue6jXN4E49/h4SEeiUQMfF6XdyjJZNIEWZe1d/HixQjKUHL+7R3zsdrcnNGFQlFUPcznFArFgcPh2EMZisPh2FMoFAd8zRm6QjK3mfE9jo0Uo9G4mEwmf0C9hLS5uXkhlUr1HBf1fNcS14oxWb1eT0mhrlev11MQUHj9DXGxJnvmzJk/pABFDD9Fg3L69Om/pABFDD9xUBlf3nQx7EPZQHn79u0pKUB58+bNV2UD5dWrV99IAYoYfooGJZ1ON0gBihh+ivacolAoqPfv3+tRh3LixIn0wcHBcWD4fQjmO5nm8/n6AGSGeH19vRblhOT6+notgMgU+3y+PqQTks3Nzb9A3jm1y8vLl1COkpWVlW6IqN/LzRndSKmk7hGMFAAAaGlp2Yb53P7+vmZhYcGEYpQsLi4a9/f3NXzNVfTTl9PpXIU9ENy6desnFKH4fD4vzIHlwoULP/N+8WKEfSaTkXEoBWXm5+dbUVq2QqEQVOEEhmFMJpORSabEyGazBUCJlxjZbLZASRfjuVwuJIrxXC4XdDFesUpsK2Wr5VS2mk+BN0EQWbGkOe7du2criwJvlmWByWQKc4gWViaTZWZmZhxCt0LIZLIMrI8mk0m6rRAsy4JEIqHmqiYhZNPQxMRE+TUNsSwL3G73KCiR9johZEIEWyIaGxuXQZ6NqDdu3Bjm05exsbHhfBpRi93BJTgUiqIUhbZsDw0NjRXSsj00NDRWSMt2MR4US1LcYGNj40hxg42NjYq4AZfeD6nJgASDwdKVAfloDx48+E4qgjlCAxFVWioajSItLaVSqZLRaLR8pKX+u/k3NTUtA8RE2JqampYpilKI9bsgkZV1u93IyBUKIfMhGWHPRCKhNpvNYZGihjGbzeFiP6lLVgJ3amrqfE1NzSOB4DA1NTWPpqamzlckcCFsdnbW0dHRcb8YYtEAAKajo+P+7OysA8W5S0JWfW5urn9lZcW5s7NjKURW3Ww2P+3u7l7t7++fQ1lWHXkoR1WZPHnypPX58+d6hmHOvXz58tuj/oBArVb/fvbs2V8bGxvTLS0t2z09PZU/IKgMCRZ4V8bnx78DALTIlbjY/TL0AAAAAElFTkSuQmCC";
                break;
            case "segments":
                ObjIcon= di+"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";
                break;
            case "venues":
                ObjIcon = di+"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";
                break;
            default:
                ObjIcon = '';
                break;
        }
        if (ObjIcon != '') { withObj = `<span class="bkm-btn bkm-share-icon"><img src="${ObjIcon}" style="height:12px;"></span>`; } else { withObj = ''; }
        return withObj;
    }
    function normalizeJSON(obj) {
        if (Array.isArray(obj)) return obj.map(normalizeJSON);
        if (obj !== null && typeof obj === 'object') {
            const sortedKeys = Object.keys(obj).sort();
            const res = {};
            for (const key of sortedKeys) {
                res[key] = normalizeJSON(obj[key]);
            }
            return res;
        }
        return obj;
    }
    function checkDatas() {
        if (checkData === false) return;
        const listFav = JSON.parse(localStorage.WMEBookmarks);
        if (listFav.length === 0) {
            $('#local2serv').css({ 'pointer-events': 'none', 'opacity': '0.3' });
            $('#razButton1').css({ 'pointer-events': 'none', 'opacity': '0.3' });
        } else {
            $('#local2serv').css({ 'pointer-events': 'auto', 'opacity': '1' });
            $('#razButton1').css({ 'pointer-events': 'auto', 'opacity': '1' });
        }
        if ($('#chkSynchro').prop('checked')) {
            GM.xmlHttpRequest({
                method: 'GET',
                url: 'https://waze-france.fr/script/bkm.php?getbookmarks2=' + BKMusername,
                headers:{"Content-Type": "application/x-www-form-urlencoded"},
                onload: function(data) {
                    const serverData = JSON.parse(data.responseText);
                    const serverJSON = normalizeJSON(serverData);
                    const localJSON = normalizeJSON(listFav);
                    localJSON.forEach(function(item) {
                        if (!item.reminder || item.reminder === null) { item.reminder = ""; }
                        if (!item.sort || item.sort === null) { item.sort = ""; }
                        if (!item.color || item.color === null) { item.color = ""; }
                        if (!item.perma.type_obj || item.perma.type_obj === null) { item.perma.type_obj = ""; }
                        if (!item.perma.id_obj || item.perma.id_obj === null) { item.perma.id_obj = ""; }
                    });
                    const syncEnabled = $('#chkSynchro').prop('checked') === true;
                    if (debug) { console.log('Diff: ', diffJSON(serverJSON, localJSON), Object.keys(diffJSON(serverJSON, localJSON)).length); }
                    if (Object.keys(diffJSON(serverJSON, localJSON)).length != 0 && syncEnabled) { $('#iconServer').click(); checkData=false; }
                    if (serverData.length === 0) {
                        $('#serv2local').css({ 'pointer-events': 'none', 'opacity': '0.3' });
                        $('#razButton2').css({ 'pointer-events': 'none', 'opacity': '0.3' });
                    } else {
                        $('#serv2local').css({ 'pointer-events': 'auto', 'opacity': '1' });
                        $('#razButton2').css({ 'pointer-events': 'auto', 'opacity': '1' });
                    }
                }
            });
        }
        pingServer();
    }
    function checkReminder() {
        const a = JSON.parse(localStorage.WMEBookmarks);
        const toSave = [];

        $.each(a, function(_, bookmark) {
            const encodedBookmark = { ...bookmark };
            const reminder = bookmark.reminder;

            if (reminder && !isNaN(Date.parse(reminder))) {
                const reminderDate = new Date(reminder);

                if (reminderDate <= new Date()) {
                    if (debug) console.log(`${scriptName}: Check Reminder`, reminder);
                    WazeWrap.Alerts.info("WME Bookmarks", `${bookmark.name}\n${bookmark.comm}`, true, false);
                    encodedBookmark.reminder = "";
                    if ($('#chkSynchro').prop('checked')) {
                        if (debug) console.log(`${scriptName}: UPDATE: `, BKMusername, encodedBookmark);
                        BKMupdateBookmarks('UPDATE',BKMusername, encodedBookmark);
                    }
                }
            }
            toSave.push(encodedBookmark);
            setTimeout(giveResults, 500);
        });
        localStorage.setItem('WMEBookmarks', JSON.stringify(toSave));
    }
    function toggleLayer(id, shouldBeChecked) {
        const el = document.getElementById(`layer-switcher-${id}`);
        if (!el) return;
        const input = el.shadowRoot ? el.shadowRoot.querySelector('input') : el;
        if (input.checked !== shouldBeChecked) { el.click(); }
    }
    function applyLayers(layers) {
        const bin = BigInt(layers).toString(2);
        layerMapping
            .filter(({ id }) => id.startsWith("group_"))
            .forEach(({ Idx, id }) => {
            const bit = bin[bin.length - 1 - Idx] || '0';
            toggleLayer(id, bit === '1');
            if (debug) console.log("GROUP", id, bit);
        });
        setTimeout(() => {
            layerMapping
                .filter(({ id }) => id.startsWith("item_"))
                .forEach(({ Idx, id }) => {
                const bit = bin[bin.length - 1 - Idx] || '0';
                toggleLayer(id, bit === '1');
                if (debug) console.log("ITEM", id, bit);
            });
        }, 200);
    }

    // ****************
    // ** MAIN HTML  **
    // ****************

    async function BKMinit() {
        console.log(`${scriptName} ${BKMversion} starting`);
        BKMusername = wmeSDK.State.getUserInfo()?.userName;
        BKMcountryActive = wmeSDK.DataModel.Countries.getTopCountry()?.name;

        // Verify localStorages
        if ('undefined' === typeof localStorage.WMEBookmarks || !isJsonString(localStorage.WMEBookmarks)) { localStorage.setItem('WMEBookmarks', '[]'); }
        if ('undefined' === typeof localStorage.WMEBookmarksShared || !isJsonString(localStorage.WMEBookmarksShared)) { localStorage.setItem('WMEBookmarksShared', '[]'); }
        if ('undefined' === typeof localStorage.WMEHistoric || !isJsonString(localStorage.WMEHistoric)) { localStorage.setItem('WMEHistoric', '[]'); }
        if ('undefined' === typeof localStorage.WMECopyPastePOI || !isJsonString(localStorage.WMECopyPastePOI)) { localStorage.setItem('WMECopyPastePOI', '[]'); }
        if ('undefined' === typeof localStorage.WMEBookmarksSettings || !isJsonString(localStorage.WMEBookmarksSettings)) { localStorage.setItem('WMEBookmarksSettings', '{"version":'+BKMversion+',"zoom":true, "showBookmark":true, "layersPaste":true, "server":false, "synchro":true, "lclic":false, "lcopy":false, "chkLastClic":true}'); }
        if ('undefined' === typeof localStorage.WMEPrevNext || !isJsonString(localStorage.WMEPrevNext)) { localStorage.setItem('WMEPrevNext', '{"prev":[],"next":[]}'); }


        // Convert to new format
        let arr = JSON.parse(localStorage.WMEBookmarks);
        const typeMap = { MC: "mapComments", MP: "mapProblems", UR: "mapUpdateRequests" };
        arr.forEach(item => {
            if (!item.perma) return;
            for (let k of ["MC", "MP", "UR"]) {
                if (item.perma[k] && item.perma[k] !== "0") {
                    item.perma.type_obj = typeMap[k];
                    item.perma.id_obj = item.perma[k];
                    break;
                }
            }
            delete item.perma.MC;
            delete item.perma.MP;
            delete item.perma.UR;
        });
        localStorage.WMEBookmarks = JSON.stringify(arr);

        // Translation
        var BKMLang = I18n.locale;
        if (BKMLang == 'fr') {
            lang = new Array('Favoris', 'Partage des amis', 'Partage ', 'Historique', 'Sauvegarde / Restauration', 'Synchronisation', 'Nom', 'Copier / Restaurer POI', 'Valider', 'Annuler', 'Ajouter', 'Supprimer', 'Relocaliser', 'Commentaire', 'Alarme', 'Sans Nom', 'Modifier', 'Couleur', 'Paramètres', 'Cliquez pour la recherche', 'Cliquez pour la sélection de pays', 'Écrivez pour rechercher', 'Êtes-vous sûr de remplacer vos données ?', 'Êtes-vous sûr d\'ajouter à vos données ?');
            tset = new Array('Appliquer le zoom', 'Appliquer les calques', 'Changement auto de serveur (usa/intl)', 'Synchroniser avec le serveur', 'Effacer les données locales', 'Effacer les données serveur du script', 'Va supprimer votre tri personnalisé, toujours d\'accord ?', 'Effacer l\'historique', 'Nouveau', 'Fusionner', 'Surligner le dernier lien visité', 'Activer l\'historique de navigation', 'Nombre max de l\'historique', 'Date de l\'historique au format US', 'Données non synchronisées', 'Données synchronisées', 'Etat du serveur');
            text1 = ' Copiez ces données dans un fichier TXT pour les conserver.<br/>Collez vos données pour les restaurer.';
            text2 = ' Écrivez les pseudos avec qui vous souhaitez partager le favoris. Le séparateur se mettra automatiquement.';
            text3 = ' Lorsque vous êtes synchronisé avec le serveur, le script envoie des données à celui-ci.<br>Les données sont: Pseudo, coordonnées, pays, nom du favoris, commentaires et pseudo des partages.<br>Effacer les données serveur du script supprime toutes traces de votre profil.';
        }
        else if (BKMLang == 'de') {
            lang = new Array('Favoriten', 'Freunde teilen', 'Teilen', 'Verlauf', 'Sichern/Wiederherstellen', 'Synchronisieren', 'Name', 'POI kopieren/wiederherstellen', 'Bestätigen', 'Abbrechen', 'Hinzufügen', 'Löschen', 'Verschieben', 'Kommentar', 'Alarm', 'Unbenannt', 'Bearbeiten', 'Farbe', 'Einstellungen', 'Zum Suchen klicken', 'Zum Auswählen von Ländern klicken', 'Eingabe zum Suchen', 'Möchten Sie Ihre Daten wirklich ersetzen?', 'Möchten Sie Ihre Daten wirklich ergänzen?');
            tset = new Array('Zoomstufe sichern', 'Ebenen sichern', 'Server automatisch wechseln (US/ROW)', 'Mit Server synchronisieren', 'Lokale Daten löschen', 'Serverdaten aus Skript löschen', 'Benutzerdefinierte Sortierung wird gelöscht, ist das in Ordnung?', 'Verlauf löschen', 'Neu', 'Zusammenführen', 'Zuletzt besuchten Link hervorheben', 'Browserverlauf aktivieren', 'Maximale Verlaufsgröße', 'Verlaufsdatum im US-Format', 'Daten nicht synchronisiert', 'Daten synchronisiert', 'Serverstatus');
            text1 = ' Daten zur Sicherung in eine TXT-Datei sichern.<br/>Zur Wiederherstellung Daten hier einfügen.';
            text2 = ' Usernamen des Users eintragen, mit dem du den Favoriten teilen willst. Trennzeichen werden automatisch eingefügt.';
            text3 = ' Beim Synchronisieren mit dem Server werden folgende Daten übermittelt:<br>Username, Koordinaten, Land, Name des Favoriten, Kommentare und Usernamen, mit denen geteilt wurde.<br>Server-Daten löschen entfernt alle deiner Spuren auf dem Server.';
        }
        else {
            lang = new Array('Bookmarks', 'Friend Sharing', 'Share', 'History', 'Backup/Restore', 'Sync', 'Name', 'Copy/Restore POI', 'Confirm', 'Cancel', 'Add', 'Delete', 'Relocate', 'Comment', 'Alarm', 'Unnamed', 'Edit', 'Color', 'Settings', 'Click to Search', 'Click to Select Countries', 'Type to Search', 'Are you sure you want to replace your data?', 'Are you sure you want to add to your data?');
            tset = new Array('Apply zoom', 'Apply layers', 'Auto switch server (usa/intl)', 'Synchronize with server', 'Clear local data', 'Clear server data from script', 'Will delete your custom sort, still okay?', 'Clear history', 'New', 'Merge', 'Highlight last visited link', 'Enable browsing history', 'Max history size', 'History date in US format', 'Data not synchronized', 'Data synchronized', 'Server status');
            text1 = ' Copy data into a TXT file to preserve them.<br/>Paste your data to restore them.';
            text2 = ' Write the nick you want to share the bookmark. The separator will be inserted automatically';
            text3 = ' When you are synchronized with the server, the script sends data to it <br>Data is: Nickname, coordinates, country, name of bookmarks, comments and nicknames shares <br> Clear script data server deletes all traces of your profile.';
        }
        mainPart();
    }
    function mainPart() {
        var $section = $('<div>', { id: 'WMEBookmarks' });

        // Function to inject custom CSS
        function addCustomStyles() {
            const style = document.createElement('style');
            style.textContent = `
            #addNodeButton { float:left; margin:2px 5px 0 0; cursor:pointer; font-size:16px; width:26px; height:26px; border-radius:50%; text-align:center; background-color:white; color:#000; transition: background-color 0.3s ease, color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease; line-height:26px; box-shadow:0 2px 4px rgba(0,0,0,0.2); }
            #addNodeButton:hover { background-color:#09f; color:white; transform:scale(1.1); box-shadow:0 4px 8px rgba(0,0,0,0.3); }

            .toolbarBKM { width:100%; margin-top:10px; }
            .toolbarBKM-list { display:flex; justify-content:flex-start; align-items:center; padding:0; border-bottom:1px solid #ddd; padding-bottom:8px; }
            .toolbarBKM-list li { display:flex; align-items:center; justify-content:center; font-size:20px; margin:5px; cursor:pointer; transition: color 0.2s ease; }
            .toolbarBKM-list li span { font-size:20px; color:#bbb; transition: color 0.2s ease; }
            .toolbarBKM-list li.active span { color: #0099ff; }

            .toolbarBKM-content > div { display:none; }
            .toolbarBKM-content > div.active { display:block; }
            #divContent, #divShareContent, #divHistoContent, .divContainer { position:relative; background:#f2f4f7; border-radius:6px; box-shadow:0 0 0 1px #d5d7db; padding:5px; min-height:30px; overflow-y:auto; }
            #selectCountry, #BKMSearch, #bkmSortMode { border:0; width:90%; margin:5px 3px 0 0; }
            #divSelectCountry span { cursor:pointer; }
            #BKMSearch { display:none; }
            #sortAZ, #sortZA { margin:5px; height:20px; font-size:10px; }

            .bkm-list-container { margin:1px auto; }
            .bkm-list { list-style:none; padding:0; margin:0; }
            .bkm-item { position:relative; display:flex; flex-direction:column; align-items:flex-start; padding:3px 8px; min-height:45px; border-radius:8px; margin-bottom:5px; cursor:pointer; background-color:rgb(255, 255, 255); transition: background-color 0.3s ease; box-shadow:0 4px 12px rgba(0,0,0,0.08); }
            .bkm-item:hover { background-color:#e5f6ff !important; }
            .bkm-item.bkm-selected { background-color:#e8f2ff; }
            .bkm-name { font-weight:500; font-size:13px; }
            .bkm-comment { font-size:10px; color:#0099ff; cursor:text; }

            .bkm-actions { position:absolute; top:8px; right:2px; display:flex; background-color:#ffffff; }
            .bkm-item .bkm-actions button { display:none; }
            .bkm-item:hover .bkm-actions button { display:inline-block; }
            .bkm-item:not(:hover) .bkm-actions .bkm-reminder-icon, .bkm-item:not(:hover) .bkm-actions .bkm-share-icon { display:inline-block; }
            .bkm-item:hover .bkm-actions .bkm-reminder-icon, .bkm-item:hover .bkm-actions .bkm-share-icon { display:none; }

            .bkm-btn { background:none; border:0; cursor:pointer; padding:4px 6px; border-radius:6px; transition: background-color 0.2s ease, color 0.2s ease; }
            .bkm-btn:hover { background:#0585dc; color:white; }

            .bkm-edit-form-container { background:#f2f4f7; border-radius:12px; box-shadow:0 0 0 1px #d5d7db; padding:16px 16px 5px; margin:0 auto 10px; transition: all 0.2s ease; }
            .bkm-edit-form-container label { display:block; margin-bottom:8px; font-size:12px; color:#111; }
            .bkm-edit-form-container input[type="text"], .bkm-edit-form-container input[type="datetime-local"] , .bkm-edit-form-container textarea { padding:6px 8px; border-radius:6px; border:1px solid #e6e9ee; font-size:12px; width:100%; box-sizing:border-box; margin-top:4px; transition: border-color 0.2s ease, background-color 0.2s ease; }
            .bkm-edit-form-container button { border-radius:100px; font-size:15px; height:var(--wz-button-height, var(--space-button-medium, 40px)); padding:0 calc(var(--space-always-xxs, 4px) + var(--space-always-m, 16px)); background-color:var(--wz-button-background-color, var(--primary, #0099ff)); color:var(--on_primary, #ffffff); border:0; font-size:13px; transition: background-color 0.2s ease, color 0.2s ease; }
            .bkm-edit-form-container button:hover { background:#095cb5; }

            .bkm-color-swatch { width:24px; height:24px; display:inline-block; cursor:pointer; border:1px solid #ccc; margin-right:4px; border-radius:4px; transition: border-color 0.2s ease; }
            .bkm-color-swatch.selected { border:2px solid #0b74de; }
            .bkm-color-swatch:hover { border-color:#0b74de; }

            .subtitle-bkm {padding:10px; margin-bottom:10px; letter-spacing:var(--wz-letter-spacing, 1.5px); font-weight:var(--wz-font-weight, 500); font-size:12px; color:var(--content_p1, #3c4043); border-bottom:1px solid #ddd;}
            .subtitle-bkm .fa { font-size: 16px; margin-right:5px; color:var(--leading_icon, #90959c); }
            .subtitle-bkm span { text-transform:uppercase; position:relative; top:-2px; }

            #iconSynchro { margin-left:10px; cursor:default; }
            .synchButton { margin:10px; padding:5px; background-color:#09f; transition:background-color 0.3s ease; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); border-radius:8px; }
            .synchButton span { font-size:20px; }
            .synchButton:hover { background-color:#095CB5; transform:scale(1.1); box-shadow:0 4px 8px rgba(0,0,0,0.3); }
            `;
            document.head.appendChild(style);
        }

        // Append the HTML content to the section
        var settings = JSON.parse(localStorage.WMEBookmarksSettings);
        const separator = '<hr style="margin:0px 0 15px; border-top:1px solid #aaa;" />';
        $section.append(`
        <div style="float:left;margin-left:5px;"><b><a href="https://greasyfork.org/scripts/4515-wme-bookmarks" target="_blank"><u>WME Bookmarks</u></a></b> v${GM_info.script.version}</div>
        <div style="clear:both;"></div>
        <div class="toolbarBKM">
            <ul class="toolbarBKM-list">
                <li id="iconBookmarks"><span class="fa fa-star" title="${lang[0]}"></span></li>
                <li id="iconShare"><span class="fa fa-share-alt-square" title="${lang[1]}"></span></li>
                <li id="iconHisto"><span class="fa fa-history" title="${lang[3]}"></span></li>
                <li id="iconCopy"><span class="fa fa-copy" title="${lang[7]}"></span></li>
                <li id="iconBackup"><span class="fa fa-cube" title="${lang[4]}"></span></li>
                <li id="iconSettings"><span class="fa fa-sliders" title="${lang[18]}"></span></li>
                <li id="iconServer"><span class="fa fa-sitemap" title="${lang[5]}"></span></li>
                <li id="iconSynchLive" style="pointer-events:none; margin-left:auto; display:none;"><span class="fa fa-cloud" style="color:#09f;"></span></li>
            </ul>
            <div class="toolbarBKM-content">
                <div id="divBookmarks" style="display:none;">
                    <div id="divSelectCountry" class="bkm-edit-form-container">
                        <select type="text" name="Country" id="selectCountry"></select>
                        <input type="text" name="Country" id="BKMSearch" title="${lang[21]}">
                        <span id="goToSearch" title="${lang[19]}">🔍</span><span id="goToSelect" style="display:none;" title="${lang[20]}">📜</span>
                        <div id="sortOptions" style="padding-top:10px; text-align:center;"><button id="sortAZ" type="button">A-Z</button><button id="sortZA" type="button">Z-A</button></div>

                    </div>
                    <div id="BKMedit"></div>
                    <div id="divContent" style="max-height:60vh;"></div>
                </div>

                <div id="divShare" style="display:none;">
                    <div class="subtitle-bkm"><div class="fa fa-share-alt-square"></div><span>${lang[1]}</span></div>
                    <div id="divShareContent"></div>
                </div>

                <div id="divHisto" style="display:none;">
                    <div class="subtitle-bkm"><div class="fa fa-history"></div><span>${lang[3]}</span></div>
                    <div id="divHistoContent"></div>
                    <div class="bkm-edit-form-container" id="histoButtonDiv"><button id="razButton3" type="button" style="margin-top:5px;">${tset[7]}</button></div>
                </div>

                <div id="divCopy" style="display:none;"><div id="divCopyContent"></div></div>
                <div id="divBackup" style="display:none;">
                    <div class="subtitle-bkm"><div class="fa fa-cube"></div><span>${lang[4]}</span></div>
                     <div class="bkm-edit-form-container">
                        <b>JSON</b> <textarea id="divBackupJSON" style="width:100%; min-height:200px;"></textarea>
                        <div style="text-align:center;">
                            <wz-button class="button--_PLJB" id="tmnBackupAdd" disabled="" color="primary" size="md" type="button" name="" value=""><template shadowrootmode="open"><button class="wz-button primary md" disabled="" type="button" name="" value=""><span class="color-layer"></span><slot name="left-icon"></slot><span class="button-text"><slot></slot></span><slot name="right-icon"></slot></button></template>${tset[9]}</wz-button>
                            <wz-button class="button--_PLJB" id="tmnBackupNew" disabled="" color="primary" size="md" type="button" name="" value=""><template shadowrootmode="open"><button class="wz-button primary md" disabled="" type="button" name="" value=""><span class="color-layer"></span><slot name="left-icon"></slot><span class="button-text"><slot></slot></span><slot name="right-icon"></slot></button></template>${tset[8]}</wz-button>
                            <img id="spinnerBackup" src="https://cdn.pixabay.com/animation/2023/03/20/02/45/02-45-27-186_256.gif" style="height:26px;display:none;"/>
                            <img id="validBackup" src="https://upload.wikimedia.org/wikipedia/commons/5/57/Check_mark_%28blue%29.svg" style="height:20px;display:none;"/>
                        </div>
                    </div>
                    <p style="margin:25px 0 0;"><span class="fa fa-info-circle" style="padding:0;color:#36c;"></span><span style="font-size:12px;">${text1}</span></p>
                </div>

                <div id="divSettings" style="display:none;">
                    <div class="subtitle-bkm"><div class="fa fa-sliders"></div><span>${lang[18]}</span></div>
                    <div class="bkm-edit-form-container">
                        <wz-label html-for=""><template shadowrootmode="open"><label for=""><wz-subhead5><template shadowrootmode="open"><slot></slot></template><slot></slot></wz-subhead5></label></template>${lang[0]}</wz-label>
                        <wz-toggle-switch name="chkZoom" id="chkZoom" checked="${settings.zoom ? 'checked' : 'false'}" value="" style="margin-bottom: 15px;">${tset[0]}
                            <input type="checkbox" name="chkZoom" value="" style="display: none; visibility: hidden;">
                        </wz-toggle-switch><br style="margin:10px;"/>
                        <wz-toggle-switch name="chkLayers" id="chkLayers" checked="${settings.layers ? 'checked' : 'false'}" value="" style="margin-bottom: 15px;">${tset[1]}
                            <input type="checkbox" name="chkLayers" value="" style="display: none; visibility: hidden;">
                        </wz-toggle-switch><br/>
                        <wz-toggle-switch name="chkLastClic" id="chkLastClic" checked="${settings.chkLastClic ? 'checked' : 'false'}" value="" style="margin-bottom: 15px;">${tset[10]}
                            <input type="checkbox" name="chkLastClic" value="" style="display: none; visibility: hidden;">
                        </wz-toggle-switch><br/>
                        <wz-toggle-switch name="chkServer" id="chkServer" checked="${settings.server ? 'checked' : 'false'}" value="" style="margin-bottom: 15px;">${tset[2]}
                            <input type="checkbox" name="chkServer" value="" style="display: none; visibility: hidden;">
                        </wz-toggle-switch><br/>
                        ${separator}
                        <wz-label html-for=""><template shadowrootmode="open"><label for=""><wz-subhead5><template shadowrootmode="open"><slot></slot></template><slot></slot></wz-subhead5></label></template>${lang[3]}</wz-label>
                        <wz-toggle-switch name="chkHistoNav" id="chkHistoNav" checked="${settings.histonav ? 'checked' : 'false'}" value="" style="margin-bottom: 15px;">${tset[11]}
                            <input type="checkbox" name="chkHistoNav" value="" style="display: none; visibility: hidden;">
                        </wz-toggle-switch><br/>
                        <wz-toggle-switch name="chkHistoDate" id="chkHistoDate" checked="${settings.histodate ? 'checked' : 'false'}" value="" style="margin-bottom: 15px;">${tset[13]}
                            <input type="checkbox" name="chkHistoDate" value="" style="display: none; visibility: hidden;">
                        </wz-toggle-switch><br/>
                        <div style="margin-bottom:15px;">${tset[12]} <select type="text" name="HistoLimit" id="HistoLimit"><option value="20">20</option><option value="50">50</option><option value="100">100</option><option value="200">200</option></select></div>
                    </div>
                </div>

                <div id="divServer" style="display:none;">
                    <div class="subtitle-bkm"><div class="fa fa-sitemap"></div><span>${lang[5]}</span></div>
                    <div class="toolbarBKM-list" style="margin:15px 0; border:0;">
                        <span>${tset[16]}: </span><span id="iconSynchro" style="font-size:15px;" title="${tset[3]}"></span><span id="pingAttempt" style="font-size:11px; padding-left:5px;"</span>
                    </div>
                    <div class="divContainer">
                        <wz-toggle-switch name="chkSynchro" id="chkSynchro" checked="${settings.synchro ? 'checked' : 'false'}" class="alert-settings-visibility-toggle" tabindex="0" value="">${tset[3]}
                        <input type="checkbox" name="chkSynchro" value="" style="display: none; visibility: hidden;">
                        </wz-toggle-switch>
                        <center><table style="text-align:center;margin-top:15px; border:0;">
                            <tr>
                                <td style="font-size:40px;"><span id="bkmServer">📟</span></td>
                                <td style="width:40px; height:40px;">
                                <img id="transmit" style="width:30px;" src="https://waze-france.fr/script/crossing_circles.gif" title="Connected" />
                                <span id="notransmit" style="width:30px; display:none;" title="Not connected">❌</span>
                                <span id="noStable" style="width:30px;display:none;" title="Not stable">✖️</span>
                                <span id="noSynch" style="width:30px;display:none;" title="Not synch">➰</span></td>
                                <td style="font-size:40px;">💻</td>
                            </tr>
                            <tr style="height:30px;"><td style="font-size:10px;">Server</td><td></td><td style="font-size:10px;">Local</td></tr>
                            <tr>
                                <td><div class="bkm-edit-form-container" style="box-shadow:none; text-align:center; padding:0;"><button id="razButton2" type="button" style="margin:0 10px;" title="${tset[5]}">Clear</button></div></td><td></td>
                                <td><div class="bkm-edit-form-container" style="box-shadow:none; text-align:center; padding:0;"><button id="razButton1" type="button" style="margin:0 10px;" title="${tset[4]}">Clear</button></div></td>
                            </tr>
                            <tr class="synchOptions" style="display:none;"><td colspan="3" style="height:50px;">➰ ${tset[14]}</td></tr>
                            <tr class="synchDone" style="display:none;"><td colspan="3" style="height:50px;">✅ ${tset[15]}</td></tr>
                            <tr class="synchOptions" style="display:none;">
                                <td><div id="serv2local" class="synchButton" title="Server to local"><span>📟</span><br>⏬<br><span>💻</span></div><img id="spinnerS2C" src="https://cdn.pixabay.com/animation/2023/03/20/02/45/02-45-27-186_256.gif" style="height:26px;display:none;"/></td><td></td>
                                <td><div id="local2serv" class="synchButton" title="Local to server"><span>💻</span><br>⏬<br><span>📟</span></div><img id="spinnerC2S" src="https://cdn.pixabay.com/animation/2023/03/20/02/45/02-45-27-186_256.gif" style="height:26px;display:none;"/></td>
                            </tr>
                        </table></center>
                    </div>
                    <p style="margin:25px 0 0;"><span class="fa fa-info-circle" style="padding:0;color:#36c;"></span><span style="font-size:12px;">${text3}</span></p>
                </div>
            </div>
        </div>`);
        addCustomStyles();

        if (!settings.histolength || settings.histolength === "") {
            settings.histolength = 100;
            localStorage.setItem('WMEBookmarksSettings', JSON.stringify(settings));
        }

        // Register the script tab with the sidebar
        wmeSDK.Sidebar.registerScriptTab()
            .then(({ tabLabel, tabPane }) => {
            // Set the tab label and title
            tabLabel.textContent = '⭐';
            tabLabel.title = GM_info.script.name;

            // Append the section to the tab pane
            tabPane.appendChild($section.get(0));

            if ($('#chkSynchro').prop('checked') === false) {
                $('#iconShare').hide();
                $('#transmit').hide();
                $('#notransmit').show();
                $('#bkmServer').css("opacity","0.3");
                $('#shareButton').animate({ width: '0' }, 250);
            }
            if ($('#chkCopyPaste').prop('checked') === false) {
                $('#iconCopy').hide();
            }

            $(document).on('click', '.toolbarBKM-list li', function() {
                const $clickedLi = $(this);
                const iconId = $clickedLi.attr('id');
                const targetDiv = '#div' + iconId.replace('icon', '');
                const icons = ['#iconBookmarks', '#iconShare', '#iconHisto', '#iconCopy', '#iconBackup', '#iconSettings', '#iconServer'];
                const divs = ['#divBookmarks', '#divShare', '#divHisto', '#divCopy', '#divBackup', '#divSettings', '#divServer'];
                const iconActions = {
                    '#iconBookmarks': () => {
                        checkData = true;
                        checkDatas();
                    },
                    '#iconShare': () => loadShareData(),
                    '#iconCopy': () => initCopyFeature(),
                    '#iconBackup': () => BKMloadBackup(),
                    '#iconServer': () => checkDatas()
                };
                divs.forEach((id, i) => {
                    if (id === targetDiv) {
                        $(id).stop(true, true).slideDown(200);
                    } else {
                        $(id).stop(true, true).slideUp(200);
                    }
                });
                $('.toolbarBKM-list li').removeClass('active');
                $clickedLi.addClass('active')

                const clickedIcon = `#${iconId}`;
                if (typeof iconActions[clickedIcon] === 'function') { iconActions[clickedIcon](); }
            });
            $('#divBookmarks').show();
            $('#iconBookmarks').addClass('active');
            $('#selectCountry').on('change', function() {
                giveResults("");
            });
            $('#goToSearch').click(function() {
                $(this).hide();
                $('#goToSelect').show();
                $('#selectCountry').hide();
                $('#sortOptions').hide();
                $('#BKMSearch').show();
                $('#divSelectCountry').css('padding','16px');
                giveResults("BKMSearch");
            });
            $('#goToSelect').click(function() {
                $(this).hide();
                $('#goToSearch').show();
                $('#BKMSearch').hide();
                $('#selectCountry').show();
                $('#sortOptions').show();
                $('#divSelectCountry').css('padding','16px 16px 5px');
                giveResults("");
            });
            $('#sortAZ').click(function () {
                const answer = window.confirm(tset[6]);
                if (!answer) return;
                normalizeSort("name-asc");
            });
            $('#sortZA').click(function () {
                const answer = window.confirm(tset[6]);
                if (!answer) return;
                normalizeSort("name-desc");
            });
            $('#BKMSearch').on('keyup', function() {
                giveResults("BKMSearch");
            });
            $('#chkSynchro').click(function () {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                if ($('#chkSynchro').prop('checked')) {
                    a.synchro=true;
                    $('#iconShare').show();
                    $('#transmit').show();
                    $('#notransmit').hide();
                    $('#noStable').hide();
                    $('#noSynch').hide();
                    $('#bkmServer').css("opacity","1");
                    $('#shareButton').animate({ width: '34px' }, 250);
                    pingServer();
                } else {
                    a.synchro=false;
                    $('#iconShare').hide();
                    $('#transmit').hide();
                    $('#notransmit').show();
                    $('#noStable').hide();
                    $('#noSynch').hide();
                    $('.synchDone').hide();
                    $('#bkmServer').css("opacity","0.3");
                    $('#shareButton').animate({ width: '0' }, 250);
                }
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#chkZoom').click(function () {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                ($('#chkZoom').prop('checked') ? a.zoom=true : a.zoom=false)
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#chkLayers').click(function () {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                ($('#chkLayers').prop('checked') ? a.layers=true : a.layers=false)
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#HistoLimit').val(settings.histolength);
            $('#HistoLimit').on('change', function() {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                a.histolength=$('#HistoLimit').val();
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#tmnBackupAdd').click(function () {
                backup('add');
            });
            $('#tmnBackupNew').click(function () {
                backup('new');
            });
            $('#divBackupJSON').on('paste', function() {
                $('#tmnBackupAdd').prop( "disabled", false );
                $('#tmnBackupNew').prop( "disabled", false );
            });
            $('#chkServer').click(function () {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                ($('#chkServer').prop('checked') ? a.server=true : a.server=false)
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#chkCopyPaste').click(function () {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                if ($('#chkCopyPaste').prop('checked')) {
                    a.lcopy=true;
                    $('#iconCopy').fadeIn(400);
                } else {
                    a.lcopy=false;
                    $('#iconCopy').fadeOut(400);
                }
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#razButton1').click(function () {
                var answer = window.confirm(tset[4] + ' ?');
                if (answer) {
                    localStorage.setItem('WMEBookmarks', '[]');
                    BKMtableCountries();
                    select(countries, 'selectCountry');
                    giveResults("");
                    $('#iconBookmarks').click();
                }
            });
            $('#razButton2').click(function () {
                var answer = window.confirm(tset[5] + ' ?');
                if (answer) {
                    initBookmarks();
                    checkDatas();
                }
            });
            $('#razButton3').click(function () {
                var answer = window.confirm(tset[7] + ' ?');
                if (answer) {
                    localStorage.setItem('WMEHistoric', '[]');
                    $('#bkmHisto').empty();
                    $('#histoButtonDiv').hide();
                }
            });
            $('#chkHistoNav').click(function () {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                if ($('#chkHistoNav').prop('checked')) {
                    a.histonav=true;
                    $('#iconHisto').fadeIn(400);
                } else {
                    a.histonav=false;
                    $('#iconHisto').fadeOut(400);
                }
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#chkHistoDate').click(function () {
                var a=JSON.parse(localStorage.WMEBookmarksSettings);
                if ($('#chkHistoDate').prop('checked')) {
                    a.histodate=true;
                } else {
                    a.histodate=false;
                }
                localStorage.setItem('WMEBookmarksSettings', JSON.stringify(a));
            });
            $('#serv2local').click(function () {
                getBookmarks('serv2local');
            });
            $('#local2serv').click(function () {
                $('#local2serv').hide();
                $('#spinnerC2S').show();
                BKMpostBookmarks();
                setTimeout(function() {
                    $('#spinnerC2S').hide();
                    $('#local2serv').show();
                    $('#iconServer').click();
                    setTimeout(function() { $('#iconBookmarks').click(); }, 1500);
                }, 1500);
            });

            $('#iconCopy').hide();
            const auto = document.querySelector('#search-autocomplete');
            const input = auto.shadowRoot
            .querySelector('wz-text-input')
            .shadowRoot
            .querySelector('input');
            input.addEventListener('paste', (e) => {
                const pasted = e.clipboardData.getData('text');
                let lat, lon, zoom, objKeyRaw, layers, objKey, objId;
                if (/google/i.test(pasted)) {
                    const match = pasted.match(/@(-?\d+\.\d+),(-?\d+\.\d+),([\d.]+)z/);
                    if (!match) return null;
                    lat = parseFloat(match[1]);
                    lon = parseFloat(match[2]);
                    zoom = parseInt(match[3]);
                } else if (/waze/i.test(pasted)) {
                    const params = new URL(pasted).searchParams;
                    if (!params.has("lat") || !params.has("lon") || !params.has("zoomLevel")) return null;
                    lat = parseFloat(params.get("lat"));
                    lon = parseFloat(params.get("lon"));
                    zoom = parseFloat(params.get("zoomLevel"));
                    layers = parseFloat(params.get("s"));
                    objKeyRaw = ["venues", "segments", "nodes"].find(k => params.has(k));
                    objKey = objKeyRaw ? objKeyRaw : '';
                    objId = objKeyRaw ? params.get(objKeyRaw).toString() : '';
                } else {
                    return null;
                }
                BKMjump(`${lon}*${lat}`,zoom, layers, objKey, objId);
            });

            initBookmarksLayer();
            checkDatas();
        }).catch((error) => {
            console.error(`${scriptName}: Error registering the script tab: `, error);
        });

        const $containerBKM = $('<div></div>')
        .addClass('bkm-list-container')
        .html(`<ul class="bkm-list" id="bkmList"></ul>`);

        const $containerShare = $('<div></div>')
        .addClass('bkm-list-container')
        .html(`<ul class="bkm-list" id="bkmShare"></ul>`);

        const $containerHisto = $('<div></div>')
        .addClass('bkm-list-container')
        .html(`<ul class="bkm-list" id="bkmHisto"></ul>`);

        var waitFordivContent = setInterval(function() {
            if ($('#divContent').length) {
                BKMtableCountries();
                $('#divContent').append($containerBKM);
                $('#divShareContent').append($containerShare);
                $('#divHistoContent').append($containerHisto);

                const $list = $('#bkmList');
                const $listSh = $('#divShareContent');
                const $listHi = $('#divHistoContent');
                $list.empty();
                var waitFordivCountry = setInterval(function() {
                    if ($('#selectCountry').length) {
                        giveResults("");
                        clearInterval(waitFordivCountry);
                    }
                }, 200);

                // --- Interactions ---
                $list.on('click', '.bkm-item', function(e){
                    if($(e.target).closest('.bkm-actions').length || $(e.target).is('input')) return;
                    const $li = $(this);
                    BKMjump($li.data('lonlat'),$li.data('zoom'),$li.data('layers'),$li.data('type_obj'),$li.data('id_obj').toString());
                    $('#bkmList .bkm-item').css('border', '');
                    if ($('#chkLastClic').prop('checked')) { $(this).css('border', '1px solid #0099ff'); }
                });
                $list.on('click', '.bkm-deleteBtn', function(e){
                    e.stopPropagation();
                    const $li = $(this).closest('.bkm-item');
                    const $bkmName = $li.find('.bkm-name').text();

                    if (confirm(lang[11] + ' « ' + $bkmName + ' » ?')) {
                        $li.remove();

                        const id = $li.data('lonlat');
                        if (!id) return;

                        const [lon, lat] = id.split('*');
                        let jsonData = JSON.parse(localStorage.WMEBookmarks);
                        jsonData = jsonData.filter(item => !(item.perma?.lon === lon && item.perma?.lat === lat));
                        localStorage.WMEBookmarks = JSON.stringify(jsonData);
                        normalizeSort();

                        console.log(`${scriptName}: Deleted: `, $bkmName);
                        if ($('#chkSynchro').prop('checked')) {
                            BKMupdateBookmarks('DELETE', BKMusername, { perma: { lon, lat } });
                        }
                    }
                });
                $list.on('click', '.bkm-relocBtn', function(e){
                    e.stopPropagation();
                    const $li = $(this).closest('.bkm-item');
                    if(confirm(lang[12] + ' « ' + $li.find('.bkm-name').text()+' » ?')) {
                        const selectObjet = wmeSDK.Editing.getSelection();
                        console.log("Objet", selectObjet ?? "pas de sélection");
                        const [lon, lat] = $li.data('lonlat').split('*');
                        const jsonData=JSON.parse(localStorage.WMEBookmarks);
                        const bkmToChange = jsonData.find(item => item.perma.lon === lon && item.perma.lat === lat);
                        console.log('bkmToChange', bkmToChange);
                        if (bkmToChange) {
                            link={}; getLink(wmeSDK.Map.getPermalink({ includeLayers: true }));
                            bkmToChange.perma.lon = link.lon;
                            bkmToChange.perma.lat = link.lat;
                            bkmToChange.perma.zoom = link.zoom;
                            bkmToChange.perma.layers = link.layers;
                            if (selectObjet) {
                                bkmToChange.perma.type_obj = selectObjet.objectType+"s";
                                bkmToChange.perma.id_obj = selectObjet.ids.join(',').toString();
                            } else {
                                bkmToChange.perma.type_obj = "";
                                bkmToChange.perma.id_obj = "";
                            }
                            localStorage.setItem('WMEBookmarks', JSON.stringify(jsonData));
                            bkmToChange.oldPerma = $li.data('lonlat');
                            $li.data('lonlat', link.lon + "*" + link.lat);
                            $li.data('zoom', link.zoom);
                            $li.data('layers', link.layers);
                            console.log(lang[12], $li.find('.bkm-name').text(), bkmToChange);
                            if ($('#chkSynchro').prop('checked')) {
                                BKMupdateBookmarks('RELOCATE',BKMusername, bkmToChange);
                            }
                            giveResults("");
                        }
                    }
                });
                $list.off('click', '.bkm-editBtn');
                $list.on('click', '.bkm-editBtn', function(e){
                    e.stopPropagation();
                    const $li = $(this).closest('.bkm-item');
                    openEditForm($li);
                    $('#divBookmarks').animate({ scrollTop: $('#BKMedit').position().top }, 400);
                });
                $listSh.on('click', '.bkm-item', function(e){
                    const $li = $(this);
                    const coord = $li.data('lonlat');
                    const [lon, lat] = coord.split('*');
                    wmeSDK.Map.setMapCenter({ lonLat: { lon: parseFloat(lon), lat: parseFloat(lat) } });
                    wmeSDK.Map.setZoomLevel({ zoomLevel: $li.data('zoom') });
                    applyLayers($li.data('layers'));
                });
                $listHi.on('click', '.bkm-item', function(e){
                    const $li = $(this);
                    const coord = $li.data('lonlat');
                    const [lon, lat] = coord.split('*');
                    wmeSDK.Map.setMapCenter({ lonLat: { lon: parseFloat(lon), lat: parseFloat(lat) } });
                    wmeSDK.Map.setZoomLevel({ zoomLevel: $li.data('zoom') });
                });
                const jsonData = JSON.parse(localStorage.WMEHistoric);
                $('#bkmHisto').empty();
                jsonData.slice().reverse().forEach(item => {
                    $('#bkmHisto').append(makeLiShort(item,"Histo"));
                });
                clearInterval(waitFordivContent);
            }

            // Button on the map
            const $addNodeButton = $('<div></div>')
            .attr('id', 'addNodeButton')
            .addClass('fa fa-thumb-tack')
            .on('click', BKMinsertPermalink);
            const $location = $('.topbar').eq(0).find('.location-info-region').eq(0);
            if ($location.length) {
                $('.topbar').eq(0).css('padding', '0 10px');
                $location.before($addNodeButton);
            }
            wmeSDK.Events.on({ eventName: "wme-map-data-loaded", eventHandler: BKMtableCountries });
            makeListSortable();
            getBookmarks('');
            checkReminder();
            window.setInterval(checkReminder, 60000);
        }, 200);
    }

    function BKMinsertPermalink() { // Action when you add a new permalink (DB)
        const jsonData = JSON.parse(localStorage.WMEBookmarks);
        link={}; getLink(wmeSDK.Map.getPermalink({ includeLayers: true }));
        const result = jsonData.filter(item => item.perma.lon === link.lon && item.perma.lat === link.lat);
        if (result.length === 1) return;

        $('ul.nav-tabs li a span:contains("⭐")').parent('a').click();
        const topCountry = wmeSDK.DataModel.Countries.getTopCountry();
        const BKMcountryActive = topCountry ? topCountry.name : 'Unknown';
        const countLi = jsonData.filter(item => item.country === BKMcountryActive).length;

        //JSON for new permalink
        const encodedBookmark = {
            country: BKMcountryActive,
            name: getName(),
            perma: link,
            comm: '',
            share: '',
            reminder: '',
            color: '',
            sort: '-1',
        };

        jsonData.push(encodedBookmark);
        localStorage.WMEBookmarks = JSON.stringify(jsonData);
        normalizeSort;

        if ($('#chkSynchro').prop('checked') === true) {
            if (debug) { console.log('WME Bookmarks: ADD', BKMusername, encodedBookmark); }
            BKMupdateBookmarks('ADD', BKMusername, encodedBookmark);
        }

        const $li = makeLi(encodedBookmark);
        $('#bkmList').append($li);
        openEditForm($li);
        W.selectionManager.unselectAll();
        $('#divBookmarks').animate({ scrollTop: $('#BKMedit').position().top }, 400);
        giveResults("");
        BKMpostBookmarks();
    }
    function getBookmarks(source) {
        $("#iconSynchLive").fadeIn(100).delay(600).fadeOut(100);
        $("#addNodeButton").fadeOut(100).delay(1000).fadeIn(100);
        const listFav = localStorage.WMEBookmarks;
        const url = 'https://waze-france.fr/script/bkm.php?getbookmarks2=' + BKMusername;

        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            headers:{"Content-Type": "application/x-www-form-urlencoded"},
            onload: function(data) {
                const serverData = data.responseText;
                if (isJsonString(serverData)) {
                    const serverJSON = normalizeJSON(JSON.parse(serverData));
                    const localJSON = normalizeJSON(JSON.parse(listFav));
                    localJSON.forEach(function(item) {
                        if (!item.reminder || item.reminder === null) { item.reminder = ""; }
                        if (!item.sort || item.sort === null) { item.sort = ""; }
                        if (!item.color || item.color === null) { item.color = ""; }
                        if (!item.perma.type_obj || item.perma.type_obj === null) { item.perma.type_obj = ""; }
                        if (!item.perma.id_obj || item.perma.id_obj === null) { item.perma.id_obj = ""; }
                    });
                    if (debug) {
                        console.log('WME Bookmarks: From BKM Server: ', serverJSON);
                        console.log('WME Bookmarks: From BKM local : ', localJSON);
                    }
                    console.log('Diff: ', diffJSON(serverJSON, localJSON), Object.keys(diffJSON(serverJSON, localJSON)).length);
                    if (source === 'serv2local') {
                        $('#serv2local').hide();
                        $('#spinnerS2C').show();
                        localStorage.setItem('WMEBookmarks', serverData);
                        giveResults("");
                        setTimeout(function() {
                            $('#spinnerS2C').hide();
                            $('#serv2local').show();
                            setTimeout(function() { $('#iconBookmarks').click(); }, 1500);
                        }, 1000);
                    } else {
                        const syncEnabled = $('#chkSynchro').prop('checked') === true;
                        if (Object.keys(diffJSON(serverJSON, localJSON)).length != 0 && syncEnabled) {
                            noSynch = 1;
                            $('.synchOptions').show();
                            $('#transmit').hide();
                            $('#notransmit').hide();
                            $('#noStable').hide();
                            $('#noSynch').show();
                            $('.synchDone').hide();
                        } else {
                            $('.synchOptions').hide();
                            $('.synchDone').show();
                            if (debug) { console.log('WME Bookmarks: Bookmarks Sync !'); console.log(localJSON); }
                        }
                        if (serverJSON.length === 0 && localJSON.length === 0) {
                            $('#transmit').hide();
                            $('#notransmit').show();
                            $('#noSynch').hide();
                        }
                    }
                }
            }
        });
    }
    function initBookmarks() {
        if (debug) { console.log('WME Bookmarks: Send: ' + BKMusername + ' to init'); }
        var url = 'https://waze-france.fr/script/bkm.php?initbookmarks=' + BKMusername

        GM.xmlHttpRequest({
            method: 'POST',
            url: url,
            headers:{ "Content-Type": "application/x-www-form-urlencoded" },
            onload: function(data) {
                if (debug) { console.log('WME Bookmarks: Server Response: ', data.responseText); }
                if (data.responseText === 'Check') { getBookmarks(); }
            }
        });
    }
    function BKMpostBookmarks() {
        $("#iconSynchLive").fadeIn(100).delay(600).fadeOut(100);
        $("#addNodeButton").fadeOut(100).delay(1000).fadeIn(100);
        const a = JSON.parse(localStorage.WMEBookmarks);
        const toSave=[];
        $.each(a, function (p, item) {
            if (!a.hasOwnProperty(p)) return;
            (item.sort === "" ? item.sort = "0": item.sort)
            const encodedBookmark = {
                country: encodeURIComponent(item.country),
                name: encodeURIComponent(item.name),
                perma: item.perma,
                comm: encodeURIComponent(item.comm),
                share: item.share,
                reminder: item.reminder,
                color: item.color,
                sort: item.sort
            };
            toSave.push(encodedBookmark);
        });

        if (debug) { console.log('WME Bookmarks: Send Data: ', BKMusername, toSave); }
        const url = "https://waze-france.fr/script/bkm.php";
        const datas = "nickname=" + BKMusername + "&postbookmarks2=" + JSON.stringify(toSave);

        GM.xmlHttpRequest({
            method: 'POST',
            url: url,
            data: datas,
            headers:{ "Content-Type": "application/x-www-form-urlencoded" },
            onload: function(data) {
                if (debug) { console.log('WME Bookmarks: Server Response: ', data.responseText); }
                if (data.responseText === 'Check') {
                    $("#iconSynchLive").fadeIn(100).delay(600).fadeOut(100);
                    $("#addNodeButton").fadeOut(100).delay(1000).fadeIn(100);
                    getBookmarks('');
                }
            },
            onerror: function (err) {
                console.error('Erreur GM.xmlHttpRequest:', err);
            }
        });
    }
    function BKMupdateBookmarks(action, owner, data) {
        if (debug) { console.log('WME Bookmarks: BKMupdateBookmarks ',action, owner, data); }
        $("#iconSynchLive").fadeIn(100).delay(600).fadeOut(100);
        $("#addNodeButton").fadeOut(100).delay(1000).fadeIn(100);
        var bNew = {};
        bNew.action=action;
        bNew.owner=owner;
        bNew.data=data;
        var url = "https://waze-france.fr/script/bkm.php"
        var datas = "addbookmark2=" + JSON.stringify(bNew)
        var method = 'POST'

        GM.xmlHttpRequest({
            method: method,
            url: url,
            data: datas,
            headers:{ "Content-Type": "application/x-www-form-urlencoded" },
            onload: function(data) {
                if (debug) { console.log('WME Bookmarks: Server Response: ', data.responseText); }
                if (data.responseText != 'Check') {
                    giveResults('');
                }
            }
        });
    }

    // ***************************
    // ** ACTIONS WITH BOOKMARK **
    // ****************************

    function BKMjump(id,zoom,layers,objectType,objectId) { // Action when you click a link
        //console.log(id,zoom,layers,layers.length,isNaN(layers),objectType,objectId);
        const [lon, lat] = id.split('*');
        const jsonData=JSON.parse(localStorage.WMEBookmarks);
        const result = jsonData.filter(item => item.perma?.lon === lon && item.perma?.lat === lat);

        wmeSDK.Map.setMapCenter({ lonLat: { lon: parseFloat(lon), lat: parseFloat(lat) } });
        if ($('#chkZoom').prop('checked')) {
            if (zoom != "") {
                wmeSDK.Map.setZoomLevel({ zoomLevel: zoom });
            }
            else if (result[0].perma.zoom) {
                wmeSDK.Map.setZoomLevel({ zoomLevel: result[0].perma.zoom });
            }
        }
        if ($('#chkLayers').prop('checked') && layers && layers.length != 0 && !isNaN(layers))  {
            applyLayers(layers);
        }
        setTimeout(function() {
            if (objectType && objectId != null) {
                let objectIds = [];
                switch (objectType) {
                    case 'segments':
                        objectIds = objectId
                            .split(',')
                            .map(id => Number(id.trim()))
                            .filter(id => !isNaN(id));
                        break;

                    case 'nodes':
                        objectIds = [Number(objectId)];
                        break;
                    case 'venues':
                        objectIds = [objectId];
                        break;
                    default:
                        console.warn("Unsupported objectType:", objectType);
                        return;
                }
                let objects = [];
                switch (objectType) { // mapComment, city, restrictedDrivingArea, permanentHazard, venue, node, segmentSuggestion, bigJunction, segment
                    case 'segments':
                        objectType = 'segment';
                        objects = objectIds.map(id => wmeSDK.DataModel.Segments.getById({ segmentId: id })).filter(Boolean);
                        break;
                    case 'nodes':
                        objectType = 'node';
                        objects = objectIds.map(id => wmeSDK.DataModel.Nodes.getById({ nodeId: id })).filter(Boolean);
                        break;
                    case 'venues':
                        objectType = 'venue';
                        objects = objectIds.map(id => wmeSDK.DataModel.Venues.getById({ venueId: id })).filter(Boolean);
                        break;
                }
                if (objects.length === 0) {
                    console.warn("No object found for the given IDs");
                    return;
                }
                wmeSDK.Editing.setSelection({
                    selection: {
                        objectType: objectType,
                        ids: objectIds
                    }
                });
                console.log(`Selected ${objectType}(s) with IDs:`, objectIds);
            }
        }, 1000);
        var waitForCountry = setInterval(function() { // test if newcountry
            if (wmeSDK.DataModel.Countries.getTopCountry()) {
                BKMtableCountries();
                clearInterval(waitForCountry);
            }
        }, 200);
    }
    function loadShareData() {
        var listFav = localStorage.WMEBookmarksShared;
        var url = 'https://waze-france.fr/script/bkm.php?getshared2=' + BKMusername;

        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            onload: function(response) {
                try {
                    var serverData = JSON.parse(response.responseText);
                    var localData = listFav ? JSON.parse(listFav) : {};

                    var s1 = JSON.stringify(serverData, Object.keys(serverData).sort());
                    var s2 = JSON.stringify(localData, Object.keys(localData).sort());

                    if ((s1 !== s2) && serverData && $('#chkSynchro').prop('checked') === true) {
                        if (debug) console.log('WME Bookmarks: From Share Server: ', serverData);
                        if (debug) console.log('WME Bookmarks: From Share local: ', localData);

                        localStorage.setItem('WMEBookmarksShared', JSON.stringify(serverData));
                        const jsonData = JSON.parse(localStorage.WMEBookmarksShared);
                        $('#divShareContent').empty();
                        jsonData.slice().reverse().forEach(item => {
                            $('#divShareContent').append(makeLiShort(item,"Share"));
                        });
                    } else {
                        const jsonData = JSON.parse(localStorage.WMEBookmarksShared);
                        $('#divShareContent').empty();
                        jsonData.slice().reverse().forEach(item => {
                            $('#divShareContent').append(makeLiShort(item,"Share"));
                        });
                        if (debug) console.log('WME Bookmarks: Shared Sync !');
                    }
                } catch (e) {
                    console.error('Erreur parsing JSON:', e);
                }
            },
            onerror: function(err) {
                console.error('Erreur requête GM.xmlHttpRequest:', err);
            }
        });
    }

    // ***************
    // **  BACKUP   **
    // ***************

    function BKMloadBackup() {
        const data = localStorage.WMEBookmarks;
        if (data) {
            const bookmarks = JSON.parse(data);
            $('#divBackupJSON').val(JSON.stringify(bookmarks));
            $('#divBackupJSON').off('click').on('click', function () {
                $(this).focus().select();
            });
        }
    }
    function backup(action) {
        const jsonContent = $('#divBackupJSON').val().trim();
        const isNew = action === "new";
        const isAdd = action === "add";

        if ((isNew && confirm(lang[22])) ||
            (isAdd && confirm(lang[23]))) {
            $('#spinnerBackup').show();
            setTimeout(function() {
                $('#spinnerBackup').hide();
                $('#validBackup').show();
                setTimeout(function() {
                    $('#validBackup').hide();
                    giveResults("");
                    $('#iconBookmarks').click();
                }, 1000);
            }, 1000);

            if (isNew) localStorage.setItem('WMEBookmarks', '[]');
            if (jsonContent) importJSONtoWMEBookmarks(jsonContent);
        }
    }
    function importJSONtoWMEBookmarks(jsonContent) {
        const json1 = JSON.parse(localStorage.WMEBookmarks);
        const json2 = JSON.parse(jsonContent);

        const normalize = obj =>
        Object.keys(obj)
        .sort()
        .reduce((acc, key) => {
            acc[key] = obj[key];
            return acc;
        }, {});

        const convertItem = item => {
            const newItem = { ...item };

            if (typeof newItem.comm === "string") {
                newItem.comm = newItem.comm.replace(/\n+/g, " ").trim();
            }

            if (newItem.perma) {
                const p = { ...newItem.perma };

                ["segments", "nodes", "venues"].forEach(key => {
                    if (p[key] !== undefined) {
                        p.id_obj = key;
                        p.type_obj = p[key];
                        delete p[key];
                    }
                });

                newItem.perma = p;
            }
            newItem.sort = newItem.sort || "0";

            return newItem;
        };

        const converted2 = json2.map(convertItem);
        const merged = [...json1, ...converted2].filter(
            (item, index, self) =>
            index === self.findIndex(
                t => JSON.stringify(normalize(t)) === JSON.stringify(normalize(item))
            )
        );
        localStorage.setItem("WMEBookmarks", JSON.stringify(merged));
        console.log("Bookmarks fusionnés :", merged);
        BKMpostBookmarks();
    }

    // ********************
    // **  MENU & LAYER  **
    // ********************

    function layerToggled() {
        const isVisible = JSON.parse(localStorage.WMEBookmarksSettings);
        isVisible.showBookmark = !isVisible.showBookmark;
        wmeSDK.Map.setLayerVisibility({ layerName: "__WME_Bookmarks", visibility: isVisible.showBookmark });
        wmeSDK.LayerSwitcher.setLayerCheckboxChecked({ name: 'Bookmarks', isChecked: isVisible.showBookmark });
        localStorage.WMEBookmarksSettings = JSON.stringify(isVisible);
    }
    const bookmarkStyleConfig = {
        styleContext: {
            bookmarkLabel: (context) => {
                return context?.feature?.properties?.name ?? "";
            },
        },
        styleRules: [
            {
                predicate: () => true,
                style: {
                    externalGraphic: "data:image/svg+xml;base64," + btoa(`
                    <svg width="32" height="44" viewBox="0 0 32 44" xmlns="http://www.w3.org/2000/svg">
                        <path d="M16 2 C8.268 2 2 8.268 2 16 C2 28 16 44 16 44 C16 44 30 28 30 16 C30 8.268 23.732 2 16 2Z"
                        fill="#e5f6ff" stroke="#0099ff" stroke-width="4" stroke-linejoin="round"/>
                    </svg>`),
                    graphicWidth: 24,
                    graphicHeight: 32,
                    graphicYOffset: -32,
                    graphicOpacity: 1,
                    label: "${bookmarkLabel}",
                    labelText: "${bookmarkLabel}",
                    labelColor: "white",
                    fontColor: "white",
                    labelOutlineColor: "#0099ff",
                    labelOutlineWidth: "5",
                    fontFamily: "Open Sans, Alef, helvetica, sans-serif, monospace",
                    fontSize: "14",
                    labelYOffset: 14,
                    labelXOffset: 18,
                    labelAlign: "lm"
                },
            },
        ],
    };
    function initBookmarksLayer() {
        const isVisible = JSON.parse(localStorage.WMEBookmarksSettings || '{}');

        try {
            wmeSDK.Map.addLayer({
                layerName: "__WME_Bookmarks",
                styleRules: bookmarkStyleConfig.styleRules,
                styleContext: bookmarkStyleConfig.styleContext,
                zIndexing: true
            });
        } catch (e) {
            console.log("Layer already existing :", e);
        }

        wmeSDK.Map.setLayerVisibility({
            layerName: "__WME_Bookmarks",
            visibility: isVisible.showBookmark ?? true
        });

        wmeSDK.LayerSwitcher.addLayerCheckbox({ name: "Bookmarks" });
        wmeSDK.LayerSwitcher.setLayerCheckboxChecked({
            name: "Bookmarks",
            isChecked: isVisible.showBookmark ?? true
        });
        updateLayer();
    }
    function updateLayer() {
        const bookmarkList = [];
        const data = JSON.parse(localStorage.getItem('WMEBookmarks') || '[]');

        data.forEach(item => {
            if (!item || !item.perma) return;
            bookmarkList.push({
                name: item.name,
                lon: parseFloat(item.perma.lon),
                lat: parseFloat(item.perma.lat)
            });
        });

        if (!bookmarkList.length) return;
        wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: "__WME_Bookmarks" });

        const features = bookmarkList.map((b, index) => ({
            type: "Feature",
            id: `bookmark_${index}`,
            geometry: {
                type: "Point",
                coordinates: [b.lon, b.lat]
            },
            properties: {
                name: b.name
            }
        }));
        wmeSDK.Map.addFeaturesToLayer({
            layerName: "__WME_Bookmarks",
            features: features
        });
        wmeSDK.Events.on({
            eventName: 'wme-layer-checkbox-toggled',
            eventHandler: layerToggled
        });
    }
})();