Litnet Reader & FB2 Exporter

Читалка для Litnet в стиле Author.Today с обходом защиты от копирования и встроенным экспортом в FB2.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Litnet Reader & FB2 Exporter
// @description  Читалка для Litnet в стиле Author.Today с обходом защиты от копирования и встроенным экспортом в FB2.
// @match        https://litnet.com/*/reader/*
// @noframes
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      *
// @require      https://update.greasyfork.org/scripts/468831/1575776/HTML2FB2Lib.js
// @run-at       document-idle
// @version      0.5
// @namespace https://greasyfork.org/users/789838
// ==/UserScript==

(function() {
    'use strict';

    if (window.top !== window.self) return;

    const defaultSettings = {
        themeStep: 1,
        fontSize: 22,
        lineHeight: 1.6,
        textWidth: 900,
        fontFamily: 'Roboto',
        textColor: '#333333',
        bgColor: '#ffffff',
        hyphens: false
    };

    let userSettings = JSON.parse(localStorage.getItem('at-reader-settings')) || defaultSettings;
    if (userSettings.themeStep === undefined) userSettings.themeStep = 1;
    if (userSettings.hyphens === undefined) userSettings.hyphens = false;

    const urlPathParts = window.location.pathname.split('?')[0].split('/');
    const bookSlug = urlPathParts[urlPathParts.length - 1];
    const fallbackIdMatch = bookSlug.match(/-b(\d+)$/);

    let bookMeta = {
        bookSlug: bookSlug,
        idBook: fallbackIdMatch ? fallbackIdMatch[1] : null,
        inLibrary: false,
        isLiked: false,
        likeCount: 0,
        tags: [],
        genres: [],
        rawStats: [],
        title: document.title,
        headerTitle: document.title,
        author: 'Автор неизвестен',
        authorLink: '#',
        annotation: '',
        cover: '',
        url: window.location.origin + window.location.pathname.replace(/\/reader\//, '/book/').split('?')[0]
    };

    let tocHtml = '';
    let currentChapterTitle = '';
    let nextChapterUrl = '';
    let prevChapterUrl = '';
    let nextChapterTitle = '';
    let prevChapterTitle = '';
    let availableChapters = [];

    if (typeof FB2Image !== 'undefined') {
        FB2Image.prototype._load = async function(url, params) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url.toString(),
                    responseType: "blob",
                    onload: (r) => {
                        if (r.status >= 200 && r.status < 300) resolve(r.response);
                        else reject(new Error("HTTP " + r.status));
                    },
                    onerror: reject
                });
            });
        };
    }

    function getTodayString() {
        return new Date().toLocaleDateString('ru-RU');
    }

    function cleanOldCaches() {
        const today = getTodayString();
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            if (key && key.startsWith('at-chap-')) {
                try {
                    const data = JSON.parse(localStorage.getItem(key));
                    if (data.date !== today) {
                        localStorage.removeItem(key);
                    }
                } catch(e) {}
            }
        }
    }

    function loadChapterCache(chapterId, currentLastEdit) {
        cleanOldCaches();
        try {
            const data = JSON.parse(localStorage.getItem(`at-chap-${chapterId}`));
            if (data && data.date === getTodayString()) {
                if (currentLastEdit && data.lastEdit && currentLastEdit !== data.lastEdit) {
                    console.log("Litnet Reader: Обнаружено обновление главы, сброс кэша.");
                    localStorage.removeItem(`at-chap-${chapterId}`);
                    return null;
                }
                if (data.totalPages && data.totalPages > 1 && window.atTotalChapterPages === 1) {
                    window.atTotalChapterPages = data.totalPages;
                }
                return data.pages;
            }
        } catch(e) {}
        return null;
    }

    function saveChapterCache(chapterId, pagesObj) {
        try {
            localStorage.setItem(`at-chap-${chapterId}`, JSON.stringify({
                date: getTodayString(),
                lastEdit: window.atCurrentLastEdit || '',
                totalPages: window.atTotalChapterPages || 1,
                pages: pagesObj
            }));
        } catch(e) {
            console.warn('Litnet Reader: LocalStorage переполнен, очистка старых данных');
        }
    }

    function fixNativeModals() {
        const modals = ['complaint-modal', 'reward-author-modal-dialog', 'modal_user_no_book', 'modal_user_no_lib', 'modal_no_user'];
        modals.forEach(id => {
            const el = document.getElementById(id);
            if (el && el.parentElement !== document.body) {
                document.body.appendChild(el);
            }
        });

        GM_addStyle(`
            .modal-backdrop { z-index: 2147483640 !important; visibility: visible !important; pointer-events: auto !important; display: block !important; }
            .modal { z-index: 2147483645 !important; visibility: visible !important; pointer-events: auto !important; }
            .bootbox { z-index: 2147483646 !important; visibility: visible !important; pointer-events: auto !important; }
        `);
    }

    function killAntiCopy() {
        const stopProp = (e) => {
            if (e.target && e.target.closest && (e.target.closest('.modal') || e.target.closest('.modal-backdrop') || e.target.closest('.bootbox') || e.target.closest('#reward-author-modal-dialog') || e.target.closest('#complaint-modal'))) {
                return;
            }
            e.stopPropagation();
        };
        ['selectstart', 'mousedown', 'mouseup', 'copy', 'contextmenu', 'dragstart'].forEach(evt => {
            window.addEventListener(evt, stopProp, true);
            document.addEventListener(evt, stopProp, true);
        });

        GM_addStyle(`
            * {
                -webkit-user-select: text !important;
                -moz-user-select: text !important;
                -ms-user-select: text !important;
                user-select: text !important;
            }
        `);
    }

    function cleanChapterHtml(rawHtml, expectedTitle) {
        if (!rawHtml) return '';
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = rawHtml;

        tempDiv.querySelectorAll('.reader-pagination, .pagination, .js-pagination, .clearfix, script, style, .reader-btn-w, .audio_btn_prompt').forEach(el => el.remove());

        let contentEl = tempDiv.querySelector('.jsReaderText, [data-test-id="reader-text"]');
        let contentHtml = contentEl ? contentEl.innerHTML : tempDiv.innerHTML;

        const cleanDiv = document.createElement('div');
        cleanDiv.innerHTML = contentHtml;

        let headings = cleanDiv.querySelectorAll('h1, h2, h3');
        headings.forEach(heading => {
            if (expectedTitle && heading.textContent.trim().toLowerCase() === expectedTitle.toLowerCase()) {
                heading.remove();
            }
        });

        return cleanDiv.innerHTML.replace(/onmousedown="[^"]*"/gi, '').trim();
    }

    function extractData() {
        let foundTotalPages = false;
        window.atTotalChapterPages = 1;
        let serverPage = 1;

        const idBookEl = document.querySelector('.book-id');
        if (idBookEl) {
            bookMeta.idBook = idBookEl.textContent.trim();
        }

        const rightBlockGenres = document.querySelectorAll('.jsAddTargetBlank a[href*="/top/"]');
        if (rightBlockGenres.length > 0) {
            bookMeta.genres = Array.from(rightBlockGenres).map(a => a.textContent.trim());
        }

        const likeBtn = document.querySelector('.likes-btn, .not-likes-btn');
        if (likeBtn) {
            bookMeta.isLiked = likeBtn.classList.contains('likes-btn');
            const countEl = likeBtn.querySelector('.count');
            if (countEl) bookMeta.likeCount = parseInt(countEl.textContent.trim(), 10) || 0;
        }

        const libIn = document.querySelector('.to_lib.in-lib');
        bookMeta.inLibrary = !!libIn;

        window.atCurrentLastEdit = '';
        const editEl = document.querySelector('.last-edit');
        if (editEl) {
            window.atCurrentLastEdit = editEl.textContent.trim();
        } else {
            const htmlMatch = document.body.innerHTML.match(/Отредактировано:\s*[\d\.]+/i);
            if (htmlMatch) window.atCurrentLastEdit = htmlMatch[0];
        }

        const urlParams = new URLSearchParams(window.location.search);
        let urlPageParam = parseInt(urlParams.get('p'), 10);
        if (isNaN(urlPageParam)) urlPageParam = null;
        window.atCurrentPage = urlPageParam || 1;
        window.atCurrentChapterId = urlParams.get('c');
        window.atBaseUrl = window.location.pathname.split('?')[0];

        let chapterHtml = '';

        try {
            const ngStateScript = document.getElementById('ng-state');
            if (ngStateScript) {
                const stateStr = ngStateScript.textContent;

                const tpMatch = stateStr.match(/"totalPages"\s*:\s*(\d+)/);
                if (tpMatch) {
                    window.atTotalChapterPages = parseInt(tpMatch[1], 10);
                    foundTotalPages = true;
                }

                const stateData = JSON.parse(stateStr);
                for (const key in stateData) {
                    const obj = stateData[key]?.body || stateData[key]?.data || stateData[key];

                    if (obj && obj.idBook) bookMeta.idBook = obj.idBook;
                    if (obj && obj.bookId) bookMeta.idBook = obj.bookId;
                    if (obj && obj.bookInLib !== undefined) bookMeta.inLibrary = obj.bookInLib;

                    if (obj && obj.totalPages !== undefined) {
                        window.atTotalChapterPages = parseInt(obj.totalPages, 10);
                        foundTotalPages = true;

                        if (obj.data && typeof obj.data === 'string') {
                            chapterHtml = cleanChapterHtml(obj.data, obj.chapterTitle);
                        }
                        if (obj.page !== undefined) serverPage = parseInt(obj.page, 10);
                        if (obj.chapterTitle) currentChapterTitle = obj.chapterTitle;
                    }

                    if (obj && obj.author) {
                        if (obj.author.name) bookMeta.author = obj.author.name;
                    }
                    if (obj && obj.title) bookMeta.title = obj.title;

                    if (obj && obj.chapters && Array.isArray(obj.chapters) && availableChapters.length === 0) {
                        obj.chapters.forEach((ch, idx) => {
                            availableChapters.push({
                                idx: idx + 1,
                                id: ch.id.toString(),
                                title: ch.title,
                                url: window.atBaseUrl + '?c=' + ch.id
                            });
                        });
                    }
                }
            }
        } catch (e) {}

        if (!chapterHtml) {
            const textContainer = document.querySelector('.jsReaderText, [data-test-id="reader-text"]');
            if (textContainer) {
                chapterHtml = cleanChapterHtml(textContainer.parentElement.innerHTML, currentChapterTitle);
            }
        }

        if (!foundTotalPages) {
            let maxPage = window.atTotalChapterPages || 1;
            document.querySelectorAll('.pagination a, .js-pagination a, a[href*="&p="], a[href*="?p="]').forEach(a => {
                const match = a.href.match(/[?&]p=(\d+)/);
                if (match) maxPage = Math.max(maxPage, parseInt(match[1], 10));
            });
            document.querySelectorAll('[onclick*="Reader.goTo"]').forEach(el => {
                const match = el.getAttribute('onclick').match(/Reader\.goTo\((\d+)\)/);
                if (match) maxPage = Math.max(maxPage, parseInt(match[1], 10));
            });
            window.atTotalChapterPages = maxPage;
        }

        if (!chapterHtml || chapterHtml.length < 50) return false;

        const authorDomLinks = document.querySelectorAll('.book-page-author__about, .sa-name, a[href*="-u"]');
        for (let a of authorDomLinks) {
            if (a.href && !a.href.includes('search')) {
                bookMeta.authorLink = a.href;
                bookMeta.author = a.textContent.replace(/Автор\s*•.*$/i, '').replace(/Автор\s*книг.*$/i, '').trim();
                break;
            }
        }

        if (!bookMeta.authorLink || bookMeta.authorLink === '#') {
            const langPrefix = window.location.pathname.split('/')[1] || 'ru';
            bookMeta.authorLink = `/${langPrefix}/search?q=${encodeURIComponent(bookMeta.author)}`;
        }

        let rawTitle = bookMeta.title || document.title;
        rawTitle = rawTitle.replace(/^Книга\s+/i, '').split(/,(?:\s*глава)?/i)[0].split(/\s*—/)[0].split(/\|/)[0].trim();

        const h1BookTitle = document.querySelector('h1.book-heading a, h1.book-heading');
        if (h1BookTitle) {
            rawTitle = h1BookTitle.textContent.trim();
        }
        bookMeta.title = rawTitle;
        bookMeta.headerTitle = rawTitle;

        window.atRequestedPage = urlPageParam || serverPage || 1;
        window.atCurrentPage = window.atRequestedPage;

        window.atChapterPagesCache = loadChapterCache(window.atCurrentChapterId, window.atCurrentLastEdit) || {};

        if (serverPage === window.atRequestedPage && !window.atChapterPagesCache[serverPage]) {
            window.atChapterPagesCache[serverPage] = chapterHtml;
        }

        if (availableChapters.length === 0) {
            const chapterSelect = document.querySelector('select.js-chapter-change');
            if (chapterSelect) {
                if (!window.atCurrentChapterId) window.atCurrentChapterId = chapterSelect.value;
                const options = Array.from(chapterSelect.options);
                options.forEach((opt, index) => {
                    availableChapters.push({
                        idx: index + 1,
                        id: opt.value,
                        title: opt.text,
                        url: window.atBaseUrl + '?c=' + opt.value
                    });
                });
            }
        }

        availableChapters.forEach((ch, index) => {
            const isActive = ch.id === window.atCurrentChapterId;
            tocHtml += `<a href="${ch.url}" class="at-toc-item ${isActive ? 'active' : ''}">${ch.title}</a>`;
            if (isActive) {
                currentChapterTitle = currentChapterTitle || ch.title;
                if (index > 0) {
                    prevChapterUrl = availableChapters[index - 1].url;
                    prevChapterTitle = availableChapters[index - 1].title;
                }
                if (index < availableChapters.length - 1) {
                    nextChapterUrl = availableChapters[index + 1].url;
                    nextChapterTitle = availableChapters[index + 1].title;
                }
            }
        });

        return true;
    }

    async function fetchBookExtraInfo() {
        const cacheKey = `at-book-meta-${bookMeta.bookSlug}`;
        const cachedDataStr = localStorage.getItem(cacheKey);

        if (cachedDataStr) {
            try {
                const cachedData = JSON.parse(cachedDataStr);
                let cacheValid = true;
                if (cachedData.genres && cachedData.genres.some(g => /\d/.test(g) || g.includes('из'))) {
                    cacheValid = false;
                }
                if (cacheValid && cachedData.cover && cachedData.author && cachedData.author !== 'Автор неизвестен') {
                    Object.assign(bookMeta, cachedData);
                    if (!bookMeta.genres) bookMeta.genres = [];
                    updateSidebarUI();
                    return;
                }
            } catch (e) {}
        }

        try {
            const response = await fetch(bookMeta.url);
            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, "text/html");

            const tagEls = doc.querySelectorAll('.view-book-page-header-desktop__tags-tag span, .lib-tag span');
            if (tagEls.length > 0) bookMeta.tags = Array.from(tagEls).map(el => el.textContent.trim()).filter((v, i, a) => a.indexOf(v) === i);

            if (!bookMeta.genres || bookMeta.genres.length === 0) {
                let genreEls = doc.querySelectorAll('.book-info a[href*="/top/"], .view-book-page-header-desktop__details a[href*="/top/"], .jsAddTargetBlank a[href*="/top/"]');
                if (genreEls.length === 0) {
                    genreEls = doc.querySelectorAll('a[href*="/top/"]');
                }

                let parsedGenres = [];
                Array.from(genreEls).forEach(el => {
                    if (el.closest('.dropdown, .menu, nav, header, footer, .lib-dropdown__content')) return;
                    let text = el.textContent.replace(/^\s*\d+\s*(?:[-–—]\s*)?/, '').replace(/\s*\(из.*?\)\s*/i, '').trim();
                    if (text) parsedGenres.push(text);
                });
                if (parsedGenres.length > 0) {
                    bookMeta.genres = Array.from(new Set(parsedGenres)).slice(0, 5);
                }
            }

            const descEl = doc.querySelector('.lib-description__description');
            if (descEl) bookMeta.annotation = descEl.innerHTML.trim();

            const statEls = doc.querySelectorAll('.view-book-page-header-desktop__labels .lib-label');
            if (statEls.length > 0) {
                bookMeta.rawStats = Array.from(statEls).map(el => {
                    const iconEl = el.querySelector('img.lib-icon__img');
                    let icon = '';
                    if (iconEl && iconEl.src) {
                        if (iconEl.src.includes('star')) icon = '⭐';
                        else if (iconEl.src.includes('eye')) icon = '👁️';
                        else if (iconEl.src.includes('like')) icon = '❤️';
                    }
                    return icon ? `${icon} ${el.querySelector('.lib-label__text')?.textContent?.trim() || ''}` : '';
                }).filter(Boolean);
            }

            const authorEl = doc.querySelector('.book-page-author__about, .sa-name, a.view-book-page-header-desktop__author, a.lib-author__name, a[href*="-u"]');
            if (authorEl && authorEl.href && !authorEl.href.includes('search')) {
                bookMeta.authorLink = authorEl.href;
                const nameEl = authorEl.querySelector('.book-page-author__about-info-name');
                if (nameEl) {
                    bookMeta.author = nameEl.textContent.trim();
                } else {
                    bookMeta.author = authorEl.textContent.replace(/Автор\s*•.*$/i, '').replace(/Автор\s*книг.*$/i, '').trim();
                }
            }

            const ogImage = doc.querySelector('meta[property="og:image"]');
            if (ogImage && ogImage.content) bookMeta.cover = ogImage.content;

            localStorage.setItem(cacheKey, JSON.stringify({
                tags: bookMeta.tags, genres: bookMeta.genres, annotation: bookMeta.annotation, rawStats: bookMeta.rawStats,
                author: bookMeta.author, authorLink: bookMeta.authorLink, cover: bookMeta.cover
            }));
            updateSidebarUI();
        } catch (e) {}
    }

    async function fetchChapterPage(chapterId, pageNum, expectedTitle) {
        try {
            const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
            const bodyParams = new URLSearchParams();
            bodyParams.append('chapterId', chapterId);
            bodyParams.append('page', pageNum);
            if (csrfToken) bodyParams.append('_csrf', csrfToken);

            const apiRes = await fetch('/reader/get-page', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-Requested-With': 'XMLHttpRequest'
                },
                body: bodyParams.toString()
            });

            if (apiRes.ok) {
                const json = await apiRes.json();
                if (json && json.status === 1 && json.data) {
                    if (json.idBook) bookMeta.idBook = json.idBook;
                    if (json.bookInLib !== undefined) bookMeta.inLibrary = json.bookInLib;

                    let html = cleanChapterHtml(json.data, expectedTitle);
                    if (html.includes('<p>')) {
                        return { html: html, totalPages: json.totalPages || 1 };
                    }
                }
            }
        } catch (e) {
            console.warn("API fetch error, using fallback", e);
        }

        const url = window.atBaseUrl + (window.atBaseUrl.includes('?') ? '&' : '?') + 'c=' + chapterId + '&p=' + pageNum;

        return new Promise((resolve, reject) => {
            const iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            document.body.appendChild(iframe);

            let checkInterval;
            let timeout = setTimeout(() => {
                clearInterval(checkInterval);
                iframe.remove();
                reject(new Error("Таймаут загрузки страницы"));
            }, 10000);

            iframe.src = url;

            checkInterval = setInterval(() => {
                try {
                    const doc = iframe.contentDocument || iframe.contentWindow.document;
                    if (doc) {
                        const textContainer = doc.querySelector('.jsReaderText, [data-test-id="reader-text"]');
                        if (textContainer && textContainer.querySelectorAll('p').length > 0) {
                            clearInterval(checkInterval);
                            clearTimeout(timeout);

                            let maxPage = 1;
                            doc.querySelectorAll('.pagination a, .js-pagination a, a[href*="&p="], a[href*="?p="]').forEach(a => {
                                const m = a.href.match(/[?&]p=(\d+)/);
                                if (m) maxPage = Math.max(maxPage, parseInt(m[1], 10));
                            });
                            doc.querySelectorAll('[onclick*="Reader.goTo"]').forEach(el => {
                                const m = el.getAttribute('onclick').match(/Reader\.goTo\((\d+)\)/);
                                if (m) maxPage = Math.max(maxPage, parseInt(m[1], 10));
                            });

                            let html = cleanChapterHtml(textContainer.parentElement.innerHTML, expectedTitle);
                            iframe.remove();
                            resolve({ html: html, totalPages: maxPage });
                        }
                    }
                } catch (e) {}
            }, 100);
        });
    }

    function injectStyles() {
        GM_addStyle(`
            @import url('https://fonts.googleapis.com/css2?family=Alegreya:wght@400;500;700&family=Roboto:wght@400;500;700&family=Comfortaa:wght@400;700&display=swap');

            :root {
                --at-bg-color: ${userSettings.bgColor};
                --at-text-color: ${userSettings.textColor};
                --at-font-size: ${userSettings.fontSize}px;
                --at-line-height: ${userSettings.lineHeight};
                --at-text-width: ${userSettings.textWidth === '100%' ? '100%' : userSettings.textWidth + 'px'};
                --at-font-family: '${userSettings.fontFamily}', sans-serif;
                --at-hyphens: ${userSettings.hyphens ? 'auto' : 'none'};
            }

            .ln_topbar, .wrap, footer, .main_footer, .cookies-w, .pwa-banner, .pwa-banner-wrapper {
                position: fixed !important;
                top: -9999px !important;
                left: -9999px !important;
                visibility: hidden !important;
                pointer-events: none !important;
                z-index: -1 !important;
            }

            html, body { background-color: var(--at-bg-color) !important; margin: 0 !important; padding: 0 !important; width: 100% !important; min-height: 100vh !important; }

            #at-app { display: flex; flex-direction: column; min-height: 100vh; font-family: 'Roboto', sans-serif; background-color: var(--at-bg-color); transition: background-color 0.2s ease; }

            #at-header { position: fixed; top: 0; left: 0; right: 0; min-height: 50px; background: #ffffff; border-bottom: 1px solid #e9ecef; display: flex; align-items: center; justify-content: space-between; padding: 5px 20px; z-index: 1030; box-shadow: 0 1px 3px rgba(0,0,0,0.05); transition: transform 0.3s ease !important; }
            #at-header.hidden { transform: translateY(-100%) !important; }
            .at-header-group { display: flex; gap: 10px; align-items: center; }
            .at-btn { background: #f8f9fa; border: 1px solid #ced4da; border-radius: 4px; padding: 6px 12px; cursor: pointer; color: #495057; font-size: 14px; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px; transition: all 0.2s; font-weight: 500; outline: none; white-space: nowrap; }
            .at-btn:hover { background: #e2e6ea; color: #212529; text-decoration: none; }
            .at-btn.btn-action { background: #e0f2fe; color: #0284c7; border-color: #bae6fd; }
            .at-btn.btn-action:hover { background: #bae6fd; }

            .at-btn-icon { padding: 6px 8px; font-size: 0; line-height: 0; }
            .at-btn-icon svg { width: 18px; height: 18px; }
            .at-btn-icon.active { color: #e11d48; border-color: #f43f5e; background: #ffe4e6; }
            .at-btn-icon.lib-active { color: #0284c7; border-color: #bae6fd; background: #e0f2fe; }
            .at-divider { width: 1px; height: 24px; background: #ced4da; margin: 0 5px; }

            .at-header-title { font-weight: 700; color: #333; font-size: 15px; text-align: center; max-width: 100%; line-height:1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}

            #at-sidebar-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.5); z-index: 1034; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; }
            #at-sidebar-overlay.visible { opacity: 1; pointer-events: auto; }
            #at-sidebar { position: fixed; top: 0; left: -340px; bottom: 0; width: 340px; background: #fff; box-shadow: 2px 0 12px rgba(0,0,0,0.15); transition: left 0.3s ease; z-index: 1035; overflow-y: auto; }
            #at-sidebar.open { left: 0; }
            .at-sidebar-close { position: absolute; top: 15px; right: 15px; background: none; border: none; font-size: 28px; color: #999; cursor: pointer; line-height: 1; padding: 0; outline: none; transition: color 0.2s; }
            .at-sidebar-close:hover { color: #333; }
            .at-sidebar-inner { padding: 40px 20px 20px 20px; display: flex; flex-direction: column; gap: 15px; }

            .at-meta-cover { width: 100%; max-width: 200px; margin: 0 auto; border-radius: 6px; display: block; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
            .at-meta-title { font-size: 18px; font-weight: 700; color: #333; text-align: center; margin: 0; line-height: 1.3;}

            .at-meta-author { font-size: 14px; font-weight: bold; color: #4582af; text-align: center; display: block; text-decoration: none; transition: color 0.2s; }
            .at-meta-author:hover { color: #0284c7; text-decoration: underline; }

            .at-meta-stats { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; font-size: 12px; color: #555; background: #f8f9fa; padding: 10px; border-radius: 6px; }
            .at-meta-stats span { background: #e9ecef; padding: 3px 8px; border-radius: 12px; font-weight: 500; }
            .at-meta-tags { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; }
            .at-meta-tag { background: #e0f2fe; color: #0284c7; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: bold;}
            .at-meta-genres { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; color: #d9534f; font-size: 12px; font-weight: bold; }
            .at-meta-anno { font-size: 13px; color: #555; line-height: 1.5; background: #fdfdfd; padding: 10px; border: 1px dashed #eee; border-radius: 4px; max-height: 250px; overflow-y: auto; text-align: justify; }

            .at-toc-title { font-size: 15px; font-weight: 700; color: #555; text-transform: uppercase; border-bottom: 2px solid #f0f0f0; padding-bottom: 5px; margin-top: 10px; }
            .at-toc-list { display: flex; flex-direction: column; gap: 2px; }
            .at-toc-item { display: block; padding: 8px 12px; color: #333; text-decoration: none; border-radius: 4px; font-size: 14px; transition: background 0.2s;}
            .at-toc-item.active { background: #e9ecef; font-weight: 700; border-left: 3px solid #4582af; padding-left: 9px; }

            #at-settings-panel { position: fixed; top: 50px; right: 20px; width: 320px; background: #fff; border: 1px solid #ced4da; border-top: none; border-radius: 0 0 6px 6px; box-shadow: 0 8px 16px rgba(0,0,0,0.1); padding: 20px; display: none; flex-direction: column; gap: 15px; z-index: 1035; }
            #at-settings-panel.open { display: flex; }
            .at-setting-row { display: flex; flex-direction: column; gap: 5px; }
            .at-setting-row label { font-size: 13px; font-weight: 600; color: #555; display: flex; justify-content: space-between; align-items: center;}
            .at-slider { width: 100%; cursor: pointer; accent-color: #4582af; }
            .at-select, .at-color { width: 100%; padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; outline: none; }
            .at-checkbox { width: 18px; height: 18px; cursor: pointer; accent-color: #4582af; }

            #at-main { flex: 1; margin-top: 50px; padding: 60px 20px 80px 20px; display: flex; flex-direction: column; align-items: center; }
            #at-text-content { width: 100%; max-width: var(--at-text-width); box-sizing: border-box; font-family: var(--at-font-family); font-size: var(--at-font-size); line-height: var(--at-line-height); color: var(--at-text-color); text-align: justify; -webkit-hyphens: var(--at-hyphens); hyphens: var(--at-hyphens); transition: color 0.2s ease; }
            .at-chapter-heading { text-align: center; font-weight: 400; font-size: 1.6em; margin-top: 0; margin-bottom: 1.5em; color: var(--at-text-color); }
            #at-text-content p { text-indent: 1.5em; margin-top: 0; margin-bottom: 0.5em; }

            .at-page-block { position: relative; display: block; }
            .at-page-separator { display: none; }

            @keyframes at-spin { 100% { transform: rotate(360deg); } }
            .at-spinner { width: 40px; height: 40px; margin: 0 auto 15px auto; border: 4px solid #f3f3f3; border-top: 4px solid #4582af; border-radius: 50%; animation: at-spin 1s linear infinite; }
            .at-loader-container { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 100px 20px; color: #777; font-size: 16px; font-weight: 500; font-family: 'Roboto', sans-serif;}

            .at-pagination { width: 100%; max-width: var(--at-text-width); display: flex; justify-content: space-between; margin-top: 60px; padding-top: 20px; border-top: 1px solid rgba(128,128,128,0.3); }
            .at-pagination a { color: #4582af; padding: 8px 16px; border: 1px solid #4582af; border-radius: 4px; text-decoration: none; font-family: 'Roboto', sans-serif;}

            .ate-dlg-overlay { position:fixed; top:0; left:0; bottom:0; right:0; background-color:rgba(0,0,0,0.5); z-index:1055; display:flex; align-items:center; justify-content:center; opacity:0; pointer-events:none; transition: opacity 0.2s;}
            .ate-dlg-overlay.open { opacity:1; pointer-events:auto; }
            .ate-dialog { background:#fff; width: 90%; max-width: 550px; height: 80vh; max-height: 600px; border-radius: 6px; display:flex; flex-direction:column; box-shadow:0 5px 15px rgba(0,0,0,0.5); overflow:hidden; font-family: 'Roboto', sans-serif; }
            .ate-title { flex: 0 0 auto; display:flex; align-items:center; justify-content:space-between; padding: 12px 15px; background: #edf1f2; border-bottom: 1px solid #e5e5e5; color: #66757f; font-weight:bold; font-size: 15px;}
            .ate-close-btn { cursor:pointer; background:none; border:none; font-size:24px; line-height:1; color:#000; opacity:0.4; padding:0; outline:none; transition: opacity 0.2s; }
            .ate-close-btn:hover { opacity:0.9; }
            .ate-form-body { display:flex; flex-direction:column; flex:1; overflow:hidden; padding: 15px; }
            .ate-page { display:flex; flex-direction:column; flex:1; overflow:hidden; gap: 15px; }
            .ate-page.hidden { display: none !important; }
            .ate-fieldset { border: 1px solid #bbb; border-radius: 6px; padding: 10px; margin: 0; display:flex; flex-direction:column; flex:1; overflow:hidden;}
            .ate-legend { font-size: 13px; font-weight:bold; color: #333; margin: 0; padding: 0 5px; width:auto; border:none; line-height:1;}
            .ate-note { font-size: 12px; color: #66757f; margin-bottom: 10px; line-height:1.4; }
            .ate-chapter-list { flex:1; overflow-y:auto; border: 1px solid #eee; padding: 5px; border-radius:4px; display:flex; flex-direction:column; gap:4px; }
            .ate-toolbar { display:flex; align-items:center; justify-content:space-between; padding-top:10px; border-top:1px solid #bbb; margin-top:10px; font-size: 13px; color:#333; }
            .ate-group-select { background:none; border:1px solid #ccc; border-radius:4px; padding:4px 8px; cursor:pointer; font-size:12px; color:#555; }
            .ate-settings-list { display:flex; flex-direction:column; gap: 8px; margin-top: 10px; }
            .ate-checkbox-wrap { display:flex; align-items:center; font-size: 13px; color: #333; cursor:pointer; gap:8px;}
            .ate-checkbox-wrap input { width: 16px; height: 16px; cursor:pointer; accent-color: #4582af; }
            .ate-log { flex:1; overflow-y:auto; background:#1e1e1e; color:#ccc; font-family:monospace; font-size:13px; padding:10px; border-radius:4px; line-height:1.5; white-space:pre-wrap; border: 1px solid #333; }
            .ate-buttons { display:flex; gap: 10px; justify-content:flex-end; border-top: 1px solid #eee; padding-top: 15px; margin-top: auto; }
            .ate-btn-main { background: #5cb85c; color: #fff; border: 1px solid #4cae4c; border-radius:4px; padding: 8px 16px; cursor:pointer; font-weight:500; font-size:14px; outline:none; transition:0.2s;}
            .ate-btn-main:hover { background: #449d44; }
            .ate-btn-main:disabled { background: #a5d8a5; cursor:not-allowed; }
            .ate-btn-alt { background: #fff; color: #333; border: 1px solid #ccc; border-radius:4px; padding: 8px 16px; cursor:pointer; font-size:14px; outline:none; transition:0.2s;}
        `);
    }

    function updateSidebarUI() {
        const metaContainer = document.getElementById('at-meta-container');
        if (!metaContainer) return;

        let statsHtml = '';
        if (bookMeta.rawStats && bookMeta.rawStats.length > 0) {
            statsHtml = `<div class="at-meta-stats">${bookMeta.rawStats.map(s => `<span>${s}</span>`).join('')}</div>`;
        }

        let genresHtml = '';
        if (bookMeta.genres && bookMeta.genres.length > 0) {
            genresHtml = `<div class="at-meta-genres">` + bookMeta.genres.join(', ') + `</div>`;
        }

        let tagsHtml = '';
        if (bookMeta.tags && bookMeta.tags.length > 0) {
            tagsHtml = `<div class="at-meta-tags">` + bookMeta.tags.map(t => `<span class="at-meta-tag">${t}</span>`).join('') + `</div>`;
        }

        metaContainer.innerHTML = `
            ${bookMeta.cover ? `<img src="${bookMeta.cover}" class="at-meta-cover">` : ''}
            <h2 class="at-meta-title">${bookMeta.title}</h2>
            <a href="${bookMeta.authorLink}" target="_self" class="at-meta-author" id="at-author-link">${bookMeta.author}</a>
            ${genresHtml}
            ${statsHtml}
            ${tagsHtml}
            ${bookMeta.annotation ? `<div class="at-meta-anno">${bookMeta.annotation}</div>` : ''}
        `;

        const authorLinkEl = document.getElementById('at-author-link');
        if (authorLinkEl) {
            authorLinkEl.onclick = (e) => {
                e.stopPropagation();
                window.location.href = bookMeta.authorLink;
            };
        }
    }

    function renderInterface() {
        document.body.style.background = 'none';

        let themeName = 'Белая';
        switch(userSettings.themeStep) {
            case 1: themeName = 'Белая'; break;
            case 2: themeName = 'Сепия'; break;
            case 3: themeName = 'Серая'; break;
            case 4: themeName = 'Темная'; break;
            case 5: themeName = 'Черная'; break;
        }

        const app = document.createElement('div');
        app.id = 'at-app';

        app.innerHTML = `
            <header id="at-header">
                <div class="at-header-group">
                    <button type="button" id="at-btn-toc" class="at-btn">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line></svg>
                        Оглавление
                    </button>
                    <a href="${bookMeta.url}" class="at-btn">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path></svg>
                        К книге
                    </a>
                    <button type="button" id="at-btn-download" class="at-btn btn-action">Скачать FB2</button>
                </div>
                <div class="at-header-group" style="flex: 1; justify-content: center; overflow: hidden; padding: 0 15px;">
                    <span class="at-header-title" title="${bookMeta.headerTitle}">${bookMeta.headerTitle}</span>
                </div>
                <div class="at-header-group">
                    <button type="button" id="at-btn-like" class="at-btn at-btn-icon ${bookMeta.isLiked ? 'active' : ''}" title="Мне нравится">
                        <svg width="18" height="18" viewBox="0 0 24 24" fill="${bookMeta.isLiked ? 'currentColor' : 'none'}" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
                        <span id="at-like-count" style="margin-left:5px; font-size:13px; font-weight:bold;">${bookMeta.likeCount > 0 ? bookMeta.likeCount : ''}</span>
                    </button>
                    <button type="button" id="at-btn-library" class="at-btn at-btn-icon" title="Добавить в библиотеку"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg></button>
                    <button type="button" id="at-btn-reward" class="at-btn at-btn-icon" title="Наградить автора"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path></svg></button>
                    <button type="button" id="at-btn-complain" class="at-btn at-btn-icon" title="Пожаловаться"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line></svg></button>
                    <div class="at-divider"></div>
                    <button type="button" id="at-btn-refresh" class="at-btn" title="Очистить кэш и обновить главу">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.59-9.21L21.5 8"></path></svg>
                    </button>
                    <button type="button" id="at-btn-settings" class="at-btn">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
                    </button>
                </div>
            </header>

            <div id="at-sidebar-overlay"></div>

            <aside id="at-sidebar">
                <button type="button" id="at-btn-close-sidebar" class="at-sidebar-close" title="Закрыть">×</button>
                <div class="at-sidebar-inner">
                    <div id="at-meta-container">
                        <h2 class="at-meta-title">${bookMeta.title}</h2>
                        <span style="color:#777; font-size:12px; text-align:center; display:block;">Загрузка информации...</span>
                    </div>
                        <div class="at-toc-title">Оглавление</div>
                        <div class="at-toc-list">${tocHtml}</div>
                    </div>
                </aside>

                <div id="at-settings-panel">
                    <div class="at-setting-row">
                        <label>Тема (Фон) <span id="at-val-theme">${themeName}</span></label>
                        <input type="range" class="at-slider" id="at-inp-theme" min="1" max="5" step="1" value="${userSettings.themeStep}">
                    </div>
                    <div class="at-setting-row">
                        <label>Размер шрифта <span id="at-val-fs">${userSettings.fontSize}px</span></label>
                        <input type="range" class="at-slider" id="at-inp-fs" min="14" max="36" value="${userSettings.fontSize}">
                    </div>
                    <div class="at-setting-row">
                        <label>Ширина текста <span id="at-val-tw">${userSettings.textWidth === '100%' ? 'Макс' : userSettings.textWidth + 'px'}</span></label>
                        <input type="range" class="at-slider" id="at-inp-tw" min="500" max="1400" step="50" value="${userSettings.textWidth === '100%' ? 1400 : userSettings.textWidth}">
                    </div>
                    <div class="at-setting-row">
                        <label>Высота строк <span id="at-val-lh">${userSettings.lineHeight}</span></label>
                        <input type="range" class="at-slider" id="at-inp-lh" min="1.0" max="2.5" step="0.1" value="${userSettings.lineHeight}">
                    </div>
                    <div class="at-setting-row">
                        <label>Перенос слов
                            <input type="checkbox" class="at-checkbox" id="at-inp-hy" ${userSettings.hyphens ? 'checked' : ''}>
                        </label>
                    </div>
                    <div class="at-setting-row">
                        <label>Шрифт</label>
                        <select class="at-select" id="at-inp-ff">
                            <option value="Roboto" ${userSettings.fontFamily === 'Roboto' ? 'selected' : ''}>Roboto</option>
                            <option value="Alegreya" ${userSettings.fontFamily === 'Alegreya' ? 'selected' : ''}>Alegreya</option>
                            <option value="Comfortaa" ${userSettings.fontFamily === 'Comfortaa' ? 'selected' : ''}>Comfortaa</option>
                            <option value="Arial" ${userSettings.fontFamily === 'Arial' ? 'selected' : ''}>Arial</option>
                            <option value="Georgia" ${userSettings.fontFamily === 'Georgia' ? 'selected' : ''}>Georgia</option>
                        </select>
                    </div>
                    <div class="at-flex-row" style="display:flex; gap:10px;">
                        <div class="at-setting-row" style="flex:1;">
                            <label>Текст</label>
                            <input type="color" class="at-color" id="at-inp-tc" value="${userSettings.textColor}">
                        </div>
                        <div class="at-setting-row" style="flex:1;">
                            <label>Фон</label>
                            <input type="color" class="at-color" id="at-inp-bc" value="${userSettings.bgColor}">
                        </div>
                    </div>
                </div>

                <main id="at-main">
                    <div id="at-text-content" lang="ru"></div>

                    <div class="at-pagination">
                        ${prevChapterUrl ? `<a href="${prevChapterUrl}">← <span>${prevChapterTitle}</span></a>` : '<div></div>'}
                        ${nextChapterUrl ? `<a href="${nextChapterUrl}"><span>${nextChapterTitle}</span> →</a>` : '<div></div>'}
                    </div>
                </main>

                <div id="ate-overlay" class="ate-dlg-overlay">
                    <div class="ate-dialog">
                        <div class="ate-title"><span>Формирование файла FB2</span><button id="ate-close" class="ate-close-btn">×</button></div>
                        <div class="ate-form-body">
                            <div id="ate-page-1" class="ate-page">
                                <fieldset class="ate-fieldset">
                                    <legend class="ate-legend">Главы для выгрузки</legend>
                                    <div class="ate-note">Выберите главы для скачивания. Все внутренние страницы глав будут загружены автоматически.</div>
                                    <div class="ate-chapter-list" id="ate-chapter-container"></div>
                                    <div class="ate-toolbar">
                                        <span>Выбрано глав: <strong id="ate-selected-count">0</strong> из <strong id="ate-total-count">0</strong></span>
                                        <button id="ate-select-all" class="ate-group-select">✔ Выделить всё/ничего</button>
                                    </div>
                                </fieldset>
                                <div class="ate-settings-list">
                                    <label class="ate-checkbox-wrap"><input type="checkbox" id="ate-chk-notes" checked> Добавить аннотацию книги</label>
                                    <label class="ate-checkbox-wrap"><input type="checkbox" id="ate-chk-cover" checked> Вшить обложку книги</label>
                                    <label class="ate-checkbox-wrap"><input type="checkbox" id="ate-chk-img" checked> Загружать иллюстрации в главах</label>
                                </div>
                            </div>
                            <div id="ate-page-2" class="ate-page hidden"><div id="ate-log-container" class="ate-log"></div></div>
                            <div class="ate-buttons">
                                <button id="ate-btn-cancel" class="ate-btn-alt">Закрыть</button>
                                <button id="ate-btn-action" class="ate-btn-main">Продолжить</button>
                            </div>
                        </div>
                    </div>
                </div>
            `;

        document.body.appendChild(app);
        bindEvents();
    }

    async function toggleLibraryState() {
        if (!bookMeta.idBook) {
            alert('ID книги не найден. Попробуйте обновить страницу.');
            return;
        }

        bookMeta.inLibrary = !bookMeta.inLibrary;
        updateLibraryIcon();

        const langPrefix = window.location.pathname.split('/')[1] || 'ru';
        const url = bookMeta.inLibrary ? `/${langPrefix}/book/to-library` : `/${langPrefix}/book/from-library`;

        const bodyParams = new URLSearchParams();
        bodyParams.append('bookId', bookMeta.idBook);

        const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
        if (csrfToken) bodyParams.append('_csrf', csrfToken);

        try {
            await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-Requested-With': 'XMLHttpRequest'
                },
                body: bodyParams.toString()
            });
        } catch (e) {
            console.error(e);
            bookMeta.inLibrary = !bookMeta.inLibrary;
            updateLibraryIcon();
        }
    }

    function updateLibraryIcon() {
        const btn = document.getElementById('at-btn-library');
        if (!btn) return;
        if (bookMeta.inLibrary) {
            btn.classList.add('lib-active');
            btn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg>`;
            btn.title = 'В библиотеке (Нажмите, чтобы удалить)';
        } else {
            btn.classList.remove('lib-active');
            btn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg>`;
            btn.title = 'Добавить в библиотеку';
        }
    }

    function updateLikeIcon() {
        const btnLike = document.getElementById('at-btn-like');
        const countSpan = document.getElementById('at-like-count');
        if (!btnLike) return;

        if (bookMeta.isLiked) {
             btnLike.classList.add('active');
             btnLike.querySelector('svg').setAttribute('fill', 'currentColor');
        } else {
             btnLike.classList.remove('active');
             btnLike.querySelector('svg').setAttribute('fill', 'none');
        }
        if (countSpan) countSpan.textContent = bookMeta.likeCount > 0 ? bookMeta.likeCount : '';
    }

    function bindDownloadModalEvents() {
        const overlay = document.getElementById('ate-overlay');
        const btnOpen = document.getElementById('at-btn-download');
        const btnCloseX = document.getElementById('ate-close');
        const btnCancel = document.getElementById('ate-btn-cancel');
        const btnAction = document.getElementById('ate-btn-action');

        const page1 = document.getElementById('ate-page-1');
        const page2 = document.getElementById('ate-page-2');
        const chapterContainer = document.getElementById('ate-chapter-container');
        const selectedCount = document.getElementById('ate-selected-count');
        const totalCount = document.getElementById('ate-total-count');
        const btnSelectAll = document.getElementById('ate-select-all');
        const logContainer = document.getElementById('ate-log-container');

        let isDownloading = false;
        let generatedBlobUrl = null;
        let generatedFilename = '';

        totalCount.textContent = availableChapters.length;
        availableChapters.forEach((ch) => {
            const row = document.createElement('label');
            row.className = 'ate-checkbox-wrap';
            row.style.padding = '4px';
            row.style.borderBottom = '1px solid #f5f5f5';

            const chk = document.createElement('input');
            chk.type = 'checkbox';
            chk.className = 'ate-chapter-chk';
            chk.checked = true;
            chk.dataset.id = ch.id;
            chk.dataset.idx = ch.idx;

            row.appendChild(chk);
            row.appendChild(document.createTextNode(` ${ch.idx}. ${ch.title}`));
            chapterContainer.appendChild(row);
        });

        const updateCount = () => {
            const checked = document.querySelectorAll('.ate-chapter-chk:checked').length;
            selectedCount.textContent = checked;
            btnAction.disabled = checked === 0;
        };

        chapterContainer.addEventListener('change', updateCount);
        updateCount();

        btnSelectAll.onclick = () => {
            const checkboxes = document.querySelectorAll('.ate-chapter-chk');
            const allChecked = Array.from(checkboxes).every(c => c.checked);
            checkboxes.forEach(c => c.checked = !allChecked);
            updateCount();
        };

        const closeModal = () => {
            if (isDownloading) return;
            overlay.classList.remove('open');
            setTimeout(() => {
                page1.classList.remove('hidden');
                page2.classList.add('hidden');
                logContainer.innerHTML = '';
                btnAction.textContent = 'Продолжить';
                btnAction.style.background = '';
                btnCancel.style.display = '';
            }, 300);
        };

        btnOpen.onclick = () => overlay.classList.add('open');
        btnCloseX.onclick = closeModal;
        btnCancel.onclick = closeModal;
        overlay.onclick = (e) => { if (e.target === overlay) closeModal(); };

        function writeLog(text, color = '#ccc') {
            const line = document.createElement('div');
            line.style.color = color;
            line.textContent = text;
            logContainer.appendChild(line);
            logContainer.scrollTop = logContainer.scrollHeight;
            return line;
        }

        btnAction.onclick = async () => {
            if (btnAction.textContent === 'Продолжить') {
                if (typeof FB2Document === 'undefined') {
                    alert('Библиотека конвертации FB2 не загрузилась. Проверьте @require.');
                    return;
                }

                page1.classList.add('hidden');
                page2.classList.remove('hidden');
                btnCancel.style.display = 'none';
                btnAction.textContent = 'Прервать';
                btnAction.style.background = '#d9534f';
                isDownloading = true;

                writeLog('Подготовка структуры FB2...', '#5bc0de');
                let safeAuthor = 'Автор';

                try {
                    const doc = new FB2Document();
                    doc.bookTitle = bookMeta.title || 'Неизвестная книга';

                    if (typeof FB2GenreList !== 'undefined' && Array.isArray(FB2GenreList._keys)) {
                        const litnetKeys = [
                            [ "sf_fantasy", "бояръ-аниме", ["бояръ"] ],
                            [ "fairy_fantasy", "уся", ["wuxia", "азиатское"] ],
                            [ "sf_fantasy", "бытовое фэнтези", ["бытовое"] ],
                            [ "sf_fantasy", "магическая академия", ["академия"] ],
                            [ "sf_fantasy", "темное фэнтези", ["темное"] ],
                            [ "love_sf", "любовное фэнтези", ["мистический любовный", "мистический любовный роман"] ],
                            [ "love_erotica", "эротическое фэнтези", ["эротическая фантастика"] ],
                            [ "love_contemporary", "служебный роман", ["служебный"] ],
                            [ "love_contemporary", "студенческий роман", ["студенческий"] ],
                            [ "love_hard", "жесткая эротика", ["жесткая"] ],
                            [ "love_detective", "остросюжетный любовный", ["криминальный любовный"] ],
                            [ "sf_detective", "магический детектив", ["магический"] ],
                            [ "child_prose", "молодежная проза", ["молодежная", "подростковая"] ],
                            [ "popadanec", "попаданцы в другие миры", ["попаданцы во времени", "попаданцы"] ],
                            [ "fanfiction", "аниме фанфики", ["фанфики по", "манга фанфики"] ],
                            [ "thriller", "мистический триллер", ["психологический триллер", "политический триллер", "криминальный триллер"] ],
                            [ "sf_horror", "хоррор", ["готика", "фолк-хоррор", "научно-фантастический хоррор"] ],
                            [ "sf_mystic", "мистика", ["паранормальное"] ],
                            [ "sf_fantasy_city", "городское фэнтези", ["городское"] ]
                        ];
                        litnetKeys.forEach(lk => {
                            if (!FB2GenreList._keys.some(k => k[1] === lk[1])) {
                                FB2GenreList._keys.push(lk);
                            }
                        });
                    }

                    if (bookMeta.genres && bookMeta.genres.length > 0) {
                        doc.genres = [ bookMeta.genres.join(", ") ];
                    } else {
                        doc.genres = ["novel"];
                    }

                    safeAuthor = (bookMeta.author || 'Автор').replace(/[\/\\?%*:|"<>]/g, '').trim();
                    doc.bookAuthors.push(new FB2Author(safeAuthor));
                    doc.sourceURL = bookMeta.url;

                    if (bookMeta.annotation && document.getElementById('ate-chk-notes').checked) {
                        const li = writeLog('Парсинг аннотации... ');
                        try {
                            doc.bindParser("a", new FB2AnnotationParser());
                            const dom = new DOMParser().parseFromString(bookMeta.annotation, "text/html");
                            doc.parse("a", dom.body);
                            li.innerHTML += '<span style="color:#5cb85c">ok</span>';
                        } catch(e) {
                            li.innerHTML += `<span style="color:#d9534f">ошибка (${e.message})</span>`;
                        } finally {
                            doc.bindParser();
                        }
                    }

                    if (bookMeta.cover && document.getElementById('ate-chk-cover').checked) {
                        const li = writeLog('Скачивание обложки... ');
                        try {
                            const img = new FB2Image(bookMeta.cover);
                            await img.load();
                            img.id = "cover" + img.suffix();
                            doc.coverpage = img;
                            doc.binaries.push(img);
                            li.innerHTML += '<span style="color:#5cb85c">ok</span>';
                        } catch(e) {
                            li.innerHTML += '<span style="color:#d9534f">ошибка</span>';
                        }
                    }

                    writeLog('---');
                    doc.bindParser("c", new FB2ChapterParser());

                    const selectedChks = document.querySelectorAll('.ate-chapter-chk:checked');
                    for (let i = 0; i < selectedChks.length; i++) {
                        if (!isDownloading) break;

                        const chk = selectedChks[i];
                        const ch = availableChapters.find(c => c.id === chk.dataset.id);
                        const li = writeLog(`Загрузка ${i+1}/${selectedChks.length}: ${ch.title}... `);

                        try {
                            let chapterFullHtml = '';
                            let chTotalPages = 1;

                            const cachePages = loadChapterCache(ch.id) || {};
                            if (ch.id === window.atCurrentChapterId && cachePages[1] && cachePages[1] !== 'loading') {
                                chapterFullHtml += cachePages[1];
                                chTotalPages = window.atTotalChapterPages;
                            } else {
                                const data1 = await fetchChapterPage(ch.id, 1, ch.title);
                                chapterFullHtml += data1.html;
                                chTotalPages = data1.totalPages;
                            }

                            for (let p = 2; p <= chTotalPages; p++) {
                                if (!isDownloading) break;
                                li.innerHTML = `Загрузка ${i+1}/${selectedChks.length}: ${ch.title} (стр ${p}/${chTotalPages})... `;

                                if (ch.id === window.atCurrentChapterId && cachePages[p] && cachePages[p] !== 'loading') {
                                    chapterFullHtml += cachePages[p];
                                } else {
                                    const pageData = await fetchChapterPage(ch.id, p, null);
                                    chapterFullHtml += pageData.html;
                                    await new Promise(r => setTimeout(r, 50));
                                }
                            }

                            if (chapterFullHtml && chapterFullHtml.trim().length > 0) {
                                const chDoc = new DOMParser().parseFromString(chapterFullHtml, "text/html");
                                const tempDiv = chDoc.createElement('div');
                                tempDiv.innerHTML = chapterFullHtml;

                                doc.parse("c", tempDiv, ch.title);
                                li.innerHTML = `Загрузка ${i+1}/${selectedChks.length}: ${ch.title}... <span style="color:#5cb85c">ok</span>`;
                            } else {
                                li.innerHTML = `Загрузка ${i+1}/${selectedChks.length}: ${ch.title}... <span style="color:#d9534f">ошибка (пусто)</span>`;
                            }
                        } catch (e) {
                             li.innerHTML = `Загрузка ${i+1}/${selectedChks.length}: ${ch.title}... <span style="color:#d9534f">ошибка (${e.message})</span>`;
                        }
                    }
                    doc.bindParser();

                    if (isDownloading && document.getElementById('ate-chk-img').checked) {
                        const chapterImages = doc.binaries.filter(bin => bin instanceof FB2Image && bin !== doc.coverpage && !bin.value);
                        if (chapterImages.length > 0) {
                            const li = writeLog(`Скачивание иллюстраций (${chapterImages.length} шт)... `);
                            let loadedCount = 0;
                            for (let img of chapterImages) {
                                if (!isDownloading) break;
                                try {
                                    await img.load();
                                    loadedCount++;
                                } catch(e) {}
                            }
                            li.innerHTML += `<span style="color:#5cb85c">ok (${loadedCount}/${chapterImages.length})</span>`;
                        }
                    }

                    if (isDownloading) {
                        writeLog('---');
                        writeLog('Сборка и очистка FB2 файла...', '#f0ad4e');

                        let xmlString = doc.toString();

                        xmlString = xmlString.replace(/<program-used>.*?<\/program-used>/gi, '');
                        xmlString = xmlString.replace(/<first-name>Ox90<\/first-name>/gi, `<first-name>${safeAuthor}</first-name>`);

                        const blob = new Blob([xmlString], { type: 'application/x-fictionbook+xml;charset=utf-8' });
                        generatedBlobUrl = URL.createObjectURL(blob);

                        let safeTitle = (bookMeta.title || 'Книга').replace(/[\/\\?%*:|"<>]/g, '').trim();
                        generatedFilename = `${safeAuthor}. ${safeTitle}`;

                        if (selectedChks.length === 1) {
                            generatedFilename += `. Глава ${selectedChks[0].dataset.idx}`;
                        } else if (selectedChks.length > 1 && selectedChks.length < availableChapters.length) {
                            generatedFilename += `. Главы ${selectedChks[0].dataset.idx}-${selectedChks[selectedChks.length - 1].dataset.idx}`;
                        }

                        writeLog('Готово! Файл можно скачивать.', '#5cb85c');
                        btnAction.textContent = 'Сохранить в файл';
                        btnAction.style.background = '#5cb85c';
                        isDownloading = false;
                    }

                } catch (fatalErr) {
                    writeLog(`Критическая ошибка: ${fatalErr.message}`, '#d9534f');
                    console.error(fatalErr);
                    isDownloading = false;
                    btnAction.textContent = 'Закрыть';
                    btnAction.style.background = '';
                }
            }
            else if (btnAction.textContent === 'Прервать') {
                isDownloading = false;
                writeLog('Операция прервана!', '#d9534f');
                btnAction.textContent = 'Закрыть';
                btnAction.style.background = '';
            }
            else if (btnAction.textContent === 'Сохранить в файл') {
                if (generatedBlobUrl) {
                    const downloadLink = document.createElement('a');
                    downloadLink.href = generatedBlobUrl;
                    downloadLink.download = `${generatedFilename}.fb2`;
                    document.body.appendChild(downloadLink);
                    downloadLink.click();
                    document.body.removeChild(downloadLink);
                }
                closeModal();
            }
            else if (btnAction.textContent === 'Закрыть') {
                closeModal();
            }
        };
    }

    async function buildFullChapter(forceRefresh = false) {
        if ('scrollRestoration' in history) {
            history.scrollRestoration = 'manual';
        }

        const contentContainer = document.getElementById('at-text-content');

        contentContainer.innerHTML = `
            <div class="at-loader-container">
                <div class="at-spinner"></div>
                <div id="at-loader-text">Подготовка главы (всего страниц: ${window.atTotalChapterPages})...</div>
            </div>
        `;

        if (forceRefresh) {
            localStorage.removeItem(`at-chap-${window.atCurrentChapterId}`);
            window.atChapterPagesCache = {};
        }

        let results = [];

        for (let p = 1; p <= window.atTotalChapterPages; p++) {
            if (window.atChapterPagesCache[p] && window.atChapterPagesCache[p] !== 'loading') {
                results.push({ p: p, html: window.atChapterPagesCache[p] });
            } else {
                const loaderText = document.getElementById('at-loader-text');
                if (loaderText) loaderText.textContent = `Загрузка страницы ${p} из ${window.atTotalChapterPages}...`;

                try {
                    const data = await fetchChapterPage(window.atCurrentChapterId, p, currentChapterTitle);

                    if (data.totalPages && data.totalPages > window.atTotalChapterPages) {
                        window.atTotalChapterPages = data.totalPages;
                    }

                    results.push({ p: p, html: data.html });
                    window.atChapterPagesCache[p] = data.html;

                    if (p < window.atTotalChapterPages) {
                        await new Promise(r => setTimeout(r, 50));
                    }
                } catch (e) {
                    results.push({ p: p, html: `<div style="color:red; text-align:center;">Ошибка загрузки страницы ${p}</div>` });
                }
            }
        }

        saveChapterCache(window.atCurrentChapterId, window.atChapterPagesCache);

        let pagesHtml = '';
        results.forEach(res => {
            let resHtml = res.html;
            pagesHtml += `<div class="at-page-block" data-page="${res.p}" id="at-page-${res.p}">${resHtml}</div>`;
        });

        contentContainer.innerHTML = (currentChapterTitle ? `<h1 class="at-chapter-heading">${currentChapterTitle}</h1>` : '') + `<div id="at-pages-container">${pagesHtml}</div>`;

        pingServerBookmark(window.atCurrentChapterId, window.atRequestedPage);

        let isUserScrolling = false;
        const stopScrollFix = () => { isUserScrolling = true; };
        window.addEventListener('wheel', stopScrollFix, { once: true });
        window.addEventListener('touchstart', stopScrollFix, { once: true });
        window.addEventListener('mousedown', stopScrollFix, { once: true });

        const scrollToTarget = () => {
            if (isUserScrolling) return;

            let restored = false;
            try {
                const bmStr = localStorage.getItem(`at-bookmark-${bookMeta.bookSlug}`);
                if (bmStr) {
                    const bm = JSON.parse(bmStr);
                    if (bm.chapterId === window.atCurrentChapterId && bm.percent !== undefined) {
                        const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
                        if (scrollHeight > 0) {
                            window.scrollTo(0, scrollHeight * bm.percent);
                            restored = true;
                        }
                    }
                }
            } catch(e) {}

            if (!restored) {
                const targetPageEl = document.getElementById(`at-page-${window.atRequestedPage}`);
                if (targetPageEl && window.atRequestedPage > 1) {
                    const y = targetPageEl.getBoundingClientRect().top + window.scrollY - 60;
                    window.scrollTo(0, y);
                } else if (window.atRequestedPage === 1) {
                    window.scrollTo(0, 0);
                }
            }
        };

        [50, 150, 300, 600, 1000].forEach(delay => setTimeout(scrollToTarget, delay));

        setTimeout(initPageObserver, 1200);
    }

    function pingServerBookmark(chapterId, pageNum) {
        try {
            const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
            const bodyParams = new URLSearchParams();
            bodyParams.append('chapterId', chapterId);
            bodyParams.append('page', pageNum);
            if (csrfToken) bodyParams.append('_csrf', csrfToken);

            fetch('/reader/get-page', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-Requested-With': 'XMLHttpRequest'
                },
                body: bodyParams.toString()
            }).catch(() => {});
        } catch (e) {}
    }

    function initPageObserver() {
        let scrollTimeout;
        window.addEventListener('scroll', () => {
            if (scrollTimeout) clearTimeout(scrollTimeout);
            scrollTimeout = setTimeout(() => {

                const scrollY = window.scrollY || document.documentElement.scrollTop;
                const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
                if (scrollHeight > 0) {
                    const percent = Math.max(0, Math.min(1, scrollY / scrollHeight));
                    try {
                        localStorage.setItem(`at-bookmark-${bookMeta.bookSlug}`, JSON.stringify({
                            chapterId: window.atCurrentChapterId,
                            percent: percent,
                            time: Date.now()
                        }));
                    } catch(e) {}
                }

                const blocks = document.querySelectorAll('.at-page-block');
                let activePage = window.atCurrentPage;
                const triggerLine = window.innerHeight * 0.4;

                for (let block of blocks) {
                    const rect = block.getBoundingClientRect();
                    if (rect.top <= triggerLine && rect.bottom >= triggerLine) {
                        activePage = parseInt(block.dataset.page);
                        break;
                    }
                }

                if (activePage && activePage !== window.atCurrentPage) {
                    window.atCurrentPage = activePage;
                    const url = new URL(window.location);
                    url.searchParams.set('p', activePage);
                    window.history.replaceState(null, '', url.toString());

                    pingServerBookmark(window.atCurrentChapterId, activePage);
                }
            }, 100);
        }, { passive: true });
    }

    function bindEvents() {
        bindDownloadModalEvents();

        const btnRefresh = document.getElementById('at-btn-refresh');
        const btnToc = document.getElementById('at-btn-toc');
        const sidebar = document.getElementById('at-sidebar');
        const sidebarOverlay = document.getElementById('at-sidebar-overlay');
        const settingsPanel = document.getElementById('at-settings-panel');
        const btnSettings = document.getElementById('at-btn-settings');
        const headerEl = document.getElementById('at-header');

        const btnLike = document.getElementById('at-btn-like');
        const btnLibrary = document.getElementById('at-btn-library');
        const btnReward = document.getElementById('at-btn-reward');
        const btnComplain = document.getElementById('at-btn-complain');

        btnLibrary.onclick = async (e) => {
            e.preventDefault();
            e.stopPropagation();

            if (!bookMeta.idBook) {
                alert('ID книги не найден. Попробуйте обновить страницу.');
                return;
            }

            bookMeta.inLibrary = !bookMeta.inLibrary;
            updateLibraryIcon();

            const langPrefix = window.location.pathname.split('/')[1] || 'ru';
            const url = bookMeta.inLibrary ? `/${langPrefix}/book/to-library` : `/${langPrefix}/book/from-library`;

            const bodyParams = new URLSearchParams();
            bodyParams.append('bookId', bookMeta.idBook);

            const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
            if (csrfToken) bodyParams.append('_csrf', csrfToken);

            try {
                await fetch(url, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'X-Requested-With': 'XMLHttpRequest'
                    },
                    body: bodyParams.toString()
                });
            } catch (err) {
                console.error(err);
                bookMeta.inLibrary = !bookMeta.inLibrary;
                updateLibraryIcon();
            }
        };

        btnLike.onclick = async (e) => {
            e.preventDefault();
            e.stopPropagation();

            if (!bookMeta.idBook) return;

            bookMeta.isLiked = !bookMeta.isLiked;
            bookMeta.likeCount += bookMeta.isLiked ? 1 : -1;
            updateLikeIcon();

            try {
                const langPrefix = window.location.pathname.split('/')[1] || 'ru';
                const action = bookMeta.isLiked ? 'like' : 'dislike';
                await fetch(`/${langPrefix}/book/${action}?id=${bookMeta.idBook}`, {
                    method: 'GET',
                    headers: { 'X-Requested-With': 'XMLHttpRequest' }
                });
            } catch(err) {
                bookMeta.isLiked = !bookMeta.isLiked;
                bookMeta.likeCount += bookMeta.isLiked ? 1 : -1;
                updateLikeIcon();
            }
        };

        btnReward.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            const nativeBtn = document.querySelector('[onclick*="rewardAuthor"]');
            if (nativeBtn) nativeBtn.click();
            else alert('Функция награды недоступна для данной книги.');
        };

        btnComplain.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            const nativeBtn = document.querySelector('#common-complaint, .complain-btn');
            if (nativeBtn) nativeBtn.click();
            else alert('Функция жалобы недоступна.');
        };

        btnRefresh.onclick = () => {
            buildFullChapter(true);
        };

        function closeSidebar() { sidebar.classList.remove('open'); sidebarOverlay.classList.remove('visible'); }

        btnToc.onclick = () => { sidebar.classList.toggle('open'); sidebarOverlay.classList.toggle('visible'); settingsPanel.classList.remove('open'); };
        sidebarOverlay.onclick = closeSidebar;

        document.addEventListener('click', (e) => {
            if (!settingsPanel.contains(e.target) && e.target !== btnSettings) settingsPanel.classList.remove('open');
        });

        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') { closeSidebar(); settingsPanel.classList.remove('open'); }
        });

        btnSettings.onclick = (e) => { e.stopPropagation(); settingsPanel.classList.toggle('open'); closeSidebar(); };

        function applySettings() {
            const themeStep = parseInt(document.getElementById('at-inp-theme').value, 10);
            userSettings.themeStep = themeStep;

            let themeName = 'Белая';
            switch(themeStep) {
                case 1: userSettings.bgColor = '#ffffff'; userSettings.textColor = '#333333'; themeName = 'Белая'; break;
                case 2: userSettings.bgColor = '#f4ecd8'; userSettings.textColor = '#333333'; themeName = 'Сепия'; break;
                case 3: userSettings.bgColor = '#e0e0e0'; userSettings.textColor = '#333333'; themeName = 'Серая'; break;
                case 4: userSettings.bgColor = '#222222'; userSettings.textColor = '#eeeeee'; themeName = 'Темная'; break;
                case 5: userSettings.bgColor = '#000000'; userSettings.textColor = '#808080'; themeName = 'Черная'; break;
            }

            userSettings.fontSize = parseInt(document.getElementById('at-inp-fs').value, 10);
            userSettings.lineHeight = parseFloat(document.getElementById('at-inp-lh').value);
            let twVal = parseInt(document.getElementById('at-inp-tw').value, 10);
            userSettings.textWidth = twVal >= 1400 ? '100%' : twVal;
            userSettings.fontFamily = document.getElementById('at-inp-ff').value;
            userSettings.textColor = document.getElementById('at-inp-tc').value;
            userSettings.bgColor = document.getElementById('at-inp-bc').value;
            userSettings.hyphens = document.getElementById('at-inp-hy').checked;

            document.getElementById('at-val-theme').textContent = themeName;
            document.getElementById('at-inp-tc').value = userSettings.textColor;
            document.getElementById('at-inp-bc').value = userSettings.bgColor;

            document.getElementById('at-val-fs').textContent = userSettings.fontSize + 'px';
            document.getElementById('at-val-lh').textContent = userSettings.lineHeight;
            document.getElementById('at-val-tw').textContent = userSettings.textWidth === '100%' ? 'Макс' : userSettings.textWidth + 'px';

            const root = document.documentElement;
            root.style.setProperty('--at-bg-color', userSettings.bgColor);
            root.style.setProperty('--at-text-color', userSettings.textColor);
            root.style.setProperty('--at-font-size', userSettings.fontSize + 'px');
            root.style.setProperty('--at-line-height', userSettings.lineHeight);
            root.style.setProperty('--at-text-width', userSettings.textWidth === '100%' ? '100%' : userSettings.textWidth + 'px');
            root.style.setProperty('--at-font-family', `'${userSettings.fontFamily}', sans-serif`);
            root.style.setProperty('--at-hyphens', userSettings.hyphens ? 'auto' : 'none');

            localStorage.setItem('at-reader-settings', JSON.stringify(userSettings));
        }

        ['theme', 'fs', 'lh', 'tw', 'ff', 'tc', 'bc', 'hy'].forEach(id => {
            const el = document.getElementById(`at-inp-${id}`);
            if (el) {
                el.addEventListener('input', applySettings);
                el.addEventListener('change', applySettings);
            }
        });

        document.getElementById('at-inp-theme').addEventListener('input', (e) => {
            let tc = '#333', bc = '#fff';
            switch(parseInt(e.target.value, 10)) {
                case 1: bc = '#ffffff'; tc = '#333333'; break;
                case 2: bc = '#f4ecd8'; tc = '#333333'; break;
                case 3: bc = '#e0e0e0'; tc = '#333333'; break;
                case 4: bc = '#222222'; tc = '#eeeeee'; break;
                case 5: bc = '#000000'; tc = '#808080'; break;
            }
            document.getElementById('at-inp-tc').value = tc;
            document.getElementById('at-inp-bc').value = bc;
            applySettings();
        });

        let lastScroll = 0, isHeaderHidden = false;
        window.addEventListener('scroll', () => {
            const currentScroll = window.scrollY || document.documentElement.scrollTop;
            if (currentScroll > lastScroll + 10 && currentScroll > 60) {
                if (!isHeaderHidden) { headerEl.classList.add('hidden'); isHeaderHidden = true; }
            } else if (currentScroll < lastScroll - 10 || currentScroll <= 60) {
                if (isHeaderHidden) { headerEl.classList.remove('hidden'); isHeaderHidden = false; }
            }
            lastScroll = currentScroll <= 0 ? 0 : currentScroll;
        }, { passive: true });

        document.addEventListener('mousemove', (e) => {
            if (e.clientY <= 60 && isHeaderHidden) { headerEl.classList.remove('hidden'); isHeaderHidden = false; }
        });
    }

    function init() {
        if (extractData()) {
            fixNativeModals();
            killAntiCopy();
            injectStyles();
            renderInterface();

            updateLibraryIcon();
            updateLikeIcon();
            fetchBookExtraInfo();
            buildFullChapter();
            return true;
        }
        return false;
    }

    let initAttempts = 0;
    const maxAttempts = 150;

    const initTimer = setInterval(() => {
        initAttempts++;

        if (document.title.includes('Один момент') || document.querySelector('#challenge-error-text') || document.querySelector('.cf-browser-verification')) {
            console.log("Litnet Reader: Обнаружена проверка Cloudflare. Ожидание...");
            return;
        }

        const hasState = document.getElementById('ng-state');
        const hasText = document.querySelector('.jsReaderText, [data-test-id="reader-text"]');

        if (hasState || hasText) {
            clearInterval(initTimer);
            if (!window.litnetReaderInitialized) {
                window.litnetReaderInitialized = true;
                setTimeout(init, 50);
            }
        } else if (initAttempts >= maxAttempts) {
            clearInterval(initTimer);
            console.warn("Litnet Reader: Превышено время ожидания загрузки страницы.");
        }
    }, 100);

})();