UI-компоненты
Интерактивная карта клиентов
Блок для сайтов онлайн-услуг, клиник, школ и корпоративных проектов. Посетитель выбирает город или направление, а справа видит формат работы, услугу и быстрый переход к заявке.
География работы
Покажите, где уже работают ваши услуги
Интерактивная карта помогает показать города, филиалы, онлайн-направления или регионы, где уже есть клиенты.
Омск
Основной регион работы. Подходит для очных услуг, консультаций и локального продвижения.
42 клиента
Очно / онлайн
<section class="client-map">
<div class="client-map__head">
<span class="client-map__badge">География работы</span>
<h2>Покажите, где уже работают ваши услуги</h2>
<p>
Интерактивная карта помогает показать города, филиалы, онлайн-направления
или регионы, где уже есть клиенты.
</p>
</div>
<div class="client-map__grid">
<div class="client-map__canvas">
<button class="client-map__pin active" type="button" data-city="omsk" style="--x: 62%; --y: 46%;">Омск</button>
<button class="client-map__pin" type="button" data-city="vilnius" style="--x: 39%; --y: 35%;">Вильнюс</button>
<button class="client-map__pin" type="button" data-city="tbilisi" style="--x: 48%; --y: 60%;">Тбилиси</button>
<button class="client-map__pin" type="button" data-city="online" style="--x: 76%; --y: 28%;">Онлайн</button>
<div class="client-map__route client-map__route--one"></div>
<div class="client-map__route client-map__route--two"></div>
</div>
<article class="client-map__card">
<span class="client-map__label">Выбранный город</span>
<h3 class="client-map__city">Омск</h3>
<p class="client-map__text">
Основной регион работы. Подходит для очных услуг, консультаций и локального продвижения.
</p>
<div class="client-map__stats">
<span><strong class="client-map__clients">42</strong> клиента</span>
<span class="client-map__format">Очно / онлайн</span>
</div>
<button class="client-map__button" type="button">
Оставить заявку
</button>
</article>
</div>
</section>
.client-map {
padding: 46px;
border-radius: 28px;
overflow: hidden;
background:
radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.30), transparent 32%),
radial-gradient(circle at 88% 82%, rgba(34, 197, 94, 0.16), transparent 34%),
linear-gradient(135deg, #020617, #0f172a);
}
.client-map__head {
max-width: 860px;
margin-bottom: 34px;
}
.client-map__badge {
display: inline-flex;
margin-bottom: 16px;
padding: 8px 13px;
border-radius: 999px;
color: #bbf7d0;
background: rgba(34, 197, 94, 0.12);
border: 1px solid rgba(34, 197, 94, 0.35);
font-size: 13px;
}
.client-map__head h2 {
margin: 0 0 16px;
color: #ffffff;
font-size: clamp(32px, 5vw, 56px);
line-height: 1.02;
letter-spacing: -0.05em;
}
.client-map__head p {
color: #cbd5e1;
font-size: 17px;
line-height: 1.7;
}
.client-map__grid {
display: grid;
grid-template-columns: 1.25fr 0.75fr;
gap: 26px;
align-items: stretch;
}
.client-map__canvas,
.client-map__card {
border: 1px solid rgba(148, 163, 184, 0.28);
border-radius: 26px;
background: rgba(15, 23, 42, 0.78);
box-shadow: 0 28px 80px rgba(2, 6, 23, 0.5);
}
.client-map__canvas {
position: relative;
min-height: 460px;
overflow: hidden;
background:
linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px),
linear-gradient(rgba(148, 163, 184, 0.08) 1px, transparent 1px),
radial-gradient(circle at 50% 50%, rgba(59, 130, 246, 0.20), transparent 45%),
#020617;
background-size: 42px 42px, 42px 42px, auto, auto;
}
.client-map__canvas::before {
content: "";
position: absolute;
inset: 64px;
border: 1px dashed rgba(96, 165, 250, 0.28);
border-radius: 50%;
}
.client-map__canvas::after {
content: "";
position: absolute;
inset: 118px 90px;
border: 1px dashed rgba(34, 197, 94, 0.22);
border-radius: 48% 52% 44% 56%;
}
.client-map__pin {
position: absolute;
left: var(--x);
top: var(--y);
z-index: 5;
transform: translate(-50%, -50%);
border: 1px solid rgba(96, 165, 250, 0.55);
border-radius: 999px;
padding: 9px 13px;
color: #dbeafe;
background: rgba(15, 23, 42, 0.88);
cursor: pointer;
transition: 0.25s;
}
.client-map__pin::before {
content: "";
position: absolute;
left: 50%;
bottom: -18px;
width: 10px;
height: 10px;
transform: translateX(-50%);
border-radius: 50%;
background: #3b82f6;
box-shadow: 0 0 24px #3b82f6;
}
.client-map__pin:hover,
.client-map__pin.active {
color: #ffffff;
border-color: #22c55e;
background: rgba(34, 197, 94, 0.18);
transform: translate(-50%, -50%) translateY(-4px);
}
.client-map__pin.active::before {
background: #22c55e;
box-shadow: 0 0 28px #22c55e;
}
.client-map__route {
position: absolute;
height: 2px;
transform-origin: left center;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.75), transparent);
opacity: 0.65;
}
.client-map__route--one {
left: 40%;
top: 38%;
width: 230px;
transform: rotate(18deg);
}
.client-map__route--two {
left: 50%;
top: 58%;
width: 250px;
transform: rotate(-28deg);
}
.client-map__card {
position: relative;
padding: 30px;
overflow: hidden;
}
.client-map__card::before {
content: "";
position: absolute;
width: 240px;
height: 240px;
right: -100px;
top: -100px;
border-radius: 50%;
background: radial-gradient(circle, rgba(34, 197, 94, 0.25), transparent 70%);
}
.client-map__card > * {
position: relative;
z-index: 2;
}
.client-map__label {
color: #94a3b8;
}
.client-map__city {
margin: 14px 0 16px;
color: #ffffff;
font-size: clamp(38px, 6vw, 64px);
line-height: 1;
letter-spacing: -0.06em;
}
.client-map__text {
color: #cbd5e1;
font-size: 17px;
line-height: 1.7;
}
.client-map__stats {
display: grid;
gap: 12px;
margin: 28px 0;
}
.client-map__stats span {
border: 1px solid #334155;
border-radius: 16px;
padding: 13px;
color: #dbeafe;
background: #020617;
}
.client-map__stats strong {
color: #86efac;
}
.client-map__button {
width: 100%;
border: 0;
border-radius: 16px;
padding: 15px 18px;
color: #ffffff;
background: linear-gradient(135deg, #16a34a, #2563eb);
cursor: pointer;
transition: 0.25s;
}
.client-map__button:hover {
transform: translateY(-2px);
}
.client-map__card.is-changing {
animation: clientMapPulse 0.28s ease;
}
@keyframes clientMapPulse {
from {
opacity: 0.75;
transform: scale(0.985);
}
to {
opacity: 1;
transform: scale(1);
}
}
@media (max-width: 850px) {
.client-map {
padding: 26px 18px;
}
.client-map__grid {
grid-template-columns: 1fr;
}
.client-map__canvas {
min-height: 360px;
}
.client-map__pin {
font-size: 13px;
padding: 8px 11px;
}
}
const clientMapData = {
omsk: {
city: "Омск",
text: "Основной регион работы. Подходит для очных услуг, консультаций и локального продвижения.",
clients: "42",
format: "Очно / онлайн"
},
vilnius: {
city: "Вильнюс",
text: "Пример направления для русскоязычных клиентов за рубежом и онлайн-консультаций.",
clients: "18",
format: "Онлайн"
},
tbilisi: {
city: "Тбилиси",
text: "Подходит для показа международных заявок, удалённых услуг и консультаций для русскоязычной аудитории.",
clients: "27",
format: "Онлайн / партнёры"
},
online: {
city: "Онлайн",
text: "Отдельная точка для услуг без привязки к городу: консультации, обучение, сопровождение и поддержка.",
clients: "80+",
format: "Без географии"
}
};
const clientPins = document.querySelectorAll(".client-map__pin");
const clientCard = document.querySelector(".client-map__card");
const clientCity = document.querySelector(".client-map__city");
const clientText = document.querySelector(".client-map__text");
const clientCount = document.querySelector(".client-map__clients");
const clientFormat = document.querySelector(".client-map__format");
function updateClientMap(key) {
const data = clientMapData[key];
clientCard.classList.remove("is-changing");
void clientCard.offsetWidth;
clientCard.classList.add("is-changing");
clientCity.textContent = data.city;
clientText.textContent = data.text;
clientCount.textContent = data.clients;
clientFormat.textContent = data.format;
}
clientPins.forEach(function (pin) {
pin.addEventListener("click", function () {
clientPins.forEach(item => item.classList.remove("active"));
pin.classList.add("active");
updateClientMap(pin.dataset.city);
});
});