BlockPup

Blocks unwanted popups with whitelist/blocklist control and interactive dialogs.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         BlockPup
// @namespace    Violentmonkey Scripts
// @version      0.8
// @description  Blocks unwanted popups with whitelist/blocklist control and interactive dialogs.
// @author       0xArCHDeViL
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @license      MIT
// @exclude      https://www.linkedin.com/*
// @exclude      https://*.facebook.com/*
// @exclude      *://*.bing.com/*
// @exclude      *://*.duckduckgo.com/*
// @exclude      *://*.baidu.com/*
// @exclude      *://*.ecosia.org/*
// @exclude      *://*.brave.com/*
// @exclude      *://*.startpage.com/*
// @exclude      https://accounts.google.com/*
// @exclude      https://*.google.com/*
// @exclude      https://*.google.ad/*
// @exclude      https://*.google.ae/*
// @exclude      https://*.google.com.af/*
// @exclude      https://*.google.com.ag/*
// @exclude      https://*.google.com.ai/*
// @exclude      https://*.google.al/*
// @exclude      https://*.google.am/*
// @exclude      https://*.google.co.ao/*
// @exclude      https://*.google.com.ar/*
// @exclude      https://*.google.as/*
// @exclude      https://*.google.at/*
// @exclude      https://*.google.com.au/*
// @exclude      https://*.google.az/*
// @exclude      https://*.google.ba/*
// @exclude      https://*.google.com.bd/*
// @exclude      https://*.google.be/*
// @exclude      https://*.google.bf/*
// @exclude      https://*.google.bg/*
// @exclude      https://*.google.com.bh/*
// @exclude      https://*.google.bi/*
// @exclude      https://*.google.bj/*
// @exclude      https://*.google.com.bn/*
// @exclude      https://*.google.com.bo/*
// @exclude      https://*.google.com.br/*
// @exclude      https://*.google.bs/*
// @exclude      https://*.google.bt/*
// @exclude      https://*.google.co.bw/*
// @exclude      https://*.google.by/*
// @exclude      https://*.google.com.bz/*
// @exclude      https://*.google.ca/*
// @exclude      https://*.google.cd/*
// @exclude      https://*.google.cf/*
// @exclude      https://*.google.cg/*
// @exclude      https://*.google.ch/*
// @exclude      https://*.google.ci/*
// @exclude      https://*.google.co.ck/*
// @exclude      https://*.google.cl/*
// @exclude      https://*.google.cm/*
// @exclude      https://*.google.cn/*
// @exclude      https://*.google.com.co/*
// @exclude      https://*.google.co.cr/*
// @exclude      https://*.google.com.cu/*
// @exclude      https://*.google.cv/*
// @exclude      https://*.google.com.cy/*
// @exclude      https://*.google.cz/*
// @exclude      https://*.google.de/*
// @exclude      https://*.google.dj/*
// @exclude      https://*.google.dk/*
// @exclude      https://*.google.dm/*
// @exclude      https://*.google.com.do/*
// @exclude      https://*.google.dz/*
// @exclude      https://*.google.com.ec/*
// @exclude      https://*.google.ee/*
// @exclude      https://*.google.com.eg/*
// @exclude      https://*.google.es/*
// @exclude      https://*.google.com.et/*
// @exclude      https://*.google.fi/*
// @exclude      https://*.google.com.fj/*
// @exclude      https://*.google.fm/*
// @exclude      https://*.google.fr/*
// @exclude      https://*.google.ga/*
// @exclude      https://*.google.ge/*
// @exclude      https://*.google.gg/*
// @exclude      https://*.google.com.gh/*
// @exclude      https://*.google.com.gi/*
// @exclude      https://*.google.gl/*
// @exclude      https://*.google.gm/*
// @exclude      https://*.google.gp/*
// @exclude      https://*.google.gr/*
// @exclude      https://*.google.com.gt/*
// @exclude      https://*.google.gy/*
// @exclude      https://*.google.com.hk/*
// @exclude      https://*.google.hn/*
// @exclude      https://*.google.hr/*
// @exclude      https://*.google.ht/*
// @exclude      https://*.google.hu/*
// @exclude      https://*.google.co.id/*
// @exclude      https://*.google.ie/*
// @exclude      https://*.google.co.il/*
// @exclude      https://*.google.im/*
// @exclude      https://*.google.co.in/*
// @exclude      https://*.google.iq/*
// @exclude      https://*.google.is/*
// @exclude      https://*.google.it/*
// @exclude      https://*.google.je/*
// @exclude      https://*.google.com.jm/*
// @exclude      https://*.google.jo/*
// @exclude      https://*.google.co.jp/*
// @exclude      https://*.google.co.ke/*
// @exclude      https://*.google.com.kh/*
// @exclude      https://*.google.ki/*
// @exclude      https://*.google.kg/*
// @exclude      https://*.google.co.kr/*
// @exclude      https://*.google.com.kw/*
// @exclude      https://*.google.kz/*
// @exclude      https://*.google.la/*
// @exclude      https://*.google.com.lb/*
// @exclude      https://*.google.li/*
// @exclude      https://*.google.lk/*
// @exclude      https://*.google.co.ls/*
// @exclude      https://*.google.lt/*
// @exclude      https://*.google.lu/*
// @exclude      https://*.google.lv/*
// @exclude      https://*.google.com.ly/*
// @exclude      https://*.google.co.ma/*
// @exclude      https://*.google.md/*
// @exclude      https://*.google.me/*
// @exclude      https://*.google.mg/*
// @exclude      https://*.google.mk/*
// @exclude      https://*.google.ml/*
// @exclude      https://*.google.com.mm/*
// @exclude      https://*.google.mn/*
// @exclude      https://*.google.ms/*
// @exclude      https://*.google.com.mt/*
// @exclude      https://*.google.mu/*
// @exclude      https://*.google.mv/*
// @exclude      https://*.google.mw/*
// @exclude      https://*.google.com.mx/*
// @exclude      https://*.google.com.my/*
// @exclude      https://*.google.co.mz/*
// @exclude      https://*.google.com.na/*
// @exclude      https://*.google.com.nf/*
// @exclude      https://*.google.com.ng/*
// @exclude      https://*.google.com.ni/*
// @exclude      https://*.google.ne/*
// @exclude      https://*.google.nl/*
// @exclude      https://*.google.no/*
// @exclude      https://*.google.com.np/*
// @exclude      https://*.google.nr/*
// @exclude      https://*.google.nu/*
// @exclude      https://*.google.co.nz/*
// @exclude      https://*.google.com.om/*
// @exclude      https://*.google.com.pa/*
// @exclude      https://*.google.com.pe/*
// @exclude      https://*.google.com.pg/*
// @exclude      https://*.google.com.ph/*
// @exclude      https://*.google.com.pk/*
// @exclude      https://*.google.pl/*
// @exclude      https://*.google.pn/*
// @exclude      https://*.google.com.pr/*
// @exclude      https://*.google.ps/*
// @exclude      https://*.google.pt/*
// @exclude      https://*.google.com.py/*
// @exclude      https://*.google.com.qa/*
// @exclude      https://*.google.ro/*
// @exclude      https://*.google.ru/*
// @exclude      https://*.google.rw/*
// @exclude      https://*.google.com.sa/*
// @exclude      https://*.google.com.sb/*
// @exclude      https://*.google.sc/*
// @exclude      https://*.google.se/*
// @exclude      https://*.google.com.sg/*
// @exclude      https://*.google.sh/*
// @exclude      https://*.google.si/*
// @exclude      https://*.google.sk/*
// @exclude      https://*.google.com.sl/*
// @exclude      https://*.google.sn/*
// @exclude      https://*.google.so/*
// @exclude      https://*.google.sm/*
// @exclude      https://*.google.sr/*
// @exclude      https://*.google.st/*
// @exclude      https://*.google.com.sv/*
// @exclude      https://*.google.td/*
// @exclude      https://*.google.tg/*
// @exclude      https://*.google.co.th/*
// @exclude      https://*.google.com.tj/*
// @exclude      https://*.google.tk/*
// @exclude      https://*.google.tl/*
// @exclude      https://*.google.tm/*
// @exclude      https://*.google.tn/*
// @exclude      https://*.google.to/*
// @exclude      https://*.google.com.tr/*
// @exclude      https://*.google.tt/*
// @exclude      https://*.google.com.tw/*
// @exclude      https://*.google.co.tz/*
// @exclude      https://*.google.com.ua/*
// @exclude      https://*.google.co.ug/*
// @exclude      https://*.google.co.uk/*
// @exclude      https://*.google.com.uy/*
// @exclude      https://*.google.co.uz/*
// @exclude      https://*.google.com.vc/*
// @exclude      https://*.google.co.ve/*
// @exclude      https://*.google.vg/*
// @exclude      https://*.google.co.vi/*
// @exclude      https://*.google.com.vn/*
// @exclude      https://*.google.vu/*
// @exclude      https://*.google.ws/*
// @exclude      https://*.google.rs/*
// @exclude      https://*.google.co.za/*
// @exclude      https://*.google.co.zm/*
// @exclude      https://*.google.co.zw/*
// @exclude      https://*.google.cat/*
// @exclude      https://*.youtube.com/*
// @exclude      *://disqus.com/embed/*
// @exclude      https://vk.com/*
// @exclude      https://*.vk.com/*
// @exclude      https://vimeo.com/*
// @exclude      https://*.vimeo.com/*
// @exclude      *://*.coub.com/*
// @exclude      *://coub.com/*
// @exclude      *://*.googlesyndication.com/*
// @exclude      *://*.naver.com/*
// @exclude      https://gstatic.com/*
// @exclude      https://*.gstatic.com/*
// @exclude      https://yandex.ru/*
// @exclude      https://*.yandex.ru/*
// @exclude      https://yandex.ua/*
// @exclude      https://*.yandex.ua/*
// @exclude      https://yandex.by/*
// @exclude      https://*.yandex.by/*
// @exclude      https://yandex.com/*
// @exclude      https://*.yandex.com/*
// @exclude      https://yandex.com.tr/*
// @exclude      https://*.yandex.com.tr/*
// @exclude      https://yandex.kz/*
// @exclude      https://*.yandex.kz/*
// @exclude      https://yandex.fr/*
// @exclude      https://*.yandex.fr/*
// @exclude      https://*.twitch.tv/*
// @exclude      https://tinder.com/*
// @exclude      *://*.yahoo.com/*
// @exclude      *://chatovod.ru/*
// @exclude      *://*.chatovod.ru/*
// @exclude      *://vc.ru/*
// @exclude      *://tjournal.ru/*
// @exclude      *://amanice.ru/*
// @exclude      *://ka-union.ru/*
// @exclude      *://gameforge.com/*
// @exclude      *://*.gameforge.com/*
// @exclude      *://*.ssgdfm.com/*
// @exclude      *://*.brainpop.com/*
// @exclude      *://*.taobao.com/*
// @exclude      *://*.ksl.com/*
// @exclude      *://*.t-online.de/*
// @exclude      *://boards.4channel.org/*
// @exclude      *://*.washingtonpost.com/*
// @exclude      *://*.kakao.com/*
// @exclude      *://*.discounttire.com/*
// @exclude      *://mail.ukr.net/*
// @exclude      *://*.mail.ukr.net/*
// @exclude      *://*.sahadan.com/*
// @exclude      *://*.groupon.*/*
// @exclude      *://*.amoma.com/*
// @exclude      *://*.jccsmart.com/*
// @exclude      *://wp.pl/*
// @exclude      *://*.wp.pl/*
// @exclude      *://money.pl/*
// @exclude      *://*.money.pl/*
// @exclude      *://o2.pl/*
// @exclude      *://*.o2.pl/*
// @exclude      *://pudelek.pl/*
// @exclude      *://*.pudelek.pl/*
// @exclude      *://komorkomania.pl/*
// @exclude      *://*.komorkomania.pl/*
// @exclude      *://gadzetomania.pl/*
// @exclude      *://*.gadzetomania.pl/*
// @exclude      *://fotoblogia.pl/*
// @exclude      *://*.fotoblogia.pl/*
// @exclude      *://autokult.pl/*
// @exclude      *://*.autokult.pl/*
// @exclude      *://abczdrowie.pl/*
// @exclude      *://*.abczdrowie.pl/*
// @exclude      *://parenting.pl/*
// @exclude      *://*.parenting.pl/*
// @exclude      *://dobreprogramy.pl/*
// @exclude      *://*.dobreprogramy.pl/*
// @exclude      *://polygamia.pl/*
// @exclude      *://*.polygamia.pl/*
// @exclude      *://*.mosreg.ru/*
// @exclude      *://vietjetair.com/*
// @exclude      *://*.vietjetair.com/*
// @exclude      https://web.skype.com/*
// @exclude      *://karelia.press/*
// @exclude      *://*.karelia.press/*
// @exclude      *://microsoft.com/*
// @exclude      *://*.microsoft.com/*
// @exclude      *://bancoctt.pt/*
// @exclude      *://*.bancoctt.pt/*
// @exclude      *://print24.com/*
// @exclude      *://*.print24.com/*
// @exclude      *://shellfcu.org/*
// @exclude      *://*.shellfcu.org/*
// @exclude      *://yesfile.com/*
// @exclude      *://*.yesfile.com/*
// @exclude      *://sunrise.ch/*
// @exclude      *://*.sunrise.ch/*
// @exclude      *://cetesdirecto.com/*
// @exclude      *://*.cetesdirecto.com/*
// @exclude      *://ubi.com/*
// @exclude      *://*.ubi.com/*
// @exclude      *://*.sistic.com.sg/*
// @exclude      *://*.ilfattoquotidiano.it/*
// @exclude      *://*.vanis.io/*
// @exclude      *://*.senpa.io/*
// @exclude      *://wielkopolskiebilety.pl/*
// @exclude      *://*.wielkopolskiebilety.pl/*
// @exclude      *://*.astrogo.astro.com.my/*
// @exclude      *://*.chaturbate.com/*
// @exclude      *://play.pl/*
// @exclude      *://*.play.pl/*
// @exclude      *://web.de/*
// @exclude      *://*.web.de/*
// @exclude      *://gmx.net/*
// @exclude      *://*.gmx.net/*
// @exclude      *://clashofclans.com/*
// @exclude      *://*.clashofclans.com/*
// @exclude      *://online.bfgruppe.de/*
// @exclude      *://*.online.bfgruppe.de/*
// @exclude      *://portalpasazera.pl/*
// @exclude      *://*.portalpasazera.pl/*
// @exclude      *://jeanne-laffitte.com/*
// @exclude      *://*.jeanne-laffitte.com/*
// @exclude      *://epicgames.com/*
// @exclude      *://*.epicgames.com/*
// @exclude      *://freizeithugl.de/*
// @exclude      *://*.freizeithugl.de/*
// @exclude      *://koleje-wielkopolskie.com.pl/*
// @exclude      *://*.koleje-wielkopolskie.com.pl/*
// @exclude      *://ygosu.com/*
// @exclude      *://*.ygosu.com/*
// @exclude      *://ppss.kr/*
// @exclude      *://*.ppss.kr/*
// @exclude      *://nordea.com/*
// @exclude      *://*.nordea.com/*
// @exclude      *://*.gov/*
// @exclude      *://austintestingandtherapy.com/*
// @exclude      *://*.austintestingandtherapy.com/*
// @exclude      *://learn-anything.xyz/*
// @exclude      *://*.learn-anything.xyz/*
// @exclude      *://egybest.*/*
// @exclude      *://*.egybest.*/*
// @exclude      *://ancestry.com/*
// @exclude      *://*.ancestry.com/*
// @exclude      *://login.mts.ru/*
// @exclude      *://*.login.mts.ru/*
// @exclude      *://ebay.com/*
// @exclude      *://*.ebay.com/*
// @exclude      *://outlook.live.*/*
// @exclude      *://*.outlook.live.*/*
// @exclude      *://joom.com.*/*
// @exclude      *://*.joom.com.*/*
// @exclude      *://unrealengine.com/*
// @exclude      *://*.unrealengine.com/*
// @exclude      freelancer.com
// @exclude      ov-chipkaart.nl
// @exclude      tezgoal.com
// @exclude      joom.com
// @exclude      *://id.gov.ua/*
// @exclude      *://github.com/*
// @exclude      *://tiktok.com/*
// @exclude      *://*.tiktok.com/*
// @exclude      *://namu.wiki/*
// @exclude      *://*.namu.wiki/*
// @exclude      *://beinconnect.com.tr/*
// @exclude      *://*.beinconnect.com.tr/*
// @exclude      *://deadshot.io/*
// @exclude      *://*.deadshot.io/*
// @exclude      *://gofile.io/*
// @exclude      *://*.gofile.io/*
// @exclude      *://xcancel.com/*
// @exclude      *://*.xcancel.com/*
// @exclude      *://reddit.com/*
// @exclude      *://*.reddit.com/*
// @exclude      *://challenges.cloudflare.com/*
// @exclude      https://ygosu.com/*
// @exclude      https://m.ygosu.com/*
// @exclude      https://ad-shield.io/*
// @exclude      https://feedclick.net/*
// @exclude      https://sportalkorea.com/*
// @exclude      https://*.sportalkorea.com/*
// @exclude      https://enetnews.co.kr/*
// @exclude      https://*.enetnews.co.kr/*
// @exclude      https://edaily.co.kr/*
// @exclude      https://*.edaily.co.kr/*
// @exclude      https://economist.co.kr/*
// @exclude      https://*.economist.co.kr/*
// @exclude      https://etoday.co.kr/*
// @exclude      https://*.etoday.co.kr/*
// @exclude      https://hankyung.com/*
// @exclude      https://*.hankyung.com/*
// @exclude      https://isplus.com/*
// @exclude      https://*.isplus.com/*
// @exclude      https://hometownstation.com/*
// @exclude      https://*.hometownstation.com/*
// @exclude      https://inven.co.kr/*
// @exclude      https://*.inven.co.kr/*
// @exclude      https://loawa.com/*
// @exclude      https://*.loawa.com/*
// @exclude      https://viva100.com/*
// @exclude      https://*.viva100.com/*
// @exclude      https://joongdo.co.kr/*
// @exclude      https://*.joongdo.co.kr/*
// @exclude      https://kagit.kr/*
// @exclude      https://*.kagit.kr/*
// @exclude      https://jjang0u.com/*
// @exclude      https://*.jjang0u.com/*
// @exclude      https://cboard.net/*
// @exclude      https://*.cboard.net/*
// @exclude      https://interfootball.co.kr/*
// @exclude      https://*.interfootball.co.kr/*
// @exclude      https://fourfourtwo.co.kr/*
// @exclude      https://*.fourfourtwo.co.kr/*
// @exclude      https://newdaily.co.kr/*
// @exclude      https://*.newdaily.co.kr/*
// @exclude      https://genshinlab.com/*
// @exclude      https://*.genshinlab.com/*
// @exclude      https://thephoblographer.com/*
// @exclude      https://*.thephoblographer.com/*
// @exclude      https://dogdrip.net/*
// @exclude      https://*.dogdrip.net/*
// @exclude      https://honkailab.com/*
// @exclude      https://*.honkailab.com/*
// @exclude      https://warcraftrumbledeck.com/*
// @exclude      https://*.warcraftrumbledeck.com/*
// @exclude      https://mlbpark.donga.com/*
// @exclude      https://*.mlbpark.donga.com/*
// @exclude      https://gamingdeputy.com/*
// @exclude      https://*.gamingdeputy.com/*
// @exclude      https://thestockmarketwatch.com/*
// @exclude      https://*.thestockmarketwatch.com/*
// @exclude      https://thesaurus.net/*
// @exclude      https://*.thesaurus.net/*
// @exclude      https://forexlive.com/*
// @exclude      https://*.forexlive.com/*
// @exclude      https://tweaksforgeeks.com/*
// @exclude      https://*.tweaksforgeeks.com/*
// @exclude      https://alle-tests.nl/*
// @exclude      https://*.alle-tests.nl/*
// @exclude      https://allthetests.com/*
// @exclude      https://*.allthetests.com/*
// @exclude      https://issuya.com/*
// @exclude      https://*.issuya.com/*
// @exclude      https://maketecheasier.com/*
// @exclude      https://*.maketecheasier.com/*
// @exclude      https://motorbikecatalog.com/*
// @exclude      https://*.motorbikecatalog.com/*
// @exclude      https://automobile-catalog.com/*
// @exclude      https://*.automobile-catalog.com/*
// @exclude      https://topstarnews.net/*
// @exclude      https://*.topstarnews.net/*
// @exclude      https://worldhistory.org/*
// @exclude      https://*.worldhistory.org/*
// @exclude      https://etnews.com/*
// @exclude      https://*.etnews.com/*
// @exclude      https://iusm.co.kr/*
// @exclude      https://*.iusm.co.kr/*
// @exclude      https://etoland.co.kr/*
// @exclude      https://*.etoland.co.kr/*
// @exclude      https://apkmirror.com/*
// @exclude      https://*.apkmirror.com/*
// @exclude      https://uttranews.com/*
// @exclude      https://*.uttranews.com/*
// @exclude      https://fntimes.com/*
// @exclude      https://*.fntimes.com/*
// @exclude      https://javatpoint.com/*
// @exclude      https://*.javatpoint.com/*
// @exclude      https://text-compare.com/*
// @exclude      https://*.text-compare.com/*
// @exclude      https://vipotv.com/*
// @exclude      https://*.vipotv.com/*
// @exclude      https://lamire.jp/*
// @exclude      https://*.lamire.jp/*
// @exclude      https://dt.co.kr/*
// @exclude      https://*.dt.co.kr/*
// @exclude      https://g-enews.*/*
// @exclude      https://*.g-enews.*/*
// @exclude      https://allthekingz.com/*
// @exclude      https://*.allthekingz.com/*
// @exclude      https://gadgets360.com/*
// @exclude      https://*.gadgets360.com/*
// @exclude      https://sports.hankooki.com/*
// @exclude      https://*.sports.hankooki.com/*
// @exclude      https://ajunews.com/*
// @exclude      https://*.ajunews.com/*
// @exclude      https://munhwa.com/*
// @exclude      https://*.munhwa.com/*
// @exclude      https://zal.kr/*
// @exclude      https://*.zal.kr/*
// @exclude      https://wfmz.com/*
// @exclude      https://*.wfmz.com/*
// @exclude      https://thestar.co.uk/*
// @exclude      https://*.thestar.co.uk/*
// @exclude      https://yorkshirepost.co.uk/*
// @exclude      https://*.yorkshirepost.co.uk/*
// @exclude      https://mydaily.co.kr/*
// @exclude      https://*.mydaily.co.kr/*
// @exclude      https://raenonx.cc/*
// @exclude      https://*.raenonx.cc/*
// @exclude      https://ndtvprofit.com/*
// @exclude      https://*.ndtvprofit.com/*
// @exclude      https://badmouth1.com/*
// @exclude      https://*.badmouth1.com/*
// @exclude      https://logicieleducatif.fr/*
// @exclude      https://*.logicieleducatif.fr/*
// @exclude      https://taxguru.in/*
// @exclude      https://*.taxguru.in/*
// @exclude      https://islamicfinder.org/*
// @exclude      https://*.islamicfinder.org/*
// @exclude      https://aikatu.jp/*
// @exclude      https://*.aikatu.jp/*
// @exclude      https://secure-signup.net/*
// @exclude      https://*.secure-signup.net/*
// @exclude      https://globalrph.com/*
// @exclude      https://*.globalrph.com/*
// @exclude      https://sportsrec.com/*
// @exclude      https://*.sportsrec.com/*
// @exclude      https://reportera.co.kr/*
// @exclude      https://*.reportera.co.kr/*
// @exclude      https://slobodnadalmacija.hr/*
// @exclude      https://*.slobodnadalmacija.hr/*
// @exclude      https://carscoops.com/*
// @exclude      https://*.carscoops.com/*
// @exclude      https://indiatimes.com/*
// @exclude      https://*.indiatimes.com/*
// @exclude      https://flatpanelshd.com/*
// @exclude      https://*.flatpanelshd.com/*
// @exclude      https://sportsseoul.com/*
// @exclude      https://*.sportsseoul.com/*
// @exclude      https://gloria.hr/*
// @exclude      https://*.gloria.hr/*
// @exclude      https://videogamemods.com/*
// @exclude      https://*.videogamemods.com/*
// @exclude      https://adintrend.tv/*
// @exclude      https://ark-unity.com/*
// @exclude      https://*.ark-unity.com/*
// @exclude      https://cool-style.com.tw/*
// @exclude      https://*.cool-style.com.tw/*
// @exclude      https://dziennik.pl/*
// @exclude      https://*.dziennik.pl/*
// @exclude      https://eurointegration.com.ua/*
// @exclude      https://*.eurointegration.com.ua/*
// @exclude      https://jin115.com/*
// @exclude      https://*.jin115.com/*
// @exclude      https://onlinegdb.com/*
// @exclude      https://*.onlinegdb.com/*
// @exclude      https://winfuture.de/*
// @exclude      https://*.winfuture.de/*
// @exclude      https://hoyme.jp/*
// @exclude      https://*.hoyme.jp/*
// @exclude      https://pravda.com.ua/*
// @exclude      https://*.pravda.com.ua/*
// @exclude      https://freemcserver.net/*
// @exclude      https://*.freemcserver.net/*
// @exclude      https://esuteru.com/*
// @exclude      https://*.esuteru.com/*
// @exclude      https://pressian.com/*
// @exclude      https://*.pressian.com/*
// @exclude      https://blog.livedoor.jp/kinisoku/*
// @exclude      https://blog.livedoor.jp/nanjstu/*
// @exclude      https://itainews.com/*
// @exclude      https://*.itainews.com/*
// @exclude      https://infinityfree.com/*
// @exclude      https://*.infinityfree.com/*
// @exclude      https://wort-suchen.de/*
// @exclude      https://*.wort-suchen.de/*
// @exclude      https://dramabeans.com/*
// @exclude      https://*.dramabeans.com/*
// @exclude      https://word-grabber.com/*
// @exclude      https://*.word-grabber.com/*
// @exclude      https://palabr.as/*
// @exclude      https://*.palabr.as/*
// @exclude      https://motscroises.fr/*
// @exclude      https://*.motscroises.fr/*
// @exclude      https://cruciverba.it/*
// @exclude      https://*.cruciverba.it/*
// @exclude      https://missyusa.com/*
// @exclude      https://*.missyusa.com/*
// @exclude      https://smsonline.cloud/*
// @exclude      https://*.smsonline.cloud/*
// @exclude      https://crosswordsolver.com/*
// @exclude      https://*.crosswordsolver.com/*
// @exclude      https://heureka.cz/*
// @exclude      https://*.heureka.cz/*
// @exclude      https://oradesibiu.ro/*
// @exclude      https://*.oradesibiu.ro/*
// @exclude      https://oeffnungszeitenbuch.de/*
// @exclude      https://*.oeffnungszeitenbuch.de/*
// @exclude      https://the-crossword-solver.com/*
// @exclude      https://*.the-crossword-solver.com/*
// @exclude      https://woxikon.*/*
// @exclude      https://*.woxikon.*/*
// @exclude      https://oraridiapertura24.it/*
// @exclude      https://*.oraridiapertura24.it/*
// @exclude      https://laleggepertutti.it/*
// @exclude      https://*.laleggepertutti.it/*
// @exclude      https://news4vip.livedoor.biz/*
// @exclude      *://onecall2ch.com/*
// @exclude      *://*.onecall2ch.com/*
// @exclude      https://ff14net.2chblog.jp/*
// @exclude      https://ondemandkorea.com/*
// @exclude      https://*.ondemandkorea.com/*
// @exclude      https://economictimes.com/*
// @exclude      https://*.economictimes.com/*
// @exclude      https://mynet.com/*
// @exclude      https://*.mynet.com/*
// @exclude      https://rabitsokuhou.2chblog.jp/*
// @exclude      https://talkwithstranger.com/*
// @exclude      https://*.talkwithstranger.com/*
// @exclude      https://petitfute.com/*
// @exclude      https://*.petitfute.com/*
// @exclude      https://netzwelt.de/*
// @exclude      https://*.netzwelt.de/*
// @exclude      https://convertcase.net/*
// @exclude      https://*.convertcase.net/*
// @exclude      https://picrew.me/*
// @exclude      https://*.picrew.me/*
// @exclude      https://rostercon.com/*
// @exclude      https://*.rostercon.com/*
// @exclude      https://woxikon.de/*
// @exclude      https://*.woxikon.de/*
// @exclude      https://suzusoku.blog.jp/*
// @exclude      https://kreuzwortraetsel.de/*
// @exclude      https://*.kreuzwortraetsel.de/*
// @exclude      https://slashdot.org/*
// @exclude      https://*.slashdot.org/*
// @exclude      https://yutura.net/*
// @exclude      https://*.yutura.net/*
// @exclude      https://jutarnji.hr/*
// @exclude      https://*.jutarnji.hr/*
// @exclude      https://sourceforge.net/*
// @exclude      https://*.sourceforge.net/*
// @exclude      https://manta.com/*
// @exclude      https://*.manta.com/*
// @exclude      https://tportal.hr/*
// @exclude      https://*.tportal.hr/*
// @exclude      https://horairesdouverture24.fr/*
// @exclude      https://*.horairesdouverture24.fr/*
// @exclude      https://nyitvatartas24.hu/*
// @exclude      https://*.nyitvatartas24.hu/*
// @exclude      https://verkaufsoffener-sonntag.com/*
// @exclude      https://*.verkaufsoffener-sonntag.com/*
// @exclude      https://raetsel-hilfe.de/*
// @exclude      https://*.raetsel-hilfe.de/*
// @exclude      https://zeta-ai.io/*
// @exclude      https://*.zeta-ai.io/*
// @exclude      https://zagreb.info/*
// @exclude      https://*.zagreb.info/*
// @exclude      https://powerpyx.com/*
// @exclude      https://*.powerpyx.com/*
// @exclude      https://webdesignledger.com/*
// @exclude      https://*.webdesignledger.com/*
// @exclude      https://dolldivine.com/*
// @exclude      https://*.dolldivine.com/*
// @exclude      https://cinema.com.my/*
// @exclude      https://*.cinema.com.my/*
// @exclude      https://lacuarta.com/*
// @exclude      https://*.lacuarta.com/*
// @exclude      https://wetteronline.de/*
// @exclude      https://*.wetteronline.de/*
// @exclude      https://yugioh-starlight.com/*
// @exclude      https://*.yugioh-starlight.com/*
// ==/UserScript==

(function() {
    'use strict';

    const createPatternManager = (storageKey, defaultPatterns = []) => ({
        STORAGE_KEY: storageKey,
        CACHE_DURATION: 5 * 60 * 1000,
        _cache: null,

        _patternToRegex(pattern) {
            const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
            return new RegExp(`^https?:\/\/${escaped}`, 'i');
        },

        _clearCache() {
            this._cache = null;
        },

        async _load() {
            if (this._cache && this._cache.expires > Date.now()) {
                return this._cache.patterns;
            }
            const patterns = await GM_getValue(this.STORAGE_KEY, defaultPatterns);
            this._cache = {
                patterns: new Set(patterns),
                expires: Date.now() + this.CACHE_DURATION,
            };
            return this._cache.patterns;
        },

        async isMatch(url) {
            if (!url || url.startsWith('about:')) return false;
            const patterns = await this._load();
            for (const pattern of patterns) {
                if (this._patternToRegex(pattern).test(url)) return true;
            }
            return false;
        },

        async add(pattern) {
            const patterns = await this._load();
            patterns.add(pattern);
            this._cache.patterns = patterns;
            await GM_setValue(this.STORAGE_KEY, Array.from(patterns));
        },

        async remove(pattern) {
            const patterns = await this._load();
            patterns.delete(pattern);
            this._cache.patterns = patterns;
            await GM_setValue(this.STORAGE_KEY, Array.from(patterns));
        },

        async getAll() {
            return Array.from(await this._load());
        },

        async replaceAll(newPatterns) {
            if (Array.isArray(newPatterns)) {
                await GM_setValue(this.STORAGE_KEY, newPatterns);
                this._clearCache();
            }
        }
    });

    const getSharedStyles = (isDarkMode) => `
        :host {
            --background: ${isDarkMode ? 'hsl(240 10% 3.9%)' : 'hsl(0 0% 100%)'};
            --foreground: ${isDarkMode ? 'hsl(0 0% 98%)' : 'hsl(240 10% 3.9%)'};
            --muted-foreground: ${isDarkMode ? 'hsl(240 3.7% 62.9%)' : 'hsl(240 3.7% 45.9%)'};
            --card: ${isDarkMode ? 'hsl(240 4.8% 12%)' : 'hsl(0 0% 100%)'};
            --border: ${isDarkMode ? 'hsl(240 3.7% 15.9%)' : 'hsl(240 5.9% 90%)'};
            --input: ${isDarkMode ? 'hsl(240 3.7% 15.9%)' : 'hsl(240 5.9% 90%)'};
            --primary: ${isDarkMode ? 'hsl(0 0% 98%)' : 'hsl(240 5.9% 10%)'};
            --primary-foreground: ${isDarkMode ? 'hsl(240 5.9% 10%)' : 'hsl(0 0% 98%)'};
            --secondary: ${isDarkMode ? 'hsl(240 3.7% 15.9%)' : 'hsl(240 4.9% 95.9%)'};
            --secondary-foreground: ${isDarkMode ? 'hsl(0 0% 98%)' : 'hsl(240 5.9% 10%)'};
            --destructive: ${isDarkMode ? 'hsl(0 72% 51%)' : 'hsl(0 84.2% 60.2%)'};
            --destructive-foreground: ${isDarkMode ? 'hsl(0 0% 98%)' : 'hsl(0 0% 98%)'};
            --constructive: ${isDarkMode ? 'hsl(142.1 70.6% 45.1%)' : 'hsl(142.1 76.2% 41.2%)'};
            --constructive-foreground: ${isDarkMode ? 'hsl(144.9 80.4% 10%)' : 'hsl(0 0% 98%)'};
            --overlay-bg: ${isDarkMode ? 'hsl(240 10% 3.9% / 0.5)' : 'hsl(0 0% 100% / 0.5)'};
        }
        .overlay {
            position: fixed; inset: 0; z-index: 2147483647;
            background-color: var(--overlay-bg);
            backdrop-filter: blur(8px);
            -webkit-backdrop-filter: blur(8px);
            display: flex; justify-content: center; align-items: center;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        .dialog {
            background-color: var(--card); color: var(--foreground); border: 1px solid var(--border);
            border-radius: 0.75rem; max-width: 90vw;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            animation: fadeIn 0.2s ease-out;
            display: flex; flex-direction: column;
        }
        @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
        .header { padding: 1.5rem; font-size: 1.125rem; font-weight: 600; border-bottom: 1px solid var(--border); }
        .content { padding: 1.5rem; }
        .footer { padding: 1.5rem; border-top: 1px solid var(--border); display: flex; gap: 0.75rem; }
        .input {
            flex-grow: 1; padding: 0.5rem 0.75rem; border: 1px solid var(--input);
            border-radius: 0.375rem; background-color: var(--background);
            color: var(--foreground); box-sizing: border-box;
        }
        .btn {
            padding: 0.5rem 1rem; border-radius: 0.375rem; border: none;
            cursor: pointer; font-weight: 500;
        }
        .btn-primary { background-color: var(--primary); color: var(--primary-foreground); }
        .btn-constructive { background-color: var(--constructive); color: var(--constructive-foreground); }
    `;

    const createManagementDialog = async (manager, title) => {
        document.querySelector('.popup-manager-container')?.remove();

        const container = document.createElement('div');
        container.className = 'popup-manager-container';
        const shadow = container.attachShadow({ mode: 'open' });
        const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
        const patterns = await manager.getAll();

        const style = document.createElement('style');
        style.textContent = `
            ${getSharedStyles(isDarkMode)}
            .dialog { width: 600px; max-height: 80vh; }
            .content { flex-grow: 1; overflow-y: auto; max-height: 50vh; }
            .pattern-item { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem; border-radius: 0.5rem; gap: 1rem; }
            .pattern-item:nth-child(odd) { background-color: ${isDarkMode ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.02)'}; }
            .pattern-text { font-family: monospace; font-size: 0.875rem; flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
            .btn-icon { background: none; border: none; cursor: pointer; padding: 0.5rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; }
            .btn-icon:hover { background-color: ${isDarkMode ? 'rgba(255, 82, 82, 0.2)' : 'rgba(220, 53, 69, 0.1)'}; }
            .btn-icon svg { stroke: var(--muted-foreground); transition: stroke 0.2s; }
            .btn-icon:hover svg { stroke: var(--destructive); }
        `;

        const overlay = document.createElement('div');
        overlay.className = 'overlay';
        overlay.innerHTML = `
            <div class="dialog">
                <div class="header">${title}</div>
                <div class="content"></div>
                <div class="footer">
                    <input type="text" class="input" placeholder="e.g., *.google.com/*">
                    <button class="btn btn-primary">Add</button>
                </div>
            </div>
        `;

        const dialog = overlay.querySelector('.dialog');
        const content = dialog.querySelector('.content');
        const addInput = dialog.querySelector('.input');

        const renderList = () => {
            content.innerHTML = '';
            patterns.sort();
            patterns.forEach(pattern => {
                const item = document.createElement('div');
                item.className = 'pattern-item';
                item.innerHTML = `
                    <span class="pattern-text" title="${pattern}">${pattern}</span>
                    <button class="btn-icon" title="Remove">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M3 6h18"></path>
                            <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                            <line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line>
                        </svg>
                    </button>
                `;
                item.querySelector('.btn-icon').onclick = async () => {
                    const index = patterns.indexOf(pattern);
                    if (index > -1) patterns.splice(index, 1);
                    await manager.remove(pattern);
                    renderList();
                };
                content.appendChild(item);
            });
        };

        dialog.querySelector('.btn-primary').onclick = async () => {
            const newPattern = addInput.value.trim();
            if (newPattern && !patterns.includes(newPattern)) {
                patterns.push(newPattern);
                await manager.add(newPattern);
                addInput.value = '';
                renderList();
            }
        };

        overlay.onclick = (e) => { if (e.target === overlay) container.remove(); };
        dialog.onclick = (e) => e.stopPropagation();

        renderList();
        shadow.append(style, overlay);
        document.body.appendChild(container);
    };

    const createPopupDialog = ({ url, onAllow }) => {
        document.querySelector('.popup-blocker-container')?.remove();
        const container = document.createElement('div');
        container.className = 'popup-blocker-container';
        const shadow = container.attachShadow({ mode: 'open' });
        const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
        const targetUrl = new URL(url);
        const targetDomain = targetUrl.hostname;
        const targetPath = targetUrl.pathname;

        const style = document.createElement('style');
        style.textContent = `
            ${getSharedStyles(isDarkMode)}
            .dialog { width: 400px; }
            .header { text-align: center; }
            .title { font-size: 1.125rem; font-weight: 600; }
            .description { font-size: 0.875rem; color: var(--muted-foreground); margin-top: 0.25rem; }
            .content { padding: 0 1.5rem 1.5rem; }
            .url-display { font-size: 0.8rem; background-color: ${isDarkMode ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.02)'}; padding: 0.5rem 0.75rem; border-radius: 0.375rem; word-break: break-all; max-height: 90px; overflow-y: auto; text-align: left; border: 1px solid var(--border); }
            .footer { flex-direction: column; }
            .btn { width: 100%; padding: 0.6rem; border: 1px solid transparent; }
            .btn-secondary { background-color: var(--secondary); color: var(--secondary-foreground); border-color: var(--border); }
            .btn-destructive { background-color: var(--destructive); color: var(--destructive-foreground); }
            .initial-buttons, .input-container { display: flex; flex-direction: column; gap: 0.75rem; }
            .input-container { display: none; margin-top: 0.5rem; animation: slideDown 0.3s ease-out; }
            @keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
            .input-header { display: flex; justify-content: flex-end; margin-bottom: 0.5rem; }
            .btn-back { background: none; border: none; color: var(--muted-foreground); cursor: pointer; font-size: 0.875rem; padding: 0.25rem; }
            .input { width: 100%; margin-bottom: 0.5rem; }
        `;

        const overlay = document.createElement('div');
        overlay.className = 'overlay';
        overlay.innerHTML = `
            <div class="dialog">
                <div class="header">
                    <div class="title">Popup Request</div>
                    <div class="description">A script is trying to open a new tab.</div>
                </div>
                <div class="content">
                    <div class="url-display">${url}</div>
                </div>
                <div class="footer">
                    <button class="btn btn-primary btn-allow-once">Allow Once</button>
                    <div class="initial-buttons">
                        <button class="btn btn-secondary btn-show-whitelist">Always Allow...</button>
                        <button class="btn btn-secondary btn-show-blocklist">Always Block...</button>
                    </div>
                    <div class="input-container whitelist-input-container">
                        <div class="input-header"><button class="btn-back">← Back</button></div>
                        <input type="text" class="input" value="${targetDomain}${targetPath === '/' ? '/*' : ''}">
                        <button class="btn btn-constructive btn-confirm-whitelist">Allow & Add to Whitelist</button>
                    </div>
                    <div class="input-container blocklist-input-container">
                        <div class="input-header"><button class="btn-back">← Back</button></div>
                        <input type="text" class="input" value="${targetDomain}">
                        <button class="btn btn-destructive btn-confirm-blocklist">Block & Add to Blocklist</button>
                    </div>
                </div>
            </div>
        `;

        const dialog = overlay.querySelector('.dialog');
        const removeDialog = () => container.remove();

        const initialButtons = dialog.querySelector('.initial-buttons');
        const whitelistContainer = dialog.querySelector('.whitelist-input-container');
        const blocklistContainer = dialog.querySelector('.blocklist-input-container');
        const whitelistInput = whitelistContainer.querySelector('.input');
        const blocklistInput = blocklistContainer.querySelector('.input');

        const showInitialButtons = () => {
            initialButtons.style.display = 'flex';
            whitelistContainer.style.display = 'none';
            blocklistContainer.style.display = 'none';
        };

        const showInputContainer = (containerToShow, inputToFocus) => {
            initialButtons.style.display = 'none';
            containerToShow.style.display = 'block';
            inputToFocus.focus();
            inputToFocus.select();
        };

        dialog.querySelector('.btn-allow-once').onclick = () => { onAllow(); removeDialog(); };
        initialButtons.querySelector('.btn-show-whitelist').onclick = () => showInputContainer(whitelistContainer, whitelistInput);
        initialButtons.querySelector('.btn-show-blocklist').onclick = () => showInputContainer(blocklistContainer, blocklistInput);
        whitelistContainer.querySelector('.btn-back').onclick = showInitialButtons;
        blocklistContainer.querySelector('.btn-back').onclick = showInitialButtons;

        dialog.querySelector('.btn-confirm-whitelist').onclick = async () => {
            const pattern = whitelistInput.value.trim();
            if (pattern) {
                await whitelistManager.add(pattern);
                onAllow();
                removeDialog();
            }
        };

        dialog.querySelector('.btn-confirm-blocklist').onclick = async () => {
            const pattern = blocklistInput.value.trim();
            if (pattern) {
                await blocklistManager.add(pattern);
                removeDialog();
            }
        };

        overlay.onclick = (e) => { if (e.target === overlay) removeDialog(); };
        dialog.onclick = (e) => e.stopPropagation();

        shadow.append(style, overlay);
        document.body.appendChild(container);
    };

    const createImportExportDialog = async () => {
        document.querySelector('.popup-manager-container')?.remove();

        const container = document.createElement('div');
        container.className = 'popup-manager-container';
        const shadow = container.attachShadow({ mode: 'open' });
        const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;

        const style = document.createElement('style');
        style.textContent = `
            ${getSharedStyles(isDarkMode)}
            .dialog { width: 600px; }
            .content { display: flex; flex-direction: column; gap: 1.5rem; }
            .section-title { font-weight: 500; margin-bottom: 0.5rem; }
            .textarea {
                width: 100%; min-height: 120px; box-sizing: border-box;
                padding: 0.5rem 0.75rem; border: 1px solid var(--input);
                border-radius: 0.375rem; background-color: var(--background);
                color: var(--foreground); font-family: monospace; resize: vertical;
            }
            .footer { justify-content: flex-end; }
            .status-message { font-size: 0.875rem; margin-top: 0.5rem; }
            .success { color: var(--constructive); }
            .error { color: var(--destructive); }
        `;

        const overlay = document.createElement('div');
        overlay.className = 'overlay';
        overlay.innerHTML = `
            <div class="dialog">
                <div class="header">Import / Export Lists</div>
                <div class="content">
                    <div>
                        <div class="section-title">Export</div>
                        <textarea class="textarea export-area" readonly></textarea>
                        <div class="footer">
                            <button class="btn btn-primary btn-copy">Copy to Clipboard</button>
                        </div>
                    </div>
                    <div>
                        <div class="section-title">Import (Merges with existing)</div>
                        <textarea class="textarea import-area" placeholder="Paste your JSON data here..."></textarea>
                        <div class="import-status"></div>
                        <div class="footer">
                            <button class="btn btn-constructive btn-import">Import</button>
                        </div>
                    </div>
                </div>
            </div>
        `;

        shadow.append(style, overlay);

        const dialog = overlay.querySelector('.dialog');
        const exportArea = dialog.querySelector('.export-area');
        const importArea = dialog.querySelector('.import-area');
        const copyBtn = dialog.querySelector('.btn-copy');
        const importBtn = dialog.querySelector('.btn-import');
        const importStatus = dialog.querySelector('.import-status');

        const populateExport = async () => {
            const data = {
                whitelist: await whitelistManager.getAll(),
                blocklist: await blocklistManager.getAll()
            };
            exportArea.value = JSON.stringify(data, null, 2);
        };
        populateExport();

        copyBtn.onclick = async () => {
            try {
                await navigator.clipboard.writeText(exportArea.value);
                copyBtn.textContent = 'Copied!';
            } catch (err) {
                copyBtn.textContent = 'Failed!';
                console.error('BlockPup: Failed to copy text: ', err);
            } finally {
                setTimeout(() => { copyBtn.textContent = 'Copy to Clipboard'; }, 2000);
            }
        };

        importBtn.onclick = async () => {
            const jsonString = importArea.value.trim();
            if (!jsonString) {
                importStatus.innerHTML = `<p class="status-message error">Text area is empty.</p>`;
                return;
            }

            try {
                const data = JSON.parse(jsonString);
                if (!Array.isArray(data.whitelist) || !Array.isArray(data.blocklist)) {
                     throw new Error("Invalid data structure. 'whitelist' and 'blocklist' must be arrays.");
                }
                const currentWhitelist = await whitelistManager.getAll();
                const currentBlocklist = await blocklistManager.getAll();

                const mergedWhitelist = [...new Set([...currentWhitelist, ...data.whitelist])];
                const mergedBlocklist = [...new Set([...currentBlocklist, ...data.blocklist])];

                await whitelistManager.replaceAll(mergedWhitelist);
                await blocklistManager.replaceAll(mergedBlocklist);

                const addedWhitelistCount = mergedWhitelist.length - currentWhitelist.length;
                const addedBlocklistCount = mergedBlocklist.length - currentBlocklist.length;

                importStatus.innerHTML = `<p class="status-message success">Merge successful! Added ${addedWhitelistCount} whitelist and ${addedBlocklistCount} blocklist patterns.</p>`;
                setTimeout(() => container.remove(), 2500);
            } catch (e) {
                importStatus.innerHTML = `<p class="status-message error">Error: ${e.message}</p>`;
            }
        };

        overlay.onclick = (e) => { if (e.target === overlay) container.remove(); };
        dialog.onclick = (e) => e.stopPropagation();

        document.body.appendChild(container);
    };

    const whitelistManager = createPatternManager('popup_whitelist_patterns', ['localhost', '127.0.0.1']);
    const blocklistManager = createPatternManager('popup_blocklist_patterns');
    const originalOpenFunctions = new WeakMap();

    const decideOnPopup = async (url, name, features, openCallback) => {
        let fullUrl;
        try {
            fullUrl = (!url || String(url).trim().toLowerCase().startsWith('about:'))
                ? 'about:blank'
                : new URL(url, window.location.origin).href;
        } catch (e) {
            console.warn('BlockPup: Invalid URL passed to open():', url, e);
            return null;
        }

        if (await blocklistManager.isMatch(fullUrl)) {
            console.log(`BlockPup: Blocked popup to "${fullUrl}" by blocklist.`);
            return null;
        }

        if (await whitelistManager.isMatch(fullUrl)) {
            return openCallback(url, name, features);
        }

        createPopupDialog({
            url: fullUrl,
            onAllow: () => openCallback(url, name, features)
        });

        return null;
    };

    const patchWindow = (win) => {
        if (!win || originalOpenFunctions.has(win)) return;

        try {
            const originalOpen = win.open;
            if (typeof originalOpen !== 'function') return;

            originalOpenFunctions.set(win, originalOpen);

            const hijacker = function(...args) {
                return decideOnPopup(...args, originalOpen.bind(this));
            };

            Object.defineProperty(win, 'open', {
                value: hijacker,
                writable: true,
                configurable: true
            });
        } catch (e) {
            // Fails for cross-origin iframes, which is expected.
        }
    };

    const initializePopupInterceptor = () => {
        patchWindow(unsafeWindow);

        const observer = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.tagName === 'IFRAME') {
                        node.addEventListener('load', () => patchWindow(node.contentWindow), { once: true, passive: true });
                        patchWindow(node.contentWindow);
                    }
                }
            }
        });

        observer.observe(document.documentElement, { childList: true, subtree: true });

        const interceptEvent = (e) => {
            const target = e.target;
            let url = null;

            let baseTargetIsBlank = null;
            const getBaseTargetIsBlank = () => {
                if (baseTargetIsBlank === null) {
                    baseTargetIsBlank = !!document.querySelector('base[target="_blank"]');
                }
                return baseTargetIsBlank;
            };

            const link = target.closest('a');
            if (link && link.href) {
                if (link.target === '_blank' || (getBaseTargetIsBlank() && !link.hasAttribute('target'))) {
                    url = link.href;
                }
            }

            const form = target.closest('form');
            if (!url && form && form.action) {
                if (form.target === '_blank' || (getBaseTargetIsBlank() && !form.hasAttribute('target'))) {
                    url = form.action;
                }
            }

            if (url) {
                e.preventDefault();
                e.stopImmediatePropagation();
                const originalMainWindowOpen = originalOpenFunctions.get(unsafeWindow);
                if (originalMainWindowOpen) {
                    decideOnPopup(url, '_blank', null, originalMainWindowOpen.bind(unsafeWindow));
                }
            }
        };

        document.addEventListener('click', interceptEvent, true);
        document.addEventListener('submit', interceptEvent, true);
    };

    initializePopupInterceptor();
    GM_registerMenuCommand('Manage Whitelist', () => createManagementDialog(whitelistManager, 'Manage Whitelist Patterns'));
    GM_registerMenuCommand('Manage Blocklist', () => createManagementDialog(blocklistManager, 'Manage Blocklist Patterns'));
    GM_registerMenuCommand('Import/Export Lists', createImportExportDialog);

})();