Greasy Fork is available in English.

KG_Chat_Empowerment

Enhance the chat abilities

// ==UserScript==
// @name         KG_Chat_Empowerment
// @namespace    klavogonki
// @version      0.2
// @description  Enhance the chat abilities
// @author       Patcher
// @match        *://klavogonki.ru/g*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=klavogonki.ru
// @grant        none
// ==/UserScript==

(function () {

  // USERS DEFINITION

  // Your actual nickname to use it as an exclusion for the message beep and voice notifications
  const myNickname = document.querySelector('.userpanel .user-block .user-dropdown .name span').textContent;

  // Function to dynamically append font link to the head
  function appendFontLink(fontFamily, fontWeights) {
    // Check if the font link element with the specified class already exists
    const existingFont = document.querySelector(`.font-${fontFamily.replace(/\s/g, '-')}`);

    // If it doesn't exist, create a new link element and append it to the document head
    if (!existingFont) {
      const fontLink = document.createElement('link');
      fontLink.rel = 'stylesheet';
      fontLink.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/\s/g, '+')}:wght@${fontWeights.join(';')}&display=swap`;
      fontLink.classList.add(`font-${fontFamily.replace(/\s/g, '-')}`);

      // Append the font link element to the document head
      document.head.appendChild(fontLink);
    }
  }

  // Specify the font weights you want to include
  const montserratFontWeights = ['100', '200', '300', '400', '500', '600', '700', '800', '900'];
  const orbitronFontWeights = ['400', '500', '600', '700', '800', '900'];
  const robotoMonoFontWeights = ['100', '200', '300', '400', '500', '600', '700'];

  // Call the function to append Montserrat font link
  appendFontLink('Montserrat', montserratFontWeights);

  // Call the function to append Orbitron font link
  appendFontLink('Orbitron', orbitronFontWeights);

  // Call the function to append Roboto Mono font link
  appendFontLink('Roboto Mono', robotoMonoFontWeights);

  // Define voice speed limits
  const minVoiceSpeed = 0;
  const maxVoiceSpeed = 2.5;

  // Define voice pitch limits
  const minVoicePitch = 0;
  const maxVoicePitch = 2.0;

  // Define default voice speed and pitch
  const defaultVoiceSpeed = 1.5;
  const defaultVoicePitch = 1.0;

  // Retrieve KG_Chat_Empowerment from localStorage or create an object with empty voiceSettings if it doesn't exist
  // This is the main key for the settings
  let KG_Chat_Empowerment = JSON.parse(localStorage.getItem('KG_Chat_Empowerment'));

  // If KG_Chat_Empowerment doesn't exist in localStorage, create it with an empty voiceSettings object
  if (!KG_Chat_Empowerment) {
    KG_Chat_Empowerment = {
      voiceSettings: {
        voiceSpeed: defaultVoiceSpeed, // Set default values for voiceSpeed
        voicePitch: defaultVoicePitch, // Set default values for voicePitch
      },
      messageSettings: {},
    };
    localStorage.setItem('KG_Chat_Empowerment', JSON.stringify(KG_Chat_Empowerment));
  }

  // Define the default voice speed and pitch
  let voiceSpeed = KG_Chat_Empowerment.voiceSettings.voiceSpeed !== null
    ? KG_Chat_Empowerment.voiceSettings.voiceSpeed
    : defaultVoiceSpeed; // Default value if KG_Chat_Empowerment.voiceSettings.voiceSpeed is null

  let voicePitch = KG_Chat_Empowerment.voiceSettings.voicePitch !== null
    ? KG_Chat_Empowerment.voiceSettings.voicePitch
    : defaultVoicePitch; // Default value if KG_Chat_Empowerment.voiceSettings.voicePitch is null

  // Define the users to track and notify with popup and audio
  let usersToTrack = [
    { name: 'Даниэль', gender: 'Male', pronunciation: 'Даниэль' }
  ];

  // Notify if someone addresses me using these aliases (case-insensitive)
  let mentionKeywords = [];

  // Define a list of moderator whose new user nicknames in the chat list should have a shield icon.
  let moderator = [];

  // Define user list of users whose messages should be hidden
  let ignored = [];

  // Define empty array for the toggle settings
  let toggle = [];

  // Check and load settings from localStorage if available and not empty
  const storedUsersToTrack = JSON.parse(localStorage.getItem('usersToTrack')) || [];
  const storedMentionKeywords = JSON.parse(localStorage.getItem('mentionKeywords')) || [];
  const storedModerators = JSON.parse(localStorage.getItem('moderator')) || [];
  const storedIgnored = JSON.parse(localStorage.getItem('ignored')) || [];

  // Replace usersToTrack with stored value if it exists and is not empty
  usersToTrack = storedUsersToTrack?.length ? storedUsersToTrack : usersToTrack;
  // Replace mentionKeywords with stored value if it exists and is not empty
  mentionKeywords = storedMentionKeywords?.length ? storedMentionKeywords : mentionKeywords;
  mentionKeywords.push(myNickname); // Actual nickname
  // Replace moderator with stored value if it exists and is not empty
  moderator = storedModerators?.length ? storedModerators : moderator;
  // Replace ignored with stored value if it exists and is not empty
  ignored = storedIgnored?.length ? storedIgnored : ignored;

  // Key Events: CTRL and ALT

  // Initialize variables to track the state of Ctrl and Alt keys
  let isCtrlKeyPressed = false;
  let isAltKeyPressed = false;

  // Helper function to set key state based on key events
  const setKeyPressed = (key, value) => {
    if (key === 'Control') isCtrlKeyPressed = value;
    if (key === 'Alt') isAltKeyPressed = value;
  };

  // Add event listeners for keydown and keyup events
  document.addEventListener('keydown', (event) => setKeyPressed(event.key, true));
  document.addEventListener('keyup', (event) => setKeyPressed(event.key, false));

  // Add a blur event listener to reset variables when the document loses focus
  document.addEventListener('blur', () => {
    // Check if Ctrl or Alt keys were pressed
    if (isCtrlKeyPressed || isAltKeyPressed) {
      // Log the combination of keys that were true
      console.log(`${isCtrlKeyPressed ? 'Ctrl ' : ''}${isAltKeyPressed ? 'Alt ' : ''}key was true`);
      // Reset key states
      isCtrlKeyPressed = false;
      isAltKeyPressed = false;
    }
  });


  // SOUND NOTIFICATION

  // Function to create the audio context and return a Promise that resolves when the context is ready
  function createAudioContext() {
    const audioContext = new AudioContext();
    return new Promise(resolve => {
      audioContext.onstatechange = function () {
        if (audioContext.state === 'running') {
          resolve(audioContext);
        }
      };
    });
  }

  // Create the audio context and wait for it to be ready
  const audioContextPromise = createAudioContext();

  // List of frequencies to play for "User Left" && "User Entered" && "New Messages"
  const userEnteredFrequencies = [300, 600];
  const userLeftFrequencies = [600, 300];
  const usualMessageFrequencies = [500];
  const mentionMessageFrequencies = [600, 800];

  // Volume of the reader voice
  const voiceVolume = 0.8;
  // Volume of the beep signal
  const beepVolume = 0.2;
  // Duration for each frequency
  const duration = 80;
  // Smooth inception and termination for each note
  const fade = 10;
  // Space between each note to make noticeable pauses
  const delay = 100;

  // Function to play a beep given a list of frequencies
  function playBeep(frequencies, volume) {
    audioContextPromise.then(audioContext => {
      for (let i = 0; i < frequencies.length; i++) {
        const frequency = frequencies[i];
        if (frequency === 0) {
          // Rest note
          setTimeout(() => { }, duration);
        } else {
          // Play note
          const oscillator = audioContext.createOscillator();
          const gain = audioContext.createGain();
          oscillator.connect(gain);
          oscillator.frequency.value = frequency;
          oscillator.type = "sine";

          // Create low pass filter to cut frequencies below 250Hz
          const lowPassFilter = audioContext.createBiquadFilter();
          lowPassFilter.type = 'lowpass';
          lowPassFilter.frequency.value = 250;
          oscillator.connect(lowPassFilter);

          // Create high pass filter to cut frequencies above 16kHz
          const highPassFilter = audioContext.createBiquadFilter();
          highPassFilter.type = 'highpass';
          highPassFilter.frequency.value = 16000;
          lowPassFilter.connect(highPassFilter);

          gain.connect(audioContext.destination);
          gain.gain.setValueAtTime(0, audioContext.currentTime);
          gain.gain.linearRampToValueAtTime(volume, audioContext.currentTime + fade / 1000);
          oscillator.start(audioContext.currentTime + i * delay / 1000);
          oscillator.stop(audioContext.currentTime + (i * delay + duration) / 1000);
          gain.gain.setValueAtTime(volume, audioContext.currentTime + (i * delay + (duration - fade)) / 1000);
          gain.gain.linearRampToValueAtTime(0, audioContext.currentTime + (i * delay + duration) / 1000);
        }
      }
    });
  }

  // Create a promise that will resolve when the list of available voices is populated
  const awaitVoices = new Promise(resolve => {
    // Create a speech synthesis object
    const synth = window.speechSynthesis;
    // Retrieve the list of available voices
    let voices = synth.getVoices();

    // Define the voice names for Pavel and Irina
    const pavelVoiceName = 'Microsoft Pavel - Russian (Russia)';
    const irinaVoiceName = 'Microsoft Irina - Russian (Russia)';

    // Find and store Pavel's voice
    let pavelVoice = voices.find(voice => voice.name === pavelVoiceName);
    // Find and store Irina's voice
    let irinaVoice = voices.find(voice => voice.name === irinaVoiceName);

    // If either voice is not found or the voices list is empty, wait for it to populate
    if (!pavelVoice || !irinaVoice || voices.length === 0) {
      synth.addEventListener('voiceschanged', () => {
        voices = synth.getVoices();
        pavelVoice = voices.find(voice => voice.name === pavelVoiceName);
        irinaVoice = voices.find(voice => voice.name === irinaVoiceName);

        // If both voices are found, continue with the initialization
        if (pavelVoice && irinaVoice) {
          // Define the utterance object as a global variable
          const utterance = new SpeechSynthesisUtterance();
          // Set the "lang" property of the utterance object to 'ru-RU'
          utterance.lang = 'ru-RU';
          // Set the "voice" property of the utterance object to Pavel's voice
          utterance.voice = irinaVoice;
          // Resolve the promise
          resolve({ synth, utterance, voices, pavelVoice, irinaVoice });
        }
      });
    } else {
      // Define the utterance object as a global variable
      const utterance = new SpeechSynthesisUtterance();
      // Set the "lang" property of the utterance object to 'ru-RU'
      utterance.lang = 'ru-RU';
      // Set the "voice" property of the utterance object to (Needed) voice
      utterance.voice = irinaVoice;
      // Resolve the promise
      resolve({ synth, utterance, voices, pavelVoice, irinaVoice });
    }
  });

  // Converts text to speech using the Web Speech API.
  async function textToSpeech(text, voiceSpeed = voiceSpeed) {
    return new Promise(async (resolve) => {
      // Wait for the voices to be loaded asynchronously
      const { synth, utterance, voice } = await awaitVoices;

      const cleanedMessage = text
        .replace(/_/g, '-') // Replace underscores with hyphens
        .replace(/https?:\/\/([a-zA-Z0-9\-\.]+)(\/[^\s]*)?/g, (_, p1) => p1) // Replace URLs with domains
        .split(' ').map(word => word.replace(/\d+/g, '')).filter(Boolean).join(' ').trim(); // Clean digits and format

      // Set utterance properties, such as text to be spoken, rate, volume, pitch, and voice
      Object.assign(utterance, {
        text: cleanedMessage, // Cleaned message to be spoken
        rate: voiceSpeed, // Speed at which the speech will be delivered
        volume: voiceVolume, // Volume level of the speech
        pitch: voicePitch, // Pitch of the speech
        voice: voice // The selected voice for the utterance
      });

      // Speak the utterance using the Web Speech synthesis engine
      synth.speak(utterance);

      // Resolve the promise once the speech finishes
      utterance.onend = resolve; // Trigger resolve to indicate completion
    });
  }

  const verbs = {
    Male: { enter: 'зашёл', leave: 'вышел' },
    Female: { enter: 'зашла', leave: 'вышла' }
  };

  function getUserGender(userName) {
    const user = usersToTrack.find((user) => user.name === userName);
    return user ? user.gender : null;
  }

  // Handles user entering and leaving actions
  function userAction(user, actionType, userGender) {
    const userToTrack = usersToTrack.find(userToTrack => userToTrack.name === user);
    const action = actionType === "enter" ? verbs[userGender].enter : verbs[userGender].leave;
    const frequencies = actionType === "enter" ? userEnteredFrequencies : userLeftFrequencies;
    const message = `${userToTrack.pronunciation} ${action}`;

    playBeep(frequencies, beepVolume);

    setTimeout(() => {
      textToSpeech(message, voiceSpeed);
    }, 300);
  }

  // POPUPS

  // Generate HSL color with optional parameters for hue, saturation, lightness
  function getHSLColor(hue = 180, saturation = 50, lightness = 50) {
    return `hsl(${hue},${saturation}%,${lightness}%)`;
  }

  // Function to purge chat user actions with a smooth step-by-step animation
  // Parameters:
  //   - delayBetweenAnimations: Delay between each animation step (default: 300ms)
  //   - smoothScrollDuration: Duration of smooth scrolling (default: 500ms)
  function purgeStaticChatNotifications(delayBetweenAnimations = 300, smoothScrollDuration = 500) {
    // Get all elements with the class .static-chat-notification
    const staticChatNotifications = Array.from(document.querySelectorAll('.static-chat-notification')).reverse();

    // Get the chat container
    const chatContainer = document.querySelector(".messages-content");

    // Return early if the chat container does not exist
    if (!chatContainer) return;

    // Function to check if an element is visible in the viewport
    function isElementVisible(element) {
      const rect = element.getBoundingClientRect();
      return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
      );
    }

    // Function to apply the animation to an element
    async function animateOut(element, index) {
      // Calculate the delay for each element
      const delay = index * delayBetweenAnimations;

      // Apply opacity and translation animation with delay
      await new Promise(resolve => setTimeout(resolve, delay));
      element.style.transition = `opacity ${delayBetweenAnimations / 1000}s cubic-bezier(0.83, 0, 0.17, 1), transform ${delayBetweenAnimations / 1000}s cubic-bezier(0.83, 0, 0.17, 1)`;
      element.style.opacity = 0;
      element.style.transform = `translateX(1em)`;

      // After the animation duration, remove the element
      await new Promise(resolve => setTimeout(resolve, delayBetweenAnimations));
      element.remove();

      // Check if the next notification is visible
      const nextIndex = index + 1;
      const nextElement = staticChatNotifications[nextIndex];

      if (nextElement) {
        // Use await to ensure element is visible before scrolling
        if (!isElementVisible(nextElement)) {
          await scrollToNextElement(nextElement, chatContainer, smoothScrollDuration);
        }
        // Remove the next element after scrolling or if already visible
        nextElement.remove();
      } else {
        // If last element, smooth scroll back to the bottom
        chatContainer.scrollTop = chatContainer.scrollHeight;
        await new Promise(resolve => setTimeout(resolve, smoothScrollDuration));
        chatContainer.style.scrollBehavior = 'auto';
        const containers = document.querySelectorAll('.static-chat-notifications-container');
        containers.forEach(container => container.remove());
      }
    }

    // Function to handle smooth scrolling to the next element
    async function scrollToNextElement(nextElement, chatContainer, smoothScrollDuration) {
      const closestContainer = nextElement.closest('.static-chat-notifications-container');
      const containerHeight = closestContainer ? closestContainer.offsetHeight : 0;
      const extraSpace = 200;

      const distanceToTop = nextElement.offsetTop - chatContainer.offsetTop - containerHeight - extraSpace;
      chatContainer.style.scrollBehavior = 'smooth';
      chatContainer.scrollTop = distanceToTop;

      // Wait for smooth scroll animation to finish
      await new Promise(resolve => setTimeout(resolve, smoothScrollDuration));
    }

    // Use forEach on the reversed array and apply animations
    staticChatNotifications.forEach((element, index) => {
      animateOut(element, index);
    });
  }

  // Constants for SVG icon properties
  const actionIconWidth = 16;
  const actionIconHeight = 16;
  const actionStrokeWidth = 2;

  // SVG icon for entering
  const enterIcon = `
  <svg xmlns="http://www.w3.org/2000/svg" width="${actionIconWidth}" height="${actionIconHeight}"
      viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${actionStrokeWidth}"
      stroke-linecap="round" stroke-linejoin="round" class="icon-enter icon-feather icon-log-in">
    <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
    <polyline points="10 17 15 12 10 7"></polyline>
    <line x1="15" y1="12" x2="3" y2="12"></line>
  </svg>
`;

  // SVG icon for leaving
  const leaveIcon = `
  <svg xmlns="http://www.w3.org/2000/svg" width="${actionIconWidth}" height="${actionIconHeight}"
      viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${actionStrokeWidth}"
      stroke-linecap="round" stroke-linejoin="round" class="icon-leave icon-feather icon-log-out">
    <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
    <polyline points="16 17 21 12 16 7"></polyline>
    <line x1="21" y1="12" x2="9" y2="12"></line>
  </svg>
`;

  // Function to check if notifications should be shown based on localStorage settings
  function shouldShowNotifications(type) {
    const toggleData = JSON.parse(localStorage.getItem('toggle')) || []; // Retrieve toggle settings or default to empty array

    // Define toggle names based on notification type
    const toggleNames = {
      static: 'showChatStaticNotifications',
      dynamic: 'showGlobalDynamicNotifications'
    };

    // Check if the specified notification toggle is set to 'yes'
    return toggleData.some(toggle =>
      toggle.name === toggleNames[type] && toggle.option === 'yes'
    );
  }

  // Timeout before the dynamicChatNotification should be removed
  const dynamicChatNotificationTimeout = 5000;
  // Set the initial top distance for the first dynamicChatNotification
  const dynamicChatNotificationTopOffset = 160;

  function showUserAction(user, iconType, presence) {
    // Make sure if the user is tracked and has the state 'thawed' (watched) to notify about presence in the chat to leave static stamps
    const isTrackedUser = usersToTrack.some((trackedUser) =>
      trackedUser.name === user && trackedUser.state === 'thawed'
    );

    // Get current time in format "[hour:minutes:seconds]"
    const time = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });

    // Determine the icon based on the action type (enter/leave)
    const actionIcon = document.createElement('div');
    actionIcon.classList.add('action-icon');
    actionIcon.style.margin = '0 4px';
    // Fix issue with white border on default white site theme
    actionIcon.style.setProperty('border', 'none', 'important');
    actionIcon.innerHTML = iconType;

    // Append containers with notifications inside the chat only for the tracked users
    // Ensure static notifications are enabled for tracked users
    if (isTrackedUser && shouldShowNotifications('static')) {
      // Get the container for all chat messages
      const messagesContainer = document.querySelector('.messages-content div');

      // Get the last child of messagesContainer
      const latestChild = messagesContainer.lastElementChild;

      // Check if the latest child is a static-chat-notifications-container
      const isLatestContainer = latestChild && latestChild.classList.contains('static-chat-notifications-container');

      // If the latest child is not a container or the container doesn't exist, create a new one
      if (!isLatestContainer) {
        // Create a new container for chat notifications
        const staticChatNotificationsContainer = document.createElement('div');
        staticChatNotificationsContainer.classList.add('static-chat-notifications-container');
        // Append the container to the messages container
        messagesContainer.appendChild(staticChatNotificationsContainer);
      }

      // Create a new div element for the chat notification
      const staticChatNotification = document.createElement('div');

      // Add a double-click event listener to initiate the removal of chat user actions
      staticChatNotification.addEventListener('dblclick', () => {
        // Call the function to purge chat user actions with a delay of (N)ms between animations and (N) scroll speed
        purgeStaticChatNotifications(150, 100);
      });

      // Set the text content of the chat notification to include the user and time
      staticChatNotification.innerHTML = `${user} ${actionIcon.outerHTML} ${time}`;
      // Add main class for chat notifications
      staticChatNotification.classList.add('static-chat-notification');

      // Check if the presence is true or false
      if (presence) {
        // Add the 'user-entered' class to the chat notification
        staticChatNotification.classList.add('user-entered');
        // Set the background color, font color, and border color for the chat notification
        staticChatNotification.style.color = getHSLColor(100, 50, 50);
        staticChatNotification.style.backgroundColor = getHSLColor(100, 50, 10);
        staticChatNotification.style.setProperty('border', `1px solid ${getHSLColor(100, 50, 25)}`, 'important');
      } else {
        // Add the 'user-left' class to the chat notification
        staticChatNotification.classList.add('user-left');
        // Set the background color, font color, and border color for the chat notification
        staticChatNotification.style.color = getHSLColor(0, 50, 70);
        staticChatNotification.style.backgroundColor = getHSLColor(0, 50, 15);
        staticChatNotification.style.setProperty('border', `1px solid ${getHSLColor(0, 50, 40)}`, 'important');
      }

      // Set the padding, display, and margin for the chat notification
      staticChatNotification.style.padding = '8px';
      staticChatNotification.style.display = 'inline-flex';
      staticChatNotification.style.margin = '4px 2px';
      staticChatNotification.style.fontSize = '1em';

      // Append the chat notification to the latest chat notifications container
      messagesContainer.lastElementChild.appendChild(staticChatNotification);

      // Call the function to scroll to the bottom of the chat
      scrollMessages();
    } // Static notifications END

    // Handle dynamic notifications only if dynamic notifications are enabled for all users
    if (shouldShowNotifications('dynamic')) {

      // Check dynamicChatNotificationsContainer for accessibility
      let dynamicChatNotificationsContainer = document.querySelector('.dynamic-chat-notifications-container');
      // Create container for dynamic chat notifications if not exist in DOM
      if (!dynamicChatNotificationsContainer) {
        // Container doesn't exist, so create it
        dynamicChatNotificationsContainer = document.createElement('div');
        dynamicChatNotificationsContainer.classList.add('dynamic-chat-notifications-container');
        dynamicChatNotificationsContainer.style.pointerEvents = 'none';
        dynamicChatNotificationsContainer.style.position = 'fixed';
        dynamicChatNotificationsContainer.style.display = 'flex';
        dynamicChatNotificationsContainer.style.flexDirection = 'column';
        dynamicChatNotificationsContainer.style.top = '0';
        dynamicChatNotificationsContainer.style.bottom = '0';
        dynamicChatNotificationsContainer.style.left = '0';
        dynamicChatNotificationsContainer.style.right = '0';
        dynamicChatNotificationsContainer.style.paddingTop = dynamicChatNotificationTopOffset + 'px';

        // Append the container to the body
        document.body.appendChild(dynamicChatNotificationsContainer);
      }

      // Create dynamicChatNotification element
      const dynamicChatNotification = document.createElement('div');
      dynamicChatNotification.classList.add('dynamic-chat-notification');

      // Set the text content of the dynamicChatNotification to include the user and append the icon
      dynamicChatNotification.insertAdjacentHTML('beforeend', `${user}${actionIcon.outerHTML}${time}`);

      // Set the initial static styles for the dynamicChatNotification
      dynamicChatNotification.style.position = 'relative';
      dynamicChatNotification.style.width = 'fit-content';
      dynamicChatNotification.style.display = 'flex';
      dynamicChatNotification.style.marginBottom = '0.2em';
      dynamicChatNotification.style.padding = '8px 16px 8px 12px';
      dynamicChatNotification.style.alignItems = 'center';
      dynamicChatNotification.style.left = '0';
      // Set the initial dynamicChatNotification transform beyond the screen of its 100% width
      dynamicChatNotification.style.transform = 'translateX(-100%)';
      dynamicChatNotification.style.opacity = '1';
      dynamicChatNotification.style.transition = 'transform 0.3s cubic-bezier(0.83, 0, 0.17, 1), opacity 0.3s cubic-bezier(0.83, 0, 0.17, 1)';
      // Set the dynamic colorization of the dynamicChatNotification
      dynamicChatNotification.style.color = presence ? getHSLColor(100, 50, 50) : getHSLColor(0, 50, 70); // fontColor green && red
      dynamicChatNotification.style.backgroundColor = presence ? getHSLColor(100, 50, 10) : getHSLColor(0, 50, 15); // backgroundColor green && red
      dynamicChatNotification.style.border = presence ? `1px solid ${getHSLColor(100, 50, 25)}` : `1px solid ${getHSLColor(0, 50, 40)}`; // borderColor green && red
      dynamicChatNotification.style.setProperty('border-radius', '0 4px 4px 0', 'important');

      // Append dynamicChatNotification to dynamicChatNotificationsContainer
      dynamicChatNotificationsContainer.appendChild(dynamicChatNotification);

      // Animate dynamicChatNotification
      setTimeout(() => {
        // Initiate the animation by showing the dynamicChatNotification
        dynamicChatNotification.style.transform = 'translateX(0)';

        setTimeout(() => {
          // After (N) seconds, hide it beyond the screen
          dynamicChatNotification.style.transform = 'translateX(-100%)';

          setTimeout(() => {
            // Remove the dynamicChatNotification from DOM after 300ms
            dynamicChatNotificationsContainer.removeChild(dynamicChatNotification);
          }, 300); // Remove
        }, dynamicChatNotificationTimeout); // Hide
      }, 300);
    } // Dynamic notifications END

  }


  // FUNCTIONALITY

  /*
     * Converts links to images in chat messages by creating a thumbnail and a big image on click.
     * Looks for links that contain ".jpg" or ".jpeg" or ".png" or ".gif" or "webp" extension and creates a thumbnail with the image.
     * If a thumbnail already exists, it skips the link and looks for the next one.
     * When a thumbnail is clicked, it creates a dimming layer and a big image that can be closed by clicking on the dimming layer or the big image itself.
     * Allows navigation through images using the left (<) and right (>) arrow keys.
     */

  // Define global variables for the current big image and dimming background
  let bigImage = null;

  // Define an array to store all the thumbnail links and their corresponding image URLs
  const thumbnailLinks = [];
  let currentImageIndex = 0;
  const imageChangeDelay = 50; // Prevent double slide by single press adding slight delay
  let isChangingImage = false; // Flag to track if an image change is in progress

  // Emoji for the image extension
  const imageExtensionEmoji = '📸';
  // Emoji for the web domain
  const webDomainEmoji = '🖥️';

  // List of trusted domains
  const trustedDomains = [
    'imgur.com',
    'pikabu.ru',
    'userapi.com', // vk.com
    'ibb.co', // imgbb.com
    'yaplakal.com',
    'freepik.com'
  ];

  /**
   * Checks if a given URL's domain is trusted.
   * @param {string} url - The URL to check.
   * @returns {{isTrusted: boolean, domain: string, isValid: boolean}} - Result and the extracted domain.
   */
  function isTrustedDomain(url) {
    let isValid = true; // Assume URL is valid initially
    let domain = '';

    try {
      // Parse the URL
      const parsedURL = new URL(url);
      // Split the lowercase hostname into parts
      const hostnameParts = parsedURL.hostname.toLowerCase().split('.');
      // Get the last two parts of the hostname if there are more than two, otherwise, use all parts
      const lastTwoHostnameParts = hostnameParts.length > 2 ? hostnameParts.slice(-2) : hostnameParts;
      // Join the last two parts to form the domain
      domain = lastTwoHostnameParts.join('.');
      // Check if the domain is trusted
      const isTrusted = trustedDomains.includes(domain);

      // Return an object with the result, the domain, and the validity of the URL
      return { isTrusted, domain, isValid };
    } catch (error) {
      // If an error occurs, the URL is invalid
      isValid = false;
      return { isTrusted: false, domain: '', isValid };
    }
  }

  /**
   * Function to check if a given URL has an allowed image extension
   * @param {string} url - The URL to check
   * @returns {Object} - An object with properties 'allowed' (boolean), 'extension' (string), and 'valid' (boolean)
   */
  function isAllowedImageExtension(url) {
    let valid = true; // Assume URL is valid initially

    try {
      // Use URL API to get pathname
      const extensionMatch = new URL(url).pathname.match(/\.([^.]+)$/);
      // Extract the file extension from the pathname (if any)
      const extension = extensionMatch ? extensionMatch[1].toLowerCase() : '';
      // List of allowed image file extensions
      const allowedImageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
      // Check if the extracted extension is in the list of allowed extensions
      const allowed = allowedImageExtensions.includes(extension);
      // Return an object with the result, the extracted extension, and the validity of the URL
      return { valid, allowed, extension };
    } catch (error) {
      // If an error occurs, the URL is invalid
      valid = false;
      return { valid, allowed: false, extension: '' };
    }
  }

  function convertImageLinkToImage() {
    // get the container for all chat messages
    const messagesContainer = document.querySelector('.messages-content div');
    // get all links inside the messages container
    const links = messagesContainer.querySelectorAll('p a:not(.skipped)');

    // loop through all links
    for (let i = 0; i < links.length; i++) {
      const link = links[i];

      // Check if the link has a valid href
      if (!link.href || !link.href.startsWith('http')) {
        continue; // Skip invalid links
      }

      // Check if the link's href includes allowed image extension
      const { allowed, extension } = isAllowedImageExtension(link.href);

      // Check if the link's href includes trusted domain
      const { isTrusted, domain } = isTrustedDomain(link.href);

      // Check if the link's href includes the allowed image extension and the domain is trusted
      if (allowed && isTrusted) {

        // Change the text content of the link to indicate it's an image with extension and trusted domain
        link.textContent = `${imageExtensionEmoji} Image (${extension.toUpperCase()}) ${webDomainEmoji} Hostname (${domain})`;

        // Assign the href value as the title
        link.title = link.href;

        // check if thumbnail already exists
        const thumbnail = link.nextSibling;
        if (!thumbnail || !thumbnail.classList || !thumbnail.classList.contains('thumbnail')) {
          // create a new thumbnail
          const thumbnail = document.createElement('div');
          thumbnail.classList.add('thumbnail');
          thumbnail.style.width = '6vw';
          thumbnail.style.minWidth = '100px';
          thumbnail.style.maxHeight = '200px';
          thumbnail.style.height = 'auto';
          thumbnail.style.cursor = 'pointer';
          thumbnail.style.backgroundColor = 'transparent';
          thumbnail.style.padding = '2px';
          thumbnail.style.margin = '6px';

          // create an image inside the thumbnail
          const img = document.createElement('img');
          img.src = link.href; // Assign the src directly

          // Add an onload event to check if the image is loaded successfully
          img.onload = function () {
            // Check if the domain is trusted
            if (isTrustedDomain(link.href)) {
              thumbnail.appendChild(img);

              // insert the thumbnail after the link
              link.parentNode.insertBefore(thumbnail, link.nextSibling);

              // Store the thumbnail link and its corresponding image URL
              thumbnailLinks.push({ link, imgSrc: link.href });

              // add click event to thumbnail to create a big image and dimming layer
              thumbnail.addEventListener('click', function (e) {
                e.preventDefault();
                e.stopPropagation();

                // Reset bigImage to null before processing the new thumbnail click
                bigImage = null;

                currentImageIndex = thumbnailLinks.findIndex((item) => item.imgSrc === link.href);

                // Check if bigImage is already created
                if (!bigImage) {
                  // Create the big image
                  bigImage = createBigImage(img.src);

                  bigImage.style.top = '50%';
                  bigImage.style.left = '50%';
                  bigImage.style.transform = 'translate(-50%, -50%) scale(1)';
                  bigImage.style.position = 'fixed';
                  bigImage.style.opacity = '0';
                  bigImage.style.zIndex = '999';
                  bigImage.style.transformOrigin = 'center center';

                  // Fade in the big image
                  fadeTargetElement(bigImage, 'show');

                  // To show the dimming background
                  fadeDimmingElement('show');

                  // Attach a keydown event listener to the document object
                  document.addEventListener('keydown', function (event) {
                    // Check if the key pressed was the "Escape" key
                    if (event.key === 'Escape') {
                      // Fade out the big image
                      fadeTargetElement(bigImage, 'hide');
                      fadeDimmingElement('hide');
                    }
                    // Check if the key pressed was the left arrow key (<)
                    else if (event.key === 'ArrowLeft') {
                      // Navigate to the previous image
                      navigateImages(-1);
                    }
                    // Check if the key pressed was the right arrow key (>)
                    else if (event.key === 'ArrowRight') {
                      // Navigate to the next image
                      navigateImages(1);
                    }
                  });
                }
              }); // thumbnail event end

              // add mouseover and mouseout event listeners to the thumbnail
              thumbnail.addEventListener('mouseover', function () {
                img.style.opacity = 0.7;
                img.style.transition = 'opacity 0.3s';
              });

              thumbnail.addEventListener('mouseout', function () {
                img.style.opacity = 1;
              });

              // Call the function to scroll to the bottom of the chat
              scrollMessages();
            } else {
              // Handle the case where the domain is not trusted
              console.error("Not a trusted domain:", link.href);

              // Add a class to the link to skip future conversion attempts
              link.classList.add('skipped');
            }
          };

          // Add an onerror event to handle cases where the image fails to load
          img.onerror = function () {
            // Handle the case where the image failed to load (e.g., it's a fake image)
            console.error("Failed to load image:", link.href);

            // Add a class to the link to skip future conversion attempts
            link.classList.add('skipped');
          };

          img.style.maxHeight = '100%';
          img.style.maxWidth = '100%';
          img.style.backgroundColor = 'transparent';
        }
      }
    }
  } // end convertImageLinkToImage

  // Function to create a big image with a dimming layer
  function createBigImage(src) {
    const bigImage = document.createElement('img');
    bigImage.src = src;
    bigImage.classList.add('scaled-thumbnail');
    bigImage.style.maxHeight = '90vh';
    bigImage.style.maxWidth = '90vw';

    document.body.appendChild(bigImage);

    // ZOOM AND MOVE -- START

    // Set the initial zoom scale and scaling factor
    let zoomScale = 1;
    let scalingFactor = 0.1;

    // Set up variables for dragging
    let isDragging = false;
    let startX = 0;
    let startY = 0;
    let translateX = -50; // Initial translation in percentage
    let translateY = -50; // Initial translation in percentage

    // Define the movement speed
    const movementSpeed = 5;

    // Function to handle zooming
    function handleZoom(event) {
      // Determine the direction of the mouse wheel movement
      const deltaY = event.deltaY;
      const direction = deltaY < 0 ? 1 : -1;

      // Update the zoom scale based on the direction and scaling factor
      zoomScale += direction * scalingFactor * zoomScale;

      // Clamp the zoom scale to a minimum of 1
      zoomScale = Math.max(zoomScale, 1);

      // Apply the new zoom scale and transform origin
      bigImage.style.transformOrigin = 'center center';
      bigImage.style.transform = `translate(${translateX}%, ${translateY}%) scale(${zoomScale})`;

      // Prevent the default scrolling behavior
      event.preventDefault();
    }

    // Function to update the image position smoothly
    function updateImagePosition(event) {
      if (isDragging) {
        // Calculate the distance moved since the last mousemove event
        const deltaX = (event.clientX - startX) / zoomScale * movementSpeed;
        const deltaY = (event.clientY - startY) / zoomScale * movementSpeed;

        // Update the translate values in percentages
        translateX += (deltaX / bigImage.clientWidth) * 100;
        translateY += (deltaY / bigImage.clientHeight) * 100;

        // Apply the new translate values in percentages
        bigImage.style.transform = `translate(${translateX}%, ${translateY}%) scale(${zoomScale})`;

        // Update the start position
        startX = event.clientX;
        startY = event.clientY;
      }
    }

    // Add event listener for mousedown
    const mouseDownHandler = (event) => {
      // Check if the middle mouse button is pressed
      if (event.button === 1) {
        isDragging = true; // Set the dragging flag
        [startX, startY] = [event.clientX, event.clientY]; // Calculate initial position
      }
    };

    // Add event listener for mouseup
    const mouseUpHandler = () => {
      isDragging = false; // Reset the dragging flag
    };

    // Add event listener for mousemove
    const mouseMoveHandler = updateImagePosition; // Assuming updateImagePosition is defined elsewhere

    // Add event listener for wheel
    const wheelHandler = handleZoom; // Assuming handleZoom is defined elsewhere

    // Attach event listeners
    document.addEventListener('mousedown', mouseDownHandler);
    document.addEventListener('mouseup', mouseUpHandler);
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('wheel', wheelHandler);

    // Create a MutationObserver to watch for the removal of scaled-thumbnail elements
    const observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        // Check if nodes have been removed
        if (mutation.removedNodes.length) {
          mutation.removedNodes.forEach((node) => {
            // Check if the removed node is the scaled-thumbnail
            if (node.classList && node.classList.contains('scaled-thumbnail')) {
              // Remove all event listeners directly
              document.removeEventListener('mousedown', mouseDownHandler);
              document.removeEventListener('mouseup', mouseUpHandler);
              document.removeEventListener('mousemove', mouseMoveHandler);
              document.removeEventListener('wheel', wheelHandler);

              // Disconnect the observer after handling the removal
              observer.disconnect();
            }
          });
        }
      }
    });

    // Start observing the body or the relevant parent container
    observer.observe(document.body, {
      childList: true, // Watch for the addition/removal of child nodes
      subtree: true, // Observe all descendants
    });

    return bigImage;
  }

  // ZOOM AND MOVE -- END

  // Function to navigate between images within bounds
  function navigateImages(direction) {
    const newIndex = currentImageIndex + direction;

    // Ensure the new index stays within bounds
    if (newIndex >= 0 && newIndex < thumbnailLinks.length) {
      if (isChangingImage) {
        return; // If an image change is already in progress, do nothing
      }

      isChangingImage = true; // Set the flag to indicate image change is in progress

      // Update the bigImage with the new image URL
      if (bigImage) {
        bigImage.src = thumbnailLinks[newIndex].imgSrc;
      }

      // Set a timeout to reset the flag after a short delay
      setTimeout(() => {
        isChangingImage = false;
      }, imageChangeDelay); // Adjust the delay duration as needed (e.g., 50 milliseconds)

      // Update the current index
      currentImageIndex = newIndex;
    }
  }

  // Function to convert YouTube links to embedded iframes in a chat messages container
  function convertYoutubeLinkToIframe() {
    // Get the container for all chat messages
    const messagesContainer = document.querySelector('.messages-content div');

    // Find all links inside the messages container
    const links = messagesContainer.querySelectorAll('p a');

    // Loop through each link
    for (const link of links) {
      const url = link.href;

      // Use the regular expression to match different YouTube link formats and extract the video ID
      const match = url.match(/(?:shorts\/|live\/|watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/i);

      // If the link is a valid YouTube link, replace it with an embedded iframe
      if (match && match[1]) {
        // Extract the video ID from the matched result
        const videoId = match[1];

        // Create a new iframe element
        const iframe = document.createElement('iframe');

        // Set attributes and styles for the iframe
        iframe.width = '280';
        iframe.height = '157.5';
        iframe.allowFullscreen = true;
        iframe.style.display = 'flex';
        iframe.style.margin = '6px';
        iframe.style.border = 'none';

        // Set the iframe source to embed the YouTube video
        iframe.src = `https://www.youtube.com/embed/${videoId}`;

        // Replace the original link with the newly created iframe in the DOM
        link.parentNode.replaceChild(iframe, link);
      }
    }

    // Call the function to scroll to the bottom of the chat
    scrollMessages();

  } // end convertYoutubeLinkToIframe

  const empowermentButtonsMargin = 4;

  // Retrieve body element to inject this beast elements
  const bodyElement = document.querySelector('body');
  // Create parent container for the beast elements
  const empowermentButtonsPanel = document.createElement('div');
  empowermentButtonsPanel.classList.add('empowerment-panel');

  // Create user count container to store the user count number
  const userCount = document.createElement('div');
  userCount.title = 'Current Chat Users Count';
  userCount.classList.add('user-count-indicator');
  userCount.style.filter = 'grayscale(100%)';
  userCount.style.transition = '0.2s ease-in-out';
  userCount.style.fontFamily = "'Orbitron', sans-serif";
  userCount.style.fontSize = '24px';
  userCount.style.color = '#83cf40';
  userCount.style.backgroundColor = '#2b4317';
  userCount.style.width = '48px';
  userCount.style.height = '48px';
  userCount.style.display = 'flex';
  userCount.style.justifyContent = 'center';
  userCount.style.alignItems = 'center';
  userCount.style.border = '1px solid #4b7328';
  userCount.style.margin = `${empowermentButtonsMargin}px`;
  // Set initial value as 0
  userCount.innerHTML = '0';

  // Append user count element inside empowerment panel
  empowermentButtonsPanel.appendChild(userCount);
  // Apply positioning styles for the empowerment panel
  empowermentButtonsPanel.style.position = 'fixed';
  empowermentButtonsPanel.style.top = '60px';
  empowermentButtonsPanel.style.right = '12px';
  empowermentButtonsPanel.style.padding = '6px';
  // Append panel element inside the body
  bodyElement.appendChild(empowermentButtonsPanel);

  const userCountStyles = `
  .pulse {
    animation-name: pulse;
    animation-duration: 0.5s;
    animation-iteration-count: 1;
  }

  @keyframes pulse {
    0% {
      filter: brightness(1);
    }
    50% {
      filter: brightness(1.5);
    }
    100% {
      filter: brightness(1);
    }
  }
`;

  // Append styles in head element for the user count element
  const userCountStylesElement = document.createElement('style');
  userCountStylesElement.classList.add('user-count-pulse');
  userCountStylesElement.textContent = userCountStyles;
  document.head.appendChild(userCountStylesElement);

  // Constants for fade speed control
  const fadeIntervalTime = 10; // Time between each opacity change step in ms
  const fadeDuration = 100; // Total fade duration in ms

  // Function to create and fade the dimming element
  function fadeDimmingElement(action) {
    // Check if the dimming element already exists
    let dimming = document.querySelector('.dimming-background');

    // If the action is 'hide' and the dimming element doesn't exist, return
    if (action === 'hide' && !dimming) return;

    // Create the dimming element only if it doesn't exist
    if (!dimming) {
      dimming = document.createElement('div');
      dimming.classList.add('dimming-background');
      dimming.style.background = 'black';
      dimming.style.top = '0';
      dimming.style.left = '0';
      dimming.style.right = '0';
      dimming.style.bottom = '0';
      dimming.style.position = 'fixed';
      dimming.style.opacity = '0';
      dimming.style.zIndex = '998';

      // Append the dimming element to the body
      document.body.appendChild(dimming);

      // Add click event listener to remove the dimming element and the upper element
      dimming.addEventListener('click', function () {
        // Get the previous sibling element (the one above the dimming background) and remove it if it exists
        dimming.previousElementSibling && dimming.previousElementSibling.parentNode.removeChild(dimming.previousElementSibling);
        fadeDimmingElement('hide');
      });
    }

    let opacity = parseFloat(dimming.style.opacity) || 0; // Current opacity
    const targetOpacity = action === 'show' ? 0.5 : 0; // Target opacity based on action
    const step = (targetOpacity - opacity) / (fadeDuration / fadeIntervalTime); // Calculate the change in opacity per step

    const interval = setInterval(() => {
      opacity += step; // Update the opacity
      if ((step > 0 && opacity >= targetOpacity) || (step < 0 && opacity <= targetOpacity)) {
        opacity = targetOpacity; // Cap opacity
        clearInterval(interval); // Stop the interval
        if (targetOpacity === 0) {
          // Check if the element is still a child of document.body before removing it
          if (document.body.contains(dimming)) {
            document.body.removeChild(dimming); // Remove the element from the DOM
          }
        }
      }
      dimming.style.opacity = opacity.toString(); // Update the opacity
    }, fadeIntervalTime);
  }

  // Function to gradually fade a target element to show or hide it
  function fadeTargetElement(element, action) {
    if (!element) return; // Return if the element does not exist

    const targetOpacity = action === 'show' ? 1 : 0; // Set target opacity based on action
    let opacity = parseFloat(element.style.opacity) || 0; // Get the current opacity
    const step = (targetOpacity - opacity) / (fadeDuration / fadeIntervalTime); // Calculate the change in opacity per step

    const interval = setInterval(() => {
      opacity += step; // Update the opacity
      if ((step > 0 && opacity >= targetOpacity) || (step < 0 && opacity <= targetOpacity)) {
        opacity = targetOpacity; // Set opacity to the target value
        clearInterval(interval); // Clear the interval

        // Check if element still has a parent before removing it
        if (targetOpacity === 0 && element.parentNode) {
          element.parentNode.removeChild(element); // Remove the target element from the DOM
        }
      }
      element.style.opacity = opacity.toString(); // Update the element's opacity
    }, fadeIntervalTime);

    // Add a double click event listener to hide the element
    element.addEventListener('dblclick', () => {
      fadeTargetElement(element, 'hide'); // Call fadeTargetElement to hide on double click
      fadeDimmingElement('hide'); // Hide the dimming element on double click
    });
  }

  // NEW CHAT CACHE CONTROL PANEL (START)

  // Rank order mapping
  const rankOrder = {
    'Экстракибер': 1,
    'Кибергонщик': 2,
    'Супермен': 3,
    'Маньяк': 4,
    'Гонщик': 5,
    'Профи': 6,
    'Таксист': 7,
    'Любитель': 8,
    'Новичок': 9
  };

  // Rank color mapping
  const rankColors = {
    'Экстракибер': '#06B4E9', // Light Blue
    'Кибергонщик': '#5681ff', // Medium Blue
    'Супермен': '#B543F5', // Purple
    'Маньяк': '#DA0543', // Red
    'Гонщик': '#FF8C00', // Orange
    'Профи': '#C1AA00', // Yellow
    'Таксист': '#2DAB4F', // Green
    'Любитель': '#61B5B3', // Light Cyan
    'Новичок': '#AFAFAF' // Grey
  };

  // Function to display the cached user list panel
  function showCachePanel() {
    // Check if the panel already exists
    if (document.querySelector('.cached-users-panel')) return;

    // Get data from localStorage
    const fetchedUsersData = localStorage.getItem('fetchedUsers');

    // Initialize users by parsing fetched data or setting as empty object
    let users = JSON.parse(fetchedUsersData) || {};

    // Create a container div with class 'cached-users-panel'
    const cachedUsersPanel = document.createElement('div');
    cachedUsersPanel.className = 'cached-users-panel';
    // Set initial styles
    cachedUsersPanel.style.opacity = '0';
    cachedUsersPanel.style.backgroundColor = '#1b1b1b';
    cachedUsersPanel.style.setProperty('border-radius', '0.6em', 'important');
    cachedUsersPanel.style.position = 'fixed';
    cachedUsersPanel.style.top = '100px';
    cachedUsersPanel.style.left = '50%';
    cachedUsersPanel.style.transform = 'translateX(-50%)';
    cachedUsersPanel.style.width = '90vw';
    cachedUsersPanel.style.height = '80vh';
    cachedUsersPanel.style.zIndex = '999';

    // Attach a keydown event listener to the document object
    document.addEventListener('keydown', function (event) {
      // Check if the key pressed was the "Escape" key
      if (event.key === 'Escape') {
        // Fade out the cached users panel
        fadeTargetElement(cachedUsersPanel, 'hide');
        fadeDimmingElement('hide');
      }
    });

    // Create a container div with class 'panel-header'
    const panelHeaderContainer = document.createElement('div');
    panelHeaderContainer.className = 'panel-header';
    panelHeaderContainer.style.display = 'flex';
    panelHeaderContainer.style.flexDirection = 'row';
    panelHeaderContainer.style.justifyContent = 'space-between';
    panelHeaderContainer.style.padding = '0.6em';

    // Create a container div with class 'drop-time'
    const dropTime = document.createElement('div');
    dropTime.className = 'drop-time';
    dropTime.style.display = 'flex';
    dropTime.style.justifyContent = 'center';
    dropTime.style.alignItems = 'center';
    dropTime.style.minWidth = 'fit-content';

    // Create span with description for threshold time element
    const dropTimeThresholdDescription = document.createElement('span');
    dropTimeThresholdDescription.className = 'drop-time-threshold-description';
    dropTimeThresholdDescription.textContent = '🚧 Threshold';
    dropTimeThresholdDescription.style.padding = '0.6em';
    dropTimeThresholdDescription.style.color = '#c6b209';

    const dropTimeThreshold = document.createElement('span');
    dropTimeThreshold.className = 'drop-time-threshold';
    dropTimeThreshold.style.padding = '0.6em';
    dropTimeThreshold.style.color = 'lightcoral';
    dropTimeThreshold.style.fontFamily = "'Roboto Mono', monospace";
    dropTimeThreshold.style.fontSize = '1.1em';
    dropTimeThreshold.style.fontWeight = 'bold';
    dropTimeThreshold.style.setProperty('border-radius', '0.2em', 'important');
    dropTimeThreshold.style.border = '1px solid rgba(240, 128, 128, 0.20)';
    dropTimeThreshold.style.backgroundColor = 'rgba(240, 128, 128, 0.05)';
    dropTimeThreshold.style.transition = 'filter 0.3s';
    dropTimeThreshold.style.cursor = 'pointer';

    // Add mouseover event to apply brightness filter
    dropTimeThreshold.addEventListener('mouseover', () => {
      dropTimeThreshold.style.filter = 'sepia(1)'; // Increase brightness on hover
    });

    // Add mouseout event to reset filter
    dropTimeThreshold.addEventListener('mouseout', () => {
      dropTimeThreshold.style.filter = 'sepia(0)'; // Reset brightness on mouse out
    });

    // Get the value from the localStorage key 'cacheRefreshThresholdHours'
    const storedThresholdTime = localStorage.getItem('cacheRefreshThresholdHours');
    // Update the innerHTML with the stored value (default to '00:00:00' if the key is not set)
    dropTimeThreshold.innerHTML = storedThresholdTime || '00:00:00';
    // Attach click event to the dropTimeThreshold element
    dropTimeThreshold.addEventListener('click', setCacheRefreshTime);

    // Create span with description for expiration time element
    const dropTimeExpirationDescription = document.createElement('span');
    dropTimeExpirationDescription.className = 'drop-time-expiration-description';
    dropTimeExpirationDescription.textContent = '💣 Countdown';
    dropTimeExpirationDescription.style.padding = '0.6em';
    dropTimeExpirationDescription.style.color = '#d0562c';

    const dropTimeExpiration = document.createElement('span');
    dropTimeExpiration.className = 'drop-time-expiration';
    dropTimeExpiration.style.padding = '0.6em';
    dropTimeExpiration.style.color = 'antiquewhite';
    dropTimeExpiration.style.fontFamily = "'Roboto Mono', monospace";
    dropTimeExpiration.style.fontSize = '1.1em';

    // Function to prompt the user for a cache refresh time and update the content
    function setCacheRefreshTime() {
      let isValidInput = false;

      // Keep prompting the user until valid input is provided or they click "Cancel"
      while (!isValidInput) {
        // Prompt the user for a time
        const userInput = prompt('Enter a cache refresh time (e.g., HH, HH:mm, or HH:mm:ss):');

        // Get the dropTimeThreshold element
        const dropTimeThreshold = document.querySelector('.drop-time-threshold');

        // Validate the user input
        const timeRegex = /^([0-9]+|[01][0-9]|2[0-4])(:([0-5]?[0-9])(:([0-5]?[0-9]))?)?$/; // HH, HH:mm, or HH:mm:ss

        if (userInput === null) {
          // User clicked "Cancel," exit the loop
          isValidInput = true;
        } else if (timeRegex.test(userInput)) {
          // Valid input, extract hours and set default values for minutes and seconds if not provided
          const formattedInput = userInput.split(':');
          const hours = ('0' + formattedInput[0]).slice(-2);
          const minutes = ('0' + (formattedInput[1] || '00')).slice(-2);
          const seconds = ('0' + (formattedInput[2] || '00')).slice(-2);

          // Update the content of the dropTimeThreshold element
          dropTimeThreshold.textContent = `${hours}:${minutes}:${seconds}`;

          // Combine the values and store in localStorage with the key 'cacheRefreshThresholdHours'
          const formattedTime = `${hours}:${minutes}:${seconds}`;
          localStorage.setItem('cacheRefreshThresholdHours', formattedTime);

          // Remove fetchedUsers, lastClearTime, and nextClearTime keys
          localStorage.removeItem('fetchedUsers');
          localStorage.removeItem('lastClearTime');
          localStorage.removeItem('nextClearTime');

          // Reload the current page after (N) time after changing the cache threshold
          setTimeout(() => location.reload(), 1000);

          // Set isValidInput to true to exit the loop
          isValidInput = true;
        } else {
          // Alert the user for invalid input
          alert('Invalid time format. Please enter a valid time in the format HH, HH:mm, or HH:mm:ss.');
        }
      }
    }

    // Append the childs to the drop time parent element
    dropTime.appendChild(dropTimeThresholdDescription);
    dropTime.appendChild(dropTimeThreshold);
    dropTime.appendChild(dropTimeExpirationDescription);
    dropTime.appendChild(dropTimeExpiration);

    // Append the drop time element to the panel header container
    panelHeaderContainer.appendChild(dropTime);

    // Create a container div for the search input
    const cacheSearchContainer = document.createElement('div');
    cacheSearchContainer.className = 'search-for-cached-users';
    cacheSearchContainer.style.width = '100%';
    cacheSearchContainer.style.margin = '0 20px';
    cacheSearchContainer.style.display = 'flex';

    // Create the input field for searching users
    const cacheSearchInput = document.createElement('input');
    cacheSearchInput.className = 'cached-users-search-input';
    cacheSearchInput.type = 'text';
    cacheSearchInput.style.outline = 'none';
    cacheSearchInput.style.width = '100%';
    cacheSearchInput.style.padding = '10px';
    cacheSearchInput.style.margin = '0 1em';
    cacheSearchInput.style.fontSize = '1em';
    cacheSearchInput.style.fontFamily = 'Montserrat';
    cacheSearchInput.style.setProperty('color', 'bisque', 'important');
    cacheSearchInput.style.setProperty('border-radius', '0.2em', 'important');
    cacheSearchInput.style.boxSizing = 'border-box';
    cacheSearchInput.style.backgroundColor = '#111';
    cacheSearchInput.style.border = '1px solid #222';

    // Append search input to the search container
    cacheSearchContainer.appendChild(cacheSearchInput);

    // Add click event listener to clear the search input by LMB click with Ctrl key pressed
    cacheSearchInput.addEventListener('click', () => isCtrlKeyPressed && (cacheSearchInput.value = ''));

    // Add event listener to listen for keydown events
    cacheSearchInput.addEventListener('keydown', async (event) => {
      const oldUsersContainer = document.querySelector('.old-users');
      const newUsersContainer = document.querySelector('.new-users');
      const fetchedUsersContainer = document.querySelector('.fetched-users');

      // Handle Backspace key
      if (event.key === 'Backspace' && event.target.value.length === 0) {
        oldUsersContainer.style.display = 'grid';
        newUsersContainer.style.display = 'grid';

        const searchResultsContainer = document.querySelector('.search-results');
        if (searchResultsContainer && fetchedUsersContainer) {
          fetchedUsersContainer.removeChild(searchResultsContainer);
        }
      }
      // Handle Enter key
      else if (event.key === 'Enter') {
        const inputValue = event.target.value.trim();

        // If input is empty, set it to 'user '
        if (inputValue.length === 0) {
          event.preventDefault(); // Prevent the default behavior
          event.target.value = 'user '; // Set input to 'user '
        }
      }
    });

    // Create a function to handle the search process
    const handleSearch = async (username) => {
      const oldUsersContainer = document.querySelector('.old-users');
      const newUsersContainer = document.querySelector('.new-users');
      const fetchedUsersContainer = document.querySelector('.fetched-users');

      if (username) {
        // Temporarily hide old and new user containers
        oldUsersContainer.style.display = 'none';
        newUsersContainer.style.display = 'none';

        // Find or create the search results container
        let searchResultsContainer = document.querySelector('.search-results');
        if (!searchResultsContainer) {
          searchResultsContainer = createUserContainer('search-results');
          fetchedUsersContainer.appendChild(searchResultsContainer); // Append if it's newly created
        } else {
          // Clear previous search results if the container already exists
          searchResultsContainer.innerHTML = null; // Clear existing elements
        }

        const userElements = []; // Initialize userElements array

        try {
          // Fetch user IDs by username
          const userIds = await getUserIdsByName(username);

          // Iterate over each user ID and retrieve profile data
          await Promise.all(userIds.map(async (userId) => {
            // Retrieve the user's profile data once
            const profileData = await getUserProfileData(userId, false); // Do not touch localStorage key "fetchedUsers"

            // Create user element data using the retrieved profile data
            const userData = {
              rank: profileData.rank, // Assign rank directly
              login: profileData.login,
              registered: profileData.registeredDate, // Set registered to registeredDate
              bestSpeed: profileData.bestSpeed,
              ratingLevel: profileData.ratingLevel,
              friends: profileData.friends,
              cars: profileData.cars,
              avatarTimestamp: profileData.avatarTimestamp,
              avatar: profileData.avatar // Include avatar in userData
            };

            // Create the user element with userId and userData
            const userElementData = createCachePanelUserElement(userId, userData);
            if (userElementData) {
              userElements.push(userElementData);
            }
          }));

          // Sort userElements by rank and best speed
          userElements.sort((a, b) =>
            a.order !== b.order ? a.order - b.order : b.bestSpeed - a.bestSpeed
          );

          // Append user elements to the search results container
          userElements.forEach(({ userElement }) => {
            searchResultsContainer.appendChild(userElement);
          });

          // Create and append the description for search results
          const searchDescription = createDescription(`Search Results for: ${username}`, 'search-results-description');
          searchResultsContainer.prepend(searchDescription); // Append description as the first element

        } catch (error) {
          console.error('Error fetching user profile:', error);

          // Create an error message element and append it to the container
          const errorMessage = document.createElement('div');
          errorMessage.className = 'error-message';
          errorMessage.textContent = `Error fetching user profile: ${error.message}`;
          errorMessage.style.width = 'fit-content';
          errorMessage.style.whiteSpace = 'nowrap';
          errorMessage.style.fontFamily = 'Montserrat';
          errorMessage.style.color = 'lightcoral';
          searchResultsContainer.appendChild(errorMessage);
        }
      }
    };

    // Debounce the handleSearch function to prevent excessive calls
    cacheSearchInput.addEventListener(
      'input',
      debounce((event) => {
        const inputValue = event.target.value.trim();
        const searchMode = localStorage.getItem('cachePanelSearchMode');

        // Extract username if input starts with 'user ', or use input directly in 'fetch' mode
        const username = inputValue.startsWith('user ')
          ? inputValue.substring(5).trim()
          : (searchMode === 'fetch' ? inputValue : '');

        // Trigger search if a valid username exists
        if (username) handleSearch(username);
      }, debounceTimeout)
    );

    // Append the search container to the panel header container
    panelHeaderContainer.appendChild(cacheSearchContainer);

    // Use a mutation observer to wait for the element to appear in the DOM
    const observer = new MutationObserver(mutations => {
      if (mutations.some(mutation => mutation.type === 'childList' && mutation.addedNodes.length > 0)) {
        const cachePanelSearchInput = document.querySelector('.cached-users-search-input');
        const cachePanelLogins = Array.from(document.querySelectorAll('.fetched-users .login'));

        // Fuzzy match scoring function
        const getFuzzyMatchScore = (query, text) => {
          let score = 0, queryIndex = 0;
          for (const char of text.toLowerCase()) {
            if (queryIndex < query.length && char === query[queryIndex].toLowerCase()) {
              score += 2; // Increment score for matching character
              queryIndex++; // Increment index for the next character
            }
          }
          return queryIndex === query.length ? score : 0;
        };

        // Filter items based on input query
        const filterItems = query => {
          cachePanelLogins.forEach(item => {
            const userContainer = item.closest('.user');
            userContainer.style.display = (!query || getFuzzyMatchScore(query, item.textContent) > 0) ? 'grid' : 'none';
          });
        };

        // Set focus to the search input field
        cachePanelSearchInput.focus();

        // Add input event listener to filter items as the user types
        cachePanelSearchInput.addEventListener('input', () => filterItems(cachePanelSearchInput.value.trim()));

        observer.disconnect();
      }
    });

    // Start observing the panel header container for changes
    observer.observe(panelHeaderContainer, { childList: true, subtree: true });

    // Create a container div with class 'panel-control-buttons'
    const panelControlButtons = document.createElement('div');
    panelControlButtons.className = 'panel-control-buttons';
    panelControlButtons.style.display = 'flex';

    // Helper function to apply common styles to a button
    function applyHeaderButtonStyles(button, backgroundColor, margin = '0 0.5em') {
      button.style.backgroundColor = backgroundColor;
      button.style.width = '48px';
      button.style.height = '48px';
      button.style.display = 'flex';
      button.style.justifyContent = 'center';
      button.style.alignItems = 'center';
      button.style.cursor = 'pointer';
      button.style.setProperty('border-radius', '0.2em', 'important');
      button.style.margin = margin; // Set margin using the provided value
      button.style.filter = 'brightness(1)';
      button.style.transition = 'filter 0.3s ease';
    }

    // Create cache panel search mode button with the provided SVG icon
    const cachePanelSearchMode = document.createElement('div');
    cachePanelSearchMode.className = 'user-mode-button';
    cachePanelSearchMode.innerHTML = usersSVG;
    // Apply common styles using the helper function
    applyHeaderButtonStyles(cachePanelSearchMode, 'darkslateblue');

    // Set the initial value or existing for cachePanelSearchMode if it doesn't exist
    const currentSearchMode = localStorage.getItem('cachePanelSearchMode') || (localStorage.setItem('cachePanelSearchMode', 'cache'), 'cache');

    // Set the title dynamically
    cachePanelSearchMode.title = `Current active mode: ${currentSearchMode}`;

    // Function to update styles based on the current mode
    function updateStyles(mode) {
      const backgroundColor = mode === 'fetch' ? '#b2a4f9' : 'darkslateblue';
      const strokeColor = mode === 'fetch' ? 'darkslateblue' : '#b2a4f9';

      // Apply the new background color using the helper function
      applyHeaderButtonStyles(cachePanelSearchMode, backgroundColor);

      // Update the SVG stroke color
      const svg = cachePanelSearchMode.querySelector('svg');
      if (svg) {
        svg.setAttribute('stroke', strokeColor);
      }
    }

    // Initial mode setup
    updateStyles(currentSearchMode);

    // Add click event listener to the cache panel search mode button
    cachePanelSearchMode.addEventListener('click', () => {
      // Toggle between 'cache' and 'fetch' values
      const currentMode = localStorage.getItem('cachePanelSearchMode');
      const newMode = currentMode === 'cache' ? 'fetch' : 'cache';
      // Set new mode in localStorage
      localStorage.setItem('cachePanelSearchMode', newMode);
      // Update styles based on the new mode
      updateStyles(newMode);
      // Set the title dynamically based on the new mode
      cachePanelSearchMode.title = `Current active mode: ${newMode}`;
      // Optional: Log the current mode for debugging
      // console.log(`Current mode: ${newMode}`);
    });

    // Append the search mode button to the panel header container
    panelControlButtons.appendChild(cachePanelSearchMode);

    // Create a clear cache button with the provided SVG icon
    const clearCacheButton = document.createElement('div');
    clearCacheButton.className = 'clear-cache-button';
    clearCacheButton.title = 'Clear cache';
    clearCacheButton.innerHTML = trashSVG;
    // Apply common styles using the helper function
    applyHeaderButtonStyles(clearCacheButton, 'brown');

    // Add a click event listener to the clear cache button
    clearCacheButton.addEventListener('click', () => {
      // Call the helper function to hide and remove the cachedUsersPanel
      hideCachePanel();
      // Clear the cache manually and reset the timer
      refreshFetchedUsers(true, cacheRefreshThresholdHours);

      // Set the user count element to 0
      const userCountElement = document.querySelector('.cache-panel-load-button .user-count');
      if (userCountElement) userCountElement.textContent = '0'; // Set the user count to 0
    });

    // Append the clear cache button to the panel header container
    panelControlButtons.appendChild(clearCacheButton);

    // Create a close button with the provided SVG icon
    const closePanelButton = document.createElement('div');
    closePanelButton.className = 'close-panel-button';
    closePanelButton.title = 'Close panel';
    closePanelButton.innerHTML = closeSVG;
    // Apply common styles using the helper function
    applyHeaderButtonStyles(closePanelButton, 'darkolivegreen', '0 0 0 0.5em');

    // Add a click event listener to the close panel button
    closePanelButton.addEventListener('click', () => {
      // Remove the cached-users-panel when the close button is clicked
      hideCachePanel();
    });

    // Create an array containing the buttons we want to apply the events to
    const buttons = [clearCacheButton, closePanelButton];

    // Iterate through each button in the array
    buttons.forEach(button => {
      // Add a mouseover event listener to change the button's brightness on hover
      button.addEventListener('mouseover', () => {
        button.style.filter = 'brightness(0.8)'; // Dim the button
      });

      // Add a mouseout event listener to reset the button's brightness when not hovered
      button.addEventListener('mouseout', () => {
        button.style.filter = 'brightness(1)'; // Reset to original brightness
      });
    });

    // Append the close button to the panel header container
    panelControlButtons.appendChild(closePanelButton);

    // Append the panel control buttons element inside the panel header container
    panelHeaderContainer.appendChild(panelControlButtons);

    // Create a container div with class 'fetched-users'
    const fetchedUsersContainer = document.createElement('div');
    fetchedUsersContainer.className = 'fetched-users';

    // Set grid layout properties
    fetchedUsersContainer.style.display = 'grid'; // Use grid layout
    fetchedUsersContainer.style.gridTemplateRows = '1fr 1fr'; // Stack two rows for new and old users
    fetchedUsersContainer.style.height = 'calc(100% - (64px + 0.6em))'; // Set height for main container
    fetchedUsersContainer.style.overflowY = 'auto'; // Enable vertical scrolling if needed

    // Function to create a user container with common styles
    function createUserContainer(className) {
      const userContainer = document.createElement('div');
      userContainer.className = className;

      // Add common CSS styles for grid layout and centering
      userContainer.style.display = 'grid';
      userContainer.style.gridAutoFlow = 'dense'; // Allows items to fill empty spaces
      userContainer.style.gridTemplateColumns = 'repeat(auto-fill, minmax(200px, 1fr))'; // Responsive columns
      userContainer.style.gap = '12px'; // Space between items
      userContainer.style.padding = '24px';
      userContainer.style.height = 'fit-content';

      return userContainer;
    }

    // Create containers for old and new users
    const oldUsersContainer = createUserContainer('old-users');
    const newUsersContainer = createUserContainer('new-users');

    // Function to create a description for user groups
    function createDescription(text, className) {
      const description = document.createElement('span');
      description.className = className;
      description.textContent = text;
      description.style.color = 'bisque';
      description.style.fontFamily = 'Montserrat';
      description.style.fontSize = '1em';
      description.style.margin = '0';
      description.style.padding = '0.4em 0.2em';
      // Make description span all columns
      description.style.gridColumn = '1 / -1';
      description.style.height = 'fit-content';
      return description;
    }

    // Create and style descriptions for old and new users
    const oldUsersDescription = createDescription('Active Users', 'old-users-description'); // Create description for old users
    const newUsersDescription = createDescription('New Registrations', 'new-users-description'); // Create description for new users

    // Append descriptions to their respective containers
    oldUsersContainer.appendChild(oldUsersDescription); // Append description to old users container
    newUsersContainer.appendChild(newUsersDescription); // Append description to new users container

    // Append containers to the fetchedUsersContainer
    fetchedUsersContainer.appendChild(oldUsersContainer);
    fetchedUsersContainer.appendChild(newUsersContainer);

    // Create an array to hold user elements
    const userElements = [];

    // Get current date for comparison
    const currentDate = new Date();

    // Helper function to check if registered date is within the last 24 hours
    const isNewUser = (registered) => {
      const registeredDate = new Date(registered);
      const timeDifference = currentDate - registeredDate; // Difference in milliseconds
      return timeDifference <= 24 * 60 * 60 * 1000; // 24 hours in milliseconds
    };

    // This function creates a user element for the cache panel with detailed user information and metrics.
    const createCachePanelUserElement = (userId, userData) => {
      // Create the main container for the user.
      const userElement = document.createElement('div');
      userElement.className = 'user';
      userElement.style.padding = '0.2em';
      userElement.style.margin = '0.4em 0.2em';
      userElement.style.display = 'grid';
      userElement.style.gridTemplateColumns = 'auto 1fr';
      userElement.style.alignItems = 'center';
      userElement.style.height = 'fit-content';

      // Define base styling for tracked and untracked users.
      const baseStyle = {
        marginLeft: '8px',
        borderRadius: '2px !important'
      };

      // Styles for tracked and untracked users.
      const styles = {
        tracked: { ...baseStyle, color: 'greenyellow', backgroundColor: 'darkgreen', fontWeight: 'bold', padding: '0 6px' },
        untracked: { ...baseStyle, color: 'orange', fontWeight: 'normal' }
      };

      // Helper function to convert styles into a CSS string.
      const generateStylesString = (styles) =>
        Object.entries(styles)
          .map(([key, value]) => `${key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}: ${value}`)
          .join('; ');

      // Choose the appropriate style based on whether the user is tracked.
      const chosenStyles = userData.tracked ? styles.tracked : styles.untracked;

      // Create an avatar container.
      const avatarElement = document.createElement('div');
      avatarElement.className = 'avatar';
      avatarElement.style.marginRight = '8px';

      // Handle avatar URL and display logic.
      const avatarTimestamp = fetchedUsers[userId]?.avatarTimestamp;
      const bigAvatarUrl = `/storage/avatars/${userId}_big.png`;

      if ((avatarTimestamp && avatarTimestamp !== '00') || (userData.avatar && Object.keys(userData.avatar).length > 0)) {
        const finalAvatarUrl = `${bigAvatarUrl}?updated=${avatarTimestamp}`;
        const imgElement = document.createElement('img');
        imgElement.src = finalAvatarUrl;
        imgElement.alt = `${userData.login}'s avatar`;
        imgElement.style.height = '24px';
        imgElement.style.width = '24px';
        imgElement.style.objectFit = 'cover';
        avatarElement.appendChild(imgElement);
      } else {
        // Display a random emoji avatar if no avatar is available.
        avatarElement.style.fontSize = '1.8rem';
        avatarElement.innerHTML = getRandomEmojiAvatar();
      }

      // Create the login element with a link to the user's profile.
      const loginElement = document.createElement('a');
      loginElement.className = 'login';
      loginElement.textContent = userData.login;
      loginElement.href = `https://klavogonki.ru/profile/${userId}`;

      // If the user has visit data, display it.
      if (userData.visits !== undefined) {
        loginElement.innerHTML += `<span style="${generateStylesString(chosenStyles)}">${userData.visits}</span>`;
      }

      // Set styles and hover behavior for the login link.
      loginElement.style.setProperty('color', 'skyblue', 'important');
      loginElement.style.textDecoration = 'none';
      loginElement.style.fontFamily = 'Montserrat';
      loginElement.style.transition = 'color 0.3s ease';

      loginElement.addEventListener('mouseover', () => {
        loginElement.style.setProperty('color', 'cornsilk', 'important');
      });

      loginElement.addEventListener('mouseout', () => {
        loginElement.style.setProperty('color', 'skyblue', 'important');
      });

      // Load a given URL into the iframe.
      const loadProfileInIframe = (url) => {
        // Create an iframe to display user profile pages.
        const profileIframe = document.createElement('iframe');
        profileIframe.classList.add('profile-iframe-container');
        profileIframe.style.border = 'none';
        profileIframe.src = url;
        profileIframe.style.display = 'flex';
        profileIframe.style.position = 'fixed';
        profileIframe.style.zIndex = '999';
        profileIframe.style.width = '75vw';
        profileIframe.style.minWidth = '1000px';
        profileIframe.style.height = '80vh';
        profileIframe.style.top = '48.5vh';
        profileIframe.style.left = '50vw';
        profileIframe.style.transform = 'translate(-50%, -50%)';

        document.body.appendChild(profileIframe); // Append iframe to the document body.

        // Function to handle the space key press.
        const removeIframe = () => {
          profileIframe.remove(); // Remove the iframe from the document.
          document.removeEventListener('keydown', handleSpaceKey); // Clean up the event listener from the document.
        };

        const handleSpaceKey = (event) => {
          if (event.code === 'Space') {
            event.preventDefault(); // Prevent scroll caused by the space key.
            removeIframe(); // Call the remove function.
          }
        };

        // Add event listener for the 'keydown' event to listen for space key presses.
        document.addEventListener('keydown', handleSpaceKey);

        // Prevent space key scrolling inside the iframe.
        profileIframe.onload = () => {
          // Add event listener for the iframe's contentWindow to listen for space key presses.
          profileIframe.contentWindow.addEventListener('keydown', handleSpaceKey);

          // Add event listener for double click to remove the iframe.
          profileIframe.contentWindow.addEventListener('dblclick', removeIframe);

          // Create the MutationObserver to watch for specific elements being removed.
          const observer = new MutationObserver((mutations) => {
            if (mutations.some(mutation =>
              Array.from(mutation.removedNodes).some(node =>
                node.nodeType === Node.ELEMENT_NODE &&
                (node.classList.contains('dimming-background') || node.classList.contains('cached-users-panel'))
              )
            )) {
              removeIframe(); // Call the remove function.
              observer.disconnect(); // Stop observing.
            }
          });

          // Start observing the document body for changes.
          observer.observe(document.body, { childList: true, subtree: true });
        };
      };

      // Load the user's profile in the iframe on click.
      loginElement.addEventListener('click', (event) => {
        event.preventDefault(); // Prevent page navigation.
        loadProfileInIframe(loginElement.href);
      });

      // Helper function to create metric elements (speed, rating, etc.).
      const createMetricElement = (className, color, icon, value, title, url) => {
        const element = document.createElement('span');
        element.className = className;
        element.style.color = color;
        element.innerHTML = `${icon}${value || 0}&nbsp;&nbsp;`;
        element.title = title;
        element.style.cursor = 'pointer';
        element.addEventListener('click', () => loadProfileInIframe(url));
        return element;
      };

      // Create individual metric elements for the user.
      const bestSpeedElement = createMetricElement(
        'best-speed',
        'cyan',
        '🚀',
        userData.bestSpeed,
        'Best speed',
        `https://klavogonki.ru/u/#/${userId}/stats/normal/`
      );

      const ratingLevelElement = createMetricElement(
        'rating-level',
        'gold',
        '⭐',
        userData.ratingLevel,
        'Rating level',
        `https://klavogonki.ru/top/rating/today?s=${userData.login}`
      );

      const carsElement = createMetricElement(
        'cars-count',
        'lightblue',
        '🚖',
        userData.cars,
        'Cars count',
        `https://klavogonki.ru/u/#/${userId}/car/`
      );

      const friendsElement = createMetricElement(
        'friends-count',
        'lightgreen',
        '🤝',
        userData.friends,
        'Friends count',
        `https://klavogonki.ru/u/#/${userId}/friends/list/`
      );

      // Group all metrics into a container.
      const userMetrics = document.createElement('div');
      userMetrics.className = 'user-metrics';
      userMetrics.style.marginTop = '4px';
      userMetrics.style.gridColumn = 'span 2';
      userMetrics.append(bestSpeedElement, ratingLevelElement, carsElement, friendsElement);

      // Create the user data container and append login and rank elements.
      const userDataElement = document.createElement('div');
      userDataElement.className = 'user-data';
      userDataElement.appendChild(loginElement);

      const rankElement = document.createElement('div');
      rankElement.className = 'rank';
      rankElement.textContent = userData.rank || 'N/A';
      rankElement.style.color = rankColors[userData.rank] || 'white';
      rankElement.style.padding = '2px 0';
      userDataElement.appendChild(rankElement);

      // Add a registered date element with hover behavior.
      const registeredElement = document.createElement('div');
      registeredElement.className = 'registered';
      registeredElement.textContent = userData.registered || 'N/A';
      registeredElement.style.color = 'cadetblue';
      registeredElement.style.fontSize = '12px';

      let hoverTimer;
      const originalContent = registeredElement.textContent;

      registeredElement.addEventListener('mouseover', () => {
        clearTimeout(hoverTimer);
        hoverTimer = setTimeout(() => {
          registeredElement.textContent = calculateTimeOnSite(userData.registered);
        }, 300);
      });

      registeredElement.addEventListener('mouseout', () => {
        clearTimeout(hoverTimer);
        registeredElement.textContent = originalContent;
      });

      // Append registered element to user data and user data to user element.
      userDataElement.appendChild(registeredElement);
      userElement.append(avatarElement, userDataElement, userMetrics);

      // Return the created user element and its relevant data.
      return {
        userElement,
        order: rankOrder[userData.rank] || 10,
        bestSpeed: userData.bestSpeed || 0,
        registered: userData.registered
      };
    };

    // Check if the current mode is 'cache'
    if (localStorage.getItem('cachePanelSearchMode') === 'cache') {
      // Iterate through each user
      Object.keys(users).forEach(async (userId) => {
        const userData = users[userId];
        const userElementData = createCachePanelUserElement(userId, userData);
        userElements.push(userElementData);
      });

      // Sort userElements by rank and best speed
      userElements.sort((a, b) =>
        // First by rank, then by speed
        a.order !== b.order ? a.order - b.order : b.bestSpeed - a.bestSpeed
      );

      // Distribute userElements into new or old users containers
      userElements.forEach(({ userElement, registered }) => {
        // Choose container
        const targetContainer = isNewUser(registered) ? newUsersContainer : oldUsersContainer;
        // Append userElement
        targetContainer.appendChild(userElement);
      });
    }

    // Append the panel-header container to the cached-users-panel
    cachedUsersPanel.appendChild(panelHeaderContainer);
    // Append the fetched-users container to the cached-users-panel
    cachedUsersPanel.appendChild(fetchedUsersContainer);

    // Append the cached-users-panel to the body
    document.body.appendChild(cachedUsersPanel);

    // Fade in the cached users panel
    fadeTargetElement(cachedUsersPanel, 'show');

    // Show the dimming background
    fadeDimmingElement('show');

    // Function to update the remaining time
    function updateRemainingTime() {
      const lastClearTime = localStorage.getItem('lastClearTime');
      const nextClearTime = localStorage.getItem('nextClearTime');
      const dropTimeExpiration = document.querySelector('.drop-time-expiration');

      if (lastClearTime && nextClearTime && dropTimeExpiration) {
        const currentTime = new Date().getTime();

        // Calculate the remaining time until the next cache clear
        const remainingTime = nextClearTime - currentTime;

        // If remaining time is zero or less, execute the refreshFetchedUsers function
        remainingTime <= 0
          ? refreshFetchedUsers(true, cacheRefreshThresholdHours)
          : updatedropTimeExpiration(dropTimeExpiration, remainingTime);
      }
    }

    // Create a mapping of seconds to clock emojis
    const emojiMap = {
      0: '🕛',
      5: '🕐',
      10: '🕑',
      15: '🕒',
      20: '🕓',
      25: '🕔',
      30: '🕕',
      35: '🕖',
      40: '🕗',
      45: '🕘',
      50: '🕙',
      55: '🕚',
    };

    // Function to update the drop-time-expiration span
    function updatedropTimeExpiration(dropTimeExpiration, remainingTime) {
      // Calculate hours, minutes, and seconds
      const hours = String(Math.floor(remainingTime / (60 * 60 * 1000))).padStart(2, '0');
      const minutes = String(Math.floor((remainingTime % (60 * 60 * 1000)) / (60 * 1000))).padStart(2, '0');
      const seconds = String(Math.floor((remainingTime % (60 * 1000)) / 1000)).padStart(2, '0');

      // Create the formatted time string
      const remainingTimeString = `${hours}:${minutes}:${seconds}`;

      // Determine the current seconds
      const parsedSeconds = parseInt(seconds, 10);

      // Use the parsed seconds to find the emoji index, moving one forward
      const nextInterval = Math.ceil(parsedSeconds / 5) * 5; // Move to the next 5-second mark
      const currentEmoji = emojiMap[nextInterval] || emojiMap[0]; // Default to 00 if not found

      // Update the drop-time-expiration span with the time and emoji
      dropTimeExpiration.textContent = `${remainingTimeString} ${currentEmoji}`;
    }

    // Call the function to update the remaining time every second
    setInterval(updateRemainingTime, 1000);

    // Initial update
    updateRemainingTime();
  }

  // Global function to smoothly hide and remove the cachedUsersPanel
  function hideCachePanel() {
    const cachedUsersPanel = document.querySelector('.cached-users-panel');

    if (cachedUsersPanel) {
      // Call the fade function for the cachedUsersPanel
      fadeTargetElement(cachedUsersPanel, 'hide');
      // Call the fade function for the dimming element
      fadeDimmingElement('hide');
    }
  }

  // NEW CHAT CACHE CONTROL PANEL (END)


  // NEW CHAT USER LIST (START)

  // Add styles for hover effects dynamically to the head
  const newChatUserListStyles = document.createElement('style');

  // Apply class to the style element
  newChatUserListStyles.classList.add('new_chat_user_list');

  newChatUserListStyles.innerHTML = `
    #chat-general .userlist-content {
      opacity: 0;
    }

    #chat-general .smile-tab {
      background-color: ${((c) => c[0] == '#' ? c : '#' + c.match(/\d+/g).map(Number).map(x => x.toString(16).padStart(2, '0')).join(''))
      (getComputedStyle(document.querySelector('.chat .messages')).backgroundColor)};
      position: relative;
      z-index: 1;
    }

    .chat-user-list {
        display: flex;
        flex-direction: column;
        position: absolute;
        top: 20px;
        padding-top: 8px;
        width: 200px;
        height: 94%;
        overflow-y: auto;
        overflow-x: hidden;
        background-color: ${((c) => c[0] == '#' ? c : '#' + c.match(/\d+/g).map(Number).map(x => x.toString(16).padStart(2, '0')).join(''))
      (getComputedStyle(document.querySelector('.chat .messages')).backgroundColor)};
    }

    .chat-user-list [class^="rank-group"] {
        display: flex;
        flex-direction: column;
    }

    .chat-user-list [class^="user"] {
        display: inline-flex;
        margin: 2px 0;
    }

    .chat-user-list .avatar {
        width: 24px;
        height: 24px;
        display: inline-flex;
    }

    .chat-user-list .avatar img,
    .fetched-users .avatar img {
        transition: transform 0.3s;
        transform-origin: left;
    }

    .chat-user-list .avatar img:hover,
    .fetched-users .avatar img:hover {
        transform: scale(2);
    }

    .chat-user-list .name {
        text-decoration: none;
        display: inline-flex;
        width: auto;
        height: 24px;
        line-height: 24px;
        padding: 0 8px;
        max-width: 124px;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .chat-user-list .name:hover {
        text-decoration: underline;
    }

    .chat-user-list .profile,
    .chat-user-list .tracked,
    .chat-user-list .ignored,
    .chat-user-list .moderator {
        display: inline-flex;
        width: 24px;
        height: 24px;
        justify-content: center;
        align-items: center;
    }

    svg.feather-meh,
    svg.feather-smile,
    svg.feather-frown {
        stroke: #A47C5E;
    }

    /* Common rotation animation */
    @keyframes rotateProfileIconAnimation {
        0% {
            transform: rotate(0deg) scale(1);
            transition-timing-function: ease-in-out;
        }
        50% {
            transform: rotate(180deg) scale(1.2);
            transition-timing-function: linear;
        }
        100% {
            transform: rotate(360deg) scale(1);
        }
    }

    /* Animation for online status */
    .chat-user-list svg.online {
        stroke: lightgreen;
        animation: rotateProfileIconAnimation 1s forwards;
    }

    /* Animation for offline status */
    .chat-user-list svg.offline {
        stroke: chocolate;
        animation: rotateProfileIconAnimation 1s forwards;
    }

    /* Shake Profile Icon Animation for Small Icons */
    @keyframes shakeProfileIconAnimation {
        0% { transform: translate(0.5px, 0.5px) rotate(0deg); }
        10% { transform: translate(-0.5px, -1px) rotate(-1deg); }
        20% { transform: translate(-1.5px, 0px) rotate(1deg); }
        30% { transform: translate(1.5px, 1px) rotate(0deg); }
        40% { transform: translate(0.5px, -0.5px) rotate(1deg); }
        50% { transform: translate(-0.5px, 1px) rotate(-1deg); }
        60% { transform: translate(-1.5px, 0.5px) rotate(0deg); }
        70% { transform: translate(1.5px, 0.5px) rotate(-1deg); }
        80% { transform: translate(-0.5px, -0.5px) rotate(1deg); }
        90% { transform: translate(0.5px, 1px) rotate(0deg); }
        100% { transform: translate(0.5px, -1px) rotate(-1deg); }
    }

    /* Apply shake animation to sto profile svg iconkwith the class eProfileIconAnimation */
    .chat-user-list svg.online:hover,
    .chat-user-list svg.offline:hover {
      animation: shakeProfileIconAnimation 0.5s linear infinite;
    }
`;

  document.head.appendChild(newChatUserListStyles);

  // Function to validate required user data
  function validateUserData(user) {
    const requiredFields = ['rank', 'login', 'registered', 'bestSpeed', 'ratingLevel', 'friends', 'cars', 'avatarTimestamp'];
    return user && typeof user === 'object' && requiredFields.every(field => user?.[field] !== undefined);
  }

  // Function to get profile summary and registration data
  async function getUserProfileData(userId, useLocalStorage = true) {
    return new Promise(async (resolve, reject) => {
      let cachedUserInfo = useLocalStorage ? JSON.parse(localStorage.getItem('fetchedUsers')) || {} : {};
      const user = cachedUserInfo[userId];

      // Validate if user data exists and has the required properties
      if (useLocalStorage && validateUserData(user)) {
        // If all data is cached, resolve with the cached data
        resolve({
          rank: user.rank,
          login: user.login,
          registeredDate: user.registered,
          bestSpeed: user.bestSpeed,
          ratingLevel: user.ratingLevel,
          friends: user.friends, // Use cached friends count
          cars: user.cars, // Use cached cars count
          avatar: user.avatar, // Get avatar availability state
          avatarTimestamp: user.avatarTimestamp // Cached avatar timestamp
        });
      } else {
        try {
          // Fetch profile summary and registered date
          const summaryApiUrl = `https://klavogonki.ru/api/profile/get-summary?id=${userId}`;
          const profileApiUrl = `https://klavogonki.ru/api/profile/get-index-data?userId=${userId}`;

          // Fetch both profile summary and registration data in parallel
          const [summaryResponse, profileResponse] = await Promise.all([
            fetch(summaryApiUrl),
            fetch(profileApiUrl),
          ]);

          // Check if both responses are successful
          if (!summaryResponse.ok || !profileResponse.ok) {
            throw new Error('Failed to fetch data from one of the APIs.');
          }

          const summaryData = await summaryResponse.json();
          const profileData = await profileResponse.json();

          if (
            summaryData?.user?.login &&
            summaryData.title &&
            profileData?.stats?.registered
          ) {
            // Extract the relevant data
            const rank = summaryData.title;
            const login = summaryData.user.login;
            const registered = profileData.stats.registered.sec
              ? convertSecondsToDate(profileData.stats.registered.sec)
              : 'Invalid Date';

            // Extract new fields
            const bestSpeed = profileData.stats.best_speed || 0; // Default to 0 if undefined
            const ratingLevel = profileData.stats.rating_level || 0; // Default to 0 if undefined
            const friends = profileData.stats.friends_cnt || 0; // Extract friends count
            const cars = profileData.stats.cars_cnt || 0; // Extract cars count

            // Extract sec and usec from user.avatar, with null check
            const avatar = summaryData.user?.avatar || null; // Default to null if undefined or not present
            const sec = summaryData.user.avatar?.sec || 0; // Default to 0 if undefined or null
            const usec = summaryData.user.avatar?.usec || 0; // Default to 0 if undefined or null
            const avatarTimestamp = convertToUpdatedTimestamp(sec, usec); // Combine sec and usec to get avatar timestamp

            // Cache the fetched data if useLocalStorage is true, excluding the avatar
            if (useLocalStorage) {
              cachedUserInfo[userId] = {
                rank: rank,
                login: login,
                registered: registered,
                bestSpeed: bestSpeed,
                ratingLevel: ratingLevel,
                friends: friends, // Cache friends count
                cars: cars, // Cache cars count
                avatar: avatar,
                avatarTimestamp: avatarTimestamp // Cache avatar timestamp
              };

              // Update localStorage with the new cached data
              localStorage.setItem('fetchedUsers', JSON.stringify(cachedUserInfo));
            }

            // Resolve with the combined data
            resolve({
              rank: rank,
              login: login,
              registeredDate: registered,
              bestSpeed: bestSpeed,
              ratingLevel: ratingLevel,
              friends: friends,
              cars: cars,
              avatar: avatar, // Return avatar for current session
              avatarTimestamp: avatarTimestamp // Include avatar timestamp in the result
            });
          } else {
            throw new Error('Invalid data format received from the API.');
          }
        } catch (error) {
          console.error(`Error fetching user profile data for ${userId}:`, error);
          reject(error);
        }
      }
    });
  }

  // Function to convert seconds to a human-readable date format
  function convertSecondsToDate(seconds) {
    const date = new Date(seconds * 1000);
    return date.toISOString().slice(0, 19).replace('T', ' '); // Converts to 'YYYY-MM-DD HH:mm:ss' format
  }

  // Function to convert sec and usec to the 'updated' timestamp
  function convertToUpdatedTimestamp(sec, usec) {
    // Create the full timestamp by combining sec and usec (in microseconds)
    return sec.toString() + Math.floor(usec / 1000).toString();
  }

  // Helper to fetch JSON and validate response
  async function fetchJSON(url) {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`Failed to fetch ${url}`);
    return response.json();
  }

  // Helper function to get user IDs by username via the search API
  async function getUserIdsByName(userName) {
    const searchApiUrl = `https://klavogonki.ru/api/profile/search-users?query=${userName}`;
    const searchResults = await fetchJSON(searchApiUrl);

    const foundUsers = searchResults.all; // Get all search results
    if (!foundUsers || foundUsers.length === 0) throw new Error(`User ${userName} not found.`);

    // Return an array of user IDs
    return foundUsers.map(user => user.id);
  }

  // Function to calculate time spent on the site
  function calculateTimeOnSite(registeredDate) {
    const totalSeconds = Math.floor((new Date() - new Date(registeredDate)) / 1000);
    const years = Math.floor(totalSeconds / (365 * 24 * 60 * 60));
    const months = Math.floor((totalSeconds % (365 * 24 * 60 * 60)) / (30.44 * 24 * 60 * 60));
    const days = Math.floor((totalSeconds % (30.44 * 24 * 60 * 60)) / (24 * 60 * 60));
    const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
    const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
    const seconds = totalSeconds % 60;

    const timeComponents = [];

    if (years > 0) {
      timeComponents.push(`${years} year${years > 1 ? 's' : ''}`);
      if (months > 0) timeComponents.push(`${months} month${months > 1 ? 's' : ''}`);
    } else if (months > 1 || (months === 1 && days > 0)) {
      timeComponents.push(`${months} month${months > 1 ? 's' : ''}`);
      if (days > 0) timeComponents.push(`${days} day${days > 1 ? 's' : ''}`);
    } else if (days > 0) {
      timeComponents.push(`${days} day${days > 1 ? 's' : ''}`);
      if (hours > 0) timeComponents.push(`${hours} hour${hours > 1 ? 's' : ''}`);
      if (minutes > 0) timeComponents.push(`${minutes} minute${minutes > 1 ? 's' : ''}`);
    } else if (hours > 0) {
      timeComponents.push(`${hours} hour${hours > 1 ? 's' : ''}`);
      if (minutes > 0) timeComponents.push(`${minutes} minute${minutes > 1 ? 's' : ''}`);
    } else if (minutes > 0) {
      timeComponents.push(`${minutes} minute${minutes > 1 ? 's' : ''}`);
      if (seconds > 0) timeComponents.push(`${seconds} second${seconds > 1 ? 's' : ''}`);
    } else {
      timeComponents.push(`${seconds} second${seconds > 1 ? 's' : ''}`);
    }

    return timeComponents.filter(Boolean).join(' '); // Filter out empty strings and join components
  }

  // Function to get rank color based on status title
  function getRankColor(mainTitle) {
    const statusColors = {
      'Экстракибер': '#06B4E9', // Light Blue
      'Кибергонщик': '#5681ff', // Medium Blue
      'Супермен': '#B543F5', // Purple
      'Маньяк': '#DA0543', // Red
      'Гонщик': '#FF8C00', // Orange
      'Профи': '#C1AA00', // Yellow
      'Таксист': '#2DAB4F', // Green
      'Любитель': '#61B5B3', // Light Cyan
      'Новичок': '#AFAFAF' // Grey
    };

    return statusColors[mainTitle] || '#000000'; // Default to black color if status title not found
  }

  // Function to get rank class based on status title in English
  function getRankClass(mainTitle) {
    const statusClasses = {
      'Экстракибер': 'extra',
      'Кибергонщик': 'cyber',
      'Супермен': 'superman',
      'Маньяк': 'maniac',
      'Гонщик': 'racer',
      'Профи': 'profi',
      'Таксист': 'driver',
      'Любитель': 'amateur',
      'Новичок': 'newbie'
    };

    const defaultClass = 'unknown';
    const rankClass = statusClasses[mainTitle] || defaultClass;

    if (rankClass === defaultClass) {
      console.log(`Class not found for status title: ${mainTitle}. Using default class: ${defaultClass}`);
    }

    return rankClass;
  }

  // Function to handle private message
  function insertPrivate(userId) {
    const userName = document.querySelector(`.name[data-user="${userId}"]`).textContent;
    const message = `<${userName}>`;

    const textElement = document.querySelector('.messages .text');
    textElement.value = message;

    textElement.focus();
    textElement.selectionEnd = textElement.value.length;

    console.log(`Setting private message to: ${message}`);
  }

  const infoSVG = (userId, isRevoked) => {
    const statusClass = isRevoked ? 'offline' : 'online';

    return `
        <svg xmlns="http://www.w3.org/2000/svg"
            width="14"
            height="14"
            viewBox="0 0 24 24"
            fill="none"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="feather feather-info ${statusClass}">
            <circle cx="12" cy="12" r="10"></circle>
            <line x1="12" y1="16" x2="12" y2="12"></line>
            <line x1="12" y1="8" x2="12.01" y2="8"></line>
        </svg>`;
  };

  // Inline SVG source for the "meh" icon
  const mehSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke-width="1.4"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-meh">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="8" y1="15" x2="16" y2="15"></line>
      <line x1="9" y1="9" x2="9.01" y2="9"></line>
      <line x1="15" y1="9" x2="15.01" y2="9"></line>
  </svg>`;

  // Inline SVG source for the "smile" icon
  const smileSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke-width="1.4"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-smile">
      <circle cx="12" cy="12" r="10"></circle>
      <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
      <line x1="9" y1="9" x2="9.01" y2="9"></line>
      <line x1="15" y1="9" x2="15.01" y2="9"></line>
  </svg>`;

  // Inline SVG source for the "frown" icon
  const frownSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke-width="1.4"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-frown">
      <circle cx="12" cy="12" r="10"></circle>
      <path d="M16 16s-1.5-2-4-2-4 2-4 2"></path>
      <line x1="9" y1="9" x2="9.01" y2="9"></line>
      <line x1="15" y1="9" x2="15.01" y2="9"></line>
  </svg>`;

  // SVG icon for the moderator with gradient
  const moderatorSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="18"
        height="18"
        viewBox="0 0 24 24"
        fill="url(#moderatorGradient)"  <!-- Use a gradient fill -->
        stroke="none"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-shield">
        <!-- Define the gradient -->
        <defs>
            <linearGradient id="moderatorGradient" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0%" style="stop-color: gold; stop-opacity: 1" />
                <stop offset="100%" style="stop-color: darkorange; stop-opacity: 1" />
            </linearGradient>
        </defs>
        <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
    </svg>`;

  // SVG icon for the tracked with gradient stroke
  const trackedSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
       width="16"
       height="16"
       viewBox="0 0 24 24"
       fill="url(#trackedGradient)"  <!-- Use a gradient fill -->
       class="feather feather-star">
      <!-- Define the gradient for the fill -->
      <defs>
        <linearGradient id="trackedGradient" x1="0%" y1="0%" x2="0%" y2="100%">
            <stop offset="0%" style="stop-color: LightSkyBlue; stop-opacity: 1" />
            <stop offset="100%" style="stop-color: DeepSkyBlue; stop-opacity: 1" />
        </linearGradient>
      </defs>
      <!-- Use the gradient for the fill -->
      <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
               stroke="url(#trackedGradient)"
               stroke-width="2"
               stroke-linecap="round"
               stroke-linejoin="round"
      ></polygon>
  </svg>`;

  // SVG icon for ignored users
  const ignoredSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="16"
      height="16"
      viewBox="0 0 24 24"
      fill="none"
      stroke="lightsalmon"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-slash">
    <circle cx="12" cy="12" r="10"></circle>
    <line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line>
  </svg>`;

  // Helper function to get a random SVG
  // function getRandomIconSVG() {
  //   const svgs = [mehSVG, smileSVG, frownSVG];
  //   const randomIndex = Math.floor(Math.random() * svgs.length);
  //   return svgs[randomIndex];
  // }

  // Variable to store the last selected emoji
  let lastEmojiAvatar = null;

  // Helper function to get a random emoji avatar
  function getRandomEmojiAvatar() {
    let newEmoji;
    do {
      newEmoji = emojiFaces[Math.floor(Math.random() * emojiFaces.length)];
    } while (newEmoji === lastEmojiAvatar);

    lastEmojiAvatar = newEmoji;
    return newEmoji;
  }

  const emojiFaces = [
    // People Emojis
    '😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆',
    '😉', '😊', '😋', '😎', '😏', '😐', '😑', '😒',
    '😓', '😔', '😕', '😖', '😗', '😘', '😙', '😚',
    '😜', '😝', '😛', '🤑', '🤗', '🤔', '🤐', '🤨',
    '😏', '😣', '😥', '😮', '🤯', '😳', '😱', '😨',
    '😰', '😢', '😥', '😓', '🤪', '😵', '😲', '😵',
    '🤤', '😷', '🤒', '🤕', '🤢', '🤧', '😇', '🥳',
    '🥺', '😏', '😬', '😴', '😌', '🤥', '😼', '😺',

    // Animal Emojis
    '🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼',
    '🐨', '🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐵',
    '🙈', '🙉', '🙊', '🐔', '🐧', '🐦', '🐤', '🐣',
    '🦅', '🦆', '🦇', '🐢', '🐍', '🦎', '🦧', '🦄',
    '🐉', '🐲', '🦙', '🐕', '🐩', '🐾'
  ];

  // Array to store user IDs and their status titles
  let fetchedUsers = JSON.parse(localStorage.getItem('fetchedUsers')) || {};

  // Function to create a user element with avatar, name, and profile link based on user details
  function createUserChatElement(userId, mainTitle, userName, isRevoked) {
    const avatarTimestamp = fetchedUsers[userId]?.avatarTimestamp;

    // Ensure the bigAvatarUrl is only constructed if avatarTimestamp is not '00'
    const bigAvatarUrl = avatarTimestamp !== '00' ? `/storage/avatars/${userId}_big.png?updated=${avatarTimestamp}` : '';

    const newUserElement = document.createElement('div');
    const rankClass = getRankClass(mainTitle);
    newUserElement.classList.add(`user${userId}`, rankClass); // Assign the rank class

    const newAvatarElement = document.createElement('div');
    newAvatarElement.classList.add('avatar');

    // Only create and append an image element if avatarTimestamp is not '00'
    if (avatarTimestamp !== '00') {
      const avatarImage = document.createElement('img');
      avatarImage.src = bigAvatarUrl;
      newAvatarElement.appendChild(avatarImage);
    } else {
      // Insert a random SVG icon instead of an image when avatarTimestamp is '00'
      // newAvatarElement.innerHTML = getRandomIconSVG();
      newAvatarElement.style.fontSize = '1.8rem';
      newAvatarElement.innerHTML = getRandomEmojiAvatar();
    }

    const newNameElement = document.createElement('a');
    newNameElement.classList.add('name');
    newNameElement.title = 'Написать в приват';
    newNameElement.dataset.user = userId;
    newNameElement.textContent = userName;

    const rankColor = getRankColor(mainTitle);
    newNameElement.style.setProperty('color', rankColor, 'important');

    const newProfileElement = document.createElement('a');
    newProfileElement.classList.add('profile');
    newProfileElement.title = 'Профиль';
    newProfileElement.target = '_blank';
    newProfileElement.href = `/profile/${userId}/`;
    newProfileElement.innerHTML = infoSVG(userId, isRevoked); // Update this line

    newNameElement.addEventListener('click', function () {
      insertPrivate(userId);
    });

    newUserElement.appendChild(newAvatarElement);
    newUserElement.appendChild(newNameElement);
    newUserElement.appendChild(newProfileElement);

    // Check if there is a user in 'usersToTrack' array by their name and state
    const userToTrack = usersToTrack.find((user) =>
      user.name === userName && user.state === 'thawed'
    );

    if (userToTrack) {
      const trackedIcon = document.createElement('div');
      trackedIcon.title = 'Tracked user';
      trackedIcon.classList.add('tracked');
      trackedIcon.innerHTML = trackedSVG;
      newUserElement.appendChild(trackedIcon);
    }

    // Check if the user is in the ignore list
    const isIgnoredUser = ignored.includes(userName);

    // Create and hide a message element if the user is in ignored
    if (isIgnoredUser) {
      const ignoredIcon = document.createElement('div');
      ignoredIcon.title = 'Ignored user';
      ignoredIcon.classList.add('ignored');
      ignoredIcon.innerHTML = ignoredSVG;
      newUserElement.appendChild(ignoredIcon);
    }

    // Check if there is an <img> element with a src attribute containing the word "moderator" inside the <ins> element
    const hasModeratorIcon = document.querySelector(`.userlist-content ins.user${userId} img[src*="moderator"]`);

    // Check if the user is in the moderator list
    const isModerator = moderator.includes(userName);

    // If a moderator icon is found or the current user is in the moderator array, append the moderator icon.
    if (hasModeratorIcon || isModerator) {
      const moderatorIcon = document.createElement('div');
      moderatorIcon.classList.add('moderator');
      moderatorIcon.innerHTML = moderatorSVG; // Assuming 'moderatorSVG' contains the SVG for the icon
      newUserElement.appendChild(moderatorIcon);
    }

    return newUserElement;
  }

  // Function to update users in the custom chat
  async function refreshUserList(retrievedLogin, actionType) {
    try {
      // Get the original user list container
      const originalUserListContainer = document.querySelector('.userlist-content');

      // Get or create the user list container
      let userListContainer = document.querySelector('.chat-user-list');
      if (!userListContainer) {
        userListContainer = document.createElement('div');
        userListContainer.classList.add('chat-user-list');

        // Find the element with the class "userlist"
        const userlistElement = document.querySelector('.userlist');

        // Append the userListContainer to the userlistElement if found
        if (userlistElement) {
          userlistElement.appendChild(userListContainer);
        }
      }

      // Define the rank order
      const rankOrder = ['extra', 'cyber', 'superman', 'maniac', 'racer', 'profi', 'driver', 'amateur', 'newbie'];

      // Create an object to store subparent elements for each rank class
      const rankSubparents = {};

      // Check if subparent elements already exist, if not, create them
      rankOrder.forEach(rankClass => {
        const existingSubparent = userListContainer.querySelector(`.rank-group-${rankClass}`);
        if (!existingSubparent) {
          rankSubparents[rankClass] = document.createElement('div');
          rankSubparents[rankClass].classList.add(`rank-group-${rankClass}`);
          userListContainer.appendChild(rankSubparents[rankClass]);
        } else {
          rankSubparents[rankClass] = existingSubparent;
        }
      });

      // Create a set to store existing user IDs in the updated user list
      const existingUserIds = new Set();

      // Iterate over each user element in the original user list
      for (const userElement of originalUserListContainer.querySelectorAll('ins')) {
        const nameElement = userElement.querySelector('.name');
        const userId = nameElement.getAttribute('data-user');
        const userName = nameElement.textContent;

        // Check if the user already exists in the updated user list
        if (!existingUserIds.has(userId)) {
          try {
            // Retrieve the user's profile data
            const { rank: mainTitle, login, registeredDate, bestSpeed, ratingLevel, friends, cars, avatarTimestamp } = await getUserProfileData(userId);

            // If the user data is not already stored in the fetchedUsers object
            if (!fetchedUsers[userId]) {
              // Set rank, login, registeredDate, bestSpeed, ratingLevel, friends, cars, and avatarTimestamp
              fetchedUsers[userId] = {
                rank: mainTitle,
                login,
                registered: registeredDate,
                bestSpeed,
                ratingLevel,
                friends,
                cars,
                avatarTimestamp
              };
            } else {
              // Update the user's data
              fetchedUsers[userId].rank = mainTitle;
              fetchedUsers[userId].login = login;
              fetchedUsers[userId].registered = registeredDate;
              fetchedUsers[userId].bestSpeed = bestSpeed;
              fetchedUsers[userId].ratingLevel = ratingLevel;
              fetchedUsers[userId].friends = friends;
              fetchedUsers[userId].cars = cars;
              fetchedUsers[userId].avatarTimestamp = avatarTimestamp;
            }

            // If actionType is 'enter' and retrievedLogin === userName, multiply the visits for the entered user
            if (actionType === 'enter' && retrievedLogin === userName) {
              fetchedUsers[userId].visits = (fetchedUsers[userId].visits || 0) + 1;
              // Check if the user is in the usersToTrack array
              fetchedUsers[userId].tracked = usersToTrack.some(userToTrack => userToTrack.name === retrievedLogin);
            }

            // Check if the user with the same ID already exists in the corresponding rank group
            const existingUserElement = rankSubparents[getRankClass(mainTitle)].querySelector(`.user${userId}`);
            if (!existingUserElement) {
              const newUserElement = createUserChatElement(userId, mainTitle, userName, userElement.classList.contains('revoked'));
              // Add the user to the corresponding rank group
              rankSubparents[getRankClass(mainTitle)].appendChild(newUserElement);
            }

            // Update existing user IDs
            existingUserIds.add(userId);
          } catch (error) {
            console.error(`Error fetching profile summary for user ${userId}:`, error);
          }
        }
      }

      // Additional removal logic based on your provided code
      userListContainer.querySelectorAll('.chat-user-list [class^="user"]').forEach(userElement => {
        const userId = userElement.querySelector('.name').getAttribute('data-user');
        if (!existingUserIds.has(userId)) {
          userElement.remove();
        }
      });

      // Update localStorage outside the if conditions
      localStorage.setItem('fetchedUsers', JSON.stringify(fetchedUsers));

      // Call updateUserCountText to refresh user count display
      updateUserCountText();

    } catch (error) {
      console.error('Error refreshing user list:', error);
    }
  }

  // Helper function to convert time string to single hours
  function convertToSingleHours(timeString) {
    const [hours, minutes = 0, seconds = 0] = timeString.split(':').map(Number);
    return hours + minutes / 60 + seconds / 3600;
  }

  // Global constant for default cache refresh threshold in hours
  const defaultCacheRefreshThresholdHours = 24;

  // Get the value from localStorage
  let storedFresholdTimeKey = localStorage.getItem('cacheRefreshThresholdHours');

  // If the key doesn't exist, set it to the default value
  if (!storedFresholdTimeKey) {
    storedFresholdTimeKey = defaultCacheRefreshThresholdHours;
    localStorage.setItem('cacheRefreshThresholdHours', storedFresholdTimeKey);
  }

  // Convert the value to single hours
  let cacheRefreshThresholdHours = convertToSingleHours(storedFresholdTimeKey);

  // Function to refresh or manually clear fetched users and reset the timer
  // @param {boolean} isManual - If true, clears cache unconditionally; if false, clears based on threshold (default is false)
  // @param {number} thresholdHours - Time threshold in hours for automatic cache clearing (default is 24 hours)
  function refreshFetchedUsers(isManual = false, thresholdHours = 24) {
    // Retrieve the last clear time from localStorage
    const lastClearTime = localStorage.getItem('lastClearTime');
    const timeElapsed = lastClearTime ? (new Date().getTime() - lastClearTime) / (1000 * 60 * 60) : Infinity;

    // If clearing manually or the time threshold has been reached, clear the cache
    if (isManual || timeElapsed >= thresholdHours) {
      // Clear the fetchedUsers from localStorage
      localStorage.removeItem('fetchedUsers');

      // Reset the in-memory fetchedUsers object
      fetchedUsers = {};

      // Reset the timer by updating 'lastClearTime' and 'nextClearTime'
      const nextClearTime = new Date().getTime() + thresholdHours * 60 * 60 * 1000;
      localStorage.setItem('lastClearTime', new Date().getTime().toString());
      localStorage.setItem('nextClearTime', nextClearTime.toString());

      // Optional: Notify the user about the cache clearing
      // const message = isManual
      //   ? `Cache manually cleared. Next clearing time: ${new Date(nextClearTime)}`
      //   : `Cache automatically cleared. Next clearing time: ${new Date(nextClearTime)}`;

      // alert(message);
    }
  }


  // NEW CHAT USER LIST (END)


  // Define reference for chat user list
  const userList = document.querySelector('.userlist-content');

  // Initialize variables to keep track of the current and previous users
  let currentUsers = [];
  let previousUsers = [];
  // Set flag to false to prevent initialization of the notifications
  // About entered and left users on the page load after refreshing the page
  let hasObservedChanges = false;
  let prevUserCountValue = 0;

  // Initialize variables for the user count animation
  let currentTextContent = [];
  let isAnimated = false;

  // Define a constant to set the debounce delay
  const debounceTimeout = 1500;

  // Define a debounce function to limit the rate at which the mutation observer callback is called
  const debounce = (func, delay) => {
    let timeoutId;
    return (...args) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        func.apply(this, args);
      }, delay);
    };
  };

  // Mutation observer to track all the users with only graphical popup notification
  // Also play notification sound "Left" or "Entered" if the one of them is identical from "usersToTrack" array
  // Create a mutation observer to detect when the user list is modified
  const chatUsersObserver = new MutationObserver(debounce((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.type === 'childList') {
        // Get the sound switcher element and check which option is selected
        const soundSwitcher = document.querySelector('#voice, #beep, #silence');
        const isSilence = soundSwitcher && soundSwitcher.id === 'silence';

        // Check if the chat is closed or opened
        const chatHidden = document.querySelector('#chat-wrapper.chat-hidden');
        // Retrieve all users textContent from userList ins elements
        const newUserList = Array.from(userList.children).map(child => child.textContent);

        // Find new users and left users
        const newUsers = newUserList.filter(user => !currentUsers.includes(user));
        const leftUsers = currentUsers.filter(user => !newUserList.includes(user));

        // Retrieve fresh user count length
        const userCountValue = newUserList.length;
        // Retrieve the counter element
        const userCount = document.querySelector('.user-count-indicator');

        // Update grayscale filter
        userCount.style.filter = userCountValue > 0 ? 'none' : 'grayscale(100%)';

        // Check if the user count animation needs to be started only when the chat is not closed
        if (!chatHidden && currentTextContent.length === 0 && newUserList.length > 0 && !isAnimated) {
          isAnimated = true;
          const actualUserCount = newUserList.length;
          const speed = 20; // Change the speed here (in milliseconds)
          let count = 0;
          const userCountIncrement = () => {
            if (count <= actualUserCount) {
              const progress = count / actualUserCount;
              const grayscale = 100 - progress * 100;
              userCount.innerHTML = `${count++}`;
              userCount.style.filter = `grayscale(${grayscale}%)`;
              setTimeout(userCountIncrement, speed);
            } else {
              currentTextContent = Array.from(userList.children).map(child => child.textContent);
              userCount.style.filter = 'none';
              addPulseEffect(userCount);
            }
          };
          setTimeout(userCountIncrement, speed);
        } // Animation END

        // Check if chat is not closed and animation not in progress
        if (!chatHidden && !isAnimated) {
          // Check if the user count has changed and add pulse animation
          if (userCountValue !== prevUserCountValue) {
            addPulseEffect(userCount);
            // Updating the counter element value
            userCount.innerHTML = userCountValue;
          }
        }

        // Check if chat is not closed and animation is not in progress
        if (!chatHidden && hasObservedChanges) {
          newUsers.forEach((newUser) => {
            if (!previousUsers.includes(newUser)) {
              const userGender = getUserGender(newUser) || 'male'; // use 'male' as default
              // Check if the user is in the usersToTrack array for 'enter'
              const isUserToTrackEnter = usersToTrack.some(user =>
                user.name === newUser && user.state === 'thawed'
              );
              const iconType = enterIcon;
              showUserAction(newUser, iconType, true);
              // Pass 'enter' as the action type and the user's login to refreshUserList
              refreshUserList(newUser, "enter");
              // Prevent voice notification if mode is silence
              if (!isSilence && isUserToTrackEnter) {
                userAction(newUser, "enter", userGender);
              }
            }
          });

          leftUsers.forEach((leftUser) => {
            const userGender = getUserGender(leftUser) || 'male'; // use 'male' as default
            // Check if the user is in the usersToTrack array for 'leave'
            const isUserToTrackLeave = usersToTrack.some(user =>
              user.name === leftUser && user.state === 'thawed'
            );
            const iconType = leaveIcon;
            showUserAction(leftUser, iconType, false);
            // Pass 'leave' as the action type and the user's login to refreshUserList
            refreshUserList(leftUser, "leave");
            // Prevent voice notification if mode is silence
            if (!isSilence && isUserToTrackLeave) {
              userAction(leftUser, "leave", userGender);
            }
          });

        } else {
          // Indicator should look deactivated after the chat is closed
          userCount.style.filter = "grayscale(1)";
          userCount.innerHTML = "0";
          // Set flag to true to initialize notifications about entered and left users
          hasObservedChanges = true;
        }

        // Update the previous users and user count
        previousUsers = currentUsers;
        currentUsers = newUserList;
        prevUserCountValue = userCountValue;

      }
    });
  }, debounceTimeout));

  // Start observing the chat user list for changes to notify about them
  chatUsersObserver.observe(userList, { childList: true });

  // Button to close the chat
  const chatCloseButton = document.querySelector('.mostright');

  // Event listener for mostright click event
  chatCloseButton.addEventListener('click', () => {
    // Trigger the logic you want to perform when the mostright button is clicked
    setTimeout(() => {
      // Check if the chat is not closed
      const chatHidden = document.querySelector('#chat-wrapper.chat-hidden');
      if (chatHidden) {
        // Avoid "newMessagesObserver" run the call functions multiple times when the chat opens again
        isInitialized = false;
      } else {
        // Call the function to assign all the removing functionality again after the chat was closed
        executeMessageRemover();
        // Set chat field focus
        setChatFieldFocus();
        // Allow after "N" delay to run the "newMessagesObserver" call functions safely without repeating
        isInitialized = false;
        setTimeout(() => (isInitialized = false), 3000);
      }
    }, 300);
  });

  // Function to restore the chat state based on 'shouldShowPopupMessage' key in localStorage
  function restoreChatState() {
    // Main chat parent wrap element
    const chatMainWrapper = document.querySelector('#chat-fixed-placeholder');

    // Check if the key exists in localStorage
    if ('shouldShowPopupMessage' in localStorage) {
      // Retrieve the value from localStorage
      const shouldShowPopupMessage = JSON.parse(localStorage.getItem('shouldShowPopupMessage'));

      // Set the display property based on the retrieved value
      chatMainWrapper.style.display = shouldShowPopupMessage ? 'none' : 'unset';
    } else {
      // Default to 'none' if the key doesn't exist
      chatMainWrapper.style.display = 'none';
    }
  }

  // Call restoreChatState when needed, for example, on page load
  restoreChatState();

  // Check if the key exists in localStorage
  if (!('shouldShowPopupMessage' in localStorage)) {
    localStorage.setItem('shouldShowPopupMessage', false);
  }

  // Custom chat hider with hotkeys Ctr + Space
  document.addEventListener('keydown', (event) => {
    // Check if Ctrl key and Space key are pressed simultaneously
    if (event.ctrlKey && event.code === 'Space') {
      // Main chat parent wrap element
      const chatMainWrapper = document.querySelector('#chat-fixed-placeholder');
      // Check if the 'style' attribute is present
      const hasStyleAttribute = chatMainWrapper.hasAttribute('style');
      // Check if the 'display' property is set on chatMainWrapper element
      const isDisplayUnset = chatMainWrapper.style.display === 'unset';
      // Popup messages container element
      const popupMessagesContainer = document.querySelector('.popup-messages-container');

      // Toggle the display property
      if (hasStyleAttribute) {
        if (isDisplayUnset) {
          // Set the display property to 'none'
          chatMainWrapper.style.display = 'none';
          localStorage.setItem('shouldShowPopupMessage', true);
        } else {
          // Set the display property to 'unset'
          chatMainWrapper.style.display = 'unset';
          localStorage.setItem('shouldShowPopupMessage', false);

          // Retrieve the chat input field and length popup container based on the current URL
          const { inputField } = retrieveChatElementsByRoomType(); // Use your helper function

          // Check if inputField is valid before focusing
          if (inputField) {
            inputField.focus(); // Set focus to the chat input field
          } else {
            console.error('Input field not found. Cannot set focus.');
          }
        }
      } else {
        // Initial case: Set the display property to 'none'
        chatMainWrapper.style.display = 'none';
        localStorage.setItem('shouldShowPopupMessage', true);
      }

      // Remove the element with class 'popup-messages-container' if it exists and display is 'unset'
      if (popupMessagesContainer && hasStyleAttribute && isDisplayUnset) {
        popupMessagesContainer.remove();
      }
    }
  });

  // EVERY NEW MESSAGE READER

  // Initialize the variable to keep track of the last username seen
  let lastUsername = null;

  // Set the flag as false for the mention beep sound to trigger at first usual beep sound for usual messages
  let isMention = false;

  // Function to check if a username is mentioned in the message
  function isMentionForMe(message) {
    const messageLowercase = message.toLowerCase();
    return mentionKeywords.some(keyword => messageLowercase.includes(keyword.toLowerCase()));
  }

  // Function to replace username mentions with their respective pronunciations
  function replaceWithPronunciation(text) {
    if (text === null) {
      return text;
    }

    const replaceUsername = (username) => {
      const user = usersToTrack.find(user => user.name === username);
      return user ? user.pronunciation : username;
    }

    const pattern = new RegExp(usersToTrack.map(user => user.name).join('|'), 'g');
    return text.replace(pattern, replaceUsername);
  }

  // Function what will highlight every mention word in the mention message only
  function highlightMentionWords() {
    // Get the container for all chat messages
    const messagesContainer = document.querySelector('.messages-content div');
    // Get all the message elements from messages container
    const messages = messagesContainer.querySelectorAll('.messages-content div p');

    // Loop through each chat message element
    messages.forEach((message) => {
      // Loop through each text node inside the message element
      Array.from(message.childNodes).forEach((node) => {
        if (node.nodeType === Node.TEXT_NODE) {
          // Split the text node content into words
          const regex = /[\s]+|[^\s\wа-яА-ЯёЁ]+|[\wа-яА-ЯёЁ]+/g;
          const words = node.textContent.match(regex); // Split using the regex

          // Create a new fragment to hold the new nodes
          const fragment = document.createDocumentFragment();

          // Loop through each word in the text node
          words.forEach((word) => {
            // Check if the word is included in the "mentionKeywords" array (case insensitive)
            if (mentionKeywords.map(alias => alias.toLowerCase()).includes(word.toLowerCase())) {
              // Create a new <span> element with the mention class
              const mentionHighlight = document.createElement('span');
              mentionHighlight.classList.add('mention');
              mentionHighlight.textContent = word;

              // Highlight styles
              mentionHighlight.style.color = '#83cf40';
              mentionHighlight.style.backgroundColor = '#2b4317';
              mentionHighlight.style.border = '1px solid #4b7328';
              mentionHighlight.style.padding = '2px';
              mentionHighlight.style.display = 'inline-flex';

              // Append the new <span> element to the fragment
              fragment.appendChild(mentionHighlight);
            } else {
              // Check if the word is already inside a mention span
              const span = document.createElement('span');
              span.innerHTML = word;
              if (span.querySelector('.mention')) {
                // If it is, simply append the word to the fragment
                fragment.appendChild(word);
              } else {
                // If it isn't, create a new text node with the word
                const textNode = document.createTextNode(word);

                // Append the new text node to the fragment
                fragment.appendChild(textNode);
              }
            }
          });

          // Replace the original text node with the new fragment
          node.parentNode.replaceChild(fragment, node);
        }
      });
    });
  }

  const rgbToHsl = (r, g, b) => {
    r /= 255;
    g /= 255;
    b /= 255;

    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    let h, s, l = (max + min) / 2;

    if (max === min) {
      h = s = 0; // Achromatic
    } else {
      const delta = max - min;
      s = l < 0.5 ? delta / (max + min) : delta / (2 - max - min);
      h = (
        max === r
          ? (g - b) / delta + (g < b ? 6 : 0)
          : max === g
            ? (b - r) / delta + 2
            : (r - g) / delta + 4
      ) / 6;
    }

    h = Math.round(h * 360); // Convert to degrees
    s = Math.min(Math.round(s * 100), 90); // Cap saturation at 90
    l = Math.round(l * 100); // Convert lightness to 0–100

    // Adjust hue to allow only 0–230 and 280–360 ranges
    if (h > 215 && h < 280) {
      h = h < 255 ? 215 : 280; // Shift to nearest valid range
    }

    return { h, s, l };
  };


  const hslToRgb = (h, s, l) => {
    s /= 100; l /= 100;
    let r, g, b;
    if (s === 0) r = g = b = l * 255; // Achromatic
    else {
      const q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
      const hue2rgb = (p, q, t) => {
        if (t < 0) t += 1;
        if (t > 1) t -= 1;
        return t < 1 / 6 ? p + (q - p) * 6 * t :
          t < 1 / 2 ? q :
            t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 :
              p;
      };
      r = Math.round(hue2rgb(p, q, h / 360 + 1 / 3) * 255);
      g = Math.round(hue2rgb(p, q, h / 360) * 255);
      b = Math.round(hue2rgb(p, q, h / 360 - 1 / 3) * 255);
    }
    return `rgb(${r}, ${g}, ${b})`;
  };

  // Normalize chat username color to be readable in the personal messages panel
  function normalizeUsernameColor(initialColor) {
    const [r, g, b] = initialColor.match(/\d+/g).map(Number);
    const { h, s, l } = rgbToHsl(r, g, b);

    // Adjust lightness to ensure it's at least 50
    const normalizedLightness = l < 50 ? 50 : l;
    const finalColor = hslToRgb(h, s, normalizedLightness);

    // Round the RGB values in one go
    return finalColor;
  }

  // Function to get the cleaned text content of the latest message with username prefix
  function getLatestMessageData() {
    // Select the last <p> element specifically
    const messageElement = document.querySelector('.messages-content div p:last-of-type');
    // Return null if no message found
    if (!messageElement) return null;

    // Helper function to check if a node is a text node
    const isTextNode = (node) => node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '';
    // Helper function to check if a node is an img (emoticon)
    const isImageNode = (node) => node.nodeName === 'IMG' && node.getAttribute('title');
    // Helper function to check if a node is an anchor (link)
    const isAnchorNode = (node) => node.nodeName === 'A' && node.getAttribute('href');

    // Function to collect message parts (text + emoticons + links) from any container
    const collectMessageParts = (container) =>
      [...container.childNodes]
        .map(node =>
          isTextNode(node) ? node.textContent.trim() :
            isImageNode(node) ? node.getAttribute('title') :
              isAnchorNode(node) ? node.getAttribute('href') : ''
        )
        .filter(Boolean); // Remove empty strings

    // Initialize variables for message texts
    let commonMessageText = '';
    let privateMessageText = '';
    let systemMessageText = '';
    const systemUsername = 'Клавобот';

    // Process common message
    const commonMessageParts = collectMessageParts(messageElement);
    commonMessageText = commonMessageParts.join(' ').trim(); // Final common message text

    // Process private message
    const privateMessageContainer = messageElement.querySelector('.room.private');
    if (privateMessageContainer && privateMessageContainer.textContent.includes('[шепчет вам]')) {
      const privateMessageElement = messageElement.querySelector('span.private');
      if (privateMessageElement) {
        const privateMessageParts = collectMessageParts(privateMessageElement);
        privateMessageText = privateMessageParts.join(' ').trim(); // Final private message
      }
    }

    // Process system message
    const systemMessageElement = messageElement.querySelector('.system-message');
    if (systemMessageElement) {
      const systemMessageParts = collectMessageParts(systemMessageElement);
      // Join system message parts into final systemMessageText
      systemMessageText = systemMessageParts.join(' ').trim(); // Final system message
      // Clear <Клавобот> from systemMessageText
      systemMessageText = systemMessageText.replace(/<Клавобот>\s*/g, '');
    }

    // Retrieve or initialize personalMessages from localStorage
    const personalMessages = JSON.parse(localStorage.getItem('personalMessages')) || {};

    // Helper to get the current date in YYYY-MM-DD format
    const getCurrentDate = () => new Date().toLocaleDateString('en-CA');

    // Helper function to handle messages
    const handleMessage = (messageType, messageText) => {
      const time = messageElement.querySelector('.time')?.textContent || 'N/A';
      const usernameElement = messageElement.querySelector('.username span[data-user]');
      const username = usernameElement ? usernameElement.textContent : systemUsername;
      const usernameColor = usernameElement ? usernameElement.parentElement.style.color : 'rgb(180, 180, 180)';
      const normalizedColor = normalizeUsernameColor(usernameColor);

      // Create a unique key based on the time and username to avoid collisions
      const messageKey = `${time}_${username}`;

      // Store the message data in personalMessages with the new order
      personalMessages[messageKey] = {
        time,
        date: getCurrentDate(),
        username,
        usernameColor: normalizedColor,
        message: messageText,
        type: messageType
      };

      // Save to localStorage only if the user is not in the ignored
      if (!ignored.includes(username)) {
        localStorage.setItem('personalMessages', JSON.stringify(personalMessages));
      }
    };

    // Check if the message contains a mention for the current user
    const usernameElement = messageElement.querySelector('.username');
    let usernameText = (usernameElement && usernameElement.textContent)
      ? usernameElement.textContent.replace(/</g, '').replace(/>/g, '')
      : systemUsername; // Default to systemUsername if not found

    let usernamePrefix = ''; // Initialize usernamePrefix

    // Determine the final message text based on the availability of common, private, or system messages
    let finalMessageText = commonMessageText; // Start with the common message

    if (privateMessageText) {
      finalMessageText = `${privateMessageText}`; // If it's a private message
      handleMessage('private', finalMessageText);
    } else if (systemMessageText && isMentionForMe(systemMessageText)) {
      finalMessageText = `${systemMessageText}`; // If there's a system message with a mention
      handleMessage('system', finalMessageText);
    }

    // Handle mentions
    if (isMentionForMe(finalMessageText)) {
      isMention = true;
      usernamePrefix = `${replaceWithPronunciation(usernameText)} обращается: `; // Use usernameText directly
      handleMessage('mention', finalMessageText); // Pass 'mention' and the original final message text
      highlightMentionWords();
    } else if (usernameText !== lastUsername) {
      isMention = false;
      usernamePrefix = `${replaceWithPronunciation(usernameText)} пишет: `; // Use usernameText directly
    }

    lastUsername = usernameText; // Update the last seen username

    // Combine the username prefix and the final message text
    const messageWithPronunciation = `${usernamePrefix}${replaceWithPronunciation(finalMessageText)}`;

    // Return all relevant message data including the system message
    return {
      modifiedMessageText: messageWithPronunciation || systemMessageText, // If messageWithPronunciation is null, assign systemMessageText
      originalMessageText: finalMessageText || systemMessageText, // If finalMessageText is null, assign systemMessageText
      usernameText: usernameText // Always return usernameText
    };
  }

  // Prevent the "readNewMessages" function from being called multiple times until all messages in the set have been read
  let isReading = false;

  // Create a Set to store the new messages
  const newMessages = new Set();

  // This function adds a new message to the Set and triggers the "readNewMessages" function if the Set was empty before
  function addNewMessage(message) {
    // Check if the new message is not already in the Set
    if (!newMessages.has(message)) {
      // Add the new message to the Set
      newMessages.add(message);
      // If the "readNewMessages" function is not already in progress, trigger it
      if (!isReading) {
        // Change the flag to true to be initialized accent beep sound for mention message
        isReading = true;
        readNewMessages();
      }
    }
  }

  // This function reads the new messages from the Set and removes them after reading
  async function readNewMessages() {
    // Read each message in sequence from the Set
    for (let message of newMessages) {
      // Call the textToSpeech function to read the message
      await textToSpeech(message, voiceSpeed);
      // Remove the message from the Set after reading
      newMessages.delete(message);
    }
    // Set the isReading flag to false after reading all messages
    isReading = false;
  }

  // Track if the user has loaded messages for the first time
  let firstTime = true;
  // The distance from the bottom at which we should trigger auto-scrolling
  const scrollThreshold = 600;

  // Scrolls the chat container to the bottom if the user has scrolled close enough
  function scrollMessages() {
    // Get the chat container
    const chatContainer = document.querySelector(".messages-content");

    // If it's the user's first time loading messages, auto-scroll to the bottom
    if (firstTime) {
      chatContainer.scrollTop = chatContainer.scrollHeight;
      firstTime = false;
    } else {
      // Calculate how far the user is from the bottom
      const distanceFromBottom = chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight;
      // If the user is close enough to the bottom, auto-scroll to the bottom
      if (distanceFromBottom <= scrollThreshold) {
        chatContainer.scrollTop = chatContainer.scrollHeight;
      }
    }
  }

  function applyChatMessageGrouping() {
    // Get the messages container element
    const messagesContainer = document.getElementById('chat-content');

    // Get all the chat message elements from the messages container
    const chatMessages = messagesContainer.querySelectorAll('.messages-content div p');

    // Initialize variables
    let previousUser = null;
    let isFirstMessage = true;
    let spacing = '14px';

    // Loop through the chat messages
    for (let i = 0; i < chatMessages.length; i++) {
      const message = chatMessages[i];
      const usernameElement = message.querySelector('span.username');

      // Check if it's a system message with the "system-message" class
      const isSystemMessage = message.querySelector('.system-message');

      if (isSystemMessage) {
        // Apply margins to system messages
        message.style.marginTop = spacing;
        message.style.marginBottom = spacing;
      } else if (usernameElement) { // Check if the message contains a username
        // Get the username from the current message
        const usernameElementWithDataUser = usernameElement.querySelector('span[data-user]');

        if (!usernameElementWithDataUser) {
          continue; // Skip messages without a data-user element
        }

        let usernameText = usernameElementWithDataUser.textContent;

        // Remove the "<" and ">" symbols from the username if they are present
        usernameText = usernameText.replace(/</g, '').replace(/>/g, '');

        // Apply margin-top for the first message or when the user changes
        if (previousUser === null || usernameText !== previousUser) {
          // Check if it's not the first message overall
          if (!isFirstMessage) {
            // Add margin-top to create separation between the current message and the previous message
            message.style.marginTop = spacing;
          }
        } else {
          // Check if it's not the first message of the current user
          if (!isFirstMessage) {
            // Remove the margin-bottom property from the current message to remove any previously set margin
            message.style.removeProperty('margin-bottom');
          }
        }

        // Check if there is a next message
        const hasNextMessage = i < chatMessages.length - 1;

        // Check if there is a next message and it contains a username
        if (hasNextMessage) {
          const nextMessage = chatMessages[i + 1];
          const nextUsernameElement = nextMessage.querySelector('span.username');

          if (nextUsernameElement) {
            const nextUsernameElementWithDataUser = nextUsernameElement.querySelector('span[data-user]');

            if (!nextUsernameElementWithDataUser) {
              continue; // Skip messages without a data-user element
            }

            // Get the username from the next message
            const nextUsernameText = nextUsernameElementWithDataUser.textContent;

            // Apply margin-bottom for the last message of each user
            if (usernameText !== nextUsernameText) {
              message.style.marginBottom = spacing;
            }
          }
        }

        // Update the previousUser variable to store the current username
        previousUser = usernameText;
        // Set isFirstMessage to false to indicate that this is not the first message overall
        isFirstMessage = false;
      }
    }
  }

  // Call the function to apply chat message grouping
  applyChatMessageGrouping();

  // Algorithm to check for similarity between two strings
  function similarity(s1, s2) {
    const [longer, shorter] = s1.length >= s2.length ? [s1, s2] : [s2, s1];
    const longerLength = longer.length;
    if (longerLength === 0) return 1.0;
    return (longerLength - editDistance(longer, shorter)) / longerLength;
  }

  function editDistance(s1, s2) {
    s1 = s1.toLowerCase();
    s2 = s2.toLowerCase();

    const costs = Array(s2.length + 1).fill(0).map((_, j) => j);
    for (let i = 1; i <= s1.length; i++) {
      let lastValue = costs[0];
      costs[0] = i;
      for (let j = 1; j <= s2.length; j++) {
        const newValue = costs[j];
        costs[j] = s1.charAt(i - 1) === s2.charAt(j - 1) ? lastValue : Math.min(Math.min(newValue, lastValue), costs[j - 1]) + 1;
        lastValue = newValue;
      }
    }
    return costs[s2.length];
  }

  // Time difference threshold (in milliseconds) to identify spam
  const timeDifferenceThreshold = 400;
  // Message limit per timeDifferenceThreshold
  const messageLimit = 1;
  // Object to track user-specific data
  let userChatData = {};
  // Maximum number of consecutive times a user is allowed to exceed the message limit
  const thresholdMaxTries = 10;

  // Function to format time difference
  function formatTimeDifference(difference) {
    // Define time units
    const units = ['hour', 'minute', 'second', 'millisecond'];

    // Calculate values for each time unit
    const values = [
      Math.floor(difference / (1000 * 60 * 60)), // hours
      Math.floor((difference / (1000 * 60)) % 60), // minutes
      Math.floor((difference / 1000) % 60), // seconds
      difference % 1000 // milliseconds
    ];

    // Map each non-zero value to a formatted string with its corresponding unit
    const formattedStrings = values
      .map((value, index) => (value > 0 ? `${value} ${units[index]}${value > 1 ? 's' : ''}` : ''));

    // Filter out empty strings (units with a value of 0) and join the remaining strings
    const formattedTime = formattedStrings
      .filter(Boolean)
      .join(' ');

    // Return the formatted time string
    return formattedTime;
  }

  // Helper function to remove all messages by a user
  function removeUserMessages(userId) {
    const userMessages = document.querySelectorAll(`.messages-content span[data-user="${userId}"]`);
    userMessages.forEach(message => {
      const pTag = message.closest('p');
      if (pTag) {
        pTag.remove();
      }
    });
  }

  const digits = '0-9';
  const whitespaces = '\\s';
  const latinChars = 'a-zA-Z';
  const cyrillicChars = 'а-яА-ЯёЁ';
  const commonSymbols = '!@#$%^&*()-_=+[\\]{}|;:\'",.<>/?`~';

  // Special symbols as characters
  const copyrightSymbol = '\\u00A9'; // ©
  const trademarkSymbol = '\\u2122'; // ™
  const registeredSymbol = '\\u00AE'; // ®
  const leftDoubleAngleQuote = '\\u00AB'; // «
  const rightDoubleAngleQuote = '\\u00BB'; // »
  const plusMinus = '\\u00B1'; // ±
  const multiplication = '\\u00D7'; // ×
  const division = '\\u00F7'; // ÷
  const degreeSymbol = '\\u00B0'; // °
  const notEqual = '\\u2260'; // ≠
  const lessThanOrEqual = '\\u2264'; // ≤
  const greaterThanOrEqual = '\\u2265'; // ≥
  const infinity = '\\u221E'; // ∞
  const euroSymbol = '\\u20AC'; // €
  const poundSymbol = '\\u00A3'; // £
  const yenSymbol = '\\u00A5'; // ¥
  const sectionSymbol = '\\u00A7'; // §
  const bulletPoint = '\\u2022'; // •
  const ellipsis = '\\u2026'; // …
  const minus = '\\u2212'; // −
  const enDash = '\\u2013'; // –
  const emDash = '\\u2014'; // —

  // Arrow and Mathematical symbols as Unicode escape sequences
  const leftArrow = '\\u2190'; // ←
  const rightArrow = '\\u2192'; // →
  const upArrow = '\\u2191'; // ↑
  const downArrow = '\\u2193'; // ↓

  const half = '\\u00BD'; // ½
  const oneThird = '\\u2153'; // ⅓
  const twoThirds = '\\u2154'; // ⅔

  const summation = '\\u2211'; // ∑
  const acuteAccent = '\\u00B4'; // ´

  const emojiRanges = '\\uD83C-\\uDBFF\\uDC00-\\uDFFF';

  // Initialized to store characters found in a message that are not allowed
  let disallowedChars = null;

  function messageContainsAllowedChars(message) {
    const allowedCharsRegex = new RegExp(
      `[${digits}${latinChars}${cyrillicChars}${whitespaces}${commonSymbols}` +
      `${copyrightSymbol}${trademarkSymbol}${registeredSymbol}${leftDoubleAngleQuote}${rightDoubleAngleQuote}` +
      `${plusMinus}${multiplication}${division}${degreeSymbol}${notEqual}${lessThanOrEqual}${greaterThanOrEqual}` +
      `${infinity}${euroSymbol}${poundSymbol}${yenSymbol}${sectionSymbol}${bulletPoint}${ellipsis}${minus}${enDash}${emDash}` +
      `${leftArrow}${rightArrow}${upArrow}${downArrow}${half}${oneThird}${twoThirds}${summation}` +
      `${acuteAccent}${emojiRanges}]+`, 'g'
    );

    const allowedChars = message.match(allowedCharsRegex);

    if (allowedChars && allowedChars.join('') === message) {
      return true;
    } else {
      disallowedChars = message.replace(allowedCharsRegex, '');
      return false;
    }
  }

  // Helper function to handle threshold check
  function handleThresholdExceeded(userId, generateLogUserInfo) {
    if (userChatData[userId].thresholdMaxTries >= thresholdMaxTries) {
      // Set 'banned' to true after passing the max thresholdMaxTries to remove user messages passing the messages limit checking
      userChatData[userId].banned = true;
      console.log(generateLogUserInfo(), 'color: pink');
      console.log(`%c${userChatData[userId].userName} cannot send messages anymore`, 'color: pink');
    }
  }

  // Function to track and handle spam messages
  function banSpammer() {
    // Get the current timestamp
    const currentTime = new Date().getTime();

    // Select the last p element in the chat
    const latestMessage = document.querySelector('.messages-content p:last-child');

    if (latestMessage) {
      // Get user ID from the last message
      const userIdElement = latestMessage.querySelector('span[data-user]');
      const userId = userIdElement ? userIdElement.getAttribute('data-user') : null;

      // Initialize user-specific data outside the if block
      if (!userChatData[userId]) {
        userChatData[userId] = {
          messagesCount: 0,
          thresholdMaxTries: 0,
          time: currentTime,
          userName: userIdElement ? userIdElement.textContent : 'Unknown User',
          previousTime: null,
          firstInteraction: true,
          banned: false
        };
      }

      // Calculate time difference
      const timeDifference = currentTime - userChatData[userId].time;

      // Function to generate log information dynamically
      function generateLogUserInfo() {
        return `%cID: ${userId}, Name: ${userChatData[userId].userName}, ` +
          `Time Difference: ${formatTimeDifference(timeDifference)}, ` +
          `Messages Count: ${userChatData[userId].messagesCount}, ` +
          `Spam Tries: ${userChatData[userId].thresholdMaxTries}, ` +
          `Banned: ${userChatData[userId].banned}`;
      }

      // Check if the message contains not allowed chars
      if (!messageContainsAllowedChars(latestMessage.textContent, userId) && !userChatData[userId].banned) {
        // Increase thresholdMaxTries on every limit pass
        userChatData[userId].thresholdMaxTries++;
        // If the message contains not allowed chars, log the information
        console.log(
          `%c${userChatData[userId].userName} has sent a message with not allowed characters ${disallowedChars}.
          Threshold: ${userChatData[userId].thresholdMaxTries}.`,
          'color: orange;'
        );
        handleThresholdExceeded(userId, generateLogUserInfo);
      }

      // Special handling for the first interaction
      if (userChatData[userId].firstInteraction) {
        console.log(`%c${userChatData[userId].userName} posted the first message for the current chat session.`, 'color: yellow');
        userChatData[userId].firstInteraction = false;
      }

      // Check if the user is banned
      else if (userChatData[userId].banned) {
        // Remove all the messages by that user continuously until banned
        removeUserMessages(userId);
      } else {
        if (timeDifference < timeDifferenceThreshold) {
          // Check if the time difference is less than the threshold
          userChatData[userId].messagesCount++;

          if (userChatData[userId].messagesCount > messageLimit) {
            // Remove all messages by that user if messages limit was exceeded
            removeUserMessages(userId);

            // Increase thresholdMaxTries on every limit pass
            userChatData[userId].thresholdMaxTries++;

            handleThresholdExceeded(userId, generateLogUserInfo);

            // Log the information immediately after updating the values if not banned
            if (!userChatData[userId].banned) {
              console.log(generateLogUserInfo(), 'color: red');
            }
          } else {
            // Log the information immediately after updating the values if not banned and not exceeding the limit
            console.log(generateLogUserInfo(), 'color: green');
          }
        } else {
          // If none of the above conditions are met, update user-specific data for the current interaction
          userChatData[userId].previousTime = userChatData[userId].time;
          userChatData[userId].time = currentTime;
          userChatData[userId].messagesCount = 1;

          // Log the information immediately after updating the values if not banned and not exceeding the limit
          console.log(generateLogUserInfo(), 'color: green');
        }
      }
    }
  }


  // POPUP MESSAGES START

  const popupMessageIconSize = 16;

  // SVG markup for a clock icon
  const clockSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
       width="${popupMessageIconSize - 2}"
       height="${popupMessageIconSize - 2}"
       viewBox="0 0 24 24"
       fill="none"
       stroke="currentColor"
       stroke-width="2"
       stroke-linecap="round"
       stroke-linejoin="round"
       class="feather feather-clock">
    <circle cx="12" cy="12" r="10"></circle>
    <polyline points="12 6 12 12 16 14"></polyline>
  </svg>
`;

  // SVG markup for a user icon
  const userSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
       width="${popupMessageIconSize - 2}"
       height="${popupMessageIconSize - 2}"
       viewBox="0 0 24 24"
       fill="none"
       stroke="currentColor"
       stroke-width="2"
       stroke-linecap="round"
       stroke-linejoin="round"
       class="feather feather-user">
    <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
    <circle cx="12" cy="7" r="4"></circle>
  </svg>
`;

  const popupChatMessageStyles = document.createElement('style');
  popupChatMessageStyles.textContent = `
    .popup-messages-container {
      display: flex;
      flex-direction: column;
      justify-content: flex-end;
      align-items: start;
      user-select: none;
      pointer-events: none;
      position: fixed;
      left: 0;
      right: 0;
      top: 50px;
      bottom: 0;
    }

    .popup-chat-message {
      display: flex;
      background-color: hsl(100, 50%, 10%);
      position: relative;
      max-width: 70vw;
      border-radius: 0.2em !important;
      color: hsl(100, 50%, 50%);
      border: 1px solid hsl(100, 50%, 25%);
      padding: 4px;
      margin: 6px 15vw;
      opacity: 0;
      transform: translateY(20px);
      transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
      animation: fadeIn 0.3s ease-in-out forwards;
    }

    @keyframes fadeIn {
      from {
        opacity: 0;
        transform: translateY(20px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }

    .popup-chat-message.fade-out {
      animation: fadeOut 0.3s ease-in-out forwards;
    }

    @keyframes fadeOut {
      from {
        opacity: 1;
        transform: translateY(0);
      }
      to {
        opacity: 0;
        transform: translateY(-20px);
      }
    }

    .popup-chat-message > div {
      padding: 2px;
      display: flex;
      font-family: 'Montserrat', sans-serif;
    }

    .popup-chat-message .time,
    .popup-chat-message .time-icon {
      opacity: 0.7;
    }
`;

  popupChatMessageStyles.classList.add('popup-chat-message-styles');

  document.head.appendChild(popupChatMessageStyles);

  // Set the maximum number of popup messages to display globally
  const maxPopupMessagesCount = 10;

  // Define an object to store the hue for each username
  const usernameHueMap = {};
  // Increase step for noticeable color changes
  const hueStep = 15;

  // Define the function to show popup messages when the main chat is hidden by hotkeys Ctrl + Space (only)
  function showPopupMessage() {
    // Check if the key 'shouldShowPopupMessage' exists and has a value of true
    const shouldShowPopupMessage = localStorage.getItem('shouldShowPopupMessage');

    // Stop execution if shouldShowPopupMessage is false
    if (shouldShowPopupMessage !== 'true') {
      return;
    }

    // Get the last message in the chat
    const latestMessage = document.querySelector('.messages-content p:last-child');

    if (latestMessage) {
      // Extract elements for time and username from the latest message
      const time = latestMessage.querySelector('.time');
      const username = latestMessage.querySelector('.username');

      // Get all nodes and concatenate their values
      const nodes = Array.from(latestMessage.childNodes);
      const elements = nodes.map(node => {
        if (node.nodeType === Node.TEXT_NODE) {
          return { type: 'text', value: node.nodeValue.trim() };
        } else if (node.nodeType === Node.ELEMENT_NODE) {
          if (node.tagName.toLowerCase() === 'img') {
            const imgTitle = node.getAttribute('title');
            return { type: 'img', title: imgTitle };
          } else if (node.tagName.toLowerCase() === 'a') {
            const anchorHref = node.getAttribute('href');
            return { type: 'anchor', href: anchorHref };
          }
        }
      }).filter(Boolean);

      // Extract relevant data from the time and username elements
      const cleanTime = time.textContent.replace(/[\[\]]/g, '');
      const cleanUsername = username.textContent.replace(/[<>]/g, '');

      // Check if the hue for this username is already stored
      let hueForUsername = usernameHueMap[cleanUsername];

      // If the hue is not stored, generate a new random hue with the specified step
      if (!hueForUsername) {
        hueForUsername = Math.floor(Math.random() * (360 / hueStep)) * hueStep;
        // Store the generated hue for this username
        usernameHueMap[cleanUsername] = hueForUsername;
      }

      // Create or get the main container for all messages
      let popupMessagesContainer = document.querySelector('.popup-messages-container');
      if (!popupMessagesContainer) {
        popupMessagesContainer = document.createElement('div');
        popupMessagesContainer.classList.add('popup-messages-container');
        document.body.appendChild(popupMessagesContainer);
      }

      // Check if the total number of messages in the container exceeds the maximum
      if (popupMessagesContainer.childElementCount >= maxPopupMessagesCount) {
        // Get the oldest message
        const oldestMessage = popupMessagesContainer.firstChild;

        // Apply a CSS class to initiate the fade-out animation
        oldestMessage.classList.add('fade-out');

        // After the animation duration, remove the message from the DOM
        setTimeout(() => {
          popupMessagesContainer.removeChild(oldestMessage);
        }, 300); // Adjust the time to match your CSS animation duration
      }

      // Create a container div for each message
      const popupChatMessage = document.createElement('div');
      popupChatMessage.classList.add('popup-chat-message');
      // Apply the hue-rotate filter to the entire message container
      popupChatMessage.style.filter = `hue-rotate(${hueForUsername}deg)`;

      // Append time SVG icon before the time
      const timeIcon = document.createElement('div');
      timeIcon.classList.add('time-icon');
      timeIcon.innerHTML = clockSVG;

      // Append spans for each part with respective classes
      const timeElement = document.createElement('div');
      timeElement.classList.add('time');
      timeElement.textContent = cleanTime;

      // Append user SVG icon after the time
      const userIcon = document.createElement('div');
      userIcon.classList.add('user-icon');
      userIcon.innerHTML = userSVG;

      const usernameElement = document.createElement('div');
      usernameElement.classList.add('username');
      usernameElement.textContent = cleanUsername;

      // Append action SVG icon after the username
      const actionIcon = document.createElement('div');
      actionIcon.classList.add('action-icon');
      actionIcon.innerHTML = chevronRightSVG;

      const messageElement = document.createElement('div');
      messageElement.classList.add('message');

      // Append elements to the message container
      popupChatMessage.appendChild(timeIcon);
      popupChatMessage.appendChild(timeElement);
      popupChatMessage.appendChild(userIcon);
      popupChatMessage.appendChild(usernameElement);
      popupChatMessage.appendChild(actionIcon);
      popupChatMessage.appendChild(messageElement);

      // Fill the message container with text, images, and anchors
      elements.forEach(element => {
        const elementContainer = document.createElement('div');

        if (element.type === 'text') {
          elementContainer.textContent = element.value;
        } else if (element.type === 'img') {
          elementContainer.innerHTML = `&nbsp;${element.title}&nbsp;`;
        } else if (element.type === 'anchor') {
          elementContainer.innerHTML = `&nbsp;${element.href}&nbsp;`;
        }

        messageElement.appendChild(elementContainer);
      });

      // Append the message container to the main container
      popupMessagesContainer.appendChild(popupChatMessage);
    }
  }

  // POPUP MESSAGES END

  // Function to convert Cyrillic characters to Latin
  function convertCyrillicToLatin(input) {
    const cyrillicToLatinMap = {
      'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D',
      'Е': 'E', 'Ё': 'Yo', 'Ж': 'Zh', 'З': 'Z', 'И': 'I',
      'Й': 'Y', 'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N',
      'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T',
      'У': 'U', 'Ф': 'F', 'Х': 'Kh', 'Ц': 'Ts', 'Ч': 'Ch',
      'Ш': 'Sh', 'Щ': 'Shch', 'Ъ': 'y', // 'ъ' maps to 'y'
      'Ы': 'Y', 'Ь': 'i', // 'ь' maps to 'i'
      'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya',
      'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
      'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i',
      'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
      'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
      'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch',
      'ш': 'sh', 'щ': 'shch', 'ъ': 'y', // 'ъ' maps to 'y'
      'ы': 'y', 'ь': 'i', // 'ь' maps to 'i'
      'э': 'e', 'ю': 'yu', 'я': 'ya'
    };

    // Convert the input string to Latin using the mapping
    return input.split('').map(char => cyrillicToLatinMap[char] || char).join('');
  }

  // Function to convert Russian usernames
  function convertRussianUsernameToLatin(username) {
    // Use the conversion function on the username
    return convertCyrillicToLatin(username);
  }

  // Skip reading the messages on page load to read them normally when the user is present and the page is stable
  let isInitialized = false;
  // Define the maximum number of messages per user
  const maxMessagesPerUser = 5;
  // Set a similarity threshold (you can adjust this value as needed)
  const similarityThreshold = 0.8;
  // Create a map to hold messages for each user
  const messagesForSimilarityCheck = new Map();

  // Function to remove all messages from users in the ignored
  function removeIgnoredUserMessages() {
    document.querySelectorAll('.messages-content p').forEach(message => {
      const usernameElement = message.querySelector('.username'); // Adjust selector if needed
      const username = usernameElement?.textContent?.replace(/[<>]/g, '') || null;

      if (username && ignored.includes(username)) {
        // console.log(`Hidden message from ignored user: ${username}`);
        // Convert Cyrillic username to Latin
        const latinUsername = convertRussianUsernameToLatin(username);
        message.classList.add('ignored-user', latinUsername);
        message.style.display = 'none'; // Hide the message
      }
    });
  }

  // Function to play sound as a notification for system message banned
  function playSound() {
    const marioGameOver = 'https://github.com/VimiummuimiV/Sounds/raw/refs/heads/main/Mario_Game_Over.mp3';
    const audio = new Audio(marioGameOver);
    audio.play();
  }

  // Function to detect a system ban message based on the message text content
  function isBanMessageFromSystem(messageText) {
    return ['Клавобот', 'Пользователь', 'заблокирован'].every(word => messageText.includes(word));
  }

  /**
   * Normalizes the color of usernames and resets their filter based on the specified mode.
   *
   * @param {NodeList|Element} usernameElements - A NodeList of username elements or a single username element.
   * @param {string} mode - The mode of operation; either 'one' to process a single username or 'all' to process multiple.
   */
  function normalizeAndResetUsernames(usernameElements, mode) {
    // Exit if usernameElements is null or undefined
    if (!usernameElements) return console.error("usernameElements is null or undefined.");

    if (mode === 'one') {
      // Directly process the single username element
      const userSpan = usernameElements.querySelector('span[data-user]'); // Get the span[data-user] inside the .username element
      const computedColor = getComputedStyle(usernameElements).color; // Get the computed color of the usernameElement
      const normalizedColor = normalizeUsernameColor(computedColor); // Normalize the color
      usernameElements.style.setProperty('color', normalizedColor, 'important'); // Apply the normalized color to usernameElement
      userSpan.style.setProperty('filter', 'invert(0)', 'important'); // Reset the filter for userSpan
    } else if (mode === 'all') {
      // Process all username elements within the context of the provided NodeList
      const elementsToProcess = Array.from(usernameElements); // Convert NodeList to an array
      elementsToProcess.forEach(usernameElement => {
        const userSpan = usernameElement.querySelector('span[data-user]'); // Get the span[data-user] inside the .username element
        if (!userSpan) return; // Exit if userSpan does not exist

        const computedColor = getComputedStyle(usernameElement).color; // Get the computed color of the usernameElement
        const normalizedColor = normalizeUsernameColor(computedColor); // Normalize the color
        usernameElement.style.setProperty('color', normalizedColor, 'important'); // Apply the normalized color to usernameElement
        userSpan.style.setProperty('filter', 'invert(0)', 'important'); // Reset the filter for userSpan
      });
    } else {
      console.error("Invalid mode. Use 'one' or 'all'.");
    }
  }

  // Create a mutation observer to watch for new messages being added
  const newMessagesObserver = new MutationObserver(mutations => {
    // If isInitialized is false, return without doing anything
    if (!isInitialized) {
      isInitialized = true;

      // Remove the 'sessionChatMessages' key from localStorage if it exists
      localStorage.getItem('sessionChatMessages') && localStorage.removeItem('sessionChatMessages');

      // Normalize chat usernames color for dark theme
      const allUsernameElements = document.querySelectorAll('.username'); // Get all username elements
      normalizeAndResetUsernames(allUsernameElements, 'all'); // Process all username elements

      return; // Stop processing further
    }

    for (let mutation of mutations) {
      if (mutation.type === 'childList') {
        for (let node of mutation.addedNodes) {
          if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'P') {
            const singleUsernameElement = node.querySelector('.username'); // Get a single username element
            normalizeAndResetUsernames(singleUsernameElement, 'one'); // Process the single username element

            // Get previous message from localStorage
            let previousMessageText = localStorage.getItem('previousMessageText');

            // Get the latest message data
            let latestMessageData = getLatestMessageData();

            // Get the modified and original actual messages of the user who sent it
            let actualModifiedMessageText = latestMessageData?.modifiedMessageText || null;
            let actualOriginalMessageText = latestMessageData?.originalMessageText || null;
            // Get the actual username of the user who sent the latest message
            let latestMessageUsername = latestMessageData?.usernameText || null;

            // Initialize the user's message array if it doesn't exist
            messagesForSimilarityCheck.set(latestMessageUsername, messagesForSimilarityCheck.get(latestMessageUsername) || []);

            // Get the user's messages
            const userMessages = messagesForSimilarityCheck.get(latestMessageUsername);

            // Check if the new message is similar to any existing message in the user's message array
            const isSimilarMessage = userMessages.some(msg => {
              const messageSimilarity = similarity(actualOriginalMessageText, msg); // Store similarity value in a constant
              return messageSimilarity > similarityThreshold;
            });

            // If the message is similar, set filter opacity and blur
            if (isSimilarMessage) {
              node.style.filter = 'opacity(0.3) blur(1px)';
            } else {
              // Add the new message to the user's message array and update the map
              userMessages.push(actualOriginalMessageText); // Push the new message into the user's message array
              messagesForSimilarityCheck.set(latestMessageUsername, userMessages); // Update the map with the latest messages for the user

              // Prepare the sessionMessages object to store chat messages in the desired format
              let sessionMessages = JSON.parse(localStorage.getItem('sessionChatMessages')) || {}; // Retrieve existing session messages or initialize an empty object

              // Ensure there's an entry for the current user in sessionMessages
              sessionMessages[latestMessageUsername] = sessionMessages[latestMessageUsername] || []; // Create an array if the username doesn't exist

              // Add the new message to the user's array in sessionMessages
              sessionMessages[latestMessageUsername].push(actualOriginalMessageText); // Append the new message to the user's message array

              // Check if the number of messages for the user exceeds the maximum allowed
              if (userMessages.length > maxMessagesPerUser) {
                messagesForSimilarityCheck.delete(latestMessageUsername); // Remove the user from the messagesForSimilarityCheck map
                delete sessionMessages[latestMessageUsername]; // Delete the user's messages from sessionMessages
              }

              // Save the updated session messages back to localStorage to persist changes
              localStorage.setItem('sessionChatMessages', JSON.stringify(sessionMessages)); // Update localStorage with the new sessionMessages
            }

            // Convert Cyrillic username to Latin
            const latinUsername = convertRussianUsernameToLatin(latestMessageUsername);

            // Detect and handle the ban message (play sound if detected)
            if (isBanMessageFromSystem(actualModifiedMessageText)) {
              console.log('Ban message detected:', actualModifiedMessageText);
              playSound(); // Play the Mario Game Over sound
            }

            // Check if the username is in the ignored
            if (latestMessageUsername && ignored.includes(latestMessageUsername)) {
              node.classList.add('ignored-user', latinUsername);
              node.style.display = 'none'; // Hide the message
              continue; // Skip the rest of the processing for this message
            }

            // Get the sound switcher element and check which option is selected
            const soundSwitcher = document.querySelector('#voice, #beep, #silence');
            const isVoice = soundSwitcher && soundSwitcher.id === 'voice';
            const isBeep = soundSwitcher && soundSwitcher.id === 'beep';

            // Get the message mode element and check which option is selected
            const messageMode = document.querySelector('#every-message, #mention-message');
            const isEveryMessageMode = messageMode && messageMode.id === 'every-message';
            const isMentionMessageMode = messageMode && messageMode.id === 'mention-message';

            // Define the constant for the private message check
            const privateMessageIndicator = '[шепчет вам]';
            // Check if the message element contains a private message
            const privateMessageContainer = node.querySelector('.room.private');
            const isPrivateMessage = privateMessageContainer && privateMessageContainer.textContent.includes(privateMessageIndicator);

            // If mode is voice, speak the new message and update the latest message content in local storage
            if (isVoice && isInitialized && actualModifiedMessageText && actualModifiedMessageText !== previousMessageText) {
              // Update localStorage key "previousMessageText"
              localStorage.setItem('previousMessageText', actualModifiedMessageText);

              // Do not read personal messages. Only unique other people's messages.
              if (latestMessageUsername && !latestMessageUsername.includes(myNickname)) {

                // Read all messages in every-message mode
                if (isEveryMessageMode) {
                  console.log('Triggered Voice: Every message mode');
                  addNewMessage(actualModifiedMessageText);
                }
                // Read mention messages only in mention-message mode
                else if (isMentionMessageMode && isMention) {
                  console.log('Triggered Voice: Mention message mode');
                  addNewMessage(actualModifiedMessageText);
                }
                // Read when private messages is addressed to you
                else if (isPrivateMessage) {
                  console.log('Triggered Voice: Private message');
                  addNewMessage(actualModifiedMessageText);
                }
                else {
                  console.log('No matching condition for Voice Mode');
                }

              }
            }

            // If mode is beep, play the beep sound for the new message
            if (isBeep && isInitialized && actualModifiedMessageText && actualModifiedMessageText !== previousMessageText) {

              // Update localStorage key "previousMessageText"
              localStorage.setItem('previousMessageText', actualModifiedMessageText);

              // Do not read personal messages. Only unique other people's messages.
              if (latestMessageUsername && !latestMessageUsername.includes(myNickname)) {

                // Beep all messages in every-message mode
                if (isEveryMessageMode) {
                  console.log('Triggered Beep: Every message mode');
                  const frequenciesToPlay = isMention ? mentionMessageFrequencies : usualMessageFrequencies;
                  playBeep(frequenciesToPlay, beepVolume);
                }
                // Beep mention messages only in mention-message mode
                else if (isMentionMessageMode && isMention) {
                  console.log('Triggered Beep: Mention message mode');
                  const frequenciesToPlay = mentionMessageFrequencies;
                  playBeep(frequenciesToPlay, beepVolume);
                }
                // Beep when private messages are addressed to you
                else if (isPrivateMessage) {
                  console.log('Triggered Beep: Private message');
                  const frequenciesToPlay = mentionMessageFrequencies;
                  playBeep(frequenciesToPlay, beepVolume);
                }
                else {
                  console.log('No matching condition for Beep Mode');
                }

                // Reset mention flag if it was true
                if (isMention) isMention = false;
              }
            }

            if (isInitialized) {
              // Attach contextmenu event listener for messages deletion
              attachEventsToMessages();
              // Convert image links to visible image containers
              convertImageLinkToImage();
              // Convert YouTube links to visible iframe containers
              convertYoutubeLinkToIframe();
              // Call the function to apply the chat message grouping
              applyChatMessageGrouping();
              // Call the function to scroll to the bottom of the chat
              scrollMessages();
              // Call the banSpammer function to track and handle potential spam messages
              banSpammer();
              // Call the function to show the latest popup message
              showPopupMessage();
              // Call the function to update the total and new message count display
              updatePersonalMessageCounts();
            }
          }
        }
      }
    }
  });

  // Observe changes to the messages container element
  const messagesContainer = document.querySelector('.messages-content div');
  newMessagesObserver.observe(messagesContainer, { childList: true, subtree: true });


  // SOUND GRAPHICAL SWITCHER

  const iconStrokeWidth = 1.8;
  const iconSize = 28;
  const iconSilenceStroke = 'hsl(355, 80%, 65%)'; // red
  const iconBeepStroke = 'hsl(55, 80%, 65%)'; // yellow
  const iconVoiceStroke = 'hsl(80, 80%, 40%)'; // green
  const svgUrl = "http://www.w3.org/2000/svg";

  // Icons for sound switcher button
  // Button SVG icons "silence", "beep", "voice" representation
  const iconSoundSilence = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconSilenceStroke}" stroke-width="${iconStrokeWidth}" stroke-linecap="round" stroke-linejoin="round">
      <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
      <line x1="23" y1="9" x2="17" y2="15"></line>
      <line x1="17" y1="9" x2="23" y2="15"></line>
      </svg>`;
  const iconSoundBeep = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconBeepStroke}" stroke-width="${iconStrokeWidth}" stroke-linecap="round" stroke-linejoin="round">
      <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
      <path d="M19.07 4.93a10 10 0 0 1 0 14.14" opacity="0.3"></path>
      <path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
      </svg>`;
  const iconSoundVoice = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconVoiceStroke}" stroke-width="${iconStrokeWidth}" stroke-linecap="round" stroke-linejoin="round">
      <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
      <path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>
      <path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
      </svg>`;

  // Icons for message mode button
  // Button SVG icons "every", "mention" representation
  const iconModeEvery = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="hsl(100, 50%, 50%)" stroke-width="${iconStrokeWidth}" stroke-linecap="round" stroke-linejoin="round">
      <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
      <circle cx="9" cy="7" r="4"></circle>
      <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
      <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
      </svg>`;
  const iconModeMention = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="hsl(180, 60%, 50%)" stroke-width="${iconStrokeWidth}" stroke-linecap="round" stroke-linejoin="round">
      <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
      <circle cx="12" cy="7" r="4"></circle>
      </svg>`;
  // Icon for the out of range value
  const iconRangeisOut = `<svg xmlns="${svgUrl}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather feather-slash">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line>
      </svg>`;
  // Icon for userlistCache
  const iconUserlistCache = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="#b4d583" stroke-width="${iconStrokeWidth}"
      stroke-linecap="round" stroke-linejoin="round" class="feather feather-database">
      <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
      <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
      <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
      </svg>`;
  // Icon for personal messages
  const iconPersonalMessages = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="#ffa07a" stroke-width="${iconStrokeWidth}"
      stroke-linecap="round" stroke-linejoin="round" class="feather feather-mail">
      <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
      <polyline points="22,6 12,13 2,6"></polyline>
      </svg>`;
  // Icon for chat logs
  const iconChatLogs = `<svg xmlns="${svgUrl}" width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none"
    stroke="cornflowerblue" stroke-width="${iconStrokeWidth}" stroke-linecap="round" stroke-linejoin="round"
    class="feather feather-message-circle">
    <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
  </svg>`;

  // Declare variables for the sound switcher button and its icon
  let soundSwitcher, soundSwitcherIcon;
  // Declare variables for the message mode button and its icon
  let messageMode, messageModeIcon;

  // Helper function to add pulse effect
  function addPulseEffect(element) {
    element.classList.add('pulse');
    setTimeout(() => {
      element.classList.remove('pulse');
    }, 300);
  }

  // Helper function to add jump effect like a ball with more keyframes
  function addJumpEffect(element, initialTranslateX = 50, initialTranslateY = 50) {
    const transforms = [
      `translate(${initialTranslateX}%, ${initialTranslateY}%)`, // Initial start position
      `translate(${initialTranslateX}%, ${initialTranslateY - 30}%)`, // Jump up
      `translate(${initialTranslateX}%, ${initialTranslateY - 50}%)`, // Higher jump peak
      `translate(${initialTranslateX}%, ${initialTranslateY}%)`, // Return to original position
      `translate(${initialTranslateX}%, ${initialTranslateY + 10}%)`, // Slight bounce down
      `translate(${initialTranslateX}%, ${initialTranslateY + 20}%)`, // Adjust slightly up
      `translate(${initialTranslateX}%, ${initialTranslateY}%)` // Final position (original)
    ];

    // Define an initial delay and a decrement factor for timing
    let delay = 300; // Start with 300ms
    const decrement = 40; // Decrease the delay by 40ms for each keyframe

    transforms.forEach((transform, index) => {
      setTimeout(() => {
        element.style.transform = transform; // Apply the current transform
      }, delay); // Schedule the transform
      delay -= decrement; // Decrease delay for the next keyframe
    });
  }

  // Helper function to add a shake effect for messages not found in the personal messages panel.
  function addShakeEffect(element) {
    const transforms = [
      'translate3d(0, 0, 0)', // Initial start position
      'translate3d(-2px, 0, 0)', // Shake left (larger)
      'translate3d(4px, 0, 0)', // Shake right (larger)
      'translate3d(-8px, 0, 0)', // Shake left more (larger)
      'translate3d(8px, 0, 0)', // Shake right more (larger)
      'translate3d(-2px, 0, 0)', // Shake left (larger)
      'translate3d(0, 0, 0)' // Return to original position
    ];

    // Define an initial delay and a decrement factor for timing
    let delay = 100; // Start with 100ms
    const increment = 50; // Increase the delay by 50ms for each keyframe

    transforms.forEach((transform, index) => {
      setTimeout(() => {
        element.style.transform = transform; // Apply the current transform
        element.style.transition = 'transform 0.1s ease'; // Ensure smooth transition
      }, delay); // Schedule the transform
      delay += increment; // Increase delay for the next keyframe
    });
  }

  // Helper function to add a blink effect using color opacity.
  function addBlinkEffect(element) {
    // Set the initial color to bisque with full opacity.
    element.style.backgroundColor = 'rgba(255, 228, 196, 1)'; // bisque color.

    const opacities = [1, 0, 1, 0]; // Full -> Hidden -> Full -> End at Hidden.
    const delay = 100; // Static delay of 200ms between frames.

    // Repeat the blink effect three times.
    for (let i = 0; i < 3; i++) {
      opacities.forEach((opacity, index) => {
        setTimeout(() => {
          // Apply the opacity to the background color.
          element.style.backgroundColor = `rgba(255, 228, 196, ${opacity})`;
          element.style.transition = 'background-color 0.3s ease'; // Smooth transition.
        }, delay * (i * opacities.length + index)); // Schedule the color change.
      });
    }
  }

  // Helper function to apply common styles to buttons
  function applyBaseButtonStyles(element) {
    Object.assign(element.style, {
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      width: '48px',
      height: '48px',
      cursor: 'pointer',
      margin: `${empowermentButtonsMargin}px`,
      backgroundColor: '#212226',
      border: '1px solid #45474b',
    });
  }

  // CREATE SOUND SWITCHER BUTTON (START)

  function createSoundSwitcherButton() {
    // Create a new element with class 'sound-switcher-button' and id 'silence'
    soundSwitcher = document.createElement('div');
    // Retrieve the value from localStorage key "messageNotificationState"
    const messageNotificationState = KG_Chat_Empowerment.messageSettings.messageNotificationState || 'silence';
    // Add the class 'sound-switcher-button' to the 'soundSwitcher' element
    soundSwitcher.classList.add('sound-switcher-button');
    // Initial button id if the localStorage key isn't created with assigned value by user
    soundSwitcher.id = messageNotificationState;
    // Retrieve the value from localStorage key "messageNotificationTitle"

    // Apply base button styles
    applyBaseButtonStyles(soundSwitcher);

    // Retrieve the value from KG_Chat_Empowerment.messageSettings.messageNotificationTitle
    const messageNotificationTitle = KG_Chat_Empowerment.messageSettings.messageNotificationTitle || 'Do not disturb';
    // Assign title for the current notification state
    soundSwitcher.title = messageNotificationTitle;

    // Create sound switcher button icon container
    soundSwitcherIcon = document.createElement('span');
    // Add class to icon container
    soundSwitcherIcon.classList.add('sound-switcher-icon');

    // Append icon container inside sound switcher button
    soundSwitcher.appendChild(soundSwitcherIcon);
    // Append sound switcher button to chat buttons panel
    empowermentButtonsPanel.appendChild(soundSwitcher);
  } createSoundSwitcherButton();

  // Add the isAltKeyPressed condition to the soundSwitcher event listener
  soundSwitcher.addEventListener('click', function (event) {
    // Only execute the code if both isCtrlKeyPressed and isAltKeyPressed are false
    if (!isCtrlKeyPressed && !isAltKeyPressed) {

      // Get progress bar elements if they exist in the DOM
      let currentVoiceSpeed = document.querySelector('.current-voice-speed');
      let currentVoicePitch = document.querySelector('.current-voice-pitch');

      // Remove voice speed setting progress bar
      if (currentVoiceSpeed) {
        currentVoiceSpeed.remove();
      }

      // Remove voice pitch setting progress bar
      if (currentVoicePitch) {
        currentVoicePitch.remove();
      }

      // Add pulse effect for soundSwitcher
      addPulseEffect(this);

      switch (this.id) {
        case 'silence':
          this.id = 'beep';
          this.title = 'Notify with beep signal';
          KG_Chat_Empowerment.messageSettings.messageNotificationState = 'beep';
          KG_Chat_Empowerment.messageSettings.messageNotificationTitle = 'Notify with beep signal';
          break;
        case 'beep':
          this.id = 'voice';
          this.title = 'Notify with voice API';
          KG_Chat_Empowerment.messageSettings.messageNotificationState = 'voice';
          KG_Chat_Empowerment.messageSettings.messageNotificationTitle = 'Notify with voice API';
          break;
        case 'voice':
          this.id = 'silence';
          this.title = 'Do not disturb';
          KG_Chat_Empowerment.messageSettings.messageNotificationState = 'silence';
          KG_Chat_Empowerment.messageSettings.messageNotificationTitle = 'Do not disturb';
          break;
      }
      // Stringify KG_Chat_Empowerment before updating in localStorage
      localStorage.setItem('KG_Chat_Empowerment', JSON.stringify(KG_Chat_Empowerment));

      updateSoundSwitcherIcon();
    }
  });

  function updateSoundSwitcherIcon() {
    switch (soundSwitcher.id) {
      case 'silence':
        soundSwitcherIcon.innerHTML = iconSoundSilence;
        break;
      case 'beep':
        soundSwitcherIcon.innerHTML = iconSoundBeep;
        break;
      case 'voice':
        soundSwitcherIcon.innerHTML = iconSoundVoice;
        break;
    }
  } updateSoundSwitcherIcon();

  // This function combines the results of the above functions to return an object
  // with both the speed and pitch percentages as strings with a "%" sign appended.
  function getVoiceSettingsPercentage() {
    const speedPercent = ((voiceSpeed - minVoiceSpeed) / (maxVoiceSpeed - minVoiceSpeed)) * 100;
    const pitchPercent = ((voicePitch - minVoicePitch) / (maxVoicePitch - minVoicePitch)) * 100;

    return {
      speed: `${speedPercent}%`,
      pitch: `${pitchPercent}%`,
    };
  }

  // Function to assign common styles for voice speed and pitch elements
  function assignVoiceSettingsStyles(voiceSettings) {
    voiceSettings.style.position = 'absolute';
    voiceSettings.style.top = '65px';
    voiceSettings.style.right = '70px';
    voiceSettings.style.opacity = 0;
    voiceSettings.style.transition = 'opacity 0.3s ease';
    voiceSettings.style.fontFamily = 'Orbitron, sans-serif';
  }

  /*
  * Shows the current voice speed or pitch as a span element with appropriate styles.
  * If the Ctrl key is pressed, displays the current voice speed.
  * If the Alt key is pressed, displays the current voice pitch.
  */
  function showVoiceSettings() {
    let voiceSettings = document.querySelector('.voice-settings');
    let currentVoiceSpeed = document.querySelector('.current-voice-speed');
    let currentVoicePitch = document.querySelector('.current-voice-pitch');

    if (isCtrlKeyPressed) {
      // Create voiceSettings if it doesn't exist
      if (!voiceSettings) {
        voiceSettings = document.createElement('div');
        voiceSettings.classList.add('voice-settings');
        soundSwitcher.appendChild(voiceSettings);
        assignVoiceSettingsStyles(voiceSettings);
        void voiceSettings.offsetWidth;
        voiceSettings.style.opacity = '1';
      }

      // Remove currentVoicePitch if it exists
      if (currentVoicePitch) {
        currentVoicePitch.remove();
      }

      // Create currentVoiceSpeed if it doesn't exist
      if (!currentVoiceSpeed) {
        currentVoiceSpeed = document.createElement('span');
        currentVoiceSpeed.classList.add('current-voice-speed');
        voiceSettings.appendChild(currentVoiceSpeed);
      }

      // Create progress text info
      let voiceSpeedInfo = voiceSettings.querySelector('.current-voice-speed .voice-value-info');
      if (!voiceSpeedInfo) {
        voiceSpeedInfo = document.createElement('span');
        voiceSpeedInfo.classList.add('voice-value-info');
        voiceSettings.querySelector('.current-voice-speed').appendChild(voiceSpeedInfo);
        voiceSpeedInfo.style.display = 'flex';
        voiceSpeedInfo.style.width = '100%';
        voiceSpeedInfo.style.justifyContent = 'center';
        voiceSpeedInfo.style.marginBottom = '6px';
        voiceSpeedInfo.style.color = 'hsl(100, 50%, 50%)';
      }

      if (voiceSpeedInfo) {
        // Set the text content of voiceSpeed
        if (voiceSpeed <= minVoiceSpeed || voiceSpeed >= maxVoiceSpeed) {
          voiceSpeedInfo.innerHTML = iconRangeisOut;
        } else {
          voiceSpeedInfo.innerHTML = voiceSpeed.toFixed(1);
        }
      }

      // Create a new progress element if it doesn't exist
      let voiceSpeedProgress = voiceSettings.querySelector('.current-voice-speed .voice-progress');
      if (!voiceSpeedProgress) {
        voiceSpeedProgress = document.createElement('span');
        voiceSpeedProgress.classList.add('voice-progress');
        // Create the progress fill element
        let fill = document.createElement('span');
        fill.classList.add('voice-progress-fill');
        // Append the fill element to the progress element
        voiceSpeedProgress.appendChild(fill);
        // Append the progress element to the voice settings element
        voiceSettings.querySelector('.current-voice-speed').appendChild(voiceSpeedProgress);
      }

      // Update progress fill width based on voice pitch percentage
      voiceSpeedProgress.querySelector('.voice-progress-fill').style.width = getVoiceSettingsPercentage().speed;

      // Apply styles to the progress and fill elements
      const progressStyle = {
        display: 'block',
        width: '120px',
        height: '12px',
        backgroundColor: 'hsl(90, 60%, 30%)',
        borderRadius: '6px'
      };

      const fillStyle = {
        display: 'block',
        height: '100%',
        backgroundColor: 'hsl(90, 60%, 50%)',
        borderRadius: '6px'
      };

      for (let property in progressStyle) {
        voiceSpeedProgress.style[property] = progressStyle[property];
      }

      for (let property in fillStyle) {
        voiceSpeedProgress.querySelector('.voice-progress-fill').style[property] = fillStyle[property];
      }

      // Clear any existing timeout on voiceSettings and set a new one
      if (voiceSettings.timeoutId) {
        clearTimeout(voiceSettings.timeoutId);
      }

      voiceSettings.timeoutId = setTimeout(() => {
        voiceSettings.style.opacity = '0';
        setTimeout(() => {
          voiceSettings.remove();
        }, 500);
      }, 2000);

    } else if (isAltKeyPressed) {
      // Create voiceSettings if it doesn't exist
      if (!voiceSettings) {
        voiceSettings = document.createElement('div');
        voiceSettings.classList.add('voice-settings');
        soundSwitcher.appendChild(voiceSettings);
        assignVoiceSettingsStyles(voiceSettings);
        void voiceSettings.offsetWidth;
        voiceSettings.style.opacity = '1';
      }

      // Remove currentVoiceSpeed if it exists
      if (currentVoiceSpeed) {
        currentVoiceSpeed.remove();
      }

      // Create currentVoicePitch if it doesn't exist
      if (!currentVoicePitch) {
        currentVoicePitch = document.createElement('span');
        currentVoicePitch.classList.add('current-voice-pitch');
        voiceSettings.appendChild(currentVoicePitch);
      }

      // Create progress text info
      let voicePitchInfo = voiceSettings.querySelector('.current-voice-pitch .voice-value-info');
      if (!voicePitchInfo) {
        voicePitchInfo = document.createElement('span');
        voicePitchInfo.classList.add('voice-value-info');
        voiceSettings.querySelector('.current-voice-pitch').appendChild(voicePitchInfo);
        voicePitchInfo.style.display = 'flex';
        voicePitchInfo.style.width = '100%';
        voicePitchInfo.style.justifyContent = 'center';
        voicePitchInfo.style.marginBottom = '6px';
        voicePitchInfo.style.color = 'hsl(180, 60%, 50%)';
      }

      if (voicePitchInfo) {
        // Set the text content of voicePitch
        if (voicePitch <= minVoicePitch || voicePitch >= maxVoicePitch) {
          voicePitchInfo.innerHTML = iconRangeisOut;
        } else {
          voicePitchInfo.innerHTML = voicePitch.toFixed(1);
        }
      }

      // Create a new progress element if it doesn't exist
      let pitchProgress = voiceSettings.querySelector('.current-voice-pitch .voice-progress');
      if (!pitchProgress) {
        pitchProgress = document.createElement('span');
        pitchProgress.classList.add('voice-progress');
        // Create the progress fill element
        let fill = document.createElement('span');
        fill.classList.add('voice-progress-fill');
        // Append the fill element to the progress element
        pitchProgress.appendChild(fill);
        // Append the progress element to the voice settings element
        voiceSettings.querySelector('.current-voice-pitch').appendChild(pitchProgress);
      }

      // Update progress fill width based on voice pitch percentage
      pitchProgress.querySelector('.voice-progress-fill').style.width = getVoiceSettingsPercentage().pitch;

      // Apply styles to the progress and fill elements
      const progressStyle = {
        display: 'block',
        width: '120px',
        height: '12px',
        backgroundColor: 'hsl(180, 60%, 30%)',
        borderRadius: '6px'
      };

      const fillStyle = {
        display: 'block',
        height: '100%',
        backgroundColor: 'hsl(180, 60%, 50%)',
        borderRadius: '6px'
      };

      for (let property in progressStyle) {
        pitchProgress.style[property] = progressStyle[property];
      }

      for (let property in fillStyle) {
        pitchProgress.querySelector('.voice-progress-fill').style[property] = fillStyle[property];
      }

      // Clear any existing timeout on voiceSettings and set a new one
      if (voiceSettings.timeoutId) {
        clearTimeout(voiceSettings.timeoutId);
      }

      voiceSettings.timeoutId = setTimeout(() => {
        voiceSettings.style.opacity = '0';
        setTimeout(() => {
          voiceSettings.remove();
        }, 500);
      }, 2000);

    } else {
      // If neither Ctrl nor Alt is pressed, remove voiceSettings if it exists
      if (voiceSettings) {
        voiceSettings.remove();
      }
    }
  }

  // Add event listeners for both regular click and right-click (contextmenu)
  soundSwitcher.addEventListener('click', handleVoiceChange);
  soundSwitcher.addEventListener('contextmenu', handleVoiceChange);

  // Event handler function for handling both click and right-click events
  function handleVoiceChange(event) {
    event.preventDefault(); // Prevent default context menu on right-click

    // Check if it's a left click or right click
    const isLeftClick = event.button === 0;
    const isRightClick = event.button === 2;

    // Check for Ctrl + Left Click or Ctrl + Right Click
    if ((isCtrlKeyPressed && isLeftClick) || (isCtrlKeyPressed && isRightClick)) {
      // Determine whether to change voice speed or pitch
      const prop = 'voiceSpeed';
      // Calculate new value and limit it within specified bounds
      const newValue = parseFloat(KG_Chat_Empowerment.voiceSettings[prop]) +
        (isLeftClick ? -0.1 : 0.1);
      const limitedValue = Math.min(maxVoiceSpeed, Math.max(minVoiceSpeed, newValue));
      // Update the voice setting with the limited value
      updateVoiceSetting(prop, limitedValue);
    }
    // Check for Alt + Left Click or Alt + Right Click
    else if ((isAltKeyPressed && isLeftClick) || (isAltKeyPressed && isRightClick)) {
      // Determine whether to change voice speed or pitch
      const prop = 'voicePitch';
      // Calculate new value and limit it within specified bounds
      const newValue = parseFloat(KG_Chat_Empowerment.voiceSettings[prop]) +
        (isLeftClick ? -0.1 : 0.1);
      const limitedValue = Math.min(maxVoicePitch, Math.max(minVoicePitch, newValue));
      // Update the voice setting with the limited value
      updateVoiceSetting(prop, limitedValue);
    }
  }

  // Function to update the voice setting, round the value, and update storage
  function updateVoiceSetting(prop, value) {
    // Round the value to one decimal place
    const roundedValue = parseFloat(value.toFixed(1));
    // Update the voice setting in the application state
    KG_Chat_Empowerment.voiceSettings[prop] = roundedValue;
    // Update voiceSpeed and voicePitch variables
    if (prop === 'voiceSpeed') {
      voiceSpeed = roundedValue;
    } else if (prop === 'voicePitch') {
      voicePitch = roundedValue;
    }
    // Store the updated state in localStorage
    localStorage.setItem('KG_Chat_Empowerment', JSON.stringify(KG_Chat_Empowerment));
    // Show the updated voice settings
    showVoiceSettings();
  }

  // CREATE SOUND SWITCHER BUTTON (END)


  // CREATE MESSAGE MODE BUTTON (START)

  function createMessageModeButton() {
    // Create a new element with class 'message-mode-button' and id 'every-messages'
    messageMode = document.createElement('div');
    // Retrieve the value from KG_Chat_Empowerment.messageSettings.messageModeState
    const messageModeState = KG_Chat_Empowerment.messageSettings.messageModeState || 'every-message';
    // Add the class 'message-mode-button' to the 'messagesMode' element
    messageMode.classList.add('message-mode-button');
    // Initial button id if the localStorage key isn't created with assigned value by user
    messageMode.id = messageModeState;

    // Apply base button styles
    applyBaseButtonStyles(messageMode);

    // Retrieve the value from KG_Chat_Empowerment.messageSettings.messageModeTitle
    const messageModeTitle = KG_Chat_Empowerment.messageSettings.messageModeTitle || 'Notify about every message';
    // Assign title for the current notification state
    messageMode.title = messageModeTitle;

    // Create message mode button icon container
    messageModeIcon = document.createElement('span');
    // Add class to icon container
    messageModeIcon.classList.add('message-mode-icon');

    // Append icon container inside message mode button
    messageMode.appendChild(messageModeIcon);
    // Append sound switcher button to chat buttons panel
    empowermentButtonsPanel.appendChild(messageMode);
  } createMessageModeButton();

  // Add the isAltKeyPressed condition to the messagesMode event listener
  messageMode.addEventListener('click', function (event) {
    // Only execute when isCtrlKeyPressed or isAltKeyPressed are false
    if (!isCtrlKeyPressed || !isAltKeyPressed) {

      // Add pulse effect for messageMode
      addPulseEffect(this);

      switch (this.id) {
        case 'every-message':
          this.id = 'mention-message';
          this.title = 'Notify about mention message';
          KG_Chat_Empowerment.messageSettings.messageModeState = 'mention-message';
          KG_Chat_Empowerment.messageSettings.messageModeTitle = 'Notify about mention message';
          break;
        case 'mention-message':
          this.id = 'every-message';
          this.title = 'Notify about every message';
          KG_Chat_Empowerment.messageSettings.messageModeState = 'every-message';
          KG_Chat_Empowerment.messageSettings.messageModeTitle = 'Notify about every message';
          break;
      }

      // Stringify KG_Chat_Empowerment before updating in localStorage
      localStorage.setItem('KG_Chat_Empowerment', JSON.stringify(KG_Chat_Empowerment));

      updateMessageModeIcon();
    }
  });

  function updateMessageModeIcon() {
    switch (messageMode.id) {
      case 'every-message':
        messageModeIcon.innerHTML = iconModeEvery;
        break;
      case 'mention-message':
        messageModeIcon.innerHTML = iconModeMention;
        break;
    }
  } updateMessageModeIcon();

  // CREATE MESSAGE MODE BUTTON (END)


  // CREATE USER LIST CACHE BUTTON (START)

  // Function to create the button for showCachePanel
  function createShowUserListCacheButton() {
    // Create a new element with class 'cache-panel-load-button'
    const showUserListCacheButton = document.createElement('div');

    // Add the class 'cache-panel-load-button' to the button
    showUserListCacheButton.classList.add('cache-panel-load-button');

    // Apply base button styles
    applyBaseButtonStyles(showUserListCacheButton);

    // Add cache-specific styles directly
    showUserListCacheButton.style.position = 'relative';
    showUserListCacheButton.style.zIndex = '3';

    // Add data base icon to the button
    showUserListCacheButton.innerHTML = iconUserlistCache;

    // Create the small indicator for user count
    const userCount = document.createElement('div');
    userCount.classList.add('user-count');
    userCount.style.display = 'flex';
    userCount.style.position = 'absolute';
    userCount.style.justifyContent = 'center';
    userCount.style.alignItems = 'center';
    userCount.style.left = '0';
    userCount.style.bottom = '0';
    userCount.style.transform = 'translate(-50%, 50%)';
    userCount.style.zIndex = '1';
    userCount.style.height = '20px';
    userCount.style.padding = '0 4px';
    userCount.style.setProperty('border-radius', '2px', 'important');
    userCount.style.backgroundColor = '#9db380';
    userCount.style.color = 'rgb(2, 2, 2)';
    userCount.style.fontSize = '12px';
    userCount.style.fontFamily = 'Roboto';
    userCount.style.fontWeight = 'bold';

    // Initially set the count based on localStorage
    const fetchedUsers = JSON.parse(localStorage.getItem('fetchedUsers')) || {};
    const userCountValue = Object.keys(fetchedUsers).length;
    userCount.textContent = userCountValue;

    showUserListCacheButton.appendChild(userCount);

    // Assign a title to the button
    showUserListCacheButton.title = 'Show Cache Panel';

    // Add a click event listener to the button
    showUserListCacheButton.addEventListener('click', function () {

      // Add pulse effect for cacheButton
      addPulseEffect(showUserListCacheButton);

      // Call showCachePanel function to show the cache panel
      showCachePanel();
    });

    // Append the button to the existing panel
    empowermentButtonsPanel.appendChild(showUserListCacheButton);
  } createShowUserListCacheButton();

  // Function to update the user count displayed near the cache button based on localStorage
  function updateUserCountText() {
    const userCountElement = document.querySelector('.cache-panel-load-button .user-count');
    if (!userCountElement) return; // Ensure the element exists

    const newUserCount = Object.keys(JSON.parse(localStorage.getItem('fetchedUsers')) || {}).length.toString();

    // Update the text content and add pulse effect if the count has changed
    if (newUserCount !== userCountElement.textContent) {
      userCountElement.textContent = newUserCount;
      addPulseEffect(userCountElement);
    }
  }

  // CREATE USER LIST CACHE BUTTON (END)


  // CREATE PERSONAL MESSAGES BUTTON (START)

  // Function to create the button for opening personal messages
  function createPersonalMessagesButton() {
    // Create a new element with class 'personal-messages-button'
    const showPersonalMessagesButton = document.createElement('div');
    showPersonalMessagesButton.classList.add('personal-messages-button');

    // Apply base button styles
    applyBaseButtonStyles(showPersonalMessagesButton);

    // Add personal messages-specific styles
    showPersonalMessagesButton.style.position = 'relative';
    showPersonalMessagesButton.style.zIndex = '2';
    showPersonalMessagesButton.innerHTML = iconPersonalMessages; // Add icon

    // Create the small indicator for all message count
    const allMessageIndicator = createMessageCountIndicator('total-message-count', '#fa8072');
    const personalMessages = JSON.parse(localStorage.getItem('personalMessages')) || {};
    allMessageIndicator.textContent = Object.keys(personalMessages).length;

    // Position the all message count to the left
    allMessageIndicator.style.left = '0';
    allMessageIndicator.style.transform = 'translate(-50%, 50%)';
    showPersonalMessagesButton.appendChild(allMessageIndicator);

    // Create the small indicator for new message count
    const newMessageIndicator = createMessageCountIndicator('new-message-count', '#ffd700');

    // Get the new messages count from localStorage or set to 0 if not present
    let newMessagesCount = Number(localStorage.getItem('newMessagesCount')) || (localStorage.setItem('newMessagesCount', '0'), 0);

    newMessageIndicator.textContent = newMessagesCount;

    // Check the newMessagesCount value and set visibility
    newMessageIndicator.style.visibility = newMessagesCount > 0 ? 'visible' : 'hidden'; // Set visibility based on count

    // Position the new message count to the right
    newMessageIndicator.style.right = '0';
    newMessageIndicator.style.transform = 'translate(50%, 50%)';
    showPersonalMessagesButton.appendChild(newMessageIndicator);

    // Assign a title to the button
    showPersonalMessagesButton.title = 'Show Personal Messages';

    // Add a click event listener to the button
    showPersonalMessagesButton.addEventListener('click', function () {
      addPulseEffect(showPersonalMessagesButton); // Add pulse effect
      const personalMessagesCount = Object.keys(JSON.parse(localStorage.getItem('personalMessages')) || {}).length;
      // Open the personal messages panel only when there are messages present.
      if (personalMessagesCount > 0) {
        showPersonalMessagesPanel(); // Show the personal messages panel
        // Reset newMessagesCount in localStorage to 0 when opening the panel
        localStorage.setItem('newMessagesCount', '0');
        newMessagesCount = 0; // Reset the local variable
        newMessageIndicator.textContent = newMessagesCount; // Update the displayed count
      }
    });

    // Append the button to the existing panel
    empowermentButtonsPanel.appendChild(showPersonalMessagesButton);
  }

  // Helper function to create a message count indicator
  function createMessageCountIndicator(className, backgroundColor) {
    const messageCount = document.createElement('div');
    messageCount.classList.add(className);
    messageCount.style.display = 'flex';
    messageCount.style.position = 'absolute';
    messageCount.style.justifyContent = 'center';
    messageCount.style.alignItems = 'center';
    messageCount.style.height = '20px'; // Fixed height for all indicators
    messageCount.style.padding = '0 4px';
    messageCount.style.setProperty('border-radius', '2px', 'important');
    messageCount.style.backgroundColor = backgroundColor;
    messageCount.style.color = 'rgb(2, 2, 2)';
    messageCount.style.fontSize = '12px';
    messageCount.style.fontFamily = 'Roboto';
    messageCount.style.fontWeight = 'bold';
    messageCount.style.bottom = '0'; // Common bottom positioning for both indicators
    return messageCount;
  }

  // Call the function to create the button
  createPersonalMessagesButton();

  // Find chat message by time in range and matching username
  function findChatMessage(targetTime, targetUsername, allowScroll) {
    const parent = document.querySelector('.messages-content'); // Chat container
    if (!parent) return null; // Return null if the container isn't found

    // Convert time string "[HH:MM:SS]" to total seconds
    const timeStringToSeconds = (str) =>
      str.replace(/[\[\]]/g, '').split(':').reduce((acc, time, i) =>
        acc + Number(time) * (60 ** (2 - i)), 0
      );

    const initialTimeValue = timeStringToSeconds(targetTime); // Target time in seconds

    // Helper to find <p> elements by matching time and username
    const findMatchingElement = (condition) =>
      Array.from(parent.querySelectorAll('p')).find((p) => {
        const timeElement = p.querySelector('.time'); // Get the child element with class 'time'
        const usernameElement = p.querySelector('.username span[data-user]'); // Get the username element

        if (timeElement && usernameElement) {
          const currentTimeValue = timeStringToSeconds(timeElement.textContent.trim());
          const usernameText = usernameElement.textContent.trim(); // Extract the text content of the username

          // Check if the time and username match the conditions
          return condition(currentTimeValue) && usernameText === targetUsername;
        }
        return false;
      });

    // 1. Try to find an exact match first
    let foundElement = findMatchingElement(
      (currentTimeValue) => currentTimeValue === initialTimeValue
    );

    // 2. If no exact match, search within ±2 seconds
    if (!foundElement) {
      foundElement = findMatchingElement(
        (currentTimeValue) => Math.abs(currentTimeValue - initialTimeValue) <= 2
      );
    }

    if (foundElement) {
      // Scroll to the found element if allowScroll is true
      if (allowScroll) {
        const { top, height } = foundElement.getBoundingClientRect(); // Get the position and height of the found element
        const { top: parentTop, height: parentHeight } = parent.getBoundingClientRect(); // Get the position and height of the parent

        // Calculate the middle position of the parent container
        const parentMiddle = parentTop + parentHeight / 2;

        // Determine how far to scroll to center the found element
        const scrollOffset = top - parentMiddle + height / 2;

        // Scroll to the found element to center it within the parent
        parent.scrollBy({
          top: scrollOffset,
          behavior: 'smooth'
        });

        setTimeout(() => (parent.style.scrollBehavior = 'auto'), 500); // Reset scroll behavior
        setTimeout(() => addShakeEffect(foundElement), 300); // Add shake effect
      }
      return foundElement; // Return the found element
    }

    // console.log('No matching element found.');
    return false; // Return false if no match is found
  }

  // Function to display the personal messages panel
  function showPersonalMessagesPanel() {
    // Check if the cached messages panel already exists
    if (document.querySelector('.cached-messages-panel')) return;

    // Reset the new messages indicator to 0
    const newMessagesCountElement = document.querySelector('.personal-messages-button .new-message-count');
    if (newMessagesCountElement) newMessagesCountElement.textContent = '0';
    newMessagesCountElement.style.visibility = 'hidden';
    // Remove the localStorage key for new personal messages after opening the messages panel (always)
    localStorage.removeItem('newMessagesCount');

    // Get data from localStorage
    const cachedMessagesData = localStorage.getItem('personalMessages');

    // Initialize messages by parsing fetched data or setting as empty array
    let messages = JSON.parse(cachedMessagesData) || [];

    // Create a container div with class 'cached-messages-panel'
    const cachedMessagesPanel = document.createElement('div');
    cachedMessagesPanel.className = 'cached-messages-panel';
    // Set initial styles
    cachedMessagesPanel.style.opacity = '0';
    cachedMessagesPanel.style.backgroundColor = '#1b1b1b';
    cachedMessagesPanel.style.setProperty('border-radius', '0.6em', 'important');
    cachedMessagesPanel.style.position = 'fixed';
    cachedMessagesPanel.style.top = '100px';
    cachedMessagesPanel.style.left = '50%';
    cachedMessagesPanel.style.transform = 'translateX(-50%)';
    cachedMessagesPanel.style.width = '50vw';
    cachedMessagesPanel.style.height = '80vh';
    cachedMessagesPanel.style.zIndex = '999';
    cachedMessagesPanel.style.minWidth = '1000px';

    // Create a container div for the panel header
    const panelHeaderContainer = document.createElement('div');
    panelHeaderContainer.className = 'panel-header';
    panelHeaderContainer.style.display = 'flex';
    panelHeaderContainer.style.flexDirection = 'row';
    panelHeaderContainer.style.justifyContent = 'flex-end'; // Aligns to the right
    panelHeaderContainer.style.padding = '0.6em';

    // Create the search input container and append it to the panel header
    const messagesSearchContainer = document.createElement('div');
    messagesSearchContainer.className = 'search-for-personal-messages';
    messagesSearchContainer.style.width = '100%';
    messagesSearchContainer.style.margin = '0 20px';
    messagesSearchContainer.style.display = 'flex';

    // Create the input field for searching personal messages
    const messagesSearchInput = document.createElement('input');
    messagesSearchInput.className = 'personal-messages-search-input';
    messagesSearchInput.type = 'text';
    messagesSearchInput.style.outline = 'none';
    messagesSearchInput.style.width = '100%';
    messagesSearchInput.style.padding = '10px';
    messagesSearchInput.style.margin = '0 1em';
    messagesSearchInput.style.fontSize = '1em';
    messagesSearchInput.style.fontFamily = 'Montserrat';
    messagesSearchInput.style.color = 'bisque';
    messagesSearchInput.style.setProperty('border-radius', '0.2em', 'important');
    messagesSearchInput.style.boxSizing = 'border-box';
    messagesSearchInput.style.backgroundColor = '#111';
    messagesSearchInput.style.border = '1px solid #222';

    // Append the search input to the search container
    messagesSearchContainer.appendChild(messagesSearchInput);

    // Create a container div with class 'panel-control-buttons'
    const panelControlButtons = document.createElement('div');
    panelControlButtons.className = 'panel-control-buttons';
    panelControlButtons.style.display = 'flex';

    // Helper function to apply common styles to a button
    function applyHeaderButtonStyles(button, backgroundColor, margin = '0 0.5em') {
      button.style.backgroundColor = backgroundColor;
      button.style.width = '48px';
      button.style.height = '48px';
      button.style.display = 'flex';
      button.style.justifyContent = 'center';
      button.style.alignItems = 'center';
      button.style.cursor = 'pointer';
      button.style.setProperty('border-radius', '0.2em', 'important');
      button.style.margin = margin; // Set margin using the provided value
      button.style.filter = 'brightness(1)';
      button.style.transition = 'filter 0.3s ease';
    }

    // Create a copy personal messages button element
    const copyPersonalMessagesButton = document.createElement('div');
    copyPersonalMessagesButton.className = 'copy-personal-messages-button';
    // Set the inner HTML of the copy personal messages button element with the clipboard SVG
    copyPersonalMessagesButton.innerHTML = clipboardSVG;
    copyPersonalMessagesButton.title = 'Copy Personal Messages';
    // Apply common styles to the button element
    applyHeaderButtonStyles(copyPersonalMessagesButton, 'steelblue');

    // Event listener to copy the text content of the messages container
    copyPersonalMessagesButton.addEventListener('click', () => {
      addJumpEffect(copyPersonalMessagesButton, 0, 0);
      const textContent = Array.from(document.querySelector('.messages-container').children)
        .filter(node => window.getComputedStyle(node).display !== 'none') // Ignore hidden messages
        .map(node => node.classList.contains('date-item') ? node.textContent.trim() :
          [node.querySelector('.message-time'), node.querySelector('.message-username'), node.querySelector('.message-text')]
            .map(el => el?.textContent.trim()).filter(Boolean).join(' '))
        .filter(Boolean).join(' \n');

      // Copy to clipboard
      navigator.clipboard.writeText(textContent).catch(console.error);
    });

    // Create a clear cache button with the provided SVG icon
    const clearCacheButton = document.createElement('div');
    clearCacheButton.className = 'clear-cache-button';
    clearCacheButton.title = 'Clear personal messages';
    clearCacheButton.innerHTML = trashSVG;
    applyHeaderButtonStyles(clearCacheButton, 'brown');

    // Add a click event listener to the clear cache button
    clearCacheButton.addEventListener('click', () => {
      // Clear the messages container
      messagesContainer.innerHTML = null;

      // Set the 'personalMessages' key in localStorage to an empty object
      localStorage.setItem('personalMessages', JSON.stringify({}));

      // Fade out the cached messages panel when the clear cache button is clicked
      fadeTargetElement(cachedMessagesPanel, 'hide');
      fadeDimmingElement('hide');

      // Update the message count displayed in the personal messages button
      const messagesCountElement = document.querySelector('.personal-messages-button .total-message-count');
      if (messagesCountElement) messagesCountElement.textContent = '0';
    });

    // Create a close button with the provided SVG icon
    const closePanelButton = document.createElement('div');
    closePanelButton.className = 'close-panel-button';
    closePanelButton.title = 'Close panel';
    closePanelButton.innerHTML = closeSVG;
    applyHeaderButtonStyles(closePanelButton, 'darkolivegreen', '0 0 0 0.5em');

    // Add a click event listener to the close panel button
    closePanelButton.addEventListener('click', () => {
      // Fade out the cached messages panel when the close button is clicked
      fadeTargetElement(cachedMessagesPanel, 'hide');
      fadeDimmingElement('hide');
    });

    // Create an array containing the buttons we want to apply the events to
    const buttons = [clearCacheButton, closePanelButton];

    // Iterate through each button in the array
    buttons.forEach(button => {
      // Add a mouseover event listener to change the button's brightness on hover
      button.addEventListener('mouseover', () => {
        button.style.filter = 'brightness(0.8)'; // Dim the button
      });

      // Add a mouseout event listener to reset the button's brightness when not hovered
      button.addEventListener('mouseout', () => {
        button.style.filter = 'brightness(1)'; // Reset to original brightness
      });
    });

    // Append the search container to the panel header container
    panelHeaderContainer.appendChild(messagesSearchContainer);

    panelControlButtons.appendChild(copyPersonalMessagesButton);

    // Append the clear cache button to the panel header container
    panelControlButtons.appendChild(clearCacheButton);

    // Append the close button to the panel control buttons
    panelControlButtons.appendChild(closePanelButton);

    // Append the panel control buttons element inside the panel header container
    panelHeaderContainer.appendChild(panelControlButtons);

    // Append the header to the cached messages panel
    cachedMessagesPanel.appendChild(panelHeaderContainer);

    // Create a container for the messages
    const messagesContainer = document.createElement('div');
    messagesContainer.className = 'messages-container';
    messagesContainer.style.overflowY = 'auto'; // Enable scrolling for messages
    messagesContainer.style.height = 'calc(100% - 70px)'; // Adjust height considering header
    messagesContainer.style.padding = '1em';

    let lastUsername = null; // Store the last username processed
    let pingCheckCounter = 0; // Initialize a counter
    let maxPingChecks = 100; // Set the limit to 100
    let pingMessages = false; // Initialize pingMessages as false
    let lastDate = null; // Store the last processed date

    const today = new Intl.DateTimeFormat('en-CA').format(new Date()); // 'en-CA' gives 'YYYY-MM-DD' format

    // Loop through the messages and create elements
    Object.entries(messages).forEach(([, { time, date, username, message, usernameColor, type }]) => {
      // If the current date is different from the last processed one, create a new date-item
      if (lastDate !== date) {
        const dateItem = document.createElement('div');
        dateItem.className = 'date-item';
        // show "Today" if date matches
        dateItem.textContent = date === today ? 'Today ⏳' : `${date} 📅`;
        dateItem.style.position = 'relative';
        dateItem.style.font = '1em Montserrat';
        dateItem.style.color = 'burlywood';
        // burlywood with transparency 0.1
        dateItem.style.backgroundColor = 'rgba(222, 184, 135, 0.1)';
        dateItem.style.width = 'fit-content';
        dateItem.style.margin = '2em 1em 1em';
        dateItem.style.padding = '0.4em 0.8em';
        dateItem.style.textAlign = 'center';
        dateItem.style.setProperty('border-radius', '0.4em', 'important');
        dateItem.style.left = '50%';
        dateItem.style.transform = 'translateX(-50%)';

        messagesContainer.appendChild(dateItem); // Add the date-item to the container
        lastDate = date; // Update the last processed date
      }

      // Create a message-item for the current message
      const messageElement = document.createElement('div');
      messageElement.className = 'message-item';
      messageElement.style.padding = '0.2em';

      // Add margin-top if this is the first message of a new username group
      if (username !== lastUsername) {
        messageElement.style.marginTop = '0.6em';
        lastUsername = username; // Update the lastUsername
      }

      // Remove square brackets from the time string
      const formattedTime = time.replace(/[\[\]]/g, '').trim();

      // Create time, username, and message elements
      const timeElement = document.createElement('span');
      timeElement.className = 'message-time';
      timeElement.textContent = formattedTime;
      timeElement.style.margin = '0.4em';

      // Change the timeElement color based on type
      const timeColors = {
        private: 'coral',
        mention: 'darkseagreen'
      }

      timeElement.style.color = timeColors[type] || 'slategray';

      // Add click event listener only for "mention" type
      if (type === 'mention') {
        timeElement.style.cursor = 'pointer'; // Pointer cursor on hover
        timeElement.style.transition = 'color 0.2s ease'; // Smooth color change

        // Hover effect: change color to light green
        timeElement.addEventListener('mouseover', () => {
          timeElement.style.color = 'lightgreen';
        });

        // Revert color on mouseout
        timeElement.addEventListener('mouseout', () => {
          timeElement.style.color = timeColors[type];
        });

        // Click event: open the chat log link with the provided message date
        timeElement.addEventListener('click', () => {
          const url = `https://klavogonki.ru/chatlogs/${date}.html#${formattedTime}`;
          window.open(url, '_blank', 'noopener,noreferrer'); // Open in a new tab securely
        });
      }

      const usernameElement = document.createElement('span');
      usernameElement.className = 'message-username';
      usernameElement.textContent = username;
      usernameElement.style.color = usernameColor;
      usernameElement.style.margin = '0.4em';

      const messageTextElement = document.createElement('span');
      messageTextElement.className = 'message-text';
      messageTextElement.style.cursor = 'pointer'; // Pointer cursor
      messageTextElement.style.margin = '0.4em';

      messageTextElement.innerHTML = message.replace(/:(?=\w*[a-zA-Z])(\w+):/g,
        (_, word) => `<img src="/img/smilies/${word}.gif" alt=":${word}:" title=":${word}:" class="smile">`
      );

      // Change the messageTextElement color based on type
      const messageColors = {
        private: 'coral',
        mention: 'lightsteelblue'
      };

      if (pingCheckCounter < maxPingChecks) {
        // Find chat message and increment the counter
        pingMessages = findChatMessage(time, username, false);
        pingCheckCounter++; // Increment the counter

        // Check if the counter has reached or exceeded the maximum
        if (pingCheckCounter >= maxPingChecks) {
          // Reset pingMessages to false if the limit is reached
          pingMessages = false;
          console.log("Reached maximum ping checks, resetting pingMessages.");
        }
      }
      // Colorize the messageTextElement accordingly
      messageTextElement.style.color =
        pingMessages && type === 'mention' ? 'lightgreen' :
          pingMessages && type === 'private' ? 'lemonchiffon' :
            messageColors[type] || 'slategray';

      // Add click event listener
      messageTextElement.addEventListener('click', () => {
        // Call the function to search for the chat message by time in range and username
        const foundMessage = findChatMessage(time, username, true);
        if (foundMessage) {
          // Fade out the cached messages panel if the message is found
          fadeTargetElement(cachedMessagesPanel, 'hide');
          fadeDimmingElement('hide');
        } else {
          addShakeEffect(messageTextElement.parentElement); // Add shake effect to the parent
        }
      });

      // Append time, username, and message to the message element
      messageElement.appendChild(timeElement);
      messageElement.appendChild(usernameElement);
      messageElement.appendChild(messageTextElement);

      // Append the message element to the messages container
      messagesContainer.appendChild(messageElement);

      requestAnimationFrame(() => {
        messagesContainer.scrollTop = messagesContainer.scrollHeight; // Scroll after next repaint
      });

    });

    // Append the messages container to the cached messages panel
    cachedMessagesPanel.appendChild(messagesContainer);

    // Append the cached messages panel to the body
    document.body.appendChild(cachedMessagesPanel);

    // Fade in the cached messages panel
    fadeTargetElement(cachedMessagesPanel, 'show');
    // Show the dimming background
    fadeDimmingElement('show');

    // Add click event listener to clear the search input by LMB click with Ctrl key pressed
    messagesSearchInput.addEventListener('click', () => isCtrlKeyPressed && (messagesSearchInput.value = ''));

    // Event listener to handle input search for matching personal messages
    // It searches through messages grouped by date and displays the corresponding date 
    // Only if there are matching messages in that group.
    messagesSearchInput.addEventListener('input', () => {
      const query = messagesSearchInput.value.toLowerCase().replace(/_/g, ' ');

      messagesContainer.querySelectorAll('.date-item').forEach(dateEl => {
        let showDateForGroup = false;
        let nextEl = dateEl.nextElementSibling;

        // Iterate through messages in the current group (until the next date item)
        while (nextEl && !nextEl.classList.contains('date-item')) {
          const match = (nextEl.querySelector('.message-time')?.textContent.toLowerCase().replace(/_/g, ' ') + ' ' +
            nextEl.querySelector('.message-username')?.textContent.toLowerCase().replace(/_/g, ' ') + ' ' +
            nextEl.querySelector('.message-text')?.textContent.toLowerCase().replace(/_/g, ' ')).includes(query);

          nextEl.style.display = match ? '' : 'none';
          showDateForGroup = showDateForGroup || match; // Show date if any match found in the group

          nextEl = nextEl.nextElementSibling;
        }

        dateEl.style.display = showDateForGroup ? '' : 'none'; // Show or hide the date based on the match results in the group
      });
    });

    // Focus on the search input using requestAnimationFrame
    function focusOnSearchField() { requestAnimationFrame(function () { messagesSearchInput.focus(); }); } focusOnSearchField();

    // Attach a keydown event listener to the document object
    document.addEventListener('keydown', function (event) {
      // Check if the key pressed was the "Escape" key
      if (event.key === 'Escape') {
        // Fade out the cached messages panel
        fadeTargetElement(cachedMessagesPanel, 'hide');
        fadeDimmingElement('hide');
      }
    });
  }

  // Initialize previousTotalCount with the current personal messages count from localStorage
  let previousTotalCount =
    (localStorage.personalMessages && Object.keys(JSON.parse(localStorage.personalMessages)).length) || 0;

  /**
   * Updates total and new personal message counts near the personal messages button.
   * - Increments new message count only when total message count increases.
   * - Manages visibility and pulse effects for the new message indicator.
   */
  function updatePersonalMessageCounts() {
    const totalCountElement = document.querySelector('.personal-messages-button .total-message-count');
    const newCountElement = document.querySelector('.personal-messages-button .new-message-count');
    if (!totalCountElement || !newCountElement) return; // Exit if elements are missing

    const personalMessages = JSON.parse(localStorage.getItem('personalMessages')) || {};
    const totalCount = Object.keys(personalMessages).length;

    let newCount = Number(localStorage.getItem('newMessagesCount')) || 0;
    if (totalCount > previousTotalCount) {
      newCount++;
      localStorage.setItem('newMessagesCount', newCount);
      addPulseEffect(newCountElement); // Apply pulse effect for new messages
      addJumpEffect(newCountElement); // Apply jump effect for new messages
    }

    // Update counts in the UI
    totalCountElement.textContent = totalCount;
    newCountElement.textContent = newCount;

    // Manage visibility of the new message indicator
    newCountElement.style.visibility = newCount > 0 ? 'visible' : 'hidden';

    // Apply pulse effect if total count changes
    if (totalCount !== previousTotalCount) addPulseEffect(totalCountElement);

    previousTotalCount = totalCount; // Update previous count
  }

  // CREATE PERSONAL MESSAGES BUTTON (END)


  // CREATE CHAT LOGS BUTTON (START)

  // Function to create the button for opening chat logs
  function createChatLogsButton() {
    const showChatLogsButton = document.createElement('div');
    showChatLogsButton.classList.add('chat-logs-button');

    // Apply base button styles
    applyBaseButtonStyles(showChatLogsButton);

    showChatLogsButton.style.position = 'relative';
    showChatLogsButton.style.zIndex = '1';
    showChatLogsButton.innerHTML = iconChatLogs; // Add icon

    showChatLogsButton.title = 'Show Chat Logs';

    showChatLogsButton.addEventListener('click', function () {
      addPulseEffect(showChatLogsButton); // Add pulse effect
      showChatLogsPanel();
    });

    empowermentButtonsPanel.appendChild(showChatLogsButton);
  }

  // Call the function to create the button
  createChatLogsButton();

  // Function to fetch chat logs from the specified URL for a given date
  const fetchChatLogs = async (date, messagesContainer) => {
    // Clear the messagesContainer if it exists
    messagesContainer && (messagesContainer.innerHTML = '');

    // Format date to 'YYYY-MM-DD'
    const formattedDate = new Intl.DateTimeFormat('en-CA').format(new Date(date));

    // Generate a random 20-digit number
    const randomParam = Math.floor(Math.random() * 10 ** 20);

    // Construct the URL to fetch chat logs for the specified date with the random parameter
    const url = `https://klavogonki.ru/chatlogs/${formattedDate}.html?rand=${randomParam}`;

    // Function to parse the HTML and extract chat log entries
    const parseChatLog = (html) => {
      const doc = new DOMParser().parseFromString(html, 'text/html');

      return [...doc.querySelectorAll('.ts')].map((timeElement) => {
        const usernameElement = timeElement.nextElementSibling;
        const messageNode = usernameElement?.nextSibling;

        const extractMessageText = (node) => {
          if (!node) return '';
          return [...node.childNodes].reduce((acc, child) => {
            if (child.nodeType === Node.TEXT_NODE) {
              acc += child.textContent;
            } else if (child.nodeType === Node.ELEMENT_NODE) {
              if (child.tagName === 'A') {
                acc += child.textContent;
              } else if (child.tagName === 'BR') {
                return acc;
              }
            }
            return acc;
          }, '').trim();
        };

        if (usernameElement?.classList.contains('mn') && messageNode) {
          let messageText = '';

          if (messageNode.nodeType === Node.ELEMENT_NODE) {
            messageText = extractMessageText(messageNode);
          } else if (messageNode.nodeType === Node.TEXT_NODE) {
            const nextSibling = usernameElement.nextElementSibling;
            if (nextSibling && nextSibling.tagName === 'A') {
              messageText = `${messageNode.textContent.trim()} ${nextSibling.textContent.trim()}`;
            } else {
              messageText = messageNode.textContent.trim();
            }
          }

          if (!messageText) {
            const combinedText = extractMessageText(usernameElement.nextSibling);
            messageText = combinedText;
          }

          return {
            time: timeElement.textContent.trim().replace(/[\[\]]/g, ''),
            username: usernameElement.textContent.trim().replace(/<|>/g, ''),
            message: messageText || null,
          };
        }

        return null;
      }).filter(Boolean);
    };

    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Network response was not ok');

      const html = await response.text();
      return { chatlogs: parseChatLog(html), url }; // Return chat logs and the URL
    } catch (error) {
      console.error('Fetch error:', error);
      return { chatlogs: [] }; // Return an empty array in case of an error
    }
  };

  function getRandomDateInRange() {
    const startDate = new Date('2012-02-12'); // Start date
    const endDate = new Date(); // Current date

    // Calculate the difference in milliseconds
    const dateDifference = endDate - startDate;

    // Generate a random number of milliseconds between 0 and dateDifference
    const randomMilliseconds = Math.floor(Math.random() * dateDifference);

    // Create a random date by adding the random milliseconds to the start date
    const randomDate = new Date(startDate.getTime() + randomMilliseconds);

    // Format the date to 'YYYY-MM-DD' using Intl.DateTimeFormat
    const formattedDate = new Intl.DateTimeFormat('en-CA').format(randomDate);

    return formattedDate;
  }

  // Function to display the chat logs panel
  async function showChatLogsPanel() {
    // Check if the chat logs panel already exists; if it does, exit the function to avoid duplication
    if (document.querySelector('.chat-logs-panel')) return;

    // Create a container div with class 'chat-logs-panel'
    const chatLogsPanel = document.createElement('div');
    chatLogsPanel.className = 'chat-logs-panel';

    // Set initial styles for the chat logs panel
    chatLogsPanel.style.opacity = '0';
    chatLogsPanel.style.backgroundColor = '#1b1b1b';
    chatLogsPanel.style.setProperty('border-radius', '0.6em', 'important');
    chatLogsPanel.style.position = 'fixed';
    chatLogsPanel.style.top = '100px';
    chatLogsPanel.style.left = '50%';
    chatLogsPanel.style.transform = 'translateX(-50%)';
    chatLogsPanel.style.width = '80vw';
    chatLogsPanel.style.height = '80vh';
    chatLogsPanel.style.zIndex = '999';
    chatLogsPanel.style.minWidth = '1000px';
    chatLogsPanel.style.display = 'grid';
    chatLogsPanel.style.flexDirection = 'column';
    chatLogsPanel.style.gridTemplateColumns = '1fr';
    chatLogsPanel.style.gridTemplateRows = 'min-content';
    chatLogsPanel.style.gridTemplateAreas = `
      "header header header"
      "messages scroll-buttons users"
    `;

    // Create a container div for the panel header
    const panelHeaderContainer = document.createElement('div');
    panelHeaderContainer.className = 'panel-header';
    panelHeaderContainer.style.display = 'flex';
    panelHeaderContainer.style.flexDirection = 'row';
    panelHeaderContainer.style.gridArea = 'header';
    panelHeaderContainer.style.justifyContent = 'flex-end';
    panelHeaderContainer.style.padding = '0.6em';

    // Create a container div with class 'panel-control-buttons'
    const panelControlButtons = document.createElement('div');
    panelControlButtons.className = 'panel-control-buttons';
    panelControlButtons.style.display = 'flex';

    // Create a container div for the search input
    const chatlogsSearchContainer = document.createElement('div');
    chatlogsSearchContainer.className = 'search-for-chatlogs-messages';
    chatlogsSearchContainer.style.width = '100%';
    chatlogsSearchContainer.style.margin = '0 20px';
    chatlogsSearchContainer.style.display = 'flex';

    // Create the input field for searching users
    const chatlogsSearchInput = document.createElement('input');
    chatlogsSearchInput.className = 'chatlogs-search-input';
    chatlogsSearchInput.type = 'text';
    chatlogsSearchInput.style.outline = 'none';
    chatlogsSearchInput.style.height = '48px';
    chatlogsSearchInput.style.width = '100%';
    chatlogsSearchInput.style.padding = '10px';
    chatlogsSearchInput.style.margin = '0 1em';
    chatlogsSearchInput.style.fontSize = '1em';
    chatlogsSearchInput.style.fontFamily = 'Montserrat';
    chatlogsSearchInput.style.setProperty('color', 'bisque', 'important');
    chatlogsSearchInput.style.setProperty('border-radius', '0.2em', 'important');
    chatlogsSearchInput.style.boxSizing = 'border-box';
    chatlogsSearchInput.style.backgroundColor = '#111';
    chatlogsSearchInput.style.border = '1px solid #222';

    // Append search input to the search container
    chatlogsSearchContainer.appendChild(chatlogsSearchInput);
    // Append the search container to the panel header container
    panelHeaderContainer.appendChild(chatlogsSearchContainer);

    // Add input event listener to filter items as the user types
    chatlogsSearchInput.addEventListener('input', () => filterItems(chatlogsSearchInput.value));

    // Add click event listener to clear the search input by LMB click with Ctrl key pressed
    chatlogsSearchInput.addEventListener('click', () => isCtrlKeyPressed && (chatlogsSearchInput.value = ''));

    // Focus on the search input using requestAnimationFrame
    function focusOnSearchField() { requestAnimationFrame(function () { chatlogsSearchInput.focus(); }); } focusOnSearchField();

    // Helper function to apply common styles to a header button
    function applyHeaderButtonStyles(button, backgroundColor, margin = '0 0.5em') {
      button.style.backgroundColor = backgroundColor;
      button.style.width = '48px';
      button.style.height = '48px';
      button.style.display = 'flex';
      button.style.justifyContent = 'center';
      button.style.alignItems = 'center';
      button.style.cursor = 'pointer';
      button.style.setProperty('border-radius', '0.2em', 'important');
      button.style.margin = margin; // Set margin using the provided value
      button.style.filter = 'brightness(1)';
      button.style.transition = 'filter 0.3s ease';
    }

    // Helper function to apply common styles to a scroll button
    function applyScrollButtonStyles(button) {
      button.style.width = '48px';
      button.style.height = '48px';
      button.style.display = 'flex';
      button.style.justifyContent = 'center';
      button.style.alignItems = 'center';
      button.style.cursor = 'pointer';
      button.style.setProperty('border-radius', '0.2em', 'important');
      button.style.backgroundColor = '#282b2f';
      button.style.margin = '0.5em 0';
      button.style.filter = 'brightness(1)';
      button.style.transition = 'filter 0.3s ease';
    }

    // Create a date input toggle with similar styles as the close button
    const dateInputToggle = document.createElement('div');
    dateInputToggle.className = 'date-panel-button';
    dateInputToggle.innerHTML = calendarSVG;
    // Apply common styles using the helper function with a different background color
    applyHeaderButtonStyles(dateInputToggle, 'steelblue');
    dateInputToggle.style.margin = '0px 0.5em 0 0';

    // Function to toggle visibility of an element
    function toggleDateInputVisibility(element) {
      element.style.display = element.style.display === 'none' ? 'flex' : 'none';
    }

    // Function to show the date input if it is currently hidden
    function showDateInput(element) {
      if (element.style.display === 'none') element.style.display = 'flex';
    }

    // Toggle the visibility of the date input when the toggle is clicked
    dateInputToggle.addEventListener('click', () => {
      toggleDateInputVisibility(dateInput);
    });

    // Create the date input field
    const dateInput = document.createElement('input');
    dateInput.type = 'date';
    dateInput.className = 'chatlogs-date-input';

    // Apply consistent styles
    dateInput.style.backgroundColor = '#111';
    dateInput.style.color = 'bisque';
    dateInput.style.border = '1px solid #222';
    dateInput.style.width = 'fit-content';
    dateInput.style.height = '48px';
    dateInput.style.padding = '10px';
    dateInput.style.fontSize = '1em';
    dateInput.style.fontFamily = 'Montserrat';
    dateInput.style.display = 'none'; // Hidden by default
    dateInput.style.setProperty('border-radius', '0.2em', 'important');
    dateInput.style.boxSizing = 'border-box';
    dateInput.style.margin = '0 0.5em';

    // Append the date button and input field to the control buttons container
    panelControlButtons.appendChild(dateInputToggle);
    panelControlButtons.appendChild(dateInput);

    // Create a copy chatlogs button element
    const copyChatLogsUrl = document.createElement('div');
    copyChatLogsUrl.className = 'copy-current-chatlogs-url';
    // Set the inner HTML of the copy chat logs element with the clipboard SVG
    copyChatLogsUrl.innerHTML = clipboardSVG;
    copyChatLogsUrl.title = 'Copy Chat Logs Url';
    // Apply common styles to the button element
    applyHeaderButtonStyles(copyChatLogsUrl, 'steelblue');

    // Add a click event listener to copy chatLogsUrlForCopy to the clipboard
    copyChatLogsUrl.addEventListener('click', () => {
      addJumpEffect(copyChatLogsUrl, 0, 0);
      navigator.clipboard.writeText(chatLogsUrlForCopy)
        .then(() => {
          console.log('Chat logs URL copied to clipboard:', chatLogsUrlForCopy);
          // Optionally, you can provide user feedback here (e.g., show a message)
        })
        .catch(err => {
          console.error('Failed to copy: ', err);
        });
    });

    panelControlButtons.appendChild(copyChatLogsUrl);

    // Retrieve `shouldShowActiveUsers` from localStorage or set it to 'shown' if it doesn't exist
    const shouldShowActiveUsers = localStorage.getItem('shouldShowActiveUsers') || (localStorage.setItem('shouldShowActiveUsers', 'shown'), 'shown');

    // Create a toggle active users button
    const toggleActiveUsers = document.createElement('div');
    toggleActiveUsers.className = 'toggle-active-users';
    updateActiveUsersToggle(shouldShowActiveUsers); // Set initial SVG based on stored state
    applyHeaderButtonStyles(toggleActiveUsers, '#144e9d'); // Apply common styles

    // Set initial title based on stored state
    toggleActiveUsers.title = shouldShowActiveUsers === 'shown' ? 'Hide User List' : 'Show User List';

    // Function to update the toggle button's SVG and title based on current state
    function updateActiveUsersToggle(state) {
      toggleActiveUsers.innerHTML = state === 'shown' ? toggleLeftSVG : toggleRightSVG; // Toggle between SVGs
      toggleActiveUsers.title = state === 'shown' ? 'Hide User List' : 'Show User List'; // Update title based on state
    }

    // Function to toggle active users and update localStorage, SVG, and title
    function toggleActiveUsersState() {
      const newState = localStorage.getItem('shouldShowActiveUsers') === 'shown' ? 'hidden' : 'shown'; // Determine new state
      localStorage.setItem('shouldShowActiveUsers', newState); // Update localStorage
      updateActiveUsersToggle(newState); // Update the displayed SVG and title

      if (newState === 'shown') {
        // Call renderActiveUsers to update the display of active users based on their message counts
        renderActiveUsers(usernameMessageCountMap, chatLogsPanel);
      } else {
        // Remove the active users container if the state is hidden
        const activeUsersContainer = chatLogsPanel.querySelector('.active-users');
        if (activeUsersContainer) {
          chatLogsPanel.removeChild(activeUsersContainer);
        }
      }
    }

    // Add click event to toggle active users
    toggleActiveUsers.addEventListener('click', toggleActiveUsersState);

    // Append the toggle active users to the panel control buttons
    panelControlButtons.appendChild(toggleActiveUsers);

    // Create and style the chevron left button
    const oneDayBackward = document.createElement('div');
    oneDayBackward.className = 'chevron-left-button';
    oneDayBackward.title = 'Previous Day';
    oneDayBackward.innerHTML = chevronLeftSVG; // Assuming you have chevronLeftSVG defined
    applyHeaderButtonStyles(oneDayBackward, 'darkcyan');

    // Create and style the chevron right button
    const oneDayForward = document.createElement('div');
    oneDayForward.className = 'chevron-right-button';
    oneDayForward.title = 'Next Day';
    oneDayForward.innerHTML = chevronRightSVG; // Assuming you have chevronRightSVG defined
    applyHeaderButtonStyles(oneDayForward, 'darkcyan');

    // Create and style the shuffle button
    const randomDay = document.createElement('div');
    randomDay.className = 'shuffle-button';
    randomDay.title = 'Random Date';
    randomDay.innerHTML = shuffleSVG; // Assuming you have shuffleSVG defined
    applyHeaderButtonStyles(randomDay, 'darkslateblue');

    // Function to get current date or fallback to today's date
    function getEffectiveDate() {
      return dateInput.value ? new Date(dateInput.value) : new Date(); // Use dateInput value or today's date
    }

    // Event listener for the chevron left button
    oneDayBackward.addEventListener('click', async () => {
      const currentDate = getEffectiveDate(); // Get the effective date
      currentDate.setDate(currentDate.getDate() - 1); // Go one day back
      const formattedDate = new Intl.DateTimeFormat('en-CA').format(currentDate);
      dateInput.value = formattedDate; // Update the date input
      dateInputToggle.title = `Current date: ${formattedDate}`; // Update title
      await loadChatLogs(currentDate); // Load chat logs for the updated date
      showDateInput(dateInput);
      focusOnSearchField();
    });

    // Event listener for the chevron right button
    oneDayForward.addEventListener('click', async () => {
      const currentDate = getEffectiveDate(); // Get the effective date
      currentDate.setDate(currentDate.getDate() + 1); // Go one day forward
      const formattedDate = new Intl.DateTimeFormat('en-CA').format(currentDate);
      dateInput.value = formattedDate; // Update the date input
      dateInputToggle.title = `Current date: ${formattedDate}`; // Update title
      await loadChatLogs(currentDate); // Load chat logs for the updated date
      showDateInput(dateInput);
      focusOnSearchField();
    });

    // Event listener for the shuffle button
    randomDay.addEventListener('click', async () => {
      const randomDate = getRandomDateInRange(); // Get a random date
      const formattedDate = new Intl.DateTimeFormat('en-CA').format(new Date(randomDate));
      dateInput.value = formattedDate; // Update the date input
      dateInputToggle.title = `Current date: ${formattedDate}`; // Update title
      await loadChatLogs(randomDate); // Load chat logs for the random date
      showDateInput(dateInput);
      focusOnSearchField();
    });

    // Append buttons to the control buttons container
    panelControlButtons.appendChild(oneDayBackward);
    panelControlButtons.appendChild(oneDayForward);
    panelControlButtons.appendChild(randomDay);

    // Create a close button with the provided SVG icon
    const closePanelButton = document.createElement('div');
    closePanelButton.className = 'close-panel-button';
    closePanelButton.title = 'Close panel';
    closePanelButton.innerHTML = closeSVG;
    // Apply common styles using the helper function
    applyHeaderButtonStyles(closePanelButton, 'darkolivegreen', '0 0 0 0.5em');

    // Add a click event listener to the close panel button
    closePanelButton.addEventListener('click', () => {
      // Fade out the chat logs panel when the close button is clicked
      fadeTargetElement(chatLogsPanel, 'hide');
      fadeDimmingElement('hide');
    });

    // Append close button to control buttons, and control buttons to header
    panelControlButtons.appendChild(closePanelButton);
    panelHeaderContainer.appendChild(panelControlButtons);


    // Create a container for the chat logs
    const chatLogsContainer = document.createElement('div');
    chatLogsContainer.className = 'chat-logs-container';
    chatLogsContainer.style.overflowY = 'auto';
    chatLogsContainer.style.height = 'calc(100% - 1em)';
    chatLogsContainer.style.padding = '1em';
    chatLogsContainer.style.display = 'flex';
    chatLogsContainer.style.gridArea = 'messages';
    chatLogsContainer.style.flexDirection = 'column';

    // Create a container for the chat logs scroll buttons
    const scrollButtonsContainer = document.createElement('div');
    scrollButtonsContainer.className = 'scroll-buttons-container';
    scrollButtonsContainer.style.display = 'flex';
    scrollButtonsContainer.style.justifyContent = 'center';
    scrollButtonsContainer.style.gridArea = 'scroll-buttons';
    scrollButtonsContainer.style.flexDirection = 'column';
    scrollButtonsContainer.style.height = 'calc(100% - 1em)';
    scrollButtonsContainer.style.padding = '1em';

    // Function to scroll the chat logs
    function scrollChatLogs(direction, isFullScroll) {
      if (chatLogsContainer) {
        const scrollAmount = isFullScroll ? chatLogsContainer.scrollHeight : chatLogsContainer.clientHeight;

        if (direction === 'up') {
          chatLogsContainer.scrollBy({ top: -scrollAmount, behavior: 'smooth' });
        } else if (direction === 'down') {
          chatLogsContainer.scrollBy({ top: scrollAmount, behavior: 'smooth' });
        }

        // Update button opacity after scrolling
        updateScrollButtonOpacity();
      }
    }

    // Compact function to update scroll button styles based on scroll position
    function updateScrollButtonOpacity() {
      if (chatLogsContainer) {
        const tolerance = 3; // Account for minor discrepancies
        const isAtTop = chatLogsContainer.scrollTop === 0;
        const isAtBottom = chatLogsContainer.scrollTop + chatLogsContainer.clientHeight >= chatLogsContainer.scrollHeight - tolerance;

        // Helper to set opacity and pointer-events
        const setButtonState = (button, condition) => {
          button.style.opacity = condition ? '0.3' : '1';
          button.style.pointerEvents = condition ? 'none' : 'auto';
        };

        // Apply state to buttons
        [fullScrollUpButton, partialScrollUpButton].forEach(btn => setButtonState(btn, isAtTop));
        [fullScrollDownButton, partialScrollDownButton].forEach(btn => setButtonState(btn, isAtBottom));
      }
    }

    // Create the "Full Scroll Up" button (chevrons)
    const fullScrollUpButton = document.createElement('div');
    fullScrollUpButton.innerHTML = chevronsUpSVG;
    applyScrollButtonStyles(fullScrollUpButton);
    fullScrollUpButton.title = 'Scroll Up (Full)';
    fullScrollUpButton.addEventListener('click', () => scrollChatLogs('up', true)); // Full scroll up
    scrollButtonsContainer.appendChild(fullScrollUpButton);

    // Create the "Partial Scroll Up" button (single chevron)
    const partialScrollUpButton = document.createElement('div');
    partialScrollUpButton.innerHTML = chevronUpSVG;
    applyScrollButtonStyles(partialScrollUpButton);
    partialScrollUpButton.title = 'Scroll Up (Partial)';
    partialScrollUpButton.addEventListener('click', () => scrollChatLogs('up', false)); // Single scroll up
    scrollButtonsContainer.appendChild(partialScrollUpButton);

    // Create the "Partial Scroll Down" button (single chevron)
    const partialScrollDownButton = document.createElement('div');
    partialScrollDownButton.innerHTML = chevronDownSVG;
    applyScrollButtonStyles(partialScrollDownButton);
    partialScrollDownButton.title = 'Scroll Down (Partial)';
    partialScrollDownButton.addEventListener('click', () => scrollChatLogs('down', false)); // Single scroll down
    scrollButtonsContainer.appendChild(partialScrollDownButton);

    // Create the "Full Scroll Down" button (chevrons)
    const fullScrollDownButton = document.createElement('div');
    fullScrollDownButton.innerHTML = chevronsDownSVG;
    applyScrollButtonStyles(fullScrollDownButton);
    fullScrollDownButton.title = 'Scroll Down (Full)';
    fullScrollDownButton.addEventListener('click', () => scrollChatLogs('down', true)); // Full scroll down
    scrollButtonsContainer.appendChild(fullScrollDownButton);

    // Initial check for button opacity
    updateScrollButtonOpacity();

    // Listen for scroll events to dynamically update button opacity
    chatLogsContainer.addEventListener('scroll', updateScrollButtonOpacity);

    // Append the header and chat logs container to the chat logs panel
    chatLogsPanel.appendChild(panelHeaderContainer);
    chatLogsPanel.appendChild(chatLogsContainer);
    chatLogsPanel.appendChild(scrollButtonsContainer);

    // Create an array containing the buttons we want to apply the events to
    const buttons = [
      fullScrollUpButton,
      fullScrollDownButton,
      partialScrollUpButton,
      partialScrollDownButton,
      copyChatLogsUrl,
      toggleActiveUsers,
      dateInputToggle,
      oneDayBackward,
      oneDayForward,
      randomDay,
      closePanelButton
    ];

    // Iterate through each button in the array
    buttons.forEach(button => {
      // Add a mouseover event listener to change the button's brightness on hover
      button.addEventListener('mouseover', () => {
        button.style.filter = 'brightness(0.8)'; // Dim the button
      });

      // Add a mouseout event listener to reset the button's brightness when not hovered
      button.addEventListener('mouseout', () => {
        button.style.filter = 'brightness(1)'; // Reset to original brightness
      });
    });

    // Append the chat logs panel to the body
    document.body.appendChild(chatLogsPanel);

    // Fade in the chat logs panel and dimming background
    fadeTargetElement(chatLogsPanel, 'show');
    fadeDimmingElement('show');

    // Define an object to store the hue for each username
    const usernameHueMap = {};
    const hueStep = 15;
    let lastDisplayedUsername = null; // Variable to track the last displayed username
    // Initialize a map to track message counts for unique usernames
    const usernameMessageCountMap = new Map();
    // Store the current chat logs URL for clipboard copy.
    let chatLogsUrlForCopy = ''; // Store the current chat logs URL for copying

    // Function to load and display chat logs into the container
    const loadChatLogs = async (date) => {
      // Fetch chat logs and pass the chatLogsContainer as the parent container
      const { chatlogs, url } = await fetchChatLogs(date, chatLogsContainer);

      // Assign the fetched URL to the chatLogsUrlForCopy variable
      chatLogsUrlForCopy = url;

      // Clear previous counts
      usernameMessageCountMap.clear();

      chatlogs.forEach(({ time, username, message }) => {
        // Update message count for each unique username
        usernameMessageCountMap.set(username, (usernameMessageCountMap.get(username) || 0) + 1);

        // Create a container for each message
        const messageContainer = document.createElement('div');
        messageContainer.classList.add('message-item');
        messageContainer.style.padding = '0.2em'; // Set padding for the message container
        messageContainer.style.display = 'inline-flex';

        // Create time element
        const timeElement = document.createElement('span');
        timeElement.className = 'message-time';
        timeElement.textContent = time;
        timeElement.style.color = 'darkseagreen';
        timeElement.style.margin = '0 0.4em';

        // Create username element
        const usernameElement = document.createElement('span');
        usernameElement.className = 'message-username';
        usernameElement.textContent = username; // Use the original username for display
        usernameElement.style.margin = '0 0.4em';

        // Check if the hue for this username is already stored
        let hueForUsername = usernameHueMap[username]; // Use the original username as the key

        // If the hue is not stored, generate a new random hue with the specified step
        if (!hueForUsername) {
          hueForUsername = Math.floor(Math.random() * (210 / hueStep)) * hueStep; // Limit hue to a maximum of 210
          // Store the generated hue for this username
          usernameHueMap[username] = hueForUsername; // Store hue using the original username as the key
        }

        // Apply the hue color to the username element
        usernameElement.style.color = `hsl(${hueForUsername}, 80%, 50%)`;

        // Create message text element
        const messageTextElement = document.createElement('span');
        messageTextElement.className = 'message-text';
        messageTextElement.style.color = 'lightsteelblue';
        messageTextElement.style.margin = '0 0.4em';
        messageTextElement.style.overflowWrap = 'anywhere';

        // Replace emoticons with images
        messageTextElement.innerHTML = message.replace(/:(?=\w*[a-zA-Z])(\w+):/g,
          (_, word) => `<img src="/img/smilies/${word}.gif" alt=":${word}:" title=":${word}:" class="smile">`
        );

        // Apply margin for the first message of a new user
        messageContainer.style.marginTop = lastDisplayedUsername !== username ? '0.6em' : '';

        // Update the last displayed username
        lastDisplayedUsername = username;

        // Append elements to the message container
        messageContainer.appendChild(timeElement);
        messageContainer.appendChild(usernameElement);
        messageContainer.appendChild(messageTextElement);

        // Append the message container to the chat logs container
        chatLogsContainer.appendChild(messageContainer);
      });

      // Call renderActiveUsers to update the display of active users based on their message counts
      renderActiveUsers(usernameMessageCountMap, chatLogsPanel, chatlogsSearchInput);

      requestAnimationFrame(() => {
        chatLogsContainer.scrollTop = chatLogsContainer.scrollHeight; // Scroll to the very bottom
      });

    };

    // Renders the active users based on their message counts from the provided map
    function renderActiveUsers(usernameMessageCountMap, parentContainer, searchField) {
      // Check if active users should be shown
      if (localStorage.getItem('shouldShowActiveUsers') === 'shown') {
        // Check if the activeUsers container already exists
        let activeUsers = parentContainer.querySelector('.active-users');

        // If it doesn't exist, create it
        if (!activeUsers) {
          activeUsers = document.createElement('div');
          activeUsers.className = 'active-users';
          activeUsers.style.padding = '1em';
          activeUsers.style.height = 'calc(100% - 1em)';
          activeUsers.style.width = 'fit-content';
          activeUsers.style.overflowY = 'auto';
          activeUsers.style.overflowX = 'hidden';
          activeUsers.style.gridArea = 'users';
          activeUsers.style.display = 'flex';
          activeUsers.style.flexDirection = 'column';

          // Append the newly created activeUsers container to the parent container
          parentContainer.appendChild(activeUsers);
        }

        // Sort usernames by message count in descending order
        const sortedUsernames = Array.from(usernameMessageCountMap.entries())
          .sort(([, countA], [, countB]) => countB - countA); // Sort in descending order

        // Clear previous user list in the activeUsers container
        activeUsers.innerHTML = ''; // Clear previous user list

        // Append sorted users to the activeUsers container
        sortedUsernames.forEach(([username, count]) => {
          // Create a user element
          const userElement = document.createElement('div');
          userElement.className = 'active-user-item';
          userElement.style.display = 'flex';
          userElement.style.height = 'fit-content';
          userElement.style.alignItems = 'center';
          userElement.style.justifyContent = 'left';
          userElement.style.margin = '0.2em 0';
          userElement.style.cursor = 'pointer';
          userElement.style.transition = 'filter 0.15s';

          // Compact event listeners for mouse over and mouse out
          userElement.addEventListener('mouseover', () => (userElement.style.filter = 'brightness(0.8)'));
          userElement.addEventListener('mouseout', () => (userElement.style.filter = 'brightness(1)'));

          // Add click event to populate the search input with the clicked username
          userElement.addEventListener('click', () => {
            const currentValue = chatlogsSearchInput.value.trim();
            const usernameEntry = isCtrlKeyPressed ? `, ${username}` : username;

            if (isCtrlKeyPressed) {
              // Only add the username if it's not already present
              if (!currentValue.includes(username)) {
                chatlogsSearchInput.value += currentValue ? usernameEntry : username;
              }
            } else {
              // If Ctrl key isn't pressed, replace the input value with the clicked username
              chatlogsSearchInput.value = username;
            }

            // Call the filter function with the updated input value
            filterItems(chatlogsSearchInput.value);
          });


          // Create nickname element
          const nicknameElement = document.createElement('span');
          nicknameElement.className = 'active-user-name';
          nicknameElement.textContent = username;
          nicknameElement.style.padding = '0.4em';

          // Fetch the color for the username from the hue map
          const userHue = usernameHueMap[username] || 0; // Fallback to 0 if hue not found
          nicknameElement.style.color = `hsl(${userHue}, 80%, 50%)`; // Apply the hue color

          // Create message count element
          const messageCountElement = document.createElement('span');
          messageCountElement.className = 'active-user-messages-count';
          messageCountElement.textContent = count;
          messageCountElement.style.padding = '0.4em';
          messageCountElement.style.color = `hsl(${userHue}, 80%, 50%)`; // Apply the hue color
          messageCountElement.style.backgroundColor = `hsla(${userHue}, 80%, 50%, 0.2)`;
          messageCountElement.style.setProperty('border-radius', '0.2em', 'important');

          // Append elements to user element
          userElement.appendChild(messageCountElement);
          userElement.appendChild(nicknameElement);

          // Append user element to activeUsers container
          activeUsers.appendChild(userElement);
        });
      }
    }

    // Load today's chat logs initially
    const today = new Intl.DateTimeFormat('en-CA').format(new Date()); // 'en-CA' gives 'YYYY-MM-DD' format
    await loadChatLogs(today); // Load today's logs

    // Set the max attribute to today's date
    dateInput.max = today; // Disable future dates
    dateInput.value = today; // Set the initial value to today's date
    dateInputToggle.title = `Current date: ${today}`; // Set the title with the current date

    // Add an event listener for the date input change
    dateInput.addEventListener('change', async (event) => {
      const selectedDate = event.target.value; // Get the selected date
      await loadChatLogs(selectedDate); // Load chat logs for the selected date
      dateInputToggle.title = `Current date: ${selectedDate}`; // Update the title with the selected date
    });

    // Retrieves details from message items including usernames and message text.
    function getMessageDetails(messageItems) {
      // Cache message details including text, username, and message content
      return messageItems.map(item => {
        const usernameElement = item.querySelector('.message-username');
        const username = usernameElement ? usernameElement.textContent.toLowerCase().trim() : ''; // Get username text, if available
        const messageTextElement = item.querySelector('.message-text');
        const messageText = messageTextElement ? messageTextElement.textContent.toLowerCase().trim() : ''; // Get message text, if available
        return { username, messageText };
      });
    }

    // Filters message items based on the provided query and displays matching messages.
    function filterItems(query) {
      // Helper function to replace underscores and hyphens with spaces and convert to lowercase
      function normalizeText(text) {
        return text.replace(/[_-]/g, ' ').toLowerCase(); // Replaces _ and - with spaces
      }

      // Normalize query by removing underscores and hyphens, then trimming spaces
      const queryWithoutSymbols = normalizeText(query).trim();

      // Retrieve message items within the filterItems function
      const messageItems = Array.from(document.querySelectorAll('.chat-logs-container > .message-item'));

      const messageDetails = getMessageDetails(messageItems); // Get the message details
      const isEmptyQuery = !queryWithoutSymbols;

      // Split query by commas and trim parts
      const queryParts = queryWithoutSymbols.split(',').map(part => part.trim()).filter(Boolean);

      // Count matching usernames
      const matchingUsernamesCount = queryParts.filter(part =>
        messageDetails.some(detail => normalizeText(detail.username) === part)
      ).length;

      // Determine if User Mode is active (2 or more matching usernames)
      const isUserMode = matchingUsernamesCount >= 2;

      // Filter message items based on the query
      messageItems.forEach((item, index) => {
        const messageContainer = item.closest('.message-item'); // Get the closest message item container
        const messageDetailsItem = messageDetails[index];

        let shouldDisplay = false;

        // Normalize underscores and hyphens in the username and message text
        const normalizedUsername = normalizeText(messageDetailsItem.username);
        const normalizedMessageText = normalizeText(messageDetailsItem.messageText);

        if (isEmptyQuery) {
          // Display all messages if the query is empty
          shouldDisplay = true;
        } else if (isUserMode) {
          // User Mode: Match only by username
          shouldDisplay = queryParts.some(part => normalizedUsername === part);
        } else {
          // Simple Mode: Treat the entire query (including commas) as part of the text search
          shouldDisplay = normalizedUsername.includes(queryWithoutSymbols) ||
            normalizedMessageText.includes(queryWithoutSymbols);
        }

        // Apply visibility based on shouldDisplay
        messageContainer.style.display = shouldDisplay ? 'inline-flex' : 'none';
      });
    }

    // Attach a keydown event listener to the document object
    document.addEventListener('keydown', function (event) {
      // Check if the key pressed was the "Escape" key
      if (event.key === 'Escape') {
        // Fade out the chat logs panel
        fadeTargetElement(chatLogsPanel, 'hide');
        fadeDimmingElement('hide');
      }
    });
  }

  // CREATE CHAT LOGS BUTTON (END)


  // CREATE PANEL GRAPHICAL SETTINGS BUTTON (START)

  // Global function to handle file input and process uploaded settings
  async function handleUploadSettings(event) {
    const file = event.target.files[0];
    if (file) {
      const reader = new FileReader();

      // Return a Promise to handle the asynchronous reading
      return new Promise((resolve, reject) => {
        reader.onload = function (e) {
          const jsonData = e.target.result; // Get the raw JSON string
          try {
            const settingsData = JSON.parse(jsonData); // Attempt to parse the JSON data
            // Call a function to process the uploaded settings data
            processUploadedSettings(settingsData);
            resolve(); // Resolve the promise if successful
          } catch (error) {
            console.error('Error parsing JSON data:', error.message); // Log the error message
            console.error('Invalid JSON:', jsonData); // Log the raw JSON string for debugging
            // Optional: Notify the user about the error
            alert('Failed to parse JSON data. Please check the format and try again.');
            reject(error); // Reject the promise on error
          }
        };

        reader.onerror = function (e) {
          console.error('Error reading file:', e.target.error); // Handle file reading errors
          reject(e.target.error); // Reject the promise on error
        };

        reader.readAsText(file); // Read the file as text
      });
    }
  }

  // Function to download settings as a JSON file
  function handleDownloadSettings(settingsData) {
    if (!settingsData || typeof settingsData !== 'object') {
      console.error('Invalid settings data for download.');
      alert('Cannot export settings. Please try again.');
      return;
    }

    try {
      // Define constants for indentation sizes within the function
      const tabSize2 = '  '; // 2 spaces
      const tabSize4 = '    '; // 4 spaces

      // Convert 'usersToTrack' to single-line entries with proper indentation
      const usersToTrackFormatted = settingsData.usersToTrack
        .map((user) => `${tabSize4}${JSON.stringify(user)}`) // Use defined const for indentation
        .join(',\n'); // Join with a new line for better formatting

      // Convert 'toggle' to formatted entries with proper indentation
      const toggleFormatted = settingsData.toggle
        .map(toggle => `${tabSize4}${JSON.stringify(toggle)}`) // Format each toggle item
        .join(',\n'); // Join with a new line for better formatting

      // Build the JSON structure with appropriate formatting using string concatenation
      const jsonData = '{\n' +
        `${tabSize2}"usersToTrack": [\n` +
        `${usersToTrackFormatted}\n` +
        `${tabSize2}],\n` +
        `${tabSize2}"mentionKeywords": [\n` +
        `${settingsData.mentionKeywords.map(keyword => `${tabSize4}"${keyword}"`).join(',\n')}\n` +
        `${tabSize2}],\n` +
        `${tabSize2}"moderator": [\n` + // Added moderator
        `${settingsData.moderator.map(moderator => `${tabSize4}"${moderator}"`).join(',\n')}\n` +
        `${tabSize2}],\n` +
        `${tabSize2}"ignored": [\n` +
        `${settingsData.ignored.map(user => `${tabSize4}"${user}"`).join(',\n')}\n` +
        `${tabSize2}],\n` +
        `${tabSize2}"toggle": [\n` + // Added toggle
        `${toggleFormatted}\n` +
        `${tabSize2}]\n` +
        '}';

      // Generate a filename with the current date (YYYY-MM-DD)
      const currentDate = new Intl.DateTimeFormat('en-CA').format(new Date());
      const filename = `KG_Chat_Empowerment_Settings_${currentDate}.json`;

      // Create a Blob from the JSON string and prepare it for download
      const blob = new Blob([jsonData], { type: 'application/json' });
      const url = URL.createObjectURL(blob);

      // Create a temporary link to trigger the download
      const tempLink = document.createElement('a');
      tempLink.href = url;
      tempLink.download = filename;
      document.body.appendChild(tempLink); // Append link to body

      tempLink.click(); // Trigger download
      document.body.removeChild(tempLink); // Clean up link

      URL.revokeObjectURL(url); // Clean up the Blob URL
    } catch (error) {
      console.error('Error exporting settings:', error);
      alert('Failed to export settings. Please try again.');
    }
  }

  // Function to retrieve settings from localStorage and combine them into a single object
  function getSettingsData() {
    // Retrieve data from localStorage using the appropriate keys
    const usersToTrack = JSON.parse(localStorage.getItem('usersToTrack')) || [];
    const mentionKeywords = JSON.parse(localStorage.getItem('mentionKeywords')) || [];
    const moderator = JSON.parse(localStorage.getItem('moderator')) || [];
    const ignored = JSON.parse(localStorage.getItem('ignored')) || [];
    const toggle = JSON.parse(localStorage.getItem('toggle')) || [];

    // Combine the retrieved data into a single object
    const settingsData = {
      usersToTrack: usersToTrack,
      mentionKeywords: mentionKeywords,
      moderator: moderator,
      ignored: ignored,
      toggle: toggle
    };

    return settingsData;
  }

  // Create a button to upload and apply new settings
  function createSettingsButton() {
    // Create a new element with class 'settings-button'
    const showSettingsButton = document.createElement('div');
    // Add the class 'settings-button' to the button
    showSettingsButton.classList.add('settings-button');

    showSettingsButton.title = 'Show Settings Panel';

    // Apply base button styles
    applyBaseButtonStyles(showSettingsButton);

    // Add settings-specific styles directly
    showSettingsButton.style.position = 'relative';

    // Add settings icon to the button (use the SVG icon you provided)
    showSettingsButton.innerHTML = `
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="bisque" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sliders">
      <line x1="4" y1="21" x2="4" y2="14"></line>
      <line x1="4" y1="10" x2="4" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="12"></line>
      <line x1="12" y1="8" x2="12" y2="3"></line>
      <line x1="20" y1="21" x2="20" y2="16"></line>
      <line x1="20" y1="12" x2="20" y2="3"></line>
      <line x1="1" y1="14" x2="7" y2="14"></line>
      <line x1="9" y1="8" x2="15" y2="8"></line>
      <line x1="17" y1="16" x2="23" y2="16"></line>
    </svg>
  `;

    // Create a hidden file input for uploading settings
    const importFileInput = document.createElement('input');
    importFileInput.type = 'file';
    importFileInput.accept = '.json'; // Specify the file type if needed (e.g., JSON)
    importFileInput.style.display = 'none'; // Hide the file input

    // Add an event listener to handle file selection
    importFileInput.addEventListener('change', handleUploadSettings);

    // Add a click event listener to the button
    showSettingsButton.addEventListener('click', function () {
      // Add pulse effect for the settings button
      addPulseEffect(showSettingsButton);

      if (isAltKeyPressed) {
        // Export settings
        const settingsData = getSettingsData(); // Retrieve the settings data
        handleDownloadSettings(settingsData); // Pass the retrieved settings data to the download function
      }
      else if (isCtrlKeyPressed) {
        // Import settings
        importFileInput.click();
      }
      else {
        // If Alt or Ctrl is not pressed open settings panel
        showSettingsPanel();
      }
    });

    // Append the file input to the button
    showSettingsButton.appendChild(importFileInput);

    // Append the button to the existing panel
    empowermentButtonsPanel.appendChild(showSettingsButton);
  }

  // Call the function to create the settings button
  createSettingsButton();

  // Save the current settings to localStorage
  function saveSettingsToLocalStorage() {
    localStorage.setItem('usersToTrack', JSON.stringify(usersToTrack));
    localStorage.setItem('mentionKeywords', JSON.stringify(mentionKeywords));
    localStorage.setItem('moderator', JSON.stringify(moderator));
    localStorage.setItem('ignored', JSON.stringify(ignored));
    localStorage.setItem('toggle', JSON.stringify(toggle));
  }

  // Process and apply uploaded settings
  function processUploadedSettings({
    usersToTrack: u = [],
    mentionKeywords: mk = [],
    moderator: md = [],
    ignored: i = [],
    toggle: t = []
  }) {
    // Ensure the uploaded values are valid arrays or default to the existing ones
    usersToTrack = Array.isArray(u) ? u : usersToTrack;
    mentionKeywords = Array.isArray(mk) ? mk : mentionKeywords;
    moderator = Array.isArray(md) ? md : moderator;
    ignored = Array.isArray(i) ? i : ignored;
    toggle = Array.isArray(t) ? t : toggle;

    // Save to localStorage after applying the settings
    saveSettingsToLocalStorage();
    console.log('Uploaded settings applied:', { usersToTrack, mentionKeywords, moderator, ignored, toggle });
  }

  // Inline SVG source for the "x" icon (close button)
  const closeSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="lightgreen"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-x">
        <line x1="18" y1="6" x2="6" y2="18"></line>
        <line x1="6" y1="6" x2="18" y2="18"></line>
    </svg>`;

  // Inline SVG source for the "chevrons up" icon
  const chevronsUpSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-chevrons-up">
      <polyline points="17 11 12 6 7 11"></polyline>
      <polyline points="17 18 12 13 7 18"></polyline>
  </svg>`;

  // Inline SVG source for the "chevron up" icon
  const chevronUpSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-chevron-up">
      <polyline points="18 15 12 9 6 15"></polyline>
  </svg>`;

  // Inline SVG source for the "chevron down" icon
  const chevronDownSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-chevron-down">
      <polyline points="6 9 12 15 18 9"></polyline>
  </svg>`;

  // Inline SVG source for the "chevrons down" icon
  const chevronsDownSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-chevrons-down">
      <polyline points="7 13 12 18 17 13"></polyline>
      <polyline points="7 6 12 11 17 6"></polyline>
  </svg>`;

  // Inline SVG source for the "toggle-right" icon
  const toggleRightSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 25 25"
      fill="none"
      stroke="#89bbff"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-toggle-right">
      <rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect>
      <circle cx="16" cy="12" r="3"></circle>
  </svg>`;

  // Inline SVG source for the "toggle-left" icon
  const toggleLeftSVG = `
  <svg xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 25 25"
      fill="none"
      stroke="#89bbff"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="feather feather-toggle-left">
      <rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect>
      <circle cx="8" cy="12" r="3"></circle>
  </svg>
  `;

  // Inline SVG source for the "calendar" icon
  const calendarSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="lightsteelblue"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-calendar">
        <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
        <line x1="16" y1="2" x2="16" y2="6"></line>
        <line x1="8" y1="2" x2="8" y2="6"></line>
        <line x1="3" y1="10" x2="21" y2="10"></line>
  </svg>`;

  // Inline SVG source for the "clipboard" icon
  const clipboardSVG = `
    <svg xmlns="http://www.w3.org/2000/svg" 
        width="24" 
        height="24" 
        viewBox="0 0 24 24" 
        fill="none" 
        stroke="lightsteelblue" 
        stroke-width="2" 
        stroke-linecap="round" 
        stroke-linejoin="round" 
        class="feather feather-clipboard">
        <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
        <rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
    </svg>`;

  // SVG for the "chevron left" icon, used to change chat logs one day backward
  const chevronLeftSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#1ce5e5"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-chevron-left">
      <polyline points="15 18 9 12 15 6"></polyline>
    </svg>`;

  // SVG for the "chevron right" icon, used to change chat logs one day forward
  const chevronRightSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#1ce5e5"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-chevron-right">
      <polyline points="9 18 15 12 9 6"></polyline>
    </svg>`;

  // SVG for the "shuffle" icon, used to select a random year, month, and day
  const shuffleSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#a99bff"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-shuffle">
      <polyline points="16 3 21 3 21 8"></polyline>
      <line x1="4" y1="20" x2="21" y2="3"></line>
      <polyline points="21 16 21 21 16 21"></polyline>
      <line x1="15" y1="15" x2="21" y2="21"></line>
      <line x1="4" y1="4" x2="9" y2="9"></line>
    </svg>`;

  // Inline SVG source for the trash icon
  const trashSVG = `
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
      fill="none" stroke="darkorange" stroke-width="2" stroke-linecap="round"
      stroke-linejoin="round" class="feather feather-trash-2">
      <polyline points="3 6 5 6 21 6"></polyline>
      <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
      <line x1="10" y1="11" x2="10" y2="17"></line>
      <line x1="14" y1="11" x2="14" y2="17"></line>
    </svg>`;

  // Inline SVG source for the users icon
  const usersSVG = `
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
    fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
    stroke-linejoin="round" class="feather feather-users">
    <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
    <circle cx="9" cy="7" r="4"></circle>
    <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
    <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
  </svg>`;

  // Inline SVG source for the "download" icon (export button)
  const exportSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#90b9ee"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-download">
        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
        <polyline points="7 10 12 15 17 10"></polyline>
        <line x1="12" y1="15" x2="12" y2="3"></line>
    </svg>`;

  // Inline SVG source for the "upload" icon (import button)
  const importSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#d190ee"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-upload">
        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
        <polyline points="17 8 12 3 7 8"></polyline>
        <line x1="12" y1="3" x2="12" y2="15"></line>
    </svg>`;

  // Inline SVG source for the "save" icon (save button)
  const saveSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#90eedc"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-save">
        <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
        <polyline points="17 21 17 13 7 13 7 21"></polyline>
        <polyline points="7 3 7 8 15 8"></polyline>
    </svg>`;

  // Inline SVG source for the "remove" icon (remove button)
  const removeSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="16"
        height="16"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#ee9090"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-trash">
        <polyline points="3 6 5 6 21 6"></polyline>
        <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
    </svg>`;

  // Inline SVG source for the "snowflake" icon
  const snowflakeSVG = `
  <svg xmlns="http://www.w3.org/2000/svg" 
       width="20" 
       height="20" 
       viewBox="0 0 24 24" 
       fill="none" 
       stroke="lightsteelblue" 
       stroke-width="2" 
       stroke-linecap="round" 
       stroke-linejoin="round" 
       class="feather feather-snowflake">
    <g id="snowflake">
      <line x1="12.06" y1="2.74" x2="12.06" y2="12.06" />
      <line x1="20.12" y1="7.4" x2="12.06" y2="12.06" />
      <line x1="20.12" y1="16.71" x2="12.06" y2="12.06" />
      <line x1="12.06" y1="21.37" x2="12.06" y2="12.06" />
      <line x1="3.99" y1="16.71" x2="12.06" y2="12.06" />
      <line x1="3.99" y1="7.4" x2="12.06" y2="12.06" />
      <polyline points="8.96,4.67 12.06,7.77 15.16,4.67"/>
      <polyline points="16.9,5.68 15.76,9.92 20,11.05"/>
      <polyline points="20,13.06 15.76,14.2 16.9,18.43"/>
      <polyline points="15.16,19.44 12.06,16.34 8.96,19.44"/>
      <polyline points="7.21,18.43 8.35,14.2 4.11,13.06"/>
      <polyline points="4.11,11.05 8.35,9.92 7.21,5.68"/>
    </g>
  </svg>
`;

  // Inline SVG source for the "add" icon (add button)
  const addSVG = `
    <svg xmlns="http://www.w3.org/2000/svg"
        width="16"
        height="16"
        viewBox="0 0 24 24"
        fill="none"
        stroke="#d190ee"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="feather feather-plus">
        <line x1="12" y1="5" x2="12" y2="19"></line>
        <line x1="5" y1="12" x2="19" y2="12"></line>
    </svg>`;

  // Function to display the settings panel
  function showSettingsPanel() {
    // Check if the settings panel already exists
    if (document.querySelector('.settings-panel')) return;

    // Create the settings panel container
    const settingsPanel = document.createElement('div');
    settingsPanel.className = 'settings-panel';

    // Set initial styles
    settingsPanel.style.opacity = '0';
    settingsPanel.style.backgroundColor = '#1b1b1b';
    settingsPanel.style.setProperty('border-radius', '0.6em', 'important');
    settingsPanel.style.position = 'fixed';
    settingsPanel.style.top = '100px';
    settingsPanel.style.left = '50%';
    settingsPanel.style.transform = 'translateX(-50%)';
    settingsPanel.style.width = '50vw';
    settingsPanel.style.height = '80vh';
    settingsPanel.style.zIndex = '999';
    settingsPanel.style.minWidth = '1000px';

    // Add a keydown event listener for the Esc key
    document.addEventListener('keydown', (event) => {
      if (event.key === 'Escape') {
        // Fade out the settings panel and dimming element when the Esc key is pressed
        fadeTargetElement(settingsPanel, 'hide');
        fadeDimmingElement('hide');
      }
    });

    // Create a container div for the panel header
    const panelHeaderContainer = document.createElement('div');
    panelHeaderContainer.className = 'panel-header';
    panelHeaderContainer.style.display = 'flex';
    panelHeaderContainer.style.flexDirection = 'row';
    panelHeaderContainer.style.justifyContent = 'flex-end'; // Aligns to the right
    panelHeaderContainer.style.padding = '0.6em';

    // Helper function to apply common styles to a button
    function applyHeaderButtonStyles(button, backgroundColor, margin = '0 0.5em') {
      button.style.backgroundColor = backgroundColor;
      button.style.width = '48px';
      button.style.height = '48px';
      button.style.display = 'flex';
      button.style.justifyContent = 'center';
      button.style.alignItems = 'center';
      button.style.cursor = 'pointer';
      button.style.setProperty('border-radius', '0.2em', 'important');
      button.style.margin = margin; // Set margin using the provided value
      button.style.filter = 'brightness(1)';
      button.style.transition = 'filter 0.3s ease';
    }

    // Create a close button with the provided SVG icon
    const closePanelButton = document.createElement('div');
    closePanelButton.className = 'close-panel-button';
    closePanelButton.innerHTML = closeSVG;
    closePanelButton.title = 'Close panel';
    // Apply common styles using the helper function
    applyHeaderButtonStyles(closePanelButton, 'darkolivegreen', '0 0 0 0.5em');

    // Add a click event listener to the close panel button
    closePanelButton.addEventListener('click', () => {
      // Fade out the settings panel when the close button is clicked
      fadeTargetElement(settingsPanel, 'hide');
      fadeDimmingElement('hide');
    });

    // Create a clear cache button with the provided SVG icon
    const clearCacheButton = document.createElement('div');
    clearCacheButton.className = 'clear-cache-button';
    clearCacheButton.innerHTML = trashSVG;
    clearCacheButton.title = 'Clear settings';

    // Apply common styles using the helper function
    applyHeaderButtonStyles(clearCacheButton, 'brown');

    // Add a click event listener to the clear cache button
    clearCacheButton.addEventListener('click', () => {
      clearSettingsContainers();
    })

    // Create an import button with the provided SVG icon
    const importSettingsButton = document.createElement('div');
    importSettingsButton.className = 'import-settings-button';
    importSettingsButton.innerHTML = importSVG;
    importSettingsButton.title = 'Import settings';

    // Apply common styles using the helper function
    applyHeaderButtonStyles(importSettingsButton, '#502f6b');

    // Create a save button with the provided SVG icon
    const saveSettingsButton = document.createElement('div');
    saveSettingsButton.className = 'save-settings-button';
    saveSettingsButton.innerHTML = saveSVG;
    saveSettingsButton.title = 'Save settings';
    saveSettingsButton.style.opacity = '0';

    function initializeSaveButtonLogic(saveButton) {
      const container = document.querySelector('.settings-content-container');
      if (!container) return console.error("Container not found.");

      const showButton = () => (saveButton.style.opacity = '1');
      const hideButton = () => (saveButton.style.opacity = '0');

      // Get previous values from localStorage
      const previousValues = getSettingsData();

      const handleInputChange = () => {
        const currentValues = {
          usersToTrack: [],
          mentionKeywords: [],
          moderator: [],
          ignored: [],
          toggle: []
        };

        // Process tracked items
        container.querySelectorAll('.settings-tracked-container .tracked-item').forEach(item => {
          const usernameField = item.querySelector('.tracked-username-field');
          const genderField = item.querySelector('.tracked-gender-select');
          const pronunciationField = item.querySelector('.tracked-pronunciation-field');
          const snowflakeButton = item.querySelector('.assigned-thawed-config, .assigned-frozen-config');

          const usernameValue = usernameField ? usernameField.value.trim() : '';
          const genderValue = genderField ? genderField.value.trim() : '';
          const pronunciationValue = pronunciationField ? pronunciationField.value.trim() : '';
          // Determine the state based on the button's class
          const state = snowflakeButton.classList.contains('assigned-frozen-config') ? 'frozen' : 'thawed';

          // Push current values to usersToTrack
          currentValues.usersToTrack.push({
            name: usernameValue,
            gender: genderValue,
            pronunciation: pronunciationValue,
            state
          });
        });

        // Process mention items
        container.querySelectorAll('.settings-mention-container .mention-item').forEach(item => {
          const mentionField = item.querySelector('.mention-field');
          const mentionValue = mentionField ? mentionField.value.trim() : '';
          currentValues.mentionKeywords.push(mentionValue);
        });

        // Process moderator
        container.querySelectorAll('.settings-moderator-container .moderator-item').forEach(item => {
          const moderatorField = item.querySelector('.moderator-field');
          const moderatorValue = moderatorField ? moderatorField.value.trim() : '';
          currentValues.moderator.push(moderatorValue);
        });

        // Process ignored items
        container.querySelectorAll('.settings-ignored-container .ignored-item').forEach(item => {
          const ignoredField = item.querySelector('.ignored-field');
          const ignoredValue = ignoredField ? ignoredField.value.trim() : '';
          currentValues.ignored.push(ignoredValue);
        });

        // Process toggle (yes/no) settings based on select elements within each toggle-setting item
        container.querySelectorAll('.settings-toggle-container .toggle-item').forEach(item => {
          const descriptionElement = item.querySelector('.toggle-description'); // Get the description element
          const selectElement = item.querySelector('.toggle-select'); // Select the toggle (select) element within the current toggle-item
          const selectedValue = selectElement ? selectElement.value.trim() : 'no'; // Default to 'no' if not selected

          // Get the data-toggle-name attribute value from the descriptionElement
          const toggleName = descriptionElement.getAttribute('data-toggle-name');

          // Push the current toggle setting as an object into the toggle array
          if (toggleName) {
            currentValues.toggle.push({
              name: toggleName, // Store the toggle name
              option: selectedValue // Store the selected value directly
            });
          }
        });

        // Check if any values have changed compared to previous state
        const valuesChanged = JSON.stringify(previousValues) !== JSON.stringify(currentValues);

        // Show or hide the save button based on whether values have changed
        valuesChanged ? showButton() : hideButton();

        return currentValues; // Return current values for saving later
      };

      // Attach click event to save settings when there are changes
      saveButton.addEventListener('click', () => {
        const currentValues = handleInputChange(); // Get current values before saving
        processUploadedSettings(currentValues); // Process and save the current settings
        // Update previousValues to the current state after saving
        Object.assign(previousValues, currentValues);
        hideButton(); // Optionally hide the button after saving
      });

      // Add input listeners to existing fields
      container.querySelectorAll('input, select').forEach(field => {
        field.addEventListener('input', handleInputChange);
      });

      // Function to attach event listeners to dynamically added input and select elements
      const attachEventListeners = (element) => {
        if (element.tagName === 'INPUT' || element.tagName === 'SELECT') {
          element.addEventListener('input', handleInputChange);
          // console.log('Listener attached to:', element);
        } else {
          // Check its children for input or select elements
          element.querySelectorAll('input, select').forEach((child) => {
            child.addEventListener('input', handleInputChange);
            // console.log('Listener attached to child:', child);
          });
        }
      };

      // Create a mutation observer to monitor changes in the target container
      const observer = new MutationObserver(debounce((mutationsList) => {
        mutationsList.forEach((mutation) => {
          if (mutation.type === 'childList') {
            mutation.addedNodes.forEach((node) => {
              if (node.nodeType === Node.ELEMENT_NODE) {
                // console.log('Added:', node);
                attachEventListeners(node); // Attach event listeners to new elements
              }
            });

            mutation.removedNodes.forEach((node) => {
              if (node.nodeType === Node.ELEMENT_NODE) {
                // console.log('Removed:', node);
                handleInputChange(); // Call handleInputChange to check the state after any changes
              }
            });
          }
        });
      }, 300));

      // Start observing the target container for child list changes
      observer.observe(container, {
        childList: true,
        subtree: true, // Observe all descendants as well
      });
    }

    // Apply common styles using the helper function
    applyHeaderButtonStyles(saveSettingsButton, '#2f6b63');

    // Create a hidden file input for importing settings
    const importFileInput = document.createElement('input');
    importFileInput.type = 'file';
    importFileInput.accept = '.json'; // Specify the file type
    importFileInput.style.display = 'none'; // Hide the file input

    // Add an event listener for the import file input
    importFileInput.addEventListener('change', async (event) => {
      await handleUploadSettings(event); // Wait for processing uploaded settings
      // Clear the containers before populating new data
      clearSettingsContainers();
      // Populate the UI with updated settings
      populateSettings();
    });

    // Function to clear the content of settings containers
    function clearSettingsContainers() {
      const containers = [
        '.settings-tracked-container',
        '.settings-mention-container',
        '.settings-moderator-container',
        '.settings-ignored-container'
      ];

      containers.forEach(selector => {
        const container = document.querySelector(selector);
        // Find the .add-setting-button if it exists
        const addButton = container.querySelector('.add-setting-button');
        if (container) {
          container.innerHTML = ''; // Clear the container
        }
        // Re-add the .add-setting-button if it was found
        addButton && container.appendChild(addButton);
      });
    }

    // Add a click event listener to the import button
    importSettingsButton.addEventListener('click', () => {
      importFileInput.click(); // Trigger file input click
    });

    // Append the file input to the import button
    importSettingsButton.appendChild(importFileInput);

    // Create an export button with the provided SVG icon
    const exportSettingsButton = document.createElement('div');
    exportSettingsButton.className = 'export-settings-button';
    exportSettingsButton.innerHTML = exportSVG;
    exportSettingsButton.title = 'Export settings';

    // Apply common styles using the helper function
    applyHeaderButtonStyles(exportSettingsButton, '#2f4c6b');

    // Example of how to use the getSettingsData function in the export event
    exportSettingsButton.addEventListener('click', function () {
      const settingsData = getSettingsData(); // Retrieve the settings data
      handleDownloadSettings(settingsData); // Pass the retrieved settings data to the download function
    });

    // Create an array containing the buttons we want to apply the events to
    const buttons = [
      clearCacheButton,
      closePanelButton,
      importSettingsButton,
      exportSettingsButton
    ];

    // Iterate through each button in the array
    buttons.forEach(button => {
      // Add a mouseover event listener to change the button's brightness on hover
      button.addEventListener('mouseover', () => {
        button.style.filter = 'brightness(0.8)'; // Dim the button
      });

      // Add a mouseout event listener to reset the button's brightness when not hovered
      button.addEventListener('mouseout', () => {
        button.style.filter = 'brightness(1)'; // Reset to original brightness
      });
    });

    // Append the buttons to the panel header container
    panelHeaderContainer.appendChild(saveSettingsButton);
    panelHeaderContainer.appendChild(importSettingsButton);
    panelHeaderContainer.appendChild(exportSettingsButton);
    panelHeaderContainer.appendChild(clearCacheButton);
    panelHeaderContainer.appendChild(closePanelButton);

    // Append the header to the settings panel
    settingsPanel.appendChild(panelHeaderContainer);

    // Append the header to the settings panel
    settingsPanel.appendChild(panelHeaderContainer);

    // Create a container for the settings content
    const settingsContainer = document.createElement('div');
    settingsContainer.className = 'settings-content-container';
    settingsContainer.style.overflowY = 'auto'; // Enable scrolling for settings content
    settingsContainer.style.height = 'calc(100% - 70px)'; // Adjust height considering header
    settingsContainer.style.padding = '1em';

    // Helper function to assign styles to description elements
    function assignDescriptionStyles(element) {
      element.style.position = 'relative';
      element.style.font = '1em Montserrat';
      element.style.color = 'burlywood';
      element.style.backgroundColor = 'rgba(222, 184, 135, 0.1)';
      element.style.width = 'fit-content';
      element.style.margin = '2em 1em 1em';
      element.style.padding = '0.4em 0.8em';
      element.style.textAlign = 'center';
      element.style.setProperty('border-radius', '0.4em', 'important');
      element.style.left = '50%';
      element.style.transform = 'translateX(-50%)';
    }

    // Array of settings types with corresponding emoji
    const settingsTypes = [
      { type: 'tracked', emoji: '👀' },
      { type: 'mention', emoji: '📢' },
      { type: 'moderator', emoji: '⚔️' },
      { type: 'ignored', emoji: '🛑' },
      { type: 'toggle', emoji: '🔘' }
    ];

    // Loop through each type and create description and container elements
    settingsTypes.forEach(({ type, emoji }) => {
      const description = document.createElement('div');
      description.className = `settings-${type}-description`; // Add specific class for description

      assignDescriptionStyles(description);

      // Create the description container directly
      const container = document.createElement('div');
      container.className = `settings-${type}-container`; // Add specific class for container

      // Set the text content with first letter capitalized and append emoji
      description.textContent = `${type.charAt(0).toUpperCase()}${type.slice(1).toLowerCase()} ${emoji}`;

      settingsContainer.appendChild(description);
      settingsContainer.appendChild(container);
    });


    // Append the settings content container to the settings panel
    settingsPanel.appendChild(settingsContainer);

    // Applies common styles to an settings input field element
    function styleInput(input) {
      input.style.height = '30px';
      input.style.maxWidth = '200px';
      input.style.minWidth = '150px';
      input.style.padding = '0.4em';
      input.style.font = '1em Montserrat';
      input.style.fontFamily = 'Montserrat';
      input.style.color = 'bisque';
      input.style.setProperty('border-radius', '0.2em', 'important');
      input.style.boxSizing = 'border-box';
      input.style.backgroundColor = 'rgb(17,17,17)';
      input.style.border = '1px solid rgb(34,34,34)';
    }

    /* Applies common styles to a button element for saving or removing actions.
    * @param {HTMLElement} button - The button element to style.
    * @param {string} strokeColor - The stroke color for the button.
    * @param {string} backgroundColor - The background color for the button.
    * @param {boolean} disabled - Whether the button should be styled as disabled.
    */
    function styleButton(button, strokeColor, backgroundColor, disabled) {
      button.style.stroke = strokeColor;
      button.style.width = '30px';
      button.style.height = '30px';
      button.style.display = 'flex';
      button.style.justifyContent = 'center';
      button.style.alignItems = 'center';
      button.style.backgroundColor = backgroundColor;
      button.style.setProperty('border-radius', '0.2em', 'important');
      button.style.cursor = 'pointer';
      button.style.transition = 'filter 0.3s';

      // Compact event listeners for mouse over and mouse out
      button.addEventListener('mouseover', () => (button.style.filter = 'brightness(0.8)'));
      button.addEventListener('mouseout', () => (button.style.filter = 'brightness(1)'));

      if (disabled) {
        button.style.filter = 'grayscale(1)';
        button.style.pointerEvents = 'none';
        button.style.opacity = '0.5';
      } else {
        button.style.filter = 'grayscale(0)';
      }
    }

    // Applies common styles to a select element and its options
    function styleSelect(select) {
      select.style.height = '30px';
      select.style.maxWidth = '120px';
      select.style.minWidth = '105px';
      select.style.padding = '0.4em';
      select.style.font = '1em Montserrat';
      select.style.fontFamily = 'Montserrat';
      select.style.setProperty('color', 'bisque', 'important');
      select.style.setProperty('border-radius', '0.2em', 'important');
      select.style.boxSizing = 'border-box';
      select.style.setProperty('background-color', 'rgb(17,17,17)', 'important');
      select.style.setProperty('border', '1px solid rgb(34,34,34)', 'important');

      // Style each option element
      Array.from(select.options).forEach(option => {
        option.style.height = '30px';
        option.style.setProperty('background-color', 'rgb(17,17,17)', 'important');
        option.style.setProperty('color', 'bisque', 'important');
        option.style.fontFamily = 'Montserrat';
      });
    }

    // Common function to attach click event for removing an item
    function attachRemoveListener(removeButton, item) {
      removeButton.addEventListener('click', () => {
        item.remove(); // Remove the parent element
      });
    }

    // Function to attach click event for toggling between "assigned-thawed-config" and "assigned-frozen-config"
    function attachSnowflakeListener(snowflakeButton, username) {
      snowflakeButton.addEventListener('click', () => {
        const isFrozen = snowflakeButton.classList.toggle('assigned-frozen-config');
        snowflakeButton.classList.toggle('assigned-thawed-config');

        // Set opacity based on the assigned class
        snowflakeButton.style.opacity = isFrozen ? '1' : '0.3';

        // Update localStorage using the helper function
        updateUserState(username, isFrozen ? 'frozen' : 'thawed');
      });
    }

    // Helper function to create a container element
    function createContainer(type, layout = 'inline-flex') {
      const item = document.createElement('div');
      item.className = `${type}-item`;
      item.style.display = layout;
      item.style.gap = '0.5em';
      item.style.padding = '0.25em';
      return item;
    }

    // Helper function to create an input element
    function createInput(type, value = '', placeholder = '') {
      const input = document.createElement('input');
      input.className = `${type}-field`;
      input.value = value;
      input.placeholder = placeholder;
      styleInput(input);
      return input;
    }

    // Helper function to create a remove button with styles and event listener
    function createRemoveButton(type, item) {
      const removeButton = document.createElement('div');
      removeButton.className = `remove-${type}-word`;
      removeButton.innerHTML = removeSVG;
      attachRemoveListener(removeButton, item);
      styleButton(removeButton, '#ee9090', '#6b2f2f', false);
      return removeButton;
    }

    // Helper function to create a snowflake button with styles and event listener
    function createSnowflakeButton(state = 'thawed', username) {
      const snowflakeButton = document.createElement('div');
      snowflakeButton.className = `assigned-${state}-config`;

      // Set initial opacity based on the state
      snowflakeButton.style.opacity = state === 'thawed' ? '0.3' : '1';
      snowflakeButton.innerHTML = snowflakeSVG;

      attachSnowflakeListener(snowflakeButton, username); // Pass username here
      styleButton(snowflakeButton, 'lightsteelblue', 'steelblue', false);

      return snowflakeButton;
    }

    // Function to update a specific user in localStorage to add the state property
    function updateUserState(username, state) {
      const usersData = localStorage.getItem("usersToTrack");
      if (usersData) {
        const updatedUsers = JSON.parse(usersData).map(user =>
          user.name === username ? { ...user, state } : user
        );
        localStorage.setItem("usersToTrack", JSON.stringify(updatedUsers));
      }
    }

    // Function to create a tracked item (with gender select)
    function createTrackedItem(user) {
      const item = createContainer('tracked', 'flex');

      const usernameInput = createInput('tracked-username', user.name, 'Username');
      const pronunciationInput = createInput('tracked-pronunciation', user.pronunciation, 'Pronunciation');
      const removeButton = createRemoveButton('tracked', item);

      // Set the initial state based on the user's state property, defaulting to 'thawed' if it doesn't exist
      const initialState = (user.state === 'frozen') ? 'frozen' : 'thawed';
      const snowflakeButton = createSnowflakeButton(initialState, user.name); // Pass username

      const genderSelect = document.createElement('select');
      genderSelect.className = 'tracked-gender-select';
      const genders = [
        { value: 'Male', emoji: '👨' },
        { value: 'Female', emoji: '👩' },
      ];
      genders.forEach(({ value, emoji }) => {
        const option = document.createElement('option');
        option.value = value;
        option.textContent = `${emoji} ${value}`;
        if (user.gender === value) option.selected = true;
        genderSelect.appendChild(option);
      });
      styleSelect(genderSelect);

      item.appendChild(usernameInput);
      item.appendChild(genderSelect);
      item.appendChild(pronunciationInput);
      item.appendChild(removeButton);
      item.appendChild(snowflakeButton);

      return item;
    }

    // Function to create a mention item
    function createMentionItem(keyword) {
      const item = createContainer('mention');
      const mentionInput = createInput('mention', keyword, 'Mention Keyword');
      const removeButton = createRemoveButton('mention', item);

      item.appendChild(mentionInput);
      item.appendChild(removeButton);

      return item;
    }

    // Function to create a moderator item
    function createModeratorItem(moderator) {
      const item = createContainer('moderator');
      const moderatorInput = createInput('moderator', moderator, 'Moderator Name');
      const removeButton = createRemoveButton('moderator', item);

      item.appendChild(moderatorInput);
      item.appendChild(removeButton);

      return item;
    }

    // Function to create an ignored item
    function createIgnoredItem(user) {
      const item = createContainer('ignored');
      const ignoredInput = createInput('ignored', user, 'Ignored User');
      const removeButton = createRemoveButton('ignored', item);

      item.appendChild(ignoredInput);
      item.appendChild(removeButton);

      return item;
    }

    // Function to create a toggle item with a description and select for yes/no options
    function createToggleItem(toggle, name, optionValue) {
      const item = createContainer('toggle', 'flex');
      item.style.alignItems = 'center';

      // Create the select element for yes/no
      const select = document.createElement('select');
      select.className = 'toggle-select';

      // Create the description element
      const description = document.createElement('span');
      description.className = 'toggle-description';
      description.innerText = toggle.description;
      // Set the custom data attribute for the setting using the name parameter
      description.setAttribute('data-toggle-name', name); // Set data-toggle-name to the name parameter

      // Add click event to open the image in a new tab
      description.style.cursor = 'pointer'; // Add pointer cursor to indicate it's clickable
      description.style.color = 'burlywood';
      description.style.transition = 'color 0.15s ease-in-out';

      description.addEventListener('click', () => {
        if (toggle.image) {
          window.open(toggle.image, '_blank'); // Open the image in a new tab
        }
      });

      // Compact mouseover and mouseout events
      description.addEventListener('mouseover', function () { description.style.color = 'lightgoldenrodyellow'; })
      description.addEventListener('mouseout', function () { description.style.color = 'burlywood'; });

      // Define options with emojis for yes and no
      const options = [
        { value: 'yes', emoji: '✔️' },
        { value: 'no', emoji: '❌' }
      ];

      // Create options for the select element
      options.forEach(({ value, emoji }) => {
        const option = document.createElement('option');
        option.value = value;
        option.textContent = `${emoji} ${value}`; // Format text as "✔️ yes" or "❌ no"
        select.appendChild(option);
      });

      // Set the initial value of the select based on the optionValue parameter
      select.value = optionValue; // Assign the optionValue to the select element

      // Style the select element
      styleSelect(select); // Call the styling function

      // Append the description and select to the toggle item
      item.appendChild(select);
      item.appendChild(description);

      return item; // Return the created toggle item
    }

    // Populate settings dynamically
    function populateSettings() {
      const containers = {
        usersToTrack: '.settings-tracked-container',
        mentionKeywords: '.settings-mention-container',
        moderator: '.settings-moderator-container',
        ignored: '.settings-ignored-container'
      };

      const creators = {
        usersToTrack: createTrackedItem,
        mentionKeywords: createMentionItem,
        moderator: createModeratorItem,
        ignored: createIgnoredItem
      };

      const data = getSettingsData();

      Object.entries(data).forEach(([key, items]) => {
        const container = document.querySelector(containers[key]);
        if (!container) return; // Skip if the container is null
        container.style.width = '100%';

        // Apply specific styles for mention and ignored containers
        if (key === 'mentionKeywords' || key === 'moderator' || key === 'ignored') {
          container.style.display = 'inline-flex';
          container.style.flexWrap = 'wrap';
          container.style.alignItems = 'center';
        }

        // Create and append existing items
        items.forEach(item => container.appendChild(creators[key](item)));

        // Create and append the add button with the appropriate creator
        const addButton = createAddButton(containers[key], creators[key]);
        container.appendChild(addButton);
      });

      // Retrieve the toggle settings from localStorage
      const storedToggleSettings = JSON.parse(localStorage.getItem('toggle')) || [];

      // Create and append toggle items directly for the toggle settings
      const toggleContainer = document.querySelector('.settings-toggle-container');
      const toggleSettings = [
        {
          name: 'showChatStaticNotifications',
          description: 'Show chat static notifications',
          image: 'https://i.imgur.com/oUPSi9I.jpeg'
        },
        {
          name: 'showGlobalDynamicNotifications',
          description: 'Show global dynamic notifications',
          image: 'https://i.imgur.com/8ffCdUG.jpeg'
        }
      ];


      // Create and append toggle items directly
      toggleSettings.forEach(toggle => {
        // Find the stored setting for the current toggle or default to 'yes'
        const storedSetting = storedToggleSettings.find(item => item.name === toggle.name);
        const optionValue = storedSetting ? storedSetting.option : 'yes'; // Default to 'yes' if not set
        const toggleItem = createToggleItem(toggle, toggle.name, optionValue); // Pass the toggle and name
        toggleContainer.appendChild(toggleItem); // Append the toggle item to the container
      });

    }

    // Function to create an "Add" button for dynamic item creation
    function createAddButton(containerSelector, itemCreator) {
      const addButton = document.createElement('div');
      const middleWord = containerSelector.split('-')[1]; // Extract key type (e.g., tracked, mention)
      const existingButton = document.querySelector(`.add-${middleWord}-item`); // Check if the button already exists

      if (existingButton) return existingButton; // Return the existing button if it exists

      // Set class, content, and style for the button
      addButton.className = `add-setting-button add-${middleWord}-item`;
      addButton.innerHTML = addSVG; // Add SVG icon to the button
      styleButton(addButton, '#d190ee', '#502f6b', false); // Style the button
      addButton.style.margin = '0.4em';

      // On click, validate the last item and create a new one if valid
      addButton.addEventListener('click', () => {
        const container = document.querySelector(containerSelector); // Get the container element

        // Get all settings {type} items and select the last one
        const allItems = container.querySelectorAll(`.${middleWord}-item`);
        const lastItem = allItems.length > 0 ? allItems[allItems.length - 1] : null;

        // Check if the last item has any input fields
        const inputFields = lastItem ? lastItem.querySelectorAll('input') : []; // Get all input fields in the last item
        const hasEmptyFields = Array.from(inputFields).some(field => field.value.trim().length === 0); // Check for empty fields

        // Allow creation only if the last item has no empty fields (or if there are no items yet)
        const canCreateNewItem = !lastItem || !hasEmptyFields;

        if (canCreateNewItem) {
          // Create a new empty item based on the item creator function
          const emptyItem = itemCreator === createTrackedItem
            ? itemCreator({ name: '', pronunciation: '' }) // Remove gender from tracked item creation
            : itemCreator('');

          // Check if the new item is a valid HTMLElement before inserting
          if (emptyItem instanceof HTMLElement) {
            container.insertBefore(emptyItem, addButton); // Insert the new item before the Add button
          } else {
            console.error('Invalid item created.'); // Log an error if the item is not valid
          }
        } else {
          // Alert the user if the last item is filled
          alert('Please fill in the previous item before adding a new one.');
        }
      });

      return addButton; // Return the created button
    }

    // Append the settings panel to the body
    document.body.appendChild(settingsPanel);

    // Call the function to populate settings on page load
    populateSettings();

    // Make save button work as expected
    initializeSaveButtonLogic(saveSettingsButton);

    // Fade in the settings panel and dimming background element
    fadeTargetElement(settingsPanel, 'show');
    fadeDimmingElement('show');
  }

  // CREATE PANEL GRAPHICAL SETTINGS BUTTON (END)


  // Function to retrieve the chat input field and length popup container based on the current URL
  function retrieveChatElementsByRoomType() {
    const currentURL = window.location.href; // Get the current URL
    let inputField, lengthPopupContainer;

    if (currentURL.includes('gamelist')) {
      inputField = document.querySelector('#chat-general .text'); // Selector for the general chat input
      lengthPopupContainer = document.querySelector('#chat-general .messages'); // Selector for the general chat messages
    } else if (currentURL.includes('gmid')) {
      inputField = document.querySelector('[id^="chat-game"] .text'); // Selector for the game chat input
      lengthPopupContainer = document.querySelector('[id^="chat-game"] .messages'); // Selector for the game chat messages
    } else {
      console.error('No matching room type found in the URL.');
      return null; // Return null if no matching type is found
    }

    return { inputField, lengthPopupContainer }; // Return both the input field and the length popup container
  }


  // CHAT POPUP INDICATOR LENGTH (START)

  // Select the input element and length popup container using the helper function
  const { inputField: chatField, lengthPopupContainer } = retrieveChatElementsByRoomType();

  // Create a style element for animations
  const lengthPopupAnimations = document.createElement('style');
  lengthPopupAnimations.textContent = `
    @keyframes bounceIn {
      0% { transform: translateY(0); opacity: 0; }
      50% { transform: translateY(-10px); opacity: 1; }
      100% { transform: translateY(0); opacity: 1; }
    }
    @keyframes bounceOut {
      0% { transform: translateY(0); opacity: 1; }
      50% { transform: translateY(-10px); opacity: 1; }
      100% { transform: translateY(0); opacity: 0; }
    }
    .length-field-popup {
      position: absolute; font: bold 12px Montserrat; bottom: 40px; height: 20px;
      display: flex; align-items: center; justify-content: center; padding: 2px 4px; margin: 2px;
      line-height: 20px; opacity: 0;
    }`;

  document.head.appendChild(lengthPopupAnimations);

  const lengthPopup = document.createElement('div');
  lengthPopup.className = 'length-field-popup';
  lengthPopupContainer.appendChild(lengthPopup);

  // Rename the timeout variable to be more descriptive
  let hidePopupTimeout;

  // Track the previous input length
  let previousLength = 0;

  // Function to update the color of the length popup
  function updateLengthPopupColor(length) {
    if (!lengthPopup) {
      console.error('lengthPopup is not defined');
      return;
    }

    let textColor;

    // Determine color based on the length
    if (length === 0) {
      textColor = 'hsl(200, 20%, 50%)'; // Light Blue
    } else if (length >= 1 && length <= 90) {
      textColor = 'hsl(120, 100%, 40%)'; // Bright Green
    } else if (length > 90 && length <= 100) {
      const factor = (length - 90) / 10;
      const h = Math.round(120 + factor * (60 - 120)); // Interpolating hue
      textColor = `hsl(${h}, 100%, 40%)`;
    } else if (length > 100 && length <= 190) {
      textColor = 'hsl(60, 100%, 50%)'; // Bright Yellow
    } else if (length > 190 && length <= 200) {
      const factor = (length - 190) / 10;
      const h = Math.round(60 + factor * (30 - 60)); // Interpolating hue
      textColor = `hsl(${h}, 100%, 50%)`;
    } else if (length > 200 && length <= 250) {
      textColor = 'hsl(40, 100%, 50%)'; // Orange (Updated)
    } else if (length > 250 && length <= 300) {
      const factor = (length - 250) / 50;
      const h = Math.round(40 + factor * (0 - 40)); // Interpolating hue
      textColor = `hsl(${h}, 100%, 70%)`;
    } else {
      textColor = 'hsl(0, 100%, 70%)'; // Red (Updated)
    }

    // Apply the text color to the length popup
    lengthPopup.style.color = textColor;
  }

  // Function to show the length popup with updated color and arrow direction
  function showLengthPopup(length) {
    let displayText;

    // Check if a symbol is added (→) or removed (←)
    if (length > previousLength) {
      displayText = `${length} 🡆`; // Typing: Right arrow after the length
    } else if (length < previousLength) {
      displayText = `🡄 ${length}`; // Deleting: Left arrow before the length
    } else {
      displayText = `${length}`; // No change: No arrows
    }

    lengthPopup.textContent = displayText; // Display the length and arrow
    lengthPopup.style.opacity = '1'; // Ensure it's visible
    updateLengthPopupColor(length); // Update the text color based on length
    lengthPopup.style.animation = 'bounceIn 0.5s forwards'; // Apply bounce-in animation

    // Update the previous length
    previousLength = length;
  }

  function hideLengthPopup() {
    lengthPopup.style.animation = 'bounceOut 0.5s forwards';
    setTimeout(() => {
      lengthPopup.style.opacity = '0';
    }, 500);
  }

  // Event listener for input
  chatField.addEventListener('input', function () {
    clearTimeout(hidePopupTimeout);

    const length = chatField.value.length;
    showLengthPopup(length); // Show the length popup with updated length and arrow

    // Calculate position of the popup
    const fieldTextWidthCalculator = document.createElement('span');
    fieldTextWidthCalculator.style.visibility = 'hidden';
    fieldTextWidthCalculator.style.whiteSpace = 'nowrap';
    fieldTextWidthCalculator.style.font = getComputedStyle(chatField).font;
    fieldTextWidthCalculator.textContent = chatField.value;
    document.body.appendChild(fieldTextWidthCalculator);

    const inputWidth = fieldTextWidthCalculator.offsetWidth;
    const newLeft = chatField.offsetLeft + inputWidth + 5;
    const maxLeft = chatField.offsetLeft + chatField.offsetWidth - lengthPopup.offsetWidth;
    lengthPopup.style.left = `${Math.min(newLeft, maxLeft)}px`;

    document.body.removeChild(fieldTextWidthCalculator);

    // Reset the timeout for hiding the popup
    hidePopupTimeout = setTimeout(hideLengthPopup, 1000);
  });

  // Event listener for keydown (Enter key)
  chatField.addEventListener('keydown', function (event) {
    if (event.key === 'Enter') {
      showLengthPopup(0);
      lengthPopup.style.left = '0px';
      lengthPopup.style.color = 'hsl(200, 20%, 50%)'; // Light Blue
      // Reset the timeout for hiding the popup
      hidePopupTimeout = setTimeout(hideLengthPopup, 1000);
    }
  });

  // CHAT POPUP INDICATOR LENGTH (END)


  // REMOVE UNWANTED MESSAGES

  /*
  ** This algorithm enables the removal of unpleasant messages in the chat that are unwanted.
  ** The messages are saved in localStorage and remain there until they are visible in the chat.
  ** Once a message is no longer visible in the chat, its corresponding value in localStorage is also removed.
  ** This method is helpful in storing only necessary unwanted messages, preventing an overgrowth of values over time.
  */

  function executeMessageRemover() {
    attachEventsToMessages();
    createToggleButton();
    wipeDeletedMessages();
  } // executeMessageRemover function END

  // Functions to assign different toggle button styles
  // Red color tones
  function assignHiddenButtonStyle(toggleButton) {
    toggleButton.style.backgroundColor = 'hsl(0, 20%, 10%)';
    toggleButton.style.color = 'hsl(0, 50%, 50%)';
    toggleButton.style.border = '1px solid hsl(0, 50%, 50%)';
  }
  // Green color tones
  function assignShowButtonStyle(toggleButton) {
    toggleButton.style.backgroundColor = 'hsl(90, 20%, 10%)';
    toggleButton.style.color = 'hsl(90, 50%, 50%)';
    toggleButton.style.border = '1px solid hsl(90, 50%, 50%)';
  }
  // Yellow color tones
  function assignHideButtonStyle(toggleButton) {
    toggleButton.style.backgroundColor = 'hsl(50, 20%, 10%)';
    toggleButton.style.color = 'hsl(50, 50%, 50%)';
    toggleButton.style.border = '1px solid hsl(50, 50%, 50%)';
  }

  // Function to assign styles to the delete button
  function assignDeleteButtonStyles(deleteButton, event) {
    // Set the delete button styles
    deleteButton.style.position = 'fixed';
    deleteButton.style.top = `${event.clientY}px`;
    deleteButton.style.left = `${event.clientX}px`;
    deleteButton.style.zIndex = 999;
    deleteButton.style.padding = '8px 16px';
    deleteButton.style.backgroundColor = 'hsl(0, 50%, 20%)';
    deleteButton.style.color = 'hsl(0, 60%, 70%)';
    deleteButton.style.border = '1px solid hsl(0, 50%, 35%)';
    deleteButton.style.transition = 'all 0.3s';
    deleteButton.style.filter = 'brightness(1)';

    // Set the hover styles
    deleteButton.addEventListener('mouseenter', () => {
      deleteButton.style.filter = 'brightness(1.5)';
    });

    // Set the mouse leave styles
    deleteButton.addEventListener('mouseleave', () => {
      deleteButton.style.filter = 'brightness(1)';
    });
  }

  // Functions to assign selection to the messages
  // Set selection
  function assignMessageSelection(message) {
    message.style.setProperty('background-color', 'hsla(0, 50%, 30%, .5)', 'important');
    message.style.setProperty('box-shadow', 'inset 0px 0px 0px 1px rgb(191, 64, 64)', 'important');
    message.style.setProperty('background-clip', 'padding-box', 'important');
  }
  // Clear the selection
  function clearMessageSelection() {
    const messages = document.querySelectorAll('.messages-content div p');
    messages.forEach(message => {
      message.style.removeProperty('background-color');
      message.style.removeProperty('box-shadow');
      message.style.removeProperty('background-clip');
    });
  }

  // Declare a new Set to hold selected messages
  const selectedMessages = new Set();
  // To store the data of the right mouse button drag
  let isDragging = false;
  let isRightMouseButton = false;

  // Function to attach events on every message what doesn't have any event assigned
  function attachEventsToMessages() {
    const messages = document.querySelectorAll('.messages-content div p');
    // Store timeoutID to regulate it by multiple events
    let timeoutId = null;

    messages.forEach(message => {
      // Check if the element has the 'contextmenu' id before adding a new event listener
      if (!message.hasAttribute('id') || message.getAttribute('id') !== 'contextmenu') {

        message.addEventListener('mousedown', event => {
          isRightMouseButton = event.button === 2;
          if (isRightMouseButton) {
            isDragging = true;
            clearTimeout(timeoutId);

            // Extract content from various types of child nodes
            const messageContent = getMessageContent(message);
            if (!selectedMessages.has(messageContent)) {
              selectedMessages.add(messageContent);
              console.log('Added new message inside the selectedMessages Set:', messageContent);
            }

            assignMessageSelection(message);
          }
        });

        message.addEventListener('mouseup', event => {
          isRightMouseButton = event.button === 2;
          if (isRightMouseButton) {
            isDragging = false;
          }
        });

        message.addEventListener('mouseover', event => {
          if (isDragging && isRightMouseButton) {
            // Extract content from various types of child nodes
            const messageContent = getMessageContent(message);
            if (!selectedMessages.has(messageContent)) {
              selectedMessages.add(messageContent);
              console.log('Added new message inside the selectedMessages Set:', messageContent);
            }

            assignMessageSelection(message);
          }
        });

        // Add id contextmenu to check in the future if the element has the event
        message.setAttribute('id', 'contextmenu');
        // Add an event listener for right-clicks on messages
        message.addEventListener('contextmenu', event => {
          // Prevent the default context menu from appearing
          event.preventDefault();
          // Wrap the message into visible selection to visually know what message will be deleted
          assignMessageSelection(message);

          // Check if a delete-message button already exists in the document
          const deleteButton = document.querySelector('.delete-message');

          if (deleteButton) {
            // If it exists, remove it
            deleteButton.remove();
          }

          // Create a new delete-message button
          const newDeleteButton = document.createElement('button');
          newDeleteButton.innerText = 'Delete';
          newDeleteButton.classList.add('delete-message');

          // Attach event click to new delete-message button
          newDeleteButton.addEventListener('click', () => {
            deleteSelectedMessages(message);
            newDeleteButton.remove();
            createToggleButton();
            selectedMessages.clear();
          });

          // Style the delete button
          assignDeleteButtonStyles(newDeleteButton, event);

          // Set the hover styles
          newDeleteButton.addEventListener('mouseenter', () => {
            newDeleteButton.style.filter = 'brightness(1.5)';
          });

          // Set the mouse leave styles
          newDeleteButton.addEventListener('mouseleave', () => {
            newDeleteButton.style.filter = 'brightness(1)';
          });

          // Append the new delete-message button to the document body
          document.body.appendChild(newDeleteButton);

          function hideDeleteButton() {
            // Set a new timeout to remove the delete button
            timeoutId = setTimeout(() => {
              if (!newDeleteButton.matches(':hover')) {
                newDeleteButton.remove();
                clearMessageSelection(message);
                selectedMessages.clear();
              }
            }, 1000);
          }

          hideDeleteButton();

          // Add event listener for the mouseleave event on the delete button
          newDeleteButton.addEventListener('mouseleave', () => {
            hideDeleteButton();
          });

          // Add event listener for the mouseenter event on the delete button to clear the previous timeout
          newDeleteButton.addEventListener('mouseenter', () => {
            clearTimeout(timeoutId);
          });

        });
      }
    });
  }

  // Function to extract content from various types of child nodes within a message element
  function getMessageContent(messageElement) {
    // Query the .time and .username elements
    const timeElement = messageElement.querySelector('.time');
    const usernameElement = messageElement.querySelector('.username');

    // Extract content from .time and .username elements
    const timeContent = timeElement ? timeElement.textContent.trim() : '';
    const usernameContent = usernameElement ? ` ${usernameElement.textContent.trim()} ` : '';

    // Extract content from other types of child nodes
    const otherContentArray = Array.from(messageElement.childNodes)
      .filter(node => node !== timeElement && node !== usernameElement)
      .map(node => {
        if (node.nodeType === Node.TEXT_NODE) {
          return node.textContent; // Handle #text node without trimming
        } else if (node.tagName === 'A') {
          return node.getAttribute('href').trim(); // Handle #anchor (link) node
        } else if (node.tagName === 'IMG') {
          return node.title.trim(); // Handle #img node
        } else if (node.tagName === 'IFRAME') {
          return node.getAttribute('src').trim(); // Handle #iframe node
        }
        return ''; // Return empty string for other node types
      });

    // Concatenate content while respecting the order of child nodes
    const allContentArray = [timeContent, usernameContent, ...otherContentArray];

    return allContentArray.join('');
  }

  function deleteSelectedMessages() {
    // Retrieve and backup all current selectedMessages and convert into Array
    const messagesToDelete = [...selectedMessages];

    // Get all message elements
    const messages = document.querySelectorAll('.messages-content div p');

    // Loop over each selected message content
    messagesToDelete.forEach((messageContent) => {
      // Find the corresponding DOM element
      const messageElement = Array.from(messages).find(message => getMessageContent(message) === messageContent);

      // Check if the element is found before using it
      if (messageElement) {
        // Retrieve the stored deleted messages array, or create an empty array if none exist
        const deletedMessages = JSON.parse(localStorage.getItem('deletedChatMessagesContent') || '[]');
        // Add the deleted message content to the array if it doesn't already exist
        if (!deletedMessages.includes(messageContent)) {
          deletedMessages.push(messageContent);
        }
        // Store the updated deleted messages array in localStorage
        localStorage.setItem('deletedChatMessagesContent', JSON.stringify(deletedMessages));
        // Remove the message from the selectedMessages Set
        selectedMessages.delete(messageContent);
      }
    });

    // Hide all the messages that match the localStorage value
    wipeDeletedMessages();
  }

  // Function to remove from localStorage deleted messages values what are not anymore matching the chat message
  // And also make messages in the chat to be invisible only for whose what are matching the localStorage message
  function wipeDeletedMessages() {
    const messages = document.querySelectorAll('.messages-content div p');
    // Retrieve the stored deleted messages array
    const deletedMessages = JSON.parse(localStorage.getItem('deletedChatMessagesContent') || '[]');
    // Remove any deleted messages from the array that no longer exist in the chat messages container
    const newDeletedMessages = deletedMessages.filter(content => {
      return Array.from(messages).some(message => getMessageContent(message) === content);
    });
    // Remove messages from the chat that match the deleted messages in localStorage
    deletedMessages.forEach(deletedMessage => {
      messages.forEach(message => {
        if (getMessageContent(message) === deletedMessage) {
          message.style.display = 'none';
        }
      });
    });
    // Store the updated deleted messages array in localStorage
    localStorage.setItem('deletedChatMessagesContent', JSON.stringify(newDeletedMessages));
  } // wipeDeletedMessages END

  // Declare toggleButton variable outside of the function so it is a global variable
  let toggleButton;

  // Function to create the button only if localStorage "deletedChatMessagesContent" has at least one deleted message value
  function createToggleButton() {
    // Retrieve the stored deleted messages array
    const deletedMessages = JSON.parse(localStorage.getItem('deletedChatMessagesContent') || '[]');

    // Only create the toggle button if there are deleted messages to show/hide
    if (deletedMessages.length > 0) {
      // Check if the button already exists in the DOM
      toggleButton = document.getElementById('toggleButton');
      if (toggleButton === null) {
        // Create the toggle button
        toggleButton = document.createElement('button');
        toggleButton.id = 'toggleButton';
        toggleButton.addEventListener('click', toggleHiddenMessages);
        toggleButton.style.position = 'absolute';
        toggleButton.style.top = '0';
        toggleButton.style.right = '0';
        toggleButton.style.padding = '8px 16px';
        // Initial textContent if at least one message is hidden
        toggleButton.innerText = 'Hidden';
        // Initial styles for the Hidden button
        assignHiddenButtonStyle(toggleButton);
        toggleButton.style.transition = 'all 0.3s';
        toggleButton.style.filter = 'brightness(1)';
        let backupTextContent = toggleButton.textContent;

        // Set the hover styles
        toggleButton.addEventListener('mouseenter', () => {
          if (isCtrlKeyPressed) {
            backupTextContent = toggleButton.textContent;
            toggleButton.textContent = 'Restore';
            toggleButton.style.filter = 'grayscale(1) brightness(2)';
          } else {
            toggleButton.style.filter = 'grayscale(0) brightness(2)';
          }
        });

        // Set the mouse leave styles
        toggleButton.addEventListener('mouseleave', () => {
          const isRestore = toggleButton.textContent === 'Restore';
          if (isCtrlKeyPressed || !isCtrlKeyPressed && isRestore) {
            toggleButton.textContent = backupTextContent;
          }
          toggleButton.style.filter = 'hue-rotate(0) brightness(1)';
        });

        messagesContainer.appendChild(toggleButton);
      }
    }
  }

  // Function to toggle messages display state from "NONE" to "BLOCK" and reverse
  function toggleHiddenMessages() {
    const messages = document.querySelectorAll('.messages-content div p');
    // Retrieve the stored deleted messages array
    const deletedMessages = JSON.parse(localStorage.getItem('deletedChatMessagesContent') || '[]');

    if (isCtrlKeyPressed) {
      // Set deletedChatMessagesContent in local storage as an empty array
      localStorage.setItem('deletedChatMessagesContent', JSON.stringify([]));

      // Display all messages
      messages.forEach(message => {
        message.style.display = 'block';
        message.style.removeProperty('background-color');
        message.style.removeProperty('box-shadow');
        message.style.removeProperty('background-clip');
      });

      toggleButton.remove();
    }

    if (!isCtrlKeyPressed) {

      // Check if there are any deleted messages in the local storage
      if (deletedMessages.length === 0) {
        // Hide the toggle button if there are no deleted messages
        toggleButton.style.display = 'none';
        return;
      } else {
        // Show the toggle button if there are deleted messages
        toggleButton.style.display = 'block';
      }

      // Toggle the display of each message that matches the key "deletedChatMessagesContent" data
      messages.forEach(message => {
        const messageContent = getMessageContent(message);

        if (deletedMessages.includes(messageContent)) {
          // Show hidden messages if innerText is "Hidden" and display equal "NONE"
          if (toggleButton.innerText === 'Hidden') {
            if (message.style.display === 'none') {
              // Change display to "BLOCK"
              message.style.display = 'block';
              // Wrap the message into visible selection to visually know what message will be deleted
              message.style.setProperty('background-color', 'hsla(0, 50%, 30%, .5)', 'important');
              message.style.setProperty('box-shadow', 'inset 0px 0px 0px 1px rgb(191, 64, 64)', 'important');
              message.style.setProperty('background-clip', 'padding-box', 'important');
            }
            // Show hidden messages if innerText is "Show" and display equal "NONE"
          } else if (toggleButton.innerText === 'Show') {
            if (message.style.display === 'none') {
              message.style.display = 'block';
              // Wrap the message into visible selection to visually know what message will be deleted
              message.style.setProperty('background-color', 'hsla(0, 50%, 30%, .5)', 'important');
              message.style.setProperty('box-shadow', 'inset 0px 0px 0px 1px rgb(191, 64, 64)', 'important');
              message.style.setProperty('background-clip', 'padding-box', 'important');
            }
          } else if (toggleButton.innerText === 'Hide') {
            if (message.style.display === 'block') {
              message.style.display = 'none';
              message.style.removeProperty('background-color');
              message.style.removeProperty('box-shadow');
              message.style.removeProperty('background-clip');
            }
          }
        }
      });

      // Toggle the button text and style
      if (toggleButton.innerText === 'Hide') {
        toggleButton.innerText = 'Show';
        assignShowButtonStyle(toggleButton);
      } else {
        toggleButton.innerText = 'Hide';
        assignHideButtonStyle(toggleButton);
      }

    }

  } // toggleHiddenMessages function END

  // Icon for the disabled chat button
  const iconDenied = `<svg xmlns="${svgUrl}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="rgb(255, 100, 100)" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather feather-slash">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line>
      </svg>`;

  function checkForChatState() {
    // Get references to the chat field and send button elements
    let chatField = document.querySelector('.chat .text');
    let chatSend = document.querySelector('.chat .send');

    // Define the text patterns to check for in the chatField value
    const blockedChatMessage = 'Вы не можете отправлять сообщения'; // Message indicating sending is blocked
    const lostConnectionMessage = 'Связь с сервером потеряна'; // Message indicating connection loss

    const initialTimeoutDuration = 5000; // Default timeout duration in milliseconds
    // Get the previous timeout duration from localStorage, or use 5000 (5 seconds) if not set
    let timeoutDuration = parseInt(localStorage.getItem('chatTimeoutDuration')) || initialTimeoutDuration;

    // Function to handle changes when the chatField gets disabled
    const handleChatStateChange = () => {
      // Reset timeout to 3 seconds only once at the beginning, if it's not the default
      if (!chatField.disabled && timeoutDuration !== initialTimeoutDuration) {
        timeoutDuration = initialTimeoutDuration;
        localStorage.setItem('chatTimeoutDuration', timeoutDuration.toString());
        return;
      }

      // Get the current value of chatField
      const chatFieldValue = chatField.value;

      // If the chatField is disabled, check for the blocked or lost connection messages
      if (chatField.disabled) {
        // If the chatField contains the blocked message
        if (chatFieldValue.includes(blockedChatMessage)) {
          // Enable the chatField and send button, applying styles to indicate the state
          chatField.disabled = chatSend.disabled = false; // Enable chatField and send button
          // Apply styles to the chatSend button with !important
          chatSend.style.setProperty('background-color', 'rgb(160, 35, 35)', 'important');
          chatSend.style.setProperty('background-image', `url("data:image/svg+xml,${encodeURIComponent(iconDenied)}")`, 'important');
          chatSend.style.setProperty('background-repeat', 'no-repeat', 'important');
          chatSend.style.setProperty('background-position', 'center', 'important');
          chatSend.style.setProperty('color', 'transparent', 'important');
          chatField.value = null; // Clear the chatField content
        }
        // If the chatField contains the lost connection message
        else if (chatFieldValue.includes(lostConnectionMessage)) {
          // Increment the timeout duration by 1 second (1000 ms) and store it in localStorage
          timeoutDuration += 1000;
          localStorage.setItem('chatTimeoutDuration', timeoutDuration.toString());

          // Reload the page immediately
          window.location.reload();
        }
      }
    };

    // Observe the chatField for 'disabled' attribute changes
    if (chatField) {
      new MutationObserver(mutations => {
        mutations.forEach(mutation => {
          if (mutation.attributeName === 'disabled') handleChatStateChange();
        });
      }).observe(chatField, { attributes: true });
    }

    // Handle the chat state change after a dynamic timeout
    setTimeout(handleChatStateChange, timeoutDuration);
  }


  // CHAT SWITCHER

  // Get all elements with the 'general' class
  let generalChatTabs = document.querySelectorAll('.general');
  // Get all elements with the 'game' class
  let gameChatTabs = document.querySelectorAll('.game');

  // Function to set focus on the chat input field based on the current URL on page load
  function setChatFieldFocus() {
    // Check if the chat is closed or opened
    const chatHidden = document.querySelector('#chat-wrapper.chat-hidden');

    // Determine the current URL and chat type based on URL keywords
    const currentURL = window.location.href;
    let chatInput; // Variable to store the chat input element

    if (currentURL.includes('gamelist')) {
      // If the URL contains "gamelist," it's a general chat
      chatInput = document.querySelector('#chat-general .text');
    } else if (currentURL.includes('gmid')) {
      // If the URL contains "gmid," it's a game chat
      chatInput = document.querySelector('[id^="chat-game"] .text');
    }

    // Run if the chat is not closed and a chat input element is found
    if (!chatHidden && chatInput) {
      chatInput.focus(); // Set focus on the selected chat input field
    }
  }

  // Function to set focus on the chat input field based on active chat tab on tab key press
  function toggleFocusAndSwitchTab() {
    // Check if the chat is closed or opened
    const chatHidden = document.querySelector('#chat-wrapper.chat-hidden');

    // Get general chat tabs and game chat tabs
    let generalChatTabs = document.querySelectorAll('.general');
    let gameChatTabs = document.querySelectorAll('.game');

    // Find the first visible general chat tab that is not active
    let visibleGeneralChatTab = Array.from(generalChatTabs).find(function (tab) {
      let computedStyle = window.getComputedStyle(tab);
      return computedStyle.display !== 'none' && !tab.classList.contains('active');
    });

    // Find the first visible game chat tab that is not active
    let visibleGameChatTab = Array.from(gameChatTabs).find(function (tab) {
      let computedStyle = window.getComputedStyle(tab);
      return computedStyle.display !== 'none' && !tab.classList.contains('active');
    });

    // Run if a chat tab is found
    if (!chatHidden && (visibleGeneralChatTab || visibleGameChatTab)) {
      // Click on the visible chat tab
      if (visibleGeneralChatTab) {
        visibleGeneralChatTab.click();
      } else if (visibleGameChatTab) {
        visibleGameChatTab.click();
      }

      // Determine the chat input element based on visible tabs
      let chatInput; // Variable to store the chat input element

      if (visibleGeneralChatTab) {
        // If the visible chat tab is a general chat tab, focus on general chat input
        chatInput = document.querySelector('#chat-general .text');
      } else if (visibleGameChatTab) {
        // If the visible chat tab is a game chat tab, focus on game chat input
        chatInput = document.querySelector('[id^="chat-game"] .text');
      }

      // Run if a chat input element is found
      if (chatInput) {
        chatInput.focus(); // Set focus on the selected chat input field
      }
    }
  }

  // Function to handle click event and log the clicked element
  function switchChatTab(event) {
    console.log('Clicked element:', event.target);
    let activeTab = event.target.classList.contains('general') ? 'general' : 'game';
    localStorage.setItem('activeChatTab', activeTab);
  }

  // Add click event listeners to the general chat tabs
  generalChatTabs.forEach(function (tab) {
    tab.addEventListener('click', switchChatTab);
  });

  // Add click event listeners to the game chat tabs
  gameChatTabs.forEach(function (tab) {
    tab.addEventListener('click', switchChatTab);
  });

  // Add keydown event listener to the document
  document.addEventListener('keydown', function (event) {
    // Check if the Tab key is pressed
    if (event.key === 'Tab') {
      // Call toggleFocusAndSwitchTab function when Tab key is pressed
      toggleFocusAndSwitchTab();
      // Prevent the default tab behavior (moving focus to the next element in the DOM)
      event.preventDefault();
    }
  });

  // Function to restore chat tab from localStorage and set the focus for game page
  function restoreChatTabAndFocus() {
    let activeTab = localStorage.getItem('activeChatTab');
    let chatInput; // Variable to store the chat input element to be focused

    if (activeTab === 'general') {
      let visibleGeneralChatTab = Array.from(generalChatTabs).find(function (tab) {
        let computedStyle = window.getComputedStyle(tab);
        return computedStyle.display !== 'none' && !tab.classList.contains('active');
      });
      if (visibleGeneralChatTab) {
        visibleGeneralChatTab.click();
        chatInput = document.querySelector('#chat-general .text');
      }
    } else if (activeTab === 'game') {
      let visibleGameChatTab = Array.from(gameChatTabs).find(function (tab) {
        let computedStyle = window.getComputedStyle(tab);
        return computedStyle.display !== 'none' && !tab.classList.contains('active');
      });
      if (visibleGameChatTab) {
        visibleGameChatTab.click();
        chatInput = document.querySelector('[id^="chat-game"] .text');
      }
    }

    // Set focus on the chat input field if chatInput is defined
    if (chatInput) {
      chatInput.focus();
    }
  }

  // Function to break text into pieces of a maximum length
  function breakSentence(text) {
    const maxLength = 300; // Maximum length of each piece
    const words = text.split(' '); // Split the text into words
    const pieces = []; // Array to hold the final pieces
    let currentPiece = ''; // Variable to build the current piece

    words.forEach((word) => {
      // Check if adding the next word would exceed maxLength
      if ((currentPiece + word).length > maxLength) {
        // Push the current piece to pieces and reset currentPiece
        pieces.push(currentPiece.trim());
        currentPiece = word + ' '; // Start a new piece with the current word
      } else {
        currentPiece += word + ' '; // Add the word to the current piece
      }
    });

    // Push the last piece if it exists
    if (currentPiece) {
      pieces.push(currentPiece.trim());
    }

    return pieces;
  }

  // Function to send the message in parts
  async function sendMessageInParts(message) {
    const pieces = breakSentence(message); // Break the message into pieces
    const inputField = document.querySelector('.text'); // Get the input field element
    const sendButton = document.querySelector('.send'); // Get the send button element

    // Disable the input field only if the message is longer than 300 characters
    const isLongMessage = message.length > 300;
    if (isLongMessage) {
      inputField.disabled = true; // Disable input field for long messages
    }

    for (let index = 0; index < pieces.length; index++) {
      // Set the input field to the current piece
      const fullMessage = pieces[index]; // Use the current piece
      inputField.value = fullMessage;

      // Log each piece and its length
      console.log(`Sending piece ${index + 1}: "${fullMessage}" (Length: ${fullMessage.length})`);

      // Simulate sending the message
      sendButton.click(); // Click the send button

      // If not the last piece, generate a random delay before sending the next one
      if (index < pieces.length - 1) {
        const randomDelay = Math.floor(Math.random() * 500) + 500; // 500 ms to 1000 ms
        console.log(`Waiting for ${randomDelay} ms before sending the next piece.`);
        await new Promise(resolve => setTimeout(resolve, randomDelay)); // Use await for async delay
      }
    }

    // Re-enable the input field after all pieces have been sent, if it was disabled
    if (isLongMessage) {
      inputField.disabled = false;
    }
  }

  // Function to set up the input field listener
  function setupInputFieldListener() {
    const inputField = document.querySelector('.text'); // Get the input field element
    inputField.setAttribute('maxlength', '1000'); // Set the initial maxlength attribute to 1000
    inputField.addEventListener('keydown', (event) => {
      const message = inputField.value; // Get the current message
      // Check if the pressed key is Enter
      if (event.key === 'Enter') {
        // If the message is longer than 300, prevent the default behavior and send it in parts
        if (message.length > 300) {
          event.preventDefault(); // Prevent the default behavior (like a newline)
          // Call the function to send the message in parts
          sendMessageInParts(message);
          console.log(`Long message processed: "${message}"`);

          // Clear the input field after sending
          inputField.value = '';
        } else {
          // If the message is no longer than 300, just allow the default behavior (like a newline)
          console.log(`Short message processed: "${message}"`);
        }
      }
    });
  }

  // Function to set up input field backup and restore
  function setupInputBackup(inputSelector) {
    const inputField = document.querySelector(inputSelector); // Select the input element
    if (inputField) {
      // Restore the input value
      inputField.value = localStorage.getItem('inputBackup') || '';
      // Backup on input with debounce
      inputField.addEventListener('input', debounce(() => localStorage.setItem('inputBackup', inputField.value), 250));
      // Clear local storage on Enter
      inputField.addEventListener('keydown', (event) => { if (event.key === 'Enter') localStorage.removeItem('inputBackup'); });
    }
  }

  // create a new MutationObserver to wait for the chat to fully load with all messages
  let waitForChatObserver = new MutationObserver(mutations => {
    // Get the container for all chat messages
    const messagesContainer = document.querySelector('.messages-content div');
    // Get all the message elements from messages container
    const messages = document.querySelectorAll('.messages-content div p');

    // check if the chat element has been added to the DOM
    if (document.contains(messagesContainer)) {

      // Check for chat state
      checkForChatState();

      // check if there are at least 20 messages in the container
      if (messages.length >= 20) {
        // stop observing the DOM
        waitForChatObserver.disconnect();

        // Remove ignored users' messages if the page is not initialized
        removeIgnoredUserMessages();
        // Convert image links to visible image containers
        convertImageLinkToImage();
        // Convert YouTube links to visible iframe containers
        convertYoutubeLinkToIframe();
        // Restore chat tab from localStorage
        restoreChatTabAndFocus();
        // Call the function with the selector for the input field
        setupInputBackup('#chat-general .text');
        // Call the function to re-highlight all the mention words of the messages
        highlightMentionWords();
        // Call the function to apply the chat message grouping
        applyChatMessageGrouping();
        // Call the function to scroll to the bottom of the chat
        scrollMessages();
        // Call the function to refresh the user list and clear the cache if needed
        refreshFetchedUsers(false, cacheRefreshThresholdHours);
        // Refresh experimental custom chat user list on old list changes
        refreshUserList();
        // Call the setChatFieldFocus function when the page loads
        setChatFieldFocus();
        // Execute the function to trigger the process of chat cleaning after the youtube and images convertation to avoid issues
        executeMessageRemover();
        // Initialize the input field listener to handle message sending when Enter is pressed
        setupInputFieldListener();
      }
    }
  });

  // start observing the DOM for changes
  waitForChatObserver.observe(document, { childList: true, subtree: true });
})();