// ========================================================= // ANIMATIONS - ЦВЕТА, СТИЛИ И АНИМАЦИИ // ========================================================= // ========================================================= // ПАРСИНГ ГРАДИЕНТОВ // ========================================================= function parseGradient(gradientString) { const colors = []; const positions = []; if (!gradientString || typeof gradientString !== 'string') { return { colors: ['rgb(0,0,0)'], positions: [0, 100] }; } const rgbRegex = /rgb\(\d+,\s*\d+,\s*\d+\)\s*(\d+%)?/g; let match; while ((match = rgbRegex.exec(gradientString)) !== null) { const rgbMatch = match[0].match(/rgb\(\d+,\s*\d+,\s*\d+\)/); if (rgbMatch) { colors.push(rgbMatch[0]); } const posMatch = match[0].match(/(\d+)%/); if (posMatch) { positions.push(parseInt(posMatch[1])); } } if (positions.length < colors.length && colors.length > 0) { while (positions.length < colors.length) { const index = positions.length; positions.push(Math.round((index / (colors.length - 1)) * 100)); } } if (colors.length === 0) { return { colors: ['rgb(0,0,0)'], positions: [0, 100] }; } return { colors, positions }; } function extractRgb(colorString) { const matches = colorString.match(/rgb\(\d+,\s*\d+,\s*\d+\)/g); return matches || ['rgb(0,0,0)']; } // ========================================================= // АНИМАЦИЯ ПОДСВЕТКИ // ========================================================= function animateBackground(tagsContainer, element, targetColorElement = 'rgb(117, 66, 19)', targetColorBorder = 'rgb(255, 123, 0)') { // Получаем исходные градиенты const elemGradient = parseGradient(getComputedStyle(element).background); const borderGradient = parseGradient(getComputedStyle(tagsContainer).background); console.log('innerElement:', element); const elemColors = elemGradient.colors.length > 0 ? elemGradient.colors : ['rgb(0,0,0)']; const elemPositions = elemGradient.positions.length > 0 ? elemGradient.positions : [0, 100]; const borderColors = borderGradient.colors.length > 0 ? borderGradient.colors : ['rgb(0,0,0)']; const borderPositions = borderGradient.positions.length > 0 ? borderGradient.positions : [0, 100]; const animName = `pulse-${Date.now()}`; const style = document.createElement('style'); let css = ''; // @property для элемента (load-order-row с 135deg) elemColors.forEach((color, index) => { css += ` @property --elem-color-${index} { syntax: ''; initial-value: ${color}; inherits: false; } `; }); // @property для tagsContainer (tag-marker с 180deg) borderColors.forEach((color, index) => { css += ` @property --border-color-${index} { syntax: ''; initial-value: ${color}; inherits: false; } `; }); // Ключевые кадры для элемента (135deg) css += ` @keyframes ${animName} { 0% { ${elemColors.map((color, index) => `--elem-color-${index}: ${color};`).join('\n')} } 30% { ${elemColors.map((_, index) => `--elem-color-${index}: ${targetColorElement};`).join('\n')} } 100% { ${elemColors.map((color, index) => `--elem-color-${index}: ${color};`).join('\n')} } } `; // Ключевые кадры для tagsContainer (180deg) css += ` @keyframes ${animName}border { 0% { ${borderColors.map((color, index) => `--border-color-${index}: ${color};`).join('\n')} } 30% { ${borderColors.map((_, index) => `--border-color-${index}: ${targetColorBorder};`).join('\n')} } 100% { ${borderColors.map((color, index) => `--border-color-${index}: ${color};`).join('\n')} } } `; style.textContent = css; document.head.appendChild(style); // Собираем градиенты с сохранением оригинальных позиций const elemStops = elemColors.map((_, i) => { const pos = elemPositions[i] !== undefined ? elemPositions[i] : Math.round((i / (elemColors.length - 1)) * 100); return `var(--elem-color-${i}) ${pos}%`; }).join(', '); const borderStops = borderColors.map((_, i) => { const pos = borderPositions[i] !== undefined ? borderPositions[i] : Math.round((i / (borderColors.length - 1)) * 100); return `var(--border-color-${i}) ${pos}%`; }).join(', '); // Применяем к элементу (load-order-row) - 135deg element.style.background = elemColors.length > 1 ? `linear-gradient(135deg, ${elemStops})` : `var(--elem-color-0)`; element.style.animation = `${animName} 1s ease 2`; if (tagsContainer === element) { // Применяем к tagsContainer те же эффекты, что и к element tagsContainer.style.background = elemColors.length > 1 ? `linear-gradient(135deg, ${elemStops})` : `var(--elem-color-0)`; tagsContainer.style.animation = `${animName} 1s ease 2`; } else { // Применяем к tagsContainer (tag-marker) - 180deg tagsContainer.style.background = borderColors.length > 1 ? `linear-gradient(180deg, ${borderStops})` : `var(--border-color-0)`; tagsContainer.style.animation = `${animName}border 1s ease 2`; } setTimeout(() => { element.style.animation = ''; element.style.background = ''; if (tagsContainer === element) { tagsContainer.style.animation = ''; tagsContainer.style.background = ''; } else { tagsContainer.style.animation = ''; tagsContainer.style.background = ''; } style.remove(); }, 2000); } // ========================================================= // ЦВЕТА ДЛЯ ТЭГОВ // ========================================================= const tagColorsMap = { '🆕': { color: '#3bac41', rgb: 'rgb(18,58,26)', dark: 'rgb(10,32,16)', name: 'new' }, '🔄': { color: '#418ce2', rgb: 'rgb(21,60,92)', dark: 'rgb(15,31,44)', name: 'updated' }, '⚠️': { color: '#c62828', rgb: 'rgb(88,22,22)', dark: 'rgb(45,14,14)', name: 'critical' }, '🔧': { color: '#f9a825', rgb: 'rgb(92,67,18)', dark: 'rgb(47,35,10)', name: 'optional' }, '💰': { color: '#ff6d00', rgb: 'rgb(96,46,10)', dark: 'rgb(50,23,6)', name: 'paid' }, '📍': { color: '#0004ff', rgb: 'rgb(29,43,102)', dark: 'rgb(16,23,55)', name: 'moved' } }; function getTagName(tag) { return tagColorsMap[tag]?.name || tag.replace(/[^\w]/g, ''); } function isMarkerTag(tag) { return tag in tagColorsMap; } function getMarkerTags(tags) { if (!tags || !Array.isArray(tags)) return []; return tags.filter(tag => isMarkerTag(tag)); } // ========================================================= // ГЕНЕРАЦИЯ CSS ДЛЯ МАРКЕРОВ ТЭГОВ // ========================================================= function generateTagMarkerCSS(tags) { if (!tags || tags.length === 0) return ''; const markerTags = getMarkerTags(tags); if (markerTags.length === 0) return ''; const count = markerTags.length; const classNames = markerTags.map(tag => getTagName(tag)); const className = 'tag-marker-' + classNames.join('-'); let css = ''; const hexColors = markerTags.map(tag => tagColorsMap[tag].color); let positions = []; if (count === 1) positions = [0, 100]; else if (count === 2) positions = [10, 48, 55, 120]; else if (count === 3) positions = [10, 12, 48, 75, 120]; else if (count === 4) positions = [10, 25, 45, 60, 85, 120]; else if (count === 5) positions = [10, 22, 34, 56, 58, 80, 120]; else if (count === 6) positions = [10, 20, 30, 50, 51, 70, 80, 120]; if (count === 1) { css += `.${className}{background:${hexColors[0]}}\n`; } else { let markerColors = []; markerTags.forEach((tag, index) => { if (index === 0) { markerColors.push(hexColors[index]); } markerColors.push(hexColors[index]); if (index === markerTags.length - 1) { markerColors.push(hexColors[index]); } }); while (markerColors.length > positions.length) { markerColors.pop(); } while (markerColors.length < positions.length) { markerColors.push(markerColors[markerColors.length - 1]); } const gradientStops = markerColors.map((color, i) => { return `${color} ${positions[i]}%`; }); css += `.${className}{background:linear-gradient(180deg,${gradientStops.join(',')})}\n`; } const rgbColors = markerTags.map(tag => tagColorsMap[tag].rgb); const darkColors = markerTags.map(tag => tagColorsMap[tag].dark); let rowPositions = []; let rowColors = []; if (count === 1) { rowPositions = [0, 100]; rowColors = [rgbColors[0], darkColors[0]]; } else { rowColors = []; markerTags.forEach((tag, index) => { if (index === 0) { rowColors.push(darkColors[index]); } rowColors.push(rgbColors[index]); if (index === markerTags.length - 1) { rowColors.push(darkColors[index]); } }); if (count === 2) rowPositions = [10, 48, 55, 120]; else if (count === 3) rowPositions = [10, 12, 48, 75, 120]; else if (count === 4) rowPositions = [10, 25, 45, 60, 85, 120]; else if (count === 5) rowPositions = [10, 22, 34, 56, 58, 80, 120]; else if (count === 6) rowPositions = [10, 20, 30, 50, 51, 70, 80, 120]; } const gradientStopsRow = rowColors.map((color, i) => { return `${color} ${rowPositions[i]}%`; }); css += `.load-order-row-tags.${className}>.load-order-row,.load-order-row-tags.${className}>.load-order-group{background:linear-gradient(135deg,${gradientStopsRow.join(',')})}\n`; return css; } function generateAllTagMarkersCSS(modsData) { if (!modsData || modsData.length === 0) return ''; const combinations = new Map(); modsData.forEach(item => { if (!item.tags) return; const markerTags = getMarkerTags(item.tags); if (markerTags.length === 0) return; const key = markerTags.join('-'); if (!combinations.has(key)) { combinations.set(key, markerTags); } }); let css = ''; const allTags = Array.from(combinations.values()); allTags.forEach(tags => { css += generateTagMarkerCSS(tags); }); return css; } let tagMarkersGenerated = false; function generateTagMarkerStyles() { if (tagMarkersGenerated) { return; } if (!modsData || modsData.length === 0) { setTimeout(generateTagMarkerStyles, 100); return; } const css = generateAllTagMarkersCSS(modsData); const oldStyle = document.getElementById('tag-marker-styles-auto'); if (oldStyle) oldStyle.remove(); if (css) { const style = document.createElement('style'); style.id = 'tag-marker-styles-auto'; style.textContent = css; document.head.appendChild(style); tagMarkersGenerated = true; } } // ========================================================= // СКРОЛЛ К МОДУ С ПОДСВЕТКОЙ // ========================================================= function scrollToModInLoadOrder(modName) { if (!modName) return; const cleanName = modName.trim().toLowerCase(); const ignoreList = ['nothing', 'release!', 'релиз!', 'ничего']; if (ignoreList.includes(cleanName)) return; const modRows = document.querySelectorAll('[class^="load-order-row"], [class^="load-order-group"]'); let targetRow = null; let foundType = null; for (const row of modRows) { const nameElement = row.querySelector('.order-name'); if (nameElement) { const rowName = nameElement.textContent.trim().toLowerCase(); if (rowName === cleanName) { targetRow = row; foundType = 'mod'; break; } } const groupTitle = row.querySelector('.group-title'); if (groupTitle && !targetRow) { const groupName = groupTitle.textContent.trim().toLowerCase(); if (groupName === cleanName) { targetRow = row; foundType = 'group'; break; } } } let innerElement = null; if (foundType === 'mod') { innerElement = targetRow.querySelector('.load-order-row'); } else if (foundType === 'group') { innerElement = targetRow.querySelector('.load-order-group'); } if (!innerElement) { innerElement = targetRow; } targetRow.style.transition = ''; const headerOffset = 80; const elementPosition = targetRow.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); setTimeout(() => { animateBackground(targetRow, innerElement); }, 1100); } // ========================================================= // ДЕЛАЕМ CHANGELOG КЛИКАБЕЛЬНЫМ // ========================================================= function makeChangelogItemsClickable() { const changelogLists = document.querySelectorAll('.changelog-category ul'); changelogLists.forEach(list => { let categoryColor = ''; let isRemovedCategory = false; const categoryHeader = list.closest('.changelog-category'); if (categoryHeader) { const headerText = categoryHeader.querySelector('h4')?.textContent || ''; if (headerText.includes('УДАЛЕНО') || headerText.includes('REMOVED')) { categoryColor = '#ef5350'; isRemovedCategory = true; // 👈 ПОМЕЧАЕМ КАТЕГОРИЮ КАК "УДАЛЕНО" } else if (headerText.includes('ОБНОВЛЕНО') || headerText.includes('UPDATED')) { categoryColor = '#42a5f5'; } else if (headerText.includes('ДОБАВЛЕНО') || headerText.includes('ADDED')) { categoryColor = '#66bb6a'; } else if (headerText.includes('ИЗМЕНЁН ПОРЯДОК') || headerText.includes('ORDER CHANGED') || headerText.includes('ИЗМЕНЁН ПОРЯДOK')) { categoryColor = '#201cf5'; } } const items = list.querySelectorAll('li'); items.forEach(item => { const originalText = item.textContent.trim(); const lowerText = originalText.toLowerCase(); const skipValues = ['nothing', 'release!', 'релиз!', 'ничего', 'no records']; if (isRemovedCategory) { if (categoryColor) { item.style.color = categoryColor; } item.style.cursor = 'default'; return; } if (skipValues.includes(lowerText)) { if (categoryColor) { item.style.color = categoryColor; } item.style.cursor = 'default'; return; } if (categoryColor) { item.style.color = categoryColor; } if (originalText.includes(',')) { const parts = originalText.split(',').map(p => p.trim()); item.innerHTML = ''; parts.forEach((part, index) => { const span = document.createElement('span'); span.textContent = part; span.style.cursor = 'pointer'; span.style.display = 'inline'; if (categoryColor) { span.style.color = categoryColor; } span.onclick = (e) => { e.stopPropagation(); scrollToModInLoadOrder(part); }; span.onmouseenter = () => { span.style.textDecoration = 'underline'; span.style.color = '#ffaa66'; }; span.onmouseleave = () => { span.style.textDecoration = 'none'; if (categoryColor) { span.style.color = categoryColor; } else { span.style.color = ''; } }; item.appendChild(span); if (index < parts.length - 1) { const comma = document.createTextNode(', '); if (categoryColor) { const commaSpan = document.createElement('span'); commaSpan.textContent = ', '; commaSpan.style.color = categoryColor; item.appendChild(commaSpan); } else { item.appendChild(comma); } } }); } else { item.style.cursor = 'pointer'; item.onclick = () => scrollToModInLoadOrder(originalText); item.onmouseenter = () => { item.style.textDecoration = 'underline'; item.style.color = '#ffaa66'; }; item.onmouseleave = () => { item.style.textDecoration = 'none'; if (categoryColor) { item.style.color = categoryColor; } else { item.style.color = ''; } }; } }); }); }