document.addEventListener("DOMContentLoaded", () => { const searchInput = document.getElementById("search-input"); const searchResults = document.getElementById("search-results"); const selectedServices = document.getElementById("selected-services"); const selectedCountEl = document.getElementById("selected-count"); const copyButton = document.getElementById("copyButton"); const downloadButton = document.getElementById("downloadButton"); const themeToggle = document.getElementById("theme-toggle"); const owner = "xixu-me"; const repo = "RFM"; const branch = "universal"; const apiUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`; let allServices = []; let selectedServicesList = []; // Theme management function getSystemTheme() { return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } function initTheme() { const savedTheme = localStorage.getItem("theme"); const systemTheme = getSystemTheme(); const initialTheme = savedTheme || systemTheme; document.documentElement.setAttribute("data-theme", initialTheme); updateThemeIcon(initialTheme); // Listen for system theme changes window .matchMedia("(prefers-color-scheme: dark)") .addEventListener("change", (e) => { // Only auto-switch if user hasn't manually set a preference if (!localStorage.getItem("theme")) { const newSystemTheme = e.matches ? "dark" : "light"; document.documentElement.setAttribute("data-theme", newSystemTheme); updateThemeIcon(newSystemTheme); } }); } function updateThemeIcon(theme) { const icon = themeToggle.querySelector("i"); const isSystemDefault = !localStorage.getItem("theme"); if (isSystemDefault) { // Show auto icon when following system icon.className = "fas fa-circle-half-stroke"; themeToggle.title = "Theme: Auto (following system)"; } else if (theme === "dark") { icon.className = "fas fa-sun"; themeToggle.title = "Switch to light mode"; } else { icon.className = "fas fa-moon"; themeToggle.title = "Switch to dark mode"; } } function toggleTheme() { const currentTheme = document.documentElement.getAttribute("data-theme"); const savedTheme = localStorage.getItem("theme"); if (!savedTheme) { // First click: set to opposite of current system theme const newTheme = currentTheme === "dark" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", newTheme); localStorage.setItem("theme", newTheme); updateThemeIcon(newTheme); } else { // Subsequent clicks: cycle through light -> dark -> auto if (savedTheme === "light") { document.documentElement.setAttribute("data-theme", "dark"); localStorage.setItem("theme", "dark"); updateThemeIcon("dark"); } else if (savedTheme === "dark") { // Reset to auto (system preference) localStorage.removeItem("theme"); const systemTheme = getSystemTheme(); document.documentElement.setAttribute("data-theme", systemTheme); updateThemeIcon(systemTheme); } } } async function fetchServices() { try { const response = await fetch(apiUrl); if (!response.ok) { throw new Error(`GitHub API error: ${response.status}`); } const data = await response.json(); const mrsFiles = data.tree.filter( (file) => file.path.endsWith(".mrs") && file.type === "blob" ); allServices = mrsFiles.map((file) => { const name = file.path.replace(".mrs", ""); // A simple heuristic to format the name const formattedName = name .replace(/-/g, " ") .replace("Google", "Google ") .replace(" ", ""); const serviceDefinition = { name: formattedName, }; // Improved heuristics for tld, sld, alias, domain based on favicon availability and service patterns if (name.includes("google-")) { // Google services: use google TLD for favicon consistency serviceDefinition.tld = "google"; serviceDefinition.alias = formattedName; } else if (name === "xai") { // xAI: specific case with SLD needed for proper favicon serviceDefinition.name = "xAI"; serviceDefinition.tld = "ai"; serviceDefinition.sld = "x"; } else if (name === "rednote") { // RedNote: use domain for services with non-standard favicon requirements serviceDefinition.name = "rednote"; serviceDefinition.domain = "xiaohongshu.com"; serviceDefinition.alias = "Xiaohongshu"; } else if (name === "claude-ai") { // Claude: use domain for Anthropic's service serviceDefinition.domain = "claude.ai"; } else if (name === "openai") { // OpenAI: simple case where service name matches SLD serviceDefinition.tld = "com"; } else if (name === "github") { // GitHub: simple case where service name matches SLD serviceDefinition.tld = "com"; } else if (name.includes("-")) { // Services with hyphens: check if last part is a known TLD const parts = name.split("-"); const lastPart = parts[parts.length - 1]; if (["com", "org", "net", "ai", "io", "co"].includes(lastPart)) { // If ends with known TLD, use it and add SLD if needed serviceDefinition.tld = lastPart; if (parts.length > 1) { const remainingParts = parts.slice(0, -1); if (remainingParts.length === 1) { // Single part before TLD becomes SLD serviceDefinition.sld = remainingParts[0]; } else { // Multiple parts: join as SLD or use domain for complex cases const potentialSld = remainingParts.join("-"); if (potentialSld.length <= 15) { serviceDefinition.sld = potentialSld; } else { // For very long or complex names, prefer domain serviceDefinition.domain = `${name.replace("-", ".")}.com`; } } } } else { // No clear TLD pattern: check if it's a compound service name if ( parts.length === 2 && parts[0].length <= 10 && parts[1].length <= 10 ) { // Two reasonable parts: treat as SLD-TLD or use SLD with common TLD serviceDefinition.tld = "com"; serviceDefinition.sld = parts[0]; } else { // Complex name: use domain to ensure favicon availability serviceDefinition.domain = `${name.replace(/-/g, "")}.com`; } } } else { // Single word services: default to com TLD serviceDefinition.tld = "com"; } return serviceDefinition; }); updateSelectedCount(); } catch (error) { searchResults.innerHTML = `
Error loading services: ${error.message}
`; console.error(error); } } function showSearchResults(searchTerm) { if (!searchTerm.trim()) { searchResults.classList.remove("show"); return; } const filteredServices = allServices.filter( (service) => service.name.toLowerCase().includes(searchTerm.toLowerCase()) && !selectedServicesList.find((selected) => selected.name === service.name) ); if (filteredServices.length === 0) { searchResults.innerHTML = 'No services selected
Search for services above to get started