Compare commits
No commits in common. "b0d6fcc6d8f0782fce96a407aec0d049555b4236" and "36aecd800e198c33e1bb41e0914415e837e5a087" have entirely different histories.
b0d6fcc6d8
...
36aecd800e
@ -8,97 +8,66 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"Storage": [
|
"Development": [
|
||||||
{
|
|
||||||
"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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist.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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jellyfin",
|
|
||||||
"link": "https://watch.justin.deal",
|
|
||||||
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/jellyfin-light.svg",
|
|
||||||
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/jellyfin-dark.svg",
|
|
||||||
"alt": "Jellyfin",
|
|
||||||
"tags": ["movies", "tv", "shows", "watch"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
"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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ollama.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate.svg",
|
||||||
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate-dark.svg",
|
|
||||||
"alt": "TeslaMate",
|
"alt": "TeslaMate",
|
||||||
"tags": ["car", "tesla"]
|
"tags": ["car", "tesla"]
|
||||||
}
|
}
|
||||||
@ -107,51 +76,63 @@
|
|||||||
"Infrastructure": [
|
"Infrastructure": [
|
||||||
{
|
{
|
||||||
"name": "Pi-hole",
|
"name": "Pi-hole",
|
||||||
"link": "http://pi.hole/admin/",
|
"link": "https://pihole.justin.deal",
|
||||||
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik.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-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik.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": "Syncthing",
|
"name": "Silverbullet",
|
||||||
"link": "https://sync.justin.deal",
|
"link": "https://notes.justin.deal",
|
||||||
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-light.svg",
|
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/png/silverbullet.png",
|
||||||
"iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/syncthing-dark.svg",
|
"alt": "Silverbullet",
|
||||||
"alt": "Syncthing",
|
"tags": ["notes", "markdown", "knowledge base"]
|
||||||
"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"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -389,17 +389,11 @@ self.addEventListener('message', event => {
|
|||||||
cacheNames.map(cacheName => caches.delete(cacheName))
|
cacheNames.map(cacheName => caches.delete(cacheName))
|
||||||
);
|
);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Notify client that caches were cleared, but only if port is available
|
// Notify client that caches were cleared
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -73,9 +73,6 @@ 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 => {
|
||||||
@ -99,8 +96,6 @@ 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
|
||||||
@ -114,36 +109,9 @@ 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 => {
|
||||||
|
@ -7,8 +7,7 @@
|
|||||||
* <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-light.svg"
|
* img="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.svg"
|
||||||
* imgDark="https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-dark.svg"
|
|
||||||
* alt="Gitea"
|
* alt="Gitea"
|
||||||
* />
|
* />
|
||||||
* ```
|
* ```
|
||||||
@ -25,26 +24,17 @@ interface Props {
|
|||||||
href: string;
|
href: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL of the service icon (light version)
|
* The URL of the service icon
|
||||||
*/
|
*/
|
||||||
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, imgDark, alt } = Astro.props;
|
const { name, href, img, 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)}`;
|
||||||
@ -60,22 +50,10 @@ 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 dark-theme-only"
|
class="service-icon"
|
||||||
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"
|
||||||
@ -126,8 +104,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 - reduced will-change usage to prevent high memory consumption */
|
/* Performance optimizations */
|
||||||
/* Only use will-change on hover to reduce memory usage */
|
will-change: transform, box-shadow, border-color, background-color, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gradient background effect */
|
/* Gradient background effect */
|
||||||
@ -215,8 +193,6 @@ 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 {
|
||||||
|
@ -102,7 +102,6 @@ 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>
|
||||||
|
@ -50,32 +50,10 @@ 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>
|
||||||
@ -114,15 +92,13 @@ import "../styles/global.css";
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Preload and include Press Start 2P font -->
|
<!-- Preload critical font files -->
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="https://cdn.jsdelivr.net/npm/@fontsource/press-start-2p/index.css"
|
href="https://cdn.jsdelivr.net/fontsource/fonts/press-start-2p@latest/latin-400-normal.woff2"
|
||||||
as="style"
|
as="font"
|
||||||
/>
|
type="font/woff2"
|
||||||
<link
|
crossorigin
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@fontsource/press-start-2p/index.css"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Preload Alpine.js -->
|
<!-- Preload Alpine.js -->
|
||||||
|
@ -74,7 +74,6 @@ 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[];
|
||||||
}
|
}
|
||||||
|
@ -103,17 +103,10 @@ export type Service = {
|
|||||||
link: string;
|
link: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL to the service icon (light version)
|
* The URL to the service icon
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user