Skip to content
Open
8 changes: 4 additions & 4 deletions public/diagnostics.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@
class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100">
<i class="fab fa-github"></i> GitHub
</a>
<button id="themeToggle"
<button data-theme-toggle
class="rounded-lg p-2 text-slate-500 hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 transition-colors"
title="Toggle dark/light mode">
<i class="fas fa-moon" id="themeIcon"></i>
<i class="fas fa-moon" data-theme-icon></i>
</button>
</nav>
</div>
Expand Down Expand Up @@ -168,8 +168,8 @@ <h1 class="text-3xl font-bold text-slate-900 dark:text-white mb-2 flex items-cen
buildList();

// Theme toggle
const themeToggle = document.getElementById('themeToggle');
const themeIcon = document.getElementById('themeIcon');
const themeToggle = document.querySelector('[data-theme-toggle]');
const themeIcon = document.querySelector('[data-theme-icon]');
themeToggle.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
themeIcon.className = isDark ? 'fas fa-sun' : 'fas fa-moon';
Expand Down
64 changes: 4 additions & 60 deletions public/how-it-works.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,6 @@
<title>How It Works — BLT-Leaf</title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">

<script>
window.tailwind = {
config: {
darkMode: 'class'
}
};

(function () {
const savedTheme = localStorage.getItem('theme');
const isDark = savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (isDark) {
document.documentElement.classList.add('dark');
}
})();
</script>
<script src="error-reporter.js"></script>

<script src="https://cdn.tailwindcss.com"></script>
Expand Down Expand Up @@ -94,10 +79,10 @@
class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100">
<i class="fab fa-github"></i> GitHub
</a>
<button id="themeToggle"
<button data-theme-toggle
class="rounded-lg p-2 text-slate-500 hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 transition-colors"
title="Toggle dark/light mode">
<i class="fas fa-moon" id="themeIcon"></i>
<i class="fas fa-moon" data-theme-icon></i>
</button>
</nav>
</div>
Expand Down Expand Up @@ -1098,50 +1083,9 @@ <h2 class="text-2xl font-semibold text-slate-900 dark:text-white mb-4">
</div>
</footer>

<!-- Theme Toggle JavaScript -->
<script>
// Initialize theme on page load
function initTheme() {
const savedTheme = localStorage.getItem('theme');
const isDark = savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches);
const themeIcon = document.getElementById('themeIcon');

if (isDark) {
document.documentElement.classList.add('dark');
if (themeIcon) themeIcon.className = 'fas fa-sun';
} else {
document.documentElement.classList.remove('dark');
if (themeIcon) themeIcon.className = 'fas fa-moon';
}
}

// Toggle theme
function toggleTheme() {
const isDark = document.documentElement.classList.contains('dark');
const themeIcon = document.getElementById('themeIcon');

if (isDark) {
document.documentElement.classList.remove('dark');
if (themeIcon) themeIcon.className = 'fas fa-moon';
localStorage.setItem('theme', 'light');
} else {
document.documentElement.classList.add('dark');
if (themeIcon) themeIcon.className = 'fas fa-sun';
localStorage.setItem('theme', 'dark');
}
}
<!-- Theme behavior provided by shared script -->

// Attach event listener when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
// Initialize theme icon (dark class already set in head)
initTheme();

const themeToggleBtn = document.getElementById('themeToggle');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', toggleTheme);
}
});
</script>
<script src="theme.js"></script>

<!-- JavaScript for Interactive Calculator -->
<script src="how-it-works.js"></script>
Expand Down
85 changes: 21 additions & 64 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,6 @@
<link rel="preconnect" href="https://cdn.tailwindcss.com">
<link rel="preconnect" href="https://cdnjs.cloudflare.com">
<script src="https://cdn.tailwindcss.com"></script>
<script>
window.tailwind = {
config: {
darkMode: 'class'
}
};

// Initialize theme immediately to prevent FOUC
(function () {
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = savedTheme || (prefersDark ? 'dark' : 'light');

if (theme === 'dark') {
document.documentElement.classList.add('dark');
}
})();
</script>
<script src="error-reporter.js"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
Expand Down Expand Up @@ -328,9 +310,9 @@ <h1 class="text-xs font-bold text-slate-900 dark:text-white">BLT-LEAF</h1>
title="Toggle auto-refresh">
<i class="fas fa-sync-alt text-xs" id="autoRefreshIcon"></i>
</button>
<button id="themeToggle"
<button data-theme-toggle
class="rounded-lg p-1.5 text-slate-500 hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700">
<i class="fas fa-moon text-xs" id="themeIcon"></i>
<i class="fas fa-moon text-xs" data-theme-icon></i>
</button>
<a href="/how-it-works.html"
class="inline-flex items-center rounded-lg p-1.5 text-slate-700 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700"
Expand Down Expand Up @@ -425,9 +407,9 @@ <h1 class="text-sm font-bold text-slate-900 dark:text-white sm:text-base">BLT-LE
title="Toggle auto-refresh">
<i class="fas fa-sync-alt" id="autoRefreshIcon"></i>
</button>
<button id="themeToggle"
<button data-theme-toggle
class="rounded-lg p-2 text-slate-500 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-200 dark:text-slate-400 dark:hover:bg-slate-700 dark:focus:ring-slate-600">
<i class="fas fa-moon" id="themeIcon"></i>
<i class="fas fa-moon" data-theme-icon></i>
</button>
<a href="/how-it-works.html"
class="inline-flex items-center gap-2 rounded-lg px-2 sm:px-3 py-2 text-sm font-medium text-slate-700 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700 transition-colors"
Expand Down Expand Up @@ -3142,10 +3124,7 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${emptyTitl
}
}

// Theme toggle - handle both mobile and desktop buttons
document.querySelectorAll('#themeToggle').forEach(btn => {
btn.addEventListener('click', toggleTheme);
});
// Theme handled by shared theme.js

// Auto-refresh toggle - handle both mobile and desktop buttons
document.querySelectorAll('#autoRefreshToggle').forEach(btn => {
Expand Down Expand Up @@ -3248,41 +3227,7 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${emptyTitl
});
}

// Theme toggle functionality
function initTheme() {
// Theme class is already set in the head, just update the icon
const isDark = document.documentElement.classList.contains('dark');
const themeIcons = document.querySelectorAll('#themeIcon');

if (isDark) {
themeIcons.forEach(icon => {
icon.className = 'fas fa-sun' + (icon.classList.contains('text-xs') ? ' text-xs' : '');
});
} else {
themeIcons.forEach(icon => {
icon.className = 'fas fa-moon' + (icon.classList.contains('text-xs') ? ' text-xs' : '');
});
}
}

function toggleTheme() {
const isDark = document.documentElement.classList.contains('dark');
const themeIcons = document.querySelectorAll('#themeIcon');

if (isDark) {
document.documentElement.classList.remove('dark');
themeIcons.forEach(icon => {
icon.className = 'fas fa-moon' + (icon.classList.contains('text-xs') ? ' text-xs' : '');
});
localStorage.setItem('theme', 'light');
} else {
document.documentElement.classList.add('dark');
themeIcons.forEach(icon => {
icon.className = 'fas fa-sun' + (icon.classList.contains('text-xs') ? ' text-xs' : '');
});
localStorage.setItem('theme', 'dark');
}
}

// Raw data modal event listeners
document.getElementById('closeRawDataModal').addEventListener('click', hideRawDataModal);
Expand Down Expand Up @@ -3543,7 +3488,6 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${emptyTitl
}
}

initTheme();
loadRateLimit();
loadLatestRelease();
// initOrgFilter sets up event listeners only; dropdown items are populated from `repos`
Expand All @@ -3564,9 +3508,21 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${emptyTitl

// Service Worker Registration
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW Registered:', reg.scope))
.catch(err => console.warn('SW Registration failed:', err));
const isLocalDev = window.location.hostname === '127.0.0.1' || window.location.hostname === 'localhost';
if (isLocalDev) {
navigator.serviceWorker.getRegistrations()
.then(registrations => Promise.all(registrations.map(reg => reg.unregister())))
.catch(err => console.warn('SW unregister failed:', err));
if ('caches' in window) {
caches.keys()
.then(keys => Promise.all(keys.map(key => caches.delete(key))))
.catch(err => console.warn('Cache clear failed:', err));
}
} else {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW Registered:', reg.scope))
.catch(err => console.warn('SW Registration failed:', err));
}
}
</script>

Expand Down Expand Up @@ -3631,6 +3587,7 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${emptyTitl
}
})();
</script>
<script src="theme.js"></script>
</body>

</html>
27 changes: 27 additions & 0 deletions public/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const CACHE_VERSION = 'blt-leaf-v2';
const STATIC_CACHE = `${CACHE_VERSION}-static`;
const API_CACHE = `${CACHE_VERSION}-api`;
const CDN_CACHE = `${CACHE_VERSION}-cdn`;
const IS_LOCAL_DEV = self.location.hostname === '127.0.0.1' || self.location.hostname === 'localhost';

// Static assets to precache on install
const PRECACHE_URLS = [
Expand Down Expand Up @@ -33,6 +34,10 @@ const API_CACHE_MAX_AGE = 5 * 60 * 1000;

// Install
self.addEventListener('install', (event) => {
if (IS_LOCAL_DEV) {
event.waitUntil(self.skipWaiting());
return;
}
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => cache.addAll(PRECACHE_URLS))
Expand All @@ -46,6 +51,14 @@ self.addEventListener('install', (event) => {

// Activate
self.addEventListener('activate', (event) => {
if (IS_LOCAL_DEV) {
event.waitUntil(
caches.keys()
.then((keys) => Promise.all(keys.map((key) => caches.delete(key))))
.then(() => self.clients.claim())
);
return;
}
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
Expand All @@ -64,6 +77,20 @@ self.addEventListener('fetch', (event) => {
// Only handle GET requests
if (request.method !== 'GET') return;

// In local dev, bypass SW cache for static assets to avoid stale JS/CSS/HTML/sw.js.
if (IS_LOCAL_DEV &&
url.origin === self.location.origin &&
(
url.pathname === '/' ||
url.pathname === '/sw.js' ||
url.pathname.endsWith('.js') ||
url.pathname.endsWith('.css') ||
url.pathname.endsWith('.html')
)) {
event.respondWith(fetch(request, { cache: 'no-store' }));
return;
}

// CDN resources
if (CDN_ORIGINS.some((origin) => request.url.startsWith(origin))) {
event.respondWith(cacheFirst(request, CDN_CACHE));
Expand Down
8 changes: 4 additions & 4 deletions public/test-error.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@
class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100">
<i class="fab fa-github"></i> GitHub
</a>
<button id="themeToggle"
<button data-theme-toggle
class="rounded-lg p-2 text-slate-500 hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 transition-colors"
title="Toggle dark/light mode">
<i class="fas fa-moon" id="themeIcon"></i>
<i class="fas fa-moon" data-theme-icon></i>
</button>
</nav>
</div>
Expand Down Expand Up @@ -222,8 +222,8 @@ <h2 class="text-lg font-semibold text-slate-900 dark:text-white mb-1">Frontend J

// ── Theme toggle ──────────────────────────────────────────────────────────

const themeToggle = document.getElementById('themeToggle');
const themeIcon = document.getElementById('themeIcon');
const themeToggle = document.querySelector('[data-theme-toggle]');
const themeIcon = document.querySelector('[data-theme-icon]');
themeToggle.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
themeIcon.className = isDark ? 'fas fa-sun' : 'fas fa-moon';
Expand Down
50 changes: 50 additions & 0 deletions public/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Shared theme utilities for BLT-Leaf
// - sets Tailwind config for dark mode
// - applies the saved or preferred theme early to avoid FOUC
// - provides `toggleTheme()` and initializes theme icons on DOM ready

window.tailwind = window.tailwind || {};
window.tailwind.config = window.tailwind.config || {};
window.tailwind.config.darkMode = 'class';

(function applyInitialTheme() {
try {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} catch (e) {
document.documentElement.classList.remove('dark');
}
})();

function updateThemeIcons() {
const icons = document.querySelectorAll('[data-theme-icon]');
icons.forEach(icon => {
const isTextXs = icon.classList.contains('text-xs');
const base = document.documentElement.classList.contains('dark') ? 'fas fa-sun' : 'fas fa-moon';
icon.className = base + (isTextXs ? ' text-xs' : '');
});
}

function toggleTheme() {
const isDark = document.documentElement.classList.contains('dark');
if (isDark) {
document.documentElement.classList.remove('dark');
try { localStorage.setItem('theme', 'light'); } catch (e) {}
} else {
document.documentElement.classList.add('dark');
try { localStorage.setItem('theme', 'dark'); } catch (e) {}
}
updateThemeIcons();
}

updateThemeIcons();

document.querySelectorAll('[data-theme-toggle]')
.forEach(btn => btn.addEventListener('click', toggleTheme));

// Export for other scripts
window.toggleTheme = toggleTheme;
Loading