// ===========================
// Scroll Progress Indicator
// ===========================
(function() {
const progressBar = document.querySelector('.progress-bar');
if (!progressBar) return;
window.addEventListener('scroll', () => {
const windowHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrolled = (window.scrollY / windowHeight) * 100;
progressBar.style.width = scrolled + '%';
});
})();
// ===========================
// Hero Slider Functionality
// ===========================
(function() {
const slides = document.querySelectorAll('.slide');
if (slides.length === 0) return;
let currentSlide = 0;
let slideInterval;
function showSlide(index) {
slides.forEach(slide => slide.classList.remove('active'));
slides[index].classList.add('active');
currentSlide = index;
}
function nextSlide() {
let nextIndex = (currentSlide + 1) % slides.length;
showSlide(nextIndex);
}
function startSlider() {
slideInterval = setInterval(nextSlide, 5000);
}
// Initialize slider
showSlide(0);
startSlider();
})();
// ===========================
// Falling Stars Effect
// ===========================
(function() {
function createFallingStars() {
const starsContainer = document.getElementById('starsContainer');
if (!starsContainer) return;
starsContainer.innerHTML = '';
for (let i = 0; i < 15; i++) {
const star = document.createElement('div');
star.classList.add('falling-star');
const left = Math.random() * 100;
const delay = Math.random() * 15;
const duration = 3 + Math.random() * 4;
star.style.left = `${left}%`;
star.style.animationDelay = `${delay}s`;
star.style.animationDuration = `${duration}s`;
starsContainer.appendChild(star);
}
}
document.addEventListener('DOMContentLoaded', createFallingStars);
})();
// ===========================
// Background Particles
// ===========================
(function() {
function createParticles() {
const particlesContainer = document.querySelectorAll('.slide');
if (particlesContainer.length === 0) return;
particlesContainer.forEach(slide => {
for (let i = 0; i < 8; i++) {
const spark = document.createElement('div');
spark.classList.add('spark');
const left = Math.random() * 100;
const top = Math.random() * 100;
const delay = Math.random() * 5;
const duration = 1 + Math.random() * 2;
spark.style.left = `${left}%`;
spark.style.top = `${top}%`;
spark.style.animationDelay = `${delay}s`;
spark.style.animationDuration = `${duration}s`;
slide.appendChild(spark);
}
for (let i = 0; i < 5; i++) {
const connection = document.createElement('div');
connection.classList.add('connection');
const left = Math.random() * 100;
const top = Math.random() * 100;
const width = 50 + Math.random() * 200;
const rotation = Math.random() * 360;
const delay = Math.random() * 5;
connection.style.left = `${left}%`;
connection.style.top = `${top}%`;
connection.style.width = `${width}px`;
connection.style.transform = `rotate(${rotation}deg)`;
connection.style.animationDelay = `${delay}s`;
slide.appendChild(connection);
}
});
}
document.addEventListener('DOMContentLoaded', createParticles);
})();
// ===========================
// Light Streaks Effect
// ===========================
(function() {
function createLightStreaks() {
const slides = document.querySelectorAll('.slide');
if (slides.length === 0) return;
slides.forEach(slide => {
for (let i = 0; i < 5; i++) {
const streak = document.createElement('div');
streak.classList.add('streak');
const top = Math.random() * 100;
const delay = Math.random() * 5;
const duration = 4 + Math.random() * 4;
streak.style.top = `${top}%`;
streak.style.animationDelay = `${delay}s`;
streak.style.animationDuration = `${duration}s`;
slide.appendChild(streak);
}
});
}
document.addEventListener('DOMContentLoaded', createLightStreaks);
})();
// ===========================
// Ultra Subtle Cursor Trail - Ghost Effect
// ===========================
(function() {
const trailElements = [];
const MAX_TRAIL_ELEMENTS = 15;
function createGhostGlow(x, y) {
const d = document.createElement('div');
d.className = 'cursor-trail';
d.style.left = x + 'px';
d.style.top = y + 'px';
d.style.width = '45px';
d.style.height = '45px';
d.style.opacity = '0.60'; // Extremely low opacity
d.style.background = 'radial-gradient(circle, rgba(26,146,236,0.12) 0%, rgba(26,146,236,0.05) 20%, transparent 60%)';
d.style.filter = 'blur(15px)'; // Maximum blur
d.style.borderRadius = '50%';
d.style.position = 'fixed';
d.style.pointerEvents = 'none';
d.style.zIndex = '0';
d.style.transform = 'translate(-50%, -50%)';
d.style.transition = 'all 0.5s ease-out, opacity 0.6s ease-out';
d.style.boxShadow = '0 0 50px rgba(26,146,236,0.2)';
document.body.appendChild(d);
trailElements.push(d);
if(trailElements.length > MAX_TRAIL_ELEMENTS) {
const oldest = trailElements.shift();
if(oldest && oldest.parentNode) oldest.parentNode.removeChild(oldest);
}
setTimeout(() => {
d.style.opacity = '0';
d.style.width = '220px';
d.style.height = '220px';
d.style.filter = 'blur(25px)';
d.style.boxShadow = '0 0 80px rgba(26,146,236,0.1)';
}, 100);
setTimeout(() => {
if(d.parentNode) {
d.parentNode.removeChild(d);
const index = trailElements.indexOf(d);
if(index > -1) trailElements.splice(index, 1);
}
}, 700);
}
let lastTime = 0;
addEventListener('mousemove', (e) => {
const now = Date.now();
if(now - lastTime > 40) { // Even slower trail creation
createGhostGlow(e.clientX, e.clientY);
lastTime = now;
}
});
})();
// ===========================
// Hero Section Animations
// ===========================
(function() {
const title = document.querySelector('.main-title');
const subtitle = document.querySelector('.subtitle');
const cta = document.querySelector('.cta');
const icons = document.querySelectorAll('.icon-wrap');
if (!title && !subtitle && !cta && icons.length === 0) return;
function enter(el, delay = 0) {
setTimeout(() => {
if (el) el.classList.add('enter-active');
}, delay);
}
// Initial chain
if (title) enter(title, 250);
if (subtitle) enter(subtitle, 650);
if (cta) enter(cta, 1000);
// CTA interaction
if (cta) {
cta.addEventListener('click', (ev) => {
ev.preventDefault();
const pressed = cta.getAttribute('aria-pressed') === 'true';
cta.setAttribute('aria-pressed', (!pressed).toString());
const r = document.createElement('span');
r.style.position = 'absolute';
r.style.borderRadius = '50%';
r.style.left = (ev.offsetX - 6) + 'px';
r.style.top = (ev.offsetY - 6) + 'px';
r.style.width = r.style.height = '12px';
r.style.background = 'rgba(0,0,0,0.08)';
r.style.zIndex = '10';
r.style.transform = 'scale(1)';
r.style.transition = 'all .5s ease';
cta.appendChild(r);
setTimeout(() => {
r.style.opacity = '0';
r.style.transform = 'scale(3)';
}, 10);
setTimeout(() => r.remove(), 520);
});
}
// Icon interactions
icons.forEach((el, idx) => {
el.addEventListener('mouseenter', () => el.classList.add('hover'));
el.addEventListener('mouseleave', () => el.classList.remove('hover'));
el.addEventListener('focus', () => el.classList.add('hover'));
el.addEventListener('blur', () => el.classList.remove('hover'));
el.addEventListener('click', (e) => {
e.preventDefault();
el.style.transform = 'translateY(-6px) scale(1.06)';
setTimeout(() => el.style.transform = '', 420);
});
});
// Scroll reveal
const io = new IntersectionObserver((entries) => {
entries.forEach(ent => {
if(ent.isIntersecting) {
ent.target.classList.add('enter-active');
}
});
}, { threshold: 0.2 });
if (title) io.observe(title);
if (subtitle) io.observe(subtitle);
if (cta) io.observe(cta);
})();
// ===========================
// Icon Vibration Effect
// ===========================
(function() {
const iconWrappers = document.querySelectorAll('.icon-wrap');
if (iconWrappers.length === 0) return;
iconWrappers.forEach((el, i) => {
setInterval(() => {
if(!el.matches(':hover')) {
el.style.transform = `translateY(${(i % 2 ? -2 : 2)}px) rotate(${(i % 2 ? -1 : 1)}deg)`;
setTimeout(() => el.style.transform = '', 600);
}
}, 3400 + i * 300);
});
})();
// ===========================
// Testimonials Slider
// ===========================
document.addEventListener("DOMContentLoaded", () => {
const container = document.querySelector(".testimonials-container");
const dotsContainer = document.querySelector(".testimonial-nav");
const arrows = container.querySelector(".nav-arrows");
const prevArrow = arrows.querySelector(".prev-arrow");
const nextArrow = arrows.querySelector(".next-arrow");
// Fetch testimonials from backend
fetch("https://creativobe.com/backend/fetch_testimonial.php")
.then(res => res.json())
.then(data => {
if (!data || data.length === 0) return;
// Remove old cards
container.querySelectorAll(".testimonial-card").forEach(el => el.remove());
dotsContainer.innerHTML = "";
// Create testimonial cards dynamically
data.forEach((item, index) => {
const card = document.createElement("div");
card.classList.add("testimonial-card");
if (index === 0) card.classList.add("active");
else if (index === 1) card.classList.add("right");
else if (index === data.length - 1) card.classList.add("left");
card.innerHTML = `
${"ā
".repeat(item.rating)}
${item.message}
${item.company ? item.company.slice(0, 2).toUpperCase() : "CL"}
`;
// Insert card **before arrows**
container.insertBefore(card, arrows);
// Create nav dot
const dot = document.createElement("div");
dot.classList.add("nav-dot");
if (index === 0) dot.classList.add("active");
dotsContainer.appendChild(dot);
});
// Initialize slider
initTestimonialSlider();
})
.catch(err => console.error("Error fetching testimonials:", err));
// Slider logic
function initTestimonialSlider() {
const testimonialCards = container.querySelectorAll(".testimonial-card");
const testimonialDots = dotsContainer.querySelectorAll(".nav-dot");
let currentIndex = 0;
if (testimonialCards.length === 0) return;
function showTestimonial(n) {
testimonialCards.forEach(c => c.classList.remove("active", "left", "right"));
testimonialDots.forEach(d => d.classList.remove("active"));
currentIndex = (n + testimonialCards.length) % testimonialCards.length;
testimonialCards[currentIndex].classList.add("active");
testimonialDots[currentIndex].classList.add("active");
const prevIndex = (currentIndex - 1 + testimonialCards.length) % testimonialCards.length;
const nextIndex = (currentIndex + 1) % testimonialCards.length;
testimonialCards[prevIndex].classList.add("left");
testimonialCards[nextIndex].classList.add("right");
}
showTestimonial(0);
prevArrow.addEventListener("click", () => showTestimonial(currentIndex - 1));
nextArrow.addEventListener("click", () => showTestimonial(currentIndex + 1));
testimonialDots.forEach((dot, i) => dot.addEventListener("click", () => showTestimonial(i)));
}
});
// ===========================
// Media Gallery
// ===========================
(function() {
const mediaGallerySection = document.querySelector('.media-gallery-section');
if (!mediaGallerySection) return;
const mediaItems = document.querySelectorAll('.media-gallery-section .media-item');
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top <= (window.innerHeight || document.documentElement.clientHeight) * 0.8
);
}
function handleScroll() {
mediaItems.forEach(item => {
if (isInViewport(item)) {
item.classList.add('visible');
}
});
}
window.addEventListener('scroll', handleScroll);
handleScroll();
// Individual view buttons
document.querySelectorAll('.media-gallery-section .view-btn').forEach(button => {
button.addEventListener('click', function(e) {
e.stopPropagation();
const title = this.closest('.media-overlay').querySelector('.media-title').textContent;
alert(`Viewing details for: ${title}`);
});
});
})();
// ===========================
// Social Media Section
// ===========================
(function() {
const socialSection = document.querySelector('.social-media-section');
if (!socialSection) return;
const followIcons = document.querySelectorAll('.follow-icon');
// Add animation to icons when they come into view
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
setTimeout(() => {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}, index * 100);
}
});
}, { threshold: 0.1 });
// Initialize icons with hidden state
followIcons.forEach(icon => {
icon.style.opacity = '0';
icon.style.transform = 'translateY(20px)';
icon.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
observer.observe(icon);
});
})();
//============================
//services Section
//============================
document.addEventListener('DOMContentLoaded', function () {
console.log('Initializing services section...');
let servicesData = [];
let currentService = null;
// DOM Elements
const list = document.getElementById('main-list');
const previewImg = document.getElementById('main-preview-img');
const overlayTitle = document.getElementById('main-overlay-title');
const overlayDesc = document.getElementById('main-overlay-desc');
const viewBtn = document.getElementById('main-view-btn');
const descTitle = document.getElementById('main-desc-title');
const descText = document.getElementById('main-desc-text');
const ctaBtn = document.getElementById('main-cta');
const imgBox = document.getElementById('main-img-box');
const descBox = document.getElementById('main-desc-box');
const backBtn = document.getElementById('main-back-btn');
// Initialize services
function initializeServices() {
loadServicesFromBackend();
setupEventListeners();
}
// Load services from backend
function loadServicesFromBackend() {
console.log('Fetching services from backend...');
fetch('backend/services.php?action=getAll')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.success) {
servicesData = data.services;
console.log('Services loaded successfully:', servicesData.length);
console.log('Services data:', servicesData);
displayServices();
// Activate first service if available
if (servicesData.length > 0) {
setTimeout(() => {
setActiveService(servicesData[0].id);
}, 500);
}
} else {
console.error('Failed to load services:', data.message);
loadDefaultServices(); // Fallback to default services
}
})
.catch(error => {
console.error('Error fetching services:', error);
loadDefaultServices(); // Fallback to default services
});
}
// Fallback to default services if backend fails
function loadDefaultServices() {
console.warn('Using fallback services data');
servicesData = [
{
id: 1,
name: "Ad Creative Designs",
description: "We create compelling and high-converting ad creatives that capture attention and drive results across all digital platforms.",
detailed_description: "Our Ad Creative Design service combines artistic excellence with data-driven strategies to create advertisements that not only look stunning but also deliver measurable results across all digital platforms. We specialize in creating visually appealing and conversion-optimized designs that resonate with your target audience and drive business growth.",
image: "https://images.unsplash.com/photo-1561070791-2526d30994b5?ixlib=rb-4.0.3&auto=format&fit=crop&w=1064&q=80",
icon: "fas fa-ad",
service_url: "service.php?id=1"
},
{
id: 2,
name: "Product Designing",
description: "We design innovative and user-centered products that solve real problems and deliver exceptional user experiences.",
detailed_description: "Our Product Design service takes a human-centered approach to creating digital products that users love. We conduct extensive user research to understand pain points and opportunities, then translate these insights into intuitive and beautiful designs that solve real problems effectively and efficiently.",
image: "https://images.unsplash.com/photo-1558655146-9f40138edfeb?ixlib=rb-4.0.3&auto=format&fit=crop&w=1064&q=80",
icon: "fas fa-cube",
service_url: "service.php?id=2"
},
{
id: 3,
name: "Motion Graphics",
description: "We create engaging motion graphics and animations that bring your brand story to life and captivate your audience.",
detailed_description: "We create engaging motion graphics and animations that bring your brand story to life and captivate your audience across various platforms. Our dynamic visual storytelling approach ensures your content stands out in today's crowded digital landscape and delivers your message effectively.",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80",
icon: "fas fa-film",
service_url: "service.php?id=3"
}
];
displayServices();
// Activate first service
if (servicesData.length > 0) {
setTimeout(() => {
setActiveService(servicesData[0].id);
}, 500);
}
}
// Display services in the list
function displayServices() {
if (!servicesData || servicesData.length === 0) {
list.innerHTML = 'No services available
';
return;
}
list.innerHTML = servicesData.map(service => `
`).join('');
console.log('Services displayed:', servicesData.length);
// Add click events to the new cards
document.querySelectorAll('.main-card').forEach(card => {
card.addEventListener('click', function() {
const serviceId = this.getAttribute('data-id');
setActiveService(serviceId);
});
});
}
// Set active service
function setActiveService(serviceId) {
const service = servicesData.find(s => s.id == serviceId);
if (!service) {
console.error('Service not found:', serviceId);
return;
}
currentService = service;
console.log('Selected service:', service.name);
console.log('Service image data:', service.image);
// Remove active classes from all cards
document.querySelectorAll('.main-card').forEach(card => {
card.classList.remove('main-active');
});
// Add active classes to selected card
const activeCard = document.querySelector(`.main-card[data-id="${serviceId}"]`);
if (activeCard) {
activeCard.classList.add('main-active');
// Always flip the card
activeCard.classList.toggle('flipped');
}
// Update preview section
updatePreview(service);
}
// Update preview section - FIXED VERSION
function updatePreview(service) {
console.log('Updating preview for service:', service.name);
console.log('Service image value:', service.image);
// Update image view
let finalImagePath = '';
if (service.image && service.image.trim() !== '') {
// Check what type of image path we have
if (service.image.startsWith('http') || service.image.startsWith('//')) {
// It's a full URL (like Unsplash images)
finalImagePath = service.image;
console.log('Using full URL image:', finalImagePath);
} else if (service.image.includes('/')) {
// It already has a path (like 'images/services/filename.jpg')
finalImagePath = service.image;
console.log('Using existing path image:', finalImagePath);
} else {
// It's just a filename (like '6927700e84c78_1764192270.jpg')
finalImagePath = 'images/services/' + service.image;
console.log('Converted filename to path:', finalImagePath);
}
} else {
// Fallback image if no image is set
finalImagePath = 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80';
console.log('No image set, using fallback');
}
console.log('Final image path being used:', finalImagePath);
// Set the image source
previewImg.src = finalImagePath;
previewImg.alt = service.name;
// Add image load/error handlers for debugging
previewImg.onload = function() {
console.log('ā
Image loaded successfully:', finalImagePath);
};
previewImg.onerror = function() {
console.error('ā Failed to load image:', finalImagePath);
// Try fallback
this.src = 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80';
};
overlayTitle.textContent = service.name;
overlayDesc.textContent = service.description;
viewBtn.style.display = 'inline-flex';
// Update view button
viewBtn.onclick = (e) => {
e.stopPropagation();
if (service.service_url) {
window.location.href = service.service_url;
}
};
// Update description view
descTitle.textContent = service.name;
descText.textContent = service.detailed_description || service.description;
// Update CTA button
ctaBtn.href = service.service_url || `service.php?id=${service.id}`;
ctaBtn.onclick = (e) => {
e.preventDefault();
window.location.href = service.service_url || `service.php?id=${service.id}`;
};
}
// Setup event listeners
function setupEventListeners() {
// Image box click - switch to description with fade animation
imgBox.addEventListener('click', () => {
if (currentService) {
imgBox.classList.add('fade-out');
setTimeout(() => {
imgBox.style.display = 'none';
descBox.style.display = 'flex';
descBox.classList.add('active');
}, 500);
}
});
// Back button click - switch to image with fade animation
backBtn.addEventListener('click', (e) => {
e.stopPropagation();
descBox.classList.remove('active');
setTimeout(() => {
descBox.style.display = 'none';
imgBox.style.display = 'block';
imgBox.classList.remove('fade-out');
}, 300);
});
// View details button in overlay
viewBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (currentService && currentService.service_url) {
window.location.href = currentService.service_url;
}
});
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (!currentService || servicesData.length === 0) return;
const currentIndex = servicesData.findIndex(s => s.id === currentService.id);
let newIndex;
if (e.key === 'ArrowDown') {
e.preventDefault();
newIndex = (currentIndex + 1) % servicesData.length;
setActiveService(servicesData[newIndex].id);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
newIndex = (currentIndex - 1 + servicesData.length) % servicesData.length;
setActiveService(servicesData[newIndex].id);
}
});
}
// Initialize everything
initializeServices();
console.log('Services section fully loaded!');
});
//===========================================
//==================newsletter===============
//===========================================
// newsletter.js - complete fixed version
(function() {
const newsletterSection = document.querySelector('.newsletter-section');
if (!newsletterSection) return;
const form = document.getElementById("newsletterForm");
const nameInput = form?.querySelector("input[type='text']");
const emailInput = form?.querySelector("input[type='email']");
const submitBtn = document.getElementById("submitBtn");
const successMessage = document.getElementById("successMessage");
const checkbox = document.getElementById("privacyCheckbox");
const checkIcon = checkbox?.querySelector("i");
// Unsubscribe elements
const unsubscribeBtn = document.getElementById("unsubscribeBtn");
const unsubscribeConfirm = document.getElementById("unsubscribeConfirm");
const confirmUnsubscribeBtn = document.getElementById("confirmUnsubscribeBtn");
const cancelUnsubscribeBtn = document.getElementById("cancelUnsubscribeBtn");
let agreed = false;
let currentEmail = '';
/* ---------------- initialization ---------------- */
async function initializeNewsletter() {
console.log('š§ Newsletter system initializing...');
try {
// Check current subscription status
const response = await fetch("/backend/check_newsletter_session.php");
const data = await response.json();
console.log('š Current subscription status:', data);
if (data.subscribed) {
console.log('ā
User is subscribed:', data.user_data);
updateUIForSubscribedUser(data.user_data);
} else {
console.log('ā User is not subscribed');
resetNewsletterForm();
}
return true;
} catch (error) {
console.error('š„ Newsletter initialization failed:', error);
return false;
}
}
/* ---------------- update UI for subscribed users ---------------- */
function updateUIForSubscribedUser(userData) {
if (!form || !successMessage) return;
form.style.display = "none";
successMessage.style.display = "block";
// Update success message with user data
const successText = successMessage.querySelector("p");
if (successText && userData) {
successText.textContent = `Thank you for subscribing, ${userData.name || 'there'}! You're all set to enjoy all features.`;
}
// Store email for unsubscribe functionality
currentEmail = userData.email || '';
console.log('š UI updated for subscribed user:', userData);
}
/* ---------------- reset form ---------------- */
function resetNewsletterForm() {
if (!form || !successMessage) return;
form.style.display = "block";
successMessage.style.display = "none";
unsubscribeConfirm.style.display = "none";
if (form) form.reset();
agreed = false;
if (checkbox) {
checkbox.classList.remove("checked");
if (checkIcon) checkIcon.style.display = "none";
}
currentEmail = '';
}
/* ---------------- checkbox functionality ---------------- */
function initializeCheckbox() {
if (!checkbox) return;
checkbox.addEventListener("click", () => {
agreed = !agreed;
checkbox.classList.toggle("checked");
if (checkIcon) {
checkIcon.style.display = agreed ? "block" : "none";
}
console.log(`ā Privacy policy ${agreed ? 'accepted' : 'not accepted'}`);
});
}
/* ---------------- form validation ---------------- */
function validateForm(name, email) {
const errors = [];
if (!name.trim()) errors.push('Name is required');
else if (name.trim().length < 2) errors.push('Name should be at least 2 characters long');
if (!email.trim()) errors.push('Email is required');
else if (!isValidEmail(email)) errors.push('Please enter a valid email address');
if (!agreed) errors.push('Please accept the privacy policy');
return errors;
}
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/* ---------------- loading states ---------------- */
function showLoading() {
if (!submitBtn) return;
submitBtn.disabled = true;
const loader = submitBtn.querySelector(".btn-loader");
const textSpan = submitBtn.querySelector("span");
if (loader) loader.style.display = "inline-block";
if (textSpan) textSpan.textContent = "Subscribing...";
}
function hideLoading() {
if (!submitBtn) return;
submitBtn.disabled = false;
const loader = submitBtn.querySelector(".btn-loader");
const textSpan = submitBtn.querySelector("span");
if (loader) loader.style.display = "none";
if (textSpan) textSpan.textContent = "Subscribe Now";
}
/* ---------------- success handling ---------------- */
function showSuccess(message, email) {
if (!form || !successMessage) return;
currentEmail = email;
form.style.display = "none";
successMessage.style.display = "block";
// Update success message text
const successText = successMessage.querySelector("p");
if (successText && message) {
successText.textContent = message;
}
console.log('š Subscription successful! Email:', email);
// Trigger subscription status update for other components
setTimeout(() => {
if (window.updateSubscriptionStatus) {
window.updateSubscriptionStatus(true);
}
}, 100);
}
/* ---------------- form submission ---------------- */
async function handleSubmit(event) {
event.preventDefault();
if (!nameInput || !emailInput) return;
const name = nameInput.value.trim();
const email = emailInput.value.trim();
// Validate form
const validationErrors = validateForm(name, email);
if (validationErrors.length > 0) {
showErrorModal(`Please fix the following errors:\n⢠${validationErrors.join('\n⢠')}`);
return;
}
// Prepare form data
const formData = new FormData();
formData.append("name", name);
formData.append("email", email);
// Show loading state
showLoading();
try {
const response = await fetch("/backend/add_subscriber.php", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log("Server response:", data);
if (data.status === "success") {
showSuccess(data.message, email);
} else {
throw new Error(data.message || "Subscription failed");
}
} catch (error) {
console.error('š„ Submission error:', error);
showErrorModal("Subscription failed: " + error.message);
} finally {
hideLoading();
}
}
/* ---------------- unsubscribe functionality ---------------- */
async function handleUnsubscribe() {
console.log('š« Unsubscribe initiated for:', currentEmail);
if (!currentEmail) {
showErrorModal('No email found to unsubscribe');
return;
}
try {
const formData = new FormData();
formData.append("email", currentEmail);
const response = await fetch("/backend/remove_subscriber.php", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('š Unsubscribe response:', data);
if (data.status === "success") {
// Show unsubscribed message
if (successMessage) {
successMessage.innerHTML = `
Unsubscribed Successfully
You have been unsubscribed from our newsletter. We're sorry to see you go!
Subscribe Again
`;
}
// Hide unsubscribe confirmation
if (unsubscribeConfirm) {
unsubscribeConfirm.style.display = "none";
}
// Trigger subscription status update for other components
setTimeout(() => {
if (window.updateSubscriptionStatus) {
window.updateSubscriptionStatus(false);
}
}, 100);
} else {
throw new Error(data.message || "Unsubscribe failed");
}
} catch (error) {
console.error('š„ Unsubscribe error:', error);
showErrorModal('Unsubscribe failed: ' + error.message);
}
}
/* ---------------- error modal ---------------- */
function showErrorModal(message) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
backdrop-filter: blur(5px);
`;
modal.innerHTML = `
`;
document.body.appendChild(modal);
document.getElementById('closeErrorModal').addEventListener('click', () => {
document.body.removeChild(modal);
});
}
/* ---------------- event listeners ---------------- */
function initializeEventListeners() {
// Form submission
if (form) {
form.addEventListener("submit", handleSubmit);
}
// Checkbox
initializeCheckbox();
// Unsubscribe functionality
if (unsubscribeBtn) {
unsubscribeBtn.addEventListener("click", () => {
if (unsubscribeConfirm) {
unsubscribeConfirm.style.display = "block";
}
});
}
if (cancelUnsubscribeBtn) {
cancelUnsubscribeBtn.addEventListener("click", () => {
if (unsubscribeConfirm) {
unsubscribeConfirm.style.display = "none";
}
});
}
if (confirmUnsubscribeBtn) {
confirmUnsubscribeBtn.addEventListener("click", handleUnsubscribe);
}
}
/* ---------------- global function for other components ---------------- */
window.updateSubscriptionStatus = function(isSubscribed) {
console.log('š Global subscription status updated:', isSubscribed);
// This can be used by other components to update their state
if (window.blogUserState) {
window.blogUserState.isSubscribed = isSubscribed;
}
};
/* ---------------- initialization ---------------- */
document.addEventListener("DOMContentLoaded", function() {
console.log('š Initializing newsletter system...');
initializeEventListeners();
initializeNewsletter();
// Re-check subscription status every 30 seconds
setInterval(initializeNewsletter, 30000);
});
})();
//============================================
// why choose us Section
//============================================
// Load features from backend
document.addEventListener('DOMContentLoaded', function() {
loadFeatures();
initStatsCounter();
});
async function loadFeatures() {
try {
const response = await fetch('/backend/features_api.php?action=get_all_features');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.success && data.data) {
renderFeatures(data.data);
} else {
console.error('Failed to load features:', data.message || 'No data returned');
// Fallback to static content if API fails
renderStaticFeatures();
}
} catch (error) {
console.error('Error loading features:', error);
// Fallback to static content
renderStaticFeatures();
}
}
function renderFeatures(features) {
const container = document.getElementById('features-container');
if (!container) {
console.error('Features container not found!');
return;
}
container.innerHTML = '';
features.forEach(feature => {
// Only render active features
if (feature.is_active !== undefined && !feature.is_active) {
return;
}
const featureCard = document.createElement('div');
featureCard.className = 'feature-card';
featureCard.innerHTML = `
${feature.title || 'Feature Title'}
${feature.short_description || 'No description available'}
Learn more
`;
container.appendChild(featureCard);
});
// If no features were rendered (all inactive), show static features
if (container.children.length === 0) {
console.log('No active features found, showing static content');
renderStaticFeatures();
}
}
function renderStaticFeatures() {
// Fallback static content
const container = document.getElementById('features-container');
if (!container) {
console.error('Features container not found for static content!');
return;
}
container.innerHTML = `
Data-Driven Strategy
Our approach is rooted in analytics and market insights, ensuring every creative decision is backed by data to maximize ROI for your B2B campaigns.
Learn more
Industry Expertise
With years of experience across multiple B2B sectors, we understand the unique challenges and opportunities in your industry.
Learn more
Agile Methodology
We work in sprints to deliver results faster, adapt to market changes, and continuously optimize your campaigns for peak performance.
Learn more
Integrated Solutions
From brand strategy to digital execution, we provide end-to-end solutions that create cohesive brand experiences across all touchpoints.
Learn more
Partnership Approach
We become an extension of your team, working collaboratively to understand your business goals and drive long-term success.
Learn more
Transparent Reporting
We provide clear, comprehensive reporting that connects creative efforts to business outcomes, so you always know the impact of our work.
Learn more
`;
}
// Stats Counter Functionality
function initStatsCounter() {
const statItems = document.querySelectorAll('.stat-item');
// Define target values for each stat
const targetValues = {
0: { value: 98, suffix: '%', isDecimal: false },
1: { value: 250, suffix: '+', isDecimal: false },
2: { value: 15, suffix: '+', isDecimal: false },
3: { value: 3.2, suffix: 'x', isDecimal: true }
};
// Create an Intersection Observer to trigger animation when stats are in view
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
statItems.forEach((item, index) => {
const valueElement = item.querySelector('.stats-value');
const target = targetValues[index];
if (target && valueElement) {
startCounter(valueElement, target);
}
});
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.5, // Trigger when 50% of the element is visible
rootMargin: '0px 0px -50px 0px' // Adjust trigger point
});
// Observe the stats section
const statsSection = document.querySelector('.stats-section');
if (statsSection) {
observer.observe(statsSection);
}
}
function startCounter(element, target) {
const { value: targetValue, suffix, isDecimal } = target;
const duration = 2000; // Animation duration in milliseconds
const frameRate = 60; // Frames per second
const totalFrames = Math.round(duration / (1000 / frameRate));
const increment = targetValue / totalFrames;
let currentFrame = 0;
let currentValue = 0;
// Format number based on whether it's a decimal or integer
const formatNumber = (num) => {
return isDecimal ? num.toFixed(1) : Math.floor(num);
};
const counter = setInterval(() => {
currentFrame++;
currentValue += increment;
if (currentFrame >= totalFrames) {
currentValue = targetValue;
clearInterval(counter);
}
element.textContent = formatNumber(currentValue) + suffix;
}, 1000 / frameRate);
}
//=========================================
//portfolio Section
//=========================================
// Complete Enhanced Portfolio Manager with Video Support
class PortfolioManager {
constructor() {
this.portfolioGrid = document.getElementById('portfolioGrid');
this.filterContainer = document.getElementById('portfolioFilter');
this.toggleBtn = document.getElementById('toggleBtn');
// Navigation state
this.navigationStack = [];
this.currentView = {
type: 'categories',
data: null,
title: 'Our Portfolio'
};
// Pagination state
this.itemsPerPage = 6;
this.currentPage = 1;
this.allItems = [];
this.isLoading = false;
// Cache for API responses
this.cache = new Map();
// Page URLs configuration
this.pageUrls = {
projectDetail: '../backend/project-detail.php',
categoryGallery: '../backend/category-gallery.php',
subcategoryGallery: '../backend/subcategory-gallery.php'
};
// All categories for filtering
this.allCategories = [];
this.init();
}
async init() {
try {
await this.loadAndSetupCategories();
this.setupEventListeners();
} catch (error) {
console.error('Portfolio initialization failed:', error);
this.showError('Failed to load portfolio. Please refresh the page.');
}
}
// Load categories and setup filter buttons
async loadAndSetupCategories() {
try {
this.isLoading = true;
this.showLoading();
const data = await this.cachedFetch('../backend/get_categories.php');
if (data.success) {
this.allCategories = data.categories || [];
this.createFilterButtons();
this.displayCategories(this.allCategories);
} else {
throw new Error(data.error || 'Failed to load categories');
}
} catch (error) {
console.error('Error loading categories:', error);
this.showError('Failed to load categories.');
this.allCategories = [];
this.createFilterButtons();
} finally {
this.isLoading = false;
}
}
// Create filter buttons dynamically
createFilterButtons() {
const allBtn = this.filterContainer.querySelector('[data-filter="all"]');
this.filterContainer.innerHTML = '';
if (allBtn) {
this.filterContainer.appendChild(allBtn);
} else {
const allBtn = document.createElement('button');
allBtn.className = 'filter-btn active';
allBtn.dataset.filter = 'all';
allBtn.textContent = 'All Projects';
this.filterContainer.appendChild(allBtn);
}
this.allCategories.forEach(category => {
const filterBtn = document.createElement('button');
filterBtn.className = 'filter-btn';
filterBtn.dataset.filter = category.slug;
filterBtn.textContent = this.formatDisplayName(category.name);
this.filterContainer.appendChild(filterBtn);
});
this.setupFilterListeners();
}
// Cache management
getCacheKey(endpoint, params = {}) {
return `../backend/${endpoint}?${new URLSearchParams(params).toString()}`;
}
async cachedFetch(url, options = {}) {
const cacheKey = url;
const now = Date.now();
const cacheExpiry = 5 * 60 * 1000;
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (now - cached.timestamp < cacheExpiry) {
return cached.data;
}
}
const response = await fetch(url, options);
if (!response.ok) {
const errorText = await response.text();
console.error('Fetch error:', errorText);
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.cache.set(cacheKey, {
data: data,
timestamp: now
});
return data;
}
// Setup filter button listeners
setupFilterListeners() {
const filterButtons = this.filterContainer.querySelectorAll('.filter-btn');
filterButtons.forEach(btn => {
btn.addEventListener('click', this.debounce((e) => {
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const filter = btn.dataset.filter;
if (filter === 'all') {
this.navigationStack = [];
this.currentPage = 1;
this.displayCategories(this.allCategories);
} else {
const category = this.allCategories.find(cat => cat.slug === filter);
if (category) {
this.navigateTo('subcategories', category.slug, category.name);
}
}
}, 300));
});
}
// Navigation Methods
async navigateTo(viewType, data = null, title = '') {
this.currentPage = 1;
this.allItems = [];
this.navigationStack.push({...this.currentView});
this.currentView = { type: viewType, data, title };
switch (viewType) {
case 'categories':
this.displayCategories(this.allCategories);
break;
case 'subcategories':
await this.loadSubcategories(data);
break;
case 'projects':
await this.loadProjects(data.category, data.subcategory);
break;
}
}
goBack() {
if (this.navigationStack.length > 0) {
const previousView = this.navigationStack.pop();
this.currentView = previousView;
this.currentPage = 1;
switch (previousView.type) {
case 'categories':
this.displayCategories(this.allCategories);
break;
case 'subcategories':
this.loadSubcategories(previousView.data);
break;
case 'projects':
this.loadProjects(previousView.data.category, previousView.data.subcategory, true);
break;
}
}
}
// Data Loading Methods
async loadSubcategories(categorySlug) {
if (this.isLoading) return;
try {
this.isLoading = true;
this.showLoading();
const data = await this.cachedFetch(`../backend/get_subcategories.php?category=${categorySlug}`);
if (data.success) {
this.displaySubcategories(data.subcategories, categorySlug);
} else {
throw new Error(data.error || 'Failed to load subcategories');
}
} catch (error) {
console.error('Error loading subcategories:', error);
this.showError('Failed to load subcategories.');
} finally {
this.isLoading = false;
}
}
async loadProjects(categorySlug, subcategorySlug = null, fromCache = false) {
if (this.isLoading && !fromCache) return;
try {
if (!fromCache) {
this.isLoading = true;
this.showLoading();
}
let url = `../backend/get_projects.php?category=${categorySlug}`;
if (subcategorySlug) {
url += `&subcategory=${subcategorySlug}`;
}
console.log('Loading projects from:', url);
const data = await this.cachedFetch(url);
console.log('Projects loaded:', data);
if (data.success) {
this.allItems = data.projects || [];
console.log(`Total projects: ${this.allItems.length}`);
this.displayPaginatedProjects();
} else {
throw new Error(data.error || 'Failed to load projects');
}
} catch (error) {
console.error('Error loading projects:', error);
this.showError('Failed to load projects.');
} finally {
this.isLoading = false;
}
}
// Display Methods with Pagination
displayCategories(categories) {
this.portfolioGrid.innerHTML = '';
if (!categories || categories.length === 0) {
this.showEmptyState('No categories available.');
return;
}
this.createHeader('Our Services', 'Choose a service category to explore our work');
categories.forEach((category, index) => {
const card = this.createCategoryCard(category, index);
this.portfolioGrid.appendChild(card);
});
this.hideToggleButton();
this.addAnimations();
}
displaySubcategories(subcategories, categorySlug) {
this.portfolioGrid.innerHTML = '';
if (!subcategories || subcategories.length === 0) {
this.loadProjects(categorySlug);
return;
}
const category = this.allCategories.find(cat => cat.slug === categorySlug);
const categoryName = category ? category.name : categorySlug.replace(/-/g, ' ');
this.createHeader(categoryName, 'Choose a subcategory');
this.createBackButton();
subcategories.forEach((subcategory, index) => {
const card = this.createSubcategoryCard(subcategory, categorySlug, index);
this.portfolioGrid.appendChild(card);
});
this.hideToggleButton();
this.addAnimations();
}
displayPaginatedProjects() {
this.portfolioGrid.innerHTML = '';
if (!this.allItems || this.allItems.length === 0) {
this.showEmptyState('No projects found in this category.');
return;
}
const startIndex = 0;
const endIndex = this.currentPage * this.itemsPerPage;
const displayedItems = this.allItems.slice(startIndex, endIndex);
const hasMoreItems = endIndex < this.allItems.length;
let title = this.allItems[0]?.category_name || this.currentView.data?.category?.replace(/-/g, ' ');
if (this.currentView.data?.subcategory) {
title += ` - ${this.allItems[0]?.subcategory_name || this.currentView.data.subcategory.replace(/-/g, ' ')}`;
}
this.createHeader(title, `${this.allItems.length} projects found`);
this.createBackButton();
displayedItems.forEach((project, index) => {
const card = this.createProjectCard(project, index);
this.portfolioGrid.appendChild(card);
});
this.updateToggleButton(hasMoreItems);
this.addAnimations();
}
// View More/Less Functionality
viewMore() {
this.currentPage++;
this.displayPaginatedProjects();
setTimeout(() => {
this.portfolioGrid.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 100);
}
viewLess() {
this.currentPage = 1;
this.displayPaginatedProjects();
setTimeout(() => {
this.portfolioGrid.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 100);
}
updateToggleButton(hasMoreItems) {
if (!this.toggleBtn) return;
if (this.allItems.length <= this.itemsPerPage) {
this.hideToggleButton();
return;
}
this.showToggleButton();
if (this.currentPage * this.itemsPerPage >= this.allItems.length) {
this.toggleBtn.innerHTML = `
View Less
`;
this.toggleBtn.classList.add('see-less');
this.toggleBtn.onclick = () => this.viewLess();
} else {
this.toggleBtn.innerHTML = `
View More Projects
`;
this.toggleBtn.classList.remove('see-less');
this.toggleBtn.onclick = () => this.viewMore();
}
}
// FIXED: Card Creation Methods with Video Support
createCategoryCard(category, index) {
const card = document.createElement('div');
card.className = 'portfolio-item';
card.dataset.index = index;
const displayName = this.formatDisplayName(category.name);
card.innerHTML = `
${displayName}
Service Category
${category.description ? `
${this.escapeHtml(category.description)}
` : ''}
${category.project_count || 0} Projects
Explore Here
`;
const exploreBtn = card.querySelector('.explore-btn');
exploreBtn.addEventListener('click', this.debounce(() => {
this.navigateTo('subcategories', category.slug, category.name);
}, 300));
return card;
}
createSubcategoryCard(subcategory, categorySlug, index) {
const card = document.createElement('div');
card.className = 'portfolio-item';
card.dataset.index = index;
const displayName = this.formatDisplayName(subcategory.name);
card.innerHTML = `
${displayName}
${subcategory.category_name}
${subcategory.description ? `
${this.escapeHtml(subcategory.description)}
` : ''}
${subcategory.project_count || 0} Projects
Explore Projects
`;
const exploreBtn = card.querySelector('.explore-btn');
exploreBtn.addEventListener('click', this.debounce(() => {
this.navigateTo('projects', {
category: categorySlug,
subcategory: subcategory.slug
}, `${this.formatDisplayName(categorySlug)} - ${displayName}`);
}, 300));
return card;
}
createProjectCard(project, index) {
const card = document.createElement('div');
card.className = 'portfolio-item';
card.dataset.index = index;
const isVideo = project.media_type === 'video';
let thumbnailUrl = project.thumbnail_url || '';
// Handle thumbnail for video projects
if (!thumbnailUrl && isVideo) {
thumbnailUrl = '../assets/portfolio/video-placeholder.jpg';
} else if (!thumbnailUrl) {
thumbnailUrl = '../assets/portfolio/placeholder.jpg';
}
// Fix URL if needed
thumbnailUrl = this.fixImageUrl(thumbnailUrl);
card.innerHTML = `
${isVideo ? `
` : `
`}
${this.escapeHtml(project.title)}
${this.formatDisplayName(project.category_name)}
${project.subcategory_name ? ` āŗ ${this.formatDisplayName(project.subcategory_name)}` : ''}
${project.tags ? `
${project.tags.split(',').slice(0, 3).map(tag =>
`${this.escapeHtml(tag.trim())} `
).join('')}
${project.tags.split(',').length > 3 ? '+ more ' : ''}
` : ''}
View Details
${project.external_url ? `
Live Demo
` : ''}
`;
const detailBtn = card.querySelector('.detail-btn');
detailBtn.addEventListener('click', this.debounce(() => {
this.openProjectDetail(project.id, project.slug);
}, 300));
return card;
}
// URL Generation Methods
getProjectDetailUrl(projectId, projectSlug) {
return `${this.pageUrls.projectDetail}?id=${projectId}&slug=${projectSlug}`;
}
getCategoryGalleryUrl(categorySlug) {
return `${this.pageUrls.categoryGallery}?category=${categorySlug}`;
}
getSubcategoryGalleryUrl(categorySlug, subcategorySlug) {
return `${this.pageUrls.subcategoryGallery}?category=${categorySlug}&subcategory=${subcategorySlug}`;
}
// Navigation to Dedicated Pages
openProjectDetail(projectId, projectSlug) {
window.location.href = this.getProjectDetailUrl(projectId, projectSlug);
}
openCategoryGallery(categorySlug) {
window.location.href = this.getCategoryGalleryUrl(categorySlug);
}
openSubcategoryGallery(categorySlug, subcategorySlug) {
window.location.href = this.getSubcategoryGalleryUrl(categorySlug, subcategorySlug);
}
// UI Components
createHeader(title, subtitle = '') {
const header = document.createElement('div');
header.className = 'portfolio-header';
header.style.gridColumn = '1 / -1';
header.style.textAlign = 'center';
header.style.marginBottom = '40px';
header.innerHTML = `
${title}
${subtitle ? `${subtitle}
` : ''}
`;
this.portfolioGrid.appendChild(header);
}
createBackButton() {
const backBtn = document.createElement('div');
backBtn.className = 'back-button';
backBtn.style.gridColumn = '1 / -1';
backBtn.style.textAlign = 'center';
backBtn.style.marginBottom = '20px';
backBtn.innerHTML = `
Back
`;
this.portfolioGrid.insertBefore(backBtn, this.portfolioGrid.firstChild);
}
// NEW: Fix image URL helper
fixImageUrl(url) {
if (!url) return '../assets/portfolio/placeholder.jpg';
// If URL already starts with http, https, /, or ../, return as-is
if (url.match(/^(http|https|\/|\.\.\/)/)) {
return url;
}
// Otherwise, prepend ../
return '../' + url;
}
// Performance Optimizations
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Utility Methods
showLoading() {
this.portfolioGrid.innerHTML = `
`;
}
showEmptyState(message) {
this.portfolioGrid.innerHTML = `
Nothing Here Yet
${message}
${this.navigationStack.length > 0 ? `
Go Back
` : ''}
`;
}
showError(message) {
this.portfolioGrid.innerHTML = `
Something Went Wrong
${message}
Try Again
`;
}
retry() {
switch (this.currentView.type) {
case 'categories':
this.displayCategories(this.allCategories);
break;
case 'subcategories':
this.loadSubcategories(this.currentView.data);
break;
case 'projects':
this.loadProjects(this.currentView.data.category, this.currentView.data.subcategory);
break;
}
}
addAnimations() {
setTimeout(() => {
document.querySelectorAll('.portfolio-item').forEach((item, index) => {
item.style.animationDelay = `${index * 0.1}s`;
});
}, 100);
}
hideToggleButton() {
if (this.toggleBtn) {
this.toggleBtn.style.display = 'none';
this.toggleBtn.onclick = null;
}
}
showToggleButton() {
if (this.toggleBtn) {
this.toggleBtn.style.display = 'inline-flex';
}
}
formatDisplayName(slug) {
if (!slug) return '';
return slug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
}
escapeHtml(unsafe) {
if (!unsafe) return '';
return unsafe
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
setupEventListeners() {
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' && this.navigationStack.length > 0) {
e.preventDefault();
this.goBack();
}
});
window.addEventListener('popstate', (e) => {
if (e.state && e.state.portfolioView) {
this.currentView = e.state.portfolioView;
this.retry();
}
});
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
this.clearCache();
}
});
}
clearCache() {
this.cache.clear();
console.log('Portfolio cache cleared');
}
refresh() {
this.clearCache();
this.retry();
}
filterByCategory(categorySlug) {
const button = this.filterContainer.querySelector(`[data-filter="${categorySlug}"]`);
if (button) {
button.click();
} else {
this.navigateTo('subcategories', categorySlug);
}
}
getCurrentState() {
return {
view: this.currentView,
page: this.currentPage,
totalItems: this.allItems.length,
displayedItems: Math.min(this.currentPage * this.itemsPerPage, this.allItems.length)
};
}
preloadNextPageImages() {
if (this.currentPage * this.itemsPerPage < this.allItems.length) {
const nextItems = this.allItems.slice(
this.currentPage * this.itemsPerPage,
(this.currentPage + 1) * this.itemsPerPage
);
nextItems.forEach(item => {
const img = new Image();
img.src = this.fixImageUrl(item.thumbnail_url) ||
(item.media_type === 'video' ? '../assets/portfolio/video-placeholder.jpg' : '../assets/portfolio/placeholder.jpg');
});
}
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
window.portfolioManager = new PortfolioManager();
setInterval(() => {
if (window.portfolioManager) {
window.portfolioManager.clearCache();
}
}, 10 * 60 * 1000);
});
// Global navigation helpers
window.navigateToProject = function(projectId, projectSlug) {
if (window.portfolioManager) {
window.portfolioManager.openProjectDetail(projectId, projectSlug);
} else {
window.location.href = `../backend/project-detail.php?id=${projectId}&slug=${projectSlug}`;
}
};
window.navigateToCategory = function(categorySlug) {
if (window.portfolioManager) {
window.portfolioManager.openCategoryGallery(categorySlug);
} else {
window.location.href = `../backend/category-gallery.php?category=${categorySlug}`;
}
};
window.navigateToSubcategory = function(categorySlug, subcategorySlug) {
if (window.portfolioManager) {
window.portfolioManager.openSubcategoryGallery(categorySlug, subcategorySlug);
} else {
window.location.href = `../backend/subcategory-gallery.php?category=${categorySlug}&subcategory=${subcategorySlug}`;
}
};
window.isPortfolioReady = function() {
return !!window.portfolioManager;
};
document.addEventListener('visibilitychange', function() {
if (!document.hidden && window.portfolioManager) {
window.portfolioManager.clearCache();
}
});
if (typeof module !== 'undefined' && module.exports) {
module.exports = PortfolioManager;
}