<!DOCTYPE html>
<!--
VIBE BLOCK: Бесконечный 2D-холст (Сетка / Grid)
Архитектура: Single-File Vibe Block (T1370)
Формат карточек: Квадратные (1:1), без подписей
Отступы: 60px между карточками
Высота: 100vh (100dvh)
Управление:
- Десктоп: дрейф от положения курсора (в центре — пауза, к краям — плавное ускорение)
- Мобильные: непрерывный автономный авто-дрейф без вмешательства пользователя
- Скролл страницы: свободный (wheel не блокируется, страница пролистывается)
Контент: 12 уникальных слотов для изображений в HTML-разметке
-->
<style>
.vb-canvas-grid {
box-sizing: border-box;
position: relative;
width: 100%;
margin: 0 auto;
height: 100vh !important;
height: 100dvh !important;
background-color: var(--uc-color-bg, #0d0d12);
overflow: hidden;
user-select: none;
-webkit-user-select: none;
touch-action: pan-y;
}
.vb-canvas-grid *,
.vb-canvas-grid *::before,
.vb-canvas-grid *::after {
box-sizing: border-box;
}
/* Сцена (матрица 2х2 квадранта для бесшовного зацикливания) */
.vb-canvas-grid__stage {
position: absolute;
top: 0;
left: 0;
display: grid;
grid-template-columns: repeat(2, auto);
width: max-content;
height: max-content;
will-change: transform;
pointer-events: auto;
}
/* Квадрант: 12 карточек в 6 колонок, отступ ровно 60px */
.vb-canvas-grid__quadrant {
display: grid;
grid-template-columns: repeat(6, 320px);
gap: 60px;
padding: 30px;
width: max-content;
height: max-content;
}
/* Карточка: строгий квадрат 1:1, прямые углы 0px, без текста */
.vb-canvas-grid__card {
position: relative;
width: 320px;
height: 320px;
aspect-ratio: 1 / 1;
border-radius: 0 !important;
overflow: hidden;
background: #14151e;
outline: 1px solid rgba(255, 255, 255, 0.12);
cursor: pointer;
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
will-change: transform;
}
.vb-canvas-grid__card:hover {
transform: scale(1.04);
z-index: 10;
}
.vb-canvas-grid__card-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: 0 !important;
pointer-events: none;
transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.vb-canvas-grid__card:hover .vb-canvas-grid__card-img {
transform: scale(1.05);
}
/* Полноэкранный просмотр (Lightbox) */
.vb-canvas-grid__lightbox {
position: fixed;
inset: 0;
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1), visibility 0.35s ease;
}
.vb-canvas-grid__lightbox.is-open {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.vb-canvas-grid__lightbox-backdrop {
position: absolute;
inset: 0;
background-color: rgba(6, 6, 10, 0.9);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
cursor: pointer;
}
.vb-canvas-grid__lightbox-dialog {
position: relative;
max-width: 90vw;
max-height: 90vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transform: scale(0.95);
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
}
.vb-canvas-grid__lightbox.is-open .vb-canvas-grid__lightbox-dialog {
transform: scale(1);
}
.vb-canvas-grid__lightbox-img {
max-width: 90vw;
max-height: 88vh;
width: auto;
height: auto;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.7);
display: block;
user-select: none;
-webkit-user-select: none;
pointer-events: auto;
}
.vb-canvas-grid__lightbox-close {
position: fixed !important;
top: 24px !important;
right: 24px !important;
z-index: 100000 !important;
width: 44px !important;
height: 44px !important;
background: rgba(255, 255, 255, 0.12) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
border-radius: 50% !important;
color: #ffffff !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
padding: 0 !important;
box-sizing: border-box !important;
transition: background-color 0.25s ease, transform 0.25s cubic-bezier(0.22, 1, 0.36, 1) !important;
}
.vb-canvas-grid__lightbox-close:hover {
background: rgba(255, 255, 255, 0.25) !important;
transform: scale(1.08) !important;
}
/* Адаптив под планшеты и смартфоны */
@media (max-width: 960px) {
.vb-canvas-grid__quadrant {
grid-template-columns: repeat(6, 240px);
gap: 40px;
padding: 20px;
}
.vb-canvas-grid__card {
width: 240px;
height: 240px;
}
}
@media (max-width: 600px) {
.vb-canvas-grid__quadrant {
grid-template-columns: repeat(6, 180px);
gap: 24px;
padding: 12px;
}
.vb-canvas-grid__card {
width: 180px;
height: 180px;
}
.vb-canvas-grid__lightbox {
padding: 16px;
}
.vb-canvas-grid__lightbox-dialog {
max-width: 96vw;
max-height: 86vh;
}
.vb-canvas-grid__lightbox-img {
max-width: 96vw;
max-height: 84vh;
}
.vb-canvas-grid__lightbox-close {
top: 16px !important;
right: 16px !important;
width: 38px !important;
height: 38px !important;
}
}
</style>
<section class="vb-block vb-canvas-grid" aria-label="Бесконечный холст - Сетка">
<div class="vb-canvas-grid__stage">
<!--
МАСТЕР-КВАДРАНТ: 12 карточек в HTML.
Каждая ссылка вынесена отдельно для легкой замены в Контенте Тильды.
-->
<div class="vb-canvas-grid__quadrant">
<!-- Слот 01 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=01"
alt="Фото 01" loading="lazy">
</article>
<!-- Слот 02 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=02"
alt="Фото 02" loading="lazy">
</article>
<!-- Слот 03 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=03"
alt="Фото 03" loading="lazy">
</article>
<!-- Слот 04 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=04"
alt="Фото 04" loading="lazy">
</article>
<!-- Слот 05 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=05"
alt="Фото 05" loading="lazy">
</article>
<!-- Слот 06 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=06"
alt="Фото 06" loading="lazy">
</article>
<!-- Слот 07 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=07"
alt="Фото 07" loading="lazy">
</article>
<!-- Слот 08 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=08"
alt="Фото 08" loading="lazy">
</article>
<!-- Слот 09 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=09"
alt="Фото 09" loading="lazy">
</article>
<!-- Слот 10 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=10"
alt="Фото 10" loading="lazy">
</article>
<!-- Слот 11 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=11"
alt="Фото 11" loading="lazy">
</article>
<!-- Слот 12 -->
<article class="vb-card vb-canvas-grid__card">
<img class="vb-card__img vb-canvas-grid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=12"
alt="Фото 12" loading="lazy">
</article>
</div>
</div>
<!-- Полноэкранный просмотр (Lightbox) -->
<div class="vb-canvas-grid__lightbox" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Просмотр изображения">
<div class="vb-canvas-grid__lightbox-backdrop"></div>
<div class="vb-canvas-grid__lightbox-dialog">
<img class="vb-canvas-grid__lightbox-img" src="" alt="">
</div>
<button class="vb-canvas-grid__lightbox-close vb-btn" type="button" aria-label="Закрыть">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</section>
<script>
(function () {
'use strict';
/* Поиск корня инстанса */
var root = (function () {
var s = document.currentScript;
if (s) {
var el = s.parentElement;
while (el) {
if (el.classList && el.classList.contains('vb-canvas-grid')) return el;
el = el.parentElement;
}
}
return document.querySelector('.vb-canvas-grid');
}());
if (!root || root.dataset.initialized) return;
root.dataset.initialized = 'true';
var stage = root.querySelector('.vb-canvas-grid__stage');
var masterQuad = stage.querySelector('.vb-canvas-grid__quadrant');
if (!masterQuad) return;
/* ══════════════════════════════════════════════════
КЛОНИРОВАНИЕ 2х2 КВАДРАНТОВ ДЛЯ БЕСШОВНОГО ТОРА
══════════════════════════════════════════════════ */
for (var i = 0; i < 3; i++) {
stage.appendChild(masterQuad.cloneNode(true));
}
var quadW = 0;
var quadH = 0;
function measure() {
quadW = masterQuad.offsetWidth;
quadH = masterQuad.offsetHeight;
}
measure();
/* Начальная позиция по центру */
var posX = -quadW / 2;
var posY = -quadH / 2;
/* ══════════════════════════════════════════════════
УПРАВЛЕНИЕ СКОРОСТЬЮ: КУРСОР НА ДЕСКТОПЕ / АВТО НА МОБАЙЛЕ
══════════════════════════════════════════════════ */
var isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
var MAX_SPEED = 12.0; // Пиковая скорость дрейфа холста (увеличена в 2 раза)
var DEAD_ZONE = 0.08; // 8% мертвая зона в центре (полный покой)
var LERP_FACTOR = 0.06; // Плавность нарастания и торможения
var targetSpeedX = 0;
var targetSpeedY = 0;
var currentSpeedX = 0;
var currentSpeedY = 0;
// Автономный счетчик времени для мобильных
var autoTime = 0;
/* ══════════════════════════════════════════════════
ПОЛНОЭКРАННЫЙ ПРОСМОТР (LIGHTBOX)
══════════════════════════════════════════════════ */
var lightbox = root.querySelector('.vb-canvas-grid__lightbox');
var lightboxImg = lightbox ? lightbox.querySelector('.vb-canvas-grid__lightbox-img') : null;
var lightboxCloseBtn = lightbox ? lightbox.querySelector('.vb-canvas-grid__lightbox-close') : null;
var lightboxBackdrop = lightbox ? lightbox.querySelector('.vb-canvas-grid__lightbox-backdrop') : null;
var isLightboxOpen = false;
function openLightbox(src, alt) {
if (!lightbox || !lightboxImg) return;
lightboxImg.src = src;
lightboxImg.alt = alt || '';
lightbox.classList.add('is-open');
lightbox.setAttribute('aria-hidden', 'false');
isLightboxOpen = true;
targetSpeedX = 0;
targetSpeedY = 0;
currentSpeedX = 0;
currentSpeedY = 0;
}
function closeLightbox() {
if (!lightbox || !lightboxImg) return;
lightbox.classList.remove('is-open');
lightbox.setAttribute('aria-hidden', 'true');
isLightboxOpen = false;
setTimeout(function () {
if (!isLightboxOpen && lightboxImg) {
lightboxImg.src = '';
}
}, 350);
}
if (lightbox) {
if (lightboxCloseBtn) {
lightboxCloseBtn.addEventListener('click', function (e) {
e.stopPropagation();
closeLightbox();
});
}
if (lightboxBackdrop) {
lightboxBackdrop.addEventListener('click', closeLightbox);
}
lightbox.addEventListener('click', function (e) {
if (e.target === lightbox) {
closeLightbox();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && isLightboxOpen) {
closeLightbox();
}
});
}
// Защита от ложного клика при свайпе/прокрутке на смартфонах
var touchStartX = 0;
var touchStartY = 0;
var isSwiping = false;
stage.addEventListener('touchstart', function (e) {
if (e.touches.length === 1) {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
isSwiping = false;
}
}, { passive: true });
stage.addEventListener('touchmove', function (e) {
if (e.touches.length === 1) {
var dx = Math.abs(e.touches[0].clientX - touchStartX);
var dy = Math.abs(e.touches[0].clientY - touchStartY);
if (dx > 10 || dy > 10) {
isSwiping = true;
}
}
}, { passive: true });
// Делегирование клика на карточку (работает и для оригиналов, и для клонированных квадрантов)
stage.addEventListener('click', function (e) {
if (isSwiping) return;
var card = e.target.closest('.vb-canvas-grid__card');
if (!card) return;
var img = card.querySelector('img');
if (!img) return;
openLightbox(img.currentSrc || img.src, img.alt);
});
function onMouseMove(e) {
if (isTouch || isLightboxOpen) return;
var rect = root.getBoundingClientRect();
// Если блок вне зоны видимости — останавливаем расчет
if (rect.bottom < 0 || rect.top > window.innerHeight) {
targetSpeedX = 0;
targetSpeedY = 0;
return;
}
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
var nx = (e.clientX - cx) / (rect.width / 2);
var ny = (e.clientY - cy) / (rect.height / 2);
nx = Math.max(-1, Math.min(1, nx));
ny = Math.max(-1, Math.min(1, ny));
// Центр экрана — покой, при смещении к краям — движение в сторону взгляда
if (Math.abs(nx) < DEAD_ZONE) {
targetSpeedX = 0;
} else {
var signX = nx > 0 ? 1 : -1;
var factorX = (Math.abs(nx) - DEAD_ZONE) / (1 - DEAD_ZONE);
targetSpeedX = -signX * factorX * MAX_SPEED;
}
if (Math.abs(ny) < DEAD_ZONE) {
targetSpeedY = 0;
} else {
var signY = ny > 0 ? 1 : -1;
var factorY = (Math.abs(ny) - DEAD_ZONE) / (1 - DEAD_ZONE);
targetSpeedY = -signY * factorY * MAX_SPEED;
}
}
function onMouseLeave() {
if (isTouch) return;
targetSpeedX = 0;
targetSpeedY = 0;
}
/* ══════════════════════════════════════════════════
БЕСШОВНОЕ ЗАЦИКЛИВАНИЕ КООРДИНАТ (2D TORUS)
══════════════════════════════════════════════════ */
function wrap() {
if (quadW <= 0 || quadH <= 0) return;
if (posX < -quadW) posX += quadW;
else if (posX > 0) posX -= quadW;
if (posY < -quadH) posY += quadH;
else if (posY > 0) posY -= quadH;
}
function render() {
stage.style.transform = 'translate3d(' + posX.toFixed(2) + 'px, ' + posY.toFixed(2) + 'px, 0)';
}
/* ══════════════════════════════════════════════════
ФИЗИЧЕСКИЙ РАФ-ЦИКЛ
══════════════════════════════════════════════════ */
function tick() {
if (!isLightboxOpen) {
if (isTouch) {
// На мобильных: плавный, волнообразный автономный дрейф
autoTime += 0.005;
posX += -1.5 * Math.cos(autoTime * 0.7);
posY += -1.1 * Math.sin(autoTime * 0.9);
} else {
// На десктопе: кинематика от положения курсора
currentSpeedX += (targetSpeedX - currentSpeedX) * LERP_FACTOR;
currentSpeedY += (targetSpeedY - currentSpeedY) * LERP_FACTOR;
posX += currentSpeedX;
posY += currentSpeedY;
}
wrap();
render();
}
requestAnimationFrame(tick);
}
root.addEventListener('mousemove', onMouseMove);
root.addEventListener('mouseleave', onMouseLeave);
window.addEventListener('resize', function () {
isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
measure();
});
render();
requestAnimationFrame(tick);
})();
</script>
<!DOCTYPE html>
<!--
VIBE BLOCK: Бесконечный 2D-холст (Мейсонри / Masonry)
Архитектура: Single-File Vibe Block (T1370)
Формат карточек: Ступенчатый сдвиг четных колонок, без подписей
Отступы: 60px между карточками
Высота: 100vh (100dvh)
Управление:
- Десктоп: дрейф от положения курсора (в центре — пауза, к краям — плавное ускорение)
- Мобильные: непрерывный автономный авто-дрейф без вмешательства пользователя
- Скролл страницы: свободный (wheel не блокируется, страница пролистывается)
Контент: 12 уникальных слотов для изображений в HTML-разметке
-->
<style>
.vb-canvas-masonry {
box-sizing: border-box;
position: relative;
width: 100%;
margin: 0 auto;
height: 100vh !important;
height: 100dvh !important;
background-color: var(--uc-color-bg, #0d0d12);
overflow: hidden;
user-select: none;
-webkit-user-select: none;
touch-action: pan-y;
}
.vb-canvas-masonry *,
.vb-canvas-masonry *::before,
.vb-canvas-masonry *::after {
box-sizing: border-box;
}
/* Сцена (матрица 2х2 квадранта для бесшовного зацикливания) */
.vb-canvas-masonry__stage {
position: absolute;
top: 0;
left: 0;
display: grid;
grid-template-columns: repeat(2, auto);
width: max-content;
height: max-content;
will-change: transform;
pointer-events: auto;
}
/* Квадрант: 12 карточек в 6 колонок, отступ 60px */
.vb-canvas-masonry__quadrant {
display: grid;
grid-template-columns: repeat(6, 290px);
gap: 60px;
padding: 30px;
width: max-content;
height: max-content;
}
/* Карточка: строгие прямые углы 0px, без текста */
.vb-canvas-masonry__card {
position: relative;
width: 290px;
height: 370px;
border-radius: 0 !important;
overflow: hidden;
background: #14151e;
outline: 1px solid rgba(255, 255, 255, 0.12);
cursor: pointer;
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
will-change: transform;
}
/* Ступенчатый ритм Мейсонри: смещение четных элементов вниз */
.vb-canvas-masonry__card:nth-child(even) {
margin-top: 110px;
}
.vb-canvas-masonry__card:hover {
transform: scale(1.04);
z-index: 10;
}
.vb-canvas-masonry__card-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: 0 !important;
pointer-events: none;
transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.vb-canvas-masonry__card:hover .vb-canvas-masonry__card-img {
transform: scale(1.05);
}
/* Полноэкранный просмотр (Lightbox) */
.vb-canvas-masonry__lightbox {
position: fixed;
inset: 0;
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1), visibility 0.35s ease;
}
.vb-canvas-masonry__lightbox.is-open {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.vb-canvas-masonry__lightbox-backdrop {
position: absolute;
inset: 0;
background-color: rgba(6, 6, 10, 0.9);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
cursor: pointer;
}
.vb-canvas-masonry__lightbox-dialog {
position: relative;
max-width: 90vw;
max-height: 90vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transform: scale(0.95);
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
}
.vb-canvas-masonry__lightbox.is-open .vb-canvas-masonry__lightbox-dialog {
transform: scale(1);
}
.vb-canvas-masonry__lightbox-img {
max-width: 90vw;
max-height: 88vh;
width: auto;
height: auto;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.7);
display: block;
user-select: none;
-webkit-user-select: none;
pointer-events: auto;
}
.vb-canvas-masonry__lightbox-close {
position: fixed !important;
top: 24px !important;
right: 24px !important;
z-index: 100000 !important;
width: 44px !important;
height: 44px !important;
background: rgba(255, 255, 255, 0.12) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
border-radius: 50% !important;
color: #ffffff !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
padding: 0 !important;
box-sizing: border-box !important;
transition: background-color 0.25s ease, transform 0.25s cubic-bezier(0.22, 1, 0.36, 1) !important;
}
.vb-canvas-masonry__lightbox-close:hover {
background: rgba(255, 255, 255, 0.25) !important;
transform: scale(1.08) !important;
}
/* Адаптив для планшетов и смартфонов */
@media (max-width: 960px) {
.vb-canvas-masonry__quadrant {
grid-template-columns: repeat(6, 230px);
gap: 40px;
padding: 20px;
}
.vb-canvas-masonry__card {
width: 230px;
height: 300px;
}
.vb-canvas-masonry__card:nth-child(even) {
margin-top: 80px;
}
}
@media (max-width: 600px) {
.vb-canvas-masonry__quadrant {
grid-template-columns: repeat(6, 175px);
gap: 24px;
padding: 12px;
}
.vb-canvas-masonry__card {
width: 175px;
height: 235px;
}
.vb-canvas-masonry__card:nth-child(even) {
margin-top: 55px;
}
.vb-canvas-masonry__lightbox {
padding: 16px;
}
.vb-canvas-masonry__lightbox-dialog {
max-width: 96vw;
max-height: 86vh;
}
.vb-canvas-masonry__lightbox-img {
max-width: 96vw;
max-height: 84vh;
}
.vb-canvas-masonry__lightbox-close {
top: 16px !important;
right: 16px !important;
width: 38px !important;
height: 38px !important;
}
}
</style>
<section class="vb-block vb-canvas-masonry" aria-label="Бесконечный холст - Мейсонри">
<div class="vb-canvas-masonry__stage">
<!--
МАСТЕР-КВАДРАНТ: 12 карточек портфолио в HTML.
Каждая ссылка на картинку вынесена отдельно для легкой замены в Контенте Тильды.
-->
<div class="vb-canvas-masonry__quadrant">
<!-- Слот 01 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=01"
alt="Фото 01" loading="lazy">
</article>
<!-- Слот 02 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=02"
alt="Фото 02" loading="lazy">
</article>
<!-- Слот 03 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=03"
alt="Фото 03" loading="lazy">
</article>
<!-- Слот 04 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=04"
alt="Фото 04" loading="lazy">
</article>
<!-- Слот 05 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=05"
alt="Фото 05" loading="lazy">
</article>
<!-- Слот 06 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=06"
alt="Фото 06" loading="lazy">
</article>
<!-- Слот 07 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=07"
alt="Фото 07" loading="lazy">
</article>
<!-- Слот 08 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=08"
alt="Фото 08" loading="lazy">
</article>
<!-- Слот 09 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=09"
alt="Фото 09" loading="lazy">
</article>
<!-- Слот 10 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=10"
alt="Фото 10" loading="lazy">
</article>
<!-- Слот 11 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=11"
alt="Фото 11" loading="lazy">
</article>
<!-- Слот 12 -->
<article class="vb-card vb-canvas-masonry__card">
<img class="vb-card__img vb-canvas-masonry__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=12"
alt="Фото 12" loading="lazy">
</article>
</div>
</div>
<!-- Полноэкранный просмотр (Lightbox) -->
<div class="vb-canvas-masonry__lightbox" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Просмотр изображения">
<div class="vb-canvas-masonry__lightbox-backdrop"></div>
<div class="vb-canvas-masonry__lightbox-dialog">
<img class="vb-canvas-masonry__lightbox-img" src="" alt="">
</div>
<button class="vb-canvas-masonry__lightbox-close vb-btn" type="button" aria-label="Закрыть">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</section>
<script>
(function () {
'use strict';
/* Поиск корня инстанса */
var root = (function () {
var s = document.currentScript;
if (s) {
var el = s.parentElement;
while (el) {
if (el.classList && el.classList.contains('vb-canvas-masonry')) return el;
el = el.parentElement;
}
}
return document.querySelector('.vb-canvas-masonry');
}());
if (!root || root.dataset.initialized) return;
root.dataset.initialized = 'true';
var stage = root.querySelector('.vb-canvas-masonry__stage');
var masterQuad = stage.querySelector('.vb-canvas-masonry__quadrant');
if (!masterQuad) return;
/* Клонирование 2х2 квадрантов для тороидального зацикливания */
for (var i = 0; i < 3; i++) {
stage.appendChild(masterQuad.cloneNode(true));
}
var quadW = 0;
var quadH = 0;
function measure() {
quadW = masterQuad.offsetWidth;
quadH = masterQuad.offsetHeight;
}
measure();
/* Начальная позиция */
var posX = -quadW / 2;
var posY = -quadH / 2;
/* ══════════════════════════════════════════════════
УПРАВЛЕНИЕ: КУРСОР НА ДЕСКТОПЕ / АВТО НА МОБАЙЛЕ
══════════════════════════════════════════════════ */
var isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
var MAX_SPEED = 12.0; // Пиковая скорость дрейфа холста (увеличена в 2 раза)
var DEAD_ZONE = 0.08; // 8% мертвая зона в центре (полный покой)
var LERP_FACTOR = 0.06; // Плавность нарастания и торможения
var targetSpeedX = 0;
var targetSpeedY = 0;
var currentSpeedX = 0;
var currentSpeedY = 0;
var autoTime = 0;
/* ══════════════════════════════════════════════════
ПОЛНОЭКРАННЫЙ ПРОСМОТР (LIGHTBOX)
══════════════════════════════════════════════════ */
var lightbox = root.querySelector('.vb-canvas-masonry__lightbox');
var lightboxImg = lightbox ? lightbox.querySelector('.vb-canvas-masonry__lightbox-img') : null;
var lightboxCloseBtn = lightbox ? lightbox.querySelector('.vb-canvas-masonry__lightbox-close') : null;
var lightboxBackdrop = lightbox ? lightbox.querySelector('.vb-canvas-masonry__lightbox-backdrop') : null;
var isLightboxOpen = false;
function openLightbox(src, alt) {
if (!lightbox || !lightboxImg) return;
lightboxImg.src = src;
lightboxImg.alt = alt || '';
lightbox.classList.add('is-open');
lightbox.setAttribute('aria-hidden', 'false');
isLightboxOpen = true;
targetSpeedX = 0;
targetSpeedY = 0;
currentSpeedX = 0;
currentSpeedY = 0;
}
function closeLightbox() {
if (!lightbox || !lightboxImg) return;
lightbox.classList.remove('is-open');
lightbox.setAttribute('aria-hidden', 'true');
isLightboxOpen = false;
setTimeout(function () {
if (!isLightboxOpen && lightboxImg) {
lightboxImg.src = '';
}
}, 350);
}
if (lightbox) {
if (lightboxCloseBtn) {
lightboxCloseBtn.addEventListener('click', function (e) {
e.stopPropagation();
closeLightbox();
});
}
if (lightboxBackdrop) {
lightboxBackdrop.addEventListener('click', closeLightbox);
}
lightbox.addEventListener('click', function (e) {
if (e.target === lightbox) {
closeLightbox();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && isLightboxOpen) {
closeLightbox();
}
});
}
// Защита от ложного клика при свайпе/прокрутке на смартфонах
var touchStartX = 0;
var touchStartY = 0;
var isSwiping = false;
stage.addEventListener('touchstart', function (e) {
if (e.touches.length === 1) {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
isSwiping = false;
}
}, { passive: true });
stage.addEventListener('touchmove', function (e) {
if (e.touches.length === 1) {
var dx = Math.abs(e.touches[0].clientX - touchStartX);
var dy = Math.abs(e.touches[0].clientY - touchStartY);
if (dx > 10 || dy > 10) {
isSwiping = true;
}
}
}, { passive: true });
// Делегирование клика на карточку (работает и для оригиналов, и для клонированных квадрантов)
stage.addEventListener('click', function (e) {
if (isSwiping) return;
var card = e.target.closest('.vb-canvas-masonry__card');
if (!card) return;
var img = card.querySelector('img');
if (!img) return;
openLightbox(img.currentSrc || img.src, img.alt);
});
function onMouseMove(e) {
if (isTouch || isLightboxOpen) return;
var rect = root.getBoundingClientRect();
if (rect.bottom < 0 || rect.top > window.innerHeight) {
targetSpeedX = 0;
targetSpeedY = 0;
return;
}
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
var nx = (e.clientX - cx) / (rect.width / 2);
var ny = (e.clientY - cy) / (rect.height / 2);
nx = Math.max(-1, Math.min(1, nx));
ny = Math.max(-1, Math.min(1, ny));
if (Math.abs(nx) < DEAD_ZONE) {
targetSpeedX = 0;
} else {
var signX = nx > 0 ? 1 : -1;
var factorX = (Math.abs(nx) - DEAD_ZONE) / (1 - DEAD_ZONE);
targetSpeedX = -signX * factorX * MAX_SPEED;
}
if (Math.abs(ny) < DEAD_ZONE) {
targetSpeedY = 0;
} else {
var signY = ny > 0 ? 1 : -1;
var factorY = (Math.abs(ny) - DEAD_ZONE) / (1 - DEAD_ZONE);
targetSpeedY = -signY * factorY * MAX_SPEED;
}
}
function onMouseLeave() {
if (isTouch) return;
targetSpeedX = 0;
targetSpeedY = 0;
}
/* Зацикливание координат */
function wrap() {
if (quadW <= 0 || quadH <= 0) return;
if (posX < -quadW) posX += quadW;
else if (posX > 0) posX -= quadW;
if (posY < -quadH) posY += quadH;
else if (posY > 0) posY -= quadH;
}
function render() {
stage.style.transform = 'translate3d(' + posX.toFixed(2) + 'px, ' + posY.toFixed(2) + 'px, 0)';
}
/* Физический РАФ-цикл */
function tick() {
if (!isLightboxOpen) {
if (isTouch) {
autoTime += 0.005;
posX += -1.5 * Math.cos(autoTime * 0.7);
posY += -1.1 * Math.sin(autoTime * 0.9);
} else {
currentSpeedX += (targetSpeedX - currentSpeedX) * LERP_FACTOR;
currentSpeedY += (targetSpeedY - currentSpeedY) * LERP_FACTOR;
posX += currentSpeedX;
posY += currentSpeedY;
}
wrap();
render();
}
requestAnimationFrame(tick);
}
root.addEventListener('mousemove', onMouseMove);
root.addEventListener('mouseleave', onMouseLeave);
window.addEventListener('resize', function () {
isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
measure();
});
render();
requestAnimationFrame(tick);
})();
</script>
<!DOCTYPE html>
<!--
VIBE BLOCK: Бесконечный 2D-холст (Полароид / Polaroid)
Архитектура: Single-File Vibe Block (T1370)
Формат карточек: Винтажные белые рамки Полароид, естественный наклон, выравнивание при ховере
Отступы: 60px между карточками
Высота: 100vh (100dvh)
Управление:
- Десктоп: дрейф от положения курсора (в центре — пауза, к краям — плавное ускорение)
- Мобильные: непрерывный автономный авто-дрейф без вмешательства пользователя
- Скролл страницы: свободный (wheel не блокируется, страница пролистывается)
Контент: 12 уникальных слотов для изображений в HTML-разметке
-->
<style>
.vb-canvas-polaroid {
box-sizing: border-box;
position: relative;
width: 100%;
margin: 0 auto;
height: 100vh !important;
height: 100dvh !important;
background-color: var(--uc-color-bg, #0d0d12);
overflow: hidden;
user-select: none;
-webkit-user-select: none;
touch-action: pan-y;
}
.vb-canvas-polaroid *,
.vb-canvas-polaroid *::before,
.vb-canvas-polaroid *::after {
box-sizing: border-box;
}
/* Сцена (матрица 2х2 квадранта для бесшовного зацикливания) */
.vb-canvas-polaroid__stage {
position: absolute;
top: 0;
left: 0;
display: grid;
grid-template-columns: repeat(2, auto);
width: max-content;
height: max-content;
will-change: transform;
pointer-events: auto;
}
/* Квадрант: 12 карточек в 6 колонок, отступ 60px */
.vb-canvas-polaroid__quadrant {
display: grid;
grid-template-columns: repeat(6, 290px);
gap: 60px;
padding: 30px;
width: max-content;
height: max-content;
}
/* Карточка: белая рамка Полароид с широким нижним полем */
.vb-canvas-polaroid__card {
position: relative;
width: 290px;
height: 360px;
background: #ffffff;
border: 10px solid #ffffff;
border-bottom: 38px solid #ffffff;
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.35s ease;
will-change: transform;
}
/* Легкий живой наклон карточек */
.vb-canvas-polaroid__card:nth-child(even) {
margin-top: 60px;
transform: rotate(3deg);
}
.vb-canvas-polaroid__card:nth-child(odd) {
transform: rotate(-2.5deg);
}
/* При наведении карточка выравнивается и увеличивается */
.vb-canvas-polaroid__card:hover {
transform: rotate(0deg) scale(1.06) !important;
box-shadow: 0 26px 54px rgba(0, 0, 0, 0.75) !important;
z-index: 20;
}
.vb-canvas-polaroid__card-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
pointer-events: none;
}
/* Полноэкранный просмотр (Lightbox) */
.vb-canvas-polaroid__lightbox {
position: fixed;
inset: 0;
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1), visibility 0.35s ease;
}
.vb-canvas-polaroid__lightbox.is-open {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.vb-canvas-polaroid__lightbox-backdrop {
position: absolute;
inset: 0;
background-color: rgba(6, 6, 10, 0.9);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
cursor: pointer;
}
.vb-canvas-polaroid__lightbox-dialog {
position: relative;
max-width: 90vw;
max-height: 90vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transform: scale(0.95);
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
}
.vb-canvas-polaroid__lightbox.is-open .vb-canvas-polaroid__lightbox-dialog {
transform: scale(1);
}
.vb-canvas-polaroid__lightbox-img {
max-width: 90vw;
max-height: 88vh;
width: auto;
height: auto;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.7);
display: block;
user-select: none;
-webkit-user-select: none;
pointer-events: auto;
}
.vb-canvas-polaroid__lightbox-close {
position: fixed !important;
top: 24px !important;
right: 24px !important;
z-index: 100000 !important;
width: 44px !important;
height: 44px !important;
background: rgba(255, 255, 255, 0.12) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
border-radius: 50% !important;
color: #ffffff !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
padding: 0 !important;
box-sizing: border-box !important;
transition: background-color 0.25s ease, transform 0.25s cubic-bezier(0.22, 1, 0.36, 1) !important;
}
.vb-canvas-polaroid__lightbox-close:hover {
background: rgba(255, 255, 255, 0.25) !important;
transform: scale(1.08) !important;
}
/* Адаптив для планшетов и смартфонов */
@media (max-width: 960px) {
.vb-canvas-polaroid__quadrant {
grid-template-columns: repeat(6, 220px);
gap: 40px;
padding: 20px;
}
.vb-canvas-polaroid__card {
width: 220px;
height: 280px;
border: 8px solid #ffffff;
border-bottom: 28px solid #ffffff;
}
.vb-canvas-polaroid__card:nth-child(even) {
margin-top: 40px;
}
}
@media (max-width: 600px) {
.vb-canvas-polaroid__quadrant {
grid-template-columns: repeat(6, 165px);
gap: 24px;
padding: 12px;
}
.vb-canvas-polaroid__card {
width: 165px;
height: 215px;
border: 6px solid #ffffff;
border-bottom: 22px solid #ffffff;
}
.vb-canvas-polaroid__card:nth-child(even) {
margin-top: 30px;
}
.vb-canvas-polaroid__lightbox {
padding: 16px;
}
.vb-canvas-polaroid__lightbox-dialog {
max-width: 96vw;
max-height: 86vh;
}
.vb-canvas-polaroid__lightbox-img {
max-width: 96vw;
max-height: 84vh;
}
.vb-canvas-polaroid__lightbox-close {
top: 16px !important;
right: 16px !important;
width: 38px !important;
height: 38px !important;
}
}
</style>
<section class="vb-block vb-canvas-polaroid" aria-label="Бесконечный холст - Полароид">
<div class="vb-canvas-polaroid__stage">
<!--
МАСТЕР-КВАДРАНТ: 12 карточек в HTML.
Каждая ссылка на картинку вынесена отдельно для легкой замены в Контенте Тильды.
-->
<div class="vb-canvas-polaroid__quadrant">
<!-- Слот 01 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=01"
alt="Фото 01" loading="lazy">
</article>
<!-- Слот 02 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=02"
alt="Фото 02" loading="lazy">
</article>
<!-- Слот 03 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=03"
alt="Фото 03" loading="lazy">
</article>
<!-- Слот 04 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=04"
alt="Фото 04" loading="lazy">
</article>
<!-- Слот 05 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=05"
alt="Фото 05" loading="lazy">
</article>
<!-- Слот 06 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=06"
alt="Фото 06" loading="lazy">
</article>
<!-- Слот 07 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=07"
alt="Фото 07" loading="lazy">
</article>
<!-- Слот 08 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=08"
alt="Фото 08" loading="lazy">
</article>
<!-- Слот 09 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=09"
alt="Фото 09" loading="lazy">
</article>
<!-- Слот 10 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=10"
alt="Фото 10" loading="lazy">
</article>
<!-- Слот 11 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=11"
alt="Фото 11" loading="lazy">
</article>
<!-- Слот 12 -->
<article class="vb-card vb-canvas-polaroid__card">
<img class="vb-card__img vb-canvas-polaroid__card-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=12"
alt="Фото 12" loading="lazy">
</article>
</div>
</div>
<!-- Полноэкранный просмотр (Lightbox) -->
<div class="vb-canvas-polaroid__lightbox" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Просмотр изображения">
<div class="vb-canvas-polaroid__lightbox-backdrop"></div>
<div class="vb-canvas-polaroid__lightbox-dialog">
<img class="vb-canvas-polaroid__lightbox-img" src="" alt="">
</div>
<button class="vb-canvas-polaroid__lightbox-close vb-btn" type="button" aria-label="Закрыть">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</section>
<script>
(function () {
'use strict';
/* Поиск корня инстанса */
var root = (function () {
var s = document.currentScript;
if (s) {
var el = s.parentElement;
while (el) {
if (el.classList && el.classList.contains('vb-canvas-polaroid')) return el;
el = el.parentElement;
}
}
return document.querySelector('.vb-canvas-polaroid');
}());
if (!root || root.dataset.initialized) return;
root.dataset.initialized = 'true';
var stage = root.querySelector('.vb-canvas-polaroid__stage');
var masterQuad = stage.querySelector('.vb-canvas-polaroid__quadrant');
if (!masterQuad) return;
/* Клонирование 2х2 квадрантов для тороидального зацикливания */
for (var i = 0; i < 3; i++) {
stage.appendChild(masterQuad.cloneNode(true));
}
var quadW = 0;
var quadH = 0;
function measure() {
quadW = masterQuad.offsetWidth;
quadH = masterQuad.offsetHeight;
}
measure();
/* Начальная позиция */
var posX = -quadW / 2;
var posY = -quadH / 2;
/* ══════════════════════════════════════════════════
УПРАВЛЕНИЕ: КУРСОР НА ДЕСКТОПЕ / АВТО НА МОБАЙЛЕ
══════════════════════════════════════════════════ */
var isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
var MAX_SPEED = 12.0; // Пиковая скорость дрейфа холста (увеличена в 2 раза)
var DEAD_ZONE = 0.08; // 8% мертвая зона в центре (полный покой)
var LERP_FACTOR = 0.06; // Плавность нарастания и торможения
var targetSpeedX = 0;
var targetSpeedY = 0;
var currentSpeedX = 0;
var currentSpeedY = 0;
var autoTime = 0;
/* ══════════════════════════════════════════════════
ПОЛНОЭКРАННЫЙ ПРОСМОТР (LIGHTBOX)
══════════════════════════════════════════════════ */
var lightbox = root.querySelector('.vb-canvas-polaroid__lightbox');
var lightboxImg = lightbox ? lightbox.querySelector('.vb-canvas-polaroid__lightbox-img') : null;
var lightboxCloseBtn = lightbox ? lightbox.querySelector('.vb-canvas-polaroid__lightbox-close') : null;
var lightboxBackdrop = lightbox ? lightbox.querySelector('.vb-canvas-polaroid__lightbox-backdrop') : null;
var isLightboxOpen = false;
function openLightbox(src, alt) {
if (!lightbox || !lightboxImg) return;
lightboxImg.src = src;
lightboxImg.alt = alt || '';
lightbox.classList.add('is-open');
lightbox.setAttribute('aria-hidden', 'false');
isLightboxOpen = true;
targetSpeedX = 0;
targetSpeedY = 0;
currentSpeedX = 0;
currentSpeedY = 0;
}
function closeLightbox() {
if (!lightbox || !lightboxImg) return;
lightbox.classList.remove('is-open');
lightbox.setAttribute('aria-hidden', 'true');
isLightboxOpen = false;
setTimeout(function () {
if (!isLightboxOpen && lightboxImg) {
lightboxImg.src = '';
}
}, 350);
}
if (lightbox) {
if (lightboxCloseBtn) {
lightboxCloseBtn.addEventListener('click', function (e) {
e.stopPropagation();
closeLightbox();
});
}
if (lightboxBackdrop) {
lightboxBackdrop.addEventListener('click', closeLightbox);
}
lightbox.addEventListener('click', function (e) {
if (e.target === lightbox) {
closeLightbox();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && isLightboxOpen) {
closeLightbox();
}
});
}
// Защита от ложного клика при свайпе/прокрутке на смартфонах
var touchStartX = 0;
var touchStartY = 0;
var isSwiping = false;
stage.addEventListener('touchstart', function (e) {
if (e.touches.length === 1) {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
isSwiping = false;
}
}, { passive: true });
stage.addEventListener('touchmove', function (e) {
if (e.touches.length === 1) {
var dx = Math.abs(e.touches[0].clientX - touchStartX);
var dy = Math.abs(e.touches[0].clientY - touchStartY);
if (dx > 10 || dy > 10) {
isSwiping = true;
}
}
}, { passive: true });
// Делегирование клика на карточку (работает и для оригиналов, и для клонированных квадрантов)
stage.addEventListener('click', function (e) {
if (isSwiping) return;
var card = e.target.closest('.vb-canvas-polaroid__card');
if (!card) return;
var img = card.querySelector('img');
if (!img) return;
openLightbox(img.currentSrc || img.src, img.alt);
});
function onMouseMove(e) {
if (isTouch || isLightboxOpen) return;
var rect = root.getBoundingClientRect();
if (rect.bottom < 0 || rect.top > window.innerHeight) {
targetSpeedX = 0;
targetSpeedY = 0;
return;
}
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
var nx = (e.clientX - cx) / (rect.width / 2);
var ny = (e.clientY - cy) / (rect.height / 2);
nx = Math.max(-1, Math.min(1, nx));
ny = Math.max(-1, Math.min(1, ny));
if (Math.abs(nx) < DEAD_ZONE) {
targetSpeedX = 0;
} else {
var signX = nx > 0 ? 1 : -1;
var factorX = (Math.abs(nx) - DEAD_ZONE) / (1 - DEAD_ZONE);
targetSpeedX = -signX * factorX * MAX_SPEED;
}
if (Math.abs(ny) < DEAD_ZONE) {
targetSpeedY = 0;
} else {
var signY = ny > 0 ? 1 : -1;
var factorY = (Math.abs(ny) - DEAD_ZONE) / (1 - DEAD_ZONE);
targetSpeedY = -signY * factorY * MAX_SPEED;
}
}
function onMouseLeave() {
if (isTouch) return;
targetSpeedX = 0;
targetSpeedY = 0;
}
/* Зацикливание координат */
function wrap() {
if (quadW <= 0 || quadH <= 0) return;
if (posX < -quadW) posX += quadW;
else if (posX > 0) posX -= quadW;
if (posY < -quadH) posY += quadH;
else if (posY > 0) posY -= quadH;
}
function render() {
stage.style.transform = 'translate3d(' + posX.toFixed(2) + 'px, ' + posY.toFixed(2) + 'px, 0)';
}
/* Физический РАФ-цикл */
function tick() {
if (!isLightboxOpen) {
if (isTouch) {
autoTime += 0.005;
posX += -1.5 * Math.cos(autoTime * 0.7);
posY += -1.1 * Math.sin(autoTime * 0.9);
} else {
currentSpeedX += (targetSpeedX - currentSpeedX) * LERP_FACTOR;
currentSpeedY += (targetSpeedY - currentSpeedY) * LERP_FACTOR;
posX += currentSpeedX;
posY += currentSpeedY;
}
wrap();
render();
}
requestAnimationFrame(tick);
}
root.addEventListener('mousemove', onMouseMove);
root.addEventListener('mouseleave', onMouseLeave);
window.addEventListener('resize', function () {
isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
measure();
});
render();
requestAnimationFrame(tick);
})();
</script> John Doe 01. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
John Doe 02. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
John Doe 03. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
John Doe 04. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
John Doe 05. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
John Doe 06. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
John Doe 07. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
<!DOCTYPE html>
<!--
VIBE BLOCK: Apple-Style Split Product Viewer (T1370)
Архитектура: Single-File Vibe Block
Финальная спецификация с ультраплавной кинематикой Apple:
- Светлая палитра (#f5f5f7 / #e8e8ed / #1d1d1f)
- Полное отсутствие теней (Flat Swiss Minimalist): zero box-shadows
- На всю ширину экрана с отступом ровно 40px по краям
- Названия кнопок: строго John Doe 01..07
- Текст раскрытия: строго John Doe + Lorem Ipsum ровно в 2 строчки
- Ультраплавная трансформация кнопок в плашку: бесшовное физическое расширение (width, height, border-radius) по фирменной кинематической кривой Apple cubic-bezier(0.16, 1, 0.3, 1)
- Стрелочки навигации ^ и v: выровнены строго вертикально по центру слева от списка
- Правая часть: фото-зона на 50% экрана со сверхплавной кинематографической сменой кадров (Crossfade + Micro-Zoom)
- Кнопка закрытия (×) в правом верхнем углу картинки
- Полная мобильная адаптация и тач-свайпы
- 100% чистый Vanilla JavaScript и CSS3, без сторонних библиотек
-->
<style>
/* ══════════════════════════════════════════════════
КОНТЕЙНЕР НА ВСЮ ШИРИНУ ЭКРАНА (СВЕТЛЫЙ ФОН, 40px ОТСТУП)
══════════════════════════════════════════════════ */
.vb-split-viewer {
box-sizing: border-box;
position: relative;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 40px !important;
background-color: var(--uc-color-bg, #f5f5f7);
color: var(--uc-color-text, #1d1d1f);
font-family: var(--t-text-font, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif);
overflow: hidden;
user-select: none;
-webkit-user-select: none;
}
.vb-split-viewer *,
.vb-split-viewer *::before,
.vb-split-viewer *::after {
box-sizing: border-box;
}
/* Сетка 50 / 50 на всю ширину экрана */
.vb-split-viewer__grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 48px;
align-items: center;
width: 100%;
min-height: calc(100vh - 80px);
}
/* ══════════════════════════════════════════════════
ЛЕВАЯ КОЛОНКА: СТРЕЛКИ ПО ЦЕНТРУ + ТАБЫ
══════════════════════════════════════════════════ */
.vb-split-viewer__left {
display: flex;
flex-direction: row;
align-items: center; /* Стрелки вертикально по центру */
gap: 20px;
width: 100%;
min-width: 0;
}
/* ══════════════════════════════════════════════════
СТРЕЛКИ НАВИГАЦИИ ^ И v (ПО ЦЕНТРУ СЛЕВА, БЕЗ ТЕНЕЙ)
══════════════════════════════════════════════════ */
.vb-split-viewer__nav {
display: flex;
flex-direction: column;
align-self: center;
gap: 8px;
width: 38px;
flex-shrink: 0;
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: translateX(-12px);
transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
visibility 0.4s;
}
.vb-split-viewer.has-active .vb-split-viewer__nav {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: translateX(0);
}
.vb-split-viewer__arrow-btn {
width: 38px;
height: 38px;
border-radius: 50% !important;
display: flex;
align-items: center;
justify-content: center;
background: #e8e8ed !important;
border: none !important;
box-shadow: none !important;
color: #1d1d1f !important;
cursor: pointer;
transition: background-color 0.25s ease, transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.vb-split-viewer__arrow-btn:hover {
background: #dcdce2 !important;
}
.vb-split-viewer__arrow-btn:active {
transform: scale(0.92);
}
.vb-split-viewer__arrow-btn svg {
width: 16px;
height: 16px;
display: block;
stroke: currentColor;
stroke-width: 2.2;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
/* ══════════════════════════════════════════════════
СПИСОК ТАБОВ С КИНЕМАТИЧЕСКИМ МОРФИНГОМ
══════════════════════════════════════════════════ */
.vb-split-viewer__tabs {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 14px;
width: 100%;
min-width: 0;
}
/* Базовый таб: анимирует ширину, высоту и скругления */
.vb-split-viewer__tab {
position: relative;
width: auto;
border-radius: 9999px;
background: #e8e8ed;
color: #1d1d1f;
box-shadow: none !important;
cursor: pointer;
overflow: hidden;
will-change: width, height, border-radius;
transition: width 0.52s cubic-bezier(0.16, 1, 0.3, 1),
height 0.52s cubic-bezier(0.16, 1, 0.3, 1),
border-radius 0.48s cubic-bezier(0.16, 1, 0.3, 1),
background-color 0.3s ease;
}
.vb-split-viewer__tab:hover {
background: #dedee4;
}
/* 1. Свернутое состояние: кнопка-пилюля с плюсиком */
.vb-tab__pill {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 20px 10px 12px;
white-space: nowrap !important;
opacity: 1;
transform: translateY(0) scale(1);
transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.38s cubic-bezier(0.16, 1, 0.3, 1);
}
.vb-tab__icon {
flex-shrink: 0;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 1.5px solid #1d1d1f;
color: #1d1d1f;
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.vb-tab__icon svg {
width: 10px;
height: 10px;
display: block;
stroke: currentColor;
stroke-width: 2.2;
fill: none;
stroke-linecap: round;
}
.vb-tab__title {
font-size: 15px;
font-weight: 500;
letter-spacing: -0.01em;
color: #1d1d1f;
white-space: nowrap !important;
}
/* 2. Раскрытое состояние: текстовая плашка */
.vb-tab__card {
position: absolute;
top: 0;
left: 0;
width: 100%;
box-sizing: border-box;
padding: 22px 26px;
opacity: 0;
pointer-events: none;
transform: translateY(8px);
transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.vb-split-viewer__card-text {
margin: 0;
font-size: 15px;
line-height: 1.52;
color: #1d1d1f;
}
.vb-split-viewer__card-text strong {
font-weight: 600;
color: #1d1d1f;
}
/* АКТИВНОЕ СОСТОЯНИЕ ТАБА */
.vb-split-viewer__tab.is-active {
border-radius: 22px !important;
background: #e8e8ed !important;
}
.vb-split-viewer__tab.is-active .vb-tab__pill {
opacity: 0;
transform: translateY(-8px) scale(0.96);
pointer-events: none;
}
.vb-split-viewer__tab.is-active .vb-tab__card {
opacity: 1;
pointer-events: auto;
transform: translateY(0);
transition-delay: 0.12s; /* Текст плавно проявляется во время расширения плашки */
}
/* ══════════════════════════════════════════════════
ПРАВАЯ КОЛОНКА: МЕДИА-ЗОНА (50% ЭКРАНА, БЕЗ ТЕНЕЙ)
══════════════════════════════════════════════════ */
.vb-split-viewer__media {
position: relative;
width: 100%;
aspect-ratio: 16 / 11;
min-height: 480px;
max-height: 720px;
background: #e8e8ed;
border-radius: 24px;
overflow: hidden;
box-shadow: none !important;
cursor: grab;
touch-action: pan-y;
}
.vb-split-viewer__media:active {
cursor: grabbing;
}
/* Кнопка закрыть (×) */
.vb-split-viewer__close-btn {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
width: 38px;
height: 38px;
border-radius: 50% !important;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.92) !important;
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border: none !important;
box-shadow: none !important;
color: #1d1d1f !important;
cursor: pointer;
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: scale(0.85);
transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
background-color 0.2s ease,
visibility 0.35s;
}
.vb-split-viewer.has-active .vb-split-viewer__close-btn {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: scale(1);
}
.vb-split-viewer__close-btn:hover {
background: #ffffff !important;
transform: scale(1.06);
}
.vb-split-viewer__close-btn:active {
transform: scale(0.94);
}
.vb-split-viewer__close-btn svg {
width: 14px;
height: 14px;
display: block;
stroke: currentColor;
stroke-width: 2.4;
fill: none;
stroke-linecap: round;
}
/* Слайды с изображениями (Ультраплавный Crossfade + Micro-Zoom) */
.vb-split-viewer__slide {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: scale(1.04);
filter: blur(4px);
transition: opacity 0.65s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.75s cubic-bezier(0.16, 1, 0.3, 1),
filter 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.vb-split-viewer__slide.is-active {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: scale(1);
filter: blur(0);
z-index: 2;
}
.vb-split-viewer__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* ══════════════════════════════════════════════════
АДАПТИВНОСТЬ ПОД ПЛАНШЕТЫ И СМАРТФОНЫ
══════════════════════════════════════════════════ */
@media (max-width: 1024px) {
.vb-split-viewer {
padding: 30px 20px !important;
}
.vb-split-viewer__grid {
grid-template-columns: 1fr;
gap: 20px;
min-height: auto;
}
.vb-split-viewer__media {
order: -1;
min-height: 280px;
max-height: 480px;
aspect-ratio: 16 / 10;
border-radius: 22px;
}
.vb-split-viewer__left {
flex-direction: column;
align-items: stretch;
gap: 12px;
width: 100%;
}
/* На мобильных стрелки переходят в горизонтальный бар под фото справа */
.vb-split-viewer__nav {
flex-direction: row;
align-self: flex-end;
width: auto;
gap: 8px;
margin: 0;
max-height: 0;
overflow: hidden;
transform: translateY(-6px);
transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
max-height 0.35s ease,
margin 0.35s ease,
visibility 0.35s;
}
.vb-split-viewer.has-active .vb-split-viewer__nav {
max-height: 46px;
margin-bottom: 2px;
transform: translateY(0);
}
/* В горизонтальном баре стрелки превращаются в < и > */
.vb-split-viewer__arrow-up svg {
transform: rotate(-90deg);
}
.vb-split-viewer__arrow-down svg {
transform: rotate(-90deg);
}
.vb-split-viewer__tabs {
width: 100%;
align-items: flex-start;
gap: 12px;
}
.vb-tab__card {
padding: 18px 22px;
}
}
@media (max-width: 640px) {
.vb-split-viewer {
padding: 20px 16px !important;
}
.vb-split-viewer__media {
min-height: 240px;
border-radius: 18px;
}
.vb-tab__pill {
white-space: nowrap !important;
padding: 9px 16px 9px 12px;
gap: 8px;
}
.vb-tab__icon {
width: 22px;
height: 22px;
}
.vb-tab__title {
font-size: 14px;
white-space: nowrap !important;
}
.vb-tab__card {
padding: 16px 18px;
}
.vb-split-viewer__card-text {
font-size: 14px;
line-height: 1.46;
}
}
</style>
<section class="vb-block vb-split-viewer" aria-label="Product Showcase">
<div class="vb-split-viewer__grid">
<!-- ЛЕВАЯ КОЛОНКА: СТРЕЛКИ СЛЕВА ПО ЦЕНТРУ + ТАБЫ-ПИЛЮЛИ -->
<div class="vb-split-viewer__left">
<!-- СТРЕЛКИ НАВИГАЦИИ ^ И v СЛЕВА ПО ЦЕНТРУ -->
<div class="vb-split-viewer__nav" aria-label="Навигация по пунктам">
<button class="vb-split-viewer__arrow-btn vb-split-viewer__arrow-up" type="button" aria-label="Предыдущий пункт">
<svg viewBox="0 0 24 24"><path d="M18 15l-6-6-6 6"/></svg>
</button>
<button class="vb-split-viewer__arrow-btn vb-split-viewer__arrow-down" type="button" aria-label="Следующий пункт">
<svg viewBox="0 0 24 24"><path d="M6 9l6 6 6-6"/></svg>
</button>
</div>
<!-- СПИСОК ВСЕХ 7 ТАБОВ: СТРОГО JOHN DOE + LOREM IPSUM В 2 СТРОКИ -->
<div class="vb-split-viewer__tabs" role="tablist">
<!-- Таб 0 -->
<article class="vb-split-viewer__tab" data-index="0">
<div class="vb-tab__pill">
<span class="vb-tab__icon" aria-hidden="true">
<svg viewBox="0 0 10 10"><path d="M5 1v8M1 5h8"/></svg>
</span>
<span class="vb-tab__title">John Doe 01</span>
</div>
<div class="vb-tab__card">
<p class="vb-split-viewer__card-text">
<strong>John Doe 01.</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</article>
<!-- Таб 1 -->
<article class="vb-split-viewer__tab" data-index="1">
<div class="vb-tab__pill">
<span class="vb-tab__icon" aria-hidden="true">
<svg viewBox="0 0 10 10"><path d="M5 1v8M1 5h8"/></svg>
</span>
<span class="vb-tab__title">John Doe 02</span>
</div>
<div class="vb-tab__card">
<p class="vb-split-viewer__card-text">
<strong>John Doe 02.</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</article>
<!-- Таб 2 -->
<article class="vb-split-viewer__tab" data-index="2">
<div class="vb-tab__pill">
<span class="vb-tab__icon" aria-hidden="true">
<svg viewBox="0 0 10 10"><path d="M5 1v8M1 5h8"/></svg>
</span>
<span class="vb-tab__title">John Doe 03</span>
</div>
<div class="vb-tab__card">
<p class="vb-split-viewer__card-text">
<strong>John Doe 03.</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</article>
<!-- Таб 3 -->
<article class="vb-split-viewer__tab" data-index="3">
<div class="vb-tab__pill">
<span class="vb-tab__icon" aria-hidden="true">
<svg viewBox="0 0 10 10"><path d="M5 1v8M1 5h8"/></svg>
</span>
<span class="vb-tab__title">John Doe 04</span>
</div>
<div class="vb-tab__card">
<p class="vb-split-viewer__card-text">
<strong>John Doe 04.</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</article>
<!-- Таб 4 -->
<article class="vb-split-viewer__tab" data-index="4">
<div class="vb-tab__pill">
<span class="vb-tab__icon" aria-hidden="true">
<svg viewBox="0 0 10 10"><path d="M5 1v8M1 5h8"/></svg>
</span>
<span class="vb-tab__title">John Doe 05</span>
</div>
<div class="vb-tab__card">
<p class="vb-split-viewer__card-text">
<strong>John Doe 05.</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</article>
<!-- Таб 5 -->
<article class="vb-split-viewer__tab" data-index="5">
<div class="vb-tab__pill">
<span class="vb-tab__icon" aria-hidden="true">
<svg viewBox="0 0 10 10"><path d="M5 1v8M1 5h8"/></svg>
</span>
<span class="vb-tab__title">John Doe 06</span>
</div>
<div class="vb-tab__card">
<p class="vb-split-viewer__card-text">
<strong>John Doe 06.</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</article>
<!-- Таб 6 -->
<article class="vb-split-viewer__tab" data-index="6">
<div class="vb-tab__pill">
<span class="vb-tab__icon" aria-hidden="true">
<svg viewBox="0 0 10 10"><path d="M5 1v8M1 5h8"/></svg>
</span>
<span class="vb-tab__title">John Doe 07</span>
</div>
<div class="vb-tab__card">
<p class="vb-split-viewer__card-text">
<strong>John Doe 07.</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</div>
</article>
</div>
</div>
<!-- ПРАВАЯ КОЛОНКА: ФОТО-ЗОНА (50% ЭКРАНА, БЕЗ ТЕНЕЙ) И КНОПКА (×) -->
<div class="vb-split-viewer__media" aria-label="Media Showcase">
<!-- КНОПКА ЗАКРЫТЬ (×) -->
<button class="vb-split-viewer__close-btn" type="button" aria-label="Закрыть режим">
<svg viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
</button>
<!-- Исходное изображение (когда всё закрыто) -->
<figure class="vb-split-viewer__slide is-active" data-index="-1">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="Overview"
loading="eager">
</figure>
<!-- Слайд 0 -->
<figure class="vb-split-viewer__slide" data-index="0">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="John Doe 01"
loading="lazy">
</figure>
<!-- Слайд 1 -->
<figure class="vb-split-viewer__slide" data-index="1">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="John Doe 02"
loading="lazy">
</figure>
<!-- Слайд 2 -->
<figure class="vb-split-viewer__slide" data-index="2">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="John Doe 03"
loading="lazy">
</figure>
<!-- Слайд 3 -->
<figure class="vb-split-viewer__slide" data-index="3">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="John Doe 04"
loading="lazy">
</figure>
<!-- Слайд 4 -->
<figure class="vb-split-viewer__slide" data-index="4">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="John Doe 05"
loading="lazy">
</figure>
<!-- Слайд 5 -->
<figure class="vb-split-viewer__slide" data-index="5">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="John Doe 06"
loading="lazy">
</figure>
<!-- Слайд 6 -->
<figure class="vb-split-viewer__slide" data-index="6">
<img class="vb-split-viewer__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg"
alt="John Doe 07"
loading="lazy">
</figure>
</div>
</div>
</section>
<script>
(function () {
'use strict';
// 1. Поиск изолированного корня через currentScript
var root = (function () {
var s = document.currentScript;
if (s) {
var el = s.parentElement;
while (el) {
if (el.classList && el.classList.contains('vb-split-viewer')) return el;
el = el.parentElement;
}
}
return document.querySelector('.vb-split-viewer');
}());
if (!root || root.dataset.initialized) return;
root.dataset.initialized = 'true';
// 2. DOM Элементы
var tabs = Array.prototype.slice.call(root.querySelectorAll('.vb-split-viewer__tab'));
var slides = Array.prototype.slice.call(root.querySelectorAll('.vb-split-viewer__slide'));
var closeBtn = root.querySelector('.vb-split-viewer__close-btn');
var upBtn = root.querySelector('.vb-split-viewer__arrow-up');
var downBtn = root.querySelector('.vb-split-viewer__arrow-down');
var mediaContainer = root.querySelector('.vb-split-viewer__media');
var activeIndex = -1; // -1: всё закрыто (исходное состояние)
var total = tabs.length;
var tabsContainer = root.querySelector('.vb-split-viewer__tabs');
// 3. Точный расчет физических размеров кнопок для гладкой интерполяции ширины и высоты
function syncDimensions() {
var containerWidth = (tabsContainer && tabsContainer.clientWidth > 40)
? tabsContainer.clientWidth
: (window.innerWidth <= 1024 ? (window.innerWidth - 32) : Math.round(window.innerWidth * 0.45));
tabs.forEach(function (tab) {
var pill = tab.querySelector('.vb-tab__pill');
var card = tab.querySelector('.vb-tab__card');
if (pill) {
tab.dataset.pillWidth = (pill.offsetWidth + 2) + 'px';
tab.dataset.pillHeight = pill.offsetHeight + 'px';
}
if (card) {
// Замеряем высоту карточки строго при фактической рабочей ширине контейнера
var prevPos = card.style.position;
var prevVis = card.style.visibility;
var prevW = card.style.width;
var prevDisp = card.style.display;
card.style.position = 'absolute';
card.style.visibility = 'hidden';
card.style.display = 'block';
card.style.width = containerWidth + 'px';
tab.dataset.cardHeight = (card.offsetHeight) + 'px';
card.style.position = prevPos;
card.style.visibility = prevVis;
card.style.width = prevW;
card.style.display = prevDisp;
}
});
}
// Первичный расчет геометрии
syncDimensions();
setTimeout(syncDimensions, 60);
// Задаем исходную компактную форму всем кнопкам
function applyPillStyles() {
tabs.forEach(function (tab, i) {
if (i !== activeIndex) {
tab.style.width = tab.dataset.pillWidth || 'fit-content';
tab.style.height = tab.dataset.pillHeight || '44px';
}
});
}
applyPillStyles();
// Пересчет при ресайзе окна
var resizeTimer;
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
syncDimensions();
if (activeIndex === -1) {
applyPillStyles();
} else {
tabs.forEach(function (tab, i) {
if (i === activeIndex) {
tab.style.width = '100%';
tab.style.height = tab.dataset.cardHeight || 'auto';
} else {
tab.style.width = tab.dataset.pillWidth || 'fit-content';
tab.style.height = tab.dataset.pillHeight || '44px';
}
});
}
}, 100);
});
// 4. Функция переключения активного состояния с ультраплавной анимацией
function setActive(index) {
activeIndex = index;
if (activeIndex === -1) {
// ИСХОДНОЕ СОСТОЯНИЕ: все табы плавно сжимаются в пилюли (+), стрелки и (×) скрываются
root.classList.remove('has-active');
tabs.forEach(function (tab) {
tab.classList.remove('is-active');
tab.style.width = tab.dataset.pillWidth || 'fit-content';
tab.style.height = tab.dataset.pillHeight || '44px';
});
// Дефолтный слайд (-1)
slides.forEach(function (slide) {
if (slide.dataset.index === '-1') {
slide.classList.add('is-active');
} else {
slide.classList.remove('is-active');
}
});
} else {
// АКТИВНОЕ СОСТОЯНИЕ: выбранная кнопка плавно физически расширяется в плашку
root.classList.add('has-active');
tabs.forEach(function (tab, i) {
if (i === activeIndex) {
tab.classList.add('is-active');
// Плавное расширение в 100% ширины и высоту карточки
tab.style.width = '100%';
tab.style.height = tab.dataset.cardHeight || '86px';
} else {
tab.classList.remove('is-active');
// Плавное сжатие в компактную пилюлю
tab.style.width = tab.dataset.pillWidth || 'fit-content';
tab.style.height = tab.dataset.pillHeight || '44px';
}
});
// Смена слайда справа
slides.forEach(function (slide) {
if (parseInt(slide.dataset.index, 10) === activeIndex) {
slide.classList.add('is-active');
} else {
slide.classList.remove('is-active');
}
});
}
}
// 5. Клик по кнопкам-пилюлям
tabs.forEach(function (tab, i) {
tab.addEventListener('click', function (e) {
e.preventDefault();
// Клик по уже открытой плашке плавно сворачивает её обратно
if (activeIndex === i) {
setActive(-1);
} else {
setActive(i);
}
});
});
// 6. Клик по кнопке ЗАКРЫТЬ (×) в правой части
if (closeBtn) {
closeBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
setActive(-1);
});
}
// 7. Стрелочки навигации ^ (Вверх / предыдущий) и v (Вниз / следующий)
if (upBtn) {
upBtn.addEventListener('click', function (e) {
e.preventDefault();
if (activeIndex === -1) {
setActive(0);
} else {
var prev = (activeIndex - 1 + total) % total;
setActive(prev);
}
});
}
if (downBtn) {
downBtn.addEventListener('click', function (e) {
e.preventDefault();
if (activeIndex === -1) {
setActive(0);
} else {
var next = (activeIndex + 1) % total;
setActive(next);
}
});
}
// 8. Тач-свайпы на мобильных устройствах
if (mediaContainer) {
var touchStartX = 0;
var touchStartY = 0;
mediaContainer.addEventListener('touchstart', function (e) {
if (e.touches && e.touches.length > 0) {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}
}, { passive: true });
mediaContainer.addEventListener('touchend', function (e) {
if (e.changedTouches && e.changedTouches.length > 0) {
var diffX = e.changedTouches[0].clientX - touchStartX;
var diffY = e.changedTouches[0].clientY - touchStartY;
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 40) {
if (diffX < 0) {
var next = (activeIndex === -1 || activeIndex >= total - 1) ? 0 : activeIndex + 1;
setActive(next);
} else {
var prev = (activeIndex <= 0) ? total - 1 : activeIndex - 1;
setActive(prev);
}
}
}
}, { passive: true });
}
// 9. Клавиатурная навигация: стрелки Вверх / Вниз и Escape для закрытия
window.addEventListener('keydown', function (e) {
if (!root.matches(':hover') && !root.contains(document.activeElement)) return;
if (e.key === 'Escape') {
setActive(-1);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
var prev = (activeIndex <= 0) ? total - 1 : activeIndex - 1;
setActive(prev);
} else if (e.key === 'ArrowDown') {
e.preventDefault();
var next = (activeIndex === -1 || activeIndex >= total - 1) ? 0 : activeIndex + 1;
setActive(next);
}
});
})();
</script> <!DOCTYPE html>
<!--
VIBE BLOCK: On-Scroll 3D Carousel & Case Study Reveal (Формат 9:16 / Vertical Mobile)
Архитектура: Single-File Vibe Block (T1370)
Формат карточек: Строгий вертикальный 9:16 (Stories / Reels / Mobile Mockups) и в скрытом, и в раскрытом виде
Особенности:
- Нейтральные унифицированные классы (.vb-scroll-carousel, .vb-preview-grid)
- 3D-цилиндр вращается от скролла страницы и ручного drag-перетаскивания
- Пропорция 9:16 сохраняется во всех состояниях и на всех брейкпоинтах
- Полноэкранный 3D Reveal («взрыв» карточек в сетку) при клике на любую карточку
- Закрытие по кнопке, клику на фон или клавише Escape
- Нулевые скругления (0px, Flat Swiss Minimal), 1px рамка
- Совместимость с дизайн-токенами Тильды
-->
<style>
.vb-scroll-carousel {
box-sizing: border-box;
position: relative;
width: 100%;
margin: 0 auto;
padding: 40px 0;
background-color: var(--uc-color-bg, #0d0d11);
color: var(--uc-color-text, #ffffff);
font-family: var(--t-text-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
overflow: hidden;
user-select: none;
-webkit-user-select: none;
}
.vb-scroll-carousel *,
.vb-scroll-carousel *::before,
.vb-scroll-carousel *::after {
box-sizing: border-box;
}
/* ══════════════════════════════════════════════════
3D ВЬЮПОРТ КАРОСЕЛИ (ВЫСОТА ПОД КАРТОЧКИ 9:16 С ЗАПАСОМ)
══════════════════════════════════════════════════ */
.vb-scroll-carousel__viewport {
position: relative;
width: 100%;
height: 520px;
display: flex;
align-items: center;
justify-content: center;
perspective: 1600px;
perspective-origin: 50% 50%;
transform-style: preserve-3d;
cursor: grab;
touch-action: pan-y;
}
.vb-scroll-carousel__viewport:active {
cursor: grabbing;
}
.vb-scroll-carousel__stage {
position: relative;
width: 0;
height: 0;
transform-style: preserve-3d;
will-change: transform;
transition: transform 0.12s ease-out;
}
/* ══════════════════════════════════════════════════
КАРТОЧКА В КАРОСЕЛИ: СТРОГИЙ ВЕРТИКАЛЬНЫЙ ФОРМАТ 9:16
══════════════════════════════════════════════════ */
.vb-scroll-carousel__card {
position: absolute;
top: 50%;
left: 50%;
width: 234px;
height: 416px;
aspect-ratio: 9 / 16;
margin-left: -117px;
margin-top: -208px;
transform-style: preserve-3d;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
border-radius: 0 !important;
overflow: hidden;
cursor: pointer;
background: #14151e;
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.65);
outline: 1px solid rgba(255, 255, 255, 0.15);
transition: outline 0.25s ease, filter 0.3s ease, transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.vb-scroll-carousel__card:hover {
outline: 1px solid #ffffff !important;
filter: brightness(1.15) !important;
}
.vb-scroll-carousel__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: 0 !important;
pointer-events: none;
transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.vb-scroll-carousel__card:hover .vb-scroll-carousel__img {
transform: scale(1.04);
}
.vb-scroll-carousel__info {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0,0,0,0) 60%, rgba(0,0,0,0.88) 100%);
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 20px 18px;
pointer-events: none;
border-radius: 0 !important;
}
.vb-scroll-carousel__num {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
color: rgba(255, 255, 255, 0.65);
text-transform: uppercase;
}
.vb-scroll-carousel__name {
margin: 4px 0 0;
font-size: 16px;
font-weight: 500;
letter-spacing: -0.01em;
color: #ffffff;
}
/* ══════════════════════════════════════════════════
РАСКРЫТЫЙ ВИД: ПОЛНОЭКРАННАЯ 3D СЕТКА 9:16 (THE 3D REVEAL)
Стиль: Flat Swiss Minimal (0px скругления, 1px рамки)
══════════════════════════════════════════════════ */
.vb-preview-grid {
position: fixed;
inset: 0;
z-index: 99999;
background: rgba(9, 10, 14, 0.96);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
overflow-y: auto;
overflow-x: hidden;
opacity: 0;
pointer-events: none;
perspective: 1600px;
perspective-origin: 50% 50%;
transition: opacity 0.45s cubic-bezier(0.22, 1, 0.36, 1);
padding: 40px 24px 60px;
border-radius: 0 !important;
}
.vb-preview-grid.is-open {
opacity: 1;
pointer-events: auto;
}
.vb-preview-grid__header {
max-width: 1160px;
margin: 0 auto 36px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
padding-bottom: 18px;
}
.vb-preview-grid__title {
margin: 0;
font-size: clamp(20px, 3vw, 32px);
font-weight: 500;
text-transform: uppercase;
letter-spacing: -0.01em;
color: #ffffff;
}
.vb-preview-grid__close {
display: inline-flex !important;
align-items: center !important;
gap: 8px !important;
padding: 10px 22px !important;
border-radius: 0 !important;
background: transparent !important;
border: 1px solid rgba(255, 255, 255, 0.35) !important;
color: #ffffff !important;
font-size: 12px !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 0.1em !important;
cursor: pointer !important;
transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease !important;
}
.vb-preview-grid__close:hover {
background: #ffffff !important;
color: #000000 !important;
border-color: #ffffff !important;
}
/* Сетка карточек 9:16 */
.vb-preview-grid__list {
max-width: 1160px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 28px;
transform-style: preserve-3d;
}
.vb-preview-grid__item {
position: relative;
border-radius: 0 !important;
overflow: hidden;
background: #14151e;
aspect-ratio: 9 / 16 !important;
width: 100%;
transform-style: preserve-3d;
outline: 1px solid rgba(255, 255, 255, 0.15);
will-change: transform, opacity;
transition: outline 0.25s ease;
}
.vb-preview-grid__item:hover {
outline: 1px solid #ffffff;
}
.vb-preview-grid__item-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: 0 !important;
transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.vb-preview-grid__item:hover .vb-preview-grid__item-img {
transform: scale(1.04);
}
.vb-preview-grid__item-caption {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0,0,0,0) 65%, rgba(0,0,0,0.92) 100%);
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 24px 20px;
pointer-events: none;
}
.vb-preview-grid__item-caption h3 {
margin: 0;
font-size: 18px;
font-weight: 500;
color: #ffffff;
}
.vb-preview-grid__item-caption p {
margin: 4px 0 0;
font-size: 13px;
color: rgba(255, 255, 255, 0.65);
}
/* ══════════════════════════════════════════════════
АДАПТИВНОСТЬ (ПЛАНШЕТЫ И СМАРТФОНЫ)
══════════════════════════════════════════════════ */
@media (max-width: 960px) {
.vb-scroll-carousel__viewport {
height: 420px;
}
.vb-scroll-carousel__card {
width: 180px;
height: 320px;
margin-left: -90px;
margin-top: -160px;
}
.vb-preview-grid__list {
grid-template-columns: repeat(2, 1fr);
max-width: 720px;
gap: 20px;
}
.vb-preview-grid {
padding: 30px 20px 50px;
}
}
@media (max-width: 580px) {
.vb-scroll-carousel {
padding: 24px 0;
}
.vb-scroll-carousel__viewport {
height: 350px;
}
.vb-scroll-carousel__card {
width: 144px;
height: 256px;
margin-left: -72px;
margin-top: -128px;
}
.vb-preview-grid__list {
grid-template-columns: 1fr;
max-width: 320px;
gap: 20px;
}
.vb-preview-grid {
padding: 20px 16px 40px;
}
.vb-preview-grid__header {
margin-bottom: 24px;
padding-bottom: 14px;
}
.vb-preview-grid__close {
padding: 8px 16px !important;
font-size: 11px !important;
}
}
</style>
<section class="vb-block vb-scroll-carousel" aria-label="3D Carousel 9:16">
<!-- Вьюпорт карусели: карточки формата 9:16 -->
<div class="vb-scroll-carousel__viewport">
<div class="vb-scroll-carousel__stage">
<!-- 6 карточек 9:16 со строгими углами 0px -->
<!-- Карточка 01 -->
<article class="vb-card vb-scroll-carousel__card" data-idx="0">
<img class="vb-card__img vb-scroll-carousel__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=01"
alt="Case 01" loading="lazy">
<div class="vb-card__info vb-scroll-carousel__info">
<span class="vb-scroll-carousel__num">01 / 06</span>
<h4 class="vb-card__title vb-scroll-carousel__name">Case Study 01</h4>
</div>
</article>
<!-- Карточка 02 -->
<article class="vb-card vb-scroll-carousel__card" data-idx="1">
<img class="vb-card__img vb-scroll-carousel__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=02"
alt="Case 02" loading="lazy">
<div class="vb-card__info vb-scroll-carousel__info">
<span class="vb-scroll-carousel__num">02 / 06</span>
<h4 class="vb-card__title vb-scroll-carousel__name">Case Study 02</h4>
</div>
</article>
<!-- Карточка 03 -->
<article class="vb-card vb-scroll-carousel__card" data-idx="2">
<img class="vb-card__img vb-scroll-carousel__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=03"
alt="Case 03" loading="lazy">
<div class="vb-card__info vb-scroll-carousel__info">
<span class="vb-scroll-carousel__num">03 / 06</span>
<h4 class="vb-card__title vb-scroll-carousel__name">Case Study 03</h4>
</div>
</article>
<!-- Карточка 04 -->
<article class="vb-card vb-scroll-carousel__card" data-idx="3">
<img class="vb-card__img vb-scroll-carousel__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=04"
alt="Case 04" loading="lazy">
<div class="vb-card__info vb-scroll-carousel__info">
<span class="vb-scroll-carousel__num">04 / 06</span>
<h4 class="vb-card__title vb-scroll-carousel__name">Case Study 04</h4>
</div>
</article>
<!-- Карточка 05 -->
<article class="vb-card vb-scroll-carousel__card" data-idx="4">
<img class="vb-card__img vb-scroll-carousel__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=05"
alt="Case 05" loading="lazy">
<div class="vb-card__info vb-scroll-carousel__info">
<span class="vb-scroll-carousel__num">05 / 06</span>
<h4 class="vb-card__title vb-scroll-carousel__name">Case Study 05</h4>
</div>
</article>
<!-- Карточка 06 -->
<article class="vb-card vb-scroll-carousel__card" data-idx="5">
<img class="vb-card__img vb-scroll-carousel__img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=06"
alt="Case 06" loading="lazy">
<div class="vb-card__info vb-scroll-carousel__info">
<span class="vb-scroll-carousel__num">06 / 06</span>
<h4 class="vb-card__title vb-scroll-carousel__name">Case Study 06</h4>
</div>
</article>
</div>
</div>
<!-- Раскрытый вид: полноэкранная 3D-сетка 9:16 при клике на карточку -->
<div class="vb-preview-grid" aria-modal="true" role="dialog" aria-hidden="true" aria-label="Сетка кейсов 9:16">
<header class="vb-preview-grid__header">
<h2 class="vb-preview-grid__title">Vertical Showcase</h2>
<button class="vb-preview-grid__close vb-btn" type="button" aria-label="Закрыть">
<span>Закрыть</span>
<span aria-hidden="true">✕</span>
</button>
</header>
<div class="vb-preview-grid__list">
<!-- Слот 01 в сетке -->
<div class="vb-preview-grid__item">
<img class="vb-preview-grid__item-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=01"
alt="Case Study 01">
<div class="vb-preview-grid__item-caption">
<h3>Case Study 01</h3>
<p>Mobile App & Social Reels Showcase</p>
</div>
</div>
<!-- Слот 02 в сетке -->
<div class="vb-preview-grid__item">
<img class="vb-preview-grid__item-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=02"
alt="Case Study 02">
<div class="vb-preview-grid__item-caption">
<h3>Case Study 02</h3>
<p>Mobile App & Social Reels Showcase</p>
</div>
</div>
<!-- Слот 03 в сетке -->
<div class="vb-preview-grid__item">
<img class="vb-preview-grid__item-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=03"
alt="Case Study 03">
<div class="vb-preview-grid__item-caption">
<h3>Case Study 03</h3>
<p>Mobile App & Social Reels Showcase</p>
</div>
</div>
<!-- Слот 04 в сетке -->
<div class="vb-preview-grid__item">
<img class="vb-preview-grid__item-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=04"
alt="Case Study 04">
<div class="vb-preview-grid__item-caption">
<h3>Case Study 04</h3>
<p>Mobile App & Social Reels Showcase</p>
</div>
</div>
<!-- Слот 05 в сетке -->
<div class="vb-preview-grid__item">
<img class="vb-preview-grid__item-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=05"
alt="Case Study 05">
<div class="vb-preview-grid__item-caption">
<h3>Case Study 05</h3>
<p>Mobile App & Social Reels Showcase</p>
</div>
</div>
<!-- Слот 06 в сетке -->
<div class="vb-preview-grid__item">
<img class="vb-preview-grid__item-img"
src="https://static.tildacdn.com/ffb6456b-781b-40e8-9517-ffb5225e8bcd/imgfish.jpg?slot=06"
alt="Case Study 06">
<div class="vb-preview-grid__item-caption">
<h3>Case Study 06</h3>
<p>Mobile App & Social Reels Showcase</p>
</div>
</div>
</div>
</div>
</section>
<script>
(function () {
'use strict';
/* Изолированный корень Vibe-блока */
var root = (function () {
var s = document.currentScript;
if (s) {
var el = s.parentElement;
while (el) {
if (el.classList && el.classList.contains('vb-scroll-carousel')) return el;
el = el.parentElement;
}
}
return document.querySelector('.vb-scroll-carousel');
}());
if (!root || root.dataset.initialized) return;
root.dataset.initialized = 'true';
var viewport = root.querySelector('.vb-scroll-carousel__viewport');
var stage = root.querySelector('.vb-scroll-carousel__stage');
var cards = Array.from(root.querySelectorAll('.vb-scroll-carousel__card'));
var preview = root.querySelector('.vb-preview-grid');
var previewClose = root.querySelector('.vb-preview-grid__close');
var previewItems = Array.from(root.querySelectorAll('.vb-preview-grid__item'));
var N = cards.length;
var angleStep = 360 / N;
/* Радиус 3D цилиндра, адаптированный под пропорции 9:16 */
function getRadius() {
if (window.innerWidth <= 480) return 250;
if (window.innerWidth <= 768) return 330;
if (window.innerWidth <= 960) return 380;
return 480;
}
/* Раскладка 3D цилиндра */
function positionCards() {
var rad = getRadius();
cards.forEach(function (card, i) {
var angle = i * angleStep;
card.style.transform = 'rotateY(' + angle + 'deg) translateZ(' + rad + 'px)';
});
}
/* ══════════════════════════════════════════════════
ON-SCROLL ТРИГГЕР: СТАРТУЕТ СРАЗУ, КАК ТОЛЬКО
НИЗ ЭКРАНА ДОХОДИТ ДО БЛОКА КАРТОЧЕК
══════════════════════════════════════════════════ */
var targetRotY = 0;
var currentRotY = 0;
var dragOffset = 0;
var scrollProgress = 0;
function onScroll() {
var rect = root.getBoundingClientRect();
var winH = window.innerHeight;
// Вход: rect.top == winH (верх блока коснулся низа экрана)
// Выход: rect.bottom == 0 (низ блока вышел за верх экрана)
var totalTravel = winH + rect.height;
var traveled = winH - rect.top;
var p = traveled / totalTravel;
scrollProgress = Math.max(0, Math.min(1, p));
targetRotY = scrollProgress * -200 + dragOffset;
}
/* LERP-цикл для плавной кинематики вращения */
function tick() {
currentRotY += (targetRotY - currentRotY) * 0.12;
var tiltX = (scrollProgress - 0.5) * 5;
var tiltZ = (scrollProgress - 0.5) * -3;
stage.style.transform =
'translateZ(-' + (getRadius() * 0.2) + 'px) ' +
'rotateX(' + tiltX.toFixed(2) + 'deg) ' +
'rotateY(' + currentRotY.toFixed(2) + 'deg) ' +
'rotateZ(' + tiltZ.toFixed(2) + 'deg)';
cards.forEach(function (card, i) {
var cardAngle = (i * angleStep + currentRotY) % 360;
var rad = (cardAngle * Math.PI) / 180;
var cosVal = Math.cos(rad);
var brightness = 0.78 + 0.42 * Math.max(0, cosVal);
card.style.filter = 'brightness(' + brightness.toFixed(2) + ')';
});
requestAnimationFrame(tick);
}
/* ══════════════════════════════════════════════════
ИНТЕРАКТИВНОЕ ПЕРЕТАСКИВАНИЕ (МЫШЬ / ТАЧ)
══════════════════════════════════════════════════ */
var isDragging = false;
var startX = 0;
var prevDragX = 0;
var hasMoved = false;
viewport.addEventListener('mousedown', function (e) {
isDragging = true;
hasMoved = false;
startX = e.clientX;
prevDragX = startX;
});
window.addEventListener('mousemove', function (e) {
if (!isDragging) return;
var dx = e.clientX - prevDragX;
if (Math.abs(e.clientX - startX) > 6) hasMoved = true;
prevDragX = e.clientX;
dragOffset += dx * 0.45;
targetRotY += dx * 0.45;
});
window.addEventListener('mouseup', function () {
isDragging = false;
});
viewport.addEventListener('touchstart', function (e) {
if (e.touches.length > 0) {
isDragging = true;
hasMoved = false;
startX = e.touches[0].clientX;
prevDragX = startX;
}
}, { passive: true });
window.addEventListener('touchmove', function (e) {
if (!isDragging || e.touches.length === 0) return;
var dx = e.touches[0].clientX - prevDragX;
if (Math.abs(e.touches[0].clientX - startX) > 6) hasMoved = true;
prevDragX = e.touches[0].clientX;
dragOffset += dx * 0.45;
targetRotY += dx * 0.45;
}, { passive: true });
window.addEventListener('touchend', function () {
isDragging = false;
});
/* ══════════════════════════════════════════════════
THE 3D EXPLOSION GRID TRANSITION (REVEAL КЕЙСА 9:16)
══════════════════════════════════════════════════ */
function openCaseStudy() {
if (hasMoved) return;
preview.classList.add('is-open');
preview.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
var cols = window.innerWidth <= 580 ? 1 : (window.innerWidth <= 960 ? 2 : 3);
previewItems.forEach(function (item, index) {
var col = index % cols;
var row = Math.floor(index / cols);
var dx = (col - (cols - 1) / 2) * 60;
var dy = (row - 0.5) * 45;
item.style.transition = 'none';
item.style.transform = 'translate3d(' + dx + 'px, ' + (dy + 70) + 'px, -1100px) rotateY(' + (dx > 0 ? 25 : (dx < 0 ? -25 : 0)) + 'deg) scale(0.65)';
item.style.opacity = '0';
void item.offsetHeight;
var delayMs = index * 50;
item.style.transition =
'transform 750ms cubic-bezier(0.22, 1, 0.36, 1) ' + delayMs + 'ms, ' +
'opacity 500ms cubic-bezier(0.22, 1, 0.36, 1) ' + delayMs + 'ms';
item.style.transform = 'translate3d(0, 0, 0) rotateY(0deg) scale(1)';
item.style.opacity = '1';
});
}
function closeCaseStudy() {
previewItems.forEach(function (item, index) {
var delayMs = (previewItems.length - 1 - index) * 25;
item.style.transition =
'transform 350ms cubic-bezier(0.76, 0, 0.24, 1) ' + delayMs + 'ms, ' +
'opacity 250ms ease ' + delayMs + 'ms';
item.style.transform = 'translate3d(0, 40px, -700px) scale(0.75)';
item.style.opacity = '0';
});
setTimeout(function () {
preview.classList.remove('is-open');
preview.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
}, 400);
}
cards.forEach(function (card) {
card.addEventListener('click', function () {
openCaseStudy();
});
});
if (previewClose) previewClose.addEventListener('click', closeCaseStudy);
// Закрытие по клику на фон
preview.addEventListener('click', function (e) {
if (e.target === preview) {
closeCaseStudy();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && preview.classList.contains('is-open')) {
closeCaseStudy();
}
});
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', function () {
positionCards();
onScroll();
});
// Инициализация
positionCards();
onScroll();
tick();
})();
</script>