Iwara NeoUI

Enhanced UI for Iwara with tabbed layout, theater mode, customizable sections, and improved video page experience.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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,
    };
}