// ==UserScript== // @name Sinflix Modifier // @namespace hhttps://openuserjs.org/users/asurpbs // @version 26.4.15.3 // @description Enhances SinFlix pages with Google & MyDramaList search icons, BuzzHeavier ID auto-linking, back-to-top button, inline search, customizable section ordering, and a SinFlix chat button. On pst.moe: clickable links, copy-all-links per resolution, and Mega.nz bypass circles (click to instantly bypass & download, or copy all bypass links). On mega.nz file pages: floating bypass download button that skips Mega quota limits. // @license MIT // @author asurpbs // @match https://rentry.co/sin-flix // @match https://text.is/Sinflix // @match https://pst.moe/paste/* // @match https://buzzheavier.com/* // @match https://mega.nz/file/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @run-at document-start // @copyright 2026, asurpbs (https://openuserjs.org/users/asurpbs) // @downloadURL none // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const config = { showGoogleCircle: GM_getValue('showGoogleCircle', true), showMdlCircle: GM_getValue('showMdlCircle', true), convertBuzzheavierLinks: GM_getValue('convertBuzzheavierLinks', true), showBackToTopButton: GM_getValue('showBackToTopButton', true), // NEW: Add setting for link opening style, defaulting to 'popup' linkOpenStyle: GM_getValue('linkOpenStyle', 'popup'), // NEW: Add setting for moving Currently Airing to top moveCurrentlyAiringToTop: GM_getValue('moveCurrentlyAiringToTop', false), // NEW: Add setting for SinFlix chat box button showChatBoxButton: GM_getValue('showChatBoxButton', true), // NEW: Add setting for chat box opening style, defaulting to 'tab' chatBoxOpenStyle: GM_getValue('chatBoxOpenStyle', 'popup'), // NEW: pst.moe enhancements pstMoeEnhancements: GM_getValue('pstMoeEnhancements', true), // NEW: Google search keyword suffix googleSearchSuffix: GM_getValue('googleSearchSuffix', 'TV Series'), // NEW: Buzzheavier enhancements buzzheavierEnhancements: GM_getValue('buzzheavierEnhancements', false), buzzSplitQuality: GM_getValue('buzzSplitQuality', false), buzzDirectDownload: GM_getValue('buzzDirectDownload', false), buzzCopyLinks: GM_getValue('buzzCopyLinks', false), // NEW: Mega.nz bypass megaBypass: GM_getValue('megaBypass', true), // NEW: Mega.nz floating download button megaNzButton: GM_getValue('megaNzButton', true), // NEW: URL prefixes that trigger "Copy Links" button in pst.moe sections (one per line) copyLinksPatterns: GM_getValue('copyLinksPatterns', 'https://mega.nz/'), // NEW: Top search bar with Dynamic Island animation showTopSearchBar: GM_getValue('showTopSearchBar', true) }; // --- Style Definitions --- GM_addStyle(` /* --- Buzzheavier Tool Buttons --- */ .bh-actions { display: inline-flex; gap: 4px; margin-left: 12px; vertical-align: middle; opacity: 0.7; transition: opacity 0.2s ease; } .bh-actions.single-page { opacity: 0.9; margin-left: 8px; } .bh-actions.single-page:hover { opacity: 1; } tr.editable:hover .bh-actions { opacity: 1; } .bh-btn { cursor: pointer; border: none; background: transparent; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: inherit; transition: all 0.2s ease; } .bh-actions.single-page .bh-btn { color: #ccc; padding: 6px; } .bh-btn:hover { background-color: rgba(255, 255, 255, 0.15); transform: scale(1.1); color: #fff; box-shadow: 0 0 8px rgba(0,0,0,0.2); } .bh-btn.play-btn:hover { color: #4ade80; } .bh-btn.copy-btn:hover { color: #60a5fa; } .bh-btn.dl-btn:hover { color: #f472b6; } .bh-btn svg { width: 18px; height: 18px; fill: currentColor; stroke: currentColor; stroke-width: 0; } .bh-actions.single-page .bh-btn svg { width: 20px; height: 20px; } .bh-btn.loading svg { animation: spin 0.8s linear infinite; fill: #fbbf24; } @keyframes spin { 100% { transform: rotate(360deg); } } /* --- pst.moe Enhancements --- */ .sinflix-res-header { display: inline-flex; align-items: center; gap: 10px; font-weight: bold; margin: 8px 0; } .sinflix-copy-btn { background: #2a2b2c; color: #e8eaed; border: 1px solid #5f6368; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 11px; font-family: inherit; transition: all 0.2s; } .sinflix-copy-btn:hover { background: #1a73e8; border-color: #1a73e8; color: white; } /* --- Mega Bypass Circle --- */ .sinflix-mega-circle { display: inline-block; width: 13px; height: 13px; border-radius: 50%; background: #9aa0a6; cursor: pointer; vertical-align: middle; margin-left: 5px; opacity: 0.5; transition: opacity 0.2s ease, transform 0.2s ease, background 0.2s ease; flex-shrink: 0; } .sinflix-mega-circle:hover { opacity: 1; transform: scale(1.25); background: #34a853; } .sinflix-mega-bypass-header { display: inline-flex; align-items: center; gap: 10px; margin: 6px 0; } .sinflix-copy-bypass-btn { background: #2a2b2c; color: #e8eaed; border: 1px solid #9aa0a6; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 11px; font-family: inherit; transition: all 0.2s; } .sinflix-copy-bypass-btn:hover { background: #34a853; border-color: #34a853; color: white; } /* --- Prevent section flash on load --- */ .entry-text article h4 { opacity: 0; transition: opacity 0.1s ease-in-out; } .entry-text article h4.sinflix-visible { opacity: 1; } .entry-text article.sinflix-processed h4 { opacity: 1; } /* --- Rentry content box rounded corners --- */ .col-12.long-words { border-radius: 12px; overflow: hidden; } .entry-text { border-radius: 12px; overflow: hidden; } /* --- Settings Button --- */ /* Settings button is inside the search capsule when capsule is ON */ #kdrama-settings-button { position: fixed; top: 20px; right: 20px; width: 40px; height: 40px; z-index: 10001; background: rgba(50, 50, 50, 0.4); backdrop-filter: blur(6px); border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background 0.3s ease; } #kdrama-settings-button:hover { background: rgba(80, 80, 80, 0.6); } /* --- Modal Styles --- */ #kdrama-settings-modal { display: none; position: fixed; z-index: 10002; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); backdrop-filter: blur(4px); justify-content: center; align-items: center; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; opacity: 0; transition: opacity 0.2s ease-out; } #kdrama-settings-modal.show { opacity: 1; } .kdrama-modal-content { background: #202124; color: #e8eaed; padding: 0; border-radius: 8px; width: 95%; max-width: 520px; max-height: 90vh; box-shadow: 0 8px 32px rgba(0,0,0,0.6); border: 1px solid #3c4043; position: relative; overflow: hidden; transform: scale(0.9); transition: transform 0.2s ease-out; } #kdrama-settings-modal.show .kdrama-modal-content { transform: scale(1); } .kdrama-modal-header { background: #2d2e30; color: #e8eaed; padding: 16px 20px; border-radius: 8px 8px 0 0; position: relative; border-bottom: 1px solid #3c4043; } .kdrama-modal-header h2 { margin: 0; font-size: 18px; font-weight: 500; display: flex; align-items: center; gap: 8px; color: #e8eaed; } .kdrama-modal-header .header-icon { width: 20px; height: 20px; } #kdrama-settings-close { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); width: 28px; height: 28px; border-radius: 4px; background: transparent; border: none; color: #9aa0a6; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s ease, color 0.2s ease; } #kdrama-settings-close:hover { background: #3c4043; color: #e8eaed; } .kdrama-modal-body { padding: 20px; max-height: 60vh; overflow-y: auto; background: #202124; } .kdrama-modal-body::-webkit-scrollbar { width: 8px; } .kdrama-modal-body::-webkit-scrollbar-track { background: #2d2e30; border-radius: 4px; } .kdrama-modal-body::-webkit-scrollbar-thumb { background: #5f6368; border-radius: 4px; } .kdrama-modal-body::-webkit-scrollbar-thumb:hover { background: #80868b; } /* Settings Sections */ .kdrama-settings-section { margin-bottom: 32px; } .kdrama-settings-section:last-child { margin-bottom: 0; } .kdrama-section-title { font-size: 16px; font-weight: 500; color: #9aa0a6; margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px; text-transform: uppercase; letter-spacing: 0.5px; } .kdrama-section-title .section-icon { width: 16px; height: 16px; } .kdrama-toggle-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: #2d2e30; border-radius: 6px; margin-bottom: 8px; border: 1px solid #3c4043; transition: all 0.2s ease; } .kdrama-toggle-item:last-child { margin-bottom: 0; } .kdrama-toggle-item:hover { background: #35363a; border-color: #5f6368; } .kdrama-toggle-info { flex: 1; } .kdrama-toggle-label { font-size: 14px; font-weight: 500; margin: 0 0 2px 0; color: #e8eaed; } .kdrama-toggle-description { font-size: 12px; color: #9aa0a6; margin: 0; line-height: 1.3; } /* Custom Toggle Switch */ .kdrama-toggle-switch { position: relative; width: 50px; height: 26px; margin-left: 16px; } .kdrama-toggle-switch input { opacity: 0; width: 0; height: 0; } .kdrama-toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #5f6368; transition: 0.3s; border-radius: 26px; } .kdrama-toggle-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 4px; bottom: 4px; background-color: #ffffff; transition: 0.3s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.3); } .kdrama-toggle-switch input:checked + .kdrama-toggle-slider { background: #1a73e8; } .kdrama-toggle-switch input:checked + .kdrama-toggle-slider:before { transform: translateX(24px); } .kdrama-text-input { width: 100%; background: #3c4043; border: 1px solid #5f6368; color: #e8eaed; padding: 8px 12px; border-radius: 4px; font-size: 14px; box-sizing: border-box; transition: all 0.2s ease; font-family: inherit; } .kdrama-text-input:focus { outline: none; border-color: #1a73e8; background: #202124; } .kdrama-radio-group { background: #2d2e30; border-radius: 6px; padding: 16px; border: 1px solid #3c4043; margin-bottom: 16px; } .kdrama-radio-group-title { font-size: 14px; font-weight: 500; color: #e8eaed; margin: 0 0 12px 0; } .kdrama-radio-options { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .kdrama-radio-option { position: relative; } .kdrama-radio-option input[type="radio"] { opacity: 0; position: absolute; width: 100%; height: 100%; margin: 0; cursor: pointer; } .kdrama-radio-option-label { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: #3c4043; border: 1px solid #5f6368; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; font-size: 13px; font-weight: 400; color: #e8eaed; } .kdrama-radio-option input:checked + .kdrama-radio-option-label { background: #1a73e8; border-color: #1a73e8; color: white; } .kdrama-radio-option-label:hover { background: #484a4d; border-color: #80868b; } .kdrama-radio-option input:checked + .kdrama-radio-option-label:hover { background: #1557b0; border-color: #1557b0; } .kdrama-radio-option-icon { width: 14px; height: 14px; } .kdrama-modal-footer { padding: 16px 20px; background: #2d2e30; border-radius: 0 0 8px 8px; border-top: 1px solid #3c4043; } #kdrama-save-button { background: #1a73e8; color: white; padding: 10px 20px; font-size: 14px; font-weight: 500; border-radius: 4px; border: none; cursor: pointer; width: 100%; transition: background 0.2s ease; } #kdrama-save-button:hover { background: #1557b0; } #kdrama-save-button:active { background: #1142a0; } .kdrama-circle-container { display: inline-flex; align-items: center; gap: 3px; margin-right: 5px; vertical-align: middle; position: relative; top: -1px; } .kdrama-circle { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 7px; border: none; cursor: pointer; font-size: 10px; font-weight: 700; font-family: "Segoe UI", system-ui, sans-serif; letter-spacing: -0.2px; line-height: 1; opacity: 0.28; transition: opacity 0.18s ease, transform 0.18s ease, background 0.18s ease, box-shadow 0.18s ease; user-select: none; flex-shrink: 0; } .kdrama-circle:hover { opacity: 1; transform: scale(1.18); } .google-circle { background: rgba(66, 133, 244, 0.15); color: #4285f4; border: 1px solid rgba(66, 133, 244, 0.3); } .google-circle::after { content: "G"; } .google-circle:hover { background: #4285f4; color: #fff; border-color: #4285f4; box-shadow: 0 2px 8px rgba(66, 133, 244, 0.45); } .mdl-circle { background: rgba(0, 150, 136, 0.12); color: #00897b; border: 1px solid rgba(0, 150, 136, 0.28); } .mdl-circle::after { content: "M"; } .mdl-circle:hover { background: #00897b; color: #fff; border-color: #00897b; box-shadow: 0 2px 8px rgba(0, 150, 136, 0.45); } /* --- Floating Buttons (Back to Top & Search) --- */ .kdrama-float-button { position: fixed; width: 44px; height: 44px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.2) !important; outline: none !important; background: rgba(30, 30, 30, 0.4); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 10003; user-select: none; transition: background-color 0.3s ease, opacity 0.3s ease, border 0.3s ease, bottom 0.5s cubic-bezier(0.4, 0.0, 0.2, 1); opacity: 0; pointer-events: none; right: 20px; } .kdrama-float-button.show { opacity: 1; pointer-events: auto; /* Enable interaction when shown */ } .kdrama-float-button:hover, .kdrama-float-button:focus, .kdrama-float-button:active { background: rgba(50, 50, 50, 0.6); border: 1px solid rgba(255, 255, 255, 0.35) !important; outline: none !important; transform: scale(1.05); transition: background-color 0.3s ease, opacity 0.3s ease, border 0.3s ease, bottom 0.5s cubic-bezier(0.4, 0.0, 0.2, 1), transform 0.2s ease; } #kdrama-back-to-top { bottom: 30px; } #kdrama-search-button { bottom: 30px; /* Will be dynamically positioned */ } #kdrama-chat-button { bottom: 84px; /* Will be dynamically positioned */ } /* --- Floating Notification --- */ .kdrama-notification { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.9); color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 10005; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; font-size: 14px; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; pointer-events: none; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); } .kdrama-notification.show { opacity: 1; transform: translateX(-50%) translateY(10px); } .kdrama-notification.success { background: rgba(34, 139, 34, 0.9); border-color: rgba(34, 139, 34, 0.3); } .kdrama-notification.error { background: rgba(220, 20, 60, 0.9); border-color: rgba(220, 20, 60, 0.3); } .kdrama-notification.info { background: rgba(30, 144, 255, 0.9); border-color: rgba(30, 144, 255, 0.3); } /* --- Top Search Bar (Dynamic Island) --- */ #sfx-top-searchbar-wrap { position: fixed; top: 14px; left: 50%; transform: translateX(-50%); z-index: 10006; /* Dynamic Island pill shape */ width: min(560px, calc(100vw - 32px)); height: 48px; border-radius: 28px; background: rgba(18, 18, 22, 0.82); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(255,255,255,0.13); box-shadow: 0 4px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(0,0,0,0.3); display: flex; align-items: center; padding: 0 16px; gap: 10px; overflow: hidden; cursor: text; /* Transition for all morphing */ transition: width 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), height 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), border-radius 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), background 0.4s ease, box-shadow 0.4s ease, top 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), opacity 0.35s ease; } #sfx-top-searchbar-wrap.sfx-collapsed { width: 192px; height: 34px; border-radius: 17px; border: 1px solid rgba(255,255,255,0.07); background: rgba(22, 22, 26, 0.72); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); cursor: pointer; box-shadow: none; padding: 0 14px 0 10px; justify-content: center; gap: 6px; overflow: hidden; } #sfx-top-searchbar-wrap.sfx-collapsed:hover { background: rgba(38, 38, 44, 0.82); border-color: rgba(255,255,255,0.13); transform: translateX(-50%) scale(1.03); } #sfx-top-searchbar-wrap.sfx-expanding { /* Briefly scale up a touch during expansion */ } #sfx-top-search-icon { display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: rgba(255,255,255,0.55); pointer-events: none; transition: color 0.3s ease; } #sfx-top-searchbar-wrap:hover #sfx-top-search-icon { color: rgba(255,255,255,0.85); } /* Collapsed: icon stays in normal flex flow, centred by justify-content on parent */ #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-icon { color: rgba(255,255,255,0.38); flex-shrink: 0; } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-icon svg { width: 13px; height: 13px; } #sfx-top-searchbar-wrap.sfx-collapsed:hover #sfx-top-search-icon { color: rgba(255,255,255,0.65); } /* Collapsed label */ #sfx-top-search-label { font-size: 12px; font-weight: 400; font-family: "Segoe UI", system-ui, sans-serif; color: rgba(255,255,255,0.32); white-space: nowrap; letter-spacing: 0.3px; /* Hidden in expanded state — use max-width so transition works (auto is not animatable) */ max-width: 0; opacity: 0; overflow: hidden; pointer-events: none; transition: opacity 0.35s ease, max-width 0.45s cubic-bezier(0.34, 1.38, 0.64, 1); } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-label { max-width: 100px; opacity: 1; } #sfx-top-searchbar-wrap.sfx-collapsed:hover #sfx-top-search-label { color: rgba(255,255,255,0.62); } #sfx-top-search-input { flex: 1; background: transparent; border: none; outline: none; color: #fff; font-size: 15px; font-family: "Segoe UI", system-ui, sans-serif; caret-color: #a78bfa; min-width: 0; transition: opacity 0.3s ease, width 0.4s ease; } #sfx-top-search-input::placeholder { color: rgba(255,255,255,0.38); } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-input { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } #sfx-top-search-count { font-size: 12px; color: rgba(255,255,255,0.45); white-space: nowrap; flex-shrink: 0; transition: opacity 0.3s ease, width 0.4s ease; } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-count { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } .sfx-top-nav-btn { background: none; border: none; color: rgba(255,255,255,0.55); width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: background 0.2s, color 0.2s, opacity 0.3s, width 0.4s; } .sfx-top-nav-btn:hover:not(:disabled) { background: rgba(255,255,255,0.12); color: #fff; } .sfx-top-nav-btn:disabled { opacity: 0.25; cursor: not-allowed; } #sfx-top-searchbar-wrap.sfx-collapsed .sfx-top-nav-btn { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } #sfx-top-search-close { background: none; border: none; color: rgba(255,255,255,0.45); font-size: 18px; line-height: 1; cursor: pointer; flex-shrink: 0; padding: 0 2px; transition: color 0.2s, opacity 0.3s, width 0.4s; } #sfx-top-search-close:hover { color: #fff; } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-close { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } /* --- Capsule action buttons (settings + chat) --- */ .sfx-cap-sep { display: none; } .sfx-cap-action { background: none !important; border: none !important; outline: none !important; box-shadow: none !important; -webkit-appearance: none; color: rgba(255,255,255,0.38); width: 26px; height: 26px; border-radius: 0 !important; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; padding: 0; transition: color 0.18s ease; } .sfx-cap-action:hover, .sfx-cap-action:focus, .sfx-cap-action:active { background: none !important; border: none !important; outline: none !important; box-shadow: none !important; color: rgba(255,255,255,0.85); } .sfx-cap-action svg { width: 14px; height: 14px; } /* In expanded state: shrink separator+actions slightly so they don't dominate */ #sfx-top-searchbar-wrap:not(.sfx-collapsed) .sfx-cap-sep { height: 14px; } #kdrama-search-modal { position: fixed; bottom: 0; left: 0; width: 100%; z-index: 10004; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 -4px 15px rgba(0,0,0,0.3); padding: 15px 20px; box-sizing: border-box; display: flex; justify-content: center; align-items: center; opacity: 0; pointer-events: none; transition: opacity 0.2s ease-in-out; } #kdrama-search-modal.show { opacity: 1; pointer-events: auto; } .kdrama-search-controls { position: relative; width: calc(100% - 60px); max-width: 500px; display: flex; align-items: center; background-color: transparent; border-radius: 8px; } .kdrama-search-input-wrapper { position: relative; flex-grow: 1; display: flex; align-items: center; } #kdrama-search-input { width: 100%; padding: 12px 15px 12px 40px; border: none; border-radius: 8px; background-color: #333; color: white; font-size: 16px; outline: none; box-sizing: border-box; } #kdrama-search-input:focus { outline: 1px solid #0078D4; } #kdrama-search-input::placeholder { color: #bbb; } .kdrama-search-icon { position: absolute; left: 12px; color: #bbb; font-size: 18px; pointer-events: none; } .kdrama-search-nav-buttons { display: flex; align-items: center; margin-left: 10px; gap: 5px; } .kdrama-search-nav-button { background: none; border: none; color: white; width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0.7; transition: background-color 0.2s, opacity 0.2s; } .kdrama-search-nav-button:hover:not(:disabled) { background-color: rgba(255, 255, 255, 0.1); opacity: 1; } .kdrama-search-nav-button:disabled { opacity: 0.3; cursor: not-allowed; } #kdrama-search-close { background: none; border: none; color: white; font-size: 24px; cursor: pointer; margin-left: 15px; line-height: 1; opacity: 0.7; transition: opacity 0.2s; } #kdrama-search-close:hover { opacity: 1; } /* --- Highlight style --- */ .kdrama-highlight { background-color: #FFEB3B; color: black; font-weight: bold; padding: 2px 0; border-radius: 2px; } .kdrama-highlight.current { background-color: #4CAF50; color: white; box-shadow: 0 0 8px rgba(76, 175, 80, 0.8); } code .kdrama-highlight { background-color: #383131; color: #e0aeb4; } code .kdrama-highlight.current { background-color: #6a5e5e; color: #e0aeb4; } `); // --- Enhanced Drama Detection Patterns --- const dramaPatterns = [ // Pattern for lines with (reup) or (redo) prefix, broken by HTML tags. /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[/, // NEW: Pattern for drama names followed by mixed brackets like (AMZN...] with episode info /^(?:\(reup\)|\(redo\))?\s*([^([]+?)\s*[\[(][^\])]*[\])][\s\S]*?\((?:e?\d+(?:\s+of\s+\d+)?|\d+)ep\)\s*-/i, // NEW: Pattern for lines WITHOUT a prefix, broken by HTML tags. /^([a-zA-Z0-9][^[]*?)\s*\[/, // NEW: Pattern for drama names followed by "- coming soon" /^(?:\(reup\)|\(redo\))?\s*([^-]+?)\s*-\s*coming\s+soon/i, // NEW: Pattern for drama names followed by just "-" (nothing or whitespace after) /^(?:\(reup\)|\(redo\))?\s*([^-]+?)\s*-\s*$/, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\((?:e?\d+(?:\s+of\s+\d+)?|\d+)ep\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?(?:\s+S\d+)?)\s*\[.*?\]\s*\(\d+ep\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\(e\d+\s+of\s+\d+\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\(.*?ep.*?\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\((?:e?\d+(?:\s+of\s+\d+)?|\d+)ep\)/i ]; // --- Helper function to extract drama name --- function extractDramaName(text) { const cleanText = text.trim(); if (cleanText.length < 10) return null; for (const pattern of dramaPatterns) { const match = cleanText.match(pattern); if (match && match[1]) { let dramaName = match[1].trim().replace(/:$/, '').trim().replace(/\s+/g, ' '); // Allow drama names as short as 1 character (like "M" or "DP") if (dramaName.length > 0 && dramaName.length < 200) return dramaName; } } return null; } // --- Helper function to open windows in the center of the screen --- function openInCenter(url, title) { const popWidth = 1000, popHeight = 700; // Get the actual screen dimensions const screenWidth = window.screen.availWidth || window.screen.width; const screenHeight = window.screen.availHeight || window.screen.height; // Calculate center position const left = Math.round((screenWidth / 2) - (popWidth / 2)); const top = Math.round((screenHeight / 2) - (popHeight / 2)); // Ensure the window doesn't go off-screen const finalLeft = Math.max(0, left); const finalTop = Math.max(0, top); const features = `width=${popWidth},height=${popHeight},top=${finalTop},left=${finalLeft},resizable=yes,scrollbars=yes,status=no,toolbar=no,menubar=no,location=no`; window.open(url, title, features); } // --- Helper function to show floating notifications --- function showNotification(message, type = 'info', duration = 3000) { // Remove any existing notification const existingNotification = document.querySelector('.kdrama-notification'); if (existingNotification) { existingNotification.remove(); } // Create new notification const notification = document.createElement('div'); notification.className = `kdrama-notification ${type}`; notification.textContent = message; document.body.appendChild(notification); // Trigger animation setTimeout(() => { notification.classList.add('show'); }, 10); // Auto-remove after specified duration setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); }, duration); } // --- Helper function to copy text to clipboard --- async function copyToClipboard(text) { try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return true; } else { // Fallback for older browsers or non-secure contexts const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); const result = document.execCommand('copy'); document.body.removeChild(textArea); return result; } } catch (err) { console.error('Failed to copy text: ', err); return false; } } // --- Function to get MDL first result URL for copying --- async function getMdlFirstResultUrl(searchUrl, dramaName) { try { // Show loading indicator const circles = document.querySelectorAll('.mdl-circle'); circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = '0.5'; circle.style.cursor = 'wait'; } }); const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: searchUrl, timeout: 5000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Cache-Control': 'no-cache' }, onload: function(response) { resolve(response); }, onerror: function(error) { reject(error); }, ontimeout: function() { reject(new Error('Request timed out')); } }); }); // Reset circle appearance circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = ''; circle.style.cursor = 'pointer'; } }); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } const htmlText = response.responseText; const resultMatch = htmlText.match(/
, replacing only Text nodes.
// Element nodes (Pygments etc.) are left untouched.
const childNodes = Array.from(preElement.childNodes);
for (const node of childNodes) {
if (node.nodeType === Node.TEXT_NODE) {
const fragment = processTextContent(node.textContent);
preElement.replaceChild(fragment, node);
}
// Element nodes: leave as-is to preserve syntax highlighting
}
// --- Copy All Links buttons (only in pattern-matching sections) ---
document.querySelectorAll('.sinflix-copy-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const res = e.target.getAttribute('data-res');
const allLinks = resolutionLinks[res] || [];
// Copy only the pattern-matching links from this section
const matchingLinks = copyPatterns.length > 0
? allLinks.filter(u => copyPatterns.some(p => u.includes(p)))
: allLinks;
if (matchingLinks.length > 0) {
showNotification('Copying links...', 'info');
navigator.clipboard.writeText(matchingLinks.join('\n')).then(() => {
showNotification(`${matchingLinks.length} link(s) copied!`, 'success');
const oldText = e.target.innerText;
e.target.innerText = 'Copied!';
setTimeout(() => { e.target.innerText = oldText; }, 2000);
}).catch(() => {
showNotification('Failed to copy links!', 'error');
});
}
});
});
// --- Copy Links buttons (shows for sections with pattern-matching URLs) ---
if (config.megaBypass) {
document.querySelectorAll('.sinflix-copy-bypass-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const res = e.target.getAttribute('data-bypass-res');
// Collect all links in this section that match any configured pattern
const allLinks = resolutionLinks[res] || [];
const matchingLinks = copyPatterns.length > 0
? allLinks.filter(u => copyPatterns.some(p => u.includes(p)))
: allLinks;
if (matchingLinks.length === 0) {
showNotification('No matching links found in this section.', 'error');
return;
}
// For mega.nz links, use the bypass transform; for others copy as-is
const linksToCopy = matchingLinks.map(url =>
url.includes('mega.nz/file/') ? getMegaBypassUrl(url) : url
);
const textToCopy = linksToCopy.join('\n');
showNotification('Copying links...', 'info');
navigator.clipboard.writeText(textToCopy).then(() => {
showNotification(`${linksToCopy.length} link(s) copied!`, 'success');
const oldText = e.target.innerText;
e.target.innerText = 'Copied!';
setTimeout(() => { e.target.innerText = oldText; }, 2000);
}).catch(() => {
showNotification('Failed to copy links!', 'error');
});
});
});
// --- Mega bypass: grey circle click → trigger bypass download ---
document.querySelectorAll('.sinflix-mega-circle').forEach(circle => {
circle.addEventListener('click', (e) => {
e.stopPropagation();
const megaUrl = circle.getAttribute('data-mega-url');
if (!megaUrl) return;
const bypassUrl = getMegaBypassUrl(megaUrl);
showNotification('Download started…', 'success', 2000);
triggerDownload(bypassUrl);
});
});
}
return true;
}
// --- Buzzheavier Enhancements ---
function enhanceBuzzheavierContent() {
if (!config.buzzheavierEnhancements) return false;
if (!window.location.hostname.includes('buzzheavier.com')) return false;
const isHomePage = window.location.pathname.length > 1 && !window.location.pathname.endsWith('/download') && document.querySelector('#tbody');
const isSinglePage = document.querySelector('#preview') && document.querySelector('a[href$="/download"]');
const ICONS = {
play: '',
copy: '',
downloadSimple: '',
check: '',
loading: ''
};
const fetchDirectLink = (url, callback) => {
const domain = new URL(url).origin;
const downloadUrl = url.replace(/\/$/, '') + '/download';
GM_xmlhttpRequest({
method: "HEAD",
url: downloadUrl,
headers: {
"hx-current-url": url,
"hx-request": "true",
"referer": url
},
onload: function(response) {
let redirectPath = null;
const headers = response.responseHeaders;
const headerMatch = headers.match(/hx-redirect:\s*(.*)/i);
if (headerMatch && headerMatch[1]) {
redirectPath = headerMatch[1].trim();
}
if (redirectPath) {
let finalUrl = redirectPath.startsWith('http') ? redirectPath : domain + redirectPath;
callback(finalUrl);
} else {
showNotification("Failed to obtain direct link.", "error");
callback(null);
}
},
onerror: function(err) {
showNotification("Network error obtaining link.", "error");
callback(null);
}
});
};
const handleAction = (type, pageUrl, btnElement) => {
if (btnElement.classList.contains('loading')) return;
const originalIcon = btnElement.innerHTML;
btnElement.innerHTML = ICONS.loading;
btnElement.classList.add('loading');
fetchDirectLink(pageUrl, (directUrl) => {
btnElement.classList.remove('loading');
if (!directUrl) {
btnElement.innerHTML = originalIcon;
return;
}
if (type === 'copy') {
navigator.clipboard.writeText(directUrl).then(() => {
btnElement.innerHTML = ICONS.check;
setTimeout(() => { btnElement.innerHTML = originalIcon; }, 2000);
});
} else if (type === 'download') {
btnElement.innerHTML = originalIcon;
window.location.assign(directUrl);
}
});
};
const createActionBtn = (icon, title, type, fileUrl, extraClass) => {
const btn = document.createElement('button');
btn.className = `bh-btn ${extraClass || ''}`;
btn.title = title;
btn.innerHTML = icon;
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
handleAction(type, fileUrl, btn);
};
return btn;
};
const copyLinks = (linksArray, btnElement) => {
if (!linksArray || linksArray.length === 0) return;
showNotification('Copying links... Please wait!', 'info');
let directLinks = [];
let processed = 0;
let total = linksArray.length;
linksArray.forEach(pageUrl => {
fetchDirectLink(pageUrl, (directUrl) => {
processed++;
if (directUrl) {
directLinks.push(directUrl);
}
if (processed === total) {
if (directLinks.length > 0) {
navigator.clipboard.writeText(directLinks.join('\n')).then(() => {
showNotification('All direct links copied successfully!', 'success');
const oldText = btnElement.innerText;
btnElement.innerText = 'Copied!';
setTimeout(() => btnElement.innerText = oldText, 2000);
}).catch(() => {
showNotification('Failed to copy links!', 'error');
});
} else {
showNotification('No direct links found!', 'error');
}
}
});
});
};
if (isHomePage) {
const tbody = document.querySelector('#tbody');
if (tbody && config.buzzSplitQuality) {
const rows = Array.from(tbody.querySelectorAll('tr.editable'));
const qualities = { '1080p': [], '720p': [], '540p': [], '480p': [], 'Other': [] };
rows.forEach(row => {
const linkEl = row.querySelector('a[href^="/"]');
if (!linkEl) return;
const name = linkEl.innerText.toLowerCase();
let matched = false;
for (const q of Object.keys(qualities)) {
if (q !== 'Other' && name.includes(q)) {
qualities[q].push(row);
matched = true;
break;
}
}
if (!matched) qualities['Other'].push(row);
});
const parentTable = tbody.closest('table');
const container = parentTable.parentNode;
const headerRow = parentTable.querySelector('thead').outerHTML;
const tableClass = parentTable.className;
parentTable.style.display = 'none';
Object.keys(qualities).forEach(key => {
if (qualities[key].length > 0) {
const wrapper = document.createElement('div');
wrapper.className = 'w-full relative shadow overflow-hidden sm:rounded-lg overflow-x-auto my-6';
let copyAllBtnHtml = '';
if (config.buzzCopyLinks) {
copyAllBtnHtml = ``;
}
wrapper.innerHTML = `
${key}
${copyAllBtnHtml}
${headerRow}
`;
container.insertBefore(wrapper, parentTable);
const newTbody = wrapper.querySelector(`#tbody-${key}`);
const linksForQuality = [];
qualities[key].forEach(row => {
newTbody.appendChild(row);
const linkEl = row.querySelector('a[href^="/"]');
if (!linkEl) return;
const fileUrl = linkEl.href;
linksForQuality.push(fileUrl);
if (!row.querySelector('.bh-actions')) {
const actionsContainer = document.createElement('div');
actionsContainer.className = 'bh-actions';
if (config.buzzCopyLinks) {
actionsContainer.appendChild(createActionBtn(ICONS.copy, 'Copy Direct Link', 'copy', fileUrl, 'copy-btn'));
}
if (config.buzzDirectDownload) {
actionsContainer.appendChild(createActionBtn(ICONS.downloadSimple, 'Direct Download', 'download', fileUrl, 'dl-btn'));
}
linkEl.parentNode.appendChild(actionsContainer);
}
});
const copyBtn = wrapper.querySelector(`.sinflix-copy-btn[data-q="${key}"]`);
if (copyBtn) {
copyBtn.addEventListener('click', function() {
copyLinks(linksForQuality, this);
});
}
}
});
} else if (tbody) {
const rows = Array.from(tbody.querySelectorAll('tr.editable'));
const allLinks = [];
rows.forEach(row => {
const linkEl = row.querySelector('a[href^="/"]');
if (!linkEl) return;
const fileUrl = linkEl.href;
allLinks.push(fileUrl);
if (!row.querySelector('.bh-actions')) {
const actionsContainer = document.createElement('div');
actionsContainer.className = 'bh-actions';
if (config.buzzCopyLinks) {
actionsContainer.appendChild(createActionBtn(ICONS.copy, 'Copy Direct Link', 'copy', fileUrl, 'copy-btn'));
}
if (config.buzzDirectDownload) {
actionsContainer.appendChild(createActionBtn(ICONS.downloadSimple, 'Direct Download', 'download', fileUrl, 'dl-btn'));
}
linkEl.parentNode.appendChild(actionsContainer);
}
});
if (config.buzzCopyLinks) {
const parentTable = tbody.closest('table');
const copyBtn = document.createElement('button');
copyBtn.className = 'sinflix-copy-btn btn btn-sm bg-blue-600 text-white px-3 py-1 rounded my-2';
copyBtn.innerText = 'Copy All Links';
copyBtn.onclick = function() { copyLinks(allLinks, this); };
parentTable.parentNode.insertBefore(copyBtn, parentTable);
}
}
} else if (isSinglePage) {
const dlBtn = document.querySelector('a.gay-button') || document.querySelector('a[href$="/download"]');
if (dlBtn && !document.querySelector('.bh-actions.single-page')) {
const fileUrl = window.location.href;
const container = document.createElement('div');
container.className = 'bh-actions single-page';
if (config.buzzCopyLinks) {
container.appendChild(createActionBtn(ICONS.copy, 'Copy Direct Link', 'copy', fileUrl, 'copy-btn'));
}
if (dlBtn.parentNode) {
dlBtn.parentNode.insertBefore(container, dlBtn.nextSibling);
}
}
}
return true;
}
// --- Main Processing Function ---
function enhancePageContent() {
const content = document.querySelector('.entry-text article');
if (!content) {
console.log('Sinflix Modifier: Content not found, retrying...');
return false;
}
const currentVersion = 'v6.4.1_selective_popup';
if (content.dataset.enhancedv === currentVersion) {
console.log(`Sinflix Modifier: Content already enhanced (${currentVersion}). Skipping.`);
return true;
}
console.log(`Sinflix Modifier (${currentVersion}): Processing...`);
content.dataset.enhancedv = currentVersion;
// NEW: Reorder sections FIRST, before any other processing
if (config.moveCurrentlyAiringToTop) {
reorderSections();
}
const buzzRegex = /\b(?![a-zA-Z]{12}\b)([a-zA-Z0-9]{12})\b/g;
if (config.showGoogleCircle || config.showMdlCircle) {
document.querySelectorAll('.kdrama-circle-container').forEach(el => el.remove());
const textNodes = [];
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, {
acceptNode: n => (!n.parentNode.closest('a, .kdrama-highlight, .kdrama-search-icon, .kdrama-circle-container') && n.nodeValue.trim().length > 0) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
});
let node;
while (node = walker.nextNode()) textNodes.push(node);
textNodes.forEach(textNode => {
let fullText = textNode.textContent.replace(/=\r?\n/g, '').replace(/=([0-9A-Fa-f]{2})/g, (m, p1) => {
try { return String.fromCharCode(parseInt(p1, 16)); } catch(e) { return m; }
});
const lines = fullText.split('\n');
let fragment = document.createDocumentFragment();
let lastOffset = 0;
let processedAnyLine = false;
lines.forEach(line => {
const dramaName = extractDramaName(line);
if (dramaName) {
const lineStartIndex = fullText.indexOf(line, lastOffset);
if (lineStartIndex > lastOffset) fragment.appendChild(document.createTextNode(fullText.substring(lastOffset, lineStartIndex)));
const container = document.createElement('span');
container.className = 'kdrama-circle-container';
if (config.showGoogleCircle) {
const searchQuery = `${dramaName} ${config.googleSearchSuffix}`.trim();
const googleUrl = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`;
const googleCircle = document.createElement('span');
googleCircle.className = 'kdrama-circle google-circle';
googleCircle.title = `Search '${dramaName}' on Google`;
googleCircle.onclick = (e) => {
e.stopPropagation();
// USE SETTING: Check which style the user prefers
if (config.linkOpenStyle === 'popup') {
openInCenter(googleUrl, 'sinflix_Google Search');
} else {
window.open(googleUrl, '_blank');
}
};
container.appendChild(googleCircle);
}
if (config.showMdlCircle) {
const mdlUrl = `https://mydramalist.com/search?q=${encodeURIComponent(dramaName)}&adv=titles&ty=68&co=3&so=relevance`;
const mdlCircle = document.createElement('span');
mdlCircle.className = 'kdrama-circle mdl-circle';
mdlCircle.title = `Search '${dramaName}' on MyDramaList\nCtrl+Click to copy first result link`;
mdlCircle.onclick = async (e) => {
e.stopPropagation();
// Check if Ctrl key is pressed
if (e.ctrlKey) {
showNotification('Getting the link...', 'info');
const firstResultUrl = await getMdlFirstResultUrl(mdlUrl, dramaName);
if (firstResultUrl) {
const success = await copyToClipboard(firstResultUrl);
if (success) {
showNotification('Link copied to clipboard!', 'success');
} else {
showNotification('Failed to copy link to clipboard', 'error');
}
} else {
showNotification('No results found for this drama', 'error');
}
return;
}
// Normal click behavior - Load page in background and get first result
loadMdlPageAndOpenFirstResult(mdlUrl, dramaName);
};
container.appendChild(mdlCircle);
}
fragment.appendChild(container);
fragment.appendChild(document.createTextNode(line));
processedAnyLine = true;
} else {
const lineStartIndex = fullText.indexOf(line, lastOffset);
if (lineStartIndex > lastOffset) fragment.appendChild(document.createTextNode(fullText.substring(lastOffset, lineStartIndex)));
fragment.appendChild(document.createTextNode(line));
}
fragment.appendChild(document.createTextNode('\n'));
lastOffset = fullText.indexOf(line, lastOffset) + line.length + 1;
});
if (lastOffset < fullText.length) fragment.appendChild(document.createTextNode(fullText.substring(lastOffset)));
if (processedAnyLine && textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
});
}
if (config.convertBuzzheavierLinks) {
const linkWalker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, { acceptNode: n => n.parentNode.nodeName !== 'A' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT });
const linkNodes = [];
let n;
while (n = linkWalker.nextNode()) {
buzzRegex.lastIndex = 0;
if (buzzRegex.test(n.textContent)) linkNodes.push(n);
}
linkNodes.forEach(node => {
if (!document.body.contains(node) || node.parentNode.nodeName === 'A') return;
const fragment = document.createDocumentFragment();
const text = node.textContent;
let lastIndex = 0;
buzzRegex.lastIndex = 0;
let match;
while ((match = buzzRegex.exec(text)) !== null) {
if (match.index > lastIndex) fragment.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
const link = document.createElement('a');
link.href = `https://buzzheavier.com/${match[1]}`;
link.textContent = link.href;
link.target = '_blank';
fragment.appendChild(link);
lastIndex = match.index + match[0].length;
}
if (lastIndex < text.length) fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
if (fragment.hasChildNodes() && node.parentNode) node.parentNode.replaceChild(fragment, node);
});
}
// NEW: Process existing links to apply special behavior only to specific links
const existingLinks = content.querySelectorAll('a[href]');
existingLinks.forEach(link => {
// Skip links that are already processed or are internal links
if (link.dataset.sinflixProcessed || link.href.startsWith('#') || link.href.startsWith('javascript:')) return;
link.dataset.sinflixProcessed = 'true';
// Special handling for SinFlix chat box link
if (link.href.includes('my.cbox.ws/sin-flix')) {
// Remove existing target and click handlers
link.removeAttribute('target');
// Add new click handler based on chat box preference
link.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
openInCenter(link.href, 'sinflix_chat');
});
}
// For all other links, leave them as they are (normal behavior)
});
console.log('Sinflix Modifier: Processing complete. ✨');
// Mark content as processed to show all headers
content.classList.add('sinflix-processed');
return true;
}
// --- Immediate early reordering function ---
function tryEarlyReorder() {
if (!config.moveCurrentlyAiringToTop) return;
const content = document.querySelector('.entry-text article');
if (content && content.dataset.sectionsReordered !== 'true') {
reorderSections();
}
}
// --- Section Reordering Function ---
function reorderSections() {
const content = document.querySelector('.entry-text article');
if (!content) {
console.log('Sinflix Modifier: Content not found for reordering');
return;
}
// Check if already reordered to avoid duplicate processing
if (content.dataset.sectionsReordered === 'true') {
return;
}
try {
// Find all h4 elements that represent section headers
const headersNodeList = content.querySelectorAll('h4');
const headers = Array.from(headersNodeList);
let currentlyAiringHeader = null;
let insertBeforeHeader = null;
// Find the "Currently Airing" header
for (const header of headers) {
if (header.textContent.trim().toLowerCase().includes('currently airing')) {
currentlyAiringHeader = header;
break;
}
}
if (!currentlyAiringHeader) {
console.log('Sinflix Modifier: "Currently Airing" section not found');
headers.forEach(h => h.classList.add('sinflix-visible'));
content.dataset.sectionsReordered = 'true';
return;
}
// Find the insertion point dynamically:
// The first h4 that is NOT the page-title header (index 0) and NOT Currently Airing.
// This works regardless of what the second section is named.
const firstH4 = headers[0];
for (const header of headers) {
if (header === firstH4) continue; // skip page title
if (header === currentlyAiringHeader) continue; // skip itself
insertBeforeHeader = header;
break;
}
if (!insertBeforeHeader) {
console.log('Sinflix Modifier: No valid insertion point found for reordering');
headers.forEach(h => h.classList.add('sinflix-visible'));
content.dataset.sectionsReordered = 'true';
return;
}
// Check if Currently Airing is already before the insertion point
const airingIndex = headers.indexOf(currentlyAiringHeader);
const insertIndex = headers.indexOf(insertBeforeHeader);
if (airingIndex < insertIndex) {
console.log('Sinflix Modifier: Currently Airing is already at the top');
content.dataset.sectionsReordered = 'true';
headers.forEach(h => h.classList.add('sinflix-visible'));
return;
}
// Collect all content belonging to the "Currently Airing" section
// (everything between the h4 and the next h4)
const currentlyAiringContent = [];
let currentElement = currentlyAiringHeader.nextElementSibling;
while (currentElement && currentElement.tagName !== 'H4') {
currentlyAiringContent.push(currentElement);
currentElement = currentElement.nextElementSibling;
}
// Remove "Currently Airing" section from its current position
currentlyAiringHeader.remove();
currentlyAiringContent.forEach(el => el.remove());
// Re-insert before the target section
const parentElement = insertBeforeHeader.parentNode;
parentElement.insertBefore(currentlyAiringHeader, insertBeforeHeader);
let insertAfter = currentlyAiringHeader;
currentlyAiringContent.forEach(el => {
parentElement.insertBefore(el, insertAfter.nextSibling);
insertAfter = el;
});
// Add a visual separator after the moved section if one doesn't already exist
if (insertAfter.nextSibling && insertAfter.nextSibling.tagName !== 'HR') {
const separator = document.createElement('hr');
parentElement.insertBefore(separator, insertAfter.nextSibling);
}
// Mark as reordered to prevent duplicate processing
content.dataset.sectionsReordered = 'true';
// Reveal all headers now that reordering is complete
content.querySelectorAll('h4').forEach(h => h.classList.add('sinflix-visible'));
console.log('Sinflix Modifier: Successfully moved "Currently Airing" section to top');
} catch (error) {
console.error('Sinflix Modifier: Error reordering sections:', error);
// Show all headers in case of error
content.querySelectorAll('h4').forEach(h => h.classList.add('sinflix-visible'));
}
}
// --- Settings UI ---
function createSettingsUI() {
const settingsButton = document.createElement('div');
settingsButton.id = 'kdrama-settings-button';
settingsButton.innerHTML = ``;
// Only show the fixed settings button when the top search capsule is OFF
if (!config.showTopSearchBar) {
document.body.appendChild(settingsButton);
}
const modal = document.createElement('div');
modal.id = 'kdrama-settings-modal';
modal.innerHTML = `
`;
document.body.appendChild(modal);
// NEW: Set the correct radio button based on saved config
document.querySelector(`input[name="linkStyle"][value="${config.linkOpenStyle}"]`).checked = true;
document.querySelector(`input[name="chatBoxStyle"][value="${config.chatBoxOpenStyle}"]`).checked = true;
const saveButton = document.getElementById('kdrama-save-button');
const closeBtn = document.getElementById('kdrama-settings-close');
settingsButton.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
modal.style.display = 'flex';
// Add a small delay for the animation
setTimeout(() => modal.classList.add('show'), 10);
});
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('show');
setTimeout(() => modal.style.display = 'none', 200);
}
});
closeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
modal.classList.remove('show');
setTimeout(() => modal.style.display = 'none', 200);
});
// Additional escape key handler for settings modal
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.style.display === 'flex') {
modal.classList.remove('show');
setTimeout(() => modal.style.display = 'none', 200);
e.preventDefault();
}
});
saveButton.addEventListener('click', (e) => {
e.preventDefault();
// Save checkbox values
GM_setValue('showGoogleCircle', document.getElementById('setting-google').checked);
GM_setValue('showMdlCircle', document.getElementById('setting-mdl').checked);
GM_setValue('convertBuzzheavierLinks', document.getElementById('setting-buzz').checked);
GM_setValue('showBackToTopButton', document.getElementById('setting-back-to-top').checked);
GM_setValue('moveCurrentlyAiringToTop', document.getElementById('setting-move-airing').checked);
GM_setValue('showChatBoxButton', document.getElementById('setting-chat-box').checked);
GM_setValue('pstMoeEnhancements', document.getElementById('setting-pstmoe').checked);
GM_setValue('megaBypass', document.getElementById('setting-mega-bypass').checked);
GM_setValue('megaNzButton', document.getElementById('setting-mega-nz-btn').checked);
GM_setValue('copyLinksPatterns', document.getElementById('setting-copy-patterns').value.trim());
// NEW: Save the selected radio button values
const selectedStyle = document.querySelector('input[name="linkStyle"]:checked').value;
GM_setValue('linkOpenStyle', selectedStyle);
const selectedChatBoxStyle = document.querySelector('input[name="chatBoxStyle"]:checked').value;
GM_setValue('chatBoxOpenStyle', selectedChatBoxStyle);
GM_setValue('googleSearchSuffix', document.getElementById('setting-google-suffix').value);
// NEW: Buzzheavier Save
GM_setValue('buzzheavierEnhancements', document.getElementById('setting-buzz-main').checked);
GM_setValue('buzzSplitQuality', document.getElementById('setting-buzz-split').checked);
GM_setValue('buzzDirectDownload', document.getElementById('setting-buzz-dl').checked);
GM_setValue('buzzCopyLinks', document.getElementById('setting-buzz-copy').checked);
const customSchemeEl = document.getElementById('setting-buzz-custom-scheme');
if (customSchemeEl) GM_setValue('buzzCustomScheme', customSchemeEl.value);
// NEW: Top search bar toggle
GM_setValue('showTopSearchBar', document.getElementById('setting-top-searchbar').checked);
location.reload();
});
}
// --- Floating Action Buttons (Back to Top & Search) ---
let highlightedElements = [];
let currentMatchIndex = -1;
function createFloatingButtons() {
const backToTopBtn = document.createElement('button');
backToTopBtn.id = 'kdrama-back-to-top';
backToTopBtn.className = 'kdrama-float-button';
backToTopBtn.setAttribute('aria-label', 'Back to top');
backToTopBtn.title = 'Back to top';
backToTopBtn.innerHTML = ``;
document.body.appendChild(backToTopBtn);
// Chat button: only show as floating button when top search capsule is OFF
let chatBtn = null;
if (config.showChatBoxButton && !config.showTopSearchBar) {
chatBtn = document.createElement('button');
chatBtn.id = 'kdrama-chat-button';
chatBtn.className = 'kdrama-float-button show';
chatBtn.setAttribute('aria-label', 'Open SinFlix Chat');
chatBtn.title = 'SinFlix Chat';
chatBtn.innerHTML = ``;
document.body.appendChild(chatBtn);
}
function updateButtonPositions() {
const baseBottom = 30;
const buttonSpacing = 54;
// Position chat button if enabled
if (config.showChatBoxButton && chatBtn) {
chatBtn.style.bottom = `${baseBottom}px`;
}
// Position back-to-top button at the top of the stack (it will handle its own visibility)
const topPosition = config.showChatBoxButton ? baseBottom + buttonSpacing : baseBottom;
backToTopBtn.style.bottom = `${topPosition}px`;
}
function updateButtonPositionsWithAnimation() {
const baseBottom = 30;
const buttonSpacing = 54;
let currentBottom = baseBottom;
// Calculate positions based on visible buttons
const isBackToTopVisible = backToTopBtn.classList.contains('show');
if (isBackToTopVisible) {
// Back-to-top is visible, so other buttons move up
backToTopBtn.style.bottom = `${currentBottom}px`;
currentBottom += buttonSpacing;
}
// Chat button position (if enabled)
if (config.showChatBoxButton && chatBtn) {
chatBtn.style.bottom = `${currentBottom}px`;
}
}
updateButtonPositions();
window.addEventListener('scroll', () => {
const scrollThreshold = 200;
const shouldShowBackToTop = window.scrollY > scrollThreshold && config.showBackToTopButton;
const isCurrentlyShowing = backToTopBtn.classList.contains('show');
if (shouldShowBackToTop && !isCurrentlyShowing) {
// Show back-to-top button and animate other buttons
backToTopBtn.classList.add('show');
updateButtonPositionsWithAnimation();
} else if (!shouldShowBackToTop && isCurrentlyShowing) {
// Hide back-to-top button and animate other buttons
backToTopBtn.classList.remove('show');
updateButtonPositionsWithAnimation();
}
});
backToTopBtn.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// NEW: Add chat button click handler
if (config.showChatBoxButton && chatBtn) {
chatBtn.addEventListener('click', () => {
const chatUrl = 'https://my.cbox.ws/sin-flix';
openInCenter(chatUrl, 'sinflix_chat');
});
}
}
// --- Top Search Bar (Dynamic Island) ---
function createTopSearchBar() {
if (!config.showTopSearchBar) return;
// The wrapper pill/circle
const wrap = document.createElement('div');
wrap.id = 'sfx-top-searchbar-wrap';
// Search icon
const iconEl = document.createElement('span');
iconEl.id = 'sfx-top-search-icon';
iconEl.innerHTML = ``;
wrap.appendChild(iconEl);
// Collapsed label (hidden when expanded, visible when collapsed)
const labelEl = document.createElement('span');
labelEl.id = 'sfx-top-search-label';
labelEl.textContent = 'Search';
wrap.appendChild(labelEl);
// Text input
const input = document.createElement('input');
input.id = 'sfx-top-search-input';
input.type = 'text';
input.placeholder = 'Search on page…';
input.setAttribute('autocomplete', 'off');
input.setAttribute('spellcheck', 'false');
wrap.appendChild(input);
// Match counter
const counter = document.createElement('span');
counter.id = 'sfx-top-search-count';
wrap.appendChild(counter);
// Prev / Next buttons
const prevBtn = document.createElement('button');
prevBtn.className = 'sfx-top-nav-btn';
prevBtn.title = 'Previous match';
prevBtn.disabled = true;
prevBtn.innerHTML = ``;
wrap.appendChild(prevBtn);
const nextBtn = document.createElement('button');
nextBtn.className = 'sfx-top-nav-btn';
nextBtn.title = 'Next match';
nextBtn.disabled = true;
nextBtn.innerHTML = ``;
wrap.appendChild(nextBtn);
// Close / collapse button
const closeBtn = document.createElement('button');
closeBtn.id = 'sfx-top-search-close';
closeBtn.title = 'Dismiss search bar';
closeBtn.innerHTML = '×';
wrap.appendChild(closeBtn);
// Separator between search controls and utility icons
const capSep = document.createElement('span');
capSep.className = 'sfx-cap-sep';
wrap.appendChild(capSep);
// Settings icon button (inside capsule)
const capSettings = document.createElement('button');
capSettings.className = 'sfx-cap-action';
capSettings.title = 'Settings';
capSettings.innerHTML = ``;
capSettings.addEventListener('click', (e) => {
e.stopPropagation();
const modal = document.getElementById('kdrama-settings-modal');
if (modal) { modal.style.display = 'flex'; setTimeout(() => modal.classList.add('show'), 10); }
});
wrap.appendChild(capSettings);
// Chat icon button (inside capsule, only if enabled)
if (config.showChatBoxButton) {
const capChat = document.createElement('button');
capChat.className = 'sfx-cap-action';
capChat.title = 'SinFlix Chat';
capChat.innerHTML = ``;
capChat.addEventListener('click', (e) => {
e.stopPropagation();
const chatUrl = 'https://my.cbox.ws/sin-flix';
openInCenter(chatUrl, 'sinflix_chat');
});
wrap.appendChild(capChat);
}
document.body.appendChild(wrap);
// ---- State: start collapsed ----
let isCollapsed = true;
wrap.classList.add('sfx-collapsed');
let sfxHighlights = [];
let sfxCurrentIdx = -1;
// ---- Collapse / expand helpers ----
function collapse() {
if (isCollapsed) return;
isCollapsed = true;
wrap.classList.add('sfx-collapsed');
input.blur();
}
function expand() {
if (!isCollapsed) return;
isCollapsed = false;
wrap.classList.remove('sfx-collapsed');
// Small bounce feedback
wrap.classList.add('sfx-expanding');
setTimeout(() => wrap.classList.remove('sfx-expanding'), 600);
// Focus input after animation settles
setTimeout(() => input.focus(), 200);
}
// ---- Search highlight logic ----
function clearSfxHighlights() {
sfxHighlights.forEach(el => {
const parent = el.parentNode;
if (parent) {
parent.replaceChild(document.createTextNode(el.textContent), el);
parent.normalize();
}
});
sfxHighlights = [];
sfxCurrentIdx = -1;
}
function runSfxSearch(query) {
clearSfxHighlights();
if (!query || query.length < 1) {
counter.textContent = '';
prevBtn.disabled = true;
nextBtn.disabled = true;
return;
}
const lq = query.toLowerCase();
// Only search inside the article content (drama list)
const article = document.querySelector('.entry-text article');
if (!article) {
counter.textContent = 'No content';
return;
}
// Walk text nodes inside the article, skipping links and script/style
const walker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT, {
acceptNode: n => {
const el = n.parentElement;
if (!el) return NodeFilter.FILTER_REJECT;
const tag = el.tagName;
if (['SCRIPT','STYLE','NOSCRIPT','A'].includes(tag)) return NodeFilter.FILTER_REJECT;
if (el.closest('a')) return NodeFilter.FILTER_REJECT; // skip nested link text
return n.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
});
const textNodes = [];
let node;
while ((node = walker.nextNode())) textNodes.push(node);
textNodes.forEach(tn => {
const fullText = tn.textContent;
// Process line by line — only highlight within drama name lines
const lines = fullText.split('\n');
let anyMatch = false;
const frag = document.createDocumentFragment();
lines.forEach((line, lineIdx) => {
const dramaName = extractDramaName(line);
if (dramaName && dramaName.toLowerCase().includes(lq)) {
// This line has a drama name that matches — highlight occurrences of query
const lLine = line.toLowerCase();
let last = 0;
let pos = lLine.indexOf(lq);
while (pos !== -1) {
if (pos > last) frag.appendChild(document.createTextNode(line.slice(last, pos)));
const mark = document.createElement('mark');
mark.className = 'kdrama-highlight';
mark.textContent = line.slice(pos, pos + lq.length);
frag.appendChild(mark);
sfxHighlights.push(mark);
last = pos + lq.length;
pos = lLine.indexOf(lq, last);
}
if (last < line.length) frag.appendChild(document.createTextNode(line.slice(last)));
anyMatch = true;
} else {
frag.appendChild(document.createTextNode(line));
}
// Re-add the newline between lines (but not after the very last)
if (lineIdx < lines.length - 1) frag.appendChild(document.createTextNode('\n'));
});
if (anyMatch && tn.parentNode) tn.parentNode.replaceChild(frag, tn);
});
if (sfxHighlights.length === 0) {
counter.textContent = 'No results';
prevBtn.disabled = true;
nextBtn.disabled = true;
} else {
sfxCurrentIdx = 0;
jumpToSfxMatch(0);
prevBtn.disabled = false;
nextBtn.disabled = false;
}
}
function jumpToSfxMatch(idx) {
sfxHighlights.forEach((el, i) => el.classList.toggle('current', i === idx));
const el = sfxHighlights[idx];
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
counter.textContent = `${idx + 1} / ${sfxHighlights.length}`;
}
}
// ---- Events ----
let searchDebounce = null;
const triggerSearch = () => {
clearTimeout(searchDebounce);
searchDebounce = setTimeout(() => runSfxSearch(input.value.trim()), 280);
};
input.addEventListener('input', triggerSearch);
// Collapse on blur only when no active search results.
// While results are shown the user needs the bar to navigate between them.
input.addEventListener('blur', e => {
if (sfxHighlights.length > 0) return; // keep open while navigating results
if (wrap.contains(e.relatedTarget)) return; // keep open when focusing bar buttons
collapse();
});
input.addEventListener('keydown', e => {
if (e.key === 'Enter') {
if (sfxHighlights.length === 0) return;
sfxCurrentIdx = e.shiftKey
? (sfxCurrentIdx - 1 + sfxHighlights.length) % sfxHighlights.length
: (sfxCurrentIdx + 1) % sfxHighlights.length;
jumpToSfxMatch(sfxCurrentIdx);
} else if (e.key === 'Escape') {
collapse();
}
});
prevBtn.addEventListener('click', () => {
if (sfxHighlights.length === 0) return;
sfxCurrentIdx = (sfxCurrentIdx - 1 + sfxHighlights.length) % sfxHighlights.length;
jumpToSfxMatch(sfxCurrentIdx);
input.focus();
});
nextBtn.addEventListener('click', () => {
if (sfxHighlights.length === 0) return;
sfxCurrentIdx = (sfxCurrentIdx + 1) % sfxHighlights.length;
jumpToSfxMatch(sfxCurrentIdx);
input.focus();
});
closeBtn.addEventListener('click', e => {
e.stopPropagation();
clearSfxHighlights();
input.value = '';
counter.textContent = '';
prevBtn.disabled = true;
nextBtn.disabled = true;
collapse();
});
// Clicking the collapsed circle expands it
wrap.addEventListener('click', e => {
if (isCollapsed) {
expand();
}
});
// Collapse on user scroll only when no active search results.
let scrollListenerActive = false;
setTimeout(() => { scrollListenerActive = true; }, 300);
window.addEventListener('scroll', () => {
if (!scrollListenerActive) return;
if (sfxHighlights.length > 0) return; // keep open while navigating results
if (!isCollapsed) collapse();
}, { passive: true });
// Clicking anywhere outside the bar always collapses it (even with active results).
document.addEventListener('click', e => {
if (!isCollapsed && !wrap.contains(e.target)) {
collapse();
}
}, { capture: true, passive: true });
}
// --- Mega.nz Floating Bypass Button ---
function enhanceMegaNzPage() {
if (!config.megaNzButton) return;
if (!window.location.hostname.includes('mega.nz')) return;
// Only act on file pages
if (!window.location.href.includes('/file/')) return;
GM_addStyle(`
#sinflix-mega-bypass-btn {
position: fixed;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
z-index: 99999;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 0 24px;
height: 48px;
border-radius: 24px;
border: 1px solid rgba(217, 39, 46, 0.45);
background: rgba(28, 28, 28, 0.92);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
font-weight: 600;
letter-spacing: 0.2px;
cursor: pointer;
white-space: nowrap;
box-shadow: 0 4px 24px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.06);
transition: background 0.2s ease, border-color 0.2s ease,
box-shadow 0.2s ease, transform 0.15s ease;
user-select: none;
}
#sinflix-mega-bypass-btn:hover {
background: rgba(217, 39, 46, 0.88);
border-color: rgba(217, 39, 46, 0.9);
box-shadow: 0 6px 28px rgba(217,39,46,0.35), 0 2px 8px rgba(0,0,0,0.4);
transform: translateX(-50%) translateY(-2px);
}
#sinflix-mega-bypass-btn:active {
transform: translateX(-50%) translateY(0px);
box-shadow: 0 2px 12px rgba(217,39,46,0.25);
}
#sinflix-mega-bypass-btn .sfx-mega-icon {
width: 18px;
height: 18px;
opacity: 0.9;
flex-shrink: 0;
}
#sinflix-mega-bypass-btn .sfx-mega-badge {
font-size: 10px;
font-weight: 700;
color: #d9272e;
background: rgba(217,39,46,0.15);
border: 1px solid rgba(217,39,46,0.35);
border-radius: 4px;
padding: 1px 5px;
letter-spacing: 0.5px;
text-transform: uppercase;
}
`);
const btn = document.createElement('button');
btn.id = 'sinflix-mega-bypass-btn';
btn.title = 'Download via bypass — skips Mega quota limits';
btn.innerHTML = `
Download
Bypass`;
// Wait for body to be ready then append
const appendBtn = () => document.body && document.body.appendChild(btn);
if (document.body) appendBtn();
else document.addEventListener('DOMContentLoaded', appendBtn);
btn.addEventListener('click', () => {
const bypassUrl = getMegaBypassUrl(window.location.href);
showNotification('Download started…', 'success', 2500);
triggerDownload(bypassUrl);
});
}
// --- Enhanced Initialization ---
function initialize() {
if (window.location.hostname === 'pst.moe') {
try {
enhancePstMoeContent();
} catch (e) {
console.error('Sinflix Modifier error during pst.moe enhancement:', e);
}
// For pst.moe, we just setup the UI and stop rentry-specific logic
createSettingsUI();
return;
}
if (window.location.hostname.includes('mega.nz')) {
try {
enhanceMegaNzPage();
} catch (e) {
console.error('Sinflix Modifier error during mega.nz enhancement:', e);
}
// No settings gear on mega.nz — keep the page clean
return;
}
if (window.location.hostname.includes('buzzheavier.com')) {
try {
enhanceBuzzheavierContent();
} catch (e) {
console.error('Sinflix Modifier error during buzzheavier enhancement:', e);
}
createSettingsUI();
return;
}
// Immediate content enhancement - no delay
try {
enhancePageContent();
} catch (e) {
console.error('Sinflix Modifier error during immediate enhancement:', e);
}
createSettingsUI();
createFloatingButtons();
createTopSearchBar();
const settingsModal = document.getElementById('kdrama-settings-modal');
window.addEventListener('scroll', () => {
if (settingsModal && settingsModal.style.display === 'flex') {
if (document.activeElement !== document.getElementById('kdrama-search-input')) {
settingsModal.style.display = 'none';
}
}
});
// Single late-fallback retry in case the MutationObserver missed the content
// (e.g. content was already in the DOM but not yet observed). Fires after
// the page is fully interactive to avoid blocking early user input.
setTimeout(() => {
try {
enhancePageContent();
} catch (e) {
console.error('Sinflix Modifier error during fallback enhancement:', e);
}
}, 1200);
if (typeof MutationObserver !== 'undefined') {
// Debounce: wait 150ms after the last mutation before processing.
// This prevents dozens of expensive TreeWalker scans from firing
// back-to-back during the initial page load, which would block
// the main thread and make clicks unresponsive.
let mutationTimer = null;
const debouncedEnhance = () => {
clearTimeout(mutationTimer);
mutationTimer = setTimeout(() => {
try {
enhancePageContent();
} catch (e) {
console.error('Sinflix Modifier error during MutationObserver processing:', e);
}
}, 150);
};
const observer = new MutationObserver((mutations) => {
let shouldProcess = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
if (mutation.target.closest('.entry-text article') || mutation.target.matches('.entry-text article')) {
shouldProcess = true;
}
}
});
if (shouldProcess) {
debouncedEnhance();
}
});
const content = document.querySelector('.entry-text article');
if (content) {
observer.observe(content, { childList: true, subtree: true });
// Also process immediately if content already exists
try {
enhancePageContent();
} catch (e) {
console.error('Sinflix Modifier error during immediate content processing:', e);
}
} else {
const bodyObserver = new MutationObserver((mutations, obs) => {
if (document.querySelector('.entry-text article')) {
obs.disconnect();
const foundContent = document.querySelector('.entry-text article');
observer.observe(foundContent, { childList: true, subtree: true });
debouncedEnhance();
}
});
bodyObserver.observe(document.body, { childList: true, subtree: true });
}
}
}
// Initialize the script — single entry point to avoid main-thread contention.
// Previously, enhancePageContent() was fired 5+ times simultaneously
// (script-start, readystatechange, DOMContentLoaded, setInterval × N),
// blocking the main thread for 1-2 s and making clicks/scrolls unresponsive.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
})();