diff --git a/sidebar.js b/sidebar.js index 47f3ffc..4d3df04 100644 --- a/sidebar.js +++ b/sidebar.js @@ -145,3 +145,193 @@ init(); } })(); + +// ============================================================= +// Icon Replacement Module — selfh.st CDN icons for Docker/VMs +// ============================================================= +(function() { + 'use strict'; + + var CDN_PNG = 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/png'; + var CDN_SVG = 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/svg'; + + // Container name → selfh.st icon name (lowercase) + // Only needed for names that don't auto-match. + // Add entries here or use the glass.icon Docker label. + var ICON_MAP = { + 'autokuma': 'uptime-kuma', + 'uptimekuma': 'uptime-kuma', + 'uptime-kuma-api': 'uptime-kuma', + 'homeassistant_inabox': 'home-assistant', + 'dockersocket': 'docker', + 'pgadmin4': 'pgadmin', + 'postgresql17': 'postgresql', + 'woodpecker-agent': 'woodpecker-ci', + 'woodpecker-server': 'woodpecker-ci', + 'rustdesk-hbbr': 'rustdesk', + 'rustdesk-hbbs': 'rustdesk', + 'rustfs': 'rustdesk', + 'netbox-redis-cache': 'redis', + 'netbox-worker': 'netbox', + 'netdisco-backend': 'networking-toolbox', + 'netdisco-web': 'networking-toolbox', + 'diode-agent': 'netbox', + 'diode-auth': 'netbox', + 'diode-auth-bootstrap': 'netbox', + 'diode-hydra': 'netbox', + 'diode-hydra-migrate': 'netbox', + 'diode-ingester': 'netbox', + 'diode-ingress': 'netbox', + 'diode-reconciler': 'netbox', + 'authentik-worker': 'authentik', + 'adguardhome': 'adguard-home', + 'adguardhome-sync': 'adguard-home', + 'timemachine': 'apple', + 'libation': 'audible', + 'netalertx': 'netalertx', + 'actual-budget': 'actual-budget', + 'speedtest-tracker': 'speedtest-tracker', + 'open-webui': 'open-webui', + 'urbackup': 'networking-toolbox', + 'seekandwatch': 'databasement', + 'unmarr': 'homarr', + 'dockhand': 'docker' + }; + + // Resolve container name to selfh.st CDN URL + // mode: 'color' for expanded containers, 'light' for dashboard/folder-preview + function resolveIconUrl(name, mode) { + var key = name.toLowerCase(); + var iconName = ICON_MAP[key] || key; + if (mode === 'light') { + return CDN_SVG + '/' + iconName + '-light.svg'; + } + return CDN_PNG + '/' + iconName + '.png'; + } + + // Extract container name from an element by walking up the DOM + function getContainerName(img) { + // Strategy 1: sibling .inner > .appname text + var outer = img.closest('span.outer'); + if (outer) { + var appname = outer.querySelector('span.appname'); + if (appname) { + var a = appname.querySelector('a'); + return (a ? a.textContent : appname.textContent).trim(); + } + } + + // Strategy 2: .inner > first non-state span (Dashboard view) + if (outer) { + var inner = outer.querySelector('span.inner'); + if (inner) { + var nameSpan = inner.querySelector('span:not(.state)'); + if (nameSpan && nameSpan.textContent.trim()) { + return nameSpan.textContent.trim(); + } + } + } + + // Strategy 3: .inner > a.exec text (FolderView uses this) + if (outer) { + var exec = outer.querySelector('a.exec'); + if (exec) return exec.textContent.trim(); + } + + // Strategy 4: parent row ct-name + var row = img.closest('tr'); + if (row) { + var ctName = row.querySelector('td.ct-name span.appname'); + if (ctName) { + var a2 = ctName.querySelector('a'); + return (a2 ? a2.textContent : ctName.textContent).trim(); + } + } + + // Strategy 4: extract from icon src path (fallback) + var src = img.getAttribute('src') || ''; + var match = src.match(/images\/(.+?)-icon\.png/); + if (match) return match[1]; + + return null; + } + + // Determine icon mode based on DOM context + function getIconMode(img) { + // Dashboard → light + if (img.closest('table.dashboard')) return 'light'; + // Folder preview (collapsed row) → light + if (img.closest('div.folder-preview')) return 'light'; + // Expanded container inside folder → color + if (img.closest('tr.folder-element')) return 'color'; + // Regular container row → color + return 'color'; + } + + // Replace all container icons on the page + function replaceIcons() { + var imgs = document.querySelectorAll('img.img'); + imgs.forEach(function(img) { + // Skip sidebar logo and non-container icons + if (img.id === 'sidebar-logo') return; + // Skip folder category icons (SVGs managed by FolderView) + if (img.classList.contains('folder-img') && img.closest('td.folder-name')) return; + if (img.classList.contains('folder-img-docker')) return; + + var name = getContainerName(img); + if (!name) return; + + // Skip folder names (they start with "folder-") + if (name.indexOf('folder-') === 0) return; + + var mode = getIconMode(img); + var url = resolveIconUrl(name, mode); + + // Only replace if different (avoid infinite MutationObserver loop) + if (img.src !== url && img.getAttribute('src') !== url) { + img.src = url; + // Remove cache-busting onerror that resets to question mark + img.removeAttribute('onerror'); + } + }); + } + + // Debounce to avoid excessive calls during DOM manipulation + var replaceTimer = null; + function debouncedReplace() { + if (replaceTimer) clearTimeout(replaceTimer); + replaceTimer = setTimeout(replaceIcons, 200); + } + + // Watch for DOM changes (AJAX loads, FolderView manipulation) + function startObserver() { + var target = document.getElementById('displaybox') || document.body; + var observer = new MutationObserver(function(mutations) { + var dominated = false; + for (var i = 0; i < mutations.length; i++) { + if (mutations[i].addedNodes.length > 0) { + dominated = true; + break; + } + } + if (dominated) debouncedReplace(); + }); + observer.observe(target, { childList: true, subtree: true }); + } + + // Initialize when DOM is ready + function initIcons() { + // Initial replacement + setTimeout(replaceIcons, 500); + // Second pass after FolderView finishes its DOM work + setTimeout(replaceIcons, 2000); + // Start watching for future changes + startObserver(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initIcons); + } else { + initIcons(); + } +})(); diff --git a/style.css b/style.css index c36d2b6..a7a26a1 100644 --- a/style.css +++ b/style.css @@ -1490,6 +1490,16 @@ span.folder-outer { border: none !important; } +/* Folder category icons (SVG on the folder row itself) — make white */ +tr.folder > td.folder-name img.folder-img { + filter: brightness(0) invert(1) !important; +} + +/* Dashboard folder category icons — make white (same SVGs, different DOM) */ +img.folder-img-docker { + filter: brightness(0) invert(1) !important; +} + /* Folder dropdown button */ button[class*="dropDown-"] { background: rgba(255, 255, 255, 0.06) !important; @@ -1623,3 +1633,382 @@ div.clone-settings { background: rgba(126, 184, 218, 0.3); color: #fff; } + + +/* ============================================ + COMMUNITY APPLICATIONS (APPS PAGE) OVERRIDES + ============================================ */ + +/* --- Override CA gray theme variables for glass look --- */ +:root { + --template-background: rgba(255, 255, 255, 0.06); + --template-hover-background: rgba(255, 255, 255, 0.12); + --template-favourite: rgba(255, 255, 255, 0.10); + --border-color: rgba(255, 255, 255, 0.12); + --sidebar-background: rgba(255, 255, 255, 0.08); + --sidebar-text: var(--glass-text); + --ca-legacy-background-color: transparent; + --support-popup-text: var(--glass-text); + --support-popup-background: rgba(255, 255, 255, 0.10); +} + +/* --- Apps page left sidebar menu --- */ +.menuItems { + background-color: transparent !important; + background: transparent !important; +} + +ul.caMenu, +ul.nonselectMenu { + color: var(--glass-text) !important; +} + +li.caMenuItem, +a.caMenuItem { + color: var(--glass-text-muted) !important; +} + +.caMenuItem:hover { + color: var(--glass-accent) !important; +} + +.selectedMenu { + color: var(--glass-accent) !important; +} + +.menuItems hr { + border-color: rgba(255, 255, 255, 0.08) !important; +} + +/* --- Search area bar --- */ +.searchArea { + background-color: transparent !important; + background: transparent !important; +} + +.searchAreaHolder { + background: transparent !important; +} + +#searchBox { + background: rgba(255, 255, 255, 0.08) !important; + border: 1px solid rgba(255, 255, 255, 0.12) !important; + border-radius: 10px !important; + color: var(--glass-text) !important; + backdrop-filter: blur(20px) !important; + -webkit-backdrop-filter: blur(20px) !important; +} + +#searchBox::placeholder { + color: var(--glass-text-muted) !important; +} + +.searchSubmit { + background: rgba(255, 255, 255, 0.06) !important; + border: none !important; + color: var(--glass-text-muted) !important; + border-radius: 0 10px 10px 0 !important; +} + +/* --- App cards (ca_holder) --- */ +.ca_holder, +.dockerHubHolder { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.10) !important; + border-radius: 16px !important; + backdrop-filter: blur(30px) saturate(1.3) !important; + -webkit-backdrop-filter: blur(30px) saturate(1.3) !important; + overflow: hidden; + transition: background 0.2s, border-color 0.2s, box-shadow 0.2s !important; +} + +.ca_holder:hover, +.dockerHubHolder:hover { + background: rgba(255, 255, 255, 0.12) !important; + border-color: rgba(255, 255, 255, 0.18) !important; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.08) !important; +} + +.ca_holderFav { + background: rgba(255, 255, 255, 0.10) !important; +} + +/* Card text */ +.ca_applicationName { + color: var(--glass-text-heading) !important; +} + +.cardDescription, +.cardDesc { + color: var(--glass-text-muted) !important; +} + +.ca_author { + color: var(--glass-text-muted) !important; +} + +.ca_categories, +a.ca_categories { + color: var(--glass-text-muted) !important; +} + +/* Card description fade-out gradient — match glass bg */ +.cardDesc::after { + background: linear-gradient(to right, + transparent 0%, + rgba(255, 255, 255, 0.03) 50%, + rgba(30, 30, 40, 0.6) 100%) !important; +} + +/* --- Buttons (caButton) --- */ +.caButton, +.popupRepoDescription, +.popupDescription { + background: rgba(255, 255, 255, 0.10) !important; + border: 1px solid rgba(255, 255, 255, 0.12) !important; + color: var(--glass-text) !important; + border-radius: 10px !important; + backdrop-filter: blur(15px) !important; + -webkit-backdrop-filter: blur(15px) !important; +} + +.caButton:hover { + background: var(--glass-accent) !important; + border-color: var(--glass-accent) !important; + color: #fff !important; +} + +.caButton:hover a, +a.caButton:hover { + color: #fff !important; +} + +.caButton a { + color: var(--glass-text) !important; +} + +/* --- Home templates section headers --- */ +.ca_homeTemplatesHeader { + color: var(--glass-text-heading) !important; +} + +.ca_homeTemplatesLine2 { + color: var(--glass-text-muted) !important; +} + +/* --- Home templates horizontal scroll area --- */ +.ca_homeTemplates { + scrollbar-color: rgba(255, 255, 255, 0.15) transparent; +} + +/* --- Alternate view (app detail popup) --- */ +#alternateView { + background-color: rgba(15, 15, 25, 0.92) !important; + backdrop-filter: blur(60px) saturate(1.5) !important; + -webkit-backdrop-filter: blur(60px) saturate(1.5) !important; + color: var(--glass-text) !important; + border-left: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +#alternateView table { + background-color: transparent !important; +} + +.popupCloseArea { + background-color: transparent !important; +} + +.popupName { + color: var(--glass-text-heading) !important; +} + +.popupAuthor, +.popupAuthorMain { + color: var(--glass-text-muted) !important; +} + +.popupInfoSection { + color: var(--glass-text) !important; +} + +/* Popup description box */ +.popupDescription { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.10) !important; + border-radius: 14px !important; + color: var(--glass-text) !important; +} + +.popupRepoDescription { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.10) !important; + border-radius: 14px !important; + color: var(--glass-text) !important; +} + +/* --- Sidebar detail panel (sidenav) --- */ +.sidenav { + background-color: rgba(15, 15, 25, 0.90) !important; + backdrop-filter: blur(50px) saturate(1.5) !important; + -webkit-backdrop-filter: blur(50px) saturate(1.5) !important; + color: var(--glass-text) !important; +} + +/* --- Mobile overlay --- */ +.mobileOverlay.menuShowing { + background-color: rgba(0, 0, 0, 0.5) !important; +} + +/* --- Multi-install bottom bar --- */ +.multi_installDiv { + background-color: rgba(15, 15, 25, 0.85) !important; + backdrop-filter: blur(40px) !important; + -webkit-backdrop-filter: blur(40px) !important; + border-top: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +/* --- Card badge triangles — keep visible but soften --- */ +.installedCardBackground { + background-color: rgba(50, 47, 255, 0.75) !important; +} + +.officialCardBackground, +.spotlightCardBackground { + background-color: rgba(133, 65, 83, 0.75) !important; +} + +.betaCardBackground { + background-color: rgba(255, 140, 47, 0.7) !important; +} + +.warningCardBackground { + background-color: rgba(129, 0, 0, 0.75) !important; +} + +.greenCardBackground { + background-color: rgba(0, 129, 0, 0.75) !important; +} + +/* --- Links inside CA --- */ +.popUpLink, +a.popUpLink { + color: var(--glass-accent) !important; +} + +.popUpLink:hover, +a.popUpLink:hover { + color: var(--glass-accent-hover) !important; +} + +/* --- Sort icons --- */ +.sortIcons { + color: var(--glass-text-muted) !important; +} + +.sortIcons:hover { + color: var(--glass-accent) !important; +} + +/* --- Page navigation --- */ +.pageNumber { + color: var(--glass-text-muted) !important; +} + +.pageNumber:hover { + color: var(--glass-accent) !important; +} + +.pageSelected { + color: var(--glass-accent) !important; +} + +/* --- Template display area --- */ +.ca_template { + background-color: rgba(255, 255, 255, 0.06) !important; + color: var(--glass-text) !important; + border-radius: 16px !important; +} + +/* --- Statistics / no-apps-found text --- */ +.ca_NoAppsFound, +.ca_NoDockerAppsFound { + color: var(--glass-text-muted) !important; +} + +/* --- Repo table --- */ +.repoTable { + background-color: transparent !important; +} + +/* --- Screenshot borders --- */ +.screen { + border-color: var(--glass-accent) !important; +} + +/* --- Mod comment --- */ +.modComment { + border-color: rgba(207, 49, 49, 0.5) !important; + background: rgba(207, 49, 49, 0.08) !important; + border-radius: 12px !important; +} + +/* --- Category display --- */ +#Category { + color: var(--glass-text-muted) !important; +} + +/* --- Docker card background (inside cards) --- */ +.dockerCardBackground { + background: transparent !important; +} + +/* --- Favourite button styles --- */ +.fav { + background: linear-gradient( + to bottom, + transparent 0%, + rgba(0, 0, 0, 0.3) 100% + ), + linear-gradient( + to right, + rgba(0, 153, 0, 0.7) 0%, + rgba(0, 153, 0, 0.85) 50%, + rgba(0, 153, 0, 0.7) 100% + ) !important; +} + +.nonfav { + background-color: rgba(255, 255, 255, 0.10) !important; +} + +/* --- Icon display inside popup --- */ +img.displayIcon, +img.popupIcon { + border-radius: 12px !important; +} + +/* --- Spinner overlay --- */ +.spinnerBackground { + background: rgba(0, 0, 0, 0.3) !important; + backdrop-filter: blur(5px) !important; + -webkit-backdrop-filter: blur(5px) !important; +} + +/* --- Readmore gradient mask fix for dark theme --- */ +.readmore-js-collapsed { + -webkit-mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,1)), to(rgba(255,255,255,0.1))) !important; +} + +/* --- Credits page --- */ +.credits { + color: var(--glass-text) !important; +} + +.ca_creditTitle, +.ca_creditheader { + color: var(--glass-text-heading) !important; +} + +.ca_credit { + color: var(--glass-text-muted) !important; +} diff --git a/update-icons.sh b/update-icons.sh index 8489a30..10b0282 100644 --- a/update-icons.sh +++ b/update-icons.sh @@ -84,8 +84,8 @@ auto_detect_icon() { rustfs) echo "rustdesk"; return 0 ;; netbox-redis-cache) echo "redis"; return 0 ;; netbox-worker) echo "netbox"; return 0 ;; - netdisco-backend) echo "netdisco"; return 0 ;; - netdisco-web) echo "netdisco"; return 0 ;; + netdisco-backend) echo "networking-toolbox"; return 0 ;; + netdisco-web) echo "networking-toolbox"; return 0 ;; libation) echo "audible"; return 0 ;; diode-*) echo "netbox"; return 0 ;; authentik-worker) echo "authentik"; return 0 ;; @@ -95,8 +95,11 @@ auto_detect_icon() { speedtest-tracker) echo "speedtest-tracker"; return 0 ;; timemachine) echo "apple"; return 0 ;; open-webui) echo "open-webui"; return 0 ;; - urbackup) echo "urbackup"; return 0 ;; + urbackup) echo "networking-toolbox"; return 0 ;; netalertx) echo "netalertx"; return 0 ;; + seekandwatch) echo "databasement"; return 0 ;; + unmarr) echo "homarr"; return 0 ;; + dockhand) echo "docker"; return 0 ;; esac # Strategy 1: lowercase name directly