Jump to content

MediaWiki:Gadget-readerchrome.js

From Anandamakaranda
Revision as of 18:10, 2 July 2026 by Vaishnavi (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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 );

}() );