// =========================== // 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 = `
${item.name.charAt(0).toUpperCase()}

${item.name}

${item.position ? item.position + (item.company ? ', ' + item.company : '') : (item.company || '')}

${"ā˜…".repeat(item.rating)}

${item.message}

`; // 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 => `

${service.name}

${service.name}

`).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!

`; } // 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 = `
āŒ

Error

${message}

`; 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}

${displayName}

Service Category
${category.description ? `

${this.escapeHtml(category.description)}

` : ''}
${category.project_count || 0} Projects
`; 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}

${displayName}

${subcategory.category_name}
${subcategory.description ? `

${this.escapeHtml(subcategory.description)}

` : ''}
${subcategory.project_count || 0} 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 = `
${this.escapeHtml(project.title)} ${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' : ''}
` : ''}
${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 = ` `; 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 = `

Loading...

`; } showEmptyState(message) { this.portfolioGrid.innerHTML = `

Nothing Here Yet

${message}

${this.navigationStack.length > 0 ? ` ` : ''}
`; } showError(message) { this.portfolioGrid.innerHTML = `

Something Went Wrong

${message}

`; } 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; }