Перейти к содержимому страницы
Переключить меню
Переключить меню настроек
Переключить персональное меню
Вы не представились системе
Ваш IP-адрес будет виден всем, если вы внесёте какие-либо изменения.
Сообщество «Повелителя Тайн»Telegram — новости, дунхуа, игры и мерчDiscord — обсуждение книг и теорий, вопросы по вики

MediaWiki:Common.js

Страница интерфейса MediaWiki

Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.

  • Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
  • Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
  • Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
  • Opera: Нажмите Ctrl+F5.
/* ===== PERF (волна 43): адаптивное качество — html.lotm-lite на слабых устройствах =====
   Признаки: ≤ 4 потоков CPU, ≤ 4 ГБ памяти, prefers-reduced-motion, «экономия трафика», режим производительности
   Citizen, либо замер — средний кадр > 20 мс за 3 с после загрузки (запоминается на 7 дней).
   lite: нет нитей фона (canvas), облака тумана спойлеров не дрейфуют (статичная текстура), звёзды/искры Заглавной
   без мерцания. Принудительно: ?lotmlite=1 / ?lotmlite=0 (запоминается; ?lotmlite=auto — сброс). Отладка: window.lotmLite. */
(function () {
	if (window.lotmLite) { return; }
	var H = document.documentElement, K = 'lotm-lite', ls = null, force = null, why = [];
	try { ls = window.localStorage; } catch (e) {}
	try {
		var u = new URLSearchParams(location.search).get('lotmlite');
		if (ls && (u === '0' || u === '1')) { ls.setItem(K + '-force', u); }
		if (ls && u === 'auto') { ls.removeItem(K + '-force'); ls.removeItem(K + '-slow'); }
		force = ls && ls.getItem(K + '-force');
	} catch (e) {}
	var nav = navigator, mm = window.matchMedia, PM = 'citizen-feature-performance-mode-clientpref-1';
	function set(on, r) {
		if (on && r && why.indexOf(r) < 0) { why.push(r); }
		if (on === H.classList.contains(K)) { return; }
		H.classList.toggle(K, on);
		try { window.dispatchEvent(new CustomEvent('lotm-lite', { detail: on })); } catch (e) {}
	}
	window.lotmLite = { on: function () { return H.classList.contains(K); }, why: why, set: set };
	if (force === '1') { set(true, 'force'); return; }
	if (force === '0') { return; }
	if (nav.hardwareConcurrency && nav.hardwareConcurrency <= 2) { why.push('cores'); }
	if (nav.deviceMemory && nav.deviceMemory <= 2) { why.push('memory'); }
	if (mm && mm('(prefers-reduced-motion: reduce)').matches) { why.push('reduced-motion'); }
	if (nav.connection && nav.connection.saveData) { why.push('save-data'); }
	if (H.classList.contains(PM)) { why.push('citizen-performance'); }
	try { if (ls && +ls.getItem(K + '-slow') > Date.now()) { why.push('slow-frames'); } } catch (e) {}
	if (why.length) { set(true); }
	/* режим производительности Citizen можно переключить без перезагрузки */
	if (window.MutationObserver) {
		new MutationObserver(function () {
			if (H.classList.contains(PM)) { set(true, 'citizen-performance'); }
			else if (why.length === 1 && why[0] === 'citizen-performance') { why.length = 0; set(false); }
		}).observe(H, { attributes: true, attributeFilter: ['class'] });
	}
	if (why.length) { return; }
	/* замер: 3 с после загрузки (не во время самой загрузки), только в видимой вкладке */
	function measure() {
		if (document.hidden || H.classList.contains(K)) { return; }
		var nv = navigator; if ((nv.hardwareConcurrency || 0) >= 6 && (!nv.deviceMemory || nv.deviceMemory >= 6)) { return; } /* мощное устройство: не включать lite по кадрам */
		var arr = [];
		var n = 0, t0 = 0, sum = 0, prev = 0;
		function f(t) {
			if (document.hidden) { return; }
			if (!t0) { t0 = prev = t; } else { sum += t - prev; arr.push(t - prev); n++; prev = t; }
			if (t - t0 < 3000) { requestAnimationFrame(f); return; }
			arr.sort(function (x, y) { return x - y; }); if (n > 20 && arr[n >> 1] > 34) { /* медиана кадра > 34 мс */
				set(true, 'slow-frames');
				try { ls && ls.setItem(K + '-slow', String(Date.now() + 864e5)); } catch (e) {}
			}
		}
		requestAnimationFrame(f);
	}
	if (document.readyState === 'complete') { setTimeout(measure, 600); }
	else { window.addEventListener('load', function () { setTimeout(measure, 600); }); }
})();

/* PERF: пауза бесконечных CSS-анимаций Заглавной вне экрана (звёзды, искры) и в скрытой вкладке */
(function () {
	if (window.lotmOffscreen) { return; }
	window.lotmOffscreen = true;
	document.addEventListener('visibilitychange', function () { document.documentElement.classList.toggle('lotm-tab-hidden', document.hidden); });
	if (!('IntersectionObserver' in window)) { return; }
	var io = new IntersectionObserver(function (l) {
		l.forEach(function (e) { e.target.classList.toggle('lotm-offscreen', !e.isIntersecting); });
	}, { rootMargin: '100px 0px' });
	mw.hook('wikipage.content').add(function ($c) {
		var root = ($c && $c[0]) || document, seen = [];
		[].forEach.call(root.querySelectorAll('.lotm-stars, .lotm-spark'), function (el) {
			var p = el.parentElement;
			if (p && seen.indexOf(p) < 0 && !p.dataset.lotmOff) { seen.push(p); p.dataset.lotmOff = '1'; io.observe(p); }
		});
	});
})();


/* PERF: подписи цитат с названием книги капителью (span style=small-caps) — класс .lotm-q-sc вместо CSS :has([style*=…]),
   который заставлял пересчитывать стили всей страницы на каждое изменение inline-стиля */
mw.hook('wikipage.content').add(function ($c) {
	var root = ($c && $c[0]) || document;
	if (!root.querySelectorAll) { return; }
	[].forEach.call(root.querySelectorAll('blockquote.pull-quote .pull-quote__source > div, .blockquote.blockwrapper > div:last-child cite'), function (el) {
		if (el.querySelector('[style*="small-caps"]')) { el.classList.add('lotm-q-sc'); }
	});
});



/*BackToTopButton*/
window.BackToTopModern = true;

// Custom Tooltip CSS removal
window.tooltips_config = {
	offsetX: 20,
    offsetY: 20,
    waitForImages: true,
    noCSS: true,
},
window.tooltips_list = [
    {
        classname: 'sequence-tooltip',
        parse: '{' + '{Template:Seq/data|1=<#seq#>}}'
    }, {
        classname: 'pathway-tooltip',
        parse: '{' + '{Template:Pathways/data|1=<#pathway#>}}'
    }, {
        classname: 'ingredient-tooltip',
        parse: '{' + '{Template:Ingr/data|1=<#name#>}}'
    }, {
        classname: 'uniqueness-tooltip',
        parse: '{' + '{Template:Uniqueness/data|1=<#name#>|2=<#description#>}}'
    }
],

/*LockOldComments*/
window.lockOldComments = (window.lockOldComments || {});
window.lockOldComments.limit = 120;

/*Custom Preload*/
window.preloadTemplates_list = "MediaWiki:Custom-PreloadTemplatesList";
window.preloadTemplates_subpage = "case-by-case";
window.preloadTemplates_namespace = "Template";
/* PERF: подсказки нужны только при наведении — грузим в простое после загрузки страницы */
(function () {
	function later() {
		mw.loader.load('/index.php?title=MediaWiki:Dev-Tooltips.js&action=raw&ctype=text/javascript');
		mw.loader.load('/index.php?title=MediaWiki:Dev-ReferencePopups-code.js&action=raw&ctype=text/javascript');
	}
	function idle() { if (window.requestIdleCallback) { requestIdleCallback(later, { timeout: 2500 }); } else { setTimeout(later, 300); } }
	if (document.readyState === 'complete') { idle(); } else { window.addEventListener('load', idle); }
})();
mw.loader.load('/index.php?title=MediaWiki:Dev-Accordion.js&action=raw&ctype=text/javascript');
mw.loader.load('/index.php?title=MediaWiki:CurrencyConversion.js&action=raw&ctype=text/javascript');

/* спойлеры: серый туман v2 — независимые кнопки, облака тумана, доступность */
( function () {
	var SH = 'mw-customcollapsible-ShowSpoiler', SHB = 'mw-customcollapsible-ShowSpoilerBox';
	var seq = window.lotmSpoilerSeq || 0;

	function hasCls( el, c ) { return el.classList && el.classList.contains( c ); }
	function isToggler( el ) { return hasCls( el, 'mw-customtoggle-ShowSpoiler' ) || hasCls( el, 'mw-customtoggle-ShowSpoilerBox' ); }

	// Выдать группе элементов свой id и перепривязать makeCollapsible (кнопка управляет только своей группой)
	function assign( els, base ) {
		if ( !els.length ) { return; }
		var gid = base + '-' + ( ++seq ), $els = $( els ), made = [];
		window.lotmSpoilerSeq = seq;
		els.forEach( function ( el ) {
			if ( el.id === 'mw-customcollapsible-' + base ) { el.id = 'mw-customcollapsible-' + gid; }
			if ( hasCls( el, 'mw-customtoggle-' + base ) ) { el.classList.add( 'mw-customtoggle-' + gid ); el.classList.remove( 'mw-customtoggle-' + base ); }
			if ( $( el ).data( 'mw-made-collapsible' ) ) { made.push( el ); }
		} );
		els.forEach( function ( el ) { el.classList.add( 'lotm-spoiler-own' ); } );
		if ( made.length ) {
			// makeCollapsible уже успел привязать общий id: снять и привязать заново с новым
			$els.off( '.mw-collapsible' );
			$( made ).removeData( 'mw-made-collapsible' ).removeClass( 'mw-made-collapsible mw-customtoggle' );
			mw.loader.using( 'jquery.makeCollapsible' ).then( function () { $( made ).makeCollapsible(); } );
		}
	}

	// Облака тумана: тело с текстом + слой облаков поверх (3 слоя; на телефоне CSS прячет третий)
	var reduceMotion = !!( window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches );
	// дрейф облаков — только у блоков в зоне видимости (класс .lotm-fog-live)
	var liveIO = ( !reduceMotion && 'IntersectionObserver' in window ) ? new IntersectionObserver( function ( list ) {
		var lite = document.documentElement.classList.contains( 'lotm-lite' );
		list.forEach( function ( e ) { e.target.classList.toggle( 'lotm-fog-live', e.isIntersecting && !lite ); } );
	}, { rootMargin: '120px 0px' } ) : null;
	if ( !window.lotmFogVis ) {
		window.lotmFogVis = true;
		document.addEventListener( 'visibilitychange', function () {
			document.documentElement.classList.toggle( 'lotm-fog-paused', document.hidden );
		} );
	}
	function fogify( el ) {
		if ( el.classList.contains( 'lotm-fog-ready' ) ) { return; }
		var body = document.createElement( 'div' ), sky = document.createElement( 'div' ), i, c;
		body.className = 'lotm-fog-body';
		while ( el.firstChild ) { body.appendChild( el.firstChild ); }
		sky.className = 'lotm-fog-sky';
		sky.setAttribute( 'aria-hidden', 'true' );
		for ( i = 1; i <= 3; i++ ) {
			c = document.createElement( 'i' );
			c.className = 'lotm-cloud lotm-cloud-' + i;
			sky.appendChild( c );
		}
		el.appendChild( body );
		el.appendChild( sky );
		el.classList.add( 'lotm-fog-ready' );
		if ( liveIO ) { liveIO.observe( el ); } else if ( !reduceMotion ) { el.classList.add( 'lotm-fog-live' ); }
	}

	// Скрытый под туманом текст не читается скринридерами и не фокусируется;
	// у блоков (animateHeight) высота плавно меняется при раскрытии/сворачивании: под туманом виден только «срез» текста
	function a11y( el, animateHeight ) {
		var reduce = reduceMotion;
		var last = el.classList.contains( 'mw-collapsed' );
		var goneTimer = 0, animTimer = 0;
		var sync = function () {
			var collapsed = el.classList.contains( 'mw-collapsed' );
			// раскрытый блок: после разлёта облаков слои тумана выключаются (.lotm-fog-gone), при «Скрыть» — сразу обратно
			if ( collapsed !== last ) {
				clearTimeout( goneTimer ); clearTimeout( animTimer );
				el.classList.add( 'lotm-fog-anim' );
				animTimer = setTimeout( function () { el.classList.remove( 'lotm-fog-anim' ); }, 1800 );
				if ( collapsed ) { el.classList.remove( 'lotm-fog-gone' ); }
				else { goneTimer = setTimeout( function () { if ( !el.classList.contains( 'mw-collapsed' ) ) { el.classList.add( 'lotm-fog-gone' ); } }, 1800 ); }
			}
			if ( collapsed ) { el.setAttribute( 'aria-hidden', 'true' ); el.setAttribute( 'inert', '' ); }
			else { el.removeAttribute( 'aria-hidden' ); el.removeAttribute( 'inert' ); }
			if ( animateHeight && !reduce && collapsed !== last && el.firstElementChild && el.firstElementChild.animate ) {
				var body = el.firstElementChild, cap = parseFloat( getComputedStyle( body ).fontSize ) * 14,
					full = collapsed ? body.scrollHeight : body.offsetHeight,
					from = collapsed ? full : Math.min( full, cap ), to = collapsed ? Math.min( full, cap ) : full;
				if ( full && from !== to ) {
					body.style.overflow = 'clip';
					body.animate( [ { height: from + 'px', maxHeight: from + 'px' }, { height: to + 'px', maxHeight: to + 'px' } ], { duration: 1150, easing: 'cubic-bezier(.3,.1,.2,1)' } )
						.finished.then( function () { body.style.overflow = ''; }, function () { body.style.overflow = ''; } );
				}
			}
			last = collapsed;
		};
		sync();
		if ( !last ) { el.classList.add( 'lotm-fog-gone' ); }
		new MutationObserver( sync ).observe( el, { attributes: true, attributeFilter: [ 'class' ] } );
	}

	function init( root ) {
		var all = [].slice.call( root.querySelectorAll( '[id="' + SH + '"]' ) ), done = [];
		var take = function ( list ) { list.forEach( function ( e ) { done.push( e ); } ); return list; };
		var free = function ( e ) { return done.indexOf( e ) < 0; };

		// 1. Блоки «Показать спойлеры» (Template:SpoilerToggle/context): одна кнопка = один блок
		[].forEach.call( root.querySelectorAll( '.spoiler_block' ), function ( blk ) {
			var els = all.filter( function ( e ) { return free( e ) && e.closest( '.spoiler_block' ) === blk; } );
			if ( !els.length ) { return; }
			assign( take( els ), 'ShowSpoiler' );
			var content = [].filter.call( blk.children, function ( c ) { return c.classList.contains( 'mw-collapsible' ) && !isToggler( c ); } )[ 0 ];
			if ( content ) { fogify( content ); a11y( content, true ); }
		} );

		// 2. Локальные пары «[Нажмите переключатель]» + содержимое в одном родителе (таблицы, поля инфобокса)
		all.filter( function ( e ) { return free( e ) && isToggler( e ); } ).forEach( function ( t ) {
			if ( !free( t ) ) { return; }
			var sibs = [].filter.call( t.parentElement.children, function ( c ) { return c.id === SH; } );
			if ( sibs.some( function ( c ) { return !isToggler( c ); } ) ) { assign( take( sibs ), 'ShowSpoiler' ); }
		} );

		// 3. Переключатель инфобокса («выкл/вкл»): управляет скрытыми элементами только своего инфобокса
		var masters = [], seen = [];
		all.filter( function ( e ) { return free( e ) && isToggler( e ); } ).forEach( function ( t ) {
			var p = t.parentElement;
			if ( seen.indexOf( p ) < 0 ) { seen.push( p ); masters.push( { scope: p.closest( '.portable-infobox' ), els: [] } ); }
			masters[ seen.indexOf( p ) ].els.push( t );
		} );
		all.filter( free ).forEach( function ( e ) {
			if ( isToggler( e ) ) { return; }
			var box = e.closest( '.portable-infobox' ), m = null;
			masters.forEach( function ( x ) { if ( !m && x.scope && x.scope === box ) { m = x; } } );
			// вне инфобокса (например, «†» в тексте) подчиняется первому переключателю страницы
			if ( !m ) { m = masters[ 0 ]; }
			if ( m ) { m.els.push( e ); }
		} );
		masters.forEach( function ( m ) { assign( m.els, 'ShowSpoiler' ); } );

		// 4. Баннеры Template:HideSpoiler: каждый баннер открывает только свой блок
		[].forEach.call( root.querySelectorAll( '.SpoilerBox-A' ), function ( a ) {
			var b = a.nextElementSibling, t = a.querySelector( '.mw-customtoggle-ShowSpoilerBox' );
			if ( !b || !t || b.id !== SHB ) { return; }
			assign( [ t, b ], 'ShowSpoilerBox' );
			fogify( b );
			a11y( b );
		} );
	}

	mw.hook( 'wikipage.content' ).add( function ( $content ) {
		$content.each( function () { if ( this.querySelectorAll ) { init( this ); } } );
	} );
}() );

/* ===== анимации A1–A3 (v2) ===== */
/* A1: фоновый туман убран по решению владельца (2026-11-01) */

/* A2: наклон карточек Таро за курсором (только устройства с hover) */
mw.hook('wikipage.content').add(function ($c) {
	var root = ($c && $c[0]) || document;
	var cards = root.querySelectorAll ? root.querySelectorAll('.lotm-tarot') : [];
	if (!cards.length || window.matchMedia('(prefers-reduced-motion: reduce)').matches) { return; }
	var hover = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
	Array.prototype.forEach.call(cards, function (el) {
		if (el.getAttribute('data-lotm-tarot')) { return; }
		el.setAttribute('data-lotm-tarot', '1');
		if (hover) {
			var rc = null, mx = 0, my = 0, fr = 0;
			var apply = function () {
				fr = 0;
				if (!rc) { rc = el.getBoundingClientRect(); }
				var x = (mx - rc.left) / rc.width - 0.5, y = (my - rc.top) / rc.height - 0.5;
				if (!el.classList.contains('lotm-tilting')) { el.classList.add('lotm-tilting'); }
				el.style.setProperty('--lotm-ry', (x * 16).toFixed(2) + 'deg');
				el.style.setProperty('--lotm-rx', (-y * 16).toFixed(2) + 'deg');
			};
			el.addEventListener('mouseenter', function () { rc = null; }, { passive: true });
			el.addEventListener('mousemove', function (e) {
				mx = e.clientX; my = e.clientY;
				if (!fr) { fr = requestAnimationFrame(apply); }
			}, { passive: true });
			el.addEventListener('mouseleave', function () {
				if (fr) { cancelAnimationFrame(fr); fr = 0; } rc = null;
				el.classList.remove('lotm-tilting');
				el.style.removeProperty('--lotm-ry');
				el.style.removeProperty('--lotm-rx');
			});
		} else {
			var t;
			el.addEventListener('touchstart', function () { clearTimeout(t); el.classList.add('lotm-press'); }, { passive: true });
			var end = function () { t = setTimeout(function () { el.classList.remove('lotm-press'); }, 500); };
			el.addEventListener('touchend', end, { passive: true });
			el.addEventListener('touchcancel', end, { passive: true });
		}
	});
});

/* ===== спойлеры: spoilerfix ===== */
/* spoilerfix.js — общий переключатель спойлеров + «сироты» (wave30/spoilerfix).
 * Не зависит от вида тумана: работает с классами/атрибутами шаблонов
 * (.spoilerblur, .spoiler_block, .SpoilerBox-B, [id^=mw-customcollapsible-ShowSpoiler], .spoiler[class*=mw-customtoggle-]).
 * Идемпотентен: можно подключать несколько раз / после Common.js. */
( function () {
	'use strict';
	if ( window.lotmSpoilerFix ) { return; }
	window.lotmSpoilerFix = true;

	var HINT = 'Нажмите, чтобы открыть спойлер';
	var KEY = 'lotm-spoilers-open:' + ( ( window.mw && mw.config && mw.config.get( 'wgPageName' ) ) || location.pathname );
	var busy = false;

	function store( v ) { try { if ( v === null ) { sessionStorage.removeItem( KEY ); } else { sessionStorage.setItem( KEY, v ? '1' : '0' ); } } catch ( e ) {} }
	function stored() { try { return sessionStorage.getItem( KEY ); } catch ( e ) { return null; } }
	function has( el, c ) { return !!( el && el.classList && el.classList.contains( c ) ); }
	function isToggler( el ) { return !!el.className && /(^|\s)mw-customtoggle(-|\s|$)/.test( el.className ) || /(^|\s)mw-customtoggle-ShowSpoiler/.test( el.className ); }

	/* ---------- реестр целей ---------- */
	// kind: 'blur' (inline .spoilerblur) | 'block' (mw-collapsible-блок: spoiler_block, SpoilerBox-B, ShowSpoiler-ячейки)
	function blocks() {
		var out = [], seen = [], ms = masters();
		function add( el, kind ) { if ( el && seen.indexOf( el ) < 0 ) { seen.push( el ); out.push( { el: el, kind: kind } ); } }
		[].forEach.call( document.querySelectorAll( '.spoilerblur' ), function ( e ) { add( e, 'blur' ); } );
		[].forEach.call( document.querySelectorAll( '.spoiler_block > .mw-collapsible, .SpoilerBox-B, [id^="mw-customcollapsible-ShowSpoiler"]' ), function ( e ) {
			if ( ms.indexOf( e ) >= 0 || has( e, 'spoiler_block-headerT' ) || /(^|\s)mw-customtoggle-/.test( e.className ) && !/mw-collapsible/.test( e.className ) ) { return; }
			if ( e.closest( '.spoiler_block-header' ) ) { return; }
			add( e, 'block' );
		} );
		return out;
	}
	function isClosed( t ) { return t.kind === 'blur' ? !has( t.el, 'lotm-revealed' ) : has( t.el, 'mw-collapsed' ); }

	// «своя» кнопка блока: видимый переключатель, который управляет этим блоком
	function ownToggle( el ) {
		var m = ( el.id || '' ).match( /^mw-customcollapsible-(.+)$/ ), list, vis;
		if ( !m ) { return null; }
		list = [].slice.call( document.querySelectorAll( '.mw-customtoggle-' + m[ 1 ] ) );
		var ms = masters();
		list = list.filter( function ( x ) { return ms.indexOf( x ) < 0 && x !== el && !el.contains( x ) && !has( x, 'basic-tooltip' ); } );
		vis = list.filter( function ( x ) { return !has( x, 'mw-collapsed' ); } )[ 0 ];
		return list.length > 2 ? null : ( vis || list[ 0 ] || null );
	}

	function setBlock( t, open ) {
		if ( open === !isClosed( t ) ) { return; }
		if ( t.kind === 'blur' ) {
			t.el.classList.toggle( 'lotm-revealed', open );
			t.el.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
			t.el.setAttribute( 'aria-pressed', open ? 'true' : 'false' );
			return;
		}
		var tg = ownToggle( t.el );
		if ( tg ) { tg.click(); }
		if ( open === has( t.el, 'mw-collapsed' ) ) { // кнопки нет / не сработала: переключаем напрямую
			t.el.classList.toggle( 'mw-collapsed', !open );
			t.el.classList.toggle( 'mw-made-collapsible', true );
		}
	}

	/* ---------- общий переключатель ---------- */
	function masters() {
		var by = [], out = [];
		[].forEach.call( document.querySelectorAll( '.spoiler[class*="mw-customtoggle-"]' ), function ( e ) {
			if ( has( e, 'spoiler_block-headerT' ) || has( e, 'basic-tooltip' ) || !e.parentElement ) { return; }
			var i = by.indexOf( e.parentElement ); if ( i < 0 ) { by.push( e.parentElement ); out.push( [] ); i = by.length - 1; }
			out[ i ].push( e );
		} );
		return out.filter( function ( g ) { return g.length === 2; } ).reduce( function ( a, g ) { return a.concat( g ); }, [] );
	}
	// пара «выкл»/«вкл»: первая в DOM = «выкл» (нажатие открывает спойлеры)
	function openFromMaster( el ) {
		var sibs = [].filter.call( el.parentElement ? el.parentElement.children : [], function ( c ) { return masters().indexOf( c ) >= 0; } );
		return sibs.length > 1 ? sibs.indexOf( el ) === 0 : true;
	}
	function syncMasters( open ) {
		var ms = masters(), seen = [];
		ms.forEach( function ( m ) {
			var p = m.parentElement; if ( seen.indexOf( p ) >= 0 ) { return; } seen.push( p );
			var pair = [].filter.call( p.children, function ( c ) { return ms.indexOf( c ) >= 0; } );
			if ( pair.length < 2 ) { return; }
			var off = pair[ 0 ], on = pair[ 1 ];
			// open: виден «вкл» (кнопка «выкл» свёрнута); closed: наоборот
			if ( open && !has( off, 'mw-collapsed' ) ) { busy = true; try { off.click(); } finally { busy = false; } }
			else if ( !open && has( off, 'mw-collapsed' ) ) { busy = true; try { on.click(); } finally { busy = false; } }
		} );
	}
	function setAll( open, fromMaster ) {
		store( open );
		if ( !fromMaster ) { syncMasters( open ); } // сначала общий переключатель (он нативно листает блоки с общим id), потом добиваем остальное
		blocks().forEach( function ( t ) { setBlock( t, open ); } );
		refresh();
	}
	function bindMasters() {
		masters().forEach( function ( m ) {
			if ( m.dataset.lotmSfMaster ) { return; }
			m.dataset.lotmSfMaster = '1';
			var h = function ( e ) {
				if ( busy ) { return; }
				if ( e.type === 'keydown' && e.which !== 13 && e.which !== 32 ) { return; }
				var open = openFromMaster( m );
				setTimeout( function () { setAll( open, true ); }, 0 );
				setTimeout( function () { setAll( open, true ); }, 60 ); // страховка от гонок с другими обработчиками
			};
			m.addEventListener( 'click', h );
			m.addEventListener( 'keydown', h );
		} );
	}

	/* ---------- inline .spoilerblur: тап / Enter / пробел ---------- */
	function bindBlur() {
		[].forEach.call( document.querySelectorAll( '.spoilerblur' ), function ( sb ) {
			sb.setAttribute( 'tabindex', '0' );
			sb.setAttribute( 'role', 'button' );
			if ( !sb.hasAttribute( 'aria-expanded' ) ) { sb.setAttribute( 'aria-expanded', has( sb, 'lotm-revealed' ) ? 'true' : 'false' ); }
			if ( !has( sb, 'lotm-revealed' ) ) { sb.setAttribute( 'aria-label', HINT ); sb.title = HINT; } else { sb.removeAttribute( 'aria-label' ); sb.removeAttribute( 'title' ); }
			if ( sb.dataset.lotmBlur || sb.dataset.lotmSf ) { return; }
			sb.dataset.lotmSf = '1'; sb.dataset.lotmBlur = '1'; // второй обработчик Common.js не навешиваем дважды
			var flip = function ( e ) {
				if ( e.type === 'keydown' && e.which !== 13 && e.which !== 32 ) { return; }
				if ( e.type === 'click' && has( sb, 'lotm-revealed' ) && e.target.closest && e.target.closest( 'a' ) ) { return; }
				if ( e.type === 'click' && String( window.getSelection() ) && has( sb, 'lotm-revealed' ) ) { return; } // выделение открытого текста не сворачивает
				e.preventDefault();
				setBlock( { el: sb, kind: 'blur' }, !has( sb, 'lotm-revealed' ) );
				refresh();
			};
			sb.addEventListener( 'click', flip );
			sb.addEventListener( 'keydown', flip );
		} );
	}

	/* ---------- блоки: тап по самому блоку (устойчиво к inert/pointer-events у тумана) ---------- */
	function onDocClick( e ) {
		if ( e.defaultPrevented || e.button > 0 ) { return; }
		var x = e.clientX, y = e.clientY, hit = null;
		if ( e.target.closest && e.target.closest( 'a[href], button, input, textarea, select, [class*="mw-customtoggle-"]' ) ) { return; }
		blocks().forEach( function ( t ) {
			if ( hit || t.kind !== 'block' || !isClosed( t ) ) { return; }
			var r = t.el.getBoundingClientRect();
			if ( r.width && r.height && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom ) { hit = t; }
		} );
		if ( hit ) { e.preventDefault(); e.stopPropagation(); setBlock( hit, true ); refresh(); }
	}
	// клавиатура: фокус на самом блоке, если он не inert
	function bindBlockA11y() {
		cached = null; blocks().forEach( function ( t ) {
			var el = t.el, closed = isClosed( t ), tg;
			if ( t.kind === 'blur' ) { return; }
			tg = ownToggle( el );
			if ( tg ) { tg.setAttribute( 'aria-expanded', closed ? 'false' : 'true' ); if ( !tg.hasAttribute( 'tabindex' ) ) { tg.setAttribute( 'tabindex', '0' ); } if ( !tg.hasAttribute( 'role' ) ) { tg.setAttribute( 'role', 'button' ); } }
			if ( el.dataset.lotmSfKey ) { return; }
			el.dataset.lotmSfKey = '1';
			el.addEventListener( 'keydown', function ( e ) {
				if ( e.target !== el || ( e.which !== 13 && e.which !== 32 ) || !has( el, 'mw-collapsed' ) ) { return; }
				e.preventDefault(); setBlock( t, true ); refresh();
			} );
		} );
		var bl = blocks(), cur = bl.map( function ( t ) { return t.el; } );
		[].forEach.call( document.querySelectorAll( '.lotm-sf-closed' ), function ( el ) { if ( cur.indexOf( el ) < 0 ) { el.classList.remove( 'lotm-sf-closed' ); } } );
		bl.forEach( function ( t ) {
			if ( t.kind !== 'block' ) { return; }
			var closed = isClosed( t );
			if ( closed !== has( t.el, 'lotm-sf-closed' ) ) { t.el.classList.toggle( 'lotm-sf-closed', closed ); }
			if ( closed && !t.el.hasAttribute( 'inert' ) ) { t.el.setAttribute( 'tabindex', '0' ); t.el.setAttribute( 'role', 'button' ); t.el.setAttribute( 'aria-label', HINT ); t.el.setAttribute( 'aria-expanded', 'false' ); }
			else if ( !closed && t.el.dataset.lotmSfKey ) { t.el.removeAttribute( 'tabindex' ); if ( t.el.getAttribute( 'role' ) === 'button' ) { t.el.removeAttribute( 'role' ); } t.el.removeAttribute( 'aria-label' ); t.el.removeAttribute( 'aria-expanded' ); }
		} );
	}

	/* ---------- подсказка «Нажмите, чтобы открыть спойлер» поверх закрытых целей ---------- */
	var layer, pills = [], raf = 0, cached = null;
	function ensureLayer() {
		if ( layer && layer.isConnected ) { return; }
		layer = document.createElement( 'div' );
		layer.className = 'lotm-sf-layer'; layer.setAttribute( 'aria-hidden', 'true' );
		document.body.appendChild( layer );
	}
	function place() {
		raf = 0; ensureLayer();
		if ( !cached ) { cached = blocks().filter( isClosed ); }
		var ts = cached, vh = window.innerHeight, vw = document.documentElement.clientWidth, pos = [];
		while ( pills.length < ts.length ) { var p = document.createElement( 'span' ); p.className = 'lotm-sf-pill'; p.textContent = HINT; layer.appendChild( p ); pills.push( p ); }
		// 1) только чтения
		pills.forEach( function ( p, i ) {
			var t = ts[ i ], r, top, bottom;
			if ( !t || !t.el.isConnected ) { pos[ i ] = null; return; }
			r = t.el.getBoundingClientRect(); // для многострочного inline — общий прямоугольник
			top = Math.max( r.top, 0 ); bottom = Math.min( r.bottom, vh );
			if ( !r.width || bottom - top < 14 || r.right < 0 || r.left > vw ) { pos[ i ] = null; return; }
			pos[ i ] = [ Math.max( 0, r.left ) + ( Math.min( r.right, vw ) - Math.max( 0, r.left ) ) / 2, top + ( bottom - top ) / 2 ];
		} );
		// 2) только записи (и только изменившиеся значения)
		pills.forEach( function ( p, i ) {
			var q = pos[ i ], s = p.style;
			if ( !q ) { if ( s.display !== 'none' ) { s.display = 'none'; } return; }
			var l = q[ 0 ] + 'px', tp = q[ 1 ] + 'px';
			if ( s.display ) { s.display = ''; }
			if ( s.left !== l ) { s.left = l; }
			if ( s.top !== tp ) { s.top = tp; }
		} );
	}
	function refresh() { bindBlur(); bindBlockA11y(); cached = null; if ( !raf ) { raf = requestAnimationFrame( place ); } }
	// прокрутка/ресайз: только перестановка подсказок (без пересборки реестра и перепривязки), не чаще кадра
	function reposition() { if ( !raf ) { raf = requestAnimationFrame( place ); } }

	/* ---------- запуск ---------- */
	function init() {
		bindMasters(); bindBlur();
		var s = stored();
		if ( s === '1' ) { setAll( true, false ); } else { refresh(); }
	}
	if ( !document.body.dataset.lotmSfDoc ) {
		document.body.dataset.lotmSfDoc = '1';
		document.addEventListener( 'click', onDocClick, true );
		window.addEventListener( 'scroll', reposition, { passive: true } );
		window.addEventListener( 'resize', reposition, { passive: true } );
		window.addEventListener( 'hashchange', function () { var s = stored(); if ( s === '1' ) { setAll( true, false ); } } );
		window.addEventListener( 'pageshow', function () { setTimeout( init, 50 ); } );
			var OWN = /^(lotm-(sf-closed|fog-live|fog-anim|fog-gone|tilting|press|offscreen))$/, mo = 0;
			var significant = function ( r ) {
				var t = r.target;
				if ( t === layer || ( t.classList && t.classList.contains( 'lotm-sf-pill' ) ) ) { return false; }
				var a = ( r.oldValue || '' ).split( /\s+/ ), b = ( t.getAttribute( 'class' ) || '' ).split( /\s+/ ), i;
				for ( i = 0; i < a.length; i++ ) { if ( a[ i ] && b.indexOf( a[ i ] ) < 0 && !OWN.test( a[ i ] ) ) { return true; } }
				for ( i = 0; i < b.length; i++ ) { if ( b[ i ] && a.indexOf( b[ i ] ) < 0 && !OWN.test( b[ i ] ) ) { return true; } }
				return false;
			};
			new MutationObserver( function ( recs ) {
				if ( mo || !recs.some( significant ) ) { return; }
				mo = requestAnimationFrame( function () { mo = 0; bindMasters(); refresh(); } );
			} ).observe( document.body, { attributes: true, attributeFilter: [ 'class' ], attributeOldValue: true, subtree: true } );
	}
	if ( window.mw && mw.hook ) { mw.hook( 'wikipage.content' ).add( function () { setTimeout( init, 400 ); } ); }
	else { setTimeout( init, 400 ); }
}() );


/* ===== примечания ===== */
/* refs.js — раздел «Примечания»: скрыть пустые, убрать дубли заголовков, почистить оглавление */
(function(){
  function run(){
    var secs=[].slice.call(document.querySelectorAll('.mw-parser-output section.citizen-section, #mw-content-text section.citizen-section'));
    var seen=false;
    secs.forEach(function(s){
      var h=s.querySelector(':scope > h2, :scope > .citizen-section-heading');
      if(!h)return;
      var t=h.textContent.replace(/\s+/g,' ').trim();
      if(!/^Примечания$/i.test(t))return;
      var body=s.querySelector(':scope > .citizen-section-body')||s;
      var has=!!s.querySelector('ol.references')||body.textContent.replace(/\s+/g,'').length>0&&!body.querySelector('.lotm-refs')||!!body.querySelector('img,table,ul,ol,pre');
      var drop=!has||seen;
      if(has&&!seen)seen=true;
      if(drop){
        s.classList.add('lotm-refs-hidden');
        var id=(h.querySelector('.mw-headline')||{}).id;
        if(id){
          [].slice.call(document.querySelectorAll('a[href="#'+id+'"], a[href="#'+encodeURIComponent(id)+'"]')).forEach(function(a){
            var li=a.closest('li'); if(li&&!li.querySelector('ul li a:not([href="#'+id+'"])'))li.classList.add('lotm-refs-hidden');
          });
        }
      }
    });
  }
  function go(){ if(window.requestIdleCallback)requestIdleCallback(run,{timeout:1500});else setTimeout(run,0); }
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',go);else go();
})();



/* ===== BGT v3 — нити марионеток (духовное тело Марионеточника, Путь Шута), быстрая отрисовка =====
   Вид — как v2: в каждом поле вокруг панели статьи 1–2 невидимые «руки кукловода» высоко над экраном, от них
   веером провисающие серебристые нити с грузиком/крючком, пылинки, изредка искра по нити. Всё — функции реального
   времени (общий старт и зерно — в sessionStorage): после перехода на другую статью всё продолжается с той же фазы.
   v3 (скорость): вместо canvas на весь экран — два узких canvas только на поля (DPR ≤ 1.5, contain:strict,
   свой композитный слой); поле уже 40 px не рисуется; градиенты нитей и спрайт свечения — заранее, без shadowBlur.
   Рисует Worker через OffscreenCanvas (transferControlToOffscreen): прокрутка и тяжёлый главный поток нити
   не стопорят — ровные 30 кадров/с и при листании (после 15 с без действий — 15). Нет Worker/OffscreenCanvas
   или blob: запрещён — то же ядро в главном потоке. Скрытая вкладка — пауза; lite-режим — нитей нет;
   prefers-reduced-motion — один статичный кадр. Нет на телефоне (< 721px) и в светлой теме (CSS). */
(function () {
	if (window.lotmBgt) { return; }
	var mqDesk = window.matchMedia('(min-width: 721px)'), mqRM = window.matchMedia('(prefers-reduced-motion: reduce)');

	var START = Date.now(), SEED = (Math.random() * 4294967296) >>> 0, FRESH = true;
	try {
		var st = JSON.parse(sessionStorage.getItem('lotmBgt') || 'null');
		if (st && st.s > 0 && st.s <= START && START - st.s < 864e5) { START = st.s; SEED = st.k >>> 0; FRESH = false; }
		else { sessionStorage.setItem('lotmBgt', JSON.stringify({ s: START, k: SEED })); }
	} catch (e) {}

	/* Ядро: не обращается ни к чему снаружи (исходник уходит в Worker через toString). G: seed, start, cvs[2], raf, caf, stat */
	function core(G) {
		var SEED = G.seed, START = G.start, N = 34, DPR = 1, W = 0, H = 0, sides = [], threads = [], motes = [], glow;
		var T0 = performance.timeOrigin || (Date.now() - performance.now());
		var on = false, hid = false, raf = 0, prevT = 0, prevTs = 0, lastDraw = 0, ema = 16, lastInput = performance.now(), FRAME = 1000 / 30 - 2;
		var still = false, stA = [], stN = 0, stT = 0, mouse = { x: -1e4, y: -1e4, in: false };
		var SILVER = [208, 224, 255], HALO_S = [130, 170, 255];
		function now() { return (T0 + performance.now() - START) / 1000; }
		function rand(a, b) { /* mulberry32 от (зерно, a, b) */
			var t = (SEED ^ Math.imul(a + 1, 0x9E3779B1) ^ Math.imul(b + 7, 0x85EBCA77)) >>> 0;
			return function () { t = (t + 0x6D2B79F5) >>> 0; var r = Math.imul(t ^ (t >>> 15), 1 | t); r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r; return ((r ^ (r >>> 14)) >>> 0) / 4294967296; };
		}
		function rgba(c, a) { return 'rgba(' + c[0] + ',' + c[1] + ',' + c[2] + ',' + Math.max(0, a).toFixed(3) + ')'; }
		function sprite() {
			var s = 64, c = typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(s, s) : document.createElement('canvas'), x, g;
			c.width = c.height = s; x = c.getContext('2d'); g = x.createRadialGradient(s / 2, s / 2, 0, s / 2, s / 2, s / 2);
			g.addColorStop(0, 'rgba(255,255,255,1)'); g.addColorStop(0.12, 'rgba(225,238,255,.85)');
			g.addColorStop(0.35, 'rgba(160,195,255,.22)'); g.addColorStop(1, 'rgba(120,160,255,0)');
			x.fillStyle = g; x.fillRect(0, 0, s, s);
			return c;
		}
		glow = sprite();

		function build() {
			threads = []; motes = [];
			sides.forEach(function (sd, si) {
				if (!sd.on) { sd.n = 0; return; }
				var narrow = sd.w < 160;
				sd.n = narrow ? (sd.w < 60 ? 2 : 3) : Math.max(6, Math.min(9, Math.round(sd.w / 50)));
				var hr = rand(si * 13 + 5, 0), hands = [], nh = narrow ? 1 : 2, h, i;
				for (h = 0; h < nh; h++) {
					hands.push({ x: sd.x + sd.w * (nh === 1 ? 0.5 : (h ? 0.7 : 0.3)) + (hr() - 0.5) * sd.w * 0.12, y: -H * (0.13 + 0.1 * hr()) - 40, p: hr() * 6.283 });
				}
				for (i = 0; i < sd.n; i++) {
					var r = rand(si * 31 + 3, i), hand = hands[nh === 1 || i < sd.n / 2 ? 0 : 1];
					var z = narrow ? 0.7 + 0.3 * r() : [0.95, 0.35, 0.7, 0.2, 0.85, 0.5, 0.3, 0.75, 0.6][i % 9] * (0.85 + 0.3 * r());
					z = Math.min(1, z);
					var ex = sd.x + (narrow ? sd.w * (0.25 + 0.5 * (sd.n > 1 ? i / (sd.n - 1) : 0.5)) : 10 + (sd.w - 20) * ((i + 0.5 + (r() - 0.5) * 0.5) / sd.n));
					var ey = H * (r() < 0.4 ? 0.38 + 0.2 * r() : 0.62 + 0.4 * r());
					var len = Math.hypot(ex - hand.x, ey - hand.y);
					var w0 = 2 * Math.PI / (2 * Math.sqrt(len / 300) + 3) * (1.15 - 0.3 * (1 - z));
					threads.push({
						side: si, hand: hand, ex: ex, ey: ey, len: len, z: z,
						lw: 0.6 + 1.0 * z, a: (narrow ? 0.26 : 0.12) + 0.42 * z * z,
						w1: w0, w2: w0 * (0.37 + 0.1 * r()), w3: w0 * (2.3 + 0.6 * r()),
						p1: r() * 6.283, p2: r() * 6.283, p3: r() * 6.283,
						A1: len * (0.012 + 0.012 * r()),
						S0: (r() < 0.5 ? -1 : 1) * (0.025 + 0.035 * r()) * len, S1: (0.015 + 0.02 * r()) * len,
						A3: 2 + 3 * r(),
						end: r() < 0.45 ? 1 : r() < 0.6 ? 2 : 0,
						hookDir: r() < 0.5 ? -1 : 1,
						px: new Float32Array(N + 1), py: new Float32Array(N + 1),
						pull: 0, cs: 0.7, tx: 0, ty: 0, d: 1e9, near: false
					});
				}
				var nm = Math.round(sd.w / 45);
				for (var m = 0; m < nm; m++) {
					var q = rand(si * 97 + 11, m);
					motes.push({ side: si, fx: q(), y0: q(), v: 4 + 7 * q(), ax: 6 + 10 * q(), wx: 0.05 + 0.08 * q(), ph: q() * 6.283, tw: 0.3 + 0.5 * q(), s: 1.2 + 1.4 * q(), a: 0.05 + 0.1 * q() });
				}
			});
			/* градиенты по длине нити (абсолютные y): выход из темноты сверху, таяние к низу — один раз на геометрию */
			threads.forEach(function (th) {
				var x = sides[th.side].cx, c = SILVER, hc = HALO_S, a = th.a, y1 = th.ey;
				var f = function (y) { return Math.max(0, Math.min(1, y / y1)); };
				var g1 = x.createLinearGradient(0, 0, 0, y1);
				g1.addColorStop(0, rgba(c, 0)); g1.addColorStop(f(130), rgba(c, a * 0.8)); g1.addColorStop(f(Math.min(y1 * 0.45, 380)), rgba(c, a));
				g1.addColorStop(0.8, rgba(c, a * 0.45)); g1.addColorStop(1, rgba(c, a * 0.1));
				var g0 = x.createLinearGradient(0, 0, 0, y1);
				g0.addColorStop(0, rgba(hc, 0)); g0.addColorStop(f(170), rgba(hc, a * 0.2)); g0.addColorStop(0.55, rgba(hc, a * 0.22));
				g0.addColorStop(1, rgba(hc, 0));
				th.g0 = g0; th.g1 = g1; th.col = rgba(c, 1); th.colE = rgba(c, th.a * 0.55);
			});
		}

		function setGeom(g) {
			W = g.W; H = g.H; DPR = g.DPR;
			sides = g.s.map(function (s, i) {
				var sd = { x: s[0], w: s[1], on: s[1] >= 40 }, c = G.cvs[i], x;
				c.width = sd.on ? Math.max(1, Math.round(sd.w * DPR)) : 1; c.height = sd.on ? Math.max(1, Math.round(H * DPR)) : 1;
				x = c.getContext('2d'); x.setTransform(DPR, 0, 0, DPR, -sd.x * DPR, 0); x.lineCap = 'round'; x.lineJoin = 'round';
				sd.cx = x; return sd;
			});
			build();
		}

		function posThread(th, t, dt) {
			var hd = th.hand, hx = hd.x + 8 * Math.sin(0.041 * t + hd.p) + 3 * Math.sin(0.13 * t + hd.p * 2), hy = hd.y + 4 * Math.sin(0.07 * t + hd.p);
			var sw = Math.sin(th.w1 * t + th.p1), ex = th.ex + th.A1 * sw + 6 * Math.sin(0.23 * t - th.ex * 0.004), ey = th.ey - Math.abs(th.A1 * sw) * th.A1 / th.len * 0.5;
			var dx = ex - hx, dy = ey - hy, L = Math.hypot(dx, dy), nx = -dy / L, ny = dx / L;
			var sag = th.S0 + th.S1 * Math.sin(th.w2 * t + th.p2) - th.A1 * sw * 0.35;
			var s0 = Math.max(0, Math.min(0.95, (-14 - hy) / dy)), i, s, b, o;
			for (i = 0; i <= N; i++) {
				s = s0 + (1 - s0) * i / N;
				b = 4 * s * (1 - s);
				o = sag * b + th.A3 * Math.sin(9 * s - th.w3 * t + th.p3) * b * s;
				th.px[i] = hx + dx * s + nx * o; th.py[i] = hy + dy * s + ny * o;
			}
			var tgt = 0; th.d = 1e9;
			if (mouse.in && dt > 0) {
				var best = 1e9, bi = 0, ddx, ddy, d;
				for (i = 4; i <= N; i += 2) { ddx = th.px[i] - mouse.x; ddy = th.py[i] - mouse.y; d = ddx * ddx + ddy * ddy; if (d < best) { best = d; bi = i; } }
				th.d = best;
				if (th.near && best < 130 * 130) { tgt = 1 - best / (130 * 130); th.cs += (bi / N - th.cs) * Math.min(1, dt * 3); th.tx = mouse.x; th.ty = mouse.y; }
			}
			if (dt > 0) { th.pull += (tgt - th.pull) * (1 - Math.exp(-dt / (tgt > th.pull ? 0.45 : 1.1))); }
			if (th.pull > 0.002) {
				var ci = Math.max(1, Math.min(N, Math.round(th.cs * N))), ox = th.tx - th.px[ci], oy = th.ty - th.py[ci], m = Math.sqrt(ox * ox + oy * oy), cap = 70;
				if (m > cap) { ox *= cap / m; oy *= cap / m; }
				var k = th.pull * 0.55 * (0.6 + 0.4 * th.z);
				for (i = 1; i <= N; i++) {
					s = i / N; var u = (s - th.cs) / (s < th.cs ? Math.max(0.15, th.cs * 0.9) : 0.22), w = Math.exp(-u * u) * Math.pow(s, 0.7);
					th.px[i] += ox * k * w; th.py[i] += oy * k * w * 0.7;
				}
			}
		}
		function path(x, th) {
			var px = th.px, py = th.py, i;
			x.moveTo(px[0], py[0]);
			for (i = 1; i < N; i++) { x.quadraticCurveTo(px[i], py[i], (px[i] + px[i + 1]) / 2, (py[i] + py[i + 1]) / 2); }
			x.lineTo(px[N], py[N]);
		}
		var AT = [0, 0];
		function at(th, s) {
			var f = Math.max(0, Math.min(N, s * N)), i = Math.min(N - 1, Math.floor(f)), u = f - i;
			AT[0] = th.px[i] + (th.px[i + 1] - th.px[i]) * u; AT[1] = th.py[i] + (th.py[i + 1] - th.py[i]) * u; return AT;
		}
		function dot(x, px, py, r, a) { x.globalAlpha = a; x.drawImage(glow, px - r, py - r, r * 2, r * 2); }
		var SP = 7;
		function sparks(t) {
			for (var n = Math.floor(t / SP) - 1; n <= Math.floor(t / SP); n++) {
				var r = rand(9001, n & 0x7fffffff);
				if (r() < 0.35 || !threads.length) { continue; }
				var th = threads[Math.floor(r() * threads.length)], dur = 3 + 1.5 * r(), st = n * SP + r() * 2.5, u = (t - st) / dur;
				if (u < 0 || u > 1) { continue; }
				var x = sides[th.side].cx, e = u * u * (3 - 2 * u), s = 0.05 + 0.85 * e, fade = Math.min(1, u * 6, (1 - u) * 3) * (1 - 0.6 * e), A = fade * (0.5 + 0.5 * th.z), p;
				for (var k = 7; k >= 1; k--) { p = at(th, s - k * 0.012); dot(x, p[0], p[1], 3 + 2 * th.z, A * 0.2 * (1 - k / 8)); }
				p = at(th, s);
				dot(x, p[0], p[1], 13 + 6 * th.z, A * 0.3); dot(x, p[0], p[1], 3.5 + 1.5 * th.z, A * 0.9);
			}
		}
		function byD(p, q) { return p.d - q.d; }
		function draw(t, dt) {
			var i, th, x, sd;
			for (i = 0; i < sides.length; i++) {
				sd = sides[i]; if (!sd.on) { continue; }
				x = sd.cx; x.globalCompositeOperation = 'source-over'; x.globalAlpha = 1; x.clearRect(sd.x, 0, sd.w, H);
				x.globalCompositeOperation = 'lighter';
			}
			for (i = 0; i < threads.length; i++) { posThread(threads[i], t, dt); }
			var ord = threads.slice().sort(byD);
			for (i = 0; i < ord.length; i++) { ord[i].near = i < 3; }
			for (i = 0; i < motes.length; i++) {
				var m = motes[i], y = (((m.y0 * (H + 40) - m.v * t) % (H + 40)) + H + 40) % (H + 40) - 20;
				sd = sides[m.side];
				dot(sd.cx, sd.x + 6 + (sd.w - 12) * m.fx + m.ax * Math.sin(m.wx * t + m.ph), y, m.s, m.a * (0.55 + 0.45 * Math.sin(m.tw * t + m.ph * 3)));
			}
			for (i = 0; i < threads.length; i++) {
				th = threads[i]; x = sides[th.side].cx;
				x.globalAlpha = 1; x.beginPath(); path(x, th);
				x.strokeStyle = th.g0; x.lineWidth = 3.5 + 3 * th.z; x.stroke();
				x.strokeStyle = th.g1; x.lineWidth = th.lw; x.stroke();
				var ex = th.px[N], ey = th.py[N], ea = th.a * 0.55;
				if (th.end === 1) {
					dot(x, ex, ey, 4 + 3 * th.z, ea * 0.35);
					x.globalAlpha = ea; x.fillStyle = th.col; x.beginPath(); x.arc(ex, ey + 1, 1 + 0.6 * th.z, 0, 6.2832); x.fill();
				} else if (th.end === 2) {
					var ang = Math.atan2(ey - th.py[N - 1], ex - th.px[N - 1]), r = 3 + 1.5 * th.z, d = th.hookDir;
					var ccx = ex - Math.sin(ang) * r * d, ccy = ey + Math.cos(ang) * r * d, a0 = Math.atan2(ey - ccy, ex - ccx);
					x.globalAlpha = 1; x.strokeStyle = th.colE; x.lineWidth = th.lw * 0.9; x.beginPath();
					x.arc(ccx, ccy, r, a0, a0 + d * 3.6, d < 0); x.stroke();
				}
			}
			sparks(t);
			for (i = 0; i < sides.length; i++) { if (sides[i].on) { sides[i].cx.globalCompositeOperation = 'source-over'; sides[i].cx.globalAlpha = 1; } }
		}

		/* 30 кадров/с, и при прокрутке тоже; после 15 с без действий или если кадры сами идут > 45 мс — 15 */
		function loop(ts) {
			raf = 0;
			if (!on || hid) { return; }
			raf = G.raf(loop);
			ts = performance.now();
			if (prevTs) { var dd = Math.min(250, ts - prevTs); ema += (dd - ema) * 0.05; } prevTs = ts;
			FRAME = ema > 45 || ts - lastInput > 15000 ? 1000 / 15 - 2 : ema < 24 ? 1000 / 30 - 2 : FRAME;
			if (ts - lastDraw < FRAME) { return; }
			var t = now(), dt = prevT ? Math.min(0.1, t - prevT) : 0;
			lastDraw = ts; prevT = t;
			var p0 = performance.now(); draw(t, dt); var p1 = performance.now();
			if (G.stat) { stA.push(p1 - p0); stN++; if (p1 - stT > 250) { G.stat(stA, stN); stA = []; stN = 0; stT = p1; } }
		}
		function run(v) {
			on = v && !still;
			if (on && !hid && !raf && sides.length) { prevT = 0; prevTs = 0; lastInput = performance.now(); raf = G.raf(loop); }
			if (!on && raf) { G.caf(raf); raf = 0; }
		}
		return {
			msg: function (d) {
				if (d.g) { setGeom(d.g); if (still) { draw(now(), 0); } else if (on && !hid && !raf) { run(true); } }
				if (d.still) { still = true; on = false; if (sides.length) { draw(now(), 0); } }
				if (d.inp) { lastInput = performance.now(); }
				if (d.m) { mouse.x = d.m[0]; mouse.y = d.m[1]; mouse.in = d.m[2]; lastInput = performance.now(); }
				if ('hid' in d) { hid = d.hid; if (hid && raf) { G.caf(raf); raf = 0; } else { run(on); } }
				if ('run' in d) { run(d.run); }
			},
			threads: function () { return threads; }
		};
	}

	var cvs = [], E = null, WK = null, mode = '', on = false, last = '', sides = [], tmr = 0, geomMsg = null, runMsg = null, lastPost = 0, mIn = false;
	var api = window.lotmBgt = { mode: '', frames: 0, threads: function () { return E ? E.threads() : []; } }; /* для отладки */
	function onStat(a, n) { api.frames += n; if (window.lotmBgtStat) { for (var i = 0; i < a.length; i++) { window.lotmBgtStat.push(a[i]); } } }
	function post(d) { if (WK) { WK.postMessage(d); } else if (E) { E.msg(d); } }
	function mkCanvas(i) {
		var c = document.createElement('canvas'); c.className = 'lotm-bgt lotm-bgt-' + (i ? 'r' : 'l'); c.setAttribute('aria-hidden', 'true');
		c.style.width = '0px';
		return c;
	}
	function mainCore() {
		mode = api.mode = 'main';
		E = core({ seed: SEED, start: START, cvs: cvs, raf: function (f) { return requestAnimationFrame(f); }, caf: function (h) { cancelAnimationFrame(h); }, stat: onStat });
	}
	function engine() {
		var src = 'var E;onmessage=function(e){var d=e.data;if(d.init){var R=self.requestAnimationFrame?function(f){return requestAnimationFrame(f)}:function(f){return setTimeout(function(){f(performance.now())},33)},C=self.cancelAnimationFrame?function(h){cancelAnimationFrame(h)}:clearTimeout;' +
			'E=(' + core.toString() + ')({seed:d.seed,start:d.start,cvs:d.cvs,raf:R,caf:C,stat:function(a,n){postMessage({st:a,n:n})}});postMessage({ready:1})}else if(E){E.msg(d)}};';
		try {
			if (!window.Worker || !window.OffscreenCanvas || !cvs[0].transferControlToOffscreen || !window.Blob || !window.URL) { throw 0; }
			var url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' })), ready = false;
			WK = new Worker(url);
			var off = [cvs[0].transferControlToOffscreen(), cvs[1].transferControlToOffscreen()];
			WK.onmessage = function (e) { var d = e.data; if (d.ready) { ready = true; URL.revokeObjectURL(url); } else if (d.st) { onStat(d.st, d.n); } };
			WK.onerror = function () { if (!ready) { fallback(); } };
			WK.postMessage({ init: 1, seed: SEED, start: START, cvs: off }, off);
			mode = api.mode = 'worker';
		} catch (e) { WK = null; if (cvs[0].transferControlToOffscreen && e !== 0) { fallback(); } else { mainCore(); } }
	}
	function fallback() { /* Worker не поднялся (например, blob: запрещён) — новые canvas и ядро в главном потоке */
		if (WK) { try { WK.terminate(); } catch (e) {} WK = null; }
		cvs.forEach(function (c, i) { var n = mkCanvas(i); n.style.cssText = c.style.cssText; if (c.parentNode) { c.parentNode.replaceChild(n, c); } cvs[i] = n; });
		mainCore();
		if (geomMsg) { E.msg(geomMsg); }
		if (mqRM.matches) { E.msg({ still: 1 }); } else if (runMsg) { E.msg(runMsg); }
	}

	function geom() {
		var main = document.querySelector('.mw-body'), hdr = document.querySelector('.citizen-header');
		if (!main) { return false; }
		var r = main.getBoundingClientRect(), vw = document.documentElement.clientWidth, vh = window.innerHeight, hl = 0, hc = hdr && hdr.getBoundingClientRect();
		if (hc && hc.height > vh * 0.6 && hc.left < 1) { hl = Math.round(hc.right); }
		var pl = Math.round(r.left), pr = Math.round(r.right), key = [hl, pl, pr, vw, vh].join();
		if (key === last) { return false; }
		last = key;
		sides = [[hl, Math.max(0, pl - hl)], [pr, Math.max(0, vw - pr)]];
		sides.forEach(function (s, i) { var c = cvs[i].style; c.left = s[0] + 'px'; c.width = (s[1] >= 40 ? s[1] : 0) + 'px'; });
		geomMsg = { g: { s: sides, W: vw, H: vh, DPR: Math.min(1.5, window.devicePixelRatio || 1) } };
		post(geomMsg);
		return true;
	}
	function alive() { return cvs[0].getClientRects().length > 0 && !document.documentElement.classList.contains('lotm-lite'); }
	function tick() { /* геометрия и «не скрыт ли canvas» — раз в секунду, пока нити идут */
		tmr = 0;
		if (!on || document.hidden) { return; }
		geom();
		if (!alive()) { stop(); return; }
		tmr = setTimeout(tick, 1000);
	}
	function stop() { on = false; runMsg = { run: false }; post(runMsg); if (tmr) { clearTimeout(tmr); tmr = 0; } }
	function start() {
		if (!cvs.length || mqRM.matches) { return; }
		if (!alive()) { if (on) { stop(); } return; }
		on = true; geom(); runMsg = { run: true }; post(runMsg);
		if (!tmr && !document.hidden) { tmr = setTimeout(tick, 1000); }
	}
	function poke() { var t = performance.now(); if (t - lastPost > 1000) { lastPost = t; post({ inp: 1 }); } }

	function init() {
		if (cvs.length || !mqDesk.matches || !document.querySelector('.mw-body') || document.documentElement.classList.contains('lotm-lite')) { return; }
		cvs = [mkCanvas(0), mkCanvas(1)];
		document.body.insertBefore(cvs[1], document.body.firstChild);
		document.body.insertBefore(cvs[0], document.body.firstChild);
		engine();
		if (document.hidden) { post({ hid: true }); }
		geom();
		if (mqRM.matches) { post({ still: 1 }); return; } /* без движения — один статичный кадр */
		if (FRESH) { /* проявление — только в начале сессии: CSS-переход opacity (композитор) */
			cvs.forEach(function (c) { c.style.opacity = '0'; c.style.transition = 'opacity 3s linear'; });
			requestAnimationFrame(function () { requestAnimationFrame(function () { cvs.forEach(function (c) { c.style.opacity = ''; }); }); });
			setTimeout(function () { cvs.forEach(function (c) { c.style.transition = ''; }); }, 3600);
		}
		window.addEventListener('scroll', poke, { passive: true });
		window.addEventListener('keydown', poke, { passive: true });
		window.addEventListener('pointerdown', poke, { passive: true });
		window.addEventListener('lotm-lite', function (e) { if (e.detail) { stop(); } });
		window.addEventListener('pointermove', function (e) {
			var x = e.clientX, inn = false;
			for (var i = 0; i < sides.length; i++) { if (sides[i][1] >= 40 && x >= sides[i][0] && x < sides[i][0] + sides[i][1]) { inn = true; } }
			inn = inn && e.pointerType !== 'touch';
			if (inn || mIn) { post({ m: [x, e.clientY, inn] }); lastPost = performance.now(); } else { poke(); }
			mIn = inn;
		}, { passive: true });
		var out = function () { if (mIn) { mIn = false; post({ m: [-1e4, -1e4, false] }); } };
		document.addEventListener('pointerleave', out);
		window.addEventListener('blur', out);
		window.addEventListener('resize', function () { if (on) { geom(); } else { start(); } }, { passive: true });
		document.addEventListener('visibilitychange', function () {
			post({ hid: document.hidden });
			if (!document.hidden) { start(); } else if (tmr) { clearTimeout(tmr); tmr = 0; }
		});
		if (window.MutationObserver) { new MutationObserver(function () { if (!on) { last = ''; start(); } }).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); }
		start();
	}
	if (mqDesk.addEventListener) { mqDesk.addEventListener('change', function () { init(); start(); }); }
	if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }
})();

/* ===== QFIX (wave30): подсказка «Нажмите, чтобы открыть спойлер» — одна, только при наведении =====
   Дополнение к бандлу: вставить В КОНЕЦ MediaWiki:Common.js (после spoilerfix). Постоянный слой
   .lotm-sf-layer скрыт в QFIX.css; здесь — одна плашка у курсора над закрытой целью, прижатая к границам
   инфобокса/колонки статьи и окна. На сенсорных экранах (нет hover) не показывается: тап сразу открывает. */
( function () {
	'use strict';
	if ( window.lotmQfPill ) { return; }
	window.lotmQfPill = true;
	var HINT = 'Нажмите, чтобы открыть спойлер';
	var SEL = '.spoilerblur:not(.lotm-revealed), .lotm-sf-closed';
	var pill = null, cur = null, lx = 0, ly = 0, raf = 0;

	function ensure() {
		if ( pill && pill.isConnected ) { return pill; }
		pill = document.createElement( 'span' );
		pill.className = 'lotm-qf-pill';
		pill.setAttribute( 'aria-hidden', 'true' );
		pill.textContent = HINT;
		document.body.appendChild( pill );
		return pill;
	}
	function target( el ) {
		return el && el.closest ? el.closest( SEL ) : null;
	}
	function hide() { cur = null; if ( pill ) { pill.classList.remove( 'is-on' ); } }
	function place() {
		raf = 0;
		if ( !cur || !cur.isConnected || !cur.matches( SEL ) ) { hide(); return; }
		var p = ensure(), box = cur.closest( '.portable-infobox, aside, table.infobox' ) || cur.closest( '.mw-parser-output' ) || document.body;
		var b = box.getBoundingClientRect(), vw = document.documentElement.clientWidth, vh = window.innerHeight;
		var L = Math.max( 4, b.left + 2 ), R = Math.min( vw - 4, b.right - 2 );
		p.style.maxWidth = Math.max( 120, R - L ) + 'px';
		var w = p.offsetWidth, h = p.offsetHeight;
		var x = Math.min( Math.max( lx - w / 2, L ), Math.max( L, R - w ) );
		var y = ly - h - 14;
		if ( y < 4 ) { y = ly + 18; }
		y = Math.min( Math.max( 4, y ), vh - h - 4 );
		p.style.transform = 'translate(' + Math.round( x ) + 'px,' + Math.round( y ) + 'px)';
		p.classList.add( 'is-on' );
	}
	function sched() { if ( !raf ) { raf = requestAnimationFrame( place ); } }

	document.addEventListener( 'pointermove', function ( e ) {
		if ( e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== 'pen' ) { return; }
		var t = target( e.target );
		if ( !t ) { if ( cur ) { hide(); } return; }
		cur = t; lx = e.clientX; ly = e.clientY; sched();
	}, { passive: true } );
	document.addEventListener( 'pointerdown', hide, true );
	document.addEventListener( 'click', function () { setTimeout( function () { if ( cur && !cur.matches( SEL ) ) { hide(); } }, 0 ); }, true );
	document.addEventListener( 'mouseleave', hide );
	window.addEventListener( 'scroll', hide, { passive: true } );
	window.addEventListener( 'blur', hide );
	/* клавиатура: фокус на закрытой цели — подсказка над её центром */
	document.addEventListener( 'focusin', function ( e ) {
		var t = target( e.target );
		if ( !t || !t.matches( ':focus-visible' ) ) { return; }
		var r = t.getBoundingClientRect(); cur = t; lx = r.left + r.width / 2; ly = r.top; sched();
	} );
	document.addEventListener( 'focusout', function ( e ) { if ( cur && e.target === cur ) { hide(); } } );
}() );

/* (V1) рамка-цитата, весь видимый текст которой скрыт (свёрнутый спойлер) — схлопнуть, а не показывать пустую коробку */
( function () {
	'use strict';
	if ( window.lotmQfEmpty ) { return; }
	window.lotmQfEmpty = true;
	var SEL = '.mw-parser-output .ramki, #content blockquote.pull-quote, #content .blockquote.blockwrapper', raf = 0, mo;
	function run() {
		raf = 0; mo.disconnect();
		[].forEach.call( document.querySelectorAll( SEL ), function ( q ) {
			var was = q.classList.contains( 'lotm-qf-empty' );
			if ( was ) { q.classList.remove( 'lotm-qf-empty' ); }
			if ( !q.getClientRects().length ) { return; } // в скрытой вкладке — не трогаем
			var txt = ( q.innerText || '' ).replace( /[\s​«»"“”—–-]+/g, '' );
			var media = q.querySelector( 'img, video, svg, .spoilerblur, .lotm-sf-closed, .mw-collapsed' );
			if ( !txt && !( media && media.getClientRects().length ) ) { q.classList.add( 'lotm-qf-empty' ); }
		} );
		mo.takeRecords(); watch();
	}
	function watch() { mo.observe( document.body, { attributes: true, attributeFilter: [ 'class', 'style' ], subtree: true, childList: true } ); }
	function sched() { if ( !raf ) { raf = requestAnimationFrame( run ); } }
	var lotmQfOwn = /^(lotm-(sf-closed|fog-live|fog-anim|fog-gone|tilting|press|offscreen|qf-empty)|lotm-leaving|lotm-back)$/;
	mo = new MutationObserver( function ( recs ) {
		for ( var i = 0; i < recs.length; i++ ) {
			var r = recs[ i ], t = r.target;
			if ( !t.closest || t.closest( '.lotm-sf-layer, .lotm-bgt, .lotm-glow' ) ) { continue; }
			if ( r.type === 'attributes' && r.attributeName === 'style' && !t.closest( SEL ) ) { continue; }
			if ( r.type === 'attributes' && r.attributeName === 'class' && t === document.documentElement ) { continue; }
			sched(); return;
		}
	} ); watch();
	if ( window.mw && mw.hook ) { mw.hook( 'wikipage.content' ).add( function () { setTimeout( sched, 450 ); } ); }
	setTimeout( sched, 500 );
}() );

/* ===== ANIM (волна 42): затухание статьи при уходе по внутренней ссылке =====
 * Клик по внутренней ссылке того же окна (без Ctrl/Cmd/Shift/Alt, левой кнопкой) →
 * html.lotm-leaving (контент гаснет за 180 мс, CSS в ANIM.css) → переход.
 * Не трогает: модификаторы и средний клик, target≠_self, download, якоря той же страницы,
 * ссылки-кнопки (role=button, переключатели спойлеров/свёрток), формы, редактор, файлы,
 * другие домены, уже обработанные кем-то клики (defaultPrevented — Citizen, превью, спойлеры).
 * Возврат «назад» (bfcache, pageshow.persisted) — класс снимается, контент возвращается.
 * prefers-reduced-motion: ничего не перехватывается. */
( function () {
	if ( window.lotmPageLeave ) { return; }
	window.lotmPageLeave = 1;
	var root = document.documentElement, DUR = 180, timer = 0, safety = 0;
	var mqRM = window.matchMedia ? window.matchMedia( '(prefers-reduced-motion: reduce)' ) : { matches: false };
	var FILE = /\.(png|jpe?g|gif|webp|svg|avif|pdf|zip|7z|rar|mp3|ogg|mp4|webm|txt|json)$/i;

	function reset( soft ) {
		clearTimeout( timer ); clearTimeout( safety ); timer = safety = 0;
		if ( !root.classList.contains( 'lotm-leaving' ) ) { return; }
		if ( soft ) {
			root.classList.add( 'lotm-back' );
			root.classList.remove( 'lotm-leaving' );
			setTimeout( function () { root.classList.remove( 'lotm-back' ); }, 300 );
		} else {
			root.classList.remove( 'lotm-leaving', 'lotm-back' );
		}
	}

	function eligible( e ) {
		if ( e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey ) { return null; }
		if ( mqRM.matches || root.classList.contains( 'lotm-leaving' ) ) { return null; }
		var t = e.target;
		if ( !t || !t.closest ) { return null; }
		var a = t.closest( 'a[href]' );
		if ( !a || a.closest( 'form, [contenteditable], .ve-ce-surface, .ve-init-target' ) ) { return null; }
		if ( a.hasAttribute( 'download' ) || a.getAttribute( 'role' ) === 'button' ) { return null; }
		var tgt = ( a.getAttribute( 'target' ) || '' ).toLowerCase();
		if ( tgt && tgt !== '_self' ) { return null; }
		var raw = a.getAttribute( 'href' ) || '';
		if ( !raw || raw.charAt( 0 ) === '#' || /^\s*(javascript|mailto|tel|data):/i.test( raw ) ) { return null; }
		if ( /(^|\s)(mw-collapsible-toggle|mw-customtoggle|mw-selflink|new)(\s|$)/.test( a.className ) || /mw-customtoggle/.test( a.className ) ) { return null; }
		var u;
		try { u = new URL( a.href, location.href ); } catch ( err ) { return null; }
		if ( u.origin !== location.origin || !/^https?:$/.test( u.protocol ) ) { return null; }
		if ( u.pathname === location.pathname && u.search === location.search ) { return null; } /* якорь / та же страница */
		if ( FILE.test( u.pathname ) || /^\/(images|load\.php|api\.php|rest\.php)/.test( u.pathname ) ) { return null; }
		if ( /[?&]action=(raw|render)/.test( u.search ) ) { return null; }
		return u.href;
	}

	window.addEventListener( 'click', function ( e ) { /* window, а не document: срабатывает последним, после всех обработчиков на document */
		var href = eligible( e );
		if ( !href ) { return; }
		e.preventDefault();
		root.classList.remove( 'lotm-back' );
		root.classList.add( 'lotm-leaving' );
		timer = setTimeout( function () { location.href = href; }, DUR );
		/* если переход не случился (скачивание, 204, отмена) — вернуть контент через 4 с */
		safety = setTimeout( function () { reset( true ); }, 4000 );
	} );

	window.addEventListener( 'pageshow', function ( e ) { reset( !!e.persisted ); } );
	window.addEventListener( 'pagehide', function () { clearTimeout( safety ); } );
	document.addEventListener( 'visibilitychange', function () {
		/* вкладку спрятали во время затухания (например, Ctrl+клик по превью) — не оставлять прозрачной */
		if ( document.visibilityState === 'visible' && root.classList.contains( 'lotm-leaving' ) && !timer ) { reset( true ); }
	} );
}() );

/* GLOW_final — «Багровая ночь» (усиленная) + «Затмение». Пара к GLOW_final.css. Тема/ширину решает CSS.
   Одна система времени на сессию: sessionStorage 'lotmGlow' = {t0: старт (мс), s: зерно}.
   - Налёт: CSS-анимация 20 с, фаза = (сейчас − t0) через --lotm-glow-t (отрицательный animation-delay).
   - Затмения: детерминированное расписание из зерна: первое через 15–20 с, дальше интервалы 60–90 с, длительность 3–4 с.
     Каждое затмение — Web Animation (opacity, композитор) с currentTime = сейчас − начало события, поэтому
     при переходе на другую статью / возврате во вкладку оно продолжается с нужной фазы и не повторяется.
     Между событиями — один setTimeout до следующего (скрытая вкладка: таймер дросселируется, кадров нет).
   - ?glowdemo=1 — затмения каждые 15 с (запоминается в сессии; ?glowdemo=0 — выключить).
   - prefers-reduced-motion — статичный налёт, затмений нет. */
(function () {
	'use strict';
	if (window.lotmGlow) { return; }
	var K = 'lotmGlow', st = null, demo = false, el, ecl, rim, tl0 = 0, base = 0, timer = 0, cur = -1, anims = [];
	try { st = JSON.parse(sessionStorage.getItem(K) || 'null'); } catch (e) {}
	if (!st || !st.t0) { st = { t0: Date.now(), s: Math.floor(Math.random() * 4294967296) }; try { sessionStorage.setItem(K, JSON.stringify(st)); } catch (e) {} }
	try {
		var m = /[?&]glowdemo=([01])/.exec(location.search);
		if (m) { sessionStorage.setItem('lotmGlowDemo', m[1]); }
		demo = sessionStorage.getItem('lotmGlowDemo') === '1';
	} catch (e) { demo = /[?&]glowdemo=1/.test(location.search); }
	var mqRM = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : { matches: false };

	/* детерминированное псевдослучайное число [0,1) для события k (mulberry32-хэш от зерна и k) */
	function rnd(k, j) {
		var a = (st.s ^ Math.imul(k + 1, 0x9E3779B1) ^ Math.imul(j + 1, 0x85EBCA77)) >>> 0;
		a = (a + 0x6D2B79F5) >>> 0; var t = Math.imul(a ^ (a >>> 15), 1 | a);
		t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
	}
	/* событие k: {k, a: начало (с от t0), d: длительность (с)} */
	function ev(k) {
		if (demo) { return { k: k, a: 5 + 15 * k, d: 11 }; }
		var a = 15 + 5 * rnd(0, 0);
		for (var i = 1; i <= k; i++) { a += 45 + 15 * rnd(i, 0); }
		return { k: k, a: a, d: 10 + 3 * rnd(k, 1) };
	}
	/* первое событие, которое ещё не закончилось к моменту t (с) */
	function find(t) {
		if (demo) { var j = Math.max(0, Math.floor((t - 8.5) / 15)), e = ev(j); while (e.a + e.d <= t) { e = ev(++j); } return e; }
		var a = 15 + 5 * rnd(0, 0), k = 0, d = 10 + 3 * rnd(0, 1);
		while (a + d <= t) { k++; a += 45 + 15 * rnd(k, 0); d = 10 + 3 * rnd(k, 1); }
		return { k: k, a: a, d: d };
	}
	function now() { return (Date.now() - st.t0) / 1000; }
	function tl() { return (document.timeline && document.timeline.currentTime != null ? document.timeline.currentTime : performance.now()) / 1000; }

	/* плавно темнеет и краснеет (≈35 %), держится (≈30 %), плавно уходит (≈35 %); подсвет чуть запаздывает */
	var KE = [{ opacity: 0, offset: 0, easing: 'ease-in-out' }, { opacity: 1, offset: 0.3 }, { opacity: 1, offset: 0.7, easing: 'ease-in-out' }, { opacity: 0, offset: 1 }];
	var KR = [{ opacity: 0, offset: 0, easing: 'ease-in-out' }, { opacity: 0, offset: 0.08, easing: 'ease-in-out' }, { opacity: 1, offset: 0.42 }, { opacity: 0.85, offset: 0.6, easing: 'ease-in-out' }, { opacity: 0, offset: 1 }];
	function play(e, t) {
		if (cur === e.k || !ecl.animate) { return; }
		cur = e.k; stop();
		var ms = e.d * 1000, at = Math.max(0, (t - e.a) * 1000);
		anims = [ecl.animate(KE, { duration: ms, fill: 'none' }), rim.animate(KR, { duration: ms, fill: 'none' })];
		anims.forEach(function (a) { a.currentTime = at; });
	}
	function stop() { anims.forEach(function (a) { try { a.cancel(); } catch (e) {} }); anims = []; }
	function plan() {
		clearTimeout(timer); timer = 0;
		if (mqRM.matches || document.hidden) { stop(); cur = -1; return; }
		var t = now(), e = find(t);
		if (t >= e.a) { play(e, t); e = find(e.a + e.d + 0.001); }
		timer = setTimeout(plan, Math.max(50, (e.a - now()) * 1000));
	}
	function resync() {
		var want = now(), have = (tl() - tl0) + base;
		if (Math.abs(want - have) > 0.25) { base += want - have; el.style.setProperty('--lotm-glow-t', base.toFixed(3)); }
	}
	function init() {
		if (el || !document.body) { return; }
		el = document.createElement('div');
		el.className = 'lotm-glow' + (demo ? ' lotm-glow--demo' : '');
		el.setAttribute('aria-hidden', 'true');
		el.innerHTML = '<div class="lotm-glow__wash"></div><div class="lotm-glow__eclipse"></div><div class="lotm-glow__rim"></div>';
		ecl = el.children[1]; rim = el.children[2];
		tl0 = tl(); base = now(); el.style.setProperty('--lotm-glow-t', base.toFixed(3));
		document.body.insertBefore(el, document.body.firstChild);
		plan(); /* до первой отрисовки: если затмение уже идёт — сразу с нужной фазы, без вспышки */
		window.addEventListener('pageshow', function (e) { if (e.persisted) { resync(); cur = -1; plan(); } });
		document.addEventListener('visibilitychange', function () { if (!document.hidden) { resync(); cur = -1; } plan(); });
		if (mqRM.addEventListener) { mqRM.addEventListener('change', plan); }
	}
	window.lotmGlow = { variant: 'final', now: now, next: function () { return find(now()); }, demo: demo };
	if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }
})();

/* ===== SEALFX (wave30): разметка значков Путей для подсветки (замена A3) =====
 * Только МЕЛКИЕ значки (ширина ≤ 48px: у ссылок, фишки, таблицы, навбоксы, вкладки карточек).
 * Без обработчиков событий: один проход по новому контенту ставит обёртке значка
 * class="lotm-sfx" и data-sfx="<путь>" (из имени файла …/Door_Symbol2.png → door).
 * Цвет и движение задаёт CSS по data-sfx — ни getComputedStyle, ни mouseover на документ. */
( function () {
	var RX = /\/(?:\d+px-)?([A-Za-z][A-Za-z_-]*?)_Symbol\d*\.\w+(?:$|[?#])/;
	function tag( root ) {
		var imgs = root.querySelectorAll( 'img[src*="_Symbol"]' ), i, img, w, m;
		for ( i = 0; i < imgs.length; i++ ) {
			img = imgs[ i ]; w = img.parentNode;
			if ( !w || w.nodeType !== 1 || w.hasAttribute( 'data-sfx' ) ) { continue; }
			if ( ( +img.getAttribute( 'width' ) || img.width || 0 ) > 48 ) { continue; } /* крупные символы (≥ ~64px) — зона BIGSEAL.css */
			if ( w.tagName !== 'A' && !( w.tagName === 'SPAN' && w.children.length === 1 ) ) { continue; }
			m = RX.exec( img.getAttribute( 'src' ) || '' );
			w.setAttribute( 'data-sfx', m ? m[ 1 ].toLowerCase().replace( /_/g, '-' ) : 'x' );
			w.classList.add( 'lotm-sfx' );
		}
	}
	mw.hook( 'wikipage.content' ).add( function ( $c ) {
		var root = ( $c && $c[ 0 ] ) || document;
		if ( root.querySelectorAll ) { tag( root ); }
	} );
}() );

/* POPFIX: превью ссылок — текст обрезается целыми строками: по концу предложения, иначе по слову + «…» */
(function () {
	'use strict';
	if (window.lotmPopfix) return; window.lotmPopfix = 1;
	var SENT = /[.!?…][»"”)\]]*(?=\s+[«"„(\[A-ZА-ЯЁ0-9—–-]|\s*$)/g;
	function texts(root) {
		var w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT), a = [], n;
		while ((n = w.nextNode())) a.push(n);
		return a;
	}
	/* оставить первые k символов текста (разметку сохраняем), ell — добавить «…» */
	function cut(ex, orig, k, ell) {
		ex.innerHTML = orig;
		var tn = texts(ex), pos = 0, done = false;
		tn.forEach(function (n) {
			var len = n.data.length;
			if (done) { n.data = ''; return; }
			if (pos + len >= k) {
				n.data = n.data.slice(0, k - pos);
				if (ell) n.data = n.data.replace(/[\s,;:—–\-(«"„]+$/, '') + '…';
				done = true;
			}
			pos += len;
		});
		Array.prototype.slice.call(ex.querySelectorAll('*')).reverse().forEach(function (e) {
			if (!/^(BR|IMG)$/.test(e.tagName) && !e.textContent.trim() && !e.querySelector('img')) e.remove();
		});
	}
	/* высота текста от верха области содержимого (верх пересчитываем: у перевёрнутого вверх превью он сдвигается) */
	function textH(ex, pt) {
		var r = document.createRange(); r.selectNodeContents(ex);
		var rs = r.getClientRects(), b = 0, i, t = ex.getBoundingClientRect().top + pt;
		for (i = 0; i < rs.length; i++) if (rs[i].height) b = Math.max(b, rs[i].bottom);
		return b - t;
	}
	function fix(ex) {
		if (ex.getAttribute('data-lotm-popfix')) return;
		var pop = ex.closest('.mwe-popups');
		if (!pop || pop.className.indexOf('mwe-popups-type-page') < 0) return;
		ex.setAttribute('data-lotm-popfix', '1');
		var cs = getComputedStyle(ex), lh = parseFloat(cs.lineHeight) || 1.5 * parseFloat(cs.fontSize) || 21,
			pt = parseFloat(cs.paddingTop) || 0, pb = parseFloat(cs.paddingBottom) || 0,
			box = ex.getBoundingClientRect(),
			mh = parseFloat(cs.maxHeight),                     /* is-not-tall: высота по тексту, до max-height */
			avail = mh ? mh - (cs.boxSizing === 'border-box' ? pt + pb : 0) : box.height - pt - pb;
		if (textH(ex, pt) <= avail + 1) return;             /* текст помещается целиком */
		var N = Math.floor((avail + 1) / lh);
		if (N < 1) return;
		var lim = N * lh + 1, orig = ex.innerHTML, full = ex.textContent, sc = [], wc = [], m, lo, hi, mid, best;
		ex.style.maxHeight = (N * lh + pt + pb) + 'px';         /* видимая область — ровно N строк */
		try {
			SENT.lastIndex = 0;
			while ((m = SENT.exec(full))) sc.push(m.index + m[0].length);
			full.replace(/[^\s—–-](?=[\s—–-])/g, function (_, i) { wc.push(i + 1); });
			best = -1; lo = 0; hi = sc.length - 1;
			while (lo <= hi) { mid = (lo + hi) >> 1; cut(ex, orig, sc[mid], false); if (textH(ex, pt) <= lim) { best = mid; lo = mid + 1; } else hi = mid - 1; }
			if (best >= 0) {
				cut(ex, orig, sc[best], false);
				/* конец предложения, если теряем не больше четверти окна (минимум 1 строку) */
				var used = Math.round(textH(ex, pt) / lh);
				if (N - used <= Math.max(1, Math.floor(N / 4))) return;
			}
			best = -1; lo = 0; hi = wc.length - 1;
			while (lo <= hi) { mid = (lo + hi) >> 1; cut(ex, orig, wc[mid], true); if (textH(ex, pt) <= lim) { best = mid; lo = mid + 1; } else hi = mid - 1; }
			if (best >= 0) cut(ex, orig, wc[best], true); else ex.innerHTML = orig;
		} catch (e) { ex.innerHTML = orig; }
	}
	function scan(root) {
		if (!root.querySelectorAll) return;
		if (root.matches && root.matches('.mwe-popups-extract')) fix(root);
		root.querySelectorAll('.mwe-popups-extract').forEach(fix);
	}
	function start() {
		new MutationObserver(function (ms) {
			var nodes = [];
			ms.forEach(function (m) { m.addedNodes.forEach(function (n) { if (n.nodeType === 1) nodes.push(n); }); });
			if (nodes.length) requestAnimationFrame(function () { nodes.forEach(scan); });
		}).observe(document.body, { childList: true });
		scan(document.body);
	}
	if (document.body) start(); else document.addEventListener('DOMContentLoaded', start);
}());

/* BIGSEAL (wave30 v2): крупные символы Путей (≥64px) — обёртка .lotm-bigseal, цвет Пути, силуэт для сияния, тап (1 с) на телефоне */
(function () {
	var C = { Fool: 'lom', Error: 'lom', Door: 'lom', Visionary: 'ga', 'Hanged Man': 'ga', Sun: 'ga', Tyrant: 'ga', 'White Tower': 'ga',
		Darkness: 'ed', Death: 'ed', 'Twilight Giant': 'ed', 'Red Priest': 'cod', Demoness: 'cod', Paragon: 'dok', Hermit: 'dok',
		'Wheel of Fortune': 'kol', Moon: 'goo', Mother: 'goo', Chained: 'fod', Abyss: 'fod', 'Black Emperor': 'ta', Justiciar: 'ta',
		'Eternal Aeon': 'coi', 'Chaos Primogenitor': 'mgod', Patriarch: 'mtod', 'Chaos Mist': 'soc', 'Eternal Edict': 'gof',
		Condenser: 'sd', 'Tail-Devourer': 'ph', 'Sublunary Eye': 'hdo', Everlasting: 'ir', 'Second Law': 'ir', 'Monarch of Decay': 'mod' };
	var MIN = 64;
	function mark(img) {
		var w = img.parentNode;
		if (!w || w.nodeType !== 1 || w.classList.contains('lotm-bigseal') || w.hasAttribute('data-sfx')) { return; } /* data-sfx — мелкий значок SEALFX */
		var m = /\/([^\/]+?)_Symbol\d*\.(?:png|webp|svg|jpe?g)/i.exec(decodeURIComponent(img.getAttribute('src') || ''));
		var k = m && C[m[1].replace(/_/g, ' ')];
		w.classList.add('lotm-bigseal');
		if (k) { w.style.setProperty('--lotm-seal', 'var(--color-' + k + ')'); }
		w.style.setProperty('--lotm-seal-img', 'url("' + (img.currentSrc || img.src).replace(/["\\]/g, '') + '")');
		if (!w.hasAttribute('data-lotm-seal-tap')) {
			w.setAttribute('data-lotm-seal-tap', '');
			var t;
			w.addEventListener('touchstart', function () {
				clearTimeout(t); w.classList.add('lotm-seal-tap');
				t = setTimeout(function () { w.classList.remove('lotm-seal-tap'); }, 1000);
			}, { passive: true });
		}
	}
	function run($c) {
		var root = ($c && $c[0]) || document;
		var imgs = root.querySelectorAll('img[src*="Symbol"].mw-file-element'), small = [];
		Array.prototype.forEach.call(imgs, function (img) {
			if ((+img.getAttribute('width') || 0) >= MIN) { mark(img); } else { small.push(img); }
		});
		if (!small.length) { return; }
		(window.requestIdleCallback || function (f) { return setTimeout(f, 300); })(function () {
			small.forEach(function (img) { if (img.offsetWidth >= MIN) { mark(img); } });
		});
	}
	mw.hook('wikipage.content').add(run);
}());

/* ===== QUNIFY — единый вид цитатных рамок: убрать дублирующие внешние «…», снять «— » в подписи молитв,
   пометить длинные тексты (см. QUNIFY.css) ===== */
( function () {
	var SKIP = '.pull-quote__source,.ramki-caption,.refpopups-custom-content,sup,.mw-ref,.blockquote__source';
	function texts( box ) {
		var out = [], w = document.createTreeWalker( box, NodeFilter.SHOW_TEXT, null ), n;
		while ( ( n = w.nextNode() ) ) {
			if ( n.parentElement && n.parentElement.closest( SKIP ) ) { continue; }
			if ( box.classList.contains( 'blockwrapper' ) && n.parentElement.closest( '.blockwrapper>div:last-child:not(.poem)' ) ) { continue; }
			if ( n.nodeValue.trim() ) { out.push( n ); }
		}
		return out;
	}
	function run( box ) {
		if ( box.getAttribute( 'data-qu' ) ) { return; }
		box.setAttribute( 'data-qu', '1' );
		if ( box.querySelector( 'div.pull-quote__text' ) ) { return; } /* диалог — без кавычек */
		var ns = texts( box ), all = ns.map( function ( n ) { return n.nodeValue; } ).join( '' ).trim();
		if ( all.length > 380 ) { box.classList.add( 'lotm-qu-long' ); }
		if ( !ns.length || !/^[«„"“]/.test( all ) ) { return; }
		var one = /^«[^«»]*»[.!?…]*$/.test( all ) || /^[„“][^„“”]*[“”][.!?…]*$/.test( all ) || /^"[^"]*"[.!?…]*$/.test( all );
		if ( !one ) { box.classList.add( 'lotm-qu-noqm' ); return; }
		var a = ns[ 0 ], z = ns[ ns.length - 1 ];
		a.nodeValue = a.nodeValue.replace( /^(\s*)[«„“"]/, '$1' );
		z.nodeValue = z.nodeValue.replace( /[»“”"]([.!?…]*\s*)$/, '$1' );
		box.classList.add( 'lotm-qu-g' );
	}
	function cap( c ) {
		var n = c.firstChild;
		while ( n && n.nodeType === 3 && !n.nodeValue.trim() ) { n = n.nextSibling; }
		if ( n && n.nodeType === 3 ) { n.nodeValue = n.nodeValue.replace( /^\s*[—–-]\s*/, '' ); }
	}
	mw.hook( 'wikipage.content' ).add( function ( $c ) {
		var root = ( $c && $c[ 0 ] ) || document;
		if ( !root.querySelectorAll ) { return; }
		[].forEach.call( root.querySelectorAll( 'blockquote.pull-quote, .blockquote.blockwrapper, .ramki.ramki--prayer' ), run );
		[].forEach.call( root.querySelectorAll( '.ramki.ramki--prayer>.ramki-caption' ), cap );
	} );
}() );

/* STARS (wave30): точнее различать слабое устройство (html.lotm-lite) на телефонах.
   1) pointer: coarse — «мало ядер» не признак слабости (iOS Safari занижает hardwareConcurrency до 2–4).
   2) Замер кадров: старый критерий (медиана > 34 мс, флаг lotm-lite-slow на сутки) ловил 30 fps режима
      энергосбережения (33,3 мс + джиттер). Старый флаг игнорируется; новый — медиана > 45 мс И 25-й перцентиль > 34 мс
      за 2,5 с (стабильно медленно), флаг lotm-lite-slow2 на сутки. Срабатывание старого замера перепроверяется новым.
   3) Звёзды/искры Заглавной (только opacity/transform) в lite больше не выключаются — см. Common.css «STARS».
   Остальные признаки (память ≤ 2 ГБ, Save-Data, reduced-motion, режим производительности Citizen) и ручной
   ?lotmlite=0/1/auto — без изменений. */
(function () {
	var L = window.lotmLite, H = document.documentElement, K = 'lotm-lite', ls = null, force = null;
	if (!L || L.starsFix) { return; }
	L.starsFix = 1;
	try { ls = window.localStorage; } catch (e) {}
	try {
		if (ls && new URLSearchParams(location.search).get('lotmlite') === 'auto') { ls.removeItem(K + '-slow2'); }
		force = ls && ls.getItem(K + '-force');
	} catch (e) {}
	if (force === '1' || force === '0') { return; }
	var why = L.why, slow2 = false, nav = navigator, busy = false;
	var coarse = !!(window.matchMedia && matchMedia('(pointer: coarse)').matches);
	var oldMeasure = !why.length; /* старый замер запланирован только при пустом why */
	function drop(r) { var i = why.indexOf(r), had = i >= 0; if (had) { why.splice(i, 1); } if (had && !why.length) { L.set(false); } return had; }
	try { ls && ls.removeItem(K + '-slow'); } catch (e) {}
	try { slow2 = !!ls && +ls.getItem(K + '-slow2') > Date.now(); } catch (e) {}
	if (coarse) { drop('cores'); }
	if (slow2) { L.set(true, 'slow-frames'); } else { drop('slow-frames'); }
	function measure() {
		if (busy || slow2 || document.hidden) { return; }
		if (why.length && !(why.length === 1 && why[0] === 'slow-frames')) { return; } /* lite уже по другой причине */
		if ((nav.hardwareConcurrency || 0) >= 6 && (!nav.deviceMemory || nav.deviceMemory >= 6)) { drop('slow-frames'); return; }
		busy = true;
		var arr = [], t0 = 0, prev = 0;
		function f(t) {
			if (document.hidden) { busy = false; return; }
			if (!t0) { t0 = prev = t; } else { arr.push(t - prev); prev = t; }
			if (t - t0 < 2500) { requestAnimationFrame(f); return; }
			busy = false;
			arr.sort(function (a, b) { return a - b; });
			var n = arr.length;
			if (n > 15 && arr[n >> 1] > 45 && arr[n >> 2] > 34) {
				slow2 = true; L.set(true, 'slow-frames');
				try { ls && ls.setItem(K + '-slow2', String(Date.now() + 864e5)); } catch (e) {}
			} else { drop('slow-frames'); }
		}
		requestAnimationFrame(f);
	}
	function later() { setTimeout(function () { if (window.requestIdleCallback) { requestIdleCallback(measure, { timeout: 4000 }); } else { measure(); } }, 1000); }
	/* старый замер (порог 34 мс) может сработать позже — перепроверка новым критерием */
	window.addEventListener('lotm-lite', function (e) {
		if (!e.detail || slow2 || why.indexOf('slow-frames') < 0) { return; }
		try { ls && ls.removeItem(K + '-slow'); } catch (x) {}
		setTimeout(measure, 500);
	});
	/* если признаки сняты здесь, старый замер не запланирован — запускаем свой */
	if (!oldMeasure && !why.length && !slow2) {
		if (document.readyState === 'complete') { later(); } else { window.addEventListener('load', later); }
	}
	/* возврат из bfcache (Safari): снять паузу скрытой вкладки */
	window.addEventListener('pageshow', function () { H.classList.toggle('lotm-tab-hidden', document.hidden); });
})();

/* ===== SPFOG2: строчные спойлеры (.spoilerblur) — тот же туман, что у блоков (.lotm-fog-sky > i.lotm-cloud-1/2/3) =====
 * Дописать В КОНЕЦ Common.js. Существующий код не меняется: DOM-слой тот же, что строит fogify() у блоков
 * (fogify закрыт в замыкании блоков и переносит детей в div.lotm-fog-body — для строки это не годится, поэтому
 * здесь те же 10 строк, но sky — <span> и без обёртки). Классы и жизненный цикл — как у блоков:
 * .lotm-fog-ready, .lotm-fog-live (дрейф только в зоне видимости, не в lite), .lotm-fog-anim/.lotm-fog-gone (разлёт при открытии),
 * пауза скрытой вкладки — общая html.lotm-fog-paused / html.lotm-tab-hidden.
 * Открытие (.lotm-revealed), общий переключатель и подсказку по-прежнему ведёт spoilerfix — здесь только наблюдаем класс. */
( function () {
	'use strict';
	if ( window.lotmSpFog2 ) { return; }
	window.lotmSpFog2 = true;
	var H = document.documentElement;
	var reduceMotion = !!( window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches );
	var multi = []; // строчные спойлеры, разорванные переносом строки: облака обрезаются по фрагментам
	var io = ( 'IntersectionObserver' in window ) ? new IntersectionObserver( function ( list ) {
		var lite = H.classList.contains( 'lotm-lite' );
		list.forEach( function ( e ) {
			var el = e.target;
			el.classList.toggle( 'lotm-fog-live', e.isIntersecting && !lite && !reduceMotion );
			if ( e.isIntersecting && el.lotmSf2Dirty ) { clip( el ); }
		} );
	}, { rootMargin: '120px 0px' } ) : null;
	if ( !window.lotmFogVis ) {
		window.lotmFogVis = true;
		document.addEventListener( 'visibilitychange', function () { H.classList.toggle( 'lotm-fog-paused', document.hidden ); } );
	}

	// Перенос строки: abspos-слой у строчного элемента — рамка от первого до последнего фрагмента,
	// поэтому для >1 фрагмента обрезаем слой путём из прямоугольников самих фрагментов (только у таких, обычно 0–3 на странице).
	function clip( el ) {
		el.lotmSf2Dirty = false;
		var sky = el.lotmSf2Sky, rs = el.getClientRects(), b, d = '', i, r;
		if ( !sky ) { return; }
		if ( rs.length < 2 ) { sky.style.clipPath = ''; return; }
		b = sky.getBoundingClientRect();
		for ( i = 0; i < rs.length; i++ ) {
			r = rs[ i ];
			if ( r.width < 1 ) { continue; }
			d += 'M' + ( r.left - b.left ).toFixed( 1 ) + ' ' + ( r.top - b.top ).toFixed( 1 ) + 'h' + r.width.toFixed( 1 ) + 'v' + r.height.toFixed( 1 ) + 'h' + ( -r.width ).toFixed( 1 ) + 'Z';
		}
		sky.style.clipPath = d ? 'path("' + d + '")' : '';
	}
	var pend = 0;
	function reclip() {
		if ( pend ) { return; }
		pend = requestAnimationFrame( function () {
			pend = 0;
			multi.forEach( function ( el ) { el.lotmSf2Dirty = true; } );
			// видимые — сразу, остальные — при входе в зону видимости
			var vh = window.innerHeight;
			multi.forEach( function ( el ) {
				var r = el.getBoundingClientRect();
				if ( r.bottom > -200 && r.top < vh + 200 ) { clip( el ); }
			} );
		} );
	}

	function watch( el ) {
		var last = el.classList.contains( 'lotm-revealed' ), goneTimer = 0, animTimer = 0;
		if ( last ) { el.classList.add( 'lotm-fog-gone' ); }
		new MutationObserver( function () {
			var open = el.classList.contains( 'lotm-revealed' );
			if ( open === last ) { return; }
			last = open;
			clearTimeout( goneTimer ); clearTimeout( animTimer );
			el.classList.add( 'lotm-fog-anim' );
			animTimer = setTimeout( function () { el.classList.remove( 'lotm-fog-anim' ); }, 1800 );
			if ( !open ) { el.classList.remove( 'lotm-fog-gone' ); if ( el.lotmSf2Dirty ) { clip( el ); } }
			else { goneTimer = setTimeout( function () { if ( el.classList.contains( 'lotm-revealed' ) ) { el.classList.add( 'lotm-fog-gone' ); } }, 1800 ); }
		} ).observe( el, { attributes: true, attributeFilter: [ 'class' ] } );
	}

	function fogifyInline( el ) {
		if ( el.classList.contains( 'lotm-fog-ready' ) ) { return; }
		var sky = document.createElement( 'span' ), i, c;
		sky.className = 'lotm-fog-sky';
		sky.setAttribute( 'aria-hidden', 'true' );
		for ( i = 1; i <= 3; i++ ) {
			c = document.createElement( 'i' );
			c.className = 'lotm-cloud lotm-cloud-' + i;
			sky.appendChild( c );
		}
		el.appendChild( sky );
		el.lotmSf2Sky = sky;
		el.classList.add( 'lotm-fog-ready', 'lotm-fog-inline' );
		watch( el );
		if ( io ) { io.observe( el ); } else if ( !reduceMotion ) { el.classList.add( 'lotm-fog-live' ); }
		if ( el.getClientRects().length > 1 ) { multi.push( el ); clip( el ); }
	}

	function init( root ) {
		[].forEach.call( ( root || document ).querySelectorAll( '.spoilerblur' ), fogifyInline );
	}
	if ( window.mw && mw.hook ) {
		mw.hook( 'wikipage.content' ).add( function ( $c ) { $c.each( function () { if ( this.querySelectorAll ) { init( this ); } } ); } );
	}
	if ( document.readyState === 'loading' ) { document.addEventListener( 'DOMContentLoaded', function () { init(); } ); } else { init(); }

	window.addEventListener( 'resize', reclip, { passive: true } );
	if ( document.fonts && document.fonts.ready ) { document.fonts.ready.then( reclip ); }
	if ( 'ResizeObserver' in window ) {
		var main = document.querySelector( '#mw-content-text' ) || document.body, first = true;
		new ResizeObserver( function () { if ( first ) { first = false; return; } reclip(); } ).observe( main );
	}
	// lite включился позже (замер кадров): дрейф снимает CSS, здесь лишь гасим .lotm-fog-live для единообразия с блоками
	window.addEventListener( 'lotm-lite', function ( e ) {
		if ( e.detail ) { [].forEach.call( document.querySelectorAll( '.lotm-fog-inline.lotm-fog-live' ), function ( el ) { el.classList.remove( 'lotm-fog-live' ); } ); }
	} );
}() );

/* ===== TABLES — данные-таблицы статей (article-table / wikitable / fandom-table) =====
   Дополнение в конец MediaWiki:Common.js. Пара к TABLES.css.
   Что делает (один проход при загрузке, без постоянных наблюдателей кроме scroll/resize):
   1) оборачивает таблицу в div.lotm-tw > div.lotm-tw-s (прокрутка внутри обёртки, не страницы);
      если Citizen уже обернул .wikitable в .citizen-overflow-wrapper — берёт его обёртку;
   2) размечает шапку (tr.lotm-th, 1-й уровень двухуровневой — .lotm-th1),
      ячейки первой колонки сетки (.lotm-c0, с учётом rowspan/colspan), группы rowspan
      (ячейка .lotm-grp, строка начала группы .lotm-gs), числовые колонки (.lotm-num);
   3) классы состояния обёртки: .lotm-tw--wide (таблица шире обёртки → липкая 1-я колонка),
      .lotm-tw--l / .lotm-tw--r (есть что листать слева/справа → тени-подсказки).
   Не трогает: инфобоксы, навбоксы, .WikiTable (Пути), .lotm-gods-table (Боги), вложенные таблицы. */
( function () {
	'use strict';
	var SEL = 'table.article-table, table.wikitable, table.fandom-table';
	var SKIP = '.portable-infobox, .infobox, .navbox, .lotm-gods-wrap, .lotm-tw, .mw-references-wrap, .va-transcript-text';
	var NUM = /^[~≈±+\-−–]?\s?\d[\d\s .,:\/–—\-]*(\s?(д\.\s?п\.|до\s?н\.\s?э\.|г\.|гг\.|год[а-я]*|лет|%|×|x|шт\.?|[a-zа-яё£€$.]{1,9}))?\s?\*?$/i;
	var EMPTY = /^(—|–|-|\?|н\/д|нет|)$/i;

	function txt( c ) {
		return ( c.textContent || '' ).replace( /\[\d+\]/g, '' ).replace( /\s+/g, ' ' ).trim();
	}

	function eligible( t ) {
		if ( t.classList.contains( 'WikiTable' ) || t.classList.contains( 'lotm-gods-table' ) ||
			t.classList.contains( 'navbox' ) || t.classList.contains( 'infobox' ) || t.classList.contains( 'lotm-t' ) ) {
			return false;
		}
		if ( t.closest( SKIP ) ) {
			return false;
		}
		// вложенная таблица внутри другой таблицы — оформляет родитель
		if ( t.parentElement && t.parentElement.closest( 'table' ) ) {
			return false;
		}
		return t.rows.length >= 2;
	}

	/* сетка: для каждой ячейки — индекс колонки с учётом rowspan/colspan */
	function grid( t ) {
		var rows = t.rows, occ = [], out = [], i, j, r, c, col, rs, cs, a, b;
		for ( i = 0; i < rows.length; i++ ) {
			r = rows[ i ];
			occ[ i ] = occ[ i ] || [];
			col = 0;
			for ( j = 0; j < r.cells.length; j++ ) {
				c = r.cells[ j ];
				while ( occ[ i ][ col ] ) {
					col++;
				}
				rs = Math.max( 1, c.rowSpan || 1 );
				cs = Math.max( 1, c.colSpan || 1 );
				for ( a = 0; a < rs && i + a < rows.length; a++ ) {
					occ[ i + a ] = occ[ i + a ] || [];
					for ( b = 0; b < cs; b++ ) {
						occ[ i + a ][ col + b ] = 1;
					}
				}
				out.push( { c: c, row: i, col: col, rs: rs, cs: cs } );
				col += cs;
			}
		}
		return out;
	}

	function mark( t ) {
		var rows = t.rows, nHead = 0, i, j, r, allTh, cells, byCol = {}, k, g, n, ok, list, hasGrp = false;
		t.classList.add( 'lotm-t' );
		cells = grid( t );
		var ncol = 0, start = 0, lead = [];
		for ( k = 0; k < cells.length; k++ ) {
			ncol = Math.max( ncol, cells[ k ].col + cells[ k ].cs );
		}
		// строки-разделы: одна th на всю ширину («Номиналы», «Банкноты и монеты»)
		for ( k = 0; k < cells.length; k++ ) {
			g = cells[ k ];
			if ( ncol > 1 && g.cs >= ncol && g.c.tagName === 'TH' ) {
				g.c.classList.add( 'lotm-sec' );
				rows[ g.row ].classList.add( g.row === 0 ? 'lotm-tcap' : 'lotm-secr' );
			}
		}
		if ( rows[ 0 ].classList.contains( 'lotm-tcap' ) ) {
			start = 1;
			rows[ 0 ].classList.add( 'lotm-th' );
		}
		// шапка: ведущие строки (после подписи), где все ячейки — th и их больше одной
		for ( i = start; i < rows.length && i < start + 3; i++ ) {
			r = rows[ i ];
			allTh = r.cells.length > 0 && !r.classList.contains( 'lotm-secr' );
			for ( j = 0; allTh && j < r.cells.length; j++ ) {
				if ( r.cells[ j ].tagName !== 'TH' ) {
					allTh = false;
				}
			}
			if ( !allTh ) {
				break;
			}
			lead.push( r );
		}
		// одиночная строка шапки без тела под ней (таблица-карточка) — не шапка
		nHead = start + lead.length;
		for ( i = 0; i < lead.length; i++ ) {
			lead[ i ].classList.add( 'lotm-th' );
			if ( i < lead.length - 1 ) {
				lead[ i ].classList.add( 'lotm-th1' );
			}
		}
		if ( lead.length ) {
			lead[ lead.length - 1 ].classList.add( 'lotm-thl' );
		} else if ( start ) {
			rows[ 0 ].classList.add( 'lotm-thl' );
		}
		for ( k = 0; k < cells.length; k++ ) {
			g = cells[ k ];
			if ( g.row < nHead ) {
				continue;
			}
			if ( g.col === 0 && g.cs < ncol ) {
				g.c.classList.add( 'lotm-c0' );
				if ( g.rs > 1 && g.rs < rows.length - nHead + 1 ) {
					g.c.classList.add( 'lotm-grp' );
					hasGrp = true;
				}
			}
			if ( g.cs === 1 ) {
				( byCol[ g.col ] = byCol[ g.col ] || [] ).push( g.c );
			}
		}
		// первая колонка и в шапке (для липкости)
		for ( k = 0; k < cells.length; k++ ) {
			if ( cells[ k ].row < nHead && cells[ k ].col === 0 && cells[ k ].cs < ncol ) {
				cells[ k ].c.classList.add( 'lotm-c0' );
			}
		}
		if ( hasGrp ) {
			t.classList.add( 'lotm-t--grp' );
			for ( k = 0; k < cells.length; k++ ) {
				if ( cells[ k ].row >= nHead && cells[ k ].col === 0 && cells[ k ].cs < ncol ) {
					cells[ k ].c.classList.add( 'lotm-grp' );
				}
			}
			for ( i = nHead; i < rows.length; i++ ) {
				if ( rows[ i ].cells.length && rows[ i ].cells[ 0 ].classList.contains( 'lotm-c0' ) ) {
					rows[ i ].classList.add( 'lotm-gs' );
				}
			}
		}
		// числовые колонки: ≥ 60 % непустых ячеек — числа/даты, и таких ≥ 2
		for ( k in byCol ) {
			list = byCol[ k ];
			n = 0;
			ok = 0;
			for ( j = 0; j < list.length; j++ ) {
				var s = txt( list[ j ] );
				if ( EMPTY.test( s ) || list[ j ].tagName === 'TH' ) {
					continue;
				}
				n++;
				if ( s.length <= 24 && NUM.test( s ) && !list[ j ].querySelector( 'img, ul, ol, table, br' ) ) {
					ok++;
				}
			}
			if ( ok >= Math.min( 2, n ) && ok >= 1 && ok >= n * 0.6 && k !== '0' ) {
				for ( j = 0; j < list.length; j++ ) {
					if ( list[ j ].tagName === 'TD' ) {
						list[ j ].classList.add( 'lotm-num' );
					}
				}
			}
		}
	}

	function wrap( t ) {
		var p = t.parentElement, w, s;
		if ( p && p.classList.contains( 'citizen-overflow-content' ) && p.parentElement ) {
			w = p.parentElement;
			s = p;
			w.classList.add( 'lotm-tw', 'lotm-tw--cz' );
		} else {
			// Citizen ещё не обернул (его обработчик хука может сработать позже) — запрещаем двойную обёртку
			t.classList.add( 'citizen-table-nowrap' );
			w = document.createElement( 'div' );
			w.className = 'lotm-tw';
			s = document.createElement( 'div' );
			s.className = 'lotm-tw-s';
			p.insertBefore( w, t );
			w.appendChild( s );
			s.appendChild( t );
		}
		s.classList.add( 'lotm-tw-s' );
		return { w: w, s: s, t: t };
	}

	function state( o ) {
		var s = o.s, sw = s.scrollWidth, cw = s.clientWidth, x = Math.abs( s.scrollLeft ), wide = sw > cw + 1, c0;
		o.w.classList.toggle( 'lotm-tw--wide', wide && o.stick );
		o.w.classList.toggle( 'lotm-tw--l', wide && x > 1 );
		o.w.classList.toggle( 'lotm-tw--r', wide && x + cw < sw - 1 );
		if ( wide && o.stick === undefined ) {
			// липкая 1-я колонка — только если она не шире ~40 % видимой области
			c0 = o.t.querySelector( 'td.lotm-c0, th.lotm-c0' );
			o.stick = !!c0 && c0.offsetWidth <= cw * 0.42;
			o.w.classList.toggle( 'lotm-tw--wide', o.stick );
		}
	}

	function init( root ) {
		var list = ( root || document ).querySelectorAll( '.mw-parser-output ' + SEL.split( ', ' ).join( ', .mw-parser-output ' ) ),
			todo = [], i, o, items = [], raf = 0;
		for ( i = 0; i < list.length; i++ ) {
			if ( eligible( list[ i ] ) ) {
				todo.push( list[ i ] );
			}
		}
		if ( !todo.length ) {
			return;
		}
		// чтение: какие таблицы были во всю ширину колонки (width=100% и т. п.)
		var full = todo.map( function ( t ) {
			var p = t.closest( '.citizen-overflow-wrapper' ) || t, pw = p.parentElement ? p.parentElement.clientWidth : 0;
			return pw > 0 && p.offsetWidth >= pw - 4;
		} );
		// запись
		for ( i = 0; i < todo.length; i++ ) {
			mark( todo[ i ] );
			o = wrap( todo[ i ] );
			if ( full[ i ] ) {
				o.w.classList.add( 'lotm-tw--full' );
			}
			items.push( o );
		}
		function upd() {
			raf = 0;
			items.forEach( state );
		}
		function req() {
			if ( !raf ) {
				raf = requestAnimationFrame( upd );
			}
		}
		items.forEach( function ( it ) {
			it.s.addEventListener( 'scroll', function () {
				if ( !it.raf ) {
					it.raf = requestAnimationFrame( function () {
						it.raf = 0;
						state( it );
					} );
				}
			}, { passive: true } );
		} );
		window.addEventListener( 'resize', req, { passive: true } );
		// раскрытие свёрнутых таблиц (mw-collapsible) меняет ширину
		document.addEventListener( 'click', function ( e ) {
			if ( e.target && e.target.closest && e.target.closest( '.mw-collapsible-toggle, .mw-collapsible-text' ) ) {
				setTimeout( req, 50 );
			}
		}, { passive: true } );
		req();
	}

	if ( window.mw && mw.hook ) {
		mw.hook( 'wikipage.content' ).add( function ( $c ) {
			// Citizen оборачивает .wikitable в своём обработчике того же хука — дождёмся его
			setTimeout( function () {
				init( $c && $c[ 0 ] );
			}, 0 );
		} );
	} else if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', function () {
			init();
		} );
	} else {
		init();
	}
}() );

/* TOCFIX2: переход по якорю (пункт «Содержания», ссылка #…) на заголовок внутри неактивной вкладки .wds-tabber -
   сначала открываем нужную вкладку (синхронно, до штатной прокрутки к якорю), потом докручиваем к заголовку.
   После любого переключения вкладки просим scroll-spy Citizen пересчитать активный пункт (событие scroll).
   Плюс: scroll-spy Citizen наблюдает заголовки без id (.mw-heading или h2…h6 с id на вложенном .mw-headline) и сравнивает новый активный набор со старым по el.id -
   все id пустые, поэтому набор той же длины считается «не изменился» и подсветка застревает. Даём им уникальное
   свойство el.id (lotm-sec-<id заголовка>) без атрибута - селекторы, якоря и TOC это не затрагивает. */
( function () {
	function tagHeadings( root ) {
		Array.prototype.forEach.call( ( root || document ).querySelectorAll( '.mw-parser-output :is(.mw-heading, h2, h3, h4, h5, h6):not([id])' ), function ( w ) {
			var h = w.querySelector( '.mw-headline[id], h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]' );
			if ( !h || Object.prototype.hasOwnProperty.call( w, 'id' ) ) { return; }
			/* только свойство el.id, без атрибута: Citizen выбирает наблюдаемые заголовки селектором h2:not([id]) */
			var key = 'lotm-sec-' + h.id;
			Object.defineProperty( w, 'id', {
				configurable: true,
				get: function () { return this.getAttribute( 'id' ) || key; },
				set: function ( v ) { delete this.id; this.setAttribute( 'id', v ); }
			} );
		} );
	}
	tagHeadings();
	if ( window.mw && mw.hook ) { mw.hook( 'wikipage.content' ).add( function ( $c ) { tagHeadings( $c && $c[ 0 ] ); } ); }
	function respy() {
		setTimeout( function () { window.dispatchEvent( new Event( 'scroll' ) ); }, 0 );
	}
	function open( hash, scroll ) {
		if ( !hash || hash.length < 2 ) { return false; }
		var id;
		try { id = decodeURIComponent( hash.slice( 1 ) ); } catch ( e ) { id = hash.slice( 1 ); }
		var t = document.getElementById( id );
		var panel = t && t.closest( '.wds-tab__content' );
		if ( !panel || panel.classList.contains( 'wds-is-current' ) ) { return false; }
		/* вложенные вкладки: открываем все неактивные предки по цепочке */
		var p = panel.parentElement && panel.parentElement.closest( '.wds-tab__content' );
		if ( p && !p.classList.contains( 'wds-is-current' ) ) { open( '#' + ( p.querySelector( '[id]' ) || {} ).id ); }
		var tabber = panel.parentElement;
		var panels = Array.prototype.filter.call( tabber.children, function ( el ) { return el.classList.contains( 'wds-tab__content' ); } );
		var tabs = Array.prototype.filter.call( tabber.querySelectorAll( '.wds-tabs__tab' ), function ( el ) { return el.closest( '.wds-tabber' ) === tabber; } );
		var tab = tabs[ panels.indexOf( panel ) ];
		if ( !tab ) { return false; }
		tab.click();
		if ( !panel.classList.contains( 'wds-is-current' ) ) {
			/* скрипт вкладок (MediaWiki:Citizen.js) не отреагировал или не загружен - переключаем классы сами */
			tabs.forEach( function ( el ) { el.classList.toggle( 'wds-is-current', el === tab ); } );
			panels.forEach( function ( el ) { el.classList.toggle( 'wds-is-current', el === panel ); } );
		}
		if ( scroll ) {
			requestAnimationFrame( function () {
				var h = t.closest( '.mw-heading, h2, h3, h4, h5, h6' ) || t;
				h.scrollIntoView( { block: 'start' } );
			} );
		}
		return true;
	}
	document.addEventListener( 'click', function ( e ) {
		if ( !e.target.closest ) { return; }
		var a = e.target.closest( 'a[href^="#"]' );
		if ( a ) {
			/* синхронно: штатная прокрутка к якорю идёт уже к открытой вкладке; докрутка в rAF - на случай,
			   если хэш не изменился и браузер не прокрутил */
			open( a.getAttribute( 'href' ), true );
		}
		if ( e.target.closest( '.wds-tabs__tab' ) ) { respy(); }
	}, true );
	document.addEventListener( 'keydown', function ( e ) {
		if ( ( e.key === 'Enter' || e.key === ' ' ) && e.target.closest && e.target.closest( '.wds-tabs__tab' ) ) { respy(); }
	}, true );
	window.addEventListener( 'hashchange', function () { open( location.hash, true ); } );
	if ( location.hash ) { setTimeout( function () { open( location.hash, true ); }, 500 ); }
}() );

/* TOCVERIFY: переход из «Содержания» к заголовку в скрытой вкладке - докрутка TOCFIX2 (scrollIntoView самого h2/h3)
   на телефоне ставит заголовок на 73 px ниже, чем штатный переход по якорю (на ПК - на 1 px). Повторяем докрутку
   к самому якорю (элемент с id), как это делает браузер: наш rAF зарегистрирован после rAF TOCFIX2 и выполняется после него. */
( function () {
	document.addEventListener( 'click', function ( e ) {
		var a = e.target.closest && e.target.closest( 'a[href^="#"]' );
		if ( !a ) { return; }
		var id, href = a.getAttribute( 'href' );
		try { id = decodeURIComponent( href.slice( 1 ) ); } catch ( err ) { id = href.slice( 1 ); }
		var t = id && document.getElementById( id );
		if ( !t || !t.closest( '.wds-tab__content' ) ) { return; }
		requestAnimationFrame( function () { t.scrollIntoView( { block: 'start' } ); } );
	}, true );
}() );

/* Волна 33 — MERGED33.js (ревизор R1). Добавка поверх /tmp/dep_current/Common.js. */

/* C4-07 (fix/C4.js): у ссылок-перенаправлений на английские названия (Visionary Pathway, Fool Pathway/Abilities…)
   браузер показывал английскую всплывающую подсказку (title). ИСХОДНЫЙ fix/C4.js удалял title — это ЛОМАЛО превью ссылок
   (Popups выбирает только a[title]; R1: на «Путь Шута»/«Клейн Моретти» превью у перенаправлений пропадало). Здесь title заменяется
   русским текстом ссылки. Проверено: превью работают, латинских title на «Путь Шута» 193 → 0 (кроме ссылок без кириллицы в тексте). */
mw.hook( 'wikipage.content' ).add( function ( $c ) {
	var root = ( $c && $c[ 0 ] ) || document;
	if ( !root.querySelectorAll ) { return; }
	[].forEach.call( root.querySelectorAll( 'a.mw-redirect[title]' ), function ( a ) {
		var t = a.getAttribute( 'title' );
		var txt = ( a.textContent || '' ).replace( /\s+/g, ' ' ).trim();
		/* title НЕ удаляем: превью ссылок (Popups) берёт только a[title]; без него превью пропадает (проверено R1).
		   Подменяем английский title русским текстом ссылки — превью строится по href, ему значение title не важно. */
		if ( /[A-Za-z]/.test( t ) && !/[А-Яа-яЁё]/.test( t ) && /[А-Яа-яЁё]/.test( txt ) ) { a.setAttribute( 'title', txt ); }
	} );
} );

/* C10-2: пустой абзац <p><br></p> первым/последним в блоке или сразу после заголовка — помечаем, CSS скрывает. */
mw.hook( 'wikipage.content' ).add( function ( $c ) {
	var root = ( $c && $c[ 0 ] ) || document;
	if ( !root.querySelectorAll ) { return; }
	[].forEach.call( root.querySelectorAll( 'p' ), function ( p ) {
		if ( p.textContent.trim() !== '' ) { return; }
		var k = p.children, i, prev, next;
		if ( !k.length ) { return; }
		for ( i = 0; i < k.length; i++ ) { if ( k[ i ].tagName !== 'BR' ) { return; } }
		prev = p.previousElementSibling; next = p.nextElementSibling;
		if ( !prev || !next || /^H[1-6]$/.test( prev.tagName ) || prev.classList.contains( 'mw-heading' ) ) {
			p.classList.add( 'lotm-emptyp' );
		}
	} );
} );