diff --git a/config/services.json b/config/services.json
index 64d843f..1606fd4 100644
--- a/config/services.json
+++ b/config/services.json
@@ -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",
"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",
"tags": ["git", "code", "repository"]
},
{
"name": "OpenGist",
"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",
"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",
"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",
"tags": ["dev"]
},
{
"name": "Ollama",
"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",
"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": [
{
"name": "Uptime Kuma",
"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",
"tags": ["status"]
},
{
"name": "Umami",
"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",
"tags": ["analytics"]
},
{
"name": "TeslaMate",
"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",
"tags": ["car", "tesla"]
}
@@ -76,63 +99,51 @@
"Infrastructure": [
{
"name": "Pi-hole",
- "link": "https://pihole.justin.deal",
- "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole.svg",
+ "link": "http://pi.hole/admin/",
+ "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",
"tags": ["dns"]
},
{
"name": "Ntfy",
"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",
"tags": ["notifications"]
},
{
"name": "Vaultwarden",
"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",
"tags": ["passwords"]
},
{
"name": "Authentik",
"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",
"tags": ["SSO", "Auth", "Authentication"]
},
{
"name": "Traefik",
"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",
"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",
- "link": "https://notes.justin.deal",
- "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/png/silverbullet.png",
- "alt": "Silverbullet",
- "tags": ["notes", "markdown", "knowledge base"]
- },
- {
- "name": "Vikunja",
- "link": "https://todo.justin.deal",
- "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vikunja.svg",
- "alt": "Vikunja",
- "tags": ["todo", "tasks", "productivity"]
+ "name": "Syncthing",
+ "link": "https://sync.justin.deal",
+ "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-light.svg",
+ "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-dark.svg",
+ "alt": "Syncthing",
+ "tags": ["sync", "files"]
}
]
}
diff --git a/public/service-worker.js b/public/service-worker.js
index 8447086..ff8606c 100644
--- a/public/service-worker.js
+++ b/public/service-worker.js
@@ -389,11 +389,17 @@ self.addEventListener('message', event => {
cacheNames.map(cacheName => caches.delete(cacheName))
);
}).then(() => {
- // Notify client that caches were cleared
- event.ports[0].postMessage({
- status: 'success',
- message: 'All caches cleared successfully'
- });
+ // Notify client that caches were cleared, but only if port is available
+ if (event.ports && event.ports.length > 0) {
+ try {
+ event.ports[0].postMessage({
+ status: 'success',
+ message: 'All caches cleared successfully'
+ });
+ } catch (err) {
+ console.log('Could not post message to client: port may be closed');
+ }
+ }
})
);
}
diff --git a/src/components/common/ScrollReveal.astro b/src/components/common/ScrollReveal.astro
index 900967a..b8699d4 100644
--- a/src/components/common/ScrollReveal.astro
+++ b/src/components/common/ScrollReveal.astro
@@ -73,6 +73,9 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`;
if (!scrollRevealElements.length) return;
+ // Store observers to properly disconnect them later
+ const observers = new Map();
+
// Check if IntersectionObserver is supported
if ('IntersectionObserver' in window) {
scrollRevealElements.forEach(element => {
@@ -96,6 +99,8 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`;
// Unobserve if once is true
if (once) {
observer.unobserve(entry.target);
+ // Remove from observers map to allow garbage collection
+ observers.delete(entry.target);
}
} else if (!once) {
// 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
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 {
// Fallback for browsers that don't support IntersectionObserver
scrollRevealElements.forEach(element => {
diff --git a/src/components/common/ServiceCard.astro b/src/components/common/ServiceCard.astro
index d7e4026..fd41673 100644
--- a/src/components/common/ServiceCard.astro
+++ b/src/components/common/ServiceCard.astro
@@ -7,7 +7,8 @@
*