Files
2026-05-17 05:22:06 +03:00

482 lines
17 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function () {
// ===== THEME SWITCHER =====
var themeBtn = document.getElementById('theme-btn');
var themeIcon = document.getElementById('theme-icon');
var themeTooltip = document.getElementById('theme-tooltip');
var html = document.documentElement;
var themes = ['system', 'dark', 'light'];
var icons = { system: '💻', dark: '🌙', light: '☀️' };
var labels = {
system: 'Системная тема',
dark: 'Тёмная тема',
light: 'Светлая тема'
};
function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
function applyTheme(mode) {
var actual = mode === 'system' ? getSystemTheme() : mode;
html.setAttribute('data-theme', actual);
if (themeIcon) themeIcon.textContent = icons[mode];
if (themeTooltip) themeTooltip.textContent = labels[mode] + ' — нажмите для смены';
}
function getStoredTheme() {
return localStorage.getItem('theme') || 'system';
}
function cycleTheme() {
var current = getStoredTheme();
var idx = themes.indexOf(current);
var next = themes[(idx + 1) % themes.length];
localStorage.setItem('theme', next);
applyTheme(next);
}
// Init theme
applyTheme(getStoredTheme());
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
if (getStoredTheme() === 'system') {
applyTheme('system');
}
});
if (themeBtn) {
themeBtn.addEventListener('click', cycleTheme);
}
// ===== YANDEX MAP THEME =====
var mapFrame = document.getElementById('yandex-map');
if (mapFrame) {
var mapLon = mapFrame.getAttribute('data-lon');
var mapLat = mapFrame.getAttribute('data-lat');
var mapAddress = mapFrame.getAttribute('data-address') || '';
function updateMapTheme() {
var theme = html.getAttribute('data-theme') || 'light';
var params = 'll=' + mapLon + ',' + mapLat +
'&z=16' +
'&pt=' + mapLon + ',' + mapLat + ',pm2rdl' +
'&text=' + encodeURIComponent(mapAddress) +
'&theme=' + theme;
mapFrame.src = 'https://yandex.ru/map-widget/v1/?' + params;
}
updateMapTheme();
var mapObserver = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
if (mutations[i].attributeName === 'data-theme') {
updateMapTheme();
break;
}
}
});
mapObserver.observe(html, { attributes: true });
}
// ===== BURGER MENU =====
var burger = document.getElementById('burger');
var nav = document.querySelector('.header__nav');
if (burger && nav) {
burger.addEventListener('click', function () {
nav.classList.toggle('active');
});
}
// ===== SMOOTH SCROLL =====
document.querySelectorAll('a[href^="#"]').forEach(function (link) {
link.addEventListener('click', function (e) {
var id = this.getAttribute('href');
if (id === '#') return;
var target = document.querySelector(id);
if (target) {
e.preventDefault();
var headerH = document.querySelector('.header');
var offset = headerH ? headerH.offsetHeight : 0;
var top = target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({ top: top, behavior: 'smooth' });
if (nav) nav.classList.remove('active');
}
});
});
// ===== PHONE MASK =====
var phoneInput = document.getElementById('phone');
if (phoneInput) {
phoneInput.addEventListener('focus', function () {
if (this.value === '') {
this.value = '8 (___) ___-__-__';
}
});
phoneInput.addEventListener('input', function () {
var val = this.value.replace(/\D/g, '');
var formatted = '8 (';
if (val.length > 1) formatted += val.substring(1, 4);
if (val.length >= 4) formatted += ') ';
if (val.length > 4) formatted += val.substring(4, 7);
if (val.length >= 7) formatted += '-';
if (val.length > 7) formatted += val.substring(7, 9);
if (val.length >= 9) formatted += '-';
if (val.length > 9) formatted += val.substring(9, 11);
this.value = formatted.trim();
});
phoneInput.addEventListener('blur', function () {
if (this.value === '8 (___) ___-__-__' || this.value.replace(/\D/g, '').length < 11) {
this.value = '';
}
});
}
// ===== CHARACTER COUNTER =====
var messageField = document.getElementById('message');
var charCount = document.querySelector('.char-count');
if (messageField && charCount) {
messageField.addEventListener('input', function () {
var remaining = 500 - this.value.length;
charCount.textContent = 'Осталось: ' + remaining;
});
}
// ===== CAROUSEL =====
var carouselEl = document.querySelector('.carousel');
var track = document.querySelector('.carousel__track');
var slides = track ? Array.from(track.querySelectorAll('.case')) : [];
var prevBtn = document.querySelector('.carousel__prev');
var nextBtn = document.querySelector('.carousel__next');
var counterEl = document.getElementById('carousel-counter');
if (carouselEl && track && slides.length > 0 && prevBtn && nextBtn) {
var realCount = slides.length;
var isTransitioning = false;
// Clone first and last slide for infinite loop
if (realCount > 1) {
var firstClone = slides[0].cloneNode(true);
var lastClone = slides[realCount - 1].cloneNode(true);
firstClone.classList.add('clone');
lastClone.classList.add('clone');
track.insertBefore(lastClone, track.firstChild);
track.appendChild(firstClone);
}
// Re-query all slides including clones
slides = Array.from(track.querySelectorAll('.case'));
var totalSlides = slides.length;
// Start at real slide #2 (index 2 because clone of last is at index 0)
var currentIndex = realCount > 1 ? 2 : 1;
var autoTimer = null;
function getSlideWidth() {
return Math.round(carouselEl.clientWidth * 0.72);
}
function setupSlides() {
var w = getSlideWidth();
for (var i = 0; i < slides.length; i++) {
slides[i].style.width = w + 'px';
}
}
function updateActiveClass() {
var realIdx = getRealIndex(currentIndex);
for (var i = 0; i < slides.length; i++) {
if (slides[i].classList.contains('clone')) {
slides[i].classList.remove('case--active');
} else {
var thisRealIdx = i - 1;
slides[i].classList.toggle('case--active', thisRealIdx === realIdx);
}
}
}
function getRealIndex(idx) {
if (idx <= 0) return realCount - 1;
if (idx >= realCount + 1) return 0;
return idx - 1;
}
function goToSlide(index, animate) {
var slideW = getSlideWidth();
var carouselW = carouselEl.clientWidth;
var offset = (slideW * index) - (carouselW / 2) + (slideW / 2);
if (offset < 0) offset = 0;
if (animate === false) {
track.style.transition = 'none';
} else {
track.style.transition = 'transform 0.5s cubic-bezier(0.4, 0, 0.2, 1)';
}
track.style.transform = 'translateX(-' + offset + 'px)';
updateActiveClass();
if (counterEl) {
counterEl.textContent = (getRealIndex(currentIndex) + 1) + ' / ' + realCount;
}
}
function handleTransitionEnd() {
isTransitioning = false;
// If we landed on a clone, jump to the real slide without animation
if (currentIndex <= 0) {
currentIndex = realCount;
goToSlide(currentIndex, false);
} else if (currentIndex >= realCount + 1) {
currentIndex = 1;
goToSlide(currentIndex, false);
}
}
track.addEventListener('transitionend', handleTransitionEnd);
function startAuto() {
stopAuto();
if (realCount > 1) {
autoTimer = setInterval(function () {
currentIndex++;
goToSlide(currentIndex, true);
}, 5000);
}
}
function stopAuto() {
if (autoTimer) {
clearInterval(autoTimer);
autoTimer = null;
}
}
prevBtn.addEventListener('click', function () {
if (isTransitioning) return;
isTransitioning = true;
stopAuto();
currentIndex--;
goToSlide(currentIndex, true);
startAuto();
});
nextBtn.addEventListener('click', function () {
if (isTransitioning) return;
isTransitioning = true;
stopAuto();
currentIndex++;
goToSlide(currentIndex, true);
startAuto();
});
var resizeTimer;
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
setupSlides();
goToSlide(currentIndex, false);
}, 150);
});
setupSlides();
goToSlide(currentIndex, false);
startAuto();
}
// ===== COOKIE BANNER =====
var cookieBanner = document.getElementById('cookie-banner');
var cookieAccept = document.getElementById('cookie-accept');
if (cookieBanner && cookieAccept) {
if (!localStorage.getItem('cookies_accepted')) {
cookieBanner.classList.add('active');
}
cookieAccept.addEventListener('click', function () {
localStorage.setItem('cookies_accepted', '1');
cookieBanner.classList.remove('active');
});
}
// ===== STICKY PANEL =====
var stickyPanel = document.getElementById('sticky-panel');
var heroSection = document.getElementById('hero');
var footerEl = document.querySelector('.footer');
if (stickyPanel && heroSection) {
var footerVisible = false;
if (footerEl) {
var footerObserver = new IntersectionObserver(function (entries) {
footerVisible = entries[0].isIntersecting;
if (footerVisible) {
stickyPanel.classList.remove('active');
}
}, { threshold: 0.1 });
footerObserver.observe(footerEl);
}
var ticking = false;
function updateSticky() {
var heroBottom = heroSection.getBoundingClientRect().bottom;
if (!footerVisible) {
stickyPanel.classList.toggle('active', heroBottom < 0);
}
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) {
window.requestAnimationFrame(updateSticky);
ticking = true;
}
}, { passive: true });
updateSticky();
}
// ===== FAQ ACCORDION =====
document.querySelectorAll('.faq-question').forEach(function (btn) {
btn.addEventListener('click', function () {
var item = this.closest('.faq-item');
if (!item) return;
var isActive = item.classList.contains('active');
document.querySelectorAll('.faq-item').forEach(function (el) {
el.classList.remove('active');
});
if (!isActive) {
item.classList.add('active');
}
});
});
// ===== SCROLL REVEAL ANIMATIONS =====
var revealElements = document.querySelectorAll(
'.problem-card, .service-card, .industry-card, .step, .case, .faq-item, .testimonial-card, .info-card, .about-card'
);
if (revealElements.length > 0) {
var revealObserver = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
revealObserver.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -40px 0px'
});
revealElements.forEach(function (el, i) {
el.style.transitionDelay = (i % 3) * 0.08 + 's';
revealObserver.observe(el);
});
}
// ===== FORM SUBMIT =====
var form = document.getElementById('contact-form');
var responseDiv = document.getElementById('form-response');
if (form && responseDiv) {
// Убираем ошибку при фокусе на поле
form.querySelectorAll('input, textarea').forEach(function (input) {
input.addEventListener('focus', function () {
var group = this.closest('.form-group');
if (group) {
group.classList.remove('form-group--error');
var err = group.querySelector('.error-msg');
if (err) {
err.classList.remove('visible');
err.textContent = '';
}
}
});
});
form.addEventListener('submit', async function (e) {
e.preventDefault();
// Сброс всех ошибок
document.querySelectorAll('.form-group--error').forEach(function (el) {
el.classList.remove('form-group--error');
});
document.querySelectorAll('.error-msg.visible').forEach(function (el) {
el.classList.remove('visible');
el.textContent = '';
});
// Клиентская валидация
var hasError = false;
var nameInput = document.getElementById('name');
if (nameInput && !nameInput.value.trim()) {
showFieldError(nameInput, 'Введите ваше имя');
hasError = true;
}
var phoneInput = document.getElementById('phone');
if (phoneInput) {
var digits = phoneInput.value.replace(/\D/g, '');
if (digits.length < 11) {
showFieldError(phoneInput, 'Введите корректный номер телефона');
hasError = true;
}
}
var agreeInput = document.getElementById('agree');
if (agreeInput && !agreeInput.checked) {
var agreeGroup = agreeInput.closest('.form-group');
if (agreeGroup) {
agreeGroup.classList.add('form-group--error');
var errSpan = agreeGroup.querySelector('.error-msg');
if (errSpan) {
errSpan.textContent = 'Необходимо согласиться с политикой обработки персональных данных';
errSpan.classList.add('visible');
}
}
hasError = true;
}
if (hasError) return;
var formData = new FormData(form);
formData.append('ajax', '1');
try {
var res = await fetch(window.location.href, {
method: 'POST',
body: formData
});
var data = await res.json();
if (data.success) {
form.style.display = 'none';
responseDiv.innerHTML =
'<div class="success-msg">' +
'<div class="check-icon">' +
'<svg viewBox="0 0 24 24"><polyline points="4 12 10 18 20 6"></polyline></svg>' +
'</div>' +
'<p>' + data.message + '</p>' +
'</div>';
} else {
responseDiv.innerHTML = '<p style="color:var(--error); font-weight:600; text-align:center;">' + data.message + '</p>';
}
} catch (err) {
responseDiv.innerHTML = '<p style="color:var(--error); font-weight:600; text-align:center;">Произошла ошибка. Попробуйте позже.</p>';
}
});
}
function showFieldError(input, message) {
var group = input.closest('.form-group');
if (!group) return;
group.classList.add('form-group--error');
var errSpan = group.querySelector('.error-msg');
if (errSpan) {
errSpan.textContent = message;
errSpan.classList.add('visible');
}
}
});