feat: added review block

This commit is contained in:
Ignat Karelov
2026-05-27 15:53:03 +03:00
parent 7ce71ffa33
commit 98f3d12455
5 changed files with 429 additions and 15 deletions

View File

@@ -122,6 +122,119 @@
});
})();
(() => {
const slider = document.querySelector("[data-reviews-slider]");
if (!slider) return;
const viewport = slider.querySelector("[data-reviews-viewport]");
const track = slider.querySelector("[data-reviews-track]");
const prevButton = slider.querySelector("[data-reviews-prev]");
const nextButton = slider.querySelector("[data-reviews-next]");
const dotsContainer = document.querySelector("[data-reviews-dots]");
const cards = Array.from(track?.querySelectorAll(".review-card") ?? []);
if (!viewport || !track || !prevButton || !nextButton || !dotsContainer || cards.length < 2) return;
let currentIndex = 0;
let touchStartX = 0;
const readVisibleSlides = () => {
const visibleValue = Number.parseInt(getComputedStyle(slider).getPropertyValue("--reviews-visible"), 10);
return Number.isFinite(visibleValue) && visibleValue > 0 ? visibleValue : 1;
};
const readTrackGap = () => {
const styles = getComputedStyle(track);
const gapValue = styles.gap || styles.columnGap || "0";
const parsedGap = Number.parseFloat(gapValue);
return Number.isFinite(parsedGap) ? parsedGap : 0;
};
const maxIndex = () => Math.max(0, cards.length - readVisibleSlides());
const renderDots = () => {
const total = maxIndex() + 1;
dotsContainer.innerHTML = "";
for (let index = 0; index < total; index += 1) {
const dot = document.createElement("button");
dot.type = "button";
dot.className = "reviews__dot";
dot.setAttribute("aria-label", `Показать отзыв ${index + 1}`);
dot.addEventListener("click", () => {
currentIndex = index;
update();
});
dotsContainer.appendChild(dot);
}
};
const update = () => {
const max = maxIndex();
currentIndex = Math.min(Math.max(currentIndex, 0), max);
const cardWidth = cards[0].getBoundingClientRect().width;
const offset = (cardWidth + readTrackGap()) * currentIndex;
track.style.transform = `translateX(${-offset}px)`;
prevButton.disabled = currentIndex === 0;
nextButton.disabled = currentIndex === max;
const dots = Array.from(dotsContainer.querySelectorAll(".reviews__dot"));
dots.forEach((dot, index) => {
dot.classList.toggle("is-active", index === currentIndex);
dot.setAttribute("aria-current", index === currentIndex ? "true" : "false");
});
};
prevButton.addEventListener("click", () => {
currentIndex -= 1;
update();
});
nextButton.addEventListener("click", () => {
currentIndex += 1;
update();
});
viewport.addEventListener("touchstart", (event) => {
touchStartX = event.changedTouches[0].clientX;
}, { passive: true });
viewport.addEventListener("touchend", (event) => {
const deltaX = event.changedTouches[0].clientX - touchStartX;
const threshold = 48;
if (deltaX > threshold) {
currentIndex -= 1;
update();
return;
}
if (deltaX < -threshold) {
currentIndex += 1;
update();
}
}, { passive: true });
const handleResize = () => {
renderDots();
update();
};
let resizeRaf = null;
window.addEventListener("resize", () => {
if (resizeRaf) return;
resizeRaf = window.requestAnimationFrame(() => {
resizeRaf = null;
handleResize();
});
});
renderDots();
update();
})();
(() => {
const bookingForm = document.getElementById("booking-form");
const successModal = document.getElementById("booking-success-modal");