Greasy Fork

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      2.1
// @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 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);

  // --- 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
    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: 20px;
      background-color: #4b0082; color: white;
      padding: 10px 14px; border-radius: 8px;
      z-index: 9999; border: none; cursor: pointer;
    }

    /* Bookmark panel */
    .bookmark-panel {
      position: fixed; top: 60px; right: 40px; width: 600px;
      background: #1a1a1a; color: #fff; border: 1px solid #4b0082;
      border-radius: 10px; padding: 10px; z-index: 9999;
      display: none; max-height: 80vh; 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(' ');
  }

  function findMatchingKey(searchTitle, dataObject) {
    const normalizedSearch = searchTitle.toLowerCase()
      .replace(/[''`]/g, '')
      .replace(/\s+/g, ' ')
      .replace(/[^\w\s]/g, '')
      .trim();

    for (const key in dataObject) {
      const normalizedKey = key.toLowerCase()
        .replace(/[''`]/g, '')
        .replace(/\s+/g, ' ')
        .replace(/[^\w\s]/g, '')
        .trim();

      if (normalizedKey === normalizedSearch ||
          normalizedKey.includes(normalizedSearch) ||
          normalizedSearch.includes(normalizedKey)) {
        return key;
      }
    }
    return null;
  }

  // --- PANEL RENDERING ---
  function updatePanel(container, tab) {
    // Patch legacy bookmarks: ensure each has a chapter (default "Chapter 0" if missing)
    let patched = false;
    for (const key in bookmarks) {
      if (!bookmarks[key].chapter || !bookmarks[key].chapter.trim()) {
        bookmarks[key].chapter = 'Chapter 0';
        patched = true;
      }
    }
    if (patched) save(bookmarkKey, bookmarks);

    container.innerHTML = '';
    let items = [];

    if (tab === 'bookmarks') {
      // Merge duplicate bookmarks by normalized title
      const merged = {};
      Object.values(bookmarks).forEach(obj => {
        let norm = (obj.title || '').replace(/ASURA\+Premium\s*/i, '').replace(/\s+/g, ' ').trim();
        norm = norm.replace(/-\w{6,}$/, '').toLowerCase();
        if (!norm) return;
        if (!merged[norm]) {
          merged[norm] = { ...obj };
        } else {
          if ((obj.lastRead || 0) > (merged[norm].lastRead || 0)) merged[norm] = { ...obj };
          if (!merged[norm].cover && obj.cover) merged[norm].cover = obj.cover;
        }
      });
      items = Object.values(merged).sort((a, b) => (b.lastRead || 0) - (a.lastRead || 0));
    } else if (tab === 'want') {
      items = Object.values(wantToRead);
    } else if (tab === 'hidden') {
      items = Object.entries(hidden).map(([title, obj]) => ({ title, chapter: '', url: '', cover: obj.cover || '' }));
    }

    items.forEach(obj => {
      let cleanTitle = (obj.title || '').replace(/\s+/g, ' ').replace(/[\r\n]+/g, '').trim();
      cleanTitle = cleanTitle.replace(/-\w{6,}$/, '').replace(/^ASURA\+Premium\s*/i, '').trim();

      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 = cleanTitle || 'No title';

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

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

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

      // 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[cleanTitle] = { ...obj, title: cleanTitle };
          delete bookmarks[cleanTitle];
          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[cleanTitle] = { cover: obj.cover };
          delete bookmarks[cleanTitle];
          save(bookmarkKey, bookmarks);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(hideBtn);

        // Remove completely
        const removeBtn = document.createElement('button');
        removeBtn.className = 'asura-btn';
        removeBtn.textContent = 'Remove';
        removeBtn.title = 'Remove from all lists';
        removeBtn.onclick = () => {
          delete bookmarks[cleanTitle];
          delete wantToRead[cleanTitle];
          delete hidden[cleanTitle];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          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[cleanTitle] = { ...obj, title: cleanTitle, chapter: obj.chapter || 'Chapter 0' };
          delete hidden[cleanTitle];
          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[cleanTitle] = { ...obj, title: cleanTitle };
          delete hidden[cleanTitle];
          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.onclick = () => {
          delete bookmarks[cleanTitle];
          delete wantToRead[cleanTitle];
          delete hidden[cleanTitle];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(hideKey, hidden);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(removeBtn);
      }
      // Want to Read tab: 📌❌
      else if (tab === 'want') {
        // 📌 Move to Bookmarks
        const pinBtn = document.createElement('button');
        pinBtn.className = 'asura-btn';
        pinBtn.textContent = '📌';
        pinBtn.title = 'Move to Bookmarks';
        pinBtn.onclick = () => {
          bookmarks[cleanTitle] = { ...obj, title: cleanTitle, chapter: obj.chapter || 'Chapter 0' };
          delete wantToRead[cleanTitle];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          updatePanel(container, tab);
          updateTitleButtons();
        };
        btnGroup.appendChild(pinBtn);

        // ❌ Move to Hidden
        const hideBtn = document.createElement('button');
        hideBtn.className = 'asura-btn';
        hideBtn.textContent = '❌';
        hideBtn.title = 'Move to Hidden';
        hideBtn.onclick = () => {
          hidden[cleanTitle] = { cover: obj.cover };
          delete wantToRead[cleanTitle];
          save(hideKey, hidden);
          save(wantKey, wantToRead);
          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.onclick = () => {
          delete bookmarks[cleanTitle];
          delete wantToRead[cleanTitle];
          delete hidden[cleanTitle];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          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="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),
            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 hidden
              if (importedData.hidden) {
                const currentHidden = load(hideKey);
                Object.assign(currentHidden, importedData.hidden);
                save(hideKey, currentHidden);
              }

              // Reload data
              bookmarks = load(bookmarkKey);
              wantToRead = load(wantKey);
              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(hideKey);
            bookmarks = {};
            wantToRead = {};
            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>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="reset-colors-btn" style="background: #dc2626; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer;">
              🔄 Reset to Default Colors
            </button>
          </div>
        `;

        // Fix: Always update both color and text inputs and colors object on change
        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 value
            colorInput.addEventListener('input', (e) => {
              const newColor = e.target.value;
              textInput.value = newColor;
              colors[colorKey] = newColor;
              saveColors();
              updateTitleButtons();
            });
            // Text input changes color and value
            textInput.addEventListener('input', (e) => {
              const newColor = e.target.value;
              if (/^#([0-9A-F]{3}){1,2}$/i.test(newColor)) {
                colorInput.value = newColor;
                colors[colorKey] = newColor;
                saveColors();
                updateTitleButtons();
              }
            });
          }
        });

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

      } else if (tab === 'hidden') {
        const hiddenItems = Object.entries(hidden).map(([title, obj]) => ({ title, chapter: '', 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 => {
            let cleanTitle = (obj.title || '').replace(/\s+/g, ' ').replace(/[\r\n]+/g, '').trim();
            cleanTitle = cleanTitle.replace(/-\w{6,}$/, '').replace(/^ASURA\+Premium\s*/i, '').trim();

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

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

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

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

        window.removeItem = (title) => {
          delete bookmarks[title];
          delete wantToRead[title];
          delete hidden[title];
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          save(hideKey, hidden);
          updateSettingsContent('hidden');
          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 title = extractTitleFromHref(href);
      if (!title) return;

      // Remove or comment out the debug log to avoid console spam
      // console.log('Main page title:', title);

      const container = card.closest('.grid-cols-12');
      if (hidden[title]) container?.classList.add('asura-hidden');
      else container?.classList.remove('asura-hidden');
      card.querySelectorAll('.asura-btn-group').forEach(el => el.remove());
      // Use old logic for cover image on non-/series/ pages
      const imgSrc = container?.querySelector('img.rounded-md.object-cover')?.src || '';
      const btnGroup = document.createElement('span');
      btnGroup.className = 'asura-btn-group';

      // --- Enhanced fuzzy matching function ---
      function findMatchingKey(searchTitle, dataObject) {
        const normalizedSearch = searchTitle.toLowerCase()
          .replace(/[''`]/g, '') // Remove all types of apostrophes
          .replace(/\s+/g, ' ')
          .replace(/[^\w\s]/g, '') // Remove special characters except spaces
          .trim();

        for (const key in dataObject) {
          const normalizedKey = key.toLowerCase()
            .replace(/[''`]/g, '') // Remove all types of apostrophes
            .replace(/\s+/g, ' ')
            .replace(/[^\w\s]/g, '') // Remove special characters except spaces
            .trim();

          if (normalizedKey === normalizedSearch ||
              normalizedKey.includes(normalizedSearch) ||
              normalizedSearch.includes(normalizedKey)) {
            return key;
          }
        }
        return null;
      }

      // Set title color based on status using user-selected colors
      const matchingBookmarkKey = findMatchingKey(title, bookmarks);
      const matchingWantKey = findMatchingKey(title, wantToRead);

      if (matchingWantKey) {
        const isLocked = wantToRead[matchingWantKey].locked;
        if (isLocked) {
          titleLink.style.color = colors.wantToRead; // use user color for want to read
        } else {
          titleLink.style.color = colors.defaultTitle; // use user color for default
        }
      } else if (matchingBookmarkKey) {
        titleLink.style.color = colors.bookmarked; // use user color for bookmarked
      } else {
        titleLink.style.color = colors.defaultTitle; // use user color for default
      }

      // Bookmarked 📌
      if (matchingBookmarkKey) {
        const pinBtn = document.createElement('button');
        pinBtn.className = 'asura-btn';
        pinBtn.textContent = '📌';
        pinBtn.title = 'Marked as read';
        pinBtn.onclick = (e) => {
          e.preventDefault();
          delete bookmarks[matchingBookmarkKey];
          save(bookmarkKey, bookmarks);
          setTimeout(updateTitleButtons, 0); // Force full refresh
        };
        btnGroup.appendChild(pinBtn);
      } else {
        const isWantLocked = matchingWantKey && wantToRead[matchingWantKey].locked;
        // 📙 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) {
            delete wantToRead[matchingWantKey];
          } else {
            if (matchingBookmarkKey) delete bookmarks[matchingBookmarkKey];
            // Use existing matching title if found, otherwise use current title
            const titleToUse = findMatchingKey(title, wantToRead) || title;
            wantToRead[titleToUse] = { title: titleToUse, chapter: 'Chapter 0', url: href, cover: imgSrc, locked: true };
          }
          save(bookmarkKey, bookmarks);
          save(wantKey, wantToRead);
          setTimeout(updateTitleButtons, 0); // Force full refresh
        };
        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();
            if (matchingWantKey) delete wantToRead[matchingWantKey];
            // Use existing matching title if found, otherwise use current title
            const titleToUse = findMatchingKey(title, bookmarks) || title;
            bookmarks[titleToUse] = { title: titleToUse, chapter: 'Chapter 0', url: href, cover: imgSrc };
            save(bookmarkKey, bookmarks);
            save(wantKey, wantToRead);
            setTimeout(updateTitleButtons, 0); // Force full refresh
          };
          btnGroup.appendChild(markBtn);
          // ❌ Hide
          const hideBtn = document.createElement('button');
          hideBtn.className = 'asura-btn';
          hideBtn.textContent = '❌';
          hideBtn.title = 'Hide comic';
          hideBtn.onclick = (e) => {
            e.preventDefault();
            // Use existing matching title if found, otherwise use current title
            const titleToUse = findMatchingKey(title, hidden) || title;
            hidden[titleToUse] = { cover: imgSrc };
            save(hideKey, hidden);
            setTimeout(updateTitleButtons, 0); // Force full refresh
          };
          btnGroup.appendChild(hideBtn);
        }
      }
      titleLink.parentElement.appendChild(btnGroup);
      // --- Chapter Highlighting (last read = purple, above = yellow, below = red) ---
      const bookmarkedChapterRaw = (matchingBookmarkKey ? bookmarks[matchingBookmarkKey]?.chapter : '') || '';

      let bookmarkedNum = null;
      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 = () => {
          // Use enhanced fuzzy matching for chapter saves
          let matchingKey = findMatchingKey(title, bookmarks) || title;

          console.log('Main page saveClick - looking for:', title);
          console.log('Found matching key:', matchingKey);

          // 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[matchingKey] || { title: matchingKey }),
            title: matchingKey,
            chapter: cleanChapterText,
            url: chapLink.getAttribute('href'),
            cover: bookmarks[matchingKey]?.cover || imgSrc || '',
            lastRead: Date.now()
          };

          // Remove existing entry and add to top
          delete bookmarks[matchingKey];
          bookmarks = { [matchingKey]: 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 or color
      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 title
      let pageTitle = titleHeader.textContent?.trim() || '';

      // If the title is just "ASURA+Premium", try to find the actual manga title
      if (pageTitle === 'ASURA+Premium' || pageTitle.startsWith('ASURA+Premium')) {
        // Try alternative selectors for the actual manga title
        const altTitleSelectors = [
          'h1.text-3xl.font-bold',
          'h1.text-2xl.font-bold',
          '.text-3xl.font-bold',
          '.text-2xl.font-bold',
          'h1[class*="font-bold"]',
          'h2[class*="font-bold"]'
        ];

        for (const selector of altTitleSelectors) {
          const altTitle = document.querySelector(selector);
          if (altTitle && altTitle.textContent?.trim() !== 'ASURA+Premium' && altTitle.textContent?.trim()) {
            titleHeader = altTitle;
            pageTitle = altTitle.textContent.trim();
            break;
          }
        }

        // If still "ASURA+Premium", try extracting from URL
        if (pageTitle === 'ASURA+Premium' || pageTitle.startsWith('ASURA+Premium')) {
          const urlTitle = extractTitleFromHref(location.pathname);
          if (urlTitle) {
            pageTitle = urlTitle;
          }
        }
      }

      // 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 (e.g. "ASURA+Premium\nChapter 283"), 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,}$/, '');

      // DEBUG: Add this to see what titles are generated
      // console.log('Series page title:', pageTitle);

      // Find best matching bookmark key using enhanced fuzzy matching
      let bookmarkKeyName = findMatchingKey(pageTitle, bookmarks) || pageTitle;

      // console.log('Bookmark key name:', bookmarkKeyName);

      // Get canonical series URL
      let seriesUrl = location.pathname;
      const canonicalLink = document.querySelector('link[rel="canonical"]');
      if (canonicalLink) {
        seriesUrl = canonicalLink.getAttribute('href') || seriesUrl;
      }

      // Get cover image from poster
      const coverImg =
        document.querySelector('img[alt="poster"].rounded.mx-auto.md\\:mx-0')?.src ||
        document.querySelector('img[alt="poster"].rounded.mx-auto')?.src ||
        document.querySelector('img[alt="poster"]')?.src ||
        '';

      // Remove any previous button group
      if (titleHeader.parentElement.querySelector('.asura-series-btn-group')) {
        titleHeader.parentElement.querySelector('.asura-series-btn-group').remove();
      }
      const btnGroup = document.createElement('span');
      btnGroup.className = 'asura-series-btn-group';
      btnGroup.style.marginLeft = '10px';

      // 📙 Want to Read button
      const wantBtn = document.createElement('button');
      wantBtn.className = 'asura-btn';
      wantBtn.textContent = '📙';
      wantBtn.title = wantToRead[bookmarkKeyName] ? 'Remove Want to Read' : 'Want to Read';
      wantBtn.onclick = (e) => {
        e.preventDefault();
        if (wantToRead[bookmarkKeyName]) {
          delete wantToRead[bookmarkKeyName];
        } else {
          // Use existing matching title if found, otherwise use bookmarkKeyName
          const titleToUse = findMatchingKey(pageTitle, wantToRead) || bookmarkKeyName;
          wantToRead[titleToUse] = { title: titleToUse, chapter: 'Chapter 0', url: seriesUrl, cover: coverImg };
        }
        save(wantKey, wantToRead);
        updateTitleButtons(); // immediate update for color
      };
      btnGroup.appendChild(wantBtn);

      // 📌 Bookmark button
      const pinBtn = document.createElement('button');
      pinBtn.className = 'asura-btn';
      pinBtn.textContent = '📌';
      pinBtn.title = bookmarks[bookmarkKeyName] ? 'Remove Bookmark' : 'Bookmark';
      pinBtn.onclick = (e) => {
        e.preventDefault();
        if (bookmarks[bookmarkKeyName]) {
          delete bookmarks[bookmarkKeyName];
        } else {
          // Use existing matching title if found, otherwise use bookmarkKeyName
          const titleToUse = findMatchingKey(pageTitle, bookmarks) || bookmarkKeyName;
          bookmarks[titleToUse] = { title: titleToUse, chapter: 'Chapter 0', url: seriesUrl, cover: coverImg };
        }
        save(bookmarkKey, bookmarks);
        updateTitleButtons(); // immediate update for color
      };
      btnGroup.appendChild(pinBtn);

      titleHeader.parentElement.appendChild(btnGroup);

      // --- Set color immediately after buttons (fix: always correct color) ---
      if (wantToRead[bookmarkKeyName]) {
        titleHeader.style.color = colors.wantToRead;
      } else if (bookmarks[bookmarkKeyName]) {
        titleHeader.style.color = colors.bookmarked;
      } else {
        titleHeader.style.color = colors.defaultTitle;
      }

      // --- Chapter highlighting and save ---
      // Find all chapter links in the list (inside .group.w-full)
      const chapterGroups = document.querySelectorAll('.group.w-full');
      const bookmarkedChapterRaw = bookmarks[bookmarkKeyName]?.chapter || '';
      let bookmarkedNum = null;
      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 = () => {
          // Use enhanced fuzzy matching for chapter saves
          let matchingKey = findMatchingKey(pageTitle, bookmarks) || bookmarkKeyName;

          console.log('Series page saveClick - looking for:', pageTitle);
          console.log('Found matching key:', matchingKey);

          // Chapter text is already cleaned in the series page logic above
          // (it's set to `Chapter ${match[1]}` format)

          // Add to top when saving new chapter progress
          const newBookmarkEntry = {
            ...(bookmarks[matchingKey] || { title: matchingKey }),
            title: matchingKey,
            chapter: chapterText,
            url: seriesUrl,
            cover: bookmarks[matchingKey]?.cover || coverImg || '',
            lastRead: Date.now()
          };

          // Remove existing entry and add to top
          delete bookmarks[matchingKey];
          bookmarks = { [matchingKey]: 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);

  // --- INITIALIZATION ---
  function waitForContent() {
    const observer = new MutationObserver((_, obs) => {
      if (document.querySelector('.grid-cols-12') || location.pathname.startsWith('/series/')) {
        obs.disconnect();
        if (location.pathname.startsWith('/series/')) {
          document.body.setAttribute('data-series-page', 'true');
        } else {
          document.body.removeAttribute('data-series-page');
        }
        loadColors(); // Load colors before creating UI
        createUI();
        updateTitleButtons();
      }
    });
    observer.observe(document.body, { childList: true, subtree: true });
  }

  waitForContent();
})();