MediaWiki:Common.js: відмінності між версіями

Матеріал з Київський національний лінгвістичний університет
Перейти до навігації Перейти до пошуку
Немає опису редагування
Мітка: Скасовано
Немає опису редагування
Мітка: Ручний відкіт
Рядок 1: Рядок 1:
/* ── Заголовок сторінки ── */
$(function() {
#firstHeading {
     $('.category-card').each(function() {
     font-size: clamp(28px, 4vw, 48px);
        var $card = $(this);
}
        var href = $card.attr('data-href');
       
        if (href) {
            $card.css('cursor', 'pointer');
           
            $card.on('click', function(e) {
                if ($(e.target).closest('a').length === 0) {
                    window.location.href = href;
                }
            });
           
            $card.hover(
                function() { $card.addClass('card-hover'); },
                function() { $card.removeClass('card-hover'); }
            );
        }
    });
});
/* ── Випадкові статті на головній сторінці ── */


/* ── Бокове меню ── */
/* Витягнути назву зображення з вікі-тексту */
#mw-panel {
function extractImageName(wikitext) {
     font-size: clamp(14px, 1.2vw, 18px);
     if (!wikitext) return null;
}


/* ── Прибираємо обмеження Vector ── */
    // 1. Шукаємо в Infobox: | image = Файл.jpg
body.skin-vector-2022 .mw-page-container-inner,
    var infoboxImg = wikitext.match(/\|\s*image\s*=\s*([^\|\n\}]+)/i);
body.skin-vector .mw-content-container,
    if (infoboxImg) {
body.skin-vector .vector-body,
        var name = infoboxImg[1].trim();
body.skin-vector #bodyContent {
        if (name && name.length > 0) return name;
    max-width: none !important;
     }
    padding-left: 0 !important;
     padding-right: 0 !important;
}


body.skin-vector-2022 .mw-body,
    // 2. Шукаємо [[Файл:Назва.jpg|...]] або [[File:...]]
body.skin-vector-2022 .mw-body-content {
     var fileMatch = wikitext.match(/\[\[(?:Файл|File|Зображення|Image):([^\|\]]+)/i);
     max-width: none !important;
     if (fileMatch) {
     margin: 0 !important;
        return fileMatch[1].trim();
}
    }


/* ════════════════════════════════════
     return null;
  ПРИВІТАННЯ + СТАТИСТИКА
═══════════════════════════════════════ */
.welcome-wrapper {
     display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 40px;
    padding: 40px 5vw 40px;
    box-sizing: border-box;
    width: 100%;
}
}


.welcome-text {
/* Витягнути перший змістовний абзац */
    flex: 1;
function extractDescription(wikitext) {
    text-align: center;
     if (!wikitext) return '';
    /* Зміщуємо центр тексту компенсуючи ширину панелі статистики */
     transform: translateX(-80px);
}


.welcome-subtitle {
    // Прибрати інфобокс {{...}} (може бути багаторядковим)
     font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    var text = wikitext;
     font-size: 1.45rem;
     var depth = 0;
     font-weight: 400;
     var infoboxEnd = -1;
    color: #444;
     for (var i = 0; i < text.length; i++) {
    letter-spacing: 0.6px;
        if (text[i] === '{') depth++;
     margin-bottom: 10px;
        else if (text[i] === '}') {
     opacity: 0.92;
            depth--;
}
            if (depth === 0) { infoboxEnd = i + 1; break; }
        }
     }
     if (infoboxEnd > 0) text = text.substring(infoboxEnd);


.welcome-title {
     // Прибрати [[Файл:...]] блоки
     font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
     text = text.replace(/\[\[(?:Файл|File|Зображення|Image):[^\]]*\]\]/gi, '');
    font-size: 2.45rem;
     font-weight: 700;
    line-height: 1.18;
    color: #003d82;
    letter-spacing: -0.3px;
    margin: 0;
}


/* ── Панель статистики (справа від привітання) ── */
    // Прибрати wikitable {| ... |}
.stats-panel {
     text = text.replace(/\{\|[\s\S]*?\|\}/g, '');
    width: 260px;
    flex-shrink: 0;
    background: white;
    border-radius: 16px;
     border: 1px solid rgba(0,61,130,0.08);
    box-shadow: 0 6px 24px rgba(0,0,0,0.07);
    overflow: hidden;
}


.stats-panel-header {
    // Прибрати шаблони {{...}}
     background: rgba(210, 230, 255, 0.35);
     text = text.replace(/\{\{[^}]*\}\}/g, '');
    color: #003d82;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    font-size: 1rem;
    font-weight: 700;
    padding: 12px 18px;
    border-bottom: 1px solid rgba(0,61,130,0.1);
}


.stats-panel-list {
    // Прибрати заголовки == ... ==
     padding: 4px 0;
     text = text.replace(/={2,6}[^=\n]+=+/g, '');
}


.stats-panel-item {
     // Прибрати вікі-посилання — залишити текст
     display: flex;
     text = text.replace(/\[\[(?:[^\|\]]*\|)?([^\]]+)\]\]/g, '$1');
    justify-content: space-between;
     align-items: center;
    padding: 10px 18px;
    border-bottom: 1px solid rgba(0,61,130,0.05);
    transition: background 0.2s;
}


.stats-panel-item:last-child {
    // Прибрати зовнішні посилання [url текст]
     border-bottom: none;
    text = text.replace(/\[[^\s\]]+\s+([^\]]+)\]/g, '$1');
}
     text = text.replace(/\[[^\]]+\]/g, '');


.stats-panel-item:hover {
    // Прибрати жирний/курсив
     background: rgba(210, 230, 255, 0.2);
     text = text.replace(/'{2,3}/g, '');
}


.stats-panel-label {
    // Прибрати HTML теги
     font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
     text = text.replace(/<[^>]+>/g, '');
    font-size: 14px;
    color: #555;
}


.stats-panel-number {
     // Прибрати рядки що починаються з * # : ;
     font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
     text = text.replace(/^[\*#:;].*/gm, '');
     font-size: 18px;
    font-weight: 800;
    color: #003d82;
}


/* ════════════════════════════════════
    // Прибрати порожні рядки і зайві пробіли
  ЗАГОЛОВОК КАТЕГОРІЙ
    var lines = text.split('\n').map(function(l) { return l.trim(); }).filter(function(l) { return l.length > 20; });
═══════════════════════════════════════ */
.section-categories {
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    font-size: 2.1rem;
    font-weight: 700;
    color: #003d82;
    text-align: center;
    margin: 30px 0 30px;
    /* Компенсуємо ширину правої колонки щоб текст був по центру сітки */
    padding: 0 calc(360px + 24px) 0 0;
    background: transparent;
    box-sizing: border-box;
}


.section-categories::after {
     // Взяти перший змістовний рядок
    content: '';
     if (lines.length > 0) {
    display: block;
        var result = lines[0].substring(0, 160);
    width: 90px;
        if (lines[0].length > 160) result += '…';
    height: 4px;
        return result;
     background: #ffd700;
     }
    margin: 14px auto 0;
    border-radius: 2px;
}
 
/* ════════════════════════════════════
  ОСНОВНИЙ LAYOUT: категорії + права колонка
═══════════════════════════════════════ */
.main-content-layout {
    display: flex;
    gap: 24px;
    align-items: flex-start;
     padding: 0 5vw;
    margin: 0 0 70px;
    box-sizing: border-box;
    width: 100%;
}
 
/* ── Сітка карток — 3 в ряд ── */
.category-grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    flex: 1;
    min-width: 0;
}
 
/* ── Права колонка ── */
.right-column {
    width: 360px;
    min-width: 360px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 20px;
    align-self: flex-start;
    padding-top: 0;
}
 
/* ════════════════════════════════════
  КАРТКИ КАТЕГОРІЙ
═══════════════════════════════════════ */
.category-card {
    background: white;
    border-radius: 16px;
    overflow: hidden;
    box-shadow: 0 6px 24px rgba(0,0,0,0.08);
    transition: all 0.28s cubic-bezier(0.165, 0.84, 0.44, 1);
    cursor: pointer;
    border: 1px solid rgba(0,61,130,0.08);
    height: 100%;
    display: flex;
     flex-direction: column;
}
 
.category-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 20px 40px rgba(0,61,130,0.18);
    border-color: rgba(0,61,130,0.15);
}


.card-image {
     return '';
     padding: 28px 0 16px;
    flex-grow: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: white;
}
}


.card-image img {
function loadRandomArticles() {
     width: 88px;
     var list = document.getElementById('random-articles-list');
    height: 88px;
     if (!list) return;
    object-fit: contain;
     transition: transform 0.35s ease;
}


.category-card:hover .card-image img {
    list.innerHTML = '<div class="random-articles-loading">Завантаження...</div>';
    transform: scale(1.12);
}


.card-title {
    var apiBase = mw.config.get('wgScriptPath') + '/api.php';
    font-size: 15px;
    font-weight: 600;
    color: #003d82;
    text-align: center;
    padding: 8px 12px 12px;
    line-height: 1.4;
    flex-grow: 0;
    background: rgba(210, 230, 255, 0.15);
    border-radius: 10px;
    margin: 0 10px 14px;
}


.card-title pre {
     // Сторінки які не повинні з'являтись у випадкових статтях
    background: transparent !important;
     var excludedPages = ['Головна сторінка', 'Структурні підрозділи', 'Викладачі'];
    border: none !important;
    box-shadow: none !important;
     padding: 0 !important;
     margin: 0 !important;
    font-family: inherit !important;
    font-size: inherit !important;
    font-weight: inherit !important;
    color: inherit !important;
    white-space: normal !important;
}


/* ════════════════════════════════════
    fetch(apiBase + '?action=query&list=random&rnnamespace=0&rnlimit=10&format=json')
  ВИПАДКОВІ СТАТТІ
        .then(function(r) { return r.json(); })
═══════════════════════════════════════ */
        .then(function(data) {
.random-articles-panel {
            var pages = data.query.random.filter(function(p) {
    background: white;
                return excludedPages.indexOf(p.title) === -1;
    border-radius: 16px;
            }).slice(0, 3);
    border: 1px solid rgba(0,61,130,0.08);
    box-shadow: 0 6px 24px rgba(0,0,0,0.07);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    position: relative;
}


            var titles = pages.map(function(p) { return p.title; }).join('|');


            return fetch(
                apiBase +
                '?action=query' +
                '&titles=' + encodeURIComponent(titles) +
                '&prop=revisions' +
                '&rvprop=content' +
                '&format=json'
            );
        })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            var pages = Object.values(data.query.pages);
            list.innerHTML = '';


.random-articles-header {
            var promises = pages.map(function(page) {
    background: rgba(210, 230, 255, 0.35);
                var title = page.title;
    color: #003d82;
                var pageUrl = mw.config.get('wgArticlePath').replace(
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                    '$1', encodeURIComponent(title.replace(/ /g, '_'))
    font-size: 1rem;
                );
    font-weight: 700;
    padding: 14px 18px;
    border-bottom: 1px solid rgba(0,61,130,0.1);
}


#random-articles-list {
                var content = '';
    padding: 12px;
                if (page.revisions && page.revisions[0]) {
    display: flex;
                    content = page.revisions[0]['*'] || '';
    flex-direction: column;
                }
    gap: 12px;
}


.random-article-card {
                var excerpt = extractDescription(content);
    display: flex;
                if (!excerpt) excerpt = 'Немає опису.';
    gap: 14px;
    padding: 14px;
    border-radius: 12px;
    background: rgba(210, 230, 255, 0.12);
    border: 1px solid rgba(0,61,130,0.07);
    text-decoration: none;
    color: inherit;
    transition: all 0.22s ease;
    align-items: flex-start;
}


.random-article-card:hover {
                var imgName = extractImageName(content);
    background: rgba(210, 230, 255, 0.35);
    border-color: rgba(0,61,130,0.18);
    transform: translateX(3px);
    text-decoration: none;
}


.random-article-thumb {
                if (imgName) {
    width: 90px;
                    var fileTitle = imgName.match(/^(Файл|File|Image|Зображення):/i)
    height: 90px;
                        ? imgName
    border-radius: 10px;
                        : 'File:' + imgName;
    object-fit: cover;
    flex-shrink: 0;
    background: #eef2f8;
}


.random-article-thumb-placeholder {
                    return fetch(
    width: 90px;
                        apiBase +
    height: 90px;
                        '?action=query' +
    border-radius: 10px;
                        '&titles=' + encodeURIComponent(fileTitle) +
    flex-shrink: 0;
                        '&prop=imageinfo' +
    background: linear-gradient(135deg, #e8eef7, #c8d8ee);
                        '&iiprop=url' +
    display: flex;
                        '&iiurlwidth=120' +
    align-items: center;
                        '&format=json'
    justify-content: center;
                    )
    font-size: 22px;
                    .then(function(r) { return r.json(); })
}
                    .then(function(imgData) {
                        var imgPages = Object.values(imgData.query.pages);
                        var imgSrc = null;
                        if (imgPages[0] && imgPages[0].imageinfo && imgPages[0].imageinfo[0]) {
                            imgSrc = imgPages[0].imageinfo[0].thumburl || imgPages[0].imageinfo[0].url;
                        }
                        return { title: title, excerpt: excerpt, pageUrl: pageUrl, imgSrc: imgSrc };
                    })
                    .catch(function() {
                        return { title: title, excerpt: excerpt, pageUrl: pageUrl, imgSrc: null };
                    });
                }


.random-article-info {
                return Promise.resolve({ title: title, excerpt: excerpt, pageUrl: pageUrl, imgSrc: null });
    flex: 1;
            });
    min-width: 0;
}


.random-article-title {
            return Promise.all(promises);
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        })
    font-size: 15px;
        .then(function(results) {
    font-weight: 700;
            var list2 = document.getElementById('random-articles-list');
    color: #003d82;
            if (!list2) return;
    margin-bottom: 6px;
            list2.innerHTML = '';
    line-height: 1.35;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}


.random-article-excerpt {
            results.forEach(function(item) {
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                var thumbHtml = item.imgSrc
    font-size: 13px;
                    ? '<img class="random-article-thumb" src="' + item.imgSrc + '" alt="">'
    color: #555;
                    : '<div class="random-article-thumb-placeholder">📄</div>';
    line-height: 1.45;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
}


.random-articles-loading {
                var card = document.createElement('a');
    padding: 20px;
                card.href = item.pageUrl;
    text-align: center;
                card.className = 'random-article-card';
    color: #888;
                card.innerHTML =
    font-size: 13px;
                    thumbHtml +
    font-family: system-ui, sans-serif;
                    '<div class="random-article-info">' +
}
                        '<div class="random-article-title">' + mw.html.escape(item.title) + '</div>' +
                        '<div class="random-article-excerpt">' + mw.html.escape(item.excerpt) + '</div>' +
                    '</div>';


.random-articles-refresh {
                list2.appendChild(card);
    display: none;
            });
        })
        .catch(function(err) {
            var list3 = document.getElementById('random-articles-list');
            if (list3) list3.innerHTML = '<div class="random-articles-loading">Не вдалося завантажити статті.</div>';
            console.error('[RA] error:', err);
        });
}
}


/* ════════════════════════════════════
/* Запуск */
  РЕСУРСИ ТА ПОСИЛАННЯ
mw.hook('wikipage.content').add(function() {
═══════════════════════════════════════ */
     var panel = document.getElementById('random-articles-panel');
.resources-and-contacts {
     if (!panel) return;
     display: flex;
    flex-wrap: wrap;
     gap: 28px;
    margin: 30px 0 50px;
    align-items: stretch;
}


.resource-block,
     loadRandomArticles();
.contacts-block {
     flex: 1;
    min-width: 300px;
    background: rgba(220, 235, 255, 0.18);
    border: 1px solid rgba(180, 210, 240, 0.5);
    border-radius: 14px;
    padding: 28px 32px;
    box-shadow: 0 4px 12px rgba(0,61,130,0.06);
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
}


.resource-block ul,
    var btn = document.getElementById('random-articles-refresh');
.contacts-block ul {
    if (btn) {
    flex: 1;
        btn.addEventListener('click', function() {
    list-style: none;
            loadRandomArticles();
    padding-left: 0;
        });
     margin: 0;
     }
}
});


.resource-block h2,
/* ══════════════════════════════════════════════
.contacts-block h2 {
   ГОЛОВНА СТОРІНКА: покращення
    font-size: 1.3rem !important;
══════════════════════════════════════════════ */
    font-weight: 700 !important;
    color: #003d82 !important;
    margin: 0 0 20px 0 !important;
    padding: 0 0 14px 0 !important;
    border: none !important;
    border-bottom: 2px solid rgba(0,61,130,0.12) !important;
    line-height: 1.3 !important;
}
 
.resource-block li,
.contacts-block li {
    margin-bottom: 11px;
    line-height: 1.6;
    font-size: 1rem;
    color: #444;
    padding-left: 14px;
    position: relative;
}
 
.resource-block li::before,
.contacts-block li::before {
    content: '›';
    position: absolute;
    left: 0;
    color: #003d82;
    font-weight: 700;
    opacity: 0.5;
}
 
.resource-block li a,
.contacts-block li a {
    color: #1e40af;
    text-decoration: none;
    transition: color 0.2s;
}
 
.resource-block li a:hover,
.contacts-block li a:hover {
    color: #003d82;
    text-decoration: underline;
}
 
/* ════════════════════════════════════
   АДАПТИВНІСТЬ
═══════════════════════════════════════ */
@media (max-width: 1100px) {
    .welcome-wrapper {
        flex-direction: column;
        text-align: center;
    }
    .stats-panel {
        width: 100%;
        max-width: 400px;
    }
    .main-content-layout {
        flex-direction: column;
    }
    .right-column {
        width: 100%;
        min-width: unset;
        flex-direction: row;
        flex-wrap: wrap;
    }
    .category-grid-container {
        grid-template-columns: repeat(3, 1fr);
    }
}


@media (max-width: 820px) {
/* ── 1. AOS: анімації при скролі ── */
     .category-grid-container {
mw.loader.getScript('https://unpkg.com/aos@2.3.1/dist/aos.js').then(function() {
         grid-template-columns: repeat(2, 1fr);
     mw.loader.load('https://unpkg.com/aos@2.3.1/dist/aos.css', 'text/css');
   
    if (typeof AOS !== 'undefined') {
         AOS.init({
            duration: 600,
            easing: 'ease-out-cubic',
            once: true,
            offset: 50
        });
     }
     }
}
});


@media (max-width: 520px) {
/* ── 2. Анімація чисел статистики ── */
     .category-grid-container {
function animateNumber(element, target) {
        grid-template-columns: 1fr;
     element.classList.add('animating');
     }
    var current = 0;
     .welcome-title { font-size: 2rem; }
     var increment = target / 60; // 60 кадрів
    .welcome-subtitle { font-size: 1.25rem; }
     var timer = setInterval(function() {
    .section-categories { font-size: 1.8rem; }
        current += increment;
        if (current >= target) {
            current = target;
            clearInterval(timer);
            setTimeout(function() { element.classList.remove('animating'); }, 100);
        }
        element.textContent = Math.floor(current);
    }, 16); // ~60 FPS
}
}


@media (max-width: 900px) {
mw.hook('wikipage.content').add(function() {
     .resources-and-contacts {
     // Запускаємо анімацію тільки на головній сторінці
         flex-direction: column;
    if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
         gap: 20px;
   
    var statsNumbers = document.querySelectorAll('.stats-panel-number');
    var animated = false;
   
    function checkAndAnimate() {
         if (animated) return;
        var statsPanel = document.querySelector('.stats-panel');
        if (!statsPanel) return;
       
        var rect = statsPanel.getBoundingClientRect();
         if (rect.top < window.innerHeight && rect.bottom > 0) {
            animated = true;
            statsNumbers.forEach(function(el) {
                var text = el.textContent.trim();
                var num = parseInt(text.replace(/[^\d]/g, ''), 10);
                if (!isNaN(num) && num > 0) {
                    el.textContent = '0';
                    setTimeout(function() { animateNumber(el, num); }, 200);
                }
            });
        }
     }
     }
}
      
 
     window.addEventListener('scroll', checkAndAnimate);
/* ── Ліва колонка з заголовком і сіткою ── */
     checkAndAnimate();
.category-column {
});
     flex: 1;
     min-width: 0;
     display: flex;
    flex-direction: column;
}


.category-column .category-grid-container {
    flex: 1;
}




    .timeline-dot {
/* ── 4. Сортування карточок за прізвищем ── */
        left: -33px;
mw.hook('wikipage.content').add(function() {
    }
     document.querySelectorAll('div[style*="grid-template-columns"]').forEach(function(grid) {
     .timeline-year {
         var cards = Array.from(grid.children);
         font-size: 1.2rem;
        if (cards.length < 2) return;
    }
    .timeline-event {
        font-size: 14px;
    }
}


        cards.sort(function(a, b) {
            var aLink = a.querySelector('div[style*="flex-grow"] a');
            var bLink = b.querySelector('div[style*="flex-grow"] a');
            if (!aLink || !bLink) return 0;


            var aSurname = aLink.textContent.trim().split(' ')[0];
            var bSurname = bLink.textContent.trim().split(' ')[0];


            return aSurname.localeCompare(bSurname, 'uk');
        });


        cards.forEach(function(card) {
            grid.appendChild(card);
        });
    });
});


/* ══════════════════════════════════════════════
/* ══════════════════════════════════════════════
   MODERN UI: градієнти, glassmorphism, анімації
   ТАЙМЛАЙН ІСТОРІЇ УНІВЕРСИТЕТУ
══════════════════════════════════════════════ */
══════════════════════════════════════════════ */


/* ── Прогрес скролла ── */
var timelineData = [
.scroll-progress {
    { year: 1948, event: "Заснування університету" },
     position: fixed;
     { year: 1962, event: "Початок наукової школи прагмалінгвістики та мовленнєвої комунікації" },
     top: 0;
     { year: 1963, event: "Започаткування школи комп'ютерної лінгвістики та статистичної лексикографії" },
     left: 0;
     { year: 1972, event: "Створення школи лінгвостилістики в синхронії та діахронії" },
     height: 4px;
     { year: 1992, event: "Заснування наукової школи контрастивної семантики і типології" },
     background: linear-gradient(90deg, #003d82, #0066cc, #ffd700);
     { year: 1993, event: "Відкриття першої в Україні кафедри ЮНЕСКО" },
     z-index: 99999;
     { year: 1994, event: "Внесення до реєстру закладів освіти України з IV рівнем акредитації" },
     transition: width 0.1s ease;
     { year: 1996, event: "Формування школи зарубіжного і порівняльного літературознавства" },
     box-shadow: 0 2px 8px rgba(0, 61, 130, 0.3);
     { year: 2000, event: "Проведення Міжнародної конференції ЮНЕСКО «Лінгвопакс-VIII»" }
}
];


/* ── Градієнтний фон для welcome ── */
mw.hook('wikipage.content').add(function() {
.welcome-wrapper {
    var container = document.getElementById('history-timeline');
     background: linear-gradient(180deg,  
    if (!container) return;
         rgba(210, 230, 255, 0.4) 0%,
   
         rgba(210, 230, 255, 0.25) 40%,
     timelineData.forEach(function(item, index) {
         rgba(255, 255, 255, 0) 100%);
         var point = document.createElement('div');
    position: relative;
        point.className = 'timeline-point';
    overflow: hidden;
         point.setAttribute('data-aos', 'fade-up');
     padding-bottom: 60px !important;
         point.setAttribute('data-aos-delay', (100 + index * 100).toString());
}
       
        point.innerHTML =
            '<div class="timeline-dot"></div>' +
            '<div class="timeline-content">' +
                '<div class="timeline-year">' + item.year + '</div>' +
                '<div class="timeline-event">' + item.event + '</div>' +
            '</div>';
       
        container.appendChild(point);
     });
});


/* Плавний перехід до білого фону */
/* ── AOS на картки категорій ── */
.welcome-wrapper::after {
mw.hook('wikipage.content').add(function() {
     content: '';
     if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
     position: absolute;
      
     bottom: 0;
     var cards = document.querySelectorAll('.category-card');
     left: 0;
     cards.forEach(function(card, index) {
    right: 0;
        card.setAttribute('data-aos', 'fade-up');
    height: 80px;
        card.setAttribute('data-aos-delay', (100 + (index % 12) * 50).toString());
    background: linear-gradient(to bottom, transparent, white);
     });
    pointer-events: none;
});
     z-index: 1;
}


.welcome-text,
.stats-panel {
    position: relative;
    z-index: 2;
}


/* ── Glassmorphism для статистики ── */
.stats-panel {
    background: rgba(255, 255, 255, 0.7) !important;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.3) !important;
    box-shadow: 0 8px 32px rgba(0, 61, 130, 0.15) !important;
}


.stats-panel-header {
    background: linear-gradient(135deg, rgba(0, 61, 130, 0.1), rgba(0, 102, 204, 0.15)) !important;
}


/* ── Glassmorphism для випадкових статей ── */
.random-articles-panel {
    background: rgba(255, 255, 255, 0.75) !important;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.3) !important;
}


/* ── Градієнтні картки категорій ── */
/* ══════════════════════════════════════════════
.category-card {
  MODERN UI: Particles.js фон + прогрес скролла
    background: linear-gradient(135deg, #ffffff 0%, #f8fbff 100%);
══════════════════════════════════════════════ */
    border: 1px solid rgba(0, 61, 130, 0.08);
    position: relative;
    overflow: hidden;
}


.category-card::before {
/* ── 1. Прогрес скролла ── */
     content: '';
mw.hook('wikipage.content').add(function() {
     position: absolute;
     if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
     top: -50%;
      
     left: -50%;
    // Створюємо індикатор
     width: 200%;
    var progressBar = document.createElement('div');
     height: 200%;
     progressBar.className = 'scroll-progress';
    background: linear-gradient(135deg, transparent, rgba(0, 61, 130, 0.05), transparent);
     document.body.appendChild(progressBar);
    transform: rotate(45deg);
      
     transition: all 0.6s ease;
     function updateProgress() {
     opacity: 0;
        var winScroll = document.documentElement.scrollTop || document.body.scrollTop;
}
        var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
        var scrolled = (winScroll / height) * 100;
        progressBar.style.width = scrolled + '%';
     }
   
    window.addEventListener('scroll', updateProgress);
     updateProgress();
});


.category-card:hover::before {
/* ── 2. Particles.js canvas фон ── */
     opacity: 1;
mw.hook('wikipage.content').add(function() {
     top: -25%;
     if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
     left: -25%;
      
}
    var welcomeWrapper = document.querySelector('.welcome-wrapper');
 
     if (!welcomeWrapper) return;
/* ── Мікроанімації: bounce для іконок ── */
   
.category-card:hover .card-image img {
    // Створюємо canvas
     animation: icon-bounce 0.6s ease;
    var canvas = document.createElement('canvas');
}
    canvas.className = 'particles-canvas';
 
     canvas.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;';
@keyframes icon-bounce {
   
    0%, 100% { transform: scale(1.12) translateY(0); }
    // Gradient mask для плавного переходу по краях
    50% { transform: scale(1.12) translateY(-8px); }
    canvas.style.webkitMaskImage = 'linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%), linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%)';
}
    canvas.style.maskImage = 'linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%), linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%)';
 
    canvas.style.webkitMaskComposite = 'source-in';
/* ── Gradient hover для карточок ── */
     canvas.style.maskComposite = 'intersect';
.category-card {
    welcomeWrapper.style.position = 'relative';
    transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
    welcomeWrapper.insertBefore(canvas, welcomeWrapper.firstChild);
}
   
 
     var ctx = canvas.getContext('2d');
.category-card:hover {
     canvas.width = welcomeWrapper.offsetWidth;
    transform: translateY(-8px) scale(1.02);
    canvas.height = welcomeWrapper.offsetHeight;
    box-shadow: 0 24px 48px rgba(0, 61, 130, 0.2);
   
}
    var particles = [];
 
     var particleCount = 40;
/* ── Pulse для чисел статистики ── */
     var mouse = { x: null, y: null, radius: 100 };
.stats-panel-number {
      
     animation: number-pulse 2s ease-in-out infinite;
     // Частинки
}
     function Particle() {
 
        this.x = Math.random() * canvas.width;
@keyframes number-pulse {
        this.y = Math.random() * canvas.height;
     0%, 100% { transform: scale(1); }
        this.vx = (Math.random() - 0.5) * 0.5;
     50% { transform: scale(1.05); color: #0066cc; }
        this.vy = (Math.random() - 0.5) * 0.5;
}
        this.radius = Math.random() * 2 + 1;
 
    }
/* ── Rainbow effect для анімації чисел ── */
   
@keyframes rainbow {
    Particle.prototype.draw = function() {
     0% { color: #003d82; }
        ctx.beginPath();
     25% { color: #0066cc; }
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
     50% { color: #4a90e2; }
        ctx.fillStyle = 'rgba(255, 215, 0, 0.6)';
     75% { color: #0066cc; }
        ctx.fill();
     100% { color: #003d82; }
     };
}
      
 
     Particle.prototype.update = function() {
.stats-panel-number.animating {
        if (this.x > canvas.width || this.x < 0) this.vx = -this.vx;
    animation: rainbow 1.5s ease-in-out;
        if (this.y > canvas.height || this.y < 0) this.vy = -this.vy;
}
       
 
        this.x += this.vx;
/* ── Gradient title ── */
        this.y += this.vy;
.welcome-title {
       
    background: linear-gradient(135deg, #003d82 0%, #0066cc 50%, #003d82 100%);
        this.draw();
    -webkit-background-clip: text;
     };
     -webkit-text-fill-color: transparent;
   
     background-clip: text;
    // Ініціалізація
     animation: gradient-shift 3s ease infinite;
    for (var i = 0; i < particleCount; i++) {
    background-size: 200% 200%;
        particles.push(new Particle());
}
     }
 
   
@keyframes gradient-shift {
    // Анімація
    0%, 100% { background-position: 0% 50%; }
     function animate() {
     50% { background-position: 100% 50%; }
         ctx.clearRect(0, 0, canvas.width, canvas.height);
}
       
 
         for (var i = 0; i < particles.length; i++) {
/* ── Smooth reveal для карточок ── */
            particles[i].update();
.category-card {
           
     /* AOS сам додає opacity при ініціалізації */
            // З'єднання лініями
}
            for (var j = i + 1; j < particles.length; j++) {
 
                var dx = particles[i].x - particles[j].x;
/* ── Неоморфізм для випадкових статей ── */
                var dy = particles[i].y - particles[j].y;
.random-article-card {
                var distance = Math.sqrt(dx * dx + dy * dy);
     background: linear-gradient(145deg, #ffffff, #f0f6ff);
               
    box-shadow:
                if (distance < 120) {
         8px 8px 16px rgba(0, 61, 130, 0.08),
                    ctx.beginPath();
         -8px -8px 16px rgba(255, 255, 255, 0.9);
                    ctx.strokeStyle = 'rgba(255, 215, 0, ' + (1 - distance / 120) * 0.3 + ')';
    border: none !important;
                    ctx.lineWidth = 1;
}
                    ctx.moveTo(particles[i].x, particles[i].y);
 
                    ctx.lineTo(particles[j].x, particles[j].y);
.random-article-card:hover {
                    ctx.stroke();
    box-shadow:
                }
        12px 12px 24px rgba(0, 61, 130, 0.12),
            }
        -12px -12px 24px rgba(255, 255, 255, 1);
        }
    background: linear-gradient(145deg, #f0f6ff, #ffffff);
       
}
        requestAnimationFrame(animate);
 
    }
/* ── Glassmorphism для заголовків ── */
   
.section-categories {
    animate();
    text-shadow: 0 2px 8px rgba(0, 61, 130, 0.1);
   
}
    // Resize
 
    window.addEventListener('resize', function() {
/* ── Gradient підкреслення ── */
        canvas.width = welcomeWrapper.offsetWidth;
.section-categories::after {
        canvas.height = welcomeWrapper.offsetHeight;
    background: linear-gradient(90deg, #ffd700, #ffed4e, #ffd700) !important;
     });
     box-shadow: 0 2px 8px rgba(255, 215, 0, 0.4);
});
}

Версія за 20:22, 16 лютого 2026

$(function() {
    $('.category-card').each(function() {
        var $card = $(this);
        var href = $card.attr('data-href');
        
        if (href) {
            $card.css('cursor', 'pointer');
            
            $card.on('click', function(e) {
                if ($(e.target).closest('a').length === 0) {
                    window.location.href = href;
                }
            });
            
            $card.hover(
                function() { $card.addClass('card-hover'); },
                function() { $card.removeClass('card-hover'); }
            );
        }
    });
});
/* ── Випадкові статті на головній сторінці ── */

/* Витягнути назву зображення з вікі-тексту */
function extractImageName(wikitext) {
    if (!wikitext) return null;

    // 1. Шукаємо в Infobox: | image = Файл.jpg
    var infoboxImg = wikitext.match(/\|\s*image\s*=\s*([^\|\n\}]+)/i);
    if (infoboxImg) {
        var name = infoboxImg[1].trim();
        if (name && name.length > 0) return name;
    }

    // 2. Шукаємо [[Файл:Назва.jpg|...]] або [[File:...]]
    var fileMatch = wikitext.match(/\[\[(?:Файл|File|Зображення|Image):([^\|\]]+)/i);
    if (fileMatch) {
        return fileMatch[1].trim();
    }

    return null;
}

/* Витягнути перший змістовний абзац */
function extractDescription(wikitext) {
    if (!wikitext) return '';

    // Прибрати інфобокс {{...}} (може бути багаторядковим)
    var text = wikitext;
    var depth = 0;
    var infoboxEnd = -1;
    for (var i = 0; i < text.length; i++) {
        if (text[i] === '{') depth++;
        else if (text[i] === '}') {
            depth--;
            if (depth === 0) { infoboxEnd = i + 1; break; }
        }
    }
    if (infoboxEnd > 0) text = text.substring(infoboxEnd);

    // Прибрати [[Файл:...]] блоки
    text = text.replace(/\[\[(?:Файл|File|Зображення|Image):[^\]]*\]\]/gi, '');

    // Прибрати wikitable {| ... |}
    text = text.replace(/\{\|[\s\S]*?\|\}/g, '');

    // Прибрати шаблони {{...}}
    text = text.replace(/\{\{[^}]*\}\}/g, '');

    // Прибрати заголовки == ... ==
    text = text.replace(/={2,6}[^=\n]+=+/g, '');

    // Прибрати вікі-посилання — залишити текст
    text = text.replace(/\[\[(?:[^\|\]]*\|)?([^\]]+)\]\]/g, '$1');

    // Прибрати зовнішні посилання [url текст]
    text = text.replace(/\[[^\s\]]+\s+([^\]]+)\]/g, '$1');
    text = text.replace(/\[[^\]]+\]/g, '');

    // Прибрати жирний/курсив
    text = text.replace(/'{2,3}/g, '');

    // Прибрати HTML теги
    text = text.replace(/<[^>]+>/g, '');

    // Прибрати рядки що починаються з * # : ;
    text = text.replace(/^[\*#:;].*/gm, '');

    // Прибрати порожні рядки і зайві пробіли
    var lines = text.split('\n').map(function(l) { return l.trim(); }).filter(function(l) { return l.length > 20; });

    // Взяти перший змістовний рядок
    if (lines.length > 0) {
        var result = lines[0].substring(0, 160);
        if (lines[0].length > 160) result += '…';
        return result;
    }

    return '';
}

function loadRandomArticles() {
    var list = document.getElementById('random-articles-list');
    if (!list) return;

    list.innerHTML = '<div class="random-articles-loading">Завантаження...</div>';

    var apiBase = mw.config.get('wgScriptPath') + '/api.php';

    // Сторінки які не повинні з'являтись у випадкових статтях
    var excludedPages = ['Головна сторінка', 'Структурні підрозділи', 'Викладачі'];

    fetch(apiBase + '?action=query&list=random&rnnamespace=0&rnlimit=10&format=json')
        .then(function(r) { return r.json(); })
        .then(function(data) {
            var pages = data.query.random.filter(function(p) {
                return excludedPages.indexOf(p.title) === -1;
            }).slice(0, 3);

            var titles = pages.map(function(p) { return p.title; }).join('|');

            return fetch(
                apiBase +
                '?action=query' +
                '&titles=' + encodeURIComponent(titles) +
                '&prop=revisions' +
                '&rvprop=content' +
                '&format=json'
            );
        })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            var pages = Object.values(data.query.pages);
            list.innerHTML = '';

            var promises = pages.map(function(page) {
                var title = page.title;
                var pageUrl = mw.config.get('wgArticlePath').replace(
                    '$1', encodeURIComponent(title.replace(/ /g, '_'))
                );

                var content = '';
                if (page.revisions && page.revisions[0]) {
                    content = page.revisions[0]['*'] || '';
                }

                var excerpt = extractDescription(content);
                if (!excerpt) excerpt = 'Немає опису.';

                var imgName = extractImageName(content);

                if (imgName) {
                    var fileTitle = imgName.match(/^(Файл|File|Image|Зображення):/i)
                        ? imgName
                        : 'File:' + imgName;

                    return fetch(
                        apiBase +
                        '?action=query' +
                        '&titles=' + encodeURIComponent(fileTitle) +
                        '&prop=imageinfo' +
                        '&iiprop=url' +
                        '&iiurlwidth=120' +
                        '&format=json'
                    )
                    .then(function(r) { return r.json(); })
                    .then(function(imgData) {
                        var imgPages = Object.values(imgData.query.pages);
                        var imgSrc = null;
                        if (imgPages[0] && imgPages[0].imageinfo && imgPages[0].imageinfo[0]) {
                            imgSrc = imgPages[0].imageinfo[0].thumburl || imgPages[0].imageinfo[0].url;
                        }
                        return { title: title, excerpt: excerpt, pageUrl: pageUrl, imgSrc: imgSrc };
                    })
                    .catch(function() {
                        return { title: title, excerpt: excerpt, pageUrl: pageUrl, imgSrc: null };
                    });
                }

                return Promise.resolve({ title: title, excerpt: excerpt, pageUrl: pageUrl, imgSrc: null });
            });

            return Promise.all(promises);
        })
        .then(function(results) {
            var list2 = document.getElementById('random-articles-list');
            if (!list2) return;
            list2.innerHTML = '';

            results.forEach(function(item) {
                var thumbHtml = item.imgSrc
                    ? '<img class="random-article-thumb" src="' + item.imgSrc + '" alt="">'
                    : '<div class="random-article-thumb-placeholder">📄</div>';

                var card = document.createElement('a');
                card.href = item.pageUrl;
                card.className = 'random-article-card';
                card.innerHTML =
                    thumbHtml +
                    '<div class="random-article-info">' +
                        '<div class="random-article-title">' + mw.html.escape(item.title) + '</div>' +
                        '<div class="random-article-excerpt">' + mw.html.escape(item.excerpt) + '</div>' +
                    '</div>';

                list2.appendChild(card);
            });
        })
        .catch(function(err) {
            var list3 = document.getElementById('random-articles-list');
            if (list3) list3.innerHTML = '<div class="random-articles-loading">Не вдалося завантажити статті.</div>';
            console.error('[RA] error:', err);
        });
}

/* Запуск */
mw.hook('wikipage.content').add(function() {
    var panel = document.getElementById('random-articles-panel');
    if (!panel) return;

    loadRandomArticles();

    var btn = document.getElementById('random-articles-refresh');
    if (btn) {
        btn.addEventListener('click', function() {
            loadRandomArticles();
        });
    }
});

/* ══════════════════════════════════════════════
   ГОЛОВНА СТОРІНКА: покращення
══════════════════════════════════════════════ */

/* ── 1. AOS: анімації при скролі ── */
mw.loader.getScript('https://unpkg.com/aos@2.3.1/dist/aos.js').then(function() {
    mw.loader.load('https://unpkg.com/aos@2.3.1/dist/aos.css', 'text/css');
    
    if (typeof AOS !== 'undefined') {
        AOS.init({
            duration: 600,
            easing: 'ease-out-cubic',
            once: true,
            offset: 50
        });
    }
});

/* ── 2. Анімація чисел статистики ── */
function animateNumber(element, target) {
    element.classList.add('animating');
    var current = 0;
    var increment = target / 60; // 60 кадрів
    var timer = setInterval(function() {
        current += increment;
        if (current >= target) {
            current = target;
            clearInterval(timer);
            setTimeout(function() { element.classList.remove('animating'); }, 100);
        }
        element.textContent = Math.floor(current);
    }, 16); // ~60 FPS
}

mw.hook('wikipage.content').add(function() {
    // Запускаємо анімацію тільки на головній сторінці
    if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
    
    var statsNumbers = document.querySelectorAll('.stats-panel-number');
    var animated = false;
    
    function checkAndAnimate() {
        if (animated) return;
        var statsPanel = document.querySelector('.stats-panel');
        if (!statsPanel) return;
        
        var rect = statsPanel.getBoundingClientRect();
        if (rect.top < window.innerHeight && rect.bottom > 0) {
            animated = true;
            statsNumbers.forEach(function(el) {
                var text = el.textContent.trim();
                var num = parseInt(text.replace(/[^\d]/g, ''), 10);
                if (!isNaN(num) && num > 0) {
                    el.textContent = '0';
                    setTimeout(function() { animateNumber(el, num); }, 200);
                }
            });
        }
    }
    
    window.addEventListener('scroll', checkAndAnimate);
    checkAndAnimate();
});



/* ── 4. Сортування карточок за прізвищем ── */
mw.hook('wikipage.content').add(function() {
    document.querySelectorAll('div[style*="grid-template-columns"]').forEach(function(grid) {
        var cards = Array.from(grid.children);
        if (cards.length < 2) return;

        cards.sort(function(a, b) {
            var aLink = a.querySelector('div[style*="flex-grow"] a');
            var bLink = b.querySelector('div[style*="flex-grow"] a');
            if (!aLink || !bLink) return 0;

            var aSurname = aLink.textContent.trim().split(' ')[0];
            var bSurname = bLink.textContent.trim().split(' ')[0];

            return aSurname.localeCompare(bSurname, 'uk');
        });

        cards.forEach(function(card) {
            grid.appendChild(card);
        });
    });
});

/* ══════════════════════════════════════════════
   ТАЙМЛАЙН ІСТОРІЇ УНІВЕРСИТЕТУ
══════════════════════════════════════════════ */

var timelineData = [
    { year: 1948, event: "Заснування університету" },
    { year: 1962, event: "Початок наукової школи прагмалінгвістики та мовленнєвої комунікації" },
    { year: 1963, event: "Започаткування школи комп'ютерної лінгвістики та статистичної лексикографії" },
    { year: 1972, event: "Створення школи лінгвостилістики в синхронії та діахронії" },
    { year: 1992, event: "Заснування наукової школи контрастивної семантики і типології" },
    { year: 1993, event: "Відкриття першої в Україні кафедри ЮНЕСКО" },
    { year: 1994, event: "Внесення до реєстру закладів освіти України з IV рівнем акредитації" },
    { year: 1996, event: "Формування школи зарубіжного і порівняльного літературознавства" },
    { year: 2000, event: "Проведення Міжнародної конференції ЮНЕСКО «Лінгвопакс-VIII»" }
];

mw.hook('wikipage.content').add(function() {
    var container = document.getElementById('history-timeline');
    if (!container) return;
    
    timelineData.forEach(function(item, index) {
        var point = document.createElement('div');
        point.className = 'timeline-point';
        point.setAttribute('data-aos', 'fade-up');
        point.setAttribute('data-aos-delay', (100 + index * 100).toString());
        
        point.innerHTML =
            '<div class="timeline-dot"></div>' +
            '<div class="timeline-content">' +
                '<div class="timeline-year">' + item.year + '</div>' +
                '<div class="timeline-event">' + item.event + '</div>' +
            '</div>';
        
        container.appendChild(point);
    });
});

/* ── AOS на картки категорій ── */
mw.hook('wikipage.content').add(function() {
    if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
    
    var cards = document.querySelectorAll('.category-card');
    cards.forEach(function(card, index) {
        card.setAttribute('data-aos', 'fade-up');
        card.setAttribute('data-aos-delay', (100 + (index % 12) * 50).toString());
    });
});





/* ══════════════════════════════════════════════
   MODERN UI: Particles.js фон + прогрес скролла
══════════════════════════════════════════════ */

/* ── 1. Прогрес скролла ── */
mw.hook('wikipage.content').add(function() {
    if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
    
    // Створюємо індикатор
    var progressBar = document.createElement('div');
    progressBar.className = 'scroll-progress';
    document.body.appendChild(progressBar);
    
    function updateProgress() {
        var winScroll = document.documentElement.scrollTop || document.body.scrollTop;
        var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
        var scrolled = (winScroll / height) * 100;
        progressBar.style.width = scrolled + '%';
    }
    
    window.addEventListener('scroll', updateProgress);
    updateProgress();
});

/* ── 2. Particles.js canvas фон ── */
mw.hook('wikipage.content').add(function() {
    if (mw.config.get('wgPageName') !== 'Головна_сторінка') return;
    
    var welcomeWrapper = document.querySelector('.welcome-wrapper');
    if (!welcomeWrapper) return;
    
    // Створюємо canvas
    var canvas = document.createElement('canvas');
    canvas.className = 'particles-canvas';
    canvas.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;';
    
    // Gradient mask для плавного переходу по краях
    canvas.style.webkitMaskImage = 'linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%), linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%)';
    canvas.style.maskImage = 'linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%), linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 10%, rgba(0,0,0,1) 90%, rgba(0,0,0,0) 100%)';
    canvas.style.webkitMaskComposite = 'source-in';
    canvas.style.maskComposite = 'intersect';
    welcomeWrapper.style.position = 'relative';
    welcomeWrapper.insertBefore(canvas, welcomeWrapper.firstChild);
    
    var ctx = canvas.getContext('2d');
    canvas.width = welcomeWrapper.offsetWidth;
    canvas.height = welcomeWrapper.offsetHeight;
    
    var particles = [];
    var particleCount = 40;
    var mouse = { x: null, y: null, radius: 100 };
    
    // Частинки
    function Particle() {
        this.x = Math.random() * canvas.width;
        this.y = Math.random() * canvas.height;
        this.vx = (Math.random() - 0.5) * 0.5;
        this.vy = (Math.random() - 0.5) * 0.5;
        this.radius = Math.random() * 2 + 1;
    }
    
    Particle.prototype.draw = function() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(255, 215, 0, 0.6)';
        ctx.fill();
    };
    
    Particle.prototype.update = function() {
        if (this.x > canvas.width || this.x < 0) this.vx = -this.vx;
        if (this.y > canvas.height || this.y < 0) this.vy = -this.vy;
        
        this.x += this.vx;
        this.y += this.vy;
        
        this.draw();
    };
    
    // Ініціалізація
    for (var i = 0; i < particleCount; i++) {
        particles.push(new Particle());
    }
    
    // Анімація
    function animate() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        for (var i = 0; i < particles.length; i++) {
            particles[i].update();
            
            // З'єднання лініями
            for (var j = i + 1; j < particles.length; j++) {
                var dx = particles[i].x - particles[j].x;
                var dy = particles[i].y - particles[j].y;
                var distance = Math.sqrt(dx * dx + dy * dy);
                
                if (distance < 120) {
                    ctx.beginPath();
                    ctx.strokeStyle = 'rgba(255, 215, 0, ' + (1 - distance / 120) * 0.3 + ')';
                    ctx.lineWidth = 1;
                    ctx.moveTo(particles[i].x, particles[i].y);
                    ctx.lineTo(particles[j].x, particles[j].y);
                    ctx.stroke();
                }
            }
        }
        
        requestAnimationFrame(animate);
    }
    
    animate();
    
    // Resize
    window.addEventListener('resize', function() {
        canvas.width = welcomeWrapper.offsetWidth;
        canvas.height = welcomeWrapper.offsetHeight;
    });
});