MediaWiki:Gadget-readerchrome.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* readerLoader.js — brief themed loading overlay for content pages.
*
* Replaces the "blank visibility:hidden" gap (used to hide the Devanagari
* flash-then-transliterate flip) with an intentional-looking loader. The
* overlay covers only the content area and fades out the moment the page is
* "ready" — either when the first transliteration pass has run, or after a
* short safety timeout so it can never get stuck.
*
* How it coordinates with transliteration:
* • This script adds class `gr-loading` to <html> immediately (before paint).
* CSS then hides content and shows the overlay.
* • When common.js finishes its first applyScript() (or right away if the
* saved script is 'deva'), it must call window.grReaderReady() — that
* removes `gr-loading` and adds `gr-ready`, fading the overlay out and
* revealing content.
* • If grReaderReady() is never called (JS error, etc.), a 2.5s failsafe
* reveals the page anyway — the reader is never left staring at a loader.
*
* Install: load early (top of MediaWiki:Common.js, or a Gadget with
* "type":"general" so it's in <head>). It self-limits to content article
* pages and skips Main Page / Special pages.
*/
( function () {
'use strict';
/* Only show the loader on real content article pages. */
function isContentPage() {
try {
if ( !window.mw || !mw.config ) return false;
var ns = mw.config.get( 'wgNamespaceNumber' );
var pn = mw.config.get( 'wgPageName' ) || '';
if ( ns !== 0 ) return false; // main namespace only
if ( pn === ( mw.config.get( 'wgMainPageTitle' ) || 'Main_Page' ) ) return false;
if ( pn === 'Main_Page' || pn === 'Welcome' ) return false;
return true;
} catch ( e ) { return false; }
}
if ( !isContentPage() ) return;
var root = document.documentElement;
root.classList.add( 'gr-loading' );
/* ── Styles ─────────────────────────────────────────────────────── */
var css = [
/* Hide the parsed content while loading, but keep layout height so the
page doesn't jump when it reveals. */
'html.gr-loading .mw-parser-output{visibility:hidden;}',
'html.gr-ready .mw-parser-output{visibility:visible;}',
/* Overlay sits over the content column only (not the header/toolbar). */
'#gr-reader-loader{',
' position:absolute;left:0;right:0;top:0;',
' min-height:60vh;height:100%;',
' display:flex;flex-direction:column;align-items:center;justify-content:center;',
' gap:16px;z-index:50;pointer-events:none;',
' background:transparent;',
' transition:opacity .3s ease;',
'}',
'html.gr-ready #gr-reader-loader{opacity:0;}',
/* Spinner — a rotating ring in the site terracotta. */
'#gr-reader-loader .gr-rl-ring{',
' width:38px;height:38px;border-radius:50%;',
' border:3px solid rgba(181,69,27,0.18);',
' border-top-color:#b5451b;',
' animation:gr-rl-spin .8s linear infinite;',
'}',
'@keyframes gr-rl-spin{to{transform:rotate(360deg);}}',
/* Label — quiet, manuscript-toned. */
'#gr-reader-loader .gr-rl-label{',
' font-family:"Adishila","Noto Serif Devanagari",system-ui,serif;',
' font-size:15px;color:#a07040;letter-spacing:.02em;',
'}',
/* Reduced-motion: no spin, just a gentle pulse. */
'@media (prefers-reduced-motion: reduce){',
' #gr-reader-loader .gr-rl-ring{animation:gr-rl-pulse 1.2s ease-in-out infinite;border-top-color:rgba(181,69,27,0.18);}',
' @keyframes gr-rl-pulse{0%,100%{opacity:.4}50%{opacity:1}}',
'}'
].join( '' );
var style = document.createElement( 'style' );
style.id = 'gr-reader-loader-css';
style.textContent = css;
( document.head || document.documentElement ).appendChild( style );
/* ── Overlay element (inserted into the content container) ──────── */
function mountOverlay() {
if ( document.getElementById( 'gr-reader-loader' ) ) return;
var host = document.getElementById( 'mw-content-text' ) ||
document.querySelector( '.mw-body-content' ) ||
document.body;
if ( !host ) return;
/* host needs positioning context so the absolute overlay covers it */
var pos = window.getComputedStyle( host ).position;
if ( pos === 'static' ) host.style.position = 'relative';
var ov = document.createElement( 'div' );
ov.id = 'gr-reader-loader';
ov.setAttribute( 'aria-hidden', 'true' );
ov.innerHTML =
'<div class="gr-rl-ring"></div>' +
'<div class="gr-rl-label">लोड्यते…</div>'; /* "loading…" in Sanskrit */
host.appendChild( ov );
}
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', mountOverlay );
} else {
mountOverlay();
}
/* ── Reveal logic ───────────────────────────────────────────────── */
var revealed = false;
function reveal() {
if ( revealed ) return;
revealed = true;
root.classList.remove( 'gr-loading' );
root.classList.add( 'gr-ready' );
/* Remove the overlay node after the fade so it leaves no trace. */
setTimeout( function () {
var ov = document.getElementById( 'gr-reader-loader' );
if ( ov && ov.parentNode ) ov.parentNode.removeChild( ov );
}, 320 );
}
/* common.js calls this after its first applyScript(). */
window.grReaderReady = reveal;
/* Failsafe: never leave the loader up longer than 2.5s, even if the
ready signal never arrives (JS error, missing hook, etc.). */
setTimeout( reveal, 2500 );
}() );