diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..04aed77 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,25 @@ +{ + "name": "Cris Charles Garcia | Full-Stack Developer & E-Business Card", + "short_name": "Cris Charles", + "description": "Digital portfolio and e-business card of Cris Charles Garcia. Full-stack developer and Cisco certified specialist.", + "start_url": "/", + "display": "standalone", + "background_color": "#090a0f", + "theme_color": "#090a0f", + "orientation": "any", + "categories": ["portfolio", "business", "utilities"], + "icons": [ + { + "src": "/logo.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/logo.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..f3f541f --- /dev/null +++ b/public/sw.js @@ -0,0 +1,99 @@ +const CACHE_NAME = 'portfolio-cache-v1'; + +// Core assets to precache immediately on install +const PRECACHE_ASSETS = [ + '/', + '/manifest.webmanifest', + '/logo.png', + '/favicon.ico', + '/CV-Cris-Charles-Garcia.pdf' +]; + +// Install: precache shell +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(PRECACHE_ASSETS).catch((err) => { + console.warn('SW: Precache partial failure (non-critical):', err); + }); + }).then(() => self.skipWaiting()) + ); +}); + +// Activate: clean up old cache versions +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames + .filter((name) => name !== CACHE_NAME) + .map((name) => caches.delete(name)) + ); + }).then(() => self.clients.claim()) + ); +}); + +// Fetch: Network-First for HTML navigation, Stale-While-Revalidate for static assets +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // Skip non-GET requests and browser extensions + if (request.method !== 'GET' || !url.protocol.startsWith('http')) { + return; + } + + // 1. Navigation requests (HTML pages): Network-First with Cache Fallback + if (request.mode === 'navigate') { + event.respondWith( + fetch(request) + .then((networkResponse) => { + if (networkResponse && networkResponse.status === 200) { + const responseClone = networkResponse.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(request, responseClone); + }); + } + return networkResponse; + }) + .catch(async () => { + // Offline fallback + const cachedResponse = await caches.match(request); + if (cachedResponse) { + return cachedResponse; + } + // Fallback to home page root if specific subpage isn't cached + const rootCached = await caches.match('/'); + if (rootCached) { + return rootCached; + } + return new Response( + '
You are currently offline. Please reconnect to view new updates or visit previously cached pages.
Return to Home