// ======================================== // APP.JS — Shared Components & Logic // ======================================== // SVG Icons const ICONS = { search: '', cart: '', heart: '', heartFill: '', user: '', eye: '', minus: '', plus: '', trash: '', x: '', star: '★', starHalf: '★', filter: '', grid: '', list: '', check: '', arrowRight: '', truck: '🚚', shield: 'đŸ›Ąī¸', clock: '⏰', refresh: '🔄', instagram: '', telegram: '', facebook: '', }; function getStarsHTML(rating) { let html = ''; for (let i = 1; i <= 5; i++) { html += i <= Math.round(rating) ? '★' : '★'; } 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 = ` `; document.body.prepend(nav); // Mobile nav const mobileNav = document.createElement('div'); mobileNav.className = 'mobile-nav'; mobileNav.id = 'mobileNav'; mobileNav.innerHTML = ` 🏠 Bosh sahifa đŸ“Ļ Katalog 👔 Kiyimlar 🎓 Formalar 👖 Shimlar 🎀 Galistuklar 👜 Aksessuarlar â¤ī¸ Sevimlilar 🛒 Savatcha 👤 Profil `; 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 = '
Mahsulot topilmadi
'; } else { dropdown.innerHTML = results.map(p => ` ${p.name}
${p.name}
${formatPrice(p.price)}
`).join('') + `Barcha natijalar →`; } dropdown.classList.add('active'); } // ======================================== // ADVANCED SEARCH MODAL // ======================================== function renderSearchModal() { if (document.getElementById('searchModalOverlay')) return; const modalHTML = `
${ICONS.search}

Ko'p qidiriladiganlar:

Ko'ylak Kostyum Kurtka Shim
`; 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, '$1'); } 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 = '
Hech narsa topilmadi
'; } else { resultsContainer.innerHTML = results.map(p => ` ${p.name}
${highlightText(p.name, q)}
${highlightText(p.category, q)}
${formatPrice(p.price)}
`).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 = `
`; 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 = ` ${icons[type] || 'đŸ“ĸ'}
${title}
${message ? `
${message}
` : ''}
`; 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 = `
${product.name}
${product.isNew ? 'YANGI' : ''} ${product.discount > 0 ? `-${product.discount}%` : ''}
${CATEGORIES[product.category]?.name || product.category}

${product.name}

${getStarsHTML(product.rating)} (${product.reviewCount})
${formatPrice(product.price)} ${product.oldPrice ? `${formatPrice(product.oldPrice)}` : ''}
`; 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 = ` `; 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 = ''; 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 = ` `; 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 kashf eting', desc: 'Eng sifatli kiyim-kechak mahsulotlarini uyingizdan turib xarid qiling.' }, { tag: 'đŸ”Ĩ Maxsus chegirma', title: '30% gacha tejang', desc: 'Tanlangan mahsulotlarga katta chegirmalar!' }, { tag: '🎓 Maktab formasi', title: 'Sifatli formalar', desc: 'Maktab va ish uchun eng sifatli formal kiyimlar.' } ]; let cur = 0, iv; function render(i) { const s = slides[i]; slider.innerHTML = '
' + s.tag + '

' + s.title + '

' + s.desc + '

Xarid qilish →Yangi kelganlar
'; 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 = ''; 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 }); });