fix: SEO audit fixes for /otzyvy/ — Schema.org, PHP, JS, CSS, A11Y
Critical: - Move GeoCoordinates from PostalAddress to Organization level - Fix AggregateRating @id to point at #organization, not page URL High: - Replace Review.itemReviewed inline LocalBusiness with @id reference - Change @type from array to just 'School' - Remove foundingDate from reviews page (add comment for CF) - Make telephone property conditional (only when non-empty) - Add postalCode from branch_postal_code option - Fix branch_city fallback from 'городе' to 'Щёлково' - Replace inline onclick yaCounter with JS global handler - Remove inline critical CSS (styles in file) - Move price text to CF field reviews_intro_price_text Medium: - Add --color-bg and --accent CSS variables - Fix .page-rewiews typo → .page-reviews - Fix .contact-map display:flex→block - Add prefers-reduced-motion support - JS: var→let/const, scroll throttle via requestAnimationFrame - JS: catch(e)→console.warn - JS: data-target→data-target-value (avoid conflict) - A11Y: FAQ aria-expanded/aria-controls, phone aria-label - A11Y: modal role=dialog aria-modal=true - A11Y: breadcrumb role=navigation aria-label - Remove desktop-only class from breadcrumbs (mobile support) Low: - Add preconnect for unpkg.com (Phosphor Icons CDN) - Add decoding=async to gallery images
This commit is contained in:
parent
6f5feb7521
commit
c7bd6cef74
1906
wp-content/themes/dekart/assets/css/reviews-page.css
Normal file
1906
wp-content/themes/dekart/assets/css/reviews-page.css
Normal file
File diff suppressed because it is too large
Load Diff
222
wp-content/themes/dekart/assets/js/reviews-page.js
Normal file
222
wp-content/themes/dekart/assets/js/reviews-page.js
Normal file
@ -0,0 +1,222 @@
|
||||
/**
|
||||
* reviews-page.js — интерактив для page-reviews.php
|
||||
* Анимации, фильтры, sticky CTA, FAQ, пагинация
|
||||
*
|
||||
* @package Dekart
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
'use strict';
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 1. АНИМАЦИЯ ПОЯВЛЕНИЯ (Intersection Observer)
|
||||
// ───────────────────────────────────────────────
|
||||
const revealElements = document.querySelectorAll('.reveal');
|
||||
if (revealElements.length && 'IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('reveal--visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
revealElements.forEach(function (el) {
|
||||
observer.observe(el);
|
||||
});
|
||||
} else {
|
||||
// Fallback: показываем всё сразу
|
||||
revealElements.forEach(function (el) {
|
||||
el.classList.add('reveal--visible');
|
||||
});
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 2. КНОПКИ "ЧИТАТЬ ЕЩЁ"
|
||||
// ───────────────────────────────────────────────
|
||||
document.querySelectorAll('.review-card__btn-more').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const textBlock = this.parentNode.querySelector('.lw-card__text');
|
||||
if (textBlock) {
|
||||
textBlock.classList.toggle('review-card__text--expanded');
|
||||
this.textContent = textBlock.classList.contains('review-card__text--expanded')
|
||||
? 'Свернуть'
|
||||
: 'Читать ещё';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 3. КНОПКА "ПОКАЗАТЬ ЕЩЁ" (десктоп)
|
||||
// ───────────────────────────────────────────────
|
||||
const loadMoreBtn = document.getElementById('reviews-loadmore');
|
||||
if (loadMoreBtn) {
|
||||
let nextBatch = 2;
|
||||
const btn = loadMoreBtn.querySelector('.reviews-loadmore__btn');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const cards = document.querySelectorAll('.lw-card[data-batch="' + nextBatch + '"]');
|
||||
if (cards.length) {
|
||||
cards.forEach(function (card) {
|
||||
card.style.display = 'flex';
|
||||
});
|
||||
nextBatch++;
|
||||
// Если следующих батчей нет — прячем кнопку
|
||||
const remaining = document.querySelectorAll('.lw-card[data-batch="' + nextBatch + '"]');
|
||||
if (!remaining.length) {
|
||||
loadMoreBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 4. FAQ Accordion — keyboard + ARIA
|
||||
// Глобальный click handler в add.js обрабатывает
|
||||
// .box__accordion_label, синхронизируем aria-expanded
|
||||
// ───────────────────────────────────────────────
|
||||
document.querySelectorAll('.box__accordion_label').forEach(function (label) {
|
||||
label.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.click();
|
||||
}
|
||||
});
|
||||
// При клике (из add.js) синхронизируем aria-expanded
|
||||
label.addEventListener('click', function () {
|
||||
const content = document.getElementById(this.getAttribute('aria-controls'));
|
||||
const isOpen = content && content.style.maxHeight && content.style.maxHeight !== '0px';
|
||||
this.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 5. СЧЁТЧИК (CountUp) для цифр доверия
|
||||
// ───────────────────────────────────────────────
|
||||
function animateCounter(el) {
|
||||
const target = parseInt(el.getAttribute('data-target-value'), 10) ||
|
||||
parseInt(el.getAttribute('data-target'), 10);
|
||||
if (!target) return;
|
||||
let current = 0;
|
||||
const step = Math.ceil(target / 60); // 60 кадров ~ 1 сек
|
||||
const timer = setInterval(function () {
|
||||
current += step;
|
||||
if (current >= target) {
|
||||
current = target;
|
||||
clearInterval(timer);
|
||||
}
|
||||
el.textContent = current.toLocaleString('ru-RU');
|
||||
}, 16);
|
||||
}
|
||||
|
||||
document.querySelectorAll('.counter-number').forEach(function (counter) {
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
animateCounter(entry.target);
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
);
|
||||
observer.observe(counter);
|
||||
} else {
|
||||
animateCounter(counter);
|
||||
}
|
||||
});
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 6. STICKY CTA (мобильные) — троттлинг скролла
|
||||
// ───────────────────────────────────────────────
|
||||
const stickyCta = document.querySelector('.sticky-cta-mobile');
|
||||
if (stickyCta) {
|
||||
const heroSection = document.querySelector('.reviews-hero');
|
||||
if (heroSection) {
|
||||
const heroBottom = heroSection.offsetTop + heroSection.offsetHeight;
|
||||
let ticking = false;
|
||||
window.addEventListener('scroll', function () {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(function () {
|
||||
if (window.scrollY > heroBottom) {
|
||||
stickyCta.classList.add('sticky-cta-mobile--visible');
|
||||
} else {
|
||||
stickyCta.classList.remove('sticky-cta-mobile--visible');
|
||||
}
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 7. POPUP ФОРМЫ ОТЗЫВА (iframe)
|
||||
// ───────────────────────────────────────────────
|
||||
const formBtn = document.querySelector('.review-form-block__btn');
|
||||
if (formBtn) {
|
||||
formBtn.addEventListener('click', function () {
|
||||
const popup = document.querySelector('.frame__popup');
|
||||
const iframe = popup ? popup.querySelector('.frame_reviews') : null;
|
||||
const frameUrl = popup ? popup.getAttribute('data-frame-url') : '';
|
||||
if (popup && iframe && frameUrl) {
|
||||
iframe.src = frameUrl;
|
||||
popup.style.display = 'flex';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const closeBtn = document.querySelector('.btn-close-frame__popup');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', function () {
|
||||
const popup = this.parentNode.parentNode;
|
||||
popup.style.display = 'none';
|
||||
const iframe = popup.querySelector('.frame_reviews');
|
||||
if (iframe) iframe.src = '';
|
||||
});
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// 8. ЦЕЛИ ЯНДЕКС.МЕТРИКИ
|
||||
// ───────────────────────────────────────────────
|
||||
function reachGoal(name) {
|
||||
if (typeof yaCounter99835482 !== 'undefined') {
|
||||
try {
|
||||
yaCounter99835482.reachGoal(name);
|
||||
} catch (e) {
|
||||
if (window.console) {
|
||||
console.warn('Metrika reachGoal error:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Клики по всем CTA-кнопкам
|
||||
document.querySelectorAll('.js-reviews-cta').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const goal = this.getAttribute('data-goal') || 'cta_otzyvy_click';
|
||||
reachGoal(goal);
|
||||
});
|
||||
});
|
||||
|
||||
// Клик по телефону
|
||||
document.querySelectorAll('a[href^="tel:"]').forEach(function (link) {
|
||||
link.addEventListener('click', function () {
|
||||
reachGoal('phone_click_otzyvy');
|
||||
});
|
||||
});
|
||||
|
||||
// Клик по WhatsApp/Telegram
|
||||
document.querySelectorAll('a[href*="wa.me"], a[href*="t.me"]').forEach(function (link) {
|
||||
link.addEventListener('click', function () {
|
||||
reachGoal('messenger_click_otzyvy');
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user