Новые правки страницы - Возврат прав
This commit is contained in:
parent
f5669e196f
commit
24be657ac6
20 changed files with 3219 additions and 45 deletions
28
frontend/src/scripts/animations.ts
Normal file
28
frontend/src/scripts/animations.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
export function setupScrollAnimations(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: '0px 0px -50px 0px',
|
||||
threshold: 0.1
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const el = entry.target as HTMLElement;
|
||||
const delay = parseInt(el.dataset.delay || '0');
|
||||
|
||||
setTimeout(() => {
|
||||
el.classList.add('is-visible');
|
||||
}, delay);
|
||||
|
||||
observer.unobserve(el);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
document.querySelectorAll('.animate-on-scroll').forEach((el) => {
|
||||
observer.observe(el);
|
||||
});
|
||||
}
|
||||
74
frontend/src/scripts/tabs.ts
Normal file
74
frontend/src/scripts/tabs.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
export function setupTabs(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const tabButtons = document.querySelectorAll('.tab-btn');
|
||||
const tabPanels = document.querySelectorAll('.tab-panel');
|
||||
|
||||
tabButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tabId = btn.getAttribute('data-tab');
|
||||
|
||||
// Remove active from all buttons
|
||||
tabButtons.forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
|
||||
// Remove active from all panels
|
||||
tabPanels.forEach(panel => {
|
||||
panel.classList.remove('active');
|
||||
});
|
||||
|
||||
// Activate current tab
|
||||
btn.classList.add('active');
|
||||
btn.setAttribute('aria-selected', 'true');
|
||||
|
||||
const targetPanel = document.getElementById(`tab-${tabId}`);
|
||||
if (targetPanel) {
|
||||
targetPanel.classList.add('active');
|
||||
|
||||
// Animate counters if stats tab
|
||||
if (tabId === 'stats') {
|
||||
animateCounters(targetPanel);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function animateCounters(panel: HTMLElement): void {
|
||||
const counters = panel.querySelectorAll('[data-count]');
|
||||
|
||||
counters.forEach(counter => {
|
||||
const target = parseInt(counter.getAttribute('data-count') || '0');
|
||||
const suffix = counter.textContent?.replace(/[0-9]/g, '') || '';
|
||||
const duration = 1500;
|
||||
const startTime = performance.now();
|
||||
|
||||
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
|
||||
|
||||
const update = (currentTime: number) => {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
const easedProgress = easeOutCubic(progress);
|
||||
const currentValue = Math.round(easedProgress * target);
|
||||
|
||||
counter.textContent = `${currentValue}${suffix}`;
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
};
|
||||
|
||||
requestAnimationFrame(update);
|
||||
});
|
||||
|
||||
// Animate stat bars
|
||||
const statBars = panel.querySelectorAll('[data-width]');
|
||||
statBars.forEach(bar => {
|
||||
const width = bar.getAttribute('data-width');
|
||||
setTimeout(() => {
|
||||
(bar as HTMLElement).style.width = `${width}%`;
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue