Compare commits

...

2 Commits

Author SHA1 Message Date
d426e3cb3e Merge pull request 'feat: added review block' (#16) from style/edit into main
All checks were successful
Deploy Olimparena / deploy (push) Successful in 41s
Reviewed-on: #16
2026-05-27 12:53:28 +00:00
Ignat Karelov
98f3d12455 feat: added review block 2026-05-27 15:53:03 +03:00
5 changed files with 429 additions and 15 deletions

View File

@@ -719,13 +719,81 @@ img {
background-position: center;
}
.reviews {
--reviews-visible: 3;
--reviews-gap: 24px;
background: linear-gradient(90deg, #293133, #20272a);
}
.reviews .section__head {
margin-bottom: 44px;
}
.reviews__slider {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 16px;
}
.reviews__viewport {
overflow: hidden;
}
.reviews__track {
display: flex;
gap: var(--reviews-gap);
transition: transform 0.45s ease;
will-change: transform;
}
.reviews__nav {
width: 48px;
height: 48px;
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 12px;
background: rgba(255, 255, 255, 0.03);
color: rgba(255, 255, 255, 0.88);
font-size: 24px;
line-height: 1;
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, background-color 0.2s ease, opacity 0.2s ease;
}
.reviews__nav:hover:not(:disabled) {
transform: translateY(-1px);
border-color: rgba(207, 23, 23, 0.75);
background: rgba(207, 23, 23, 0.18);
}
.reviews__nav:disabled {
opacity: 0.35;
cursor: default;
}
.reviews__dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 18px;
}
.reviews__dot {
width: 10px;
height: 10px;
border: 0;
border-radius: 999px;
background: rgba(255, 255, 255, 0.28);
cursor: pointer;
transition: width 0.2s ease, background-color 0.2s ease;
}
.reviews__dot.is-active {
width: 26px;
background: #cf1717;
}
.review-card {
background: rgba(18, 18, 18, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.1);
min-height: 206px;
padding: 33px;
flex: 0 0 calc((100% - (var(--reviews-visible) - 1) * var(--reviews-gap)) / var(--reviews-visible));
min-height: 250px;
padding: 30px 28px;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: transform 0.3s ease, border-color 0.3s ease;
}
.review-card:hover {
@@ -740,15 +808,29 @@ img {
}
.review-card strong {
display: block;
margin-bottom: 8px;
margin-bottom: 10px;
font-size: 16px;
font-weight: 700;
line-height: 1.2;
}
.review-card span {
color: rgba(255, 255, 255, 0.7);
font-size: 16px;
line-height: 1.2;
.review-card__meta {
padding-top: 14px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.review-card__meta a {
color: #ffd2d2;
font-size: 14px;
font-weight: 500;
line-height: 1.4;
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
text-decoration-color: rgba(255, 210, 210, 0.75);
transition: color 0.2s ease, text-decoration-color 0.2s ease;
}
.review-card__meta a:hover {
color: #ffe4e4;
text-decoration-color: #ffe4e4;
}
.stats {
@@ -1414,6 +1496,9 @@ img {
.hero__actions {
flex-wrap: wrap;
}
.reviews {
--reviews-visible: 2;
}
.highlights__grid,
.gallery__grid,
.reviews__grid,
@@ -1477,6 +1562,16 @@ img {
}
}
@media only screen and (max-width: 768px) {
.reviews {
--reviews-visible: 1;
}
.reviews__slider {
grid-template-columns: 1fr;
gap: 12px;
}
.reviews__nav {
display: none;
}
.container {
padding: 0 16px;
}

File diff suppressed because one or more lines are too long

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");

View File

@@ -741,10 +741,88 @@ img {
background-position: center;
}
.reviews {
--reviews-visible: 3;
--reviews-gap: 24px;
background: linear-gradient(90deg, $color-surface, $color-surface-2);
.section__head {
margin-bottom: 44px;
}
&__slider {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 16px;
}
&__viewport {
overflow: hidden;
}
&__track {
display: flex;
gap: var(--reviews-gap);
transition: transform 0.45s ease;
will-change: transform;
}
&__nav {
width: 48px;
height: 48px;
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 12px;
background: rgba(255, 255, 255, 0.03);
color: rgba(255, 255, 255, 0.88);
font-size: 24px;
line-height: 1;
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, background-color 0.2s ease, opacity 0.2s ease;
&:hover:not(:disabled) {
transform: translateY(-1px);
border-color: rgba(207, 23, 23, 0.75);
background: rgba(207, 23, 23, 0.18);
}
&:disabled {
opacity: 0.35;
cursor: default;
}
}
&__dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 18px;
}
&__dot {
width: 10px;
height: 10px;
border: 0;
border-radius: 999px;
background: rgba(255, 255, 255, 0.28);
cursor: pointer;
transition: width 0.2s ease, background-color 0.2s ease;
&.is-active {
width: 26px;
background: $color-accent;
}
}
}
.review-card {
@include card;
min-height: 206px;
padding: 33px;
flex: 0 0 calc((100% - (var(--reviews-visible) - 1) * var(--reviews-gap)) / var(--reviews-visible));
min-height: 250px;
padding: 30px 28px;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: transform 0.3s ease, border-color 0.3s ease;
&:hover {
@@ -761,16 +839,32 @@ img {
strong {
display: block;
margin-bottom: 8px;
margin-bottom: 10px;
font-size: 16px;
font-weight: 700;
line-height: 1.2;
}
span {
color: $color-text-muted;
font-size: 16px;
line-height: 1.2;
&__meta {
padding-top: 14px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
a {
color: #ffd2d2;
font-size: 14px;
font-weight: 500;
line-height: 1.4;
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
text-decoration-color: rgba(255, 210, 210, 0.75);
transition: color 0.2s ease, text-decoration-color 0.2s ease;
&:hover {
color: #ffe4e4;
text-decoration-color: #ffe4e4;
}
}
}
}
@@ -1565,6 +1659,10 @@ img {
}
}
.reviews {
--reviews-visible: 2;
}
.highlights__grid,
.gallery__grid,
.reviews__grid,
@@ -1650,6 +1748,19 @@ img {
}
@include respond($bp-md) {
.reviews {
--reviews-visible: 1;
&__slider {
grid-template-columns: 1fr;
gap: 12px;
}
&__nav {
display: none;
}
}
.container {
padding: 0 16px;
}

View File

@@ -261,6 +261,101 @@
</div>
</section>
<section class="section reviews" id="reviews">
<div class="container">
<header class="section__head">
<h2>Отзывы</h2>
<p>Что говорят клиенты о тренировках и сборах в OlimpArena</p>
</header>
<div class="reviews__slider" data-reviews-slider>
<button class="reviews__nav reviews__nav--prev" type="button" data-reviews-prev aria-label="Предыдущий отзыв"></button>
<div class="reviews__viewport" data-reviews-viewport>
<div class="reviews__track" data-reviews-track>
<article class="review-card">
<p>Ледовая арена премиум-уровня с отличной локацией. Удобные раздевалки и отличный лед. Профессиональный и приветливый персонал. Лучшая арена для тренировок и соревнований</p>
<div class="review-card__meta">
<strong>Владимир Л.</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=1zaqed8uewrx0zdfqr75d3n8zm&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Отличная локация, вежливый персонал, прекрасные ледовые арены, хорошо обустроенные раздевалки. Дизайнерское решение соответствует направлению. Всё очень круто!!! Обязательно посетите данное место!</p>
<div class="review-card__meta">
<strong>Закопайло С.</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=9g4uxhfhd4cqbjdjec0unkqzug&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Отличная локация, отличный лед и его постоянно обновляют, очень вежливый персонал, очень приятно находиться в помещении. Очень современные место. Хорошее впечатление</p>
<div class="review-card__meta">
<strong>Ольга Р.</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=vpdm0u6jzp7dcwndttf7z9akv4&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Побывала на экскурсии по своему направлению деятельности, понравилось все: инфраструктура комплекса, дизайн, персонал, все на высшем уровне 👍</p>
<div class="review-card__meta">
<strong>Елена Ильина</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=fwfzfd88e6uq59qa8h6pv84ve0&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Нам очень понравилась арена! Высокий уровень-комфорт, красота, расположение, персонал. Очень понравилось качество льда. Спасибо</p>
<div class="review-card__meta">
<strong>Руслан Я.</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=81a1u705vn3kd0kmhdkc7qh02c&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Полноценное футбольное поле под крышей! Чего ещё желать? Таких объектов в Москве по пальцам одной руки можно пересчитать. И самое главное на территории легендарная футбольная школа...</p>
<div class="review-card__meta">
<strong>Andrey Kuleshov</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=k1eyc820uqf5wua1ypqua8zyww&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Отличный лед. Приветливый персонал. Очень красиво внутри. Просторные раздевалки.</p>
<div class="review-card__meta">
<strong>Сергей рылов</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=nah55c29h52e3fxw0bfuk2he1c&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Хороший персонал, хорошие место, хороший пк клуб. Всем советую, хороший пк клуб</p>
<div class="review-card__meta">
<strong>Иван Курцев</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=af38gg3nfbrz1cnfr94peuv6zc&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Выглядит очень здорово. Будете в восторге от места, где найдется всем место для комфортного катания и ожидания.Отличный лед</p>
<div class="review-card__meta">
<strong>Оксана Викторовна завхоз</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=rtr4medjwd3hev3138by8nw17c&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Отличное место для занятия спортом, очень понравилось😊 рекомендую👍</p>
<div class="review-card__meta">
<strong>Serega</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=5tctvtm88tq564tc117493nqq4&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
<article class="review-card">
<p>Отличная арена 🏒</p>
<div class="review-card__meta">
<strong>Андрей Иванов</strong>
<a href="https://yandex.ru/maps/org/144435842347/reviews?reviews%5BpublicId%5D=heccmk2e26d2bcpqtgdccq47bg&si=j6p3d50uf3hwfup3ey7u8b3myg&utm_source=review" target="_blank" rel="noopener noreferrer">Читать на Яндекс Картах</a>
</div>
</article>
</div>
</div>
<button class="reviews__nav reviews__nav--next" type="button" data-reviews-next aria-label="Следующий отзыв"></button>
</div>
<div class="reviews__dots" data-reviews-dots></div>
</div>
</section>
<section class="section facts" id="facts">
<div class="container">
<header class="section__head">