您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Game search tool directly from the Steam game page to your favourite websites search engine!
// ==UserScript== // @name Neon Game Hub // @namespace https://violentmonkey.github.io/ // @version 3.7 // @description Game search tool directly from the Steam game page to your favourite websites search engine! // @author Okagame // @license MIT // @match https://store.steampowered.com/app/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect self // @run-at document-end // ==/UserScript== (function() { 'use strict'; console.log("⚡ Neon Game Hub is on! ⚡"); // ====================== // CONFIGURATION // ====================== const CONFIG = { colors: { primary: 'rgba(10, 5, 15, 0.95)', accent: '#00ff9d', accentLight: '#00ffaa', text: '#ff99cc', border: 'rgba(0, 255, 157, 0.3)', folderBg: 'rgba(15, 10, 25, 0.7)', favourite: '#ff4444', nonFavourite: '#333333' }, storage: { FAVOURITES_KEY: 'ngsh_favourites' }, SITE_DEFINITIONS: { 'Direct Downloads': [ { name: 'CS.RIN.RU', icon: '📥', urlPattern: 'https://cs.rin.ru/forum/search.php?keywords={query}&terms=any&author=&sc=1&sf=titleonly&sk=t&sd=d&sr=topics&st=0&ch=300&t=0&submit=Search' }, { name: 'ElAmigos', icon: '📥', urlPattern: 'https://elamigos.site/?s={query}' }, { name: 'FitGirl Repacks', icon: '📥', urlPattern: 'https://fitgirl-repacks.site/?s={query}' }, { name: 'AtopGames', icon: '📥', urlPattern: 'https://atopgames.com/?s={query}' }, { name: 'GOG-Games', icon: '📥', urlPattern: 'https://gog-games.to/?search={query}' }, { name: 'G4U', icon: '📥', urlPattern: 'https://g4u.to/en/search/?str={query}' }, { name: 'AbandonwareGames', icon: '📥', urlPattern: 'https://abandonwaregames.net/search.php?q={query}' }, { name: 'AnkerGames', icon: '📥', urlPattern: 'https://ankergames.net/search/{query}' }, { name: 'CG-GamesPC', icon: '📥', urlPattern: 'https://www.cg-gamespc.com/games?game={query}' }, { name: 'GameBounty', icon: '📥', urlPattern: 'https://gamebounty.world/?s={query}' }, { name: 'GamDie', icon: '📥', urlPattern: 'https://gamdie.com/?s={query}' }, { name: 'GameDrive', icon: '📥', urlPattern: 'https://gamedrive.org/?s={query}' }, { name: 'GamePCFull', icon: '📥', urlPattern: 'https://gamepcfull.com/?s={query}' }, { name: 'Games4U', icon: '📥', urlPattern: 'https://games4u.org/?s={query}' }, { name: 'GamesDrive', icon: '📥', urlPattern: 'https://gamesdrive.net/?s={query}' }, { name: 'GamesPack', icon: '📥', urlPattern: 'https://games-pack.net/?s={query}' }, { name: 'GameZDL', icon: '📥', urlPattern: 'https://gamezdl.cc/?s={query}' }, { name: 'GetFreeGames', icon: '📥', urlPattern: 'https://getfreegames.net/?s={query}' }, { name: 'Gnarly Repacks', icon: '📥', urlPattern: 'https://rentry.org/gnarly_repacks' }, { name: 'M4ckD0ge Repacks', icon: '📥', urlPattern: 'https://m4ckd0ge-repacks.site/?s={query}' }, { name: 'MyAbandonware', icon: '📥', urlPattern: 'https://www.myabandonware.com/search?q={query}' }, { name: 'OldGamesDownload', icon: '📥', urlPattern: 'https://oldgamesdownload.com/?s={query}' }, { name: 'OvaGames', icon: '📥', urlPattern: 'https://www.ovagames.com/?s={query}' }, { name: 'ReloadedSteam', icon: '📥', urlPattern: 'https://reloadedsteam.com/?s={query}' }, { name: 'Repack-Games', icon: '📥', urlPattern: 'https://repack-games.com/?s={query}' }, { name: 'RepackLab', icon: '📥', urlPattern: 'https://repacklab.com/?s={query}' }, { name: 'Steam-Cracked', icon: '📥', urlPattern: 'https://steam-cracked.com/?s={query}' }, { name: 'SteamGG', icon: '📥', urlPattern: 'https://steamgg.net/?s={query}' }, { name: 'SteamRip', icon: '📥', urlPattern: 'https://steamrip.com/?s={query}' }, { name: 'SteamUnderground', icon: '📥', urlPattern: 'https://steamunderground.net/?s={query}' }, { name: 'Collection Chamber', icon: '📥', urlPattern: 'https://collectionchamber.blogspot.com/search?q={query}' }, { name: 'UndergroundGames', icon: '📥', urlPattern: 'https://undergroundgames.net/?s={query}' }, { name: 'Union-Crax', icon: '📥', urlPattern: 'https://union-crax.xyz/?s={query}' }, { name: 'Win7Games', icon: '📥', urlPattern: 'https://win7games.com/' }, { name: 'WorldofPCGames', icon: '📥', urlPattern: 'https://worldofpcgames.com/?s={query}' } ], 'Search Engines': [ { name: 'Google Custom Search', icon: '🔍', urlPattern: 'https://cse.google.com/cse?cx=20c2a3e5f702049aa&q={query}' }, { name: 'RaveGameSearch', icon: '🔍', urlPattern: 'https://ravegamesearch.pages.dev/search?q={query}' }, { name: 'Rezi', icon: '🔍', urlPattern: 'https://rezi.one/?q={query}' }, { name: 'Mr Pc Gamer', icon: '🔍', urlPattern: 'https://mrpcgamer.net/?s={query}' }, { name: 'Game3rb', icon: '🔍', urlPattern: 'https://game3rb.com/?s={query}' } ], 'Torrents': [ { name: 'ByXatab', icon: '🌐', urlPattern: 'https://byxatab.com/?s={query}' }, { name: 'Dodi-Repacks', icon: '🌐', urlPattern: 'https://dodi-repacks.site/?s={query}' }, { name: 'FreeGOGPCGames', icon: '🌐', urlPattern: 'https://freegogpcgames.com/?s={query}' }, { name: 'KaosKrew', icon: '🌐', urlPattern: 'https://kaoskrew.org/?s={query}' }, { name: 'Torrent-Games.games', icon: '🌐', urlPattern: 'https://torrent-games.games/?s={query}' }, { name: 'Torrent-Games.net', icon: '🌐', urlPattern: 'https://torrent-games.net/?s={query}' }, { name: 'Appnetica', icon: '🌐', urlPattern: 'https://appnetica.com/search?term={query}' } ], 'Utilities & Trainers': [ { name: 'FLiNG Trainer', icon: '🔧', urlPattern: 'https://flingtrainer.com/?s={query}' }, { name: 'GameCopyWorld', icon: '🔧', urlPattern: 'https://gamecopyworld.eu/games/search_results.shtml?q={query}&sa=%C2%A0+Google+Search%C2%A0+' }, { name: 'MegaGames', icon: '🔧', urlPattern: 'https://megagames.com/results?query={query}' }, { name: 'MrAntiFun', icon: '🔧', urlPattern: 'https://mrantifun.net/search/16823170/?q={query}&o=date' }, { name: 'WeMod', icon: '🔧', urlPattern: 'https://www.wemod.com/cheats?q={query}' }, ] } }; // ====================== // STORAGE MANAGER // ====================== class StorageManager { static get(key, defaultValue = null) { try { const saved = localStorage.getItem(key); return saved ? JSON.parse(saved) : defaultValue; } catch (e) { console.warn(`Couldn't load ${key}:`, e); return defaultValue; } } static set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch (e) { console.warn(`Couldn't save ${key}:`, e); return false; } } } // ====================== // FAVOURITES MANAGER // ====================== class FavouritesManager { constructor() { this.favourites = StorageManager.get(CONFIG.storage.FAVOURITES_KEY, []); } isFavourite(site) { return this.favourites.some(fav => fav.name === site.name && fav.urlPattern === site.urlPattern ); } toggle(site) { const index = this.favourites.findIndex(fav => fav.name === site.name && fav.urlPattern === site.urlPattern ); if (index >= 0) { this.favourites.splice(index, 1); } else { this.favourites.push(site); } StorageManager.set(CONFIG.storage.FAVOURITES_KEY, this.favourites); return this.favourites; } getAll() { const allSites = Object.values(CONFIG.SITE_DEFINITIONS).flat(); return allSites.filter(site => this.isFavourite(site)); } openAll() { const { gameName, appId } = GameInfoExtractor.getGameData(); const favSites = this.getAll(); if (favSites.length === 0) { NotificationManager.show("No favourite sites selected"); return; } if (!confirm(`Open all ${favSites.length} favourite sites?`)) return; favSites.forEach(site => { SearchManager.performSearch(site, gameName, appId); }); } } // ====================== // GAME INFO EXTRACTOR // ====================== class GameInfoExtractor { static getAppId() { const match = window.location.href.match(/app\/(\d+)/); return match ? match[1] : null; } static getGameName() { const url = window.location.href; if (url.includes('store.steampowered.com')) { const element = document.getElementById('appHubAppName'); return element ? element.textContent.trim() : ''; } return ''; } static getDeveloper() { const url = window.location.href; if (url.includes('store.steampowered.com')) { const element = document.querySelector('.dev_row a'); return element ? element.textContent.trim() : ''; } return ''; } static cleanGameName(gameName) { return gameName ? gameName.replace(/[™®©]/g, '').replace(/\s+/g, ' ').trim() : gameName; } static getGameData() { const appId = this.getAppId(); const gameName = this.cleanGameName(this.getGameName()); const developer = this.getDeveloper(); return { appId, gameName, developer }; } } // ====================== // SEARCH MANAGER // ====================== class SearchManager { static performSearch(site, query, appId) { const url = site.urlPattern .replace('{query}', encodeURIComponent(query)) .replace('{appId}', appId || ''); if (site.special) { const handler = SearchHandlers.getHandler(site.special); if (handler) { const developer = GameInfoExtractor.getDeveloper(); handler(appId, query, developer, (foundUrl) => { window.open(foundUrl, '_blank'); }); return; } } window.open(url, '_blank'); } } // ====================== // NOTIFICATION MANAGER // ====================== class NotificationManager { static show(message, isError = false) { const existing = document.getElementById('ngsh-notification'); if (existing) existing.remove(); const notification = document.createElement('div'); notification.id = 'ngsh-notification'; notification.className = 'ngsh-notification'; notification.textContent = message; notification.style.backgroundColor = isError ? '#ff4444' : 'rgba(0, 0, 0, 0.8)'; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; notification.style.transition = 'opacity 0.5s'; setTimeout(() => notification.remove(), 500); }, 3000); } } // ====================== // UI MANAGER // ====================== class UIManager { constructor() { this.elements = {}; this.menuVisible = false; this.favouritesManager = new FavouritesManager(); } createStyles() { GM_addStyle(` .ngsh-container { position: fixed; z-index: 99999; font-family: 'Segoe UI', sans-serif; pointer-events: none; } #ngsh-main-button, #ngsh-quick-access { position: fixed; border-radius: 50%; padding: 0; font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; pointer-events: auto; z-index: 100000; } #ngsh-main-button { right: 20px; bottom: 40px; background: linear-gradient(135deg, rgba(0, 255, 157, 0.7) 0%, rgba(0, 255, 180, 0.7) 100%); color: #0a0005; border: 1px solid rgba(0, 255, 157, 0.5); font-size: 18px; box-shadow: 0 4px 8px rgba(0, 255, 157, 0.2); width: 48px; height: 48px; } #ngsh-quick-access { right: 70px; bottom: 10px; background: linear-gradient(135deg, rgba(0, 255, 157, 0.5) 0%, rgba(0, 255, 180, 0.5) 100%); color: #0a0005; border: 1px solid rgba(0, 255, 157, 0.3); font-size: 14px; box-shadow: 0 2px 4px rgba(0, 255, 157, 0.15); width: 32px; height: 32px; } #ngsh-main-button:hover, #ngsh-quick-access:hover { transform: scale(1.1); box-shadow: 0 6px 12px rgba(0, 255, 157, 0.3); } .ngsh-menu { position: fixed; right: 20px; bottom: 100px; width: 330px; background: ${CONFIG.colors.primary}; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 255, 157, 0.3); border: 1px solid ${CONFIG.colors.border}; backdrop-filter: blur(10px); max-height: 80vh; overflow: hidden; pointer-events: auto; z-index: 99999; display: none; overscroll-behavior: contain; } .ngsh-menu-header { padding: 12px 20px; color: ${CONFIG.colors.text}; font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid ${CONFIG.colors.border}; background: linear-gradient(90deg, transparent 0%, rgba(0, 255, 157, 0.1) 100%); flex-shrink: 0; } .ngsh-site-list { max-height: calc(80vh - 200px); overflow-y: auto; padding: 8px; scrollbar-width: none; -ms-overflow-style: none; overscroll-behavior: contain; } .ngsh-site-list::-webkit-scrollbar { display: none; } .ngsh-search-box { padding: 8px 15px; margin: 8px; background: rgba(15, 10, 25, 0.7); border: 1px solid ${CONFIG.colors.border}; border-radius: 20px; display: flex; align-items: center; position: sticky; bottom: 0; z-index: 10; } .ngsh-search-input { flex: 1; background: transparent; border: none; color: ${CONFIG.colors.text}; font-family: inherit; font-size: 14px; outline: none; padding: 8px 0; } .ngsh-search-input::placeholder { color: #ff99cc80; } .ngsh-search-clear, .ngsh-search-icon { color: ${CONFIG.colors.accent}; cursor: pointer; font-size: 16px; } .ngsh-search-clear { margin-left: 8px; } .ngsh-search-icon { margin-right: 8px; } .ngsh-folder { margin-bottom: 8px; border-radius: 6px; overflow: hidden; background: ${CONFIG.colors.folderBg}; border: 1px solid ${CONFIG.colors.border}; position: relative; z-index: 5; } .ngsh-folder-header { padding: 12px 15px; display: flex; align-items: center; color: ${CONFIG.colors.text}; cursor: pointer; border-bottom: 1px solid rgba(0, 255, 157, 0.1); height: 15px; z-index: 4; } .ngsh-folder-header:hover { background: rgba(25, 15, 35, 0.7); z-index: 4; } .ngsh-folder-icon { margin-right: 10px; transition: transform 0.3s ease; font-size: 14px; width: 16px; text-align: center; color: ${CONFIG.colors.accent}; } .ngsh-folder.open .ngsh-folder-icon { transform: rotate(90deg); } .ngsh-folder-title { font-weight: 600; font-size: 14px; flex: 1; } .ngsh-folder-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; position: relative; overflow-y: auto; overscroll-behavior: contain; } .ngsh-folder.open .ngsh-folder-content { max-height: 300px; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; } .ngsh-folder-content::-webkit-scrollbar { display: none; } .ngsh-menu-item { padding: 12px 15px 12px 40px; display: flex; align-items: center; color: ${CONFIG.colors.text}; cursor: pointer; transition: all 0.2s ease; margin: 2px 5px; border-radius: 4px; position: relative; } .ngsh-menu-item:hover { background: rgba(0, 255, 157, 0.15); } .ngsh-site-icon { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; margin-right: 10px; color: ${CONFIG.colors.accent}; font-size: 16px; } .ngsh-favourite-icon { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 18px; cursor: pointer; transition: all 0.2s ease; z-index: 2; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.1); } .ngsh-favourite-icon:hover { background: rgba(0, 255, 157, 0.2); border-color: rgba(0, 255, 157, 0.4); transform: translateY(-50%) scale(1.1); box-shadow: 0 2px 8px rgba(0, 255, 157, 0.3); } .ngsh-game-info { padding: 10px 20px; font-size: 13px; color: #cc88aa; border-top: 1px solid ${CONFIG.colors.border}; margin-top: 10px; background: rgba(0, 0, 0, 0.2); border-radius: 0 0 8px 8px; } .ngsh-open-all { display: flex; align-items: center; padding: 10px 15px; color: ${CONFIG.colors.text}; cursor: pointer; background: rgba(0, 0, 0, 0.2); margin: 5px; border-radius: 4px; border: 1px solid ${CONFIG.colors.border}; } .ngsh-open-all:hover { background: rgba(0, 255, 157, 0.15); } .ngsh-open-all-icon { margin-right: 8px; font-size: 16px; } .ngsh-notification { position: fixed; bottom: 52px; right: 75px; padding: 10px 15px; border-radius: 4px; background-color: rgba(0, 0, 0, 0.8); color: ${CONFIG.colors.text}; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); z-index: 100000; animation: fadeIn 0.3s; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } `); } createElement(tag, className = '', content = '') { const element = document.createElement(tag); if (className) element.className = className; if (content) element.textContent = content; return element; } createButtons() { // Main button this.elements.mainButton = this.createElement('button', '', '⚡'); this.elements.mainButton.id = 'ngsh-main-button'; // Quick access button this.elements.quickButton = this.createElement('button', '', '💖'); this.elements.quickButton.id = 'ngsh-quick-access'; this.elements.quickButton.title = 'Open all favourite sites'; document.body.appendChild(this.elements.mainButton); document.body.appendChild(this.elements.quickButton); this.setupButtonEvents(); } createMenu() { this.elements.menu = this.createElement('div', 'ngsh-menu'); // Header const header = this.createElement('div', 'ngsh-menu-header', 'Search Hub'); this.elements.menu.appendChild(header); // Site list this.elements.siteList = this.createElement('div', 'ngsh-site-list'); this.elements.menu.appendChild(this.elements.siteList); // Search box this.createSearchBox(); document.body.appendChild(this.elements.menu); } createSearchBox() { const searchBox = this.createElement('div', 'ngsh-search-box'); const searchIcon = this.createElement('span', 'ngsh-search-icon', '🔍'); const searchInput = this.createElement('input', 'ngsh-search-input'); const searchClear = this.createElement('span', 'ngsh-search-clear', '✕'); searchInput.type = 'text'; searchInput.placeholder = 'Search sites...'; searchClear.style.display = 'none'; searchBox.appendChild(searchIcon); searchBox.appendChild(searchInput); searchBox.appendChild(searchClear); this.elements.menu.appendChild(searchBox); this.elements.searchInput = searchInput; this.elements.searchClear = searchClear; this.setupSearchEvents(); } setupButtonEvents() { // Main button toggle this.elements.mainButton.addEventListener('click', (e) => { e.stopPropagation(); this.toggleMenu(); }); // Quick access button this.elements.quickButton.addEventListener('click', (e) => { e.stopPropagation(); this.favouritesManager.openAll(); }); // Close menu when clicking outside document.addEventListener('click', (event) => { if (this.menuVisible && !this.isMenuRelatedTarget(event.target)) { this.closeMenu(); } }); } setupSearchEvents() { this.elements.searchInput.addEventListener('input', () => { const query = this.elements.searchInput.value.toLowerCase(); this.elements.searchClear.style.display = query ? 'block' : 'none'; this.filterSites(query); }); this.elements.searchClear.addEventListener('click', () => { this.elements.searchInput.value = ''; this.elements.searchClear.style.display = 'none'; this.filterSites(''); this.elements.searchInput.focus(); }); this.elements.searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); this.elements.searchInput.blur(); } }); } isMenuRelatedTarget(target) { return this.elements.menu.contains(target) || this.elements.mainButton.contains(target) || this.elements.quickButton.contains(target); } toggleMenu() { if (this.menuVisible) { this.closeMenu(); } else { this.openMenu(); } } openMenu() { this.menuVisible = true; this.elements.menu.style.display = 'block'; this.elements.searchInput.focus(); if (!this.elements.menu.hasBuiltContent) { this.buildMenuContent(); this.elements.menu.hasBuiltContent = true; } } closeMenu() { this.menuVisible = false; this.elements.menu.style.display = 'none'; this.elements.searchInput.value = ''; this.elements.searchClear.style.display = 'none'; this.closeAllFolders(); // Restore page scrolling when menu closes document.body.style.overflowY = ''; document.body.style.position = ''; document.body.style.width = ''; } closeAllFolders() { const folders = this.elements.menu.querySelectorAll('.ngsh-folder'); folders.forEach(folder => { folder.classList.remove('open'); const content = folder.querySelector('.ngsh-folder-content'); if (content) content.style.maxHeight = ''; }); } filterSites(query) { if (!this.elements.menu.hasBuiltContent) return; // Close all folders first if search is cleared if (query === '') { this.closeAllFolders(); } const folders = this.elements.menu.querySelectorAll('.ngsh-folder'); let hasVisibleItems = false; folders.forEach(folder => { const content = folder.querySelector('.ngsh-folder-content'); const items = content.querySelectorAll('.ngsh-menu-item:not(.ngsh-open-all)'); let folderHasVisibleItems = false; items.forEach(item => { const siteName = item.querySelector('.ngsh-site-name').textContent.toLowerCase(); const isVisible = query === '' || siteName.includes(query); item.style.display = isVisible ? 'flex' : 'none'; if (isVisible) { folderHasVisibleItems = true; hasVisibleItems = true; } }); // Show/hide folder folder.style.display = (query === '' || folderHasVisibleItems) ? 'block' : 'none'; // Keep Favourites folder visible const isFavouritesFolder = folder.querySelector('.ngsh-folder-title').textContent.includes('FAVOURITES'); if (isFavouritesFolder) { folder.style.display = 'block'; } // Auto-open folders with search results if (query !== '' && folderHasVisibleItems) { folder.classList.add('open'); } }); if (query !== '' && !hasVisibleItems) { NotificationManager.show('No sites match your search'); } } buildMenuContent() { // Clear existing content this.elements.siteList.innerHTML = ''; // Remove existing game info to prevent duplication const existingGameInfo = this.elements.menu.querySelector('.ngsh-game-info'); if (existingGameInfo) existingGameInfo.remove(); const { appId, gameName, developer } = GameInfoExtractor.getGameData(); // Add Favourites folder const favouriteSites = this.favouritesManager.getAll(); if (favouriteSites.length > 0) { const favouritesFolder = this.createFolder( `FAVOURITES (${favouriteSites.length})`, favouriteSites ); this.elements.siteList.appendChild(favouritesFolder); } // Add category folders Object.entries(CONFIG.SITE_DEFINITIONS).forEach(([category, sites]) => { const folder = this.createFolder(category, sites); this.elements.siteList.appendChild(folder); }); // Add game info if (gameName || appId || developer) { const gameInfo = this.createGameInfoSection(gameName, appId, developer); this.elements.menu.insertBefore(gameInfo, this.elements.menu.querySelector('.ngsh-search-box')); } } createFolder(title, sites) { const folder = this.createElement('div', 'ngsh-folder'); // Header const header = this.createElement('div', 'ngsh-folder-header'); const icon = this.createElement('div', 'ngsh-folder-icon', '▶'); const titleElement = this.createElement('div', 'ngsh-folder-title', title); header.appendChild(icon); header.appendChild(titleElement); const content = this.createElement('div', 'ngsh-folder-content'); // Sort sites: favourites first const sortedSites = [...sites].sort((a, b) => { const aFav = this.favouritesManager.isFavourite(a); const bFav = this.favouritesManager.isFavourite(b); if (aFav && !bFav) return -1; if (!aFav && bFav) return 1; return 0; }); // Add "Open All" button for favourites folder if (title.includes('FAVOURITES')) { const openAllItem = this.createElement('div', 'ngsh-open-all'); openAllItem.innerHTML = `<span class="ngsh-open-all-icon">💖</span><span>Open All Favourites</span>`; openAllItem.addEventListener('click', (e) => { e.stopPropagation(); this.favouritesManager.openAll(); }); content.appendChild(openAllItem); } // Add site items sortedSites.forEach(site => { const item = this.createSiteItem(site); content.appendChild(item); }); folder.appendChild(header); folder.appendChild(content); // Toggle functionality header.addEventListener('click', (e) => { e.stopPropagation(); folder.classList.toggle('open'); if (folder.classList.contains('open')) { // Prevent page scrolling when folder is open - keep scrollbar visible document.body.style.overflowY = 'scroll'; document.body.style.position = 'fixed'; document.body.style.width = '100%'; } else { // Allow page scrolling when folder closes document.body.style.overflowY = ''; document.body.style.position = ''; document.body.style.width = ''; } }); return folder; } createSiteItem(site) { const item = this.createElement('div', 'ngsh-menu-item'); const icon = this.createElement('div', 'ngsh-site-icon', site.icon || '🔍'); const label = this.createElement('div', 'ngsh-site-name', site.name); const favourite = this.createElement('div', 'ngsh-favourite-icon'); label.style.flex = '1'; const isFav = this.favouritesManager.isFavourite(site); favourite.className = `ngsh-favourite-icon ${isFav ? 'favourited' : ''}`; favourite.textContent = isFav ? '❤' : '🖤'; favourite.style.color = isFav ? CONFIG.colors.favourite : CONFIG.colors.nonFavourite; // Site click handler item.addEventListener('click', (e) => { e.stopPropagation(); const { gameName, appId } = GameInfoExtractor.getGameData(); SearchManager.performSearch(site, gameName, appId); }); // Favourite toggle handler favourite.addEventListener('click', (e) => { e.stopPropagation(); this.handleFavouriteToggle(site, favourite); }); item.appendChild(icon); item.appendChild(label); item.appendChild(favourite); return item; } handleFavouriteToggle(site, favouriteIcon) { this.favouritesManager.toggle(site); const isFav = this.favouritesManager.isFavourite(site); favouriteIcon.className = `ngsh-favourite-icon ${isFav ? 'favourited' : ''}`; favouriteIcon.textContent = isFav ? '❤' : '🖤'; favouriteIcon.style.color = isFav ? CONFIG.colors.favourite : CONFIG.colors.nonFavourite; // Update favourites folder count and content without rebuilding everything this.updateFavouritesFolder(); // Re-sort the current folder to move favourites to top this.resortCurrentFolder(site); NotificationManager.show(isFav ? `Added to favourites: ${site.name}` : `Removed from favourites: ${site.name}`); } updateFavouritesFolder() { const favouriteSites = this.favouritesManager.getAll(); const existingFavFolder = [...this.elements.siteList.children] .find(folder => folder.querySelector('.ngsh-folder-title').textContent.includes('FAVOURITES')); if (favouriteSites.length > 0) { if (existingFavFolder) { // Completely rebuild the folder to avoid any leftover elements existingFavFolder.remove(); const newFavouritesFolder = this.createFolder( `FAVOURITES (${favouriteSites.length})`, favouriteSites ); this.elements.siteList.insertBefore(newFavouritesFolder, this.elements.siteList.firstChild); } else { // Create new favourites folder and add it at the top const favouritesFolder = this.createFolder( `FAVOURITES (${favouriteSites.length})`, favouriteSites ); this.elements.siteList.insertBefore(favouritesFolder, this.elements.siteList.firstChild); } } else if (existingFavFolder) { // Remove favourites folder if no favourites existingFavFolder.remove(); } // Update heart icons in all other folders this.updateAllHeartIcons(); } updateAllHeartIcons() { // Update heart icons in all folders to reflect current favorite status const folders = this.elements.siteList.querySelectorAll('.ngsh-folder'); folders.forEach(folder => { const folderTitle = folder.querySelector('.ngsh-folder-title').textContent; if (folderTitle.includes('FAVOURITES')) return; // Skip favourites folder const items = folder.querySelectorAll('.ngsh-menu-item:not(.ngsh-open-all)'); items.forEach(item => { const siteName = item.querySelector('.ngsh-site-name').textContent; const site = this.findSiteByName(siteName); const heartIcon = item.querySelector('.ngsh-favourite-icon'); if (site && heartIcon) { const isFav = this.favouritesManager.isFavourite(site); heartIcon.textContent = isFav ? '❤' : '🖤'; heartIcon.style.color = isFav ? CONFIG.colors.favourite : CONFIG.colors.nonFavourite; } }); }); } resortCurrentFolder(site) { // Find the folder containing this site (not the favourites folder) const folders = this.elements.siteList.querySelectorAll('.ngsh-folder'); folders.forEach(folder => { const folderTitle = folder.querySelector('.ngsh-folder-title').textContent; if (folderTitle.includes('FAVOURITES')) return; // Skip favourites folder // Check if this folder contains the site const items = folder.querySelectorAll('.ngsh-menu-item:not(.ngsh-open-all)'); const siteItem = [...items].find(item => item.querySelector('.ngsh-site-name').textContent === site.name ); if (siteItem) { // Re-sort this folder's items const content = folder.querySelector('.ngsh-folder-content'); const allItems = [...content.querySelectorAll('.ngsh-menu-item:not(.ngsh-open-all)')]; // Sort: favourites first allItems.sort((a, b) => { const aName = a.querySelector('.ngsh-site-name').textContent; const bName = b.querySelector('.ngsh-site-name').textContent; const aSite = this.findSiteByName(aName); const bSite = this.findSiteByName(bName); const aFav = aSite && this.favouritesManager.isFavourite(aSite); const bFav = bSite && this.favouritesManager.isFavourite(bSite); if (aFav && !bFav) return -1; if (!aFav && bFav) return 1; return 0; }); // Remove all items and re-add in sorted order allItems.forEach(item => item.remove()); allItems.forEach(item => content.appendChild(item)); } }); } findSiteByName(name) { const allSites = Object.values(CONFIG.SITE_DEFINITIONS).flat(); return allSites.find(site => site.name === name); } createGameInfoSection(gameName, appId, developer) { const section = this.createElement('div', 'ngsh-game-info'); if (gameName) { const nameEl = this.createElement('div', '', `${gameName}`); nameEl.style.marginBottom = '4px'; section.appendChild(nameEl); } if (appId) { section.appendChild(this.createElement('div', '', `App ID: ${appId}`)); } if (developer) { section.appendChild(this.createElement('div', '', `Developer: ${developer}`)); } return section; } init() { if (document.querySelector('.ngsh-container')) return; this.createStyles(); this.createButtons(); this.createMenu(); // Set up page scroll lock when hovering over menu - keep scrollbar visible this.elements.menu.addEventListener('mouseenter', () => { document.body.style.overflowY = 'scroll'; document.body.style.position = 'fixed'; document.body.style.width = '100%'; }); this.elements.menu.addEventListener('mouseleave', () => { document.body.style.overflowY = ''; document.body.style.position = ''; document.body.style.width = ''; }); } } // ====================== // INITIALIZATION // ====================== function init() { const ui = new UIManager(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ui.init()); } else { ui.init(); } } init(); })();