webupdate

This commit is contained in:
Local Server
2026-01-18 02:22:05 -06:00
parent 6fc159051a
commit 2a2a3d99e5
135 changed files with 54897 additions and 9825 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,870 @@
/* ============================================
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", () => {
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");
});
});
}
// 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>
`;
},
};
// Export for global use
window.SkyArtShop = SkyArtShop;
window.API = API;
window.ProductRenderer = ProductRenderer;
window.BlogRenderer = BlogRenderer;
window.PortfolioRenderer = PortfolioRenderer;