Files
web-dev/assets/js/main.js
Ignat Karelov 0719b11093
Some checks failed
Deploy / deploy (push) Has been cancelled
feat: added web
2026-06-02 17:45:51 +03:00

562 lines
17 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function ($) {
'use strict';
$(function () {
const $expertise = $('.expertise');
if (!$expertise.length || typeof Swiper !== 'function') {
return;
}
const $slider = $expertise.find('.expertise__cases');
const $wrapper = $expertise.find('.expertise__track');
const $tabs = $expertise.find('.expertise__tab');
const sourceSlides = $wrapper.find('.expertise-card').map(function () {
const $slide = $(this);
return {
group: $slide.data('expertise-group'),
html: this.outerHTML,
};
}).get();
let expertiseSwiper = null;
let activeFilter = null;
const getSlidesByFilter = function (filter) {
return sourceSlides.filter(function (slide) {
return slide.group === filter;
});
};
const updateTabs = function (filter) {
$tabs.each(function () {
const $tab = $(this);
const isActive = $tab.data('expertise-filter') === filter;
$tab
.toggleClass('is-active', isActive)
.attr({
'aria-selected': String(isActive),
'aria-pressed': String(isActive),
});
});
};
const destroySlider = function () {
if (!expertiseSwiper) {
return;
}
expertiseSwiper.destroy(true, true);
expertiseSwiper = null;
};
const getLoopSlidesHtml = function (slides) {
// Для loop в Swiper важно, чтобы реальных слайдов было заметно больше,
// чем помещается в видимой области. Иначе Swiper отключает loop на широких экранах.
const minSlidesForLoop = 24;
const repeatCount = Math.max(1, Math.ceil(minSlidesForLoop / slides.length));
let html = '';
for (let i = 0; i < repeatCount; i += 1) {
slides.forEach(function (slide) {
html += slide.html;
});
}
return html;
};
const initSlider = function () {
const slidesCount = $wrapper.children('.swiper-slide').length;
const loopEnabled = slidesCount > 1;
expertiseSwiper = new Swiper($slider[0], {
slidesPerView: 'auto',
spaceBetween: 16,
speed: 650,
grabCursor: true,
allowTouchMove: true,
watchOverflow: false,
loop: loopEnabled,
loopedSlides: Math.min(12, slidesCount),
loopAdditionalSlides: Math.min(12, slidesCount),
loopPreventsSliding: false,
normalizeSlideIndex: true,
observer: true,
observeParents: true,
breakpoints: {
768: {
spaceBetween: 24,
},
1200: {
spaceBetween: 32,
},
},
});
};
const renderSlides = function (filter) {
const slides = getSlidesByFilter(filter);
if (!slides.length) {
return;
}
destroySlider();
$wrapper.html(getLoopSlidesHtml(slides));
initSlider();
};
const setActiveFilter = function (filter) {
if (activeFilter === filter || !getSlidesByFilter(filter).length) {
return;
}
activeFilter = filter;
updateTabs(filter);
renderSlides(filter);
};
const initialFilter = $tabs.filter('.is-active').first().data('expertise-filter')
|| $tabs.first().data('expertise-filter');
setActiveFilter(initialFilter);
$tabs.on('click', function () {
setActiveFilter($(this).data('expertise-filter'));
});
});
})(jQuery);
(function ($) {
'use strict';
$(function () {
const $results = $('.results');
if (!$results.length) {
return;
}
$results.addClass('is-animation-ready');
const showResults = function (target) {
$(target).addClass('is-in-view');
};
if (!('IntersectionObserver' in window)) {
$results.each(function () {
showResults(this);
});
return;
}
const observer = new IntersectionObserver(function (entries, currentObserver) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) {
return;
}
showResults(entry.target);
currentObserver.unobserve(entry.target);
});
}, {
threshold: 0.24,
rootMargin: '0px 0px -12% 0px',
});
$results.each(function () {
observer.observe(this);
});
});
})(jQuery);
(function ($) {
'use strict';
$(function () {
const $faq = $('.faq');
if (!$faq.length) {
return;
}
$faq.on('click', '.faq-item__button', function () {
const $button = $(this);
const $item = $button.closest('.faq-item');
const $panel = $item.find('.faq-item__panel').first();
const isOpen = $button.attr('aria-expanded') === 'true';
$button.attr('aria-expanded', String(!isOpen));
if (isOpen) {
$panel.stop(true, true).slideUp(220, function () {
$panel.attr('hidden', true).removeAttr('style');
});
} else {
$panel.removeAttr('hidden').hide().stop(true, true).slideDown(220, function () {
$panel.removeAttr('style');
});
}
});
});
})(jQuery);
(function ($) {
'use strict';
$(function () {
const $forms = $('.contact-sentence');
if (!$forms.length) {
return;
}
const options = {
task: ['лендинг', 'интернет-магазин', 'интеграцию', 'дизайн', 'доработку сайта'],
method: ['Telegram', 'WhatsApp', 'звонок', 'email'],
time: ['09:0012:00', '12:0015:00', '15:0018:00', 'любое время'],
};
let $dateInput = $('.contact-date-input');
if (!$dateInput.length) {
$dateInput = $('<input class="contact-date-input" type="date" aria-hidden="true">').appendTo('body');
}
const cleanValue = function ($field) {
const text = $.trim($field.text()).replace(/\s+/g, ' ');
const placeholder = $.trim($field.data('placeholder') || '');
const normalized = text.replace(/[()]/g, '').replace(/▾/g, '').trim();
if ($field.hasClass('is-placeholder') || normalized === placeholder) {
return '';
}
return normalized;
};
const getPlaceholderText = function ($field) {
const placeholder = $.trim($field.data('placeholder') || '');
const withArrow = $field.hasClass('contact-field--selector');
return '(' + placeholder + (withArrow ? ' ▾' : '') + ')';
};
const setFieldValue = function ($field, value, isPlaceholder) {
const text = isPlaceholder ? getPlaceholderText($field) : value;
$field
.text(text)
.toggleClass('is-placeholder', !!isPlaceholder)
.toggleClass('is-filled', !isPlaceholder && !!value)
.removeClass('is-error');
updateHidden($field.closest('form'));
};
const updateHidden = function ($form) {
$form.find('.contact-field').each(function () {
const $field = $(this);
const name = $field.data('field');
if (!name) {
return;
}
$form.find('input[name="' + name + '"]').val(cleanValue($field));
});
};
const selectAll = function (el) {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(el);
selection.removeAllRanges();
selection.addRange(range);
};
const setCaretEnd = function (el) {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(el);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
};
const formatPhone = function (value) {
const raw = String(value || '').replace(/\s+/g, '');
const hasPlus = raw.indexOf('+') === 0;
const digits = raw.replace(/\D/g, '').slice(0, 11);
if (!digits) {
return hasPlus ? '+' : '';
}
let formatted = hasPlus || digits.charAt(0) === '7' ? '+' : '';
formatted += digits.slice(0, 1);
if (digits.length > 1) {
formatted += ' ' + digits.slice(1, 4);
}
if (digits.length > 4) {
formatted += ' ' + digits.slice(4, 7);
}
if (digits.length > 7) {
formatted += ' ' + digits.slice(7, 9);
}
if (digits.length > 9) {
formatted += ' ' + digits.slice(9, 11);
}
return formatted;
};
const openDropdown = function ($field) {
const type = $field.data('type');
const values = options[type] || [];
if (!values.length) {
return;
}
$('.contact-dropdown').remove();
const rect = $field[0].getBoundingClientRect();
const $dropdown = $('<ul class="contact-dropdown" role="listbox"></ul>');
values.forEach(function (value) {
$('<li role="option"></li>').text(value).appendTo($dropdown);
});
$('body').append($dropdown);
const left = Math.min(rect.left + window.pageXOffset - 10, window.pageXOffset + window.innerWidth - $dropdown.outerWidth() - 12);
$dropdown.css({
top: rect.bottom + window.pageYOffset + 8,
left: Math.max(12, left),
});
$dropdown.on('click', 'li', function () {
setFieldValue($field, $(this).text(), false);
$dropdown.remove();
});
};
const openDatePicker = function ($field) {
const rect = $field[0].getBoundingClientRect();
const picker = $dateInput[0];
$dateInput
.val('')
.css({
left: rect.left + 'px',
top: rect.top + 'px',
width: Math.max(rect.width, 24) + 'px',
height: Math.max(rect.height, 24) + 'px',
})
.off('change.contact')
.one('change.contact', function () {
if (this.value) {
setFieldValue($field, this.value, false);
}
});
requestAnimationFrame(function () {
try {
if (typeof picker.showPicker === 'function') {
picker.showPicker();
} else {
picker.focus();
picker.click();
}
} catch (error) {
picker.focus();
picker.click();
}
});
};
const isEmail = function (value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
};
const validateForm = function ($form) {
let valid = true;
let $firstError = null;
const markError = function ($field) {
valid = false;
$field.addClass('is-error');
if (!$firstError) {
$firstError = $field;
}
};
$form.find('.contact-field').removeClass('is-error');
const required = ['name', 'task', 'phone', 'email', 'method', 'date', 'time'];
required.forEach(function (name) {
const $field = $form.find('.contact-field[data-field="' + name + '"]');
const value = cleanValue($field);
if (!value) {
markError($field);
}
});
const $name = $form.find('.contact-field[data-field="name"]');
const $phone = $form.find('.contact-field[data-field="phone"]');
const $email = $form.find('.contact-field[data-field="email"]');
const name = cleanValue($name);
const phone = cleanValue($phone);
const email = cleanValue($email);
if (name && name.length < 2) {
markError($name);
}
if (phone && phone.replace(/\D/g, '').length < 10) {
markError($phone);
}
if (email && !isEmail(email)) {
markError($email);
}
if (!valid && $firstError) {
$form.find('.contact-sentence__status')
.removeClass('is-success')
.text('Заполните подсвеченные поля.');
$('html, body').animate({ scrollTop: $firstError.offset().top - 120 }, 260);
}
return valid;
};
$forms.each(function () {
updateHidden($(this));
});
$(document).on('click keydown', '.contact-field--editable', function (event) {
const $field = $(this);
if ($field.attr('contenteditable')) {
return;
}
if (event.type === 'keydown' && event.key !== 'Enter' && event.key !== ' ') {
return;
}
event.preventDefault();
const value = cleanValue($field);
$field.attr('contenteditable', 'true');
$field.text(value);
$field.removeClass('is-placeholder is-error');
$field.focus();
selectAll($field[0]);
});
$(document).on('input', '.contact-field--editable', function () {
const $field = $(this);
if ($field.data('type') === 'phone') {
$field.text(formatPhone($field.text()));
setCaretEnd(this);
}
updateHidden($field.closest('form'));
});
$(document).on('paste', '.contact-field--editable[data-type="phone"]', function (event) {
event.preventDefault();
const clipboard = event.originalEvent.clipboardData || window.clipboardData;
const text = clipboard ? clipboard.getData('text') : '';
$(this).text(formatPhone(text));
setCaretEnd(this);
updateHidden($(this).closest('form'));
});
$(document).on('blur keydown', '.contact-field--editable[contenteditable="true"]', function (event) {
if (event.type === 'keydown' && event.key !== 'Enter') {
return;
}
event.preventDefault();
const $field = $(this);
const value = $.trim($field.text()).replace(/\s+/g, ' ');
$field.removeAttr('contenteditable');
if (!value) {
setFieldValue($field, '', true);
} else {
setFieldValue($field, value, false);
}
});
$(document).on('click keydown', '.contact-field--selector', function (event) {
if (event.type === 'keydown' && event.key !== 'Enter' && event.key !== ' ') {
return;
}
event.preventDefault();
event.stopPropagation();
const $field = $(this);
if ($field.data('type') === 'date') {
openDatePicker($field);
} else {
openDropdown($field);
}
});
$(document).on('click', function () {
$('.contact-dropdown').remove();
});
$forms.on('submit', function (event) {
event.preventDefault();
const $form = $(this);
const $status = $form.find('.contact-sentence__status');
updateHidden($form);
if (!validateForm($form)) {
return;
}
const payload = Object.fromEntries(new FormData($form[0]).entries());
console.log('Contact form payload:', payload);
$status
.addClass('is-success')
.text('Заявка подготовлена. Подключите обработчик отправки формы.');
});
});
})(jQuery);