Last active
February 5, 2026 23:57
-
-
Save Gishi1/fc7c69aa994b3eebfe01e2d2b489ecd2 to your computer and use it in GitHub Desktop.
WebNovel genre and paywall filter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name WebNovel genre and paywall filter | |
| // @namespace https://www.webnovel.com/ | |
| // @version 1.28 | |
| // @description Genre filter and paywall filter for WebNovel.com! | |
| // @author idMysteries and Gishi | |
| // @match https://www.webnovel.com/stories/* | |
| // @match https://www.webnovel.com/tags/* | |
| // @match https://www.webnovel.com/category/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=webnovel.com | |
| // @license Unlicense | |
| // @grant none | |
| // ==/UserScript== | |
| const PREFER_LIST = ['# ROMANCE', '# REINCARNATION', '# HAREM', '# SMUT', '# ISEKAI', '# R18', '# ACTION']; | |
| const HIDE_LIST = ['# TRAGEDY', '# YAOI', '# NO-HAREM', '# BL']; | |
| const DESCRIPTION_BLACKLIST = [ | |
| // Add words/phrases to hide novels containing these in description | |
| // Example: 'cultivation', 'male protagonist only', etc. | |
| ]; | |
| const BLOCK_LIST = new Set([ | |
| '/book/the-dark-avatar_19831406406525805', | |
| '/book/gamer-chat-group_19538419206720505', | |
| '/book/solo-leveling-avatarr_20293566506098405', | |
| '/book/attack-on-titan-mutant-system_19306939105958505', | |
| '/book/dxd-meta-essence-gacha_27252217606169605', | |
| '/book/naruto-beast-system_24893181105165405', | |
| '/book/dxd-gilgamesh-with-solo-leveling-system_24419695505300205', | |
| '/book/cyberpunk-edgerunners-hacker-system_24540721606161705', | |
| '/book/chainsaw-man-system_24775381505806105', | |
| '/book/marvel-mutant-beast-system_24957438305393705', | |
| '/book/fate-villain-system_25206723206456305', | |
| '/book/reborn-in-bleach-with-op-wishes_20989746706785305', | |
| '/book/dxd-devil-sister-testament-hybrid_21128235906520105', | |
| '/book/reborn-in-genshin-impact-with-system_21222837806880305', | |
| '/book/solo-leveling-avatarrrrr_21223243805637705', | |
| '/book/marvel-strongest-gamer_21525335306778905', | |
| '/book/bleach-hollow-gamer_22269001605214405', | |
| '/book/spider-verse-mutant-gamer_22337813206000805', | |
| '/book/reborn-in-black-clover-with-op-system_23089787506156405', | |
| '/book/naruto-dark-gamer_23521706406333205', | |
| '/book/marvel-kryptonian-gamer_23573367306660105', | |
| '/book/spy-x-family-system_23573658905399105', | |
| '/book/netori-system_18189386705628305', | |
| '/book/reborn-in-agk-with-system_19589246505698605', | |
| '/book/marvel-super-god_19885230605644805', | |
| '/book/chainsawman-fanfic-%5Bnew-version-is-out-darkgamerz%5D_20055982105930405', | |
| '/book/mha-villain-gamer-system_20233449005308405', | |
| '/book/lust-tamer-system_19969301806277005', | |
| '/book/danmachi-hentai-system_25207275305196605', | |
| '/book/dxd-meta-gacha-essencesssss-%5Bnew-fic-up%5D_25224104606505005', | |
| '/book/kanokari-romcom-system_25224195905246205', | |
| '/book/mha-villain-system_25405182406105505', | |
| '/book/black-lagoon-mob-system_25498563906341005', | |
| '/book/fairy-tail-dragon-shinigami_25920887106625905', | |
| '/book/marvel-reborn-as-carnage_26103131205040205', | |
| '/book/hentai-multiverse-system_26165372705251605', | |
| '/book/pokemon-the-legend_26180589205305905', | |
| '/book/bleach-gamer-system_26224342806689405', | |
| '/book/genshin-impact-archon-gamer_26241045905504105', | |
| '/book/dc-villain-gacha-essences_26355810906093805', | |
| '/book/attack-on-titan-mutant-systemmm_26431338206335705', | |
| '/book/marvel-anime-system_14381039706244105', | |
| '/book/fate-hentai-system_25240165405284305', | |
| '/book/marvel-lust-mage-%5Bhp-x-marvel%5D_27251688105919005', | |
| '/book/in-black-clover-with-a-system_18327452705236505', | |
| '/book/multiversal-harem-shop_26353605106088705', | |
| '/book/multiverse-harem-shop_26353605106088705', | |
| '/book/solo-leveling-avatar-%5Boriginal%5D_20293566506098405', | |
| '/book/naruto-rape-system_27625512400450005', | |
| '/book/marvel-demon-gamer_14615216705951205', | |
| '/book/fairy-tail-dragon-god_15687353206759005', | |
| '/book/dragon-ga-kill_16024345805688005', | |
| '/book/naruto-resurrection_14622746106049205', | |
| '/book/dragon-ga-kill-%5Boriginal%5D_13069022206601805', | |
| '/book/reborn-as-op-gojo-in-jjk-with-bleach-powers_27736027100920905', | |
| '/book/reborn-as-op-gojo-in-jjk_27736027100920905', | |
| '/book/marvel-sex-summon-system_27796501508279105', | |
| '/book/marvel-rivals-mutant-gamer-%5Br-18%5D_31658190308824605', | |
| '/book/dc-i-am-batman-with-gacha-system_27979466000147405', | |
| '/book/game-of-thrones-dragon-overlordd_17613176106920005' | |
| ]); | |
| // Store for processed elements | |
| const processedElements = new WeakSet(); | |
| // Helper functions | |
| function shouldRemoveNovel(novelElement) { | |
| if (novelElement.textContent.includes('ghostybones')) { | |
| return true; | |
| } | |
| // Check description for blacklisted words | |
| if (DESCRIPTION_BLACKLIST.length > 0) { | |
| // Find description elements (different selectors for different page types) | |
| const descriptionSelectors = [ | |
| 'p.fw400', // stories page | |
| 'p.lh20', // stories page alt | |
| 'a._txt', // tags page | |
| '.wn-description', // our custom class | |
| '.wn-stories-description' // our custom class | |
| ]; | |
| for (const selector of descriptionSelectors) { | |
| const descElement = novelElement.querySelector(selector); | |
| if (descElement) { | |
| const descText = descElement.textContent.toLowerCase(); | |
| for (const blacklistedWord of DESCRIPTION_BLACKLIST) { | |
| if (descText.includes(blacklistedWord.toLowerCase())) { | |
| return true; | |
| } | |
| } | |
| break; // Found description, no need to check other selectors | |
| } | |
| } | |
| } | |
| const links = novelElement.querySelectorAll('a'); | |
| for (const link of links) { | |
| const linkText = link.textContent.trim(); | |
| const href = link.getAttribute('href'); | |
| // Check both formats: "# TAG" and "TAG" (tags page uses no prefix) | |
| for (const hiddenTag of HIDE_LIST) { | |
| const tagWithoutPrefix = hiddenTag.replace('# ', ''); | |
| if (linkText === hiddenTag || linkText === tagWithoutPrefix) { | |
| return true; | |
| } | |
| } | |
| if (href && BLOCK_LIST.has(href)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| function calculatePreferenceScore(novelElement) { | |
| let score = 0; | |
| const links = novelElement.querySelectorAll('a'); | |
| for (const link of links) { | |
| const linkText = link.textContent; | |
| for (const preferredTag of PREFER_LIST) { | |
| const tagWithoutPrefix = preferredTag.replace('# ', ''); | |
| // Check both formats: "# TAG" and "TAG" (tags page uses no prefix) | |
| if (linkText.includes(preferredTag) || linkText === tagWithoutPrefix) { | |
| score = Math.min(score + 1, 5); | |
| break; | |
| } | |
| } | |
| if (score >= 5) break; | |
| } | |
| return score; | |
| } | |
| function createTextContentWrapper(detElement) { | |
| if (!detElement) return null; | |
| // Check if wrapper already exists | |
| let wrapper = detElement.querySelector('.text-content-wrapper'); | |
| if (wrapper) return wrapper; | |
| // Get all child nodes | |
| const children = Array.from(detElement.children); | |
| // Create new wrapper | |
| wrapper = document.createElement('div'); | |
| wrapper.className = 'text-content-wrapper'; | |
| // Find the first image to know where to split content | |
| let foundImage = false; | |
| for (const child of children) { | |
| if (child.tagName === 'IMG' || child.querySelector('img')) { | |
| foundImage = true; | |
| // Leave image outside wrapper (it's already positioned by CSS) | |
| continue; | |
| } | |
| // Move all non-image content to wrapper | |
| if (foundImage) { | |
| wrapper.appendChild(child.cloneNode(true)); | |
| child.remove(); | |
| } | |
| } | |
| // Add wrapper back to det element if we have content | |
| if (wrapper.children.length > 0) { | |
| detElement.appendChild(wrapper); | |
| return wrapper; | |
| } | |
| return null; | |
| } | |
| // Fix tags page layout - strip site classes and apply our own | |
| function fixTagsPageLayout(novelElement) { | |
| // Check if this is a tags page card (li.g_col._6) | |
| if (!novelElement.classList.contains('_6')) return; | |
| // Mark as processed to avoid re-processing | |
| if (novelElement.classList.contains('wn-tags-fixed')) return; | |
| novelElement.classList.add('wn-tags-fixed'); | |
| const bookItem = novelElement.querySelector('.g_book_item'); | |
| if (!bookItem) return; | |
| // Strip all classes from book item and add our own | |
| bookItem.className = 'wn-book-item'; | |
| // Fix the link containing image and title | |
| const link = bookItem.querySelector('a'); | |
| if (link) { | |
| link.className = 'wn-book-link'; | |
| } | |
| // Fix the image container - strip ALL classes | |
| const imgContainer = bookItem.querySelector('i'); | |
| if (imgContainer) { | |
| imgContainer.className = 'wn-img-container'; | |
| // Style the image | |
| const img = imgContainer.querySelector('img'); | |
| if (img) { | |
| img.className = 'wn-img'; | |
| } | |
| } | |
| // Fix title | |
| const title = link?.querySelector('h3'); | |
| if (title) { | |
| title.className = 'wn-title'; | |
| } | |
| // Fix tags | |
| const tags = bookItem.querySelector('p.g_tags, p.c_s'); | |
| if (tags) { | |
| tags.className = 'wn-tags'; | |
| } | |
| // Fix rating | |
| const rating = bookItem.querySelector('p.g_star_num'); | |
| if (rating) { | |
| rating.className = 'wn-rating'; | |
| } | |
| // Fix description | |
| const desc = bookItem.querySelector('a._txt, a.oh'); | |
| if (desc && desc !== link) { | |
| desc.className = 'wn-description'; | |
| } | |
| } | |
| // Fix stories page layout - transform to card-based layout like tags page | |
| function fixStoriesPageLayout(novelElement) { | |
| // Check if this is a stories page card (li.fl with w50%) | |
| if (!novelElement.classList.contains('fl')) return; | |
| if (!window.location.pathname.includes('/stories/')) return; | |
| // Mark as processed to avoid re-processing | |
| if (novelElement.classList.contains('wn-stories-fixed')) return; | |
| novelElement.classList.add('wn-stories-fixed'); | |
| // Add our custom card class | |
| novelElement.classList.add('wn-stories-card'); | |
| // Find the inner content wrapper (div.pr with fixed height) | |
| const contentWrapper = novelElement.querySelector('div.pr'); | |
| if (contentWrapper) { | |
| // Remove inline styles that constrain the layout | |
| contentWrapper.removeAttribute('style'); | |
| contentWrapper.className = 'wn-stories-content'; | |
| } | |
| // Fix the image container (span.pa) | |
| const imgSpan = novelElement.querySelector('span.pa, span.l0'); | |
| if (imgSpan) { | |
| imgSpan.removeAttribute('style'); | |
| imgSpan.className = 'wn-stories-img-container'; | |
| // Fix the image link | |
| const imgLink = imgSpan.querySelector('a.g_thumb'); | |
| if (imgLink) { | |
| imgLink.className = 'wn-stories-img-link'; | |
| } | |
| // Fix the image itself | |
| const img = imgSpan.querySelector('img'); | |
| if (img) { | |
| img.className = 'wn-stories-img'; | |
| img.removeAttribute('width'); | |
| img.removeAttribute('height'); | |
| } | |
| } | |
| // Fix tags container (p with tag links) | |
| const tagsContainer = novelElement.querySelector('p.mb4.pt4, p.oh.h16'); | |
| if (tagsContainer) { | |
| tagsContainer.className = 'wn-stories-tags'; | |
| } | |
| // Fix title | |
| const title = novelElement.querySelector('h3'); | |
| if (title) { | |
| title.className = 'wn-stories-title'; | |
| } | |
| // Fix description | |
| const descriptions = novelElement.querySelectorAll('p.fw400.lh20, p.ells'); | |
| descriptions.forEach(desc => { | |
| if (desc.classList.contains('wn-stories-tags')) return; | |
| if (desc.querySelector('strong') || desc.querySelector('svg')) return; // Skip stats | |
| desc.className = 'wn-stories-description'; | |
| }); | |
| // Fix stats container (p.df.aic with rating and chapters) | |
| const stats = novelElement.querySelector('p.df.aic'); | |
| if (stats) { | |
| stats.className = 'wn-stories-stats'; | |
| } | |
| } | |
| // Transform stories page container | |
| function fixStoriesPageContainer() { | |
| if (!window.location.pathname.includes('/stories/')) return; | |
| // Find the stories list container | |
| const listContainer = document.querySelector('.j_category_wrapper ul'); | |
| if (listContainer && !listContainer.classList.contains('wn-stories-list')) { | |
| listContainer.classList.add('wn-stories-list'); | |
| listContainer.removeAttribute('style'); | |
| } | |
| } | |
| function applyStyleToNovel(novelElement, score) { | |
| // Fix page layouts first | |
| fixTagsPageLayout(novelElement); | |
| fixStoriesPageLayout(novelElement); | |
| // Apply CSS class for styling | |
| novelElement.classList.add('webnovel-filter-processed'); | |
| // Remove any existing score classes | |
| for (let i = 0; i <= 5; i++) { | |
| novelElement.classList.remove(`webnovel-score-${i}`); | |
| } | |
| novelElement.classList.add(`webnovel-score-${score}`); | |
| // Create text content wrapper for better text management | |
| // Try multiple selectors for different page layouts | |
| const detElement = novelElement.querySelector('.g_col_det, .fl_det, .c_det, .det, .g_book_item'); | |
| if (detElement && !detElement.classList.contains('g_book_item')) { | |
| createTextContentWrapper(detElement); | |
| } | |
| // Add score indicator badge | |
| let scoreBadge = novelElement.querySelector('.novel-score'); | |
| if (!scoreBadge) { | |
| scoreBadge = document.createElement('div'); | |
| scoreBadge.className = 'novel-score'; | |
| novelElement.appendChild(scoreBadge); | |
| } | |
| // Color code the score badge | |
| let badgeColor = '#dc3545'; // Default red for low scores | |
| if (score >= 4) badgeColor = '#38a9da'; // Blue for high scores | |
| else if (score >= 3) badgeColor = '#28a745'; // Green for medium-high | |
| else if (score >= 1) badgeColor = '#6c757d'; // Gray for low | |
| scoreBadge.style.background = `linear-gradient(135deg, rgba(0,0,0,0.9), ${badgeColor}99)`; | |
| scoreBadge.textContent = `β ${score}/5`; | |
| // Add remove button for low-scoring novels | |
| if (score === 0) { | |
| let removeBtn = novelElement.querySelector('.remove-novel'); | |
| if (!removeBtn) { | |
| removeBtn = document.createElement('button'); | |
| removeBtn.className = 'remove-novel'; | |
| removeBtn.textContent = 'Γ Remove'; | |
| removeBtn.title = 'Remove this novel from view'; | |
| removeBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| novelElement.classList.add('webnovel-hidden'); | |
| setTimeout(() => { | |
| if (novelElement.isConnected) { | |
| novelElement.classList.add('webnovel-removing'); | |
| setTimeout(() => { | |
| if (novelElement.isConnected) novelElement.remove(); | |
| }, 300); | |
| } | |
| }, 100); | |
| }); | |
| novelElement.appendChild(removeBtn); | |
| } | |
| } else { | |
| // Remove the remove button if it exists and score > 0 | |
| const removeBtn = novelElement.querySelector('.remove-novel'); | |
| if (removeBtn) { | |
| removeBtn.remove(); | |
| } | |
| } | |
| } | |
| function processNovels() { | |
| // Fix page containers first | |
| fixStoriesPageContainer(); | |
| // Try multiple selectors for different page types | |
| const selectors = [ | |
| 'li.fl', | |
| 'li.g_col', | |
| '.c_item', | |
| '.j_catalogItem', | |
| '.catalog-item', | |
| '.tag-catalog-list li', | |
| '.j_catalogList li', | |
| '.catalog-list li' | |
| ]; | |
| let novels = []; | |
| for (const selector of selectors) { | |
| const found = document.querySelectorAll(selector); | |
| if (found.length > 0) { | |
| novels = Array.from(found); | |
| break; | |
| } | |
| } | |
| // Also look for any list items in catalog containers | |
| if (novels.length === 0) { | |
| const catalogContainers = document.querySelectorAll('.tag-catalog-list, .j_catalogList, .catalog-list, .g_col_lst, .fl_lst'); | |
| catalogContainers.forEach(container => { | |
| const items = container.querySelectorAll('li'); | |
| novels = novels.concat(Array.from(items)); | |
| }); | |
| } | |
| for (const novel of novels) { | |
| if (!novel.isConnected || processedElements.has(novel)) { | |
| continue; | |
| } | |
| processedElements.add(novel); | |
| if (shouldRemoveNovel(novel)) { | |
| novel.classList.add('webnovel-hidden'); | |
| setTimeout(() => { | |
| if (novel.isConnected) { | |
| novel.classList.add('webnovel-removing'); | |
| setTimeout(() => { | |
| if (novel.isConnected) novel.remove(); | |
| }, 300); | |
| } | |
| }, 300); | |
| continue; | |
| } | |
| const score = calculatePreferenceScore(novel); | |
| applyStyleToNovel(novel, score); | |
| } | |
| // Ensure containers have proper layout | |
| const containers = [ | |
| '.g_col_lst', '.fl_lst', | |
| '.tag-catalog-list', '.j_catalogList', '.catalog-list', | |
| '.g_wrap', '.g_bd', '.g_main' | |
| ]; | |
| containers.forEach(selector => { | |
| const container = document.querySelector(selector); | |
| if (container) { | |
| container.style.display = 'flex'; | |
| container.style.flexWrap = 'wrap'; | |
| container.style.gap = '30px'; | |
| container.style.justifyContent = 'flex-start'; | |
| container.style.alignItems = 'stretch'; | |
| container.style.width = '100%'; | |
| container.style.boxSizing = 'border-box'; | |
| } | |
| }); | |
| } | |
| // Debounced processing | |
| let processTimeout; | |
| function debouncedProcess() { | |
| clearTimeout(processTimeout); | |
| processTimeout = setTimeout(() => { | |
| processNovels(); | |
| }, 150); | |
| } | |
| // Handle scroll loading | |
| let scrollCheckTimeout; | |
| function handleScroll() { | |
| clearTimeout(scrollCheckTimeout); | |
| scrollCheckTimeout = setTimeout(() => { | |
| const scrollPosition = window.scrollY + window.innerHeight; | |
| const documentHeight = document.documentElement.scrollHeight; | |
| if (documentHeight - scrollPosition < 1000) { | |
| processNovels(); | |
| } | |
| }, 500); | |
| } | |
| // Setup Intersection Observer for lazy loaded content | |
| let intersectionObserver; | |
| function setupIntersectionObserver() { | |
| if (intersectionObserver) { | |
| intersectionObserver.disconnect(); | |
| } | |
| intersectionObserver = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| debouncedProcess(); | |
| } | |
| }); | |
| }, { | |
| root: null, | |
| rootMargin: '200px', | |
| threshold: 0.1 | |
| }); | |
| // Observe likely loading areas | |
| const observeTargets = [ | |
| '.g_col_lst', '.fl_lst', | |
| '.tag-catalog-list', '.j_catalogList', | |
| '.catalog-list', '.j_loadMore', | |
| '.load-more', '.more-btn' | |
| ]; | |
| observeTargets.forEach(selector => { | |
| const target = document.querySelector(selector); | |
| if (target) { | |
| intersectionObserver.observe(target); | |
| } | |
| }); | |
| } | |
| // Remove native tooltips from header elements | |
| function removeHeaderTooltips() { | |
| const header = document.querySelector('.g_header'); | |
| if (!header) return; | |
| // Remove title attributes from all header elements to prevent native tooltips | |
| header.querySelectorAll('[title]').forEach(el => { | |
| // Store the title in a data attribute in case it's needed later | |
| const title = el.getAttribute('title'); | |
| if (title) { | |
| el.setAttribute('data-original-title', title); | |
| el.removeAttribute('title'); | |
| } | |
| }); | |
| } | |
| (function() { | |
| 'use strict'; | |
| // Initial processing with delay | |
| setTimeout(() => { | |
| processNovels(); | |
| removeHeaderTooltips(); | |
| // Apply width to main containers | |
| document.querySelectorAll('.g_wrap, .g_row, .main-wrap, .content-wrap').forEach(container => { | |
| container.style.width = '80%'; | |
| container.style.maxWidth = '80%'; | |
| container.style.margin = '0 auto'; | |
| }); | |
| }, 1000); | |
| // Setup intersection observer | |
| setupIntersectionObserver(); | |
| // Set up scroll listener | |
| window.addEventListener('scroll', handleScroll, { passive: true }); | |
| // Set up mutation observer for dynamic content | |
| const mutationObserver = new MutationObserver((mutations) => { | |
| let shouldProcess = false; | |
| for (const mutation of mutations) { | |
| if (mutation.addedNodes.length > 0) { | |
| shouldProcess = true; | |
| break; | |
| } | |
| } | |
| if (shouldProcess) { | |
| debouncedProcess(); | |
| setupIntersectionObserver(); | |
| removeHeaderTooltips(); | |
| } | |
| }); | |
| // Start observing the entire document | |
| mutationObserver.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| // Also observe for URL changes (SPA navigation) | |
| let lastUrl = location.href; | |
| const urlObserver = new MutationObserver(() => { | |
| const currentUrl = location.href; | |
| if (currentUrl !== lastUrl) { | |
| lastUrl = currentUrl; | |
| // Clear processed elements | |
| processedElements.clear(); | |
| // Reprocess | |
| setTimeout(() => { | |
| processNovels(); | |
| setupIntersectionObserver(); | |
| }, 1500); | |
| } | |
| }); | |
| urlObserver.observe(document, { subtree: true, childList: true }); | |
| // Periodic check (for safety) | |
| setInterval(() => { | |
| processNovels(); | |
| }, 5000); | |
| // Add manual refresh button | |
| setTimeout(() => { | |
| const refreshBtn = document.createElement('button'); | |
| refreshBtn.id = 'webnovel-filter-refresh'; | |
| refreshBtn.textContent = 'π Refresh Filter'; | |
| refreshBtn.style.cssText = ` | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| z-index: 9999; | |
| background: #4a5568; | |
| color: white; | |
| border: none; | |
| border-radius: 20px; | |
| padding: 10px 20px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); | |
| transition: all 0.3s; | |
| `; | |
| refreshBtn.addEventListener('click', () => { | |
| // Clear processed elements and reprocess | |
| processedElements.clear(); | |
| document.querySelectorAll('.webnovel-filter-processed').forEach(el => { | |
| el.classList.remove('webnovel-filter-processed'); | |
| for (let i = 0; i <= 5; i++) { | |
| el.classList.remove(`webnovel-score-${i}`); | |
| } | |
| const scoreBadge = el.querySelector('.novel-score'); | |
| if (scoreBadge) scoreBadge.remove(); | |
| const removeBtn = el.querySelector('.remove-novel'); | |
| if (removeBtn) removeBtn.remove(); | |
| const textWrapper = el.querySelector('.text-content-wrapper'); | |
| if (textWrapper) textWrapper.remove(); | |
| }); | |
| processNovels(); | |
| refreshBtn.textContent = 'β Refreshed!'; | |
| setTimeout(() => { | |
| refreshBtn.textContent = 'π Refresh Filter'; | |
| }, 2000); | |
| }); | |
| refreshBtn.addEventListener('mouseenter', () => { | |
| refreshBtn.style.transform = 'scale(1.05)'; | |
| refreshBtn.style.background = '#2d3748'; | |
| }); | |
| refreshBtn.addEventListener('mouseleave', () => { | |
| refreshBtn.style.transform = 'scale(1)'; | |
| refreshBtn.style.background = '#4a5568'; | |
| }); | |
| document.body.appendChild(refreshBtn); | |
| }, 2000); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment