Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions home.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@
}
</style>
<script src="theme.js"></script>
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#0f1115">
</head>
<body>

Expand Down Expand Up @@ -223,5 +225,16 @@ <h2 id="chatbot-heading">🤖 Ask Your AI Tutor</h2>

<script src="progress.js"></script>
<script src="home.js"></script>

<script>
(function () {
if (!('serviceWorker' in navigator)) return;
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').catch(function (err) {
console.warn('LearnSphere: Service Worker registration failed:', err);
});
});
})();
</script>
</body>
</html>
14 changes: 14 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<script src="theme.js" defer></script>
<link rel="stylesheet" href="styles.css">
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="/student.png">
<meta name="theme-color" content="#0f1115">

<!-- Performance: Hero image priority for above-the-fold content -->
<link rel="preload" as="image" href="student.png">
Expand Down Expand Up @@ -95,6 +98,17 @@ <h2>Real results.</h2>
<script src="navbar.js" defer></script>
<script src="script.js" defer></script>

<script>
(function () {
if (!('serviceWorker' in navigator)) return;
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').catch(function (err) {
console.warn('LearnSphere: Service Worker registration failed:', err);
});
});
})();
</script>

</body>

</html>
17 changes: 17 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "LearnSphere",
"short_name": "LearnSphere",
"start_url": "/index.html",
"scope": "/",
"display": "standalone",
"background_color": "#0f1115",
"theme_color": "#0f1115",
"icons": [
{
"src": "/student.png",
"sizes": "256x256",
"type": "image/png"
}
]
}

14 changes: 14 additions & 0 deletions my_progress.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
<link rel="stylesheet" href="variables.css">
<script src="theme.js"></script>
<link rel="stylesheet" href="styles.css" />
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#0f1115">
<style>

.progress-wrap {
max-width: 980px;
margin: 24px auto;
Expand Down Expand Up @@ -182,6 +185,17 @@ <h2 style="margin-top:0">Topic-wise performance</h2>
<script src="quizProgress.js"></script>
<script src="my_progress.js"></script>
<script src="navbar.js"></script>

<script>
(function () {
if (!('serviceWorker' in navigator)) return;
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').catch(function (err) {
console.warn('LearnSphere: Service Worker registration failed:', err);
});
});
})();
</script>
</body>

</html>
Expand Down
11 changes: 11 additions & 0 deletions quiz/motionquiz.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ <h2 id="result-title" tabindex="-1">🎉 Quiz Completed!</h2>

<script src="../../quizProgress.js"></script>
<script src="motionquiz.js"></script>

<script>
(function () {
if (!('serviceWorker' in navigator)) return;
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').catch(function (err) {
console.warn('LearnSphere: Service Worker registration failed:', err);
});
});
})();
</script>
</body>

</html>
Expand Down
13 changes: 13 additions & 0 deletions quiz/nlmquiz.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<link rel="stylesheet" href="../variables.css">
<script src="../theme.js"></script>
<link rel="stylesheet" href="nlmquiz.css">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#0f1115">
</head>

<body>
Expand Down Expand Up @@ -64,6 +66,17 @@ <h2 id="result-title" tabindex="-1">🎉 Quiz Completed!</h2>

<script src="../../quizProgress.js"></script>
<script src="nlmquiz.js"></script>

<script>
(function () {
if (!('serviceWorker' in navigator)) return;
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').catch(function (err) {
console.warn('LearnSphere: Service Worker registration failed:', err);
});
});
})();
</script>
</body>

</html>
Expand Down
166 changes: 166 additions & 0 deletions sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* LearnSphere PWA Service Worker
* Offline-first app shell caching + runtime caching for same-origin assets.
*/

const CACHE_VERSION = "v1";
const CACHE_NAME = `learnsphere-static-${CACHE_VERSION}`;

// App shell URLs to cache at install time.
// Keep this list conservative; runtime caching covers the rest.
const APP_SHELL_URLS = [
"/",
"/index.html",
"/home.html",
"/courses.html",
"/explore.html",
"/resources.html",
"/community.html",
"/learner.html",
"/parents.html",
"/teachers.html",
"/my_progress.html",
"/404.html",

// CSS
"/styles.css",
"/variables.css",
"/home.css",

// JS
"/script.js",
"/navbar.js",
"/home.js",
"/theme.js",
"/progress.js",
"/quizProgress.js",

// Images
"/student.png",

// Fontawesome is loaded from CDN; we do not cache it here.
];

// Query parameter helpers
function isSameOrigin(url) {
return url && url.origin === self.location.origin;
}

function requestUrl(event) {
try {
return new URL(event.request.url);
} catch {
return null;
}
}

self.addEventListener("install", (event) => {
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
await cache.addAll(APP_SHELL_URLS);
// Activate SW immediately
self.skipWaiting();
})()
);
});

self.addEventListener("activate", (event) => {
event.waitUntil(
(async () => {
// Remove old caches
const keys = await caches.keys();
await Promise.all(
keys.map((k) => {
if (k !== CACHE_NAME) return caches.delete(k);
})
);
self.clients.claim();
})()
);
});

self.addEventListener("fetch", (event) => {
const req = event.request;
const url = requestUrl(event);

// Only handle GET requests
if (req.method !== "GET") return;

// Navigation: offline fallback to cached app shell.
if (req.mode === "navigate" || (req.destination === "document")) {
event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
try {
const fresh = await fetch(req);
// Optionally cache navigation responses (app shell only)
if (req.url.includes(".html")) {
cache.put(req, fresh.clone()).catch(() => {});
}
return fresh;
} catch {
const cachedIndex = await cache.match("/index.html");
const cached404 = await cache.match("/404.html");
return cachedIndex || cached404;
}
})()
);
return;
}

// Same-origin static assets: stale-while-revalidate
if (url && isSameOrigin(url)) {
const isAsset =
req.destination === "script" ||
req.destination === "style" ||
req.destination === "image" ||
req.destination === "font" ||
req.destination === "worker" ||
req.destination === "document" ||
req.destination === "fetch";

// Also cover common file types even when destination is not set
const pathname = url.pathname;
const fileLike =
pathname.endsWith(".js") ||
pathname.endsWith(".css") ||
pathname.endsWith(".png") ||
pathname.endsWith(".jpg") ||
pathname.endsWith(".jpeg") ||
pathname.endsWith(".gif") ||
pathname.endsWith(".svg") ||
pathname.endsWith(".webp") ||
pathname.endsWith(".woff") ||
pathname.endsWith(".woff2") ||
pathname.endsWith(".ttf") ||
pathname.endsWith(".eot");

if (isAsset || fileLike) {
event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(req);
const fetchPromise = fetch(req)
.then((fresh) => {
cache.put(req, fresh.clone()).catch(() => {});
return fresh;
})
.catch(() => null);

if (cached) {
// Return cached immediately, update in background.
// Note: If fetch fails, cached still serves offline.
return cached;
}

const fresh = await fetchPromise;
return fresh || cached || new Response("", { status: 404, statusText: "Offline" });
})()
);
}
}
});

// Provide install prompt behavior is not handled here (client-side).

Loading