1/* ============================================================ 2 FOOTER.JS 3 ============================================================ */ 4 5(function () { 6 'use strict'; 7 8 // ââ ÐккоÑдеон «ÐÑе ÑазделÑ» ââ 9 const sectionsBtn = document.getElementById('footerSectionsBtn'); 10 const sectionsPanel = document.getElementById('footerSectionsPanel'); 11 12 if (sectionsBtn && sectionsPanel) { 13 // ÐÐ°Ð½ÐµÐ»Ñ ÑÑ Ð»Ð¾Ð¿Ð½ÑÑа ÑеÑез grid-template-rows: 0fr + overflow: hidden. 14 // ÐÑо ÑкÑÑÐ²Ð°ÐµÑ ÐµÑ Ð²Ð¸Ð·ÑалÑно, но ÐÐ ÑбиÑÐ°ÐµÑ 27 ÑÑÑлок из Tab-Ð¾Ð±Ñ Ð¾Ð´Ð°: 15 // Ñ ÐºÐ»Ð°Ð²Ð¸Ð°ÑÑÑÑ ÑокÑÑ Ð¿ÑоваливалÑÑ Ð²Ð½ÑÑÑÑ Ð·Ð°ÐºÑÑÑой панели, Ñогда как 16 // aria-hidden="true" пÑÑÑал ÐµÑ Ð¾Ñ ÑкÑинÑидеÑа. ÐÑÑибÑÑ inert ÑÐ½Ð¸Ð¼Ð°ÐµÑ 17 // и ÑокÑÑиÑÑемоÑÑÑ, и озвÑÑÐºÑ Ñазом. 18 sectionsPanel.toggleAttribute('inert', sectionsPanel.getAttribute('aria-hidden') !== 'false'); 19 20 sectionsBtn.addEventListener('click', () => { 21 const isOpen = sectionsBtn.getAttribute('aria-expanded') === 'true'; 22 sectionsBtn.setAttribute('aria-expanded', String(!isOpen)); 23 sectionsPanel.setAttribute('aria-hidden', String(isOpen)); 24 sectionsPanel.toggleAttribute('inert', isOpen); 25 }); 26 } 27 28 // ââ ÐиÑÐºÐ»ÐµÐ¹Ð¼ÐµÑ Ð¿Ð¾Ð´Ð¿Ð¸Ñки: поÑвлÑеÑÑÑ Ð¿Ñи ÑокÑÑе на email-поле ââ 29 const form = document.getElementById('footerSubscribeForm'); 30 const emailInput = document.getElementById('footerEmail'); 31 const agreeWrap = document.getElementById('footerAgreeWrap'); 32 const agreeBox = document.getElementById('footerAgree'); 33 const agreeError = document.getElementById('footerAgreeError'); 34 const emailBtn = form ? form.querySelector('.footer__email-btn') : null; 35 36 // Ðа деÑкÑопе (â¥1024px) CSS показÑÐ²Ð°ÐµÑ Ð¾Ð±ÑÑÑÐºÑ ÑÑаÑиÑно ÑеÑез 37 // display: block !important, а aria-hidden="true" из ÑазмеÑки оÑÑавалÑÑ â 38 // ÑÐµÐºÐ±Ð¾ÐºÑ ÑоглаÑÐ¸Ñ Ð±Ñл виден и ÑокÑÑиÑÑем, но ÑкÑÑÑ Ð¾Ñ ÑкÑинÑидеÑа. 39 // ÐÑÑибÑÑ Ð·Ð´ÐµÑÑ Ð½Ðµ нÑжен вовÑе: видимоÑÑÑÑ ÑпÑавлÑÐµÑ display, 40 // а display: none и Ñак ÑбиÑÐ°ÐµÑ Ð±Ð»Ð¾Ðº из деÑева доÑÑÑпноÑÑи и из Tab-Ð¾Ð±Ñ Ð¾Ð´Ð°. 41 if (agreeWrap) agreeWrap.removeAttribute('aria-hidden'); 42 43 function showAgree() { 44 if (!agreeWrap) return; 45 agreeWrap.classList.add('is-visible'); 46 } 47 48 function hideAgree() { 49 if (!agreeWrap || !emailInput) return; 50 // СкÑÑваем ÑолÑко еÑли поле пÑÑÑое â инаÑе оÑÑавлÑем видимÑм 51 if (emailInput.value.trim() === '') { 52 agreeWrap.classList.remove('is-visible'); 53 } 54 } 55 56 // ÐодÑвеÑка ÑекбокÑа + ÑÑÑока оÑибки под ним. 57 // ÐÑÑ Ð²Ð¸Ð·Ñалка â в CSS (.is-invalid, .footer__agree-error), JS ÑолÑко 58 // пеÑеклÑÑÐ°ÐµÑ ÐºÐ»Ð°ÑÑ Ð¸ аÑÑибÑÑнÑй hidden. 59 function setAgreeError(show) { 60 if (!agreeBox) return; 61 if (agreeError) agreeError.hidden = !show; 62 agreeBox.classList.toggle('is-invalid', show); 63 agreeBox.setAttribute('aria-invalid', show ? 'true' : 'false'); 64 } 65 66 if (emailInput) { 67 emailInput.addEventListener('focus', showAgree); 68 emailInput.addEventListener('blur', hideAgree); 69 } 70 71 // Снимаем оÑÐ¸Ð±ÐºÑ ÑÑазÑ, как ÑолÑко полÑзоваÑÐµÐ»Ñ Ð¿ÑоÑÑавил галоÑÐºÑ 72 if (agreeBox) { 73 agreeBox.addEventListener('change', () => { 74 if (agreeBox.checked) setAgreeError(false); 75 }); 76 } 77 78 // ââ ФоÑма подпиÑки ââ 79 80 if (form) { 81 form.addEventListener('submit', (e) => { 82 e.preventDefault(); 83 const email = emailInput ? emailInput.value.trim() : ''; 84 const agreed = agreeBox ? agreeBox.checked : true; 85 86 if (!email) { emailInput && emailInput.focus(); return; } 87 88 // ÐалоÑка ÑоглаÑÐ¸Ñ Ð½Ð° обÑабоÑÐºÑ ÐРобÑзаÑелÑна и ÐРпÑедзаполнена: 89 // без акÑивного дейÑÑÐ²Ð¸Ñ Ð¿Ð¾Ð»ÑзоваÑÐµÐ»Ñ ÑоглаÑÐ¸Ñ Ð½ÐµÑ (Ñ. 1 ÑÑ. 9 152-ФÐ). 90 if (agreeBox && !agreed) { 91 showAgree(); // на мобилке блок мог бÑÑÑ ÐµÑÑ ÑкÑÑÑ 92 setAgreeError(true); 93 agreeBox.focus(); 94 return; 95 } 96 setAgreeError(false); 97 98 // ÐлокиÑÑем ÐºÐ½Ð¾Ð¿ÐºÑ Ð½Ð° вÑÐµÐ¼Ñ Ð·Ð°Ð¿ÑоÑа, ÑÑÐ¾Ð±Ñ Ð¸Ð·Ð±ÐµÐ¶Ð°ÑÑ Ð¿Ð¾Ð²ÑоÑной оÑпÑавки 99 if (emailBtn) emailBtn.disabled = true; 100 101 fetch('/subscribe', { 102 method: 'POST', 103 headers: { 104 'Content-Type': 'application/json' 105 }, 106 // agree ÑÑ Ð¾Ð´Ð¸Ñ Ð½Ð° бÑкенд ÑвнÑм Ñлагом â ÑеÑÐ²ÐµÑ ÑикÑиÑÑÐµÑ ÑÐ°ÐºÑ ÑоглаÑÐ¸Ñ 107 // (даÑÑ/вÑемÑ, IP, веÑÑÐ¸Ñ ÑекÑÑа) как докаÐ
107·Ð°ÑелÑÑÑво по Ñ. 1 ÑÑ. 18 108 // ФР«РÑекламе»: бÑÐµÐ¼Ñ Ð´Ð¾ÐºÐ°Ð·ÑÐ²Ð°Ð½Ð¸Ñ ÑоглаÑÐ¸Ñ Ð»ÐµÐ¶Ð¸Ñ Ð½Ð° ÑеаÑÑе 109 body: JSON.stringify({ email: email, agree: agreed }) 110 }) 111 .then((response) => { 112 if (!response.ok) { 113 throw new Error('ÐÑибка ÑеÑвеÑа: ' + response.status); 114 } 115 return response.json().catch(() => ({})); // на ÑлÑÑай пÑÑÑого Ñела оÑвеÑа 116 }) 117 .then((data) => { 118 console.log('[Footer] Subscribe success:', email, data); 119 emailInput.value = ''; 120 // Double opt-in: подпиÑки еÑÑ Ð½ÐµÑ â она наÑинаеÑÑÑ Ð¿Ð¾Ñле подÑвеÑÐ¶Ð´ÐµÐ½Ð¸Ñ 121 // из пиÑÑма. «ÐÑ Ð¿Ð¾Ð´Ð¿Ð¸ÑанÑ» здеÑÑ Ð±Ñло Ð±Ñ Ð½ÐµÐ¿Ñавдой. 122 emailInput.placeholder = 'СпаÑибо! ÐÑовеÑÑÑе поÑÑÑ'; 123 emailInput.disabled = true; 124 // ÐлаÑÑ, а не inline-ÑÑилÑ: .footer__email-btn.is-hidden Ñже еÑÑÑ 125 // в footer.css â Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸ display:flex, коÑоÑÑй пеÑебил Ð±Ñ [hidden] 126 if (emailBtn) emailBtn.classList.add('is-hidden'); 127 hideAgree(); 128 }) 129 .catch((error) => { 130 console.error('[Footer] Subscribe error:', error); 131 if (emailBtn) emailBtn.disabled = false; 132 emailInput.placeholder = 'Ðе полÑÑилоÑÑ, попÑобÑйÑе еÑÑ Ñаз'; 133 }); 134 }); 135 } 136 137})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.