Открыть меню
Переключить меню настроек
Открыть персональное меню
Вы не представились системе
Ваш IP-адрес будет виден всем, если вы внесёте какие-либо изменения.

MediaWiki:ScmcChemistry.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.
/*
 * Интерактивный каталог химии SSMC.
 * Страница: MC:Химия
 * Данные: https://github.com/DeferW/ssmc-wiki-data
 */
(function () {
    'use strict';

    var ROOT_ID = 'scmc-chemistry-app';
    var DATA_URL = 'https://raw.githubusercontent.com/DeferW/ssmc-wiki-data/main/data/chemistry-catalog.json';
    var NUMBER = new Intl.NumberFormat('ru-RU', {
        maximumFractionDigits: 3
    });

    var TAB_DEFINITIONS = [
        { id: 'ordnance', label: 'Боевая химия' },
        { id: 'medicine', label: 'Медицина' },
        { id: 'drinks', label: 'Напитки' },
        { id: 'unlisted', label: 'Прочее' }
    ];

    var EFFECT_LABELS = {
        AdjustReagent: 'Создаёт другой реагент',
        Anticorrosive: 'Лечит ожоги и кислотные повреждения',
        Antihallucinogenic: 'Подавляет галлюцинации',
        Antitoxic: 'Выводит токсины',
        Biocidic: 'Лечит клеточные повреждения',
        Carcinogenic: 'Вызывает генетические повреждения',
        CauseZombieInfection: 'Вызывает зомби-инфекцию',
        ChemHealEyeDamage: 'Лечит повреждения глаз',
        ChemVomit: 'Вызывает рвоту',
        Corrosive: 'Наносит кислотные повреждения',
        CureZombieInfection: 'Лечит зомби-инфекцию',
        Drunk: 'Вызывает опьянение',
        Electrogenetic: 'Стимулирует сердечную деятельность',
        Emote: 'Вызывает непроизвольную реакцию',
        EqualHealthChange: 'Равномерно изменяет здоровье',
        FlammableReaction: 'Повышает горючесть',
        Flowing: 'Повышает текучесть',
        Focusing: 'Повышает концентрацию',
        Fueling: 'Повышает горючесть смеси',
        GenericStatusEffect: 'Изменяет состояние организма',
        HealthChange: 'Изменяет здоровье',
        Hemogenic: 'Восстанавливает кровь',
        Hypoxemic: 'Вызывает кислородное голодание',
        Jitter: 'Вызывает дрожь',
        Ketogenic: 'Ускоряет расход питательных веществ',
        ModifyStatusEffect: 'Изменяет статусный эффект',
        MovespeedModifier: 'Изменяет скорость передвижения',
        Neogenetic: 'Лечит механические повреждения',
        Nutritious: 'Восстанавливает питание',
        Oxidizing: 'Усиливает горение',
        Oxygenating: 'Восстанавливает кислород',
        PopupMessage: 'Вызывает ощущение или сообщение',
        SatiateHunger: 'Утоляет голод',
        SatiateThirst: 'Утоляет жажду',
        Thermostabilizing: 'Стабилизирует температуру тела',
        Toxic: 'Наносит токсические повреждения',
        Viscous: 'Повышает вязкость'
    };

    var DAMAGE_LABELS = {
        Asphyxiation: 'удушье',
        Bloodloss: 'кровопотеря',
        Blunt: 'ушибы',
        Brute: 'механические',
        Burn: 'ожоги',
        Caustic: 'кислота',
        Cellular: 'клеточные',
        Cold: 'холод',
        Genetic: 'генетические',
        Heat: 'тепло',
        Piercing: 'колотые',
        Poison: 'яд',
        Radiation: 'радиация',
        Slash: 'порезы',
        Toxin: 'токсины'
    };

    var METABOLISM_LABELS = {
        Alcohol: 'Алкоголь',
        Drink: 'Напиток',
        Food: 'Пища',
        Medicine: 'Лекарство',
        Narcotic: 'Наркотик',
        Poison: 'Яд'
    };

    var state = {
        data: null,
        activeTab: 'ordnance',
        query: '',
        productReactions: Object.create(null)
    };

    function element(tag, className, text) {
        var node = document.createElement(tag);

        if (className) {
            node.className = className;
        }

        if (text !== undefined && text !== null) {
            node.textContent = String(text);
        }

        return node;
    }

    function append(parent) {
        var children = Array.prototype.slice.call(arguments, 1);

        children.forEach(function (child) {
            if (child) {
                parent.appendChild(child);
            }
        });

        return parent;
    }

    function formatNumber(value) {
        return typeof value === 'number' ? NUMBER.format(value) : String(value);
    }

    function formatAmount(value) {
        return formatNumber(value) + ' ед.';
    }

    function safeColor(value) {
        if (typeof value !== 'string') {
            return '#7f8c8d';
        }

        if (/^#[0-9a-f]{3,8}$/i.test(value) || /^[a-z]+$/i.test(value)) {
            return value;
        }

        return '#7f8c8d';
    }

    function tagName(tag) {
        return String(tag || '').replace(/^!type:/, '');
    }

    function reagentById(id) {
        return state.data.reagents[id] || state.data.dependencies[id] || null;
    }

    function reagentName(id) {
        var reagent = reagentById(id);
        return reagent && reagent.name ? reagent.name : id;
    }

    function normalizeSearch(value) {
        return String(value || '').trim().toLocaleLowerCase('ru-RU');
    }

    function matchesQuery(entry, reagent) {
        if (!state.query) {
            return true;
        }

        return normalizeSearch([
            entry.id,
            entry.name,
            reagent && reagent.name,
            reagent && reagent.description,
            entry.sectionPath && entry.sectionPath.join(' ')
        ].filter(Boolean).join(' ')).indexOf(state.query) !== -1;
    }

    function buildProductReactionIndex() {
        state.productReactions = Object.create(null);

        Object.keys(state.data.reactions).forEach(function (reactionId) {
            var reaction = state.data.reactions[reactionId];

            (reaction.products || []).forEach(function (product) {
                if (!state.productReactions[product.id]) {
                    state.productReactions[product.id] = [];
                }

                state.productReactions[product.id].push(reaction);
            });
        });
    }

    function entriesForTab(tabId) {
        if (tabId === 'unlisted') {
            return (state.data.unlistedReagents || []).map(function (id) {
                var reagent = state.data.reagents[id];

                return {
                    id: id,
                    name: reagent ? reagent.name : id,
                    origin: reagent ? reagent.origin : 'unknown',
                    sectionPath: ['Вне игровых руководств'],
                    includedBy: 'catalog'
                };
            });
        }

        return state.data.guides[tabId] || [];
    }

    function conditionText(condition) {
        var type = tagName(condition && condition.yamlTag);
        var value = condition && condition.value;
        value = value && typeof value === 'object' ? value : {};

        if (type === 'ReagentThreshold') {
            if (value.min !== undefined && value.max !== undefined) {
                return 'при количестве от ' + formatAmount(value.min) + ' до ' + formatAmount(value.max);
            }
            if (value.min !== undefined) {
                return 'от ' + formatAmount(value.min) + ' в организме';
            }
            if (value.max !== undefined) {
                return 'до ' + formatAmount(value.max) + ' в организме';
            }
        }

        if (type === 'Temperature') {
            if (value.min !== undefined) {
                return 'при температуре не ниже ' + formatNumber(value.min) + ' K';
            }
            if (value.max !== undefined) {
                return 'при температуре не выше ' + formatNumber(value.max) + ' K';
            }
        }

        if (type === 'MobStateCondition') {
            return value.mobstate === 'Critical' ? 'в критическом состоянии' : 'состояние: ' + value.mobstate;
        }

        if (type === 'OrganType') {
            return (value.shouldHave === false ? 'кроме типа ' : 'для типа ') + (value.type || 'неизвестно');
        }

        return type ? 'условие: ' + type : '';
    }

    function damageText(damage) {
        var parts = [];

        if (!damage || typeof damage !== 'object') {
            return '';
        }

        ['groups', 'types'].forEach(function (key) {
            var values = damage[key];

            if (!values || typeof values !== 'object') {
                return;
            }

            Object.keys(values).forEach(function (damageId) {
                var amount = values[damageId];
                var action = amount < 0 ? 'лечит' : 'наносит';
                parts.push(action + ' ' + formatNumber(Math.abs(amount)) + ' (' + (DAMAGE_LABELS[damageId] || damageId) + ')');
            });
        });

        return parts.join(', ');
    }

    function equalDamageText(damage) {
        if (!Array.isArray(damage)) {
            return '';
        }

        return damage.map(function (entry) {
            var key = Object.keys(entry || {})[0];
            var amount = key ? entry[key] : 0;
            var action = amount < 0 ? 'лечит' : 'наносит';
            return action + ' ' + formatNumber(Math.abs(amount)) + ' (' + (DAMAGE_LABELS[key] || key) + ')';
        }).join(', ');
    }

    function effectDetails(type, value) {
        var details = [];
        value = value && typeof value === 'object' ? value : {};

        if (value.potency !== undefined) {
            details.push('сила ' + formatNumber(value.potency));
        }
        if (value.factor !== undefined) {
            details.push('коэффициент ' + formatNumber(value.factor));
        }
        if (value.probability !== undefined && value.probability > 0) {
            details.push('шанс ' + formatNumber(value.probability * 100) + '%');
        }
        if (value.boozePower !== undefined) {
            details.push('крепость ' + formatNumber(value.boozePower));
        }
        if (value.time !== undefined) {
            details.push('длительность ' + formatNumber(value.time) + ' сек.');
        }
        if (value.damage) {
            details.push(type === 'EqualHealthChange' ? equalDamageText(value.damage) : damageText(value.damage));
        }
        if (type === 'AdjustReagent' && value.reagent) {
            details.push((value.amount >= 0 ? 'добавляет ' : 'удаляет ') + formatAmount(Math.abs(value.amount || 0)) + ' «' + reagentName(value.reagent) + '»');
        }
        if (type === 'MovespeedModifier') {
            if (value.walkSpeedModifier !== undefined) {
                details.push('ходьба ×' + formatNumber(value.walkSpeedModifier));
            }
            if (value.sprintSpeedModifier !== undefined) {
                details.push('бег ×' + formatNumber(value.sprintSpeedModifier));
            }
        }
        if (type === 'GenericStatusEffect' && value.key) {
            details.push('статус ' + value.key);
        }
        if (type === 'ModifyStatusEffect' && value.effectProto) {
            details.push('статус ' + value.effectProto);
        }
        if (type === 'Emote' && value.emote) {
            details.push('реакция ' + value.emote);
        }

        return details.filter(Boolean).join('; ');
    }

    function renderEffect(effect) {
        var type = tagName(effect && effect.yamlTag);
        var value = effect && effect.value;
        var item = element('li', 'scmc-chem__effect');
        var title = element('span', 'scmc-chem__effect-name', EFFECT_LABELS[type] || type || 'Неизвестный эффект');
        var details = effectDetails(type, value);
        var conditions = value && Array.isArray(value.conditions) ? value.conditions.map(conditionText).filter(Boolean) : [];

        item.appendChild(title);

        if (details) {
            item.appendChild(element('span', 'scmc-chem__effect-details', ' — ' + details));
        }

        if (conditions.length) {
            item.appendChild(element('span', 'scmc-chem__effect-condition', ' (' + conditions.join(', ') + ')'));
        }

        return item;
    }

    function renderMetabolisms(reagent) {
        var metabolisms = reagent.properties && reagent.properties.metabolisms;

        if (!metabolisms || !Object.keys(metabolisms).length) {
            return null;
        }

        var block = element('div', 'scmc-chem__card-section');
        block.appendChild(element('h4', 'scmc-chem__card-subtitle', 'Воздействие'));

        Object.keys(metabolisms).forEach(function (metabolismId) {
            var metabolism = metabolisms[metabolismId] || {};
            var group = element('div', 'scmc-chem__metabolism');
            var title = METABOLISM_LABELS[metabolismId] || metabolismId;

            if (metabolism.metabolismRate !== undefined) {
                title += ' · ' + formatAmount(metabolism.metabolismRate) + '/сек.';
            }

            group.appendChild(element('div', 'scmc-chem__metabolism-title', title));

            if (Array.isArray(metabolism.effects) && metabolism.effects.length) {
                var list = element('ul', 'scmc-chem__effect-list');
                metabolism.effects.forEach(function (effect) {
                    list.appendChild(renderEffect(effect));
                });
                group.appendChild(list);
            }

            block.appendChild(group);
        });

        return block;
    }

    function renderMixture(items, className) {
        var list = element('div', className);

        (items || []).forEach(function (item) {
            var row = element('div', 'scmc-chem__mixture-item');
            var dot = element('span', 'scmc-chem__mixture-dot');
            var reagent = reagentById(item.id);
            dot.style.backgroundColor = safeColor(reagent && reagent.properties && reagent.properties.color);

            append(
                row,
                dot,
                element('span', 'scmc-chem__mixture-name', item.name || reagentName(item.id)),
                element('span', 'scmc-chem__mixture-amount', formatAmount(item.amount))
            );
            list.appendChild(row);
        });

        return list;
    }

    function reactionEffectText(effect) {
        var type = tagName(effect && effect.yamlTag);
        var value = effect && effect.value;
        value = value && typeof value === 'object' ? value : {};

        if (type === 'SensitiveReactionExplosionEffect') {
            return 'Взрыв при объёме от ' + formatAmount(value.threshold || 0) + '; интенсивность за единицу: ' + formatNumber(value.intensityPerUnit || 0) + '.';
        }
        if (type === 'ExplosionReactionEffect') {
            return 'Вызывает взрыв; интенсивность за единицу: ' + formatNumber(value.intensityPerUnit || 0) + '.';
        }
        if (type === 'AreaReactionEffect') {
            return 'Создаёт эффект в области' + (value.duration ? ' на ' + formatNumber(value.duration) + ' сек.' : '.');
        }
        if (type === 'CreateEntityReactionEffect') {
            return 'Создаёт объект: ' + (value.entity || 'неизвестно') + '.';
        }

        return type || 'Дополнительный эффект реакции';
    }

    function renderReaction(reaction, index) {
        var block = element('div', 'scmc-chem__reaction');

        if (index > 0) {
            block.appendChild(element('div', 'scmc-chem__reaction-label', 'Вариант ' + (index + 1)));
        }

        var equation = element('div', 'scmc-chem__equation');
        append(
            equation,
            renderMixture(reaction.reactants, 'scmc-chem__reactants'),
            element('div', 'scmc-chem__arrow', '→'),
            renderMixture(reaction.products, 'scmc-chem__products')
        );
        block.appendChild(equation);

        if (reaction.conditions && reaction.conditions.minTemp !== undefined) {
            block.appendChild(element('div', 'scmc-chem__reaction-note', 'Минимальная температура: ' + formatNumber(reaction.conditions.minTemp) + ' K.'));
        }

        (reaction.effects || []).forEach(function (effect) {
            block.appendChild(element('div', 'scmc-chem__reaction-warning', reactionEffectText(effect)));
        });

        return block;
    }

    function renderRecipes(reagentId) {
        var reactions = state.productReactions[reagentId] || [];

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

        var block = element('div', 'scmc-chem__card-section');
        block.appendChild(element('h4', 'scmc-chem__card-subtitle', reactions.length > 1 ? 'Рецепты' : 'Рецепт'));
        reactions.forEach(function (reaction, index) {
            block.appendChild(renderReaction(reaction, index));
        });
        return block;
    }

    function renderDoseBadges(reagent) {
        var properties = reagent.properties || {};
        var row = element('div', 'scmc-chem__dose-row');

        if (properties.overdose !== undefined) {
            row.appendChild(element('span', 'scmc-chem__dose scmc-chem__dose--warning', 'Передозировка: ' + formatAmount(properties.overdose)));
        }
        if (properties.criticalOverdose !== undefined) {
            row.appendChild(element('span', 'scmc-chem__dose scmc-chem__dose--danger', 'Критическая: ' + formatAmount(properties.criticalOverdose)));
        }

        return row.childNodes.length ? row : null;
    }

    function renderCard(entry) {
        var reagent = reagentById(entry.id);
        var details = element('details', 'scmc-chem__card');
        var summary = element('summary', 'scmc-chem__card-summary');
        var color = safeColor(reagent && reagent.properties && reagent.properties.color);
        var colorBar = element('span', 'scmc-chem__color');
        var heading = element('span', 'scmc-chem__card-heading');
        var originLabel = entry.origin === 'upstream-reference' ? 'базовый' : entry.origin === 'stories' ? 'Stories' : 'RMC14';

        details.style.setProperty('--scmc-chem-color', color);
        colorBar.style.backgroundColor = color;

        append(
            heading,
            element('span', 'scmc-chem__card-name', entry.name || (reagent && reagent.name) || entry.id),
            element('code', 'scmc-chem__card-id', entry.id)
        );
        append(
            summary,
            colorBar,
            heading,
            element('span', 'scmc-chem__origin scmc-chem__origin--' + (entry.origin || 'unknown'), originLabel),
            element('span', 'scmc-chem__chevron', '')
        );
        details.appendChild(summary);

        var body = element('div', 'scmc-chem__card-body');

        if (!reagent) {
            body.appendChild(element('p', 'scmc-chem__empty', 'Данные реагента не найдены.'));
            details.appendChild(body);
            return details;
        }

        if (reagent.description) {
            body.appendChild(element('p', 'scmc-chem__description', reagent.description));
        }

        append(body, renderDoseBadges(reagent), renderRecipes(entry.id), renderMetabolisms(reagent));

        if (!body.childNodes.length) {
            body.appendChild(element('p', 'scmc-chem__empty', 'Для этого реагента нет дополнительных данных.'));
        }

        details.appendChild(body);
        return details;
    }

    function sectionKey(entry) {
        return (entry.sectionPath && entry.sectionPath.length ? entry.sectionPath : ['Без раздела']).join(' / ');
    }

    function groupEntries(entries) {
        var groups = [];
        var byKey = Object.create(null);

        entries.forEach(function (entry) {
            var key = sectionKey(entry);

            if (!byKey[key]) {
                byKey[key] = { key: key, path: entry.sectionPath || ['Без раздела'], entries: [] };
                groups.push(byKey[key]);
            }

            byKey[key].entries.push(entry);
        });

        return groups;
    }

    function renderSection(group) {
        var section = element('section', 'scmc-chem__section');
        var title = group.path[group.path.length - 1] || 'Без раздела';
        var heading = element('div', 'scmc-chem__section-heading');

        heading.appendChild(element('h2', 'scmc-chem__section-title', title));

        if (group.path.length > 1) {
            heading.appendChild(element('div', 'scmc-chem__breadcrumbs', group.path.slice(0, -1).join(' › ')));
        }

        heading.appendChild(element('span', 'scmc-chem__section-count', group.entries.length));
        section.appendChild(heading);

        var cards = element('div', 'scmc-chem__cards');
        group.entries.forEach(function (entry) {
            cards.appendChild(renderCard(entry));
        });
        section.appendChild(cards);
        return section;
    }

    function renderResults(root) {
        var results = root.querySelector('.scmc-chem__results');
        var entries = entriesForTab(state.activeTab).filter(function (entry) {
            return matchesQuery(entry, reagentById(entry.id));
        });

        results.textContent = '';

        root.querySelector('.scmc-chem__result-count').textContent = entries.length + ' из ' + entriesForTab(state.activeTab).length;

        if (!entries.length) {
            var empty = element('div', 'scmc-chem__no-results');
            append(
                empty,
                element('strong', '', 'Ничего не найдено.'),
                element('span', '', ' Попробуй другое название или ID реагента.')
            );
            results.appendChild(empty);
            return;
        }

        groupEntries(entries).forEach(function (group) {
            results.appendChild(renderSection(group));
        });
    }

    function renderTabs(root) {
        var tabs = root.querySelector('.scmc-chem__tabs');
        tabs.textContent = '';

        TAB_DEFINITIONS.forEach(function (tab) {
            var count = entriesForTab(tab.id).length;
            var button = element('button', 'scmc-chem__tab', tab.label);
            button.type = 'button';
            button.dataset.tab = tab.id;
            button.setAttribute('aria-selected', tab.id === state.activeTab ? 'true' : 'false');
            button.appendChild(element('span', 'scmc-chem__tab-count', count));

            button.addEventListener('click', function () {
                state.activeTab = tab.id;
                root.querySelectorAll('.scmc-chem__tab').forEach(function (item) {
                    item.setAttribute('aria-selected', item.dataset.tab === state.activeTab ? 'true' : 'false');
                });
                renderResults(root);
            });

            tabs.appendChild(button);
        });
    }

    function renderApp(root) {
        root.textContent = '';
        root.classList.add('scmc-chem');

        var header = element('header', 'scmc-chem__hero');
        var titleBlock = element('div', 'scmc-chem__hero-copy');
        append(
            titleBlock,
            element('div', 'scmc-chem__eyebrow', 'SSMC · автоматический справочник'),
            element('h1', 'scmc-chem__title', 'Химический каталог'),
            element('p', 'scmc-chem__lead', 'Рецепты, дозировки и эффекты из актуальных прототипов RMC14 и Space Stories.')
        );

        var stats = element('div', 'scmc-chem__stats');
        append(
            stats,
            element('span', 'scmc-chem__stat', state.data.counts.customReagents + ' реагентов'),
            element('span', 'scmc-chem__stat', state.data.counts.customReactions + ' реакций')
        );
        append(header, titleBlock, stats);
        root.appendChild(header);

        var controls = element('div', 'scmc-chem__controls');
        var search = element('label', 'scmc-chem__search');
        search.appendChild(element('span', 'scmc-chem__search-icon', '⌕'));
        var input = element('input', 'scmc-chem__search-input');
        input.type = 'search';
        input.placeholder = 'Название или ID реагента…';
        input.autocomplete = 'off';
        input.addEventListener('input', function () {
            state.query = normalizeSearch(input.value);
            renderResults(root);
        });
        search.appendChild(input);
        controls.appendChild(search);
        controls.appendChild(element('span', 'scmc-chem__result-count', ''));
        root.appendChild(controls);

        var tabs = element('div', 'scmc-chem__tabs');
        tabs.setAttribute('role', 'tablist');
        root.appendChild(tabs);
        root.appendChild(element('main', 'scmc-chem__results'));

        var footer = element('footer', 'scmc-chem__footer');
        var sourceDate = new Date(state.data.generatedAt);
        var dateText = isNaN(sourceDate.getTime()) ? state.data.generatedAt : sourceDate.toLocaleString('ru-RU');
        footer.textContent = 'Данные собраны автоматически · ' + dateText + ' · коммит ' + String(state.data.source.commit || '').slice(0, 8);
        root.appendChild(footer);

        renderTabs(root);
        renderResults(root);
    }

    function renderLoading(root) {
        root.textContent = '';
        var block = element('div', 'scmc-chem__status scmc-chem__status--loading');
        append(
            block,
            element('span', 'scmc-chem__spinner', ''),
            element('span', '', 'Загружаю химический каталог…')
        );
        root.appendChild(block);
    }

    function renderError(root, error) {
        root.textContent = '';
        var block = element('div', 'scmc-chem__status scmc-chem__status--error');
        append(
            block,
            element('strong', '', 'Каталог не загрузился.'),
            element('span', '', ' Проверь соединение и попробуй ещё раз.')
        );
        var button = element('button', 'scmc-chem__retry', 'Повторить');
        button.type = 'button';
        button.addEventListener('click', function () {
            load(root);
        });
        block.appendChild(button);
        root.appendChild(block);

        if (window.console && console.error) {
            console.error('[ScmcChemistry]', error);
        }
    }

    function validateData(data) {
        return data &&
            data.schemaVersion === 1 &&
            data.reagents &&
            data.dependencies &&
            data.reactions &&
            data.guides &&
            data.counts;
    }

    function load(root) {
        renderLoading(root);

        fetch(DATA_URL, { cache: 'no-store' })
            .then(function (response) {
                if (!response.ok) {
                    throw new Error('HTTP ' + response.status);
                }
                return response.json();
            })
            .then(function (data) {
                if (!validateData(data)) {
                    throw new Error('Неизвестная схема chemistry-catalog.json');
                }

                state.data = data;
                buildProductReactionIndex();
                renderApp(root);
            })
            .catch(function (error) {
                renderError(root, error);
            });
    }

    function init() {
        var root = document.getElementById(ROOT_ID);

        if (root) {
            load(root);
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
}());