Don't use proxy. Send the fully working full codes. --- <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Light Novel Library</title>

  <!-- Preconnect for performance -->
  <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  
  <!-- Load Fonts with swap to ensure text visibility -->
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">

  <!-- External Libraries -->
  <script src="https://code.jquery.com/jquery-3.6.4.min.js" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/dexie@3.2.5/dist/dexie.min.js" ></script>
  <script src="https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.js" ></script>
  
  <!-- AOS CSS -->
  <link href="https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.css" rel="stylesheet">

  <!-- Stylesheet -->
  <style>
    /* Root Variables */
    :root {
      --primary-color: #ffffff;
      --secondary-color: #000000;
      --background-color: #121212;
      --card-background: #1e1e1e;
      --text-primary: #ffffff;
      --text-secondary: #bbbbbb;
      --border-color: #333333;
      --shadow-sm: 0 1px 3px rgba(255,255,255,0.1);
      --shadow-md: 0 4px 6px rgba(255,255,255,0.1);
      --shadow-lg: 0 10px 15px rgba(255,255,255,0.1);
      --radius-sm: 0.375rem;
      --radius-md: 0.5rem;
      --radius-lg: 1rem;
      --transition-duration: 0.3s;
      --max-width: 1280px;
      --header-height: 80px;
      --toast-background: rgba(0, 0, 0, 0.8);
      --toast-color: #ffffff;
      --font-family: 'Inter', sans-serif;
    }

    /* Light Theme Variables */
    [data-theme="light"] {
      --primary-color: #000000;
      --secondary-color: #ffffff;
      --background-color: #f0f4f8;
      --card-background: #ffffff;
      --text-primary: #1f2937;
      --text-secondary: #6b7280;
      --border-color: #dddddd;
      --shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
      --shadow-md: 0 4px 6px rgba(0,0,0,0.1);
      --shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
      --toast-background: rgba(255, 255, 255, 0.9);
      --toast-color: #000000;
    }

    /* Reset and Base Styles */
    *, ::before, ::after {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      font-family: var(--font-family);
      background-color: var(--background-color);
      color: var(--text-primary);
      line-height: 1.6;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      transition: background-color var(--transition-duration) ease, color var(--transition-duration) ease;
    }

    /* Header Styles */
    .header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 1rem 2rem;
      background-color: var(--card-background);
      box-shadow: var(--shadow-sm);
      border-radius: var(--radius-md);
      position: sticky;
      top: 0;
      z-index: 1000;
      transition: background-color var(--transition-duration) ease;
    }

    .header__title {
      font-size: 1.75rem;
      font-weight: 700;
      color: var(--text-primary);
    }

    /* Toggle Switch Styles */
    .toggle-switch {
      position: relative;
      display: inline-block;
      width: 60px;
      height: 34px;
    }

    .toggle-switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }

    .slider {
      position: absolute;
      cursor: pointer;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: var(--border-color);
      transition: background-color var(--transition-duration) ease;
      border-radius: 34px;
    }

    .slider::before {
      position: absolute;
      content: "";
      height: 26px;
      width: 26px;
      left: 4px;
      bottom: 4px;
      background-color: var(--secondary-color);
      transition: transform var(--transition-duration) ease, background-color var(--transition-duration) ease;
      border-radius: 50%;
    }

    input:checked + .slider {
      background-color: var(--primary-color);
    }

    input:checked + .slider::before {
      transform: translateX(26px);
      background-color: var(--secondary-color);
    }

    /* Main Content Styles */
    .main {
      flex: 1;
      max-width: var(--max-width);
      margin: 2rem auto;
      padding: 0 1.5rem;
      width: 100%;
    }

    /* Search and Refresh Styles */
    .search-container {
      display: flex;
      flex-wrap: wrap;
      gap: 1rem;
      align-items: center;
      margin-bottom: 2rem;
    }

    .search-input {
      flex: 1;
      min-width: 250px;
      padding: 0.75rem 1.5rem;
      font-size: 1rem;
      border: 2px solid var(--border-color);
      border-radius: var(--radius-md);
      outline: none;
      transition: border-color var(--transition-duration) ease, background-color var(--transition-duration) ease, color var(--transition-duration) ease;
      background-color: var(--card-background);
      color: var(--text-primary);
    }

    .search-input::placeholder {
      color: var(--text-secondary);
    }

    .search-input:focus {
      border-color: var(--primary-color);
    }

    .refresh-button {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      padding: 0.75rem 1.5rem;
      font-size: 1rem;
      border: 2px solid var(--border-color);
      border-radius: var(--radius-md);
      background-color: var(--secondary-color);
      color: var(--primary-color);
      cursor: pointer;
      transition: background-color var(--transition-duration) ease, color var(--transition-duration) ease, border-color var(--transition-duration) ease, transform 0.2s ease;
      min-width: 150px;
    }

    .refresh-button:hover {
      background-color: var(--primary-color);
      color: var(--secondary-color);
      border-color: var(--primary-color);
      transform: translateY(-2px);
    }

    .refresh-button:active {
      transform: translateY(0);
    }

    .refresh-button svg {
      width: 20px;
      height: 20px;
      fill: currentColor;
      transition: transform 0.3s ease;
    }

    .refresh-button:hover svg {
      transform: rotate(360deg);
    }

    .refresh-button:disabled {
      opacity: 0.6;
      cursor: not-allowed;
      transform: none;
    }

    /* Grid Styles */
    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
      gap: 2rem;
    }

    /* Card Styles */
    .card {
      background: var(--card-background);
      border: 1px solid var(--border-color);
      border-radius: var(--radius-md);
      overflow: hidden;
      box-shadow: var(--shadow-sm);
      transition: transform var(--transition-duration) ease, box-shadow var(--transition-duration) ease, border-color var(--transition-duration) ease;
      cursor: pointer;
      display: flex;
      flex-direction: column;
      height: 100%;
      opacity: 0;
      transform: translateY(20px);
      animation: fadeInCard 0.5s forwards;
    }

    .card:hover {
      transform: translateY(-5px);
      box-shadow: var(--shadow-lg);
      border-color: var(--primary-color);
    }

    @keyframes fadeInCard {
      to { opacity: 1; transform: translateY(0); }
    }

    .card__image-container {
      position: relative;
      width: 100%;
      padding-top: 140%;
      background: #2a2a2a;
      overflow: hidden;
    }

    .card__image {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
      transition: transform 0.3s ease;
    }

    .card:hover .card__image {
      transform: scale(1.05);
    }

    .card__content {
      padding: 1rem;
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      justify-content: center;
    }

    .card__title {
      font-size: 1.1rem;
      font-weight: 600;
      color: var(--text-primary);
      text-align: center;
      overflow: hidden;
      text-overflow: ellipsis;
      display: -webkit-box;
      -webkit-line-clamp: 3;
      -webkit-box-orient: vertical;
      transition: color var(--transition-duration) ease;
    }

    .card:hover .card__title {
      color: var(--secondary-color);
    }

    /* Loader Styles */
    .loader {
      display: none;
      justify-content: center;
      align-items: center;
      height: 200px;
      color: var(--text-secondary);
    }

    .loader.visible {
      display: flex;
    }

    .loader__spinner {
      width: 50px;
      height: 50px;
      border: 5px solid #444444;
      border-top: 5px solid var(--primary-color);
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }

    /* Spinner Animation */
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }

    /* Error Message Styles */
    .error-message {
      text-align: center;
      color: #ff8080;
      padding: 1rem;
      background: var(--border-color);
      border-radius: var(--radius-sm);
      margin: 1rem 0;
      opacity: 1;
      transition: opacity 0.5s ease-out;
    }

    .error-message.fade-out {
      opacity: 0;
    }

    /* Modal Styles */
    .modal {
      display: none;
      position: fixed;
      z-index: 2000;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      overflow: auto;
      background-color: rgba(0,0,0,0.85);
      justify-content: center;
      align-items: center;
      padding: 1rem;
      transition: opacity 0.3s ease;
    }

    .modal.show {
      display: flex;
      opacity: 1;
    }

    .modal-content {
      background-color: var(--card-background);
      padding: 2rem;
      border-radius: var(--radius-lg);
      max-width: 700px;
      width: 100%;
      position: relative;
      box-shadow: var(--shadow-lg);
      animation: fadeInModal 0.3s ease;
      color: var(--text-primary);
    }

    @keyframes fadeInModal {
      from { opacity: 0; transform: translateY(-20px); }
      to { opacity: 1; transform: translateY(0); }
    }

    .close {
      position: absolute;
      top: 1rem;
      right: 1.5rem;
      color: var(--text-secondary);
      font-size: 2rem;
      font-weight: bold;
      cursor: pointer;
      transition: color var(--transition-duration) ease;
      background: none;
      border: none;
      font-family: inherit;
    }

    .close:hover,
    .close:focus {
      color: var(--primary-color);
      outline: none;
    }

    .modal__title {
      font-size: 1.75rem;
      font-weight: 700;
      margin-bottom: 1rem;
      color: var(--text-primary);
      text-align: center;
    }

    .modal__link {
      display: inline-block;
      padding: 0.75rem 1.5rem;
      background-color: var(--primary-color);
      color: var(--secondary-color);
      text-decoration: none;
      border-radius: var(--radius-md);
      font-weight: 600;
      transition: background-color var(--transition-duration) ease, color var(--transition-duration) ease;
      text-align: center;
      width: 100%;
    }

    .modal__link:hover {
      background-color: var(--secondary-color);
      color: var(--primary-color);
    }

    /* Sentinel Element for Infinite Scroll */
    .sentinel {
      height: 1px;
    }

    /* Progress Bar Styles */
    .progress-container {
      width: 100%;
      background-color: #333333;
      border-radius: var(--radius-md);
      margin: 1rem 0;
      display: none;
    }

    .progress-bar {
      height: 10px;
      width: 0%;
      background-color: var(--primary-color);
      border-radius: var(--radius-md);
      transition: width 0.3s ease;
    }

    .progress-text {
      text-align: center;
      margin-top: 0.5rem;
      font-size: 0.9rem;
      color: var(--text-primary);
      display: none;
    }

    /* Toast Styles */
    .toast-container {
      position: fixed;
      bottom: 20px;
      right: 20px;
      z-index: 3000;
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
    }

    .toast {
      background-color: var(--toast-background);
      color: var(--toast-color);
      padding: 1rem 1.5rem;
      border-radius: var(--radius-sm);
      box-shadow: var(--shadow-lg);
      display: flex;
      align-items: center;
      gap: 0.5rem;
      animation: slideIn 0.5s forwards;
      opacity: 0;
      transition: opacity 0.3s ease;
    }

    .toast.show {
      opacity: 1;
    }

    @keyframes slideIn {
      from {
        transform: translateX(100%);
        opacity: 0;
      }
      to {
        transform: translateX(0);
        opacity: 1;
      }
    }

    .toast.success {
      background-color: #28a745;
      color: #ffffff;
    }

    .toast.error {
      background-color: #dc3545;
      color: #ffffff;
    }

    .toast svg {
      width: 20px;
      height: 20px;
      fill: currentColor;
    }

    /* Responsive Styles */
    @media (max-width: 1024px) {
      .header__title {
        font-size: 1.5rem;
      }
    }

    @media (max-width: 768px) {
      .grid {
        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
        gap: 1.5rem;
      }

      .card__title {
        font-size: 1rem;
      }

      .refresh-button {
        width: 100%;
        justify-content: center;
      }
    }

    @media (max-width: 480px) {
      .grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 1rem;
      }

      .header {
        flex-direction: column;
        align-items: flex-start;
      }

      .toggle-switch {
        margin-top: 1rem;
      }
    }

    /* Menu Button and Menu Styles */
    .menu-button {
      position: fixed;
      top: 1rem;
      left: 1rem;
      z-index: 1001;
      background-color: var(--card-background);
      color: var(--text-primary);
      border: none;
      padding: 0.75rem;
      border-radius: var(--radius-sm);
      cursor: pointer;
      transition: background-color var(--transition-duration) ease, color var(--transition-duration) ease;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .menu-button:hover {
      background-color: var(--primary-color);
      color: var(--secondary-color);
    }

    .menu-button svg {
      width: 24px;
      height: 24px;
      fill: currentColor;
    }

    .menu {
      position: fixed;
      top: 0;
      left: 0;
      width: 50%;
      height: 100%;
      background-color: var(--card-background);
      box-shadow: var(--shadow-lg);
      transform: translateX(-100%);
      transition: transform var(--transition-duration) ease;
      z-index: 1000;
      display: flex;
      flex-direction: column;
      padding: 2rem;
    }

    .menu.open {
      transform: translateX(0);
    }

    .menu__item {
      padding: 1rem 0;
      color: var(--text-primary);
      text-decoration: none;
      font-size: 1.2rem;
      transition: color var(--transition-duration) ease;
    }

    .menu__item:hover {
      color: var(--primary-color);
    }

    /* Settings Panel Styles */
    .settings-panel {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.85);
      justify-content: center;
      align-items: center;
      z-index: 1001;
      opacity: 0;
      transition: opacity 0.3s ease;
    }

    .settings-panel.open {
      display: flex;
      opacity: 1;
    }

    .settings-panel__content {
      background-color: var(--card-background);
      padding: 2rem;
      border-radius: var(--radius-lg);
      max-width: 500px;
      width: 100%;
      position: relative;
      box-shadow: var(--shadow-lg);
      animation: fadeInModal 0.3s ease;
      color: var(--text-primary);
    }

    .settings-panel__close {
      position: absolute;
      top: 1rem;
      right: 1.5rem;
      color: var(--text-secondary);
      font-size: 2rem;
      font-weight: bold;
      cursor: pointer;
      transition: color var(--transition-duration) ease;
      background: none;
      border: none;
      font-family: inherit;
    }

    .settings-panel__close:hover,
    .settings-panel__close:focus {
      color: var(--primary-color);
      outline: none;
    }

    .settings-panel__title {
      font-size: 1.75rem;
      font-weight: 700;
      margin-bottom: 1rem;
      color: var(--text-primary);
      text-align: center;
    }

    .settings-panel__toggle {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 1rem;
    }

    .settings-panel__toggle label {
      font-size: 1.2rem;
      color: var(--text-primary);
    }

    .settings-panel__refresh {
      display: flex;
      justify-content: center;
      margin-top: 1rem;
    }
  </style>
</head>
<body>
  <!-- Header with Title -->
  <header class="header">
    <div class="header__title">Light Novel Library Collection</div>
  </header>

  <!-- Menu Button -->
  <button class="menu-button" id="menu-button" aria-label="Open Menu">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
      <path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
    </svg>
  </button>

  <!-- Menu -->
  <nav class="menu" id="menu">
    <a href="#" class="menu__item" data-target="home">Home</a>
    <a href="#" class="menu__item" data-target="ln-pdfs">LN PDFs</a>
    <a href="#" class="menu__item" data-target="ln-epubs">LN EPUBs</a>
    <a href="#" class="menu__item" data-target="release-dates">Release Dates</a>
    <a href="#" class="menu__item" data-target="webnovels">Web Novels</a>
    <a href="#" class="menu__item" data-target="manga">Manga</a>
    <a href="#" class="menu__item" data-target="settings">Settings</a>
  </nav>

  <!-- Settings Panel -->
  <div class="settings-panel" id="settings-panel">
    <div class="settings-panel__content">
      <button class="settings-panel__close" aria-label="Close Settings">&times;</button>
      <h2 class="settings-panel__title">Settings</h2>
      <div class="settings-panel__toggle">
        <label for="theme-toggle">Dark Mode</label>
        <label class="toggle-switch">
          <input type="checkbox" id="theme-toggle" aria-label="Toggle Dark Mode">
          <span class="slider"></span>
        </label>
      </div>
      <div class="settings-panel__refresh">
        <button id="refresh-button" class="refresh-button" aria-label="Refresh Data">
          <!-- Refresh Icon SVG -->
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
            <path d="M17.65 6.35a7.95 7.95 0 00-11.3 0l-1.41-1.42a10 10 0 0114.14 0c.39.39.39 1.02 0 1.41l-1.42 1.42zM6.35 17.65a7.95 7.95 0 0011.3 0l1.41 1.42a10 10 0 01-14.14 0c-.39-.39-.39-1.02 0-1.41l1.42-1.42z"/>
          </svg>
          Refresh Data
        </button>
      </div>
    </div>
  </div>

  <!-- Main Content -->
  <main class="main" role="main">
    <!-- Search Bar -->
    <div class="search-container">
      <input type="text" id="search-input" class="search-input" placeholder="Search light novels..." aria-label="Search light novels">
    </div>

    <!-- Progress Bar -->
    <div class="progress-container" id="progress-container">
      <div class="progress-bar" id="progress-bar"></div>
    </div>
    <div class="progress-text" id="progress-text">0%</div>

    <!-- Content Grid -->
    <div id="content" class="grid" aria-live="polite">
      <!-- Initial Loader -->
      <div class="loader" id="loader" role="status" aria-label="Loading novels">
        <div class="loader__spinner"></div>
      </div>
    </div>

    <!-- Sentinel Element for Infinite Scroll -->
    <div class="sentinel" id="sentinel"></div>
  </main>

  <!-- Modal -->
  <div id="modal" class="modal" aria-hidden="true" role="dialog" aria-labelledby="modal-title">
    <div class="modal-content">
      <button class="close" aria-label="Close modal">&times;</button>
      <h2 id="modal-title" class="modal__title"></h2>
      <a id="modal-link" class="modal__link" href="#" target="_blank" rel="noopener noreferrer">Read PDF</a>
    </div>
  </div>

  <!-- Toast Notifications -->
  <div class="toast-container" id="toast-container"></div>

  <!-- JavaScript -->
  <script>
    'use strict';
    (function($) {
      /**
       * Initialize Dexie Database with Unique Constraints on name, coverUrl, and pdfUrl
       */
      const db = new Dexie('MonochromeNovelsDB');
      db.version(31).stores({
        novels: '++id, &name, &coverUrl, &pdfUrl, timestamp', // Unique indexes on name, coverUrl, pdfUrl
        settings: '++id, &key, value' // Store settings like theme
      });

      // Constants
      const PAGE_SIZE = 20; // Number of novels to load per page
      let currentPage = 0; // Current page index
      let isLoading = false; // Flag to prevent multiple simultaneous loads
      let hasMore = true; // Flag to determine if more novels are available
      let currentSearchTerm = ''; // Current search term
      let totalNovels = 0; // Total number of novels to be fetched
      let loadedNovels = 0; // Number of novels loaded so far

      const PLACEHOLDER_IMAGE = 'https://via.placeholder.com/500x700?text=No+Image'; // Placeholder for missing images

      /**
       * Debounce Function to Limit Function Execution Rate
       * @param {Function} func
       * @param {number} delay
       * @returns {Function}
       */
      const debounce = (func, delay) => {
        let debounceTimer;
        return function(...args) {
          const context = this;
          clearTimeout(debounceTimer);
          debounceTimer = setTimeout(() => func.apply(context, args), delay);
        }
      };

      /**
       * Utility: Delay Function
       * @param {number} ms
       * @returns {Promise}
       */
      const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

      /**
       * Utility: Retry Operation with Exponential Backoff (3 Attempts)
       * @param {Function} operation
       * @param {number} attempts
       * @param {number} delayDuration
       * @returns {Promise}
       */
      const retryOperation = async (operation, attempts = 3, delayDuration = 1500) => {
        for (let i = 0; i < attempts; i++) {
          try {
            return await operation();
          } catch (error) {
            if (i === attempts - 1) throw error;
            await delay(delayDuration * Math.pow(2, i)); // Exponential backoff
          }
        }
      };

      /**
       * Initialize Intersection Observer for Infinite Scroll using Sentinel Element
       */
      const setupSentinelObserver = () => {
        const sentinel = $('#sentinel').get(0);
        if (!('IntersectionObserver' in window)) {
          // Fallback if IntersectionObserver is not supported
          $(window).on('scroll', () => {
            if ($(window).scrollTop() + $(window).height() >= $(document).height() - 100 && hasMore && !isLoading) {
              loadNextPage();
            }
          });
          return;
        }

        const sentinelObserver = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            if (entry.isIntersecting && hasMore && !isLoading) {
              loadNextPage();
            }
          });
        }, {
          rootMargin: '100px',
          threshold: 0
        });
        sentinelObserver.observe(sentinel);
      };

      /**
       * Load Next Page of Novels from IndexedDB
       */
      const loadNextPage = async () => {
        if (isLoading) return; // Prevent multiple simultaneous loads
        isLoading = true;
        showLoader();

        try {
          let novels;

          if (currentSearchTerm === '') {
            novels = await db.novels
              .orderBy('name') // Order by name for alphabetical display
              .offset(currentPage * PAGE_SIZE)
              .limit(PAGE_SIZE)
              .toArray();
          } else {
            novels = await db.novels
              .where('name')
              .startsWithIgnoreCase(currentSearchTerm)
              .offset(currentPage * PAGE_SIZE)
              .limit(PAGE_SIZE)
              .toArray();
          }

          if (novels.length < PAGE_SIZE) {
            hasMore = false; // No more novels to load
          }

          if (novels.length > 0) {
            appendNovelsToDisplay(novels);
            currentPage++;
          } else {
            if (currentPage === 0) {
              showTemporaryError('No novels found for the given search term.', 5000);
            }
          }
        } catch (error) {
          console.error('Error loading novels:', error);
          showTemporaryError('An error occurred while loading novels.', 5000);
        } finally {
          hideLoader();
          isLoading = false;
        }
      };

      /**
       * Show Loader
       */
      const showLoader = () => {
        $('#loader').addClass('visible');
      };

      /**
       * Hide Loader
       */
      const hideLoader = () => {
        $('#loader').removeClass('visible');
      };

      /**
       * Display Error Message Temporarily
       * @param {string} message
       * @param {number} duration - Duration in milliseconds
       */
      const showTemporaryError = (message, duration = 5000) => {
        const $content = $('#content');
        // Remove existing error messages
        $content.find('.error-message').remove();

        const $errorDiv = $('<div>', {
          class: 'error-message',
          role: 'alert',
          text: message
        });

        $content.prepend($errorDiv);

        // Fade out after specified duration
        setTimeout(() => {
          $errorDiv.addClass('fade-out');
          // Remove from DOM after fade-out transition
          setTimeout(() => {
            $errorDiv.remove();
          }, 500);
        }, duration);
      };

      /**
       * Append Novels to Display
       * @param {Array} novels
       */
      const appendNovelsToDisplay = novels => {
        const $content = $('#content');
        const $fragment = $(document.createDocumentFragment());

        novels.forEach(novel => {
          const $card = createNovelCard(novel);
          $fragment.append($card);
        });

        $content.append($fragment);
        AOS.refresh(); // Refresh AOS to apply animations to new elements
      };

      /**
       * Create Novel Card Element using jQuery
       * @param {Object} novel
       * @returns {jQuery}
       */
      const createNovelCard = (novel) => {
        const $card = $('<div>', {
          class: 'card',
          tabindex: '0',
          role: 'button',
          'aria-labelledby': `novel-title-${encodeURIComponent(novel.name)}`,
          'data-aos': 'fade-up'
        });

        const $imageContainer = $('<div>', { class: 'card__image-container' });
        const $image = $('<img>', {
          class: 'card__image',
          src: novel.coverUrl || PLACEHOLDER_IMAGE,
          alt: `Cover of ${novel.name}`,
          loading: 'lazy'
        }).on('error', function() {
          $(this).attr('src', PLACEHOLDER_IMAGE);
        });

        $imageContainer.append($image);
        $card.append($imageContainer);

        const $content = $('<div>', { class: 'card__content' });
        const $title = $('<h2>', {
          id: `novel-title-${encodeURIComponent(novel.name)}`,
          class: 'card__title',
          html: novel.name
        });

        $content.append($title);
        $card.append($content);

        // Event Listener to Open Modal
        $card.on('click keypress', (e) => {
          if (e.type === 'click' || (e.type === 'keypress' && (e.key === 'Enter' || e.key === ' '))) {
            e.preventDefault();
            showModal(novel);
          }
        });

        return $card;
      };

      /**
       * Utility: Escape HTML to Prevent XSS
       * @param {string} str
       * @returns {string}
       */
      const escapeHTML = (str) => {
        if (!str) return '';
        return str.replace(/&/g, "&amp;")
                  .replace(/</g, "&lt;")
                  .replace(/>/g, "&gt;")
                  .replace(/"/g, "&quot;")
                  .replace(/'/g, "&#039;");
      };

      /**
       * Modal Functionality
       */
      const $modal = $('#modal');
      const $modalTitle = $('#modal-title');
      const $modalLink = $('#modal-link');
      const $closeBtn = $('.close');

      /**
       * Show Modal with Novel Details
       * @param {Object} novel
       */
      const showModal = (novel) => {
        $modalTitle.text(novel.name);
        $modalLink.attr('href', novel.pdfUrl);
        $modal.addClass('show').attr('aria-hidden', 'false');
        $('body').css('overflow', 'hidden'); // Prevent background scrolling
        $closeBtn.focus(); // Focus on close button for accessibility
      };

      /**
       * Close Modal
       */
      const closeModal = () => {
        $modal.removeClass('show').attr('aria-hidden', 'true');
        $('body').css('overflow', 'auto');
      };

      $closeBtn.on('click', closeModal);

      $(window).on('click', (event) => {
        if ($(event.target).is($modal)) {
          closeModal();
        }
      });

      $(window).on('keydown', (event) => {
        if (event.key === 'Escape' && $modal.hasClass('show')) {
          closeModal();
        }
      });

      /**
       * Search Functionality
       */
      const $searchInput = $('#search-input');

      const handleSearch = debounce(async () => {
        currentSearchTerm = $searchInput.val().trim();
        currentPage = 0;
        hasMore = true;

        const $content = $('#content');
        $content.empty(); // Clear existing content

        // Reload the first page with the new search term
        await loadNextPage();
      }, 300); // 300ms debounce delay

      $searchInput.on('input', handleSearch);

      /**
       * Refresh Data Functionality
       */
      const $refreshButton = $('#refresh-button');
      $refreshButton.on('click', async () => {
        const confirmation = confirm('Are you sure you want to refresh the data? This may take a few moments.');
        if (!confirmation) return;

        isLoading = true;
        showLoader();
        hasMore = true;
        currentPage = 0;
        $refreshButton.prop('disabled', true);

        try {
          // Clear existing data
          await db.novels.clear();

          // Fetch and parse fresh data
          await fetchAndStoreNovels();

          // Reload the first page of novels
          $('#content').empty(); // Clear existing content

          await loadNextPage();

          // Show success toast
          showToast('Data refreshed successfully!', 'success');
        } catch (error) {
          console.error('Error refreshing data:', error);
          showTemporaryError('Failed to refresh data. Please try again later.', 5000);
          // Show error toast
          showToast('Failed to refresh data.', 'error');
        } finally {
          hideLoader();
          isLoading = false;
          $refreshButton.prop('disabled', false);
        }
      });

      /**
       * Initialize Application
       */
      const initializeApp = async () => {
        try {
          // Check if any novels are already stored in IndexedDB
          const count = await db.novels.count();
          if (count === 0) {
            // If not, fetch from the target website
            await fetchAndStoreNovels();
          } else {
            // Validate existing data
            const novels = await db.novels.toArray();
            if (!validateNovels(novels)) {
              await db.novels.clear();
              await fetchAndStoreNovels();
            }
          }

          // Load the first page of novels
          await loadNextPage();
        } catch (error) {
          console.error('Error initializing application:', error);
          showTemporaryError('Failed to load novels. Please try refreshing the page.', 5000);
          // Show error toast
          showToast('Failed to load novels.', 'error');
        }
      };

      /**
       * Fetch and Store Novels
       */
      const fetchAndStoreNovels = async () => {
        try {
          const html = await retryOperation(() => fetchHTML('https://jnovels.com/top-light-novels-to-read/'));
          let novels = parseHTML(html);

          if (novels.length === 0) {
            throw new Error('No novels found on the target page.');
          }

          totalNovels = novels.length;
          loadedNovels = 0;
          showProgress();
          updateProgress(0);

          // Bulk upsert novels to IndexedDB
          const novelsToAdd = novels.map(novel => ({
            ...novel,
            name: escapeHTML(novel.name),
            coverUrl: escapeHTML(novel.coverUrl),
            pdfUrl: escapeHTML(novel.pdfUrl),
            timestamp: Date.now()
          }));

          await db.novels.bulkPut(novelsToAdd);
          loadedNovels = novelsToAdd.length;
          updateProgress(100);

          console.log('Novels fetched and stored successfully:', novelsToAdd.length);

          // Show success toast
          showToast('Novels fetched and stored successfully.', 'success');

          // Clear fetched HTML content
          clearFetchedHTML();
        } catch (error) {
          console.error('Error fetching or storing novels:', error);
          showTemporaryError('Failed to fetch and store novels. Please try again later.', 5000);
          // Show error toast
          showToast('Failed to fetch and store novels.', 'error');
        } finally {
          hideProgress();
        }
      };

      /**
       * Fetch HTML Content with Content-Type Validation via CORS Proxy
       * @param {string} url
       * @returns {Promise<string>}
       */
      const fetchHTML = async (url) => {
        const proxyUrl = `https://api.codetabs.com/v1/proxy/?target=${encodeURIComponent(url)}`;
        try {
          const response = await fetch(proxyUrl, { cache: 'no-cache' });
          
          if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
          }

          const htmlContent = await response.text();
          
          if (!htmlContent || typeof htmlContent !== 'string') {
            throw new Error('Fetched content is invalid or empty.');
          }

          return htmlContent;
        } catch (error) {
          console.error('Error fetching HTML:', error);
          throw error; // Re-throw to be handled by caller
        }
      };

      /**
       * Parse Fetched HTML to Extract Novel Data Based on Provided Schema
       * @param {string} html
       * @returns {Array<Object>}
       */
      const parseHTML = (html) => {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const novels = [];
        const uniqueNovels = new Set();

        // Select all h3 tags as they contain the novel names and links
        const h3Elements = doc.querySelectorAll('h3');

        h3Elements.forEach((h3Element) => {
          try {
            const aTag = h3Element.querySelector('a');
            const spanTag = h3Element.querySelector('span');

            if (!aTag || !spanTag) return; // Skip if either tag is missing

            const name = spanTag.textContent.trim();
            const pdfLink = aTag.getAttribute('href').trim();

            // The cover image is inside the following p tag
            const pElement = h3Element.nextElementSibling;
            if (!pElement) return; // Skip if p tag is missing

            const imgTag = pElement.querySelector('img');
            if (!imgTag) return; // Skip if img tag is missing

            let coverUrl = imgTag.getAttribute('src') || imgTag.getAttribute('data-src');
            if (!coverUrl) return; // Skip if cover URL is missing

            // Handle different URL formats
            if (coverUrl.startsWith('//')) {
              coverUrl = `https:${coverUrl}`;
            } else if (coverUrl.startsWith('/')) {
              coverUrl = `https://jnovels.com${coverUrl}`;
            } else if (!coverUrl.startsWith('http')) {
              // Handle relative URLs without leading '/'
              coverUrl = `https://jnovels.com/${coverUrl}`;
            }

            // Validate extracted data
            if (name && pdfLink && coverUrl) {
              const novelKey = `${name}-${coverUrl}-${pdfLink}`;
              if (!uniqueNovels.has(novelKey)) {
                uniqueNovels.add(novelKey);
                novels.push({
                  name,
                  coverUrl,
                  pdfUrl: pdfLink
                });
              }
            } else {
              console.warn('Incomplete novel data found and skipped:', { name, pdfLink, coverUrl });
            }
          } catch (error) {
            // Skip any malformed entries
            console.error('Error parsing a novel entry:', error);
          }
        });

        return novels;
      };

      /**
       * Clear Fetched HTML Content
       */
      const clearFetchedHTML = () => {
        // Implementation depends on how HTML content is fetched and stored
        console.log('Clearing fetched HTML content...');
      };

      /**
       * Clear Cache on Before Unload
       */
      const clearCache = () => {
        caches.keys().then(cacheNames => {
          cacheNames.forEach(cacheName => {
            if (cacheName !== 'MonochromeNovelsDB') {
              caches.delete(cacheName);
            }
          });
        });
      };

      window.addEventListener('beforeunload', clearCache);
      document.addEventListener('visibilitychange', () => {
        if (document.visibilityState === 'hidden') {
          clearCache();
        }
      });

      /**
       * Update Progress Bar
       * @param {number} percentage
       */
      const updateProgress = (percentage) => {
        $('#progress-bar').css('width', `${percentage}%`);
        $('#progress-text').text(`${Math.round(percentage)}%`);
      };

      /**
       * Show Progress Bar and Text
       */
      const showProgress = () => {
        $('#progress-container').show();
        $('#progress-text').show();
      };

      /**
       * Hide Progress Bar and Text
       */
      const hideProgress = () => {
        $('#progress-container').hide();
        $('#progress-text').hide();
      };

      /**
       * Validate Novels
       * @param {Array<Object>} novels
       * @returns {boolean}
       */
      const validateNovels = (novels) => {
        for (const novel of novels) {
          if (!novel.name || !novel.coverUrl || !novel.pdfUrl || !novel.timestamp) {
            return false;
          }
        }
        return true;
      };

      /**
       * Toast Notification Functionality
       */
      const showToast = (message, type = 'success') => {
        const $toastContainer = $('#toast-container');
        const $toast = $('<div>', {
          class: `toast ${type}`
        });

        let iconSVG = '';

        if (type === 'success') {
          iconSVG = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
              <path d="M20.285,6.708l-11.7,11.7l-5.566-5.566c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414l6.28,6.28
              c0.391,0.391,1.023,0.391,1.414,0l12.544-12.544C20.676,7.731,20.676,7.1,20.285,6.708z"/>
            </svg>
          `;
        } else if (type === 'error') {
          iconSVG = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
              <path d="M12,0C5.373,0,0,5.373,0,12s5.373,12,12,12s12-5.373,12-12S18.627,0,12,0z M16.242,16.242
              c0.391,0.391,0.391,1.023,0,1.414c-0.391,0.391-1.023,0.391-1.414,0L12,13.414l-2.828,2.828c-0.391,0.391-1.023,0.391-1.414,0
              c-0.391-0.391-0.391-1.023,0-1.414L10.586,12l-2.828-2.828c-0.391-0.391-0.391-1.023,0-1.414s1.023-0.391,1.414,0L12,10.586
              l2.828-2.828c0.391-0.391,1.023-0.391,1.414,0s0.391,1.023,0,1.414L13.414,12l2.828,2.828C16.633,15.219,16.633,15.851,16.242,16.242z"/>
            </svg>
          `;
        }

        $toast.html(`${iconSVG} ${message}`);
        $toast.addClass('show');
        $toastContainer.append($toast);

        // Remove toast after 5 seconds
        setTimeout(() => {
          $toast.removeClass('show');
          setTimeout(() => {
            $toast.remove();
          }, 500);
        }, 5000);
      };

      /**
       * Initialize Theme Toggle
       */
      const initializeTheme = async () => {
        const $themeToggle = $('#theme-toggle');
        const themeSetting = await db.settings.get('theme');
        const currentTheme = themeSetting ? themeSetting.value : 'dark';

        if (currentTheme === 'light') {
          $('body').attr('data-theme', 'light');
          $themeToggle.prop('checked', true);
        } else {
          $('body').attr('data-theme', 'dark');
          $themeToggle.prop('checked', false);
        }

        $themeToggle.on('change', async () => {
          const newTheme = $themeToggle.is(':checked') ? 'light' : 'dark';
          $('body').attr('data-theme', newTheme);
          await db.settings.put({ key: 'theme', value: newTheme });
        });
      };

      /**
       * Initialize Menu Button and Menu
       */
      const initializeMenu = () => {
        const $menuButton = $('#menu-button');
        const $menu = $('#menu');
        const $settingsPanel = $('#settings-panel');
        const $settingsPanelClose = $('.settings-panel__close');

        $menuButton.on('click', () => {
          $menu.toggleClass('open');
          if ($menu.hasClass('open')) {
            $('body').css('overflow', 'hidden');
          } else {
            $('body').css('overflow', 'auto');
          }
        });

        $menu.find('.menu__item').on('click', (e) => {
          const target = $(e.target).data('target');
          if (target === 'home') {
            // Redirect to the first page where all novels are displayed
            currentSearchTerm = '';
            currentPage = 0;
            hasMore = true;
            $('#content').empty();
            loadNextPage();
          } else if (target === 'settings') {
            $settingsPanel.addClass('open');
            $('body').css('overflow', 'hidden');
          }
          $menu.removeClass('open');
          $('body').css('overflow', 'auto');
        });

        $settingsPanelClose.on('click', () => {
          $settingsPanel.removeClass('open');
          $('body').css('overflow', 'auto');
        });

        $(window).on('click', (event) => {
          if ($(event.target).is($settingsPanel)) {
            $settingsPanel.removeClass('open');
            $('body').css('overflow', 'auto');
          }
        });

        $(window).on('keydown', (event) => {
          if (event.key === 'Escape' && $settingsPanel.hasClass('open')) {
            $settingsPanel.removeClass('open');
            $('body').css('overflow', 'auto');
          }
        });
      };

      /**
       * Initialize AOS and Start the Application once the DOM is fully loaded
       */
      $(document).ready(() => {
        // Initialize AOS
        AOS.init({
          duration: 800,
          easing: 'ease-in-out',
          once: true,
        });

        setupSentinelObserver();
        initializeApp();
        initializeTheme();
        initializeMenu();
      });

    })(jQuery);
  </script>
</body>
</html>