Sleazy Fork is available in English.

Iwara NeoUI

為 Iwara 提供增強 UI:分頁版面、劇院模式、可自訂區塊與改良的影片頁體驗。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name         Iwara NeoUI
// @name:zh-CN Iwara NeoUI
// @name:zh-TW Iwara NeoUI
// @name:ja Iwara NeoUI
// @name:ko Iwara NeoUI
// @namespace    codeberg.org/0xdev/neoUI-iwara
// @version      0.4.0
// @description  Enhanced UI for Iwara with tabbed layout, theater mode, customizable sections, and improved video page experience.
// @description:zh-CN 为 Iwara 提供增强 UI:标签布局、影院模式、可自定义区块和改进的视频页面体验。
// @description:zh-TW 為 Iwara 提供增強 UI:分頁版面、劇院模式、可自訂區塊與改良的影片頁體驗。
// @description:ja Iwara にタブレイアウト、シネマモード、カスタマイズ可能なセクション、改善された動画ページ体験を追加します。
// @description:ko Iwara에 탭 레이아웃, 시네마 모드, 사용자 지정 섹션, 개선된 동영상 페이지 경험을 더하는 향상된 UI입니다.
// @author       0xdev
// @license      LGPL-3.0-or-later
// @match        https://www.iwara.tv/*
// @match        https://iwara.tv/*
// @grant        GM.getValue
// @grant        GM.setValue
// @run-at       document-idle
// ==/UserScript==

// THIS FILE IS AUTO-GENERATED — DO NOT MANUALLY EDIT.
// Source: neoui/iwara-neoui.user.js
'use strict';
const SUPPORTED_LOCALES = ['en', 'zh-CN', 'zh-TW', 'ja', 'ko'];
const DEFAULTS = Object.freeze({
    hideLikedBy: true,
    theaterMode: false,
    cinemaHideNav: false,
    newUI: false,
    showLikesTab: true,
    showRecsTab: true,
    language: 'auto',
});
const MESSAGES = {
    en: {
        'settings.button': 'Options',
        'settings.title': 'Iwara NeoUI - Settings',
        'settings.language': 'Language',
        'settings.language.auto': 'Auto',
        'settings.showLikes': 'Show "Likes" tab',
        'settings.showRecs': 'Show "Recommended" tab',
        'settings.hideNav': 'Hide navigation in cinema mode',
        'settings.newUi': 'Enable new UI (tabs + full-width video)',
        'actions.close': 'Close',
        'actions.save': 'Save',
        'tabs.about': 'About',
        'tabs.uploads': 'Uploads',
        'tabs.recs': 'Recommended',
        'tabs.likes': 'Likes',
        'tabs.comments': 'Comments',
        'tabs.aria': 'Iwara NeoUI Tabs',
        'cinema.button': 'Cinema',
        'cinema.title': 'Toggle cinema mode (T)',
    },
    'zh-CN': {
        'settings.button': '选项',
        'settings.title': 'Iwara NeoUI - 设置',
        'settings.language': '语言',
        'settings.language.auto': '自动',
        'settings.showLikes': '显示“喜欢”标签',
        'settings.showRecs': '显示“推荐”标签',
        'settings.hideNav': '影院模式下隐藏导航',
        'settings.newUi': '启用新界面(标签页 + 全宽视频)',
        'actions.close': '关闭',
        'actions.save': '保存',
        'tabs.about': '简介',
        'tabs.uploads': '上传',
        'tabs.recs': '推荐',
        'tabs.likes': '喜欢',
        'tabs.comments': '评论',
        'tabs.aria': 'Iwara NeoUI 标签页',
        'cinema.button': '影院',
        'cinema.title': '切换影院模式 (T)',
    },
    'zh-TW': {
        'settings.button': '選項',
        'settings.title': 'Iwara NeoUI - 設定',
        'settings.language': '語言',
        'settings.language.auto': '自動',
        'settings.showLikes': '顯示「喜歡」分頁',
        'settings.showRecs': '顯示「推薦」分頁',
        'settings.hideNav': '劇院模式下隱藏導覽列',
        'settings.newUi': '啟用新介面(分頁 + 全寬影片)',
        'actions.close': '關閉',
        'actions.save': '儲存',
        'tabs.about': '簡介',
        'tabs.uploads': '上傳',
        'tabs.recs': '推薦',
        'tabs.likes': '喜歡',
        'tabs.comments': '留言',
        'tabs.aria': 'Iwara NeoUI 分頁',
        'cinema.button': '劇院',
        'cinema.title': '切換劇院模式 (T)',
    },
    ja: {
        'settings.button': 'オプション',
        'settings.title': 'Iwara NeoUI - 設定',
        'settings.language': '言語',
        'settings.language.auto': '自動',
        'settings.showLikes': '「いいね」タブを表示',
        'settings.showRecs': '「おすすめ」タブを表示',
        'settings.hideNav': 'シネマモードでナビゲーションを隠す',
        'settings.newUi': '新UIを有効化(タブ + 全幅動画)',
        'actions.close': '閉じる',
        'actions.save': '保存',
        'tabs.about': '概要',
        'tabs.uploads': '投稿',
        'tabs.recs': 'おすすめ',
        'tabs.likes': 'いいね',
        'tabs.comments': 'コメント',
        'tabs.aria': 'Iwara NeoUI タブ',
        'cinema.button': 'シネマ',
        'cinema.title': 'シネマモードを切り替え (T)',
    },
    ko: {
        'settings.button': '옵션',
        'settings.title': 'Iwara NeoUI - 설정',
        'settings.language': '언어',
        'settings.language.auto': '자동',
        'settings.showLikes': '"좋아요" 탭 표시',
        'settings.showRecs': '"추천" 탭 표시',
        'settings.hideNav': '시네마 모드에서 내비게이션 숨기기',
        'settings.newUi': '새 UI 사용(탭 + 전체 폭 동영상)',
        'actions.close': '닫기',
        'actions.save': '저장',
        'tabs.about': '정보',
        'tabs.uploads': '업로드',
        'tabs.recs': '추천',
        'tabs.likes': '좋아요',
        'tabs.comments': '댓글',
        'tabs.aria': 'Iwara NeoUI 탭',
        'cinema.button': '시네마',
        'cinema.title': '시네마 모드 전환 (T)',
    },
};
let currentLocale = 'en';
const LANGUAGE_OPTIONS = [
    { value: 'auto', nativeLabel: '' },
    { value: 'en', nativeLabel: 'English' },
    { value: 'zh-CN', nativeLabel: '简体中文' },
    { value: 'zh-TW', nativeLabel: '繁體中文' },
    { value: 'ja', nativeLabel: '日本語' },
    { value: 'ko', nativeLabel: '한국어' },
];
function isLanguageSetting(value) {
    return value === 'auto' || SUPPORTED_LOCALES.includes(value);
}
function normalizeLanguageTag(tag) {
    return tag.trim().replace('_', '-').toLowerCase();
}
function localeFromLanguageTag(tag) {
    const n = normalizeLanguageTag(tag);
    if (n === 'zh-cn' || n === 'zh-sg' || n === 'zh-hans' || n.startsWith('zh-hans-'))
        return 'zh-CN';
    if (n === 'zh-tw' || n === 'zh-hk' || n === 'zh-mo' || n === 'zh-hant' || n.startsWith('zh-hant-'))
        return 'zh-TW';
    if (n === 'zh')
        return 'zh-CN';
    if (n.startsWith('ja'))
        return 'ja';
    if (n.startsWith('ko'))
        return 'ko';
    if (n.startsWith('en'))
        return 'en';
    return null;
}
function resolveLocale(setting, browserLanguages = []) {
    if (setting !== 'auto')
        return setting;
    for (const tag of browserLanguages) {
        const locale = localeFromLanguageTag(tag);
        if (locale)
            return locale;
    }
    return 'en';
}
function t(key, locale = currentLocale) {
    return MESSAGES[locale][key] || MESSAGES.en[key] || key;
}
function getBrowserLanguages(nav = typeof navigator !== 'undefined' ? navigator : undefined) {
    if (!nav)
        return [];
    const languages = Array.isArray(nav.languages) ? nav.languages : [];
    if (languages.length)
        return [...languages];
    return nav.language ? [nav.language] : [];
}
function createLanguageSelect(doc, value, locale) {
    const select = doc.createElement('select');
    select.id = 'itw-language';
    select.className = 'itw-select';
    for (const opt of LANGUAGE_OPTIONS) {
        const option = doc.createElement('option');
        option.value = opt.value;
        option.textContent = opt.value === 'auto' ? t('settings.language.auto', locale) : opt.nativeLabel;
        option.selected = opt.value === value;
        select.appendChild(option);
    }
    return select;
}
function normalizeSettings(saved) {
    if (!saved || typeof saved !== 'object')
        return { ...DEFAULTS };
    const raw = saved;
    const next = { ...DEFAULTS };
    for (const key of Object.keys(DEFAULTS)) {
        const value = raw[key];
        if (key === 'language') {
            if (isLanguageSetting(value))
                next.language = value;
            continue;
        }
        if (typeof value === 'boolean')
            next[key] = value;
    }
    if (typeof raw.hideLikedBy === 'boolean' && !('showLikesTab' in raw)) {
        next.showLikesTab = !raw.hideLikedBy;
    }
    return next;
}
// Move `block` into `panel` unless it is already inside, or is the panel
// itself / one of its ancestors. Blocks come from closest()/common-ancestor
// walks that can overshoot the panel after a site re-render, and appending
// an ancestor into its own descendant throws HierarchyRequestError.
// Module scope so Node can require() it for unit tests (see test/); the
// browser code below is gated on `typeof window`.
function adoptIntoPanel(panel, block) {
    if (!panel || !block)
        return false;
    if (panel.contains(block))
        return false;
    if (block.contains(panel))
        return false;
    panel.append(block);
    return true;
}
if (typeof window !== 'undefined')
    (() => {
        'use strict';
        const neoWindow = window;
        if (neoWindow.__IWARA_NEOUI_ACTIVE)
            return;
        neoWindow.__IWARA_NEOUI_ACTIVE = true;
        // ---- Constants ----
        const TIMEOUTS = {
            WAIT_FOR: 20000,
            POLL_INTERVAL: 250,
            SLEEP_SHORT: 500,
            OBSERVER_DEBOUNCE: 150
        };
        const SELECTORS = {
            VIDEO_COL: '.col-12.col-md-9',
            PAGE_VIDEO_CONTENT: '.page-video__content',
            COL_MD_9: '[class*="col-"][class*="md-9"]',
            SIDEBAR: '.page-video__sidebar',
            LIKES_LIST: '.likesList',
            LIKED_BY: '.itw-liked-by',
            RECS: '.itw-recs',
            TABBAR: '.itw-tabbar',
            PANELS: '.itw-panels'
        };
        const CSS_CLASSES = {
            TABS_ACTIVE: 'itw-tabs-active',
            THEATER: 'itw-theater'
        };
        // ---- DOM Cache ----
        const domCache = {
            cache: new Map(),
            get(selector, root = document) {
                const key = `${selector}:${root === document ? 'doc' : 'custom'}`;
                if (this.cache.has(key)) {
                    const cached = this.cache.get(key);
                    // Verify element is still in DOM
                    if (cached && cached.isConnected) {
                        return cached;
                    }
                    this.cache.delete(key);
                }
                const element = root.querySelector(selector);
                if (element) {
                    this.cache.set(key, element);
                }
                return element;
            },
            clear() {
                this.cache.clear();
            }
        };
        // ---- Observer Manager ----
        const observerManager = {
            observers: new Map(),
            create(name, callback) {
                if (this.observers.has(name)) {
                    this.disconnect(name);
                }
                const observer = new MutationObserver(callback);
                this.observers.set(name, observer);
                return observer;
            },
            observe(name, target = document.documentElement, options = { childList: true, subtree: true }) {
                const observer = this.observers.get(name);
                if (observer) {
                    observer.observe(target, options);
                }
            },
            disconnect(name) {
                const observer = this.observers.get(name);
                if (observer) {
                    observer.disconnect();
                    this.observers.delete(name);
                }
            }
        };
        // ---- Utilities ----
        const dom = {
            el(html) {
                const t = document.createElement('template');
                t.innerHTML = html.trim();
                const element = t.content.firstElementChild;
                if (!element)
                    throw new Error('empty HTML template');
                return element;
            },
            on(el, evt, cb) {
                if (el)
                    el.addEventListener(evt, cb, { passive: true });
            },
            qs(sel, root = document) {
                return root.querySelector(sel);
            },
            qsa(sel, root = document) {
                return [...root.querySelectorAll(sel)];
            },
        };
        const sleep = (ms = TIMEOUTS.SLEEP_SHORT) => new Promise(r => setTimeout(r, ms));
        const waitFor = async (selector, { root = document, timeout = TIMEOUTS.WAIT_FOR, interval = TIMEOUTS.POLL_INTERVAL } = {}) => {
            const end = Date.now() + timeout;
            while (Date.now() < end) {
                const node = root.querySelector(selector);
                if (node)
                    return node;
                await sleep(interval);
            }
            return null;
        };
        const addStyle = (css) => {
            const s = document.createElement('style');
            s.textContent = css;
            document.documentElement.appendChild(s);
            return s;
        };
        // Persistent observer for Likes relocation (initialized only when new UI is active)
        let __itwLikesObserver = null;
        // ---- Storage (GM or localStorage fallback) ----
        const store = {
            async get(key, def) {
                try {
                    if (typeof GM?.getValue === 'function')
                        return await GM.getValue(key, def);
                }
                catch { }
                try {
                    const v = localStorage.getItem(`itw:${key}`);
                    return v == null ? def : JSON.parse(v);
                }
                catch {
                    return def;
                }
            },
            async set(key, val) {
                try {
                    if (typeof GM?.setValue === 'function')
                        return await GM.setValue(key, val);
                }
                catch { }
                try {
                    localStorage.setItem(`itw:${key}`, JSON.stringify(val));
                }
                catch { }
            }
        };
        let settings = { ...DEFAULTS };
        // Runtime-only cinema state (not persisted when cinemaHideNav is true)
        let cinemaActive = false;
        const loadSettings = async () => {
            try {
                const saved = await store.get('settings', {});
                settings = normalizeSettings(saved);
                currentLocale = resolveLocale(settings.language, getBrowserLanguages());
            }
            catch (e) {
                console.warn('[Iwara NeoUI] loadSettings failed:', e);
                settings = { ...DEFAULTS };
                currentLocale = resolveLocale(settings.language, getBrowserLanguages());
            }
        };
        const saveSettings = async () => {
            try {
                await store.set('settings', settings);
            }
            catch (e) {
                console.warn('[Iwara NeoUI] saveSettings failed:', e);
            }
        };
        // ---- CSS for features ----
        addStyle(`
    /* Header button */
    .itw-btn { display:inline-flex; align-items:center; gap:.35rem; padding:.35rem .55rem; border-radius:.5rem; border:1px solid rgba(255,255,255,.12); color:inherit; background:rgba(255,255,255,.06); cursor:pointer; font:600 12px/1.2 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
    .itw-btn:hover { background:rgba(255,255,255,.12); }
    .itw-gear { width:16px; height:16px; fill:currentColor; }
    .itw-btn.itw-float { position: fixed; top: 12px; right: 12px; z-index: 2147483646; }
    .itw-btn.itw-in-header { margin-left: 8px; }

    /* Modal */
    .itw-modal-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.5); display:none; z-index: 99999; }
    .itw-modal { position:fixed; inset:auto auto 0 0; left:50%; top:50%; transform:translate(-50%,-50%); width:min(520px, calc(100vw - 24px)); background:#16181d; color:#e6e6e6; border:1px solid #2a2f36; border-radius:12px; box-shadow:0 10px 40px rgba(0,0,0,.45); padding:16px; display:none; z-index: 100000; }
    .itw-modal h3 { margin:0 0 12px; font:600 16px/1.3 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
    .itw-row { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 0; border-top:1px solid #232831; }
    .itw-row:first-of-type { border-top:none; }
    .itw-actions { display:flex; gap:8px; justify-content:flex-end; padding-top:12px; }
    .itw-select { min-width:160px; max-width:220px; color:#e6e6e6; background:#20242b; border:1px solid #3a404b; border-radius:6px; padding:6px 8px; font:600 12px/1.2 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
    .itw-switch { position:relative; width:44px; height:26px; border-radius:999px; background:#3a404b; transition:.2s; flex:0 0 auto; }
    .itw-switch input { position:absolute; inset:0; opacity:0; }
    .itw-knob { position:absolute; top:3px; left:3px; width:20px; height:20px; border-radius:50%; background:#c9c9c9; transition:.2s; }
    .itw-switch input:checked + .itw-knob { left:21px; background:#64d36d; }

    /* Feature styles */
    .itw-hide-liked-by .itw-liked-by, .itw-hide-liked-by section:has(> h2.itw-liked-by-title) { display:none !important; }
    /* Hide the likes block used by iwara.tv preview/site as well */
    .itw-hide-liked-by .block:has(.likesList), .itw-hide-liked-by .likesList { display:none !important; }
    /* Hide Recommended (More like this) when its tab is disabled in new UI */
    .itw-hide-recs .itw-recs, .itw-hide-recs .moreLikeThis { display:none !important; }
    /* When tabs UI is active, ensure any stray Recommended blocks are hidden outside the Recommended panel */
    body.itw-tabs-active .moreLikeThis { display:none !important; }
    body.itw-tabs-active .itw-panel-recs .moreLikeThis { display:block !important; }

    /* Cinema Mode — base (always applied when cinema is active) */
    body.itw-theater { overflow-y: auto; }
    body.itw-theater .page-video__sidebar,
    body.itw-theater aside { display: none !important; }
    /* Expand video content area — scoped to .page so header is untouched */
    body.itw-theater .page .container-fluid { max-width: 100% !important; width: 100% !important; padding-left: 0 !important; padding-right: 0 !important; }
    body.itw-theater .page .row { margin-left: 0 !important; margin-right: 0 !important; }
    body.itw-theater .col-12.col-md-9 { max-width: 100% !important; flex: 0 0 100% !important; padding-left: 0 !important; padding-right: 0 !important; }
    /* Player: black backdrop, fill width */
    body.itw-theater .page-video__player { background: #000 !important; }
    body.itw-theater .video-js { width: 100% !important; aspect-ratio: 16/9 !important; max-height: 85vh !important; margin: 0 auto !important; }
    body.itw-theater .vjs-tech { width: 100% !important; height: 100% !important; object-fit: contain !important; }
    /* Restore padding below the player for tabs/content */
    body.itw-theater .itw-tabbar,
    body.itw-theater .itw-panels { padding-left: 16px !important; padding-right: 16px !important; }
    /* Cinema Mode — full immersive (hide nav when setting is on) */
    body.itw-theater-hide-nav header.header,
    body.itw-theater-hide-nav div.menu { display: none !important; }

    /* Tabs layout for video page */
    body.itw-tabs-active .col-12.col-md-9 { max-width: 100% !important; flex: 0 0 100% !important; }
    body.itw-tabs-active .page-video__sidebar { display: none !important; }
    body.itw-tabs-active .page-video__player, body.itw-tabs-active .video-js, body.itw-tabs-active .vjs_video_3-dimensions { width: 100% !important; }

    .itw-tabbar { display:flex; align-items:center; gap:8px; border-bottom:1px solid #2a2f36; margin-top:12px; }
    .itw-tabbar .itw-tab { appearance:none; background:none; border:none; color:#e6e6e6; cursor:pointer; padding:10px 12px; font:600 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; border-bottom:2px solid transparent; opacity:.85; }
    .itw-tabbar .itw-tab:hover { opacity:1; }
    .itw-tabbar .itw-tab[aria-selected="true"] { color:#ff4b4b; border-color:#ff4b4b; opacity:1; }

    .itw-panels { padding-top:12px; }
    .itw-panel { display:none; }
    .itw-panel.active { display:block; }
    /* Cinema toggle button in tab bar */
    .itw-cinema-btn { appearance:none; background:none; border:1px solid rgba(255,255,255,.15); color:#e6e6e6; cursor:pointer; padding:6px 10px; font:600 12px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; border-radius:6px; margin-left:auto; opacity:.7; transition: opacity .2s, background .2s; }
    .itw-cinema-btn:hover { opacity:1; background:rgba(255,255,255,.08); }
    .itw-cinema-btn.active { color:#ff4b4b; border-color:#ff4b4b; opacity:1; }
    /* Safety: if for any reason tabs markup lingers but layout class is absent, hide the UI */
    body:not(.itw-tabs-active) .itw-tabbar, body:not(.itw-tabs-active) .itw-panels { display:none !important; }

    /* Force visibility for Likes content inside the Likes panel */
    body.itw-tabs-active .itw-panel-likes .itw-liked-by,
    body.itw-tabs-active .itw-panel-likes .block,
    body.itw-tabs-active .itw-panel-likes .block__content,
    body.itw-tabs-active .itw-panel-likes .likesList { display:block !important; visibility:visible !important; opacity:1 !important; height:auto !important; max-height:none !important; min-height:auto !important; width:auto !important; min-width:0 !important; }
    /* Two-row layout: users row + pagination row */
    body.itw-tabs-active .itw-panel-likes .block { display:flex !important; flex-direction:column !important; gap:16px !important; }
    /* Override Bootstrap row layout for horizontal scrolling */
    body.itw-tabs-active .itw-panel-likes .likesList .row { display:flex !important; flex-direction:row !important; flex-wrap:nowrap !important; gap:16px !important; overflow-x:auto !important; padding:8px 0 !important; margin:0 !important; }
    /* Override Bootstrap columns to become flex items */
    body.itw-tabs-active .itw-panel-likes .likesList .row > [class*="col-"] { flex:0 0 auto !important; width:160px !important; max-width:160px !important; min-width:160px !important; padding:0 !important; }
    /* Individual user items - compact horizontal cards */
    body.itw-tabs-active .itw-panel-likes .likesList__item { display:block !important; width:100% !important; height:100% !important; padding:8px 12px !important; background:rgba(255,255,255,0.05) !important; border-radius:8px !important; text-decoration:none !important; box-sizing:border-box !important; }
    /* Inner content layout for anchor elements */
    body.itw-tabs-active .itw-panel-likes .likesList__item > * { display:flex !important; }
    /* Pagination row - centered below users */
    body.itw-tabs-active .itw-panel-likes .pagination { display:flex !important; justify-content:center !important; margin-top:8px !important; }
    /* Ensure user avatars are properly sized */
    body.itw-tabs-active .itw-panel-likes .likesList img { width:60px !important; height:60px !important; border-radius:50% !important; object-fit:cover !important; }
  `);
        // ---- Modal creation ----
        let modalBackdrop = null;
        let modalEl = null;
        const createSwitch = (id, checked, label) => {
            const row = dom.el('<div class="itw-row"></div>');
            const labelText = dom.el('<div></div>');
            labelText.textContent = label;
            const switchLabel = dom.el(`
      <label class="itw-switch" for="${id}">
        <input id="${id}" type="checkbox" ${checked ? 'checked' : ''} />
        <span class="itw-knob"></span>
      </label>
    `);
            row.append(labelText, switchLabel);
            return row;
        };
        const createLanguageRow = () => {
            const row = dom.el('<div class="itw-row"></div>');
            const labelText = dom.el('<div></div>');
            labelText.textContent = t('settings.language');
            row.append(labelText, createLanguageSelect(document, settings.language, currentLocale));
            return row;
        };
        const ensureModal = () => {
            try {
                if (modalEl)
                    return modalEl;
                modalBackdrop = dom.el('<div class="itw-modal-backdrop"></div>');
                modalEl = dom.el('<div class="itw-modal" role="dialog" aria-modal="true"></div>');
                const content = dom.el('<div></div>');
                const title = dom.el('<h3></h3>');
                title.textContent = t('settings.title');
                content.appendChild(title);
                // Tabs visibility
                const languageRow = createLanguageRow();
                const likesTabRow = createSwitch('itw-show-likes', settings.showLikesTab, t('settings.showLikes'));
                const recsTabRow = createSwitch('itw-show-recs', settings.showRecsTab, t('settings.showRecs'));
                const cinemaNavRow = createSwitch('itw-cinema-hide-nav', settings.cinemaHideNav, t('settings.hideNav'));
                const newUiRow = createSwitch('itw-new-ui', settings.newUI, t('settings.newUi'));
                const actions = dom.el('<div class="itw-actions"></div>');
                const closeBtn = dom.el('<button class="itw-btn" type="button"></button>');
                const saveBtn = dom.el('<button class="itw-btn" type="button"></button>');
                closeBtn.textContent = t('actions.close');
                saveBtn.textContent = t('actions.save');
                actions.append(closeBtn, saveBtn);
                content.append(languageRow, likesTabRow, recsTabRow, cinemaNavRow, newUiRow, actions);
                modalEl.append(content);
                document.body.append(modalBackdrop, modalEl);
                dom.on(closeBtn, 'click', () => toggleModal(false));
                dom.on(modalBackdrop, 'click', () => toggleModal(false));
                dom.on(saveBtn, 'click', async () => {
                    const prev = {
                        newUI: settings.newUI,
                        showLikesTab: settings.showLikesTab,
                        showRecsTab: settings.showRecsTab,
                        language: settings.language,
                    };
                    if (!modalEl)
                        return;
                    settings.showLikesTab = modalEl.querySelector('#itw-show-likes')?.checked ?? settings.showLikesTab;
                    settings.showRecsTab = modalEl.querySelector('#itw-show-recs')?.checked ?? settings.showRecsTab;
                    settings.cinemaHideNav = modalEl.querySelector('#itw-cinema-hide-nav')?.checked ?? settings.cinemaHideNav;
                    settings.newUI = modalEl.querySelector('#itw-new-ui')?.checked ?? settings.newUI;
                    const languageValue = modalEl.querySelector('#itw-language')?.value;
                    if (isLanguageSetting(languageValue))
                        settings.language = languageValue;
                    currentLocale = resolveLocale(settings.language, getBrowserLanguages());
                    const settingsBtn = document.getElementById('itw-settings-btn');
                    if (settingsBtn)
                        syncHeaderBtnText(settingsBtn);
                    const hadTabs = !!document.querySelector('.itw-tabbar');
                    const newUiChanged = prev.newUI !== settings.newUI;
                    await saveSettings();
                    applySettings();
                    // If staying in New UI and tab composition changed, rebuild tabs
                    const tabsChanged = prev.showLikesTab !== settings.showLikesTab ||
                        prev.showRecsTab !== settings.showRecsTab ||
                        prev.language !== settings.language;
                    if (!newUiChanged && hadTabs && settings.newUI && tabsChanged) {
                        teardownVideoTabs();
                    }
                    if (newUiChanged) {
                        // Force a single reload to guarantee full revert/apply of layout across SPA hydration
                        location.reload();
                        return;
                    }
                    applyUiMode();
                    toggleModal(false);
                    if (prev.language !== settings.language) {
                        modalBackdrop?.remove();
                        modalEl?.remove();
                        modalBackdrop = null;
                        modalEl = null;
                    }
                });
                return modalEl;
            }
            catch (e) {
                console.warn('[Iwara NeoUI] ensureModal failed:', e);
                return null;
            }
        };
        const toggleModal = (show) => {
            ensureModal();
            if (!modalBackdrop || !modalEl)
                return;
            modalBackdrop.style.display = show ? 'block' : 'none';
            modalEl.style.display = show ? 'block' : 'none';
        };
        // ---- Insert header button (before coin indicator when possible) ----
        const GEAR_SVG = '<svg class="itw-gear" viewBox="0 0 24 24" aria-hidden="true"><path d="M19.14,12.94a7.43,7.43,0,0,0,.05-.94,7.43,7.43,0,0,0-.05-.94l2-1.56a.5.5,0,0,0,.12-.64l-1.9-3.29a.5.5,0,0,0-.6-.22l-2.36,1a7.39,7.39,0,0,0-1.63-.94l-.36-2.5A.5.5,0,0,0,12.47,2H9.53a.5.5,0,0,0-.5.42l-.36,2.5a7.39,7.39,0,0,0-1.63.94l-2.36-1a.5.5,0,0,0-.6.22L2.22,8.88a.5.5,0,0,0,.12.64l2,1.56a7.43,7.43,0,0,0-.05.94,7.43,7.43,0,0,0,.05.94l-2,1.56a.5.5,0,0,0-.12.64l1.9,3.29a.5.5,0,0,0,.6.22l2.36-1a7.39,7.39,0,0,0,1.63.94l.36,2.5a.5.5,0,0,0,.5.42h2.94a.5.5,0,0,0,.5-.42l.36-2.5a7.39,7.39,0,0,0,1.63-.94l2.36,1a.5.5,0,0,0,.6-.22l1.9-3.29a.5.5,0,0,0-.12-.64ZM11,15.5A3.5,3.5,0,1,1,14.5,12,3.5,3.5,0,0,1,11,15.5Z"/></svg>';
        const makeHeaderBtn = () => dom.el(`<button id="itw-settings-btn" class="itw-btn" type="button" title="Iwara NeoUI">${GEAR_SVG}<span>${t('settings.button')}</span></button>`);
        const syncHeaderBtnText = (btn) => {
            btn.title = 'Iwara NeoUI';
            const label = btn.querySelector('span');
            if (label)
                label.textContent = t('settings.button');
        };
        const findHeaderRoot = (btn) => {
            const candidates = document.querySelectorAll('header, nav[role="navigation"], .header, [class*="header"]');
            return [...candidates].find(candidate => candidate !== btn && !btn.contains(candidate)) ?? null;
        };
        const insertNextToSearch = (btn) => {
            const header = findHeaderRoot(btn);
            if (!header)
                return { placed: false, anchorEl: null };
            // Prefer the container that wraps the search UI
            const searchContainer = header.querySelector('.header__content__items__search, [class*="items__search" i], .header__search, [role="search"]');
            if (searchContainer?.parentElement) {
                // Already placed correctly?
                if (btn.previousElementSibling === searchContainer) {
                    return { placed: true, anchorEl: searchContainer };
                }
                searchContainer.insertAdjacentElement('afterend', btn);
                return { placed: true, anchorEl: searchContainer };
            }
            // Fallback: try to locate a search form and place after it
            const searchForm = header.querySelector('form.header__search, form[action*="search" i]') || header.querySelector('input[type="search"]')?.closest('form');
            if (searchForm?.parentElement) {
                searchForm.insertAdjacentElement('afterend', btn);
                return { placed: true, anchorEl: searchForm };
            }
            // Final fallback: append into a central items container or header
            const items = header.querySelector('.header__content__items, [class*="content__items" i]') || header;
            if (btn.parentElement !== items)
                items.appendChild(btn);
            return { placed: false, anchorEl: items };
        };
        const ensureHeaderButton = async () => {
            try {
                let btn = document.getElementById('itw-settings-btn');
                if (!btn) {
                    btn = makeHeaderBtn();
                    dom.on(btn, 'click', () => { ensureModal(); toggleModal(true); });
                    btn.classList.add('itw-float');
                    document.body.appendChild(btn);
                }
                let debounce = null;
                const reanchor = () => {
                    // Ensure the button exists in the DOM
                    if (!btn.isConnected)
                        document.body.appendChild(btn);
                    const { placed } = insertNextToSearch(btn);
                    if (placed) {
                        btn.classList.remove('itw-float');
                        btn.classList.add('itw-in-header');
                    }
                    else {
                        btn.classList.add('itw-float');
                        btn.classList.remove('itw-in-header');
                    }
                };
                // Initial attempt
                reanchor();
                // Observe DOM continuously (SPA hydration or header rerenders)
                const mo = new MutationObserver(() => {
                    if (debounce)
                        return;
                    debounce = setTimeout(() => { debounce = null; reanchor(); }, 300);
                });
                mo.observe(document.documentElement, { childList: true, subtree: true });
                // Handle SPA route changes via pushState/popstate interception
                const onRouteChange = () => {
                    domCache.clear();
                    observerManager.disconnect('likes');
                    if (cinemaActive && settings.cinemaHideNav) {
                        cinemaActive = false;
                        applyCinema();
                    }
                    reanchor();
                    applyUiMode();
                };
                const origPushState = history.pushState;
                history.pushState = function (...args) {
                    origPushState.apply(this, args);
                    onRouteChange();
                };
                const origReplaceState = history.replaceState;
                history.replaceState = function (...args) {
                    origReplaceState.apply(this, args);
                    onRouteChange();
                };
                window.addEventListener('popstate', onRouteChange);
                // Additional safety hooks
                window.addEventListener('load', reanchor, { once: true });
                document.addEventListener('visibilitychange', () => { if (!document.hidden)
                    reanchor(); });
            }
            catch (e) {
                console.warn('[Iwara NeoUI] ensureHeaderButton failed:', e);
            }
        };
        // ---- Liked by detection and toggle ----
        const LIKED_BY_TEXTS = [
            'liked by', 'likes by', 'liked-by', 'liked',
            '喜欢', '赞过',
            'いいね',
            '좋아요',
        ];
        const tagAndMoveLiked = (el) => {
            if (!el)
                return null;
            const likesPanel = settings.newUI && document.querySelector('.itw-panel-likes');
            // A scan/closest() hit can be the panel itself or an ancestor of it;
            // tagging those mis-marks page-level containers (hiding .itw-liked-by
            // would then hide the whole column), so bail out entirely.
            if (likesPanel && el.contains(likesPanel))
                return null;
            el.classList.add('itw-liked-by');
            if (likesPanel && settings.showLikesTab)
                adoptIntoPanel(likesPanel, el);
            return el;
        };
        const findLikedBySection = () => {
            try {
                const likesList = dom.qs('.likesList');
                if (likesList) {
                    const container = likesList.closest('.block, .contentBlock, .block--padding, .card, .panel') || likesList.parentElement;
                    if (container)
                        return tagAndMoveLiked(container);
                }
                // Targeted fallback: scan only structural containers (not all divs)
                for (const el of dom.qsa('.block, .contentBlock, .card, .panel, section')) {
                    const title = el.querySelector('h2, h3, .text--h3');
                    const text = (title?.textContent || '').trim().toLowerCase();
                    if (text && LIKED_BY_TEXTS.some(t => text.includes(t))) {
                        return tagAndMoveLiked(el);
                    }
                }
                return null;
            }
            catch (e) {
                console.warn('[Iwara NeoUI] findLikedBySection failed:', e);
                return null;
            }
        };
        const applyHideLikedBy = () => {
            const root = document.documentElement;
            // Vanilla mode should be vanilla: never hide on newUI=false
            const hide = settings.newUI ? !settings.showLikesTab : false;
            root.classList.toggle('itw-hide-liked-by', hide);
        };
        // Tag and hide Recommended (More like this)
        const findRecsSection = () => {
            try {
                const el = dom.qs('.moreLikeThis') || [...document.querySelectorAll('.text--h3, h2, h3')].find(h => /more like this|recommended|related/i.test(h.textContent || ''))?.closest('.block, .contentBlock, .panel, .card, .section, .moreLikeThis');
                if (el) {
                    el.classList.add('itw-recs');
                    return el;
                }
                return null;
            }
            catch (e) {
                console.warn('[Iwara NeoUI] findRecsSection failed:', e);
                return null;
            }
        };
        const applyHideRecs = () => {
            const root = document.documentElement;
            const hide = settings.newUI ? !settings.showRecsTab : false;
            root.classList.toggle('itw-hide-recs', hide);
        };
        // ---- Cinema Mode ----
        const applyCinema = () => {
            try {
                const root = document.body || document.documentElement;
                root.classList.toggle('itw-theater', cinemaActive);
                root.classList.toggle('itw-theater-hide-nav', cinemaActive && settings.cinemaHideNav);
                // Move settings button to body when cinema is active so it's always reachable
                const settingsBtn = document.getElementById('itw-settings-btn');
                if (settingsBtn) {
                    if (cinemaActive) {
                        settingsBtn.classList.remove('itw-in-header');
                        settingsBtn.classList.add('itw-float');
                        document.body.appendChild(settingsBtn);
                    }
                    // Button will be re-anchored by reanchor() when cinema turns off
                }
                // Scroll to top of player when entering cinema mode
                if (cinemaActive) {
                    const player = dom.qs('.page-video__player, .video-js');
                    if (player)
                        player.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }
            }
            catch (e) {
                console.warn('[Iwara NeoUI] applyCinema failed:', e);
            }
        };
        const toggleCinema = () => {
            cinemaActive = !cinemaActive;
            applyCinema();
            // Persist only when nav is NOT hidden (light cinema mode)
            if (!settings.cinemaHideNav) {
                settings.theaterMode = cinemaActive;
                saveSettings();
            }
        };
        // ---- Keyboard shortcut (T) for Cinema Mode ----
        dom.on(document, 'keydown', (e) => {
            const event = e;
            const target = event.target instanceof Element ? event.target : null;
            if (event.key.toLowerCase?.() === 't' && !/input|textarea|select/i.test(target?.tagName || '')) {
                toggleCinema();
                // Sync cinema button state if it exists
                const btn = dom.qs('.itw-cinema-btn');
                if (btn)
                    btn.classList.toggle('active', cinemaActive);
            }
        });
        // ---- Apply settings helpers ----
        const applySettings = () => {
            applyHideLikedBy();
            applyHideRecs();
            applyCinema();
        };
        // ---- Page type detection ----
        const isVideoPage = () => {
            // Quick route-based hint
            const path = location.pathname;
            if (/^\/(video|videos)\//i.test(path))
                return true;
            if (/^\/(search|users|image|images|posts|forums|messages|notifications|settings|login|register)\b/i.test(path))
                return false;
            // Structural markers: presence of a player and typical video-page sections
            const hasPlayer = !!document.querySelector('.page-video__player, .plyr__video-wrapper, .video-js, .jwplayer, [data-plyr], video');
            const hasMarkers = !!document.querySelector('.page-video__details, .moreFromUser, .moreLikeThis, .page-video__bottom, .page-video__tags, #comments, .comments');
            return hasPlayer && hasMarkers;
        };
        // ---- Likes relocation helpers (module-level to avoid recreation) ----
        const nearestCommonAncestor = (a, b) => {
            if (!a || !b)
                return null;
            const aChain = new Set();
            for (let n = a; n; n = n.parentElement)
                aChain.add(n);
            for (let n = b; n; n = n.parentElement)
                if (aChain.has(n))
                    return n;
            return null;
        };
        const unhideDeep = (rootEl) => {
            const stack = [rootEl];
            while (stack.length) {
                const el = stack.pop();
                if (!el)
                    continue;
                if (el.hasAttribute('hidden'))
                    el.removeAttribute('hidden');
                if (el instanceof HTMLElement) {
                    if (el.style.display === 'none')
                        el.style.display = '';
                    if (el.style.visibility === 'hidden')
                        el.style.visibility = '';
                    if (el.style.opacity === '0')
                        el.style.opacity = '';
                    if (el.style.height === '0px')
                        el.style.height = '';
                    if (el.style.maxHeight === '0px')
                        el.style.maxHeight = '';
                }
                stack.push(...Array.from(el.children));
            }
        };
        const moveLikesIntoPanel = () => {
            if (!settings.showLikesTab)
                return;
            const likesPanel = document.querySelector('.itw-panel-likes');
            if (!likesPanel)
                return;
            const list = document.querySelector('.likesList');
            const title = [...document.querySelectorAll('.text--h3, h2, h3')]
                .find(el => /liked by/i.test(el.textContent || ''));
            let block = null;
            // 1) If title and list share a .block__content ancestor, move that
            const listBC = list?.closest('.block__content') || null;
            const titleBC = title?.closest('.block__content') || null;
            if (listBC && titleBC && listBC === titleBC)
                block = listBC;
            // 2) Nearest common ancestor if reasonable
            if (!block && list && title) {
                const nca = nearestCommonAncestor(list, title);
                if (nca && !nca.classList.contains('itw-panels') && nca !== document.body)
                    block = nca;
            }
            // 3) .block__content of either
            if (!block)
                block = listBC || titleBC;
            // 4) Fall back to typical blocks
            if (!block && list) {
                block = list.closest('.block, .contentBlock, .block--padding, .card, .panel')
                    || (title ? title.parentElement : null)
                    || list.parentElement;
            }
            if (!block && title) {
                block = title.closest('.block, .contentBlock, .block--padding, .card, .panel') || title.parentElement;
            }
            // Prefer outer block/card over inner .block__content
            if (block?.matches('.block__content') && block.parentElement?.matches('.block, .contentBlock, .block--padding, .card, .panel, .section')) {
                block = block.parentElement;
            }
            // Defer if list exists but is empty (site may lazy-render)
            if (!likesPanel.contains(block || document.body) && list && list.childElementCount === 0)
                return;
            // Not redundant with adoptIntoPanel's internal check: this also gates the
            // tag + unhide side effects, which must never hit an ancestor of the panel.
            if (block && !block.contains(likesPanel)) {
                block.classList.add('itw-liked-by');
                unhideDeep(block);
                adoptIntoPanel(likesPanel, block);
            }
        };
        // ---- Tabs: About / Uploads / Recommended / Likes / Comments ----
        const setupVideoTabs = () => {
            try {
                // Do not build tabs in vanilla mode
                if (!settings.newUI)
                    return;
                // Safety: if previous class lingered but no UI is present, clear it
                if (!document.querySelector('.itw-tabbar') && !document.querySelector('.itw-panels')) {
                    document.body.classList.remove(CSS_CLASSES.TABS_ACTIVE);
                }
                if (document.querySelector('.itw-tabbar'))
                    return; // already set up
                if (!isVideoPage())
                    return; // only on actual video pages
                const mainCol = domCache.get(SELECTORS.VIDEO_COL) || domCache.get(SELECTORS.PAGE_VIDEO_CONTENT) || domCache.get(SELECTORS.COL_MD_9);
                if (!mainCol)
                    return;
                // Require a real player host; do not fall back to arbitrary first child
                const playerHost = mainCol.querySelector('.page-video__player') ||
                    mainCol.querySelector('.video-js, .plyr__video-wrapper, .jwplayer, [data-plyr]')?.closest('.page-video__player') ||
                    mainCol.querySelector('.video-js, .plyr__video-wrapper, .jwplayer, [data-plyr]');
                if (!playerHost)
                    return;
                // Panels
                const panels = dom.el('<div class="itw-panels"></div>');
                const aboutPanel = dom.el('<section class="itw-panel itw-panel-about" role="tabpanel" aria-labelledby="itw-tab-about"></section>');
                const aboutBody = dom.el('<div class="itw-panel-body itw-about-body"></div>');
                aboutPanel.append(aboutBody);
                const uploadsPanel = dom.el('<section class="itw-panel itw-panel-uploads" role="tabpanel" aria-labelledby="itw-tab-uploads"></section>');
                const recsPanel = dom.el('<section class="itw-panel itw-panel-recs" role="tabpanel" aria-labelledby="itw-tab-recs"></section>');
                const likesPanel = dom.el('<section class="itw-panel itw-panel-likes" role="tabpanel" aria-labelledby="itw-tab-likes"></section>');
                const commentsPanel = dom.el('<section class="itw-panel itw-panel-comments" role="tabpanel" aria-labelledby="itw-tab-comments"></section>');
                panels.append(aboutPanel, uploadsPanel, recsPanel, likesPanel, commentsPanel);
                // Tab bar
                const tabbar = dom.el(`<div class="itw-tabbar" role="tablist" aria-label="${t('tabs.aria')}"></div>`);
                const makeTab = (id, text, selected = false) => dom.el(`<button class="itw-tab" role="tab" id="itw-tab-${id}" aria-selected="${selected}" aria-controls="itw-panel-${id}">${text}</button>`);
                const aboutTab = makeTab('about', t('tabs.about'), true);
                const uploadsTab = makeTab('uploads', t('tabs.uploads'));
                const recsTab = makeTab('recs', t('tabs.recs'));
                const likesTab = makeTab('likes', t('tabs.likes'));
                const commentsTab = makeTab('comments', t('tabs.comments'));
                tabbar.append(aboutTab);
                tabbar.append(uploadsTab);
                if (settings.showRecsTab)
                    tabbar.append(recsTab);
                if (settings.showLikesTab)
                    tabbar.append(likesTab);
                tabbar.append(commentsTab);
                // Cinema mode toggle (pushed to the right via margin-left:auto)
                const cinemaBtn = dom.el(`<button class="itw-cinema-btn${cinemaActive ? ' active' : ''}" type="button" title="${t('cinema.title')}">${t('cinema.button')}</button>`);
                dom.on(cinemaBtn, 'click', () => {
                    toggleCinema();
                    cinemaBtn.classList.toggle('active', cinemaActive);
                });
                tabbar.append(cinemaBtn);
                // Mount tab UI before moving content to avoid ancestor insertion errors
                if (playerHost && playerHost.parentElement === mainCol) {
                    mainCol.insertBefore(tabbar, playerHost.nextSibling);
                }
                else {
                    mainCol.appendChild(tabbar);
                }
                mainCol.insertBefore(panels, tabbar.nextSibling);
                // Move content into About panel in the desired order:
                // 1) the main details div, 2) description, 3) tags, 4) bottom
                const details = mainCol.querySelector('.page-video__details');
                if (details)
                    aboutBody.append(details);
                // Description (prefer the full wrapper within details). These two are
                // ordering hoists: the node usually already sits inside details/aboutBody
                // and must be re-appended to the panel tail, so adoptIntoPanel (which
                // skips already-inside nodes) doesn't fit — guard only against the
                // closest() walk overshooting to a container of the panel.
                const descEl = (details?.querySelector('.showMore, .page-video__description, .description, .markdown')) ||
                    ([...mainCol.querySelectorAll('.showMore, .page-video__description, .description, .markdown')].find(el => !el.closest('.comments')));
                const descBlock = descEl ? (descEl.closest('.contentBlock') || descEl) : null;
                if (descBlock && !descBlock.contains(aboutBody))
                    aboutBody.append(descBlock);
                // Tags (prefer the wrapper within details)
                const tagsWrap = (details?.querySelector('.page-video__tags')?.closest('.mt-4')) ||
                    mainCol.querySelector('.page-video__tags')?.closest('.mt-4') ||
                    mainCol.querySelector('.page-video__tags');
                if (tagsWrap && !tagsWrap.contains(aboutBody))
                    aboutBody.append(tagsWrap);
                // Bottom actions/stats row
                const bottom = mainCol.querySelector('.page-video__bottom');
                if (bottom)
                    aboutBody.append(bottom);
                const comments = mainCol.querySelector('.comments');
                if (comments)
                    commentsPanel.append(comments);
                // Uploads from sidebar
                const moreFrom = document.querySelector('.page-video__sidebar .moreFromUser');
                if (moreFrom) {
                    const block = moreFrom.closest('.block, .contentBlock, .panel, .card') || moreFrom;
                    adoptIntoPanel(uploadsPanel, block);
                }
                // Recommended
                const moreLike = dom.qs('.itw-recs') || dom.qs('.moreLikeThis') || [...document.querySelectorAll('.text--h3, h2, h3')].find(h => /more like this|recommended|related/i.test(h.textContent || ''))?.closest('.block, .contentBlock, .panel, .card, .section, .moreLikeThis');
                if (moreLike && settings.showRecsTab) {
                    const block = moreLike.closest('.block, .contentBlock, .panel, .card, .section') || moreLike;
                    adoptIntoPanel(recsPanel, block);
                }
                // Initial Likes relocation
                moveLikesIntoPanel();
                // Keep trying as site re-renders/paginates the Likes block (debounced)
                if (__itwLikesObserver) {
                    try {
                        __itwLikesObserver.disconnect();
                    }
                    catch { }
                }
                let likesDebounce = null;
                __itwLikesObserver = new MutationObserver(() => {
                    if (likesDebounce)
                        return;
                    likesDebounce = setTimeout(() => { likesDebounce = null; moveLikesIntoPanel(); }, TIMEOUTS.OBSERVER_DEBOUNCE);
                });
                __itwLikesObserver.observe(document.body, { childList: true, subtree: true });
                // Ensure late-loaded Likes populate into the panel as soon as they appear
                if (settings.showLikesTab) {
                    (async () => {
                        const node = await waitFor(`${SELECTORS.LIKES_LIST}, ${SELECTORS.LIKED_BY}`, { timeout: TIMEOUTS.WAIT_FOR, interval: TIMEOUTS.POLL_INTERVAL });
                        if (!node)
                            return;
                        moveLikesIntoPanel();
                    })();
                }
                // Activate About by default
                const activate = (which) => {
                    const btns = [aboutTab, uploadsTab];
                    if (settings.showRecsTab)
                        btns.push(recsTab);
                    if (settings.showLikesTab)
                        btns.push(likesTab);
                    btns.push(commentsTab);
                    for (const btn of btns)
                        btn.setAttribute('aria-selected', String(btn === which));
                    const allPanels = [aboutPanel, uploadsPanel, recsPanel, likesPanel, commentsPanel];
                    for (const p of allPanels)
                        p.classList.remove('active');
                    if (which === aboutTab)
                        aboutPanel.classList.add('active');
                    else if (which === uploadsTab)
                        uploadsPanel.classList.add('active');
                    else if (which === recsTab)
                        recsPanel.classList.add('active');
                    else if (which === likesTab)
                        likesPanel.classList.add('active');
                    else if (which === commentsTab)
                        commentsPanel.classList.add('active');
                };
                activate(aboutTab);
                dom.on(aboutTab, 'click', () => activate(aboutTab));
                dom.on(uploadsTab, 'click', () => activate(uploadsTab));
                if (settings.showRecsTab)
                    dom.on(recsTab, 'click', () => activate(recsTab));
                if (settings.showLikesTab)
                    dom.on(likesTab, 'click', () => { moveLikesIntoPanel(); activate(likesTab); });
                dom.on(commentsTab, 'click', () => activate(commentsTab));
                document.body.classList.add(CSS_CLASSES.TABS_ACTIVE);
            }
            catch (e) {
                console.warn('[Iwara NeoUI] Tabs setup skipped:', e);
            }
        };
        const teardownVideoTabs = () => {
            try {
                // Clean up observers
                observerManager.disconnect('likes');
                const tabbar = document.querySelector(SELECTORS.TABBAR);
                const panels = document.querySelector(SELECTORS.PANELS);
                // Do NOT return early — always ensure we clear layout class below
                const mainCol = domCache.get(SELECTORS.VIDEO_COL) || domCache.get(SELECTORS.PAGE_VIDEO_CONTENT) || domCache.get(SELECTORS.COL_MD_9) || document.body;
                const sidebar = domCache.get(SELECTORS.SIDEBAR);
                if (panels) {
                    const aboutBody = panels.querySelector('.itw-about-body');
                    if (aboutBody) {
                        [...aboutBody.children].forEach(node => mainCol.appendChild(node));
                    }
                    const uploadsPanel = panels.querySelector('.itw-panel-uploads');
                    if (uploadsPanel && uploadsPanel.children.length) {
                        [...uploadsPanel.children].forEach(node => mainCol.appendChild(node));
                    }
                    const recsPanel = panels.querySelector('.itw-panel-recs');
                    if (recsPanel && recsPanel.children.length) {
                        const block = recsPanel.querySelector('.itw-recs');
                        if (block && sidebar)
                            sidebar.appendChild(block);
                        else if (recsPanel.children.length) {
                            [...recsPanel.children].forEach(node => (sidebar || mainCol).appendChild(node));
                        }
                    }
                    const likesPanel = panels.querySelector('.itw-panel-likes');
                    if (likesPanel && likesPanel.children.length) {
                        const block = likesPanel.querySelector('.itw-liked-by');
                        if (block && sidebar)
                            sidebar.appendChild(block);
                        else if (likesPanel.children.length) {
                            [...likesPanel.children].forEach(node => (sidebar || mainCol).appendChild(node));
                        }
                    }
                    const commentsPanel = panels.querySelector('.itw-panel-comments');
                    if (commentsPanel && commentsPanel.children.length) {
                        [...commentsPanel.children].forEach(node => mainCol.appendChild(node));
                    }
                    panels.remove();
                }
                if (tabbar)
                    tabbar.remove();
                // Always clear the layout class to avoid full-width/hidden-sidebar in vanilla mode
                document.body.classList.remove(CSS_CLASSES.TABS_ACTIVE);
            }
            catch (e) {
                console.warn('[Iwara NeoUI] Tabs teardown skipped:', e);
                // Still ensure layout class is not left behind on error paths
                document.body.classList.remove('itw-tabs-active');
            }
        };
        const applyUiMode = () => {
            if (settings.newUI && isVideoPage()) {
                setupVideoTabs();
            }
            else {
                teardownVideoTabs();
                // Safety: explicitly ensure classes are removed when new UI is disabled
                document.body.classList.remove('itw-tabs-active');
                document.body.classList.remove('itw-theater');
                // Ensure Likes observer is stopped in vanilla mode
                if (__itwLikesObserver) {
                    try {
                        __itwLikesObserver.disconnect();
                    }
                    catch { }
                    __itwLikesObserver = null;
                }
            }
        };
        // ---- Initialize ----
        (async () => {
            await loadSettings();
            // Restore cinema state from persisted theaterMode (only for light cinema)
            if (settings.theaterMode && !settings.cinemaHideNav) {
                cinemaActive = true;
            }
            ensureModal();
            await ensureHeaderButton();
            findLikedBySection();
            findRecsSection();
            applySettings();
            applyUiMode();
            let mainDebounce = null;
            const onDomChange = () => {
                domCache.clear();
                // Always correct stray class if UI not present
                if (!document.querySelector(SELECTORS.TABBAR) && !document.querySelector(SELECTORS.PANELS)) {
                    document.body.classList.remove(CSS_CLASSES.TABS_ACTIVE);
                }
                if (!isVideoPage() && document.querySelector('.itw-tabbar'))
                    teardownVideoTabs();
                if (isVideoPage()) {
                    if (!document.querySelector(SELECTORS.LIKED_BY))
                        findLikedBySection();
                    if (!document.querySelector(SELECTORS.RECS))
                        findRecsSection();
                    if (cinemaActive && !document.body.classList.contains(CSS_CLASSES.THEATER))
                        applyCinema();
                    if (settings.newUI && !document.querySelector(SELECTORS.TABBAR))
                        setupVideoTabs();
                    if (settings.newUI && settings.showLikesTab) {
                        const likesPanel = domCache.get('.itw-panel-likes');
                        const likedNode = domCache.get(SELECTORS.LIKED_BY) || domCache.get(SELECTORS.LIKES_LIST);
                        if (likesPanel && likedNode) {
                            const block = likedNode.classList?.contains('itw-liked-by') ? likedNode : (likedNode.closest('.block, .contentBlock, .block--padding, .card, .panel') || likedNode);
                            adoptIntoPanel(likesPanel, block);
                        }
                    }
                }
                if (!settings.newUI) {
                    if (document.querySelector('.itw-tabbar'))
                        teardownVideoTabs();
                    document.body.classList.remove(CSS_CLASSES.THEATER);
                }
            };
            observerManager.create('main', () => {
                if (mainDebounce)
                    return;
                mainDebounce = setTimeout(() => { mainDebounce = null; onDomChange(); }, TIMEOUTS.OBSERVER_DEBOUNCE);
            });
            observerManager.observe('main');
        })();
    })();
if (typeof window === 'undefined' && typeof module !== 'undefined' && module.exports) {
    module.exports = {
        DEFAULTS,
        MESSAGES,
        createLanguageSelect,
        getBrowserLanguages,
        localeFromLanguageTag,
        normalizeSettings,
        resolveLocale,
        t,
        adoptIntoPanel,
    };
}