Files
SkyArtShop/website/public/assets/js/modern-theme.js

1177 lines
35 KiB
JavaScript
Raw Normal View History

2026-01-18 02:22:05 -06:00
/* ============================================
SKY ART SHOP - MODERN THEME JAVASCRIPT
Complete Frontend Functionality
============================================ */
// Global State
const SkyArtShop = {
cart: JSON.parse(localStorage.getItem("skyart_cart") || "[]"),
wishlist: JSON.parse(localStorage.getItem("skyart_wishlist") || "[]"),
// Initialize
init() {
this.initNavbar();
// Delay slider init slightly to ensure DOM is ready
requestAnimationFrame(() => {
this.initSlider();
});
this.initCart();
this.initWishlist();
this.initWishlistDrawer();
this.initProducts();
this.initAnimations();
this.updateCartCount();
this.updateWishlistCount();
},
// Navbar Functionality
initNavbar() {
const navbar = document.querySelector(".nav-wrapper");
const mobileToggle = document.querySelector(".nav-mobile-toggle");
const navMenu = document.querySelector(".nav-menu");
// Scroll effect
if (navbar) {
window.addEventListener("scroll", () => {
if (window.scrollY > 50) {
navbar.classList.add("scrolled");
} else {
navbar.classList.remove("scrolled");
}
});
}
// Mobile menu toggle
if (mobileToggle && navMenu) {
mobileToggle.addEventListener("click", (e) => {
e.stopPropagation();
navMenu.classList.toggle("open");
mobileToggle.classList.toggle("active");
});
// Close menu when clicking a link
navMenu.querySelectorAll(".nav-link").forEach((link) => {
link.addEventListener("click", () => {
navMenu.classList.remove("open");
mobileToggle.classList.remove("active");
});
});
// Close menu when clicking outside
document.addEventListener("click", (e) => {
if (
navMenu.classList.contains("open") &&
!navMenu.contains(e.target) &&
!mobileToggle.contains(e.target)
) {
navMenu.classList.remove("open");
mobileToggle.classList.remove("active");
}
});
// Close menu when touching outside (for mobile)
document.addEventListener(
"touchstart",
(e) => {
if (
navMenu.classList.contains("open") &&
!navMenu.contains(e.target) &&
!mobileToggle.contains(e.target)
) {
navMenu.classList.remove("open");
mobileToggle.classList.remove("active");
}
},
{ passive: true },
);
}
// Set active nav link
const currentPath = window.location.pathname;
document.querySelectorAll(".nav-link").forEach((link) => {
if (link.getAttribute("href") === currentPath) {
link.classList.add("active");
}
});
},
// Hero Slider
sliderInitialized: false,
sliderInterval: null,
initSlider() {
const slider = document.querySelector(".hero-slider");
if (!slider) return;
// Prevent multiple initializations
if (this.sliderInitialized || slider.dataset.initialized === "true") {
return;
}
this.sliderInitialized = true;
slider.dataset.initialized = "true";
const slides = slider.querySelectorAll(".slide");
const dots = slider.querySelectorAll(".slider-dot");
const prevBtn = slider.querySelector(".slider-arrow.prev");
const nextBtn = slider.querySelector(".slider-arrow.next");
// Need at least 2 slides for auto-play to make sense
if (slides.length < 2) return;
let currentSlide = 0;
let isAnimating = false;
const self = this;
// Clear any existing interval
if (this.sliderInterval) {
clearInterval(this.sliderInterval);
this.sliderInterval = null;
}
// Initialize slides - first slide active, others positioned off-screen right
slides.forEach((slide, i) => {
slide.classList.remove("active", "outgoing");
slide.style.transition = "none";
if (i === 0) {
slide.classList.add("active");
}
});
// Force reflow then re-enable transitions
void slider.offsetWidth;
slides.forEach((slide) => (slide.style.transition = ""));
if (dots[0]) dots[0].classList.add("active");
const showSlide = (index) => {
if (isAnimating) return;
const prevIndex = currentSlide;
currentSlide = (index + slides.length) % slides.length;
if (prevIndex === currentSlide) return;
isAnimating = true;
const oldSlide = slides[prevIndex];
const newSlide = slides[currentSlide];
// Update dots
dots.forEach((dot, i) =>
dot.classList.toggle("active", i === currentSlide),
);
// Position new slide off-screen to the right (no transition)
newSlide.style.transition = "none";
newSlide.classList.remove("outgoing");
newSlide.style.transform = "translateX(100%)";
// Force browser to register the position
void newSlide.offsetWidth;
// Re-enable transition and animate
newSlide.style.transition = "";
newSlide.style.transform = "";
newSlide.classList.add("active");
// Old slide moves out to the left
oldSlide.classList.remove("active");
oldSlide.classList.add("outgoing");
// Cleanup after animation (800ms matches CSS)
setTimeout(() => {
oldSlide.classList.remove("outgoing");
oldSlide.style.transform = "";
isAnimating = false;
}, 850);
};
const nextSlide = () => showSlide(currentSlide + 1);
const prevSlide = () => showSlide(currentSlide - 1);
// Auto-play with 7 second intervals (7000ms)
const startAutoPlay = () => {
// Clear any existing interval first
if (self.sliderInterval) {
clearInterval(self.sliderInterval);
}
self.sliderInterval = setInterval(nextSlide, 7000);
};
const stopAutoPlay = () => {
if (self.sliderInterval) {
clearInterval(self.sliderInterval);
self.sliderInterval = null;
}
};
// Event listeners
if (prevBtn)
prevBtn.addEventListener("click", () => {
stopAutoPlay();
prevSlide();
startAutoPlay();
});
if (nextBtn)
nextBtn.addEventListener("click", () => {
stopAutoPlay();
nextSlide();
startAutoPlay();
});
dots.forEach((dot, i) => {
dot.addEventListener("click", () => {
stopAutoPlay();
showSlide(i);
startAutoPlay();
});
});
// Start auto-play immediately (first slide already initialized)
startAutoPlay();
// Pause on hover
slider.addEventListener("mouseenter", stopAutoPlay);
slider.addEventListener("mouseleave", startAutoPlay);
// Pause when tab is not visible, resume when visible
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
stopAutoPlay();
} else {
startAutoPlay();
}
});
},
// Cart Functionality
initCart() {
const cartBtn = document.querySelector(".cart-btn");
const cartDrawer = document.querySelector(".cart-drawer");
const cartOverlay = document.querySelector(".cart-overlay");
const cartClose = document.querySelector(".cart-close");
if (cartBtn && cartDrawer) {
cartBtn.addEventListener("click", () => this.openCart());
}
if (cartClose) cartClose.addEventListener("click", () => this.closeCart());
if (cartOverlay)
cartOverlay.addEventListener("click", () => this.closeCart());
// Close on escape
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") this.closeCart();
});
},
openCart() {
const cartDrawer = document.querySelector(".cart-drawer");
const cartOverlay = document.querySelector(".cart-overlay");
if (cartDrawer) cartDrawer.classList.add("open");
if (cartOverlay) cartOverlay.classList.add("open");
document.body.style.overflow = "hidden";
this.renderCart();
},
closeCart() {
const cartDrawer = document.querySelector(".cart-drawer");
const cartOverlay = document.querySelector(".cart-overlay");
if (cartDrawer) cartDrawer.classList.remove("open");
if (cartOverlay) cartOverlay.classList.remove("open");
document.body.style.overflow = "";
},
addToCart(product) {
const existingItem = this.cart.find((item) => item.id === product.id);
if (existingItem) {
existingItem.quantity += 1;
} else {
this.cart.push({ ...product, quantity: 1 });
}
this.saveCart();
this.updateCartCount();
this.showNotification(`${product.name} added to cart!`);
this.openCart();
},
removeFromCart(productId) {
this.cart = this.cart.filter((item) => item.id !== productId);
this.saveCart();
this.updateCartCount();
this.renderCart();
},
updateCartQty(productId, change) {
const item = this.cart.find((item) => item.id === productId);
if (item) {
item.quantity += change;
if (item.quantity <= 0) {
this.removeFromCart(productId);
} else {
this.saveCart();
this.renderCart();
}
}
},
saveCart() {
localStorage.setItem("skyart_cart", JSON.stringify(this.cart));
},
updateCartCount() {
const count = this.cart.reduce((sum, item) => sum + item.quantity, 0);
document.querySelectorAll(".cart-count").forEach((el) => {
el.textContent = count;
el.style.display = count > 0 ? "flex" : "none";
});
},
getCartTotal() {
return this.cart.reduce(
(sum, item) => sum + parseFloat(item.price) * item.quantity,
0,
);
},
renderCart() {
const cartItems = document.querySelector(".cart-items");
const cartTotal = document.querySelector(".cart-total-amount");
if (!cartItems) return;
if (this.cart.length === 0) {
cartItems.innerHTML = `
<div class="cart-empty">
<i class="bi bi-cart-x" style="font-size: 3rem; color: var(--text-light); margin-bottom: 16px;"></i>
<p>Your cart is empty</p>
<a href="/shop" class="btn btn-primary" style="margin-top: 16px; min-width: 200px;">Continue Shopping</a>
</div>
`;
} else {
cartItems.innerHTML = this.cart
.map(
(item) => `
<div class="cart-item">
<div class="cart-item-image">
<img src="${item.image || "/assets/images/placeholder.jpg"}" alt="${
item.name
}">
</div>
<div class="cart-item-info">
<div class="cart-item-name">${item.name}</div>
${
item.color
? `<div class="cart-item-color" style="font-size: 0.85rem; color: #666;">Color: ${item.color}</div>`
: ""
}
<div class="cart-item-price">$${parseFloat(item.price).toFixed(
2,
)}</div>
<div class="cart-item-qty">
<button class="qty-btn" onclick="SkyArtShop.updateCartQty('${
item.id
}', -1)">-</button>
<span>${item.quantity}</span>
<button class="qty-btn" onclick="SkyArtShop.updateCartQty('${
item.id
}', 1)">+</button>
<button class="qty-btn" onclick="SkyArtShop.removeFromCart('${
item.id
}')" style="margin-left: auto; color: #e74c3c;">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
`,
)
.join("");
}
if (cartTotal) {
cartTotal.textContent = `$${this.getCartTotal().toFixed(2)}`;
}
},
// Wishlist Functionality
initWishlist() {
this.updateWishlistCount();
},
toggleWishlist(product) {
const index = this.wishlist.findIndex((item) => item.id === product.id);
if (index > -1) {
this.wishlist.splice(index, 1);
this.showNotification(`${product.name} removed from wishlist`);
} else {
this.wishlist.push(product);
this.showNotification(`${product.name} added to wishlist!`);
}
this.saveWishlist();
this.updateWishlistCount();
this.updateWishlistButtons();
},
isInWishlist(productId) {
return this.wishlist.some((item) => item.id === productId);
},
saveWishlist() {
localStorage.setItem("skyart_wishlist", JSON.stringify(this.wishlist));
},
updateWishlistCount() {
const count = this.wishlist.length;
document.querySelectorAll(".wishlist-count").forEach((el) => {
el.textContent = count;
el.style.display = count > 0 ? "flex" : "none";
});
},
updateWishlistButtons() {
document.querySelectorAll(".wishlist-btn").forEach((btn) => {
const productId = btn.dataset.productId;
if (this.isInWishlist(productId)) {
btn.classList.add("active");
btn.innerHTML = '<i class="bi bi-heart-fill"></i>';
} else {
btn.classList.remove("active");
btn.innerHTML = '<i class="bi bi-heart"></i>';
}
});
},
// Wishlist Drawer
initWishlistDrawer() {
const wishlistBtn = document.querySelector(".wishlist-btn-nav");
const wishlistDrawer = document.querySelector(".wishlist-drawer");
const wishlistOverlay = document.querySelector(".wishlist-overlay");
const wishlistClose = document.querySelector(".wishlist-close");
if (wishlistBtn && wishlistDrawer) {
wishlistBtn.addEventListener("click", (e) => {
e.preventDefault();
this.openWishlist();
});
}
if (wishlistClose)
wishlistClose.addEventListener("click", () => this.closeWishlist());
if (wishlistOverlay)
wishlistOverlay.addEventListener("click", () => this.closeWishlist());
// Close on escape
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") this.closeWishlist();
});
},
openWishlist() {
const wishlistDrawer = document.querySelector(".wishlist-drawer");
const wishlistOverlay = document.querySelector(".wishlist-overlay");
if (wishlistDrawer) wishlistDrawer.classList.add("open");
if (wishlistOverlay) wishlistOverlay.classList.add("open");
document.body.style.overflow = "hidden";
this.renderWishlist();
},
closeWishlist() {
const wishlistDrawer = document.querySelector(".wishlist-drawer");
const wishlistOverlay = document.querySelector(".wishlist-overlay");
if (wishlistDrawer) wishlistDrawer.classList.remove("open");
if (wishlistOverlay) wishlistOverlay.classList.remove("open");
document.body.style.overflow = "";
},
renderWishlist() {
const wishlistItems = document.querySelector(".wishlist-items");
if (!wishlistItems) return;
if (this.wishlist.length === 0) {
wishlistItems.innerHTML = `
<div class="wishlist-empty">
<i class="bi bi-heart"></i>
<p>Your wishlist is empty</p>
<p style="font-size: 0.9rem;">Browse our products and add items you love!</p>
</div>
`;
return;
}
wishlistItems.innerHTML = this.wishlist
.map(
(item) => `
<div class="wishlist-item" data-id="${item.id}">
<div class="wishlist-item-image">
<img src="${item.image || "/uploads/default-product.png"}" alt="${
item.name
}">
</div>
<div class="wishlist-item-info">
<div class="wishlist-item-name">${item.name}</div>
${
item.color
? `<div class="wishlist-item-color" style="font-size: 0.85rem; color: #666;">Color: ${item.color}</div>`
: ""
}
<div class="wishlist-item-price">$${parseFloat(item.price).toFixed(
2,
)}</div>
<div class="wishlist-item-actions">
<button class="wishlist-add-to-cart" onclick="SkyArtShop.moveToCart('${
item.id
}')">
<i class="bi bi-cart-plus"></i> Add to Cart
</button>
<button class="wishlist-remove" onclick="SkyArtShop.removeFromWishlistById('${
item.id
}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
`,
)
.join("");
},
moveToCart(productId) {
const item = this.wishlist.find((item) => item.id === productId);
if (item) {
// Pass the full item including color and image
this.addToCart({
id: item.id,
productId: item.productId || item.id,
name: item.name,
price: item.price,
image: item.image,
color: item.color || null,
});
this.removeFromWishlistById(productId);
}
},
removeFromWishlistById(productId) {
const index = this.wishlist.findIndex((item) => item.id === productId);
if (index > -1) {
const item = this.wishlist[index];
this.wishlist.splice(index, 1);
this.saveWishlist();
this.updateWishlistCount();
this.updateWishlistButtons();
this.renderWishlist();
this.showNotification(`${item.name} removed from wishlist`);
}
},
// Products
initProducts() {
// Attach event listeners to product cards
document.querySelectorAll(".add-to-cart-btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
const card = btn.closest(".product-card");
const product = this.getProductFromCard(card);
this.addToCart(product);
});
});
document.querySelectorAll(".wishlist-btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
const card = btn.closest(".product-card");
const product = this.getProductFromCard(card);
this.toggleWishlist(product);
});
});
this.updateWishlistButtons();
},
getProductFromCard(card) {
return {
id: card.dataset.productId,
name:
card.querySelector(".product-name a")?.textContent ||
card.querySelector(".product-name")?.textContent ||
"Product",
price:
card.dataset.productPrice ||
card.querySelector(".price-current")?.textContent?.replace("$", "") ||
"0",
image: card.querySelector(".product-image img")?.src || "",
};
},
// Animations
initAnimations() {
// Intersection Observer for scroll animations
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("fade-in");
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 },
);
document
.querySelectorAll(".section, .product-card, .blog-card, .portfolio-card")
.forEach((el) => {
observer.observe(el);
});
},
// Notifications
showNotification(message, type = "success") {
// Remove existing notifications
document.querySelectorAll(".notification").forEach((n) => n.remove());
const notification = document.createElement("div");
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<i class="bi bi-${
type === "success" ? "check-circle" : "exclamation-circle"
}"></i>
<span>${message}</span>
`;
notification.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background: ${type === "success" ? "#202023" : "#e74c3c"};
color: white;
padding: 16px 24px;
border-radius: 12px;
display: flex;
align-items: center;
gap: 10px;
font-weight: 500;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
z-index: 9999;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = "slideOut 0.3s ease forwards";
setTimeout(() => notification.remove(), 300);
}, 3000);
},
};
// Add notification animations
const style = document.createElement("style");
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
.cart-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
text-align: center;
color: var(--text-light);
}
`;
document.head.appendChild(style);
// Initialize on DOM ready
document.addEventListener("DOMContentLoaded", () => {
SkyArtShop.init();
});
// API Functions
const API = {
baseUrl: "/api",
async get(endpoint, noCache = false) {
try {
const url = noCache
? `${this.baseUrl}${endpoint}${
endpoint.includes("?") ? "&" : "?"
}_t=${Date.now()}`
: `${this.baseUrl}${endpoint}`;
const response = await fetch(url);
const data = await response.json();
return data.success ? data : null;
} catch (error) {
console.error("API Error:", error);
return null;
}
},
async loadFeaturedProducts() {
const data = await this.get("/products/featured?limit=4");
return data?.products || [];
},
async loadAllProducts() {
const data = await this.get("/products");
return data?.products || [];
},
async loadProduct(slug) {
// Always fetch fresh product data to get latest color variants
const data = await this.get(`/products/${slug}`, true);
return data?.product || null;
},
async loadHomepageSections() {
const data = await this.get("/homepage/sections");
return data?.sections || [];
},
async loadBlogPosts() {
const data = await this.get("/blog/posts");
return data?.posts || [];
},
async loadPortfolioProjects() {
const data = await this.get("/portfolio/projects", true);
return data?.projects || [];
},
async loadTeamMembers() {
const data = await this.get("/team-members");
return data?.teamMembers || [];
},
async loadCategories() {
const data = await this.get("/categories");
return data?.categories || [];
},
};
// Product Renderer
const ProductRenderer = {
renderCard(product) {
const primaryImage =
product.images?.find((img) => img.is_primary) || product.images?.[0];
const imageUrl =
primaryImage?.image_url ||
product.imageurl ||
"/assets/images/placeholder.jpg";
const inWishlist = SkyArtShop.isInWishlist(product.id);
return `
<div class="product-card" data-product-id="${
product.id
}" data-product-slug="${
product.slug || product.id
}" data-product-price="${product.price}" style="cursor: pointer;">
<div class="product-image">
<img src="${imageUrl}" alt="${product.name}" loading="lazy">
${
product.isfeatured
? '<div class="product-badges"><span class="product-badge new">Featured</span></div>'
: ""
}
<div class="product-actions">
<button class="product-action-btn wishlist-btn ${
inWishlist ? "active" : ""
}" data-product-id="${product.id}" title="Add to Wishlist">
<i class="bi bi-heart${inWishlist ? "-fill" : ""}"></i>
</button>
<button class="product-action-btn quick-view-btn" title="Quick View">
<i class="bi bi-eye"></i>
</button>
</div>
</div>
<div class="product-info">
<div class="product-category">${product.category || "General"}</div>
<h3 class="product-name">${product.name}</h3>
<div class="product-price">
<span class="price-current">$${parseFloat(product.price).toFixed(
2,
)}</span>
</div>
</div>
<div class="product-footer">
<span class="product-stock ${
product.stockquantity > 0
? product.stockquantity < 10
? "low-stock"
: "in-stock"
: ""
}">
${
product.stockquantity > 0
? product.stockquantity < 10
? `Only ${product.stockquantity} left`
: "In Stock"
: "Out of Stock"
}
</span>
<button class="add-to-cart-btn" ${
product.stockquantity <= 0 ? "disabled" : ""
}>
<i class="bi bi-cart-plus"></i> Add
</button>
</div>
</div>
`;
},
async renderProducts(container, products) {
if (!container) return;
if (products.length === 0) {
container.innerHTML = '<p class="text-center">No products found.</p>';
return;
}
container.innerHTML = products.map((p) => this.renderCard(p)).join("");
SkyArtShop.initProducts();
},
};
// Blog Renderer
const BlogRenderer = {
renderCard(post) {
const date = new Date(post.createdat).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
return `
<article class="blog-card">
<div class="blog-image">
<a href="/blog/${post.slug}">
<img src="${
post.imageurl || "/assets/images/blog-placeholder.jpg"
}" alt="${post.title}" loading="lazy">
</a>
</div>
<div class="blog-content">
<div class="blog-meta">
<span><i class="bi bi-calendar3"></i> ${date}</span>
</div>
<h3 class="blog-title">
<a href="/blog/${post.slug}">${post.title}</a>
</h3>
<p class="blog-excerpt">${post.excerpt || ""}</p>
<a href="/blog/${post.slug}" class="blog-read-more">
Read More <i class="bi bi-arrow-right"></i>
</a>
</div>
</article>
`;
},
};
// Portfolio Renderer
const PortfolioRenderer = {
renderCard(project) {
return `
<div class="portfolio-card" data-project-id="${project.id}">
<img src="${
project.featuredimage || "/assets/images/portfolio-placeholder.jpg"
}" alt="${project.title}" loading="lazy">
<div class="portfolio-overlay">
<h3>${project.title}</h3>
<p>${project.description || ""}</p>
</div>
</div>
`;
},
};
// ============================================
// AUTO-REFRESH SYSTEM
// Automatically refresh frontend when admin makes changes
// ============================================
const AutoRefresh = {
lastChecked: Date.now(),
checkInterval: 5000, // Check every 5 seconds
init() {
// Listen for BroadcastChannel messages from admin
try {
const channel = new BroadcastChannel("skyartshop_updates");
channel.onmessage = (event) => {
console.log("[AutoRefresh] Received update:", event.data);
this.handleUpdate(event.data.type);
};
} catch (e) {
// Fallback to localStorage polling if BroadcastChannel not supported
this.startPolling();
}
// Also start polling as backup
this.startPolling();
// Check on visibility change (when user returns to tab)
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
this.checkForUpdates();
}
});
},
startPolling() {
setInterval(() => {
if (!document.hidden) {
this.checkForUpdates();
}
}, this.checkInterval);
},
checkForUpdates() {
const lastChange = localStorage.getItem("skyartshop_last_change");
if (lastChange && parseInt(lastChange) > this.lastChecked) {
this.lastChecked = parseInt(lastChange);
// Determine what changed
const productsChanged = localStorage.getItem(
"skyartshop_change_products",
);
const settingsChanged = localStorage.getItem(
"skyartshop_change_settings",
);
const pagesChanged = localStorage.getItem("skyartshop_change_pages");
if (
productsChanged &&
parseInt(productsChanged) > this.lastChecked - this.checkInterval
) {
this.handleUpdate("products");
}
if (
settingsChanged &&
parseInt(settingsChanged) > this.lastChecked - this.checkInterval
) {
this.handleUpdate("settings");
}
if (
pagesChanged &&
parseInt(pagesChanged) > this.lastChecked - this.checkInterval
) {
this.handleUpdate("pages");
}
}
},
handleUpdate(type) {
console.log(`[AutoRefresh] Handling ${type} update`);
switch (type) {
case "products":
this.refreshProducts();
break;
case "settings":
this.refreshSettings();
break;
case "pages":
// Soft reload for page content changes
location.reload();
break;
case "all":
location.reload();
break;
default:
// For unknown types, refresh products as default
this.refreshProducts();
}
},
async refreshProducts() {
// Refresh featured products on homepage
const featuredGrid = document.querySelector(".featured-products-grid");
if (featuredGrid) {
try {
const response = await fetch(`/api/products/featured?_t=${Date.now()}`);
const data = await response.json();
if (data.success && data.products) {
featuredGrid.innerHTML = data.products
.map((p) => ProductRenderer.renderCard(p))
.join("");
console.log("[AutoRefresh] Featured products refreshed");
}
} catch (e) {
console.error("[AutoRefresh] Failed to refresh products:", e);
}
}
// Refresh products page if we're on it
const productsGrid = document.querySelector(".products-grid");
if (productsGrid && window.loadProducts) {
window.loadProducts();
console.log("[AutoRefresh] Products page refreshed");
}
},
async refreshSettings() {
// Settings changes might affect header/footer, just reload
location.reload();
},
};
// Initialize auto-refresh on page load
document.addEventListener("DOMContentLoaded", () => {
AutoRefresh.init();
});
// Export for global use
window.SkyArtShop = SkyArtShop;
window.API = API;
window.ProductRenderer = ProductRenderer;
window.BlogRenderer = BlogRenderer;
window.PortfolioRenderer = PortfolioRenderer;
window.AutoRefresh = AutoRefresh;
// ============================================
// MOBILE TOUCH OPTIMIZATION
// Prevent double-tap behavior on mobile devices
// ============================================
(function () {
"use strict";
// Only run on touch devices
if (!("ontouchstart" in window)) return;
// Optimize touch interaction on mobile devices
document.addEventListener("DOMContentLoaded", function () {
// Add mobile-optimized class to body for CSS targeting
document.body.classList.add("mobile-optimized");
// Override hover states on touch devices
if (window.matchMedia("(hover: none)").matches) {
const style = document.createElement("style");
style.id = "mobile-touch-override";
style.textContent = `
/* Remove hover delays on touch devices */
.product-card, .portfolio-card, .blog-card, .btn, .nav-link,
.filter-btn, .add-to-cart-btn, .product-action-btn, .view-project,
.nav-icon-btn, .social-link, .footer-links a, .inspiration-card,
.featured-post, .faq-question, .user-btn, .back-to-top {
-webkit-tap-highlight-color: transparent !important;
touch-action: manipulation !important;
}
/* Immediate visual feedback on touch */
.product-card:active, .portfolio-card:active, .blog-card:active,
.btn:active, .nav-link:active, .filter-btn:active,
.add-to-cart-btn:active, .product-action-btn:active {
transform: scale(0.98) !important;
opacity: 0.85 !important;
transition: transform 0.1s ease, opacity 0.1s ease !important;
}
`;
document.head.appendChild(style);
}
// Fast click handling for immediate response
let touchStartTime = 0;
let touchStartX = 0;
let touchStartY = 0;
document.addEventListener(
"touchstart",
function (e) {
touchStartTime = Date.now();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
},
{ passive: true },
);
document.addEventListener(
"touchend",
function (e) {
const touchEndTime = Date.now();
const touchDuration = touchEndTime - touchStartTime;
// Only process quick taps (not long presses)
if (touchDuration < 300) {
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
// Check if it's a tap (not a swipe)
const deltaX = Math.abs(touchEndX - touchStartX);
const deltaY = Math.abs(touchEndY - touchStartY);
if (deltaX < 10 && deltaY < 10) {
const target = e.target.closest(
"a, button, [data-bs-toggle], .product-card, .portfolio-card, .blog-card, .nav-link, .btn, .filter-btn, .add-to-cart-btn",
);
if (target && !target.disabled) {
// Add immediate visual feedback
target.style.transform = "scale(0.95)";
target.style.transition = "transform 0.1s ease";
// Reset after short delay
setTimeout(() => {
target.style.transform = "";
target.style.transition = "";
}, 100);
}
}
}
},
{ passive: false },
);
});
})();
2026-01-19 01:17:43 -06:00
// Load and display social media links in footer
(function loadFooterSocialLinks() {
document.addEventListener("DOMContentLoaded", async () => {
try {
const response = await fetch("/api/settings");
if (!response.ok) return;
const settings = await response.json();
// Map of social platform to element ID and URL format
const socialMap = {
socialFacebook: { id: "footerFacebook", url: (v) => v },
socialInstagram: { id: "footerInstagram", url: (v) => v },
socialTwitter: { id: "footerTwitter", url: (v) => v },
socialYoutube: { id: "footerYoutube", url: (v) => v },
socialPinterest: { id: "footerPinterest", url: (v) => v },
socialTiktok: { id: "footerTiktok", url: (v) => v },
socialWhatsapp: {
id: "footerWhatsapp",
url: (v) =>
v.startsWith("http")
? v
: `https://wa.me/${v.replace(/[^0-9]/g, "")}`,
},
socialLinkedin: { id: "footerLinkedin", url: (v) => v },
};
for (const [key, config] of Object.entries(socialMap)) {
const value = settings[key];
const el = document.getElementById(config.id);
if (el && value && value.trim()) {
el.href = config.url(value.trim());
el.target = "_blank";
el.rel = "noopener noreferrer";
el.style.display = "";
}
}
} catch (error) {
console.log("Could not load social links:", error);
}
});
})();