diff --git a/icons/nexus-icon.svg b/icons/nexus-icon.svg new file mode 100644 index 0000000..0f7ed7b --- /dev/null +++ b/icons/nexus-icon.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/nexus-maskable.svg b/icons/nexus-maskable.svg new file mode 100644 index 0000000..ae4d623 --- /dev/null +++ b/icons/nexus-maskable.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 34af931..e941c22 100644 --- a/index.html +++ b/index.html @@ -5,15 +5,38 @@ Timmy's Nexus + + + + + + + + + + + + + + + + + + + + + + + @@ -27,5 +50,52 @@ + + + + + + diff --git a/manifest.json b/manifest.json index 8137537..38d5182 100644 --- a/manifest.json +++ b/manifest.json @@ -1,20 +1,61 @@ { - "name": "Timmy's Nexus", + "name": "The Nexus", "short_name": "Nexus", + "description": "Timmy's sovereign 3D world — a Three.js environment serving as the central hub for all portals", "start_url": "/", - "display": "fullscreen", - "background_color": "#050510", - "theme_color": "#050510", + "display": "standalone", + "display_override": ["fullscreen", "minimal-ui"], + "orientation": "any", + "background_color": "#0a1628", + "theme_color": "#4af0c0", + "categories": ["entertainment", "games"], + "lang": "en", + "dir": "ltr", + "scope": "/", "icons": [ { - "src": "icons/t-logo-192.png", - "sizes": "192x192", - "type": "image/png" + "src": "icons/nexus-icon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any" }, { - "src": "icons/t-logo-512.png", - "sizes": "512x512", - "type": "image/png" + "src": "icons/nexus-maskable.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "maskable" } - ] + ], + "screenshots": [ + { + "src": "screenshots/nexus-wide.png", + "sizes": "1280x720", + "type": "image/png", + "form_factor": "wide", + "label": "The Nexus 3D environment" + }, + { + "src": "screenshots/nexus-narrow.png", + "sizes": "750x1334", + "type": "image/png", + "form_factor": "narrow", + "label": "The Nexus on mobile" + } + ], + "shortcuts": [ + { + "name": "Enter Nexus", + "short_name": "Enter", + "description": "Jump directly into the Nexus world", + "url": "/?action=enter", + "icons": [ + { + "src": "icons/nexus-icon.svg", + "sizes": "any" + } + ] + } + ], + "related_applications": [], + "prefer_related_applications": false } diff --git a/offline.html b/offline.html new file mode 100644 index 0000000..19eee18 --- /dev/null +++ b/offline.html @@ -0,0 +1,198 @@ + + + + + + Offline — The Nexus + + + + +
+ + + + + + + + + + +

The Nexus is Dormant

+ +
+ You're offline +
+ +

+ The crystalline pathways cannot form without a connection to the sovereign network. + Check your connection and try again to enter the 3D realm. +

+ + + +

+ Core assets are cached for offline use. Some features may be limited without connectivity. +

+
+ + + + diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..239f688 --- /dev/null +++ b/sw.js @@ -0,0 +1,218 @@ +/** + * The Nexus Service Worker + * Provides offline capability and home screen install support + * Strategy: Cache-first for local assets, stale-while-revalidate for CDN + */ + +const CACHE_VERSION = 'nexus-v1'; +const STATIC_CACHE = `${CACHE_VERSION}-static`; +const CDN_CACHE = `${CACHE_VERSION}-cdn`; +const OFFLINE_PAGE = '/offline.html'; + +// Core local assets that must be cached +const CORE_ASSETS = [ + '/', + '/index.html', + '/style.css', + '/app.js', + '/manifest.json', + '/icons/nexus-icon.svg', + '/icons/nexus-maskable.svg', + OFFLINE_PAGE +]; + +// CDN resources that benefit from caching but can be stale +const CDN_PATTERNS = [ + /^https:\/\/unpkg\.com/, + /^https:\/\/cdn\.jsdelivr\.net/, + /^https:\/\/fonts\.googleapis\.com/, + /^https:\/\/fonts\.gstatic\.com/, + /^https:\/\/cdn\.threejs\.org/ +]; + +/** + * Check if a URL matches any CDN pattern + */ +function isCdnResource(url) { + return CDN_PATTERNS.some(pattern => pattern.test(url)); +} + +/** + * Install event - cache core assets + */ +self.addEventListener('install', (event) => { + console.log('[Nexus SW] Installing...'); + + event.waitUntil( + caches.open(STATIC_CACHE) + .then(cache => { + console.log('[Nexus SW] Caching core assets'); + return cache.addAll(CORE_ASSETS); + }) + .then(() => { + console.log('[Nexus SW] Core assets cached'); + return self.skipWaiting(); + }) + .catch(err => { + console.error('[Nexus SW] Cache failed:', err); + }) + ); +}); + +/** + * Activate event - clean up old caches + */ +self.addEventListener('activate', (event) => { + console.log('[Nexus SW] Activating...'); + + event.waitUntil( + caches.keys() + .then(cacheNames => { + return Promise.all( + cacheNames + .filter(name => name.startsWith('nexus-') && name !== STATIC_CACHE && name !== CDN_CACHE) + .map(name => { + console.log('[Nexus SW] Deleting old cache:', name); + return caches.delete(name); + }) + ); + }) + .then(() => { + console.log('[Nexus SW] Activated'); + return self.clients.claim(); + }) + ); +}); + +/** + * Fetch event - handle requests with appropriate strategy + */ +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // Skip non-GET requests + if (request.method !== 'GET') { + return; + } + + // Skip cross-origin requests that aren't CDN resources + if (url.origin !== self.location.origin && !isCdnResource(url.href)) { + return; + } + + // Handle CDN resources with stale-while-revalidate + if (isCdnResource(url.href)) { + event.respondWith(handleCdnRequest(request)); + return; + } + + // Handle local assets with cache-first strategy + event.respondWith(handleLocalRequest(request)); +}); + +/** + * Cache-first strategy for local assets + * Fastest response, updates cache in background + */ +async function handleLocalRequest(request) { + try { + const cache = await caches.open(STATIC_CACHE); + const cached = await cache.match(request); + + // Return cached version immediately if available + if (cached) { + // Revalidate in background for next time + fetch(request) + .then(response => { + if (response.ok) { + cache.put(request, response.clone()); + } + }) + .catch(() => { + // Network failed, cached version is already being used + }); + + return cached; + } + + // Not in cache - fetch from network + const response = await fetch(request); + + if (response.ok) { + cache.put(request, response.clone()); + } + + return response; + } catch (error) { + console.error('[Nexus SW] Local request failed:', error); + + // Return offline page for navigation requests + if (request.mode === 'navigate') { + const cache = await caches.open(STATIC_CACHE); + const offlinePage = await cache.match(OFFLINE_PAGE); + if (offlinePage) { + return offlinePage; + } + } + + throw error; + } +} + +/** + * Stale-while-revalidate strategy for CDN resources + * Serves stale content while updating in background + */ +async function handleCdnRequest(request) { + const cache = await caches.open(CDN_CACHE); + const cached = await cache.match(request); + + // Always try to fetch fresh version + const fetchPromise = fetch(request) + .then(response => { + if (response.ok) { + cache.put(request, response.clone()); + } + return response; + }) + .catch(error => { + console.error('[Nexus SW] CDN fetch failed:', error); + throw error; + }); + + // Return cached version immediately if available, otherwise wait for network + if (cached) { + // Return stale but revalidate for next time + fetchPromise.catch(() => {}); // Swallow errors, we have cached version + return cached; + } + + // No cache - must wait for network + return fetchPromise; +} + +/** + * Message handler for runtime cache updates + */ +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } + + if (event.data && event.data.type === 'GET_VERSION') { + event.ports[0].postMessage({ version: CACHE_VERSION }); + } +}); + +/** + * Background sync for deferred actions (future enhancement) + */ +self.addEventListener('sync', (event) => { + if (event.tag === 'nexus-sync') { + console.log('[Nexus SW] Background sync triggered'); + // Future: sync chat messages, state updates, etc. + } +}); + +console.log('[Nexus SW] Service worker loaded');