Update Icons to change with theme and resolve errors in console

This commit is contained in:
Justin Deal 2025-05-06 18:41:42 -07:00
parent 36aecd800e
commit b60c707b80
8 changed files with 170 additions and 64 deletions

View File

@ -8,66 +8,89 @@
} }
], ],
"Development": [ "Storage": [
{
"name": "Nextcloud",
"link": "https://cloud.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/nextcloud-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/nextcloud-dark.svg",
"alt": "Nextcloud"
},
{ {
"name": "Gitea", "name": "Gitea",
"link": "https://code.justin.deal", "link": "https://code.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-dark.svg",
"alt": "Gitea", "alt": "Gitea",
"tags": ["git", "code", "repository"] "tags": ["git", "code", "repository"]
}, },
{ {
"name": "OpenGist", "name": "OpenGist",
"link": "https://snippets.justin.deal", "link": "https://snippets.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist-dark.svg",
"alt": "OpenGist", "alt": "OpenGist",
"tags": ["gist", "snippets"] "tags": ["gist", "snippets"]
}, },
{
"name": "Calibre-Web",
"link": "https://books.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre-web-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre-web-dark.svg",
"alt": "Calibre-Web",
"tags": ["books", "read"]
}
],
"Utilities": [
{
"name": "Searxng",
"link": "https://search.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng-dark.svg",
"alt": "Searxng",
"tags": ["search", "privacy", "metasearch"]
},
{ {
"name": "IT-Tools", "name": "IT-Tools",
"link": "https://tools.justin.deal", "link": "https://tools.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools-dark.svg",
"alt": "IT-Tools", "alt": "IT-Tools",
"tags": ["dev"] "tags": ["dev"]
}, },
{ {
"name": "Ollama", "name": "Ollama",
"link": "https://ai.justin.deal", "link": "https://ai.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ollama.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ollama-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ollama-dark.svg",
"alt": "Ollama", "alt": "Ollama",
"tags": ["LLM", "AI", "models", "chatbot"] "tags": ["LLM", "AI", "models", "chatbot"]
} }
], ],
"Entertainment": [
{
"name": "Calibre-Web",
"link": "https://books.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre.svg",
"alt": "Calibre-Web",
"tags": ["books", "read"]
}
],
"Analytics & Monitoring": [ "Analytics & Monitoring": [
{ {
"name": "Uptime Kuma", "name": "Uptime Kuma",
"link": "https://status.justin.deal", "link": "https://status.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma-dark.svg",
"alt": "Uptime Kuma", "alt": "Uptime Kuma",
"tags": ["status"] "tags": ["status"]
}, },
{ {
"name": "Umami", "name": "Umami",
"link": "https://analytics.justin.deal", "link": "https://analytics.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami-dark.svg",
"alt": "Umami", "alt": "Umami",
"tags": ["analytics"] "tags": ["analytics"]
}, },
{ {
"name": "TeslaMate", "name": "TeslaMate",
"link": "https://tesla.justin.deal", "link": "https://tesla.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate-dark.svg",
"alt": "TeslaMate", "alt": "TeslaMate",
"tags": ["car", "tesla"] "tags": ["car", "tesla"]
} }
@ -76,63 +99,51 @@
"Infrastructure": [ "Infrastructure": [
{ {
"name": "Pi-hole", "name": "Pi-hole",
"link": "https://pihole.justin.deal", "link": "http://pi.hole/admin/",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole-dark.svg",
"alt": "Pi-hole", "alt": "Pi-hole",
"tags": ["dns"] "tags": ["dns"]
}, },
{ {
"name": "Ntfy", "name": "Ntfy",
"link": "https://ntfy.justin.deal", "link": "https://ntfy.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy-dark.svg",
"alt": "Ntfy", "alt": "Ntfy",
"tags": ["notifications"] "tags": ["notifications"]
}, },
{ {
"name": "Vaultwarden", "name": "Vaultwarden",
"link": "https://passwords.justin.deal", "link": "https://passwords.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden-dark.svg",
"alt": "Vaultwarden", "alt": "Vaultwarden",
"tags": ["passwords"] "tags": ["passwords"]
}, },
{ {
"name": "Authentik", "name": "Authentik",
"link": "https://auth.justin.deal", "link": "https://auth.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik-dark.svg",
"alt": "Authentik", "alt": "Authentik",
"tags": ["SSO", "Auth", "Authentication"] "tags": ["SSO", "Auth", "Authentication"]
}, },
{ {
"name": "Traefik", "name": "Traefik",
"link": "https://proxy.justin.deal:8080", "link": "https://proxy.justin.deal:8080",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik.svg", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik-light.svg",
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik-dark.svg",
"alt": "Traefik", "alt": "Traefik",
"tags": ["proxy", "reverse-proxy", "load-balancer"] "tags": ["proxy", "reverse-proxy", "load-balancer"]
}
],
"Utilities": [
{
"name": "Searxng",
"link": "https://search.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng.svg",
"alt": "Searxng",
"tags": ["search", "privacy", "metasearch"]
}, },
{ {
"name": "Silverbullet", "name": "Syncthing",
"link": "https://notes.justin.deal", "link": "https://sync.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/png/silverbullet.png", "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-light.svg",
"alt": "Silverbullet", "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-dark.svg",
"tags": ["notes", "markdown", "knowledge base"] "alt": "Syncthing",
}, "tags": ["sync", "files"]
{
"name": "Vikunja",
"link": "https://todo.justin.deal",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vikunja.svg",
"alt": "Vikunja",
"tags": ["todo", "tasks", "productivity"]
} }
] ]
} }

View File

@ -389,11 +389,17 @@ self.addEventListener('message', event => {
cacheNames.map(cacheName => caches.delete(cacheName)) cacheNames.map(cacheName => caches.delete(cacheName))
); );
}).then(() => { }).then(() => {
// Notify client that caches were cleared // Notify client that caches were cleared, but only if port is available
if (event.ports && event.ports.length > 0) {
try {
event.ports[0].postMessage({ event.ports[0].postMessage({
status: 'success', status: 'success',
message: 'All caches cleared successfully' message: 'All caches cleared successfully'
}); });
} catch (err) {
console.log('Could not post message to client: port may be closed');
}
}
}) })
); );
} }

View File

@ -73,6 +73,9 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`;
if (!scrollRevealElements.length) return; if (!scrollRevealElements.length) return;
// Store observers to properly disconnect them later
const observers = new Map();
// Check if IntersectionObserver is supported // Check if IntersectionObserver is supported
if ('IntersectionObserver' in window) { if ('IntersectionObserver' in window) {
scrollRevealElements.forEach(element => { scrollRevealElements.forEach(element => {
@ -96,6 +99,8 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`;
// Unobserve if once is true // Unobserve if once is true
if (once) { if (once) {
observer.unobserve(entry.target); observer.unobserve(entry.target);
// Remove from observers map to allow garbage collection
observers.delete(entry.target);
} }
} else if (!once) { } else if (!once) {
// Remove class if element leaves viewport and once is false // Remove class if element leaves viewport and once is false
@ -109,9 +114,36 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`;
} }
); );
// Store observer reference for cleanup
observers.set(element, observer);
// Start observing // Start observing
observer.observe(element); observer.observe(element);
}); });
// Cleanup function to disconnect observers when page changes or component unmounts
const cleanup = () => {
observers.forEach((observer, element) => {
observer.unobserve(element);
observer.disconnect();
});
observers.clear();
};
// Clean up observers when page is about to unload
window.addEventListener('beforeunload', cleanup);
// For SPA navigation (if applicable)
document.addEventListener('astro:before-swap', cleanup);
// Additional cleanup for any framework-specific unmounting
document.addEventListener('astro:after-swap', () => {
// Re-initialize on page navigation for SPAs
const newScrollRevealElements = document.querySelectorAll('.scroll-reveal');
if (newScrollRevealElements.length) {
// This will be handled by the DOMContentLoaded event in the new page
}
});
} else { } else {
// Fallback for browsers that don't support IntersectionObserver // Fallback for browsers that don't support IntersectionObserver
scrollRevealElements.forEach(element => { scrollRevealElements.forEach(element => {

View File

@ -7,7 +7,8 @@
* <ServiceCard * <ServiceCard
* name="Gitea" * name="Gitea"
* href="https://code.justin.deal" * href="https://code.justin.deal"
* img="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.svg" * img="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-light.svg"
* imgDark="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-dark.svg"
* alt="Gitea" * alt="Gitea"
* /> * />
* ``` * ```
@ -24,17 +25,26 @@ interface Props {
href: string; href: string;
/** /**
* The URL of the service icon * The URL of the service icon (light version)
*/ */
img: string; img: string;
/**
* The URL of the service icon for dark mode (dark version)
* If not provided, falls back to the light version
*/
imgDark?: string;
/** /**
* Alternative text for the service icon * Alternative text for the service icon
*/ */
alt: string; alt: string;
} }
const { name, href, img, alt } = Astro.props; const { name, href, img, imgDark, alt } = Astro.props;
// Use the provided dark icon or fall back to the light icon
const darkIcon = imgDark || img;
// Generate a unique ID for the service card // Generate a unique ID for the service card
const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`; const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
@ -50,10 +60,22 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
> >
<div class="service-icon-container flex-shrink-0 relative"> <div class="service-icon-container flex-shrink-0 relative">
<div class="service-icon-background"></div> <div class="service-icon-background"></div>
<!-- Light icon (shown in dark mode) -->
<img <img
src={img} src={img}
alt={alt} alt={alt}
class="service-icon" class="service-icon dark-theme-only"
loading="lazy"
decoding="async"
width="64"
height="64"
fetchpriority="low"
/>
<!-- Dark icon (shown in light mode) -->
<img
src={darkIcon}
alt={alt}
class="service-icon light-theme-only"
loading="lazy" loading="lazy"
decoding="async" decoding="async"
width="64" width="64"
@ -104,8 +126,8 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
opacity var(--card-transition-duration) var(--card-transition-timing), opacity var(--card-transition-duration) var(--card-transition-timing),
padding var(--card-transition-duration) var(--card-transition-timing); padding var(--card-transition-duration) var(--card-transition-timing);
/* Performance optimizations */ /* Performance optimizations - reduced will-change usage to prevent high memory consumption */
will-change: transform, box-shadow, border-color, background-color, opacity; /* Only use will-change on hover to reduce memory usage */
} }
/* Gradient background effect */ /* Gradient background effect */
@ -193,6 +215,8 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
border-color: var(--color-zag-accent); border-color: var(--color-zag-accent);
background-color: var(--color-zag-bg-hover); background-color: var(--color-zag-bg-hover);
z-index: 10; z-index: 10;
/* Apply will-change only on hover to reduce memory consumption */
will-change: transform, box-shadow;
} }
.service-card:hover::before { .service-card:hover::before {

View File

@ -102,6 +102,7 @@ const categoryLower = category.toLowerCase();
name={app.name} name={app.name}
href={app.link} href={app.link}
img={app.icon} img={app.icon}
imgDark={app.iconDark}
alt={app.name} alt={app.name}
/> />
</div> </div>

View File

@ -50,10 +50,32 @@ import "../styles/global.css";
navigator.serviceWorker.register('/service-worker.js') navigator.serviceWorker.register('/service-worker.js')
.then(registration => { .then(registration => {
console.log('SW registered: ', registration.scope); console.log('SW registered: ', registration.scope);
// Handle communication errors
navigator.serviceWorker.addEventListener('message', event => {
// Process messages from service worker
if (event.data && event.data.type) {
console.log('Message from SW:', event.data.type);
}
});
// Handle controller change
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('Service worker controller changed');
});
}) })
.catch(error => { .catch(error => {
console.log('SW registration failed: ', error); console.log('SW registration failed: ', error);
}); });
// Handle communication errors globally
window.addEventListener('error', event => {
if (event.message && event.message.includes('postMessage')) {
console.log('Caught postMessage error:', event.message);
// Prevent the error from bubbling up
event.preventDefault();
}
});
}); });
} }
</script> </script>
@ -92,13 +114,15 @@ import "../styles/global.css";
rel="stylesheet" rel="stylesheet"
/> />
<!-- Preload critical font files --> <!-- Preload and include Press Start 2P font -->
<link <link
rel="preload" rel="preload"
href="https://cdn.jsdelivr.net/fontsource/fonts/press-start-2p@latest/latin-400-normal.woff2" href="https://cdn.jsdelivr.net/npm/@fontsource/press-start-2p/index.css"
as="font" as="style"
type="font/woff2" />
crossorigin <link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@fontsource/press-start-2p/index.css"
/> />
<!-- Preload Alpine.js --> <!-- Preload Alpine.js -->

View File

@ -74,6 +74,7 @@ export interface Service {
name: string; name: string;
link: string; link: string;
icon: string; icon: string;
iconDark?: string;
alt: string; alt: string;
tags?: string[]; tags?: string[];
} }

View File

@ -103,10 +103,17 @@ export type Service = {
link: string; link: string;
/** /**
* The URL to the service icon * The URL to the service icon (light version)
* Used in dark theme
*/ */
icon: string; icon: string;
/**
* The URL to the dark version of the service icon
* Used in light theme
*/
iconDark?: string;
/** /**
* Alternative text for the service icon * Alternative text for the service icon
*/ */