Strava and Garmin Kudos All (Working)

Adds a button to give kudos to all visible activities on Strava and Garmin Connect.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Strava and Garmin Kudos All (Working)
// @namespace    typpi.online
// @version      2.2
// @description  Adds a button to give kudos to all visible activities on Strava and Garmin Connect.
// @author       Nick2bad4u
// @license      Unlicense
// @homepageURL  https://github.com/Nick2bad4u/UserStyles/
// @grant        none
// @run-at       document-end
// @include      https://www.strava.com/*
// @include      https://connect.garmin.com/modern/*
// @icon         https://i.gyazo.com/e2fabcfc9e9fd6d011e98215764c109c.png
// @tag          garmin
// @tag          strava
// ==/UserScript==

(function () {
	'use strict';

	// Function to get localized message
	function getMessage(messageName, substitutions) {
		const messages = {
			en: { kudo_all: 'Kudo All' },
			es: { kudo_all: 'Dar Kudos a Todos' },
			fr: { kudo_all: 'Féliciter Tout le Monde' },
			de: { kudo_all: 'Allen Kudos geben' },
			it: { kudo_all: 'Dai Kudos a Tutti' },
			pt: { kudo_all: 'Dar Kudos a Todos' },
			nl: { kudo_all: 'Geef Kudos aan Iedereen' },
			ru: { kudo_all: 'Похвалить всех' },
			zh: { kudo_all: '赞所有' },
			ja: { kudo_all: '全員にKudos' },
			ko: { kudo_all: '모두에게 칭찬하기' },
			ar: { kudo_all: 'إعطاء Kudos للجميع' },
			hi: { kudo_all: 'सभी को कुडोस दें' },
			bn: { kudo_all: 'সবাইকে কুডোস দিন' },
			ta: { kudo_all: 'அனைவருக்கும் குடோஸ் கொடுக்கவும்' },
			kn: { kudo_all: 'ಎಲ್ಲರಿಗೂ ಕುದೋಸ್ ನೀಡಿ' },
			mr: { kudo_all: 'सर्वांना कूडो द्या' },
			pa: { kudo_all: 'ਸਭ ਨੂੰ ਕੂਡੋ ਦਿਓ' },
			ml: { kudo_all: 'എല്ലാവരെയും കുഡോസ് നൽകുക' },
			gu: { kudo_all: 'બધાને કૂડોસ આપો' },
			ur: { kudo_all: 'سب کو کڈو دیں' },
			vi: { kudo_all: 'Kudo Tất cả' },
			zh_tw: { kudo_all: '給所有人Kudos' },
			af: { kudo_all: 'Gee Kudos aan almal' },
			zu: { kudo_all: 'Nike Kudos bonke' },
			xh: { kudo_all: 'Nike Kudos bonke' },
			he: { kudo_all: 'תן לכולם קודוס' },
			id: { kudo_all: 'Berikan Kudos kepada Semua' },
			ms: { kudo_all: 'Berikan Kudos kepada Semua' },
			sw: { kudo_all: 'Toa Kudos kwa Wote' },
			da: { kudo_all: 'Giv Kudos til Alle' },
			no: { kudo_all: 'Gi Kudos til Alle' },
			sv: { kudo_all: 'Ge Kudos till Alla' },
			fi: { kudo_all: 'Anna Kudos kaikille' },
			th: { kudo_all: 'ให้คุดดอกทุกคน' },
			tr: { kudo_all: 'Herkese Kudos Ver' },
			pl: { kudo_all: 'Pochwal wszystkich' },
			cs: { kudo_all: 'Pošli Kudos všem' },
			sk: { kudo_all: 'Pošli Kudos všetkým' },
			hu: { kudo_all: 'Kudos Mindenkinek' },
			ro: { kudo_all: 'Felicită pe Toată Lumea' },
			hr: { kudo_all: 'Pošalji Kudos svima' },
			sr: { kudo_all: 'Пошаљи Кудос свима' },
			sl: { kudo_all: 'Pošlji Kudos vsem' },
			et: { kudo_all: 'Saada Kudos kõigile' },
			lv: { kudo_all: 'Sūtīt Kudos visiem' },
			lt: { kudo_all: 'Siųsti Kudos visiems' },
			bg: { kudo_all: 'Изпрати Кудос на всички' },
			el: { kudo_all: 'Στείλτε Kudos σε όλους' },
			uk: { kudo_all: 'Похвалити всіх' },
			ka: { kudo_all: 'ყველას Kudos მიეცი' },
			az: { kudo_all: 'Bütün Kudos göndər' },
			kk: { kudo_all: 'Барлығына Kudos жіберу' },
			tg: { kudo_all: 'Ба ҳама Kudos фиристед' },
			tk: { kudo_all: 'Hemme Kudos iber' },
			ky: { kudo_all: 'Бардыгына Kudos жөнөтүү' },
			mn: { kudo_all: 'Бүгдэд Kudos илгээх' },
			ne: { kudo_all: 'सबैलाई कूडोस दिनुहोस्' },
			ku: { kudo_all: 'Herkese Kudos Bide' },
			am: { kudo_all: 'ለሁሉም Kudos ይስጡ' },
			ps: { kudo_all: 'ټولو ته Kudos ورکړئ' },
		};

		const userLanguage = (navigator.language || navigator.userLanguage || 'en').split('-')[0]; // Get the user's language
		const messageSet = messages[userLanguage] || messages['en']; // Default to English if language not supported

		return messageSet[messageName] || (typeof substitutions === 'string' ? substitutions : '') || messageName;
	}

	const Strava = {
		getStravaContainer, // Retrieves the Strava container element
		findStravaKudosButtons, // Finds Strava kudos buttons within a specified container or the entire document
		createStravaFilter, // Creates a filter function for Strava activities based on an athlete's link
		getStravaKudosButtons, // Retrieves Strava kudos buttons based on athlete link and activity filtering
		createStravaButton, // Creates a Strava button element with a specified label
		stravaKudoAllHandler, // Handles the event to click all Strava kudos buttons on the page
		stravaStandBy, // Initiates the Strava standby mode
	};

	/**
	 * Retrieves the Strava container element.
	 *
	 * @returns {HTMLElement | null} The Strava container element, or null if not found.
	 */
	function getStravaContainer() {
		const container = document.querySelector('[class="user-nav nav-group"]');
		console.log('Strava: Found container:', container);
		return container;
	}

	/**
	 * Finds Strava kudos buttons within a specified container or the entire document.
	 * These buttons are identified by their data-testid attributes for 'kudos_button' and 'unfilled_kudos'.
	 *
	 * @param {HTMLElement} [container] - The HTML element to search within. If not provided, the entire document is searched.
	 * @returns {HTMLElement[]} An array of HTML elements representing the Strava kudos buttons found.
	 */
	function findStravaKudosButtons(container) {
		// Find Strava kudos buttons
		const kudosButtonSelector = "button[data-testid='kudos_button']"; // Define the selector for the kudos button
		const unfilledKudosSelector = "svg[data-testid='unfilled_kudos']"; // Define the selector for the unfilled kudos icon
		const selector = `${kudosButtonSelector} > ${unfilledKudosSelector}`; // Combine the selectors
		const buttons = container ? Array.from(container.querySelectorAll(selector)) : document.querySelectorAll(selector); // Find the kudos buttons
		console.log('Strava: Found kudos buttons:', buttons); // Log the found kudos buttons
		return buttons; // Return the found kudos buttons
	}

	/**
	 * Creates a filter function for Strava activities based on an athlete's link.
	 * The filter function checks if an activity item contains a link to the specified athlete.
	 * It utilizes caching to improve performance by storing the filter function and athlete link.
	 *
	 * @param {HTMLAnchorElement} athleteLink - The anchor element representing the athlete's link.
	 * @returns {function(HTMLElement): boolean} A filter function that returns true if the activity item does NOT contain a link to the specified athlete, false otherwise.
	 */
	let cachedAthleteLink = null; // Cache the athlete link
	let cachedFilterFunction = null; // Cache the filter function

	function createStravaFilter(athleteLink) {
		// Create a filter function for Strava activities
		if (!athleteLink) {
			console.error('Strava: Athlete link is null or undefined.');
			return () => false; // Return a default filter function
		}
		const url = new URL(athleteLink.href); // Get the athlete link URL
		const href = url.pathname; // Get the pathname from the URL
		if (cachedAthleteLink === href) {
			// Check if the filter is already cached
			console.log('Strava: Using cached filter for athlete link:', href); // Log the cached filter
			return cachedFilterFunction; // Return the cached filter function
		}
		console.log('Strava: Filter created for athlete link:', href); // Log the new filter
		cachedAthleteLink = href; // Cache the athlete link
		cachedFilterFunction = (item) => !item.querySelector(`a[href^="${href}"]`); // Create the filter function
		return cachedFilterFunction; // Return the filter function
	}

	/**
	 * Finds the athlete link on the page. It first attempts to find the link in the athlete profile section.
	 * If not found, it falls back to searching for any link that starts with '/athletes'.
	 * As a last resort, it searches for any link that contains '/athletes'.
	 *
	 * @returns {HTMLAnchorElement | null} The athlete link element if found, otherwise null.
	 */
	function findAthleteLink() {
		// Find the athlete link
		// Attempt to find the athlete link in the profile or fallback locations
		let athleteLink = document.querySelector("#athlete-profile a[href^='/athletes']"); // Find the athlete link in the profile
		if (!athleteLink) {
			// If the athlete link is not found
			athleteLink = document.querySelector("a[href^='/athletes']"); // Find the athlete link in the feed
			console.log('Strava: Fallback athlete link:', athleteLink); // Log the fallback athlete link
			if (!athleteLink) {
				// If the fallback athlete link is not found
				athleteLink = document.querySelector("a[href*='/athletes']"); // Find the second fallback athlete link
				console.log('Strava: Second fallback athlete link:', athleteLink); // Log the second fallback athlete link
			}
		}
		console.log('Strava: Athlete link:', athleteLink); // Log the athlete link
		return athleteLink; // Return the athlete link
	}

	/**
	 * Finds Strava activities based on the presence of athlete links within feed entries.
	 *
	 * @returns {NodeListOf<HTMLAnchorElement>} A NodeList of anchor elements representing activities.
	 */
	function findActivities() {
		// Find activities based on the athlete link
		let activities = document.querySelectorAll("div[data-testid='web-feed-entry'] > div > div > div > a[href^='/athletes']"); // Find activities based on the athlete link
		console.log('Strava: Found activities:', activities); // Log the found activities
		return activities; // Return the found activities
	}

	/**
	 * Filters an array of activities based on a given athlete link.
	 *
	 * @param {string} athleteLink - The athlete link to filter activities by.
	 * @param {HTMLElement[]} activities - An array of activity elements to filter.
	 * @returns {HTMLElement[]} - A new array containing only the activities that match the athlete link.
	 */
	function filterActivities(athleteLink, activities) {
		// Filter activities based on athlete link
		activities = Array.from(activities).filter(createStravaFilter(athleteLink)); // Filter activities based on the athlete link
		console.log('Strava: Filtered activities:', activities); // Log the filtered activities
		return activities; // Return the filtered activities
	}

	/**
	 * Collects kudos buttons from a list of activities.
	 * It flattens the array of activities and finds Strava kudos buttons in each activity.
	 *
	 * @param {HTMLElement[]} activities - An array of HTML elements representing activities.
	 * @returns {HTMLButtonElement[]} - An array of HTML button elements representing the kudos buttons found in the activities.
	 */
	function collectKudosButtons(activities) {
		// Collect kudos buttons from the filtered activities
		const buttons = activities.flatMap((activity) => findStravaKudosButtons(activity)); // Collect kudos buttons from the filtered activities
		console.log('Strava: Final kudos buttons:', buttons); // Log the final kudos buttons
		return buttons; // Return the final kudos buttons
	}

	/**
	 * Retrieves Strava kudos buttons based on athlete link and activity filtering.
	 * It attempts to find kudos buttons associated with activities of a specific athlete.
	 * If athlete link or activities are not found, or if no activities remain after filtering,
	 * it falls back to the default method of finding Strava kudos buttons.
	 *
	 * @returns {NodeListOf<HTMLButtonElement> | undefined} A collection of kudos buttons,
	 * or undefined if no buttons are found.
	 */
	function getStravaKudosButtons() {
		const athleteLink = findAthleteLink(); // Find the athlete link

		// If athlete link is still not found, use the default kudos button finding method
		if (!athleteLink) {
			return findStravaKudosButtons(document); // Use the default kudos button finding method
		}

		const activities = findActivities(); // Find activities based on the athlete link

		// If no activities found, use the default kudos button finding method
		if (activities.length < 1) {
			return findStravaKudosButtons(); // Use the default kudos button finding method
		}

		const filteredActivities = filterActivities(athleteLink, activities); // Filter activities based on the athlete link

		// If no activities left after filtering, use the default kudos button finding method
		if (filteredActivities.length < 1) {
			return findStravaKudosButtons(); // Use the default kudos button finding method
		}

		return collectKudosButtons(filteredActivities); // Collect kudos buttons from the filtered activities
	}

	/**
	 * Creates a Strava button element with a specified label.
	 *
	 * @returns {HTMLLIElement} The created Strava button as an `li` element.
	 */
	function createStravaButton() {
		const label = getMessage('kudo_all', 'Kudo All'); // Get the localized message for the button label
		console.log('Strava: Creating button with label:', label); // Log the button label

		const navItemLi = document.createElement('li'); // Create the button element
		const navItemA = document.createElement('a'); // Create the button link element

		navItemLi.className = 'nav-item'; // Set the button class
		navItemLi.style.marginRight = '10px'; // Set the button margin

		navItemA.href = '#'; // Set the button link href
		navItemA.className = 'btn btn-default btn-sm empty'; // Set the button link class

		const navItemIcon = document.createElement('span'); // Create the button icon element
		navItemIcon.className = 'app-icon icon-kudo'; // Set the button icon class
		navItemIcon.style.marginRight = '5px'; // Set the button icon margin

		const navItemText = document.createElement('span'); // Create the button text element
		navItemText.className = 'ka-progress text-caption1'; // Set the button text class
		navItemText.textContent = label;

		navItemA.append(navItemIcon); // Append the icon to the button link
		navItemA.append(navItemText); // Append the text to the button link
		navItemLi.append(navItemA); // Append the link to the button

		// Add hover effect
		navItemA.addEventListener('mouseover', () => {
			navItemA.style.backgroundColor = '#2ea44f'; // Change background color on hover
			navItemA.style.color = 'white'; // Change text color on hover
			navItemA.style.transform = 'scale(1.1)'; // Slightly enlarge the button on hover
			navItemA.style.transition = 'all 0.3s ease'; // Smooth transition for the effects
			navItemA.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; // Add a shadow effect
		});

		navItemA.addEventListener('mouseout', () => {
			navItemA.style.backgroundColor = ''; // Reset background color
			navItemA.style.color = ''; // Reset text color
			navItemA.style.transform = 'scale(1)'; // Reset size
			navItemA.style.boxShadow = ''; // Remove shadow
		});

		// Add click effect
		navItemA.addEventListener('click', () => {
			navItemA.style.transform = 'scale(0.95)'; // Shrink the button slightly on click
			navItemA.style.opacity = '0.9'; // Slightly fade the button on click
			setTimeout(() => {
				navItemA.style.transform = 'scale(1)'; // Reset size after click effect
				navItemA.style.opacity = '1'; // Reset opacity after click effect
			}, 150); // Duration of the click effect
		});

		return navItemLi; // Return the button element
	}

	/**
	 * Handles the event to click all Strava kudos buttons on the page.
	 *
	 * @param {Event} event - The event that triggered the handler (e.g., a button click).
	 * @returns {void}
	 */
	function stravaKudoAllHandler(event) {
		// Handle the event to click all kudos buttons
		event.preventDefault(); // Prevent the default event behavior

		const icons = getStravaKudosButtons(); // Get the Strava kudos buttons
		console.log('Strava: Clicking all kudos buttons, count:', icons.length); // Log the number of kudos buttons

		let likedCount = 0; // Counter for the number of items liked

		icons.forEach((item) => {
			// Click all kudos buttons
			const parentItem = item?.parentElement; // Get the parent element of the kudos button
			if (parentItem) {
				// If the parent element exists
				parentItem.click(); // Click the parent element
				likedCount++; // Increment the counter
			}
		});

		// Show a popup with the number of items liked
		showPopup(`You gave kudos 👍 to ${likedCount} activities!`);
	}

	/**
	 * Initiates the Strava standby mode, which checks for the existence of a Kudo All button.
	 * If the button does not exist, it creates and prepends the button to the Strava container.
	 * The button is then attached with a click event listener to the stravaKudoAllHandler function.
	 */
	function stravaStandBy() {
		// Initiate the Strava standby mode
		console.log('Strava: Standby initiated'); // Log the initiation of the standby mode
		const buttonExisted = document.querySelector('div[class="ka-progress text-caption1"]'); // Check if the button already exists
		if (!buttonExisted) {
			// If the button does not exist
			console.log('Strava: Button does not exist, creating'); // Log the creation of the button

			const container = getStravaContainer(); // Get the Strava container

			if (container) {
				// If the container exists
				const button = createStravaButton(); // Create the button
				container.prepend(button); // Prepend the button to the container
				button.addEventListener('click', stravaKudoAllHandler); // Add a click event listener to the button
			}
		}
	}

	/**
	 * @namespace GC
	 * @description Namespace containing functions related to Garmin Connect integration.
	 * @property {function} getGarminContainer - Retrieves the Garmin container element.
	 * @property {function} findGarminKudosButtons - Finds Garmin kudos buttons within a specified container or the entire document.
	 * @property {function} createGarminButton - Creates a new kudos button for Garmin.
	 * @property {function} garminKudoAllHandler - Handles the "kudo all" action on Garmin.
	 * @property {function} executeGarmin - Executes the Garmin integration logic.
	 * @property {function} garminConnectStandBy - Sets up a standby observer for Garmin Connect.
	 */
	const GC = {
		getGarminContainer, // Retrieves the Garmin container element
		findGarminKudosButtons, // Finds Garmin kudos buttons within a specified container or the entire document
		createGarminButton, // Creates a new kudos button for Garmin
		garminKudoAllHandler, // Handles the "kudo all" action on Garmin
		executeGarmin, // Executes the Garmin integration logic
		garminConnectStandBy, // Sets up a standby observer for Garmin Connect
	};

	/**
	 * Retrieves the Garmin container element where the Kudo All button will be prepended.
	 * It searches for a div with the class "header-nav", creates a new div element with specific classes and styles,
	 * prepends it to the found container, and returns the newly created div element.
	 *
	 * @returns {HTMLElement|null} The newly created Kudo All navigation item element, or null if the container is not found.
	 */
	function getGarminContainer() {
		// Get the Garmin container
		const container = document.querySelector('div[class="header-nav"]'); // Find the Garmin container
		console.log('Garmin: Found container:', container); // Log the found container

		if (container) {
			const el = document.createElement('div'); // Create the new div element
			el.classList.add('kudo-all-nav-item', 'header-nav-item'); // Add classes to the new div element
			el.style.height = '60px'; // Set the height of the new div element
			el.style.width = '50px'; // Set the width of the new div element
			container.prepend(el); // Prepend the new div element to the container
			return document.querySelector('div[class^=kudo-all-nav-item]'); // Return the new div element
		}
		return null; // Return null if the container is not found
	}

	/**
	 * Finds Garmin kudos buttons within a specified container or the entire document.
	 *
	 * @param {HTMLElement} [container] - The container element to search within. If not provided, the entire document is searched.
	 * @returns {HTMLElement[]} An array of HTMLElement objects representing the Garmin kudos buttons found.
	 */
	function findGarminKudosButtons(container) {
		// Find Garmin kudos buttons
		const selector = `
		button[class^="CommentLikeSection_socialIconWrapper"] >
		div[class*="CommentLikeSection_animateBox"] >
		i[class*=icon-heart-inverted]
	`; // Define the selector for the kudos buttons
		const buttons = container ? Array.from(container.querySelectorAll(selector)) : Array.from(document.querySelectorAll(selector)); // Find the kudos buttons
		console.log('Garmin: Found kudos buttons:', buttons); // Log the found kudos buttons
		return buttons; // Return the found kudos buttons
	}

	/**
	 * Creates a Garmin-style button element.
	 *
	 * @returns {HTMLAnchorElement} The created Garmin-style button element.
	 */
	function createGarminButton() {
		// Create a Garmin-style button
		const label = getMessage('kudo_all', 'Kudo All'); // Get the localized message for the button label
		console.log('Garmin: Creating button with label:', label); // Log the button label

		const link = document.createElement('a'); // Create the button element
		link.href = '#'; // Set the button link href
		link.className = 'header-nav-link icon-heart-inverted'; // Set the button link class
		link.setAttribute('aria-label', label); // Set the button link aria-label
		link.setAttribute('data-original-title', label); // Set the button link data-original-title
		link.setAttribute('data-rel', 'tooltip'); // Set the button link data-rel

		// Add hover effect to fill the heart red
		link.addEventListener('mouseover', () => {
			link.style.color = 'red'; // Change the heart color to red on hover
			link.style.transform = 'scale(1.2)'; // Slightly enlarge the heart on hover
			link.style.transition = 'all 0.3s ease'; // Smooth transition for the effects
			link.style.boxShadow = '0 4px 8px rgba(255, 0, 0, 0.5)'; // Add a glowing red shadow
		});
		link.addEventListener('mouseout', () => {
			link.style.color = ''; // Reset the heart color when not hovering
			link.style.transform = 'scale(1)'; // Reset the size when not hovering
			link.style.boxShadow = ''; // Remove the shadow when not hovering
		});

		// Add click effect to briefly shrink the heart
		link.addEventListener('click', () => {
			link.style.transform = 'scale(0.9)'; // Shrink the heart slightly on click
			link.style.opacity = '0.8'; // Slightly fade the heart on click
			setTimeout(() => {
				link.style.transform = 'scale(1)'; // Reset the size after the click effect
				link.style.opacity = '1'; // Reset the opacity after the click effect
			}, 150); // Duration of the click effect
		});

		// Add a pulsating animation effect
		const pulsateKeyframes = `
			@keyframes pulsate {
				0% { transform: scale(1); }
				50% { transform: scale(1.1); }
				100% { transform: scale(1); }
			}
		`;
		const styleSheet = document.createElement('style');
		styleSheet.type = 'text/css';
		styleSheet.innerText = pulsateKeyframes;
		document.head.appendChild(styleSheet);

		link.addEventListener('mouseenter', () => {
			link.style.animation = 'pulsate 1s infinite'; // Start pulsating on hover
		});
		link.addEventListener('mouseleave', () => {
			link.style.animation = ''; // Stop pulsating when not hovering
		});

		return link;
	}

	/**
	 * Handles the event to give all kudos on the Garmin newsfeed page.
	 * Prevents default event behavior, checks if the current page is the newsfeed,
	 * finds all kudos buttons, and clicks them.
	 *
	 * @param {Event} event - The event that triggered the handler.
	 * @returns {void}
	 */
	function garminKudoAllHandler(event) {
		// Handle the event to give all kudos
		event.preventDefault(); // Prevent the default event behavior
		if (window.location.pathname !== '/modern/newsfeed') {
			// Check if the current page is the newsfeed
			console.log('Garmin: Not on the newsfeed page, redirecting'); // Log the redirect message
			window.location.href = 'https://connect.garmin.com/modern/newsfeed'; // Redirect to the newsfeed page
			return; // Abort the function
		}

		const icons = findGarminKudosButtons(); // Find all kudos buttons
		console.log('Garmin: Clicking all kudos buttons, count:', icons.length); // Log the number of kudos buttons

		let likedCount = 0; // Counter for the number of items liked

		icons.forEach((item) => {
			// Click all kudos buttons
			if (item) {
				item.click(); // Click the kudos button
				likedCount++; // Increment the counter
			}
		});

		// Show a popup with the number of items liked
		showPopup(`You gave hearts ❤️ to ${likedCount} activities!`);
	}

	/**
	 * Executes the Garmin functionality by injecting a "Kudo All" button into the Garmin page.
	 * It first retrieves the container element where the button will be appended.
	 * If the container is found, it creates the button, appends it to the container,
	 * and attaches a click event listener to the button that triggers the garminKudoAllHandler function.
	 */
	function executeGarmin() {
		// Execute the Garmin functionality
		console.log('Garmin: Execute function called'); // Log the execution of the function
		const container = getGarminContainer(); // Get the Garmin container

		if (container) {
			// If the container exists
			const button = createGarminButton(); // Create the button
			container.append(button); // Append the button to the container
			button.addEventListener('click', garminKudoAllHandler); // Add a click event listener to the button
		}
	}

	/**
	 * @function garminConnectStandBy
	 * @description Initiates a standby mode for Garmin Connect, observing the DOM for the appearance of a specific element (`header-nav`) to trigger the execution of `executeGarmin`.
	 * @listens MutationObserver
	 * @returns {void}
	 */
	function garminConnectStandBy() {
		// Initiate the Garmin standby mode
		console.log('Garmin: Standby initiated'); // Log the initiation of the standby mode

		let loaded = false; // Set the loaded flag to false

		var observer = new MutationObserver(function (mutations) {
			// Create a new mutation observer
			mutations.forEach(function (mutation) {
				// For each mutation
				if (mutation.addedNodes.length > 0) {
					// If nodes are added
					mutation.addedNodes.forEach((node) => {
						if (
							// If the target element is loaded
							!loaded && // Ensure it's not already loaded
							node.nodeType === 1 && // Ensure it's an element
							node.className && // Ensure it has a class name
							typeof node.className === 'string' && // Ensure the class name is a string
							node.className.startsWith('header-nav') // Ensure the class name starts with 'header-nav'
						) {
							console.log('Garmin: Target element loaded, executing...'); // Log the execution of the function
							loaded = true; // Set the loaded flag to true
							executeGarmin(); // Execute the Garmin functionality
							observer.disconnect(); // Disconnect the observer
						}
					});
				}
			});
		});

		if (!loaded) {
			observer.observe(document.body, {
				// Observe the body for mutations
				childList: true, // Observe child nodes
				subtree: true, // Observe all descendants
			});
		}
	}

	// Check if the current host is Strava
	/**
	 * Checks if the current hostname matches a Strava domain pattern.
	 *
	 * @returns {boolean} True if the hostname is a Strava domain, false otherwise.
	 */
	function isHostStrava() {
		// Check if the current host is Strava
		const currentHostname = window.location.hostname; // Get the current hostname
		const stravaDomainPattern = /^.*\.strava\.com$/; // Define the Strava domain pattern
		const isStrava = stravaDomainPattern.test(currentHostname); // Check if the current hostname matches the Strava domain pattern
		console.log('Host check: Is Strava?', isStrava); // Log the result of the check
		return isStrava; // Return the result of the check
	}

	// Check if the current host is Garmin
	/**
	 * Checks if the current hostname matches a Garmin domain pattern.
	 *
	 * @returns {boolean} True if the hostname is a Garmin domain, false otherwise.
	 */
	function isHostGarmin() {
		// Check if the current host is Garmin Connect
		const currentHostname = window.location.hostname; // Get the current hostname
		const garminDomainPattern = /^.*\.garmin\.com$/; // Define the Garmin domain pattern
		const isGarmin = garminDomainPattern.test(currentHostname); // Check if the current hostname matches the Garmin domain pattern 	444444444444444442
		console.log('Host check: Is Garmin?', isGarmin); // Log the result of the check
		return isGarmin; // Return the result of the check
	}

	// Initialize script on window load
	window.onload = function () {
		console.log('Kudo All script initialization started.');
		/**
		 * Checks if the current host is Strava.
		 *
		 * @returns {boolean} True if the current host is Strava, false otherwise.
		 */
		const isStravaHost = isHostStrava(); // Check if the current host is Strava
		const isGarminHost = isHostGarmin(); // Check if the current host is Garmin Connect

		if (isStravaHost) {
			// If the current host is Strava
			console.log('Detected Strava domain. Initiating Strava standby...'); // Log the initiation of the Strava standby mode
			Strava.stravaStandBy(); // Initiate the Strava standby mode
		} else if (isGarminHost) {
			// If the current host is Garmin Connect
			console.log('Detected Garmin domain. Initiating Garmin standby...'); // Log the initiation of the Garmin standby mode
			GC.garminConnectStandBy(); // Initiate the Garmin standby mode
		} else {
			// If the current host is not recognized as Strava or Garmin
			console.log('Domain not recognized as Strava or Garmin. No actions will be performed.'); // Log the unrecognized domain
		}
	};

	/**
	 * Displays a popup message on the page.
	 *
	 * @param {string} message - The message to be displayed in the popup.
	 */
	function showPopup(message) {
		// Create a popup element
		const popup = document.createElement('div');
		// Set the popup message
		popup.textContent = message;
		// Set the popup styles
		popup.style = `
			position: fixed;
			top: 20px;
			right: 20px;
			background-color: #2ea44f;
			color: white;
			padding: 10px 20px;
			border-radius: 5px;
			box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
			z-index: 1000;
			font-size: 16px;
		`;
		// Append the popup to the body
		document.body.appendChild(popup);

		// Automatically remove the popup after 3 seconds
		setTimeout(() => {
			popup.remove();
		}, 3000);
	}
})();