Загрузка данных
/* =========================================
ФИЗИКА И ИНТЕРАКТИВ (Гравитация и Drag)
========================================= */
/* Класс для элементов, которые можно таскать */
.draggable {
cursor: grab;
z-index: 1; /* Чтобы перетаскиваемый элемент был поверх других */
}
.draggable:active {
cursor: grabbing;
z-index: 100;
}
/* Стили для падающих букв */
.physics-letter {
position: fixed; /* Фиксируем относительно экрана, чтобы они падали вниз */
z-index: 9999;
cursor: grab;
user-select: none; /* Чтобы текст не выделялся при таскании */
pointer-events: auto;
font-weight: bold;
text-shadow: 0 2px 5px rgba(0,0,0,0.5);
}
.physics-letter:active {
cursor: grabbing;
}
/* Класс-заглушка для разбитого заголовка */
.shattered-placeholder {
visibility: hidden; /* Скрываем оригинал, но оставляем его размер, чтобы сайт не схлопнулся */
}
// ==========================================
// 1. Drag & Drop для карточек (можно хватать и кидать)
// ==========================================
const makeDraggable = () => {
const draggables = document.querySelectorAll('.skill-card, .portfolio-item, .lab-item');
draggables.forEach(el => {
el.classList.add('draggable');
let isDragging = false;
let startX, startY;
let currentLeft = 0;
let currentTop = 0;
el.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
// Получаем текущие координаты
currentLeft = parseInt(window.getComputedStyle(el).left || 0, 10);
currentTop = parseInt(window.getComputedStyle(el).top || 0, 10);
// Включаем относительное позиционирование для перемещения
if (window.getComputedStyle(el).position === 'static') {
el.style.position = 'relative';
}
el.style.transition = 'none'; // Убираем плавность при перетаскивании
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
el.style.left = `${currentLeft + dx}px`;
el.style.top = `${currentTop + dy}px`;
});
window.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
el.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease'; // Возвращаем стили из CSS
}
});
});
};
// ==========================================
// 2. Механика разрушения (5 кликов) и Гравитация
// ==========================================
const initDestruction = () => {
// Ищем все заголовки
const headers = document.querySelectorAll('h1, h2');
headers.forEach(header => {
let clickCount = 0;
let clickTimer;
header.addEventListener('click', () => {
clickCount++;
clearTimeout(clickTimer);
if (clickCount >= 5) {
shatterElement(header);
clickCount = 0;
} else {
// Если кликаем медленно, счетчик сбрасывается через полсекунды
clickTimer = setTimeout(() => clickCount = 0, 500);
}
});
});
};
function shatterElement(element) {
if (element.classList.contains('shattered-placeholder')) return; // Уже сломан
const text = element.innerText;
const rect = element.getBoundingClientRect();
// Прячем оригинальный заголовок
element.classList.add('shattered-placeholder');
const letters = [];
const letterWidth = rect.width / text.length; // Примерная ширина буквы
// Создаем физические буквы
for (let i = 0; i < text.length; i++) {
if (text[i] === ' ') continue;
const span = document.createElement('span');
span.innerText = text[i];
span.className = 'physics-letter';
// Расставляем буквы точно на те места, где они были в слове
span.style.left = `${rect.left + (i * letterWidth)}px`;
span.style.top = `${rect.top}px`;
// Копируем стили шрифта у родителя
const computedStyle = window.getComputedStyle(element);
span.style.fontSize = computedStyle.fontSize;
span.style.color = computedStyle.color;
span.style.fontFamily = computedStyle.fontFamily;
document.body.appendChild(span);
// Добавляем физические параметры (скорость, позиция)
letters.push({
el: span,
x: parseFloat(span.style.left),
y: parseFloat(span.style.top),
vx: (Math.random() - 0.5) * 15, // Разлет в стороны (сильнее!)
vy: (Math.random() * -10) - 5, // Подскок вверх
isDragging: false
});
}
// --- ДВИЖОК ГРАВИТАЦИИ ---
let animationId;
const gravity = 0.8;
const bounce = 0.6; // Сила отскока
const ground = window.innerHeight - 50; // Уровень "пола" экрана
function updatePhysics() {
letters.forEach(letter => {
if (letter.isDragging) return; // Если тащим мышкой, гравитация не работает
letter.vy += gravity; // Падение
letter.x += letter.vx;
letter.y += letter.vy;
// Удар об пол
if (letter.y > ground) {
letter.y = ground;
letter.vy *= -bounce; // Отскок с потерей энергии
letter.vx *= 0.8; // Трение об пол
}
// Удар о стены
if (letter.x < 0 || letter.x > window.innerWidth - 30) {
letter.vx *= -0.8;
}
// Применяем координаты к элементу
letter.el.style.left = `${letter.x}px`;
letter.el.style.top = `${letter.y}px`;
});
animationId = requestAnimationFrame(updatePhysics);
}
updatePhysics(); // Запускаем цикл падения
// --- ЛОГИКА ВОССТАНОВЛЕНИЯ (Перетаскивание букв) ---
letters.forEach(letter => {
letter.el.addEventListener('mousedown', (e) => {
letter.isDragging = true;
let startX = e.clientX;
let startY = e.clientY;
const onMouseMove = (moveEvent) => {
const dx = moveEvent.clientX - startX;
const dy = moveEvent.clientY - startY;
letter.x += dx;
letter.y += dy;
letter.el.style.left = `${letter.x}px`;
letter.el.style.top = `${letter.y}px`;
startX = moveEvent.clientX;
startY = moveEvent.clientY;
};
const onMouseUp = () => {
letter.isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
// Проверяем: принесли ли мы букву обратно в "зону" оригинального заголовка
const origRect = element.getBoundingClientRect();
const triggerZone = 50; // Погрешность в пикселях
if (letter.x >= origRect.left - triggerZone && letter.x <= origRect.right + triggerZone &&
letter.y >= origRect.top - triggerZone && letter.y <= origRect.bottom + triggerZone) {
// ВОССТАНАВЛИВАЕМ САЙТ!
cancelAnimationFrame(animationId); // Останавливаем физику
letters.forEach(l => l.el.remove()); // Удаляем мусор с экрана
element.classList.remove('shattered-placeholder'); // Показываем оригинал
}
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
});
}
// Запускаем наши новые функции
makeDraggable();
initDestruction();