LZT Article Summarizer

Утилита, которая сокращает текст темы, экономя ваше время на прочтение.

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.

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

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         LZT Article Summarizer
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Утилита, которая сокращает текст темы, экономя ваше время на прочтение.
// @author       @dikaronok
// @match        https://lolz.live/threads/*
// @match        https://zelenka.guru/threads/*
// @match        https://lolz.guru/threads/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=zelenka.guru
// @license      MIT
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    const ICONS = {
        close: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>',
        clock: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
        key: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>',
        document: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>',
        eye: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>',
        eyeOff: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>',
        settings: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><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"/></svg>',
        sparkles: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/><path d="M5 3v4"/><path d="M19 17v4"/><path d="M3 5h4"/><path d="M17 19h4"/></svg>'
    };

    const DEFAULT_SETTINGS = {
        minTextLength: 200,
        maxSummaryLength: 500,
        maxKeyPoints: 5,
        buttonColor: '#10b981',
        summaryBgColor: '#0f1a14',
        summaryTextColor: '#e5e7eb',
        animationSpeed: 250,
        wordsPerMinute: 200
    };

    const IMPORTANT_WORDS = [
        'важно', 'главное', 'необходимо', 'ключевой', 'существенно',
        'критически', 'спонсор', 'проект', 'результат', 'итог',
        'вывод', 'следовательно', 'таким образом', 'поэтому',
        'значит', 'суть', 'основа', 'цель', 'задача'
    ];

    const KEY_INDICATORS = [
        'важно', 'главное', 'необходимо', 'следует', 'нужно',
        'основной', 'ключевой', 'рекомендуется', 'обратите внимание',
        'в первую очередь', 'самое важное', 'существенно', 'критически'
    ];

    const SKIP_TAGS = new Set(['IMG', 'SCRIPT', 'STYLE']);
    const SKIP_CLASSES = new Set(['bbCodeBlock', 'messageTextEndMarker', 'lzt-summary-button']);

    let settings = { ...DEFAULT_SETTINGS, ...GM_getValue('lztSummarizerSettings', {}) };

    const clamp = (val, min, max) => Math.min(Math.max(val, min), max);

    const adjustColor = (hex, amount) => {
        const num = parseInt(hex.replace('#', ''), 16);
        const r = clamp(((num >> 16) & 0xFF) + amount, 0, 255);
        const g = clamp(((num >> 8) & 0xFF) + amount, 0, 255);
        const b = clamp((num & 0xFF) + amount, 0, 255);
        return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
    };

    const debounce = (fn, delay) => {
        let timer;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => fn(...args), delay);
        };
    };

    const saveSettings = () => {
        GM_setValue('lztSummarizerSettings', settings);
        injectStyles();
    };

    const resetSettings = () => {
        GM_setValue('lztSummarizerSettings', {});
        settings = { ...DEFAULT_SETTINGS };
        injectStyles();
    };

    const createEl = (tag, attrs = {}, children = []) => {
        const el = document.createElement(tag);
        Object.entries(attrs).forEach(([k, v]) => {
            if (k === 'className') el.className = v;
            else if (k === 'innerHTML') el.innerHTML = v;
            else if (k.startsWith('on')) el.addEventListener(k.slice(2).toLowerCase(), v);
            else el.setAttribute(k, v);
        });
        children.forEach(c => el.appendChild(typeof c === 'string' ? document.createTextNode(c) : c));
        return el;
    };

    const animateShow = (overlay, panel) => {
        requestAnimationFrame(() => {
            overlay.style.opacity = '1';
            panel.style.transform = 'translate(-50%, -50%) scale(1)';
            panel.style.opacity = '1';
        });
    };

    const animateHide = (overlay, panel, onComplete) => {
        overlay.style.opacity = '0';
        panel.style.transform = 'translate(-50%, -50%) scale(0.95)';
        panel.style.opacity = '0';
        setTimeout(onComplete, settings.animationSpeed);
    };

    const createSettingsMenu = () => {
        const fields = [
            { id: 'minTextLength', label: 'Мин. длина текста', type: 'number' },
            { id: 'maxSummaryLength', label: 'Макс. длина резюме', type: 'number' },
            { id: 'maxKeyPoints', label: 'Макс. ключевых моментов', type: 'number' },
            { id: 'buttonColor', label: 'Цвет акцента', type: 'color' },
            { id: 'summaryBgColor', label: 'Фон резюме', type: 'color' },
            { id: 'summaryTextColor', label: 'Цвет текста', type: 'color' },
            { id: 'animationSpeed', label: 'Скорость анимации (мс)', type: 'number' }
        ];

        const container = createEl('div');
        const overlay = createEl('div', { className: 'lzt-overlay lzt-settings-overlay' });
        const panel = createEl('div', { className: 'lzt-panel lzt-settings-panel' });

        const header = createEl('div', { className: 'lzt-panel-header' });
        header.appendChild(createEl('h2', { className: 'lzt-panel-title', innerHTML: `${ICONS.settings} Настройки` }));
        const closeBtn = createEl('button', { className: 'lzt-btn lzt-btn-icon', innerHTML: ICONS.close });
        header.appendChild(closeBtn);
        panel.appendChild(header);

        const form = createEl('div', { className: 'lzt-settings-form' });
        fields.forEach(({ id, label, type }) => {
            const group = createEl('div', { className: 'lzt-form-group' });
            group.appendChild(createEl('label', { for: id }, [label]));
            group.appendChild(createEl('input', { type, id, value: settings[id] }));
            form.appendChild(group);
        });
        panel.appendChild(form);

        const actions = createEl('div', { className: 'lzt-panel-actions' });
        const saveBtn = createEl('button', { className: 'lzt-btn lzt-btn-primary' }, ['Сохранить']);
        const resetBtn = createEl('button', { className: 'lzt-btn lzt-btn-warning' }, ['Сбросить']);
        actions.append(saveBtn, resetBtn);
        panel.appendChild(actions);

        container.append(overlay, panel);
        document.body.appendChild(container);

        const close = () => animateHide(overlay, panel, () => container.remove());

        closeBtn.onclick = close;
        overlay.onclick = close;

        saveBtn.onclick = () => {
            fields.forEach(({ id, type }) => {
                const val = document.getElementById(id).value;
                settings[id] = type === 'number' ? parseInt(val, 10) : val;
            });
            saveSettings();
            close();
        };

        resetBtn.onclick = () => {
            if (confirm('Сбросить все настройки?')) {
                resetSettings();
                close();
            }
        };

        requestAnimationFrame(() => animateShow(overlay, panel));
    };

    const extractText = (element) => {
        const walk = (node) => {
            if (node.nodeType === Node.TEXT_NODE) return node.textContent.trim() + ' ';
            if (node.nodeType !== Node.ELEMENT_NODE) return '';
            if (SKIP_TAGS.has(node.tagName) || [...node.classList].some(c => SKIP_CLASSES.has(c))) return '';
            return [...node.childNodes].map(walk).join('');
        };
        return walk(element).replace(/\s+/g, ' ').trim();
    };

    const splitSentences = (text) => text.match(/[^.!?]+[.!?]+/g) || [];

    const scoreSentence = (sentence, index, total, prevWords = []) => {
        const words = sentence.toLowerCase().split(/\s+/);
        let score = 0;

        score += words.filter(w => IMPORTANT_WORDS.some(imp => w.includes(imp))).length * 3;
        if (sentence.length > 50 && sentence.length < 200) score += 2;
        if (/\d/.test(sentence)) score += 2;
        if (/[%$€₽]/.test(sentence)) score += 2;
        if (index < 3) score += 3;
        if (index >= total - 3) score += 2;
        score += words.filter(w => w.length > 3 && prevWords.includes(w)).length * 0.5;

        return score;
    };

    const summarizeText = (text) => {
        const normalized = text.replace(/\s+/g, ' ').trim();
        const sentences = normalized.split(/(?<=[.!?])\s+(?=[A-ZА-ЯЁ])/);
        if (!sentences.length) return '';

        const scored = sentences.map((sentence, i) => ({
            sentence,
            score: scoreSentence(sentence, i, sentences.length, i > 0 ? sentences[i - 1].toLowerCase().split(/\s+/) : []),
            index: i
        }));

        scored.sort((a, b) => b.score - a.score);
        const selected = scored.slice(0, Math.ceil(sentences.length * 0.3)).sort((a, b) => a.index - b.index);

        let summary = selected.map(s => s.sentence.trim()).join('\n\n');
        if (summary.length > settings.maxSummaryLength) {
            summary = summary.slice(0, settings.maxSummaryLength);
            const lastDot = summary.lastIndexOf('.');
            if (lastDot > 0) summary = summary.slice(0, lastDot + 1);
        }

        return summary;
    };

    const extractKeyPoints = (text) => {
        const sentences = splitSentences(text);
        const points = new Set();

        sentences.forEach(s => {
            const lower = s.toLowerCase();
            const trimmed = s.trim();
            if (KEY_INDICATORS.some(ind => lower.includes(ind)) ||
                (s.length > 30 && s.length < 200 && /\d/.test(s)) ||
                /^[•\-]/.test(trimmed)) {
                points.add(trimmed);
            }
        });

        return [...points].slice(0, settings.maxKeyPoints);
    };

    const formatTime = (text) => {
        const words = text.split(/\s+/).length;
        const mins = words / settings.wordsPerMinute;
        return mins < 1 ? `${Math.ceil(mins * 60)} сек` : `${Math.ceil(mins)} мин`;
    };

    const buildSummaryHTML = (summary, keyPoints, origTime, summaryTime) => `
        <div class="lzt-summary-header">
            <span class="lzt-summary-icon">${ICONS.document}</span>
            <span>Краткое содержание</span>
        </div>
        <div class="lzt-summary-body">${summary || 'Не удалось создать резюме.'}</div>
        <div class="lzt-summary-meta">
            ${ICONS.clock}
            <span>${origTime} → ${summaryTime}</span>
        </div>
        <div class="lzt-key-section">
            <div class="lzt-key-header">${ICONS.key} Ключевые моменты</div>
            ${keyPoints.length
                ? keyPoints.map(p => `<div class="lzt-key-item">${p}</div>`).join('')
                : '<div class="lzt-key-item lzt-muted">Ключевые моменты не найдены</div>'}
        </div>
    `;

    const createPopup = (content) => {
        const overlay = createEl('div', { className: 'lzt-overlay' });
        const popup = createEl('div', { className: 'lzt-panel lzt-popup', innerHTML: `
            <button class="lzt-popup-close">${ICONS.close}</button>
            ${content}
        ` });

        document.body.append(overlay, popup);

        const close = () => animateHide(overlay, popup, () => { overlay.remove(); popup.remove(); });

        popup.querySelector('.lzt-popup-close').onclick = close;
        overlay.onclick = close;

        requestAnimationFrame(() => animateShow(overlay, popup));
    };

    const addSummaryButton = (post) => {
        if (!post.classList.contains('firstPost') || post.querySelector('.lzt-summary-button')) return;

        const content = post.querySelector('.messageContent');
        if (!content) return;

        const text = extractText(content);
        if (text.length < settings.minTextLength) return;

        const btn = createEl('button', { className: 'lzt-summary-button', innerHTML: `${ICONS.sparkles} <span>Показать резюме</span>` });
        let summaryEl = null;
        let processing = false;

        const updateBtnState = (showSummary) => {
            btn.innerHTML = showSummary 
                ? `${ICONS.eyeOff} <span>Скрыть резюме</span>`
                : `${ICONS.sparkles} <span>Показать резюме</span>`;
        };

        btn.onclick = (e) => {
            e.preventDefault();
            if (processing) return;
            processing = true;

            if (summaryEl) {
                const visible = summaryEl.style.display !== 'none';
                summaryEl.style.display = visible ? 'none' : 'block';
                content.classList.toggle('lzt-hidden', !visible);
                updateBtnState(!visible);
                processing = false;
            } else {
                const summary = summarizeText(text);
                const keyPoints = extractKeyPoints(text);
                const origTime = formatTime(text);
                const summaryTime = formatTime(summary);

                summaryEl = createEl('div', {
                    className: 'lzt-summary',
                    innerHTML: buildSummaryHTML(summary, keyPoints, origTime, summaryTime)
                });

                content.after(summaryEl);
                content.classList.add('lzt-hidden');
                updateBtnState(true);

                setTimeout(() => { processing = false; }, settings.animationSpeed);
            }
        };

        content.before(btn);
        requestAnimationFrame(() => btn.classList.add('lzt-visible'));
    };

    const summarizeMainArticle = () => {
        const post = document.querySelector('li.message.firstPost');
        const content = post?.querySelector('.messageContent');

        if (!content) {
            alert('Статья не найдена');
            return;
        }

        const text = extractText(content);
        const summary = summarizeText(text);
        const keyPoints = extractKeyPoints(text);

        createPopup(buildSummaryHTML(summary, keyPoints, formatTime(text), formatTime(summary)));
    };

    const injectStyles = () => {
        const { buttonColor, summaryBgColor, summaryTextColor, animationSpeed } = settings;
        const hoverColor = adjustColor(buttonColor, -15);

        GM_addStyle(`
            :root {
                --lzt-primary: ${buttonColor};
                --lzt-primary-hover: ${hoverColor};
                --lzt-bg: ${summaryBgColor};
                --lzt-text: ${summaryTextColor};
                --lzt-speed: ${animationSpeed}ms;
                --lzt-radius: 12px;
                --lzt-shadow: 0 8px 32px rgba(0,0,0,0.3);
            }

            .lzt-overlay {
                position: fixed;
                inset: 0;
                background: rgba(0,0,0,0.75);
                backdrop-filter: blur(8px);
                z-index: 99998;
                opacity: 0;
                transition: opacity var(--lzt-speed) ease;
            }

            .lzt-panel {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%) scale(0.95);
                background: linear-gradient(145deg, rgba(30,35,40,0.98), rgba(20,25,30,0.98));
                border: 1px solid rgba(255,255,255,0.08);
                border-radius: var(--lzt-radius);
                box-shadow: var(--lzt-shadow);
                z-index: 99999;
                opacity: 0;
                transition: all var(--lzt-speed) ease;
                color: var(--lzt-text);
            }

            .lzt-settings-panel {
                width: 90%;
                max-width: 420px;
                padding: 24px;
            }

            .lzt-popup {
                width: 90%;
                max-width: 700px;
                max-height: 85vh;
                overflow-y: auto;
                padding: 28px;
            }

            .lzt-panel-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 20px;
            }

            .lzt-panel-title {
                display: flex;
                align-items: center;
                gap: 10px;
                font-size: 18px;
                font-weight: 600;
                margin: 0;
                color: var(--lzt-primary);
            }

            .lzt-panel-title svg {
                flex-shrink: 0
                font-weight: 600;
                margin: 0;
                color: var(--lzt-primary);
            }

            .lzt-panel-title svg {
                flex-shrink: 0;
            }

            .lzt-btn {
                border: none;
                border-radius: 8px;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
                transition: all var(--lzt-speed) ease;
            }

            .lzt-btn-icon {
                padding: 8px 12px;
                background: rgba(255,255,255,0.05);
                color: var(--lzt-text);
            }

            .lzt-btn-icon:hover {
                background: rgba(255,255,255,0.1);
            }

            .lzt-btn-primary {
                flex: 1;
                padding: 12px;
                background: var(--lzt-primary);
                color: white;
            }

            .lzt-btn-primary:hover {
                background: var(--lzt-primary-hover);
                transform: translateY(-1px);
            }

            .lzt-btn-warning {
                flex: 1;
                padding: 12px;
                background: rgba(245,158,11,0.15);
                color: #f59e0b;
            }

            .lzt-btn-warning:hover {
                background: rgba(245,158,11,0.25);
            }

            .lzt-settings-form {
                display: grid;
                gap: 14px;
                margin-bottom: 20px;
            }

            .lzt-form-group {
                display: flex;
                flex-direction: column;
                gap: 6px;
            }

            .lzt-form-group label {
                font-size: 13px;
                color: rgba(255,255,255,0.7);
            }

            .lzt-form-group input {
                padding: 10px 12px;
                background: rgba(0,0,0,0.3);
                border: 1px solid rgba(255,255,255,0.1);
                border-radius: 8px;
                color: var(--lzt-text);
                font-size: 14px;
                transition: border-color var(--lzt-speed) ease;
            }

            .lzt-form-group input:focus {
                outline: none;
                border-color: var(--lzt-primary);
            }

            .lzt-form-group input[type="color"] {
                height: 42px;
                padding: 4px;
                cursor: pointer;
            }

            .lzt-panel-actions {
                display: flex;
                gap: 10px;
            }

            .lzt-popup-close {
                position: absolute;
                top: 16px;
                right: 16px;
                background: rgba(255,255,255,0.05);
                border: none;
                border-radius: 8px;
                padding: 8px 10px;
                color: var(--lzt-text);
                cursor: pointer;
                transition: all var(--lzt-speed) ease;
            }

            .lzt-popup-close:hover {
                background: rgba(255,255,255,0.1);
                color: var(--lzt-primary);
            }

            .lzt-summary-button {
                display: inline-flex;
                align-items: center;
                gap: 8px;
                padding: 10px 18px;
                background: linear-gradient(135deg, var(--lzt-primary), ${hoverColor});
                border: none;
                border-radius: 8px;
                color: white;
                font-size: 14px;
                font-weight: 500;
                cursor: pointer;
                margin: 12px 0;
                opacity: 0;
                transform: translateY(8px);
                transition: all var(--lzt-speed) ease;
                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            }

            .lzt-summary-button svg {
                flex-shrink: 0;
            }

            .lzt-summary-button.lzt-visible {
                opacity: 1;
                transform: translateY(0);
            }

            .lzt-summary-button:hover {
                transform: translateY(-2px);
                box-shadow: 0 6px 20px rgba(0,0,0,0.25);
            }

            .lzt-summary {
                background: var(--lzt-bg);
                border-left: 3px solid var(--lzt-primary);
                border-radius: 0 var(--lzt-radius) var(--lzt-radius) 0;
                padding: 20px 24px;
                margin: 16px 0;
                color: var(--lzt-text);
                animation: lzt-slide-in var(--lzt-speed) ease;
            }

            @keyframes lzt-slide-in {
                from { opacity: 0; transform: translateY(-8px); }
                to { opacity: 1; transform: translateY(0); }
            }

            .lzt-summary-header {
                display: flex;
                align-items: center;
                gap: 8px;
                font-size: 16px;
                font-weight: 600;
                color: var(--lzt-primary);
                margin-bottom: 14px;
            }

            .lzt-summary-icon {
                display: flex;
            }

            .lzt-summary-body {
                line-height: 1.7;
                white-space: pre-line;
                margin-bottom: 16px;
            }

            .lzt-summary-meta {
                display: flex;
                align-items: center;
                gap: 6px;
                font-size: 12px;
                opacity: 0.7;
                margin-bottom: 16px;
            }

            .lzt-key-section {
                border-top: 1px solid rgba(255,255,255,0.1);
                padding-top: 16px;
            }

            .lzt-key-header {
                display: flex;
                align-items: center;
                gap: 8px;
                font-weight: 600;
                color: var(--lzt-primary);
                margin-bottom: 12px;
            }

            .lzt-key-item {
                position: relative;
                padding-left: 18px;
                margin: 8px 0;
                line-height: 1.5;
            }

            .lzt-key-item::before {
                content: "";
                position: absolute;
                left: 0;
                top: 8px;
                width: 6px;
                height: 6px;
                background: var(--lzt-primary);
                border-radius: 50%;
            }

            .lzt-key-item.lzt-muted {
                opacity: 0.6;
            }

            .lzt-key-item.lzt-muted::before {
                background: rgba(255,255,255,0.3);
            }

            .lzt-hidden {
                height: 0 !important;
                opacity: 0 !important;
                overflow: hidden !important;
                margin: 0 !important;
                padding: 0 !important;
            }
        `);
    };

    const init = () => {
        injectStyles();

        GM_registerMenuCommand('Резюме статьи', debounce(summarizeMainArticle, 300));
        GM_registerMenuCommand('Настройки', debounce(createSettingsMenu, 300));

        const tryAddButton = () => {
            const post = document.querySelector('li.message.firstPost');
            if (post) addSummaryButton(post);
        };

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', tryAddButton);
        } else {
            tryAddButton();
        }

        new MutationObserver(mutations => {
            mutations.forEach(m => {
                m.addedNodes.forEach(node => {
                    if (node.nodeType === 1 && node.matches?.('li.message.firstPost')) {
                        addSummaryButton(node);
                    }
                });
            });
        }).observe(document.body, { childList: true, subtree: true });
    };

    init();
})();