// ==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();
})();