forked from Timmy_Foundation/the-nexus
Adds full Progressive Web App support to The Nexus: - sw.js: Service worker with cache-first strategy for local assets and stale-while-revalidate for CDN resources (Three.js, fonts) - offline.html: Styled offline fallback page with auto-reconnect - icons/nexus-icon.svg: Nexus crystal sigil icon (SVG) - icons/nexus-maskable.svg: Maskable icon for adaptive shapes - manifest.json: Complete PWA manifest with theme color #4af0c0, standalone display mode, shortcuts, and icon definitions - index.html: Service worker registration, Apple PWA meta tags, theme colors, and MS application config The Nexus now works offline after first visit and can be installed to home screen on mobile and desktop devices. Fixes #14
199 lines
5.0 KiB
HTML
199 lines
5.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Offline — The Nexus</title>
|
|
<meta name="description" content="The Nexus is currently offline">
|
|
<style>
|
|
:root {
|
|
--color-bg: #0a1628;
|
|
--color-bg-secondary: #0d1f35;
|
|
--color-primary: #4af0c0;
|
|
--color-primary-dim: #2dd4a8;
|
|
--color-text: #e6f1ff;
|
|
--color-text-muted: #8b9bb4;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
background: var(--color-bg);
|
|
color: var(--color-text);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
padding: 2rem;
|
|
background: radial-gradient(ellipse at center, var(--color-bg-secondary) 0%, var(--color-bg) 70%);
|
|
}
|
|
|
|
.container {
|
|
max-width: 480px;
|
|
}
|
|
|
|
.icon {
|
|
width: 120px;
|
|
height: 120px;
|
|
margin-bottom: 2rem;
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|
50% { opacity: 0.7; transform: scale(0.95); }
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2rem;
|
|
font-weight: 300;
|
|
margin-bottom: 1rem;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.nexus-title {
|
|
color: var(--color-primary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
p {
|
|
color: var(--color-text-muted);
|
|
line-height: 1.6;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
background: rgba(74, 240, 192, 0.1);
|
|
border: 1px solid rgba(74, 240, 192, 0.3);
|
|
padding: 0.75rem 1.5rem;
|
|
border-radius: 8px;
|
|
font-size: 0.875rem;
|
|
color: var(--color-primary);
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.status::before {
|
|
content: '';
|
|
width: 8px;
|
|
height: 8px;
|
|
background: var(--color-primary);
|
|
border-radius: 50%;
|
|
animation: blink 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes blink {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.3; }
|
|
}
|
|
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
background: var(--color-primary);
|
|
color: var(--color-bg);
|
|
border: none;
|
|
padding: 0.875rem 1.5rem;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: var(--color-primary-dim);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.hint {
|
|
margin-top: 2rem;
|
|
font-size: 0.75rem;
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
/* Crystal animation */
|
|
.crystal {
|
|
fill: none;
|
|
stroke: var(--color-primary);
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.crystal-center {
|
|
fill: var(--color-primary);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<svg class="icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
|
<!-- Crystal icosahedron representation -->
|
|
<path class="crystal" d="M50,15 L84.6,42.5 L84.6,72.5 L50,85 L15.4,72.5 L15.4,42.5 Z" opacity="0.8"/>
|
|
<path class="crystal" d="M50,15 L50,85 M15.4,42.5 L84.6,72.5 M84.6,42.5 L15.4,72.5" opacity="0.5"/>
|
|
<circle class="crystal-center" cx="50" cy="55" r="8" opacity="0.9"/>
|
|
<!-- Offline indicator -->
|
|
<circle cx="75" cy="25" r="12" fill="#ff6b6b" opacity="0.9"/>
|
|
<path d="M69,25 L81,25" stroke="white" stroke-width="2"/>
|
|
</svg>
|
|
|
|
<h1>The <span class="nexus-title">Nexus</span> is Dormant</h1>
|
|
|
|
<div class="status">
|
|
<span>You're offline</span>
|
|
</div>
|
|
|
|
<p>
|
|
The crystalline pathways cannot form without a connection to the sovereign network.
|
|
Check your connection and try again to enter the 3D realm.
|
|
</p>
|
|
|
|
<button class="btn" onclick="window.location.reload()">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="23 4 23 10 17 10"></polyline>
|
|
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
|
</svg>
|
|
Reconnect
|
|
</button>
|
|
|
|
<p class="hint">
|
|
Core assets are cached for offline use. Some features may be limited without connectivity.
|
|
</p>
|
|
</div>
|
|
|
|
<script>
|
|
// Auto-retry when connection comes back
|
|
window.addEventListener('online', () => {
|
|
window.location.href = '/';
|
|
});
|
|
|
|
// Check if we're actually back online
|
|
setInterval(() => {
|
|
if (navigator.onLine) {
|
|
fetch('/', { method: 'HEAD', cache: 'no-store' })
|
|
.then(() => {
|
|
window.location.href = '/';
|
|
})
|
|
.catch(() => {
|
|
// Still unreachable, stay on offline page
|
|
});
|
|
}
|
|
}, 5000);
|
|
</script>
|
|
</body>
|
|
</html>
|