By V.Boss

Автоматическая проверка объёма, шрифта, размера и наличия фото в темах с точным подсчётом слов. Создано V.Boss. ✨

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         By V.Boss
// @namespace    http://tampermonkey.net/
// @version      44
// @description  Автоматическая проверка объёма, шрифта, размера и наличия фото в темах с точным подсчётом слов. Создано V.Boss. ✨
// @author       V.Boss
// @match        https://forum.blackrussia.online/*
// @grant        none
// ==/UserScript==

(function () {
'use strict';
const ALLOWED_FONTS = ['verdana', 'times new roman'], LS_KEY = 'br_check_v30_clean', LS_HIDE_KEY = 'br_indicator_collapsed';

const settings = Object.assign({checkFont:true, checkSize:true, checkVolume:true, checkPhoto:true, showNotif:true, explosion:true, deviceMode:'pc'}, JSON.parse(localStorage.getItem(LS_KEY) || '{}'));
let lastTopicId = null, notifTimeout = null, notifEl = null, obsTimer = null;
const saveSettings = () => localStorage.setItem(LS_KEY, JSON.stringify(settings));

const style = document.createElement('style');
style.textContent = `
.br-ui-element { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; box-sizing: border-box; }
@keyframes br-fade-up { from { opacity: 0; transform: translate(-50%, 20px); } to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes br-scale-in { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.br-bad-size-text { text-decoration: underline wavy #ff453a 2px !important; background-color: rgba(255, 69, 58, 0.08) !important; position: relative; }
.br-good-size-text { text-decoration: underline wavy #30d158 1.5px !important; background-color: rgba(48, 209, 88, 0.05) !important; }
.br-font-label { display: inline-block; background: #ff453a !important; color: #fff !important; font-size: 10px !important; font-weight: bold !important; padding: 2px 6px !important; border-radius: 4px !important; margin-left: 6px !important; vertical-align: middle; white-space: nowrap; }
.br-gear { position: fixed; top: 20px; right: 20px; z-index: 99999; width: 44px; height: 44px; background: rgba(28, 28, 30, 0.6); backdrop-filter: blur(20px); color: #fff; font-size: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 4px 12px rgba(0,0,0,0.2); transition: all 0.3s; }
.br-gear:hover { transform: rotate(90deg); background: rgba(50, 50, 50, 0.8); }
.br-settings { position: fixed; z-index: 99998; display: none; background: rgba(30, 30, 30, 0.85); backdrop-filter: blur(35px); border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 20px 50px rgba(0,0,0,0.6); padding: 20px; color: #fff; animation: br-scale-in 0.3s; }
.br-settings.show { display: block; }
.br-mode-pc .br-settings { top: 80px; right: 20px; width: 280px; border-radius: 18px; }
.br-mode-mobile .br-settings { bottom: 20px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 400px; border-radius: 24px; animation: br-fade-up 0.4s; }
.br-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.br-title { font-weight: 600; font-size: 17px; }
.br-close { color: #8e8e93; cursor: pointer; font-size: 24px; padding: 5px; }
.br-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.br-switch { position: relative; display: inline-block; width: 42px; height: 24px; }
.br-switch input { opacity: 0; width: 0; height: 0; }
.br-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #3a3a3c; transition: .4s; border-radius: 24px; }
.br-slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; }
input:checked + .br-slider { background-color: #30d158; }
input:checked + .br-slider:before { transform: translateX(18px); }
.br-notif { position: fixed; left: 50%; bottom: 40px; transform: translate(-50%, 20px); opacity: 0; transition: all 0.5s; background: rgba(28, 28, 30, 0.9); backdrop-filter: blur(20px); color: #fff; padding: 12px 24px; border-radius: 50px; border: 1px solid rgba(255,255,255,0.1); font-weight: 600; z-index: 100000; pointer-events: none; text-align: center; }
.br-notif.show { opacity: 1; transform: translate(-50%, 0); }
.br-notif.good { border-color: #30d158; color: #30d158; }
.br-notif.bad { border-color: #ff453a; color: #ff453a; }
.br-indicator { position: absolute; top: 10px; right: 10px; padding: 8px 12px; border-radius: 12px; font-size: 12px; font-weight: 600; color: #fff; background: rgba(0,0,0,0.75); backdrop-filter: blur(12px); display: flex; flex-direction: column; align-items: flex-end; gap: 4px; z-index: 50; border: 1px solid rgba(255,255,255,0.1); min-width: 140px; }
.br-indicator .ok { color: #30d158; }
.br-indicator .err { color: #ff453a; }
.br-indicator-content { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; width: 100%; }
.br-toggle-panel-btn { align-self: flex-start; background: rgba(255, 255, 255, 0.15) !important; color: #fff !important; border: none !important; border-radius: 4px !important; font-size: 9px !important; text-transform: uppercase !important; padding: 2px 6px !important; cursor: pointer !important; margin-bottom: 4px !important; font-weight: bold !important; }
.br-indicator.collapsed { padding: 4px 8px !important; min-width: auto !important; background: rgba(0, 0, 0, 0.9) !important; }
.br-indicator.collapsed .br-indicator-content { display: none !important; }
.br-indicator.collapsed .br-toggle-panel-btn { margin-bottom: 0 !important; background: transparent !important; font-size: 11px !important; padding: 2px 4px !important; }
.br-lines-sizes { font-size: 11px; opacity: 0.9; margin-top: 5px; border-top: 1px solid rgba(255,255,255,0.15); padding-top: 4px; width: 100%; text-align: right; }
.br-alert-text { color: #ff453a; font-size: 11px; margin-top: 2px; font-weight: bold; width: 100%; text-align: right; }
`;
document.head.appendChild(style);

const gear = document.createElement('div'); gear.className='br-gear br-ui-element'; gear.innerHTML='⚙️';
document.body.appendChild(gear);

const settingsPanel = document.createElement('div'); settingsPanel.className='br-settings br-ui-element';
const createToggle = (id, label, checked) => `<div class="br-row"><span class="br-label">${label}</span><label class="br-switch"><input type="checkbox" id="${id}" ${checked?'checked':''}><span class="br-slider"></span></label></div>`;

settingsPanel.innerHTML = `
    <div class="br-header"><span class="br-title">Настройки</span><span class="br-close">✕</span></div>
    <div class="br-row"><select id="deviceSelector" style="width:100%; padding:8px; border-radius:8px; background:#1c1c1e; color:white; border:none;"><option value="pc">🖥️ PC</option><option value="mobile">📱 Mobile</option></select></div>
    ${createToggle('fontCheck', 'Шрифт', settings.checkFont)}${createToggle('sizeCheck', 'Размер', settings.checkSize)}${createToggle('volCheck', 'Объём (200-600)', settings.checkVolume)}${createToggle('photoCheck', 'Фото / Ссылки', settings.checkPhoto)}
    <div style="border-top:1px solid rgba(255,255,255,0.1); margin:15px 0;"></div>
    ${createToggle('notifCheck', 'Уведомления', settings.showNotif)}${createToggle('explosionCheck', 'Салют', settings.explosion)}
`;
document.body.appendChild(settingsPanel);

const applyMode = () => { document.body.classList.remove('br-mode-pc', 'br-mode-mobile'); document.body.classList.add('br-mode-' + settings.deviceMode); };
gear.onclick = (e) => { e.stopPropagation(); settingsPanel.classList.toggle('show'); };
settingsPanel.querySelector('.br-close').onclick = () => settingsPanel.classList.remove('show');
document.addEventListener('click', (e) => { if (!settingsPanel.contains(e.target) && e.target !== gear) settingsPanel.classList.remove('show'); });

const devSel = document.getElementById('deviceSelector'); devSel.value = settings.deviceMode;
devSel.onchange = () => { settings.deviceMode = devSel.value; saveSettings(); applyMode(); };
settingsPanel.onchange = (e) => {
    if(e.target.tagName === 'SELECT') return;
    settings.checkFont = document.getElementById('fontCheck').checked; settings.checkSize = document.getElementById('sizeCheck').checked;
    settings.checkVolume = document.getElementById('volCheck').checked; settings.checkPhoto = document.getElementById('photoCheck').checked;
    settings.showNotif = document.getElementById('notifCheck').checked; settings.explosion = document.getElementById('explosionCheck').checked;
    saveSettings(); runCheck();
};
applyMode();

function notify(htmlMsg, type) {
    if(!settings.showNotif) return;
    if(!notifEl){ notifEl=document.createElement('div'); notifEl.className='br-notif br-ui-element'; document.body.appendChild(notifEl); }
    if (notifEl.innerHTML === htmlMsg && notifEl.classList.contains('show')) return;
    notifEl.innerHTML = htmlMsg; notifEl.className=`br-notif br-ui-element ${type} show`;
    clearTimeout(notifTimeout); notifTimeout = setTimeout(()=> { notifEl.classList.remove('show'); }, 5000);
}

function getTextNodes(node) {
    let textNodes = [];
    if (node.nodeType === Node.TEXT_NODE) { if (node.nodeValue.trim().length > 0) textNodes.push(node); }
    else if (node.nodeType === Node.ELEMENT_NODE && !node.classList.contains('br-ui-element') && !node.classList.contains('br-indicator') && !node.classList.contains('br-font-label')) {
        for (let child of node.childNodes) textNodes.push(...getTextNodes(child));
    }
    return textNodes;
}

function analyzeTopPost() {
    const wrapper = document.querySelector('.bbWrapper'); if (!wrapper) return;
    let resultHTML = "", allPassed = true, problems = [], sizeDetailsHTML = "";
    wrapper.querySelectorAll('.br-bad-size-text, .br-good-size-text').forEach(el => el.replaceWith(...el.childNodes));
    wrapper.querySelectorAll('.br-font-label').forEach(el => el.remove()); wrapper.normalize();

    // Новая строгая очистка текста перед подсчётом
    let textClean = wrapper.textContent
        .replace(/https?:\/\/\S+/gi, '') // Полностью вырезаем любые интернет-ссылки
        .replace(/[\u00A0\t\n\r]/g, ' ') // Убираем жесткие переносы строк и табы
        .replace(/[^а-яА-ЯёЁa-zA-Z\s-]/g, ''); // Удаляем цифры, знаки препинания и спецсимволы

    const words = textClean.match(/[а-яА-ЯёЁa-zA-Z-]{2,}/g); // Считаем слова только от 2-х букв (игнорируем обрывки букв)
    const wordCount = words ? words.length : 0;

    if (settings.checkFont) {
        const comp = getComputedStyle(wrapper), fonts = (comp.fontFamily || '').toLowerCase();
        let fontOK = ALLOWED_FONTS.some(f => fonts.includes(f));
        if (!fontOK) {
             const innerFontEl = wrapper.querySelector('[style*="font-family"]');
             if(innerFontEl && ALLOWED_FONTS.some(f => innerFontEl.style.fontFamily.toLowerCase().includes(f))) fontOK = true;
        }
        resultHTML += `<span class="${fontOK?'ok':'err'}">${fontOK?'✅':'❌'} Шрифт</span>`; if (!fontOK) { allPassed = false; problems.push("Шрифт"); }
    }

    if (settings.checkSize || settings.checkFont) {
        let globalSizeOK = true, uniqueSizes = new Set(), badSizesFound = new Set();
        getTextNodes(wrapper).forEach(node => {
            const parent = node.parentElement;
            if (parent) {
                const comp = getComputedStyle(parent), size = Math.round(parseFloat(comp.fontSize)), fontRaw = comp.fontFamily || '';
                if (size > 0) {
                    uniqueSizes.add(size);
                    const span = document.createElement('span'), hasSizeErr = size < 15, hasFontErr = !ALLOWED_FONTS.some(f => fontRaw.toLowerCase().includes(f));
                    let isBad = false;
                    
                    if ((settings.checkSize && hasSizeErr) || (settings.checkFont && hasFontErr)) {
                        if (hasSizeErr) { globalSizeOK = false; badSizesFound.add(size); }
                        span.className = 'br-bad-size-text'; isBad = true;
                    } else { span.className = 'br-good-size-text'; }
                    
                    node.before(span); span.appendChild(node);
                    if (isBad) {
                        const label = document.createElement('span'); label.className = 'br-font-label br-ui-element';
                        label.textContent = `[${fontRaw.replace(/['"]/g, '').split(',')[0].trim()} | ${size}px]`; span.after(label);
                    }
                }
            }
        });
        if (uniqueSizes.size === 0) {
            const base = Math.round(parseFloat(getComputedStyle(wrapper).fontSize)); uniqueSizes.add(base);
            globalSizeOK = base >= 15; if(!globalSizeOK) { badSizesFound.add(base); allPassed = false; problems.push("Размер < 15px"); }
        } else if (!globalSizeOK) { allPassed = false; problems.push("Размер < 15px"); }

        if (settings.checkSize) {
            resultHTML += `<span class="${globalSizeOK?'ok':'err'}">${globalSizeOK?'✅':'❌'} Размер</span>`;
            sizeDetailsHTML = `<div class="br-lines-sizes">В тексте: ` + Array.from(uniqueSizes).sort((a,b)=>a-b).map(s => `<b class="${s>=15?'ok':'err'}">${s}px</b>`).join(', ') + `</div>`;
            if (badSizesFound.size > 0) sizeDetailsHTML += `<div class="br-alert-text">⚠️ Мелкий текст: ${Array.from(badSizesFound).sort((a,b)=>a-b).map(s=>s+'px').join(', ')}!</div>`;
        }
    }

    if (settings.checkVolume) {
        const volOK = (wordCount >= 200 && wordCount <= 600);
        resultHTML += `<span class="${volOK?'ok':'err'}">${volOK?'✅':'❌'} Объём: ${wordCount}</span>`;
        if (!volOK) { allPassed = false; problems.push("Объём"); }
    }
    if (settings.checkPhoto) {
        const photoOK = !!(wrapper.querySelector('img[src*="http"]') || Array.from(wrapper.querySelectorAll('a')).some(a => (a.href && a.href.includes('http'))));
        resultHTML += `<span class="${photoOK?'ok':'err'}">${photoOK?'✅':'❌'} Фото/Ссылки</span>`;
        if (!photoOK) { allPassed = false; problems.push("Нет фото"); }
    }

    let ind = wrapper.querySelector('.br-indicator');
    if (!ind) {
        ind = document.createElement('div'); ind.className = 'br-indicator br-ui-element';
        if(getComputedStyle(wrapper).position === 'static') wrapper.style.position = 'relative';
        const btn = document.createElement('button'); btn.className = 'br-toggle-panel-btn br-ui-element';
        if (localStorage.getItem(LS_HIDE_KEY) === 'true') { ind.classList.add('collapsed'); btn.textContent = '👁️ Показать'; } else { btn.textContent = 'Скрыть'; }
        btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); const c = ind.classList.toggle('collapsed'); localStorage.setItem(LS_HIDE_KEY, c); btn.textContent = c ? '👁️ Показать' : 'Скрыть'; };
        const cont = document.createElement('div'); cont.className = 'br-indicator-content br-ui-element';
        ind.appendChild(btn); ind.appendChild(cont); wrapper.appendChild(ind);
    }
    const cc = ind.querySelector('.br-indicator-content'); if (cc) cc.innerHTML = resultHTML + sizeDetailsHTML;

    if (allPassed) {
        notify("✅ Биография одобрена.", "good");
        if (settings.explosion && lastTopicId !== location.pathname) { lastTopicId = location.pathname; createFireworks(); }
    } else { notify("❌ Исправьте: " + problems.join(", "), "bad"); }
}

function createFireworks() {
    const colors = ['#30d158', '#ffd60a', '#ff453a', '#0a84ff', '#fff'], container = document.createElement('div');
    container.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:100002;'; document.body.appendChild(container);
    for (let i=0; i<40; i++) {
        const p = document.createElement('div'), angle = Math.random() * Math.PI * 2, dist = 100 + Math.random() * 300;
        p.style.cssText = `position:absolute;top:50%;left:50%;width:6px;height:6px;border-radius:50%;background:${colors[Math.floor(Math.random()*colors.length)]};transition:1s ease-out;`;
        container.appendChild(p);
        requestAnimationFrame(()=>{ p.style.transform = `translate(${Math.cos(angle)*dist}px, ${Math.sin(angle)*dist}px) scale(0)`; p.style.opacity = 0; });
    }
    setTimeout(()=>container.remove(), 1200);
}

const runCheck = () => analyzeTopPost();
const observer = new MutationObserver((mutations)=>{
    let run = false;
    for(let m of mutations) {
        if((m.target.className && typeof m.target.className === 'string' && m.target.className.includes('br-')) || (m.target.closest && m.target.closest('.br-indicator'))) continue;
        run = true;
    }
    if(run) { clearTimeout(obsTimer); obsTimer=setTimeout(runCheck, 600); } // Ускорили задержку для мгновенной фиксации изменений
});
setTimeout(() => { observer.observe(document.body, {childList:true, subtree:true}); runCheck(); }, 2000);
})();