Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
/**
 *
 *    █████████     █████   █████    ███████████     █████
 *   ███▒▒▒▒▒███   ▒▒███   ▒▒███    ▒▒███▒▒▒▒▒███   ▒▒███ 
 *  ███     ▒▒▒     ▒███    ▒███     ▒███    ▒███    ▒███ 
 * ▒███             ▒███    ▒███     ▒██████████     ▒███ 
 * ▒███             ▒▒███   ███      ▒███▒▒▒▒▒▒      ▒███ 
 * ▒▒███     ███     ▒▒▒█████▒       ▒███            ▒███ 
 *  ▒▒█████████        ▒▒███         █████           █████
 *   ▒▒▒▒▒▒▒▒▒          ▒▒▒         ▒▒▒▒▒           ▒▒▒▒▒ 
 * 
 *                      Interceptor
 *                        v0.9.18
 *                      User:TonySt
 */

!function(){"use strict";const t={"0.9.18":["All edit summaries shown up top now, instead of only the latest","Edit summaries can be clicked to open their individual diffs","Revision count moved from footer to edit summaries area","View your device stats in the menu","Bug fix: vandalism button didn't immediately update after manual report"],"0.9.17":["Styling tweaks for prompts","Styling change for disabled vandalism button","Code organization changes","Handle sandbox link in other warners' signature","Add non-English communication warning on talk"],"0.9.16":["Colorblind mode","COI single-issue template","Fix for old blocks not showing as expired","Partial blocks no longer prevent reporting","Predefined manual AIV report comments","Better matching for prior uw talk templates","Style changes"],"0.9.15":["Fixed toolbar overflow on narrow screens","Different warnings in dashboard per namespace","Prevent autosubscribe to AIV when reporting"],"0.9.14":["Bigger dashboard buttons","Wider dashboard layout on mobile","Diffs are now shown slightly faster","Lots of internal stuff","No longer includes vandal's username in edit summary of AIV if reported manually","No longer includes vandal's name in edit summary of rollbacks if reported to AIV already","Birds are dinosaurs"],"0.9.13":["Redesign","In dashboard on mobile, tap the warning type to unlock it, then tap it again to use auto level","(Note: 'vandalism' button in toolbar is always unlocked)","New icons","Opening user talk page on 'other' rollbacks is optional now","Better logic for showing talk page warnings","Better state management for buttons","Bug fix for keyboard commands in modal prompts","Bug fix for 4im templates not being recognized","UI elements are now updated instead of replaced (better performance)","Texas is about twice the size of Germany"],"0.9.12":["Reduced API calls by almost half","Edits now appear more quickly","Removed 'draft' namespace (and its talk) since it's mostly noise","Improved tracking for edit/page staleness","More accurate 'analyzing' number","No more empty diffs from self-undoing editors (we just ignore those edits now)","A herd of giraffes is called a tower"],"0.9.11":["<b>MLRS</b>: Users are monitored if multiple other rollbackers roll them back","Restore visual feedback for blanked talk page warnings","Revert menu is now called dashboard","Reason for change being shown is now given","Better handling of situations where Cluebot causes a revision to go stale","Fixed a bug where page history wouldn't scroll properly on mobile","There are about 25 blimps"],"0.9.10":["Status bar","Clock in your preferred time zone","Improved logic behind just-monitored users","Better handling of connection loss","Slightly bigger gap between revert types","20% fewer calories"],"0.9.0":["New design","Mobile: <b>swipe left</b> for previous, <b>swipe right</b> for next","Logic improvements","Fancy changelog"]},e=Object.freeze({VERSION:"0.9.18",API:{RC_LOOKBACK_MS:3e4,RV_LIMIT:50,MONITOR_PAST_ROLLBACKS_HOURS:1,BATCH_SIZE:50,NAMESPACE_FILTER:"0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|100|101|126|127|828|829"},QUEUE:{MAX_SIZE:50,TOO_OLD_FOR_US_TO_CARE_ABOUT_MS:3e5},LOGIC:{REFRESH_RATE_MS:2001,MLRS_INTERVAL_MS:2e4,MLRS_LOOKBACK_MS:3e5,DEFAULT_SCORE_THRESHOLD:.15,PROMOTE_DEBOUNCE_MS:300,STALE_WARNING_METHOD:"previous_seven_days"},UI:{HISTORY_LIMIT:15,SCROLL_DELAY_MS:100,ORES_COLORS:{HIGH:.9,MEDIUM:.7,LOW:.5}}}),n=[1,3,5,7,9,11,13,15,101,119,127,829],o=[0,2,4,6,8,10,12,14,100,118,126,828],i=[2],r=[{type:"vandalism",label:"Vandalism",singleIssue:!1,noLevel4:!1,exclude_namespaces:[]},{type:"unsourced",label:"Unsourced",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...n,...i]},{type:"npa",label:"Personal attacks",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...o]},{type:"disruptive",label:"Disruptive",singleIssue:!1,noLevel4:!0,exclude_namespaces:[]},{type:"delete",label:"Blanking",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...n,...i]},{type:"tpv",label:"Editing others' comments",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...o]},{type:"advert",label:"Advert/promo",singleIssue:!1,noLevel4:!1,exclude_namespaces:[]},{type:"spam",label:"Spam links",singleIssue:!1,noLevel4:!1,exclude_namespaces:[]},{type:"test",label:"Test edits",singleIssue:!1,noLevel4:!0,exclude_namespaces:[...i]},{type:"talkinarticle",label:"Commentary",singleIssue:!1,noLevel4:!0,exclude_namespaces:[...n,...i]},{type:"npov",label:"NPOV",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...n,...i]},{type:"error",label:"False facts",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...n,...i]},{type:"ai",label:"Raw AI paste",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...n,...i]},{type:"mos",label:"MOS violation",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...n,...i]},{type:"chat",label:"Talk page not forum",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...o]},{type:"aitalk",label:"AI talk comments",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...o]},{type:"harass",label:"Harassment",singleIssue:!1,noLevel4:!1,exclude_namespaces:[...o]},{type:"english",label:"Non-English communication",singleIssue:!0,exclude_namespaces:[...o]},{type:"ewsoft",label:"Edit warring (softer)",singleIssue:!0,exclude_namespaces:[...n]},{type:"badlistentry",label:"Non-notable in list",singleIssue:!0,exclude_namespaces:[...n]},{type:"notenglishedit",label:"Non-English content",singleIssue:!0,exclude_namespaces:[...n]},{type:"editsummary",label:"No edit summary",singleIssue:!0,exclude_namespaces:[...n]},{type:"hijacking",label:"Hijacking",singleIssue:!0,exclude_namespaces:[...n]},{type:"engvar",label:"English variety disruption",singleIssue:!0,exclude_namespaces:[...n]},{type:"elinbody",label:"External link in body",singleIssue:!0,exclude_namespaces:[...n]},{type:"coi",label:"Conflict of Interest",singleIssue:!0,exclude_namespaces:[...n]}],a={vandalism:"non-constructive edits",unsourced:"unsourced content added",disruptive:"disruptive content",delete:"unexplained deletion",advert:"potentially [[WP:PROMO|promotional]] edits",spam:"external links added",test:"editing test",talkinarticle:"commentary in article",npov:"non-[[WP:NPOV|neutral]] content",error:"erroneous facts or statistics added",other:"edits",other_agf:"[[WP:AGF|good-faith]] edits"},s=["LTA"];function l(t){const e=Math.floor((Date.now()-t)/1e3),n=[[31536e3,"year"],[2592e3,"month"],[86400,"day"],[3600,"hour"],[60,"minute"],[1,"second"]];for(const[t,o]of n){const n=Math.floor(e/t);if(n>=1)return`${n} ${o}${1===n?"":"s"} ago`}return"just now"}function c(t,e,n,o){const i=encodeURIComponent;switch(t){case"page":return`/wiki/${i(e)}`;case"contribs":return`/wiki/Special:Contributions/${i(e)}`;case"talk":return`/wiki/User_talk:${i(e)}`;case"twinkletalk":return`/wiki/User_talk:${i(e)}?vanarticle=${i(o??"")}`;case"history":return`/w/index.php?title=${i(e)}&action=history`;case"diff":return 0===n?`/w/index.php?oldid=${e}`:`/w/index.php?title=${i(o??"")}&diff=${e}&oldid=${n}`;case"aiv":return"/wiki/Wikipedia:Administrator_intervention_against_vandalism";default:return""}}function d(t){if(!t)return null;const e=/\[\[Special:Contrib(?:utions|s)\/([^|\]]+)/i.exec(t);return e&&e[1]?e[1].trim().replace(/_/g," "):null}function u(t,...e){if(window.CVPI_DEBUG){const n=t?`[CVPI:${t}]`:"[CVPI]";console.log(n,...e)}}class p{constructor(){this.STORAGE_KEY="CVPI_storage",this.defaults={version:e.VERSION,statistics:{recentChanges:{seen:0,reverted:0},reports:{AIV:0},warnings:{}},preferences:{showLabels:!0,openTwinkleOnOther:!0,colorblindMode:!1}}}load(){let t=null;try{t=mw.storage.getObject(this.STORAGE_KEY)}catch(t){console.error("[CVPI] Could not parse settings from mw.storage",t)}const e=JSON.parse(JSON.stringify(this.defaults));return t&&(e.version=t.version,t.statistics&&(e.statistics={recentChanges:{...e.statistics.recentChanges,...t.statistics.recentChanges},reports:{...e.statistics.reports,...t.statistics.reports},warnings:{...t.statistics.warnings}}),t.preferences&&(e.preferences={...e.preferences,...t.preferences})),e}save(){try{const t=this.load();t.version=this.defaults.version,mw.storage.setObject(this.STORAGE_KEY,t)}catch(t){console.error("[CVPI] Could not save settings to mw.storage",t)}}setPreference(t,e){const n=this.load();n.preferences[t]=e,mw.storage.setObject(this.STORAGE_KEY,n)}incrementStat(t,e,n=null){const o=this.load();o.statistics[t]||(o.statistics[t]={}),null!==n?(o.statistics[t][e]||(o.statistics[t][e]={}),o.statistics[t][e][n]=(o.statistics[t][e][n]||0)+1):o.statistics[t][e]=(o.statistics[t][e]||0)+1;try{mw.storage.setObject(this.STORAGE_KEY,o)}catch(t){console.error("[CVPI] Could not save updated stats",t)}}}class v{constructor(){this.monitored=new Map,this.unmonitored=new Set,this.atAIV=new Set,this.blocked=new Set,this.sessionWarnLevels=new Map,this.userHasWarnings=new Map,this.userWarnings=new Map,this.MLRSRollbackHistory=new Map}isMonitored(t){return this.monitored.has(t)}monitor(t,e="Manual"){"MLRS"===e&&this.unmonitored.has(t)||(this.monitored.set(t,{reason:e,timestamp:Date.now()}),this.unmonitored.delete(t))}recordMLRSRollback(t,e){this.MLRSRollbackHistory.has(t)||this.MLRSRollbackHistory.set(t,new Set);const n=this.MLRSRollbackHistory.get(t),o=n.size;return n.add(e),o<2&&n.size>=2}unmonitor(t){this.monitored.delete(t),this.unmonitored.add(t)}getMonitorReason(t){return this.monitored.get(t)?.reason}isAtAIV(t){return this.atAIV.has(t)}markAtAIV(t){this.atAIV.add(t)}isBlocked(t){return this.blocked.has(t)}markBlocked(t){this.blocked.add(t)}getHighestLevel(t){return this.sessionWarnLevels.get(t)??0}updateLevel(t,e){e>this.getHighestLevel(t)&&this.sessionWarnLevels.set(t,e)}hasWarnings(t){return(this.userWarnings.get(t)?.length??0)>0}getWarnings(t){return this.userWarnings.get(t)??[]}setWarnings(t,e){this.userWarnings.set(t,e)}getUser(t){const e=this.getWarnings(t);return{name:t,isMonitored:this.isMonitored(t),isBlocked:this.isBlocked(t),isAtAIV:this.isAtAIV(t),warnLevel:this.getHighestLevel(t),monitorReason:this.getMonitorReason(t),warnings:e,hasWarnings:e.length>0,canWarn:!this.isBlocked(t)&&!this.isAtAIV(t)}}}class h{constructor(t,e,n,o){this._map=new Map,this.pageState=new Map,this.userTracker=t,this._getThreshold=e,this._onPromotion=n,this._onReady=o}_log(...t){u("Queue",...t)}get(t){return this._map.get(t)}has(t){return this._map.has(t)}isHistoryStale(t){const e=this.pageState.get(t.title);return!!e&&(!t.pageHistory||0===t.pageHistory.length||e.revid>t.pageHistory[0].revid)}byStatus(...t){return[...this._map.values()].filter(e=>t.includes(e.status))}nextReady(){const t=this.byStatus("monitored");return t.length?t[0]:this.byStatus("ready").sort((t,e)=>(e.ores??0)-(t.ores??0))[0]??null}stats(){const t=[...this._map.values()];return{ready:t.filter(t=>"ready"===t.status).length,monitored:t.filter(t=>"monitored"===t.status).length,pending:t.filter(t=>"pending"===t.status).length,pendingPromotion:t.filter(t=>"pendingPromotion"===t.status).length,ignored:t.filter(t=>"ignored"===t.status).length}}async syncPageState(t,n,o){const i=new Set([...this._map.values()].filter(t=>"ignored"!==t.status&&!t.stale&&"viewed"!==t.status).map(t=>t.title));if(o&&i.add(o.title),!i.size||!n)return{needsRerender:!1,streakExtended:!1};this.updatePageState(n);const r=Date.now(),a=e.QUEUE.TOO_OLD_FOR_US_TO_CARE_ABOUT_MS,s=[];for(const t of i){const o=[...this._map.values()].filter(e=>e.title===t&&!e.stale&&"ignored"!==e.status);if(!o.length)continue;const i=r-Math.max(...o.map(t=>t.timestampMs));if(i>a){o.forEach(t=>{"viewed"!==t.status&&(t.status="ignored"),t.stale=!0});continue}const l=this.pageState.get(t),c=Math.max(...o.map(t=>t.revid));if(l&&l.revid>c)continue;n.some(e=>e.title===t)||(i>=e.API.RC_LOOKBACK_MS-2e3?s.push(t):o.forEach(t=>{"viewed"!==t.status&&(t.status="ignored"),t.stale=!0}))}if(s.length>0){this._log(`Fetching latest revision IDs for ${s.length} titles not in RC list: ${s.join(", ")}`);const e=await t.getLatestRevisionIds(s),n=[];for(const[t,o]of e.entries())n.push({title:t,revid:o.revid,user:o.user});this.updatePageState(n)}this.evaluateStaleness();let l={needsRerender:!1,streakExtended:!1};if(o&&this.isHistoryStale(o)){const t=this.pageState.get(o.title);if(o.stale&&t&&t.user===o.user){this._log("Streak extension detected for:",o.user,"on",o.title);const e=o.revid;this.updateRevid(e,t.revid),l={needsRerender:!0,streakExtended:!0,newRevid:t.revid}}else this._log("Current change became stale, refreshing history:",o.title),l={needsRerender:!0,streakExtended:!1}}return l}ingest(t){for(const e of t){const t=e.oresscores?.goodfaith?.false??null;if(this._map.has(e.revid)){"pending"===this._map.get(e.revid).status&&null!==t&&this.applyOresScore(e.revid,t);continue}const{comment:n,...o}=e,i={...o,comments:[{text:n||"",revid:e.revid,parentid:e.old_revid}],timestampMs:new Date(e.timestamp).getTime(),firstSeen:Date.now(),status:"pending",stale:!1,ores:t,oresRequested:!1,reason:null,diffPromise:void 0,diffsize:void 0};this._map.set(e.revid,i),this._evaluatePending(i)}}markViewed(t){const e=this._map.get(t);e&&(this._log("Marking viewed:",t),e.status="viewed")}applyHistoryData(t,e){const n=this._map.get(t);n&&e&&(n.pageHistory=e.history,e.history?.[0]?.revid===t&&(n.rev_count=e.revCount,n.diff_from_revid=e.priorRevid??n.old_revid,e.revCount&&e.history?.length>0&&(n.comments=e.history.slice(0,e.revCount).map(t=>({text:t.comment?.replace(/\/\*.*?\*\//g,"").trim()||"",revid:t.revid,parentid:t.parentid})).reverse())))}applyOresScore(t,e){const n=this._map.get(t);n&&(n.ores=e,"pending"===n.status&&null!==e&&this._evaluatePending(n))}promoteChange(t,e,n){const o=this._map.get(t);o&&"pendingPromotion"===o.status&&(this.applyHistoryData(t,e),o.diffPromise=n,o.status=this.userTracker.isMonitored(o.user)?"monitored":"ready",this._log("Change promoted to",o.status,":",t),this._onReady?.(o))}updateRevid(t,e){const n=this._map.get(t);n&&(this._map.delete(t),n.revid=e,this._map.set(e,n),this._log("Revid updated for streak extension:",t,"->",e))}updatePageState(t){for(const e of t){const t=this.pageState.get(e.title);(!t||t.revid<e.revid)&&this.pageState.set(e.title,{revid:e.revid,user:e.user})}}evaluateStaleness(){for(const t of this._map.values()){const e=this.pageState.get(t.title);if(e&&e.revid>t.revid){const e=t.stale;t.stale=!0,"ready"!==t.status&&"monitored"!==t.status&&"pending"!==t.status&&"pendingPromotion"!==t.status||(e||this._log("Filtering stale change (overwritten):",t.revid,t.title),t.status="ignored")}else t.stale=!1}}promoteUserToMonitored(t){for(const e of this._map.values())e.user!==t||"ready"!==e.status&&"pending"!==e.status&&"pendingPromotion"!==e.status||(this._log("Promoting user to monitored:",t,"revid:",e.revid,"from status:",e.status),e.status="monitored",e.reason=this.userTracker.getMonitorReason(t)||"Monitored")}trim(){const t=this.byStatus("ready").sort((t,e)=>(e.ores??0)-(t.ores??0));t.length>e.QUEUE.MAX_SIZE&&(this._log("Trimming ready queue. Excess items being ignored."),t.slice(e.QUEUE.MAX_SIZE).forEach(t=>{t.status="ignored"}))}expireStale(){const t=Date.now(),n=e.API.RC_LOOKBACK_MS+1e4;for(const e of this.byStatus("pending"))t-e.firstSeen>n&&(this._log("Expiring stale pending item (timeout):",e.revid),e.status="ignored")}evaluatePending(){for(const t of this.byStatus("pending"))this._evaluatePending(t)}_evaluatePending(t){if("pending"!==t.status)return;const e=this._getThreshold(),n=e=>{t.reason=e,t.status="pendingPromotion",this._onPromotion?.()};if(this.userTracker.isMonitored(t.user))return this._log("Evaluating pending: Monitored user bypass ORES.",t.revid),void n(this.userTracker.getMonitorReason(t.user)||"Monitored");0!==e?null!==t.ores&&(t.ores>=e?n("ORES"):t.status="ignored"):n("ORES")}pendingPromotion(){return[...this._map.values()].filter(t=>"pendingPromotion"===t.status)}}class b{constructor(t){this._api=t,this.online=!1,this._onHttpError=null,this.callCount=0}getCallCount(){return this.callCount}onHttpError(t){this._onHttpError=t}_log(...t){u("Api",...t)}_chunk(t,e){const n=[];for(let o=0;o<t.length;o+=e)n.push(t.slice(o,o+e));return n}async _get(t){try{this.callCount++;const e=await this._api.get(t);return this.online=!0,e}catch(t){throw"http"===t&&(this.online=!1,this._onHttpError?.()),t}}async _post(t){try{this.callCount++;const e=await this._api.postWithEditToken(t);return this.online=!0,e}catch(t){throw"http"===t&&(this.online=!1,this._onHttpError?.()),t}}async _query(t,e=2){const n=performance.now(),o=t.list||t.prop||"unknown-query";try{const i=await this._get({action:"query",format:"json",formatversion:e,...t}),r=(performance.now()-n).toFixed(1);return this._log(`Query [${o}] completed in ${r}ms`,t),i}catch(e){const i=(performance.now()-n).toFixed(1);return this._log(`Query [${o}] FAILED after ${i}ms:`,e,t),null}}_linkify(t){return t.replace(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b((?:(?!&lt;|&gt;|&quot;)[-a-zA-Z0-9()@:%_+.~#?&//=;,])*)/g,t=>`<a href="${t}" target="_blank">${t}</a>`)}async getRecentChanges(){const t=await this._query({list:"recentchanges",rcnamespace:e.API.NAMESPACE_FILTER,rclimit:"max",rcend:new Date(Date.now()-e.API.RC_LOOKBACK_MS).toISOString(),rcprop:"title|ids|sizes|flags|user|tags|comment|timestamp|oresscores",rcshow:"!bot",rctype:"edit|new",rctoponly:1},1);return t?.query?.recentchanges||null}async getRecentRollbacks(t=null){const e={list:"recentchanges",rclimit:500,rcprop:"user|comment|ids",rctag:"mw-rollback"};t&&(e.rcend=new Date(Date.now()-t).toISOString());const n=await this._query(e,1);return n?.query?.recentchanges||null}async getContributions(t,e=null,n=null,o=null){const i={list:"usercontribs",uclimit:"max",ucuser:t};e&&(i.ucend=e),n&&(i.ucnamespace=n),o&&(i.uctag=o);const r=await this._query(i,1);return r?.query?.usercontribs||null}async getDiff(t,e){if(!t||!e)return null;try{const n=await this._get({action:"compare",fromrev:t,torev:e,prop:"diff|size|timestamp",difftype:"table",format:"json",formatversion:2}),{body:o,tosize:i,fromsize:r}=n.compare,a=!o||""===o.trim(),s=`<table class="diff"><colgroup>\n\t\t\t\t<col class="diff-marker"><col class="diff-content">\n\t\t\t\t<col class="diff-marker"><col class="diff-content">\n\t\t\t</colgroup><tbody>${(o??"").replace(/<td colspan="2" class="diff-lineno">/g,'<td class="diff-marker-header"></td><td class="diff-lineno">')}<tr class="cvpi-diff-bottompadding"></tr></tbody></table>`;return{html:this._linkify(s),size:i-r,isEmpty:a}}catch(t){return this._log(`getDiff FAILED for ${e}:`,t),null}}async getLatestRevisionIds(t){const n=new Map,o=[...new Set(t)];if(!o.length)return n;const i=this._chunk(o,e.API.BATCH_SIZE);return await Promise.all(i.map(async t=>{const e=await this._query({prop:"revisions",titles:t.join("|"),rvprop:"ids|user"});e?.query?.pages?.forEach(t=>{t.revisions?.[0]&&n.set(t.title,{revid:t.revisions[0].revid,user:t.revisions[0].user})})})),n}async getPageHistoryData(t){const n=await this._query({prop:"revisions",titles:t,rvprop:"ids|user|size|timestamp|comment",rvlimit:e.API.RV_LIMIT,rvdir:"older"}),o=n?.query?.pages?.[0];if(!o||o.missing||(o.revisions?.length??0)<2)return{priorRevid:null,revCount:null,history:[]};const i=o.revisions,r=i[0].user;for(let t=1;t<i.length;t++)if(i[t].user!==r)return{priorRevid:i[t].revid,revCount:t,history:i};return{priorRevid:null,revCount:i.length,history:i}}async getPage(t){const e=await this._query({prop:"revisions",titles:t,rvprop:"content|ids",rvslots:"*"}),n=e?.query?.pages?.[0];return!n||n.missing?{content:"",revid:null}:{content:n.revisions[0].slots.main.content,revid:n.revisions[0].revid}}async savePage(t,e,n,o=null){try{const i={action:"edit",title:t,text:e,summary:n,tags:"CVPI",format:"json"};return null!==o&&(i.baserevid=o),await this._post(i),{success:!0}}catch(t){return{success:!1,error:t}}}async appendToPage(t,e,n){try{return await this._post({action:"edit",title:t,appendtext:e,summary:n,tags:"CVPI",discussiontoolsautosubscribe:"no",format:"json"}),{success:!0}}catch(t){return{success:!1,error:t}}}async rollback(t,e,n){try{this.callCount++;const o=await this._api.rollback(t,e,{summary:n,tags:"CVPI"});return o?.revid?{success:!0}:{success:!1,error:"No revid returned"}}catch(t){return{success:!1,error:{alreadyrolled:"Already rolled back.",editconflict:"Conflict.",http:"No connection."}[t]||t}}}async isUserBlocked(t){const e=await this._query({action:"query",list:"blocks",bkusers:t},1),n=new Date;return{success:!!e,blocked:(e?.query?.blocks||[]).some(t=>!t.partial&&("infinity"===t.expiry||new Date(t.expiry)>n))}}async getUsersAtAIV(){const t=await this._query({prop:"revisions",titles:"Wikipedia:Administrator intervention against vandalism",rvprop:"content",rvslots:"*"});if(!t)return{success:!1,users:[],error:"Could not fetch AIV page"};const e=t?.query?.pages?.[0]?.revisions?.[0]?.slots?.main?.content||"",n=/\{\{(?:(?:ip)?vandal|user-uaa)\|(?:1=)?([^|}]+)/gi,o=[];let i;for(;null!==(i=n.exec(e));){const t=i[1].trim();t&&!o.includes(t)&&o.push(t)}return{success:!0,users:o}}async getUsersAtUAA(){const t=await this._query({prop:"revisions",titles:"Wikipedia:Usernames for administrator attention",rvprop:"content",rvslots:"*"});if(!t)return{success:!1,users:[],error:"Could not fetch UAA page"};const e=t?.query?.pages?.[0]?.revisions?.[0]?.slots?.main?.content||"",n=/^\*\s*\{\{user-uaa\|(?:1=)?([^|}]+)/gim,o=[];let i;for(;null!==(i=n.exec(e));){const t=i[1].trim();t&&!o.includes(t)&&o.push(t)}return{success:!0,users:o}}async getUserRights(){return mw.user.getRights()}async getUserInfo(){const t=await this._query({action:"query",meta:"userinfo",uiprop:"options"});return t?.query?.userinfo||null}}const m=/<\!-- Template:([uU]w-[a-z-]+)([1-4]?(?:im)?) -->/gi,g=/\d{2}:\d{2}, \d{1,2} [A-Z][a-z]+ \d{4} \(UTC\)/g,f=/\[\[(?:User|User_talk|User talk|Special:Contributions)[:/]([^|\]#]+)/gi,_=["January","February","March","April","May","June","July","August","September","October","November","December"];function y(t){if("string"!=typeof t)return[];const e=[];let n;for(m.lastIndex=0;null!==(n=m.exec(t));){const o=n[1].trim();if("uw-cluebotwarning"===o)continue;const i=parseInt(n[2],10)||0,r=n.index+n[0].length;g.lastIndex=r;const a=g.exec(t);if(!a||a.index-r>355)continue;const s=t.substring(r,g.lastIndex);let c,d="Unknown";for(f.lastIndex=0;null!==(c=f.exec(s));)d=c[1].trim().split("/")[0];try{if(d=decodeURIComponent(d.replace(/_/g," ")),d.includes("&#")){const t=document.createElement("textarea");t.innerHTML=d,d=t.value}}catch{}let u=null,p=a[0];try{u=new Date(a[0].replace(/(\d+:\d+), (.*) \(UTC\)/,"$2 $1 UTC")),p=l(u)}catch{}e.push({dateObject:u,timestamp:p,template:o,level:i,issuer:d})}return e}function k(t,n=e.LOGIC.STALE_WARNING_METHOD){if(!t.dateObject)return!1;if("current_month"===n){const e=new Date;return t.dateObject.getUTCMonth()===e.getUTCMonth()&&t.dateObject.getUTCFullYear()===e.getUTCFullYear()}return Date.now()-t.dateObject<=6048e5}class w{constructor(t,n){this._api=t,this.userTracker=n,this.staleWarningMethod=e.LOGIC.STALE_WARNING_METHOD}_currentMonthSection(){const t=new Date;return`${_[t.getUTCMonth()]} ${t.getUTCFullYear()}`}async fetchWarnings(t){const{content:n}=await this._api.getPage(`User talk:${t}`);if(!n)return{level:0,content:"",warnings:[]};const o=y(n);return{level:o.filter(t=>k(t,e.LOGIC.STALE_WARNING_METHOD)).reduce((t,e)=>Math.max(t,e.level),0),content:n,warnings:o}}async warn(t,e,n){const{user:o,title:i}=t;if("vandalism"===e&&(0===n||"0"===n)&&this.userTracker.isAtAIV(o))return{success:!0,skipped:!0,reason:"user_at_aiv"};const r=await this._api.getPage(`User talk:${o}`);if(null===r.content)return{success:!1,error:"Failed to fetch talk page."};const{level:a,additionalNote:s}=this._resolveLevel(o,r.content,n);if(null!==a&&a>4)return{success:!1,reason:"max_level",additionalNote:s};const l=this._insertWarning(r.content,e,a,i),c=this._buildSummary(e,a,i);return{...await this._api.savePage(`User talk:${o}`,l,c,r.revid),level:a}}_resolveLevel(t,e,n){if(null===n)return{level:null,additionalNote:null};if(0===n||"0"===n){const n=y(e).filter(t=>k(t,this.staleWarningMethod)).reduce((t,e)=>Math.max(t,e.level),0),o=this.userTracker.getHighestLevel(t);let i=null;return o>=4&&0===n&&(i=`see {{ph|User talk:${t}|user talk page history}} for warnings`),{level:Math.max(n,o)+1,additionalNote:i}}return{level:parseInt(String(n),10),additionalNote:null}}_insertWarning(t,e,n,o){const i=`{{subst:${`uw-${"vandalism"===e?"vand":e}${n??""}`}${o?`|${o}`:""}}} ~~~~`,r=this._currentMonthSection(),a=new RegExp(`== ?${r} ?==`);a.test(t)||(t+=`\n\n== ${r} ==\n`);const s=t.split(/(?=== ?[^=]+ ?==)/g);let l=!1;for(let t=0;t<s.length;t++)if(a.test(s[t])){s[t]=s[t].trimEnd()+`\n\n${i}\n`,l=!0;break}return l||(s[s.length-1]=s[s.length-1].trimEnd()+`\n\n${i}\n`),s.join("").replace(/(\n){3,}/g,"\n\n").trim()}_buildSummary(t,e,n){return`${{1:"Information about",2:"Notice regarding",3:"Warning:",4:"Final warning:"}[e]??"Regarding"} edits to [[${n}]] (${`uw-${"vandalism"===t?"vand":t}${e??""}`})`}}const x={DEFAULT:{allowHotkeys:!0,allowSwipes:!0,allowedKeys:[" ","]","[","ArrowRight","ArrowLeft","q","t","u","d","ArrowDown"]},MODAL:{allowHotkeys:!1,allowSwipes:!1,allowedKeys:["Escape","Enter"]},TYPING:{allowHotkeys:!1,allowSwipes:!1,allowedKeys:["Escape","Enter"]},DASHBOARD:{allowHotkeys:!0,allowSwipes:!1,allowedKeys:[" ","]","[","ArrowRight","ArrowLeft","q","t","u","d","ArrowDown"]}};class C{constructor(t){this.events=t,this.currentMode="DEFAULT",this._handleClickBound=this._handleClick.bind(this),this._handleKeyBound=this._handleKey.bind(this),this._handleTouchStartBound=this._handleTouchStart.bind(this),this._handleTouchEndBound=this._handleTouchEnd.bind(this),this._touchStartX=0,this._touchStartY=0,this._isInsideToolbarPanel=!1}init(){document.addEventListener("click",this._handleClickBound),document.addEventListener("keydown",this._handleKeyBound),document.addEventListener("touchstart",this._handleTouchStartBound,{passive:!0}),document.addEventListener("touchend",this._handleTouchEndBound,{passive:!0})}destroy(){document.removeEventListener("click",this._handleClickBound),document.removeEventListener("keydown",this._handleKeyBound),document.removeEventListener("touchstart",this._handleTouchStartBound),document.removeEventListener("touchend",this._handleTouchEndBound)}setMode(t){x[t]&&(this.currentMode=t)}_getEffectiveMode(t){if(t){const e=t.nodeName;if("INPUT"===e||"TEXTAREA"===e||t.isContentEditable)return"TYPING"}return this.currentMode}_handleClick(t){const e=t.target.closest("[data-action]");if(e){if(e.hasAttribute("disabled")||e.classList.contains("cvpi-action-btn--disabled"))return;const n=e.getAttribute("data-action"),o={event:t,target:e};for(const t of e.attributes)if(t.name.startsWith("data-")&&"data-action"!==t.name){o[t.name.substring(5).replace(/-([a-z])/g,t=>t[1].toUpperCase())]=t.value}this.events.emit(`action:${n}`,o)}}_handleKey(t){const e=this._getEffectiveMode(t.target),n=x[e];if(!n.allowedKeys.includes(t.key))return;if(t.ctrlKey||t.metaKey||t.altKey||t.shiftKey)return;" "!==t.key&&"ArrowDown"!==t.key&&"ArrowRight"!==t.key&&"ArrowLeft"!==t.key||t.preventDefault();let o=null;const i={};if("MODAL"===e||"TYPING"===e)"Escape"===t.key&&(o="close-modal"),"Enter"===t.key&&(o="submit-modal");else if(n.allowHotkeys)switch(t.key){case" ":case"]":case"ArrowRight":o="cvpi-action-next";break;case"[":case"ArrowLeft":o="cvpi-action-prev";break;case"ArrowDown":o="cvpi-action-scroll-next";break;case"q":o="cvpi-action-hotkey-rollback",i.type="vandalism";break;case"t":o="cvpi-action-hotkey-rollback",i.type="test";break;case"u":o="cvpi-action-hotkey-rollback",i.type="unsourced";break;case"d":o="cvpi-action-hotkey-rollback",i.type="delete"}o&&this.events.emit(`action:${o}`,i)}_handleTouchStart(t){const e=this._getEffectiveMode(t.target);if(!x[e].allowSwipes)return;const n=t.changedTouches[0];this._touchStartX=n.pageX,this._touchStartY=n.pageY;const o=t.target.closest('.cvpi-toolbar__toolbarPanel[data-state="cvpi-toolbar--open"]');this._isInsideToolbarPanel=!!o}_handleTouchEnd(t){const e=this._getEffectiveMode(t.target);if(!x[e].allowSwipes||this._isInsideToolbarPanel)return;const n=t.changedTouches[0],o=n.pageX-this._touchStartX,i=n.pageY-this._touchStartY;Math.abs(o)>=75&&Math.abs(i)<=100&&(o>0?this.events.emit("action:cvpi-action-prev"):this.events.emit("action:cvpi-action-next"))}}class S{constructor(){this.events={}}on(t,e){(this.events[t]||(this.events[t]=[])).push(e)}off(t,e){this.events[t]&&(this.events[t]=this.events[t].filter(t=>t!==e))}emit(t,e){const n=this.events[t]||[];for(const t of n)if(t(e),e&&!0===e.stopPropagation)break}}class I{static createElement(t,e={},n=[]){const o=document.createElement(t);for(const[t,n]of Object.entries(e))"className"===t?o.className=n:"innerHTML"===t?o.innerHTML=n:"textContent"===t?o.textContent=n:t.startsWith("data-")?o.setAttribute(t,n):"disabled"===t?n&&o.setAttribute("disabled","true"):o[t]=n;return n.forEach(t=>t&&o.appendChild("string"==typeof t?document.createTextNode(t):t)),o}static createActionButton(t){const e=document.createElement("div"),n=[t.baseClass||"cvpi-action-btn"];if(t.id&&(e.id=t.id),t.actionId&&e.setAttribute("data-action",t.actionId),t.className&&n.push(...t.className.split(" ")),t.keepOpen&&e.setAttribute("data-keep-open","true"),t.disabled&&(n.push("cvpi-action-btn--disabled"),e.setAttribute("disabled","true")),e.className=n.join(" "),t.icon){const n=document.createElement("span"),o=t.iconClass||"material-symbols-outlined";if(n.className=`${o} cvpi-action-btn__symbol`,n.textContent=t.icon,t.badge){const e=document.createElement("span");e.className="cvpi-action-btn__badge",e.textContent=t.badge,n.appendChild(e)}e.appendChild(n)}else if(t.symbol){const n=document.createElement("span");if(n.className="cvpi-action-btn__symbol",n.innerHTML=t.symbol,t.badge){const e=document.createElement("span");e.className="cvpi-action-btn__badge",e.textContent=t.badge,n.appendChild(e)}e.appendChild(n)}const o=document.createElement("span");o.className="cvpi-action-btn__label cvpi-action-btn__label--top",o.textContent=t.topLabel||"",e.appendChild(o);const i=document.createElement("span");return i.className="cvpi-action-btn__label cvpi-action-btn__label--bottom",i.textContent=t.bottomLabel||t.label||"",e.appendChild(i),e}}class T{constructor(t,e){this.events=t,this._onCloseAll=e,T._listenerAttached||(document.addEventListener("click",this._handleDocumentClick.bind(this)),T._listenerAttached=!0)}_handleDocumentClick(t){const e=t.target.closest("[data-action]"),n=t.target.closest(".cvpi-toolbar"),o=t.target.closest('[data-keep-open="true"]');if(!n)return void this.closeAllMenus();const i=t.target.closest(".cvpi-toolbar__inline-toolbar, .cvpi-toolbar__toolbarPanel"),r=t.target.closest(".cvpi-toolbar__action-item, .cvpi-toolbar__vertical-menu-item");if(r&&(!i||r.classList.contains("cvpi-toolbar__vertical-menu-item"))){if(r.hasAttribute("disabled")||r.querySelector("[disabled]"))return;const t=r._submenu||r.querySelector(".cvpi-toolbar__inline-toolbar");if(t){const e="cvpi-toolbar--open"===t.getAttribute("data-state");return this.closeAllMenus(),void(e||(t.setAttribute("data-state","cvpi-toolbar--open"),this.events.emit("ui:toolbar-opened",{panel:t})))}if(r._toolbarPanelContent){const t=r.closest(".cvpi-toolbar");if(!t)return;const e=t.querySelector(".cvpi-toolbar__toolbarPanel");if(!e)return;const n="cvpi-toolbar--open"===e.getAttribute("data-state")&&e._activeTrigger===r;if(this.closeAllMenus(),n)return;Array.from(e.children).forEach(t=>t.style.display="none"),r._toolbarPanelContent.style.display="",e.setAttribute("data-state","cvpi-toolbar--open"),e._activeTrigger=r,e.scrollTop=0,r._toolbarPanelContent.scrollTop=0;const o=r.querySelector("[data-action]")?.getAttribute("data-action");o&&e.setAttribute("data-active-action",o);const i=t.getBoundingClientRect(),a=r.getBoundingClientRect(),s=e.getBoundingClientRect();let l=a.left-i.left+a.width/2-s.width/2,c=Math.max(0,Math.min(l,i.width-s.width));return void(e.style.left=`${c}px`)}}o||e&&i&&this.closeAllMenus()}closeAllMenus(t=[]){let e=!1;document.querySelectorAll('.cvpi-toolbar__inline-toolbar[data-state="cvpi-toolbar--open"], .cvpi-toolbar__toolbarPanel[data-state="cvpi-toolbar--open"]').forEach(n=>{const o=n.getAttribute("data-active-action");o&&t.includes(o)||(n.setAttribute("data-state","cvpi-toolbar--closed"),n.classList.contains("cvpi-toolbar__toolbarPanel")&&(n._activeTrigger=null),e=!0)}),e&&this.events.emit("ui:toolbar-closed"),"function"==typeof this._onCloseAll&&this._onCloseAll()}createToolbar(t,e="top"){const n=document.createElement("menu");n.className="cvpi-toolbar";const o=document.createElement("div");return o.className="cvpi-toolbar__toolbarPanel",o.setAttribute("data-state","cvpi-toolbar--closed"),o.setAttribute("data-placement",e),n.appendChild(o),t.forEach((e,i)=>{const r=document.createElement("li");if(r.className="cvpi-toolbar__action-item",r.dataset.actionId=e.actionId,e.itemClassName&&r.classList.add(...e.itemClassName.split(" ")),0===i&&r.classList.add("cvpi-toolbar__action-item--left-end"),i===t.length-1&&r.classList.add("cvpi-toolbar__action-item--right-end"),"custom"===e.type&&e.element?r.appendChild(e.element):r.appendChild(I.createActionButton({...e,baseClass:"cvpi-action-btn"})),e.inlineToolbar){const t=document.createElement("menu");t.className="cvpi-toolbar__inline-toolbar",t.setAttribute("data-state","cvpi-toolbar--closed"),e.inlineToolbar.forEach(e=>{const n=document.createElement("li");n.className="cvpi-toolbar__action-item","custom"===e.type&&e.element?n.appendChild(e.element):n.appendChild(I.createActionButton({...e,baseClass:"cvpi-action-btn"})),t.appendChild(n)}),r.appendChild(t),r._submenu=t}if(e.verticalMenu){const t=document.createElement("menu");t.className="cvpi-toolbar__vertical-menu",e.verticalMenu.forEach(e=>{const n=document.createElement("li");n.className="cvpi-toolbar__vertical-menu-item",e.toolbarPanel&&(n._toolbarPanelContent=e.toolbarPanel instanceof HTMLElement?e.toolbarPanel:e.toolbarPanel.element,n._toolbarPanelContent.style.display="none",o.appendChild(n._toolbarPanelContent));const i=document.createElement("div");i.className="cvpi-menu-item",e.keepOpen&&i.setAttribute("data-keep-open","true"),e.actionId&&i.setAttribute("data-action",e.actionId),e.disabled&&(i.classList.add("cvpi-menu-item--disabled"),i.setAttribute("disabled","true"));const r=document.createElement("div");if(r.className="cvpi-menu-item__icon",e.icon||e.symbol){const t=document.createElement("span"),n=e.iconClass||"material-symbols-outlined";t.className=`${n} cvpi-action-btn__symbol`,t.innerHTML=e.icon||e.symbol,r.appendChild(t)}i.appendChild(r);const a=document.createElement("div");a.className="cvpi-menu-item__label",a.textContent=e.label||"",i.appendChild(a);const s=document.createElement("div");s.className="cvpi-menu-item__action",e.element&&s.appendChild(e.element),i.appendChild(s),n.appendChild(i),t.appendChild(n)}),r._toolbarPanelContent=t}e.toolbarPanel&&(e.toolbarPanel instanceof HTMLElement?r._toolbarPanelContent=e.toolbarPanel:e.toolbarPanel.element&&(r._toolbarPanelContent=e.toolbarPanel.element)),r._toolbarPanelContent&&(r._toolbarPanelContent.style.display="none",o.appendChild(r._toolbarPanelContent)),n.appendChild(r)}),n}}T._listenerAttached=!1;class L{constructor(t,e,n){this.queue=t,this._render=e,this._log=n,this._historyStack=[],this._futureStack=[],this._currentRevid=null}get historyStackLength(){return this._historyStack.length}get futureStackLength(){return this._futureStack.length}get currentRevid(){return this._currentRevid}set currentRevid(t){this._currentRevid=t}get currentChange(){return this._currentRevid?this.queue.get(this._currentRevid)??null:null}async showNext(){this._log("showNext requested"),this._currentRevid&&this._historyStack.push(this._currentRevid);let t=null;if(this._futureStack.length){const e=this._futureStack.pop();t=this.queue.get(e)??null,t&&(t.status="viewed"),this._log("Pulled from future stack:",e)}else t=this.queue.nextReady(),t&&(this.queue.markViewed(t.revid),this._log("Pulled from ready queue:",t.revid));this._currentRevid=t?.revid??null,t||this._log("No next item available."),await this._render(this.currentChange)}async showPrevious(){this._log("showPrevious requested"),this._currentRevid&&this._futureStack.push(this._currentRevid);const t=this._historyStack.pop()??null;if(t){const e=this.queue.get(t);e&&(e.status="viewed")}this._currentRevid=t,await this._render(this.currentChange)}}class E{constructor(t,e,n,o,i,r,a,s,l){this.api=t,this.users=e,this.queue=n,this.warnings=o,this.settings=i,this.nav=r,this.events=a,this.promptForReason=s,this._log=l}async handleRollback(t,e,n,o=null){const i=this.queue.get(t);if(!i)return;const{title:r,user:s}=i;this._log("Handling rollback for:",s,"on",r),this.events.emit("ui:progress",{message:"Working…",duration:5}),this.users.monitor(s,"automatic"),this.queue.promoteUserToMonitored(s),await this.nav.showNext();const l=this.users.isAtAIV(s),d=l?"[[WP:CVPI|Interceptor]]: Reverting edits":`[[WP:CVPI|Interceptor]]: Reverting ${a[e]??"edits"} by [[Special:Contributions/${s}|${s}]]${o?`: ${o}`:""}`,u=await this.api.rollback(r,s,d);if(!u.success)return this._log("Rollback failed for:",r),void this.events.emit("ui:progress",{message:u.error||"Rollback failed",duration:3});if(i.stale=!0,this.nav.currentRevid===t&&this.events.emit("ui:updateStaleIndicator",!0),this.events.emit("ui:progress",{message:"Rollback complete"}),this.settings.incrementStat("recentChanges","reverted"),"other"===e||"other_agf"===e){return void(!1!==this.settings.load().preferences.openTwinkleOnOther&&window.open(c("twinkletalk",s,t,r),"_blank"))}const p=this.users.isBlocked(s);let v={success:!0};if(v=(0===n||"0"===n)&&(p||l)?{success:!0,skipped:!0,reason:p?"blocked":"user_at_aiv"}:await this.warnings.warn({user:s,title:r},e,n),v.success)if(v.skipped){const t="blocked"===v.reason?"user is blocked":"user at AIV";this.events.emit("ui:progress",{message:`Warning skipped: ${t}`,duration:3})}else{this.events.emit("ui:progress",{message:"Warn complete"});const t=null===v.level?0:v.level;this.settings.incrementStat("warnings",`uw-${e}`,t)}else"vandalism"===e&&"max_level"===v.reason?(this._log("User reached max warning level, reporting to AIV:",s),this.reportToAIV(t,"Vandalism past final warning",v.additionalNote)):"max_level"===v.reason?this.events.emit("ui:progress",{message:"User already at max warning level",duration:3}):v.error&&this.events.emit("ui:progress",{message:v.error,duration:3});const h=v.level??0;this.users.updateLevel(s,h);const b=this.users.getHighestLevel(s);this.events.emit("ui:updateField",{revid:t,fieldType:"warnlevel",newValue:{level:h,sessionMax:b}});try{const e=await this.api.getPageHistoryData(r);this.queue.applyHistoryData(t,e),this.nav.currentRevid===t&&this.events.emit("ui:syncState",i)}catch(t){this._log("Failed to refresh history after rollback:",t)}}async reportToAIV(t,e=null,n=null){const o=this.queue.get(t);if(!o)return;const{user:i}=o;if(this.users.isAtAIV(i)||this.users.isBlocked(i))return void(null===e&&(this.events.emit("ui:progress",{message:`${i} is already reported or blocked`}),this.nav.currentRevid===t&&this.events.emit("ui:syncState",o)));let r=e;if(null===r){const e=await this.promptForReason(`Reason for reporting ${i} to AIV (required)`,{choices:s});if(!e||""===e.trim())return this.events.emit("ui:progress",{message:"Cancelled"}),void(this.nav.currentRevid===t&&this.events.emit("ui:syncState",o));r=e.trim(),this._log("Handling manual AIV report for:",i,"Reason:",r)}this.events.emit("ui:progress",{message:"Reporting…",duration:5});const a=await this.api.isUserBlocked(i);if(!a.success)return this.events.emit("ui:progress",{message:a.error,duration:3}),void(this.nav.currentRevid===t&&this.events.emit("ui:syncState",o));if(a.blocked)return this.events.emit("ui:progress",{message:`${i} is already blocked`}),this.users.isBlocked(i)||this.users.markBlocked(i),void(this.nav.currentRevid===t&&this.events.emit("ui:syncState",o));const l=await this.api.getUsersAtAIV();if(!l.success)return this.events.emit("ui:progress",{message:l.error,duration:3}),void(this.nav.currentRevid===t&&this.events.emit("ui:syncState",o));for(const t of l.users)this.users.isAtAIV(t)||this.users.markAtAIV(t);if(this.users.isAtAIV(i))return this.events.emit("ui:progress",{message:`${i} is already at AIV`}),void(this.nav.currentRevid===t&&this.events.emit("ui:syncState",o));const c=`\n* {{vandal|${i}}} &ndash; ${r}${n?`; ${n}`:""} ~~~~`;let d=`[[WP:CVPI|Interceptor]]: Reporting [[Special:Contributions/${i}|${i}]]`;null===e&&(d="[[WP:CVPI|Interceptor]]: Reporting an editor");const u=await this.api.appendToPage("Wikipedia:Administrator intervention against vandalism",c,d);u.success?(this.events.emit("ui:progress",{message:`Reported ${i} to AIV`}),this.users.markAtAIV(i),this.settings.incrementStat("reports","AIV"),this.nav.currentRevid===t&&this.events.emit("ui:syncState",o)):(this.events.emit("ui:progress",{message:u.error||"Failed to report to AIV",duration:3}),this.nav.currentRevid===t&&this.events.emit("ui:syncState",o))}async promptAndRollback(t,e){const n=e?"Revert (AGF) - Reason":"Revert - Reason",o=await this.promptForReason(n);null!==o&&""!==o.trim()&&await this.handleRollback(t,e?"other_agf":"other",null,o)}async handleWarnOnly(t,e){const n=this.queue.get(t);if(!n)return;this._log("Handling warn-only for:",n.user,e),this.events.emit("ui:progress",{message:"Working…",duration:5});const{title:o,user:i}=n,r=await this.warnings.warn({user:i,title:o},e,null);if(!r.success)return void("max_level"===r.reason?this.events.emit("ui:progress",{message:"User already at max warning level",duration:3}):this.events.emit("ui:progress",{message:r.error,duration:3}));this.events.emit("ui:progress",{message:"Warn complete"}),this.settings.incrementStat("warnings",`uw-${e}`,0);const a=r.level??0;this.users.updateLevel(i,a);const s=this.users.getHighestLevel(i);this.events.emit("ui:updateField",{revid:t,fieldType:"warnlevel",newValue:{level:a,sessionMax:s}}),this.nav.currentRevid===t&&this.events.emit("ui:syncState",n)}}class A{constructor(t,e,n,o,i){this.api=t,this.users=e,this.queue=n,this.events=o,this._log=i,this._processedRcids=new Set,this._timer=null}start(){this._timer||(this._log("Starting MLRS Scanner..."),this._timer=setInterval(()=>this.scan(),e.LOGIC.MLRS_INTERVAL_MS),this.scan())}stop(){this._timer&&(clearInterval(this._timer),this._timer=null)}async scan(){this._log("MLRS Discovery starting...");try{const t=e.LOGIC.MLRS_LOOKBACK_MS,n=await this.api.getRecentRollbacks(t);if(!n?.length)return void(window.CVPI_DEBUG&&this.events.emit("ui:progress",{message:"MLRS: 0 rollbacks found",duration:3}));const o=mw.user.getName();for(const t of n){if(this._processedRcids.has(t.rcid))continue;if(this._processedRcids.add(t.rcid),t.user===o)continue;const e=d(t.comment);if(!e)continue;this.users.recordMLRSRollback(e,t.user)&&!this.users.isMonitored(e)&&(this._log(`MLRS: Monitoring ${e} (threshold reached via persistent history)`),this.users.monitor(e,"MLRS"),this.queue.promoteUserToMonitored(e))}}catch(t){console.error("[CVPI] MLRS error:",t)}}}class P{constructor(t,e,n,o,i){this.api=t,this.warnings=e,this.users=n,this.events=o,this._log=i}async load(t,e){return Promise.all([this._loadDiff(t,e),this._loadWarnLevel(t,e)])}async _loadDiff(t,n){const{old_revid:o,diff_from_revid:i,diffPromise:r}=t;try{let a;if(r)a=await r;else if(0===o)a="<i>(New page)</i>",t.diffPromise=Promise.resolve(a);else{const e=await this.api.getDiff(i??o,t.revid);e&&(t.diffsize=e.size,a=e.html,t.diffPromise=Promise.resolve(a))}n()===t&&(this.events.emit("ui:updateField",{revid:t.revid,fieldType:"diff",newValue:a??null}),this.events.emit("ui:updateField",{revid:t.revid,fieldType:"diffsize",newValue:t.diffsize}),setTimeout(()=>this.events.emit("ui:scrollToFirstChange"),e.UI.SCROLL_DELAY_MS),this.events.emit("ui:updateStaleIndicator",t.stale))}catch(e){this._log("diff load error:",e),n()===t&&this.events.emit("ui:updateField",{revid:t.revid,fieldType:"diff",newValue:"<i>Error loading diff.</i>"})}}async _loadWarnLevel(t,e){const{user:n}=t;try{const{level:o,warnings:i}=await this.warnings.fetchWarnings(n);this.users.updateLevel(n,o);const r=this.users.getHighestLevel(n);t.warnLevel=o,this.users.setWarnings(n,i),e()===t&&(this.events.emit("ui:updateField",{revid:t.revid,fieldType:"warnlevel",newValue:{level:o,sessionMax:r}}),this.events.emit("ui:syncState",t))}catch(n){this._log("warn level error:",n),e()===t&&this.events.emit("ui:updateField",{revid:t.revid,fieldType:"warnlevel",newValue:"?"})}}}class R{constructor(t,e,n){this._events=t,this._actionBus=e,this._cb=n,this._progressTimers=[],this._isProgressActive=!1,this._liveTimeInterval=null,this._clockInterval=null,this._activeRevid=null,this._timezone="UTC",this._originalTitle=document.title,this._originalOverflow=document.body.style.overflow,this._onVisibilityChangeBound=()=>this._onVisibilityChange(),this._toolbarManager=new T(this._events,()=>this._resetDashboardGroups()),this._registerEvents()}updateTimezone(t){if(!t)return;const e=t.split("|"),n=e[0];if("ZoneInfo"===n&&e[2])this._timezone=e[2];else if("Offset"===n||"System"===n){const t=parseInt(e[1],10);isNaN(t)||(this._timezone=t)}this._startClock()}setupPage(){document.title="CVPI",document.body.style.overflow="clip",this._injectFonts(),this._injectStyles(),document.body.appendChild(I.createElement("div",{className:"cvpi-background-blur"})),this._root=I.createElement("div",{className:"cvpi-wrapper",innerHTML:'\n\t\t\t\t<p id="cvpi-loading-message">Loading CVPI…</p>\n\t\t\t\t<div class="cvpi-footer">\n\t\t\t\t\t<div id="cvpi-footer-status" class="cvpi-footer-cell--status-area">\n\t\t\t\t\t\t<span id="cvpi-footer-analyzing-container">\n\t\t\t\t\t\t\tAnalyzing <span id="cvpi-footer-data--analyzing">0</span> edits...\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class="cvpi-footer__center">\n\t\t\t\t\t\t<span id="cvpi-footer-timeago"></span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id="cvpi-footer-clock" class="cvpi-footer-timestamp">--:--:--</div>\n\t\t\t\t</div>\n\t\t\t'}),document.body.appendChild(this._root),!1===this._cb.getPreference("showLabels")&&this._root.classList.add("cvpi-hide-labels"),!0===this._cb.getPreference("colorblindMode")&&this._root.classList.add("cvpi-colorblind"),this._startClock(),document.addEventListener("visibilitychange",this._onVisibilityChangeBound),window.addEventListener("resize",()=>this._syncLayoutMode()),this._buildStaticUI(),this._syncLayoutMode()}_registerEvents(){this._events.on("ui:progress",({message:t,duration:e})=>this.showProgress(t,e)),this._events.on("ui:updateField",({revid:t,fieldType:e,newValue:n})=>this.updateSpecificFieldForRevision(t,e,n)),this._events.on("ui:updateStaleIndicator",t=>this.updateStaleIndicator(t)),this._events.on("ui:syncState",t=>this.syncState(t)),this._events.on("ui:scrollToFirstChange",()=>this.scrollToFirstChange()),this._events.on("ui:toolbar-opened",({panel:t})=>{t&&"cvpi-dashboard-container"===t.id&&this._actionBus.setMode("DASHBOARD")}),this._events.on("ui:toolbar-closed",()=>{"DASHBOARD"===this._actionBus.currentMode&&this._actionBus.setMode("DEFAULT")}),this._events.on("action:cvpi-action-rollback",t=>this._handleRollbackClick(t)),this._events.on("action:cvpi-action-warn-only",t=>{this._toolbarManager.closeAllMenus(),this._resetDashboardGroups()}),this._events.on("action:cvpi-action-other-rollback",t=>{this._toolbarManager.closeAllMenus(),this._resetDashboardGroups()}),this._events.on("action:cvpi-action-report-aiv",t=>this._handleReportClick(t)),this._events.on("action:cvpi-action-arm-single",t=>this._handleArmSingle(t)),this._events.on("action:cvpi-action-label-toggle",t=>{const e=!!t.event.target.checked;this._root.classList.toggle("cvpi-hide-labels",!e)}),this._events.on("action:cvpi-action-colorblind-toggle",t=>{const e=!!t.event.target.checked;this._root.classList.toggle("cvpi-colorblind",e)}),this._events.on("action:cvpi-action-show-statistics",()=>{const t=this._cb.getDeviceStats();var e=0;t.recentChanges.seen&&t.recentChanges.reverted&&(e=(t.recentChanges.reverted/t.recentChanges.seen*100).toFixed(1));const n=`\n\t\t\t<ul style="list-style: none;">\n\t\t\t\t<li><strong>Edits seen:</strong> ${t.recentChanges?.seen||0}</li>\n\t\t\t\t<li><strong>Edits reverted:</strong> ${t.recentChanges?.reverted||0} (${e}%)</li>\n\t\t\t\t<li><strong>AIV reports:</strong> ${t.reports?.AIV||0}</li>\n\t\t\t</ul>\n\t\t\t`;this.showInfoModal("Device statistics",n)}),this._events.on("action:cvpi-action-fullscreen",()=>{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen().catch(t=>{console.error(`Error attempting to enable full-screen mode: ${t.message} (${t.name})`)})})}_handleRollbackClick(t){const e=t.target,n=e.closest(".cvpi-rollback-group"),o=n?.classList.contains("cvpi-rollback-group--multi"),i=e.classList.contains("cvpi-btn--auto");if(o&&i&&this._root.classList.contains("cvpi-mobile-layout")&&!n.classList.contains("cvpi-rollback-group--active"))return this._resetDashboardGroups(),n.classList.add("cvpi-rollback-group--active"),n.querySelectorAll("button:not(.cvpi-btn--auto)").forEach(t=>t.classList.remove("cvpi-hidden")),void(t.stopPropagation=!0);this._toolbarManager.closeAllMenus(),this._resetDashboardGroups()}_handleReportClick(t){const e=t.target;if(!e.classList.contains("cvpi-btn--active"))return this._resetDashboardGroups(),e.classList.add("cvpi-btn--active"),e._originalText=e.innerHTML,e.innerHTML="Confirm Report?",void(t.stopPropagation=!0);this._toolbarManager.closeAllMenus(),this._resetDashboardGroups()}_handleArmSingle(t){const e=t.target.closest(".cvpi-rollback-group");e&&this._root.classList.contains("cvpi-mobile-layout")&&(this._resetDashboardGroups(),e.classList.add("cvpi-rollback-group--active"),e.querySelectorAll("button:not(.cvpi-btn--arm)").forEach(t=>t.classList.remove("cvpi-hidden")))}_syncLayoutMode(){this._root&&window.innerWidth<1e3?this._root.classList.add("cvpi-mobile-layout"):this._root&&this._root.classList.remove("cvpi-mobile-layout")}_buildStaticUI(){this._actionsToolbar=this._buildStaticActionsToolbar(),this._root.appendChild(this._actionsToolbar),this._root.appendChild(this._buildStaticNavToolbar()),this._progressNotification=I.createElement("div",{className:"cvpi-progress-notification"}),this._root.appendChild(this._progressNotification),this._historySidebarPane=document.createElement("div"),this._historySidebarPane.className="cvpi-history cvpi-sidebar-pane--history cvpi-sidebar-pane",this._root.appendChild(this._historySidebarPane),this._talkSidebarPane=document.createElement("div"),this._talkSidebarPane.className="cvpi-talk cvpi-sidebar-pane--talk cvpi-sidebar-pane",this._root.appendChild(this._talkSidebarPane),this._changeComponentContainer=document.createElement("div"),this._changeComponentContainer.style.display="contents",this._root.appendChild(this._changeComponentContainer)}_buildStaticNavToolbar(){const e=I.createElement("div",{className:"cvpi-stepper-wrapper"},[I.createElement("div",{className:"cvpi-stepper-btn",textContent:"−","data-action":"cvpi-action-threshold-dec"}),I.createElement("div",{className:"cvpi-stepper-value",id:"cvpi-action-threshold-value-display",textContent:this._cb.getThreshold().toFixed(2)}),I.createElement("div",{className:"cvpi-stepper-btn",textContent:"+","data-action":"cvpi-action-threshold-inc"})]),n=I.createElement("label",{className:"cvpi-switch"},[I.createElement("input",{type:"checkbox","data-action":"cvpi-action-label-toggle",checked:!1!==this._cb.getPreference("showLabels")}),I.createElement("span",{className:"cvpi-switch-slider"})]),o=I.createElement("label",{className:"cvpi-switch"},[I.createElement("input",{type:"checkbox","data-action":"cvpi-action-twinkle-toggle",checked:!1!==this._cb.getPreference("openTwinkleOnOther")}),I.createElement("span",{className:"cvpi-switch-slider"})]),i=[{actionId:"cvpi-action-more",bottomLabel:"More",icon:"more_vert",verticalMenu:[{type:"custom",label:"ORES Threshold",icon:"gate",keepOpen:!0,element:e},{type:"custom",label:"Show UI labels",keepOpen:!0,element:n},{type:"custom",label:"Colorblind mode",keepOpen:!0,element:I.createElement("label",{className:"cvpi-switch"},[I.createElement("input",{type:"checkbox","data-action":"cvpi-action-colorblind-toggle",checked:!0===this._cb.getPreference("colorblindMode")}),I.createElement("span",{className:"cvpi-switch-slider"})])},{type:"custom",label:"Open user talk on 'other' rollbacks",keepOpen:!0,element:o},{actionId:"cvpi-action-changelog",label:"Changelog",icon:"list_alt",keepOpen:!0,toolbarPanel:this._buildChangelogElement(t)},{actionId:"cvpi-action-show-statistics",label:"Show statistics",icon:"heap_snapshot_thumbnail"},{actionId:"cvpi-action-fullscreen",label:"Full screen",icon:"fullscreen"}]},{actionId:"cvpi-action-prev",bottomLabel:"Prev",icon:"arrow_back",disabled:!0,itemClassName:"cvpi-only-mobile"},{actionId:"cvpi-action-next",bottomLabel:"Next",icon:"arrow_forward",disabled:!0,itemClassName:"cvpi-only-mobile"}];return this._createToolbar(i,"cvpi-toolbar--top","bottom")}_buildStaticActionsToolbar(){this._historyToolbarPanel=document.createElement("div"),this._historyToolbarPanel.className="cvpi-history cvpi-toolbarPanel--history cvpi-toolbarPanel",this._talkToolbarPanel=document.createElement("div"),this._talkToolbarPanel.className="cvpi-talk cvpi-toolbarPanel--talk cvpi-toolbarPanel",this._dashboardContainer=I.createElement("div",{id:"cvpi-dashboard-container",className:"cvpi-rollback-dashboard"});const t=[{actionId:"cvpi-action-prev",bottomLabel:"Prev",icon:"arrow_back",disabled:!0,itemClassName:"cvpi-only-desktop"},{actionId:"cvpi-toolbar-history",topLabel:"Page",bottomLabel:"history",icon:"history",keepOpen:!0,disabled:!0,toolbarPanel:this._historyToolbarPanel,itemClassName:"cvpi-only-mobile"},{id:"cvpi-btn-monitor",actionId:"cvpi-toolbar-monitor",topLabel:"Monitor",bottomLabel:"editor",icon:"person_add",keepOpen:!0,disabled:!0},{id:"cvpi-btn-vandalism",actionId:"cvpi-toolbar-vandalism",bottomLabel:"Roll & warn",icon:"warning",className:"cvpi-toolbar-btn--vandalism",disabled:!0},{actionId:"cvpi-toolbar-dashboard",topLabel:"Open",bottomLabel:"dashboard",icon:"view_cozy",disabled:!0,toolbarPanel:this._dashboardContainer},{actionId:"cvpi-action-next",bottomLabel:"Next",icon:"arrow_forward",disabled:!0,itemClassName:"cvpi-only-desktop"},{actionId:"cvpi-toolbar-talk",topLabel:"User",bottomLabel:"talk",icon:"forum",keepOpen:!0,disabled:!0,toolbarPanel:this._talkToolbarPanel,itemClassName:"cvpi-only-mobile"}];return this._createToolbar(t,"cvpi-toolbar--actions","top")}destroy(){this._clearLiveTime(),this._clockInterval&&clearInterval(this._clockInterval),this._progressTimers.forEach(clearTimeout),document.removeEventListener("visibilitychange",this._onVisibilityChangeBound),this._root?.remove(),document.title=this._originalTitle,document.body.style.overflow=this._originalOverflow}_onVisibilityChange(){"visible"===document.visibilityState?document.title="CVPI":this.updateQueueDisplay(this._cb.getStats())}showProgress(t,e=2){const n=this._progressNotification;n&&(this._progressTimers.forEach(clearTimeout),this._progressTimers=[],this._isProgressActive=!0,n.textContent=t,n.classList.add("cvpi-progress-notification--visible"),e>0&&this._progressTimers.push(setTimeout(()=>{this.hideProgress()},1e3*e)))}hideProgress(){this._isProgressActive=!1;const t=this._progressNotification;t&&t.classList.remove("cvpi-progress-notification--visible")}displayChange(t){this._clearLiveTime(),this._clearMainContent(),this._toolbarManager.closeAllMenus(["cvpi-action-more","cvpi-action-changelog"]),this._activeRevid=t?t.revid:null,this.syncState(t),t?(this._changeComponentContainer.appendChild(this._buildTitleArea(t)),this._changeComponentContainer.appendChild(this._buildMetaArea(t)),this._changeComponentContainer.appendChild(this._buildCommentsArea(t)),this._changeComponentContainer.appendChild(this._buildDiffArea(t)),this._changeComponentContainer.appendChild(this._buildScrollArrow())):this._changeComponentContainer.appendChild(this._buildPlaceholder());const e=document.getElementById("cvpi-footer-timeago");e&&(t?this._startLiveTime(t.revid,t.timestampMs):e.textContent="")}syncState(t){[{id:"cvpi-toolbar-history",config:this._getHistoryButtonConfig(t)},{id:"cvpi-toolbar-talk",config:this._getTalkButtonConfig(t)},{id:"cvpi-toolbar-dashboard",config:this._getDashboardConfig(t)},{id:"cvpi-toolbar-vandalism",config:this._getVandalismButtonConfig(t)},{id:"cvpi-toolbar-monitor",config:this._getMonitorButtonConfig(t)}].forEach(t=>this._updateButtonState(t.id,t.config)),["cvpi-btn-vandalism","cvpi-btn-monitor"].forEach(e=>{const n=document.getElementById(e);n&&(t?n.dataset.revid=t.revid:delete n.dataset.revid)});const e=(e,n,o=!1)=>{e.forEach(({el:e,isToolbar:i})=>{if(e&&(e.innerHTML="",t)){if([...n(t,i).childNodes].forEach(t=>e.appendChild(t)),o){const t=e.querySelector(".cvpi-talk__list");t&&setTimeout(()=>t.scrollTop=t.scrollHeight,0)}}})};e([{el:this._historySidebarPane,isToolbar:!1},{el:this._historyToolbarPanel,isToolbar:!0}],(t,e)=>this._buildHistoryComponent(t,e)),e([{el:this._talkSidebarPane,isToolbar:!1},{el:this._talkToolbarPanel,isToolbar:!0}],(t,e)=>this._buildTalkComponent(e,t),!0),this._dashboardContainer&&(this._dashboardContainer.innerHTML=t?this._getDashboardHtml(t):""),this.updateStaleIndicator(t?.stale),this.updateQueueDisplay(this._cb.getStats())}showChangelog(){return new Promise(t=>{const e=this._root.querySelector('[data-action="cvpi-action-changelog"]');e&&e.click();const n=this._toolbarManager.closeAllMenus.bind(this._toolbarManager);this._toolbarManager.closeAllMenus=()=>{n(),this._toolbarManager.closeAllMenus=n,t()}})}updateSpecificFieldForRevision(t,e,n){const o=`cvpi-${e}-${t}`,i=document.getElementById(o);if(i)switch(e){case"diff":i.innerHTML=n??"<i>Diff not available.</i>";break;case"diffsize":i.innerHTML=this._formatSize(n);break;case"ores":i.innerHTML=this._formatOres(n);break;case"warnlevel":{let t="?",e=0;if("object"==typeof n&&null!==n){const{level:o,sessionMax:i}=n;e=o,t=`Level: ${i}`,i>e&&(t+=" (user may have removed warnings)")}else t=`(WL: ${n})`,e=parseInt(String(n),10)||0;i.textContent=t,i.style.color=e>=4?"#d33":e>=2?"#f80":"inherit";break}default:console.warn(`[CVPI:UI] Unknown fieldType for update: ${e}`)}}_updateButtonState(t,e){const n=this._root.querySelectorAll(`[data-action="${t}"]`);n.length&&n.forEach(t=>{if(void 0!==e.topLabel){const n=t.querySelector(".cvpi-action-btn__label--top");n&&(n.textContent=e.topLabel)}if(void 0!==e.bottomLabel){const n=t.querySelector(".cvpi-action-btn__label--bottom");n&&(n.textContent=e.bottomLabel)}if(void 0!==e.icon){const n=t.querySelector(".cvpi-action-btn__symbol");n&&(n.textContent=e.icon)}if(void 0!==e.badge){const n=t.querySelector(".cvpi-action-btn__symbol");if(n){let t=n.querySelector(".cvpi-action-btn__badge");null===e.badge||0===e.badge||""===e.badge?t&&t.remove():(t||(t=document.createElement("span"),t.className="cvpi-action-btn__badge",n.appendChild(t)),t.textContent=e.badge,void 0!==e.badgeClassName&&(t.className=`cvpi-action-btn__badge ${e.badgeClassName}`))}}void 0!==e.disabled&&(t.classList.toggle("cvpi-action-btn--disabled",e.disabled),e.disabled?t.setAttribute("disabled","true"):t.removeAttribute("disabled")),void 0!==e.activeClass&&t.classList.toggle(e.activeClass,!!e.active)})}updateStaleIndicator(t){const e=document.querySelector(".cvpi-diff");e&&e.classList.toggle("cvpi-diff--overridden",!0===t)}updateThreshold(t){const e=document.getElementById("cvpi-action-threshold-value-display");e&&(e.textContent=t.toFixed(2))}updateQueueDisplay(t){if(!t)return;const e=t.ready+(t.monitored||0);this._updateButtonState("cvpi-action-prev",{disabled:0===(t.historyStack||0)}),this._updateButtonState("cvpi-action-next",{disabled:!this._activeRevid&&0===e&&0===(t.futureStack||0),activeClass:"cvpi-next--alert",active:t.monitored>0,badge:e>0?e:null});const n=document.getElementById("cvpi-footer-data--analyzing");if(n){const e=t.pending+(t.pendingPromotion||0);n.textContent=e||"0"}const o=t.ready+(t.monitored||0)+(this._activeRevid?1:0);"hidden"===document.visibilityState&&o>0?document.title=`CVPI (${o})`:document.title="CVPI"}_getDashboardHtml(t){const e=t?.revid;if(!e)return"";let n="<h1>Multi-issue templates</h1>";n+='<div class="cvpi-rollback-dashboard__grid cvpi-rollback-dashboard__grid--multi">';for(const o of r.filter(e=>!e.singleIssue&&!e.exclude_namespaces.includes(t.ns))){n+='<div class="cvpi-rollback-group cvpi-rollback-group--multi">';for(let t=1;t<=4;t++){const i=o.noLevel4&&4===t?"disabled":"";n+=`<button class="${4!==t||o.noLevel4?"mw-ui-button":"mw-ui-button mw-ui-destructive"} cvpi-hidden" data-action="cvpi-action-rollback" data-revid="${e}" data-type="${o.type}" data-level="${t}" ${i}>${t}</button>`}n+=`<button class="${"vandalism"===o.type?"mw-ui-button mw-ui-destructive":"mw-ui-button mw-ui-neutral"} cvpi-btn--auto" data-action="cvpi-action-rollback" data-revid="${e}" data-type="${o.type}" data-level="0" data-keep-open="true" data-label="${o.label}">\n\t\t\t\t<span class="cvpi-btn-text-collapsed">${o.label}...</span>\n\t\t\t\t<span class="cvpi-btn-text-expanded">${o.label}<br/>(auto level)</span>\n\t\t\t</button>`,n+="</div>"}n+="</div>",n+="<h1>Single-issue templates</h1>",n+='<div class="cvpi-rollback-dashboard__grid cvpi-rollback-dashboard__grid--single">';for(const o of r.filter(e=>e.singleIssue&&!e.exclude_namespaces.includes(t.ns)))n+=`\n\t\t\t\t<div class="cvpi-rollback-group cvpi-rollback-group--single">\n\t\t\t\t\t<button class="mw-ui-button mw-ui-quiet cvpi-btn--arm" data-action="cvpi-action-arm-single" data-keep-open="true">${o.label}</button>\n\t\t\t\t\t<button class="mw-ui-button mw-ui-destructive cvpi-hidden" data-action="cvpi-action-rollback" data-revid="${e}" data-type="${o.type}" data-level="null">Warn &amp; Roll</button>\n\t\t\t\t\t<button class="mw-ui-button cvpi-hidden" data-action="cvpi-action-warn-only" data-revid="${e}" data-type="${o.type}">Warn Only</button>\n\t\t\t\t</div>\n\t\t\t`;return n+="</div>",n+="<h1>Other actions</h1>",n+=`\n\t\t\t<div class="cvpi-rollback-dashboard__grid">\n\t\t\t\t<button class="mw-ui-button" data-action="cvpi-action-other-rollback" data-revid="${e}" data-agf="false" data-keep-open="true">Other (prompt)</button>\n\t\t\t\t<button class="mw-ui-button" data-action="cvpi-action-other-rollback" data-revid="${e}" data-agf="true" data-keep-open="true">Other AGF (prompt)</button>\n\t\t\t\t<button class="mw-ui-button mw-ui-destructive" data-action="cvpi-action-report-aiv" data-revid="${e}" data-keep-open="true">Report to AIV</button>\n\t\t\t</div>\n\t\t`,n}_resetDashboardGroups(){this._dashboardContainer&&(this._dashboardContainer.querySelectorAll(".cvpi-rollback-group--multi").forEach(t=>{t.classList.remove("cvpi-rollback-group--active"),t.querySelectorAll("button:not(.cvpi-btn--auto)").forEach(t=>t.classList.add("cvpi-hidden"))}),this._dashboardContainer.querySelectorAll(".cvpi-rollback-group--single").forEach(t=>{t.classList.remove("cvpi-rollback-group--active"),t.querySelectorAll("button:not(.cvpi-btn--arm)").forEach(t=>t.classList.add("cvpi-hidden"))}),this._dashboardContainer.querySelectorAll(".cvpi-btn--active").forEach(t=>{t.classList.remove("cvpi-btn--active"),t._originalText&&(t.innerHTML=t._originalText,delete t._originalText)}))}_createBaseModal(t,e,n,o,i){this._actionBus.setMode("MODAL");const r=document.createElement("div");r.className="cvpi-modal";const a=()=>{r.remove(),this._actionBus.setMode("DEFAULT"),this._events.off("action:close-modal",s),i&&this._events.off("action:submit-modal",l)},s=()=>{a(),o&&o()},l=()=>{a(),i&&i()};this._events.on("action:close-modal",s),i&&this._events.on("action:submit-modal",l),r.addEventListener("click",t=>{t.target===r&&s()});const c=document.createElement("div");return c.className="cvpi-modal-box",c.innerHTML=`<h3>${t}</h3>`,c.appendChild(e),c.appendChild(n),r.appendChild(c),document.body.appendChild(r),{dismiss:a,handleCancel:s,handleSubmit:l}}showInfoModal(t,e){return new Promise(n=>{const o=document.createElement("div");o.className="cvpi-modal-content",o.innerHTML=e,o.style.marginBottom="1em";const i=document.createElement("div");let r;i.className="cvpi-modal-actions";const a=I.createElement("button",{innerHTML:"Close",className:"mw-ui-button mw-ui-progressive",onclick:()=>r.handleCancel()});i.appendChild(a),r=this._createBaseModal(t,o,i,()=>n())})}promptForReason(t,{choices:e=null}={}){return new Promise(n=>{const o=document.createElement("div"),i=document.createElement("div");i.className="cvpi-modal-choices-container";const r=document.createElement("div");r.style.display=e?"none":"block";const a=document.createElement("input");a.className="cvpi-modal-input",a.type="text",a.placeholder="Enter reason…",r.appendChild(a),o.appendChild(i),o.appendChild(r);const s=document.createElement("div");let l;s.className="cvpi-modal-actions";if(e){e.forEach(t=>{const e=document.createElement("button");e.className="mw-ui-button mw-ui-destructive",e.textContent=t,e.onclick=()=>{l.dismiss(),n(t)},i.appendChild(e)});const t=document.createElement("button");t.className="mw-ui-button",t.textContent="Custom reason…",t.onclick=()=>{i.style.display="none",r.style.display="block",a.focus()},i.appendChild(t)}const c=I.createElement("button",{innerHTML:"Submit",className:"mw-ui-button mw-ui-progressive",onclick:()=>l.handleSubmit()});s.appendChild(c);const d=I.createElement("button",{innerHTML:"Cancel",className:"mw-ui-button",onclick:()=>l.handleCancel()});s.appendChild(d),l=this._createBaseModal(t,o,s,()=>n(null),()=>n(a.value.trim())),e||a.focus()})}scrollToFirstChange(t=null){const e=document.querySelector(".cvpi-diff");if(!e)return;const n=t??e.querySelector("ins, del");n&&n.scrollIntoView({behavior:"smooth",block:"center"}),this._updateScrollArrow(e)}scrollToNextHidden(){const t=document.querySelector(".cvpi-diff");if(!t)return;const e=t.getBoundingClientRect();for(const n of t.querySelectorAll('ins, del, td[data-marker="−"]'))if(n.getBoundingClientRect().top>e.bottom)return void this.scrollToFirstChange(n)}_getVandalismButtonConfig(t){if(!t)return{topLabel:"Vandalism",bottomLabel:"roll & warn",icon:"warning",disabled:!0};const e=t.user,n=this._cb.getUser(e),{isBlocked:o,isAtAIV:i,warnLevel:r}=n;let a="Vandalism",s="roll & warn",l="warning";return o?(a="(User blocked)",s="roll back",l="undo"):i?(a="(Reported @ AIV)",s="roll back",l="undo"):r>=4&&(a="Report to AIV",s="roll back",l="brightness_alert"),{topLabel:a,bottomLabel:s,icon:l,disabled:!1}}_getMonitorButtonConfig(t){if(!t)return{topLabel:"Monitor",bottomLabel:"editor",icon:"person_add",disabled:!0};const e=this._cb.getUser(t.user),{isMonitored:n,monitorReason:o}=e;return{topLabel:n?"Unmonitor":"Monitor",bottomLabel:n?o:"editor",icon:this._getMonitorIcon(n,o),disabled:!1}}_getHistoryButtonConfig(t){return{disabled:!t?.pageHistory?.length}}_getTalkButtonConfig(t){if(!t)return{topLabel:"User",bottomLabel:"talk",disabled:!0,badge:null};const e=t.user,n=this._cb.getUser(e),{warnLevel:o,hasWarnings:i}=n;return{topLabel:"User",bottomLabel:"talk",disabled:!i,badge:o>0?o:null,badgeClassName:o>=4?"cvpi-badge--warning-high":o>=2?"cvpi-badge--warning-mid":""}}_getDashboardConfig(t){return{disabled:!t}}_createToolbar(t,e="",n="top"){const o=this._toolbarManager.createToolbar(t,n);return e&&o.classList.add(...e.split(" ")),o}_buildChangelogElement(t){const e=document.createElement("div");e.className="cvpi-changelog cvpi-toolbarPanel--history",e.style.padding="1em",e.style.width="24rem";let n="";for(const[e,o]of Object.entries(t))n+=`\n\t\t\t\t<h2 style="margin-top: 1.5em; border-bottom: 1px solid var(--border-color-divider, #a2a9b1); padding-bottom: 8px; font-size: 1.2em;">\n\t\t\t\t\tv${e}\n\t\t\t\t</h2>\n\t\t\t\t<ul style="padding-left: 20px; line-height: 1.6; font-size: 0.9em;">\n\t\t\t\t\t${o.map(t=>`<li style="margin-bottom: 8px;">${this._escapeHtml(t).replace(/&lt;b&gt;/g,"<b>").replace(/&lt;\/b&gt;/g,"</b>")}</li>`).join("")}\n\t\t\t\t</ul>`;return e.innerHTML=`\n\t\t\t<div style="max-height: 60dvh; overflow-y: auto; padding-right: 0.5em;">\n\t\t\t\t<p><em>Click outside of this changelog to continue...</em></p>\n\t\t\t\t${n}\n\t\t\t</div>\n\t\t\t<button class="mw-ui-button mw-ui-progressive cvpi-btn--wide" style="height: 4em; border-radius: 0.5em; margin: 1em auto 0; display: block;">Okay</button>\n\t\t`,e.querySelector("button").onclick=()=>{this._toolbarManager.closeAllMenus()},e}_buildHistoryComponent(t,n=!1){const o=document.createElement("div");if(o.className="cvpi-history"+(n?" cvpi-toolbarPanel--history":" cvpi-sidebar-pane--history"),!t?.pageHistory?.length)return t&&(o.innerHTML='<p class="cvpi-talk__empty">No history available.</p>'),o;const i=c("history",t.title),r="mw-ui-button mw-ui-neutral cvpi-btn--wide "+(n?"":"cvpi-btn--sidebar-top"),a=t.pageHistory.findIndex(e=>e.revid===t.revid);let s="";for(let n=0;n<t.pageHistory.slice(0,e.UI.HISTORY_LIMIT).length;n++){const e=t.pageHistory[n],o=e.user===t.user;s+=`\n\t\t\t\t<li class="${-1!==a&&n>=a&&n<a+(t.rev_count||1)?"cvpi-history__item--active":""}">\n\t\t\t\t\t<a href="${c("diff",e.revid,e.parentid,t.title)}" target="_blank">\n\t\t\t\t\t\t<span class="cvpi-history__ts">${l(new Date(e.timestamp))}</span>\n\t\t\t\t\t\t<span class="cvpi-history__user${o?" cvpi-history__user--current":""}">${this._escapeHtml(e.user)}</span>\n\t\t\t\t\t\t<span class="cvpi-history__comments">${this._escapeHtml(e.comment||"")}</span>\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t`}return o.innerHTML=`\n\t\t\t<a href="${i}" target="_blank" class="${r}" style="display: block; text-align: center; box-sizing: border-box; text-decoration: none;">Page history</a>\n\t\t\t<ul class="cvpi-history__list">${s}</ul>\n\t\t`,o}_buildTalkComponent(t=!1,e){const n=document.createElement("div");n.className="cvpi-talk"+(t?" cvpi-toolbarPanel--talk":" cvpi-sidebar-pane--talk");const o=e?.user;if(!o)return n;const i="mw-ui-button mw-ui-neutral cvpi-btn--wide "+(t?"":"cvpi-btn--sidebar-top"),r=c("talk",o),a=this._cb.getUser(o).warnings;if(0===a.length)return n.innerHTML=`\n\t\t\t\t<a href="${r}" target="_blank" class="${i}" style="display: block; text-align: center; box-sizing: border-box; text-decoration: none;">User talk</a>\n\t\t\t\t<p class="cvpi-talk__empty">No recent warnings found.</p>\n\t\t\t`,n;const s=[...a].sort((t,e)=>(t.dateObject??0)-(e.dateObject??0)),l=new Map,d=["January","February","March","April","May","June","July","August","September","October","November","December"];for(const t of s){const e=t.dateObject?`${d[t.dateObject.getUTCMonth()]} ${t.dateObject.getUTCFullYear()}`:"Unknown Date";l.has(e)||l.set(e,[]),l.get(e).push(t)}let u="";for(const[t,e]of l){u+=`<ul class="cvpi-talk__list"><li class="cvpi-talk__month">${t}</li>`;for(const t of e){const e=k(t,this._cb.staleWarningMethod);u+=`\n\t\t\t\t\t<li class="${`cvpi-talk__item${e?"":" cvpi-talk__item--stale"}${t.level>=4&&e?" cvpi-talk__item--final":""} cvpi-talkPageContent-item`}" onclick="window.open('${r}', '_blank')">\n\t\t\t\t\t\t<div class="cvpi-talk__ts">${t.timestamp.includes("second")?"Just now":t.timestamp}</div>\n\t\t\t\t\t\t<div class="cvpi-talk__level">Level <strong>${t.level}</strong></div>\n\t\t\t\t\t\t<div class="cvpi-talk__name">${this._escapeHtml(t.template)}</div>\n\t\t\t\t\t\t<div class="cvpi-talk__issuer">${this._escapeHtml(t.issuer)}</div>\n\t\t\t\t\t</li>\n\t\t\t\t`}u+="</ul>"}return n.innerHTML=`\n\t\t\t<a href="${r}" target="_blank" class="${i}" style="display: block; text-align: center; box-sizing: border-box; text-decoration: none;">User talk</a>\n\t\t\t${u}\n\t\t`,n}_buildPlaceholder(){const t=document.createElement("div");return t.className="cvpi-placeholder",t.textContent="Waiting for next change…",t}_buildTitleArea(t){const{title:e}=t,n=c("page",e),o=document.createElement("div");return o.className="cvpi-meta__title",o.innerHTML=`<a href="${n}" target="_blank">${e}</a>`,o}_buildMetaArea(t){const{revid:e,user:n,reason:o}=t,i=c("contribs",n),r=this._cb.getUser(n).isMonitored,a=document.createElement("div");return a.className="cvpi-meta__details",a.innerHTML=`\n\t\t\t<span id="cvpi-diffsize-${e}" class="cvpi-meta__size">${this._formatSize(t.diffsize)}</span>\n\t\t\t<span class="cvpi-meta__user${r?" cvpi-user--monitored":""}">\n\t\t\t\tby <a href="${i}" target="_blank">${n}</a>\n\t\t\t\t<span id="cvpi-warnlevel-${e}" class="cvpi-meta__warn">(…)</span>\n\t\t\t</span>\n\t\t\t<span id="cvpi-reason-${e}" class="cvpi-meta__reason">Reason: <span class="cvpi-meta__reason__${o||"ORES"}">${o||"ORES"}</span></span>\n\t\t`,a}_buildCommentsArea(t){const{comments:e,title:n,revid:o,old_revid:i,diff_from_revid:r}=t,a=document.createElement("div");a.className="cvpi-meta__summary";const s=c("diff",o,r??i,n),l=t.rev_count??1,d=`<span id="cvpi-meta__revcount-container"><a class="cvpi-meta__revcount" href="${s}" target="_blank">${""+(l>1?`Σ ${l} revisions:`:"")}</a></span>`,u=(e||[]).filter(t=>t&&t.text&&t.text.trim().length>0);return u.length>0?a.innerHTML=d+" "+u.map((t,e)=>{const o=e===u.length-1;return`<a class="cvpi-comment" href="${c("diff",t.revid,t.parentid,n)}" target="_blank">${this._escapeHtml(t.text)}${o?"":";"}</a>`}).join(""):a.innerHTML=d+' <div class="cvpi-comment">(no edit summary)</div>',a}_buildDiffArea(t){const{revid:e}=t,n=document.createElement("div");return n.className="cvpi-diff",n.id=`cvpi-diff-${e}`,n.textContent="Loading diff…",n.addEventListener("scroll",()=>this._updateScrollArrow(n)),n}_buildScrollArrow(){const t=document.createElement("div");return t.id="cvpi-arrow",t.className="cvpi-arrow",t.textContent="▼",t.title="Scroll to next change",t.style.display="none",t.addEventListener("click",()=>this.scrollToNextHidden()),t}_escapeHtml(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}_formatOres(t){if(null==t)return"<i>N/A</i>";const{HIGH:n,MEDIUM:o,LOW:i}=e.UI.ORES_COLORS;return`<span style="color:${t>n?"#d33":t>o?"#e66":t>i?"#f80":"inherit"};font-weight:bold">${t.toFixed(3)}</span>`}_formatSize(t){if(null==t)return"<small>…</small>";return`<span style="color:${t>0?"var(--color-content-added,#006400)":t<0?"var(--color-content-removed,#8b0000)":"inherit"};font-weight:bold;font-family:monospace">${t>0?"+":""}${t}</span>`}_getMonitorIcon(t,e){if(!t)return"person_add";switch(e){case"Automatic":case"Previously":default:return"person_remove";case"MLRS":return"groups"}}_updateScrollArrow(t){const e=document.querySelectorAll(".cvpi-arrow");if(0===e.length||!t)return;const n=t.getBoundingClientRect(),o=[...t.querySelectorAll('ins, del, td[data-marker="−"]')].some(t=>t.getBoundingClientRect().top>n.bottom);e.forEach(t=>{t.style.display=o?"block":"none"})}_clearMainContent(){this._changeComponentContainer&&(this._changeComponentContainer.innerHTML="");const t=document.getElementById("cvpi-loading-message");t&&t.remove()}_clearLiveTime(){this._liveTimeInterval&&(clearInterval(this._liveTimeInterval),this._liveTimeInterval=null)}_startLiveTime(t,e){const n=()=>{const t=document.getElementById("cvpi-footer-timeago");if(!t)return void this._clearLiveTime();Math.floor((Date.now()-e)/1e3);t.textContent=""};n(),this._liveTimeInterval=setInterval(n,1e3)}_startClock(){const t=document.getElementById("cvpi-footer-clock");if(!t)return;this._clockInterval&&clearInterval(this._clockInterval);const e=()=>{if(window.CVPI_DEBUG){e.startTime||(e.startTime=Date.now());const n=(Date.now()-e.startTime)/1e3;if(n<5)return void(t.textContent="API calls: calculating...");const o=this._cb.getApiCallCount()/(n/3600);return void(t.textContent=`API calls: ${o.toFixed(1)}/hr`)}const n=new Date;if("string"==typeof this._timezone)try{t.textContent=n.toLocaleTimeString("en-GB",{timeZone:this._timezone,hour12:!1})}catch(e){t.textContent=n.toLocaleTimeString("en-GB",{timeZone:"UTC",hour12:!1})}else if("number"==typeof this._timezone){const e=new Date(n.getTime()+6e4*this._timezone),o=String(e.getUTCHours()).padStart(2,"0"),i=String(e.getUTCMinutes()).padStart(2,"0"),r=String(e.getUTCSeconds()).padStart(2,"0");t.textContent=`${o}:${i}:${r}`}};e(),this._clockInterval=setInterval(e,1e3)}_injectFonts(){["https://tools-static.wmflabs.org/fontcdn/css?family=IBM+Plex+Mono:400,700&subset=latin","https://tools-static.wmflabs.org/fontcdn/css?family=Material+Symbols+Outlined:400"].forEach(t=>{document.head.appendChild(I.createElement("link",{rel:"stylesheet",href:t}))})}_injectStyles(){const t=document.createElement("style");t.textContent='\n@media screen and (prefers-color-scheme: dark) {\n    html.skin-theme-clientpref-os {\n        color-scheme:light dark;\n        --color-base: #eaecf0;\n        --color-base--hover: #f8f9fa;\n        --color-emphasized: #f8f9fa;\n        --color-neutral: #c8ccd1;\n        --color-subtle: #a2a9b1;\n        --color-disabled: #54595d;\n        --color-disabled-emphasized: #72777d;\n        --color-inverted: #101418;\n        --color-progressive: #88a3e8;\n        --color-progressive--hover: #a6bbf5;\n        --color-progressive--active: #b6d4fb;\n        --color-destructive: #fd7865;\n        --color-destructive--hover: #fea898;\n        --color-destructive--active: #ffc8bd;\n        --color-visited: #a799cd;\n        --color-visited--hover: #c5b9dd;\n        --color-visited--active: #d9d0e9;\n        --color-destructive--visited: #c99391;\n        --color-destructive--visited--hover: #dcb5b3;\n        --color-destructive--visited--active: #e8cecd;\n        --color-error: #fd7865;\n        --color-error--hover: #fea898;\n        --color-error--active: #ffc8bd;\n        --color-warning: #ca982e;\n        --color-success: #2cb491;\n        --color-notice: #a2a9b1;\n        --color-content-added: #80cdb3;\n        --color-content-removed: #fd7865;\n        --color-base--subtle: #a2a9b1;\n        --box-shadow-color-base: #72777d;\n        --box-shadow-color-progressive--focus: #6485d1;\n        --box-shadow-color-progressive-selected: #88a3e8;\n        --box-shadow-color-progressive-selected--hover: #a6bbf5;\n        --box-shadow-color-progressive-selected--active: #b6d4fb;\n        --box-shadow-color-destructive--focus: #6485d1;\n        --box-shadow-color-inverted: #000;\n        --box-shadow-color-alpha-base: rgba(0,0,0,0.87);\n        --mix-blend-mode-blend: screen;\n        --background-color-base: #101418;\n        --background-color-neutral: #27292d;\n        --background-color-neutral-subtle: #202122;\n        --background-color-interactive: #27292d;\n        --background-color-interactive--hover: #404244;\n        --background-color-interactive--active: #54595d;\n        --background-color-interactive-subtle: #202122;\n        --background-color-interactive-subtle--hover: #27292d;\n        --background-color-interactive-subtle--active: #404244;\n        --background-color-disabled: #404244;\n        --background-color-disabled-subtle: #27292d;\n        --background-color-inverted: #f8f9fa;\n        --background-color-progressive--focus: #6485d1;\n        --background-color-progressive-subtle: #1b223d;\n        --background-color-progressive-subtle--hover: #233566;\n        --background-color-progressive-subtle--active: #3056a9;\n        --background-color-destructive--focus: #6485d1;\n        --background-color-destructive-subtle: #3c1a13;\n        --background-color-destructive-subtle--hover: #612419;\n        --background-color-destructive-subtle--active: #9f3526;\n        --background-color-error-subtle: #3c1a13;\n        --background-color-error-subtle--hover: #612419;\n        --background-color-error-subtle--active: #9f3526;\n        --background-color-warning-subtle: #2d2212;\n        --background-color-success-subtle: #132821;\n        --background-color-notice-subtle: #27292d;\n        --background-color-content-added: #233566;\n        --background-color-content-removed: #453217;\n        --background-color-target-text: #572c19;\n        --background-color-backdrop-light: rgba(0,0,0,0.65);\n        --background-color-backdrop-dark: rgba(255,255,255,0.65);\n        --border-color-base: #72777d;\n        --border-color-emphasized: #eaecf0;\n        --border-color-subtle: #54595d;\n        --border-color-muted: #404244;\n        --border-color-interactive--hover: #a2a9b1;\n        --border-color-interactive--active: #c8ccd1;\n        --border-color-disabled: #54595d;\n        --border-color-inverted: #101418;\n        --border-color-progressive--hover: #88a3e8;\n        --border-color-progressive--active: #a6bbf5;\n        --border-color-progressive--focus: #6485d1;\n        --border-color-destructive--hover: #fd7865;\n        --border-color-destructive--active: #fea898;\n        --border-color-destructive--focus: #6485d1;\n        --border-color-error--hover: #fd7865;\n        --border-color-error--active: #fea898;\n        --border-color-warning--hover: #ca982e;\n        --border-color-warning--active: #edb537;\n        --border-color-content-added: #233566;\n        --border-color-content-removed: #987027\n    }\n}\n\n@media screen {\n    :root,.skin-invert,.notheme {\n        --color-base: #202122;\n        --color-base-fixed: #202122;\n        --color-base--hover: #404244;\n        --color-emphasized: #101418;\n        --color-neutral: #404244;\n        --color-subtle: #54595d;\n        --color-placeholder: #72777d;\n        --color-disabled: #a2a9b1;\n        --color-disabled-emphasized: #a2a9b1;\n        --color-inverted: #fff;\n        --color-inverted-fixed: #fff;\n        --color-progressive: #36c;\n        --color-progressive--hover: #3056a9;\n        --color-progressive--active: #233566;\n        --color-progressive--focus: #36c;\n        --color-destructive: #bf3c2c;\n        --color-destructive--hover: #9f3526;\n        --color-destructive--active: #612419;\n        --color-destructive--focus: #36c;\n        --color-visited: #6a60b0;\n        --color-visited--hover: #534fa3;\n        --color-visited--active: #353262;\n        --color-destructive--visited: #9f5555;\n        --color-destructive--visited--hover: #854848;\n        --color-destructive--visited--active: #512e2e;\n        --color-error: #bf3c2c;\n        --color-error--hover: #9f3526;\n        --color-error--active: #612419;\n        --color-warning: #886425;\n        --color-success: #177860;\n        --color-notice: #404244;\n        --color-icon-error: #f54739;\n        --color-icon-warning: #ab7f2a;\n        --color-icon-success: #099979;\n        --color-icon-notice: #72777d;\n        --color-content-added: #006400;\n        --color-content-removed: #8b0000;\n        --filter-invert-icon: 0;\n        --filter-invert-primary-button-icon: 1;\n        --box-shadow-color-base: #a2a9b1;\n        --box-shadow-color-progressive--active: #233566;\n        --box-shadow-color-progressive--focus: #36c;\n        --box-shadow-color-progressive-selected: #36c;\n        --box-shadow-color-progressive-selected--hover: #3056a9;\n        --box-shadow-color-progressive-selected--active: #233566;\n        --box-shadow-color-destructive--focus: #36c;\n        --box-shadow-color-inverted: #fff;\n        --box-shadow-color-alpha-base: rgba(0,0,0,0.06);\n        --box-shadow-color-transparent: transparent;\n        --mix-blend-mode-base: normal;\n        --mix-blend-mode-blend: multiply;\n        --background-color-base: #fff;\n        --background-color-base-fixed: #fff;\n        --background-color-neutral: #eaecf0;\n        --background-color-neutral-subtle: #f8f9fa;\n        --background-color-interactive: #eaecf0;\n        --background-color-interactive--hover: #dadde3;\n        --background-color-interactive--active: #c8ccd1;\n        --background-color-interactive-subtle: #f8f9fa;\n        --background-color-interactive-subtle--hover: #eaecf0;\n        --background-color-interactive-subtle--active: #dadde3;\n        --background-color-disabled: #dadde3;\n        --background-color-disabled-subtle: #eaecf0;\n        --background-color-inverted: #101418;\n        --background-color-progressive: #36c;\n        --background-color-progressive--hover: #3056a9;\n        --background-color-progressive--active: #233566;\n        --background-color-progressive--focus: #36c;\n        --background-color-progressive-subtle: #e8eeff;\n        --background-color-progressive-subtle--hover: #d9e2ff;\n        --background-color-progressive-subtle--active: #b6d4fb;\n        --background-color-destructive: #bf3c2c;\n        --background-color-destructive--hover: #9f3526;\n        --background-color-destructive--active: #612419;\n        --background-color-destructive--focus: #36c;\n        --background-color-destructive-subtle: #ffe9e5;\n        --background-color-destructive-subtle--hover: #ffdad3;\n        --background-color-destructive-subtle--active: #ffc8bd;\n        --background-color-error: #f54739;\n        --background-color-error--hover: #d74032;\n        --background-color-error--active: #bf3c2c;\n        --background-color-error-subtle: #ffe9e5;\n        --background-color-error-subtle--hover: #ffdad3;\n        --background-color-error-subtle--active: #ffc8bd;\n        --background-color-warning-subtle: #fdf2d5;\n        --background-color-success-subtle: #dff2eb;\n        --background-color-notice-subtle: #eaecf0;\n        --background-color-content-added: #a3d3ff;\n        --background-color-content-removed: #ffe49c;\n        --background-color-target-text: #ffead4;\n        --background-color-transparent: transparent;\n        --background-color-backdrop-light: rgba(255,255,255,0.65);\n        --background-color-backdrop-dark: rgba(0,0,0,0.65);\n        --background-color-button-quiet--hover: rgba(0,24,73,0.027);\n        --background-color-button-quiet--active: rgba(0,24,73,0.082);\n        --background-color-input-binary--checked: #36c;\n        --background-color-tab-list-item-framed--hover: rgba(255,255,255,0.3);\n        --background-color-tab-list-item-framed--active: rgba(255,255,255,0.65);\n        --opacity-icon-base: 0.87;\n        --opacity-icon-base--hover: 0.74;\n        --opacity-icon-base--selected: 1;\n        --opacity-icon-base--disabled: 0.51;\n        --opacity-icon-placeholder: 0.51;\n        --opacity-icon-subtle: 0.67;\n        --border-color-base: #a2a9b1;\n        --border-color-emphasized: #202122;\n        --border-color-subtle: #c8ccd1;\n        --border-color-muted: #dadde3;\n        --border-color-interactive: #72777d;\n        --border-color-interactive--hover: #27292d;\n        --border-color-interactive--active: #202122;\n        --border-color-disabled: #c8ccd1;\n        --border-color-inverted: #fff;\n        --border-color-inverted-fixed: #fff;\n        --border-color-progressive: #6485d1;\n        --border-color-progressive--hover: #3056a9;\n        --border-color-progressive--active: #233566;\n        --border-color-progressive--focus: #36c;\n        --border-color-destructive: #f54739;\n        --border-color-destructive--hover: #9f3526;\n        --border-color-destructive--active: #612419;\n        --border-color-destructive--focus: #36c;\n        --border-color-error: #f54739;\n        --border-color-error--hover: #9f3526;\n        --border-color-error--active: #612419;\n        --border-color-warning: #ab7f2a;\n        --border-color-warning--hover: #735421;\n        --border-color-warning--active: #453217;\n        --border-color-success: #099979;\n        --border-color-notice: #72777d;\n        --border-color-content-added: #a3d3ff;\n        --border-color-content-removed: #ffe49c;\n        --border-color-transparent: transparent;\n        --border-color-divider: #a2a9b1;\n        --outline-color-progressive--focus: #36c;\n        --color-link: var(--color-progressive);\n        --color-link--hover: var(--color-progressive--hover);\n        --color-link--active: var(--color-progressive--active);\n        --color-link--focus: var(--color-progressive--focus);\n        --color-link--visited: var(--color-visited);\n        --color-link--visited--hover: var(--color-visited--hover);\n        --color-link--visited--active: var(--color-visited--active);\n        --color-link-red: var(--color-destructive);\n        --color-link-red--hover: var(--color-destructive--hover);\n        --color-link-red--active: var(--color-destructive--active);\n        --color-link-red--focus: var(--color-destructive--focus);\n        --color-link-red--visited: var(--color-destructive--visited);\n        --color-link-red--visited--hover: var(--color-destructive--visited--hover);\n        --color-link-red--visited--active: var(--color-destructive--visited--active);\n        --accent-color-base: #36c;\n        --border-color-input--hover: var(--border-color-interactive);\n        --border-color-input-binary: var(--border-color-interactive);\n        --border-color-input-binary--hover: var(--border-color-progressive--hover);\n        --border-color-input-binary--active: var(--border-color-progressive--active);\n        --border-color-input-binary--focus: var(--border-color-progressive--focus);\n        --border-color-input-binary--checked: var(--border-color-progressive);\n        --color-base--subtle: #54595d\n    }\n}\n/* mio theme elevation shamelessly stolen from https://m3.material.io/components/toolbars/overview */\n:root {\n\t--mio-shadow-color-fore: color-mix(\n\t\tin srgb,\n\t\tvar(--color-base, #000),\n\t\ttransparent 30%\n\t);\n\t--mio-shadow-color-aft: color-mix(\n\t\tin srgb,\n\t\tvar(--color-base, #000),\n\t\ttransparent 15%\n\t);\n\t/* not using the above fore-aft variables here because i don\'t like the look of the white shadow on dark mode */\n\t--mio-theme-elevation-1: 0px 1px 2px 0px rgb(0 0 0 / 30%), 0px 1px 3px 1px rgb(0 0 0 / 15%);\n\t--mio-theme-elevation-2: 0px 1px 2px 0px rgb(0 0 0 / 30%), 0px 2px 6px 2px rgb(0 0 0 / 15%);\n\t--mio-theme-elevation-3: 0px 1px 3px 0px rgb(0 0 0 / 30%), 0px 4px 8px 3px rgb(0 0 0 / 15%);\n\n\t/* CVPI Colors */\n\t--cvpi-color-ins-bg: rgba(0, 255, 0);\n\t--cvpi-color-ins-outline: var(--border-color-content-added);\n\t--cvpi-color-del-bg: rgba(255, 0, 0);\n\t--cvpi-color-del-outline: var(--border-color-content-removed);\n\t--cvpi-color-vandalism-bg-start: #bf3c2c;\n\t--cvpi-color-vandalism-bg-end: #dd3c2c;\n\t--cvpi-color-vandalism: #fff;\n}\n\n.cvpi-colorblind {\n\t--cvpi-color-ins-bg: rgba(30, 126, 229, 0.8);\n\t--cvpi-color-ins-outline: rgba(30, 126, 229, 1);\n\t--cvpi-color-del-bg: rgba(255, 193, 7, 0.8);\n\t--cvpi-color-del-outline: rgba(255, 193, 7, 1);\n\t--cvpi-color-vandalism-bg-start: rgb(232, 174, 0);\n\t--cvpi-color-vandalism-bg-end: rgb(255, 193, 7);\n\t--cvpi-color-vandalism: #000;\n}\n\n/* ── Root layout ─────────────────────────────── */\n.cvpi-only-mobile {\n\tdisplay: none !important;\n}\n\n.cvpi-only-desktop {\n\tdisplay: flex !important;\n}\n\n.cvpi-mobile-layout .cvpi-only-mobile {\n\tdisplay: flex !important;\n}\n\n.cvpi-mobile-layout .cvpi-only-desktop {\n\tdisplay: none !important;\n}\n\n.cvpi-wrapper {\n\tposition: fixed;\n\tinset: 0;\n\tcolor: var(--color-base, #000);\n\tfont-family:\n\t\t-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n\tfont-size: 0.9rem;\n\tz-index: 1000;\n\tdisplay: grid;\n\tpadding: 2px;\n\tbox-sizing: border-box;\n\toverflow: hidden;\n\toverscroll-behavior: none;\n\tgrid-template-columns: auto 20% 1fr 1fr auto auto;\n\tgrid-template-rows: auto min-content auto auto 1fr auto;\n\tgrid-template-areas:\n\t\t"menu   menu    menu    menu    menu    menu"\n\t\t"hist   title   title   title   cvpi-toolbar--top talk"\n\t\t"hist   details details details cvpi-toolbar--top talk"\n\t\t"hist   comments comments comments comments talk"\n\t\t"hist   diff    diff    diff    diff    talk"\n\t\t"footer footer  footer  footer  footer  footer";\n}\n\n.cvpi-wrapper button {\n\tborder-radius:1.5em;\n}\n\n.cvpi-wrapper *,\n.cvpi-wrapper *::before,\n.cvpi-wrapper *::after {\n\tbox-sizing: border-box;\n}\n\n.cvpi-wrapper > * {\n\tmin-width: 0;\n}\n\n.cvpi-background-blur {\n\tposition: fixed;\n\tinset: 0;\n\tbackground: color-mix(\n\t\tin srgb,\n\t\tvar(--background-color-base, #fff),\n\t\ttransparent 10%\n\t);\n\tbackdrop-filter: blur(0.9em);\n\tz-index: 999;\n}\n\n.cvpi-progress-notification {\n\tposition: fixed;\n\tfont-size: 0.75rem;\n\tbottom: 7.5rem; /* Top edge of the 4rem tall toolbar (which is at 3.5rem bottom) */\n\tleft: 50%;\n\ttransform: translate(-50%, 100%); /* Initial state: pushed down behind the toolbar */\n\tbackground: var(--background-color-base, #fff);\n\tcolor: var(--color-base, #000);\n\tpadding: 0.6rem 1rem;\n\tborder-radius: 1.5rem 1.5rem 0 0;\n\tfont-weight: 500;\n\tbox-shadow: var(--mio-theme-elevation-2);\n\tborder: 1px solid var(--border-color-muted);\n\tborder-bottom: none;\n\tz-index: 99; /* Behind toolbar (which is 100) */\n\ttransition: transform 0.3s ease-out, opacity 0.3s ease !important;\n\tpointer-events: none;\n\topacity: 0;\n\tmin-width: 10dvw;\n\tmax-width: 40em;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\ttext-align: center;\n}\n\n.cvpi-progress-notification--visible {\n\ttransform: translate(-50%, 0); /* Slide UP into view to rest on top of the toolbar */\n\topacity: 1;\n}\n\n/* =========================================\n   Floating Toolbar\n   ========================================= */\nmenu {\n\tpadding-inline-start: 0;\n}\n\n.cvpi-toolbar {\n\tdisplay: flex;\n\tposition:absolute;\n\tbottom: 3.5rem;\n\talign-items: stretch;\n\tjustify-content: space-evenly;\n\tjustify-self: center;\n\theight: 4rem;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0 1em;\n\twidth: 95dvw;\n\tmax-width: 40em;\n\tz-index: 100;\n}\n\n.cvpi-toolbar.cvpi-toolbar--actions {\n\tdisplay:grid;\n\tgrid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto minmax(0, 1fr) minmax(0, 1fr);\n}\n\n.cvpi-mobile-layout .cvpi-toolbar {\n\tpadding: 0 0.5em;\n}\n\n/* Background and blur layer for the toolbar */\n.cvpi-toolbar::before {\n\tcontent: "";\n\tposition: absolute;\n\tinset: 0;\n\tbackground-color: color-mix(in srgb, var(--background-color-base), transparent 10%);\n\tbackdrop-filter: blur(2px);\n\t-webkit-backdrop-filter: blur(2px);\n\tborder-radius: 24px;\n\tbox-shadow: var(--mio-theme-elevation-1);\n\tborder: 1px solid var(--border-color-muted);\n\tz-index: -1;\n\tpointer-events: none;\n}\n\n.cvpi-toolbar--top {\n\tposition:relative;\n\tgrid-area: cvpi-toolbar--top;\n\tjustify-self:end;\n\talign-self:start;\n\tbottom: auto; /* Override the bottom: 3.5rem from .cvpi-toolbar */\n\twidth: auto;\n\tmax-width: none;\n\tmargin-top:0.25em;\n\tmargin-right:0.25em;\n}\n\n.cvpi-toolbar--top .cvpi-toolbar__toolbarPanel {\n\tright: 0;\n\tleft: auto !important;\n}\n\n.cvpi-toolbar__action-item {\n\tdisplay: flex;\n\tjustify-content: center;\n\tposition: relative;\n}\n\n[data-action-id="cvpi-toolbar-vandalism"] {\n\tmargin: 0 1em;\n\theight: 110%;\n\ttop: -5%;\n}\n\n.cvpi-action-btn {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\twidth:100%;\n\tmax-width: 6em;\n\ttransition: background-color 0.2s ease;\n\tcursor: pointer;\n\tborder-radius: 1.5em;\n\tpadding: 0 0.5em;\n}\n\n.cvpi-toolbar--actions .cvpi-action-btn {\n\tline-height: 0.75em;\n}\n\n.cvpi-toolbar-btn--vandalism {\n\tmin-width:6em;\n\tbackground-image: linear-gradient(316deg, var(--cvpi-color-vandalism-bg-start), var(--cvpi-color-vandalism-bg-end));\n\tcolor: var(--cvpi-color-vandalism);\n}\n\n.cvpi-toolbar-btn--vandalism > span,\n.cvpi-toolbar-btn--vandalism > span.cvpi-action-btn__label {\n\tcolor: var(--cvpi-color-vandalism);\n}\n\n.cvpi-action-btn--disabled {\n\topacity: 0.5;\n\tcursor: not-allowed;\n\tpointer-events: none;\n\ttransition: none !important;\n}\n\n.cvpi-action-btn--disabled.cvpi-toolbar-btn--vandalism {\n\topacity: 1;\n\tbackground-color: color-mix(in srgb, var(--background-color-base), var(--cvpi-color-vandalism-bg-start) 60%);\n\tbackground-image: none;\n}\n\n.cvpi-action-btn--disabled:active {\n\tbackground-color: transparent !important;\n}\n\n@media (hover: hover) {\n\t.cvpi-action-btn--disabled:hover {\n\t\tbackground-color: transparent !important;\n\t}\n\n\t.cvpi-action-btn:hover:not(.cvpi-toolbar-btn--vandalism) {\n\t\tbackground-color: var(--background-color-interactive-subtle--hover);\n\t}\n\n\t.cvpi-action-btn.cvpi-toolbar-btn--vandalism:hover {\n\t\tborder: 2px solid var(--color-emphasized);\n\t\tbackground-color: #e24633;\n\t}\n}\n\n.cvpi-action-btn:active {\n\tbackground-color: var(--background-color-interactive-subtle--active);\n}\n\n.cvpi-toolbar__action-item--left-end > .cvpi-action-btn {\n\tborder-top-left-radius: 24px;\n\tborder-bottom-left-radius: 24px;\n}\n\n.cvpi-toolbar__action-item--right-end > .cvpi-action-btn {\n\tborder-top-right-radius: 24px;\n\tborder-bottom-right-radius: 24px;\n}\n\n.cvpi-action-btn__symbol {\n\tposition: relative;\n\tmargin-bottom: 2px;\n\tcolor: var(--color-emphasized);\n\tuser-select: none;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cvpi-action-btn__badge {\n\tposition: absolute;\n\ttop: -1em;\n\tright: -1em;\n\tbackground-color: var(--background-color-progressive--focus, #6485d1);\n\tcolor: var(--color-inverted, #fff);\n\tborder-radius: 50%;\n\tmin-width: 1.2rem;\n\theight: 1.2rem;\n\tfont-size: 0.7rem;\n\tfont-family:sans-serif;\n\tfont-weight: bold;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 0 4px;\n\tbox-shadow: 0 0 0 2px var(--background-color-base, #fff);\n\tz-index: 1;\n\tpointer-events: none;\n}\n\n.cvpi-action-btn__symbol:not(.material-symbols-outlined) {\n\tfont-weight: bold;\n}\n\n.cvpi-menu-item {\n\tdisplay: grid;\n\tgrid-template-columns: 24px 1fr auto;\n\talign-items: center;\n\tpadding: 8px 16px;\n\tgap: 12px;\n\twidth: 100%;\n\ttransition: background-color 0.15s ease;\n\tcursor: pointer;\n}\n\n@media (hover: hover) {\n\t.cvpi-menu-item:hover {\n\t\tbackground-color: var(--background-color-interactive-subtle--hover);\n\t}\n}\n\n.cvpi-menu-item:active {\n\tbackground-color: var(--background-color-interactive-subtle--active);\n}\n\n.cvpi-menu-item--disabled {\n\topacity: 0.5;\n\tcursor: not-allowed;\n\tpointer-events: none;\n}\n\n.cvpi-menu-item__icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 24px;\n\theight: 24px;\n\tcolor: var(--color-emphasized);\n}\n\n.cvpi-menu-item__label {\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcolor: var(--color-base);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cvpi-menu-item__action {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-self: end;\n}\n\n.cvpi-menu-item .cvpi-action-btn__symbol {\n\tmargin: 0;\n\tfont-size: 20px;\n}\n\n.cvpi-action-btn__label {\n\tfont-size: 0.75em;\n\tfont-weight: 500;\n\tcolor: var(--color-subtle);\n\tuser-select: none;\n\ttext-align: center;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cvpi-hide-labels .cvpi-action-btn__label {\n\tdisplay: none;\n}\n\n/* Submenu: Inline */\n.cvpi-toolbar__inline-toolbar {\n\tdisplay: none;\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n\tbackground-color: var(--background-color-neutral-subtle);\n}\n\n.cvpi-toolbar__inline-toolbar[data-state="cvpi-toolbar--open"] {\n\tdisplay: flex;\n}\n\n@media (hover: hover) {\n\t.cvpi-toolbar__inline-toolbar .cvpi-action-btn:hover {\n\t\tbackground-color: var(--background-color-interactive--hover);\n\t}\n}\n\n.cvpi-toolbar__inline-toolbar .cvpi-action-btn:active {\n\tbackground-color: var(--background-color-interactive--active);\n}\n\n/* Submenu: Vertical */\n.cvpi-toolbar__vertical-menu {\n\tdisplay: flex;\n\tlist-style: none;\n\tpadding: 8px 0;\n\tmargin: 0;\n\tmin-width: 160px;\n\tmin-height: 15em;\n\tmax-height: calc(100dvh - 15em);\n\toverflow-y: scroll;\n\tflex-direction: column;\n}\n\n.cvpi-toolbar__vertical-menu-item {\n\tdisplay: block;\n\twidth: 100%;\n}\n\n/* Stepper Component */\n.cvpi-stepper-wrapper {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cvpi-stepper-btn {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 24px;\n\theight: 24px;\n\tborder-radius: 4px;\n\tbackground-color: var(--background-color-interactive-subtle);\n\tcursor: pointer;\n\tuser-select: none;\n\ttransition: background-color 0.15s ease;\n}\n\n@media (hover: hover) {\n\t.cvpi-stepper-btn:hover {\n\t\tbackground-color: var(--background-color-interactive-subtle--hover);\n\t}\n}\n\n.cvpi-stepper-btn:active {\n\tbackground-color: var(--background-color-interactive-subtle--active);\n}\n\n.cvpi-stepper-value {\n\tmin-width: 40px;\n\ttext-align: center;\n\tfont-family: monospace;\n\tfont-weight: bold;\n}\n\n/* Toggle Switch Component */\n.cvpi-switch {\n\tposition: relative;\n\tdisplay: inline-block;\n\twidth: 36px;\n\theight: 20px;\n}\n\n.cvpi-switch input {\n\topacity: 0;\n\twidth: 0;\n\theight: 0;\n}\n\n.cvpi-switch-slider {\n\tposition: absolute;\n\tcursor: pointer;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tbackground-color: var(--border-color-base);\n\ttransition: .2s;\n\tborder-radius: 20px;\n}\n\n.cvpi-switch-slider:before {\n\tposition: absolute;\n\tcontent: "";\n\theight: 14px;\n\twidth: 14px;\n\tleft: 3px;\n\tbottom: 3px;\n\tbackground-color: var(--color-inverted);\n\ttransition: .2s;\n\tborder-radius: 50%;\n}\n\ninput:checked + .cvpi-switch-slider {\n\tbackground-color: var(--color-success);\n}\n\ninput:checked + .cvpi-switch-slider:before {\n\ttransform: translateX(16px);\n}\n\n.cvpi-history {\n\tdisplay: block;\n\tgrid-area: hist;\n\toverflow-y: auto;\n}\n.cvpi-sidebar-pane--talk {\n\tgrid-area: talk;\n\toverflow-y: auto;\n}\n\n.cvpi-meta__title {\n\tgrid-area: title;\n\tfont-weight: 700;\n\tfont-size: 1.5em;\n\tdisplay: -webkit-box;\n\tline-clamp: 3;\n\t-webkit-line-clamp: 3;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tpadding: 6px 12px;\n}\n.cvpi-meta__details {\n\tgrid-area: details;\n\tdisplay: flex;\n\tgap: 0.8em;\n\talign-items: center;\n\tfont-size: 0.9em;\n\tpadding: 6px 12px;\n}\n.cvpi-meta__size {\n\tfont-weight: bold;\n}\n.cvpi-meta__user {\n\tfont-weight: 400;\n\tcolor: var(--color-subtle, #53595d);\n}\n.cvpi-meta__user a {\n\tfont-weight: 600;\n\tcolor: var(--color-progressive, #36c);\n}\n.cvpi-meta__warn {\n\tfont-weight: bold;\n\tmargin-left: 4px;\n}\n.cvpi-meta__reason {\n\tdisplay:none; /* temporarily hiding this until i find a better spot or magically manifest more space in the meta area */\n\tmargin-left: auto;\n\tcolor: var(--color-subtle, #53595d);\n\tfont-style: italic;\n}\n.cvpi-meta__reason__MLRS {\n\tbackground-color: rgba(0, 255, 0, 0.4);\n\toutline: 1px solid green;\n\tborder-radius: 0.2em;\n\tpadding: 0.2em;\n\tfont-weight: 600;\n}\n\n.cvpi-meta__summary {\n\tgrid-area: comments;\n\tword-break: break-word;\n\toverflow-y: auto;\n\tpadding: 4px 12px;\n\tmax-height:20dvh;\n}\n\na.cvpi-comment {\n\ttext-decoration: underline dotted var(--color-subtle);\n\ttext-decoration-thickness: 1px;\n}\n\na.cvpi-meta__revcount {\n\tcolor:var(--color-subtle);\n}\n\n.cvpi-comment {\n\tcolor:var(--color-subtle);\n\tfont-style: italic;\n\tdisplay:inline;\n\tpadding:0 0.2em;\n\tborder-radius:0.2em;\n}\n\n.cvpi-diff {\n\tgrid-area: diff;\n\tfont-family: "IBM Plex Mono", monospace;\n\toverflow: auto;\n\tpadding: 0;\n\tbox-shadow: 0 0 0.15rem var(--box-shadow-color-base);\n}\n.cvpi-footer {\n\tgrid-area: footer;\n\tdisplay: grid;\n\tgrid-template-columns: 1fr auto 1fr;\n\twidth: 100%;\n\tfont-size: 0.85em;\n\tcolor: var(--color-subtle, #53595d);\n\tpadding: 0.5em;\n\tbackground-color: var(--background-color-base);\n\talign-items: center;\n}\n.cvpi-footer-cell--status-area {\n\tjustify-self: start;\n}\n.cvpi-footer__center {\n\tjustify-self: center;\n}\n.cvpi-footer-timestamp {\n\tjustify-self: end;\n}\n.cvpi-footer a {\n\tcolor: var(--color-subtle, #53595d);\n\ttext-decoration: underline;\n}\n\n/* ── Placeholder ─────────────────────────────── */\n.cvpi-placeholder {\n\tgrid-area: diff;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tcolor: var(--color-subtle, #53595d);\n\theight: 100%;\n\ttext-align: center;\n}\n\n/* Specific overrides for integrated (non-overlay) views */\n.cvpi-history,\n.cvpi-sidebar-pane--talk,\n.cvpi-toolbarPanel--talk {\n\twidth: 15rem;\n\tmin-width: 15rem;\n\tfont-size: 0.75rem;\n}\n.cvpi-sidebar-pane--history,\n.cvpi-sidebar-pane--talk {\n\tdisplay: block;\n}\n\n/* Update-in-place Layout toggles */\n.cvpi-sidebar-pane {\n\tdisplay: block;\n}\n.cvpi-toolbarPanel {\n\tdisplay: none;\n}\n.cvpi-mobile-layout .cvpi-sidebar-pane {\n\tdisplay: none !important;\n}\n.cvpi-mobile-layout .cvpi-toolbarPanel {\n\tdisplay: block;\n}\n\n/* ── Blur and Focus Logic ─────────────────────── */\n.cvpi-wrapper > * {\n\ttransition:\n\t\tfilter 0.25s ease,\n\t\topacity 0.2s ease;\n}\n\n/* ── Nav specific ────────────────────────────── */\n.cvpi-next--alert .cvpi-action-btn__badge {\n\tbackground: var(--background-color-destructive, #bf3c2c) !important;\n\tcolor: #fff !important;\n}\n\n.cvpi-badge--warning-high {\n\tbackground: #d33 !important;\n\tcolor: #fff !important;\n}\n\n.cvpi-badge--warning-mid {\n\tbackground: #f80 !important;\n\tcolor: #fff !important;\n}\n\n/* ── Diff ────────────────────────────────────── */\n.cvpi-diff table.diff {\n\twidth: 100%;\n\ttable-layout: fixed;\n\tborder-collapse: collapse;\n\tmargin: 0;\n\tword-break: break-word;\n\tfont-size: 0.9em;\n}\n.cvpi-diff col.diff-marker,\n.cvpi-diff td.diff-marker {\n\twidth: 1px;\n\toverflow: hidden;\n}\n.cvpi-diff tr.cvpi-diff-bottompadding {\n\t/*\n\t * so the user can scroll to see what\'s\n\t * behind the toolbar.\n\t * \n\t * toolbarHeight + toolbarBottomOffset\n\t */\n\theight: calc(4rem + 3.5rem);\n}\n.cvpi-diff ins {\n\ttext-decoration: none;\n\tbackground: color-mix(\n\t\tin srgb,\n\t\tvar(--cvpi-color-ins-bg),\n\t\ttransparent 50%\n\t);\n\toutline: 0.2em solid var(--cvpi-color-ins-outline);\n}\n.cvpi-diff del {\n\ttext-decoration: none;\n\t\tbackground: color-mix(\n\t\tin srgb,\n\t\tvar(--cvpi-color-del-bg),\n\t\ttransparent 50%\n\t);\n\toutline: 0.2em solid var(--cvpi-color-del-outline);\n}\n.cvpi-diff .diffchange {\n\tborder-radius: 0.2em;\n\tpadding: 0 0.2em;\n\tmargin: 0 0.2em;\n}\n.cvpi-diff .diff-addedline {\n\tbackground: color-mix(in srgb, var(--cvpi-color-ins-bg), transparent 90%);\n}\n.cvpi-diff .diff-deletedline {\n\tbackground: color-mix(in srgb, var(--cvpi-color-del-bg), transparent 90%);\n}\n.cvpi-diff .diff-changedline {\n\tbackground: rgba(128, 128, 128, 0.1);\n}\n.cvpi-diff--overridden {\n\topacity: 0.7;\n\tfont-style: italic;\n}\n.cvpi-diff a {\n\ttext-decoration: underline;\n\tcolor: var(--color-notice, #404244);\n}\n\n/* ── Scroll arrow ────────────────────────────── */\n.cvpi-arrow {\n\tgrid-area: diff;\n\talign-self: start;\n\tjustify-self: center;\n\tz-index: 1;\n\tdisplay: none;\n\tcursor: pointer;\n\tuser-select: none;\n\tbackground: var(--background-color-neutral-subtle, #eaecf0);\n\tcolor: var(--color-progressive, #36c);\n\tpadding: 0.25em 1em;\n\topacity: 0.85;\n\tfont-size: 1.5em;\n\tborder-radius: 0 0 1em 1em;\n\tbox-shadow: var(--mio-theme-elevation-1);\n}\n\n/* ── Modal ───────────────────────────────────── */\n.cvpi-modal {\n\tposition: fixed;\n\tinset: 0;\n\tbackground: rgba(0, 0, 0, 0.1);\n\tz-index: 2000;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackdrop-filter: blur(0.1em);\n\t-webkit-backdrop-filter: blur(0.1em);\n}\n\n.cvpi-modal-box {\n\twidth: 90%;\n\tmax-width: 400px;\n\tpadding: 16px;\n\tbackground: var(--background-color-base, #fff);\n\tborder-radius: 4px;\n\tbox-shadow: var(--mio-theme-elevation-3);\n}\n\n.cvpi-modal-box h3 {\n\tmargin: 0 0 12px;\n\tborder-bottom: 1px solid var(--border-color-divider);\n\tpadding-bottom: 8px;\n}\n\n.cvpi-modal-choices-container {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(3, 1fr);\n\tgap: 0.5em;\n}\n\n.cvpi-modal-choices-container > button { \n\tbox-shadow: var(--mio-theme-elevation-1);\n}\n\n.cvpi-modal-input {\n\twidth: 100%;\n\tbox-sizing: border-box;\n\tpadding: 8px;\n\tborder: 1px solid var(--border-color-input-binary, #72777d);\n\tborder-radius: 2px;\n}\n\n.cvpi-modal-actions {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tmargin-top: 16px;\n}\n\n/* ── Overlay lists ───────────────────────────── */\n.cvpi-history > button,\n.cvpi-talk > button {\n\twidth: 100%;\n\tmargin-bottom: 5px;\n}\n.cvpi-history__list,\n.cvpi-talk__list {\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n}\n.cvpi-history__list li,\n.cvpi-talk__empty {\n\tpadding: 4px;\n\tborder-radius: 0.5em;\n\tbackground: var(--background-color-neutral-subtle, #eaecf0);\n\tborder: 1px solid var(--border-color-muted, #dadde3);\n\tmargin: 0.5em;\n}\n@media (hover: hover) {\n\t.cvpi-history__list li:hover {\n\t\tbackground-color: var(--background-color-interactive--hover, #dadde3);\n\t}\n}\n.cvpi-history__list li:active {\n\tbackground-color: var(--background-color-interactive--active, #c8ccd1);\n}\n.cvpi-history__list li.cvpi-history__item--active {\n\tborder-color: var(--border-color-emphasized, #202122);\n}\n\n.cvpi-talk__list {\n\tbackground: var(--background-color-neutral-subtle, #eaecf0);\n\tborder: 1px solid var(--border-color-subtle, #c8ccd1);\n\tborder-radius: 0.8rem;\n\toverflow: hidden;\n\tmargin: 0.5em;\n}\n\n/* ── History view ───────────────────────────── */\n.cvpi-history__list a {\n\tdisplay: grid;\n\tgrid-template: "ts user" "comments comments" / 1fr 1fr;\n\ttext-decoration: none;\n\tcolor: inherit;\n\trow-gap: 4px;\n}\n.cvpi-history__ts {\n\tgrid-area: ts;\n\tcolor: var(--color-subtle, #53595d);\n}\n.cvpi-history__user {\n\tgrid-area: user;\n\tcolor: var(--color-subtle, #53595d);\n\tjustify-self: right;\n\ttext-align: right;\n}\n.cvpi-history__user--current {\n\tcolor: var(--color-destructive, #bf3c2c);\n\tfont-weight: bold;\n}\n.cvpi-history__comments {\n\tgrid-area: comments;\n\tbackground: var(--background-color-backdrop-light, rgba(255, 255, 255, 0.65));\n\toutline: 1px solid var(--border-color-muted, #dadde3);\n\tborder-radius: 0.5em;\n\tpadding: 4px 6px;\n\tmin-height: 1.5rem;\n\tword-break: break-word;\n\tbox-sizing: border-box;\n}\n\n/* ── Talk view ──────────────────────────────── */\n.cvpi-talk__month {\n\tbackground: color-mix(in srgb, var(--color-subtle, #6e6e73), transparent 95%);\n\tfont-weight: 700;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n\tcolor: var(--color-subtle, #6e6e73);\n\tpadding: 0.5em;\n\ttext-align: center;\n}\n.cvpi-talk__item {\n\tcursor: pointer;\n\tdisplay: grid;\n\tgrid-template: "ts ts" "lvl name" "lvl issuer";\n\tgap: 4px;\n\talign-items: center;\n\tjustify-content: space-evenly;\n\tpadding: 8px 4px;\n\tborder-bottom: 1px solid var(--border-color-divider, #a2a9b1);\n}\n.cvpi-talk__item:last-child {\n\tborder-bottom: none;\n}\n@media (hover: hover) {\n\t.cvpi-talk__item:hover {\n\t\tbackground-color: var(--background-color-interactive--hover, #dadde3);\n\t}\n}\n.cvpi-talk__item:active {\n\tbackground-color: var(--background-color-interactive--active, #c8ccd1);\n}\n\n.cvpi-talk__item--stale {\n\topacity: 0.5;\n}\n.cvpi-talk__item.cvpi-talk__item--final {\n\tborder: 2px solid var(--border-color-destructive, #f54739);\n\tborder-radius: 4px;\n\tmargin: 4px;\n\tbackground: rgba(245, 71, 57, 0.05);\n}\n.cvpi-talk__ts {\n\tgrid-area: ts;\n\tborder-bottom: 1px solid var(--border-color-divider, #a2a9b1);\n\ttext-align: center;\n\tpadding-bottom: 2px;\n}\n.cvpi-talk__level {\n\tgrid-area: lvl;\n\tjustify-self: center;\n\tcolor: var(--color-notice, #404244);\n\tfont-size: 1rem;\n}\n.cvpi-talk__name {\n\tgrid-area: name;\n}\n.cvpi-talk__issuer {\n\tgrid-area: issuer;\n}\n.cvpi-talk__empty {\n\tpadding: 10px;\n\ttext-align: center;\n}\n\n/* Submenu: ToolbarPanel */\n.cvpi-toolbar__toolbarPanel {\n\tdisplay: none;\n\tposition: absolute;\n\twidth: max-content;\n\tbackground-color: color-mix(in srgb, var(--background-color-base), transparent 10%);\n\tbackdrop-filter: blur(4px);\n\t-webkit-backdrop-filter: blur(4px);\n\tborder-radius: 2em;\n\tbox-shadow: var(--mio-theme-elevation-1);\n\tlist-style: none;\n\tpadding: 0;\n\tz-index: 10;\n\tborder: 1px solid var(--border-color-muted);\n\toverflow: hidden;\n}\n\n.cvpi-toolbar--actions >.cvpi-toolbar__toolbarPanel {\n\tmax-width: 100%;\n}\n\n.cvpi-mobile-layout .cvpi-toolbar--actions >.cvpi-toolbar__toolbarPanel[data-active-action="cvpi-toolbar-dashboard"] {\n\tmax-width: 95dvw;\n\tleft: 50% !important;\n\ttransform: translateX(-50%);\n}\n\n.cvpi-mobile-layout .cvpi-toolbar--actions >.cvpi-toolbar__toolbarPanel .cvpi-rollback-dashboard {\n\tpadding: 1.5em 0.5em;\n}\n\n.cvpi-mobile-layout .cvpi-toolbar--actions > .cvpi-toolbar__toolbarPanel .cvpi-rollback-dashboard .cvpi-rollback-dashboard__grid {\n\tgap: 0.25em;\n}\n\n.cvpi-toolbar__toolbarPanel[data-placement="top"] {\n\tbottom: 100%;\n\tmargin-bottom: 8px;\n}\n\n.cvpi-toolbar__toolbarPanel[data-placement="bottom"] {\n\ttop: 100%;\n\tmargin-top: 8px;\n}\n\n/*\n\nborder: 10px solid;\nborder-image: repeating-linear-gradient(\n\t45deg,\n\t#fcd116,\n\t#fcd116 10px,\n\t#000 10px,\n\t#000 20px\n\t) 10;\n*/\n\n.cvpi-toolbar__toolbarPanel[data-state="cvpi-toolbar--open"] {\n\tdisplay: flex;\n}\n\n/* ── ToolbarPanel Adjustments ──────────────── */\n.cvpi-toolbarPanel--history,\n.cvpi-toolbarPanel--talk {\n\tmax-height: 60dvh;\n\toverflow-y: auto;\n\twidth: 15rem;\n\tmax-width: 95dvw;\n\tbackground-color: transparent;\n}\n\n.cvpi-toolbarPanel--history a.cvpi-btn--wide,\n.cvpi-toolbarPanel--talk a.cvpi-btn--wide {\n\tmargin-bottom: 8px;\n\tborder-radius: 2em 2em 0 0;\n}\n\n#cvpi-btn-vandalism {\n\tbox-shadow: var(--mio-theme-elevation-3);\n}\n\n/* ── Rollback dashboard ────────────────────────── */\n.cvpi-rollback-dashboard {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0.75em;\n\tmax-width: 50em;\n\tmargin: 0 auto;\n\tpadding: 1.5em;\n\tmax-height: calc(100dvh - 15rem);\n\toverflow: auto;\n}\n.cvpi-rollback-dashboard button {\n\tbox-shadow: var(--mio-theme-elevation-1);\n}\n.cvpi-rollback-dashboard__grid {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 1fr 1fr;\n\tgap: 1.25em;\n\tflex-shrink: 0;\n\talign-items: start;\n}\n\n.cvpi-rollback-dashboard__grid--single button.cvpi-btn--arm {\n\tbackground-color: var(--background-color-interactive-subtle);\n}\n\n.cvpi-rollback-group {\n\tdisplay: grid;\n\tgap: 0.25em;\n}\n.cvpi-rollback-group--multi {\n\tgrid-template: "a a" "1 2" "3 4" / 1fr 1fr;\n}\n.cvpi-rollback-group .cvpi-btn--auto {\n\tgrid-area: a;\n\tmin-height: 3em;\n}\n.cvpi-rollback-group--single {\n\tgrid-template-columns: 1fr 1fr;\n\talign-content: end;\n}\n.cvpi-rollback-group--single > button:first-child {\n\tgrid-column: 1 / -1;\n}\n.cvpi-rollback-group__title {\n\ttext-align: center;\n\tfont-weight: bold;\n\tpadding-bottom: 5px;\n\tgrid-column: 1 / -1;\n}\n.cvpi-rollback-footer {\n\tdisplay: flex;\n\tgap: 5px;\n\tflex-wrap: wrap;\n\tflex-shrink: 0;\n}\n.cvpi-rollback-footer .mw-ui-button {\n\tflex: 1;\n}\n\n.cvpi-btn--wide {\n\twidth: 100%;\n}\n\n.cvpi-rollback-group button.mw-ui-button {\n\tmin-height: 3.5em;\n}\n\n/* Auto button text toggling for mobile */\n.cvpi-rollback-group:not(.cvpi-rollback-group--active) .cvpi-btn-text-expanded {\n\tdisplay: none;\n}\n.cvpi-rollback-group.cvpi-rollback-group--active .cvpi-btn-text-collapsed {\n\tdisplay: none;\n}\n\n/* ── Desktop Overrides ── */\n/* Make hidden buttons visible */\n.cvpi-wrapper:not(.cvpi-mobile-layout) .cvpi-rollback-group .cvpi-hidden {\n\tvisibility: visible !important;\n}\n\n/* Force expanded text on auto buttons */\n.cvpi-wrapper:not(.cvpi-mobile-layout) .cvpi-btn-text-collapsed {\n\tdisplay: none !important;\n}\n.cvpi-wrapper:not(.cvpi-mobile-layout) .cvpi-btn-text-expanded {\n\tdisplay: inline !important;\n}\n\n/* ── User monitoring ─────────────────────────── */\n.cvpi-user--monitored a {\n\tbackground: #fff9c4;\n\tpadding: 0 2px;\n\tborder-radius: 2px;\n}\n\n/* ── Utility ─────────────────────────────────── */\n.cvpi-hidden {\n\tvisibility: hidden !important;\n}\n.mw-notification-area {\n\tz-index: 2000 !important;\n}\n.mw-ui-button:focus {\n\toutline: none !important;\n\tbox-shadow: none !important;\n}\n\n/* ── CVPI run page border rotation ───────────── */\n@property --cvpi-run-page-gradient-angle {\n\tsyntax: \'<angle>\';\n\tinitial-value: 135deg;\n\tinherits: false;\n}\n\n.cvpi-run-border {\n\tbackground-image: linear-gradient(var(--cvpi-run-page-gradient-angle), #86C, #6ED) !important;\n\tanimation: rotate-gradient 14s linear infinite;\n}\n\n@keyframes rotate-gradient {\n\t0% {\n\t\t--cvpi-run-page-gradient-angle: 135deg;\n\t}\n\t100% {\n\t\t--cvpi-run-page-gradient-angle: 495deg;\n\t}\n}\n',document.head.appendChild(t)}}class M{constructor(){const t=new mw.Api;this.api=new b(t),this.settings=new p,this.users=new v,this.scoreThreshold=e.LOGIC.DEFAULT_SCORE_THRESHOLD,this.queue=new h(this.users,()=>this.scoreThreshold,()=>this.triggerPromotion(),t=>this._handleReadyItem(t)),this.warnings=new w(this.api,this.users),this.nav=new L(this.queue,t=>this._render(t),(...t)=>this._log(...t)),this._isFetching=!1,this._tickTimer=null,this._seenRevids=new Set,this._promotionTimer=null,this.events=new S,this.actionBus=new C(this.events),this.actionBus.init(),this.ui=new R(this.events,this.actionBus,{getThreshold:()=>this.scoreThreshold,getPreference:t=>this.settings.load().preferences[t],getStats:()=>({...this.queue.stats(),historyStack:this.nav.historyStackLength,futureStack:this.nav.futureStackLength}),getApiCallCount:()=>this.api.getCallCount(),getUser:t=>this.users.getUser(t),staleWarningMethod:e.LOGIC.STALE_WARNING_METHOD,getDeviceStats:()=>this.settings.load().statistics}),this.actions=new E(this.api,this.users,this.queue,this.warnings,this.settings,this.nav,this.events,(...t)=>this.ui.promptForReason(...t),(...t)=>this._log(...t)),this.mlrs=new A(this.api,this.users,this.queue,this.events,(...t)=>this._log(...t)),this.details=new P(this.api,this.warnings,this.users,this.events,(...t)=>this._log(...t)),this.api.onHttpError(()=>{this.ui.showProgress("No connection",5)}),this._registerActionHandlers()}_registerActionHandlers(){this.events.on("action:cvpi-action-next",()=>this.nav.showNext()),this.events.on("action:cvpi-action-prev",()=>this.nav.showPrevious()),this.events.on("action:cvpi-action-scroll-next",()=>this.ui.scrollToNextHidden()),this.events.on("action:cvpi-action-rollback",t=>{const e=Number(t.revid),n=t.level,o=n?"null"===n?null:Number(n):0;e&&t.type&&this.actions.handleRollback(e,t.type,o)}),this.events.on("action:cvpi-action-hotkey-rollback",t=>{this.nav.currentRevid&&this.actions.handleRollback(this.nav.currentRevid,t.type,0)}),this.events.on("action:cvpi-action-warn-only",t=>{const e=Number(t.revid);e&&t.type&&this.actions.handleWarnOnly(e,t.type)}),this.events.on("action:cvpi-toolbar-monitor",t=>{const e=Number(t.revid);e&&this.toggleMonitor(e)}),this.events.on("action:cvpi-toolbar-vandalism",t=>{const e=Number(t.revid);e&&this.actions.handleRollback(e,"vandalism",0)}),this.events.on("action:cvpi-action-report-aiv",t=>{const e=Number(t.revid);e&&this.actions.reportToAIV(e)}),this.events.on("action:cvpi-action-other-rollback",t=>{const e=Number(t.revid),n="true"===t.agf;e&&this.actions.promptAndRollback(e,n)}),this.events.on("action:cvpi-action-threshold-inc",()=>this.adjustThreshold(.05)),this.events.on("action:cvpi-action-threshold-dec",()=>this.adjustThreshold(-.05)),this.events.on("action:cvpi-action-label-toggle",t=>{this.settings.setPreference("showLabels",!!t.event.target.checked)}),this.events.on("action:cvpi-action-colorblind-toggle",t=>{this.settings.setPreference("colorblindMode",!!t.event.target.checked)}),this.events.on("action:cvpi-action-twinkle-toggle",t=>{this.settings.setPreference("openTwinkleOnOther",!!t.event.target.checked)})}_log(...t){u("",...t)}get currentlyShownChange(){return this.nav.currentChange}async run(){const[n,o]=await Promise.all([this.api.getUserRights(),this.api.getUserInfo()]);if(!n.includes("rollback"))return alert("CVPI requires rollback rights."),void setTimeout(()=>{window.location.href="http://wiki.nitrosworld.org/wiki/Wikipedia:Requests_for_permissions/Rollback"},3e3);const i=this.settings.load().version,r=e.VERSION;this.ui.setupPage(),o?.options?.timecorrection&&this.ui.updateTimezone(o.options.timecorrection),i!==r&&await this.ui.showChangelog(t),this.settings.save(),await this._seedMonitoredUsers(),await this.tick(),await this.nav.showNext(),this._tickTimer=setInterval(()=>this.tick(),e.LOGIC.REFRESH_RATE_MS),this.mlrs.start()}terminate(){this._log("Terminating application..."),this.actionBus&&(this.actionBus.destroy(),this.actionBus=null),this._tickTimer&&(clearInterval(this._tickTimer),this._tickTimer=null),this.mlrs.stop(),this.ui.destroy(),window.CVPI=null}adjustThreshold(t){const e=Math.round(100*(this.scoreThreshold+t))/100;e>=0&&e<1&&(this.scoreThreshold=e,this.ui.updateThreshold(this.scoreThreshold),this._log("Threshold adjusted to:",this.scoreThreshold))}toggleMonitor(t){const e=this.queue.get(t);if(!e)return;const{user:n}=e;!this.users.isMonitored(n)?(this.users.monitor(n,"manual"),this.queue.promoteUserToMonitored(n),this._log("User added to monitored list:",n)):(this.users.unmonitor(n),this._log("User removed from monitored list:",n)),this.nav.currentRevid===t&&this.ui.syncState(e)}async tick(){if(!this._isFetching){this._isFetching=!0,this._log("Tick starting...");try{const t=await this.api.getRecentChanges();t?.length&&(this._log(`Ingesting ${t.length} changes.`),this.queue.ingest(t)),this.queue.expireStale(),this.queue.evaluatePending(),this.triggerPromotion();const e=this.currentlyShownChange,{needsRerender:n,streakExtended:o,newRevid:i}=await this.queue.syncPageState(this.api,t,e);e&&(this.ui.updateStaleIndicator(e.stale),n&&(o?(this._seenRevids.has(e.revid)&&this._seenRevids.add(i),this.nav.currentRevid=i,this.api.getPageHistoryData(e.title).then(t=>{this.queue.applyHistoryData(e.revid,t),e.diffPromise=null,this._render(e)})):this.api.getPageHistoryData(e.title).then(t=>{this.queue.applyHistoryData(e.revid,t),this.nav.currentRevid===e.revid&&this.ui.syncState(e)}))),this.queue.trim();const r={...this.queue.stats(),historyStack:this.nav.historyStackLength,futureStack:this.nav.futureStackLength};this._log("Queue stats (sync):",r),this.ui.updateQueueDisplay(r)}catch(t){console.error("[CVPI] tick error:",t)}this._isFetching=!1}}triggerPromotion(){this._promotionTimer||(this._promotionTimer=setTimeout(async()=>{this._promotionTimer=null;try{await this._fetchDiffsAndPromote()}catch(t){this._log("Promotion fetch error:",t)}},e.LOGIC.PROMOTE_DEBOUNCE_MS||300))}async _fetchDiffsAndPromote(){const t=this.queue.pendingPromotion();if(!t.length)return;this._log(`Promoting ${t.length} items to ready/monitored.`);const e=[...new Set(t.map(t=>t.title))],n=new Map(await Promise.all(e.map(async t=>[t,await this.api.getPageHistoryData(t)])));await Promise.all(t.map(async t=>{const e=n.get(t.title)??{priorRevid:null,revCount:null,history:[]},o=e.priorRevid??t.old_revid;let i;if(0===t.old_revid)i=Promise.resolve("<i>(New page)</i>");else{const e=await this.api.getDiff(o,t.revid);if(e){if(e.isEmpty)return this._log(`Filtering empty diff for ${t.revid} (${t.title})`),void(t.status="ignored");t.diffsize=e.size,i=Promise.resolve(e.html)}else i=Promise.resolve(null)}this.queue.promoteChange(t.revid,e,i)}))}async _seedMonitoredUsers(){const t=e.API.MONITOR_PAST_ROLLBACKS_HOURS;if(t<=0)return;const n=new Date(Date.now()-36e5*t).toISOString(),o=await this.api.getContributions(mw.user.getName(),n,3,"CVPI");for(const t of o??[])if(t.comment?.includes("(uw-")){const e=t.title.replace(/^User talk:/i,"");this.users.monitor(e,"previously")}}async _render(t){this.ui.displayChange(t),t&&(this._seenRevids.has(t.revid)||(this._seenRevids.add(t.revid),this.settings.incrementStat("recentChanges","seen")),this.details.load(t,()=>this.nav.currentChange))}async _handleReadyItem(t){const e={...this.queue.stats(),historyStack:this.nav.historyStackLength,futureStack:this.nav.futureStackLength};this.ui.updateQueueDisplay(e),this.nav.currentRevid||(this._log("Idle, pushing newly ready item to UI:",t.revid),await this.nav.showNext())}}if("Wikipedia:Interceptor/run"===mw.config.get("wgRelevantPageName")&&"view"===mw.config.get("wgAction")){const t=document.querySelector("a.last-modified-bar.active");t&&t.classList.remove("active")}mw.util.addPortletLink("p-personal",mw.util.getUrl("Wikipedia:Interceptor/run"),"Interceptor","pt-CVPI","Run Interceptor",null,"#pt-preferences"),"Wikipedia:Interceptor/run"===mw.config.get("wgRelevantPageName")&&"view"===mw.config.get("wgAction")&&(window.CVPI||(window.CVPI=new M,window.CVPI.run()))}();


// </nowiki>