site/script.js
Rama main · réplica de lectura del repositorio original.
// ========================================
// ATHLETIC BRUTALISM INTERACTIONS
// ActaLog - Elite CrossFit Performance Tracker
// ========================================
document.addEventListener('DOMContentLoaded', () => {
// Counter Animation
animateCounters();
// Intersection Observer for scroll animations
setupScrollAnimations();
// Smooth scroll for anchor links
setupSmoothScroll();
});
// ========================================
// COUNTER ANIMATIONS
// ========================================
function animateCounters() {
const counters = document.querySelectorAll('[data-target]');
const animateValue = (element, start, end, duration, isDecimal = false) => {
const startTime = performance.now();
const step = (currentTime) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function (ease-out)
const easeOut = 1 - Math.pow(1 - progress, 3);
const current = start + (end - start) * easeOut;
if (isDecimal) {
element.textContent = current.toFixed(2);
} else {
element.textContent = Math.floor(current);
}
if (progress < 1) {
requestAnimationFrame(step);
} else {
if (isDecimal) {
element.textContent = end.toFixed(2);
} else {
element.textContent = end;
}
}
};
requestAnimationFrame(step);
};
// Trigger counters when they come into view
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !entry.target.dataset.animated) {
const target = parseFloat(entry.target.dataset.target);
const isDecimal = entry.target.dataset.target.includes('.');
const duration = 2000; // 2 seconds
animateValue(entry.target, 0, target, duration, isDecimal);
entry.target.dataset.animated = 'true';
}
});
}, {
threshold: 0.5
});
counters.forEach(counter => observer.observe(counter));
}
// ========================================
// SCROLL ANIMATIONS
// ========================================
function setupScrollAnimations() {
const elements = document.querySelectorAll('.feature-card, .tech-item, .stat-item');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
// Stagger the animation
setTimeout(() => {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}, index * 100);
}
});
}, {
threshold: 0.1
});
elements.forEach(element => {
// Set initial state
element.style.opacity = '0';
element.style.transform = 'translateY(30px)';
element.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
observer.observe(element);
});
}
// ========================================
// SMOOTH SCROLL
// ========================================
function setupSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const href = this.getAttribute('href');
// Skip empty anchors
if (href === '#') return;
e.preventDefault();
const target = document.querySelector(href);
if (target) {
const offsetTop = target.offsetTop - 80; // Account for fixed nav
window.scrollTo({
top: offsetTop,
behavior: 'smooth'
});
}
});
});
}
// ========================================
// BUTTON RIPPLE EFFECT
// ========================================
document.querySelectorAll('.btn').forEach(button => {
button.addEventListener('click', function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const ripple = document.createElement('span');
ripple.className = 'ripple';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
this.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
});
});
// ========================================
// PARALLAX EFFECT ON HERO
// ========================================
let lastScrollY = window.scrollY;
window.addEventListener('scroll', () => {
const scrollY = window.scrollY;
const mockup = document.querySelector('.mockup-container');
if (mockup) {
// Parallax effect: move slower than scroll
const parallaxSpeed = 0.5;
const translateY = scrollY * parallaxSpeed;
mockup.style.transform = `translateY(${translateY}px)`;
}
lastScrollY = scrollY;
}, { passive: true });
// ========================================
// NAVIGATION BACKGROUND ON SCROLL
// ========================================
window.addEventListener('scroll', () => {
const nav = document.querySelector('.nav');
if (window.scrollY > 50) {
nav.style.background = 'rgba(10, 22, 40, 0.98)';
} else {
nav.style.background = 'rgba(10, 22, 40, 0.95)';
}
}, { passive: true });
// ========================================
// CODE BLOCK COPY FUNCTIONALITY (if needed)
// ========================================
document.querySelectorAll('.code-block').forEach(block => {
const copyButton = document.createElement('button');
copyButton.className = 'code-copy-btn';
copyButton.textContent = 'COPY';
copyButton.style.cssText = `
position: absolute;
top: 1rem;
right: 1rem;
padding: 0.5rem 1rem;
background: var(--color-primary);
color: var(--color-bg-dark);
border: none;
border-radius: 4px;
font-family: var(--font-mono);
font-size: 0.75rem;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s ease;
`;
block.style.position = 'relative';
block.appendChild(copyButton);
block.addEventListener('mouseenter', () => {
copyButton.style.opacity = '1';
});
block.addEventListener('mouseleave', () => {
copyButton.style.opacity = '0';
});
copyButton.addEventListener('click', async () => {
const code = block.querySelector('code').textContent;
try {
await navigator.clipboard.writeText(code);
copyButton.textContent = 'COPIED!';
setTimeout(() => {
copyButton.textContent = 'COPY';
}, 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
});
});
// ========================================
// PERFORMANCE OPTIMIZATION
// ========================================
// Lazy load images when they come into view
const lazyImages = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
});
lazyImages.forEach(img => imageObserver.observe(img));
// ========================================
// MOBILE MENU TOGGLE (if needed)
// ========================================
const createMobileMenu = () => {
// Only create mobile menu if screen is small
if (window.innerWidth > 768) return;
const nav = document.querySelector('.nav-links');
const container = document.querySelector('.nav-container');
// Create hamburger button
const hamburger = document.createElement('button');
hamburger.className = 'hamburger';
hamburger.innerHTML = `
<span></span>
<span></span>
<span></span>
`;
hamburger.style.cssText = `
display: flex;
flex-direction: column;
gap: 4px;
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
`;
hamburger.querySelectorAll('span').forEach(span => {
span.style.cssText = `
width: 24px;
height: 2px;
background: var(--color-primary);
transition: all 0.3s ease;
`;
});
container.appendChild(hamburger);
// Toggle menu
hamburger.addEventListener('click', () => {
nav.classList.toggle('active');
if (nav.classList.contains('active')) {
nav.style.cssText = `
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: var(--color-bg-darker);
flex-direction: column;
padding: 2rem;
border-top: 1px solid var(--color-border);
`;
} else {
nav.style.cssText = '';
}
});
};
// Initialize mobile menu if needed
if (window.innerWidth <= 768) {
createMobileMenu();
}
// Re-initialize on resize
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (window.innerWidth <= 768) {
createMobileMenu();
}
}, 250);
});
console.log('🏋️ ActaLog site loaded - Athletic Brutalism activated');