Files
davila7--claude-code-templates/docs/js/carousel.js
T
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

292 lines
8.8 KiB
JavaScript

// Company Stacks Carousel functionality
class CompanyCarousel {
constructor() {
this.carousel = document.getElementById('companyCarousel');
this.leftBtn = document.querySelector('.carousel-btn-left');
this.rightBtn = document.querySelector('.carousel-btn-right');
this.scrollAmount = 280; // Width of one card plus gap
this.autoScrollInterval = null;
this.init();
}
init() {
if (!this.carousel) return;
this.updateButtons();
this.setupEventListeners();
this.startAutoScroll();
}
setupEventListeners() {
// Scroll event to update button states
this.carousel.addEventListener('scroll', () => {
this.updateButtons();
});
// Stop auto-scroll on hover
this.carousel.addEventListener('mouseenter', () => {
this.stopAutoScroll();
});
// Resume auto-scroll when mouse leaves
this.carousel.addEventListener('mouseleave', () => {
this.startAutoScroll();
});
// Touch/swipe support for mobile
let startX = 0;
let scrollLeft = 0;
let isDown = false;
this.carousel.addEventListener('mousedown', (e) => {
isDown = true;
startX = e.pageX - this.carousel.offsetLeft;
scrollLeft = this.carousel.scrollLeft;
this.carousel.style.cursor = 'grabbing';
this.stopAutoScroll();
});
this.carousel.addEventListener('mouseleave', () => {
isDown = false;
this.carousel.style.cursor = 'grab';
});
this.carousel.addEventListener('mouseup', () => {
isDown = false;
this.carousel.style.cursor = 'grab';
this.startAutoScroll();
});
this.carousel.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - this.carousel.offsetLeft;
const walk = (x - startX) * 2;
this.carousel.scrollLeft = scrollLeft - walk;
});
// Touch events for mobile
this.carousel.addEventListener('touchstart', (e) => {
startX = e.touches[0].pageX - this.carousel.offsetLeft;
scrollLeft = this.carousel.scrollLeft;
this.stopAutoScroll();
});
this.carousel.addEventListener('touchmove', (e) => {
if (!startX) return;
const x = e.touches[0].pageX - this.carousel.offsetLeft;
const walk = (x - startX) * 2;
this.carousel.scrollLeft = scrollLeft - walk;
});
this.carousel.addEventListener('touchend', () => {
startX = 0;
this.startAutoScroll();
});
}
scrollLeft() {
this.carousel.scrollBy({
left: -this.scrollAmount,
behavior: 'smooth'
});
this.stopAutoScroll();
setTimeout(() => this.startAutoScroll(), 3000);
}
scrollRight() {
this.carousel.scrollBy({
left: this.scrollAmount,
behavior: 'smooth'
});
this.stopAutoScroll();
setTimeout(() => this.startAutoScroll(), 3000);
}
updateButtons() {
const scrollLeft = this.carousel.scrollLeft;
const maxScroll = this.carousel.scrollWidth - this.carousel.clientWidth;
// Update left button
if (this.leftBtn) {
this.leftBtn.disabled = scrollLeft <= 0;
this.leftBtn.style.opacity = scrollLeft <= 0 ? '0.5' : '1';
}
// Update right button
if (this.rightBtn) {
this.rightBtn.disabled = scrollLeft >= maxScroll - 1; // -1 for rounding errors
this.rightBtn.style.opacity = scrollLeft >= maxScroll - 1 ? '0.5' : '1';
}
}
startAutoScroll() {
this.stopAutoScroll();
this.autoScrollInterval = setInterval(() => {
const maxScroll = this.carousel.scrollWidth - this.carousel.clientWidth;
const currentScroll = this.carousel.scrollLeft;
if (currentScroll >= maxScroll - 1) {
// Reset to beginning
this.carousel.scrollTo({
left: 0,
behavior: 'smooth'
});
} else {
// Scroll to next item
this.scrollRight();
}
}, 4000); // Auto-scroll every 4 seconds
}
stopAutoScroll() {
if (this.autoScrollInterval) {
clearInterval(this.autoScrollInterval);
this.autoScrollInterval = null;
}
}
}
// Global functions for button clicks
function scrollCarousel(direction) {
if (window.companyCarousel) {
if (direction === 'left') {
window.companyCarousel.scrollLeft();
} else {
window.companyCarousel.scrollRight();
}
}
}
// Featured Projects Carousel functionality
class FeaturedCarousel {
constructor() {
this.carousel = document.getElementById('featuredCarousel');
this.leftBtn = document.querySelector('.featured-section .carousel-btn-left');
this.rightBtn = document.querySelector('.featured-section .carousel-btn-right');
this.scrollAmount = 304; // Width of one featured card (280px) plus gap (24px)
this.init();
}
init() {
if (!this.carousel) return;
this.updateButtons();
this.setupEventListeners();
}
setupEventListeners() {
// Scroll event to update button states
this.carousel.addEventListener('scroll', () => {
this.updateButtons();
});
// Touch/swipe support for mobile
let startX = 0;
let scrollLeft = 0;
let isDown = false;
this.carousel.addEventListener('mousedown', (e) => {
isDown = true;
startX = e.pageX - this.carousel.offsetLeft;
scrollLeft = this.carousel.scrollLeft;
this.carousel.style.cursor = 'grabbing';
});
this.carousel.addEventListener('mouseleave', () => {
isDown = false;
this.carousel.style.cursor = 'grab';
});
this.carousel.addEventListener('mouseup', () => {
isDown = false;
this.carousel.style.cursor = 'grab';
});
this.carousel.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - this.carousel.offsetLeft;
const walk = (x - startX) * 2;
this.carousel.scrollLeft = scrollLeft - walk;
});
// Touch events for mobile
this.carousel.addEventListener('touchstart', (e) => {
startX = e.touches[0].pageX - this.carousel.offsetLeft;
scrollLeft = this.carousel.scrollLeft;
});
this.carousel.addEventListener('touchmove', (e) => {
if (!startX) return;
const x = e.touches[0].pageX - this.carousel.offsetLeft;
const walk = (x - startX) * 2;
this.carousel.scrollLeft = scrollLeft - walk;
});
this.carousel.addEventListener('touchend', () => {
startX = 0;
});
}
scrollLeft() {
this.carousel.scrollBy({
left: -this.scrollAmount,
behavior: 'smooth'
});
}
scrollRight() {
this.carousel.scrollBy({
left: this.scrollAmount,
behavior: 'smooth'
});
}
updateButtons() {
const scrollLeft = this.carousel.scrollLeft;
const maxScroll = this.carousel.scrollWidth - this.carousel.clientWidth;
// Update left button
if (this.leftBtn) {
this.leftBtn.disabled = scrollLeft <= 0;
this.leftBtn.style.opacity = scrollLeft <= 0 ? '0.5' : '1';
}
// Update right button
if (this.rightBtn) {
this.rightBtn.disabled = scrollLeft >= maxScroll - 1;
this.rightBtn.style.opacity = scrollLeft >= maxScroll - 1 ? '0.5' : '1';
}
}
}
// Global functions for button clicks
function scrollFeaturedCarousel(direction) {
if (window.featuredCarousel) {
if (direction === 'left') {
window.featuredCarousel.scrollLeft();
} else {
window.featuredCarousel.scrollRight();
}
}
}
// Initialize carousel when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.companyCarousel = new CompanyCarousel();
window.featuredCarousel = new FeaturedCarousel();
});
// Re-initialize if page content changes
function reinitializeCarousel() {
if (window.companyCarousel) {
window.companyCarousel.stopAutoScroll();
}
window.companyCarousel = new CompanyCarousel();
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = { CompanyCarousel, scrollCarousel, reinitializeCarousel };
}