LZT Article Summarizer

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 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();
})();