Make this more robust, visually appealing, fast, reliable, effective, efficient, modern, smooth.

Make this single page web app even more robust and better.

Improve and refine this further.

Don't forget to focus on ui improvements too.

Use dexie and save the extracted data only.

The UPDATE and POPULAR css selectors and their urls and their fetched html content structure is different from each other so handle them correctly in any circumstances.

Avoid duplication, conflicts.

Don't compromise.

Use the full potential of dexie.

After the data( not html content ) is stored successfully in the db, For future sessions use only the data from the db as the source if it's not corrupt or incomplete.

Find and all of those edge cases, bottlenecks, memory leaks, resources consumption, errors.

Don't add any service worker. 

Don't add any offline functionality because this web app works online only. 

Remove all of the cache (not db) when user closes the web app. 

Add a search functionality, use fuzy search, remove all of the html contents when the extraction and storing of data is done successfully. 

Review the whole codes and fix all of the possible or potential errors, bugs, edge cases, bottlenecks, can be better, what ifs. 

Add a free image place holder when a image is not loaded. 

Don't retrieve all of the data from the db in one go, retrieve them in batches according to the user's scrolling speed. 

Use only the necessary lightweight libraries only. Make it smooth, fast, efficient. 

Make this single page web app more professional, visually appealing, engaging and pleasing. 

We used indexdb so we don't have to fetch and extract for the same data everytime. 

If you want you can use the necessary libraries to make all of the process as effective, efficient and fast as possible.

Send the fully working full codes in one html file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Light Novels Gallery</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dexie/3.2.4/dexie.min.js"></script>
</head>
<body class="bg-gray-100 min-h-screen">
    <div id="app" class="container mx-auto px-4 py-8">
        <!-- Header -->
        <header class="mb-8">
            <h1 class="text-4xl font-bold text-center text-gray-800 mb-2">Light Novels Gallery</h1>
            <div class="flex justify-center gap-4">
                <button id="refreshBtn" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors">
                    Refresh Data
                </button>
                <button id="clearCacheBtn" class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition-colors">
                    Clear Cache
                </button>
            </div>
        </header>

        <!-- Status Messages -->
        <div id="status" class="mb-8 text-center"></div>

        <!-- Content Sections -->
        <section class="mb-12">
            <h2 class="text-2xl font-semibold mb-6 text-gray-800">New Updates</h2>
            <div id="updates" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div>
        </section>

        <section>
            <h2 class="text-2xl font-semibold mb-6 text-gray-800">Popular Light Novels</h2>
            <div id="popular" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div>
        </section>
    </div>

    <script>
        // Database configuration
        const db = new Dexie('LightNovelsDB');
        db.version(3).stores({
            updates: '++id, title, link, imageUrl, date, downloadLinks, timestamp',
            popular: '++id, title, link, imageUrl, timestamp'
        });

        // Constants
        const PROXY_URL = 'https://api.codetabs.com/v1/proxy?quest=';
        const UPDATES_URL = 'https://jnovels.com/0m-light-novels/?a=light-novels';
        const POPULAR_URL = 'https://jnovels.com/top-light-novels-to-read/';
        const CACHE_DURATION = 1000 * 60 * 30; // 30 minutes

        // UI Elements
        const statusEl = document.getElementById('status');
        const refreshBtn = document.getElementById('refreshBtn');
        const clearCacheBtn = document.getElementById('clearCacheBtn');

        // Utility Functions
        const showStatus = (message, isError = false) => {
            statusEl.textContent = message;
            statusEl.className = `mb-8 text-center p-4 rounded ${isError ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`;
            setTimeout(() => statusEl.textContent = '', 5000);
        };

        const shouldRefreshCache = async (tableName) => {
            const lastItem = await db[tableName].orderBy('timestamp').last();
            return !lastItem || Date.now() - lastItem.timestamp > CACHE_DURATION;
        };

        // Data Fetching and Parsing
        const fetchWithRetry = async (url, retries = 3) => {
            for (let i = 0; i < retries; i++) {
                try {
                    const response = await fetch(PROXY_URL + url);
                    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                    return await response.text();
                } catch (error) {
                    if (i === retries - 1) throw error;
                    await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
                }
            }
        };

        const parseUpdatePost = (element) => {
            try {
                const title = element.querySelector('.post-title a')?.textContent.trim() || '';
                const link = element.querySelector('.post-title a')?.href || '';
                const imageUrl = element.querySelector('.featured-media img')?.src || '';
                const date = element.querySelector('.post-date')?.textContent.trim() || '';
                const downloadLinks = Array.from(element.querySelectorAll('.post-content p a'))
                    .filter(a => /volume|pdf|download/i.test(a.textContent))
                    .map(a => ({ href: a.href, text: a.textContent.trim() }));

                return { title, link, imageUrl, date, downloadLinks, timestamp: Date.now() };
            } catch (error) {
                console.error('Error parsing update post:', error);
                return null;
            }
        };

        const parsePopularNovel = (element) => {
            try {
                return {
                    title: element.textContent.trim(),
                    link: element.href,
                    imageUrl: element.parentElement.nextElementSibling?.querySelector('img')?.src || '',
                    timestamp: Date.now()
                };
            } catch (error) {
                console.error('Error parsing popular novel:', error);
                return null;
            }
        };

        // Data Management
        const fetchAndStoreData = async () => {
            try {
                showStatus('Fetching new data...');

                // Fetch and parse updates
                const updatesHtml = await fetchWithRetry(UPDATES_URL);
                const updatesDoc = new DOMParser().parseFromString(updatesHtml, 'text/html');
                const updates = Array.from(updatesDoc.querySelectorAll('article'))
                    .map(parseUpdatePost)
                    .filter(Boolean);

                // Fetch and parse popular
                const popularHtml = await fetchWithRetry(POPULAR_URL);
                const popularDoc = new DOMParser().parseFromString(popularHtml, 'text/html');
                const popular = Array.from(popularDoc.querySelectorAll('h3 a[href*="jnovels.com"]'))
                    .map(parsePopularNovel)
                    .filter(Boolean);

                // Store in database
                await db.transaction('rw', db.updates, db.popular, async () => {
                    await db.updates.clear();
                    await db.popular.clear();
                    await db.updates.bulkAdd(updates);
                    await db.popular.bulkAdd(popular);
                });

                showStatus('Data updated successfully!');
                return { updates, popular };
            } catch (error) {
                console.error('Error fetching data:', error);
                showStatus('Error fetching data. Using cached data if available.', true);
                throw error;
            }
        };

        // UI Rendering
        const renderNovel = (novel, isUpdate = false) => `
            <div class="bg-white rounded-lg shadow-lg overflow-hidden transform transition-transform hover:-translate-y-1">
                <a href="${novel.link}" target="_blank" rel="noopener noreferrer" class="block">
                    <img src="${novel.imageUrl}" alt="${novel.title}" 
                         class="w-full h-48 object-cover" 
                         onerror="this.src='/api/placeholder/400/320'">
                    <div class="p-4">
                        <h3 class="text-lg font-semibold text-gray-800 mb-2 line-clamp-2">${novel.title}</h3>
                        ${isUpdate ? `
                            <p class="text-sm text-gray-600 mb-2">${novel.date}</p>
                            <div class="flex flex-wrap gap-2">
                                ${novel.downloadLinks.map(link => `
                                    <a href="${link.href}" target="_blank" rel="noopener noreferrer"
                                       class="text-sm bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600 transition-colors">
                                        ${link.text}
                                    </a>
                                `).join('')}
                            </div>
                        ` : ''}
                    </div>
                </a>
            </div>
        `;

        const renderContent = async () => {
            try {
                const [updates, popular] = await Promise.all([
                    db.updates.toArray(),
                    db.popular.toArray()
                ]);

                document.getElementById('updates').innerHTML = updates.map(update => renderNovel(update, true)).join('');
                document.getElementById('popular').innerHTML = popular.map(novel => renderNovel(novel)).join('');
            } catch (error) {
                console.error('Error rendering content:', error);
                showStatus('Error displaying content', true);
            }
        };

        // Event Handlers
        refreshBtn.addEventListener('click', async () => {
            try {
                refreshBtn.disabled = true;
                await fetchAndStoreData();
                await renderContent();
            } finally {
                refreshBtn.disabled = false;
            }
        });

        clearCacheBtn.addEventListener('click', async () => {
            try {
                await db.delete();
                location.reload();
            } catch (error) {
                console.error('Error clearing cache:', error);
                showStatus('Error clearing cache', true);
            }
        });

        // Initialization
        const initialize = async () => {
            try {
                const [needsUpdateRefresh, needsPopularRefresh] = await Promise.all([
                    shouldRefreshCache('updates'),
                    shouldRefreshCache('popular')
                ]);

                if (needsUpdateRefresh || needsPopularRefresh) {
                    await fetchAndStoreData();
                }

                await renderContent();
            } catch (error) {
                console.error('Initialization error:', error);
                showStatus('Error initializing application', true);
            }
        };

        // Start the application
        document.addEventListener('DOMContentLoaded', initialize);
    </script>
</body>
</html>