// ========================================================= // НАСТРОЙКИ ДЛЯ HEADER / SIDE PANELS // ========================================================= const SCROLL_HIDE_POINT = 60; const header = document.querySelector("header"); const leftPanel = document.querySelector(".left-panel"); const rightPanel = document.querySelector(".right-panel"); window.addEventListener("scroll", () => { const scrollY = window.pageYOffset; if (scrollY > SCROLL_HIDE_POINT) { if (header) header.classList.add("hide"); if (leftPanel) leftPanel.classList.add("show"); if (rightPanel) rightPanel.classList.add("show"); } else { if (header) header.classList.remove("hide"); if (leftPanel) leftPanel.classList.remove("show"); if (rightPanel) rightPanel.classList.remove("show"); } }); function goToSection(sectionId) { if (!sectionId) return; const element = document.getElementById(sectionId); if (element) element.scrollIntoView({ behavior: 'smooth', block: 'start' }); const navSelect = document.getElementById('navSelect'); if (navSelect) navSelect.value = ''; const gotonavSelect = document.getElementById('gotonavSelect'); if (gotonavSelect) gotonavSelect.value = ''; } function showNoteModal(noteText) { const currentLang = localStorage.getItem('lang') || 'ru'; const t = translations[currentLang]; let modal = document.getElementById('noteModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'noteModal'; modal.className = 'modal'; modal.innerHTML = ` `; document.body.appendChild(modal); } else { const titleEl = document.getElementById('noteModalTitle'); if (titleEl) titleEl.textContent = t.noteModalTitle; } const noteContent = document.getElementById('noteContent'); if (noteContent) { noteContent.innerHTML = noteText; } modal.style.display = 'flex'; } function showNote(note) { alert(note); } function closeModal(modalId) { document.getElementById(modalId).style.display = 'none'; } // Раскрывашка function toggleAccordion(header) { const content = header.nextElementSibling; const icon = header.querySelector('.accordion-icon'); content.classList.toggle('open'); icon.classList.toggle('open'); } // Добавление ссылки в версию function addLinkToVersion(versionId) { const container = document.getElementById(versionId); const linkDiv = document.createElement('div'); linkDiv.className = 'version-link-item'; linkDiv.innerHTML = ` `; container.appendChild(linkDiv); } // ========================================================= // КАСТОМНЫЙ СКРОЛЛ БАР ДЛЯ LOAD ORDER SECTION // ========================================================= function initCustomScrollbar() { const container = document.getElementById('loadOrderScrollContainer'); const scrollBar = document.getElementById('loadOrderScrollBar'); const scrollThumb = document.getElementById('loadOrderScrollThumb'); if (!container || !scrollBar || !scrollThumb) return; function updateThumb() { const containerHeight = container.clientHeight; const contentHeight = container.scrollHeight; if (contentHeight <= containerHeight || containerHeight === 0) { scrollBar.style.opacity = '0'; return; } const thumbHeight = Math.max(30, (containerHeight / contentHeight) * scrollBar.clientHeight); scrollThumb.style.height = thumbHeight + 'px'; const scrollPercent = container.scrollTop / (contentHeight - containerHeight); const maxThumbTop = scrollBar.clientHeight - thumbHeight; const thumbTop = scrollPercent * maxThumbTop; scrollThumb.style.top = thumbTop + 'px'; scrollBar.style.opacity = '1'; } let isDragging = false; scrollThumb.addEventListener('mousedown', (e) => { isDragging = true; e.preventDefault(); const startY = e.clientY; const startTop = parseFloat(scrollThumb.style.top) || 0; const maxThumbTop = scrollBar.clientHeight - scrollThumb.clientHeight; function onMouseMove(moveEvent) { if (!isDragging) return; let newTop = startTop + (moveEvent.clientY - startY); newTop = Math.max(0, Math.min(newTop, maxThumbTop)); scrollThumb.style.top = newTop + 'px'; const scrollPercent = newTop / maxThumbTop; const maxScroll = container.scrollHeight - container.clientHeight; container.scrollTop = scrollPercent * maxScroll; } function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); container.addEventListener('scroll', updateThumb); window.addEventListener('resize', updateThumb); const observer = new MutationObserver(() => { setTimeout(updateThumb, 100); }); observer.observe(container, { childList: true, subtree: true, attributes: true }); const wrapper = document.querySelector('.load-order-section-wrapper'); if (wrapper) { wrapper.addEventListener('mouseenter', () => { if (container.scrollHeight > container.clientHeight) { scrollBar.style.opacity = '1'; } }); wrapper.addEventListener('mouseleave', () => { if (!isDragging) { scrollBar.style.opacity = '0'; } }); } setTimeout(updateThumb, 200); } // ========================================================= // ПЕРЕКЛЮЧЕНИЕ МЕЖДУ STICKY И FIXED ПРИ ПРОХОДЕ ПЕРВОГО МОДА // ========================================================= function initScrollbar() { const scrollBar = document.getElementById('loadOrderScrollBar'); const scrollBarFixed = document.getElementById('loadOrderScrollBarFixed'); const scrollThumb = document.getElementById('loadOrderScrollThumb'); const scrollThumbFixed = document.getElementById('loadOrderScrollThumbFixed'); const container = document.getElementById('loadOrderScrollContainer'); const wrapper = document.querySelector('.load-order-section-wrapper'); if (!scrollBar || !scrollBarFixed || !container || !wrapper) { return; } const allRows = document.querySelectorAll('[class^="load-order-row"], [class^="load-order-group"]'); const firstMod = allRows[0]; const lastMod = allRows[allRows.length - 1]; if (!firstMod || !lastMod) { return; } const BASE_WIDTH = 1920; const BASE_RIGHT = 268; function getFixedRightPosition() { const loadOrderSection = document.getElementById('loadOrderSection'); const sectionRect = loadOrderSection.getBoundingClientRect(); const windowWidth = window.innerWidth; const marginLeft = 11; const position = windowWidth - sectionRect.right + marginLeft; return position; } function applyFixedPosition() { const pos = getFixedRightPosition(); scrollBarFixed.style.right = pos + 'px'; scrollBarFixed.style.position = 'fixed'; scrollBarFixed.style.top = '29px'; scrollBarFixed.style.width = '12px'; scrollBarFixed.style.height = '95vh'; scrollBarFixed.style.background = 'rgba(30, 30, 30, 0.9)'; scrollBarFixed.style.borderRadius = '12px'; scrollBarFixed.style.border = '1px solid #3a2420'; scrollBarFixed.style.boxShadow = '0 0 5px rgba(0,0,0,0.3)'; scrollBarFixed.style.zIndex = '1000'; scrollBarFixed.style.opacity = '0'; scrollBarFixed.style.pointerEvents = 'auto'; } scrollBar.style.opacity = '1'; applyFixedPosition(); function updateThumbPosition(thumb, scrollPercent) { const bar = thumb.parentElement; const thumbHeight = Math.max(40, (window.innerHeight / wrapper.scrollHeight) * bar.clientHeight); thumb.style.height = thumbHeight + 'px'; const maxThumbTop = bar.clientHeight - thumbHeight; const thumbTop = scrollPercent * maxThumbTop; thumb.style.top = thumbTop + 'px'; } function updateBothBars() { const wrapperHeight = wrapper.scrollHeight; const viewportHeight = window.innerHeight; const scrollTop = window.pageYOffset; const wrapperRect = wrapper.getBoundingClientRect(); const wrapperTop = wrapperRect.top + scrollTop; const scrolled = scrollTop - wrapperTop; const maxScroll = Math.max(1, wrapperHeight - viewportHeight); const scrollPercent = Math.max(0, Math.min(1, scrolled / maxScroll)); if (scrollThumb) updateThumbPosition(scrollThumb, scrollPercent); if (scrollThumbFixed) updateThumbPosition(scrollThumbFixed, scrollPercent); } let lastState = ''; function checkScrollPosition() { const allRows = document.querySelectorAll('[class^="load-order-row"], [class^="load-order-group"]'); const currentFirstMod = allRows[0]; const currentLastMod = allRows[allRows.length - 1]; if (!currentFirstMod || !currentLastMod) { scrollBar.style.opacity = '0'; scrollBarFixed.style.opacity = '0'; return; } const firstModRect = currentFirstMod.getBoundingClientRect(); const lastModRect = currentLastMod.getBoundingClientRect(); const viewportHeight = window.innerHeight; const isPastTop = firstModRect.top < 40; const isPastBottom = lastModRect.bottom < viewportHeight - 10; let currentState = ''; const pos = getFixedRightPosition(); scrollBarFixed.style.right = pos + 'px'; if (isPastTop && !isPastBottom) { currentState = 'FIXED'; scrollBarFixed.style.opacity = '1'; scrollBar.style.opacity = '0'; scrollBarFixed.style.zIndex = '2'; scrollBarFixed.style.pointerEvents = 'auto'; } else if (!isPastBottom && !isPastTop) { currentState = 'STICKY'; scrollBar.style.opacity = '1'; scrollBar.style.top = '20px'; scrollBar.style.bottom = 'auto'; scrollBar.style.alignSelf = 'flex-start'; scrollBarFixed.style.opacity = '0'; scrollBarFixed.style.pointerEvents = 'none'; } else if (isPastBottom && isPastTop) { currentState = 'BOTTOM'; scrollBar.style.opacity = '1'; scrollBar.style.top = 'auto'; scrollBar.style.bottom = '20px'; scrollBar.style.alignSelf = 'flex-end'; scrollBarFixed.style.opacity = '0'; scrollBarFixed.style.pointerEvents = 'none'; } if (currentState !== lastState) { lastState = currentState; } updateBothBars(); } function makeDraggable(thumb, bar) { if (!thumb) return; thumb.addEventListener('mousedown', (e) => { e.preventDefault(); const startY = e.clientY; const startTop = parseFloat(thumb.style.top) || 0; const maxThumbTop = bar.clientHeight - thumb.clientHeight; function onMouseMove(moveEvent) { let newTop = startTop + (moveEvent.clientY - startY); newTop = Math.max(0, Math.min(newTop, maxThumbTop)); thumb.style.top = newTop + 'px'; const scrollPercent = newTop / maxThumbTop; const wrapperRect = wrapper.getBoundingClientRect(); const wrapperTop = wrapperRect.top + window.pageYOffset; const wrapperHeight = wrapper.scrollHeight; const viewportHeight = window.innerHeight; const maxScroll = wrapperHeight - viewportHeight; window.scrollTo({ top: wrapperTop + (scrollPercent * maxScroll), behavior: 'smooth' }); } function onMouseUp() { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); } makeDraggable(scrollThumb, scrollBar); makeDraggable(scrollThumbFixed, scrollBarFixed); window.addEventListener('resize', () => { const pos = getFixedRightPosition(); scrollBarFixed.style.right = pos + 'px'; checkScrollPosition(); }); window.addEventListener('scroll', checkScrollPosition); const observer = new MutationObserver(() => { setTimeout(() => { const newLastMod = document.querySelector('.load-order-row:last-of-type, .load-order-group:last-of-type'); if (newLastMod) { window.lastMod = newLastMod; } checkScrollPosition(); }, 100); }); observer.observe(container, { childList: true, subtree: true }); setTimeout(checkScrollPosition, 300); } let scrollMarkersList = []; // Загружаем маркеры из отдельного JSON async function loadScrollMarkers() { try { const scrollBar = document.getElementById('loadOrderScrollBar'); const scrollBarFixed = document.getElementById('loadOrderScrollBarFixed'); if (scrollBar) { const oldMarkers = scrollBar.querySelectorAll('.scroll-markers'); oldMarkers.forEach(m => m.remove()); } if (scrollBarFixed) { const oldMarkers = scrollBarFixed.querySelectorAll('.scroll-markers'); oldMarkers.forEach(m => m.remove()); } const response = await fetch('combo/scrollMarkers.json'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); scrollMarkersList = data.markers || []; addScrollMarkers(); } catch (error) { console.warn('scrollMarkers.json не загружен:', error); } } function addScrollMarkers() { const scrollBar = document.getElementById('loadOrderScrollBar'); const scrollBarFixed = document.getElementById('loadOrderScrollBarFixed'); const wrapper = document.querySelector('.load-order-section-wrapper'); if (!scrollBar || !wrapper || !scrollMarkersList.length) return; const oldMarkers = scrollBar.querySelectorAll('.scroll-markers'); oldMarkers.forEach(m => m.remove()); if (scrollBarFixed) { const oldMarkersFixed = scrollBarFixed.querySelectorAll('.scroll-markers'); oldMarkersFixed.forEach(m => m.remove()); } const markersContainer = document.createElement('div'); markersContainer.className = 'scroll-markers'; const currentLang = localStorage.getItem('lang') || 'ru'; const topText = currentLang === 'ru' ? 'Вверх' : 'Top'; const bottomText = currentLang === 'ru' ? 'Вниз' : 'Bottom'; function getLoadOrderSection() { return document.getElementById('loadOrderSection'); } function scrollToLoadOrderTop() { const section = getLoadOrderSection(); if (section) { const headerOffset = 80; const elementPosition = section.getBoundingClientRect().top + window.pageYOffset; const offsetPosition = elementPosition - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } } function scrollToLoadOrderBottom() { const section = getLoadOrderSection(); if (section) { const headerOffset = 80; const elementPosition = section.getBoundingClientRect().bottom + window.pageYOffset; const offsetPosition = elementPosition - headerOffset - 800; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } } function createTopMarker() { const marker = document.createElement('div'); marker.className = 'scroll-marker scroll-marker-top'; marker.style.top = '0px'; marker.style.height = '16px'; marker.style.width = '16px'; marker.style.borderRadius = '50%'; marker.style.background = 'linear-gradient(135deg, #8b1e1e, #d95f02)'; marker.style.display = 'flex'; marker.style.alignItems = 'center'; marker.style.justifyContent = 'center'; marker.style.fontSize = '12px'; marker.style.color = '#fff'; marker.style.fontWeight = 'bold'; marker.style.cursor = 'pointer'; marker.style.position = 'absolute'; marker.style.left = '-2px'; marker.style.zIndex = '1003'; marker.setAttribute('data-tooltip', topText); marker.textContent = ' ▲'; marker.addEventListener('click', function(e) { e.stopPropagation(); scrollToLoadOrderTop(); }); return marker; } function createBottomMarker() { const marker = document.createElement('div'); marker.className = 'scroll-marker scroll-marker-bottom'; marker.style.bottom = '0px'; marker.style.top = 'auto'; marker.style.height = '16px'; marker.style.width = '16px'; marker.style.borderRadius = '50%'; marker.style.background = 'linear-gradient(135deg, #8b1e1e, #d95f02)'; marker.style.display = 'flex'; marker.style.alignItems = 'center'; marker.style.justifyContent = 'center'; marker.style.fontSize = '12px'; marker.style.color = '#fff'; marker.style.fontWeight = 'bold'; marker.style.cursor = 'pointer'; marker.style.position = 'absolute'; marker.style.left = '-2px'; marker.style.zIndex = '1003'; marker.setAttribute('data-tooltip', bottomText); marker.textContent = ' ▼'; marker.addEventListener('click', function(e) { e.stopPropagation(); scrollToLoadOrderBottom(); }); return marker; } const topMarker = createTopMarker(); markersContainer.appendChild(topMarker); const bottomMarker = createBottomMarker(); markersContainer.appendChild(bottomMarker); const allMods = document.querySelectorAll('[class^="load-order-row"], [class^="load-order-group"]'); scrollMarkersList.forEach((markerName) => { let targetMod = null; for (const mod of allMods) { const nameEl = mod.querySelector('.order-name'); const groupEl = mod.querySelector('.group-title'); const modName = nameEl ? nameEl.textContent.trim() : (groupEl ? groupEl.textContent.trim() : ''); if (modName === markerName) { targetMod = mod; break; } } if (targetMod) { const nameEl = targetMod.querySelector('.order-name'); const groupEl = targetMod.querySelector('.group-title'); const displayName = nameEl ? nameEl.textContent.trim() : (groupEl ? groupEl.textContent.trim() : markerName); const wrapperRect = wrapper.getBoundingClientRect(); const modRect = targetMod.getBoundingClientRect(); const wrapperTop = wrapperRect.top + window.pageYOffset; const modTop = modRect.top + window.pageYOffset; const relativeTop = modTop - wrapperTop; const wrapperHeight = wrapper.scrollHeight; const scrollPercent = Math.max(0, Math.min(1, relativeTop / wrapperHeight)); const barHeight = scrollBar.clientHeight; const markerTop = scrollPercent * barHeight; const marker = document.createElement('div'); marker.className = 'scroll-marker'; marker.style.top = `calc(${markerTop}px - 2px)`; marker.style.position = 'absolute'; marker.style.left = '-2px'; marker.style.width = '16px'; marker.style.height = '4px'; marker.style.background = '#ff5e00'; marker.style.borderRadius = '2px'; marker.style.cursor = 'pointer'; marker.style.zIndex = '1002'; marker.setAttribute('data-tooltip', displayName); marker.modElement = targetMod; marker.addEventListener('click', function(e) { e.stopPropagation(); const targetModElement = this.modElement; if (targetModElement) { let targetRow = targetModElement; let innerElement = null; const isMod = targetModElement.classList.contains('load-order-row') || targetModElement.querySelector('.order-name'); const isGroup = targetModElement.classList.contains('load-order-group') || targetModElement.querySelector('.group-title'); if (isMod) { innerElement = targetModElement.querySelector('.load-order-row'); if (!innerElement) { innerElement = targetModElement; } } else if (isGroup) { innerElement = targetModElement.querySelector('.load-order-group'); if (!innerElement) { innerElement = targetModElement; } } else { innerElement = targetModElement; } const headerOffset = 80; const elementPosition = targetModElement.getBoundingClientRect().top + window.pageYOffset; const offsetPosition = elementPosition - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); setTimeout(() => { animateBackground(targetRow, innerElement); }, 1100); } }); markersContainer.appendChild(marker); } }); scrollBar.appendChild(markersContainer); // ======================================== // FIXED SCROLLBAR // ======================================== if (scrollBarFixed) { const fixedContainer = document.createElement('div'); fixedContainer.className = 'scroll-markers'; // Top marker для fixed const fixedTopMarker = createTopMarker(); fixedContainer.appendChild(fixedTopMarker); // Bottom marker для fixed const fixedBottomMarker = createBottomMarker(); fixedContainer.appendChild(fixedBottomMarker); // Клонируем остальные маркеры const regularMarkers = markersContainer.querySelectorAll('.scroll-marker:not(.scroll-marker-top):not(.scroll-marker-bottom)'); regularMarkers.forEach((originalMarker) => { const displayName = originalMarker.getAttribute('data-tooltip') || ''; const modElement = originalMarker.modElement; if (modElement) { const wrapperRect = wrapper.getBoundingClientRect(); const modRect = modElement.getBoundingClientRect(); const wrapperTop = wrapperRect.top + window.pageYOffset; const modTop = modRect.top + window.pageYOffset; const relativeTop = modTop - wrapperTop; const wrapperHeight = wrapper.scrollHeight; const scrollPercent = Math.max(0, Math.min(1, relativeTop / wrapperHeight)); const barHeight = scrollBarFixed.clientHeight; const markerTop = scrollPercent * barHeight; const marker = document.createElement('div'); marker.className = 'scroll-marker'; marker.style.top = `calc(${markerTop}px - 2px)`; marker.style.position = 'absolute'; marker.style.left = '-2px'; marker.style.width = '16px'; marker.style.height = '4px'; marker.style.background = '#ff5e00'; marker.style.borderRadius = '2px'; marker.style.cursor = 'pointer'; marker.style.zIndex = '1002'; marker.setAttribute('data-tooltip', displayName); marker.modElement = modElement; marker.addEventListener('click', function(e) { e.stopPropagation(); const targetModElement = this.modElement; if (targetModElement) { let targetRow = targetModElement; let innerElement = null; const isMod = targetModElement.classList.contains('load-order-row') || targetModElement.querySelector('.order-name'); const isGroup = targetModElement.classList.contains('load-order-group') || targetModElement.querySelector('.group-title'); if (isMod) { innerElement = targetModElement.querySelector('.load-order-row'); if (!innerElement) { innerElement = targetModElement; } } else if (isGroup) { innerElement = targetModElement.querySelector('.load-order-group'); if (!innerElement) { innerElement = targetModElement; } } else { innerElement = targetModElement; } const headerOffset = 80; const elementPosition = targetModElement.getBoundingClientRect().top + window.pageYOffset; const offsetPosition = elementPosition - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); setTimeout(() => { animateBackground(targetRow, innerElement); }, 1100); } }); fixedContainer.appendChild(marker); } }); scrollBarFixed.appendChild(fixedContainer); } } function updateMarkers() { const scrollBar = document.getElementById('loadOrderScrollBar'); const scrollBarFixed = document.getElementById('loadOrderScrollBarFixed'); const wrapper = document.querySelector('.load-order-section-wrapper'); if (!scrollBar || !wrapper) return; const markers = scrollBar.querySelectorAll('.scroll-marker'); const fixedMarkers = scrollBarFixed ? scrollBarFixed.querySelectorAll('.scroll-marker') : []; if (!markers.length) return; const wrapperRect = wrapper.getBoundingClientRect(); const wrapperTop = wrapperRect.top + window.pageYOffset; const wrapperHeight = wrapper.scrollHeight; const barHeight = scrollBar.clientHeight; markers.forEach((marker) => { const targetMod = marker.modElement; if (targetMod && targetMod.isConnected) { const modRect = targetMod.getBoundingClientRect(); const modTop = modRect.top + window.pageYOffset; const relativeTop = modTop - wrapperTop; const scrollPercent = Math.max(0, Math.min(1, relativeTop / wrapperHeight)); const markerTop = scrollPercent * barHeight; marker.style.top = `calc(${markerTop}px - 2px)`; } }); if (fixedMarkers.length) { fixedMarkers.forEach((marker, idx) => { if (markers[idx] && markers[idx].modElement) { marker.modElement = markers[idx].modElement; const targetMod = marker.modElement; if (targetMod && targetMod.isConnected) { const modRect = targetMod.getBoundingClientRect(); const modTop = modRect.top + window.pageYOffset; const relativeTop = modTop - wrapperTop; const scrollPercent = Math.max(0, Math.min(1, relativeTop / wrapperHeight)); const markerTop = scrollPercent * barHeight; marker.style.top = `calc(${markerTop}px - 2px)`; } } }); } }