Jump to content

MediaWiki:Common.js: Difference between revisions

From Vampiro
Integração completa com VisualEditor OOUI, WikiEditor e Barra Rápida Gótica
v23 fix: restaura polling com auto-limpeza para botoes de censura (VE + codigo-fonte)
 
(16 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* ==========================================================================
/* ==========================================================================
   VAMPIRO: A MÁSCARA — SCRIPTS GLOBAIS, SEGREDO DINÂMICO & VISUALEDITOR TOOL
   VAMPIRO: A MÁSCARA — SCRIPTS GLOBAIS, VIEWPORT RESPONSIVO & SEGREDO DINÂMICO
   ========================================================================== */
   ========================================================================== */


Line 6: Line 6:
   'use strict';
   'use strict';


   // ------------------------------------------------------------------------
   /* ------------------------------------------------------------------
   // 1. LÓGICA DE CENSURA & REVELAÇÃO DINÂMICA ({{Segredo}})
    UTILITÁRIO: POLLING LIMITADO (substitui setInterval infinito)
   // ------------------------------------------------------------------------
    ------------------------------------------------------------------ */
  function pollUntilFound(checkFn, callback, maxAttempts) {
    var attempts = 0;
    var max = maxAttempts || 50;
    var intervalId = setInterval(function () {
      attempts++;
      var result = checkFn();
      if (result) {
        clearInterval(intervalId);
        callback(result);
      } else if (attempts >= max) {
        clearInterval(intervalId);
      }
    }, 400);
    return intervalId;
  }
 
   /* ------------------------------------------------------------------
    0. VIEWPORT RESPONSIVO REAL PARA MOBILE (SUBSTITUI WIDTH=1120)
    ------------------------------------------------------------------ */
  function fixMobileViewport() {
    var meta = document.querySelector('meta[name="viewport"]');
    if (meta) {
      meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover');
    } else {
      meta = document.createElement('meta');
      meta.name = 'viewport';
      meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover';
      document.head.appendChild(meta);
    }
  }
  fixMobileViewport();
 
   /* ------------------------------------------------------------------
    1. CENSURA & REVELAÇÃO DINÂMICA (LEITURA)
    ------------------------------------------------------------------ */
   function initVtmSecrets() {
   function initVtmSecrets() {
     var secretElements = document.querySelectorAll('[data-vtm-secret="true"]');
     var els = document.querySelectorAll('[data-vtm-secret="true"]');
     if (!secretElements || secretElements.length === 0) return;
     if (!els || !els.length) return;
 
     var now = new Date();
     var now = new Date();


     secretElements.forEach(function (el) {
     els.forEach(function (el) {
       var liberado = (el.getAttribute('data-liberado') || '').toLowerCase().trim();
       var lib = (el.getAttribute('data-liberado') || '').toLowerCase().trim();
       var dateStr = (el.getAttribute('data-reveal-date') || '').trim();
       var ds = (el.getAttribute('data-reveal-date') || '').trim();
       var dica = (el.getAttribute('data-dica') || '').trim();
       var dica = (el.getAttribute('data-dica') || '').trim();
       var isRevealed = false;
       var revealed = false;
       var tooltipText = '🔒 [ARQUIVO CONFIDENCIAL - ACESSO RESTRITO]';
       var tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA]';


       if (liberado === 'sim' || liberado === 'true' || liberado === '1') {
       if (lib === 'sim' || lib === 'true' || lib === '1') {
         isRevealed = true;
         revealed = true;
       } else if (liberado === 'nao' || liberado === 'false' || liberado === '0') {
       } else if (lib === 'nao' || lib === 'false' || lib === '0') {
         isRevealed = false;
         tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';
        tooltipText = '🔒 [ARQUIVO CLASSIFICADO' + (dica ? ' // ' + dica : '') + ' - LIBERAÇÃO RESTRITA]';
       } else if (ds) {
       } else if (dateStr) {
         var td = null;
         var targetDate = null;
         if (ds.indexOf('/') !== -1) {
         if (dateStr.indexOf('/') !== -1) {
           var p = ds.split('/');
           var parts = dateStr.split('/');
           if (p.length === 3) td = new Date(p[2], parseInt(p[1], 10) - 1, p[0]);
           if (parts.length === 3) {
            targetDate = new Date(parts[2], parseInt(parts[1], 10) - 1, parts[0], 0, 0, 0);
          }
         } else {
         } else {
           targetDate = new Date(dateStr);
           td = new Date(ds);
         }
         }
 
         if (td && !isNaN(td.getTime())) {
         if (targetDate && !isNaN(targetDate.getTime())) {
           if (now >= td) {
           if (now >= targetDate) {
             revealed = true;
             isRevealed = true;
           } else {
           } else {
            isRevealed = false;
             var f = ('0' + td.getDate()).slice(-2) + '/' + ('0' + (td.getMonth() + 1)).slice(-2) + '/' + td.getFullYear();
             var formatted = ('0' + targetDate.getDate()).slice(-2) + '/' +
             tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ' - ' + f + ']';
                            ('0' + (targetDate.getMonth() + 1)).slice(-2) + '/' +
                            targetDate.getFullYear();
             tooltipText = '🔒 [CLASSIFICADO' + (dica ? ' // ' + dica : '') + ' - Desclassificação em: ' + formatted + ']';
           }
           }
        } else {
          isRevealed = false;
         }
         }
      } else {
        isRevealed = false;
       }
       }


       if (isRevealed) {
       if (revealed) {
         el.classList.add('is-revealed');
         el.classList.add('is-revealed');
         el.classList.remove('is-redacted');
         el.classList.remove('is-redacted');
         el.title = '🔓 [DOCUMENTO DESCLASSIFICADO' + (dica ? ' // ' + dica : '') + ']';
         el.title = '[DESCLASSIFICADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';
       } else {
       } else {
         el.classList.add('is-redacted');
         el.classList.add('is-redacted');
         el.classList.remove('is-revealed');
         el.classList.remove('is-revealed');
         el.title = tooltipText;
         el.title = tip;
       }
       }
     });
     });
   }
   }


   // ------------------------------------------------------------------------
   /* ------------------------------------------------------------------
  // 2. INTEGRAÇÃO COM VISUALEDITOR (EDITOR VISUAL OOUI)
    2. INJEÇÃO DO BOTÃO "🔒 CENSURAR" NA BARRA DO VISUALEDITOR
  // ------------------------------------------------------------------------
    ------------------------------------------------------------------ */
  function setupVisualEditorTool() {
  function injectVisualEditorButton() {
     if (!window.mw || !mw.hook) return;
    var toolbar = document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools');
    if (!toolbar) return;
 
    if (document.getElementById('vtm-ve-censor-button')) return;
 
    var btnGroup = document.createElement('div');
    btnGroup.id = 'vtm-ve-censor-button';
    btnGroup.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-toolGroup';
    btnGroup.style.cssText = 'display:inline-flex; align-items:center; margin:0 6px;';
 
    var btn = document.createElement('button');
    btn.type = 'button';
    btn.innerHTML = '🔒 Censurar Trecho';
    btn.title = 'Aplicar tarja de censura {{Segredo}} no texto selecionado';
    btn.style.cssText = 'background:linear-gradient(180deg, #450810 0%, #1a0306 100%); color:#ffffff; border:1px solid #ff2a3f; border-radius:4px; padding:4px 10px; font-family:"Cinzel", Georgia, serif; font-size:0.82em; font-weight:700; cursor:pointer; box-shadow:0 0 8px rgba(255,42,63,0.4); display:flex; align-items:center; gap:4px; transition:all 0.2s ease; height:32px; line-height:1; outline:none;';
 
    btn.onmouseover = function () {
      btn.style.background = 'linear-gradient(180deg, #6b0c19 0%, #30050c 100%)';
      btn.style.boxShadow = '0 0 14px rgba(255,42,63,0.85)';
    };
     btn.onmouseout = function () {
      btn.style.background = 'linear-gradient(180deg, #450810 0%, #1a0306 100%)';
      btn.style.boxShadow = '0 0 8px rgba(255,42,63,0.4)';
    };
 
    btn.onclick = function (e) {
      e.preventDefault();
      e.stopPropagation();
 
      try {
        var target = ve.init.target;
        if (target && target.getSurface()) {
          var surfaceModel = target.getSurface().getModel();
          var fragment = surfaceModel.getFragment();
          var selectedText = fragment.getText();


    mw.hook('ve.loadModules').add(function (addPlugin) {
          var contentToCensor = selectedText || 'Texto confidencial a ser censurado';
      addPlugin(function () {
        if (!window.ve || !ve.ui || !ve.ui.toolFactory) return;


        // Cria o comando no VisualEditor
           var transclusionNode = {
        ve.ui.commandRegistry.register(
             type: 'mwTransclusionInline',
           new ve.ui.Command(
             attributes: {
             'insertVtmSecret', 'content', 'insert',
               mw: {
             {
                 parts: [
               args: [
                 [
                   {
                   {
                     type: 'mwTransclusionInline',
                     template: {
                    attributes: {
                       target: { href: 'Template:Segredo', wt: 'Segredo' },
                       mw: {
                      params: {
                        parts: [
                        '1': { wt: contentToCensor },
                          {
                        'liberado': { wt: 'nao' }
                            template: {
                              target: { href: 'Template:Segredo', wt: 'Segredo' },
                              params: {
                                '1': { wt: 'Texto confidencial a ser censurado' },
                                'data': { wt: 'AAAA-MM-DD' },
                                'liberado': { wt: 'nao' }
                              }
                            }
                          }
                        ]
                       }
                       }
                     }
                     }
                   },
                   }
                  { type: '/mwTransclusionInline' }
                 ]
                 ]
               ],
               }
              supportedSelections: ['linear']
             }
             }
           )
           };
        );


        // Define a ferramenta no menu Inserir do VisualEditor
           fragment.insertContent([transclusionNode, { type: '/mwTransclusionInline' }]);
        function VtmSecretVeTool() {
          return;
           VtmSecretVeTool.super.apply(this, arguments);
         }
         }
         OO.inheritClass(VtmSecretVeTool, ve.ui.Tool);
      } catch (err) {
         VtmSecretVeTool.static.name = 'vtmSecret';
        if (window.console) console.warn('[VtM] Fallback de inserção:', err);
         VtmSecretVeTool.static.group = 'insert';
      }
         VtmSecretVeTool.static.icon = 'lock';
 
         VtmSecretVeTool.static.title = '🔒 Censura / Segredo (Confidencial)';
      var tb = document.getElementById('wpTextbox1');
         VtmSecretVeTool.static.commandName = 'insertVtmSecret';
      if (tb) {
        var s = tb.selectionStart, end = tb.selectionEnd;
        var t = tb.value, sel = t.substring(s, end) || 'Texto confidencial a ser censurado';
        var rep = '{{Segredo|' + sel + '|liberado=nao}}';
         tb.value = t.substring(0, s) + rep + t.substring(end);
        tb.focus();
        tb.setSelectionRange(s + 10, s + 10 + sel.length);
        if (window.$) { $(tb).trigger('input').trigger('change'); }
      }
    };
 
    btnGroup.appendChild(btn);
 
    var insertGroup = toolbar.querySelector('.ve-ui-toolbar-group-insert, .oo-ui-toolGroup:last-child');
    if (insertGroup && insertGroup.parentNode) {
      insertGroup.parentNode.insertBefore(btnGroup, insertGroup.nextSibling);
    } else {
      toolbar.appendChild(btnGroup);
    }
  }
 
  /* ------------------------------------------------------------------
    3. BOTÕES CENSURADO/LIBERADO NO MODAL DO VISUALEDITOR
    ------------------------------------------------------------------ */
  function transformLiberadoInputs() {
    var inputs = document.querySelectorAll(
      '.oo-ui-windowManager input, .oo-ui-windowManager textarea, ' +
      '.oo-ui-fieldLayout input, .oo-ui-fieldLayout textarea'
    );
    inputs.forEach(function (input) {
      var container = input.closest('.oo-ui-fieldLayout') ||
                      input.closest('.ve-ui-mwParameterPage') ||
                      input.closest('.oo-ui-pageLayout') ||
                      input.parentElement;
      if (!container) return;
      var txt = (container.textContent || '').toLowerCase();
      if (txt.indexOf('liberado') === -1) return;
      if (container.querySelector('.vtm-toggle-box')) return;
 
      var box = document.createElement('div');
      box.className = 'vtm-toggle-box';
      box.style.cssText = 'display:flex;gap:12px;margin:10px 0 16px;width:100%;max-width:460px;';
 
      var btnC = mkToggle('CENSURADO');
      var btnL = mkToggle('LIBERADO');
 
      function paint(v) {
        var isLib = (v === 'sim' || v === '1' || v === 'true');
        btnC.style.background = isLib ? '#121218' : 'linear-gradient(180deg,#5c0f18,#2b060a)';
         btnC.style.color = isLib ? '#7a7a88' : '#fff';
         btnC.style.border = isLib ? '1px solid #2d2d38' : '2px solid #ff2a3f';
         btnC.style.boxShadow = isLib ? 'none' : '0 0 12px rgba(255,42,63,.7)';
         btnL.style.background = isLib ? 'linear-gradient(180deg,#1b5e20,#0d3814)' : '#121218';
        btnL.style.color = isLib ? '#fff' : '#7a7a88';
         btnL.style.border = isLib ? '2px solid #4caf50' : '1px solid #2d2d38';
        btnL.style.boxShadow = isLib ? '0 0 12px rgba(76,175,80,.7)' : 'none';
      }
 
      btnC.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'nao'); paint('nao'); };
      btnL.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'sim'); paint('sim'); };
      input.addEventListener('input', function () { paint((input.value || '').toLowerCase().trim()); });
      paint((input.value || 'nao').toLowerCase().trim());


        ve.ui.toolFactory.register(VtmSecretVeTool);
      box.appendChild(btnC);
       });
      box.appendChild(btnL);
      var wrap = input.closest('.oo-ui-textInputWidget') || input.closest('.oo-ui-inputWidget') || input;
      wrap.parentNode.insertBefore(box, wrap);
       wrap.style.display = 'none';
     });
     });
   }
   }


  // ------------------------------------------------------------------------
   function mkToggle(label) {
  // 3. BARRA RÁPIDA GÓTICA NO EDITOR DE CÓDIGO-FONTE (#wpTextbox1)
     var b = document.createElement('button');
  // ------------------------------------------------------------------------
     b.type = 'button';
   function initVtmQuickToolbar() {
    b.textContent = (label === 'CENSURADO' ? '⬛ ' : '🔓 ') + label;
     var textbox = document.getElementById('wpTextbox1');
     b.style.cssText = 'flex:1;padding:10px 16px;border-radius:4px;font-weight:800;font-size:.9em;cursor:pointer;font-family:"Cinzel",Georgia,serif;letter-spacing:1px;transition:all .25s ease;outline:none;';
     if (!textbox) return;
    return b;
     if (document.getElementById('vtm-editor-quickbar')) return;
  }


    var bar = document.createElement('div');
  function setVal(input, v) {
     bar.id = 'vtm-editor-quickbar';
     input.value = v;
     bar.className = 'vtm-editor-quickbar';
     input.dispatchEvent(new Event('input', { bubbles: true }));
     bar.style.cssText = 'display:flex; flex-wrap:wrap; gap:8px; align-items:center; padding:10px 14px; margin:10px 0; background:linear-gradient(90deg, #181014 0%, #0d0d12 100%); border:1px solid #3d0a12; border-left:4px solid #8b0000; border-radius:4px; font-family:"Cinzel", Georgia, serif; box-shadow:0 4px 14px rgba(0,0,0,0.6); z-index:99;';
     input.dispatchEvent(new Event('change', { bubbles: true }));
    if (window.$) { $(input).trigger('input').trigger('change'); }
  }


    // Rótulo da Barra
  /* --- MutationObserver para transformLiberadoInputs (substitui setInterval) --- */
    var label = document.createElement('span');
  function setupTransformObserver() {
    label.style.cssText = 'color:#c5a059; font-size:0.85em; font-weight:700; text-transform:uppercase; letter-spacing:1px; margin-right:6px;';
     if (!document.body) return;
    label.innerHTML = '🩸 Ferramentas Góticas:';
     bar.appendChild(label);


    // Botão 1: Inline
     var lastRun = 0;
     var btnInline = document.createElement('button');
     var pending = false;
    btnInline.type = 'button';
    btnInline.className = 'vtm-quick-btn';
    btnInline.style.cssText = 'background:#240c12; color:#ffffff; border:1px solid #ff2a3f; border-radius:3px; padding:6px 12px; font-size:0.82em; font-weight:700; cursor:pointer; display:inline-flex; align-items:center; gap:6px; transition:all 0.2s ease; box-shadow:0 0 8px rgba(201,24,43,0.4);';
     btnInline.innerHTML = '🔒 Inserir Tarja de Censura (Segredo)';
    btnInline.title = 'Envolve o texto selecionado na predefinição {{Segredo}}';


     btnInline.onmouseover = function () {
     function throttledTransform() {
       btnInline.style.background = '#4a0f1b';
       var now = Date.now();
       btnInline.style.boxShadow = '0 0 14px rgba(255,42,63,0.85)';
       if (now - lastRun < 300) {
    };
        if (!pending) {
    btnInline.onmouseout = function () {
          pending = true;
      btnInline.style.background = '#240c12';
          setTimeout(function () {
      btnInline.style.boxShadow = '0 0 8px rgba(201,24,43,0.4)';
            pending = false;
    };
            lastRun = Date.now();
    btnInline.onclick = function (e) {
            transformLiberadoInputs();
       e.preventDefault();
          }, 300 - (now - lastRun));
       insertSecretTemplate(textbox, false);
        }
     };
        return;
    bar.appendChild(btnInline);
      }
       lastRun = now;
       transformLiberadoInputs();
     }


     // Botão 2: Bloco
     var observer = new MutationObserver(function (mutations) {
    var btnBlock = document.createElement('button');
      for (var i = 0; i < mutations.length; i++) {
    btnBlock.type = 'button';
        var addedNodes = mutations[i].addedNodes;
    btnBlock.className = 'vtm-quick-btn';
        for (var j = 0; j < addedNodes.length; j++) {
    btnBlock.style.cssText = 'background:#181822; color:#c5a059; border:1px solid #3d0a12; border-radius:3px; padding:6px 12px; font-size:0.82em; font-weight:700; cursor:pointer; display:inline-flex; align-items:center; gap:6px; transition:all 0.2s ease;';
          var node = addedNodes[j];
    btnBlock.innerHTML = '📁 Dossiê Confidencial (Bloco)';
          if (node.nodeType === 1) {
     btnBlock.title = 'Insere relatório confidencial em formato de bloco';
            if (node.classList && (node.classList.contains('oo-ui-windowManager') || node.classList.contains('oo-ui-window-frame'))) {
              throttledTransform();
              return;
            }
            if (node.querySelector && (node.querySelector('.oo-ui-windowManager') || node.querySelector('.oo-ui-window-frame'))) {
              throttledTransform();
              return;
            }
          }
        }
      }
     });


     btnBlock.onmouseover = function () {
     observer.observe(document.body, { childList: true, subtree: true });
      btnBlock.style.background = '#281c20';
  }
      btnBlock.style.borderColor = '#c5a059';
    };
    btnBlock.onmouseout = function () {
      btnBlock.style.background = '#181822';
      btnBlock.style.borderColor = '#3d0a12';
    };
    btnBlock.onclick = function (e) {
      e.preventDefault();
      insertSecretTemplate(textbox, true);
    };
    bar.appendChild(btnBlock);


     textbox.parentNode.insertBefore(bar, textbox);
  /* ------------------------------------------------------------------
  }
    4. BARRA DE FERRAMENTAS NO EDITOR DE CÓDIGO-FONTE (#wpTextbox1)
    ------------------------------------------------------------------ */
  function initVtmQuickToolbar() {
     var tb = document.getElementById('wpTextbox1');
    if (!tb || document.getElementById('vtm-editor-quickbar')) return;


  function insertSecretTemplate(textbox, isBlock) {
     var bar = document.createElement('div');
     var start = textbox.selectionStart;
     bar.id = 'vtm-editor-quickbar';
     var end = textbox.selectionEnd;
     bar.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 14px;margin:10px 0;background:linear-gradient(90deg,#181014,#0d0d12);border:1px solid #3d0a12;border-left:4px solid #8b0000;border-radius:4px;font-family:"Cinzel",Georgia,serif;box-shadow:0 4px 14px rgba(0,0,0,.6);z-index:99;';
     var text = textbox.value;
    var selected = text.substring(start, end);


     var pre = isBlock ? '\n{{Segredo|tipo=bloco|nivel=RELATÓRIO SAD // CONFIDENCIAL|data=AAAA-MM-DD|dica=Alvo Restrito|\n' : '{{Segredo|';
     var lbl = document.createElement('span');
     var defaultText = selected || (isBlock ? 'Escreva aqui o relatório confidencial...' : 'Texto sensível a ser censurado');
     lbl.style.cssText = 'color:#c5a059;font-size:.85em;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-right:6px;';
     var post = isBlock ? '\n}}\n' : '|data=AAAA-MM-DD|liberado=nao}}';
     lbl.textContent = '🩸 Ferramentas de Censura:';
    bar.appendChild(lbl);


     var replacement = pre + defaultText + post;
     bar.appendChild(mkBtn('⬛ Censurar Trecho', '#240c12', '#ff2a3f', function () {
     textbox.value = text.substring(0, start) + replacement + text.substring(end);
      wrapSel(tb, '{{Segredo|', '|liberado=nao}}', 'Texto censurado');
    }));
     bar.appendChild(mkBtn('🔓 Inserir Liberado', '#0d2915', '#2e7d32', function () {
      wrapSel(tb, '{{Segredo|', '|liberado=sim}}', 'Texto revelado');
    }));
    bar.appendChild(mkBtn('📁 Dossie Bloco', '#181822', '#3d0a12', function () {
      wrapSel(tb, '\n{{Segredo|tipo=bloco|nivel=CONFIDENCIAL|data=AAAA-MM-DD|dica=Operacao|\n', '\n}}\n', 'Relatorio confidencial...');
    }));
    bar.appendChild(mkBtn('🗑 Remover Censura', '#1a1a22', '#4a4a58', function () {
      removeSecret(tb);
    }));


     var newCursor = start + pre.length;
     tb.parentNode.insertBefore(bar, tb);
    textbox.focus();
  }
    textbox.setSelectionRange(newCursor, newCursor + defaultText.length);


     if (window.$) {
  function mkBtn(text, bg, border, fn) {
      $(textbox).trigger('input').trigger('change');
     var b = document.createElement('button');
     }
    b.type = 'button'; b.textContent = text;
    b.style.cssText = 'background:' + bg + ';color:#fff;border:1px solid ' + border + ';border-radius:3px;padding:6px 12px;font-size:.82em;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;gap:4px;transition:all .2s ease;';
    b.onmouseover = function () { b.style.opacity = '.85'; };
    b.onmouseout  = function () { b.style.opacity = '1'; };
    b.onclick = function (e) { e.preventDefault(); fn(); };
     return b;
   }
   }


   // ------------------------------------------------------------------------
   function wrapSel(tb, pre, post, ph) {
  // 4. ANIMAÇÃO DE CASCATA DAS SEÇÕES (.vtm-section-block)
    var s = tb.selectionStart, e = tb.selectionEnd;
  // ------------------------------------------------------------------------
    var t = tb.value, sel = t.substring(s, e) || ph;
  function initSectionAnimation() {
    tb.value = t.substring(0, s) + pre + sel + post + t.substring(e);
     var content = document.querySelector('.mw-parser-output');
     tb.focus(); tb.setSelectionRange(s + pre.length, s + pre.length + sel.length);
     if (!content || content.dataset.vtmAnimated === 'true') return;
     if (window.$) { $(tb).trigger('input').trigger('change'); }
  }


     if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  function removeSecret(tb) {
       return;
    var s = tb.selectionStart, text = tb.value;
    var i = text.lastIndexOf('{{Segredo|', s);
     if (i === -1) { alert('Nenhum {{Segredo|...}} encontrado.\nPosicione o cursor dentro do trecho.'); return; }
    var depth = 0, j, end = -1;
    for (j = i; j < text.length - 1; j++) {
      if (text[j] === '{' && text[j + 1] === '{') { depth++; j++; }
      else if (text[j] === '}' && text[j + 1] === '}') { depth--; if (depth === 0) { end = j + 2; break; } j++; }
    }
    if (end === -1) { alert('Fechamento }} nao encontrado.'); return; }
    var inner = text.substring(i + '{{Segredo|'.length, end - 2);
    var parts = [], cur = '', d = 0;
    for (j = 0; j < inner.length; j++) {
      if (inner[j] === '{' && j + 1 < inner.length && inner[j + 1] === '{') { d++; cur += '{{'; j++; }
      else if (inner[j] === '}' && j + 1 < inner.length && inner[j + 1] === '}') { d--; cur += '}}'; j++; }
      else if (inner[j] === '|' && d === 0) { parts.push(cur); cur = ''; }
      else { cur += inner[j]; }
    }
    parts.push(cur);
    var content = '';
    for (var k = 0; k < parts.length; k++) {
       if (parts[k].trim().indexOf('=') === -1) { content = parts[k].trim(); break; }
     }
     }
    tb.value = text.substring(0, i) + content + text.substring(end);
    tb.focus(); tb.setSelectionRange(i, i + content.length);
    if (window.$) { $(tb).trigger('input').trigger('change'); }
  }


     content.dataset.vtmAnimated = 'true';
  /* ------------------------------------------------------------------
     var children = Array.from(content.children);
    5. ANIMAÇÃO DE CASCATA DAS SEÇÕES
    var sections = [];
    ------------------------------------------------------------------ */
    var currentSection = [];
  function initSectionAnimation() {
 
     var c = document.querySelector('.mw-parser-output');
     children.forEach(function (child) {
    if (!c || c.dataset.vtmAnimated === 'true') return;
       if (
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
        child.classList.contains('catlinks') ||
    c.dataset.vtmAnimated = 'true';
        child.classList.contains('mw-category-generated') ||
     var kids = Array.from(c.children), secs = [], cur = [];
        child.tagName === 'SCRIPT' ||
     kids.forEach(function (ch) {
        child.tagName === 'STYLE' ||
       if (ch.classList.contains('catlinks') || ch.classList.contains('mw-category-generated') ||
        child.id === 'vtm-editor-quickbar'
          ch.tagName === 'SCRIPT' || ch.tagName === 'STYLE' || ch.id === 'vtm-editor-quickbar') {
      ) {
         if (cur.length) { secs.push(cur); cur = []; }
         if (currentSection.length > 0) {
          sections.push(currentSection);
          currentSection = [];
        }
         return;
         return;
       }
       }
      if ((ch.tagName === 'H2' || ch.classList.contains('mw-heading2')) && cur.length) { secs.push(cur); cur = []; }
      cur.push(ch);
    });
    if (cur.length) secs.push(cur);
    secs.forEach(function (items, idx) {
      var w = document.createElement('div');
      w.className = 'vtm-section-block';
      w.style.animationDelay = (0.12 + idx * 0.42).toFixed(2) + 's';
      var f = items[0];
      if (f && f.parentNode) { f.parentNode.insertBefore(w, f); items.forEach(function (el) { w.appendChild(el); }); }
    });
  }


      var isH2 = child.tagName === 'H2' || child.classList.contains('mw-heading2');
  /* ------------------------------------------------------------------
      if (isH2 && currentSection.length > 0) {
    6. SUPORTE A TOQUE PARA MENU HAMBÚRGUER NO MOBILE
        sections.push(currentSection);
    ------------------------------------------------------------------ */
        currentSection = [];
  function initMobileMenuHelper() {
      }
    var dropdown = document.getElementById('vector-main-menu-dropdown');
      currentSection.push(child);
    var checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
     });
    var label = document.getElementById('vector-main-menu-dropdown-label');
     if (!dropdown || !checkbox) return;


     if (currentSection.length > 0) {
     if (label && !label.dataset.vtmBound) {
       sections.push(currentSection);
       label.dataset.vtmBound = 'true';
      label.addEventListener('click', function () {
        setTimeout(function () {
          if (checkbox.checked) {
            dropdown.classList.add('vector-dropdown--open');
          } else {
            dropdown.classList.remove('vector-dropdown--open');
          }
        }, 10);
      });
     }
     }


     sections.forEach(function (sectionElements, index) {
     document.addEventListener('click', function (e) {
      var wrapper = document.createElement('div');
       if (checkbox.checked && !dropdown.contains(e.target)) {
      wrapper.className = 'vtm-section-block';
         checkbox.checked = false;
      var delay = (0.12 + index * 0.42).toFixed(2);
         dropdown.classList.remove('vector-dropdown--open');
      wrapper.style.animationDelay = delay + 's';
 
      var firstElem = sectionElements[0];
       if (firstElem && firstElem.parentNode) {
         firstElem.parentNode.insertBefore(wrapper, firstElem);
         sectionElements.forEach(function (el) {
          wrapper.appendChild(el);
        });
       }
       }
     });
     });
   }
   }


   // Execução
   /* ------------------------------------------------------------------
    INICIALIZAÇÃO GERAL
    ------------------------------------------------------------------ */
   function initAll() {
   function initAll() {
    fixMobileViewport();
     initVtmSecrets();
     initVtmSecrets();
     initSectionAnimation();
     initSectionAnimation();
     initVtmQuickToolbar();
     initVtmQuickToolbar();
     setupVisualEditorTool();
     injectVisualEditorButton();
    initMobileMenuHelper();
    setupTransformObserver();
   }
   }


Line 288: Line 446:
   }
   }


   if (window.mw && window.mw.hook) {
   /* --- Polling com auto-limpeza para botões de censura nos editores --- */
  var veButtonInterval = setInterval(function () {
    if (!document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools')) return;
    injectVisualEditorButton();
    if (document.getElementById('vtm-ve-censor-button')) {
      clearInterval(veButtonInterval);
    }
  }, 400);
 
  var quickbarInterval = setInterval(function () {
    if (!document.getElementById('wpTextbox1')) return;
    initVtmQuickToolbar();
    if (document.getElementById('vtm-editor-quickbar')) {
      clearInterval(quickbarInterval);
    }
  }, 400);
 
  /* --- Hooks do MediaWiki para recarregamento de conteúdo --- */
  function registerHooks() {
    if (!window.mw || !window.mw.hook) return;
     window.mw.hook('wikipage.content').add(function () {
     window.mw.hook('wikipage.content').add(function () {
       initVtmSecrets();
       initVtmSecrets();
       initSectionAnimation();
       initSectionAnimation();
      initMobileMenuHelper();
     });
     });
     window.mw.hook('ve.activationComplete').add(function () {
     window.mw.hook('ve.activationComplete').add(function () {
       setupVisualEditorTool();
       pollUntilFound(function () {
        return document.querySelector('.ve-ui-toolbar-tools');
      }, function () {
        injectVisualEditorButton();
      });
    });
    window.mw.hook('wikipage.editform').add(function () {
      initVtmQuickToolbar();
    });
  }
 
  if (window.mw && window.mw.hook) {
    registerHooks();
  } else {
    pollUntilFound(function () {
      return window.mw && window.mw.hook;
    }, function () {
      registerHooks();
     });
     });
   }
   }
})();
})();

Latest revision as of 22:52, 1 September 2026

/* ==========================================================================
   VAMPIRO: A MÁSCARA — SCRIPTS GLOBAIS, VIEWPORT RESPONSIVO & SEGREDO DINÂMICO
   ========================================================================== */

(function () {
  'use strict';

  /* ------------------------------------------------------------------
     UTILITÁRIO: POLLING LIMITADO (substitui setInterval infinito)
     ------------------------------------------------------------------ */
  function pollUntilFound(checkFn, callback, maxAttempts) {
    var attempts = 0;
    var max = maxAttempts || 50;
    var intervalId = setInterval(function () {
      attempts++;
      var result = checkFn();
      if (result) {
        clearInterval(intervalId);
        callback(result);
      } else if (attempts >= max) {
        clearInterval(intervalId);
      }
    }, 400);
    return intervalId;
  }

  /* ------------------------------------------------------------------
     0. VIEWPORT RESPONSIVO REAL PARA MOBILE (SUBSTITUI WIDTH=1120)
     ------------------------------------------------------------------ */
  function fixMobileViewport() {
    var meta = document.querySelector('meta[name="viewport"]');
    if (meta) {
      meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover');
    } else {
      meta = document.createElement('meta');
      meta.name = 'viewport';
      meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover';
      document.head.appendChild(meta);
    }
  }
  fixMobileViewport();

  /* ------------------------------------------------------------------
     1. CENSURA & REVELAÇÃO DINÂMICA (LEITURA)
     ------------------------------------------------------------------ */
  function initVtmSecrets() {
    var els = document.querySelectorAll('[data-vtm-secret="true"]');
    if (!els || !els.length) return;
    var now = new Date();

    els.forEach(function (el) {
      var lib = (el.getAttribute('data-liberado') || '').toLowerCase().trim();
      var ds = (el.getAttribute('data-reveal-date') || '').trim();
      var dica = (el.getAttribute('data-dica') || '').trim();
      var revealed = false;
      var tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA]';

      if (lib === 'sim' || lib === 'true' || lib === '1') {
        revealed = true;
      } else if (lib === 'nao' || lib === 'false' || lib === '0') {
        tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';
      } else if (ds) {
        var td = null;
        if (ds.indexOf('/') !== -1) {
          var p = ds.split('/');
          if (p.length === 3) td = new Date(p[2], parseInt(p[1], 10) - 1, p[0]);
        } else {
          td = new Date(ds);
        }
        if (td && !isNaN(td.getTime())) {
          if (now >= td) {
            revealed = true;
          } else {
            var f = ('0' + td.getDate()).slice(-2) + '/' + ('0' + (td.getMonth() + 1)).slice(-2) + '/' + td.getFullYear();
            tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ' - ' + f + ']';
          }
        }
      }

      if (revealed) {
        el.classList.add('is-revealed');
        el.classList.remove('is-redacted');
        el.title = '[DESCLASSIFICADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';
      } else {
        el.classList.add('is-redacted');
        el.classList.remove('is-revealed');
        el.title = tip;
      }
    });
  }

  /* ------------------------------------------------------------------
     2. INJEÇÃO DO BOTÃO "🔒 CENSURAR" NA BARRA DO VISUALEDITOR
     ------------------------------------------------------------------ */
  function injectVisualEditorButton() {
    var toolbar = document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools');
    if (!toolbar) return;

    if (document.getElementById('vtm-ve-censor-button')) return;

    var btnGroup = document.createElement('div');
    btnGroup.id = 'vtm-ve-censor-button';
    btnGroup.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-toolGroup';
    btnGroup.style.cssText = 'display:inline-flex; align-items:center; margin:0 6px;';

    var btn = document.createElement('button');
    btn.type = 'button';
    btn.innerHTML = '🔒 Censurar Trecho';
    btn.title = 'Aplicar tarja de censura {{Segredo}} no texto selecionado';
    btn.style.cssText = 'background:linear-gradient(180deg, #450810 0%, #1a0306 100%); color:#ffffff; border:1px solid #ff2a3f; border-radius:4px; padding:4px 10px; font-family:"Cinzel", Georgia, serif; font-size:0.82em; font-weight:700; cursor:pointer; box-shadow:0 0 8px rgba(255,42,63,0.4); display:flex; align-items:center; gap:4px; transition:all 0.2s ease; height:32px; line-height:1; outline:none;';

    btn.onmouseover = function () {
      btn.style.background = 'linear-gradient(180deg, #6b0c19 0%, #30050c 100%)';
      btn.style.boxShadow = '0 0 14px rgba(255,42,63,0.85)';
    };
    btn.onmouseout = function () {
      btn.style.background = 'linear-gradient(180deg, #450810 0%, #1a0306 100%)';
      btn.style.boxShadow = '0 0 8px rgba(255,42,63,0.4)';
    };

    btn.onclick = function (e) {
      e.preventDefault();
      e.stopPropagation();

      try {
        var target = ve.init.target;
        if (target && target.getSurface()) {
          var surfaceModel = target.getSurface().getModel();
          var fragment = surfaceModel.getFragment();
          var selectedText = fragment.getText();

          var contentToCensor = selectedText || 'Texto confidencial a ser censurado';

          var transclusionNode = {
            type: 'mwTransclusionInline',
            attributes: {
              mw: {
                parts: [
                  {
                    template: {
                      target: { href: 'Template:Segredo', wt: 'Segredo' },
                      params: {
                        '1': { wt: contentToCensor },
                        'liberado': { wt: 'nao' }
                      }
                    }
                  }
                ]
              }
            }
          };

          fragment.insertContent([transclusionNode, { type: '/mwTransclusionInline' }]);
          return;
        }
      } catch (err) {
        if (window.console) console.warn('[VtM] Fallback de inserção:', err);
      }

      var tb = document.getElementById('wpTextbox1');
      if (tb) {
        var s = tb.selectionStart, end = tb.selectionEnd;
        var t = tb.value, sel = t.substring(s, end) || 'Texto confidencial a ser censurado';
        var rep = '{{Segredo|' + sel + '|liberado=nao}}';
        tb.value = t.substring(0, s) + rep + t.substring(end);
        tb.focus();
        tb.setSelectionRange(s + 10, s + 10 + sel.length);
        if (window.$) { $(tb).trigger('input').trigger('change'); }
      }
    };

    btnGroup.appendChild(btn);

    var insertGroup = toolbar.querySelector('.ve-ui-toolbar-group-insert, .oo-ui-toolGroup:last-child');
    if (insertGroup && insertGroup.parentNode) {
      insertGroup.parentNode.insertBefore(btnGroup, insertGroup.nextSibling);
    } else {
      toolbar.appendChild(btnGroup);
    }
  }

  /* ------------------------------------------------------------------
     3. BOTÕES CENSURADO/LIBERADO NO MODAL DO VISUALEDITOR
     ------------------------------------------------------------------ */
  function transformLiberadoInputs() {
    var inputs = document.querySelectorAll(
      '.oo-ui-windowManager input, .oo-ui-windowManager textarea, ' +
      '.oo-ui-fieldLayout input, .oo-ui-fieldLayout textarea'
    );
    inputs.forEach(function (input) {
      var container = input.closest('.oo-ui-fieldLayout') ||
                      input.closest('.ve-ui-mwParameterPage') ||
                      input.closest('.oo-ui-pageLayout') ||
                      input.parentElement;
      if (!container) return;
      var txt = (container.textContent || '').toLowerCase();
      if (txt.indexOf('liberado') === -1) return;
      if (container.querySelector('.vtm-toggle-box')) return;

      var box = document.createElement('div');
      box.className = 'vtm-toggle-box';
      box.style.cssText = 'display:flex;gap:12px;margin:10px 0 16px;width:100%;max-width:460px;';

      var btnC = mkToggle('CENSURADO');
      var btnL = mkToggle('LIBERADO');

      function paint(v) {
        var isLib = (v === 'sim' || v === '1' || v === 'true');
        btnC.style.background = isLib ? '#121218' : 'linear-gradient(180deg,#5c0f18,#2b060a)';
        btnC.style.color = isLib ? '#7a7a88' : '#fff';
        btnC.style.border = isLib ? '1px solid #2d2d38' : '2px solid #ff2a3f';
        btnC.style.boxShadow = isLib ? 'none' : '0 0 12px rgba(255,42,63,.7)';
        btnL.style.background = isLib ? 'linear-gradient(180deg,#1b5e20,#0d3814)' : '#121218';
        btnL.style.color = isLib ? '#fff' : '#7a7a88';
        btnL.style.border = isLib ? '2px solid #4caf50' : '1px solid #2d2d38';
        btnL.style.boxShadow = isLib ? '0 0 12px rgba(76,175,80,.7)' : 'none';
      }

      btnC.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'nao'); paint('nao'); };
      btnL.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'sim'); paint('sim'); };
      input.addEventListener('input', function () { paint((input.value || '').toLowerCase().trim()); });
      paint((input.value || 'nao').toLowerCase().trim());

      box.appendChild(btnC);
      box.appendChild(btnL);
      var wrap = input.closest('.oo-ui-textInputWidget') || input.closest('.oo-ui-inputWidget') || input;
      wrap.parentNode.insertBefore(box, wrap);
      wrap.style.display = 'none';
    });
  }

  function mkToggle(label) {
    var b = document.createElement('button');
    b.type = 'button';
    b.textContent = (label === 'CENSURADO' ? '⬛ ' : '🔓 ') + label;
    b.style.cssText = 'flex:1;padding:10px 16px;border-radius:4px;font-weight:800;font-size:.9em;cursor:pointer;font-family:"Cinzel",Georgia,serif;letter-spacing:1px;transition:all .25s ease;outline:none;';
    return b;
  }

  function setVal(input, v) {
    input.value = v;
    input.dispatchEvent(new Event('input', { bubbles: true }));
    input.dispatchEvent(new Event('change', { bubbles: true }));
    if (window.$) { $(input).trigger('input').trigger('change'); }
  }

  /* --- MutationObserver para transformLiberadoInputs (substitui setInterval) --- */
  function setupTransformObserver() {
    if (!document.body) return;

    var lastRun = 0;
    var pending = false;

    function throttledTransform() {
      var now = Date.now();
      if (now - lastRun < 300) {
        if (!pending) {
          pending = true;
          setTimeout(function () {
            pending = false;
            lastRun = Date.now();
            transformLiberadoInputs();
          }, 300 - (now - lastRun));
        }
        return;
      }
      lastRun = now;
      transformLiberadoInputs();
    }

    var observer = new MutationObserver(function (mutations) {
      for (var i = 0; i < mutations.length; i++) {
        var addedNodes = mutations[i].addedNodes;
        for (var j = 0; j < addedNodes.length; j++) {
          var node = addedNodes[j];
          if (node.nodeType === 1) {
            if (node.classList && (node.classList.contains('oo-ui-windowManager') || node.classList.contains('oo-ui-window-frame'))) {
              throttledTransform();
              return;
            }
            if (node.querySelector && (node.querySelector('.oo-ui-windowManager') || node.querySelector('.oo-ui-window-frame'))) {
              throttledTransform();
              return;
            }
          }
        }
      }
    });

    observer.observe(document.body, { childList: true, subtree: true });
  }

  /* ------------------------------------------------------------------
     4. BARRA DE FERRAMENTAS NO EDITOR DE CÓDIGO-FONTE (#wpTextbox1)
     ------------------------------------------------------------------ */
  function initVtmQuickToolbar() {
    var tb = document.getElementById('wpTextbox1');
    if (!tb || document.getElementById('vtm-editor-quickbar')) return;

    var bar = document.createElement('div');
    bar.id = 'vtm-editor-quickbar';
    bar.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 14px;margin:10px 0;background:linear-gradient(90deg,#181014,#0d0d12);border:1px solid #3d0a12;border-left:4px solid #8b0000;border-radius:4px;font-family:"Cinzel",Georgia,serif;box-shadow:0 4px 14px rgba(0,0,0,.6);z-index:99;';

    var lbl = document.createElement('span');
    lbl.style.cssText = 'color:#c5a059;font-size:.85em;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-right:6px;';
    lbl.textContent = '🩸 Ferramentas de Censura:';
    bar.appendChild(lbl);

    bar.appendChild(mkBtn('⬛ Censurar Trecho', '#240c12', '#ff2a3f', function () {
      wrapSel(tb, '{{Segredo|', '|liberado=nao}}', 'Texto censurado');
    }));
    bar.appendChild(mkBtn('🔓 Inserir Liberado', '#0d2915', '#2e7d32', function () {
      wrapSel(tb, '{{Segredo|', '|liberado=sim}}', 'Texto revelado');
    }));
    bar.appendChild(mkBtn('📁 Dossie Bloco', '#181822', '#3d0a12', function () {
      wrapSel(tb, '\n{{Segredo|tipo=bloco|nivel=CONFIDENCIAL|data=AAAA-MM-DD|dica=Operacao|\n', '\n}}\n', 'Relatorio confidencial...');
    }));
    bar.appendChild(mkBtn('🗑 Remover Censura', '#1a1a22', '#4a4a58', function () {
      removeSecret(tb);
    }));

    tb.parentNode.insertBefore(bar, tb);
  }

  function mkBtn(text, bg, border, fn) {
    var b = document.createElement('button');
    b.type = 'button'; b.textContent = text;
    b.style.cssText = 'background:' + bg + ';color:#fff;border:1px solid ' + border + ';border-radius:3px;padding:6px 12px;font-size:.82em;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;gap:4px;transition:all .2s ease;';
    b.onmouseover = function () { b.style.opacity = '.85'; };
    b.onmouseout  = function () { b.style.opacity = '1'; };
    b.onclick = function (e) { e.preventDefault(); fn(); };
    return b;
  }

  function wrapSel(tb, pre, post, ph) {
    var s = tb.selectionStart, e = tb.selectionEnd;
    var t = tb.value, sel = t.substring(s, e) || ph;
    tb.value = t.substring(0, s) + pre + sel + post + t.substring(e);
    tb.focus(); tb.setSelectionRange(s + pre.length, s + pre.length + sel.length);
    if (window.$) { $(tb).trigger('input').trigger('change'); }
  }

  function removeSecret(tb) {
    var s = tb.selectionStart, text = tb.value;
    var i = text.lastIndexOf('{{Segredo|', s);
    if (i === -1) { alert('Nenhum {{Segredo|...}} encontrado.\nPosicione o cursor dentro do trecho.'); return; }
    var depth = 0, j, end = -1;
    for (j = i; j < text.length - 1; j++) {
      if (text[j] === '{' && text[j + 1] === '{') { depth++; j++; }
      else if (text[j] === '}' && text[j + 1] === '}') { depth--; if (depth === 0) { end = j + 2; break; } j++; }
    }
    if (end === -1) { alert('Fechamento }} nao encontrado.'); return; }
    var inner = text.substring(i + '{{Segredo|'.length, end - 2);
    var parts = [], cur = '', d = 0;
    for (j = 0; j < inner.length; j++) {
      if (inner[j] === '{' && j + 1 < inner.length && inner[j + 1] === '{') { d++; cur += '{{'; j++; }
      else if (inner[j] === '}' && j + 1 < inner.length && inner[j + 1] === '}') { d--; cur += '}}'; j++; }
      else if (inner[j] === '|' && d === 0) { parts.push(cur); cur = ''; }
      else { cur += inner[j]; }
    }
    parts.push(cur);
    var content = '';
    for (var k = 0; k < parts.length; k++) {
      if (parts[k].trim().indexOf('=') === -1) { content = parts[k].trim(); break; }
    }
    tb.value = text.substring(0, i) + content + text.substring(end);
    tb.focus(); tb.setSelectionRange(i, i + content.length);
    if (window.$) { $(tb).trigger('input').trigger('change'); }
  }

  /* ------------------------------------------------------------------
     5. ANIMAÇÃO DE CASCATA DAS SEÇÕES
     ------------------------------------------------------------------ */
  function initSectionAnimation() {
    var c = document.querySelector('.mw-parser-output');
    if (!c || c.dataset.vtmAnimated === 'true') return;
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    c.dataset.vtmAnimated = 'true';
    var kids = Array.from(c.children), secs = [], cur = [];
    kids.forEach(function (ch) {
      if (ch.classList.contains('catlinks') || ch.classList.contains('mw-category-generated') ||
          ch.tagName === 'SCRIPT' || ch.tagName === 'STYLE' || ch.id === 'vtm-editor-quickbar') {
        if (cur.length) { secs.push(cur); cur = []; }
        return;
      }
      if ((ch.tagName === 'H2' || ch.classList.contains('mw-heading2')) && cur.length) { secs.push(cur); cur = []; }
      cur.push(ch);
    });
    if (cur.length) secs.push(cur);
    secs.forEach(function (items, idx) {
      var w = document.createElement('div');
      w.className = 'vtm-section-block';
      w.style.animationDelay = (0.12 + idx * 0.42).toFixed(2) + 's';
      var f = items[0];
      if (f && f.parentNode) { f.parentNode.insertBefore(w, f); items.forEach(function (el) { w.appendChild(el); }); }
    });
  }

  /* ------------------------------------------------------------------
     6. SUPORTE A TOQUE PARA MENU HAMBÚRGUER NO MOBILE
     ------------------------------------------------------------------ */
  function initMobileMenuHelper() {
    var dropdown = document.getElementById('vector-main-menu-dropdown');
    var checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
    var label = document.getElementById('vector-main-menu-dropdown-label');
    if (!dropdown || !checkbox) return;

    if (label && !label.dataset.vtmBound) {
      label.dataset.vtmBound = 'true';
      label.addEventListener('click', function () {
        setTimeout(function () {
          if (checkbox.checked) {
            dropdown.classList.add('vector-dropdown--open');
          } else {
            dropdown.classList.remove('vector-dropdown--open');
          }
        }, 10);
      });
    }

    document.addEventListener('click', function (e) {
      if (checkbox.checked && !dropdown.contains(e.target)) {
        checkbox.checked = false;
        dropdown.classList.remove('vector-dropdown--open');
      }
    });
  }

  /* ------------------------------------------------------------------
     INICIALIZAÇÃO GERAL
     ------------------------------------------------------------------ */
  function initAll() {
    fixMobileViewport();
    initVtmSecrets();
    initSectionAnimation();
    initVtmQuickToolbar();
    injectVisualEditorButton();
    initMobileMenuHelper();
    setupTransformObserver();
  }

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

  /* --- Polling com auto-limpeza para botões de censura nos editores --- */
  var veButtonInterval = setInterval(function () {
    if (!document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools')) return;
    injectVisualEditorButton();
    if (document.getElementById('vtm-ve-censor-button')) {
      clearInterval(veButtonInterval);
    }
  }, 400);

  var quickbarInterval = setInterval(function () {
    if (!document.getElementById('wpTextbox1')) return;
    initVtmQuickToolbar();
    if (document.getElementById('vtm-editor-quickbar')) {
      clearInterval(quickbarInterval);
    }
  }, 400);

  /* --- Hooks do MediaWiki para recarregamento de conteúdo --- */
  function registerHooks() {
    if (!window.mw || !window.mw.hook) return;
    window.mw.hook('wikipage.content').add(function () {
      initVtmSecrets();
      initSectionAnimation();
      initMobileMenuHelper();
    });
    window.mw.hook('ve.activationComplete').add(function () {
      pollUntilFound(function () {
        return document.querySelector('.ve-ui-toolbar-tools');
      }, function () {
        injectVisualEditorButton();
      });
    });
    window.mw.hook('wikipage.editform').add(function () {
      initVtmQuickToolbar();
    });
  }

  if (window.mw && window.mw.hook) {
    registerHooks();
  } else {
    pollUntilFound(function () {
      return window.mw && window.mw.hook;
    }, function () {
      registerHooks();
    });
  }
})();