LZT Article Summarizer

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

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==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();
})();