Spaces:
Sleeping
Sleeping
| // ======================================== | |
| // APP.JS — Shared Components & Logic | |
| // ======================================== | |
| // SVG Icons | |
| const ICONS = { | |
| search: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>', | |
| cart: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></svg>', | |
| heart: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>', | |
| heartFill: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>', | |
| user: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>', | |
| eye: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>', | |
| minus: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14"/></svg>', | |
| plus: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M12 5v14"/></svg>', | |
| trash: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>', | |
| x: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>', | |
| star: '★', | |
| starHalf: '★', | |
| filter: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>', | |
| grid: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>', | |
| list: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></svg>', | |
| check: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>', | |
| arrowRight: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>', | |
| truck: '🚚', shield: '🛡️', clock: '⏰', refresh: '🔄', | |
| instagram: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="2" y="2" width="20" height="20" rx="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><path d="M17.5 6.5h.01"/></svg>', | |
| telegram: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M11.944 0A12 12 0 1 0 24 12.056A12.013 12.013 0 0 0 11.944 0Zm5.654 8.22l-1.8 8.49c-.136.612-.492.764-.998.476l-2.756-2.032l-1.33 1.278c-.147.147-.27.27-.554.27l.198-2.8l5.1-4.608c.222-.198-.048-.308-.344-.11l-6.306 3.972l-2.716-.848c-.59-.184-.602-.59.124-.874l10.62-4.094c.49-.176.92.12.762.87Z"/></svg>', | |
| facebook: '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>', | |
| }; | |
| function getStarsHTML(rating) { | |
| let html = ''; | |
| for (let i = 1; i <= 5; i++) { | |
| html += i <= Math.round(rating) ? '<span style="color:var(--clr-warning)">★</span>' : '<span style="color:var(--clr-gray)">★</span>'; | |
| } | |
| return html; | |
| } | |
| // Current page detection | |
| function getCurrentPage() { | |
| const path = window.location.pathname.split('/').pop().split('?')[0]; | |
| if (path === 'catalog.html') return 'catalog'; | |
| if (path === 'product.html') return 'product'; | |
| if (path === 'cart.html') return 'cart'; | |
| if (path === 'checkout.html') return 'checkout'; | |
| if (path === 'wishlist.html') return 'wishlist'; | |
| if (path === 'profile.html') return 'profile'; | |
| return 'home'; | |
| } | |
| // ======================================== | |
| // NAVBAR | |
| // ======================================== | |
| function renderNavbar() { | |
| const page = getCurrentPage(); | |
| const nav = document.createElement('nav'); | |
| nav.className = 'navbar'; | |
| nav.id = 'navbar'; | |
| nav.innerHTML = ` | |
| <div class="navbar-inner"> | |
| <a href="index.html" class="navbar-logo">M-TEXTILE</a> | |
| <div class="navbar-nav" id="navLinks"> | |
| <a href="index.html" class="${page === 'home' ? 'active' : ''}">Bosh sahifa</a> | |
| <a href="catalog.html" class="${page === 'catalog' ? 'active' : ''}">Katalog</a> | |
| <a href="catalog.html?category=kiyimlar">Kiyimlar</a> | |
| <a href="catalog.html?category=shimlar">Shimlar</a> | |
| <a href="catalog.html?category=galistuklar">Galistuklar</a> | |
| </div> | |
| <div class="navbar-actions"> | |
| <button class="btn btn-icon btn-ghost" id="navSearchBtn" title="Qidirish"> | |
| ${ICONS.search} | |
| </button> | |
| <button class="btn btn-icon btn-ghost" id="themeToggle" title="Rejimni o'zgartirish"> | |
| <span id="themeIcon">🌙</span> | |
| </button> | |
| <a href="wishlist.html" class="btn btn-icon btn-ghost" title="Sevimlilar">${ICONS.heart}</a> | |
| <a href="cart.html" class="btn btn-icon btn-ghost cart-badge" title="Savatcha" id="navCartBtn"> | |
| ${ICONS.cart} | |
| <span class="badge-count" id="cartBadge" style="display:none">0</span> | |
| </a> | |
| <a href="#" class="btn btn-icon btn-ghost" title="Profil" id="navProfileBtn">${ICONS.user}</a> | |
| <div class="hamburger" id="hamburger"> | |
| <span></span><span></span><span></span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| document.body.prepend(nav); | |
| // Mobile nav | |
| const mobileNav = document.createElement('div'); | |
| mobileNav.className = 'mobile-nav'; | |
| mobileNav.id = 'mobileNav'; | |
| mobileNav.innerHTML = ` | |
| <a href="index.html" class="${page === 'home' ? 'active' : ''}">🏠 Bosh sahifa</a> | |
| <a href="catalog.html" class="${page === 'catalog' ? 'active' : ''}">📦 Katalog</a> | |
| <a href="catalog.html?category=kiyimlar">👔 Kiyimlar</a> | |
| <a href="catalog.html?category=formalar">🎓 Formalar</a> | |
| <a href="catalog.html?category=shimlar">👖 Shimlar</a> | |
| <a href="catalog.html?category=galistuklar">🎀 Galistuklar</a> | |
| <a href="catalog.html?category=aksessuarlar">👜 Aksessuarlar</a> | |
| <a href="wishlist.html">❤️ Sevimlilar</a> | |
| <a href="cart.html">🛒 Savatcha</a> | |
| <a href="profile.html">👤 Profil</a> | |
| `; | |
| document.body.prepend(mobileNav); | |
| // Hamburger toggle | |
| const hamburger = document.getElementById('hamburger'); | |
| hamburger.addEventListener('click', () => { | |
| hamburger.classList.toggle('active'); | |
| mobileNav.classList.toggle('active'); | |
| }); | |
| // Profile button — login modal or profile page | |
| document.getElementById('navProfileBtn')?.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| if (isLoggedIn()) { window.location.href = 'profile.html'; } | |
| else { openLoginModal(); } | |
| }); | |
| // Scroll effect | |
| window.addEventListener('scroll', () => { | |
| nav.classList.toggle('scrolled', window.scrollY > 50); | |
| }); | |
| document.getElementById('navSearchBtn')?.addEventListener('click', openSearchModal); | |
| // Initialize Search Modal | |
| renderSearchModal(); | |
| updateCartBadge(); | |
| } | |
| function updateCartBadge() { | |
| const badge = document.getElementById('cartBadge'); | |
| if (!badge) return; | |
| const count = getCartCount(); | |
| badge.textContent = count; | |
| badge.style.display = count > 0 ? 'flex' : 'none'; | |
| } | |
| // ======================================== | |
| // LIVE SEARCH | |
| // ======================================== | |
| function showSearchResults(query) { | |
| const dropdown = document.getElementById('searchDropdown'); | |
| if (!dropdown) return; | |
| const q = query.trim(); | |
| if (q.length < 2) { dropdown.classList.remove('active'); return; } | |
| const results = searchProducts(q).slice(0, 6); | |
| if (results.length === 0) { | |
| dropdown.innerHTML = '<div class="search-dropdown-empty">Mahsulot topilmadi</div>'; | |
| } else { | |
| dropdown.innerHTML = results.map(p => ` | |
| <a href="product.html?id=${p.id}" class="search-dropdown-item"> | |
| <img src="${p.images[0]}" alt="${p.name}"> | |
| <div class="search-dropdown-info"> | |
| <div class="search-dropdown-name">${p.name}</div> | |
| <div class="search-dropdown-price">${formatPrice(p.price)}</div> | |
| </div> | |
| </a> | |
| `).join('') + `<a href="catalog.html?search=${encodeURIComponent(q)}" class="search-dropdown-all">Barcha natijalar →</a>`; | |
| } | |
| dropdown.classList.add('active'); | |
| } | |
| // ======================================== | |
| // ADVANCED SEARCH MODAL | |
| // ======================================== | |
| function renderSearchModal() { | |
| if (document.getElementById('searchModalOverlay')) return; | |
| const modalHTML = ` | |
| <div class="search-modal-overlay" id="searchModalOverlay"> | |
| <div class="search-modal"> | |
| <div class="search-modal-header"> | |
| <div class="search-modal-input-wrapper"> | |
| ${ICONS.search} | |
| <input type="text" placeholder="Mahsulot yoki kategoriya nomi..." id="modalSearchInput" autocomplete="off"> | |
| </div> | |
| <button class="btn btn-icon btn-ghost" onclick="closeSearchModal()">${ICONS.x}</button> | |
| </div> | |
| <div class="search-modal-body"> | |
| <div id="searchModalSuggestions" class="search-suggestions"> | |
| <h4 style="margin-bottom:1rem;font-size:0.9rem;color:var(--clr-text-secondary);">Ko'p qidiriladiganlar:</h4> | |
| <div style="display:flex;gap:0.5rem;flex-wrap:wrap;"> | |
| <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Ko\\'ylak')">Ko'ylak</span> | |
| <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Kostyum')">Kostyum</span> | |
| <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Kurtka')">Kurtka</span> | |
| <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Shim')">Shim</span> | |
| </div> | |
| </div> | |
| <div id="searchModalResults" class="search-results-grid" style="display:none;margin-top:1.5rem;display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1rem;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| document.body.insertAdjacentHTML('beforeend', modalHTML); | |
| document.getElementById('searchModalOverlay').addEventListener('click', (e) => { | |
| if (e.target.id === 'searchModalOverlay') closeSearchModal(); | |
| }); | |
| const input = document.getElementById('modalSearchInput'); | |
| let debounceTimeout; | |
| input.addEventListener('input', () => { | |
| clearTimeout(debounceTimeout); | |
| debounceTimeout = setTimeout(() => handleModalSearch(input.value), 250); | |
| }); | |
| } | |
| function openSearchModal() { | |
| const overlay = document.getElementById('searchModalOverlay'); | |
| if (!overlay) return; | |
| overlay.classList.add('active'); | |
| document.body.style.overflow = 'hidden'; | |
| setTimeout(() => document.getElementById('modalSearchInput').focus(), 100); | |
| } | |
| function closeSearchModal() { | |
| const overlay = document.getElementById('searchModalOverlay'); | |
| if (overlay) { | |
| overlay.classList.remove('active'); | |
| document.body.style.overflow = ''; | |
| } | |
| } | |
| function setSearchQuery(q) { | |
| const input = document.getElementById('modalSearchInput'); | |
| input.value = q; | |
| handleModalSearch(q); | |
| } | |
| function highlightText(text, query) { | |
| if (!query) return text; | |
| const regex = new RegExp(`(${query})`, 'gi'); | |
| return text.replace(regex, '<mark style="background:var(--clr-accent);color:var(--clr-bg);padding:0 2px;border-radius:2px;">$1</mark>'); | |
| } | |
| function handleModalSearch(query) { | |
| const resultsContainer = document.getElementById('searchModalResults'); | |
| const suggestions = document.getElementById('searchModalSuggestions'); | |
| const q = query.trim().toLowerCase(); | |
| if (q.length < 2) { | |
| resultsContainer.style.display = 'none'; | |
| suggestions.style.display = 'block'; | |
| return; | |
| } | |
| suggestions.style.display = 'none'; | |
| resultsContainer.style.display = 'grid'; | |
| const results = searchProducts(q).slice(0, 8); | |
| if (results.length === 0) { | |
| resultsContainer.innerHTML = '<div style="grid-column:1/-1;text-align:center;padding:2rem;color:var(--clr-text-secondary);">Hech narsa topilmadi</div>'; | |
| } else { | |
| resultsContainer.innerHTML = results.map(p => ` | |
| <a href="product.html?id=${p.id}" style="display:flex;gap:1rem;background:var(--clr-surface);padding:0.75rem;border-radius:var(--radius-md);border:1px solid var(--clr-border);text-decoration:none;transition:0.2s ease;"> | |
| <img src="${p.images[0]}" alt="${p.name}" style="width:60px;height:60px;object-fit:cover;border-radius:var(--radius-sm);"> | |
| <div> | |
| <div style="font-weight:600;color:var(--clr-text-primary);margin-bottom:0.25rem;">${highlightText(p.name, q)}</div> | |
| <div style="color:var(--clr-text-secondary);font-size:0.85rem;margin-bottom:0.25rem;">${highlightText(p.category, q)}</div> | |
| <div style="color:var(--clr-accent);font-weight:600;">${formatPrice(p.price)}</div> | |
| </div> | |
| </a> | |
| `).join(''); | |
| } | |
| } | |
| // ======================================== | |
| // FOOTER | |
| // ======================================== | |
| function renderFooter() { | |
| const settings = JSON.parse(localStorage.getItem('mtextile_settings')) || {}; | |
| const globalAddress = settings.address || "Toshkent, O'zbekiston"; | |
| const footer = document.createElement('footer'); | |
| footer.className = 'footer'; | |
| footer.innerHTML = ` | |
| <div class="container"> | |
| <div class="footer-grid"> | |
| <div class="footer-brand"> | |
| <div class="footer-brand-name">M-TEXTILE</div> | |
| <p class="footer-brand-desc">Eng sifatli kiyim-kechak mahsulotlarini uyingizdan turib oling. Barcha buyurtmalar do'kondan olib ketilishi kerak.</p> | |
| <div class="footer-social" style="margin-top: 1.5rem;"> | |
| <a href="#" title="Instagram">${ICONS.instagram}</a> | |
| <a href="#" title="Telegram">${ICONS.telegram}</a> | |
| <a href="#" title="Facebook">${ICONS.facebook}</a> | |
| </div> | |
| </div> | |
| <div> | |
| <h4 class="footer-heading">Kategoriyalar</h4> | |
| <div class="footer-links"> | |
| <a href="catalog.html?category=kiyimlar">Kiyimlar</a> | |
| <a href="catalog.html?category=formalar">Formalar</a> | |
| <a href="catalog.html?category=shimlar">Shimlar</a> | |
| <a href="catalog.html?category=galistuklar">Galistuklar</a> | |
| <a href="catalog.html?category=aksessuarlar">Aksessuarlar</a> | |
| </div> | |
| </div> | |
| <div> | |
| <h4 class="footer-heading">Yordam</h4> | |
| <div class="footer-links"> | |
| <a href="#">Do'kondan olib ketish usuli</a> | |
| <a href="#">Qaytarish siyosati</a> | |
| <a href="#">To'lov usullari</a> | |
| <a href="#">Ko'p so'raladigan savollar</a> | |
| </div> | |
| </div> | |
| <div> | |
| <h4 class="footer-heading">Aloqa</h4> | |
| <div class="footer-links"> | |
| <a href="tel:+998996083712">+998 99 608 37 12</a> | |
| <a href="mailto:info@m-textile.uz">info@m-textile.uz</a> | |
| <a href="#">${globalAddress}</a> | |
| <a href="#">Dushanba-Juma: 9:00-18:00</a> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="footer-bottom"> | |
| <p>© 2026 M-TEXTILE. Barcha huquqlar himoyalangan.</p> | |
| <div style="display:flex; gap: 1.5rem;"> | |
| <a href="#">Maxfiylik siyosati</a> | |
| <a href="#">Foydalanish shartlari</a> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(footer); | |
| } | |
| // ======================================== | |
| // TOAST NOTIFICATIONS | |
| // ======================================== | |
| function initToastContainer() { | |
| if (!document.getElementById('toastContainer')) { | |
| const container = document.createElement('div'); | |
| container.className = 'toast-container'; | |
| container.id = 'toastContainer'; | |
| document.body.appendChild(container); | |
| } | |
| } | |
| function showToast(title, message, type = 'success', duration = 3000) { | |
| initToastContainer(); | |
| const container = document.getElementById('toastContainer'); | |
| const icons = { success: '✅', error: '❌', info: 'ℹ️' }; | |
| const toast = document.createElement('div'); | |
| toast.className = `toast toast-${type}`; | |
| toast.innerHTML = ` | |
| <span class="toast-icon">${icons[type] || '📢'}</span> | |
| <div class="toast-content"> | |
| <div class="toast-title">${title}</div> | |
| ${message ? `<div class="toast-message">${message}</div>` : ''} | |
| </div> | |
| <button class="toast-close" onclick="this.closest('.toast').remove()">${ICONS.x}</button> | |
| `; | |
| container.appendChild(toast); | |
| setTimeout(() => { | |
| toast.classList.add('toast-exit'); | |
| setTimeout(() => toast.remove(), 300); | |
| }, duration); | |
| } | |
| // ======================================== | |
| // PRODUCT CARD RENDERER | |
| // ======================================== | |
| function renderProductCard(product) { | |
| const wishlisted = isInWishlist(product.id); | |
| const card = document.createElement('div'); | |
| card.className = 'product-card'; | |
| card.innerHTML = ` | |
| <div class="product-card-image"> | |
| <img src="${product.images[0]}" alt="${product.name}" loading="lazy"> | |
| <div class="product-card-badges"> | |
| ${product.isNew ? '<span class="badge badge-new">YANGI</span>' : ''} | |
| ${product.discount > 0 ? `<span class="badge badge-discount">-${product.discount}%</span>` : ''} | |
| </div> | |
| <div class="product-card-actions"> | |
| <button class="product-card-action-btn ${wishlisted ? 'wishlisted' : ''}" onclick="event.stopPropagation();handleWishlist(${product.id},this)" title="Sevimlilar"> | |
| ${wishlisted ? ICONS.heartFill : ICONS.heart} | |
| </button> | |
| <button class="product-card-action-btn" onclick="event.stopPropagation();openQuickView(${product.id})" title="Tezkor ko'rish">${ICONS.eye}</button> | |
| <button class="product-card-action-btn" onclick="event.stopPropagation();quickAddToCart(${product.id}, event)" title="Savatga qo'shish">${ICONS.cart}</button> | |
| </div> | |
| </div> | |
| <div class="product-card-body"> | |
| <div class="product-card-category">${CATEGORIES[product.category]?.name || product.category}</div> | |
| <h3 class="product-card-name">${product.name}</h3> | |
| <div class="product-card-rating"> | |
| <span class="stars">${getStarsHTML(product.rating)}</span> | |
| <span class="count">(${product.reviewCount})</span> | |
| </div> | |
| <div class="product-card-price"> | |
| <span class="price-current">${formatPrice(product.price)}</span> | |
| ${product.oldPrice ? `<span class="price-old">${formatPrice(product.oldPrice)}</span>` : ''} | |
| </div> | |
| </div> | |
| `; | |
| card.addEventListener('click', () => { | |
| window.location.href = `product.html?id=${product.id}`; | |
| }); | |
| return card; | |
| } | |
| function handleWishlist(id, btn) { | |
| toggleWishlist(id); | |
| const wishlisted = isInWishlist(id); | |
| btn.classList.toggle('wishlisted', wishlisted); | |
| btn.innerHTML = wishlisted ? ICONS.heartFill : ICONS.heart; | |
| showToast(wishlisted ? 'Sevimlilarga qo\'shildi' : 'Sevimlilardan olib tashlandi', '', wishlisted ? 'success' : 'info'); | |
| } | |
| function quickAddToCart(id, event) { | |
| id = parseInt(id); | |
| const product = getProductById(id); | |
| if (!product) return; | |
| addToCart(id, product.sizes[0], product.colors[0], 1); | |
| updateCartBadge(); | |
| showToast('Savatga qo\'shildi', product.name, 'success'); | |
| if (event) { | |
| const btn = event.currentTarget || event.target; | |
| let img = btn.closest('.product-card')?.querySelector('img') || btn.closest('.modal')?.querySelector('img') || document.querySelector('#mainImage'); | |
| if (img) flyToCart(img); | |
| } | |
| } | |
| function flyToCart(imgElement) { | |
| if (!imgElement) return; | |
| const cartIcon = document.getElementById('navCartBtn'); | |
| if (!cartIcon) return; | |
| const imgClone = imgElement.cloneNode(true); | |
| const rect = imgElement.getBoundingClientRect(); | |
| const cartRect = cartIcon.getBoundingClientRect(); | |
| imgClone.className = 'flying-img'; | |
| imgClone.style.top = rect.top + 'px'; | |
| imgClone.style.left = rect.left + 'px'; | |
| imgClone.style.width = rect.width + 'px'; | |
| imgClone.style.height = rect.height + 'px'; | |
| document.body.appendChild(imgClone); | |
| requestAnimationFrame(() => { | |
| requestAnimationFrame(() => { | |
| imgClone.style.top = cartRect.top + 'px'; | |
| imgClone.style.left = cartRect.left + 'px'; | |
| imgClone.style.width = '20px'; | |
| imgClone.style.height = '20px'; | |
| imgClone.style.opacity = '0'; | |
| imgClone.style.transform = 'scale(0.5)'; | |
| }); | |
| }); | |
| setTimeout(() => { | |
| imgClone.remove(); | |
| cartIcon.classList.add('bump'); | |
| setTimeout(() => cartIcon.classList.remove('bump'), 300); | |
| }, 700); | |
| } | |
| // ======================================== | |
| // INTERSECTION OBSERVER FOR ANIMATIONS | |
| // ======================================== | |
| function initRevealAnimations() { | |
| // Convert old manual reveals to AOS targets | |
| document.querySelectorAll('.reveal').forEach((el, index) => { | |
| if (!el.hasAttribute('data-aos')) { | |
| el.setAttribute('data-aos', 'fade-up'); | |
| el.setAttribute('data-aos-delay', (index % 4) * 100); | |
| el.classList.remove('reveal'); | |
| } | |
| }); | |
| // Automatically add stagger effects for product grids | |
| document.querySelectorAll('.stagger').forEach(grid => { | |
| Array.from(grid.children).forEach((child, idx) => { | |
| if (!child.hasAttribute('data-aos')) { | |
| child.setAttribute('data-aos', 'fade-up'); | |
| child.setAttribute('data-aos-delay', (idx % 8) * 100); | |
| } | |
| }); | |
| }); | |
| if (typeof AOS !== 'undefined') { | |
| AOS.refreshHard(); | |
| } | |
| } | |
| // ======================================== | |
| // QUICK VIEW MODAL | |
| // ======================================== | |
| function openQuickView(id) { | |
| const product = getProductById(id); | |
| if (!product) return; | |
| trackRecentlyViewed(id); | |
| let overlay = document.getElementById('quickViewOverlay'); | |
| if (!overlay) { | |
| overlay = document.createElement('div'); | |
| overlay.className = 'modal-overlay'; | |
| overlay.id = 'quickViewOverlay'; | |
| overlay.addEventListener('click', (e) => { if (e.target === overlay) closeQuickView(); }); | |
| document.body.appendChild(overlay); | |
| } | |
| const wishlisted = isInWishlist(product.id); | |
| overlay.innerHTML = ` | |
| <div class="modal" style="max-width:800px;"> | |
| <div class="modal-header"> | |
| <h2 class="modal-title">Tezkor ko'rish</h2> | |
| <button class="modal-close" onclick="closeQuickView()">${ICONS.x}</button> | |
| </div> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;"> | |
| <div style="aspect-ratio:3/4;border-radius:var(--radius-lg);overflow:hidden;background:var(--clr-surface);"> | |
| <img src="${product.images[0]}" alt="${product.name}" style="width:100%;height:100%;object-fit:cover;"> | |
| </div> | |
| <div> | |
| <div style="font-size:0.75rem;color:var(--clr-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:0.5rem;">${CATEGORIES[product.category]?.name}</div> | |
| <h3 style="font-size:1.35rem;margin-bottom:0.75rem;">${product.name}</h3> | |
| <div style="margin-bottom:0.75rem;">${getStarsHTML(product.rating)} <span style="color:var(--clr-text-muted);font-size:0.85rem;">(${product.reviewCount})</span></div> | |
| <div style="display:flex;align-items:baseline;gap:0.75rem;margin-bottom:1rem;"> | |
| <span style="font-size:1.5rem;font-weight:700;">${formatPrice(product.price)}</span> | |
| ${product.oldPrice ? `<span style="text-decoration:line-through;color:var(--clr-text-muted);">${formatPrice(product.oldPrice)}</span><span class="badge badge-discount">-${product.discount}%</span>` : ''} | |
| </div> | |
| <p style="color:var(--clr-text-secondary);font-size:0.9rem;line-height:1.7;margin-bottom:1.25rem;">${product.description}</p> | |
| <div style="margin-bottom:0.75rem;font-size:0.85rem;">O'lchamlar: <strong>${product.sizes.join(', ')}</strong></div> | |
| <div style="margin-bottom:1.25rem;font-size:0.85rem;">Ranglar: <strong>${product.colors.map(c => COLORS_MAP[c]?.name || c).join(', ')}</strong></div> | |
| <div style="display:flex;gap:0.75rem;"> | |
| <button class="btn btn-primary" style="flex:1;" onclick="quickAddToCart(${product.id}, event);closeQuickView()">🛒 Savatga</button> | |
| <button class="btn btn-outline" onclick="window.location.href='product.html?id=${product.id}'">${ICONS.eye} Batafsil</button> | |
| <button class="btn btn-outline btn-icon" onclick="handleWishlistQV(${product.id},this)" style="${wishlisted ? 'color:var(--clr-error)' : ''}">${wishlisted ? ICONS.heartFill : ICONS.heart}</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| overlay.classList.add('active'); | |
| document.body.style.overflow = 'hidden'; | |
| } | |
| function closeQuickView() { | |
| const overlay = document.getElementById('quickViewOverlay'); | |
| if (overlay) { overlay.classList.remove('active'); document.body.style.overflow = ''; } | |
| } | |
| function handleWishlistQV(id, btn) { | |
| toggleWishlist(id); | |
| const w = isInWishlist(id); | |
| btn.innerHTML = w ? ICONS.heartFill : ICONS.heart; | |
| btn.style.color = w ? 'var(--clr-error)' : ''; | |
| showToast(w ? 'Sevimlilarga qo\'shildi' : 'Olib tashlandi', '', w ? 'success' : 'info'); | |
| } | |
| // ======================================== | |
| // BACK TO TOP BUTTON | |
| // ======================================== | |
| function initBackToTop() { | |
| const btn = document.createElement('button'); | |
| btn.className = 'back-to-top'; | |
| btn.id = 'backToTop'; | |
| btn.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M18 15l-6-6-6 6"/></svg>'; | |
| btn.title = 'Yuqoriga'; | |
| btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })); | |
| document.body.appendChild(btn); | |
| window.addEventListener('scroll', () => { | |
| btn.classList.toggle('visible', window.scrollY > 400); | |
| }); | |
| } | |
| // ======================================== | |
| // RECENTLY VIEWED PRODUCTS | |
| // ======================================== | |
| function trackRecentlyViewed(id) { | |
| let rv = JSON.parse(localStorage.getItem('mtextile_recent') || '[]'); | |
| rv = rv.filter(i => i !== id); | |
| rv.unshift(id); | |
| if (rv.length > 10) rv = rv.slice(0, 10); | |
| localStorage.setItem('mtextile_recent', JSON.stringify(rv)); | |
| } | |
| function getRecentlyViewed() { | |
| return JSON.parse(localStorage.getItem('mtextile_recent') || '[]'); | |
| } | |
| function renderRecentlyViewed(containerId) { | |
| const container = document.getElementById(containerId); | |
| if (!container) return; | |
| const ids = getRecentlyViewed(); | |
| if (ids.length === 0) { container.closest('.recently-viewed-section')?.remove(); return; } | |
| ids.slice(0, 4).forEach(id => { | |
| const p = getProductById(id); | |
| if (p) container.appendChild(renderProductCard(p)); | |
| }); | |
| } | |
| // ======================================== | |
| // ANIMATED STAT COUNTERS | |
| // ======================================== | |
| function animateCounters() { | |
| document.querySelectorAll('.hero-stat-num[data-count]').forEach(el => { | |
| const target = parseInt(el.dataset.count); | |
| const suffix = el.dataset.suffix || ''; | |
| let current = 0; | |
| const step = Math.max(1, Math.floor(target / 40)); | |
| const timer = setInterval(() => { | |
| current += step; | |
| if (current >= target) { current = target; clearInterval(timer); } | |
| el.textContent = current + suffix; | |
| }, 30); | |
| }); | |
| } | |
| // ======================================== | |
| // SIZE GUIDE MODAL | |
| // ======================================== | |
| function openSizeGuide() { | |
| let overlay = document.getElementById('sizeGuideOverlay'); | |
| if (!overlay) { | |
| overlay = document.createElement('div'); | |
| overlay.className = 'modal-overlay'; | |
| overlay.id = 'sizeGuideOverlay'; | |
| overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSizeGuide(); }); | |
| document.body.appendChild(overlay); | |
| } | |
| overlay.innerHTML = ` | |
| <div class="modal"> | |
| <div class="modal-header"> | |
| <h2 class="modal-title">📏 O'lcham yo'riqnomasi</h2> | |
| <button class="modal-close" onclick="closeSizeGuide()">${ICONS.x}</button> | |
| </div> | |
| <table style="width:100%;border-collapse:collapse;font-size:0.9rem;"> | |
| <thead><tr style="border-bottom:2px solid var(--clr-border);"> | |
| <th style="padding:0.75rem;text-align:left;">O'lcham</th> | |
| <th style="padding:0.75rem;text-align:center;">Ko'krak (sm)</th> | |
| <th style="padding:0.75rem;text-align:center;">Bel (sm)</th> | |
| <th style="padding:0.75rem;text-align:center;">Bo'y (sm)</th> | |
| </tr></thead> | |
| <tbody> | |
| <tr style="border-bottom:1px solid var(--clr-border);"><td style="padding:0.75rem;">XS</td><td style="text-align:center;">82-86</td><td style="text-align:center;">62-66</td><td style="text-align:center;">155-162</td></tr> | |
| <tr style="border-bottom:1px solid var(--clr-border);"><td style="padding:0.75rem;">S</td><td style="text-align:center;">86-90</td><td style="text-align:center;">66-70</td><td style="text-align:center;">162-170</td></tr> | |
| <tr style="border-bottom:1px solid var(--clr-border);"><td style="padding:0.75rem;">M</td><td style="text-align:center;">90-96</td><td style="text-align:center;">70-76</td><td style="text-align:center;">170-176</td></tr> | |
| <tr style="border-bottom:1px solid var(--clr-border);"><td style="padding:0.75rem;">L</td><td style="text-align:center;">96-102</td><td style="text-align:center;">76-82</td><td style="text-align:center;">176-182</td></tr> | |
| <tr style="border-bottom:1px solid var(--clr-border);"><td style="padding:0.75rem;">XL</td><td style="text-align:center;">102-108</td><td style="text-align:center;">82-88</td><td style="text-align:center;">182-188</td></tr> | |
| <tr><td style="padding:0.75rem;">XXL</td><td style="text-align:center;">108-114</td><td style="text-align:center;">88-94</td><td style="text-align:center;">188-194</td></tr> | |
| </tbody> | |
| </table> | |
| <p style="margin-top:1rem;font-size:0.8rem;color:var(--clr-text-muted);">* O'lchamlar taxminiy. Aniq o'lchamlar uchun mahsulot tavsifiga qarang.</p> | |
| </div > | |
| `; | |
| overlay.classList.add('active'); | |
| document.body.style.overflow = 'hidden'; | |
| } | |
| function closeSizeGuide() { | |
| const overlay = document.getElementById('sizeGuideOverlay'); | |
| if (overlay) { overlay.classList.remove('active'); document.body.style.overflow = ''; } | |
| } | |
| // ======================================== | |
| // THEME TOGGLE (Dark / Light) | |
| // ======================================== | |
| function initThemeToggle() { | |
| document.getElementById('themeToggle')?.addEventListener('click', () => { | |
| const cur = document.documentElement.getAttribute('data-theme') || 'dark'; | |
| const next = cur === 'dark' ? 'light' : 'dark'; | |
| applyTheme(next); | |
| localStorage.setItem('mtextile_theme', next); | |
| }); | |
| } | |
| function applyTheme(theme) { | |
| if (theme === 'light') document.documentElement.setAttribute('data-theme', 'light'); | |
| else document.documentElement.removeAttribute('data-theme'); | |
| const icon = document.getElementById('themeIcon'); | |
| if (icon) icon.textContent = theme === 'light' ? '☀️' : '🌙'; | |
| } | |
| // ======================================== | |
| // HERO SLIDER | |
| // ======================================== | |
| function initHeroSlider() { | |
| const slider = document.getElementById('heroSlider'); | |
| if (!slider) return; | |
| const slides = [ | |
| { tag: '✨ Yangi kolleksiya — 2026 Bahor', title: 'Stilingizni <span>kashf eting</span>', desc: 'Eng sifatli kiyim-kechak mahsulotlarini uyingizdan turib xarid qiling.' }, | |
| { tag: '🔥 Maxsus chegirma', title: '<span>30%</span> gacha tejang', desc: 'Tanlangan mahsulotlarga katta chegirmalar!' }, | |
| { tag: '🎓 Maktab formasi', title: 'Sifatli <span>formalar</span>', desc: 'Maktab va ish uchun eng sifatli formal kiyimlar.' } | |
| ]; | |
| let cur = 0, iv; | |
| function render(i) { | |
| const s = slides[i]; | |
| slider.innerHTML = '<div class="hero-slide animate-fade-in"><div class="hero-tag">' + s.tag + '</div><h1 class="hero-title">' + s.title + '</h1><p class="hero-desc">' + s.desc + '</p><div class="hero-actions"><a href="catalog.html" class="btn btn-primary btn-lg">Xarid qilish →</a><a href="catalog.html?filter=new" class="btn btn-outline btn-lg">Yangi kelganlar</a></div></div>'; | |
| document.getElementById('heroDots')?.querySelectorAll('.hero-dot').forEach(function (d, j) { d.classList.toggle('active', j === i); }); | |
| } | |
| var dots = document.getElementById('heroDots'); | |
| if (dots) slides.forEach(function (_, i) { | |
| var d = document.createElement('button'); | |
| d.className = 'hero-dot' + (i === 0 ? ' active' : ''); | |
| d.addEventListener('click', function () { cur = i; render(i); clearInterval(iv); iv = setInterval(next, 5000); }); | |
| dots.appendChild(d); | |
| }); | |
| function next() { cur = (cur + 1) % slides.length; render(cur); } | |
| render(0); | |
| iv = setInterval(next, 5000); | |
| } | |
| // ======================================== | |
| // LOGIN / REGISTER MODAL | |
| // ======================================== | |
| function openLoginModal() { | |
| var ov = document.getElementById('loginOverlay'); | |
| if (!ov) { ov = document.createElement('div'); ov.className = 'modal-overlay'; ov.id = 'loginOverlay'; ov.addEventListener('click', function (e) { if (e.target === ov) closeLoginModal(); }); document.body.appendChild(ov); } | |
| ov.innerHTML = '<div class="modal" style="max-width:440px;"><div class="modal-header"><h2 class="modal-title">Kirish</h2><button class="modal-close" id="loginCloseBtn">' + ICONS.x + '</button></div>' + | |
| '<div style="padding: 0 var(--space-lg);"><button id="googleSignInBtn" class="btn" style="width:100%; display:flex; align-items:center; justify-content:center; gap:12px; padding:14px; background:#fff; color:#333; border:1px solid #ddd; border-radius:var(--radius-md); font-size:var(--fs-base); font-weight:500; cursor:pointer;"><svg width="20" height="20" viewBox="0 0 48 48"><path fill="#FFC107" d="M43.6 20.1H42V20H24v8h11.3C33.9 33.1 29.4 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3 0 5.8 1.1 7.9 3l5.7-5.7C34 5.9 29.3 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.2-2.6-.4-3.9z"/><path fill="#FF3D00" d="M6.3 14.7l6.6 4.8C14.5 15.5 18.8 12 24 12c3 0 5.8 1.1 7.9 3l5.7-5.7C34 5.9 29.3 4 24 4 16.3 4 9.7 8.3 6.3 14.7z"/><path fill="#4CAF50" d="M24 44c5.2 0 9.9-1.8 13.4-5l-6.2-5.2c-2 1.5-4.5 2.4-7.2 2.4-5.3 0-9.8-3.6-11.4-8.5l-6.5 5C9.5 39.6 16.2 44 24 44z"/><path fill="#1976D2" d="M43.6 20.1H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4.1 5.5l6.2 5.2C37 39.1 44 34 44 24c0-1.3-.2-2.6-.4-3.9z"/></svg>Google orqali kirish</button></div>' + | |
| '<div style="display:flex; align-items:center; gap:12px; padding:var(--space-md) var(--space-lg);"><div style="flex:1; height:1px; background:var(--clr-border);"></div><span style="color:var(--clr-text-muted); font-size:var(--fs-sm);">yoki</span><div style="flex:1; height:1px; background:var(--clr-border);"></div></div>' + | |
| '<div class="login-tabs"><div class="login-tab active" data-tab="login" id="tabLogin">Kirish</div><div class="login-tab" data-tab="register" id="tabRegister">Ro\'yxatdan o\'tish</div></div>' + | |
| '<div id="loginForm"><div class="form-group"><label class="form-label">Telefon yoki Email</label><input type="text" class="form-input" id="loginId" placeholder="+998 90 123 45 67"></div><div class="form-group"><label class="form-label">Parol</label><input type="password" class="form-input" id="loginPass" placeholder="Parolingiz"></div><button class="btn btn-primary btn-lg" style="width:100%" id="loginSubmitBtn">Kirish</button></div>' + | |
| '<div id="registerForm" style="display:none;"><div class="form-group"><label class="form-label">Ismingiz</label><input type="text" class="form-input" id="regName" placeholder="To\'liq ism"></div><div class="form-group"><label class="form-label">Telefon</label><input type="tel" class="form-input" id="regPhone" placeholder="+998 90 123 45 67"></div><div class="form-group"><label class="form-label">Email</label><input type="email" class="form-input" id="regEmail" placeholder="email@example.com"></div><div class="form-group"><label class="form-label">Parol</label><input type="password" class="form-input" id="regPass" placeholder="Kamida 6 belgi"></div><button class="btn btn-primary btn-lg" style="width:100%" id="regSubmitBtn">Ro\'yxatdan o\'tish</button></div></div>'; | |
| ov.classList.add('active'); document.body.style.overflow = 'hidden'; | |
| document.getElementById('loginCloseBtn').addEventListener('click', closeLoginModal); | |
| document.getElementById('tabLogin').addEventListener('click', function () { switchLoginTab('login'); }); | |
| document.getElementById('tabRegister').addEventListener('click', function () { switchLoginTab('register'); }); | |
| document.getElementById('loginSubmitBtn').addEventListener('click', doLogin); | |
| document.getElementById('regSubmitBtn').addEventListener('click', doRegister); | |
| document.getElementById('googleSignInBtn').addEventListener('click', async function () { | |
| this.disabled = true; this.style.opacity = '0.7'; | |
| await signInWithGoogle(); | |
| this.disabled = false; this.style.opacity = '1'; | |
| if (isLoggedIn()) { closeLoginModal(); if (typeof updateAuthUI === 'function') updateAuthUI(); } | |
| }); | |
| } | |
| function closeLoginModal() { var ov = document.getElementById('loginOverlay'); if (ov) { ov.classList.remove('active'); document.body.style.overflow = ''; } } | |
| function switchLoginTab(tab) { document.querySelectorAll('.login-tab').forEach(function (t) { t.classList.toggle('active', t.dataset.tab === tab); }); document.getElementById('loginForm').style.display = tab === 'login' ? 'block' : 'none'; document.getElementById('registerForm').style.display = tab === 'register' ? 'block' : 'none'; } | |
| async function doLogin() { | |
| var id = document.getElementById('loginId').value.trim(), pass = document.getElementById('loginPass').value; | |
| if (!id) { showToast('Xatolik', 'Telefon yoki email kiriting', 'error'); return; } | |
| if (!pass || pass.length < 4) { showToast('Xatolik', 'Parol kamida 4 belgi', 'error'); return; } | |
| try { | |
| var result = await API.login(id, pass); | |
| if (!result.success) { showToast('Xatolik', result.message, 'error'); return; } | |
| localStorage.setItem('mtextile_session', JSON.stringify(result.user)); | |
| closeLoginModal(); showToast('Xush kelibsiz!', result.user.name + ', tizimga kirdingiz', 'success'); | |
| if (typeof updateAuthUI === 'function') updateAuthUI(); | |
| } catch (e) { showToast('Xatolik', "Serverga ulanib bo'lmadi", 'error'); } | |
| } | |
| async function doRegister() { | |
| var name = document.getElementById('regName').value.trim(), phone = document.getElementById('regPhone').value.trim(), email = document.getElementById('regEmail').value.trim(), pass = document.getElementById('regPass').value; | |
| if (!name || name.length < 2) { showToast('Xatolik', 'Ismingizni kiriting', 'error'); return; } | |
| if (!phone || phone.length < 9) { showToast('Xatolik', 'Telefon raqam kiriting', 'error'); return; } | |
| if (!pass || pass.length < 6) { showToast('Xatolik', 'Parol kamida 6 belgi', 'error'); return; } | |
| try { | |
| var result = await API.register(name, phone, pass, email); | |
| if (!result.success) { showToast('Xatolik', result.message, 'error'); return; } | |
| localStorage.setItem('mtextile_session', JSON.stringify(result.user)); | |
| closeLoginModal(); showToast('Muvaffaqiyat!', "Ro'yxatdan o'tdingiz!", 'success'); | |
| if (typeof updateAuthUI === 'function') updateAuthUI(); | |
| } catch (e) { showToast('Xatolik', "Serverga ulanib bo'lmadi", 'error'); } | |
| } | |
| // ======================================== | |
| // PRODUCT IMAGE ZOOM | |
| // ======================================== | |
| function initImageZoom() { | |
| var img = document.getElementById('mainImage'); | |
| if (!img) return; | |
| var c = img.parentElement; c.style.overflow = 'hidden'; c.style.cursor = 'zoom-in'; | |
| img.addEventListener('mousemove', function (e) { | |
| var r = c.getBoundingClientRect(); | |
| img.style.transformOrigin = ((e.clientX - r.left) / r.width * 100) + '% ' + ((e.clientY - r.top) / r.height * 100) + '%'; | |
| img.style.transform = 'scale(1.8)'; | |
| }); | |
| img.addEventListener('mouseleave', function () { img.style.transform = 'scale(1)'; img.style.transformOrigin = 'center'; }); | |
| } | |
| // ======================================== | |
| // MICRO-INTERACTIONS | |
| // ======================================== | |
| function initRippleEffect() { | |
| document.addEventListener('click', function (e) { | |
| const btn = e.target.closest('.btn'); | |
| if (!btn) return; | |
| const circle = document.createElement('span'); | |
| const d = Math.max(btn.clientWidth, btn.clientHeight); | |
| const rect = btn.getBoundingClientRect(); | |
| circle.style.width = circle.style.height = d + 'px'; | |
| circle.style.left = e.clientX - rect.left - d / 2 + 'px'; | |
| circle.style.top = e.clientY - rect.top - d / 2 + 'px'; | |
| circle.classList.add('ripple'); | |
| const existing = btn.querySelector('.ripple'); | |
| if (existing) existing.remove(); | |
| btn.appendChild(circle); | |
| setTimeout(() => circle.remove(), 600); | |
| }); | |
| } | |
| function initTiltEffect() { | |
| const cards = document.querySelectorAll('.product-card:not(.tilt-bound)'); | |
| cards.forEach(card => { | |
| card.classList.add('tilt-bound'); | |
| card.addEventListener('mousemove', e => { | |
| const rect = card.getBoundingClientRect(); | |
| const xPct = (e.clientX - rect.left) / rect.width; | |
| const yPct = (e.clientY - rect.top) / rect.height; | |
| card.style.transform = `perspective(1000px) rotateX(${(yPct - 0.5) * -12}deg) rotateY(${(xPct - 0.5) * 12}deg) scale3d(1.02, 1.02, 1.02)`; | |
| }); | |
| card.addEventListener('mouseleave', () => { | |
| card.style.transform = ''; | |
| card.style.transition = 'transform 0.4s ease'; | |
| }); | |
| card.addEventListener('mouseenter', () => { | |
| card.style.transition = 'none'; | |
| }); | |
| }); | |
| } | |
| // ======================================== | |
| // INIT | |
| // ======================================== | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Preloader | |
| const pl = document.getElementById('global-preloader'); | |
| if (pl) { | |
| setTimeout(() => { pl.classList.add('hidden'); setTimeout(() => pl.style.display = 'none', 600); }, 300); | |
| } | |
| // AOS | |
| if (typeof AOS !== 'undefined') AOS.init({ duration: 800, once: true, offset: 50 }); | |
| document.body.classList.add('page-fade-enter'); | |
| setTimeout(() => document.body.classList.remove('page-fade-enter'), 450); | |
| var saved = localStorage.getItem('mtextile_theme') || 'dark'; | |
| applyTheme(saved); | |
| renderNavbar(); | |
| renderFooter(); | |
| initRevealAnimations(); | |
| initBackToTop(); | |
| initThemeToggle(); | |
| initRippleEffect(); | |
| document.addEventListener('cartUpdated', updateCartBadge); | |
| if (getCurrentPage() === 'home') { setTimeout(animateCounters, 600); initHeroSlider(); } | |
| if (getCurrentPage() === 'product') { | |
| var id = new URLSearchParams(window.location.search).get('id'); | |
| if (id) trackRecentlyViewed(parseInt(id)); | |
| setTimeout(initImageZoom, 500); | |
| } | |
| // Try initializing tilt on load, and setup observer for dynamic changes | |
| setTimeout(initTiltEffect, 500); | |
| const observer = new MutationObserver(initTiltEffect); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| }); | |