Asura Bookmark Manager

Track your manga reading progress with bookmarks, want-to-read list, and remove titles.

// ==UserScript==
// @name         Asura Bookmark Manager
// @namespace    Violentmonkey Scripts
// @match        https://asuracomic.net/*
// @grant        none
// @version      4.0
// @icon         https://asuracomic.net/images/logo.webp
// @description  Track your manga reading progress with bookmarks, want-to-read list, and remove titles.
// @author       Moose, GitHub Copilot, GPT
// ==/UserScript==

(function () {
  'use strict';

  // Don't run on chapter pages
  if (location.pathname.includes('/chapter/')) {
    return;
  }

  const bookmarkKey = 'asuraManualBookmarks';
  const hideKey = 'asuraManualHidden';
  const wantKey = 'asuraManualWantToRead';
  const completedKey = 'asuraManualCompleted';

  const load = (key) => JSON.parse(localStorage.getItem(key) || '{}');
  const save = (key, data) => localStorage.setItem(key, JSON.stringify(data));

  let bookmarks = load(bookmarkKey);
  let hidden = load(hideKey);
  let wantToRead = load(wantKey);
  let completed = load(completedKey);

  // --- Default colors (can be customized) ---
  let colors = {
    bookmarked: '#c084fc',     // Purple for bookmarked titles
    wantToRead: '#FFD700',     // Gold for want-to-read titles
    defaultTitle: '#00BFFF',   // Blue for default titles
    completed: '#32CD32',      // Lime green for completed titles
    chapterBookmarked: '#c084fc', // Purple for last read chapter
    chapterUnread: '#1cdf2d',     // Green for unread chapters
    chapterBookmarkedBg: '#45025f', // Darker purple background (series page)
    chapterUnreadBg: '#414101'      // Darker yellow/green background (series page)
  };

  // Load saved colors
  function loadColors() {
    const savedColors = localStorage.getItem('asuraBookmarkColors');
    if (savedColors) {
      colors = { ...colors, ...JSON.parse(savedColors) };
    }
    updateStyles();
  }

  // Save colors
  function saveColors() {
    localStorage.setItem('asuraBookmarkColors', JSON.stringify(colors));
    updateStyles();
  }

  // Update CSS styles with current colors
  function updateStyles() {
    const existingStyle = document.getElementById('asura-dynamic-styles');
    if (existingStyle) existingStyle.remove();

    const dynamicStyle = document.createElement('style');
    dynamicStyle.id = 'asura-dynamic-styles';
    dynamicStyle.textContent = `
      /* CHAPTER HIGHLIGHTING */
      .chapter-bookmarked, a[href*='/chapter/'].chapter-bookmarked {
        color: ${colors.chapterBookmarked} !important; font-weight: bold !important;
      }
      .chapter-unread, a[href*='/chapter/'].chapter-unread {
        color: ${colors.chapterUnread} !important; font-weight: bold !important;
      }

      /* Series page specific highlighting */
      body[data-series-page="true"] .chapter-bookmarked,
      body[data-series-page="true"] a[href*='/chapter/'].chapter-bookmarked {
        background: ${colors.chapterBookmarkedBg} !important;
      }
      body[data-series-page="true"] .chapter-unread,
      body[data-series-page="true"] a[href*='/chapter/'].chapter-unread {
        background: ${colors.chapterUnreadBg} !important;
      }
    `;
    document.head.appendChild(dynamicStyle);
  }

  // --- STYLES ---
  const style = document.createElement('style');
  style.textContent = `
    /* Main panel button */
    .floating-panel-btn {
      position: fixed; top: 5px; right: 5px;
      background-color: #4b0082; color: white;
      padding: 11px 14px; border-radius: 8px;
      z-index: 9999; border: none; cursor: pointer;
    }

    /* Bookmark panel */
    .bookmark-panel {
      position: fixed; top: 60px; right: 40px; width: 630px;
      background: #1a1a1a; color: #fff; border: 1px solid #4b0082;
      border-radius: 10px; padding: 10px; z-index: 9999;
      display: none; max-height: 90vh; overflow: hidden;
      display: flex; flex-direction: column;
    }

    /* Panel tabs */
    .panel-tabs {
      display: flex; gap: 10px; margin-bottom: 10px; justify-content: center;
      position: sticky; top: 0; background: #1a1a1a; z-index: 2;
      padding: 14px 0; border-radius: 10px 10px 0 0;
      box-shadow: 0 4px 16px 0 rgba(0,0,0,0.18);
    }
    .tab-btn {
      flex: 1; padding: 12px 16px; cursor: pointer;
      background: #2a2a2a; text-align: center; border: none;
      color: white; font-weight: bold; border-radius: 10px;
    }
    .tab-btn.active { background: #4b0082; }

    /* Panel content */
    .panel-content {
      display: flex; flex-direction: column; overflow-y: auto;
      max-height: calc(80vh - 100px); padding-top: 0; padding-bottom: 20px;
    }
    .panel-entry {
      display: flex; gap: 10px; margin: 4px 0; padding: 6px;
      background: #2a2a2a; border-radius: 6px; align-items: center;
    }
    .panel-entry img {
      width: 90px; height: 120px; object-fit: cover; border-radius: 4px;
    }
    .panel-entry .info {
      display: flex; flex-direction: column; justify-content: space-between; flex-grow: 1;
    }
    .panel-entry button {
      align-self: flex-start; background: #6a0dad; border: none;
      color: white; border-radius: 4px; padding: 2px 6px;
      font-size: 12px; cursor: pointer; margin-top: 6px;
    }

    /* Action buttons */
    .asura-btn {
      margin-left: 6px; font-size: 14px; cursor: pointer;
      border: none; background: none;
    }

    /* Hidden manga */
    .asura-hidden { display: none !important; }

    /* Settings styles */
    .settings-section {
      margin-bottom: 25px; padding: 15px; background: #2a2a2a; border-radius: 8px;
    }
    .settings-section h4 {
      margin: 0 0 15px 0; color: #c084fc; font-size: 16px;
    }
    .color-input-group {
      display: flex; align-items: center; margin: 10px 0; gap: 10px;
    }
    .color-input-group label {
      min-width: 150px; font-size: 14px;
    }
    .color-input-group input[type="color"] {
      width: 50px; height: 30px; border: none; border-radius: 4px; cursor: pointer;
    }
    .color-input-group input[type="text"] {
      width: 80px; padding: 5px; border: 1px solid #444; border-radius: 4px;
      background: #1a1a1a; color: white; font-family: monospace;
    }
    .settings-tabs {
      display: flex; gap: 5px; margin-bottom: 15px;
    }
    .settings-tab-btn {
      padding: 8px 16px; background: #444; color: white; border: none;
      border-radius: 4px; cursor: pointer; font-size: 12px;
    }
    .settings-tab-btn.active {
      background: #6a0dad;
    }
  `;
  document.head.appendChild(style);

  // --- UTILITIES ---
  function debounce(func, delay = 100) {
    let timeout;
    return (...args) => {
      clearTimeout(timeout);
      timeout = setTimeout(() => func(...args), delay);
    };
  }

  function extractTitleFromHref(href) {
    const match = href.match(/\/series\/([a-z0-9-]+)/i);
    if (!match) return null;
    let slug = match[1].replace(/-\w{6,}$/, '');
    return slug.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
  }

  // Enhanced getUrlKey with more flexible pattern matching
  function getUrlKey(url) {
    const match = url.match(/\/series\/[^\/]+/);
    if (!match) return url;

    const seriesPath = match[0];
    // More flexible hash pattern - handles various hash lengths
    return seriesPath.replace(/-[a-f0-9]{6,}$/, '');
  }

  // Simplified function to get display title
  function getDisplayTitle(obj) {
    if (obj.displayTitle) return obj.displayTitle;
    if (obj.url) return extractTitleFromHref(obj.url) || obj.title || 'Unknown Title';
    return obj.title || 'Unknown Title';
  }

  // Enhanced function to find matching key using fuzzy title matching (for backwards compatibility)
  function findMatchingKey(searchTitle, dataObject) {
    if (!searchTitle) return null;

    // First try exact URL key match
    const urlKey = getUrlKey(searchTitle);
    if (dataObject[urlKey]) return urlKey;

    // Try with hash patterns too - look for both normalized and hash versions
    for (const key of Object.keys(dataObject)) {
      if (getUrlKey(key) === urlKey) return key;
    }

    // Clean the search title for comparison
    const cleanSearchTitle = searchTitle.toLowerCase()
      .replace(/[^\w\s]/g, '')
      .replace(/\s+/g, ' ')
      .trim();

    // Try to find by title or displayTitle
    for (const [key, obj] of Object.entries(dataObject)) {
      if (!obj) continue;

      // Check displayTitle first
      if (obj.displayTitle) {
        const cleanDisplayTitle = obj.displayTitle.toLowerCase()
          .replace(/[^\w\s]/g, '')
          .replace(/\s+/g, ' ')
          .trim();
        if (cleanDisplayTitle === cleanSearchTitle) return key;
      }

      // Check title
      if (obj.title) {
        const cleanTitle = obj.title.toLowerCase()
          .replace(/[^\w\s]/g, '')
          .replace(/\s+/g, ' ')
          .trim();
        if (cleanTitle === cleanSearchTitle) return key;
      }

      // Check if key itself matches (for old title-based keys)
      const cleanKey = key.toLowerCase()
        .replace(/[^\w\s]/g, '')
        .replace(/\s+/g, ' ')
        .trim();
      if (cleanKey === cleanSearchTitle) return key;
    }

    return null;
  }

  // Enhanced function to find ALL matching keys (including hash variants)
  function findAllMatchingKeys(searchTitle, dataObject) {
    if (!searchTitle) return [];

    const matches = [];
    const urlKey = getUrlKey(searchTitle);

    // Find all keys that normalize to the same URL key
    for (const key of Object.keys(dataObject)) {
      if (getUrlKey(key) === urlKey) {
        matches.push(key);
      }
    }

    // Also check by title matching
    const cleanSearchTitle = searchTitle.toLowerCase()
      .replace(/[^\w\s]/g, '')
      .replace(/\s+/g, ' ')
      .trim();

    for (const [key, obj] of Object.entries(dataObject)) {
      if (!obj || matches.includes(key)) continue;

      const objTitle = obj.displayTitle || obj.title || '';
      const cleanObjTitle = objTitle.toLowerCase()
        .replace(/[^\w\s]/g, '')
        .replace(/\s+/g, ' ')
        .trim();

      if (cleanObjTitle === cleanSearchTitle) {
        matches.push(key);
      }
    }

    return matches;
  }

  // --- PANEL RENDERING ---
  function updatePanel(container, tab) {
    container.innerHTML = '';
    let items = [];

    if (tab === 'bookmarks') {
      items = Object.values(bookmarks).sort((a, b) => (b.lastRead || 0) - (a.lastRead || 0));
    } else if (tab === 'want') {
      items = Object.values(wantToRead).reverse();
    } else if (tab === 'completed') {
      items = Object.values(completed).reverse();
    } else if (tab === 'hidden') {
      items = Object.values(hidden);
    }

    items.forEach(obj => {
      const displayTitle = getDisplayTitle(obj);
      const urlKey = getUrlKey(obj.url || '');

      const entry = document.createElement('div');
      entry.className = 'panel-entry';

      const img = document.createElement('img');
      img.src = obj.cover || '';
      entry.appendChild(img);

      const info = document.createElement('div');
      info.className = 'info';

      const link = document.createElement('a');
      link.href = obj.url?.split('/chapter/')[0] || '#';
      link.target = '_blank';
      link.style.color = 'white';
      link.textContent = displayTitle;

      const titleEl = document.createElement('strong');
      titleEl.appendChild(link);

      const chapterEl = document.createElement('div');
      chapterEl.textContent = obj.chapter || '';

      // Add last read date for all tabs
      const lastReadEl = document.createElement('div');
      lastReadEl.style.fontSize = '12px';
      lastReadEl.style.color = '#888';
      lastReadEl.textContent = `Last read: ${obj.lastRead ? new Date(obj.lastRead).toLocaleDateString() : 'Unknown'}`;

      info.appendChild(titleEl);
      info.appendChild(chapterEl);
      info.appendChild(lastReadEl);

      // Panel Buttons
      const btnGroup = document.createElement('span');

      if (tab === 'bookmarks') {
        // Move to Want to Read
        const wantBtn = document.createElement('button');
        wantBtn.className = 'asura-btn';
        wantBtn.textContent = '📙';
        wantBtn.title = 'Move to Want to Read';
        wantBtn.onclick = () => {
          wantToRead[urlKey] = { ...obj, displayTitle, removable: true };
          delete bookmarks[urlKey];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(wantBtn);

        // Move to Hidden
        const hideBtn = document.createElement('button');
        hideBtn.className = 'asura-btn';
        hideBtn.textContent = '❌';
        hideBtn.title = 'Move to Hidden';
        hideBtn.onclick = () => {
          hidden[urlKey] = { cover: obj.cover, url: obj.url, displayTitle, removable: true };
          delete bookmarks[urlKey];
          save(bookmarkKey, bookmarks);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(hideBtn);

        // Move to Completed
        const completedBtn = document.createElement('button');
        completedBtn.className = 'asura-btn';
        completedBtn.textContent = '✅';
        completedBtn.title = 'Mark as Completed';
        completedBtn.onclick = () => {
          completed[urlKey] = { ...obj, displayTitle, lastRead: Date.now(), removable: true };
          delete bookmarks[urlKey];
          save(bookmarkKey, bookmarks);
          save(completedKey, completed);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(completedBtn);

        // Remove completely - Enhanced to handle all variants
        const removeBtn = document.createElement('button');
        removeBtn.className = 'asura-btn';
        removeBtn.textContent = 'Remove';
        removeBtn.title = 'Remove from all lists';
        removeBtn.style.opacity = obj.removable === false ? '0.5' : '1';
        removeBtn.disabled = obj.removable === false;
        removeBtn.onclick = () => {
          if (obj.removable === false) {
            alert('This item cannot be removed (marked as non-removable)');
            return;
          }

          // Find and remove ALL matching keys
          const allKeys = findAllMatchingKeys(displayTitle, { ...bookmarks, ...wantToRead, ...completed, ...hidden });

          allKeys.forEach(key => {
            delete bookmarks[key];
            delete wantToRead[key];
            delete completed[key];
            delete hidden[key];
          });

          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(completedKey, completed);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(removeBtn);
      }
      // Want to read tab: Remove button only
      else if (tab === 'want') {
        // Remove button - Enhanced to handle all variants
        const removeBtn = document.createElement('button');
        removeBtn.className = 'asura-btn';
        removeBtn.textContent = 'Remove';
        removeBtn.title = 'Remove from all lists';
        removeBtn.style.opacity = obj.removable === false ? '0.5' : '1';
        removeBtn.disabled = obj.removable === false;
        removeBtn.onclick = () => {
          if (obj.removable === false) {
            alert('This item cannot be removed (marked as non-removable)');
            return;
          }

          // Find and remove ALL matching keys
          const allKeys = findAllMatchingKeys(displayTitle, { ...bookmarks, ...wantToRead, ...completed, ...hidden });

          allKeys.forEach(key => {
            delete bookmarks[key];
            delete wantToRead[key];
            delete completed[key];
            delete hidden[key];
          });

          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(completedKey, completed);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(removeBtn);
      }
      // Completed tab: 📌📙❌
      else if (tab === 'completed') {
        // 📌 Move to Bookmarks
        const pinBtn = document.createElement('button');
        pinBtn.className = 'asura-btn';
        pinBtn.textContent = '📌';
        pinBtn.title = 'Move to Bookmarks';
        pinBtn.onclick = () => {
          bookmarks[urlKey] = { ...obj, displayTitle, chapter: obj.chapter || 'Chapter 0' };
          delete completed[urlKey];
          save(bookmarkKey, bookmarks);
          save(completedKey, completed);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(pinBtn);

        // 📙 Move to Want to Read
        const wantBtn = document.createElement('button');
        wantBtn.className = 'asura-btn';
        wantBtn.textContent = '📙';
        wantBtn.title = 'Move to Want to Read';
        wantBtn.onclick = () => {
          wantToRead[urlKey] = { ...obj, displayTitle };
          delete completed[urlKey];
          save(wantKey, wantToRead);
          save(completedKey, completed);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(wantBtn);

        // ❌ Move to Hidden
        const hideBtn = document.createElement('button');
        hideBtn.className = 'asura-btn';
        hideBtn.textContent = '❌';
        hideBtn.title = 'Move to Hidden';
        hideBtn.onclick = () => {
          hidden[urlKey] = { cover: obj.cover, url: obj.url, displayTitle };
          delete completed[urlKey];
          save(hideKey, hidden);
          save(completedKey, completed);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(hideBtn);

        // Remove button
        const removeBtn = document.createElement('button');
        removeBtn.className = 'asura-btn';
        removeBtn.textContent = 'Remove';
        removeBtn.title = 'Remove from all lists';
        removeBtn.style.opacity = obj.removable === false ? '0.5' : '1';
        removeBtn.disabled = obj.removable === false;
        removeBtn.onclick = () => {
          if (obj.removable === false) {
            alert('This item cannot be removed (marked as non-removable)');
            return;
          }

          // Find and remove ALL matching keys
          const allKeys = findAllMatchingKeys(displayTitle, { ...bookmarks, ...wantToRead, ...completed, ...hidden });

          allKeys.forEach(key => {
            delete bookmarks[key];
            delete wantToRead[key];
            delete completed[key];
            delete hidden[key];
          });

          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(completedKey, completed);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(removeBtn);
      }
      // Hidden tab: 📌📙
      else if (tab === 'hidden') {
        // 📌 Move to Bookmarks
        const pinBtn = document.createElement('button');
        pinBtn.className = 'asura-btn';
        pinBtn.textContent = '📌';
        pinBtn.title = 'Move to Bookmarks';
        pinBtn.onclick = () => {
          bookmarks[urlKey] = { ...obj, displayTitle, chapter: obj.chapter || 'Chapter 0' };
          delete hidden[urlKey];
          save(bookmarkKey, bookmarks);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(pinBtn);

        // 📙 Move to Want to Read
        const wantBtn = document.createElement('button');
        wantBtn.className = 'asura-btn';
        wantBtn.textContent = '📙';
        wantBtn.title = 'Move to Want to Read';
        wantBtn.onclick = () => {
          wantToRead[urlKey] = { ...obj, displayTitle };
          delete hidden[urlKey];
          save(wantKey, wantToRead);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(wantBtn);

        // Remove button
        const removeBtn = document.createElement('button');
        removeBtn.className = 'asura-btn';
        removeBtn.textContent = 'Remove';
        removeBtn.title = 'Remove from all lists';
        removeBtn.style.opacity = obj.removable === false ? '0.5' : '1';
        removeBtn.disabled = obj.removable === false;
        removeBtn.onclick = () => {
          if (obj.removable === false) {
            alert('This item cannot be removed (marked as non-removable)');
            return;
          }

          // Find and remove ALL matching keys
          const allKeys = findAllMatchingKeys(displayTitle, { ...bookmarks, ...wantToRead, ...completed, ...hidden });

          allKeys.forEach(key => {
            delete bookmarks[key];
            delete wantToRead[key];
            delete completed[key];
            delete hidden[key];
          });

          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(completedKey, completed);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(removeBtn);
      }
      info.appendChild(btnGroup);
      entry.appendChild(info);
      container.appendChild(entry);
    });
  }

  // --- SETTINGS PANEL ---
  function updateSettingsPanel(container) {
    container.innerHTML = `
      <div style="padding: 20px;">
        <div class="settings-tabs">
          <button class="settings-tab-btn active" data-settings-tab="general">🔧 General</button>
          <button class="settings-tab-btn" data-settings-tab="colors">🎨 Colors</button>
          <button class="settings-tab-btn" data-settings-tab="completed">✅ Completed</button>
          <button class="settings-tab-btn" data-settings-tab="hidden">🚫 Hidden</button>
        </div>
        <div id="settings-content"></div>
      </div>
    `;

    const settingsContent = container.querySelector('#settings-content');
    const settingsTabs = container.querySelectorAll('.settings-tab-btn');
    let currentSettingsTab = 'general';

    function updateSettingsContent(tab) {
      if (tab === 'general') {
        settingsContent.innerHTML = `
          <div class="settings-section">
            <h4>📤 Import/Export Data</h4>
            <button id="export-btn" style="background: #4b0082; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin-right: 10px;">
              📤 Export All Data
            </button>
            <button id="import-btn" style="background: #4b0082; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer;">
              📥 Import Data
            </button>
            <input type="file" id="import-file" accept=".json" style="display: none;">
            <div style="font-size: 12px; color: #888; margin-top: 10px;">
              Export saves all your bookmarks, want-to-read, and hidden lists to a JSON file.<br>
              Import will merge data with existing entries (newer entries take priority).
            </div>
          </div>

          <div class="settings-section">
            <h4>🗑️ Quick Actions</h4>
            <button id="clear-all-btn" style="background: #dc2626; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer;">
              🗑️ Clear All Data
            </button>
          </div>
        `;

        // Add event listeners for general settings
        document.getElementById('export-btn').onclick = () => {
          const allData = {
            bookmarks: load(bookmarkKey),
            wantToRead: load(wantKey),
            completed: load(completedKey),
            hidden: load(hideKey),
            colors: colors,
            exportDate: new Date().toISOString()
          };

          const blob = new Blob([JSON.stringify(allData, null, 2)], { type: 'application/json' });
          const url = URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = `asura-bookmarks-${new Date().toISOString().split('T')[0]}.json`;
          a.click();
          URL.revokeObjectURL(url);
        };

        // Import functionality
        document.getElementById('import-btn').onclick = () => {
          document.getElementById('import-file').click();
        };

        document.getElementById('import-file').onchange = (e) => {
          const file = e.target.files[0];
          if (!file) return;

          const reader = new FileReader();
          reader.onload = (e) => {
            try {
              const importedData = JSON.parse(e.target.result);

              // Merge bookmarks
              if (importedData.bookmarks) {
                const currentBookmarks = load(bookmarkKey);
                Object.assign(currentBookmarks, importedData.bookmarks);
                save(bookmarkKey, currentBookmarks);
              }

              // Merge want-to-read
              if (importedData.wantToRead) {
                const currentWant = load(wantKey);
                Object.assign(currentWant, importedData.wantToRead);
                save(wantKey, currentWant);
              }

              // Merge completed
              if (importedData.completed) {
                const currentCompleted = load(completedKey);
                Object.assign(currentCompleted, importedData.completed);
                save(completedKey, currentCompleted);
              }

              // Merge hidden
              if (importedData.hidden) {
                const currentHidden = load(hideKey);
                Object.assign(currentHidden, importedData.hidden);
                save(hideKey, currentHidden);
              }

              // Reload data
              bookmarks = load(bookmarkKey);
              wantToRead = load(wantKey);
              completed = load(completedKey);
              hidden = load(hideKey);

              alert('Data imported successfully!');
              updateTitleButtons();
            } catch (error) {
              alert('Error importing data: ' + error.message);
            }
          };
          reader.readAsText(file);
        };

        // Clear all functionality
        document.getElementById('clear-all-btn').onclick = () => {
          if (confirm('Are you sure you want to clear ALL bookmark data? This cannot be undone!')) {
            localStorage.removeItem(bookmarkKey);
            localStorage.removeItem(wantKey);
            localStorage.removeItem(completedKey);
            localStorage.removeItem(hideKey);
            bookmarks = {};
            wantToRead = {};
            completed = {};
            hidden = {};
            alert('All data cleared!');
            updateTitleButtons();
          }
        };
      } else if (tab === 'colors') {
        settingsContent.innerHTML = `
          <div class="settings-section">
            <h4>🎨 Title Colors</h4>
            <div class="color-input-group">
              <label>Bookmarked titles:</label>
              <input type="color" id="color-bookmarked" value="${colors.bookmarked}">
              <input type="text" id="text-bookmarked" value="${colors.bookmarked}">
            </div>
            <div class="color-input-group">
              <label>Want to read titles:</label>
              <input type="color" id="color-wantToRead" value="${colors.wantToRead}">
              <input type="text" id="text-wantToRead" value="${colors.wantToRead}">
            </div>
            <div class="color-input-group">
              <label>Completed titles:</label>
              <input type="color" id="color-completed" value="${colors.completed}">
              <input type="text" id="text-completed" value="${colors.completed}">
            </div>
            <div class="color-input-group">
              <label>Default titles:</label>
              <input type="color" id="color-defaultTitle" value="${colors.defaultTitle}">
              <input type="text" id="text-defaultTitle" value="${colors.defaultTitle}">
            </div>
          </div>

          <div class="settings-section">
            <h4>📖 Chapter Colors</h4>
            <div class="color-input-group">
              <label>Last read chapter:</label>
              <input type="color" id="color-chapterBookmarked" value="${colors.chapterBookmarked}">
              <input type="text" id="text-chapterBookmarked" value="${colors.chapterBookmarked}">
            </div>
            <div class="color-input-group">
              <label>Unread chapters:</label>
              <input type="color" id="color-chapterUnread" value="${colors.chapterUnread}">
              <input type="text" id="text-chapterUnread" value="${colors.chapterUnread}">
            </div>
            <div class="color-input-group">
              <label>Last read background:</label>
              <input type="color" id="color-chapterBookmarkedBg" value="${colors.chapterBookmarkedBg}">
              <input type="text" id="text-chapterBookmarkedBg" value="${colors.chapterBookmarkedBg}">
            </div>
            <div class="color-input-group">
              <label>Unread background:</label>
              <input type="color" id="color-chapterUnreadBg" value="${colors.chapterUnreadBg}">
              <input type="text" id="text-chapterUnreadBg" value="${colors.chapterUnreadBg}">
            </div>
          </div>

          <div class="settings-section">
            <button id="apply-colors-btn" style="background: #4b0082; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin-right: 10px;">
              ✅ Apply Colors
            </button>
            <button id="reset-colors-btn" style="background: #dc2626; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer;">
              🔄 Reset to Default Colors
            </button>
          </div>
        `;

        // Store temporary colors while user is adjusting
        let tempColors = { ...colors };

        // Update only temp colors without applying (no lag)
        Object.keys(colors).forEach(colorKey => {
          const colorInput = document.getElementById(`color-${colorKey}`);
          const textInput = document.getElementById(`text-${colorKey}`);

          if (colorInput && textInput) {
            // Color picker changes text and temp value only
            colorInput.addEventListener('input', (e) => {
              const newColor = e.target.value;
              textInput.value = newColor;
              tempColors[colorKey] = newColor;
            });
            // Text input changes color and temp value only
            textInput.addEventListener('input', (e) => {
              const newColor = e.target.value;
              if (/^#([0-9A-F]{3}){1,2}$/i.test(newColor)) {
                colorInput.value = newColor;
                tempColors[colorKey] = newColor;
              }
            });
          }
        });

        // Apply button applies all temp colors at once
        document.getElementById('apply-colors-btn').onclick = () => {
          colors = { ...tempColors };
          saveColors();
          updateTitleButtons();
        };

        document.getElementById('reset-colors-btn').onclick = () => {
          if (confirm('Reset all colors to default values?')) {
            colors = {
              bookmarked: '#c084fc',
              wantToRead: '#FFD700',
              defaultTitle: '#00BFFF',
              completed: '#32CD32',
              chapterBookmarked: '#c084fc',
              chapterUnread: '#1cdf2d',
              chapterBookmarkedBg: '#45025f',
              chapterUnreadBg: '#414101'
            };
            tempColors = { ...colors };
            saveColors();
            updateSettingsContent('colors');
            updateTitleButtons();
          }
        };

      } else if (tab === 'hidden') {
        const hiddenItems = Object.entries(hidden).map(([urlKey, obj]) => ({
          urlKey,
          title: getDisplayTitle(obj),
          chapter: obj.chapter || '',
          url: obj.url || '',
          cover: obj.cover || ''
        }));

        let hiddenHTML = `
          <div class="settings-section">
            <h4>🚫 Hidden Manga (${hiddenItems.length})</h4>
        `;

        if (hiddenItems.length === 0) {
          hiddenHTML += '<p style="color: #888; font-style: italic;">No hidden manga</p>';
        } else {
          hiddenItems.forEach(obj => {
            const displayTitle = obj.title;

            hiddenHTML += `
              <div class="panel-entry">
                <img src="${obj.cover || ''}" alt="">
                <div class="info">
                  <strong>${displayTitle || 'No title'}</strong>
                  <span>
                    <button class="asura-btn" onclick="unhideItem('${obj.urlKey}')" title="Move to Bookmarks">📌</button>
                    <button class="asura-btn" onclick="moveToWant('${obj.urlKey}')" title="Move to Want to Read">📙</button>
                    <button class="asura-btn" onclick="removeItem('${obj.urlKey}')" title="Remove completely">Remove</button>
                  </span>
                </div>
              </div>
            `;
          });
        }

        hiddenHTML += '</div>';
        settingsContent.innerHTML = hiddenHTML;

        // Add global functions for hidden item management
        window.unhideItem = (urlKey) => {
          const obj = hidden[urlKey];
          if (obj) {
            bookmarks[urlKey] = { ...obj, chapter: obj.chapter || 'Chapter 0', lastRead: Date.now() };
            delete hidden[urlKey];
            save(bookmarkKey, bookmarks);
            save(hideKey, hidden);
            updateSettingsContent('hidden');
            updateTitleButtons();
          }
        };

        window.moveToWant = (urlKey) => {
          const obj = hidden[urlKey];
          if (obj) {
            wantToRead[urlKey] = { ...obj };
            delete hidden[urlKey];
            save(wantKey, wantToRead);
            save(hideKey, hidden);
            updateSettingsContent('hidden');
            updateTitleButtons();
          }
        };

        window.removeItem = (urlKey) => {
          delete bookmarks[urlKey];
          delete wantToRead[urlKey];
          delete completed[urlKey];
          delete hidden[urlKey];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(completedKey, completed);
          save(hideKey, hidden);
          updateSettingsContent('hidden');
          updateTitleButtons();
        };
      } else if (tab === 'completed') {
        const completedItems = Object.entries(completed).map(([urlKey, obj]) => ({
          urlKey,
          title: getDisplayTitle(obj),
          chapter: obj.chapter || '',
          url: obj.url || '',
          cover: obj.cover || '',
          lastRead: obj.lastRead || 0
        }));

        let completedHTML = `
          <div class="settings-section">
            <h4>✅ Completed Manga (${completedItems.length})</h4>
        `;

        if (completedItems.length === 0) {
          completedHTML += '<p style="color: #888; font-style: italic;">No completed manga</p>';
        } else {
          completedItems.forEach(obj => {
            const displayTitle = obj.title;

            completedHTML += `
              <div class="panel-entry">
                <img src="${obj.cover || ''}" alt="">
                <div class="info">
                  <strong>${displayTitle || 'No title'}</strong>
                  <div>${obj.chapter || ''}</div>
                  <div style="font-size: 12px; color: #888;">Last read: ${obj.lastRead ? new Date(obj.lastRead).toLocaleDateString() : 'Unknown'}</div>
                  <span>
                    <button class="asura-btn" onclick="moveCompletedToBookmarks('${obj.urlKey}')" title="Move to Bookmarks">📌</button>
                    <button class="asura-btn" onclick="moveCompletedToWant('${obj.urlKey}')" title="Move to Want to Read">📙</button>
                    <button class="asura-btn" onclick="moveCompletedToHidden('${obj.urlKey}')" title="Move to Hidden">❌</button>
                    <button class="asura-btn" onclick="removeCompletedItem('${obj.urlKey}')" title="Remove completely">Remove</button>
                  </span>
                </div>
              </div>
            `;
          });
        }

        completedHTML += '</div>';
        settingsContent.innerHTML = completedHTML;

        // Add global functions for completed item management
        window.moveCompletedToBookmarks = (urlKey) => {
          const obj = completed[urlKey];
          if (obj) {
            bookmarks[urlKey] = { ...obj, chapter: obj.chapter || 'Chapter 0', lastRead: Date.now() };
            delete completed[urlKey];
            save(bookmarkKey, bookmarks);
            save(completedKey, completed);
            updateSettingsContent('completed');
            updateTitleButtons();
          }
        };

        window.moveCompletedToWant = (urlKey) => {
          const obj = completed[urlKey];
          if (obj) {
            wantToRead[urlKey] = { ...obj };
            delete completed[urlKey];
            save(wantKey, wantToRead);
            save(completedKey, completed);
            updateSettingsContent('completed');
            updateTitleButtons();
          }
        };

        window.moveCompletedToHidden = (urlKey) => {
          const obj = completed[urlKey];
          if (obj) {
            hidden[urlKey] = { cover: obj.cover, url: obj.url, displayTitle: getDisplayTitle(obj) };
            delete completed[urlKey];
            save(hideKey, hidden);
            save(completedKey, completed);
            updateSettingsContent('completed');
            updateTitleButtons();
          }
        };

        window.removeCompletedItem = (urlKey) => {
          delete bookmarks[urlKey];
          delete wantToRead[urlKey];
          delete completed[urlKey];
          delete hidden[urlKey];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(completedKey, completed);
          save(hideKey, hidden);
          updateSettingsContent('completed');
          updateTitleButtons();
        };
      }
    }

    settingsTabs.forEach(tab => {
      tab.addEventListener('click', () => {
        settingsTabs.forEach(t => t.classList.remove('active'));
        tab.classList.add('active');
        currentSettingsTab = tab.dataset.settingsTab;
        updateSettingsContent(currentSettingsTab);
      });
    });

    updateSettingsContent(currentSettingsTab);
  }

  // --- UI CREATION ---
  function createUI() {
    const btn = document.createElement('button');
    btn.textContent = '📂 Bookmarks';
    btn.className = 'floating-panel-btn';
    document.body.appendChild(btn);

    const panel = document.createElement('div');
    panel.className = 'bookmark-panel';
    panel.innerHTML = `
      <div class="panel-tabs">
        <button class="tab-btn active" data-tab="bookmarks">📌 Bookmarks</button>
        <button class="tab-btn" data-tab="want">📙 Want to Read</button>
        <button class="tab-btn" data-tab="settings">⚙️ Settings</button>
      </div>
      <div class="panel-content"></div>
    `;
    document.body.appendChild(panel);

    const contentArea = panel.querySelector('.panel-content');
    let currentTab = 'bookmarks';
    const tabs = panel.querySelectorAll('.tab-btn');
    tabs.forEach(tab => {
      tab.addEventListener('click', () => {
        tabs.forEach(t => t.classList.remove('active'));
        tab.classList.add('active');
        currentTab = tab.dataset.tab;
        if (currentTab === 'settings') {
          updateSettingsPanel(contentArea);
        } else {
          updatePanel(contentArea, currentTab);
        }
        updateTabCounts(tabs);
      });
    });
    // Hide the panel by default on page load
    panel.style.display = 'none';
    btn.onclick = () => {
      panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
      if (panel.style.display === 'block') {
        if (currentTab === 'settings') {
          updateSettingsPanel(contentArea);
        } else {
          updatePanel(contentArea, currentTab);
        }
        updateTabCounts(tabs);
      }
    };
    updatePanel(contentArea, currentTab);
    updateTabCounts(tabs);
  }

  // --- TAB COUNT UPDATE ---
  function updateTabCounts(tabs) {
    tabs.forEach(tab => {
      const tabType = tab.dataset.tab;
      let count = 0;
      if (tabType === 'bookmarks') count = Object.keys(bookmarks).length;
      if (tabType === 'hidden') count = Object.keys(hidden).length;
      if (tabType === 'want') count = Object.keys(wantToRead).length;
      if (tabType === 'bookmarks') tab.textContent = `📌 Bookmarks - ${count}`;
      if (tabType === 'hidden') tab.textContent = `🚫 Hidden - ${count}`;
      if (tabType === 'want') tab.textContent = `📙 Want to Read - ${count}`;
      if (tabType === 'settings') tab.textContent = `⚙️ Settings`;
    });
  }

  // --- TITLE BUTTONS ---
  let debouncedUpdateTitleButtons;
  function updateTitleButtons() {
    // --- Existing grid page logic ---
    const cards = document.querySelectorAll('.col-span-9');
    cards.forEach(card => {
      const titleLink = card.querySelector('a[href^="/series/"]');
      if (!titleLink) return;
      const href = titleLink.getAttribute('href');
      const urlKey = getUrlKey(href);
      const title = extractTitleFromHref(href);
      if (!title) return;

      const container = card.closest('.grid-cols-12');
      if (hidden[urlKey]) container?.classList.add('asura-hidden');
      else container?.classList.remove('asura-hidden');
      card.querySelectorAll('.asura-btn-group').forEach(el => el.remove());

      const imgSrc = container?.querySelector('img.rounded-md.object-cover')?.src || '';
      const btnGroup = document.createElement('span');
      btnGroup.className = 'asura-btn-group';

      // Set title color based on status using URL keys
      let titleColorSet = false;

      // First try exact URL key matches
      if (completed[urlKey]) {
        titleLink.style.color = colors.completed;
        titleColorSet = true;
      } else if (wantToRead[urlKey]) {
        const isLocked = wantToRead[urlKey].locked;
        if (isLocked) {
          titleLink.style.color = colors.wantToRead;
        } else {
          titleLink.style.color = colors.defaultTitle;
        }
        titleColorSet = true;
      } else if (bookmarks[urlKey]) {
        titleLink.style.color = colors.bookmarked;
        titleColorSet = true;
      }

      // If not found by URL key, try fallback matching
      if (!titleColorSet) {
        const foundCompletedKey = findMatchingKey(title, completed);
        const foundWantKey = findMatchingKey(title, wantToRead);
        const foundBookmarkKey = findMatchingKey(title, bookmarks);

        if (foundCompletedKey) {
          titleLink.style.color = colors.completed;
        } else if (foundWantKey) {
          const isLocked = wantToRead[foundWantKey].locked;
          if (isLocked) {
            titleLink.style.color = colors.wantToRead;
          } else {
            titleLink.style.color = colors.defaultTitle;
          }
        } else if (foundBookmarkKey) {
          titleLink.style.color = colors.bookmarked;
        } else {
          titleLink.style.color = colors.defaultTitle;
        }
      }

      // Check for buttons based on URL key or fallback
      const isBookmarked = bookmarks[urlKey] || findMatchingKey(title, bookmarks);
      const wantEntry = wantToRead[urlKey] || (findMatchingKey(title, wantToRead) ? wantToRead[findMatchingKey(title, wantToRead)] : null);

      // Bookmarked 📌
      if (isBookmarked) {
        const pinBtn = document.createElement('button');
        pinBtn.className = 'asura-btn';
        pinBtn.textContent = '📌';
        pinBtn.title = 'Marked as read';
        pinBtn.onclick = (e) => {
          e.preventDefault();
          // Remove from both URL key and any found fallback key
          delete bookmarks[urlKey];
          const fallbackKey = findMatchingKey(title, bookmarks);
          if (fallbackKey) delete bookmarks[fallbackKey];

          // Also remove any old hash-based keys for this title
          Object.keys(bookmarks).forEach(key => {
            const bookmarkTitle = bookmarks[key].displayTitle || bookmarks[key].title;
            if (bookmarkTitle && bookmarkTitle.toLowerCase().replace(/[^\w\s]/g, '').replace(/\s+/g, ' ').trim() ===
                title.toLowerCase().replace(/[^\w\s]/g, '').replace(/\s+/g, ' ').trim()) {
              delete bookmarks[key];
            }
          });

          save(bookmarkKey, bookmarks);
          setTimeout(updateTitleButtons, 0);
        };
        btnGroup.appendChild(pinBtn);
      } else {
        const isWantLocked = wantEntry && wantEntry.locked;
        // 📙 Move to Want to Read
        const wantBtn = document.createElement('button');
        wantBtn.className = 'asura-btn';
        wantBtn.textContent = '📙';
        wantBtn.title = 'Want to read';
        wantBtn.onclick = (e) => {
          e.preventDefault();
          if (isWantLocked) {
            // Enhanced removal - find and remove ALL matching keys
            const allKeys = findAllMatchingKeys(title, wantToRead);
            allKeys.forEach(key => delete wantToRead[key]);
          } else {
            // Remove from bookmarks if present
            delete bookmarks[urlKey];
            const fallbackBookmarkKey = findMatchingKey(title, bookmarks);
            if (fallbackBookmarkKey) delete bookmarks[fallbackBookmarkKey];

            wantToRead[urlKey] = {
              title: title,
              displayTitle: title,
              chapter: 'Chapter 0',
              url: href,
              cover: imgSrc,
              locked: true,
              removable: true  // New items are removable by default
            };
          }
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          setTimeout(updateTitleButtons, 0);
        };
        btnGroup.appendChild(wantBtn);
        if (!isWantLocked) {
          // 📍 Mark as Read
          const markBtn = document.createElement('button');
          markBtn.className = 'asura-btn';
          markBtn.textContent = '📍';
          markBtn.title = 'Mark as read';
          markBtn.onclick = (e) => {
            e.preventDefault();
            // Remove from want to read if present
            delete wantToRead[urlKey];
            const fallbackWantKey = findMatchingKey(title, wantToRead);
            if (fallbackWantKey) delete wantToRead[fallbackWantKey];

            bookmarks[urlKey] = {
              title: title,
              displayTitle: title,
              chapter: 'Chapter 0',
              url: href,
              cover: imgSrc,
              lastRead: Date.now()
            };
            save(bookmarkKey, bookmarks);
            save(wantKey, wantToRead);
            setTimeout(updateTitleButtons, 0);
          };
          btnGroup.appendChild(markBtn);
          // ❌ Hide
          const hideBtn = document.createElement('button');
          hideBtn.className = 'asura-btn';
          hideBtn.textContent = '❌';
          hideBtn.title = 'Hide comic';
          hideBtn.onclick = (e) => {
            e.preventDefault();
            hidden[urlKey] = {
              cover: imgSrc,
              url: href,
              displayTitle: title
            };
            save(hideKey, hidden);
            setTimeout(updateTitleButtons, 0);
          };
          btnGroup.appendChild(hideBtn);
        }
      }
      titleLink.parentElement.appendChild(btnGroup);

      // --- Chapter Highlighting ---
      let bookmarkedChapterRaw = '';
      let bookmarkedNum = null;

      // Try URL key first, then fallback to title matching
      if (bookmarks[urlKey]?.chapter) {
        bookmarkedChapterRaw = bookmarks[urlKey].chapter;
      } else {
        // Fallback: try to find bookmark by title matching
        const fallbackKey = findMatchingKey(title, bookmarks);
        if (fallbackKey && bookmarks[fallbackKey]?.chapter) {
          bookmarkedChapterRaw = bookmarks[fallbackKey].chapter;
        }
      }

      if (bookmarkedChapterRaw) {
        const bookmarkedMatch = bookmarkedChapterRaw.match(/(\d+(?:\.\d+)?)/);
        if (bookmarkedMatch) bookmarkedNum = parseFloat(bookmarkedMatch[1]);
      }

      const chapterLinks = card.querySelectorAll('a[href*="/chapter/"]');
      chapterLinks.forEach(chapLink => {
        // Try to get chapter number from <p> inside the link, then fallback to text, then URL
        let chapterNum = null;
        let chapterText = '';
        const p = chapLink.querySelector('p');
        if (p && p.textContent) {
          chapterText = p.textContent.trim();
        } else {
          // Try to find any text node with a number
          const walker = document.createTreeWalker(chapLink, NodeFilter.SHOW_TEXT, null);
          let node;
          while ((node = walker.nextNode())) {
            if (/\d/.test(node.textContent)) {
              chapterText = node.textContent.trim();
              break;
            }
          }
          if (!chapterText && chapLink.textContent) {
            chapterText = chapLink.textContent.trim();
          }
        }
        chapterText = chapterText.replace(/,/g, '').replace(/\s+/g, ' ');
        let match = chapterText.match(/(\d+(?:\.\d+)?)/);
        if (match) {
          chapterNum = parseFloat(match[1]);
        } else {
          const chapterHref = chapLink.getAttribute('href');
          const urlMatch = chapterHref.match(/chapter\/([\d.]+)/i);
          if (urlMatch) chapterNum = parseFloat(urlMatch[1]);
        }
        chapLink.classList.remove('chapter-bookmarked', 'chapter-unread', 'chapter-read');
        // Debug output
        // console.log('Chapter link:', chapLink, 'chapterNum:', chapterNum, 'bookmarkedNum:', bookmarkedNum);
        if (bookmarkedNum !== null && chapterNum !== null) {
          if (chapterNum === bookmarkedNum) {
            chapLink.classList.add('chapter-bookmarked'); // Purple (last read)
            // console.log('Applied: chapter-bookmarked');
          } else if (chapterNum > bookmarkedNum) {
            chapLink.classList.add('chapter-unread'); // Yellow (unread/new)
            // console.log('Applied: chapter-unread');
          }
        }
        // Save on middle or left click
        const saveClick = () => {
          const urlKey = getUrlKey(chapLink.getAttribute('href'));
          const displayTitle = extractTitleFromHref(chapLink.getAttribute('href')) || title;

          // Clean chapter text - extract only "Chapter X" format
          let cleanChapterText = chapterText;
          const chapterMatch = chapterText.match(/Chapter\s*(\d+(?:\.\d+)?)/i);
          if (chapterMatch) {
            cleanChapterText = `Chapter ${chapterMatch[1]}`;
          }

          // Add to top when saving new chapter progress
          const newBookmarkEntry = {
            ...(bookmarks[urlKey] || {}),
            title: displayTitle,
            displayTitle,
            chapter: cleanChapterText,
            url: chapLink.getAttribute('href'),
            cover: bookmarks[urlKey]?.cover || imgSrc || '',
            lastRead: Date.now()
          };

          delete bookmarks[urlKey];
          bookmarks = { [urlKey]: newBookmarkEntry, ...bookmarks };
          save(bookmarkKey, bookmarks);
          debouncedUpdateTitleButtons();
        };
        chapLink.addEventListener('auxclick', e => { if (e.button === 1) saveClick(); });
        chapLink.addEventListener('click', e => { if (e.button === 0) saveClick(); });
      });
    });

    // --- Simplified /series/ page logic ---
    if (location.pathname.startsWith('/series/')) {
      // Remove any previously injected button group
      const prevBtnGroup = document.querySelector('.asura-series-btn-group');
      if (prevBtnGroup) prevBtnGroup.remove();

      // Find the title element
      let titleHeader =
        document.querySelector('h1, h2, .font-bold.text-3xl, .font-bold.text-2xl, .font-bold.text-xl') ||
        document.querySelector('.text-xl.font-bold');
      if (!titleHeader) {
        const alt = document.querySelector('.text-center.sm\\:text-left .text-xl.font-bold');
        if (alt) titleHeader = alt;
      }
      if (!titleHeader) return;

      // Get URL key for this series page
      const urlKey = getUrlKey(location.pathname);

      // Get title
      let pageTitle = titleHeader.textContent?.trim() || '';

      // If title contains "Chapter", use only the last word (the actual title)
      if (/^Chapter\s+/i.test(pageTitle)) {
        pageTitle = pageTitle.replace(/^Chapter\s+/i, '').trim();
      }
      // If title contains multiple lines, use only the first non-empty line
      if (pageTitle.includes('\n')) {
        pageTitle = pageTitle.split('\n').map(l => l.trim()).filter(Boolean)[0] || pageTitle;
      }
      // Remove any trailing hex if present (for consistency)
      pageTitle = pageTitle.replace(/-\w{6,}$/, '');

      // If we still don't have a good title, extract from URL as fallback
      if (!pageTitle || pageTitle.length < 3) {
        const urlTitle = extractTitleFromHref(location.pathname);
        if (urlTitle) {
          pageTitle = urlTitle;
        }
      }

      // --- Set title color based on status using URL keys ---
      if (completed[urlKey]) {
        titleHeader.style.color = colors.completed;
      } else if (wantToRead[urlKey]) {
        titleHeader.style.color = colors.wantToRead;
      } else if (bookmarks[urlKey]) {
        titleHeader.style.color = colors.bookmarked;
      } else {
        titleHeader.style.color = colors.defaultTitle;
      }

      // --- Chapter highlighting and save ---
      const chapterGroups = document.querySelectorAll('.group.w-full');
      let bookmarkedChapterRaw = '';
      let bookmarkedNum = null;

      // Try URL key first, then fallback to title matching
      if (bookmarks[urlKey]?.chapter) {
        bookmarkedChapterRaw = bookmarks[urlKey].chapter;
      } else {
        // Fallback: try to find bookmark by title matching
        const fallbackKey = findMatchingKey(pageTitle, bookmarks);
        if (fallbackKey && bookmarks[fallbackKey]?.chapter) {
          bookmarkedChapterRaw = bookmarks[fallbackKey].chapter;
        }
      }

      if (bookmarkedChapterRaw) {
        const bookmarkedMatch = bookmarkedChapterRaw.match(/(\d+(?:\.\d+)?)/);
        if (bookmarkedMatch) bookmarkedNum = parseFloat(bookmarkedMatch[1]);
      }

      chapterGroups.forEach(groupDiv => {
        const chapLink = groupDiv.querySelector('a[href*="/chapter/"]');
        if (!chapLink) return;
        let chapterNum = null;
        let chapterText = '';
        // Try to get chapter number from h3
        const h3s = chapLink.querySelectorAll('h3');
        for (const h3 of h3s) {
          const match = h3.textContent.match(/Chapter\s*(\d+(?:\.\d+)?)/i);
          if (match) {
            chapterNum = parseFloat(match[1]);
            // Clean chapter text - only keep "Chapter X" format
            chapterText = `Chapter ${match[1]}`;
            break;
          }
        }
        if (!chapterNum) {
          // fallback: try to extract from href
          const chapterHref = chapLink.getAttribute('href');
          const urlMatch = chapterHref.match(/chapter\/([\d.]+)/i);
          if (urlMatch) chapterNum = parseFloat(urlMatch[1]);
        }
        // Remove old classes
        groupDiv.classList.remove('chapter-bookmarked', 'chapter-unread');
        chapLink.classList.remove('chapter-bookmarked', 'chapter-unread');
        // Apply color classes to the group div and the link
        if (bookmarkedNum !== null && chapterNum !== null) {
          if (chapterNum === bookmarkedNum) {
            groupDiv.classList.add('chapter-bookmarked');
            chapLink.classList.add('chapter-bookmarked');
          } else if (chapterNum > bookmarkedNum) {
            groupDiv.classList.add('chapter-unread');
            chapLink.classList.add('chapter-unread');
          }
        }
        // Save on middle or left click
        const saveClick = () => {
          const urlKey = getUrlKey(location.pathname);
          const displayTitle = extractTitleFromHref(location.pathname) || pageTitle;

          // Remove from wantToRead if present before adding to bookmarks
          if (wantToRead[urlKey]) {
            delete wantToRead[urlKey];
            save(wantKey, wantToRead);
          }

          // Try to get a cover image if missing
          let cover = bookmarks[urlKey]?.cover;
          if (!cover) {
            const posterImg = document.querySelector('img[alt="poster"].rounded.mx-auto.md\\:mx-0') ||
                              document.querySelector('img[alt="poster"].rounded.mx-auto') ||
                              document.querySelector('img[alt="poster"]');
            cover = posterImg?.src || '';
          }

          // Add to top when saving new chapter progress
          const newBookmarkEntry = {
            ...(bookmarks[urlKey] || {}),
            title: displayTitle,
            displayTitle,
            chapter: chapterText,
            url: location.pathname,
            cover,
            lastRead: Date.now()
          };

          delete bookmarks[urlKey];
          bookmarks = { [urlKey]: newBookmarkEntry, ...bookmarks };
          save(bookmarkKey, bookmarks);
          debouncedUpdateTitleButtons();
        };
        chapLink.addEventListener('auxclick', e => { if (e.button === 1) saveClick(); });
        chapLink.addEventListener('click', e => { if (e.button === 0) saveClick(); });
      });
    }
  }
  debouncedUpdateTitleButtons = debounce(updateTitleButtons, 200);

  // --- MIGRATION FUNCTION ---
  // Enhanced migration function with proper duplicate handling
  let migrationCompleted = false;
  function migrateToUrlKeys() {
    if (migrationCompleted) return;

    try {
      let migrated = false;

      // Migrate bookmarks with duplicate consolidation
      const newBookmarks = {};
      const titleTracker = new Map(); // Track by clean title to merge duplicates

      for (const [key, obj] of Object.entries(bookmarks)) {
        if (obj.url && !key.startsWith('/series/')) {
          const urlKey = getUrlKey(obj.url);
          if (urlKey !== key) {
            const cleanTitle = obj.displayTitle || extractTitleFromHref(obj.url) || obj.title;

            // Check if we already have this title
            if (titleTracker.has(cleanTitle)) {
              const existingKey = titleTracker.get(cleanTitle);
              const existing = newBookmarks[existingKey];

              // Keep the entry with newer lastRead
              if ((obj.lastRead || 0) > (existing.lastRead || 0)) {
                delete newBookmarks[existingKey];
                newBookmarks[urlKey] = {
                  ...obj,
                  displayTitle: cleanTitle
                };
                titleTracker.set(cleanTitle, urlKey);
              }
              // If existing is newer, skip this duplicate
            } else {
              newBookmarks[urlKey] = {
                ...obj,
                displayTitle: cleanTitle
              };
              titleTracker.set(cleanTitle, urlKey);
            }
            migrated = true;
          } else {
            const cleanTitle = obj.displayTitle || obj.title;
            if (titleTracker.has(cleanTitle)) {
              const existingKey = titleTracker.get(cleanTitle);
              const existing = newBookmarks[existingKey];

              if ((obj.lastRead || 0) > (existing.lastRead || 0)) {
                delete newBookmarks[existingKey];
                newBookmarks[key] = obj;
                titleTracker.set(cleanTitle, key);
              }
            } else {
              newBookmarks[key] = obj;
              titleTracker.set(cleanTitle, key);
            }
          }
        } else {
          // Handle existing URL-based keys, check for duplicates
          const cleanTitle = obj.displayTitle || obj.title;
          const normalizedKey = getUrlKey(key); // Normalize the key

          if (titleTracker.has(cleanTitle)) {
            const existingKey = titleTracker.get(cleanTitle);
            const existing = newBookmarks[existingKey];

            if ((obj.lastRead || 0) > (existing.lastRead || 0)) {
              delete newBookmarks[existingKey];
              newBookmarks[normalizedKey] = obj;
              titleTracker.set(cleanTitle, normalizedKey);
              migrated = true;
            }
          } else {
            if (normalizedKey !== key) {
              newBookmarks[normalizedKey] = obj;
              migrated = true;
            } else {
              newBookmarks[key] = obj;
            }
            titleTracker.set(cleanTitle, normalizedKey !== key ? normalizedKey : key);
          }
        }
      }

      if (migrated) {
        bookmarks = newBookmarks;
        save(bookmarkKey, bookmarks);
      }

      // Migrate wantToRead
      migrated = false;
      const newWantToRead = {};
      for (const [key, obj] of Object.entries(wantToRead)) {
        if (obj.url && !key.startsWith('/series/')) {
          const urlKey = getUrlKey(obj.url);
          if (urlKey !== key) {
            newWantToRead[urlKey] = {
              ...obj,
              displayTitle: obj.displayTitle || extractTitleFromHref(obj.url) || obj.title
            };
            migrated = true;
          } else {
            newWantToRead[key] = obj;
          }
        } else {
          newWantToRead[key] = obj;
        }
      }
      if (migrated) {
        wantToRead = newWantToRead;
        save(wantKey, wantToRead);
      }

      // Migrate completed
      migrated = false;
      const newCompleted = {};
      for (const [key, obj] of Object.entries(completed)) {
        if (obj.url && !key.startsWith('/series/')) {
          const urlKey = getUrlKey(obj.url);
          if (urlKey !== key) {
            newCompleted[urlKey] = {
              ...obj,
              displayTitle: obj.displayTitle || extractTitleFromHref(obj.url) || obj.title
            };
            migrated = true;
          } else {
            newCompleted[key] = obj;
          }
        } else {
          newCompleted[key] = obj;
        }
      }
      if (migrated) {
        completed = newCompleted;
        save(completedKey, completed);
      }

      // Migrate hidden (special handling since they may not have URLs)
      migrated = false;
      const newHidden = {};
      for (const [key, obj] of Object.entries(hidden)) {
        if (obj.url && !key.startsWith('/series/')) {
          const urlKey = getUrlKey(obj.url);
          if (urlKey !== key) {
            newHidden[urlKey] = {
              ...obj,
              displayTitle: obj.displayTitle || extractTitleFromHref(obj.url) || key
            };
            migrated = true;
          } else {
            newHidden[key] = obj;
          }
        } else if (!key.startsWith('/series/')) {
          // For hidden items without URLs, try to find a matching URL from other data
          let foundUrl = null;
          for (const data of [bookmarks, wantToRead, completed]) {
            for (const [urlKey, dataObj] of Object.entries(data)) {
              if (dataObj.displayTitle === key || dataObj.title === key) {
                foundUrl = dataObj.url || urlKey;
                break;
              }
            }
            if (foundUrl) break;
          }

          if (foundUrl && foundUrl.startsWith('/series/')) {
            const urlKey = getUrlKey(foundUrl);
            newHidden[urlKey] = {
              ...obj,
              url: foundUrl,
              displayTitle: key
            };
            migrated = true;
          } else {
            // Keep as-is if no URL found
            newHidden[key] = obj;
          }
        } else {
          newHidden[key] = obj;
        }
      }
      if (migrated) {
        hidden = newHidden;
        save(hideKey, hidden);
      }
      migrationCompleted = true;
      localStorage.setItem('asuraMigrationCompleted', 'true');
    } catch (error) {
      console.error('Migration failed:', error);
    }
  }

  // Add cleanup for global functions
  function cleanupGlobalFunctions() {
    delete window.unhideItem;
    delete window.moveToWant;
    delete window.removeItem;
    delete window.moveCompletedToBookmarks;
    delete window.moveCompletedToWant;
    delete window.moveCompletedToHidden;
    delete window.removeCompletedItem;
  }

  // --- INITIALIZATION ---
  function waitForContent() {
    // Check if migration was already completed
    migrationCompleted = localStorage.getItem('asuraMigrationCompleted') === 'true';

    const observer = new MutationObserver((_, obs) => {
      if (document.querySelector('.grid-cols-12') || location.pathname.startsWith('/series/')) {
        obs.disconnect();

        // Cleanup any existing global functions
        cleanupGlobalFunctions();

        if (location.pathname.startsWith('/series/')) {
          document.body.setAttribute('data-series-page', 'true');
        } else {
          document.body.removeAttribute('data-series-page');
        }
        loadColors();
        migrateToUrlKeys(); // Migrate existing data to URL-based keys

        // Add removable property to existing items that don't have it
        [bookmarks, wantToRead, completed, hidden].forEach(dataSet => {
          Object.values(dataSet).forEach(obj => {
            if (obj.removable === undefined) {
              obj.removable = true; // Default to removable
            }
          });
        });

        // Save the updated data with removable properties
        save(bookmarkKey, bookmarks);
        save(wantKey, wantToRead);
        save(completedKey, completed);
        save(hideKey, hidden);

        createUI();
        updateTitleButtons();
      }
    });
    observer.observe(document.body, { childList: true, subtree: true });
  }

  waitForContent();
})();