Jump to content

MediaWiki:Common.js: Difference between revisions

From ICE List Wiki
Restrict Add-a-volunteer tool + link to ICEListAdmin6 only
Show My desk + Quick add on all ICEListAdmin profiles (not just reviewer groups)
Line 1,034: Line 1,034:
     if (pn !== 'User:' + user) { return; }
     if (pn !== 'User:' + user) { return; }
     var groups = mw.config.get('wgUserGroups') || [];
     var groups = mw.config.get('wgUserGroups') || [];
     if (!['sysop', 'bureaucrat', 'reviewer', 'editor'].some(function (g) { return groups.indexOf(g) >= 0; })) { return; }
     if (!/^ICEListAdmin[0-9]+$/.test(user) && !['sysop', 'bureaucrat', 'reviewer', 'editor'].some(function (g) { return groups.indexOf(g) >= 0; })) { return; }
     var content = document.querySelector('#mw-content-text .mw-parser-output');
     var content = document.querySelector('#mw-content-text .mw-parser-output');
     if (!content) { return; }
     if (!content) { return; }
Line 1,304: Line 1,304:
     if (pn !== 'User:' + user) { return; }
     if (pn !== 'User:' + user) { return; }
     var groups = mw.config.get('wgUserGroups') || [];
     var groups = mw.config.get('wgUserGroups') || [];
     if (!['sysop', 'bureaucrat', 'reviewer', 'editor'].some(function (g) { return groups.indexOf(g) >= 0; })) { return; }
     if (!/^ICEListAdmin[0-9]+$/.test(user) && !['sysop', 'bureaucrat', 'reviewer', 'editor'].some(function (g) { return groups.indexOf(g) >= 0; })) { return; }
     var content = document.querySelector('#mw-content-text .mw-parser-output');
     var content = document.querySelector('#mw-content-text .mw-parser-output');
     if (!content) { return; }
     if (!content) { return; }

Revision as of 19:16, 18 July 2026

/* Load CRUST brand fonts — ResourceLoader strips @import in Common.css, so inject the link here */
(function(){
  var l=document.createElement('link'); l.rel='stylesheet';
  l.href='https://fonts.googleapis.com/css2?family=Anton&family=Libre+Franklin:wght@400;500;600;700;800;900&family=Spline+Sans+Mono:wght@400;500;600&display=swap';
  document.head.appendChild(l);
})();

(function () {
  'use strict';

  var KEY = 'icelist_anon_donate_dismiss_until';
  var DAYS = 14;

  if ((mw.config.get('wgUserName')!==null)) return;

  var until = parseInt(localStorage.getItem(KEY) || '0', 10);
  if (Date.now() < until) return;

  function mount() {
    var siteNotice = document.getElementById('siteNotice');
    if (!siteNotice) return;

    // If empty, do nothing
    var txt = (siteNotice.textContent || '').replace(/\s+/g, ' ').trim();
    if (!txt) return;

    siteNotice.classList.add('icelist-anon-donate');

    // Add close button once
    if (!siteNotice.querySelector('.icelist-notice-close')) {
      var btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'icelist-notice-close';
      btn.setAttribute('aria-label', 'Close');
      btn.textContent = '×';
      siteNotice.insertBefore(btn, siteNotice.firstChild);

      btn.addEventListener('click', function (e) {
        e.preventDefault();
        siteNotice.style.display = 'none';
        localStorage.setItem(KEY, String(Date.now() + DAYS * 24 * 60 * 60 * 1000));
      });
    }
  }

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

mw.loader.using(['jquery']).then(function () {
    var s = document.createElement('script');
    s.src = 'https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js';
    s.setAttribute('data-name', 'BMC-Widget');
    s.setAttribute('data-cfasync', 'false');
    s.setAttribute('data-id', 'crustnews');
    s.setAttribute('data-description', 'Support me on Buy me a coffee!');
    s.setAttribute('data-message', '');
    s.setAttribute('data-color', '#FF5F5F');
    s.setAttribute('data-position', 'Right');
    s.setAttribute('data-x_margin', '18');
    s.setAttribute('data-y_margin', '18');
    document.body.appendChild(s);
});
// Tag agent pages (any page showing an agent card) so the duplicate title/heading can be hidden
if ( document.querySelector( '.ic-agent-card' ) ) {
    document.body.classList.add( 'ic-agent-page' );
}

/* ---- Mark anonymous users so anon-only banners can show (fail-safe: hidden by default) ---- */
if ((mw.config.get('wgUserName')===null)) { document.documentElement.classList.add('vol-anon'); }

/* ---- Incident candidates page: logged-in-only + per-row dismiss ---- */
if (/Incident_candidates$/.test(mw.config.get('wgPageName'))) {
  mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
    var content = document.querySelector('#mw-content-text .mw-parser-output');
    if (!content) return;
    if ((mw.config.get('wgUserName')===null)) {
      content.innerHTML = '<div class="vol-login">This page is for logged-in ICE List volunteers. ' +
        '<a href="' + mw.util.getUrl('Special:UserLogin', {returnto: mw.config.get('wgPageName')}) +
        '">Log in</a> to review incident candidates.</div>';
      return;
    }
    var api = new mw.Api();
    content.querySelectorAll('li').forEach(function (li) {
      var create = Array.prototype.find.call(li.querySelectorAll('a'), function (a) {
        return /FormEdit\/Incident/.test(a.getAttribute('href') || '');
      });
      if (!create) return;
      var art = Array.prototype.find.call(li.querySelectorAll('a[href^="http"]'), function (a) {
        return a !== create && !/FormEdit\/Incident/.test(a.getAttribute('href') || '');
      });
      var url = art ? art.href : null;
      var btn = document.createElement('button');
      btn.textContent = '✕ dismiss';
      btn.className = 'vol-dismiss';
      btn.onclick = function () {
        if (!url) { li.remove(); return; }
        btn.disabled = true; btn.textContent = '…';
        api.get({action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', formatversion: 2})
          .then(function (r) {
            var lines = r.parse.wikitext.split('\n'), out = [], removed = false;
            for (var i = 0; i < lines.length; i++) {
              if (!removed && lines[i].indexOf(url) !== -1) {
                if (out.length && /^<!-- cand:/.test(out[out.length - 1])) out.pop();
                removed = true; continue;
              }
              out.push(lines[i]);
            }
            return api.postWithToken('csrf', {action: 'edit', title: mw.config.get('wgPageName'),
              text: out.join('\n'), summary: 'Dismiss incident candidate'});
          })
          .then(function () { li.remove(); })
          .catch(function () { btn.disabled = false; btn.textContent = '✕ dismiss'; });
      };
      li.appendChild(document.createTextNode(' '));
      li.appendChild(btn);
    });
  });
}

/* ============ ICE List client tools (vanilla; robust login detection) ============ */
(function () {
  function ready(fn) { if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } }
  /* Detect logged-in state robustly: mw.user first, then the DOM logout link
     (wgUserName can be null in cached RLCONF, so trust the personal-tools chrome). */
  function markMember() {
    var el = document.documentElement;
    if (el.classList.contains('vol-member')) { return true; }
    var inn = false;
    try { if (window.mw && mw.user && (mw.config.get('wgUserName')!==null)) { inn = true; } } catch (e) {}
    if (!inn && (document.getElementById('pt-logout') ||
        document.querySelector('a[href*="Special:UserLogout"], a[href*="UserLogout"], #pt-userpage'))) { inn = true; }
    if (inn) { el.classList.add('vol-member'); }
    return inn;
  }
  markMember();
  ready(markMember);

  /* Incident pages: footage + agents front and centre, volunteer add-forms */
  ready(function () {
    try {
      var ns = mw.config.get('wgNamespaceNumber');
      var cats = mw.config.get('wgCategories') || [];
      var pn = mw.config.get('wgPageName');
      var pnText = pn.replace(/_/g, ' ');
      var isIncident = (ns === 0 && cats.indexOf('Incidents') !== -1);
      var isAgent = (ns === 0 && cats.indexOf('Agents') !== -1) || (ns === 4 && /^ICE List:Unidentified agent - /.test(pnText));
      if (!isIncident && !isAgent) { return; }
      var content = document.querySelector('#mw-content-text .mw-parser-output');
      if (!content) { return; }
      var isVol = (typeof markMember === 'function') && markMember();
      var hero = document.createElement('div'); hero.className = 'ic-incident-hero';
      var panels = {};

      var box = content.querySelector('.ic-incident-box');
      var incState = '';
      if (box) {
        [].slice.call(box.querySelectorAll('.ic-incident-meta > div')).forEach(function (r) {
          var dt = r.querySelector('dt'), dd = r.querySelector('dd');
          if (dt && dd && /^state$/i.test(dt.textContent.trim())) { incState = dd.textContent.trim(); }
        });
      }

      if (isVol) {
        var bar = document.createElement('div'); bar.className = 'ic-incident-actions';
        function mkBtn(label, key, cls) {
          var b = document.createElement('button'); b.className = 'ic-incident-actbtn' + (cls ? ' ' + cls : '');
          b.innerHTML = label;
          b.addEventListener('click', function () {
            var open = panels[key] && panels[key].style.display === 'block';
            Object.keys(panels).forEach(function (k) { if (panels[k]) { panels[k].style.display = 'none'; } });
            if (panels[key] && !open) { panels[key].style.display = 'block'; }
          });
          bar.appendChild(b); return b;
        }
        mkBtn('&#9998; Add information', 'info', 'primary');
        mkBtn('&#9654; Add footage / media', 'media');
        if (isIncident) { mkBtn('+ Add an agent', 'agent'); }
        hero.appendChild(bar);
      }

      mw.loader.using(['mediawiki.api']).then(function () {
        var api = new mw.Api();
        function esc(x) { return (x || '').replace(/[|]/g, '&#124;').replace(/[\[\]{}]/g, ''); }
        function panel(title) {
          var p = document.createElement('div'); p.className = 'ic-incident-panel'; p.style.display = 'none';
          var h = document.createElement('div'); h.className = 'ic-incident-panel__h'; h.textContent = title; p.appendChild(h);
          return p;
        }
        function inp(ph, val) { var i = document.createElement('input'); i.type = 'text'; i.placeholder = ph || ''; i.className = 'ic-incident-input'; if (val) { i.value = val; } return i; }

        if (isVol) {
          /* INFO */
          var pInfo = panel('Add information');
          var infoP = document.createElement('p'); infoP.textContent = 'Open the editor to add or correct information on this incident.';
          var infoA = document.createElement('a'); infoA.className = 'ic-incident-actbtn primary'; infoA.textContent = 'Open editor'; infoA.href = '/index.php/' + pn + '?action=edit';
          pInfo.appendChild(infoP); pInfo.appendChild(infoA); hero.appendChild(pInfo); panels.info = pInfo;

          /* MEDIA */
          var pMedia = panel('Add footage / media');
          var urlI = inp('Paste any social/video link (YouTube, X, TikTok, Instagram, Threads, Facebook, Vimeo)');
          var capI = inp('Caption (optional)');
          var mBtn = document.createElement('button'); mBtn.className = 'ic-incident-actbtn primary'; mBtn.textContent = 'Embed on this page';
          var mMsg = document.createElement('div'); mMsg.className = 'ic-incident-msg';
          pMedia.appendChild(urlI); pMedia.appendChild(capI); pMedia.appendChild(mBtn); pMedia.appendChild(mMsg);
          hero.appendChild(pMedia); panels.media = pMedia;
          mBtn.addEventListener('click', function () {
            var u = urlI.value.trim();
            if (!/^https?:\/\//.test(u)) { mMsg.textContent = 'Enter a valid https link.'; return; }
            mBtn.disabled = true; mMsg.textContent = 'Embedding…';
            var emb = '{{Embed|url=' + esc(u) + (capI.value.trim() ? '|caption=' + esc(capI.value.trim()) : '') + '}}';
            api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 }).then(function (d) {
              var pg = d.query.pages[0]; var txt = (pg.revisions && pg.revisions[0].slots.main.content) || '';
              var marker = '<div class="ic-incident-footage">';
              if (txt.indexOf(marker) !== -1) { txt = txt.replace(marker, marker + '\n' + emb); }
              else { var ci = txt.search(/\n\[\[Category:/); var blk = '\n' + marker + '\n' + emb + '\n</div>\n'; txt = (ci !== -1) ? txt.slice(0, ci) + blk + txt.slice(ci) : txt + blk; }
              return api.postWithToken('csrf', { action: 'edit', title: pn, text: txt, summary: 'Add footage/media via incident form' });
            }).then(function () { mMsg.textContent = '✓ Added — reloading…'; setTimeout(function () { location.reload(); }, 900); })
              .catch(function (e) { mBtn.disabled = false; mMsg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
          });

          if (isIncident) {
          /* AGENT */
          var pAgent = panel('Add an agent');
          var help = document.createElement('div'); help.className = 'ic-incident-help';
          help.innerHTML = 'Leave <b>name</b> blank for an <b>unidentified</b> agent (goes to the Most Wanted board). Enter a name to create an <b>identified</b> agent page. Either way it links to this incident.';
          pAgent.appendChild(help);
          var nameI = inp('Name — blank if unidentified');
          var codeI = inp('Code for unidentified (e.g. ' + (incState ? incState.replace(/[^A-Za-z]/g, '').slice(0, 2).toUpperCase() : 'XX') + '-0001)');
          var agencyI = inp('Agency (ICE / CBP / Unknown)');
          var stateI = inp('State', incState || '');
          var descI = inp('What they did / notes');
          [nameI, codeI, agencyI, stateI, descI].forEach(function (e) { pAgent.appendChild(e); });
          var mwRow = document.createElement('label'); mwRow.className = 'ic-incident-check';
          var mwCb = document.createElement('input'); mwCb.type = 'checkbox'; mwRow.appendChild(mwCb);
          mwRow.appendChild(document.createTextNode(' Flag as Most Wanted (unidentified only)')); pAgent.appendChild(mwRow);
          var aBtn = document.createElement('button'); aBtn.className = 'ic-incident-actbtn primary'; aBtn.textContent = 'Create & link to this incident';
          var aMsg = document.createElement('div'); aMsg.className = 'ic-incident-msg';
          pAgent.appendChild(aBtn); pAgent.appendChild(aMsg); hero.appendChild(pAgent); panels.agent = pAgent;
          aBtn.addEventListener('click', function () {
            var name = nameI.value.trim(), code = codeI.value.trim().replace(/[^A-Za-z0-9 .\-]/g, '');
            var agency = agencyI.value.trim() || 'Unknown', st = stateI.value.trim(), desc = descI.value.trim();
            var named = !!name;
            if (!named && !code) { aMsg.textContent = 'Enter a name, or a code for an unidentified agent.'; return; }
            aBtn.disabled = true; aMsg.textContent = 'Creating…';
            var title, body, agentLink, stcat = st || 'No State Given';
            if (named) {
              title = name; agentLink = '[[' + name + ']]';
              body = '{{Agent page\n|name=' + esc(name) + '\n|agency=' + esc(agency) + '\n|state=' + esc(st) + '\n|status=Active\n|image=Agent-placeholder.jpg\n|summary=' + esc(name + ' was documented in connection with ' + pnText + '.') + '\n}}\n\n== Documented Incidents ==\n{{IncidentsForAgent}}\n\n{{MediaForAgent}}\n\n[[Category:Agents]][[Category:Agents in ' + stcat + ']][[Category:Agents needing a photo]]\n';
            } else {
              title = 'ICE List:Unidentified agent - ' + code; agentLink = '[[' + title + ']]';
              var wanted = mwCb.checked ? 'Most wanted' : 'Standard';
              var wcat = mwCb.checked ? 'Most wanted unidentified agents' : 'Standard unidentified agents';
              body = '{{UnknownAgent\n | code = ' + esc(code) + '\n | image = Agent-placeholder.jpg\n | description = ' + esc(desc || ('Recorded during ' + pnText)) + '. Source: [[' + pnText + ']]\n | agency = ' + esc(agency) + '\n | state = ' + esc(st) + '\n | seen = \n | status = Open\n | wanted = ' + wanted + '\n}}\n<div class="ic-wanted-submit">[[ICE List:Submit media form|Submit info on this agent &rarr;]]</div>\n{{MediaForAgent}}\n\n[[Category:Unidentified agents]][[Category:' + wcat + ']][[Category:Unidentified agents in ' + stcat + ']][[Category:Agents in ' + stcat + ']]\n';
            }
            api.get({ action: 'query', titles: title, formatversion: 2 }).then(function (d) {
              if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('That agent page already exists: ' + title); }
              return api.postWithToken('csrf', { action: 'edit', title: title, createonly: 1, text: body, summary: 'Create agent via incident form' });
            }).then(function () {
              return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 });
            }).then(function (d) {
              var txt = d.query.pages[0].revisions[0].slots.main.content;
              if (txt.indexOf(agentLink) === -1) {
                txt = txt.replace(/(\|\s*agents_involved\s*=\s*)([^\n]*)/, function (m, p, val) { return p + (val.trim() ? val.trim() + '; ' : '') + agentLink; });
              }
              return api.postWithToken('csrf', { action: 'edit', title: pn, text: txt, summary: 'Link agent ' + title + ' to incident' });
            }).then(function () {
              aMsg.innerHTML = '✓ Created ' + (named ? 'identified agent' : 'unidentified agent (now on the Most Wanted board)') + '. Reloading…';
              setTimeout(function () { location.reload(); }, 1300);
            }).catch(function (e) { aBtn.disabled = false; aMsg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
          });
          }
        }

        /* FOOTAGE hoist */
        var fWrap = document.createElement('div');
        var media = content.querySelector('.ic-media-block');
        var inFoot = content.querySelector('.ic-incident-footage');
        if (media || inFoot) { var fh = document.createElement('div'); fh.className = 'ic-incident-hero__h'; fh.textContent = 'Footage & media'; fWrap.appendChild(fh); }
        if (inFoot) { fWrap.appendChild(inFoot); }
        if (media) { fWrap.appendChild(media); }
        hero.appendChild(fWrap);

        /* AGENTS strip */
        var seen = {}, codes = [];
        if (box) {
          [].slice.call(box.querySelectorAll('a[href]')).forEach(function (a) {
            var m = decodeURIComponent(a.getAttribute('href') || '').replace(/_/g, ' ').match(/Unidentified agent - ([^"#?|\]&]+)/);
            if (m) { var c = m[1].trim(); if (!seen[c]) { seen[c] = 1; codes.push(c); } }
          });
        }
        if (codes.length) {
          var aw = document.createElement('div');
          var ah = document.createElement('div'); ah.className = 'ic-incident-hero__h'; ah.textContent = 'Agents at the scene'; aw.appendChild(ah);
          var strip = document.createElement('div'); strip.className = 'ic-incident-agents';
          codes.forEach(function (c) {
            var src = '/index.php/Special:Redirect/file/' + encodeURIComponent('Wanted-' + c + '.png');
            var card = document.createElement('div'); card.className = 'ic-incident-agent';
            card.innerHTML = '<a href="/index.php/ICE_List:Unidentified_agent_-_' + encodeURIComponent(c.replace(/ /g, '_')) + '"><img alt="Agent ' + c + '" loading="lazy"></a><div class="ic-incident-agent__cap">Agent ' + c + ' &middot; unidentified</div>';
            var img = card.querySelector('img'); img.onerror = function () { card.style.display = 'none'; }; img.src = src;
            strip.appendChild(card);
          });
          aw.appendChild(strip); hero.appendChild(aw);
        }

        var firstH = content.querySelector('.mw-heading2, h2');
        if (firstH) { content.insertBefore(hero, firstH); } else { content.appendChild(hero); }
      });
    } catch (e) {}
  });

  /* Identify: convert an unidentified-agent page into a named agent page */
  ready(function () {
    try {
    if (typeof markMember !== 'function' || !markMember()) { return; }
    if (mw.config.get('wgAction') !== 'view') { return; }
    if ((mw.config.get('wgCategories') || []).indexOf('Unidentified agents') === -1) { return; }
    var content = document.querySelector('#mw-content-text .mw-parser-output');
    if (!content) { return; }
    var pn = mw.config.get('wgPageName').replace(/_/g, ' ');
    var code = pn.replace('ICE List:Unidentified agent - ', '').trim();
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      function esc(x) { return (x || '').replace(/[|]/g, '&#124;').replace(/[\[\]{}]/g, ''); }
      function inp(ph) { var i = document.createElement('input'); i.type = 'text'; i.placeholder = ph; i.className = 'ic-incident-input'; return i; }
      var box = document.createElement('div'); box.className = 'ic-identify';
      var h = document.createElement('div'); h.className = 'ic-identify__h'; h.textContent = 'Identified this agent? Convert to a named agent page';
      box.appendChild(h);
      var nameI = inp('Full name (required)'), agencyI = inp('Agency — ICE, CBP, HSI…'), roleI = inp('Role / title (optional)'), officeI = inp('Field office (optional)');
      [nameI, agencyI, roleI, officeI].forEach(function (e) { box.appendChild(e); });
      var btn = document.createElement('button'); btn.className = 'ic-incident-actbtn primary'; btn.textContent = 'Convert & remove from Most Wanted';
      var msg = document.createElement('div'); msg.className = 'ic-incident-msg';
      box.appendChild(btn); box.appendChild(msg);
      content.insertBefore(box, content.firstChild);

      btn.addEventListener('click', function () {
        var name = nameI.value.trim();
        if (!name) { msg.textContent = 'Enter the agent’s name.'; return; }
        var agency = agencyI.value.trim() || 'Unknown', role = roleI.value.trim(), office = officeI.value.trim();
        btn.disabled = true; msg.textContent = 'Working…';
        var image = '', state = '', desc = '';
        api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 }).then(function (d) {
          var txt = d.query.pages[0].revisions[0].slots.main.content;
          var mi = txt.match(/\|\s*image\s*=\s*([^\n|]*)/); image = mi ? mi[1].trim() : '';
          var ms = txt.match(/\|\s*state\s*=\s*([^\n|]*)/); state = ms ? ms[1].trim() : '';
          var md = txt.match(/\|\s*description\s*=\s*([^\n|]*)/); desc = md ? md[1].trim() : '';
          return api.get({ action: 'query', titles: name, formatversion: 2 });
        }).then(function (d) {
          if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('A page named "' + name + '" already exists — merge manually.'); }
          return api.postWithToken('csrf', { action: 'move', from: pn, to: name, reason: 'Agent identified as ' + name, movetalk: 1 });
        }).then(function () {
          var ph = !image || /placeholder|nopfp|no-pfp|noimage/i.test(image);
          var stcat = state || 'No State Given';
          var body = '{{Agent page\n|name=' + esc(name) + '\n|agency=' + esc(agency) +
            '\n|role=' + esc(role) + '\n|field_office=' + esc(office) + '\n|state=' + esc(state) +
            '\n|status=Active\n|image=' + (image || 'nopfp.png') + '\n|verification=Verified\n|summary=' +
            esc(name + ' was documented as the federal agent previously recorded as unidentified agent ' + code + '. ' + desc) +
            '\n}}\n\n== Documented Incidents ==\n\'\'This list updates automatically when incident pages link to this agent.\'\'\n\n{{IncidentsForAgent}}\n\n' +
            '== Evidence and Sources ==\n* Formerly documented as unidentified agent \'\'\'' + code + '\'\'\'.\n* Here extra evidence can be provided, including social media links\n\n' +
            '{{MediaForAgent}}\n\n[[Category:Agents]][[Category:Agents in ' + stcat + ']]' +
            (ph ? '[[Category:Agents needing a photo]]' : '[[Category:Agents with photo]]') + '[[Category:Formerly unidentified agents]]\n';
          return api.postWithToken('csrf', { action: 'edit', title: name, text: body, summary: 'Convert unidentified agent ' + code + ' to identified agent page' });
        }).then(function () {
          return api.get({ action: 'query', list: 'backlinks', bltitle: pn, bllimit: 50, blnamespace: 0 });
        }).then(function (d) {
          var links = (d.query.backlinks || []).map(function (b) { return b.title; }).filter(function (t) { return t !== name; });
          var chain = Promise.resolve();
          var resrc = '\\[\\[\\s*' + pn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*(\\|[^\\]]*)?\\]\\]';
          links.forEach(function (t) {
            chain = chain.then(function () {
              return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: t, formatversion: 2 }).then(function (dd) {
                var pg = dd.query.pages[0]; if (!pg.revisions) { return; }
                var tx = pg.revisions[0].slots.main.content;
                var nx = tx.replace(new RegExp(resrc, 'g'), function (m, p1) { return '[[' + name + (p1 || '') + ']]'; });
                if (nx !== tx) { return api.postWithToken('csrf', { action: 'edit', title: t, text: nx, summary: 'Attribute identified agent ' + name }); }
              });
            });
          });
          return chain;
        }).then(function () {
          var url = '/index.php/' + encodeURIComponent(name.replace(/ /g, '_'));
          msg.innerHTML = '✓ Converted. Redirecting to <a href="' + url + '">' + name + '</a>…';
          setTimeout(function () { location.href = url; }, 1200);
        }).catch(function (e) { btn.disabled = false; msg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
      });
    });
    } catch (e) {}
  });

  /* Submissions hub: filter chips */
  ready(function () {
    var tb = document.getElementById('ic-sub-toolbar');
    if (!tb) { return; }
    var cards = [].slice.call(document.querySelectorAll('.ic-sub-card'));
    if (!cards.length) { return; }
    var defs = [['all', 'All'], ['matched', 'Matched to a page'], ['unmatched', 'Unmatched'], ['file', 'Has file'], ['link', 'Has link'], ['video', 'Video']];
    var chips = [], cnt = document.createElement('span'); cnt.className = 'ic-sub-count';
    function match(c, f) { return f === 'all' ? true : c.getAttribute('data-' + f) === '1'; }
    function apply(f) {
      var n = 0;
      cards.forEach(function (c) { var ok = match(c, f); c.style.display = ok ? '' : 'none'; if (ok) { n++; } });
      chips.forEach(function (ch) { ch.classList.toggle('is-active', ch.getAttribute('data-f') === f); });
      cnt.textContent = n + ' shown';
    }
    defs.forEach(function (d) {
      var b = document.createElement('button'); b.className = 'ic-sub-chip'; b.setAttribute('data-f', d[0]); b.textContent = d[1];
      b.addEventListener('click', function () { apply(d[0]); }); tb.appendChild(b); chips.push(b);
    });
    tb.appendChild(cnt); apply('all');
  });

  /* Submission review queue: route each submission; routing removes the options */
  ready(function () {
    var q = document.querySelector('.ic-subq');
    if (!q) { return; }
    if (!(typeof markMember === 'function' && markMember())) {
      var g = q.querySelector('.ic-subq__buttons'); if (g) { g.innerHTML = '<em>Log in as a volunteer to route this submission.</em>'; }
      return;
    }
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      var pn = mw.config.get('wgPageName');
      var file = q.getAttribute('data-file') || '', link = q.getAttribute('data-link') || '', suggested = q.getAttribute('data-suggested') || '';
      var VIDEO = /(youtube\.com|youtu\.be|tiktok\.com|instagram\.com|threads\.(com|net)|facebook\.com|fb\.watch|vimeo\.com|x\.com|twitter\.com)/i;
      function esc(x) { return (x || '').replace(/[|]/g, '&#124;').replace(/[\[\]{}]/g, ''); }
      var wrap = q.querySelector('.ic-subq__buttons'); wrap.innerHTML = '';
      var msg = document.createElement('div'); msg.className = 'ic-subq__msg';

      function attachTo(target, done) {
        api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: target, formatversion: 2 }).then(function (d) {
          var p = d.query.pages[0];
          if (!p || p.missing !== undefined) { throw new Error('Page not found: ' + target); }
          var txt = p.revisions[0].slots.main.content, media = '';
          if (file) { media += '[[File:' + file + '|thumb|300px|Submitted media]]'; }
          if (link && VIDEO.test(link)) { media += (media ? '\n' : '') + '{{Embed|url=' + esc(link) + '}}'; }
          if (media) { txt = (txt.indexOf('{{MediaForAgent}}') !== -1) ? txt.replace('{{MediaForAgent}}', media + '\n{{MediaForAgent}}') : (txt.trimEnd() + '\n\n' + media + '\n'); }
          if (link && !VIDEO.test(link)) {
            var ev = '* ' + esc(link) + ' — submitted via form';
            if (/==\s*Evidence and Sources\s*==/.test(txt)) { txt = txt.replace(/(==\s*Evidence and Sources\s*==[^\n]*\n)/, '$1' + ev + '\n'); }
            else { txt = txt.trimEnd() + '\n\n== Evidence and Sources ==\n' + ev + '\n'; }
          }
          return api.postWithToken('csrf', { action: 'edit', title: target, text: txt, summary: 'Add submitted media (review queue)' });
        }).then(function () { return resolve('routed to [[' + target + ']]'); }).then(done).catch(function (e) { fail(e); });
      }
      function resolve(note) {
        return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 }).then(function (d) {
          var txt = d.query.pages[0].revisions[0].slots.main.content;
          txt = txt.replace(/\{\{Submission entry[^}]*\}\}/, '').replace('[[Category:Submission review]]', '');
          txt = "'''Resolved:''' " + note + '\n\n' + txt + '\n[[Category:Resolved submissions]]\n';
          return api.postWithToken('csrf', { action: 'edit', title: pn, text: txt, summary: 'Resolve submission: ' + note });
        });
      }
      function fail(e) { msg.textContent = 'Error: ' + (e && e.message ? e.message : e); [].forEach.call(wrap.querySelectorAll('button,input'), function (x) { x.disabled = false; }); }
      function done() { msg.innerHTML = '✓ Routed — reloading…'; setTimeout(function () { location.reload(); }, 900); }
      function busy() { [].forEach.call(wrap.querySelectorAll('button,input'), function (x) { x.disabled = true; }); msg.textContent = 'Working…'; }

      function btn(label, cls, fn) { var b = document.createElement('button'); b.className = 'ic-incident-actbtn' + (cls ? ' ' + cls : ''); b.textContent = label; b.addEventListener('click', fn); wrap.appendChild(b); return b; }

      if (suggested) {
        btn('Attach to ' + suggested, 'primary', function () { busy(); attachTo(suggested, done); });
      }
      var row = document.createElement('span'); row.className = 'ic-subq__row';
      var tin = document.createElement('input'); tin.type = 'text'; tin.className = 'ic-incident-input ic-subq__input'; tin.placeholder = 'Attach to another page (exact title)';
      var tbtn = document.createElement('button'); tbtn.className = 'ic-incident-actbtn'; tbtn.textContent = 'Attach';
      tbtn.addEventListener('click', function () { var t = tin.value.trim(); if (!t) { msg.textContent = 'Enter a page title.'; return; } busy(); attachTo(t, done); });
      row.appendChild(tin); row.appendChild(tbtn); wrap.appendChild(row);

      btn('Create unidentified agent', '', function () {
        var code = prompt('Code for the new unidentified agent (e.g. NY-0007):', ''); if (!code) { return; }
        code = code.replace(/[^A-Za-z0-9 .\-]/g, ''); var st = prompt('State (optional):', '') || '';
        busy();
        var title = 'ICE List:Unidentified agent - ' + code, stcat = st.trim() || 'No State Given';
        var body = '{{UnknownAgent\n | code = ' + esc(code) + '\n | image = ' + (file ? file : 'Agent-placeholder.jpg') + '\n | description = Submitted via media form.' + (link ? ' Source: ' + esc(link) : '') + '\n | agency = Unknown\n | state = ' + esc(st) + '\n | seen = \n | status = Open\n | wanted = Standard\n}}\n<div class="ic-wanted-submit">[[ICE List:Submit media form|Submit info on this agent &rarr;]]</div>\n' + (link && VIDEO.test(link) ? '{{Embed|url=' + esc(link) + '}}\n' : '') + '{{MediaForAgent}}\n\n[[Category:Unidentified agents]][[Category:Standard unidentified agents]][[Category:Unidentified agents in ' + stcat + ']][[Category:Agents in ' + stcat + ']]' + (file ? '[[Category:Agents with photo]]' : '') + '\n';
        api.get({ action: 'query', titles: title, formatversion: 2 }).then(function (d) {
          if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('Already exists: ' + title); }
          return api.postWithToken('csrf', { action: 'edit', title: title, createonly: 1, text: body, summary: 'Create unidentified agent from submission' });
        }).then(function () { return resolve('created [[' + title + ']]'); }).then(done).catch(fail);
      });

      btn('Promote to incident', '', function () {
        var title = prompt('New incident page title (include date + location):', '');
        if (!title) { return; }
        var corr = prompt('Corroborating source URL (independent evidence \u2014 required to promote a tip):', '');
        if (!corr || !/^https?:\/\//.test(corr)) { msg.textContent = 'A corroborating source URL is required to promote a tip to an incident.'; return; }
        busy();
        var media = file ? '[[File:' + file + '|thumb|360px|Submitted media]]\n' : '';
        if (link && VIDEO.test(link)) { media += '{{Embed|url=' + esc(link) + '}}\n'; }
        var body = '{{Infobox incident\n| title = ' + esc(title) + '\n| status = Developing (promoted from a community tip)\n| verification = Developing. Originated as a community tip, corroborated by an independent source (below).\n| key_sources =\n* Tip: ' + esc(link || file || 'submitted media') + '\n* Corroboration: ' + esc(corr) + '\n}}\n\n' + "''Promoted from a community tip after independent corroboration. Records only what is sourced; will be updated as verified.''\n\n" + media + '\n{{MediaForIncident}}\n\n[[Category:Incidents]][[Category:Incidents involving ICE]]\n';
        api.get({ action: 'query', titles: title, formatversion: 2 }).then(function (d) {
          if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('A page named "' + title + '" already exists.'); }
          return api.postWithToken('csrf', { action: 'edit', title: title, createonly: 1, text: body, summary: 'Create incident from corroborated tip' });
        }).then(function () { return resolve('promoted to [[' + title + ']]'); }).then(function () {
          msg.innerHTML = '\u2713 Created <a href="/index.php/' + encodeURIComponent(title.replace(/ /g, '_')) + '">' + title + '</a> \u2014 reloading\u2026'; setTimeout(function () { location.reload(); }, 1200);
        }).catch(fail);
      });

      btn('Discard', '', function () { if (!confirm('Discard this submission as not usable?')) { return; } busy(); resolve('discarded (not usable)').then(done).catch(fail); });

      wrap.appendChild(msg);
    });
  });

  /* Wanted posters: with/without photo filter (per-state pages) */
  ready(function () {
    var bar = document.getElementById('ic-photo-filter');
    if (!bar) { return; }
    var secs = [].slice.call(document.querySelectorAll('.ic-poster-section[data-photo]'));
    if (!secs.length) { return; }
    function hasTiles(sec) { return sec && sec.querySelector('.ic-poster-tile'); }
    secs.forEach(function (s) { if (!hasTiles(s)) { s.style.display = 'none'; } });
    var live = secs.filter(hasTiles);
    var hasW = live.some(function (s) { return s.getAttribute('data-photo') === 'yes'; });
    var hasN = live.some(function (s) { return s.getAttribute('data-photo') === 'no'; });
    if (!(hasW && hasN)) { return; }
    var lbl = document.createElement('span'); lbl.className = 'ic-photo-lbl'; lbl.textContent = 'Show:'; bar.appendChild(lbl);
    var defs = [['all', 'All'], ['with', 'With photo'], ['needed', 'Photo needed']];
    var btns = [];
    function apply(mode) {
      live.forEach(function (s) { var p = s.getAttribute('data-photo'); s.style.display = (mode === 'all' || (mode === 'with' && p === 'yes') || (mode === 'needed' && p === 'no')) ? '' : 'none'; });
      btns.forEach(function (b) { b.classList.toggle('is-active', b.getAttribute('data-m') === mode); });
    }
    defs.forEach(function (d) {
      var b = document.createElement('button'); b.className = 'ic-photo-btn'; b.setAttribute('data-m', d[0]); b.textContent = d[1];
      b.addEventListener('click', function () { apply(d[0]); }); bar.appendChild(b); btns.push(b);
    });
    apply('all');
  });

  /* Homepage: strip native title tooltips on interactive tiles */
  ready(function () {
    if (mw.config.get('wgPageName') === 'Main_Page') {
      document.querySelectorAll('.ic-scenarios a, .ic-stat a, .ic-hero__actions a').forEach(function (a) { a.removeAttribute('title'); });
    }
  });

  /* Render video / social embeds (all users, client-side for privacy) */
  ready(function () {
    var nodes = document.querySelectorAll('.ic-embed:not([data-done])');
    if (!nodes.length) { return; }
    var needX = false, needIG = false, needTh = false, needTT = false;
    function ytId(u){ var m=u.match(/[?&]v=([\w-]{6,})/)||u.match(/youtu\.be\/([\w-]{6,})/)||u.match(/\/(?:shorts|embed|live)\/([\w-]{6,})/); return m?m[1]:''; }
    function vimeoId(u){ var m=u.match(/vimeo\.com\/(?:video\/)?(\d+)/); return m?m[1]:''; }
    function detect(u){ var h; try{ h=new URL(u).hostname.replace(/^www\./,'').toLowerCase(); }catch(e){ return ''; }
      if(h.indexOf('youtube')>-1||h==='youtu.be') return 'youtube';
      if(h.indexOf('vimeo')>-1) return 'vimeo';
      if(h.indexOf('instagram')>-1) return 'instagram';
      if(h.indexOf('tiktok')>-1) return 'tiktok';
      if(h.indexOf('facebook')>-1||h==='fb.watch') return 'facebook';
      if(h==='x.com'||h.indexOf('twitter')>-1) return 'x';
      if(h.indexOf('threads')>-1) return 'threads';
      return ''; }
    nodes.forEach(function (el) {
      el.setAttribute('data-done', '1');
      var id = el.getAttribute('data-id'), url = el.getAttribute('data-url');
      var plat = (el.getAttribute('data-platform') || '').toLowerCase().trim();
      if (!plat) {
        if (el.classList.contains('ic-embed-youtube')) { plat = 'youtube'; }
        else if (el.classList.contains('ic-embed-vimeo')) { plat = 'vimeo'; }
        else if (el.classList.contains('ic-embed-x')) { plat = 'x'; }
        else if (el.classList.contains('ic-embed-instagram')) { plat = 'instagram'; }
        else if (el.classList.contains('ic-embed-threads')) { plat = 'threads'; }
      }
      if (!plat && url) { plat = detect(url); }
      if (plat === 'youtube' && !id && url) { id = ytId(url); }
      if (plat === 'vimeo' && !id && url) { id = vimeoId(url); }
      if (plat === 'youtube' && id) {
        el.className = 'ic-embed ic-embed--ratio'; el.innerHTML = '<iframe loading="lazy" allowfullscreen allow="encrypted-media; picture-in-picture" src="https://www.youtube-nocookie.com/embed/' + encodeURIComponent(id) + '"></iframe>';
      } else if (plat === 'vimeo' && id) {
        el.className = 'ic-embed ic-embed--ratio'; el.innerHTML = '<iframe loading="lazy" allowfullscreen src="https://player.vimeo.com/video/' + encodeURIComponent(id) + '"></iframe>';
      } else if ((plat === 'x' || plat === 'twitter') && url) {
        el.innerHTML = '<blockquote class="twitter-tweet"><a href="' + url + '"></a></blockquote>'; needX = true;
      } else if (plat === 'instagram' && url) {
        el.innerHTML = '<blockquote class="instagram-media" data-instgrm-permalink="' + url + '" data-instgrm-version="14"></blockquote>'; needIG = true;
      } else if (plat === 'threads' && url) {
        el.innerHTML = '<blockquote class="text-post-media" data-text-post-permalink="' + url + '"></blockquote>'; needTh = true;
      } else if (plat === 'tiktok' && url) {
        el.innerHTML = '<blockquote class="tiktok-embed" cite="' + url + '" style="max-width:605px;min-width:325px;"><a href="' + url + '"></a></blockquote>'; needTT = true;
      } else if (plat === 'facebook' && url) {
        el.className = 'ic-embed ic-embed--ratio'; el.innerHTML = '<iframe loading="lazy" allowfullscreen scrolling="no" frameborder="0" src="https://www.facebook.com/plugins/video.php?show_text=false&href=' + encodeURIComponent(url) + '"></iframe>';
      } else if (url) {
        el.innerHTML = '<a class="ic-embed-link" href="' + url + '" target="_blank" rel="noopener">' + (el.textContent || 'View media') + ' ↗</a>';
      }
    });
    function load(src) { var sc = document.createElement('script'); sc.async = true; sc.src = src; document.body.appendChild(sc); }
    if (needX) { load('https://platform.twitter.com/widgets.js'); }
    if (needIG) { load('https://www.instagram.com/embed.js'); }
    if (needTh) { load('https://www.threads.net/embed.js'); }
    if (needTT) { load('https://www.tiktok.com/embed.js'); }
  });

  /* Show the shareable accountability card on named-agent pages */
  ready(function () {
    try {
      if (mw.config.get('wgNamespaceNumber') !== 0) { return; }
      if ((mw.config.get('wgCategories') || []).indexOf('Agents') === -1) { return; }
      var content = document.querySelector('#mw-content-text .mw-parser-output');
      if (!content) { return; }
      var infoImg = content.querySelector('.ic-agent-card__photo img, .infobox img');
      var noPhoto = !infoImg || /placeholder|nopfp|no[-_]?pfp|noimage/i.test((infoImg.getAttribute('src') || '') + '|' + (infoImg.getAttribute('alt') || ''));
      if (noPhoto) {
        var pn = document.createElement('div'); pn.className = 'ic-photo-needed';
        pn.innerHTML = '📷 <strong>We don\'t have a photo of this agent yet.</strong> Do you? <a href="/index.php/ICE_List:Submit_media_form">Submit a photo</a> to help identify and document them — your identity is not recorded.';
        content.insertBefore(pn, content.firstChild);
      }
      var fn = 'Card-' + mw.config.get('wgPageName') + '.png';
      var src = mw.util ? mw.util.getUrl('Special:Redirect/file/' + fn) : '/index.php/Special:Redirect/file/' + encodeURIComponent(fn);
      var wrap = document.createElement('div'); wrap.className = 'ic-agent-share';
      wrap.innerHTML = '<div class="ic-agent-share-h">Share this agent</div>'
        + '<a class="ic-agent-share-img" href="' + src + '" target="_blank" rel="noopener"><img alt="Accountability share card" loading="lazy"></a>'
        + '<div class="ic-agent-share-c">Download and share this card. Recognize them, or have a clearer photo? <a href="/index.php/ICE_List:Submit_media_form">Submit info</a> &mdash; your identity is not recorded.</div>';
      var img = wrap.querySelector('img');
      img.onerror = function () { wrap.style.display = 'none'; };
      img.src = src;
      content.appendChild(wrap);
    } catch (e) {}
  });

  /* Wanted posters gallery: filter by state */
  ready(function () {
    var bar = document.getElementById('ic-poster-filter');
    if (!bar) { return; }
    var tiles = [].slice.call(document.querySelectorAll('.ic-poster-tile[data-state]'));
    if (!tiles.length) { return; }
    var counts = {};
    tiles.forEach(function (t) { var st = (t.getAttribute('data-state') || '').trim(); if (st) { counts[st] = (counts[st] || 0) + 1; } });
    var sel = document.createElement('select'); sel.className = 'ic-wanted-statesel';
    var all = document.createElement('option'); all.value = ''; all.textContent = 'All states'; sel.appendChild(all);
    Object.keys(counts).sort().forEach(function (st) { var o = document.createElement('option'); o.value = st; o.textContent = st + ' (' + counts[st] + ')'; sel.appendChild(o); });
    var lab = document.createElement('span'); lab.className = 'ic-wanted-filter-label'; lab.textContent = 'Filter by state: ';
    bar.appendChild(lab); bar.appendChild(sel);
    sel.addEventListener('change', function () {
      var v = sel.value;
      tiles.forEach(function (t) { t.style.display = (!v || (t.getAttribute('data-state') || '').trim() === v) ? '' : 'none'; });
    });
  });

  /* Header content-language switch (English <-> /es), replaces the confusing ULS trigger */
  ready(function () {
    try {
      mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () {
        var page = mw.config.get('wgPageName');
        if (mw.config.get('wgNamespaceNumber') < 0) { return; }
        var isEs = /\/es$/.test(page);
        var base = isEs ? page.replace(/\/es$/, '') : page;
        function addToggle(target, label, other) {
          var host = document.querySelector('.vector-header-end') || document.querySelector('.mw-header');
          if (!host) { return; }
          var wrap = document.createElement('div'); wrap.className = 'ic-lang-toggle';
          var cur = document.createElement('span'); cur.className = 'ic-lang-cur'; cur.textContent = other;
          var a = document.createElement('a'); a.href = mw.util.getUrl(target); a.textContent = label;
          wrap.appendChild(document.createTextNode('🌐 '));
          wrap.appendChild(cur); wrap.appendChild(document.createTextNode(' / ')); wrap.appendChild(a);
          host.appendChild(wrap);
        }
        if (isEs) {
          addToggle(base, 'English', 'Español');
        } else {
          new mw.Api().get({ action: 'query', titles: base + '/es', formatversion: 2 }).then(function (r) {
            var p = r.query && r.query.pages && r.query.pages[0];
            if (p && !p.missing) { addToggle(base + '/es', 'Español', 'English'); }
          });
        }
      });
    } catch (e) {}
  });

  /* Most Wanted board: filter by state */
  ready(function () {
    var bar = document.getElementById('ic-wanted-filter');
    if (!bar) { return; }
    var cards = [].slice.call(document.querySelectorAll('.ic-wanted-card[data-state]'));
    if (!cards.length) { return; }
    var counts = {};
    cards.forEach(function (c) { var st = (c.getAttribute('data-state') || '').trim(); if (st) { counts[st] = (counts[st] || 0) + 1; } });
    var sel = document.createElement('select'); sel.className = 'ic-wanted-statesel';
    var all = document.createElement('option'); all.value = ''; all.textContent = 'All states'; sel.appendChild(all);
    Object.keys(counts).sort().forEach(function (st) { var o = document.createElement('option'); o.value = st; o.textContent = st + ' (' + counts[st] + ')'; sel.appendChild(o); });
    var lab = document.createElement('span'); lab.className = 'ic-wanted-filter-label'; lab.textContent = 'Filter by state: ';
    bar.appendChild(lab); bar.appendChild(sel);
    function apply() {
      var v = sel.value;
      cards.forEach(function (c) {
        var wrap = c.closest('.ic-wanted-entry') || c;
        wrap.style.display = (!v || (c.getAttribute('data-state') || '').trim() === v) ? '' : 'none';
      });
      document.querySelectorAll('.ic-wanted-grid').forEach(function (g) {
        var any = [].slice.call(g.querySelectorAll('.ic-wanted-entry')).some(function (e) { return e.style.display !== 'none'; });
        var h = g.previousElementSibling;
      });
    }
    sel.addEventListener('change', apply);
  });

  /* Add unidentified agent form (Most Wanted board, logged-in only) */
  ready(function () {
    try {
      var box = document.getElementById('ic-addagent');
      if (!box || !markMember()) { return; }
      mw.loader.using(['mediawiki.api']).then(function () {
        var api = new mw.Api(); box.innerHTML = '';
        var form = document.createElement('div'); form.className = 'ic-addform';
        var drop = document.createElement('div'); drop.className = 'ic-drop';
        var hint = document.createElement('span'); hint.textContent = 'Drag & drop a photo here, or click to choose';
        var fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'image/*'; fileInput.style.display = 'none';
        var preview = document.createElement('img'); preview.className = 'ic-drop-preview'; preview.style.display = 'none';
        drop.appendChild(hint); drop.appendChild(fileInput); drop.appendChild(preview);
        var chosenFile = null;
        function setFile(f) { if (!f || f.type.indexOf('image/') !== 0) { return; } chosenFile = f; var r = new FileReader(); r.onload = function (e) { preview.src = e.target.result; preview.style.display = 'block'; hint.textContent = f.name; }; r.readAsDataURL(f); }
        drop.addEventListener('click', function () { fileInput.click(); });
        fileInput.addEventListener('change', function () { setFile(fileInput.files[0]); });
        ['dragover', 'dragenter'].forEach(function (ev) { drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.add('drag'); }); });
        ['dragleave', 'drop'].forEach(function (ev) { drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.remove('drag'); }); });
        drop.addEventListener('drop', function (e) { if (e.dataTransfer.files[0]) { setFile(e.dataTransfer.files[0]); } });
        form.appendChild(drop);
        function field(label, ph, ta) {
          var w = document.createElement('label'); w.className = 'ic-addrow'; w.appendChild(document.createTextNode(label));
          var i = document.createElement(ta ? 'textarea' : 'input'); if (!ta) { i.type = 'text'; } i.placeholder = ph || ''; w.appendChild(i); form.appendChild(w); return i;
        }
        var codeI = field('Code (e.g. IL-0008)', 'required'); var descI = field('Description / what they did', '', true);
        var agencyI = field('Agency', 'ICE / CBP / Unknown'); var stateI = field('Location / state', '');
        var srcI = field('Source or incident (optional)', 'link or [[Incident page]]');
        var vidI = field('Video link (optional)', 'paste any social video link');
        var mwRow = document.createElement('label'); mwRow.className = 'ic-addrow ic-addcheck';
        var mwCb = document.createElement('input'); mwCb.type = 'checkbox'; mwRow.appendChild(mwCb); mwRow.appendChild(document.createTextNode(' Add to Most wanted')); form.appendChild(mwRow);
        var btn = document.createElement('button'); btn.className = 'uk-flag'; btn.textContent = 'Create entry';
        var msg = document.createElement('div'); msg.className = 'ic-addmsg'; form.appendChild(btn); form.appendChild(msg); box.appendChild(form);
        btn.addEventListener('click', function () {
          var code = codeI.value.trim().replace(/[^A-Za-z0-9 .\-]/g, '');
          if (!code) { msg.textContent = 'Enter a code.'; return; }
          var title = 'ICE List:Unidentified agent - ' + code, wanted = mwCb.checked ? 'Most wanted' : 'Standard';
          btn.disabled = true; msg.textContent = 'Working…';
          function esc(x) { return x.replace(/[|]/g, '&#124;'); }
          function createPage(img) {
            var desc = descI.value.trim(); if (srcI.value.trim()) { desc += (desc ? '. ' : '') + 'Source: ' + srcI.value.trim(); }
            var body = '{{UnknownAgent\n | code = ' + code + '\n | image = ' + (img || 'Agent-placeholder.jpg') +
              '\n | description = ' + esc(desc) + '\n | agency = ' + (esc(agencyI.value.trim()) || 'Unknown') +
              '\n | state = ' + esc(stateI.value.trim()) + '\n | seen = \n | status = Open\n | wanted = ' + wanted + '\n}}\n' +
              '<div class="ic-wanted-submit">[[ICE List:Submit media form|Submit info on this agent →]]</div>\n' + (vidI.value.trim() ? '{{Embed|url=' + vidI.value.trim() + '}}\n' : '') + '{{MediaForAgent}}\n' +
              '[[Category:Unidentified agents]][[Category:' + wanted + ' unidentified agents]]\n';
            api.create(title, { summary: 'Add unidentified agent (board form)' }, body).then(function () {
              msg.innerHTML = 'Created. <a href="/index.php/' + encodeURIComponent(title.replace(/ /g, '_')) + '">Open ' + code + ' →</a>'; btn.disabled = false;
              form.querySelectorAll('input,textarea').forEach(function (el) { if (el.type !== 'file') { el.value = ''; } el.checked = false; });
              preview.style.display = 'none'; chosenFile = null; hint.textContent = 'Drag & drop a photo here, or click to choose';
            }).catch(function (e) { msg.textContent = (e === 'articleexists') ? 'An entry with that code already exists.' : ('Create failed: ' + e); btn.disabled = false; });
          }
          if (chosenFile) {
            var ext = (chosenFile.name.split('.').pop() || 'jpg').toLowerCase(); if (ext.length > 4 || !ext) { ext = 'jpg'; }
            api.upload(chosenFile, { filename: code + '.' + ext, comment: 'Unidentified agent photo', text: '[[Category:Unidentified agents]]', ignorewarnings: true })
              .then(function () { createPage(code + '.' + ext); })
              .catch(function (e) { msg.textContent = 'Photo upload unavailable (' + e + ') — creating entry without it…'; createPage(null); });
          } else { createPage(null); }
        });
      });
    } catch (e) {}
  });

  /* Unidentified agent marking panel (Most Wanted board) */
  ready(function () {
    try {
      if (!markMember()) { return; }
      if (mw.config.get('wgAction') !== 'view') { return; }
      if ((mw.config.get('wgCategories') || []).indexOf('Unidentified agents') === -1) { return; }
      var content = document.querySelector('#mw-content-text .mw-parser-output');
      if (!content) { return; }
      mw.loader.using(['mediawiki.api']).then(function () {
        var api = new mw.Api(), page = mw.config.get('wgPageName');
        var FIELDS = [['description', 'Description'], ['agency', 'Agency'], ['state', 'Location'], ['status', 'Status']];
        var panel = document.createElement('div'); panel.className = 'vol-panel uk-panel';
        panel.innerHTML = '<h4>Unidentified agent <span class="vol-status">loading…</span></h4>';
        var status = panel.querySelector('.vol-status'), inputs = {};
        var wl = document.createElement('label'); wl.className = 'uk-row';
        var wcb = document.createElement('input'); wcb.type = 'checkbox'; wcb.disabled = true;
        wl.appendChild(wcb); wl.appendChild(document.createTextNode(' Most wanted (feature at top of board)')); panel.appendChild(wl);
        FIELDS.forEach(function (f) {
          var r = document.createElement('label'); r.className = 'uk-row'; r.appendChild(document.createTextNode(f[1] + ': '));
          var i = document.createElement('input'); i.type = 'text'; i.disabled = true; r.appendChild(i); panel.appendChild(r); inputs[f[0]] = i;
        });
        var btn = document.createElement('button'); btn.className = 'uk-flag'; btn.textContent = 'Save'; btn.disabled = true; panel.appendChild(btn);
        var vrow = document.createElement('div'); vrow.className = 'uk-row'; vrow.style.marginTop = '.5rem';
        vrow.appendChild(document.createTextNode('Add video (paste any social link): '));
        var vin = document.createElement('input'); vin.type = 'text'; vin.placeholder = 'https://…'; vin.disabled = true; vin.style.minWidth = '16rem';
        var vbtn = document.createElement('button'); vbtn.className = 'uk-flag'; vbtn.textContent = 'Add video'; vbtn.disabled = true; vbtn.style.marginLeft = '.3rem';
        vrow.appendChild(vin); vrow.appendChild(vbtn); panel.appendChild(vrow);
        vbtn.addEventListener('click', function () {
          var u = vin.value.trim();
          if (!/^https?:\/\//.test(u)) { status.textContent = 'enter a full URL'; return; }
          status.textContent = 'adding…'; vbtn.disabled = true;
          var w = wt, emb = '{{Embed|url=' + u + '}}', idx = w.search(/\n\[\[Category:/);
          w = idx >= 0 ? (w.slice(0, idx) + '\n' + emb + w.slice(idx)) : (w.replace(/\s*$/, '') + '\n' + emb + '\n');
          api.postWithToken('csrf', { action: 'edit', title: page, text: w, summary: 'Add video embed' })
            .then(function () { wt = w; vin.value = ''; status.textContent = 'video added ✓ — reload to view'; vbtn.disabled = false; })
            .catch(function () { status.textContent = 'add failed (blocked link?)'; vbtn.disabled = false; });
        });
        content.insertBefore(panel, content.firstChild);
        var wt = '';
        function gv(k) { var m = wt.match(new RegExp('\\|\\s*' + k + '\\s*=\\s*([^\\n|}]*)')); return m ? m[1].trim() : ''; }
        function setP(w, k, v) { var re = new RegExp('(\\|\\s*' + k + '\\s*=)[^\\n|}]*'); if (re.test(w)) { return w.replace(re, '$1 ' + v.replace(/\$/g, '$$$$')); } return w.replace(/(\{\{UnknownAgent[\s\S]*?)(\n\}\})/, '$1\n | ' + k + ' = ' + v + '$2'); }
        api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
          try { wt = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wt = ''; }
          wcb.checked = /Category:Most wanted unidentified agents/.test(wt) || gv('wanted') === 'Most wanted';
          FIELDS.forEach(function (f) { inputs[f[0]].value = gv(f[0]); inputs[f[0]].disabled = false; });
          wcb.disabled = false; btn.disabled = false; vin.disabled = false; vbtn.disabled = false; status.textContent = '';
        });
        btn.addEventListener('click', function () {
          status.textContent = 'saving…'; btn.disabled = true;
          var w = wt, wanted = wcb.checked ? 'Most wanted' : 'Standard';
          w = setP(w, 'wanted', wanted);
          FIELDS.forEach(function (f) { w = setP(w, f[0], inputs[f[0]].value.trim()); });
          if (/Category:(?:Most wanted|Standard) unidentified agents/.test(w)) {
            w = w.replace(/\[\[Category:(?:Most wanted|Standard) unidentified agents\]\]/, '[[Category:' + wanted + ' unidentified agents]]');
          } else {
            w = w.replace(/(\[\[Category:Unidentified agents\]\])/, '$1[[Category:' + wanted + ' unidentified agents]]');
          }
          api.postWithToken('csrf', { action: 'edit', title: page, text: w, summary: 'Mark unidentified agent (' + wanted + ')' })
            .then(function () { wt = w; status.textContent = 'saved ✓ — reload to update'; btn.disabled = false; })
            .catch(function () { status.textContent = 'save failed'; btn.disabled = false; });
        });
      });
    } catch (e) {}
  });

  /* Volunteer media tagging panel (Cargo Media items) */
  ready(function () {
    try {
      if (!markMember()) { return; }
      if (mw.config.get('wgAction') !== 'view') { return; }
      if ((mw.config.get('wgCategories') || []).indexOf('Media') === -1) { return; }
      var content = document.querySelector('#mw-content-text .mw-parser-output');
      if (!content) { return; }
      mw.loader.using(['mediawiki.api']).then(function () {
        var api = new mw.Api(), page = mw.config.get('wgPageName');
        var FIELDS = [['agents','Agents (separate with ;)'],['incidents','Incidents (separate with ;)'],['state','State'],['agency','Agency']];
        var STATUS = ['Unverified','Pending','Verified'];
        var panel = document.createElement('div'); panel.className = 'vol-panel';
        panel.innerHTML = '<h4>Tag media <span class="vol-status">loading…</span></h4>';
        var status = panel.querySelector('.vol-status'), inputs = {};
        FIELDS.forEach(function (f) {
          var row = document.createElement('label'); row.className = 'vol-tag-row';
          row.appendChild(document.createTextNode(f[1] + ': '));
          var inp = document.createElement('input'); inp.type = 'text'; inp.disabled = true;
          row.appendChild(inp); panel.appendChild(row); inputs[f[0]] = inp;
        });
        var srow = document.createElement('label'); srow.className = 'vol-tag-row';
        srow.appendChild(document.createTextNode('Status: '));
        var sel = document.createElement('select'); sel.disabled = true;
        STATUS.forEach(function (v) { var o = document.createElement('option'); o.value = v; o.textContent = v; sel.appendChild(o); });
        srow.appendChild(sel); panel.appendChild(srow); inputs.status = sel;
        var btn = document.createElement('button'); btn.textContent = 'Save tags'; btn.disabled = true; btn.className = 'vol-tag-save';
        panel.appendChild(btn);
        content.insertBefore(panel, content.firstChild);
        var wikitext = '';
        function getVal(k) { var m = wikitext.match(new RegExp('\\|\\s*' + k + '\\s*=\\s*([^\\n|}]*)')); return m ? m[1].trim() : ''; }
        function setParam(wt, k, v) {
          var re = new RegExp('(\\|\\s*' + k + '\\s*=)[^\\n|}]*');
          if (re.test(wt)) { return wt.replace(re, '$1 ' + v.replace(/\$/g, '$$$$')); }
          return wt.replace(/(\{\{Media[\s\S]*?)(\n\}\})/, '$1\n | ' + k + ' = ' + v + '$2');
        }
        api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
          try { wikitext = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wikitext = ''; }
          if (wikitext.indexOf('{{Media') === -1) { panel.parentNode.removeChild(panel); return; }
          FIELDS.forEach(function (f) { inputs[f[0]].value = getVal(f[0]); inputs[f[0]].disabled = false; });
          sel.value = getVal('status') || 'Unverified'; sel.disabled = false; btn.disabled = false; status.textContent = '';
        });
        btn.addEventListener('click', function () {
          status.textContent = 'saving…'; btn.disabled = true;
          var wt = wikitext;
          FIELDS.forEach(function (f) { wt = setParam(wt, f[0], inputs[f[0]].value.trim()); });
          wt = setParam(wt, 'status', sel.value);
          api.postWithToken('csrf', { action: 'edit', title: page, text: wt, summary: 'Tag media (agents/incidents/state/status)' }).then(function () {
            wikitext = wt; status.textContent = 'saved ✓ — reload to update embeds'; btn.disabled = false;
          }).catch(function () { status.textContent = 'save failed'; btn.disabled = false; });
        });
      });
    } catch (e) {}
  });

  /* Volunteer incident marking + stage tool */
  ready(function () {
    if (!markMember()) { return; }
    if (mw.config.get('wgAction') !== 'view') { return; }
    var cats = mw.config.get('wgCategories') || [];
    if (cats.indexOf('Incidents') === -1) { return; }
    var content = document.querySelector('#mw-content-text .mw-parser-output');
    if (!content) { return; }
    var STAGES = ['', 'Intake', 'OSINT', 'Detailing', 'Verification', 'Writing', 'Published'];
    var MARKS = ['Agents recorded', 'Plates recorded', 'Faces captured', 'Video secured', 'Location confirmed', 'Witnesses identified', 'Needs review', 'Reviewed'];
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      var page = mw.config.get('wgPageName');
      var panel = document.createElement('div'); panel.className = 'vol-panel';
      panel.innerHTML = '<h4>Volunteer triage <span class="vol-status">loading…</span></h4>';
      var status = panel.querySelector('.vol-status');
      var stageRow = document.createElement('div'); stageRow.className = 'vol-stage-row';
      stageRow.appendChild(document.createTextNode('Stage: '));
      var stageSel = document.createElement('select'); stageSel.disabled = true;
      STAGES.forEach(function (st) { var o = document.createElement('option'); o.value = st; o.textContent = st || '(unstaged)'; stageSel.appendChild(o); });
      stageRow.appendChild(stageSel); panel.appendChild(stageRow);
      var boxes = {};
      MARKS.forEach(function (m) {
        var lab = document.createElement('label'); var cb = document.createElement('input'); cb.type = 'checkbox'; cb.disabled = true;
        lab.appendChild(cb); lab.appendChild(document.createTextNode(' ' + m)); panel.appendChild(lab); boxes[m] = cb;
      });
      content.insertBefore(panel, content.firstChild);
      var wikitext = '';
      function parseMarks(wt) { var re = /\{\{\s*Mark\s*\|\s*([^}|]+?)\s*\}\}/g, mm, set = {}; while ((mm = re.exec(wt))) { set[mm[1].trim()] = true; } return set; }
      function parseStage(wt) { var m = wt.match(/\{\{\s*Stage\s*\|\s*([^}|]+?)\s*\}\}/); return m ? m[1].trim() : ''; }
      api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
        try { wikitext = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wikitext = ''; }
        var set = parseMarks(wikitext);
        MARKS.forEach(function (m) { boxes[m].checked = !!set[m]; boxes[m].disabled = false; });
        stageSel.value = parseStage(wikitext); stageSel.disabled = false;
        status.textContent = '';
      });
      function save() {
        status.textContent = 'saving…';
        stageSel.disabled = true; MARKS.forEach(function (m) { boxes[m].disabled = true; });
        var stg = stageSel.value;
        var chosen = MARKS.filter(function (m) { return boxes[m].checked; });
        var inner = (stg ? '{{Stage|' + stg + '}}' : '') + chosen.map(function (m) { return '{{Mark|' + m + '}}'; }).join('');
        var region = '<div class="vol-only vol-marks"><!--VOLMARKS-->' + inner + '<!--/VOLMARKS--></div>';
        var done = false, wt = wikitext;
        wt = wikitext.replace(/<div class="vol-only vol-marks"><!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS--><\/div>/, function () { done = true; return region; });
        if (!done) { wt = wikitext.replace(/<!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS-->/, function () { done = true; return '<!--VOLMARKS-->' + inner + '<!--/VOLMARKS-->'; }); }
        if (!done) { var idx = wt.search(/\n\[\[Category:/); if (idx >= 0) { wt = wt.slice(0, idx) + '\n' + region + wt.slice(idx); } else { wt = wt.replace(/\s*$/, '') + '\n\n' + region + '\n'; } }
        api.postWithToken('csrf', { action: 'edit', title: page, text: wt, summary: 'Update volunteer triage / stage' }).then(function () {
          wikitext = wt; status.textContent = 'saved ✓';
          stageSel.disabled = false; MARKS.forEach(function (m) { boxes[m].disabled = false; });
          setTimeout(function () { status.textContent = ''; }, 1500);
        }).catch(function () { status.textContent = 'save failed'; stageSel.disabled = false; MARKS.forEach(function (m) { boxes[m].disabled = false; }); });
      }
      stageSel.addEventListener('change', save);
      MARKS.forEach(function (m) { boxes[m].addEventListener('change', save); });
    });
  });
  /* Culture index: featured spotlight + tabs + search over poster tiles */
  ready(function () {
    var app = document.getElementById('ic-cult-app');
    if (!app) { return; }
    var grid = app.querySelector('.ic-cult-grid');
    if (!grid) { return; }
    var cards = [].slice.call(grid.querySelectorAll('.ic-cult-card'));
    if (!cards.length) { return; }
    var LABEL = { documentary: 'Documentaries', film: 'Film & TV', music: 'Music', book: 'Books', podcast: 'Podcasts' };
    var GROUP = { books: ['book', 'podcast'] };
    var byType = {};
    function tx(el, sel) { var n = el.querySelector(sel); return n ? n.textContent.trim() : ''; }
    cards.forEach(function (c) {
      var ty = c.getAttribute('data-type') || 'other';
      byType[ty] = (byType[ty] || 0) + 1;
      c.setAttribute('data-s', (tx(c, '.ic-cult-mtitle') + ' ' + tx(c, '.ic-cult-mby') + ' ' + tx(c, '.ic-cult-ov')).toLowerCase());
      var lk = c.getAttribute('data-link');
      if (lk) { c.addEventListener('click', function (e) { if (e.target.closest('a')) { return; } window.open(lk, '_blank', 'noopener'); }); }
    });
    var fc = grid.querySelector('.ic-cult-card[data-featured="1"]') || cards[0];
    var fty = fc.getAttribute('data-type') || '', flk = fc.getAttribute('data-link') || '';
    var fEl = document.createElement('section'); fEl.className = 'ic-cult-featured';
    var wrap = document.createElement('div'); wrap.className = 'ic-cult-fcovwrap';
    var cov = fc.querySelector('.ic-cult-cov'); if (cov) { wrap.appendChild(cov.cloneNode(true)); }
    var fx = document.createElement('div'); fx.className = 'ic-cult-fx';
    fx.innerHTML = '<div class="ic-cult-feyebrow">Featured</div><div class="ic-cult-ftitle"></div><div class="ic-cult-fmeta"></div><p class="ic-cult-fblurb"></p>';
    fx.querySelector('.ic-cult-ftitle').textContent = tx(fc, '.ic-cult-mtitle');
    fx.querySelector('.ic-cult-fmeta').textContent = (LABEL[fty] || '') + ' \u00b7 ' + (fc.getAttribute('data-year') || '') + ' \u00b7 ' + tx(fc, '.ic-cult-mby');
    fx.querySelector('.ic-cult-fblurb').textContent = tx(fc, '.ic-cult-ov');
    if (flk) { var a = document.createElement('a'); a.className = 'ic-cult-fcta'; a.href = flk; a.target = '_blank'; a.rel = 'noopener'; a.textContent = 'Where to find it \u2192'; fx.appendChild(a); }
    fEl.appendChild(wrap); fEl.appendChild(fx);
    var bar = document.createElement('div'); bar.className = 'ic-cult-bar';
    var tabsEl = document.createElement('div'); tabsEl.className = 'ic-cult-tabs';
    var search = document.createElement('input'); search.type = 'search'; search.className = 'ic-cult-search'; search.placeholder = 'Search titles, creators\u2026';
    bar.appendChild(tabsEl); bar.appendChild(search);
    var TABS = [['all', 'All'], ['documentary', 'Documentaries'], ['film', 'Film & TV'], ['music', 'Music'], ['books', 'Books & Podcasts']]
      .filter(function (x) { return x[0] === 'all' || (GROUP[x[0]] ? GROUP[x[0]].some(function (g) { return byType[g]; }) : byType[x[0]]); });
    var active = 'all', query = '', chips = [];
    function inTab(ty2) { return active === 'all' || (GROUP[active] ? GROUP[active].indexOf(ty2) >= 0 : ty2 === active); }
    function cnt(k) { return cards.filter(function (c) { var ty2 = c.getAttribute('data-type'); return k === 'all' ? true : (GROUP[k] ? GROUP[k].indexOf(ty2) >= 0 : ty2 === k); }).length; }
    function apply() {
      cards.forEach(function (c) { var ok = inTab(c.getAttribute('data-type')) && (!query || (c.getAttribute('data-s') || '').indexOf(query) >= 0); c.style.display = ok ? '' : 'none'; });
      chips.forEach(function (ch) { ch.setAttribute('aria-selected', ch.getAttribute('data-f') === active); });
    }
    TABS.forEach(function (x) {
      var b = document.createElement('button'); b.className = 'ic-cult-tab'; b.setAttribute('data-f', x[0]); b.setAttribute('aria-selected', x[0] === active);
      b.appendChild(document.createTextNode(x[1] + ' '));
      var n = document.createElement('span'); n.className = 'ic-cult-tn'; n.textContent = cnt(x[0]); b.appendChild(n);
      b.addEventListener('click', function () { active = x[0]; apply(); });
      tabsEl.appendChild(b); chips.push(b);
    });
    search.addEventListener('input', function () { query = search.value.trim().toLowerCase(); apply(); });
    app.insertBefore(fEl, grid); app.insertBefore(bar, grid); apply();
  });
  /* Culture: recommend-a-title form -> gated review queue */
  ready(function () {
    var box = document.getElementById('ic-rec-form');
    if (!box) { return; }
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      var user = mw.config.get('wgUserName');
      if (!user) {
        box.innerHTML = '<p><em>Please <a href="/index.php?title=Special:UserLogin&returnto=' + encodeURIComponent(mw.config.get('wgPageName')) + '">log in</a> to recommend a title. New here? See the <a href="/index.php/ICE_List:Contribute">Contribute</a> page.</em></p>';
        return;
      }
      function fld(label, el) { var w = document.createElement('label'); w.className = 'ic-rec-field'; var s = document.createElement('span'); s.textContent = label; w.appendChild(s); w.appendChild(el); return w; }
      function inp(ph) { var i = document.createElement('input'); i.type = 'text'; i.placeholder = ph; return i; }
      var sel = document.createElement('select');
      ['Documentary', 'Film & TV', 'Music', 'Book', 'Podcast'].forEach(function (o) { var op = document.createElement('option'); op.textContent = o; sel.appendChild(op); });
      var title = inp('e.g. El Norte'), year = inp('e.g. 1983'), creator = inp('Director / artist / author'), link = inp('Where to watch / listen / read (URL)');
      var blurb = document.createElement('textarea'); blurb.rows = 3; blurb.placeholder = 'One sentence: what it is and why it belongs.';
      var btn = document.createElement('button'); btn.className = 'ic-cult-fcta'; btn.textContent = 'Submit recommendation';
      var msg = document.createElement('div'); msg.className = 'ic-rec-msg';
      [fld('Type', sel), fld('Title', title), fld('Year', year), fld('Creator', creator), fld('Link', link), fld('Why it belongs', blurb)].forEach(function (f) { box.appendChild(f); });
      box.appendChild(btn); box.appendChild(msg);
      function esc(x) { return (x || '').replace(/[|\[\]{}<>]/g, ' ').trim(); }
      btn.addEventListener('click', function () {
        if (!title.value.trim()) { msg.textContent = 'Please enter a title.'; return; }
        btn.disabled = true; msg.textContent = 'Submitting…';
        var id = 'cult-' + Date.now().toString(36) + Math.floor(Math.random() * 10000);
        var pn = 'ICE List:Submission - ' + id;
        var body = "'''Culture recommendation''' (submitted by [[User:" + user + "]]) — review and, if suitable, add to [[ICE List:Culture]].\n\n" +
          "* '''Type:''' " + esc(sel.value) + "\n* '''Title:''' " + esc(title.value) + "\n* '''Year:''' " + esc(year.value) +
          "\n* '''Creator:''' " + esc(creator.value) + "\n* '''Link:''' <nowiki>" + esc(link.value) + "</nowiki>\n* '''Why it belongs:''' " + esc(blurb.value) + "\n\n" +
          "[[Cat" + "egory:Culture recommendations]]\n";
        api.postWithToken('csrf', { action: 'edit', title: pn, text: body, createonly: 1, summary: 'Culture recommendation via form' })
          .then(function () { box.innerHTML = '<p><strong>✓ Thank you.</strong> Your recommendation has gone to the review queue and will appear on the library once an admin approves it.</p>'; })
          .catch(function (e) { btn.disabled = false; msg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
      });
    });
  });
  /* Profile "My desk": dashboard — stats, tools, pull-a-task buttons */
  ready(function () {
    var user = mw.config.get('wgUserName');
    if (!user) { return; }
    var pn = mw.config.get('wgPageName').replace(/_/g, ' ');
    if (pn !== 'User:' + user) { return; }
    var groups = mw.config.get('wgUserGroups') || [];
    if (!/^ICEListAdmin[0-9]+$/.test(user) && !['sysop', 'bureaucrat', 'reviewer', 'editor'].some(function (g) { return groups.indexOf(g) >= 0; })) { return; }
    var content = document.querySelector('#mw-content-text .mw-parser-output');
    if (!content) { return; }
    var panel = document.createElement('div'); panel.className = 'ic-desk';
    panel.innerHTML =
      '<div class="ic-desk__kicker">My desk</div>' +
      '<div class="ic-desk__stats"></div>' +
      '<div class="ic-desk__tools"></div>' +
      '<div class="ic-desk__pulls"><div class="ic-desk__pullkick">Pull something to work on</div><div class="ic-desk__pullbtns"><span class="ic-desk__loading">Loading your queues…</span></div><div class="ic-desk__pullresult"></div></div>';
    content.insertBefore(panel, content.firstChild);
    if (user === 'ICEListAdmin6') {
      var cs = document.createElement('div'); cs.className = 'ic-desk__culture';
      cs.innerHTML = '<div class="ic-desk__kicker">Culture recommendations</div><div id="ic-culture-inbox">Loading…</div>';
      panel.appendChild(cs);
    }
    var toolsEl = panel.querySelector('.ic-desk__tools');
    [['Review hub', '/index.php/ICE_List:Review'], ['Pending changes', '/index.php/Special:PendingChanges'], ['Unreviewed pages', '/index.php/Special:UnreviewedPages'], ['Submissions queue', '/index.php/ICE_List:Submissions']].forEach(function (l) {
      var sp = document.createElement('span'); sp.className = 'ic-hero__btn'; var a = document.createElement('a'); a.href = l[1]; a.target = '_blank'; a.rel = 'noopener'; a.textContent = l[0]; sp.appendChild(a); toolsEl.appendChild(sp);
    });
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      function unrevList() { return api.get({ action: 'query', list: 'unreviewedpages', urnamespace: 0, urlimit: 500, formatversion: 2 }).then(function (d) { return ((d.query && d.query.unreviewedpages) || []).map(function (p) { return p.title; }); }).catch(function () { return []; }); }
      function catList(cat) { return api.get({ action: 'query', list: 'categorymembers', cmtitle: cat, cmnamespace: 0, cmlimit: 200, formatversion: 2 }).then(function (d) { return ((d.query && d.query.categorymembers) || []).map(function (p) { return p.title; }); }).catch(function () { return []; }); }
      var statsEl = panel.querySelector('.ic-desk__stats'), btnsEl = panel.querySelector('.ic-desk__pullbtns'), resEl = panel.querySelector('.ic-desk__pullresult');
      function stat(n, label, href, red) { var d = document.createElement('div'); d.className = 'ic-stat' + (red ? ' ic-stat--red' : ''); d.innerHTML = '<a href="' + href + '" target="_blank" rel="noopener"><span class="n">' + n + '</span><span class="l">' + label + '</span></a>'; statsEl.appendChild(d); }
      function rnd(a) { return a[Math.floor(Math.random() * a.length)]; }
      function esc(x) { return x.replace(/</g, '&lt;'); }
      function url(t) { return '/index.php/' + encodeURIComponent(t.replace(/ /g, '_')); }
      function render(label, t, href, red, titles, review) {
        resEl.innerHTML = '<div class="ic-desk__task' + (red ? ' ic-desk__task--red' : '') + '">' +
          '<div class="ic-desk__taskkick">' + label + '</div>' +
          '<a class="ic-desk__title" href="' + href + '" target="_blank" rel="noopener">' + esc(t) + '</a>' +
          '<div class="ic-desk__act"><span class="ic-hero__btn ic-hero__btn--primary"><a href="' + href + '" target="_blank" rel="noopener">' + (review ? 'Review it →' : 'Open →') + '</a></span>' +
          '<button class="ic-desk__another" type="button">Pull another</button></div></div>';
        resEl.querySelector('.ic-desk__another').addEventListener('click', function () { showResult(label, titles, red, review); });
      }
      function showResult(label, titles, red, review) {
        if (!titles.length) { resEl.innerHTML = '<div class="ic-desk__task"><div class="ic-desk__taskkick">' + label + '</div>Nothing in this queue right now.</div>'; return; }
        var t = rnd(titles);
        if (review) {
          api.get({ action: 'query', prop: 'flagged', titles: t, formatversion: 2 }).then(function (d) {
            var p = d.query.pages[0], st = p.flagged && p.flagged.stable_revid;
            var href = st ? ('/index.php?title=' + encodeURIComponent(t.replace(/ /g, '_')) + '&diff=cur&oldid=' + st) : (url(t) + '?stable=0');
            render(label, t, href, red, titles, review);
          }).catch(function () { render(label, t, url(t), red, titles, review); });
        } else {
          render(label, t, url(t), red, titles, review);
        }
      }
      function addBtn(label, titles, red, review) {
        var sp = document.createElement('span'); sp.className = 'ic-hero__btn ic-hero__btn--primary';
        var a = document.createElement('a'); a.href = '#'; a.textContent = label + ' →';
        a.addEventListener('click', function (e) { e.preventDefault(); showResult(label, titles, red, review); });
        sp.appendChild(a); btnsEl.appendChild(sp);
      }
      Promise.all([unrevList(), catList('Category:Submission review'), catList('Category:Agents needing a photo')]).then(function (res) {
        var unrev = res[0], subs = res[1].filter(function (t) { return t.indexOf('ICE List:Submission - cult-') !== 0; }), photos = res[2];
        stat(unrev.length >= 500 ? '500+' : unrev.length, 'Pending review', '/index.php/Special:UnreviewedPages', true);
        stat(subs.length, 'Submissions to route', '/index.php/ICE_List:Submissions', false);
        stat(photos.length, 'Photos needed', '/index.php/Category:Agents_needing_a_photo', false);
        btnsEl.innerHTML = '';
        addBtn('Review a page', unrev, true, true);
        addBtn('Route a submission', subs, false, false);
        addBtn('Photo an agent', photos, false, false);
      }).catch(function () { btnsEl.innerHTML = '<em>Could not load your queues.</em>'; });
    });
  });
  /* Culture inbox: one-click review -> add to library (ICEListAdmin6) */
  ready(function () {
    if (mw.config.get('wgUserName') !== 'ICEListAdmin6') { return; }
    var box = document.getElementById('ic-culture-inbox');
    if (!box) { return; }
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      function field(txt, label) {
        var lines = txt.split('\n'), pre = "* '''" + label + ":'''";
        for (var i = 0; i < lines.length; i++) { if (lines[i].indexOf(pre) === 0) { return lines[i].slice(pre.length).replace(/<\/?nowiki>/g, '').trim(); } }
        return '';
      }
      function esc(x) { return (x || '').replace(/[|\[\]{}]/g, '').trim(); }
      api.get({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Culture recommendations', cmlimit: 100, formatversion: 2 }).then(function (d) {
        var mems = ((d.query && d.query.categorymembers) || []).filter(function (p) { return p.ns === 0 && p.title.indexOf('ICE List:Submission - cult-') === 0; });
        box.innerHTML = '';
        if (!mems.length) { box.innerHTML = '<p><em>No pending recommendations.</em></p>'; return; }
        mems.forEach(function (p) { renderCard(p.title); });
      });
      function renderCard(title) {
        api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: title, formatversion: 2 }).then(function (d) {
          var pg = d.query.pages[0]; if (!pg.revisions) { return; }
          var txt = pg.revisions[0].slots.main.content;
          var sm = txt.indexOf('[[User:'), sub = '';
          if (sm >= 0) { sub = txt.slice(sm + 7, txt.indexOf(']]', sm)).split('|')[0]; }
          var rec = { type: field(txt, 'Type'), title: field(txt, 'Title'), year: field(txt, 'Year'), creator: field(txt, 'Creator'), link: field(txt, 'Link'), blurb: field(txt, 'Why it belongs'), sub: sub };
          var card = document.createElement('div'); card.className = 'ic-inbox-card';
          card.innerHTML = '<div class="ic-inbox-h"><strong>' + esc(rec.title) + '</strong> <span class="ic-inbox-meta">' + esc(rec.type) + ' · ' + esc(rec.year) + ' · ' + esc(rec.creator) + '</span></div>' +
            '<div class="ic-inbox-blurb">' + esc(rec.blurb) + '</div>' +
            '<div class="ic-inbox-link">' + (rec.link ? '<a href="' + esc(rec.link) + '" target="_blank" rel="noopener">' + esc(rec.link) + '</a>' : '(no link)') + '</div>' +
            '<div class="ic-inbox-sub">from ' + esc(rec.sub) + '</div>';
          var actions = document.createElement('div'); actions.className = 'ic-inbox-actions';
          var msg = document.createElement('span'); msg.className = 'ic-inbox-msg';
          var addb = document.createElement('button'); addb.className = 'ic-inbox-add'; addb.textContent = 'Add to library';
          var disb = document.createElement('button'); disb.className = 'ic-inbox-discard'; disb.textContent = 'Discard';
          addb.addEventListener('click', function () {
            addb.disabled = true; disb.disabled = true; msg.textContent = 'Adding…';
            addToLibrary(rec).then(function () { return resolve(title, 'added to [[ICE List:Culture]]'); }).then(function () {
              msg.textContent = '✓ Added.'; card.style.opacity = 0.5; setTimeout(function () { card.remove(); }, 700);
            }).catch(function (e) { addb.disabled = false; disb.disabled = false; msg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
          });
          disb.addEventListener('click', function () {
            if (!confirm('Discard this recommendation?')) { return; }
            addb.disabled = true; disb.disabled = true; msg.textContent = '…';
            resolve(title, 'discarded').then(function () { card.style.opacity = 0.5; setTimeout(function () { card.remove(); }, 500); }).catch(function () { msg.textContent = 'Error'; });
          });
          actions.appendChild(addb); actions.appendChild(disb); actions.appendChild(msg); card.appendChild(actions);
          box.appendChild(card);
        });
      }
      function addToLibrary(rec) {
        var pick = '{{Culture pick|type=' + esc(rec.type) + '|title=' + esc(rec.title) + '|year=' + esc(rec.year) + '|creator=' + esc(rec.creator) + '|blurb=' + esc(rec.blurb) + '|link=' + esc(rec.link) + '}}';
        return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: 'ICE List:Culture', formatversion: 2 }).then(function (d) {
          var txt = d.query.pages[0].revisions[0].slots.main.content;
          var anchor = '\n</div>\n</div>', i = txt.indexOf(anchor);
          if (i < 0) { throw new Error('library grid not found'); }
          var nt = txt.slice(0, i) + '\n' + pick + txt.slice(i);
          return api.postWithToken('csrf', { action: 'edit', title: 'ICE List:Culture', text: nt, summary: 'Add culture pick from inbox: ' + esc(rec.title) });
        });
      }
      function resolve(title, note) {
        return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: title, formatversion: 2 }).then(function (d) {
          var txt = d.query.pages[0].revisions[0].slots.main.content;
          txt = txt.replace('[[Cat' + 'egory:Culture recommendations]]', '[[Cat' + 'egory:Resolved culture recommendations]]');
          txt = "'''Resolved:''' " + note + '\n\n' + txt;
          return api.postWithToken('csrf', { action: 'edit', title: title, text: txt, summary: 'Resolve culture recommendation: ' + note });
        });
      }
    });
  });  /* On-wiki submission forms (incident / agent / vehicle) -> gated review queue */
  ready(function () {
    var box = document.getElementById('ic-submit-form');
    if (!box) { return; }
    var kind = (box.getAttribute('data-kind') || '').toLowerCase();
    var FIELDS = {
      incident: [['date', 'Date (YYYY-MM-DD)'], ['city', 'City'], ['state', 'State'], ['agencies', 'Agencies involved (ICE, CBP…)'], ['description', 'What happened', 1], ['source', 'Source link(s)']],
      agent: [['name', 'Agent name (leave blank if unidentified)'], ['agency', 'Agency (ICE, CBP, HSI…)'], ['role', 'Role / title'], ['field_office', 'Field office'], ['state', 'State'], ['description', 'Description / distinguishing details', 1], ['source', 'Source link(s)']],
      vehicle: [['plate', 'License plate'], ['state', 'Plate state'], ['make_model', 'Make / model'], ['color', 'Color'], ['agency', 'Agency (if known)'], ['location', 'Where seen'], ['description', 'Notes', 1], ['source', 'Source link(s)']]
    };
    var defs = FIELDS[kind];
    if (!defs) { box.innerHTML = '<em>Unknown submission type.</em>'; return; }
    function CAT(n) { return '[[Cat' + 'egory:' + n + ']]'; }
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      var user = mw.config.get('wgUserName');
      if (!user) { box.innerHTML = '<p><em>Please <a href="/index.php?title=Special:UserLogin&returnto=' + encodeURIComponent(mw.config.get('wgPageName')) + '">log in</a> to submit.</em></p>'; return; }
      var inputs = {};
      defs.forEach(function (f) {
        var w = document.createElement('label'); w.className = 'ic-rec-field';
        var s = document.createElement('span'); s.textContent = f[1]; w.appendChild(s);
        var el = f[2] ? document.createElement('textarea') : document.createElement('input');
        if (f[2]) { el.rows = 3; } else { el.type = 'text'; }
        w.appendChild(el); box.appendChild(w); inputs[f[0]] = el;
      });
      var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ic-cult-fcta'; btn.textContent = 'Submit for review';
      var msg = document.createElement('div'); msg.className = 'ic-rec-msg';
      box.appendChild(btn); box.appendChild(msg);
      function esc(x) { return (x || '').replace(/[|\[\]{}<>]/g, ' ').trim(); }
      btn.addEventListener('click', function () {
        if (!inputs.description.value.trim() && !inputs[defs[0][0]].value.trim()) { msg.textContent = 'Please fill in the details.'; return; }
        btn.disabled = true; msg.textContent = 'Submitting…';
        var id = kind + '-' + Date.now().toString(36) + Math.floor(Math.random() * 10000);
        var pn = 'ICE List:Submission - ' + id;
        var body = "'''" + kind.charAt(0).toUpperCase() + kind.slice(1) + " submission''' (submitted by [[User:" + user + "]]) - review and route.\n\n* '''Kind:''' " + kind + "\n";
        defs.forEach(function (f) {
          var v = inputs[f[0]].value;
          if (v && v.trim()) {
            var lbl = f[0].charAt(0).toUpperCase() + f[0].slice(1).replace(/_/g, ' ');
            body += (f[0] === 'source') ? "* '''Source:''' <nowiki>" + esc(v) + "</nowiki>\n" : "* '''" + lbl + ":''' " + esc(v) + "\n";
          }
        });
        body += "\n" + CAT('Submission review') + CAT('Pending submissions') + "\n";
        api.postWithToken('csrf', { action: 'edit', title: pn, text: body, createonly: 1, summary: 'On-wiki ' + kind + ' submission' })
          .then(function () { box.innerHTML = '<p><strong>✓ Thank you.</strong> Your ' + kind + ' submission has gone to the review queue. A reviewer will check it before anything is published — and your identity is not recorded.</p>'; })
          .catch(function (e) { btn.disabled = false; msg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
      });
    });
  });
  /* Submission review inbox: list on-wiki submissions, promote to a page or discard */
  ready(function () {
    var box = document.getElementById('ic-sub-inbox');
    if (!box) { return; }
    var groups = mw.config.get('wgUserGroups') || [];
    if (!['sysop', 'bureaucrat', 'reviewer', 'editor'].some(function (g) { return groups.indexOf(g) >= 0; })) { box.innerHTML = '<em>Reviewer access required.</em>'; return; }
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      function field(txt, label) { var lines = txt.split('\n'), pre = "* '''" + label + ":'''"; for (var i = 0; i < lines.length; i++) { if (lines[i].indexOf(pre) === 0) { return lines[i].slice(pre.length).replace(/<\/?nowiki>/g, '').trim(); } } return ''; }
      function esc(x) { return (x || '').replace(/[|\[\]{}]/g, '').trim(); }
      function CAT(n) { return '[[Cat' + 'egory:' + n + ']]'; }
      var OB = '{' + '{';
      function yearCat(date) { var m = (date || '').match(/20\d\d/); return m ? '[[Cat' + 'egory:Incidents in ' + m[0] + ']]' : ''; }
      var ALL = ['Date', 'City', 'County', 'State', 'Agencies', 'Name', 'Agency', 'Role', 'Field office', 'Plate', 'Make model', 'Color', 'Location', 'Description', 'Source'];
      api.get({ action: 'query', list: 'categorymembers', cmtitle: 'Category:Pending submissions', cmnamespace: 0, cmlimit: 100, formatversion: 2 }).then(function (d) {
        var mems = ((d.query && d.query.categorymembers) || []).filter(function (p) { return p.title.indexOf('ICE List:Submission - ') === 0; });
        box.innerHTML = ''; if (!mems.length) { box.innerHTML = '<p><em>No pending submissions.</em></p>'; return; }
        mems.forEach(function (p) { card(p.title); });
      });
      function card(title) {
        api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: title, formatversion: 2 }).then(function (d) {
          var pg = d.query.pages[0]; if (!pg.revisions) { return; }
          var txt = pg.revisions[0].slots.main.content;
          var kind = field(txt, 'Kind') || 'submission';
          var sm = txt.indexOf('[[User:'), sub = ''; if (sm >= 0) { sub = txt.slice(sm + 7, txt.indexOf(']]', sm)).split('|')[0]; }
          var rec = { kind: kind, sub: sub }; ALL.forEach(function (l) { var v = field(txt, l); if (v) { rec[l.toLowerCase().replace(/ /g, '_')] = v; } });
          var c = document.createElement('div'); c.className = 'ic-inbox-card';
          var rows = ALL.map(function (l) { var k = l.toLowerCase().replace(/ /g, '_'); return rec[k] ? ('<div class="ic-subrow"><span>' + l + '</span> ' + esc(rec[k]) + '</div>') : ''; }).join('');
          c.innerHTML = '<div class="ic-inbox-h"><strong>' + kind.toUpperCase() + ' submission</strong> <span class="ic-inbox-meta">from ' + esc(sub) + '</span></div>' + rows;
          var act = document.createElement('div'); act.className = 'ic-inbox-actions';
          var msg = document.createElement('span'); msg.className = 'ic-inbox-msg';
          var pb = document.createElement('button'); pb.className = 'ic-inbox-add'; pb.type = 'button'; pb.textContent = 'Promote to page';
          var db = document.createElement('button'); db.type = 'button'; db.textContent = 'Discard';
          pb.addEventListener('click', function () { promote(title, kind, rec, c, msg, pb, db); });
          db.addEventListener('click', function () { if (!confirm('Discard this submission?')) { return; } pb.disabled = db.disabled = true; msg.textContent = '…'; resolve(title, 'discarded').then(function () { c.style.opacity = 0.5; setTimeout(function () { c.remove(); }, 500); }).catch(function () { msg.textContent = 'Error'; }); });
          act.appendChild(pb); act.appendChild(db); act.appendChild(msg); c.appendChild(act); box.appendChild(c);
        });
      }
      function buildPage(kind, rec) {
        if (kind === 'incident') {
          var loc = [rec.city, rec.state].filter(Boolean).join(', ');
          var title = ((rec.date ? rec.date + ' ' : '') + 'Incident' + (loc ? ' in ' + loc : '')).trim();
          var body = OB + 'Infobox incident\n| title = ' + esc(title) + '\n| date = ' + esc(rec.date || '') + '\n| city = ' + esc(rec.city || '') + '\n| state = ' + esc(rec.state || '') + '\n| agencies = ' + esc(rec.agencies || '') + '\n| status = Developing (from a volunteer submission).\n| verification = Developing - submitted for review.\n| key_sources =\n* ' + esc(rec.source || '-') + '\n}}\n\n' + esc(rec.description || '') + '\n\n' + OB + 'MediaForIncident}}\n\n' + CAT('Incidents') + CAT('Incidents involving ICE') + (rec.state ? CAT('Incidents in ' + esc(rec.state)) : '') + yearCat(rec.date) + '\n';
          return { title: title, body: body };
        }
        if (kind === 'agent') {
          if (!rec.name) { return null; }
          var b = OB + 'Agent page\n|name=' + esc(rec.name) + '\n|agency=' + esc(rec.agency || 'Unknown') + '\n|role=' + esc(rec.role || '') + '\n|field_office=' + esc(rec.field_office || '') + '\n|state=' + esc(rec.state || '') + '\n|status=Active\n|image=nopfp.png\n|verification=Submitted for review\n|summary=' + esc(rec.description || '') + '\n}}\n\n== Documented Incidents ==\n' + OB + 'IncidentsForAgent}}\n\n' + OB + 'MediaForAgent}}\n\n' + CAT('Agents') + CAT('Agents needing a photo') + '\n';
          return { title: rec.name, body: b };
        }
        if (kind === 'vehicle') {
          var vt = rec.plate ? ('Vehicle:' + rec.plate) : ('Vehicle submission ' + (rec.state || ''));
          var vb = "'''Vehicle'''" + (rec.plate ? ' plate ' + esc(rec.plate) : '') + (rec.state ? ' (' + esc(rec.state) + ')' : '') + '.\n\n* Make/model: ' + esc(rec.make_model || '-') + '\n* Color: ' + esc(rec.color || '-') + '\n* Agency: ' + esc(rec.agency || '-') + '\n* Where seen: ' + esc(rec.location || '-') + '\n* Notes: ' + esc(rec.description || '-') + '\n* Source: ' + esc(rec.source || '-') + '\n\n' + CAT('Vehicles') + '\n';
          return { title: vt, body: vb };
        }
        return null;
      }
      function promote(title, kind, rec, c, msg, pb, db) {
        var pg = buildPage(kind, rec);
        if (!pg) { msg.textContent = 'Unidentified agent / unknown kind — open the submission to build it manually.'; return; }
        pb.disabled = db.disabled = true; msg.textContent = 'Creating ' + pg.title + '…';
        api.get({ action: 'query', titles: pg.title, formatversion: 2 }).then(function (d) {
          if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('A page named "' + pg.title + '" already exists.'); }
          return api.postWithToken('csrf', { action: 'edit', title: pg.title, createonly: 1, text: pg.body, summary: 'Create from reviewed submission' });
        }).then(function () { return resolve(title, 'promoted to [[' + pg.title + ']]'); }).then(function () {
          msg.innerHTML = '✓ Created <a href="/index.php/' + encodeURIComponent(pg.title.replace(/ /g, '_')) + '" target="_blank" rel="noopener">' + esc(pg.title) + '</a> (now pending review).';
          c.style.opacity = 0.6; setTimeout(function () { c.remove(); }, 1600);
        }).catch(function (e) { pb.disabled = db.disabled = false; msg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
      }
      function resolve(title, note) {
        return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: title, formatversion: 2 }).then(function (d) {
          var txt = d.query.pages[0].revisions[0].slots.main.content;
          txt = txt.replace(CAT('Pending submissions'), CAT('Resolved submissions'));
          txt = "'''Resolved:''' " + note + '\n\n' + txt;
          return api.postWithToken('csrf', { action: 'edit', title: title, text: txt, summary: 'Resolve submission: ' + note });
        });
      }
    });
  });  /* Quick add: create incident / agent / vehicle / death directly from your profile */
  ready(function () {
    var user = mw.config.get('wgUserName');
    if (!user) { return; }
    var pn = mw.config.get('wgPageName').replace(/_/g, ' ');
    if (pn !== 'User:' + user) { return; }
    var groups = mw.config.get('wgUserGroups') || [];
    if (!/^ICEListAdmin[0-9]+$/.test(user) && !['sysop', 'bureaucrat', 'reviewer', 'editor'].some(function (g) { return groups.indexOf(g) >= 0; })) { return; }
    var content = document.querySelector('#mw-content-text .mw-parser-output');
    if (!content) { return; }
    var panel = document.createElement('div'); panel.className = 'ic-desk ic-quickadd';
    panel.innerHTML = '<div class="ic-desk__kicker">Quick add</div><div class="ic-qa__btns"></div><div class="ic-qa__form"></div>';
    var desk = content.querySelector('.ic-desk');
    if (desk) { content.insertBefore(panel, desk.nextSibling); } else { content.insertBefore(panel, content.firstChild); }
    var TYPES = {
      incident: [['date', 'Date (YYYY-MM-DD)'], ['city', 'City'], ['state', 'State'], ['agencies', 'Agencies (ICE, CBP…)'], ['description', 'What happened', 1], ['source', 'Source link']],
      agent: [['name', 'Full name (required)'], ['agency', 'Agency'], ['role', 'Role / title'], ['field_office', 'Field office'], ['state', 'State'], ['description', 'Description / details', 1], ['source', 'Source link']],
      vehicle: [['plate', 'License plate (required)'], ['state', 'Plate state'], ['make_model', 'Make / model'], ['color', 'Color'], ['agency', 'Agency'], ['location', 'Where seen'], ['description', 'Notes', 1], ['source', 'Source link']],
      death: [['name', 'Name of the deceased (required)'], ['age', 'Age'], ['nationality', 'Nationality'], ['date', 'Date of death (YYYY-MM-DD)'], ['location', 'Location / facility'], ['state', 'State'], ['agency', 'Agency'], ['cause', 'Reported cause'], ['description', 'What happened', 1], ['source', 'Source link']]
    };
    var btnsEl = panel.querySelector('.ic-qa__btns'), formEl = panel.querySelector('.ic-qa__form'), api = null;
    function esc(x) { return (x || '').replace(/[|\[\]{}<>]/g, ' ').trim(); }
    function CAT(n) { return '[[Cat' + 'egory:' + n + ']]'; }
    var OB = '{' + '{';
    function yearCat(date) { var m = (date || '').match(/20\d\d/); return m ? CAT('Incidents in ' + m[0]) : ''; }
    Object.keys(TYPES).forEach(function (k) {
      var sp = document.createElement('span'); sp.className = 'ic-hero__btn ic-hero__btn--primary';
      var a = document.createElement('a'); a.href = '#'; a.textContent = 'Add ' + k;
      a.addEventListener('click', function (e) { e.preventDefault(); openForm(k); });
      sp.appendChild(a); btnsEl.appendChild(sp);
    });
    if (mw.config.get('wgUserName') === 'ICEListAdmin6') { var mv = document.createElement('div'); mv.className = 'ic-qa__admin'; mv.innerHTML = '<a href="/index.php/ICE_List:Add_volunteer">+ Add a submission volunteer →</a>'; panel.appendChild(mv); }
    function build(kind, v) {
      if (kind === 'incident') {
        var loc = [v.city, v.state].filter(Boolean).join(', ');
        var title = ((v.date ? v.date + ' ' : '') + 'Incident' + (loc ? ' in ' + loc : '')).trim();
        return { title: title, body: OB + 'Infobox incident\n| title = ' + esc(title) + '\n| date = ' + esc(v.date) + '\n| city = ' + esc(v.city) + '\n| state = ' + esc(v.state) + '\n| agencies = ' + esc(v.agencies) + '\n| status = Developing.\n| verification = Added via quick-add.\n| key_sources =\n* ' + esc(v.source || '-') + '\n}}\n\n' + esc(v.description) + '\n\n' + OB + 'MediaForIncident}}\n\n' + CAT('Incidents') + CAT('Incidents involving ICE') + (v.state ? CAT('Incidents in ' + esc(v.state)) : '') + yearCat(v.date) + '\n' };
      }
      if (kind === 'agent') {
        if (!v.name) { return { err: 'Name is required.' }; }
        return { title: v.name, body: OB + 'Agent page\n|name=' + esc(v.name) + '\n|agency=' + esc(v.agency || 'Unknown') + '\n|role=' + esc(v.role) + '\n|field_office=' + esc(v.field_office) + '\n|state=' + esc(v.state) + '\n|status=Active\n|image=nopfp.png\n|verification=Added via quick-add\n|summary=' + esc(v.description) + '\n}}\n\n== Documented Incidents ==\n' + OB + 'IncidentsForAgent}}\n\n' + OB + 'MediaForAgent}}\n\n' + CAT('Agents') + CAT('Agents needing a photo') + '\n' };
      }
      if (kind === 'vehicle') {
        if (!v.plate) { return { err: 'Plate is required.' }; }
        return { title: 'Vehicle:' + v.plate, body: "'''Vehicle''' plate " + esc(v.plate) + (v.state ? ' (' + esc(v.state) + ')' : '') + '.\n\n* Make/model: ' + esc(v.make_model || '-') + '\n* Color: ' + esc(v.color || '-') + '\n* Agency: ' + esc(v.agency || '-') + '\n* Where seen: ' + esc(v.location || '-') + '\n* Notes: ' + esc(v.description || '-') + '\n* Source: ' + esc(v.source || '-') + '\n\n' + CAT('Vehicles') + '\n' };
      }
      if (kind === 'death') {
        if (!v.name) { return { err: 'Name is required.' }; }
        var meta = [v.age, v.nationality].filter(Boolean).join(', '), sk = esc(v.date) + ' ' + esc(v.name);
        return { title: 'Death of ' + v.name + ' in ICE custody', body: OB + 'Infobox incident\n| title = ' + esc(v.name) + '\n| date = ' + esc(v.date) + '\n| state = ' + esc(v.state) + '\n| country = United States\n| location_detail = ' + esc(v.location) + '\n| agencies = ' + esc(v.agency || 'ICE') + '\n| operation_type = Death in immigration detention / custody\n| status = Autopsy pending.\n| people_affected = 1\n| deaths = 1' + (v.cause ? ' — ' + esc(v.cause) : '') + '\n| verification = Added via quick-add, pending review.\n| key_sources =\n* ' + esc(v.source || '-') + '\n}}\n\n' + "'''" + esc(v.name) + "'''" + (meta ? ' (' + meta + ')' : '') + ' died' + (v.date ? ' on ' + esc(v.date) : '') + ' while in the custody of ICE' + (v.location ? ', at ' + esc(v.location) : '') + '.' + (v.cause ? ' Reported cause: ' + esc(v.cause) + '.' : '') + '\n\n' + esc(v.description) + '\n\n' + OB + 'MediaForIncident}}\n\n' + CAT('Incidents') + CAT('Incidents involving ICE') + (v.state ? CAT('Incidents in ' + esc(v.state)) : '') + CAT('Deaths by immigration enforcement|' + sk) + CAT('Deaths in immigration detention|' + sk) + yearCat(v.date) + '\n' };
      }
    }
    function openForm(kind) {
      formEl.innerHTML = ''; var inputs = {};
      TYPES[kind].forEach(function (f) {
        var w = document.createElement('label'); w.className = 'ic-rec-field';
        var s = document.createElement('span'); s.textContent = f[1]; w.appendChild(s);
        var el = f[2] ? document.createElement('textarea') : document.createElement('input');
        if (f[2]) { el.rows = 3; } else { el.type = 'text'; }
        w.appendChild(el); formEl.appendChild(w); inputs[f[0]] = el;
      });
      var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ic-inbox-add'; btn.textContent = 'Create ' + kind + ' page';
      var cancel = document.createElement('button'); cancel.type = 'button'; cancel.textContent = 'Cancel';
      var msg = document.createElement('div'); msg.className = 'ic-rec-msg';
      var row = document.createElement('div'); row.className = 'ic-inbox-actions'; row.appendChild(btn); row.appendChild(cancel);
      formEl.appendChild(row); formEl.appendChild(msg);
      cancel.addEventListener('click', function () { formEl.innerHTML = ''; });
      btn.addEventListener('click', function () {
        var v = {}; Object.keys(inputs).forEach(function (k) { v[k] = inputs[k].value; });
        var pg = build(kind, v);
        if (pg.err) { msg.textContent = pg.err; return; }
        btn.disabled = true; msg.textContent = 'Creating…';
        mw.loader.using(['mediawiki.api']).then(function () {
          if (!api) { api = new mw.Api(); }
          api.get({ action: 'query', titles: pg.title, formatversion: 2 }).then(function (d) {
            if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('A page named "' + pg.title + '" already exists.'); }
            return api.postWithToken('csrf', { action: 'edit', title: pg.title, createonly: 1, text: pg.body, summary: 'Quick-add ' + kind });
          }).then(function () {
            msg.innerHTML = '✓ Created <a href="/index.php/' + encodeURIComponent(pg.title.replace(/ /g, '_')) + '" target="_blank" rel="noopener">' + esc(pg.title) + '</a>.';
            setTimeout(function () { formEl.innerHTML = ''; }, 3000);
          }).catch(function (e) { btn.disabled = false; msg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
        });
      });
    }
  });  /* Add a confined submission volunteer: account + submitter group + profile */
  ready(function () {
    var box = document.getElementById('ic-addvol');
    if (!box) { return; }
    var groups = mw.config.get('wgUserGroups') || [];
    if (mw.config.get('wgUserName') !== 'ICEListAdmin6') { box.innerHTML = '<em>This tool is restricted.</em>'; return; }
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      function fld(label, el) { var w = document.createElement('label'); w.className = 'ic-rec-field'; var s = document.createElement('span'); s.textContent = label; w.appendChild(s); w.appendChild(el); return w; }
      function gen() { var c = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; var p = ''; for (var i = 0; i < 14; i++) { p += c.charAt(Math.floor(Math.random() * c.length)); } return p; }
      var uname = document.createElement('input'); uname.type = 'text'; uname.placeholder = 'Username for the new volunteer';
      var pass = document.createElement('input'); pass.type = 'text'; pass.value = gen();
      var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ic-inbox-add'; btn.textContent = 'Create volunteer';
      var msg = document.createElement('div'); msg.className = 'ic-rec-msg';
      box.appendChild(fld('Username', uname)); box.appendChild(fld('Password (auto-generated, editable)', pass)); box.appendChild(btn); box.appendChild(msg);
      function esc(x) { return (x || '').replace(/</g, '&lt;'); }
      btn.addEventListener('click', function () {
        var u = uname.value.trim(), p = pass.value.trim();
        if (!u || p.length < 8) { msg.textContent = 'Enter a username and a password of 8+ characters.'; return; }
        btn.disabled = true;
        var log = [];
        function show() { msg.innerHTML = log.join('<br>'); }
        // 1) create account
        log.push('Creating account…'); show();
        api.get({ action: 'query', meta: 'tokens', type: 'createaccount', formatversion: 2 }).then(function (d) {
          var tok = d.query.tokens.createaccounttoken;
          return api.post({ action: 'createaccount', createtoken: tok, username: u, password: p, retype: p, createreturnurl: location.href, reason: 'Confined submission volunteer' });
        }).then(function (r) {
          var st = r.createaccount && r.createaccount.status;
          log[log.length - 1] = (st === 'PASS') ? '✓ Account created.' : '✗ Account: ' + ((r.createaccount && r.createaccount.message) || st || 'failed — create it via Special:CreateAccount, then re-run to add group + profile.');
          show();
        }).catch(function (e) {
          log[log.length - 1] = '✗ Account: ' + (e && e.message ? e.message : e) + ' (try Special:CreateAccount, then re-run).'; show();
        }).then(function () {
          // 2) profile page
          log.push('Creating profile…'); show();
          var prof = "''Volunteer contributor to the ICE List.''\n\n== Submit ==\nThanks for helping document immigration enforcement. Everything you send is reviewed before it is published, and your identity is not recorded:\n* [[ICE List:Submit incident (test)|Report an incident]]\n* [[ICE List:Submit agent (test)|Submit an agent]]\n* [[ICE List:Submit vehicle (test)|Submit a vehicle]]\n";
          return api.postWithToken('csrf', { action: 'edit', title: 'User:' + u, text: prof, createonly: 1, summary: 'Create volunteer profile' })
            .then(function () { log[log.length - 1] = '✓ Profile created.'; show(); })
            .catch(function (e) { log[log.length - 1] = '• Profile: ' + (e && e.message ? e.message : e); show(); });
        }).then(function () {
          // 3) submitter group
          log.push('Adding to submitter group…'); show();
          return api.postWithToken('userrights', { action: 'userrights', user: u, add: 'submitter', reason: 'confined volunteer' })
            .then(function () { log[log.length - 1] = '✓ Added to submitter group (confined to submissions).'; show(); })
            .catch(function () { log[log.length - 1] = '• Submitter group not applied — paste the submitter LocalSettings block, then add the group in Special:UserRights.'; show(); });
        }).then(function () {
          log.push('<strong>Hand these to the volunteer — Username: <code>' + esc(u) + '</code> · Password: <code>' + esc(p) + '</code></strong> · <a href="/index.php/User:' + encodeURIComponent(u) + '" target="_blank" rel="noopener">profile</a>');
          show();
          uname.value = ''; pass.value = gen(); btn.disabled = false;
        });
      });
    });
  });
})();
/* ============ end ICE List client tools ============ */