Fix and complete it. Send the fully working full completed 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://cdn.jsdelivr.net/npm/dexie@4.0.9/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';
(() => { 

  /**
   * IndexedDB Utilities using Dexie.js
   */

  // Initialize the database using Dexie
  const db = new Dexie('MonochromeNovelsDB');

  // Define the database schema with proper versioning
  db.version(2).stores({
    novels: '++id,&name,nameLower,&coverUrl,&pdfUrl,timestamp',
    settings: '&key'
  }).upgrade(tx => {
    // Upgrade logic if needed
    // For version 2, we can add the 'nameLower' index if it didn't exist before
    return tx.table('novels').toCollection().modify(novel => {
      novel.nameLower = novel.name.toLowerCase();
    });
  });

  // Open the database and handle any errors
  try {
    await db.open();
  } catch (err) {
    console.error('Failed to open db:', err);
    // If the error is due to version mismatch, delete the database and reopen
    if (err.name === 'VersionError' || err.inner && err.inner.name === 'VersionError') {
      await Dexie.delete('MonochromeNovelsDB');
      location.reload();
    }
  }

  // Database operation functions using Dexie

  const countNovels = () => {
    return db.novels.count();
  };

  const getAllNovels = () => {
    return db.novels.toArray();
  };

  const getNovels = (offset, limit) => {
    return db.novels.orderBy('name').offset(offset).limit(limit).toArray();
  };

  const getNovelsByNameIncludes = (searchTerm, offset, limit) => {
    const lowerSearchTerm = searchTerm.toLowerCase();
    return db.novels
      .where('nameLower')
      .startsWithIgnoreCase(lowerSearchTerm)
      .offset(offset)
      .limit(limit)
      .toArray();
  };

  const bulkAddNovels = (novels) => {
    return db.novels.bulkPut(novels);
  };

  const clearNovels = () => {
    return db.novels.clear();
  };

  const getSetting = (key) => {
    return db.settings.get(key).then(setting => (setting ? setting.value : null));
  };

  const putSetting = (key, value) => {
    return db.settings.put({ key: key, value: value });
  };

  /**
   * Constants and Variables
   */
  const PAGE_SIZE = 20;
  let currentPage = 0;
  let isLoading = false;
  let hasMore = true;
  let currentSearchTerm = '';

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

  /**
   * 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 = document.getElementById('sentinel');
    if (!('IntersectionObserver' in window)) {
      // Fallback if IntersectionObserver is not supported
      window.addEventListener('scroll', onScrollLoadNextPage);
      return;
    }

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

  // Fallback function for scroll event
  let scrollTimeout;
  const onScrollLoadNextPage = () => {
    if (scrollTimeout) {
      clearTimeout(scrollTimeout);
    }
    scrollTimeout = setTimeout(() => {
      if (window.scrollY + window.innerHeight >= document.body.scrollHeight - 100 && hasMore && !isLoading) {
        loadNextPageWrapper();
      }
    }, 200);
  };

  /**
   * Load Next Page Wrapper to prevent concurrent invocations
   */
  let loadNextPageQueue = Promise.resolve();
  const loadNextPageWrapper = () => {
    loadNextPageQueue = loadNextPageQueue.then(() => loadNextPage());
  };

  /**
   * 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 getNovels(currentPage * PAGE_SIZE, PAGE_SIZE);
      } else {
        novels = await getNovelsByNameIncludes(currentSearchTerm, currentPage * PAGE_SIZE, PAGE_SIZE);
      }

      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);
        }
      }

      // Edge case: Load more if content doesn't fill viewport
      if (document.body.scrollHeight <= window.innerHeight && hasMore && !isLoading) {
        loadNextPageWrapper();
      }
    } catch (error) {
      console.error('Error loading novels:', error);
      showTemporaryError('An error occurred while loading novels.', 5000);
    } finally {
      hideLoader();
      isLoading = false;
    }
  };

  /**
   * Show Loader
   */
  const showLoader = () => {
    document.getElementById('loader').classList.add('visible');
  };

  /**
   * Hide Loader
   */
  const hideLoader = () => {
    document.getElementById('loader').classList.remove('visible');
  };

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

    const errorDiv = document.createElement('div');
    errorDiv.className = 'error-message';
    errorDiv.setAttribute('role', 'alert');
    errorDiv.textContent = message;

    content.prepend(errorDiv);

    // Fade out after specified duration
    setTimeout(() => {
      errorDiv.classList.add('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 = document.getElementById('content');
    const fragment = document.createDocumentFragment();

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

    content.appendChild(fragment);
    if (window.AOS) {
      AOS.refresh();
    }

    // Lazy load images
    lazyLoadImages();
  };

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

    const imageContainer = document.createElement('div');
    imageContainer.className = 'card__image-container';

    const image = document.createElement('img');
    image.className = 'card__image';
    image.dataset.src = novel.coverUrl || PLACEHOLDER_IMAGE;
    image.alt = `Cover of ${novel.name}`;
    image.loading = 'lazy';
    image.onerror = function() {
      this.src = PLACEHOLDER_IMAGE;
    };

    imageContainer.appendChild(image);
    card.appendChild(imageContainer);

    const content = document.createElement('div');
    content.className = 'card__content';

    const title = document.createElement('h2');
    title.id = `novel-title-${encodeURIComponent(novel.name)}`;
    title.className = 'card__title';
    title.textContent = novel.name;

    content.appendChild(title);
    card.appendChild(content);

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

    card.addEventListener('click', openModalEvent);
    card.addEventListener('keypress', openModalEvent);

    return card;
  };

  /**
   * Lazy Load Images using Intersection Observer
   */
  const lazyLoadImages = () => {
    const images = document.querySelectorAll('img[data-src]');
    const config = {
      rootMargin: '50px 0px',
      threshold: 0.01,
    };

    let observer;
    if ('IntersectionObserver' in window) {
      observer = new IntersectionObserver((entries, self) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            preloadImage(entry.target);
            self.unobserve(entry.target);
          }
        });
      }, config);

      images.forEach((image) => {
        observer.observe(image);
      });
    } else {
      // Fallback for browsers without IntersectionObserver support
      images.forEach((image) => {
        preloadImage(image);
      });
    }
  };

  const preloadImage = (img) => {
    img.src = img.dataset.src;
    img.onload = () => {
      img.removeAttribute('data-src');
    };
  };

  /**
   * 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 = document.getElementById('modal');
  const modalTitle = document.getElementById('modal-title');
  const modalLink = document.getElementById('modal-link');
  const closeBtn = document.querySelector('.close');

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

    // Trap focus inside modal
    document.addEventListener('focus', trapFocus, true);
  };

  /**
   * Close Modal
   */
  const closeModal = () => {
    modal.classList.remove('show');
    modal.setAttribute('aria-hidden', 'true');
    document.body.style.overflow = 'auto';

    // Remove focus trap
    document.removeEventListener('focus', trapFocus, true);
  };

  const trapFocus = (event) => {
    if (!modal.contains(event.target)) {
      event.stopPropagation();
      closeBtn.focus();
    }
  };

  closeBtn.addEventListener('click', closeModal);

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

  window.addEventListener('keydown', (event) => {
    if (event.key === 'Escape' && modal.classList.contains('show')) {
      closeModal();
    }
  });

  /**
   * Search Functionality
   */
  const searchInput = document.getElementById('search-input');

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

    const content = document.getElementById('content');
    content.innerHTML = ''; // Clear existing content

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

  searchInput.addEventListener('input', handleSearch);

  /**
   * Refresh Data Functionality
   */
  const refreshButton = document.getElementById('refresh-button');
  refreshButton.addEventListener('click', async () => {
    showConfirmationModal(
      'Are you sure you want to refresh the data? This may take a few moments.',
      async () => {
        isLoading = true;
        showLoader();
        hasMore = true;
        currentPage = 0;
        refreshButton.disabled = true;

        try {
          // Clear existing data
          await clearNovels();

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

          // Reload the first page of novels
          document.getElementById('content').innerHTML = ''; // 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.disabled = false;
        }
      }
    );
  });

  /**
   * Show Confirmation Modal
   * @param {string} message
   * @param {Function} onConfirm
   */
  const showConfirmationModal = (message, onConfirm) => {
    const confirmationModal = document.createElement('div');
    confirmationModal.className = 'modal';
    confirmationModal.setAttribute('role', 'dialog');
    confirmationModal.setAttribute('aria-modal', 'true');

    const modalContent = document.createElement('div');
    modalContent.className = 'modal-content';

    const modalMessage = document.createElement('p');
    modalMessage.textContent = message;

    const modalButtons = document.createElement('div');
    modalButtons.className = 'modal-buttons';

    const confirmButton = document.createElement('button');
    confirmButton.textContent = 'Confirm';
    confirmButton.className = 'button confirm-button';

    const cancelButton = document.createElement('button');
    cancelButton.textContent = 'Cancel';
    cancelButton.className = 'button cancel-button';

    modalButtons.appendChild(confirmButton);
    modalButtons.appendChild(cancelButton);

    modalContent.appendChild(modalMessage);
    modalContent.appendChild(modalButtons);
    confirmationModal.appendChild(modalContent);
    document.body.appendChild(confirmationModal);
    document.body.style.overflow = 'hidden';

    confirmButton.focus();

    const closeConfirmationModal = () => {
      confirmationModal.remove();
      document.body.style.overflow = 'auto';
    };

    confirmButton.addEventListener('click', () => {
      closeConfirmationModal();
      onConfirm();
    });

    cancelButton.addEventListener('click', closeConfirmationModal);

    window.addEventListener('keydown', (event) => {
      if (event.key === 'Escape') {
        closeConfirmationModal();
      }
    });

    window.addEventListener('click', (event) => {
      if (event.target === confirmationModal) {
        closeConfirmationModal();
      }
    });
  };

  /**
   * Initialize Application
   */
  const initializeApp = async () => {
    try {
      // Dexie opens the database automatically

      // Check if any novels are already stored in IndexedDB
      const count = await countNovels();
      if (count === 0) {
        // Fetch and store data from the external website
        await fetchAndStoreNovels();
      } else {
        // Validate existing data
        const novels = await getAllNovels();
        if (!validateNovels(novels)) {
          await clearNovels();
          await fetchAndStoreNovels();
        }
      }

      // Load the first page of novels
      await loadNextPage();

      // Edge case: Load more if content doesn't fill viewport
      if (document.body.scrollHeight <= window.innerHeight && hasMore && !isLoading) {
        loadNextPageWrapper();
      }
    } 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://api.codetabs.com/v1/proxy?quest=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.');
      }

      showProgress();
      updateProgress(0);

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

      await bulkAddNovelsWithProgress(novelsToAdd, (progress) => {
        updateProgress(progress);
      });

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

      // Show success toast
      showToast('Novels fetched and stored successfully.', 'success');
    } 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();
    }
  };

  const bulkAddNovelsWithProgress = async (novels, progressCallback) => {
    const total = novels.length;
    let completed = 0;
    const chunkSize = 100; // Adjust as appropriate

    for (let i = 0; i < total; i += chunkSize) {
      const chunk = novels.slice(i, i + chunkSize);
      await db.novels.bulkPut(chunk).catch((error) => {
        console.error('Error in bulkPut:', error);
      });
      completed += chunk.length;
      const progress = (completed / total) * 100;
      progressCallback(progress);
    }
  };

  /**
   * Fetch HTML Content from the URL
   * @param {string} url
   * @returns {Promise<string>}
   */
  const fetchHTML = async (url) => {
    try {
      const response = await fetch(url, { 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
   * @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
        let pElement = h3Element.nextElementSibling;
        while (pElement && pElement.tagName.toLowerCase() !== 'p') {
          pElement = pElement.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;
  };

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

  /**
   * Show Progress Bar and Text
   */
  const showProgress = () => {
    document.getElementById('progress-container').style.display = 'block';
    document.getElementById('progress-text').style.display = 'block';
  };

  /**
   * Hide Progress Bar and Text
   */
  const hideProgress = () => {
    document.getElementById('progress-container').style.display = 'none';
    document.getElementById('progress-text').style.display = 'none';
  };

  /**
   * 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 = document.getElementById('toast-container');
    const toast = document.createElement('div');
    toast.className = `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-12,12-12S5.373,0,12,0z M16.242,16.242
          c0.391,0.391,0.391,1.023,0,1.414c-0.391,0.391-1,0.391-1.414,0L12,13.414l-2.828,2.828c-0.391,0.391-1,0.391-1.414,0
          c-0.391-0.391-0.391-1,0-1.414L10.586,12l-2.828-2.828c-0.391-0.391-0.391-1,0-1.414s1-0.391,1.414,0L12,10.586
          l2.828-2.828c0.391-0.391,1-0.391,1.414,0s0.391,1,0,1.414L13.414,12l2.828,2.828C16.633,15.219,16.633,15.851,16.242,16.242z"/>
        </svg>
      `;
    }

    toast.innerHTML = `${iconSVG} ${message}`;
    toast.classList.add('show');
    toastContainer.appendChild(toast);

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

  /**
   * Initialize Theme Toggle
   */
  const initializeTheme = async () => {
    const themeToggle = document.getElementById('theme-toggle');
    let currentTheme;
    try {
      currentTheme = (await getSetting('theme')) || 'dark';
    } catch (error) {
      console.error('Error getting theme setting:', error);
      currentTheme = 'dark';
    }

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

    themeToggle.addEventListener('change', async () => {
      const newTheme = themeToggle.checked ? 'light' : 'dark';
      document.body.setAttribute('data-theme', newTheme);
      try {
        await putSetting('theme', newTheme);
      } catch (error) {
        console.error('Error saving theme setting:', error);
      }
    });
  };

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

    menuButton.addEventListener('click', () => {
      menu.classList.toggle('open');
      if (menu.classList.contains('open')) {
        document.body.style.overflow = 'hidden';
      } else {
        document.body.style.overflow = 'auto';
      }
    });

    const menuItems = menu.querySelectorAll('.menu__item');
    menuItems.forEach((menuItem) => {
      menuItem.addEventListener('click', async (e) => {
        const target = e.target.getAttribute('data-target');
        if (target === 'home') {
          // Redirect to the first page where all novels are displayed
          currentSearchTerm = '';
          currentPage = 0;
          hasMore = true;
          document.getElementById('content').innerHTML = '';
          await loadNextPage();
        } else if (target === 'settings') {
          settingsPanel.classList.add('open');
          document.body.style.overflow = 'hidden';
        }
        menu.classList.remove('open');
        document.body.style.overflow = 'auto';
      });
    });

    settingsPanelClose.addEventListener('click', () => {
      settingsPanel.classList.remove('open');
      document.body.style.overflow = 'auto';
    });

    window.addEventListener('click', (event) => {
      if (event.target === settingsPanel) {
        settingsPanel.classList.remove('open');
        document.body.style.overflow = 'auto';
      }
    });

    window.addEventListener('keydown', (event) => {
      if (event.key === 'Escape' && settingsPanel.classList.contains('open')) {
        settingsPanel.classList.remove('open');
        document.body.style.overflow = 'auto';
      }
    });
  };

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

    setupSentinelObserver();
    initializeTheme();
    initializeMenu();
    initializeApp();
  });
})();
 </script>
</body>
</html>