Версия от 17:19, 22 августа 2026; Kingdice (обсуждение | вклад) (Исправлен загрузчик разделов для MediaWiki 1.43 без удалённого jquery.spinner)

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

  • Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
  • Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
  • Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
  • Opera: Нажмите Ctrl+F5.
/* Variables for interface text used throughout the script, for ease of translating */
var i18n = {
    // Collapsible elements and page loader
    hideText: 'скрыть',
    showText: 'показать',
    
    // Page loader
    loadErrorTitle: 'Возникла ошибка при загрузке содержимого'
};

/**
 * Instead of cluttering up the global scope with
 * variables, they should instead be set as a
 * property of this global variable
 *
 * E.g: Instead of
 *   myVar = 'blah';
 * use
 *   mcw.myVar = 'blah';
 */
var mcw = window.mcw = {};

/* Keep track of delegated events on dynamic content */
mcw.events = {};

// Добавляет ссылку для редактирования нулевого раздела (введения; преамбулы)
    var wgNamespaceNumber = mw.config.get( 'wgNamespaceNumber' );
    var wgPageName = mw.config.get( 'wgPageName' );

    if (wgNamespaceNumber > -1){ 
        var edittopHTML = '<span class="mw-editsection">' +
        '<span class="mw-editsection-bracket" style="margin-right: 0.25em;color: #555555;">[</span>' +
        '<a href="/index.php?title=' + wgPageName + '&amp;action=edit&amp;section=0" title="Править «введение» этой страницы">править введение</a>' +
        '<span class="mw-editsection-bracket" style="margin-left: 0.25em;color: #555555;">]</span>' +
        '</span>';
        $('#firstHeading').append(edittopHTML);
    }

// Честно стащено с бывшей официальной Minecraft Wiki
// Добавляет кнопку для очистки кэша
if ( !$( '#ca-purge' ).length ) {
    $( '#p-cactions' ).find( 'ul' ).append(
        $( '<li>' ).attr( { 'id': 'ca-purge', 'class': 'mw-list-item' } ).append(
            $( '<a>' ).attr( { 'href': mw.util.getUrl( null, { action: 'purge' } ), 'title': 'Очистить серверный кэш страницы' } ).append(
                $( '<span>' ).text( 'Очистить кэш' )
            )
        )
    );
}
// Если кнопка «Ещё» у нас скрыта, отображаем её, но не на спецстраницах.
if ($( '#p-cactions' ).hasClass( 'emptyPortlet' ) & !$( '#p-views' ).hasClass( 'emptyPortlet' )){
	$( '#p-cactions' ).removeClass( 'emptyPortlet' )
}
// Меняем обычную ссылку очистки кэша на API-запрос, чтобы избежать окна подтверждения
mw.loader.using( 'mediawiki.api' ).then( function() {
    $( '#ca-purge a' ).on( 'click', function( e ) {
        new mw.Api().post( {
            action: 'purge',
            titles: mw.config.get( 'wgPageName' )
        } ).then( function() {
            location.reload();
        }, function() {
            mw.notify( 'Purge failed', { type: 'error' } );
        } );
        
        e.preventDefault();
    } );
} );

// from Minecraft Wiki
/* Добавляет кнопку «Вверх» слева */
function scrollTop(){
    $(window).scroll(function(e) {
	    if($(window).scrollTop()>0) {
	    	$("#scroll-top").fadeIn(300);
	    } else{ 
	    	$("#scroll-top").fadeOut(300);
	    }
    });
}

/* Fired whenever wiki content is added. (#mw-content-text, live preview, load page, etc.) */
mw.hook( 'wikipage.content' ).add( function( $wikipageContent ) {

$(function(){
	$("footer#footer").append($("<span></span>")
		.attr({
			"id": "scroll-top",
			"title": "Наверх"
		})
	);
	$("#scroll-top").click(function(e) {
		$('body,html').animate({scrollTop:0},300);
		});
	scrollTop();
});

// Только при редактировании, вне режима визуального редактора добавляем викификатор или панель редактирования в целом.
if ( mw.config.get( 'wgAction' ) == 'edit' || mw.config.get( 'wgAction' ) == 'submit' ) {
	console.log('MediaWiki:Common.js — editFunction start');
	if ( !mw.user.options.get( 'usebetatoolbar' ) || mw.user.options.get( 'usebetatoolbar' ) == '0' || mw.user.options.get( 'usebetatoolbar' ) == '' ) {
		mw.loader.load('MediaWiki:LegacyToolbar.js?action=raw&ctype=text/javascript');
		console.log('MediaWiki:Common.js — legacyToolbar enabled');
	}
	else {
		mw.loader.load( 'MediaWiki:Wikificator.js?action=raw&ctype=text/javascript' );
		console.log('MediaWiki:Common.js — Wikificator enabled with betatoolbar');
	};
	console.log('MediaWiki:Common.js — after Wikificator call msg');
}

/**
 * Page loader
 *
 * Allows a page to be downloaded and displayed on demand.
 * Use with Template:LoadPage and Template:LoadBox
 */

( function() {
	var $loadPage = $wikipageContent.find( '.load-page' );
	if ( !$loadPage.length ) {
		return;
	}
	
	// We need the spinner to show loading is happening, but we don't want
	// to have a delay while the module downloads, so we'll load this now,
	// regardless of if something is clicked
	// jquery.spinner was removed from newer MediaWiki versions. The loader
	// uses a small local indicator instead, so a missing module cannot abort
	// initialization of all recipe buttons.
	
	// Create button starting with hide text
	// Will be changed to the show text while calculating the maximum button size
	var $buttonTemplate = $( '<span>' ).addClass( 'mw-editsection-like load-page-button' )
		.append( '[ ', $( '<span>' ).addClass( 'jslink' ).text( i18n.hideText ), ' ]' );
	
	var extractList = function( $contentContainer, listClass ) {
		var $content = $contentContainer.find( '.mw-parser-output > ul > li > ul' ).children( ':not(.nbttree-inherited)' );
		if ( listClass ) {
			$content.addClass( listClass );
		}
		
		return $content;
	};
	
	$loadPage.each( function() {
		var $body = $( this );
		var page = $body.data( 'page' );
		if ( !page ) {
			return;
		}
		
		var template = $body.data( 'template' );
		var treeview = $body.data( 'treeview' );
		var treeviewClass = $body.data( 'treeviewclass' );
		var $heading;
		var $contentContainer;
		var $content;
		var $button = $buttonTemplate.clone();
		var $buttonLink = $button.find( '.jslink' );
		if ( treeview ) {
			$heading = $body;
			$contentContainer = $( '<div>' );
		} else {
			$heading = $body.children().first();
			$contentContainer = $body.find( '.load-page-content' );
		}
		
		// Add the button
		$heading.append( $button );
		
		// Move the edit button to the right spot
		$contentContainer.find( '.mw-editsection, .mw-editsection-like' ).insertAfter( $button );
		
		// Find max button width, and set its min-width to it
		var hideWidth = $button.width();
		$buttonLink.text( i18n.showText );
		var showWidth = $button.width();
		
		if ( hideWidth !== showWidth ) {
			$button.css( 'min-width', hideWidth > showWidth ? hideWidth : showWidth );
		}
		
		$buttonLink.click( function() {
			if ( $body.hasClass( 'pageloader-contentloaded' ) ) {
				if ( $buttonLink.text() === i18n.showText ) {
					if ( treeview ) {
						$content.insertAfter( $body );
					} else {
						$contentContainer.show();
					}
					$buttonLink.text( i18n.hideText );
				} else {
					if ( treeview ) {
						$content.detach();
					} else {
						$contentContainer.hide();
					}
					$buttonLink.text( i18n.showText );
				}
				return;
			}
			
			// See if this was loaded elsewhere before making a request
			var gotContent;
			$( '.pageloader-contentloaded' ).each( function() {
				var $fLoader = $( this );
				if ( $fLoader.data( 'page' ) === page && $fLoader.data( 'pageloader-content' ) ) {
					$contentContainer.html( $fLoader.data( 'pageloader-content' ) ).removeClass( 'noscript' );
					mw.hook( 'wikipage.content' ).fire( $contentContainer );
					
					if ( treeview ) {
						$body.find( '.noscript' ).remove();
						$content = extractList( $contentContainer, treeviewClass );
						$content.insertAfter( $body );
					}
					
					$buttonLink.text( i18n.hideText );
					$body.addClass( 'pageloader-contentloaded' );
					gotContent = true;
					return false;
				}
			} );
			if ( gotContent ) {
				return;
			}
			
			var $spinner = $( '<span>' )
				.addClass( 'mw-editsection-like load-page-spinner' )
				.css( 'min-width', $button.css( 'min-width' ) )
				.text( '…' );
			$button.hide().after( $spinner );
			
			var requestData = {
				action: 'parse',
				prop: 'text'
			};
			if ( template ) {
				requestData.page = page;
			} else {
				requestData.title = mw.config.get( 'wgPageName' );
				requestData.text = '{' + '{:' + page + '}}';
			}
			new mw.Api().get( requestData ).done( function( data ) {
				var html = data.parse.text['*'];
				$contentContainer.html( html ).removeClass( 'noscript' );
				
				// Resolve self-links
				if ( template ) {
					var curPage = '/' + mw.config.get( 'wgPageName' );
					$contentContainer.find( 'a' ).each( function() {
						var $link = $( this );
						if ( $link.attr( 'href' ) === curPage ) {
							$link.replaceWith( $( '<strong>' ).addClass( 'selflink' ).append( $link.contents() ) );
						}
					} );
					html = $contentContainer.html();
				}
				
				$body.data( 'pageloader-content', html );
				
				// Fire content hook on the new content, running all this stuff again and more :)
				mw.hook( 'wikipage.content' ).fire( $contentContainer );
				
				if ( treeview ) {
					$body.find( '.noscript' ).remove();
					$content = extractList( $contentContainer, treeviewClass );
					$content.insertAfter( $body );
				}
				
				$spinner.remove();
				$spinner = false;
				$buttonLink.text( i18n.hideText );
				$button.show();
				$body.addClass( 'pageloader-contentloaded' );
			} ).fail( function( _, error ) {
				$spinner.remove();
				$spinner = false;
				$button.show();
				
				var errorText = '';
				if ( error.textStatus ) {
					errorText = error.textStatus;
				} else if ( error.error ) {
					errorText = error.error.info;
				}
				
				mw.notify( errorText, { title: i18n.loadErrorTitle, autoHide: false } );
			} );
		} );
	} );
}() );

/**
 * Element animator
 *
 * Will cycle the active class on any child elements
 * within an element with the animated class.
 */
( function() {
	if ( !mcw.animate ) {
		mcw.animate = setInterval( function() {
			$( '.animated' ).each( function() {
				var $elem = $( this );
				var $current = $elem.children( '.active' );
				var $next = $current.nextAll( ':not(.skip):first' );
				// Loop back to the start
				if ( !$next.length ) {
					$next = $elem.children( ':not(.skip):first' );
				}
				$current.removeClass( 'active' );
				$next.addClass( 'active' );
			} );
		}, 2000 );
	}
}() );

} );
/* End wiki content hook */

/* === Dark Paradise: Pharmacology catalog v1 START === */
(function () {
    'use strict';

    function normalize(value) {
        return String(value || '').toLocaleLowerCase('ru-RU').replace(/\s+/g, ' ').trim();
    }

    function setCardOpen(card, open) {
        var summary = card.querySelector('.pharma-card__summary');
        card.classList.toggle('is-open', open);
        if (summary) {
            summary.setAttribute('aria-expanded', open ? 'true' : 'false');
        }
    }

    function initPharmaCatalog(root) {
        if (!root || root.getAttribute('data-pharma-ready') === 'true') {
            return;
        }

        var grid = root.querySelector('.pharma-catalog__grid');
        var cards = Array.prototype.slice.call(root.querySelectorAll('.pharma-card'));
        if (!grid || !cards.length) {
            return;
        }

        root.setAttribute('data-pharma-ready', 'true');

        cards.forEach(function (card) {
            var summary = card.querySelector('.pharma-card__summary');
            if (!summary) {
                return;
            }

            summary.addEventListener('click', function (event) {
                if (event.target.closest('a')) {
                    return;
                }
                setCardOpen(card, !card.classList.contains('is-open'));
            });

            summary.addEventListener('keydown', function (event) {
                if (event.key === 'Enter' || event.key === ' ') {
                    event.preventDefault();
                    setCardOpen(card, !card.classList.contains('is-open'));
                }
            });
        });

        var toolbar = document.createElement('div');
        toolbar.className = 'pharma-catalog__toolbar';
        toolbar.setAttribute('role', 'search');

        var searchWrap = document.createElement('div');
        searchWrap.className = 'pharma-catalog__search-wrap';

        var search = document.createElement('input');
        search.className = 'pharma-catalog__search';
        search.type = 'search';
        search.placeholder = 'Название, эффект или ингредиент…';
        search.setAttribute('aria-label', 'Поиск реагента по названию, эффекту или ингредиенту');
        searchWrap.appendChild(search);

        var filters = document.createElement('div');
        filters.className = 'pharma-catalog__filters';
        var filterItems = [
            ['all', 'Все'],
            ['heat', 'С нагревом'],
            ['botany', 'Ботаника'],
            ['external', 'Внешние'],
            ['danger', 'Опасные']
        ];

        filterItems.forEach(function (item, index) {
            var button = document.createElement('button');
            button.type = 'button';
            button.className = 'pharma-catalog__filter';
            button.textContent = item[1];
            button.setAttribute('data-pharma-filter', item[0]);
            button.setAttribute('aria-pressed', index === 0 ? 'true' : 'false');
            filters.appendChild(button);
        });

        var meta = document.createElement('div');
        meta.className = 'pharma-catalog__meta';

        var status = document.createElement('span');
        status.setAttribute('aria-live', 'polite');

        var actions = document.createElement('span');
        actions.className = 'pharma-catalog__actions';

        var openAll = document.createElement('button');
        openAll.type = 'button';
        openAll.className = 'pharma-catalog__action';
        openAll.textContent = 'Раскрыть найденные';

        var closeAll = document.createElement('button');
        closeAll.type = 'button';
        closeAll.className = 'pharma-catalog__action';
        closeAll.textContent = 'Свернуть все';

        actions.appendChild(openAll);
        actions.appendChild(closeAll);
        meta.appendChild(status);
        meta.appendChild(actions);

        toolbar.appendChild(searchWrap);
        toolbar.appendChild(filters);
        toolbar.appendChild(meta);
        root.insertBefore(toolbar, grid);

        var empty = document.createElement('div');
        empty.className = 'pharma-catalog__empty';
        empty.textContent = 'Ничего не найдено. Попробуйте другое название, эффект или ингредиент.';
        empty.hidden = true;
        grid.appendChild(empty);

        var activeFilter = 'all';

        function applyFilters() {
            var query = normalize(search.value);
            var visible = 0;

            cards.forEach(function (card) {
                var tags = normalize(card.getAttribute('data-pharma-tags')).split(' ');
                var matchesFilter = activeFilter === 'all' || tags.indexOf(activeFilter) !== -1;
                var matchesSearch = !query || normalize(card.textContent).indexOf(query) !== -1;
                var show = matchesFilter && matchesSearch;
                card.hidden = !show;
                if (!show) {
                    setCardOpen(card, false);
                }
                if (show) {
                    visible += 1;
                }
            });

            status.textContent = 'Показано ' + visible + ' из ' + cards.length;
            empty.hidden = visible !== 0;
        }

        search.addEventListener('input', applyFilters);
        search.addEventListener('keydown', function (event) {
            if (event.key === 'Escape' && search.value) {
                search.value = '';
                applyFilters();
            }
        });

        filters.addEventListener('click', function (event) {
            var button = event.target.closest('[data-pharma-filter]');
            if (!button) {
                return;
            }
            activeFilter = button.getAttribute('data-pharma-filter');
            Array.prototype.forEach.call(filters.querySelectorAll('[data-pharma-filter]'), function (item) {
                item.setAttribute('aria-pressed', item === button ? 'true' : 'false');
            });
            applyFilters();
        });

        openAll.addEventListener('click', function () {
            cards.forEach(function (card) {
                if (!card.hidden) {
                    setCardOpen(card, true);
                }
            });
        });

        closeAll.addEventListener('click', function () {
            cards.forEach(function (card) {
                setCardOpen(card, false);
            });
        });

        applyFilters();
    }

    function bootPharmaCatalogs() {
        Array.prototype.forEach.call(document.querySelectorAll('.pharma-catalog'), initPharmaCatalog);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', bootPharmaCatalogs);
    } else {
        bootPharmaCatalogs();
    }
}());
/* === Dark Paradise: Pharmacology catalog v1 END === */

/* === Dark Paradise: Pharmacology full catalog v2 START === */
(function () {
    'use strict';

    var PAGE_CLASS = 'page-Руководство_по_фармакологии';

    function normalizePharmaText(value) {
        return String(value || '').toLocaleLowerCase('ru-RU').replace(/\s+/g, ' ').trim();
    }

    function cleanHeading(value) {
        return String(value || '').replace(/\s*править\s*править код\s*$/i, '').trim();
    }

    function makeElement(tag, className, text) {
        var node = document.createElement(tag);
        if (className) {
            node.className = className;
        }
        if (typeof text === 'string') {
            node.textContent = text;
        }
        return node;
    }

    function cellText(cell) {
        return cell ? String(cell.textContent || '').replace(/\s+/g, ' ').trim() : '';
    }

    function cellHtml(cell) {
        return cell ? cell.innerHTML : '';
    }

    function sectionInfo(title) {
        var value = normalizePharmaText(title);
        if (value.indexOf('чистые химические') !== -1) {
            return { key: 'pure', label: 'Вещества', color: '#91a3b5' };
        }
        if (value.indexOf('компонент') !== -1) {
            return { key: 'components', label: 'Компонент', color: '#d7a52a' };
        }
        if (value.indexOf('лекар') !== -1 || value === 'особые') {
            return { key: 'medicine', label: 'Медицина', color: '#58c5b7' };
        }
        if (value.indexOf('наркот') !== -1) {
            return { key: 'narcotics', label: 'Наркотик', color: '#a978e8' };
        }
        if (value.indexOf('пиротех') !== -1) {
            return { key: 'pyro', label: 'Пиротехника', color: '#ef9b46' };
        }
        if (value.indexOf('яды синдиката') !== -1) {
            return { key: 'syndicate', label: 'Яд Синдиката', color: '#e05a62' };
        }
        if (value.indexOf('токсин') !== -1) {
            return { key: 'toxins', label: 'Токсин', color: '#e05a62' };
        }
        if (value.indexOf('ботаник') !== -1) {
            return { key: 'botany', label: 'Ботаника', color: '#70bd68' };
        }
        if (value.indexOf('еда') !== -1 || value.indexOf('еды') !== -1) {
            return { key: 'food', label: 'Еда', color: '#4fc2c8' };
        }
        if (value.indexOf('красящ') !== -1) {
            return { key: 'paint', label: 'Краситель', color: '#d879c9' };
        }
        return { key: 'other', label: 'Реагент', color: '#d7a52a' };
    }

    function snippet(value, fallback) {
        var text = String(value || '').replace(/\s+/g, ' ').trim();
        if (!text || text === '—' || text.toLocaleLowerCase('ru-RU') === 'нет') {
            return fallback || 'Откройте карточку, чтобы посмотреть свойства реагента.';
        }
        return text.length > 175 ? text.slice(0, 172).trim() + '…' : text;
    }

    function tagsFor(data, info) {
        var value = normalizePharmaText([
            data.recipeText,
            data.shortText,
            data.fullText,
            data.section
        ].join(' '));
        var tags = [info.key];

        if (/нагре|температур|°\s*k|кельвин/.test(value)) {
            tags.push('heat');
        }
        if (/ботаник|растени|семен|выращ/.test(value) || info.key === 'botany') {
            tags.push('botany');
        }
        if (/внешн|аплинк|техтоннел|нельзя изготовить|можно найти|выращ/.test(value)) {
            tags.push('external');
        }
        if (/передоз|токсин|яд|взрыв|воспламен|оглуш|остановк.*серд|урон/.test(value) ||
                ['narcotics', 'pyro', 'toxins', 'syndicate'].indexOf(info.key) !== -1) {
            tags.push('danger');
        }
        return tags.filter(function (tag, index, list) {
            return list.indexOf(tag) === index;
        }).join(' ');
    }

    function setAutoCardOpen(card, open) {
        var summary = card.querySelector('.pharma-card__summary');
        card.classList.toggle('is-open', open);
        if (summary) {
            summary.setAttribute('aria-expanded', open ? 'true' : 'false');
        }
    }

    function makeFact(label, value) {
        var fact = makeElement('span', 'pharma-card__fact');
        fact.appendChild(makeElement('span', '', label + ':'));
        fact.appendChild(makeElement('strong', '', value));
        return fact;
    }

    function makeAutoCard(data, info) {
        var card = makeElement('div', 'pharma-card pharma-card--auto');
        card.setAttribute('data-pharma-category', info.key);
        card.setAttribute('data-pharma-tags', tagsFor(data, info));
        card.style.setProperty('--pharma-accent', info.color);

        var summary = makeElement('div', 'pharma-card__summary');
        summary.setAttribute('role', 'button');
        summary.setAttribute('tabindex', '0');
        summary.setAttribute('aria-expanded', 'false');

        var top = makeElement('div', 'pharma-card__top');
        var identity = makeElement('div', 'pharma-card__identity');
        var swatch = makeElement('span', 'pharma-card__swatch');
        swatch.style.backgroundColor = info.color;

        var name = makeElement('div', 'pharma-card__auto-name');
        name.innerHTML = data.nameHtml;
        identity.appendChild(swatch);
        identity.appendChild(name);
        top.appendChild(identity);
        top.appendChild(makeElement('span', 'pharma-card__type', info.label));

        summary.appendChild(top);
        summary.appendChild(makeElement('div', 'pharma-card__short', snippet(data.shortText, data.fallbackShort)));

        if (data.recipeHtml) {
            var recipe = makeElement('div', 'pharma-card__recipe');
            recipe.appendChild(makeElement('span', 'pharma-card__label', data.recipeLabel || 'Рецепт'));
            var recipeValue = makeElement('div', 'pharma-card__recipe-value');
            recipeValue.innerHTML = data.recipeHtml;
            recipe.appendChild(recipeValue);
            summary.appendChild(recipe);
        }

        if (data.facts && data.facts.length) {
            var facts = makeElement('div', 'pharma-card__facts');
            data.facts.forEach(function (fact) {
                facts.appendChild(makeFact(fact.label, fact.value));
            });
            summary.appendChild(facts);
        }

        var toggle = makeElement('span', 'pharma-card__toggle');
        toggle.appendChild(makeElement('span', 'pharma-card__toggle-more', 'Подробнее'));
        toggle.appendChild(makeElement('span', 'pharma-card__toggle-less', 'Свернуть'));
        summary.appendChild(toggle);

        var details = makeElement('div', 'pharma-card__details');
        var fields = makeElement('div', 'pharma-card__fields');
        (data.fields || []).forEach(function (field) {
            if (!field.html || !String(field.text || '').trim()) {
                return;
            }
            var row = makeElement('div', 'pharma-card__field');
            row.appendChild(makeElement('div', 'pharma-card__field-name', field.label));
            var value = makeElement('div', 'pharma-card__field-value');
            value.innerHTML = field.html;
            row.appendChild(value);
            fields.appendChild(row);
        });
        details.appendChild(fields);

        card.appendChild(summary);
        card.appendChild(details);

        summary.addEventListener('click', function (event) {
            if (event.target.closest('a')) {
                return;
            }
            setAutoCardOpen(card, !card.classList.contains('is-open'));
        });
        summary.addEventListener('keydown', function (event) {
            if (event.key === 'Enter' || event.key === ' ') {
                event.preventDefault();
                setAutoCardOpen(card, !card.classList.contains('is-open'));
            }
        });

        return card;
    }

    function factsFromRow(headers, cells) {
        var facts = [];
        headers.forEach(function (header, index) {
            var key = normalizePharmaText(header);
            if (!/(метабол|передоз|зависим|регулируется)/.test(key)) {
                return;
            }
            var value = cellText(cells[index]);
            if (!value || value === '—' || value.toLocaleLowerCase('ru-RU') === 'нет') {
                return;
            }
            var label = header
                .replace('Скорость метаболизации', 'Метаболизм')
                .replace('Порог до передозировки', 'Передозировка')
                .replace('Шанс зависимости', 'Зависимость')
                .replace('Регулируется КЗ', 'КЗ');
            facts.push({ label: label, value: value });
        });
        return facts;
    }

    function normalRowData(row, headers, section) {
        var cells = Array.prototype.slice.call(row.cells);
        if (cells.length < 2 || !cellText(cells[0])) {
            return null;
        }

        var recipeIndex = -1;
        var actionIndex = -1;
        var descriptionIndex = -1;

        headers.forEach(function (header, index) {
            var key = normalizePharmaText(header);
            if (recipeIndex === -1 && /(рецепт|формула|альтернативный способ получения|получение)/.test(key)) {
                recipeIndex = index;
            }
            if (actionIndex === -1 && /(действие|эффект при съедании)/.test(key)) {
                actionIndex = index;
            }
            if (descriptionIndex === -1 && key.indexOf('описание') !== -1) {
                descriptionIndex = index;
            }
        });

        var fields = [];
        headers.forEach(function (header, index) {
            if (index === 0 || index === recipeIndex || !cells[index]) {
                return;
            }
            fields.push({
                label: header || ('Свойство ' + index),
                html: cellHtml(cells[index]),
                text: cellText(cells[index])
            });
        });

        var actionText = actionIndex >= 0 ? cellText(cells[actionIndex]) : '';
        var descriptionText = descriptionIndex >= 0 ? cellText(cells[descriptionIndex]) : '';
        var recipeText = recipeIndex >= 0 ? cellText(cells[recipeIndex]) : '';

        return {
            section: section,
            nameHtml: cellHtml(cells[0]),
            recipeHtml: recipeIndex >= 0 ? cellHtml(cells[recipeIndex]) : '',
            recipeText: recipeText,
            recipeLabel: recipeIndex >= 0 && /альтернатив/i.test(headers[recipeIndex]) ? 'Получение' : (/формула/i.test(headers[recipeIndex] || '') ? 'Формула' : 'Рецепт'),
            shortText: actionText || descriptionText,
            fallbackShort: 'Справочный реагент раздела «' + section + '».',
            fullText: cells.map(cellText).join(' '),
            facts: factsFromRow(headers, cells),
            fields: fields
        };
    }

    function pureCards(table, section, info) {
        var cards = [];
        Array.prototype.slice.call(table.rows, 1).forEach(function (row) {
            var cells = Array.prototype.slice.call(row.cells);
            for (var index = 0; index + 2 < cells.length; index += 3) {
                if (!cellText(cells[index])) {
                    continue;
                }
                cards.push(makeAutoCard({
                    section: section,
                    nameHtml: cellHtml(cells[index]),
                    recipeHtml: cellHtml(cells[index + 2]),
                    recipeText: cellText(cells[index + 2]),
                    recipeLabel: 'Получение',
                    shortText: cellText(cells[index + 1]),
                    fallbackShort: 'Чистое вещество из химического раздатчика.',
                    fullText: cells.slice(index, index + 3).map(cellText).join(' '),
                    facts: [],
                    fields: [
                        { label: 'Эффект при съедании', html: cellHtml(cells[index + 1]), text: cellText(cells[index + 1]) },
                        { label: 'Получение', html: cellHtml(cells[index + 2]), text: cellText(cells[index + 2]) }
                    ]
                }, info));
            }
        });
        return cards;
    }

    function paintCards(table, section, info) {
        var cards = [];
        var sharedDescription = table.rows[0] && table.rows[0].cells[2] ? table.rows[0].cells[2] : null;
        Array.prototype.slice.call(table.rows).forEach(function (row) {
            var cells = Array.prototype.slice.call(row.cells);
            if (cells.length < 2 || !cellText(cells[0])) {
                return;
            }
            var description = cells[2] || sharedDescription;
            cards.push(makeAutoCard({
                section: section,
                nameHtml: cellHtml(cells[0]),
                recipeHtml: cellHtml(cells[1]),
                recipeText: cellText(cells[1]),
                recipeLabel: 'Формула',
                shortText: cellText(description),
                fallbackShort: 'Цветной реагент для окрашивания объектов.',
                fullText: cells.map(cellText).join(' ') + ' ' + cellText(description),
                facts: [],
                fields: [
                    { label: 'Описание', html: cellHtml(description), text: cellText(description) }
                ]
            }, info));
        });
        return cards;
    }

    function convertTable(table, section, tableIndex) {
        if (table.classList.contains('pharma-source-table') ||
                table.closest('.pharma-catalog') ||
                (!table.classList.contains('wikitable') && !table.classList.contains('items-table'))) {
            return null;
        }

        var info = sectionInfo(section);
        var cards = [];
        var firstRow = table.rows[0];
        if (!firstRow) {
            return null;
        }

        if (info.key === 'pure') {
            cards = pureCards(table, section, info);
        } else if (info.key === 'paint' && !firstRow.querySelector('th')) {
            cards = paintCards(table, section, info);
        } else {
            var headers = Array.prototype.map.call(firstRow.cells, cellText);
            if (headers.length < 2 || headers[0].indexOf('Название') === -1) {
                return null;
            }
            Array.prototype.slice.call(table.rows, 1).forEach(function (row) {
                var data = normalRowData(row, headers, section);
                if (data) {
                    cards.push(makeAutoCard(data, info));
                }
            });
        }

        if (!cards.length) {
            return null;
        }

        var wrapper = makeElement('div', 'pharma-auto-catalog');
        wrapper.setAttribute('data-pharma-category', info.key);
        wrapper.style.setProperty('--pharma-accent', info.color);

        var meta = makeElement('div', 'pharma-auto-catalog__meta');
        var countLabel = makeElement('span');
        countLabel.appendChild(makeElement('strong', '', cards.length + ' '));
        countLabel.appendChild(document.createTextNode(cards.length % 10 === 1 && cards.length % 100 !== 11 ? 'реагент' : (cards.length % 10 >= 2 && cards.length % 10 <= 4 && (cards.length % 100 < 10 || cards.length % 100 >= 20) ? 'реагента' : 'реагентов')));
        meta.appendChild(countLabel);
        meta.appendChild(makeElement('span', '', 'Нажмите карточку для подробностей'));

        var grid = makeElement('div', 'pharma-catalog__grid pharma-auto-catalog__grid');
        cards.forEach(function (card) {
            grid.appendChild(card);
        });

        wrapper.appendChild(meta);
        wrapper.appendChild(grid);
        table.insertAdjacentElement('afterend', wrapper);

        Array.prototype.forEach.call(table.querySelectorAll('[id]'), function (node, index) {
            node.id = 'pharma-source-' + tableIndex + '-' + index + '-' + node.id;
        });
        table.classList.add('pharma-source-table');
        table.setAttribute('aria-hidden', 'true');

        if (cards.length > 8) {
            wrapper.classList.add('is-condensed');
            var expand = makeElement('button', 'pharma-auto-catalog__expand', 'Показать все ' + cards.length);
            expand.type = 'button';
            meta.appendChild(expand);
            expand.addEventListener('click', function () {
                var condensed = wrapper.classList.toggle('is-condensed');
                expand.textContent = condensed ? 'Показать все ' + cards.length : 'Свернуть раздел';
                if (condensed) {
                    wrapper.scrollIntoView({ block: 'start', behavior: 'smooth' });
                }
            });
        }

        return { wrapper: wrapper, cards: cards, category: info.key };
    }

    function buildDirectory(content, records) {
        var manualCards = Array.prototype.slice.call(content.querySelectorAll('.pharma-catalog .pharma-card'));
        manualCards.forEach(function (card) {
            card.setAttribute('data-pharma-category', 'components');
            card.style.setProperty('--pharma-accent', '#d7a52a');
        });

        var autoCards = [];
        records.forEach(function (record) {
            autoCards = autoCards.concat(record.cards);
        });
        var cards = manualCards.concat(autoCards);
        if (!cards.length) {
            return;
        }

        var directory = makeElement('div', 'pharma-directory');

        var hero = makeElement('div', 'pharma-directory__hero');
        var heroText = makeElement('div');
        heroText.appendChild(makeElement('div', 'pharma-directory__eyebrow', 'Интерактивный справочник'));
        heroText.appendChild(makeElement('div', 'pharma-directory__title', 'Все реагенты в одном каталоге'));
        heroText.appendChild(makeElement('div', 'pharma-directory__description', 'Ищите по названию, ингредиенту или эффекту. Выберите категорию, чтобы оставить только нужные рецепты.'));
        var heroCount = makeElement('div', 'pharma-directory__count');
        heroCount.appendChild(makeElement('strong', '', String(cards.length)));
        heroCount.appendChild(makeElement('span', '', 'карточек'));
        hero.appendChild(heroText);
        hero.appendChild(heroCount);

        var toolbar = makeElement('div', 'pharma-catalog__toolbar');
        toolbar.setAttribute('role', 'search');

        var searchWrap = makeElement('div', 'pharma-catalog__search-wrap');
        var search = makeElement('input', 'pharma-catalog__search');
        search.type = 'search';
        search.placeholder = 'Название, эффект или ингредиент…';
        search.setAttribute('aria-label', 'Поиск по всему справочнику фармакологии');
        searchWrap.appendChild(search);

        var filters = makeElement('div', 'pharma-catalog__filters');
        var categoryOrder = [
            ['all', 'Все'],
            ['medicine', 'Медицина'],
            ['components', 'Компоненты'],
            ['pure', 'Вещества'],
            ['narcotics', 'Наркотики'],
            ['pyro', 'Пиротехника'],
            ['toxins', 'Токсины'],
            ['syndicate', 'Синдикат'],
            ['botany', 'Ботаника'],
            ['food', 'Еда'],
            ['other', 'Прочее'],
            ['paint', 'Краски']
        ];
        var present = {};
        cards.forEach(function (card) {
            present[card.getAttribute('data-pharma-category')] = true;
        });
        categoryOrder.forEach(function (item) {
            if (item[0] !== 'all' && !present[item[0]]) {
                return;
            }
            var button = makeElement('button', 'pharma-catalog__filter', item[1]);
            button.type = 'button';
            button.setAttribute('data-pharma-global-filter', item[0]);
            button.setAttribute('aria-pressed', item[0] === 'all' ? 'true' : 'false');
            filters.appendChild(button);
        });

        var meta = makeElement('div', 'pharma-catalog__meta');
        var status = makeElement('span');
        status.setAttribute('aria-live', 'polite');
        var actions = makeElement('span', 'pharma-catalog__actions');
        var closeAll = makeElement('button', 'pharma-catalog__action', 'Свернуть все');
        closeAll.type = 'button';
        actions.appendChild(closeAll);
        meta.appendChild(status);
        meta.appendChild(actions);

        toolbar.appendChild(searchWrap);
        toolbar.appendChild(filters);
        toolbar.appendChild(meta);

        var empty = makeElement('div', 'pharma-directory__empty', 'Ничего не найдено. Попробуйте другое название, эффект или ингредиент.');
        empty.hidden = true;

        directory.appendChild(hero);
        directory.appendChild(toolbar);
        directory.appendChild(empty);

        var firstHeading = content.querySelector('h2');
        var insertionPoint = firstHeading ? (firstHeading.closest('.mw-heading') || firstHeading) : content.firstChild;
        if (insertionPoint && insertionPoint.parentNode) {
            insertionPoint.parentNode.insertBefore(directory, insertionPoint);
        } else {
            content.insertBefore(directory, content.firstChild);
        }

        var activeCategory = 'all';

        function applyDirectoryFilters() {
            var query = normalizePharmaText(search.value);
            var visible = 0;
            var filtering = !!query || activeCategory !== 'all';

            cards.forEach(function (card) {
                var category = card.getAttribute('data-pharma-category');
                var categoryMatch = activeCategory === 'all' || category === activeCategory;
                var searchMatch = !query || normalizePharmaText(card.textContent).indexOf(query) !== -1;
                var show = categoryMatch && searchMatch;
                card.hidden = !show;
                if (!show) {
                    setAutoCardOpen(card, false);
                }
                if (show) {
                    visible += 1;
                }
            });

            records.forEach(function (record) {
                record.wrapper.classList.toggle('is-filtering', filtering);
                record.wrapper.hidden = !record.cards.some(function (card) {
                    return !card.hidden;
                });
            });

            var manualRoot = content.querySelector('.pharma-catalog');
            if (manualRoot) {
                manualRoot.hidden = !manualCards.some(function (card) {
                    return !card.hidden;
                });
            }

            status.textContent = 'Показано ' + visible + ' из ' + cards.length;
            empty.hidden = visible !== 0;
        }

        function scrollToFirstVisible() {
            var first = cards.find(function (card) {
                return !card.hidden;
            });
            if (!first) {
                return;
            }
            var target = first.closest('.pharma-auto-catalog, .pharma-catalog') || first;
            var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
            target.scrollIntoView({ block: 'start', behavior: reducedMotion ? 'auto' : 'smooth' });
        }

        search.addEventListener('input', applyDirectoryFilters);
        search.addEventListener('keydown', function (event) {
            if (event.key === 'Escape' && search.value) {
                search.value = '';
                applyDirectoryFilters();
            } else if (event.key === 'Enter') {
                scrollToFirstVisible();
            }
        });

        filters.addEventListener('click', function (event) {
            var button = event.target.closest('[data-pharma-global-filter]');
            if (!button) {
                return;
            }
            activeCategory = button.getAttribute('data-pharma-global-filter');
            Array.prototype.forEach.call(filters.querySelectorAll('[data-pharma-global-filter]'), function (item) {
                item.setAttribute('aria-pressed', item === button ? 'true' : 'false');
            });
            applyDirectoryFilters();
            scrollToFirstVisible();
        });

        closeAll.addEventListener('click', function () {
            cards.forEach(function (card) {
                setAutoCardOpen(card, false);
            });
        });

        document.body.classList.add('pharma-directory-ready');
        applyDirectoryFilters();
    }

    function bootFullPharmaCatalog() {
        if (!document.body.classList.contains(PAGE_CLASS) ||
                document.querySelector('.pharma-directory')) {
            return;
        }

        var content = document.querySelector('#mw-content-text');
        if (!content) {
            return;
        }

        var currentSection = '';
        var tableIndex = 0;
        var records = [];

        Array.prototype.forEach.call(content.querySelectorAll('h2, h3, table'), function (node) {
            if (node.tagName === 'H2' || node.tagName === 'H3') {
                currentSection = cleanHeading(node.textContent);
                return;
            }
            var record = convertTable(node, currentSection, tableIndex);
            tableIndex += 1;
            if (record) {
                records.push(record);
            }
        });

        buildDirectory(content, records);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', bootFullPharmaCatalog);
    } else {
        bootFullPharmaCatalog();
    }
}());
/* === Dark Paradise: Pharmacology full catalog v2 END === */

/* === Dark Paradise: Drinks full catalog v1 START === */
(function () {
    'use strict';

    var PAGE_CLASS = 'page-Руководство_по_напиткам';
    var generatedId = 0;

    function normalize(value) {
        return String(value || '').toLocaleLowerCase('ru-RU').replace(/\s+/g, ' ').trim();
    }

    function cleanHeading(value) {
        return String(value || '').replace(/\s*править\s*править код\s*$/i, '').trim();
    }

    function makeElement(tag, className, text) {
        var node = document.createElement(tag);
        if (className) {
            node.className = className;
        }
        if (typeof text === 'string') {
            node.textContent = text;
        }
        return node;
    }

    function cellText(cell) {
        return cell ? String(cell.textContent || '').replace(/\s+/g, ' ').trim() : '';
    }

    function safeHtml(node, html) {
        node.innerHTML = html || '';
        Array.prototype.forEach.call(node.querySelectorAll('[id]'), function (item) {
            generatedId += 1;
            item.id = 'drink-card-copy-' + generatedId;
        });
        Array.prototype.forEach.call(node.querySelectorAll('.mw-collapsible-toggle'), function (item) {
            item.remove();
        });
    }

    function nameParts(cell) {
        if (!cell) {
            return { nameHtml: '', imageHtml: '' };
        }
        var clone = cell.cloneNode(true);
        var image = clone.querySelector('img');
        var imageHtml = image ? image.outerHTML : '';
        if (image) {
            var imageLink = image.closest('a.image, a.mw-file-description');
            if (imageLink) {
                imageLink.remove();
            } else {
                image.remove();
            }
        }
        Array.prototype.forEach.call(clone.querySelectorAll('br'), function (br) {
            if (!br.previousSibling || !br.nextSibling) {
                br.remove();
            }
        });
        return { nameHtml: clone.innerHTML, imageHtml: imageHtml };
    }

    function snippet(value, fallback) {
        var text = String(value || '').replace(/\s+/g, ' ').trim();
        if (!text || text === '—' || text.toLocaleLowerCase('ru-RU') === 'нет') {
            return fallback || 'Откройте карточку, чтобы посмотреть рецепт и свойства напитка.';
        }
        return text.length > 180 ? text.slice(0, 177).trim() + '…' : text;
    }

    function sectionInfo(section, headers) {
        var title = normalize(section);
        var headerText = normalize(headers.join(' '));
        if (title.indexOf('синтанол') !== -1) {
            return { key: 'synthetic', label: 'Синтанол', color: '#8aa4c1' };
        }
        if (title.indexOf('безалкоголь') !== -1) {
            return { key: 'soft', label: 'Без алкоголя', color: '#55bdd8' };
        }
        if (title.indexOf('сок') !== -1 || (headerText.indexOf('крепость') === -1 && headerText.indexOf('эффект при выпивании') !== -1)) {
            return { key: 'juices', label: 'Соки', color: '#64c9a9' };
        }
        if (title.indexOf('бутыл') !== -1) {
            return { key: 'bottled', label: 'Бутылочник', color: '#a978e8' };
        }
        if (title.indexOf('кухн') !== -1 || (headers.length === 2 && /ингредиент/.test(headerText))) {
            return { key: 'kitchen', label: 'Кухня', color: '#d7a52a' };
        }
        if (headerText.indexOf('предмет') !== -1 && headerText.indexOf('реагент') !== -1) {
            return { key: 'stock', label: 'Запасы', color: '#d89158' };
        }
        if (title.indexOf('алкогол') !== -1 || headerText.indexOf('крепость') !== -1) {
            return { key: 'alcohol', label: 'Алкоголь', color: '#ff765f' };
        }
        return { key: 'other', label: 'Напиток', color: '#5ca9dc' };
    }

    function setCardOpen(card, open) {
        var summary = card.querySelector('.pharma-card__summary');
        card.classList.toggle('is-open', open);
        if (summary) {
            summary.setAttribute('aria-expanded', open ? 'true' : 'false');
        }
    }

    function makeFact(label, value) {
        var fact = makeElement('span', 'pharma-card__fact');
        fact.appendChild(makeElement('span', '', label + ':'));
        fact.appendChild(makeElement('strong', '', value));
        return fact;
    }

    function makeCard(data, info) {
        var card = makeElement('article', 'pharma-card pharma-card--auto drink-card');
        card.setAttribute('data-pharma-category', info.key);
        card.setAttribute('data-drink-category', info.key);
        card.style.setProperty('--pharma-accent', info.color);

        var summary = makeElement('div', 'pharma-card__summary');
        summary.setAttribute('role', 'button');
        summary.setAttribute('tabindex', '0');
        summary.setAttribute('aria-expanded', 'false');

        var top = makeElement('div', 'pharma-card__top');
        var identity = makeElement('div', 'pharma-card__identity');
        if (data.imageHtml) {
            var image = makeElement('span', 'drink-card__image');
            safeHtml(image, data.imageHtml);
            identity.appendChild(image);
            card.classList.add('drink-card--image');
        } else {
            var swatch = makeElement('span', 'pharma-card__swatch');
            swatch.style.backgroundColor = info.color;
            identity.appendChild(swatch);
        }

        var name = makeElement('div', 'pharma-card__auto-name drink-card__name');
        safeHtml(name, data.nameHtml);
        identity.appendChild(name);
        top.appendChild(identity);
        top.appendChild(makeElement('span', 'pharma-card__type', info.label));

        summary.appendChild(top);
        summary.appendChild(makeElement('div', 'pharma-card__short', snippet(data.shortText, data.fallbackShort)));

        if (data.recipeHtml) {
            var recipe = makeElement('div', 'pharma-card__recipe');
            recipe.appendChild(makeElement('span', 'pharma-card__label', data.recipeLabel || 'Рецепт'));
            var recipeValue = makeElement('div', 'pharma-card__recipe-value');
            safeHtml(recipeValue, data.recipeHtml);
            recipe.appendChild(recipeValue);
            summary.appendChild(recipe);
        }

        if (data.facts && data.facts.length) {
            var facts = makeElement('div', 'pharma-card__facts');
            data.facts.forEach(function (fact) {
                facts.appendChild(makeFact(fact.label, fact.value));
            });
            summary.appendChild(facts);
        }

        var toggle = makeElement('span', 'pharma-card__toggle');
        toggle.appendChild(makeElement('span', 'pharma-card__toggle-more', 'Подробнее'));
        toggle.appendChild(makeElement('span', 'pharma-card__toggle-less', 'Свернуть'));
        summary.appendChild(toggle);

        var details = makeElement('div', 'pharma-card__details');
        var fields = makeElement('div', 'pharma-card__fields');
        (data.fields || []).forEach(function (field) {
            if (!field.html || !String(field.text || '').trim()) {
                return;
            }
            var row = makeElement('div', 'pharma-card__field');
            row.appendChild(makeElement('div', 'pharma-card__field-name', field.label));
            var value = makeElement('div', 'pharma-card__field-value');
            safeHtml(value, field.html);
            row.appendChild(value);
            fields.appendChild(row);
        });
        details.appendChild(fields);
        card.appendChild(summary);
        card.appendChild(details);

        summary.addEventListener('click', function (event) {
            if (!event.target.closest('a')) {
                setCardOpen(card, !card.classList.contains('is-open'));
            }
        });
        summary.addEventListener('keydown', function (event) {
            if (event.key === 'Enter' || event.key === ' ') {
                event.preventDefault();
                setCardOpen(card, !card.classList.contains('is-open'));
            }
        });
        return card;
    }

    function findHeaderIndex(headers, pattern) {
        for (var index = 0; index < headers.length; index += 1) {
            if (pattern.test(normalize(headers[index]))) {
                return index;
            }
        }
        return -1;
    }

    function normalRowData(row, headers, section, tableKind) {
        var cells = Array.prototype.slice.call(row.cells);
        if (!cells.length) {
            return null;
        }
        var nameIndex = findHeaderIndex(headers, /название|предмет/);
        var imageIndex = findHeaderIndex(headers, /изображение/);
        var recipeIndex = findHeaderIndex(headers, /рецепт|ингредиент|реагент/);
        var strengthIndex = findHeaderIndex(headers, /крепость/);
        var effectIndex = findHeaderIndex(headers, /эффект|описание/);
        var noteIndex = findHeaderIndex(headers, /примечание/);
        if (nameIndex < 0 || !cells[nameIndex] || !cellText(cells[nameIndex])) {
            return null;
        }

        var parts = nameParts(cells[nameIndex]);
        if (!parts.imageHtml && imageIndex >= 0 && cells[imageIndex]) {
            var image = cells[imageIndex].querySelector('img');
            parts.imageHtml = image ? image.outerHTML : '';
        }
        var recipeText = recipeIndex >= 0 && cells[recipeIndex] ? cellText(cells[recipeIndex]) : '';
        var shortText = effectIndex >= 0 && cells[effectIndex] ? cellText(cells[effectIndex]) : '';
        if (!shortText && noteIndex >= 0 && cells[noteIndex]) {
            shortText = cellText(cells[noteIndex]);
        }
        var facts = [];
        if (strengthIndex >= 0 && cells[strengthIndex]) {
            var strength = cellText(cells[strengthIndex]);
            if (strength && strength !== '—') {
                facts.push({ label: 'Крепость', value: strength });
            }
        }
        var fields = [];
        headers.forEach(function (header, index) {
            if (!cells[index] || index === nameIndex || index === imageIndex || index === recipeIndex || index === strengthIndex) {
                return;
            }
            var text = cellText(cells[index]);
            if (!text || text === '—') {
                return;
            }
            fields.push({ label: header || 'Сведения', html: cells[index].innerHTML, text: text });
        });
        return {
            section: section,
            nameHtml: parts.nameHtml,
            imageHtml: parts.imageHtml,
            recipeHtml: recipeIndex >= 0 && cells[recipeIndex] ? cells[recipeIndex].innerHTML : '',
            recipeText: recipeText,
            recipeLabel: tableKind === 'stock' ? 'Реагент' : (tableKind === 'synthetic' ? 'Ингредиенты' : 'Рецепт'),
            shortText: shortText,
            fallbackShort: tableKind === 'stock' ? 'Готовый напиток или реагент из доступных запасов.' : 'Откройте карточку, чтобы посмотреть рецепт и свойства напитка.',
            fullText: cells.map(cellText).join(' '),
            facts: facts,
            fields: fields
        };
    }

    function kitchenCards(table, section, info) {
        var cards = [];
        var rows = Array.prototype.slice.call(table.rows, 1);
        for (var index = 0; index < rows.length; index += 2) {
            var titleRow = rows[index];
            var recipeRow = rows[index + 1];
            if (!titleRow || titleRow.cells.length < 2) {
                continue;
            }
            var imageCell = titleRow.cells[0];
            var nameCell = titleRow.cells[1];
            var recipeCell = recipeRow && recipeRow.cells[0];
            var parts = nameParts(nameCell);
            var image = imageCell.querySelector('img');
            if (image) {
                parts.imageHtml = image.outerHTML;
            }
            cards.push(makeCard({
                section: section,
                nameHtml: parts.nameHtml,
                imageHtml: parts.imageHtml,
                recipeHtml: recipeCell ? recipeCell.innerHTML : '',
                recipeText: cellText(recipeCell),
                recipeLabel: 'Ингредиенты',
                shortText: '',
                fallbackShort: 'Напиток, который можно приготовить из кухонных ингредиентов.',
                fullText: [cellText(nameCell), cellText(recipeCell)].join(' '),
                facts: [],
                fields: []
            }, info));
        }
        return cards;
    }

    function pluralDrinks(count) {
        var mod10 = count % 10;
        var mod100 = count % 100;
        if (mod10 === 1 && mod100 !== 11) {
            return 'напиток';
        }
        if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) {
            return 'напитка';
        }
        return 'напитков';
    }

    function convertTable(table, section, tableIndex) {
        if (table.classList.contains('drinks-source-table') || table.querySelector('table') ||
                (!table.classList.contains('wikitable') && !table.classList.contains('items-table'))) {
            return null;
        }
        var firstRow = table.rows[0];
        if (!firstRow) {
            return null;
        }
        var headers = Array.prototype.map.call(firstRow.cells, cellText);
        if (headers.length < 2) {
            return null;
        }
        var info = sectionInfo(section, headers);
        var cards = [];
        if (headers.length === 2 && /изображение/.test(normalize(headers[0])) && /ингредиент/.test(normalize(headers[1]))) {
            info = { key: 'kitchen', label: 'Кухня', color: '#d7a52a' };
            cards = kitchenCards(table, section, info);
        } else {
            Array.prototype.slice.call(table.rows, 1).forEach(function (row) {
                var data = normalRowData(row, headers, section, info.key);
                if (data) {
                    cards.push(makeCard(data, info));
                }
            });
        }
        if (!cards.length) {
            return null;
        }

        var wrapper = makeElement('section', 'pharma-auto-catalog drinks-auto-catalog');
        wrapper.setAttribute('data-drink-category', info.key);
        wrapper.style.setProperty('--pharma-accent', info.color);
        var meta = makeElement('div', 'pharma-auto-catalog__meta');
        var countLabel = makeElement('span');
        countLabel.appendChild(makeElement('strong', '', cards.length + ' '));
        countLabel.appendChild(document.createTextNode(pluralDrinks(cards.length)));
        meta.appendChild(countLabel);
        meta.appendChild(makeElement('span', '', 'Нажмите карточку для подробностей'));
        var grid = makeElement('div', 'pharma-catalog__grid pharma-auto-catalog__grid');
        cards.forEach(function (card) {
            grid.appendChild(card);
        });
        wrapper.appendChild(meta);
        wrapper.appendChild(grid);
        table.insertAdjacentElement('afterend', wrapper);

        Array.prototype.forEach.call(table.querySelectorAll('[id]'), function (node, index) {
            node.id = 'drinks-source-' + tableIndex + '-' + index + '-' + node.id;
        });
        table.classList.add('drinks-source-table');
        table.setAttribute('aria-hidden', 'true');

        if (cards.length > 8) {
            wrapper.classList.add('is-condensed');
            var expand = makeElement('button', 'pharma-auto-catalog__expand', 'Показать все ' + cards.length);
            expand.type = 'button';
            meta.appendChild(expand);
            expand.addEventListener('click', function () {
                var condensed = wrapper.classList.toggle('is-condensed');
                expand.textContent = condensed ? 'Показать все ' + cards.length : 'Свернуть раздел';
                if (condensed) {
                    wrapper.scrollIntoView({ block: 'start', behavior: 'smooth' });
                }
            });
        }
        return { wrapper: wrapper, cards: cards, category: info.key };
    }

    function buildDirectory(content, records) {
        var cards = [];
        records.forEach(function (record) {
            cards = cards.concat(record.cards);
        });
        if (!cards.length) {
            return;
        }

        var directory = makeElement('section', 'pharma-directory drinks-directory');
        var hero = makeElement('div', 'pharma-directory__hero');
        var heroText = makeElement('div');
        heroText.appendChild(makeElement('div', 'pharma-directory__eyebrow', 'Интерактивный барный справочник'));
        heroText.appendChild(makeElement('div', 'pharma-directory__title', 'Все напитки и рецепты'));
        heroText.appendChild(makeElement('div', 'pharma-directory__description', 'Ищите по названию, ингредиенту или эффекту. Фильтры помогут быстро найти алкогольные, безалкогольные и синтетические напитки.'));
        var heroCount = makeElement('div', 'pharma-directory__count');
        heroCount.appendChild(makeElement('strong', '', String(cards.length)));
        heroCount.appendChild(makeElement('span', '', 'карточек'));
        hero.appendChild(heroText);
        hero.appendChild(heroCount);

        var toolbar = makeElement('div', 'pharma-catalog__toolbar');
        toolbar.setAttribute('role', 'search');
        var searchWrap = makeElement('div', 'pharma-catalog__search-wrap');
        var search = makeElement('input', 'pharma-catalog__search');
        search.type = 'search';
        search.placeholder = 'Напиток, ингредиент или эффект…';
        search.setAttribute('aria-label', 'Поиск по справочнику напитков');
        searchWrap.appendChild(search);

        var filters = makeElement('div', 'pharma-catalog__filters');
        var order = [
            ['all', 'Все'], ['alcohol', 'Алкогольные'], ['soft', 'Безалкогольные'],
            ['juices', 'Соки'], ['synthetic', 'Синтанол'], ['stock', 'Запасы'],
            ['bottled', 'Бутылочник'], ['kitchen', 'Кухня'], ['other', 'Прочее']
        ];
        var present = {};
        cards.forEach(function (card) {
            present[card.getAttribute('data-drink-category')] = true;
        });
        order.forEach(function (item) {
            if (item[0] !== 'all' && !present[item[0]]) {
                return;
            }
            var button = makeElement('button', 'pharma-catalog__filter', item[1]);
            button.type = 'button';
            button.setAttribute('data-drink-filter', item[0]);
            button.setAttribute('aria-pressed', item[0] === 'all' ? 'true' : 'false');
            filters.appendChild(button);
        });

        var meta = makeElement('div', 'pharma-catalog__meta');
        var status = makeElement('span');
        status.setAttribute('aria-live', 'polite');
        var actions = makeElement('span', 'pharma-catalog__actions');
        var closeAll = makeElement('button', 'pharma-catalog__action', 'Свернуть все');
        closeAll.type = 'button';
        actions.appendChild(closeAll);
        meta.appendChild(status);
        meta.appendChild(actions);
        toolbar.appendChild(searchWrap);
        toolbar.appendChild(filters);
        toolbar.appendChild(meta);

        var empty = makeElement('div', 'pharma-directory__empty', 'Ничего не найдено. Попробуйте другое название, ингредиент или эффект.');
        empty.hidden = true;
        directory.appendChild(hero);
        directory.appendChild(toolbar);
        directory.appendChild(empty);

        var firstHeading = content.querySelector('h2, h3');
        var insertionPoint = firstHeading ? (firstHeading.closest('.mw-heading') || firstHeading) : content.firstChild;
        if (insertionPoint && insertionPoint.parentNode) {
            insertionPoint.parentNode.insertBefore(directory, insertionPoint);
        } else {
            content.insertBefore(directory, content.firstChild);
        }

        var activeCategory = 'all';
        function applyFilters() {
            var query = normalize(search.value);
            var visible = 0;
            var filtering = !!query || activeCategory !== 'all';
            cards.forEach(function (card) {
                var categoryMatch = activeCategory === 'all' || card.getAttribute('data-drink-category') === activeCategory;
                var searchMatch = !query || normalize(card.textContent).indexOf(query) !== -1;
                var show = categoryMatch && searchMatch;
                card.hidden = !show;
                if (!show) {
                    setCardOpen(card, false);
                }
                if (show) {
                    visible += 1;
                }
            });
            records.forEach(function (record) {
                record.wrapper.classList.toggle('is-filtering', filtering);
                record.wrapper.hidden = !record.cards.some(function (card) { return !card.hidden; });
            });
            status.textContent = 'Показано ' + visible + ' из ' + cards.length;
            empty.hidden = visible !== 0;
        }

        function scrollToFirstVisible() {
            var first = cards.find(function (card) { return !card.hidden; });
            if (!first) {
                return;
            }
            var target = first.closest('.drinks-auto-catalog') || first;
            var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
            target.scrollIntoView({ block: 'start', behavior: reduced ? 'auto' : 'smooth' });
        }

        search.addEventListener('input', applyFilters);
        search.addEventListener('keydown', function (event) {
            if (event.key === 'Escape' && search.value) {
                search.value = '';
                applyFilters();
            } else if (event.key === 'Enter') {
                scrollToFirstVisible();
            }
        });
        filters.addEventListener('click', function (event) {
            var button = event.target.closest('[data-drink-filter]');
            if (!button) {
                return;
            }
            activeCategory = button.getAttribute('data-drink-filter');
            Array.prototype.forEach.call(filters.querySelectorAll('[data-drink-filter]'), function (item) {
                item.setAttribute('aria-pressed', item === button ? 'true' : 'false');
            });
            applyFilters();
            scrollToFirstVisible();
        });
        closeAll.addEventListener('click', function () {
            cards.forEach(function (card) { setCardOpen(card, false); });
        });

        Array.prototype.forEach.call(content.querySelectorAll('table:not(.drinks-source-table)'), function (table) {
            if (table.querySelector('.drinks-source-table')) {
                table.classList.add('drinks-source-layout');
                table.setAttribute('aria-hidden', 'true');
            }
        });
        document.body.classList.add('drinks-directory-ready');
        applyFilters();
    }

    function bootDrinksCatalog() {
        if (!document.body.classList.contains(PAGE_CLASS) || document.querySelector('.drinks-directory')) {
            return;
        }
        var content = document.querySelector('#mw-content-text');
        if (!content) {
            return;
        }
        var currentSection = '';
        var tableIndex = 0;
        var records = [];
        Array.prototype.forEach.call(content.querySelectorAll('h2, h3, table'), function (node) {
            if (node.tagName === 'H2' || node.tagName === 'H3') {
                currentSection = cleanHeading(node.textContent);
                return;
            }
            var record = convertTable(node, currentSection, tableIndex);
            tableIndex += 1;
            if (record) {
                records.push(record);
            }
        });
        buildDirectory(content, records);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', bootDrinksCatalog);
    } else {
        bootDrinksCatalog();
    }
}());
/* === Dark Paradise: Drinks full catalog v1 END === */