ibrohm commited on
Commit
7b3aac2
·
verified ·
1 Parent(s): 62d0f90

Initial deploy via assistant API

Browse files
.dockerignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ node_modules/
2
+ .env
3
+ .git/
4
+ .vscode/
5
+ *.log
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ # Package.json fayllarini nusxalash va kutubxonalarni o'rnatish
6
+ COPY package*.json ./
7
+ RUN npm install --production
8
+
9
+ # Barcha kodni nusxalash
10
+ COPY . .
11
+
12
+ # Hugging Face Spaces standart porti 7860
13
+ ENV PORT=7860
14
+ EXPOSE 7860
15
+
16
+ # Serverni ishga tushirish
17
+ CMD ["npm", "start"]
admin.html ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz" data-theme="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Admin Panel — M-TEXTILE</title>
8
+ <link rel="stylesheet" href="css/global.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
+ <link rel="stylesheet" href="css/admin.css">
11
+
12
+ <!-- AOS CSS -->
13
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
14
+ </head>
15
+
16
+ <body>
17
+ <!-- Global Preloader -->
18
+ <div id="global-preloader" class="preloader">
19
+ <div class="preloader-spinner"></div>
20
+ </div>
21
+ <!-- Login Modal -->
22
+ <div class="admin-login-overlay" id="adminLogin">
23
+ <div class="admin-login-box">
24
+ <h2>Admin Kirish</h2>
25
+ <input type="password" id="adminPass" class="form-input" placeholder="Parolni kiriting..."
26
+ style="margin-bottom:1rem;">
27
+ <button class="btn btn-primary" style="width:100%" onclick="checkAdmin()">Kirish</button>
28
+ </div>
29
+ </div>
30
+
31
+ <!-- Dashboard -->
32
+ <div class="admin-layout" id="adminDashboard" style="display:none;">
33
+ <aside class="admin-sidebar">
34
+ <div class="admin-logo">M-TEXTILE ADMIN</div>
35
+ <nav class="admin-nav">
36
+ <a href="#stats" class="active" onclick="showSection('stats', this)">📊 Statistika</a>
37
+ <a href="#products" onclick="showSection('products', this)">📦 Mahsulotlar</a>
38
+ <a href="#orders" onclick="showSection('orders', this)">🛒 Buyurtmalar</a>
39
+ <a href="#settings" onclick="showSection('settings', this)">⚙️ Sozlamalar</a>
40
+ <a href="index.html" style="margin-top:auto">🔙 Saytga qaytish</a>
41
+ </nav>
42
+ </aside>
43
+
44
+ <main class="admin-content">
45
+ <header class="admin-header">
46
+ <h2>Boshqaruv Paneli</h2>
47
+ <div style="display:flex;gap:1rem;align-items:center;">
48
+ <input type="text" id="adminProductSearch" placeholder="Mahsulot qidirish..."
49
+ oninput="filterAdminProducts(this.value)" class="form-input" style="width:250px;">
50
+ <button class="btn btn-primary" onclick="openProductModal()">+ Yangi Mahsulot</button>
51
+ </div>
52
+ </header>
53
+
54
+ <div class="admin-stats">
55
+ <div class="stat-card">
56
+ <div class="stat-title">Jami Mahsulotlar</div>
57
+ <div class="stat-value" id="statProducts">0</div>
58
+ </div>
59
+ <div class="stat-card">
60
+ <div class="stat-title">Buyurtmalar</div>
61
+ <div class="stat-value" id="statOrders">0</div>
62
+ </div>
63
+ <div class="stat-card">
64
+ <div class="stat-title">Umumiy Daromad</div>
65
+ <div class="stat-value" id="statRevenue">0 so'm</div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Products Section -->
70
+ <section id="section-products" class="admin-section active">
71
+ <div class="card">
72
+ <table class="admin-table">
73
+ <thead>
74
+ <tr>
75
+ <th>Rasm</th>
76
+ <th>Nomi</th>
77
+ <th>Kategoriya</th>
78
+ <th>Narxi</th>
79
+ <th>Holat</th>
80
+ <th>Amallar</th>
81
+ </tr>
82
+ </thead>
83
+ <tbody id="adminProductsTable">
84
+ <!-- JS load -->
85
+ </tbody>
86
+ </table>
87
+ </div>
88
+ </section>
89
+
90
+ <!-- Orders Section -->
91
+ <section id="section-orders" class="admin-section" style="display:none;">
92
+ <div class="card">
93
+ <table class="admin-table" id="adminOrdersTable">
94
+ <thead>
95
+ <tr>
96
+ <th>№</th>
97
+ <th>Mijoz</th>
98
+ <th>Telefon</th>
99
+ <th>Summa</th>
100
+ <th>Sana</th>
101
+ <th>Holat</th>
102
+ </tr>
103
+ </thead>
104
+ <tbody id="ordersTableBody">
105
+ <!-- JS load -->
106
+ </tbody>
107
+ </table>
108
+ </div>
109
+ </section>
110
+
111
+ <!-- Settings Section -->
112
+ <section id="section-settings" class="admin-section" style="display:none;">
113
+ <div class="card" style="max-width: 600px;">
114
+ <h3>Do'kon Sozlamalari</h3>
115
+ <p style="color:var(--clr-text-secondary);margin-bottom:1.5rem;">Saytning global ma'lumotlari,
116
+ do'kon manzili va xaritasini sozlang.</p>
117
+
118
+ <div class="form-group">
119
+ <label class="form-label">Do'kon Manzili (Matn ko'rinishida)</label>
120
+ <input type="text" id="settingAddress" class="form-input"
121
+ placeholder="Masalan: Toshkent shahar, Chilonzor tumani, 9-kvartal...">
122
+ </div>
123
+
124
+ <div class="form-group" style="margin-top:1rem;">
125
+ <label class="form-label">Google Xarita (Iframe URL yoki Embed Code)</label>
126
+ <textarea id="settingMap" class="form-input" rows="4"
127
+ placeholder="<iframe src='https://www.google.com/maps/embed?...' ...></iframe>"></textarea>
128
+ <p style="font-size:0.8rem;color:var(--clr-text-muted);margin-top:0.5rem;">Eslatma: Google
129
+ qidiruvdan 'Ulashish' -> 'Xaritani joylashtirish' orqali iframe kodini oling.</p>
130
+ </div>
131
+
132
+ <button class="btn btn-primary mt-2xl" onclick="saveAdminSettings()" style="width:100%">Sozlamalarni
133
+ Saqlash</button>
134
+
135
+ <div id="settingsMapPreview"
136
+ style="margin-top:2rem;border-radius:var(--radius-md);overflow:hidden;height:250px;background:var(--clr-surface);display:none;">
137
+ </div>
138
+ </div>
139
+ </section>
140
+ </main>
141
+ </div>
142
+
143
+ <!-- Product Modal -->
144
+ <div class="modal-overlay" id="productModal" style="display:none;z-index:99999;">
145
+ <div class="modal" style="max-width:600px;width:90%">
146
+ <div class="modal-header">
147
+ <h3 id="modalTitle">Mahsulot qo'shish</h3>
148
+ <button class="modal-close" onclick="closeProductModal()">✕</button>
149
+ </div>
150
+ <div class="modal-body">
151
+ <form id="productForm">
152
+ <input type="hidden" id="pId">
153
+ <div class="form-group">
154
+ <label>Nomi</label>
155
+ <input type="text" id="pName" class="form-input" required>
156
+ </div>
157
+ <div class="grid grid-2">
158
+ <div class="form-group">
159
+ <label>Kategoriya</label>
160
+ <select id="pCat" class="form-input" required>
161
+ <option value="kiyimlar">Kiyimlar</option>
162
+ <option value="formalar">Formalar</option>
163
+ <option value="shimlar">Shimlar</option>
164
+ <option value="galistuklar">Galistuklar</option>
165
+ <option value="aksessuarlar">Aksessuarlar</option>
166
+ </select>
167
+ </div>
168
+ <div class="form-group">
169
+ <label>Ost-kategoriya (Subcategory)</label>
170
+ <input type="text" id="pSubCat" class="form-input" placeholder="Masalan: Ko'ylaklar"
171
+ required>
172
+ </div>
173
+ </div>
174
+ <div class="grid grid-2">
175
+ <div class="form-group">
176
+ <label>Narxi (so'm)</label>
177
+ <input type="number" id="pPrice" class="form-input" required>
178
+ </div>
179
+ <div class="form-group">
180
+ <label>Eski Narxi (Yoki bo'sh qoldiring)</label>
181
+ <input type="number" id="pOldPrice" class="form-input">
182
+ </div>
183
+ </div>
184
+ <div class="form-group">
185
+ <label>Rasm URL</label>
186
+ <input type="url" id="pImg" class="form-input" required>
187
+ </div>
188
+ <div class="form-group">
189
+ <label>Tavsif</label>
190
+ <textarea id="pDesc" class="form-input" rows="3" required></textarea>
191
+ </div>
192
+ <div class="grid grid-2">
193
+ <label class="filter-option"><input type="checkbox" id="pNew"> Yangi kelgan</label>
194
+ <label class="filter-option"><input type="checkbox" id="pFeat"> Tavsiya etilgan</label>
195
+ </div>
196
+ <button type="submit" class="btn btn-primary mt-md" style="width:100%">Saqlash</button>
197
+ </form>
198
+ </div>
199
+ </div>
200
+ </div>
201
+
202
+ <script src="js/products.js"></script>
203
+ <script src="js/admin.js"></script>
204
+
205
+ <!-- AOS JS -->
206
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
207
+ </body>
208
+
209
+ </html>
cart.html ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="theme-color" content="#0D1F23">
8
+ <title>Savatcha — M-TEXTILE</title>
9
+ <link rel="stylesheet" href="css/global.css">
10
+ <link rel="stylesheet" href="css/components.css">
11
+ <link rel="stylesheet" href="css/pages.css">
12
+ <link rel="stylesheet" href="css/premium.css">
13
+ <link rel="manifest" href="manifest.json">
14
+
15
+ <!-- AOS CSS -->
16
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
17
+ </head>
18
+
19
+ <body>
20
+ <!-- Global Preloader -->
21
+ <div id="global-preloader" class="preloader">
22
+ <div class="preloader-spinner"></div>
23
+ </div>
24
+ <div class="page-wrapper">
25
+ <div class="page-content">
26
+ <section class="section-padding" style="padding-top: calc(var(--navbar-height) + 2rem);">
27
+ <div class="container">
28
+ <h1 style="margin-bottom: 2rem;">🛒 Savatcha</h1>
29
+ <div class="cart-layout" id="cartContent">
30
+ <div id="cartItems"></div>
31
+ <div class="cart-summary" id="cartSummary">
32
+ <h3 class="cart-summary-title">Buyurtma xulosasi</h3>
33
+ <div class="cart-summary-row"><span>Mahsulotlar</span><span id="sumSubtotal">0 so'm</span>
34
+ </div>
35
+ <div class="cart-summary-row"><span>Chegirma</span><span id="sumDiscount"
36
+ style="color:var(--clr-success)">0 so'm</span></div>
37
+ <div class="promo-input">
38
+ <input type="text" class="form-input" placeholder="Promo-kod" id="promoInput">
39
+ <button class="btn btn-secondary btn-sm" id="promoBtn">→</button>
40
+ </div>
41
+ <div id="promoMsg" style="font-size:0.8rem;margin-bottom:0.5rem;"></div>
42
+ <div class="cart-summary-row total"><span>Jami</span><span id="sumTotal">0 so'm</span></div>
43
+ <a href="checkout.html" class="btn btn-primary btn-lg mt-lg" style="width:100%"
44
+ id="checkoutBtn">Buyurtma berish</a>
45
+ </div>
46
+ </div>
47
+ <div id="emptyCart" style="display:none;">
48
+ <div class="empty-state">
49
+ <div class="empty-state-icon">🛒</div>
50
+ <h3 class="empty-state-title">Savatcha bo'sh</h3>
51
+ <p class="empty-state-text">Xarid qilishni boshlang!</p>
52
+ <a href="catalog.html" class="btn btn-primary">Do'konni ko'rish</a>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </section>
57
+ </div>
58
+ </div>
59
+ <script src="js/products.js"></script>
60
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
61
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
62
+ <script src="js/firebase-config.js"></script>
63
+ <script src="js/api.js"></script>
64
+ <script src="js/store.js"></script>
65
+ <script src="js/app.js"></script>
66
+ <script src="js/cart.js"></script>
67
+ <script>
68
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));
69
+ </script>
70
+
71
+ <!-- AOS JS -->
72
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
73
+ </body>
74
+
75
+ </html>
catalog.html ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="theme-color" content="#0D1F23">
8
+ <meta name="description"
9
+ content="M-TEXTILE katalogi — Barcha kiyim-kechak mahsulotlarini ko'ring, filtrlang va xarid qiling.">
10
+ <title>Katalog — M-TEXTILE</title>
11
+ <link rel="stylesheet" href="css/global.css">
12
+ <link rel="stylesheet" href="css/components.css">
13
+ <link rel="stylesheet" href="css/pages.css">
14
+ <link rel="stylesheet" href="css/premium.css">
15
+ <link rel="manifest" href="manifest.json">
16
+
17
+ <!-- AOS CSS -->
18
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
19
+ </head>
20
+
21
+ <body>
22
+ <!-- Global Preloader -->
23
+ <div id="global-preloader" class="preloader">
24
+ <div class="preloader-spinner"></div>
25
+ </div>
26
+ <div class="page-wrapper">
27
+ <div class="page-content">
28
+ <section class="section-padding" style="padding-top: calc(var(--navbar-height) + 2rem);">
29
+ <div class="container">
30
+ <div class="section-header" style="margin-bottom: 2rem;">
31
+ <h2 id="catalogTitle">Barcha Mahsulotlar</h2>
32
+ <p class="section-subtitle" id="catalogSubtitle">Eng sifatli kiyim-kechak mahsulotlari</p>
33
+ </div>
34
+
35
+ <div class="filter-overlay" id="filterOverlay"></div>
36
+ <div class="catalog-layout">
37
+ <!-- Sidebar Filters -->
38
+ <aside class="catalog-sidebar" id="filterSidebar">
39
+ <div
40
+ style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem;">
41
+ <h3 style="font-size:1.1rem;">Filtrlar</h3>
42
+ <button class="btn btn-ghost btn-sm" id="clearFilters">Tozalash</button>
43
+ </div>
44
+
45
+ <div class="filter-section">
46
+ <h4 class="filter-title">Kategoriya</h4>
47
+ <div class="filter-options" id="filterCategory"></div>
48
+ </div>
49
+
50
+ <div class="filter-section">
51
+ <h4 class="filter-title">Narx diapazoni</h4>
52
+ <div class="price-range">
53
+ <input type="number" placeholder="Min" id="priceMin" step="10000">
54
+ <span style="color:var(--clr-gray)">—</span>
55
+ <input type="number" placeholder="Max" id="priceMax" step="10000">
56
+ </div>
57
+ <button class="btn btn-sm btn-secondary mt-md" style="width:100%"
58
+ id="applyPrice">Qo'llash</button>
59
+ </div>
60
+
61
+ <div class="filter-section">
62
+ <h4 class="filter-title">O'lcham</h4>
63
+ <div class="size-selector" id="filterSizes"></div>
64
+ </div>
65
+
66
+ <div class="filter-section">
67
+ <h4 class="filter-title">Rang</h4>
68
+ <div class="color-selector" id="filterColors"></div>
69
+ </div>
70
+
71
+ <div class="filter-section">
72
+ <div class="filter-options">
73
+ <label class="filter-option" id="filterDiscount">
74
+ <div class="filter-checkbox"></div>
75
+ <span>Faqat chegirmali</span>
76
+ </label>
77
+ </div>
78
+ </div>
79
+ </aside>
80
+
81
+ <!-- Products Grid -->
82
+ <div>
83
+ <div class="catalog-toolbar">
84
+ <div>
85
+ <span class="catalog-count" id="productCount">0 ta mahsulot</span>
86
+ <button class="btn btn-sm btn-outline" id="mobileFilterBtn"
87
+ style="display:none;margin-left:0.5rem;">
88
+ Filtrlar
89
+ </button>
90
+ </div>
91
+ <div class="catalog-sort-group">
92
+ <select class="form-input" id="sortSelect"
93
+ style="width:auto;padding:0.5rem 2.5rem 0.5rem 0.75rem;font-size:0.8rem;">
94
+ <option value="default">Standart</option>
95
+ <option value="price-asc">Narx ↑</option>
96
+ <option value="price-desc">Narx ↓</option>
97
+ <option value="rating">Reyting</option>
98
+ <option value="newest">Yangi</option>
99
+ <option value="discount">Chegirma</option>
100
+ </select>
101
+ <div class="view-toggle hide-mobile">
102
+ <button class="view-btn active" id="gridViewBtn" title="Grid">▦</button>
103
+ <button class="view-btn" id="listViewBtn" title="List">☰</button>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="products-grid" id="productsGrid"></div>
109
+
110
+ <div id="noResults" style="display:none;">
111
+ <div class="empty-state">
112
+ <div class="empty-state-icon">🔍</div>
113
+ <h3 class="empty-state-title">Mahsulot topilmadi</h3>
114
+ <p class="empty-state-text">Filtrlarni o'zgartirib ko'ring</p>
115
+ <button class="btn btn-primary" onclick="clearAllFilters()">Filtrlarni
116
+ tozalash</button>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- Pagination -->
121
+ <div class="flex justify-center gap-sm mt-2xl" id="pagination"></div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </section>
126
+ </div>
127
+ </div>
128
+
129
+ <script src="js/products.js"></script>
130
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
131
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
132
+ <script src="js/firebase-config.js"></script>
133
+ <script src="js/api.js"></script>
134
+ <script src="js/store.js"></script>
135
+ <script src="js/app.js"></script>
136
+ <script src="js/catalog.js"></script>
137
+ <script>
138
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));
139
+ </script>
140
+
141
+ <!-- AOS JS -->
142
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
143
+ </body>
144
+
145
+ </html>
checkout.html ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="theme-color" content="#0D1F23">
8
+ <title>Buyurtma berish — M-TEXTILE</title>
9
+ <link rel="stylesheet" href="css/global.css">
10
+ <link rel="stylesheet" href="css/components.css">
11
+ <link rel="stylesheet" href="css/pages.css">
12
+ <link rel="stylesheet" href="css/premium.css">
13
+ <link rel="manifest" href="manifest.json">
14
+
15
+ <!-- AOS CSS -->
16
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
17
+ </head>
18
+
19
+ <body>
20
+ <!-- Global Preloader -->
21
+ <div id="global-preloader" class="preloader">
22
+ <div class="preloader-spinner"></div>
23
+ </div>
24
+ <div class="page-wrapper">
25
+ <div class="page-content">
26
+ <section class="section-padding" style="padding-top: calc(var(--navbar-height) + 2rem);">
27
+ <div class="container">
28
+ <div class="checkout-layout">
29
+ <h1 style="text-align:center;margin-bottom:2rem;">Buyurtma berish</h1>
30
+
31
+ <!-- Progress Steps -->
32
+ <div class="progress-steps" id="progressSteps">
33
+ <div class="progress-step active" data-step="1">
34
+ <span class="step-num">1</span>
35
+ <span class="step-label">Ma'lumotlar</span>
36
+ </div>
37
+ <div class="progress-step" data-step="2">
38
+ <span class="step-num">2</span>
39
+ <span class="step-label">Do'kon</span>
40
+ </div>
41
+ <div class="progress-step" data-step="3">
42
+ <span class="step-num">3</span>
43
+ <span class="step-label">To'lov</span>
44
+ </div>
45
+ <div class="progress-step" data-step="4">
46
+ <span class="step-num">4</span>
47
+ <span class="step-label">Tasdiqlash</span>
48
+ </div>
49
+ </div>
50
+
51
+ <!-- Step 1: Personal Info -->
52
+ <div class="checkout-step active" id="step1">
53
+ <h2 class="checkout-step-title">Shaxsiy ma'lumotlar</h2>
54
+ <div class="form-group">
55
+ <label class="form-label">Ismingiz *</label>
56
+ <input type="text" class="form-input" id="userName" placeholder="To'liq ismingiz"
57
+ required>
58
+ <div class="form-error" id="nameError"></div>
59
+ </div>
60
+ <div class="form-group">
61
+ <label class="form-label">Telefon raqam *</label>
62
+ <input type="tel" class="form-input" id="userPhone" placeholder="+998 90 123 45 67"
63
+ required>
64
+ <div class="form-error" id="phoneError"></div>
65
+ </div>
66
+ <div class="form-group">
67
+ <label class="form-label">Email (ixtiyoriy)</label>
68
+ <input type="email" class="form-input" id="userEmail" placeholder="email@example.com">
69
+ <div class="form-error" id="emailError"></div>
70
+ </div>
71
+ <div class="flex justify-end gap-md">
72
+ <a href="cart.html" class="btn btn-outline">← Savatchaga qaytish</a>
73
+ <button class="btn btn-primary" onclick="nextStep(1)">Keyingi →</button>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Step 2: Store Location -->
78
+ <div class="checkout-step" id="step2">
79
+ <h2 class="checkout-step-title">Do'kondan olib ketish</h2>
80
+ <p style="color:var(--clr-text-secondary);margin-bottom:1.5rem;">Buyurtmangiz tayyor
81
+ bo'lgach, uni quyidagi manzildan olib ketishingiz mumkin.</p>
82
+
83
+ <div id="storeLocationDisplay"
84
+ style="border:1px solid var(--clr-border);border-radius:var(--radius-md);overflow:hidden;background:var(--clr-surface);">
85
+ <div style="padding:1.5rem;border-bottom:1px solid var(--clr-border);">
86
+ <div
87
+ style="font-weight:600;margin-bottom:0.5rem;display:flex;align-items:center;gap:0.5rem;">
88
+ 📍 Do'kon Manzili:</div>
89
+ <div id="storeAddressText" style="color:var(--clr-text-secondary);">Hozircha
90
+ kiritilmagan</div>
91
+ </div>
92
+ <div id="storeMapContainer"
93
+ style="height:250px;width:100%;display:none;background:var(--clr-border);"></div>
94
+ </div>
95
+
96
+ <div class="flex justify-between mt-xl">
97
+ <button class="btn btn-outline" onclick="prevStep(2)">← Orqaga</button>
98
+ <button class="btn btn-primary" onclick="nextStep(2)" id="step2NextBtn">Keyingi
99
+ →</button>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- Step 3: Payment -->
104
+ <div class="checkout-step" id="step3">
105
+ <h2 class="checkout-step-title">To'lov usuli</h2>
106
+ <div style="display:flex;flex-direction:column;gap:1rem;">
107
+ <label class="filter-option active"
108
+ style="padding:1rem;border:1px solid var(--clr-border);border-radius:var(--radius-md);cursor:pointer;"
109
+ data-payment="cash">
110
+ <div class="filter-checkbox"></div>
111
+ <div>
112
+ <div style="font-weight:600;">💵 Naqd pul</div>
113
+ <div style="font-size:0.8rem;color:var(--clr-text-muted)">Do'kondan olib ketish
114
+ vaqtida to'lash</div>
115
+ </div>
116
+ </label>
117
+ <label class="filter-option"
118
+ style="padding:1rem;border:1px solid var(--clr-border);border-radius:var(--radius-md);cursor:pointer;"
119
+ data-payment="card">
120
+ <div class="filter-checkbox"></div>
121
+ <div>
122
+ <div style="font-weight:600;">💳 Plastik karta</div>
123
+ <div style="font-size:0.8rem;color:var(--clr-text-muted)">Uzcard, Humo</div>
124
+ </div>
125
+ </label>
126
+ <label class="filter-option"
127
+ style="padding:1rem;border:1px solid var(--clr-border);border-radius:var(--radius-md);cursor:pointer;"
128
+ data-payment="click">
129
+ <div class="filter-checkbox"></div>
130
+ <div>
131
+ <div style="font-weight:600;">📱 Click / Payme</div>
132
+ <div style="font-size:0.8rem;color:var(--clr-text-muted)">Onlayn to'lov</div>
133
+ </div>
134
+ </label>
135
+ </div>
136
+ <div class="flex justify-between mt-xl">
137
+ <button class="btn btn-outline" onclick="prevStep(3)">← Orqaga</button>
138
+ <button class="btn btn-primary" onclick="nextStep(3)">Keyingi →</button>
139
+ </div>
140
+ </div>
141
+
142
+ <!-- Step 4: Confirm -->
143
+ <div class="checkout-step" id="step4">
144
+ <h2 class="checkout-step-title">Buyurtmani tasdiqlash</h2>
145
+ <div id="orderSummary"
146
+ style="background:var(--clr-surface);border:1px solid var(--clr-border);border-radius:var(--radius-lg);padding:1.5rem;margin-bottom:2rem;">
147
+ </div>
148
+ <div class="flex justify-between">
149
+ <button class="btn btn-outline" onclick="prevStep(4)">← Orqaga</button>
150
+ <button class="btn btn-primary btn-lg" id="confirmBtn" onclick="confirmOrder()">✓
151
+ Buyurtmani tasdiqlash</button>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- Success -->
156
+ <div class="checkout-step" id="stepSuccess" style="display:none;">
157
+ <div class="order-success">
158
+ <div class="order-success-icon">🎉</div>
159
+ <h2 class="order-success-title">Buyurtma qabul qilindi!</h2>
160
+ <div class="order-success-id" id="orderId"></div>
161
+ <p class="order-success-text">Buyurtmangiz muvaffaqiyatli qabul qilindi. Tez orada siz
162
+ bilan bog'lanamiz.</p>
163
+ <div class="flex justify-center gap-md">
164
+ <a href="profile.html" class="btn btn-outline">Buyurtmalarni ko'rish</a>
165
+ <a href="index.html" class="btn btn-primary">Bosh sahifaga</a>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ </div>
171
+ </div>
172
+ </section>
173
+ </div>
174
+ </div>
175
+ <script src="js/products.js"></script>
176
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
177
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
178
+ <script src="js/firebase-config.js"></script>
179
+ <script src="js/api.js"></script>
180
+ <script src="js/store.js"></script>
181
+ <script src="js/app.js"></script>
182
+ <script src="js/checkout.js"></script>
183
+ <script>
184
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));
185
+ </script>
186
+
187
+ <!-- AOS JS -->
188
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
189
+ </body>
190
+
191
+ </html>
css/admin.css ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ========================================
2
+ ADMIN PANEL CSS
3
+ ======================================== */
4
+ .admin-login-overlay {
5
+ position: fixed;
6
+ inset: 0;
7
+ background: var(--clr-bg);
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ z-index: 1000;
12
+ }
13
+
14
+ .admin-login-box {
15
+ background: var(--clr-surface);
16
+ padding: var(--space-xl);
17
+ border-radius: var(--radius-lg);
18
+ border: 1px solid var(--clr-border);
19
+ width: 100%;
20
+ max-width: 400px;
21
+ text-align: center;
22
+ }
23
+
24
+ .admin-login-box h2 {
25
+ margin-bottom: var(--space-lg);
26
+ color: var(--clr-text-primary);
27
+ }
28
+
29
+ .admin-layout {
30
+ display: flex;
31
+ min-height: 100vh;
32
+ background: var(--clr-bg);
33
+ }
34
+
35
+ .admin-sidebar {
36
+ width: 260px;
37
+ background: var(--clr-surface);
38
+ border-right: 1px solid var(--clr-border);
39
+ display: flex;
40
+ flex-direction: column;
41
+ }
42
+
43
+ .admin-logo {
44
+ padding: var(--space-lg);
45
+ font-size: 1.5rem;
46
+ font-weight: 800;
47
+ color: var(--clr-accent);
48
+ letter-spacing: 2px;
49
+ border-bottom: 1px solid var(--clr-border);
50
+ }
51
+
52
+ .admin-nav {
53
+ display: flex;
54
+ flex-direction: column;
55
+ padding: var(--space-md);
56
+ gap: 8px;
57
+ flex: 1;
58
+ }
59
+
60
+ .admin-nav a {
61
+ padding: var(--space-md);
62
+ color: var(--clr-text-secondary);
63
+ border-radius: var(--radius-md);
64
+ transition: all 0.2s ease;
65
+ text-decoration: none;
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 12px;
69
+ }
70
+
71
+ .admin-nav a:hover,
72
+ .admin-nav a.active {
73
+ background: rgba(100, 255, 218, 0.1);
74
+ color: var(--clr-accent);
75
+ }
76
+
77
+ .admin-content {
78
+ flex: 1;
79
+ display: flex;
80
+ flex-direction: column;
81
+ padding: var(--space-xl);
82
+ overflow-y: auto;
83
+ }
84
+
85
+ .admin-header {
86
+ display: flex;
87
+ justify-content: space-between;
88
+ align-items: center;
89
+ margin-bottom: var(--space-xl);
90
+ }
91
+
92
+ .admin-header h2 {
93
+ font-size: 2rem;
94
+ }
95
+
96
+ .admin-stats {
97
+ display: grid;
98
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
99
+ gap: var(--space-lg);
100
+ margin-bottom: var(--space-xl);
101
+ }
102
+
103
+ .stat-card {
104
+ background: var(--clr-surface);
105
+ padding: var(--space-lg);
106
+ border-radius: var(--radius-lg);
107
+ border: 1px solid var(--clr-border);
108
+ }
109
+
110
+ .stat-title {
111
+ color: var(--clr-text-secondary);
112
+ font-size: 0.9rem;
113
+ margin-bottom: 8px;
114
+ }
115
+
116
+ .stat-value {
117
+ font-size: 2rem;
118
+ font-weight: 700;
119
+ color: var(--clr-accent);
120
+ }
121
+
122
+ .admin-section {
123
+ display: none;
124
+ }
125
+
126
+ .admin-section.active {
127
+ display: block;
128
+ }
129
+
130
+ .card {
131
+ background: var(--clr-surface);
132
+ border-radius: var(--radius-lg);
133
+ border: 1px solid var(--clr-border);
134
+ overflow: hidden;
135
+ }
136
+
137
+ .admin-table {
138
+ width: 100%;
139
+ border-collapse: collapse;
140
+ }
141
+
142
+ .admin-table th,
143
+ .admin-table td {
144
+ padding: var(--space-md) var(--space-lg);
145
+ text-align: left;
146
+ border-bottom: 1px solid var(--clr-border);
147
+ }
148
+
149
+ .admin-table th {
150
+ background: rgba(255, 255, 255, 0.02);
151
+ font-weight: 600;
152
+ color: var(--clr-text-secondary);
153
+ }
154
+
155
+ .admin-table tr:hover {
156
+ background: rgba(255, 255, 255, 0.01);
157
+ }
158
+
159
+ .admin-table img {
160
+ width: 50px;
161
+ height: 50px;
162
+ object-fit: cover;
163
+ border-radius: var(--radius-sm);
164
+ }
165
+
166
+ .action-btn {
167
+ background: none;
168
+ border: none;
169
+ color: var(--clr-text-secondary);
170
+ cursor: pointer;
171
+ font-size: 1.2rem;
172
+ padding: 4px 8px;
173
+ border-radius: var(--radius-sm);
174
+ transition: 0.2s ease;
175
+ }
176
+
177
+ .action-btn:hover {
178
+ background: var(--clr-hover);
179
+ color: var(--clr-text-primary);
180
+ }
181
+
182
+ .action-btn.delete:hover {
183
+ color: var(--clr-error);
184
+ }
185
+
186
+ /* Modal form overrides for admin */
187
+ #productForm {
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: var(--space-md);
191
+ }
192
+
193
+ .modal-header {
194
+ display: flex;
195
+ justify-content: space-between;
196
+ align-items: center;
197
+ margin-bottom: var(--space-lg);
198
+ padding-bottom: var(--space-md);
199
+ border-bottom: 1px solid var(--clr-border);
200
+ }
201
+
202
+ .modal-close {
203
+ background: none;
204
+ border: none;
205
+ color: var(--clr-text-secondary);
206
+ font-size: 1.5rem;
207
+ cursor: pointer;
208
+ }
209
+
210
+ .modal-close:hover {
211
+ color: var(--clr-text-primary);
212
+ }
css/components.css ADDED
@@ -0,0 +1,1563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ========================================
2
+ COMPONENTS — Reusable UI Elements
3
+ ======================================== */
4
+
5
+ /* --- Buttons --- */
6
+ .btn {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ gap: var(--space-sm);
11
+ padding: 0.75rem 1.75rem;
12
+ border-radius: var(--radius-md);
13
+ font-weight: var(--fw-semibold);
14
+ font-size: var(--fs-sm);
15
+ letter-spacing: 0.02em;
16
+ transition: all var(--transition-base);
17
+ cursor: pointer;
18
+ white-space: nowrap;
19
+ position: relative;
20
+ overflow: hidden;
21
+ }
22
+
23
+ .btn::after {
24
+ content: '';
25
+ position: absolute;
26
+ inset: 0;
27
+ background: rgba(255, 255, 255, 0);
28
+ transition: background var(--transition-fast);
29
+ }
30
+
31
+ .btn:hover::after {
32
+ background: rgba(255, 255, 255, 0.05);
33
+ }
34
+
35
+ .btn:active {
36
+ transform: scale(0.97);
37
+ }
38
+
39
+ /* --- Ripple Effect --- */
40
+ .ripple {
41
+ position: absolute;
42
+ border-radius: 50%;
43
+ transform: scale(0);
44
+ animation: ripple-anim 600ms linear;
45
+ background-color: rgba(255, 255, 255, 0.3);
46
+ pointer-events: none;
47
+ }
48
+
49
+ @keyframes ripple-anim {
50
+ to {
51
+ transform: scale(4);
52
+ opacity: 0;
53
+ }
54
+ }
55
+
56
+
57
+ .btn-primary {
58
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-mid));
59
+ color: var(--clr-text-primary);
60
+ box-shadow: var(--shadow-md), 0 0 20px rgba(45, 74, 83, 0.3);
61
+ }
62
+
63
+ .btn-primary:hover {
64
+ box-shadow: var(--shadow-lg), 0 0 30px rgba(45, 74, 83, 0.5);
65
+ transform: translateY(-2px);
66
+ }
67
+
68
+ .btn-secondary {
69
+ background: var(--clr-surface);
70
+ color: var(--clr-text-primary);
71
+ border: 1px solid var(--clr-border);
72
+ }
73
+
74
+ .btn-secondary:hover {
75
+ border-color: var(--clr-border-hover);
76
+ background: var(--clr-surface-hover);
77
+ }
78
+
79
+ .btn-outline {
80
+ background: transparent;
81
+ color: var(--clr-text-primary);
82
+ border: 1px solid var(--clr-border);
83
+ }
84
+
85
+ .btn-outline:hover {
86
+ background: var(--clr-surface);
87
+ border-color: var(--clr-accent);
88
+ }
89
+
90
+ .btn-ghost {
91
+ background: transparent;
92
+ color: var(--clr-text-secondary);
93
+ }
94
+
95
+ .btn-ghost:hover {
96
+ color: var(--clr-text-primary);
97
+ background: rgba(255, 255, 255, 0.05);
98
+ }
99
+
100
+ .btn-danger {
101
+ background: var(--clr-error);
102
+ color: white;
103
+ }
104
+
105
+ .btn-danger:hover {
106
+ box-shadow: 0 0 20px rgba(248, 113, 113, 0.3);
107
+ }
108
+
109
+ .btn-sm {
110
+ padding: 0.5rem 1rem;
111
+ font-size: var(--fs-xs);
112
+ }
113
+
114
+ .btn-lg {
115
+ padding: 1rem 2.5rem;
116
+ font-size: var(--fs-md);
117
+ }
118
+
119
+ .btn-icon {
120
+ padding: 0.65rem;
121
+ border-radius: var(--radius-md);
122
+ }
123
+
124
+ .btn-icon.btn-sm {
125
+ padding: 0.45rem;
126
+ }
127
+
128
+ /* --- Navbar --- */
129
+ .navbar {
130
+ position: fixed;
131
+ top: 0;
132
+ left: 0;
133
+ right: 0;
134
+ height: var(--navbar-height);
135
+ z-index: 1000;
136
+ background: rgba(13, 31, 35, 0.85);
137
+ backdrop-filter: blur(var(--glass-blur));
138
+ -webkit-backdrop-filter: blur(var(--glass-blur));
139
+ border-bottom: 1px solid var(--glass-border);
140
+ transition: all var(--transition-base);
141
+ }
142
+
143
+ .navbar.scrolled {
144
+ background: rgba(13, 31, 35, 0.95);
145
+ box-shadow: var(--shadow-md);
146
+ }
147
+
148
+ .navbar-inner {
149
+ max-width: var(--container-max);
150
+ margin: 0 auto;
151
+ padding: 0 var(--container-padding);
152
+ height: 100%;
153
+ display: flex;
154
+ align-items: center;
155
+ justify-content: space-between;
156
+ }
157
+
158
+ .navbar-logo {
159
+ font-size: var(--fs-xl);
160
+ font-weight: var(--fw-extrabold);
161
+ letter-spacing: -0.02em;
162
+ background: linear-gradient(135deg, var(--clr-light), var(--clr-mid));
163
+ -webkit-background-clip: text;
164
+ -webkit-text-fill-color: transparent;
165
+ background-clip: text;
166
+ }
167
+
168
+ .navbar-nav {
169
+ display: flex;
170
+ gap: var(--space-xs);
171
+ }
172
+
173
+ .navbar-nav a {
174
+ padding: 0.5rem 1rem;
175
+ border-radius: var(--radius-md);
176
+ color: var(--clr-text-secondary);
177
+ font-size: var(--fs-sm);
178
+ font-weight: var(--fw-medium);
179
+ transition: all var(--transition-fast);
180
+ position: relative;
181
+ }
182
+
183
+ .navbar-nav a:hover,
184
+ .navbar-nav a.active {
185
+ color: var(--clr-text-primary);
186
+ background: rgba(255, 255, 255, 0.05);
187
+ }
188
+
189
+ .navbar-nav a.active::after {
190
+ content: '';
191
+ position: absolute;
192
+ bottom: -1px;
193
+ left: 50%;
194
+ transform: translateX(-50%);
195
+ width: 20px;
196
+ height: 2px;
197
+ background: var(--clr-mid);
198
+ border-radius: var(--radius-full);
199
+ }
200
+
201
+ .navbar-actions {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: var(--space-sm);
205
+ }
206
+
207
+ /* --- Advanced Search Modal --- */
208
+ .search-modal-overlay {
209
+ position: fixed;
210
+ inset: 0;
211
+ height: 100vh;
212
+ width: 100vw;
213
+ background: rgba(0, 0, 0, 0.7);
214
+ backdrop-filter: blur(10px);
215
+ z-index: 3000;
216
+ display: flex;
217
+ align-items: flex-start;
218
+ justify-content: center;
219
+ padding-top: 10vh;
220
+ opacity: 0;
221
+ pointer-events: none;
222
+ transition: all 0.3s ease;
223
+ }
224
+
225
+ .search-modal-overlay.active {
226
+ opacity: 1;
227
+ pointer-events: all;
228
+ }
229
+
230
+ .search-modal {
231
+ width: 90%;
232
+ max-width: 600px;
233
+ background: var(--clr-surface);
234
+ border-radius: var(--radius-lg);
235
+ border: 1px solid var(--clr-border);
236
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
237
+ overflow: hidden;
238
+ transform: translateY(-20px);
239
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
240
+ }
241
+
242
+ .search-modal-overlay.active .search-modal {
243
+ transform: translateY(0);
244
+ }
245
+
246
+ .search-modal-header {
247
+ display: flex;
248
+ align-items: center;
249
+ padding: var(--space-md) var(--space-lg);
250
+ border-bottom: 1px solid var(--clr-border);
251
+ gap: var(--space-md);
252
+ }
253
+
254
+ .search-modal-input-wrapper {
255
+ flex: 1;
256
+ display: flex;
257
+ align-items: center;
258
+ gap: var(--space-md);
259
+ }
260
+
261
+ .search-modal-input-wrapper svg {
262
+ color: var(--clr-text-secondary);
263
+ }
264
+
265
+ .search-modal-input-wrapper input {
266
+ width: 100%;
267
+ font-size: 1.1rem;
268
+ padding: var(--space-sm) 0;
269
+ background: transparent;
270
+ color: var(--clr-text-primary);
271
+ }
272
+
273
+ .search-modal-body {
274
+ padding: var(--space-lg);
275
+ max-height: 70vh;
276
+ overflow-y: auto;
277
+ }
278
+
279
+ .cart-badge {
280
+ position: relative;
281
+ }
282
+
283
+ .cart-badge .badge-count {
284
+ position: absolute;
285
+ top: -6px;
286
+ right: -6px;
287
+ background: var(--clr-error);
288
+ color: white;
289
+ font-size: 10px;
290
+ font-weight: var(--fw-bold);
291
+ min-width: 18px;
292
+ height: 18px;
293
+ border-radius: var(--radius-full);
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: center;
297
+ padding: 0 4px;
298
+ animation: scaleIn var(--transition-spring);
299
+ }
300
+
301
+ /* Mobile hamburger */
302
+ .hamburger {
303
+ display: none;
304
+ flex-direction: column;
305
+ gap: 5px;
306
+ cursor: pointer;
307
+ padding: 8px;
308
+ }
309
+
310
+ .hamburger span {
311
+ width: 22px;
312
+ height: 2px;
313
+ background: var(--clr-text-primary);
314
+ border-radius: 2px;
315
+ transition: all var(--transition-base);
316
+ }
317
+
318
+ .hamburger.active span:nth-child(1) {
319
+ transform: rotate(45deg) translateY(7px);
320
+ }
321
+
322
+ .hamburger.active span:nth-child(2) {
323
+ opacity: 0;
324
+ }
325
+
326
+ .hamburger.active span:nth-child(3) {
327
+ transform: rotate(-45deg) translateY(-7px);
328
+ }
329
+
330
+ /* --- Product Card --- */
331
+ .product-card {
332
+ background: var(--clr-surface);
333
+ border: 1px solid var(--clr-border);
334
+ border-radius: var(--radius-lg);
335
+ overflow: hidden;
336
+ transition: all var(--transition-base);
337
+ position: relative;
338
+ cursor: pointer;
339
+ }
340
+
341
+ .product-card:hover {
342
+ transform: translateY(-6px);
343
+ box-shadow: var(--shadow-lg);
344
+ border-color: var(--clr-border-hover);
345
+ }
346
+
347
+ .product-card-image {
348
+ position: relative;
349
+ aspect-ratio: 3/4;
350
+ overflow: hidden;
351
+ background: var(--clr-bg-secondary);
352
+ }
353
+
354
+ .product-card-image img {
355
+ width: 100%;
356
+ height: 100%;
357
+ object-fit: cover;
358
+ transition: transform var(--transition-slow);
359
+ }
360
+
361
+ .product-card:hover .product-card-image img {
362
+ transform: scale(1.08);
363
+ }
364
+
365
+ .product-card-badges {
366
+ position: absolute;
367
+ top: var(--space-sm);
368
+ left: var(--space-sm);
369
+ display: flex;
370
+ flex-direction: column;
371
+ gap: var(--space-xs);
372
+ }
373
+
374
+ .badge {
375
+ padding: 0.25rem 0.65rem;
376
+ border-radius: var(--radius-full);
377
+ font-size: var(--fs-xs);
378
+ font-weight: var(--fw-semibold);
379
+ letter-spacing: 0.02em;
380
+ }
381
+
382
+ .badge-new {
383
+ background: var(--clr-info);
384
+ color: white;
385
+ }
386
+
387
+ .badge-discount {
388
+ background: var(--clr-error);
389
+ color: white;
390
+ }
391
+
392
+ .badge-featured {
393
+ background: var(--clr-success);
394
+ color: white;
395
+ }
396
+
397
+ .product-card-actions {
398
+ position: absolute;
399
+ top: var(--space-sm);
400
+ right: var(--space-sm);
401
+ display: flex;
402
+ flex-direction: column;
403
+ gap: var(--space-xs);
404
+ opacity: 0;
405
+ transform: translateX(10px);
406
+ transition: all var(--transition-base);
407
+ }
408
+
409
+ .product-card:hover .product-card-actions {
410
+ opacity: 1;
411
+ transform: translateX(0);
412
+ }
413
+
414
+ .product-card-action-btn {
415
+ width: 36px;
416
+ height: 36px;
417
+ border-radius: var(--radius-full);
418
+ background: rgba(13, 31, 35, 0.75);
419
+ backdrop-filter: blur(8px);
420
+ display: flex;
421
+ align-items: center;
422
+ justify-content: center;
423
+ color: var(--clr-text-primary);
424
+ transition: all var(--transition-fast);
425
+ border: 1px solid var(--glass-border);
426
+ }
427
+
428
+ .product-card-action-btn:hover {
429
+ background: var(--clr-accent);
430
+ }
431
+
432
+ .product-card-action-btn.wishlisted {
433
+ color: var(--clr-error);
434
+ }
435
+
436
+ .product-card-body {
437
+ padding: var(--space-md);
438
+ }
439
+
440
+ .product-card-category {
441
+ font-size: var(--fs-xs);
442
+ color: var(--clr-text-muted);
443
+ text-transform: uppercase;
444
+ letter-spacing: 0.06em;
445
+ margin-bottom: var(--space-xs);
446
+ }
447
+
448
+ .product-card-name {
449
+ font-size: var(--fs-base);
450
+ font-weight: var(--fw-semibold);
451
+ margin-bottom: var(--space-sm);
452
+ display: -webkit-box;
453
+ -webkit-line-clamp: 2;
454
+ line-clamp: 2;
455
+ -webkit-box-orient: vertical;
456
+ overflow: hidden;
457
+ }
458
+
459
+ .product-card-rating {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: var(--space-xs);
463
+ margin-bottom: var(--space-sm);
464
+ font-size: var(--fs-sm);
465
+ }
466
+
467
+ .product-card-rating .stars {
468
+ color: var(--clr-warning);
469
+ }
470
+
471
+ .product-card-rating .count {
472
+ color: var(--clr-text-muted);
473
+ }
474
+
475
+ .product-card-price {
476
+ display: flex;
477
+ align-items: center;
478
+ gap: var(--space-sm);
479
+ }
480
+
481
+ .price-current {
482
+ font-size: var(--fs-lg);
483
+ font-weight: var(--fw-bold);
484
+ color: var(--clr-text-primary);
485
+ }
486
+
487
+ .price-old {
488
+ font-size: var(--fs-sm);
489
+ color: var(--clr-text-muted);
490
+ text-decoration: line-through;
491
+ }
492
+
493
+ /* --- Toast Notifications --- */
494
+ .toast-container {
495
+ position: fixed;
496
+ top: calc(var(--navbar-height) + var(--space-md));
497
+ right: var(--space-lg);
498
+ z-index: 9999;
499
+ display: flex;
500
+ flex-direction: column;
501
+ gap: var(--space-sm);
502
+ }
503
+
504
+ .toast {
505
+ background: rgba(30, 40, 50, 0.65);
506
+ backdrop-filter: blur(12px);
507
+ -webkit-backdrop-filter: blur(12px);
508
+ border: 1px solid rgba(255, 255, 255, 0.1);
509
+ color: #fff;
510
+ border-radius: var(--radius-lg);
511
+ padding: var(--space-md) var(--space-xl);
512
+ display: flex;
513
+ align-items: center;
514
+ gap: var(--space-md);
515
+ min-width: 320px;
516
+ max-width: 450px;
517
+ animation: slideInRight 0.5s cubic-bezier(0.16, 1, 0.3, 1);
518
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
519
+ }
520
+
521
+ [data-theme="light"] .toast {
522
+ background: rgba(255, 255, 255, 0.75);
523
+ border: 1px solid rgba(0, 0, 0, 0.05);
524
+ color: #000;
525
+ }
526
+
527
+ .toast.toast-success {
528
+ border-left: 3px solid var(--clr-success);
529
+ }
530
+
531
+ .toast.toast-error {
532
+ border-left: 3px solid var(--clr-error);
533
+ }
534
+
535
+ .toast.toast-info {
536
+ border-left: 3px solid var(--clr-info);
537
+ }
538
+
539
+ .toast.toast-exit {
540
+ animation: slideOutRight 0.3s ease-in forwards;
541
+ }
542
+
543
+ @keyframes slideOutRight {
544
+ to {
545
+ opacity: 0;
546
+ transform: translateX(100%);
547
+ }
548
+ }
549
+
550
+ .toast-icon {
551
+ font-size: 1.25rem;
552
+ flex-shrink: 0;
553
+ }
554
+
555
+ .toast-content {
556
+ flex: 1;
557
+ }
558
+
559
+ .toast-title {
560
+ font-weight: var(--fw-semibold);
561
+ font-size: var(--fs-sm);
562
+ }
563
+
564
+ .toast-message {
565
+ font-size: var(--fs-xs);
566
+ color: var(--clr-text-secondary);
567
+ margin-top: 2px;
568
+ }
569
+
570
+ .toast-close {
571
+ color: var(--clr-gray);
572
+ cursor: pointer;
573
+ padding: 4px;
574
+ }
575
+
576
+ .toast-close:hover {
577
+ color: var(--clr-text-primary);
578
+ }
579
+
580
+ /* --- Modal --- */
581
+ .modal-overlay {
582
+ position: fixed;
583
+ inset: 0;
584
+ height: 100vh;
585
+ width: 100vw;
586
+ background: rgba(0, 0, 0, 0.6);
587
+ backdrop-filter: blur(4px);
588
+ z-index: 2000;
589
+ display: flex;
590
+ align-items: center;
591
+ justify-content: center;
592
+ padding: var(--space-lg);
593
+ animation: fadeIn 0.2s ease;
594
+ opacity: 0;
595
+ pointer-events: none;
596
+ transition: opacity var(--transition-base);
597
+ }
598
+
599
+ .modal-overlay.active {
600
+ opacity: 1;
601
+ pointer-events: all;
602
+ }
603
+
604
+ .modal {
605
+ background: var(--clr-bg-secondary);
606
+ border: 1px solid var(--clr-border);
607
+ border-radius: var(--radius-xl);
608
+ max-width: 600px;
609
+ width: 100%;
610
+ max-height: 85vh;
611
+ overflow-y: auto;
612
+ padding: var(--space-2xl);
613
+ animation: slideDownModal 0.4s cubic-bezier(0.16, 1, 0.3, 1);
614
+ box-shadow: var(--shadow-xl);
615
+ }
616
+
617
+ .modal-header {
618
+ display: flex;
619
+ justify-content: space-between;
620
+ align-items: center;
621
+ margin-bottom: var(--space-lg);
622
+ }
623
+
624
+ .modal-title {
625
+ font-size: var(--fs-xl);
626
+ font-weight: var(--fw-bold);
627
+ }
628
+
629
+ .modal-close {
630
+ width: 36px;
631
+ height: 36px;
632
+ border-radius: var(--radius-full);
633
+ display: flex;
634
+ align-items: center;
635
+ justify-content: center;
636
+ color: var(--clr-gray);
637
+ transition: all var(--transition-fast);
638
+ }
639
+
640
+ .modal-close:hover {
641
+ background: var(--clr-surface);
642
+ color: var(--clr-text-primary);
643
+ }
644
+
645
+ /* --- Input Fields --- */
646
+ .form-group {
647
+ margin-bottom: var(--space-lg);
648
+ }
649
+
650
+ .form-label {
651
+ display: block;
652
+ font-size: var(--fs-sm);
653
+ font-weight: var(--fw-medium);
654
+ color: var(--clr-text-secondary);
655
+ margin-bottom: var(--space-sm);
656
+ }
657
+
658
+ .form-input {
659
+ width: 100%;
660
+ padding: 0.75rem 1rem;
661
+ background: var(--clr-surface);
662
+ border: 1px solid var(--clr-border);
663
+ border-radius: var(--radius-md);
664
+ color: var(--clr-text-primary);
665
+ font-size: var(--fs-base);
666
+ transition: all var(--transition-fast);
667
+ }
668
+
669
+ .form-input:focus {
670
+ border-color: var(--clr-accent);
671
+ box-shadow: var(--shadow-glow);
672
+ }
673
+
674
+ .form-input::placeholder {
675
+ color: var(--clr-gray);
676
+ }
677
+
678
+ .form-input.error {
679
+ border-color: var(--clr-error);
680
+ }
681
+
682
+ .form-error {
683
+ font-size: var(--fs-xs);
684
+ color: var(--clr-error);
685
+ margin-top: var(--space-xs);
686
+ }
687
+
688
+ select.form-input {
689
+ appearance: none;
690
+ background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2369818D' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
691
+ background-repeat: no-repeat;
692
+ background-position: right 1rem center;
693
+ padding-right: 2.5rem;
694
+ }
695
+
696
+ /* --- Footer --- */
697
+ .footer {
698
+ background: var(--clr-bg-secondary);
699
+ border-top: 1px solid var(--clr-border);
700
+ padding: var(--space-4xl) 0 var(--space-xl);
701
+ margin-top: var(--space-4xl);
702
+ }
703
+
704
+ .footer-grid {
705
+ display: grid;
706
+ grid-template-columns: 2fr 1fr 1fr 1fr;
707
+ gap: var(--space-2xl);
708
+ }
709
+
710
+ .footer-brand {
711
+ max-width: 320px;
712
+ }
713
+
714
+ .footer-brand-name {
715
+ font-size: var(--fs-xl);
716
+ font-weight: var(--fw-extrabold);
717
+ background: linear-gradient(135deg, var(--clr-light), var(--clr-mid));
718
+ -webkit-background-clip: text;
719
+ -webkit-text-fill-color: transparent;
720
+ background-clip: text;
721
+ margin-bottom: var(--space-md);
722
+ }
723
+
724
+ .footer-brand-desc {
725
+ color: var(--clr-text-muted);
726
+ font-size: var(--fs-sm);
727
+ line-height: 1.7;
728
+ }
729
+
730
+ .footer-heading {
731
+ font-size: var(--fs-sm);
732
+ font-weight: var(--fw-semibold);
733
+ text-transform: uppercase;
734
+ letter-spacing: 0.08em;
735
+ color: var(--clr-text-secondary);
736
+ margin-bottom: var(--space-lg);
737
+ }
738
+
739
+ .footer-links {
740
+ display: flex;
741
+ flex-direction: column;
742
+ gap: var(--space-sm);
743
+ }
744
+
745
+ .footer-links a {
746
+ color: var(--clr-text-muted);
747
+ font-size: var(--fs-sm);
748
+ transition: color var(--transition-fast);
749
+ }
750
+
751
+ .footer-links a:hover {
752
+ color: var(--clr-text-primary);
753
+ }
754
+
755
+ .footer-bottom {
756
+ margin-top: var(--space-3xl);
757
+ padding-top: var(--space-lg);
758
+ border-top: 1px solid var(--clr-border);
759
+ display: flex;
760
+ justify-content: space-between;
761
+ align-items: center;
762
+ color: var(--clr-text-muted);
763
+ font-size: var(--fs-xs);
764
+ }
765
+
766
+ .footer-social {
767
+ display: flex;
768
+ gap: var(--space-md);
769
+ }
770
+
771
+ .footer-social a {
772
+ width: 36px;
773
+ height: 36px;
774
+ border-radius: var(--radius-full);
775
+ background: var(--clr-surface);
776
+ border: 1px solid var(--clr-border);
777
+ display: flex;
778
+ align-items: center;
779
+ justify-content: center;
780
+ color: var(--clr-text-muted);
781
+ transition: all var(--transition-fast);
782
+ }
783
+
784
+ .footer-social a:hover {
785
+ background: var(--clr-accent);
786
+ color: var(--clr-text-primary);
787
+ border-color: var(--clr-accent);
788
+ }
789
+
790
+ /* --- Empty State --- */
791
+ .empty-state {
792
+ text-align: center;
793
+ padding: var(--space-4xl) var(--space-xl);
794
+ }
795
+
796
+ .empty-state-icon {
797
+ font-size: 4rem;
798
+ margin-bottom: var(--space-lg);
799
+ opacity: 0.5;
800
+ }
801
+
802
+ .empty-state-title {
803
+ font-size: var(--fs-xl);
804
+ font-weight: var(--fw-semibold);
805
+ margin-bottom: var(--space-sm);
806
+ }
807
+
808
+ .empty-state-text {
809
+ color: var(--clr-text-muted);
810
+ margin-bottom: var(--space-xl);
811
+ }
812
+
813
+ /* --- Quantity Control --- */
814
+ .qty-control {
815
+ display: inline-flex;
816
+ align-items: center;
817
+ border: 1px solid var(--clr-border);
818
+ border-radius: var(--radius-md);
819
+ overflow: hidden;
820
+ }
821
+
822
+ .qty-btn {
823
+ width: 36px;
824
+ height: 36px;
825
+ display: flex;
826
+ align-items: center;
827
+ justify-content: center;
828
+ color: var(--clr-text-secondary);
829
+ transition: all var(--transition-fast);
830
+ background: var(--clr-surface);
831
+ }
832
+
833
+ .qty-btn:hover {
834
+ background: var(--clr-accent);
835
+ color: var(--clr-text-primary);
836
+ }
837
+
838
+ .qty-value {
839
+ width: 44px;
840
+ text-align: center;
841
+ font-weight: var(--fw-semibold);
842
+ font-size: var(--fs-sm);
843
+ background: transparent;
844
+ border-left: 1px solid var(--clr-border);
845
+ border-right: 1px solid var(--clr-border);
846
+ padding: 0.4rem 0;
847
+ }
848
+
849
+ /* --- Size/Color Selector --- */
850
+ .size-selector,
851
+ .color-selector {
852
+ display: flex;
853
+ flex-wrap: wrap;
854
+ gap: var(--space-sm);
855
+ }
856
+
857
+ .size-btn {
858
+ min-width: 42px;
859
+ height: 42px;
860
+ display: flex;
861
+ align-items: center;
862
+ justify-content: center;
863
+ border: 1px solid var(--clr-border);
864
+ border-radius: var(--radius-md);
865
+ font-size: var(--fs-sm);
866
+ font-weight: var(--fw-medium);
867
+ color: var(--clr-text-secondary);
868
+ background: var(--clr-surface);
869
+ transition: all var(--transition-fast);
870
+ cursor: pointer;
871
+ }
872
+
873
+ .size-btn:hover {
874
+ border-color: var(--clr-accent);
875
+ color: var(--clr-text-primary);
876
+ }
877
+
878
+ .size-btn.active {
879
+ border-color: var(--clr-mid);
880
+ background: var(--clr-accent);
881
+ color: var(--clr-text-primary);
882
+ }
883
+
884
+ .color-btn {
885
+ width: 32px;
886
+ height: 32px;
887
+ border-radius: var(--radius-full);
888
+ border: 2px solid transparent;
889
+ cursor: pointer;
890
+ transition: all var(--transition-fast);
891
+ position: relative;
892
+ }
893
+
894
+ .color-btn:hover {
895
+ transform: scale(1.15);
896
+ }
897
+
898
+ .color-btn.active {
899
+ border-color: var(--clr-text-primary);
900
+ box-shadow: 0 0 0 2px var(--clr-bg-primary);
901
+ }
902
+
903
+ /* --- Tabs --- */
904
+ .tabs {
905
+ display: flex;
906
+ border-bottom: 1px solid var(--clr-border);
907
+ gap: 0;
908
+ }
909
+
910
+ .tab-btn {
911
+ padding: 0.75rem 1.5rem;
912
+ font-size: var(--fs-sm);
913
+ font-weight: var(--fw-medium);
914
+ color: var(--clr-text-muted);
915
+ position: relative;
916
+ transition: all var(--transition-fast);
917
+ }
918
+
919
+ .tab-btn:hover {
920
+ color: var(--clr-text-secondary);
921
+ }
922
+
923
+ .tab-btn.active {
924
+ color: var(--clr-text-primary);
925
+ }
926
+
927
+ .tab-btn.active::after {
928
+ content: '';
929
+ position: absolute;
930
+ bottom: -1px;
931
+ left: 0;
932
+ right: 0;
933
+ height: 2px;
934
+ background: var(--clr-mid);
935
+ border-radius: var(--radius-full);
936
+ }
937
+
938
+ .tab-content {
939
+ display: none;
940
+ padding: var(--space-xl) 0;
941
+ }
942
+
943
+ .tab-content.active {
944
+ display: block;
945
+ animation: fadeIn 0.3s ease;
946
+ }
947
+
948
+ /* --- Progress Steps --- */
949
+ .progress-steps {
950
+ display: flex;
951
+ justify-content: center;
952
+ margin-bottom: var(--space-2xl);
953
+ }
954
+
955
+ .progress-step {
956
+ display: flex;
957
+ align-items: center;
958
+ gap: var(--space-sm);
959
+ color: var(--clr-text-muted);
960
+ font-size: var(--fs-sm);
961
+ }
962
+
963
+ .progress-step+.progress-step::before {
964
+ content: '';
965
+ width: 40px;
966
+ height: 1px;
967
+ background: var(--clr-border);
968
+ margin: 0 var(--space-md);
969
+ }
970
+
971
+ .progress-step .step-num {
972
+ width: 32px;
973
+ height: 32px;
974
+ border-radius: var(--radius-full);
975
+ border: 1px solid var(--clr-border);
976
+ display: flex;
977
+ align-items: center;
978
+ justify-content: center;
979
+ font-weight: var(--fw-semibold);
980
+ font-size: var(--fs-sm);
981
+ transition: all var(--transition-base);
982
+ }
983
+
984
+ .progress-step.active .step-num {
985
+ background: var(--clr-accent);
986
+ border-color: var(--clr-mid);
987
+ color: var(--clr-text-primary);
988
+ }
989
+
990
+ .progress-step.active {
991
+ color: var(--clr-text-primary);
992
+ }
993
+
994
+ .progress-step.done .step-num {
995
+ background: var(--clr-success);
996
+ border-color: var(--clr-success);
997
+ color: white;
998
+ }
999
+
1000
+ .progress-step.done {
1001
+ color: var(--clr-success);
1002
+ }
1003
+
1004
+ /* --- Responsive (Mobile) --- */
1005
+ @media (max-width: 768px) {
1006
+ .navbar-nav {
1007
+ display: none;
1008
+ }
1009
+
1010
+ .hamburger {
1011
+ display: flex;
1012
+ }
1013
+
1014
+
1015
+
1016
+ .mobile-nav {
1017
+ position: fixed;
1018
+ top: var(--navbar-height);
1019
+ left: 0;
1020
+ right: 0;
1021
+ bottom: 0;
1022
+ background: rgba(13, 31, 35, 0.98);
1023
+ backdrop-filter: blur(20px);
1024
+ z-index: 999;
1025
+ display: flex;
1026
+ flex-direction: column;
1027
+ padding: var(--space-xl);
1028
+ transform: translateX(-100%);
1029
+ transition: transform var(--transition-base);
1030
+ }
1031
+
1032
+ .mobile-nav.active {
1033
+ transform: translateX(0);
1034
+ }
1035
+
1036
+ .mobile-nav a {
1037
+ padding: var(--space-md);
1038
+ font-size: var(--fs-lg);
1039
+ border-bottom: 1px solid var(--clr-border);
1040
+ color: var(--clr-text-secondary);
1041
+ }
1042
+
1043
+ .mobile-nav a:hover,
1044
+ .mobile-nav a.active {
1045
+ color: var(--clr-text-primary);
1046
+ }
1047
+
1048
+ .footer-grid {
1049
+ grid-template-columns: 1fr 1fr;
1050
+ gap: var(--space-xl);
1051
+ }
1052
+
1053
+ .footer-brand {
1054
+ grid-column: 1 / -1;
1055
+ }
1056
+
1057
+ .footer-bottom {
1058
+ flex-direction: column;
1059
+ gap: var(--space-md);
1060
+ text-align: center;
1061
+ }
1062
+
1063
+ .product-card-actions {
1064
+ opacity: 1;
1065
+ transform: translateX(0);
1066
+ }
1067
+
1068
+ .progress-steps {
1069
+ flex-wrap: wrap;
1070
+ gap: var(--space-sm);
1071
+ }
1072
+
1073
+ .progress-step+.progress-step::before {
1074
+ width: 20px;
1075
+ margin: 0 var(--space-sm);
1076
+ }
1077
+
1078
+ .progress-step .step-label {
1079
+ display: none;
1080
+ }
1081
+
1082
+ .toast {
1083
+ min-width: auto;
1084
+ max-width: calc(100vw - 2rem);
1085
+ }
1086
+
1087
+ .toast-container {
1088
+ left: var(--space-md);
1089
+ right: var(--space-md);
1090
+ }
1091
+ }
1092
+
1093
+ /* --- Search Dropdown --- */
1094
+ .search-dropdown {
1095
+ position: absolute;
1096
+ top: calc(100% + 8px);
1097
+ left: 0;
1098
+ right: 0;
1099
+ min-width: 340px;
1100
+ background: var(--clr-bg-secondary);
1101
+ border: 1px solid var(--clr-border);
1102
+ border-radius: var(--radius-lg);
1103
+ box-shadow: var(--shadow-xl);
1104
+ z-index: 100;
1105
+ overflow: hidden;
1106
+ display: none;
1107
+ animation: fadeInDown 0.2s ease-out;
1108
+ }
1109
+
1110
+ .search-dropdown.active {
1111
+ display: block;
1112
+ }
1113
+
1114
+ .search-dropdown-item {
1115
+ display: flex;
1116
+ align-items: center;
1117
+ gap: var(--space-md);
1118
+ padding: 0.65rem 1rem;
1119
+ transition: background var(--transition-fast);
1120
+ cursor: pointer;
1121
+ }
1122
+
1123
+ .search-dropdown-item:hover {
1124
+ background: var(--clr-surface-hover);
1125
+ }
1126
+
1127
+ .search-dropdown-item img {
1128
+ width: 44px;
1129
+ height: 54px;
1130
+ object-fit: cover;
1131
+ border-radius: var(--radius-sm);
1132
+ flex-shrink: 0;
1133
+ }
1134
+
1135
+ .search-dropdown-info {
1136
+ flex: 1;
1137
+ min-width: 0;
1138
+ }
1139
+
1140
+ .search-dropdown-name {
1141
+ font-size: var(--fs-sm);
1142
+ font-weight: var(--fw-medium);
1143
+ white-space: nowrap;
1144
+ overflow: hidden;
1145
+ text-overflow: ellipsis;
1146
+ }
1147
+
1148
+ .search-dropdown-price {
1149
+ font-size: var(--fs-xs);
1150
+ color: var(--clr-text-muted);
1151
+ margin-top: 2px;
1152
+ }
1153
+
1154
+ .search-dropdown-empty {
1155
+ padding: 1.5rem;
1156
+ text-align: center;
1157
+ color: var(--clr-text-muted);
1158
+ font-size: var(--fs-sm);
1159
+ }
1160
+
1161
+ .search-dropdown-all {
1162
+ display: block;
1163
+ padding: 0.75rem 1rem;
1164
+ text-align: center;
1165
+ font-size: var(--fs-sm);
1166
+ font-weight: var(--fw-medium);
1167
+ color: var(--clr-mid);
1168
+ border-top: 1px solid var(--clr-border);
1169
+ transition: background var(--transition-fast);
1170
+ }
1171
+
1172
+ .search-dropdown-all:hover {
1173
+ background: var(--clr-surface);
1174
+ color: var(--clr-text-primary);
1175
+ }
1176
+
1177
+ /* --- Back to Top --- */
1178
+ .back-to-top {
1179
+ position: fixed;
1180
+ bottom: 2rem;
1181
+ right: 2rem;
1182
+ width: 48px;
1183
+ height: 48px;
1184
+ border-radius: var(--radius-full);
1185
+ background: var(--glass-bg);
1186
+ backdrop-filter: blur(var(--glass-blur));
1187
+ border: 1px solid var(--glass-border);
1188
+ color: var(--clr-text-primary);
1189
+ display: flex;
1190
+ align-items: center;
1191
+ justify-content: center;
1192
+ cursor: pointer;
1193
+ opacity: 0;
1194
+ transform: translateY(20px);
1195
+ transition: all var(--transition-base);
1196
+ z-index: 900;
1197
+ box-shadow: var(--shadow-md);
1198
+ }
1199
+
1200
+ .back-to-top.visible {
1201
+ opacity: 1;
1202
+ transform: translateY(0);
1203
+ }
1204
+
1205
+ .back-to-top:hover {
1206
+ background: var(--clr-accent);
1207
+ transform: translateY(-2px);
1208
+ box-shadow: var(--shadow-lg);
1209
+ }
1210
+
1211
+ /* --- Newsletter --- */
1212
+ .newsletter-section {
1213
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-bg-secondary));
1214
+ border-radius: var(--radius-xl);
1215
+ padding: var(--space-3xl);
1216
+ text-align: center;
1217
+ position: relative;
1218
+ overflow: hidden;
1219
+ }
1220
+
1221
+ .newsletter-section::before {
1222
+ content: '';
1223
+ position: absolute;
1224
+ top: -50%;
1225
+ right: -20%;
1226
+ width: 60%;
1227
+ height: 200%;
1228
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.04), transparent);
1229
+ }
1230
+
1231
+ .newsletter-title {
1232
+ font-size: var(--fs-2xl);
1233
+ font-weight: var(--fw-bold);
1234
+ margin-bottom: var(--space-sm);
1235
+ position: relative;
1236
+ z-index: 1;
1237
+ }
1238
+
1239
+ .newsletter-desc {
1240
+ color: var(--clr-text-secondary);
1241
+ margin-bottom: var(--space-xl);
1242
+ position: relative;
1243
+ z-index: 1;
1244
+ }
1245
+
1246
+ .newsletter-form {
1247
+ display: flex;
1248
+ gap: var(--space-sm);
1249
+ max-width: 480px;
1250
+ margin: 0 auto;
1251
+ position: relative;
1252
+ z-index: 1;
1253
+ }
1254
+
1255
+ .newsletter-form input {
1256
+ flex: 1;
1257
+ padding: 0.85rem 1.25rem;
1258
+ background: rgba(13, 31, 35, 0.6);
1259
+ border: 1px solid var(--glass-border);
1260
+ border-radius: var(--radius-full);
1261
+ color: var(--clr-text-primary);
1262
+ font-size: var(--fs-sm);
1263
+ }
1264
+
1265
+ .newsletter-form input::placeholder {
1266
+ color: var(--clr-gray);
1267
+ }
1268
+
1269
+ .newsletter-form .btn {
1270
+ border-radius: var(--radius-full);
1271
+ }
1272
+
1273
+ /* --- Login Modal --- */
1274
+ .login-tabs {
1275
+ display: flex;
1276
+ margin-bottom: var(--space-xl);
1277
+ border-bottom: 1px solid var(--clr-border);
1278
+ }
1279
+
1280
+ .login-tab {
1281
+ flex: 1;
1282
+ padding: 0.75rem;
1283
+ text-align: center;
1284
+ font-weight: var(--fw-medium);
1285
+ color: var(--clr-text-muted);
1286
+ cursor: pointer;
1287
+ transition: all var(--transition-fast);
1288
+ position: relative;
1289
+ }
1290
+
1291
+ .login-tab.active {
1292
+ color: var(--clr-text-primary);
1293
+ }
1294
+
1295
+ .login-tab.active::after {
1296
+ content: '';
1297
+ position: absolute;
1298
+ bottom: -1px;
1299
+ left: 0;
1300
+ right: 0;
1301
+ height: 2px;
1302
+ background: var(--clr-mid);
1303
+ }
1304
+
1305
+ /* --- Review Stars Rating --- */
1306
+ .review-stars {
1307
+ display: flex;
1308
+ gap: 4px;
1309
+ }
1310
+
1311
+ .review-star {
1312
+ font-size: 1.5rem;
1313
+ cursor: pointer;
1314
+ color: var(--clr-gray);
1315
+ transition: color var(--transition-fast);
1316
+ }
1317
+
1318
+ .review-star.active,
1319
+ .review-star:hover {
1320
+ color: var(--clr-warning);
1321
+ }
1322
+
1323
+ /* --- Mobile Quick View --- */
1324
+ @media (max-width: 768px) {
1325
+ .search-dropdown {
1326
+ min-width: auto;
1327
+ left: -60px;
1328
+ right: -20px;
1329
+ }
1330
+
1331
+ .modal[style*="max-width:800px"]>div[style*="grid-template-columns"] {
1332
+ display: flex !important;
1333
+ flex-direction: column !important;
1334
+ }
1335
+
1336
+ .back-to-top {
1337
+ bottom: 1.5rem;
1338
+ right: 1.5rem;
1339
+ width: 42px;
1340
+ height: 42px;
1341
+ }
1342
+
1343
+ .newsletter-form {
1344
+ transform: scale(1);
1345
+ }
1346
+ }
1347
+
1348
+ @keyframes slideDownModal {
1349
+ from {
1350
+ opacity: 0;
1351
+ transform: translateY(-40px) scale(0.96);
1352
+ }
1353
+
1354
+ to {
1355
+ opacity: 1;
1356
+ transform: translateY(0) scale(1);
1357
+ }
1358
+ }
1359
+
1360
+ /* --- Page Transitions --- */
1361
+ .page-fade-enter {
1362
+ animation: fadeInPage 0.4s ease-out forwards;
1363
+ }
1364
+
1365
+ @keyframes fadeInPage {
1366
+ from {
1367
+ opacity: 0;
1368
+ transform: translateY(15px);
1369
+ }
1370
+
1371
+ to {
1372
+ opacity: 1;
1373
+ transform: translateY(0);
1374
+ }
1375
+ }
1376
+
1377
+ /* --- Flying Cart Animation --- */
1378
+ .flying-img {
1379
+ position: fixed;
1380
+ z-index: 9999;
1381
+ border-radius: 50%;
1382
+ transition: all 0.7s cubic-bezier(0.25, 1, 0.5, 1);
1383
+ box-shadow: var(--shadow-lg);
1384
+ opacity: 0.9;
1385
+ pointer-events: none;
1386
+ }
1387
+
1388
+ @keyframes cartBump {
1389
+ 0% {
1390
+ transform: scale(1);
1391
+ }
1392
+
1393
+ 50% {
1394
+ transform: scale(1.3);
1395
+ color: var(--clr-accent);
1396
+ }
1397
+
1398
+ 100% {
1399
+ transform: scale(1);
1400
+ }
1401
+ }
1402
+
1403
+ .bump {
1404
+ animation: cartBump 0.3s ease-out;
1405
+ }
1406
+
1407
+ /* --- Quick View --- */
1408
+ /* --- Login Tabs --- */
1409
+ .login-tabs {
1410
+ display: flex;
1411
+ margin-bottom: 1.5rem;
1412
+ border-bottom: 2px solid var(--clr-border);
1413
+ }
1414
+
1415
+ .login-tab {
1416
+ flex: 1;
1417
+ padding: 0.75rem;
1418
+ text-align: center;
1419
+ cursor: pointer;
1420
+ font-weight: var(--fw-medium);
1421
+ color: var(--clr-text-muted);
1422
+ transition: all var(--transition-fast);
1423
+ border-bottom: 2px solid transparent;
1424
+ margin-bottom: -2px;
1425
+ }
1426
+
1427
+ .login-tab.active {
1428
+ color: var(--clr-text-primary);
1429
+ border-bottom-color: var(--clr-mid);
1430
+ }
1431
+
1432
+ .login-tab:hover {
1433
+ color: var(--clr-text-primary);
1434
+ }
1435
+
1436
+ /* --- Review Stars --- */
1437
+ .review-stars {
1438
+ display: flex;
1439
+ gap: 4px;
1440
+ font-size: 1.5rem;
1441
+ }
1442
+
1443
+ .review-star {
1444
+ cursor: pointer;
1445
+ color: var(--clr-gray);
1446
+ transition: color var(--transition-fast), transform var(--transition-fast);
1447
+ }
1448
+
1449
+ .review-star.active,
1450
+ .review-star:hover {
1451
+ color: var(--clr-warning);
1452
+ }
1453
+
1454
+ .review-star:hover {
1455
+ transform: scale(1.2);
1456
+ }
1457
+
1458
+ /* --- Product Image Zoom Transition --- */
1459
+ .product-main-image img {
1460
+ transition: transform var(--transition-base);
1461
+ }
1462
+
1463
+ /* --- Light Mode Scrollbar --- */
1464
+ [data-theme="light"]::-webkit-scrollbar-track {
1465
+ background: var(--clr-bg-primary);
1466
+ }
1467
+
1468
+ [data-theme="light"]::-webkit-scrollbar-thumb {
1469
+ background: #B8C4CC;
1470
+ }
1471
+
1472
+ [data-theme="light"]::-webkit-scrollbar-thumb:hover {
1473
+ background: #8A9AA6;
1474
+ }
1475
+
1476
+ /* --- Light Mode Cards & Surfaces --- */
1477
+ [data-theme="light"] .product-card {
1478
+ box-shadow: var(--shadow-sm);
1479
+ }
1480
+
1481
+ [data-theme="light"] .footer {
1482
+ background: #1A2B32;
1483
+ color: #E8EAEC;
1484
+ }
1485
+
1486
+ [data-theme="light"] .footer a {
1487
+ color: #AFB3B7;
1488
+ }
1489
+
1490
+ [data-theme="light"] .footer a:hover {
1491
+ color: #E8EAEC;
1492
+ }
1493
+
1494
+ [data-theme="light"] .footer-heading {
1495
+ color: #E8EAEC;
1496
+ }
1497
+
1498
+ /* --- Skeleton Loader --- */
1499
+ .skeleton {
1500
+ background: linear-gradient(90deg, var(--clr-surface) 25%, rgba(255, 255, 255, 0.06) 50%, var(--clr-surface) 75%);
1501
+ background-size: 400% 100%;
1502
+ animation: shimmer 1.2s cubic-bezier(0.4, 0.0, 0.2, 1) infinite;
1503
+ border-radius: var(--radius-md);
1504
+ }
1505
+
1506
+ [data-theme="light"] .skeleton {
1507
+ background: linear-gradient(90deg, #E2E8F0 25%, #F1F5F9 50%, #E2E8F0 75%);
1508
+ background-size: 400% 100%;
1509
+ }
1510
+
1511
+ @keyframes shimmer {
1512
+ 0% {
1513
+ background-position: 100% 0;
1514
+ }
1515
+
1516
+ 100% {
1517
+ background-position: -100% 0;
1518
+ }
1519
+ }
1520
+
1521
+ .skeleton-text {
1522
+ height: 1em;
1523
+ margin-bottom: 0.5em;
1524
+ border-radius: var(--radius-sm);
1525
+ }
1526
+
1527
+ .skeleton-text.short {
1528
+ width: 60%;
1529
+ }
1530
+
1531
+ .skeleton-text.medium {
1532
+ width: 80%;
1533
+ }
1534
+
1535
+ .skeleton-img {
1536
+ width: 100%;
1537
+ aspect-ratio: 3/4;
1538
+ }
1539
+
1540
+ @keyframes shimmer {
1541
+ 0% {
1542
+ background-position: 200% 0;
1543
+ }
1544
+
1545
+ 100% {
1546
+ background-position: -200% 0;
1547
+ }
1548
+ }
1549
+
1550
+ /* --- Light Mode Mobile Nav --- */
1551
+ [data-theme="light"] .mobile-nav {
1552
+ background: rgba(245, 247, 250, 0.98);
1553
+ }
1554
+
1555
+ [data-theme="light"] .mobile-nav a {
1556
+ color: #3A5060;
1557
+ border-bottom-color: #E0E6EA;
1558
+ }
1559
+
1560
+ [data-theme="light"] .mobile-nav a:hover,
1561
+ [data-theme="light"] .mobile-nav a.active {
1562
+ color: #1A2B32;
1563
+ }
css/global.css ADDED
@@ -0,0 +1,791 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ========================================
2
+ DESIGN SYSTEM — Kiyim-Kechak Do'koni
3
+ ======================================== */
4
+
5
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
6
+
7
+ /* --- CSS Custom Properties --- */
8
+ :root {
9
+ /* Primary Palette — Premium Teal */
10
+ --clr-bg-primary: #0A1A1F;
11
+ --clr-bg-secondary: #0F2830;
12
+ --clr-accent: #1B6B7D;
13
+ --clr-accent-light: #22A5BB;
14
+ --clr-mid: #3D8A9A;
15
+ --clr-light: #B0D4DC;
16
+ --clr-gray: #5A6D75;
17
+
18
+ /* Gold Accent */
19
+ --clr-gold: #D4A847;
20
+ --clr-gold-light: #F0D78C;
21
+
22
+ /* Semantic Colors */
23
+ --clr-text-primary: #ECF0F2;
24
+ --clr-text-secondary: #9BB0B8;
25
+ --clr-text-muted: #5C7A85;
26
+ --clr-surface: rgba(15, 40, 48, 0.85);
27
+ --clr-surface-solid: #0F2830;
28
+ --clr-surface-hover: #153540;
29
+ --clr-border: rgba(59, 138, 154, 0.15);
30
+ --clr-border-hover: rgba(59, 138, 154, 0.35);
31
+
32
+ /* Accent Colors */
33
+ --clr-success: #34D399;
34
+ --clr-warning: #FBBF24;
35
+ --clr-error: #F87171;
36
+ --clr-info: #60A5FA;
37
+
38
+ /* Glass Effect */
39
+ --glass-bg: rgba(15, 40, 48, 0.6);
40
+ --glass-border: rgba(59, 138, 154, 0.12);
41
+ --glass-blur: 24px;
42
+ --glass-highlight: rgba(255, 255, 255, 0.04);
43
+
44
+ /* Typography */
45
+ --font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
46
+ --fs-xs: 0.75rem;
47
+ --fs-sm: 0.875rem;
48
+ --fs-base: 1rem;
49
+ --fs-md: 1.125rem;
50
+ --fs-lg: 1.25rem;
51
+ --fs-xl: 1.5rem;
52
+ --fs-2xl: 2rem;
53
+ --fs-3xl: 2.5rem;
54
+ --fs-4xl: 3.5rem;
55
+ --fw-light: 300;
56
+ --fw-regular: 400;
57
+ --fw-medium: 500;
58
+ --fw-semibold: 600;
59
+ --fw-bold: 700;
60
+ --fw-extrabold: 800;
61
+
62
+ /* Spacing */
63
+ --space-xs: 0.25rem;
64
+ --space-sm: 0.5rem;
65
+ --space-md: 1rem;
66
+ --space-lg: 1.5rem;
67
+ --space-xl: 2rem;
68
+ --space-2xl: 3rem;
69
+ --space-3xl: 4rem;
70
+ --space-4xl: 6rem;
71
+
72
+ /* Border Radius */
73
+ --radius-sm: 8px;
74
+ --radius-md: 12px;
75
+ --radius-lg: 18px;
76
+ --radius-xl: 28px;
77
+ --radius-full: 9999px;
78
+
79
+ /* Shadows — Premium with colored glow */
80
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
81
+ --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.25), 0 0 40px rgba(27, 107, 125, 0.05);
82
+ --shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.3), 0 0 60px rgba(27, 107, 125, 0.08);
83
+ --shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.4), 0 0 80px rgba(27, 107, 125, 0.1);
84
+ --shadow-glow: 0 0 30px rgba(27, 107, 125, 0.25);
85
+ --shadow-glow-gold: 0 0 30px rgba(212, 168, 71, 0.2);
86
+
87
+ /* Transitions */
88
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
89
+ --transition-base: 280ms cubic-bezier(0.4, 0, 0.2, 1);
90
+ --transition-slow: 450ms cubic-bezier(0.4, 0, 0.2, 1);
91
+ --transition-spring: 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
92
+
93
+ /* Layout */
94
+ --container-max: 1280px;
95
+ --container-padding: 1.5rem;
96
+ --navbar-height: 72px;
97
+ }
98
+
99
+ /* --- Light Mode --- */
100
+ [data-theme="light"] {
101
+ --clr-bg-primary: #F3F6F9;
102
+ --clr-bg-secondary: #FFFFFF;
103
+ --clr-accent: #1B6B7D;
104
+ --clr-accent-light: #22A5BB;
105
+ --clr-mid: #3D8A9A;
106
+ --clr-light: #1A3540;
107
+ --clr-gray: #8899A4;
108
+
109
+ --clr-gold: #B8922F;
110
+ --clr-gold-light: #D4A847;
111
+
112
+ --clr-text-primary: #0F2830;
113
+ --clr-text-secondary: #3A5A66;
114
+ --clr-text-muted: #6A8A96;
115
+ --clr-surface: rgba(255, 255, 255, 0.95);
116
+ --clr-surface-solid: #FFFFFF;
117
+ --clr-surface-hover: #EBF2F5;
118
+ --clr-border: rgba(27, 107, 125, 0.1);
119
+ --clr-border-hover: rgba(27, 107, 125, 0.25);
120
+
121
+ --glass-bg: rgba(255, 255, 255, 0.85);
122
+ --glass-border: rgba(27, 107, 125, 0.08);
123
+ --glass-highlight: rgba(255, 255, 255, 0.5);
124
+
125
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
126
+ --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.06), 0 0 40px rgba(27, 107, 125, 0.03);
127
+ --shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.08), 0 0 60px rgba(27, 107, 125, 0.04);
128
+ --shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.1), 0 0 80px rgba(27, 107, 125, 0.06);
129
+ --shadow-glow: 0 0 30px rgba(27, 107, 125, 0.12);
130
+ --shadow-glow-gold: 0 0 30px rgba(212, 168, 71, 0.1);
131
+ }
132
+
133
+ /* --- Reset --- */
134
+ *,
135
+ *::before,
136
+ *::after {
137
+ margin: 0;
138
+ padding: 0;
139
+ box-sizing: border-box;
140
+ }
141
+
142
+ html {
143
+ scroll-behavior: smooth;
144
+ -webkit-text-size-adjust: 100%;
145
+ }
146
+
147
+ body {
148
+ font-family: var(--font-family);
149
+ font-size: var(--fs-base);
150
+ font-weight: var(--fw-regular);
151
+ line-height: 1.6;
152
+ color: var(--clr-text-primary);
153
+ background-color: var(--clr-bg-primary);
154
+ background-image:
155
+ radial-gradient(ellipse at 20% 50%, rgba(27, 107, 125, 0.06) 0%, transparent 50%),
156
+ radial-gradient(ellipse at 80% 20%, rgba(61, 138, 154, 0.04) 0%, transparent 50%),
157
+ radial-gradient(ellipse at 50% 100%, rgba(212, 168, 71, 0.02) 0%, transparent 40%);
158
+ min-height: 100vh;
159
+ overflow-x: hidden;
160
+ -webkit-font-smoothing: antialiased;
161
+ -moz-osx-font-smoothing: grayscale;
162
+ }
163
+
164
+ a {
165
+ text-decoration: none;
166
+ color: inherit;
167
+ transition: color var(--transition-fast);
168
+ }
169
+
170
+ a:hover {
171
+ color: var(--clr-light);
172
+ }
173
+
174
+ img {
175
+ max-width: 100%;
176
+ height: auto;
177
+ display: block;
178
+ }
179
+
180
+ ul,
181
+ ol {
182
+ list-style: none;
183
+ }
184
+
185
+ button {
186
+ cursor: pointer;
187
+ border: none;
188
+ background: none;
189
+ font-family: inherit;
190
+ font-size: inherit;
191
+ color: inherit;
192
+ outline: none;
193
+ }
194
+
195
+ input,
196
+ select,
197
+ textarea {
198
+ font-family: inherit;
199
+ font-size: inherit;
200
+ color: inherit;
201
+ outline: none;
202
+ border: none;
203
+ background: none;
204
+ }
205
+
206
+ /* --- Dark Mode Switch --- */
207
+ .dark-mode-toggle {
208
+ background: none;
209
+ border: none;
210
+ font-size: var(--fs-xl);
211
+ cursor: pointer;
212
+ color: var(--clr-text-secondary);
213
+ transition: transform var(--transition-spring), color var(--transition-fast);
214
+ }
215
+
216
+ .dark-mode-toggle:hover {
217
+ transform: scale(1.1) rotate(15deg);
218
+ color: var(--clr-text-primary);
219
+ }
220
+
221
+ /* --- Custom Scrollbar --- */
222
+ ::-webkit-scrollbar {
223
+ width: 8px;
224
+ height: 8px;
225
+ }
226
+
227
+ ::-webkit-scrollbar-track {
228
+ background: var(--clr-bg-primary);
229
+ }
230
+
231
+ ::-webkit-scrollbar-thumb {
232
+ background: var(--clr-mid);
233
+ border-radius: var(--radius-full);
234
+ }
235
+
236
+ ::-webkit-scrollbar-thumb:hover {
237
+ background: var(--clr-mid);
238
+ }
239
+
240
+ /* --- Container --- */
241
+ .container {
242
+ max-width: var(--container-max);
243
+ margin: 0 auto;
244
+ padding: 0 var(--container-padding);
245
+ width: 100%;
246
+ }
247
+
248
+ .container-narrow {
249
+ max-width: 960px;
250
+ margin: 0 auto;
251
+ padding: 0 var(--container-padding);
252
+ }
253
+
254
+ .container-wide {
255
+ max-width: 1440px;
256
+ margin: 0 auto;
257
+ padding: 0 var(--container-padding);
258
+ }
259
+
260
+ /* --- Grid System --- */
261
+ .grid {
262
+ display: grid;
263
+ gap: var(--space-lg);
264
+ }
265
+
266
+ .grid-2 {
267
+ grid-template-columns: repeat(2, 1fr);
268
+ }
269
+
270
+ .grid-3 {
271
+ grid-template-columns: repeat(3, 1fr);
272
+ }
273
+
274
+ .grid-4 {
275
+ grid-template-columns: repeat(4, 1fr);
276
+ }
277
+
278
+ .grid-5 {
279
+ grid-template-columns: repeat(5, 1fr);
280
+ }
281
+
282
+ /* --- Flexbox Utilities --- */
283
+ .flex {
284
+ display: flex;
285
+ }
286
+
287
+ .flex-col {
288
+ flex-direction: column;
289
+ }
290
+
291
+ .flex-wrap {
292
+ flex-wrap: wrap;
293
+ }
294
+
295
+ .items-center {
296
+ align-items: center;
297
+ }
298
+
299
+ .items-start {
300
+ align-items: flex-start;
301
+ }
302
+
303
+ .items-end {
304
+ align-items: flex-end;
305
+ }
306
+
307
+ .justify-center {
308
+ justify-content: center;
309
+ }
310
+
311
+ .justify-between {
312
+ justify-content: space-between;
313
+ }
314
+
315
+ .justify-end {
316
+ justify-content: flex-end;
317
+ }
318
+
319
+ .gap-xs {
320
+ gap: var(--space-xs);
321
+ }
322
+
323
+ .gap-sm {
324
+ gap: var(--space-sm);
325
+ }
326
+
327
+ .gap-md {
328
+ gap: var(--space-md);
329
+ }
330
+
331
+ .gap-lg {
332
+ gap: var(--space-lg);
333
+ }
334
+
335
+ .gap-xl {
336
+ gap: var(--space-xl);
337
+ }
338
+
339
+ .gap-2xl {
340
+ gap: var(--space-2xl);
341
+ }
342
+
343
+ /* --- Typography --- */
344
+ h1,
345
+ h2,
346
+ h3,
347
+ h4,
348
+ h5,
349
+ h6 {
350
+ line-height: 1.2;
351
+ font-weight: var(--fw-bold);
352
+ color: var(--clr-text-primary);
353
+ }
354
+
355
+ h1 {
356
+ font-size: var(--fs-4xl);
357
+ }
358
+
359
+ h2 {
360
+ font-size: var(--fs-3xl);
361
+ }
362
+
363
+ h3 {
364
+ font-size: var(--fs-2xl);
365
+ }
366
+
367
+ h4 {
368
+ font-size: var(--fs-xl);
369
+ }
370
+
371
+ h5 {
372
+ font-size: var(--fs-lg);
373
+ }
374
+
375
+ h6 {
376
+ font-size: var(--fs-md);
377
+ }
378
+
379
+ .text-xs {
380
+ font-size: var(--fs-xs);
381
+ }
382
+
383
+ .text-sm {
384
+ font-size: var(--fs-sm);
385
+ }
386
+
387
+ .text-base {
388
+ font-size: var(--fs-base);
389
+ }
390
+
391
+ .text-md {
392
+ font-size: var(--fs-md);
393
+ }
394
+
395
+ .text-lg {
396
+ font-size: var(--fs-lg);
397
+ }
398
+
399
+ .text-xl {
400
+ font-size: var(--fs-xl);
401
+ }
402
+
403
+ .text-2xl {
404
+ font-size: var(--fs-2xl);
405
+ }
406
+
407
+ .text-light {
408
+ font-weight: var(--fw-light);
409
+ }
410
+
411
+ .text-medium {
412
+ font-weight: var(--fw-medium);
413
+ }
414
+
415
+ .text-semibold {
416
+ font-weight: var(--fw-semibold);
417
+ }
418
+
419
+ .text-bold {
420
+ font-weight: var(--fw-bold);
421
+ }
422
+
423
+ .text-muted {
424
+ color: var(--clr-text-muted);
425
+ }
426
+
427
+ .text-secondary {
428
+ color: var(--clr-text-secondary);
429
+ }
430
+
431
+ .text-center {
432
+ text-align: center;
433
+ }
434
+
435
+ .text-uppercase {
436
+ text-transform: uppercase;
437
+ letter-spacing: 0.08em;
438
+ }
439
+
440
+ /* --- Spacing Utilities --- */
441
+ .mt-sm {
442
+ margin-top: var(--space-sm);
443
+ }
444
+
445
+ .mt-md {
446
+ margin-top: var(--space-md);
447
+ }
448
+
449
+ .mt-lg {
450
+ margin-top: var(--space-lg);
451
+ }
452
+
453
+ .mt-xl {
454
+ margin-top: var(--space-xl);
455
+ }
456
+
457
+ .mt-2xl {
458
+ margin-top: var(--space-2xl);
459
+ }
460
+
461
+ .mt-3xl {
462
+ margin-top: var(--space-3xl);
463
+ }
464
+
465
+ .mb-sm {
466
+ margin-bottom: var(--space-sm);
467
+ }
468
+
469
+ .mb-md {
470
+ margin-bottom: var(--space-md);
471
+ }
472
+
473
+ .mb-lg {
474
+ margin-bottom: var(--space-lg);
475
+ }
476
+
477
+ .mb-xl {
478
+ margin-bottom: var(--space-xl);
479
+ }
480
+
481
+ .mb-2xl {
482
+ margin-bottom: var(--space-2xl);
483
+ }
484
+
485
+ .mb-3xl {
486
+ margin-bottom: var(--space-3xl);
487
+ }
488
+
489
+ .pt-sm {
490
+ padding-top: var(--space-sm);
491
+ }
492
+
493
+ .pt-md {
494
+ padding-top: var(--space-md);
495
+ }
496
+
497
+ .pt-lg {
498
+ padding-top: var(--space-lg);
499
+ }
500
+
501
+ .pt-xl {
502
+ padding-top: var(--space-xl);
503
+ }
504
+
505
+ .section-padding {
506
+ padding: var(--space-4xl) 0;
507
+ }
508
+
509
+ /* --- Animations --- */
510
+ @keyframes fadeIn {
511
+ from {
512
+ opacity: 0;
513
+ }
514
+
515
+ to {
516
+ opacity: 1;
517
+ }
518
+ }
519
+
520
+ @keyframes fadeInUp {
521
+ from {
522
+ opacity: 0;
523
+ transform: translateY(24px);
524
+ }
525
+
526
+ to {
527
+ opacity: 1;
528
+ transform: translateY(0);
529
+ }
530
+ }
531
+
532
+ @keyframes fadeInDown {
533
+ from {
534
+ opacity: 0;
535
+ transform: translateY(-24px);
536
+ }
537
+
538
+ to {
539
+ opacity: 1;
540
+ transform: translateY(0);
541
+ }
542
+ }
543
+
544
+ @keyframes slideInLeft {
545
+ from {
546
+ opacity: 0;
547
+ transform: translateX(-32px);
548
+ }
549
+
550
+ to {
551
+ opacity: 1;
552
+ transform: translateX(0);
553
+ }
554
+ }
555
+
556
+ @keyframes slideInRight {
557
+ from {
558
+ opacity: 0;
559
+ transform: translateX(32px);
560
+ }
561
+
562
+ to {
563
+ opacity: 1;
564
+ transform: translateX(0);
565
+ }
566
+ }
567
+
568
+ @keyframes scaleIn {
569
+ from {
570
+ opacity: 0;
571
+ transform: scale(0.9);
572
+ }
573
+
574
+ to {
575
+ opacity: 1;
576
+ transform: scale(1);
577
+ }
578
+ }
579
+
580
+ @keyframes pulse {
581
+
582
+ 0%,
583
+ 100% {
584
+ transform: scale(1);
585
+ }
586
+
587
+ 50% {
588
+ transform: scale(1.05);
589
+ }
590
+ }
591
+
592
+ @keyframes shimmer {
593
+ 0% {
594
+ background-position: -200% 0;
595
+ }
596
+
597
+ 100% {
598
+ background-position: 200% 0;
599
+ }
600
+ }
601
+
602
+ @keyframes float {
603
+
604
+ 0%,
605
+ 100% {
606
+ transform: translateY(0);
607
+ }
608
+
609
+ 50% {
610
+ transform: translateY(-8px);
611
+ }
612
+ }
613
+
614
+ .animate-fade-in {
615
+ animation: fadeIn var(--transition-slow) ease-out;
616
+ }
617
+
618
+ .animate-fade-in-up {
619
+ animation: fadeInUp var(--transition-slow) ease-out;
620
+ }
621
+
622
+ .animate-fade-in-down {
623
+ animation: fadeInDown var(--transition-slow) ease-out;
624
+ }
625
+
626
+ .animate-slide-left {
627
+ animation: slideInLeft var(--transition-slow) ease-out;
628
+ }
629
+
630
+ .animate-slide-right {
631
+ animation: slideInRight var(--transition-slow) ease-out;
632
+ }
633
+
634
+ .animate-scale-in {
635
+ animation: scaleIn var(--transition-spring);
636
+ }
637
+
638
+ /* Staggered Animation */
639
+ .stagger>* {
640
+ opacity: 0;
641
+ animation: fadeInUp 0.5s ease-out forwards;
642
+ }
643
+
644
+ .stagger>*:nth-child(1) {
645
+ animation-delay: 0.05s;
646
+ }
647
+
648
+ .stagger>*:nth-child(2) {
649
+ animation-delay: 0.1s;
650
+ }
651
+
652
+ .stagger>*:nth-child(3) {
653
+ animation-delay: 0.15s;
654
+ }
655
+
656
+ .stagger>*:nth-child(4) {
657
+ animation-delay: 0.2s;
658
+ }
659
+
660
+ .stagger>*:nth-child(5) {
661
+ animation-delay: 0.25s;
662
+ }
663
+
664
+ .stagger>*:nth-child(6) {
665
+ animation-delay: 0.3s;
666
+ }
667
+
668
+ .stagger>*:nth-child(7) {
669
+ animation-delay: 0.35s;
670
+ }
671
+
672
+ .stagger>*:nth-child(8) {
673
+ animation-delay: 0.4s;
674
+ }
675
+
676
+ /* Intersection Observer Animation */
677
+ .reveal {
678
+ opacity: 0;
679
+ transform: translateY(30px);
680
+ transition: opacity 0.6s ease-out, transform 0.6s ease-out;
681
+ }
682
+
683
+ .reveal.visible {
684
+ opacity: 1;
685
+ transform: translateY(0);
686
+ }
687
+
688
+ /* --- Page Layout --- */
689
+ .page-wrapper {
690
+ min-height: 100vh;
691
+ display: flex;
692
+ flex-direction: column;
693
+ }
694
+
695
+ .page-content {
696
+ flex: 1;
697
+ padding-top: var(--navbar-height);
698
+ }
699
+
700
+ /* --- Section Headers --- */
701
+ .section-header {
702
+ text-align: center;
703
+ margin-bottom: var(--space-3xl);
704
+ }
705
+
706
+ .section-header h2 {
707
+ margin-bottom: var(--space-sm);
708
+ }
709
+
710
+ .section-header .section-subtitle {
711
+ color: var(--clr-text-muted);
712
+ font-size: var(--fs-md);
713
+ max-width: 600px;
714
+ margin: 0 auto;
715
+ }
716
+
717
+ .section-header .section-line {
718
+ width: 60px;
719
+ height: 3px;
720
+ background: linear-gradient(90deg, var(--clr-accent), var(--clr-mid));
721
+ margin: var(--space-md) auto 0;
722
+ border-radius: var(--radius-full);
723
+ }
724
+
725
+ /* --- Responsive Breakpoints --- */
726
+ @media (max-width: 1024px) {
727
+ :root {
728
+ --fs-4xl: 2.5rem;
729
+ --fs-3xl: 2rem;
730
+ }
731
+
732
+ .grid-4 {
733
+ grid-template-columns: repeat(3, 1fr);
734
+ }
735
+
736
+ .grid-5 {
737
+ grid-template-columns: repeat(3, 1fr);
738
+ }
739
+ }
740
+
741
+ @media (max-width: 768px) {
742
+ :root {
743
+ --fs-4xl: 2rem;
744
+ --fs-3xl: 1.75rem;
745
+ --fs-2xl: 1.5rem;
746
+ --container-padding: 1rem;
747
+ --navbar-height: 64px;
748
+ }
749
+
750
+ .grid-2 {
751
+ grid-template-columns: 1fr;
752
+ }
753
+
754
+ .grid-3 {
755
+ grid-template-columns: repeat(2, 1fr);
756
+ }
757
+
758
+ .grid-4 {
759
+ grid-template-columns: repeat(2, 1fr);
760
+ }
761
+
762
+ .grid-5 {
763
+ grid-template-columns: repeat(2, 1fr);
764
+ }
765
+
766
+ .section-padding {
767
+ padding: var(--space-3xl) 0;
768
+ }
769
+
770
+ .hide-mobile {
771
+ display: none !important;
772
+ }
773
+ }
774
+
775
+ @media (max-width: 480px) {
776
+ .grid-3 {
777
+ grid-template-columns: 1fr;
778
+ }
779
+
780
+ .grid-4 {
781
+ grid-template-columns: 1fr;
782
+ }
783
+ }
784
+
785
+ /* --- Skeleton Loading --- */
786
+ .skeleton {
787
+ background: linear-gradient(90deg, var(--clr-surface) 25%, var(--clr-accent) 50%, var(--clr-surface) 75%);
788
+ background-size: 200% 100%;
789
+ animation: shimmer 1.5s infinite;
790
+ border-radius: var(--radius-md);
791
+ }
css/pages.css ADDED
@@ -0,0 +1,1144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ========================================
2
+ PAGES — Page-Specific Styles
3
+ ======================================== */
4
+
5
+ /* === HERO SECTION === */
6
+ .hero {
7
+ position: relative;
8
+ min-height: 85vh;
9
+ display: flex;
10
+ align-items: center;
11
+ overflow: hidden;
12
+ background: linear-gradient(135deg, var(--clr-bg-primary) 0%, var(--clr-bg-secondary) 50%, var(--clr-accent) 100%);
13
+ background-attachment: fixed;
14
+ }
15
+
16
+ .hero::before {
17
+ content: '';
18
+ position: absolute;
19
+ top: -50%;
20
+ right: -20%;
21
+ width: 80%;
22
+ height: 200%;
23
+ background: radial-gradient(ellipse, rgba(45, 74, 83, 0.3) 0%, transparent 70%);
24
+ animation: float 8s ease-in-out infinite;
25
+ }
26
+
27
+ .hero::after {
28
+ content: '';
29
+ position: absolute;
30
+ bottom: -30%;
31
+ left: -10%;
32
+ width: 60%;
33
+ height: 150%;
34
+ background: radial-gradient(ellipse, rgba(105, 129, 141, 0.15) 0%, transparent 70%);
35
+ animation: float 6s ease-in-out infinite reverse;
36
+ }
37
+
38
+ .hero-content {
39
+ position: relative;
40
+ z-index: 1;
41
+ max-width: 650px;
42
+ }
43
+
44
+ .hero-tag {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ gap: var(--space-sm);
48
+ background: var(--glass-bg);
49
+ backdrop-filter: blur(12px);
50
+ border: 1px solid var(--glass-border);
51
+ padding: 0.4rem 1rem;
52
+ border-radius: var(--radius-full);
53
+ font-size: var(--fs-xs);
54
+ color: var(--clr-text-secondary);
55
+ margin-bottom: var(--space-xl);
56
+ animation: fadeInDown 0.6s ease-out;
57
+ }
58
+
59
+ .hero-title {
60
+ font-size: clamp(2.5rem, 5vw, 4rem);
61
+ font-weight: var(--fw-extrabold);
62
+ line-height: 1.1;
63
+ margin-bottom: var(--space-lg);
64
+ animation: fadeInUp 0.6s ease-out 0.1s both;
65
+ }
66
+
67
+ .hero-title span {
68
+ background: linear-gradient(135deg, var(--clr-light), var(--clr-mid));
69
+ -webkit-background-clip: text;
70
+ -webkit-text-fill-color: transparent;
71
+ background-clip: text;
72
+ }
73
+
74
+ .hero-desc {
75
+ font-size: var(--fs-md);
76
+ color: var(--clr-text-secondary);
77
+ line-height: 1.7;
78
+ margin-bottom: var(--space-2xl);
79
+ max-width: 480px;
80
+ animation: fadeInUp 0.6s ease-out 0.2s both;
81
+ }
82
+
83
+ .hero-actions {
84
+ display: flex;
85
+ gap: var(--space-md);
86
+ animation: fadeInUp 0.6s ease-out 0.3s both;
87
+ }
88
+
89
+ .hero-stats {
90
+ display: flex;
91
+ gap: var(--space-2xl);
92
+ margin-top: var(--space-3xl);
93
+ animation: fadeInUp 0.6s ease-out 0.4s both;
94
+ }
95
+
96
+ .hero-stat-num {
97
+ font-size: var(--fs-2xl);
98
+ font-weight: var(--fw-bold);
99
+ }
100
+
101
+ .hero-stat-label {
102
+ font-size: var(--fs-xs);
103
+ color: var(--clr-text-muted);
104
+ text-transform: uppercase;
105
+ letter-spacing: 0.06em;
106
+ }
107
+
108
+ /* Hero Dots (Slider Navigation) */
109
+ .hero-dots {
110
+ display: flex;
111
+ gap: var(--space-sm);
112
+ margin-top: var(--space-xl);
113
+ }
114
+
115
+ .hero-dot {
116
+ width: 12px;
117
+ height: 12px;
118
+ border-radius: var(--radius-full);
119
+ background: var(--clr-gray);
120
+ opacity: 0.4;
121
+ cursor: pointer;
122
+ transition: all var(--transition-base);
123
+ border: none;
124
+ }
125
+
126
+ .hero-dot.active {
127
+ opacity: 1;
128
+ width: 36px;
129
+ background: var(--clr-mid);
130
+ }
131
+
132
+ .hero-dot:hover {
133
+ opacity: 0.8;
134
+ }
135
+
136
+ /* Light mode navbar override */
137
+ [data-theme="light"] .navbar {
138
+ background: rgba(245, 247, 250, 0.9);
139
+ }
140
+
141
+ [data-theme="light"] .navbar.scrolled {
142
+ background: rgba(245, 247, 250, 0.97);
143
+ }
144
+
145
+ [data-theme="light"] .product-card-action-btn {
146
+ background: rgba(255, 255, 255, 0.85);
147
+ }
148
+
149
+ [data-theme="light"] .hero-title span {
150
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-mid));
151
+ -webkit-background-clip: text;
152
+ -webkit-text-fill-color: transparent;
153
+ background-clip: text;
154
+ }
155
+
156
+ [data-theme="light"] .hero {
157
+ background: linear-gradient(135deg, #EEF3F7 0%, #D8E4EA 50%, #B8CED8 100%);
158
+ }
159
+
160
+ [data-theme="light"] .hero-title {
161
+ color: #1A2B32;
162
+ }
163
+
164
+ [data-theme="light"] .hero-desc {
165
+ color: #3A5060;
166
+ }
167
+
168
+ [data-theme="light"] .hero-tag {
169
+ background: rgba(255, 255, 255, 0.7);
170
+ color: #3A5060;
171
+ }
172
+
173
+ /* === CATEGORIES SECTION === */
174
+ .categories-grid {
175
+ display: grid;
176
+ grid-template-columns: repeat(5, 1fr);
177
+ gap: var(--space-lg);
178
+ }
179
+
180
+ .category-card {
181
+ background: var(--clr-surface);
182
+ border: 1px solid var(--clr-border);
183
+ border-radius: var(--radius-lg);
184
+ padding: var(--space-2xl) var(--space-lg);
185
+ text-align: center;
186
+ transition: all var(--transition-base);
187
+ cursor: pointer;
188
+ position: relative;
189
+ overflow: hidden;
190
+ }
191
+
192
+ .category-card::before {
193
+ content: '';
194
+ position: absolute;
195
+ inset: 0;
196
+ background: linear-gradient(135deg, rgba(45, 74, 83, 0.1), transparent);
197
+ opacity: 0;
198
+ transition: opacity var(--transition-base);
199
+ }
200
+
201
+ .category-card:hover {
202
+ transform: translateY(-4px);
203
+ box-shadow: var(--shadow-lg);
204
+ border-color: var(--clr-accent);
205
+ }
206
+
207
+ .category-card:hover::before {
208
+ opacity: 1;
209
+ }
210
+
211
+ .category-card-icon {
212
+ font-size: 2.5rem;
213
+ margin-bottom: var(--space-md);
214
+ display: block;
215
+ }
216
+
217
+ .category-card-name {
218
+ font-weight: var(--fw-semibold);
219
+ margin-bottom: var(--space-xs);
220
+ }
221
+
222
+ .category-card-count {
223
+ font-size: var(--fs-xs);
224
+ color: var(--clr-text-muted);
225
+ }
226
+
227
+ /* === FEATURES SECTION === */
228
+ .features-grid {
229
+ display: grid;
230
+ grid-template-columns: repeat(4, 1fr);
231
+ gap: var(--space-lg);
232
+ }
233
+
234
+ .feature-card {
235
+ text-align: center;
236
+ padding: var(--space-xl);
237
+ border-radius: var(--radius-lg);
238
+ background: var(--clr-surface);
239
+ border: 1px solid var(--clr-border);
240
+ transition: all var(--transition-base);
241
+ }
242
+
243
+ .feature-card:hover {
244
+ transform: translateY(-4px);
245
+ box-shadow: var(--shadow-md);
246
+ }
247
+
248
+ .feature-icon {
249
+ font-size: 2rem;
250
+ margin-bottom: var(--space-md);
251
+ }
252
+
253
+ .feature-title {
254
+ font-weight: var(--fw-semibold);
255
+ margin-bottom: var(--space-xs);
256
+ font-size: var(--fs-sm);
257
+ }
258
+
259
+ .feature-desc {
260
+ font-size: var(--fs-xs);
261
+ color: var(--clr-text-muted);
262
+ }
263
+
264
+ /* === PROMO BANNER === */
265
+ .promo-banner {
266
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-bg-secondary));
267
+ border-radius: var(--radius-xl);
268
+ padding: var(--space-3xl);
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: space-between;
272
+ position: relative;
273
+ overflow: hidden;
274
+ }
275
+
276
+ .promo-banner::before {
277
+ content: '';
278
+ position: absolute;
279
+ top: -50%;
280
+ right: -20%;
281
+ width: 60%;
282
+ height: 200%;
283
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.05), transparent);
284
+ }
285
+
286
+ .promo-content {
287
+ position: relative;
288
+ z-index: 1;
289
+ }
290
+
291
+ .promo-tag {
292
+ font-size: var(--fs-xs);
293
+ color: var(--clr-warning);
294
+ text-transform: uppercase;
295
+ letter-spacing: 0.1em;
296
+ font-weight: var(--fw-semibold);
297
+ margin-bottom: var(--space-sm);
298
+ }
299
+
300
+ .promo-title {
301
+ font-size: var(--fs-3xl);
302
+ font-weight: var(--fw-bold);
303
+ margin-bottom: var(--space-sm);
304
+ }
305
+
306
+ .promo-desc {
307
+ color: var(--clr-text-secondary);
308
+ margin-bottom: var(--space-xl);
309
+ }
310
+
311
+ /* === CATALOG PAGE === */
312
+ .catalog-layout {
313
+ display: grid;
314
+ grid-template-columns: 260px 1fr;
315
+ gap: var(--space-2xl);
316
+ align-items: start;
317
+ }
318
+
319
+ .catalog-sidebar {
320
+ position: sticky;
321
+ top: calc(var(--navbar-height) + var(--space-lg));
322
+ background: var(--clr-surface);
323
+ border: 1px solid var(--clr-border);
324
+ border-radius: var(--radius-lg);
325
+ padding: var(--space-xl);
326
+ }
327
+
328
+ .filter-section {
329
+ margin-bottom: var(--space-xl);
330
+ }
331
+
332
+ .filter-section:last-child {
333
+ margin-bottom: 0;
334
+ }
335
+
336
+ .filter-title {
337
+ font-size: var(--fs-sm);
338
+ font-weight: var(--fw-semibold);
339
+ color: var(--clr-text-secondary);
340
+ margin-bottom: var(--space-md);
341
+ text-transform: uppercase;
342
+ letter-spacing: 0.06em;
343
+ }
344
+
345
+ .filter-options {
346
+ display: flex;
347
+ flex-direction: column;
348
+ gap: var(--space-sm);
349
+ }
350
+
351
+ .filter-option {
352
+ display: flex;
353
+ align-items: center;
354
+ gap: var(--space-sm);
355
+ padding: 0.4rem 0.6rem;
356
+ border-radius: var(--radius-sm);
357
+ cursor: pointer;
358
+ transition: all var(--transition-fast);
359
+ font-size: var(--fs-sm);
360
+ color: var(--clr-text-secondary);
361
+ }
362
+
363
+ .filter-option:hover {
364
+ background: rgba(255, 255, 255, 0.03);
365
+ color: var(--clr-text-primary);
366
+ }
367
+
368
+ .filter-option.active {
369
+ background: var(--clr-accent);
370
+ color: var(--clr-text-primary);
371
+ }
372
+
373
+ .filter-checkbox {
374
+ width: 18px;
375
+ height: 18px;
376
+ border: 1.5px solid var(--clr-border);
377
+ border-radius: 4px;
378
+ display: flex;
379
+ align-items: center;
380
+ justify-content: center;
381
+ flex-shrink: 0;
382
+ transition: all var(--transition-fast);
383
+ }
384
+
385
+ .filter-option.active .filter-checkbox {
386
+ background: var(--clr-mid);
387
+ border-color: var(--clr-mid);
388
+ }
389
+
390
+ .filter-option.active .filter-checkbox::after {
391
+ content: '✓';
392
+ font-size: 11px;
393
+ color: white;
394
+ }
395
+
396
+ .price-range {
397
+ display: flex;
398
+ gap: var(--space-sm);
399
+ align-items: center;
400
+ }
401
+
402
+ .price-range input {
403
+ width: 100%;
404
+ padding: 0.5rem;
405
+ background: var(--clr-bg-primary);
406
+ border: 1px solid var(--clr-border);
407
+ border-radius: var(--radius-sm);
408
+ color: var(--clr-text-primary);
409
+ font-size: var(--fs-xs);
410
+ text-align: center;
411
+ }
412
+
413
+ .catalog-toolbar {
414
+ display: flex;
415
+ justify-content: space-between;
416
+ align-items: center;
417
+ margin-bottom: var(--space-lg);
418
+ padding-bottom: var(--space-md);
419
+ border-bottom: 1px solid var(--clr-border);
420
+ }
421
+
422
+ .catalog-count {
423
+ font-size: var(--fs-sm);
424
+ color: var(--clr-text-muted);
425
+ }
426
+
427
+ .catalog-sort-group {
428
+ display: flex;
429
+ align-items: center;
430
+ gap: var(--space-md);
431
+ }
432
+
433
+ .view-toggle {
434
+ display: flex;
435
+ gap: var(--space-xs);
436
+ }
437
+
438
+ .view-btn {
439
+ width: 36px;
440
+ height: 36px;
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: center;
444
+ border-radius: var(--radius-sm);
445
+ color: var(--clr-text-muted);
446
+ transition: all var(--transition-fast);
447
+ }
448
+
449
+ .view-btn:hover,
450
+ .view-btn.active {
451
+ background: var(--clr-surface);
452
+ color: var(--clr-text-primary);
453
+ }
454
+
455
+ .products-grid {
456
+ display: grid;
457
+ grid-template-columns: repeat(3, 1fr);
458
+ gap: var(--space-lg);
459
+ }
460
+
461
+ .products-grid.list-view {
462
+ grid-template-columns: 1fr;
463
+ }
464
+
465
+ .products-grid.list-view .product-card {
466
+ display: grid;
467
+ grid-template-columns: 200px 1fr;
468
+ }
469
+
470
+ .products-grid.list-view .product-card-image {
471
+ aspect-ratio: 1;
472
+ }
473
+
474
+ /* === PRODUCT DETAIL PAGE === */
475
+ .product-detail {
476
+ display: grid;
477
+ grid-template-columns: 1fr 1fr;
478
+ gap: var(--space-3xl);
479
+ align-items: start;
480
+ }
481
+
482
+ .product-gallery {
483
+ position: sticky;
484
+ top: calc(var(--navbar-height) + var(--space-lg));
485
+ }
486
+
487
+ .product-main-image {
488
+ width: 100%;
489
+ aspect-ratio: 3/4;
490
+ border-radius: var(--radius-lg);
491
+ overflow: hidden;
492
+ background: var(--clr-surface);
493
+ border: 1px solid var(--clr-border);
494
+ margin-bottom: var(--space-md);
495
+ }
496
+
497
+ .product-main-image img {
498
+ width: 100%;
499
+ height: 100%;
500
+ object-fit: cover;
501
+ }
502
+
503
+ .product-thumbnails {
504
+ display: flex;
505
+ gap: var(--space-sm);
506
+ }
507
+
508
+ .product-thumbnail {
509
+ width: 72px;
510
+ height: 72px;
511
+ border-radius: var(--radius-md);
512
+ overflow: hidden;
513
+ cursor: pointer;
514
+ border: 2px solid transparent;
515
+ transition: all var(--transition-fast);
516
+ opacity: 0.6;
517
+ }
518
+
519
+ .product-thumbnail:hover,
520
+ .product-thumbnail.active {
521
+ opacity: 1;
522
+ border-color: var(--clr-mid);
523
+ }
524
+
525
+ .product-thumbnail img {
526
+ width: 100%;
527
+ height: 100%;
528
+ object-fit: cover;
529
+ }
530
+
531
+ .product-info-header {
532
+ margin-bottom: var(--space-xl);
533
+ }
534
+
535
+ .product-info-category {
536
+ font-size: var(--fs-xs);
537
+ color: var(--clr-text-muted);
538
+ text-transform: uppercase;
539
+ letter-spacing: 0.08em;
540
+ margin-bottom: var(--space-sm);
541
+ }
542
+
543
+ .product-info-title {
544
+ font-size: var(--fs-2xl);
545
+ font-weight: var(--fw-bold);
546
+ margin-bottom: var(--space-md);
547
+ }
548
+
549
+ .product-info-rating {
550
+ display: flex;
551
+ align-items: center;
552
+ gap: var(--space-sm);
553
+ margin-bottom: var(--space-lg);
554
+ }
555
+
556
+ .product-info-rating .stars {
557
+ color: var(--clr-warning);
558
+ }
559
+
560
+ .product-info-rating .rating-text {
561
+ color: var(--clr-text-muted);
562
+ font-size: var(--fs-sm);
563
+ }
564
+
565
+ .product-info-price {
566
+ display: flex;
567
+ align-items: baseline;
568
+ gap: var(--space-md);
569
+ margin-bottom: var(--space-xl);
570
+ }
571
+
572
+ .product-info-price .price-current {
573
+ font-size: var(--fs-3xl);
574
+ font-weight: var(--fw-bold);
575
+ }
576
+
577
+ .product-info-price .price-old {
578
+ font-size: var(--fs-lg);
579
+ }
580
+
581
+ .product-info-price .discount-badge {
582
+ background: var(--clr-error);
583
+ color: white;
584
+ padding: 0.2rem 0.6rem;
585
+ border-radius: var(--radius-full);
586
+ font-size: var(--fs-xs);
587
+ font-weight: var(--fw-semibold);
588
+ }
589
+
590
+ .product-option-group {
591
+ margin-bottom: var(--space-xl);
592
+ }
593
+
594
+ .product-option-label {
595
+ font-size: var(--fs-sm);
596
+ font-weight: var(--fw-medium);
597
+ color: var(--clr-text-secondary);
598
+ margin-bottom: var(--space-sm);
599
+ }
600
+
601
+ .product-option-label span {
602
+ color: var(--clr-text-primary);
603
+ }
604
+
605
+ .product-actions {
606
+ display: flex;
607
+ gap: var(--space-md);
608
+ margin-bottom: var(--space-xl);
609
+ padding-bottom: var(--space-xl);
610
+ border-bottom: 1px solid var(--clr-border);
611
+ }
612
+
613
+ .product-actions .btn {
614
+ flex: 1;
615
+ }
616
+
617
+ .product-features {
618
+ display: flex;
619
+ gap: var(--space-xl);
620
+ padding: var(--space-lg) 0;
621
+ }
622
+
623
+ .product-feature-item {
624
+ display: flex;
625
+ align-items: center;
626
+ gap: var(--space-sm);
627
+ font-size: var(--fs-sm);
628
+ color: var(--clr-text-muted);
629
+ }
630
+
631
+ /* === CART PAGE === */
632
+ .cart-layout {
633
+ display: grid;
634
+ grid-template-columns: 1fr 380px;
635
+ gap: var(--space-2xl);
636
+ align-items: start;
637
+ }
638
+
639
+ .cart-item {
640
+ display: grid;
641
+ grid-template-columns: 100px 1fr auto;
642
+ gap: var(--space-lg);
643
+ align-items: center;
644
+ padding: var(--space-lg);
645
+ background: var(--clr-surface);
646
+ border: 1px solid var(--clr-border);
647
+ border-radius: var(--radius-lg);
648
+ margin-bottom: var(--space-md);
649
+ transition: all var(--transition-fast);
650
+ }
651
+
652
+ .cart-item:hover {
653
+ border-color: var(--clr-border-hover);
654
+ }
655
+
656
+ .cart-item-image {
657
+ width: 100px;
658
+ height: 120px;
659
+ border-radius: var(--radius-md);
660
+ overflow: hidden;
661
+ }
662
+
663
+ .cart-item-image img {
664
+ width: 100%;
665
+ height: 100%;
666
+ object-fit: cover;
667
+ }
668
+
669
+ .cart-item-name {
670
+ font-weight: var(--fw-semibold);
671
+ margin-bottom: var(--space-xs);
672
+ }
673
+
674
+ .cart-item-meta {
675
+ font-size: var(--fs-xs);
676
+ color: var(--clr-text-muted);
677
+ }
678
+
679
+ .cart-item-price {
680
+ font-weight: var(--fw-bold);
681
+ margin-top: var(--space-sm);
682
+ }
683
+
684
+ .cart-item-remove {
685
+ color: var(--clr-gray);
686
+ font-size: var(--fs-xs);
687
+ cursor: pointer;
688
+ transition: color var(--transition-fast);
689
+ }
690
+
691
+ .cart-item-remove:hover {
692
+ color: var(--clr-error);
693
+ }
694
+
695
+ .cart-summary {
696
+ background: var(--clr-surface);
697
+ border: 1px solid var(--clr-border);
698
+ border-radius: var(--radius-lg);
699
+ padding: var(--space-xl);
700
+ position: sticky;
701
+ top: calc(var(--navbar-height) + var(--space-lg));
702
+ }
703
+
704
+ .cart-summary-title {
705
+ font-size: var(--fs-lg);
706
+ font-weight: var(--fw-semibold);
707
+ margin-bottom: var(--space-lg);
708
+ padding-bottom: var(--space-md);
709
+ border-bottom: 1px solid var(--clr-border);
710
+ }
711
+
712
+ .cart-summary-row {
713
+ display: flex;
714
+ justify-content: space-between;
715
+ padding: var(--space-sm) 0;
716
+ font-size: var(--fs-sm);
717
+ color: var(--clr-text-secondary);
718
+ }
719
+
720
+ .cart-summary-row.total {
721
+ padding-top: var(--space-md);
722
+ margin-top: var(--space-md);
723
+ border-top: 1px solid var(--clr-border);
724
+ font-weight: var(--fw-bold);
725
+ font-size: var(--fs-lg);
726
+ color: var(--clr-text-primary);
727
+ }
728
+
729
+ .cart-summary .promo-input {
730
+ display: flex;
731
+ gap: var(--space-sm);
732
+ margin: var(--space-lg) 0;
733
+ }
734
+
735
+ .cart-summary .promo-input input {
736
+ flex: 1;
737
+ }
738
+
739
+ /* === CHECKOUT PAGE === */
740
+ .checkout-layout {
741
+ max-width: 800px;
742
+ margin: 0 auto;
743
+ }
744
+
745
+ .checkout-step {
746
+ display: none;
747
+ }
748
+
749
+ .checkout-step.active {
750
+ display: block;
751
+ animation: fadeInUp 0.4s ease-out;
752
+ }
753
+
754
+ .checkout-step-title {
755
+ font-size: var(--fs-xl);
756
+ font-weight: var(--fw-semibold);
757
+ margin-bottom: var(--space-xl);
758
+ }
759
+
760
+ .order-success {
761
+ text-align: center;
762
+ padding: var(--space-4xl) 0;
763
+ }
764
+
765
+ .order-success-icon {
766
+ font-size: 4rem;
767
+ margin-bottom: var(--space-lg);
768
+ }
769
+
770
+ .order-success-title {
771
+ font-size: var(--fs-2xl);
772
+ font-weight: var(--fw-bold);
773
+ margin-bottom: var(--space-md);
774
+ }
775
+
776
+ .order-success-id {
777
+ font-size: var(--fs-lg);
778
+ color: var(--clr-mid);
779
+ font-weight: var(--fw-semibold);
780
+ margin-bottom: var(--space-md);
781
+ }
782
+
783
+ .order-success-text {
784
+ color: var(--clr-text-muted);
785
+ margin-bottom: var(--space-xl);
786
+ }
787
+
788
+ /* === WISHLIST PAGE === */
789
+ .wishlist-grid {
790
+ display: grid;
791
+ grid-template-columns: repeat(4, 1fr);
792
+ gap: var(--space-lg);
793
+ }
794
+
795
+ /* === PROFILE PAGE === */
796
+ .profile-layout {
797
+ display: grid;
798
+ grid-template-columns: 280px 1fr;
799
+ gap: var(--space-2xl);
800
+ align-items: start;
801
+ }
802
+
803
+ .profile-sidebar {
804
+ background: var(--clr-surface);
805
+ border: 1px solid var(--clr-border);
806
+ border-radius: var(--radius-lg);
807
+ padding: var(--space-xl);
808
+ }
809
+
810
+ .profile-avatar {
811
+ width: 80px;
812
+ height: 80px;
813
+ border-radius: var(--radius-full);
814
+ background: var(--clr-accent);
815
+ display: flex;
816
+ align-items: center;
817
+ justify-content: center;
818
+ font-size: var(--fs-2xl);
819
+ font-weight: var(--fw-bold);
820
+ margin: 0 auto var(--space-md);
821
+ }
822
+
823
+ .profile-name {
824
+ text-align: center;
825
+ font-weight: var(--fw-semibold);
826
+ margin-bottom: var(--space-sm);
827
+ }
828
+
829
+ .profile-email {
830
+ text-align: center;
831
+ font-size: var(--fs-sm);
832
+ color: var(--clr-text-muted);
833
+ margin-bottom: var(--space-xl);
834
+ }
835
+
836
+ .profile-nav {
837
+ display: flex;
838
+ flex-direction: column;
839
+ gap: var(--space-xs);
840
+ }
841
+
842
+ .profile-nav a {
843
+ padding: 0.6rem 1rem;
844
+ border-radius: var(--radius-md);
845
+ font-size: var(--fs-sm);
846
+ color: var(--clr-text-secondary);
847
+ transition: all var(--transition-fast);
848
+ }
849
+
850
+ .profile-nav a:hover {
851
+ background: rgba(255, 255, 255, 0.03);
852
+ color: var(--clr-text-primary);
853
+ }
854
+
855
+ .profile-nav a.active {
856
+ background: var(--clr-accent);
857
+ color: var(--clr-text-primary);
858
+ }
859
+
860
+ .profile-content {
861
+ background: var(--clr-surface);
862
+ border: 1px solid var(--clr-border);
863
+ border-radius: var(--radius-lg);
864
+ padding: var(--space-2xl);
865
+ }
866
+
867
+ .order-card {
868
+ border: 1px solid var(--clr-border);
869
+ border-radius: var(--radius-md);
870
+ padding: var(--space-lg);
871
+ margin-bottom: var(--space-md);
872
+ transition: all var(--transition-fast);
873
+ }
874
+
875
+ .order-card:hover {
876
+ border-color: var(--clr-border-hover);
877
+ }
878
+
879
+ .order-header {
880
+ display: flex;
881
+ justify-content: space-between;
882
+ align-items: center;
883
+ margin-bottom: var(--space-md);
884
+ }
885
+
886
+ .order-id {
887
+ font-weight: var(--fw-semibold);
888
+ }
889
+
890
+ .order-date {
891
+ font-size: var(--fs-xs);
892
+ color: var(--clr-text-muted);
893
+ }
894
+
895
+ .order-status {
896
+ padding: 0.25rem 0.75rem;
897
+ border-radius: var(--radius-full);
898
+ font-size: var(--fs-xs);
899
+ font-weight: var(--fw-semibold);
900
+ }
901
+
902
+ .order-status.kutilmoqda {
903
+ background: rgba(251, 191, 36, 0.15);
904
+ color: var(--clr-warning);
905
+ }
906
+
907
+ .order-status.Kutilmoqda {
908
+ background: rgba(251, 191, 36, 0.15);
909
+ color: var(--clr-warning);
910
+ }
911
+
912
+ .order-status.olib-ketildi,
913
+ .order-status[class*="Olib"] {
914
+ background: rgba(52, 211, 153, 0.15);
915
+ color: var(--clr-success);
916
+ }
917
+
918
+ .order-status.bekor-qilindi,
919
+ .order-status[class*="Bekor"] {
920
+ background: rgba(239, 68, 68, 0.15);
921
+ color: var(--clr-error);
922
+ }
923
+
924
+ .order-items {
925
+ display: flex;
926
+ gap: var(--space-sm);
927
+ flex-wrap: wrap;
928
+ }
929
+
930
+ .order-item-thumb {
931
+ width: 48px;
932
+ height: 48px;
933
+ border-radius: var(--radius-sm);
934
+ overflow: hidden;
935
+ border: 1px solid var(--clr-border);
936
+ }
937
+
938
+ .order-item-thumb img {
939
+ width: 100%;
940
+ height: 100%;
941
+ object-fit: cover;
942
+ }
943
+
944
+ .order-total {
945
+ text-align: right;
946
+ font-weight: var(--fw-semibold);
947
+ margin-top: var(--space-md);
948
+ padding-top: var(--space-md);
949
+ border-top: 1px solid var(--clr-border);
950
+ }
951
+
952
+ /* === RESPONSIVE === */
953
+ @media (max-width: 1024px) {
954
+ .categories-grid {
955
+ grid-template-columns: repeat(3, 1fr);
956
+ }
957
+
958
+ .features-grid {
959
+ grid-template-columns: repeat(2, 1fr);
960
+ }
961
+
962
+ .products-grid {
963
+ grid-template-columns: repeat(2, 1fr);
964
+ }
965
+
966
+ .wishlist-grid {
967
+ grid-template-columns: repeat(3, 1fr);
968
+ }
969
+ }
970
+
971
+ @media (max-width: 768px) {
972
+ .hero {
973
+ min-height: 70vh;
974
+ }
975
+
976
+ .hero-stats {
977
+ gap: var(--space-xl);
978
+ }
979
+
980
+ .hero-actions {
981
+ flex-direction: column;
982
+ }
983
+
984
+ .categories-grid {
985
+ grid-template-columns: repeat(2, 1fr);
986
+ }
987
+
988
+ .features-grid {
989
+ grid-template-columns: 1fr;
990
+ }
991
+
992
+ .catalog-layout {
993
+ grid-template-columns: 1fr;
994
+ }
995
+
996
+ .catalog-sidebar {
997
+ position: fixed;
998
+ left: 0;
999
+ top: 0;
1000
+ bottom: 0;
1001
+ width: 300px;
1002
+ z-index: 1500;
1003
+ border-radius: 0;
1004
+ transform: translateX(-100%);
1005
+ transition: transform var(--transition-base);
1006
+ overflow-y: auto;
1007
+ }
1008
+
1009
+ .catalog-sidebar.active {
1010
+ transform: translateX(0);
1011
+ }
1012
+
1013
+ .filter-overlay {
1014
+ position: fixed;
1015
+ inset: 0;
1016
+ background: rgba(0, 0, 0, 0.5);
1017
+ z-index: 1499;
1018
+ display: none;
1019
+ }
1020
+
1021
+ .filter-overlay.active {
1022
+ display: block;
1023
+ }
1024
+
1025
+ .products-grid {
1026
+ grid-template-columns: repeat(2, 1fr);
1027
+ }
1028
+
1029
+ .product-detail {
1030
+ grid-template-columns: 1fr;
1031
+ gap: var(--space-xl);
1032
+ }
1033
+
1034
+ .product-gallery {
1035
+ position: static;
1036
+ }
1037
+
1038
+ .cart-layout {
1039
+ grid-template-columns: 1fr;
1040
+ }
1041
+
1042
+ .cart-summary {
1043
+ position: static;
1044
+ }
1045
+
1046
+ .cart-item {
1047
+ grid-template-columns: 80px 1fr auto;
1048
+ }
1049
+
1050
+ .wishlist-grid {
1051
+ grid-template-columns: repeat(2, 1fr);
1052
+ }
1053
+
1054
+ .profile-layout {
1055
+ grid-template-columns: 1fr;
1056
+ }
1057
+
1058
+ .promo-banner {
1059
+ flex-direction: column;
1060
+ text-align: center;
1061
+ }
1062
+ }
1063
+
1064
+ @media (max-width: 480px) {
1065
+ .products-grid {
1066
+ grid-template-columns: 1fr;
1067
+ gap: var(--space-md);
1068
+ }
1069
+
1070
+ .categories-grid {
1071
+ grid-template-columns: repeat(2, 1fr);
1072
+ gap: var(--space-md);
1073
+ }
1074
+
1075
+ .wishlist-grid {
1076
+ grid-template-columns: 1fr;
1077
+ }
1078
+
1079
+ .cart-item {
1080
+ grid-template-columns: 70px 1fr;
1081
+ }
1082
+
1083
+ .cart-item>*:last-child {
1084
+ grid-column: 1 / -1;
1085
+ }
1086
+
1087
+ /* Hero slider mobile */
1088
+ .hero {
1089
+ min-height: 60vh;
1090
+ }
1091
+
1092
+ .hero-title {
1093
+ font-size: clamp(1.8rem, 7vw, 2.5rem) !important;
1094
+ }
1095
+
1096
+ .hero-desc {
1097
+ font-size: var(--fs-sm);
1098
+ }
1099
+
1100
+ .hero-stats {
1101
+ flex-wrap: wrap;
1102
+ gap: var(--space-lg);
1103
+ }
1104
+
1105
+ .hero-stat-num {
1106
+ font-size: var(--fs-xl);
1107
+ }
1108
+
1109
+ .hero-actions .btn {
1110
+ font-size: var(--fs-sm);
1111
+ padding: 0.7rem 1.2rem;
1112
+ }
1113
+
1114
+ /* Checkout mobile */
1115
+ .progress-steps {
1116
+ gap: var(--space-xs);
1117
+ }
1118
+
1119
+ .progress-step {
1120
+ font-size: var(--fs-xs);
1121
+ }
1122
+
1123
+ /* Footer mobile */
1124
+ .footer-grid {
1125
+ grid-template-columns: 1fr !important;
1126
+ }
1127
+
1128
+ .footer-brand {
1129
+ grid-column: auto;
1130
+ }
1131
+
1132
+ .footer-heading {
1133
+ margin-top: var(--space-md);
1134
+ }
1135
+
1136
+ /* Category cards mobile */
1137
+ .category-card {
1138
+ padding: var(--space-lg) var(--space-md);
1139
+ }
1140
+
1141
+ .category-card-icon {
1142
+ font-size: 2rem;
1143
+ }
1144
+ }
css/premium.css ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ========================================
2
+ PREMIUM UI OVERRIDES — Visual Polish
3
+ ======================================== */
4
+
5
+ /* --- Navbar Premium Accent Line --- */
6
+ .navbar::after {
7
+ content: '';
8
+ position: absolute;
9
+ bottom: 0;
10
+ left: 0;
11
+ right: 0;
12
+ height: 1px;
13
+ background: linear-gradient(90deg, transparent 5%, var(--clr-accent) 30%, var(--clr-gold, var(--clr-mid)) 50%, var(--clr-accent) 70%, transparent 95%);
14
+ opacity: 0.4;
15
+ transition: opacity var(--transition-base);
16
+ }
17
+
18
+ .navbar.scrolled::after {
19
+ opacity: 0.6;
20
+ }
21
+
22
+ .navbar-logo {
23
+ background: linear-gradient(135deg, var(--clr-light), var(--clr-mid), var(--clr-gold, var(--clr-mid)));
24
+ -webkit-background-clip: text;
25
+ -webkit-text-fill-color: transparent;
26
+ background-clip: text;
27
+ background-size: 200% 200%;
28
+ animation: shimmer 6s ease-in-out infinite;
29
+ }
30
+
31
+ @keyframes shimmer {
32
+
33
+ 0%,
34
+ 100% {
35
+ background-position: 0% 50%;
36
+ }
37
+
38
+ 50% {
39
+ background-position: 100% 50%;
40
+ }
41
+ }
42
+
43
+ /* --- Buttons Premium --- */
44
+ .btn-primary {
45
+ background: linear-gradient(135deg, var(--clr-accent) 0%, var(--clr-mid) 100%);
46
+ color: #fff;
47
+ box-shadow: var(--shadow-md), 0 0 24px rgba(27, 107, 125, 0.3);
48
+ border: 1px solid rgba(255, 255, 255, 0.08);
49
+ }
50
+
51
+ .btn-primary:hover {
52
+ box-shadow: var(--shadow-lg), 0 0 40px rgba(27, 107, 125, 0.45);
53
+ transform: translateY(-3px);
54
+ filter: brightness(1.1);
55
+ }
56
+
57
+ .btn-outline {
58
+ border: 1px solid var(--clr-border);
59
+ position: relative;
60
+ }
61
+
62
+ .btn-outline::before {
63
+ content: '';
64
+ position: absolute;
65
+ inset: 0;
66
+ border-radius: inherit;
67
+ background: linear-gradient(135deg, rgba(27, 107, 125, 0.08), transparent);
68
+ opacity: 0;
69
+ transition: opacity var(--transition-base);
70
+ }
71
+
72
+ .btn-outline:hover::before {
73
+ opacity: 1;
74
+ }
75
+
76
+ .btn-outline:hover {
77
+ border-color: var(--clr-accent);
78
+ box-shadow: 0 0 20px rgba(27, 107, 125, 0.12);
79
+ }
80
+
81
+ /* --- Product Cards Premium Glow --- */
82
+ .product-card {
83
+ background: var(--clr-surface-solid, var(--clr-surface));
84
+ border: 1px solid var(--clr-border);
85
+ position: relative;
86
+ }
87
+
88
+ .product-card::after {
89
+ content: '';
90
+ position: absolute;
91
+ inset: 0;
92
+ border-radius: inherit;
93
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.04) 0%, transparent 40%);
94
+ pointer-events: none;
95
+ z-index: 1;
96
+ }
97
+
98
+ .product-card:hover {
99
+ transform: translateY(-8px) scale(1.01);
100
+ box-shadow: var(--shadow-lg), 0 0 50px rgba(27, 107, 125, 0.1);
101
+ border-color: rgba(27, 107, 125, 0.3);
102
+ }
103
+
104
+ .product-card:hover .product-card-image img {
105
+ transform: scale(1.1);
106
+ }
107
+
108
+ /* --- Badge Gradients --- */
109
+ .badge-new {
110
+ background: linear-gradient(135deg, #3B82F6, #60A5FA);
111
+ box-shadow: 0 2px 8px rgba(59, 130, 246, 0.35);
112
+ }
113
+
114
+ .badge-discount {
115
+ background: linear-gradient(135deg, #EF4444, #F87171);
116
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.35);
117
+ }
118
+
119
+ .badge-featured {
120
+ background: linear-gradient(135deg, #D4A847, #F0D78C);
121
+ color: #1A1A1A;
122
+ box-shadow: 0 2px 8px rgba(212, 168, 71, 0.35);
123
+ }
124
+
125
+ /* --- Category Cards Premium --- */
126
+ .category-card {
127
+ background: var(--clr-surface-solid, var(--clr-surface));
128
+ position: relative;
129
+ overflow: hidden;
130
+ }
131
+
132
+ .category-card::after {
133
+ content: '';
134
+ position: absolute;
135
+ top: -50%;
136
+ right: -50%;
137
+ width: 100%;
138
+ height: 100%;
139
+ background: radial-gradient(circle, rgba(27, 107, 125, 0.08), transparent 70%);
140
+ opacity: 0;
141
+ transition: opacity var(--transition-slow);
142
+ }
143
+
144
+ .category-card:hover::after {
145
+ opacity: 1;
146
+ }
147
+
148
+ .category-card:hover {
149
+ transform: translateY(-6px);
150
+ box-shadow: var(--shadow-lg), 0 0 40px rgba(27, 107, 125, 0.08);
151
+ border-color: var(--clr-accent);
152
+ }
153
+
154
+ .category-card-icon {
155
+ filter: drop-shadow(0 4px 12px rgba(27, 107, 125, 0.2));
156
+ }
157
+
158
+ /* --- Feature Cards Premium --- */
159
+ .feature-card {
160
+ background: var(--clr-surface-solid, var(--clr-surface));
161
+ position: relative;
162
+ overflow: hidden;
163
+ border: 1px solid var(--clr-border);
164
+ }
165
+
166
+ .feature-card::before {
167
+ content: '';
168
+ position: absolute;
169
+ top: 0;
170
+ left: 0;
171
+ right: 0;
172
+ height: 3px;
173
+ background: linear-gradient(90deg, var(--clr-accent), var(--clr-mid), var(--clr-accent));
174
+ opacity: 0;
175
+ transition: opacity var(--transition-base);
176
+ }
177
+
178
+ .feature-card:hover::before {
179
+ opacity: 1;
180
+ }
181
+
182
+ .feature-card:hover {
183
+ transform: translateY(-6px);
184
+ box-shadow: var(--shadow-md), 0 0 30px rgba(27, 107, 125, 0.08);
185
+ border-color: var(--clr-border-hover);
186
+ }
187
+
188
+ .feature-icon {
189
+ filter: drop-shadow(0 4px 10px rgba(27, 107, 125, 0.2));
190
+ }
191
+
192
+ /* --- Hero Premium Overrides --- */
193
+ .hero {
194
+ background: linear-gradient(160deg, var(--clr-bg-primary) 0%, var(--clr-bg-secondary) 35%, rgba(27, 107, 125, 0.15) 70%, var(--clr-bg-primary) 100%);
195
+ }
196
+
197
+ .hero-title span {
198
+ background: linear-gradient(135deg, var(--clr-light), var(--clr-mid), var(--clr-gold, var(--clr-light)));
199
+ -webkit-background-clip: text;
200
+ -webkit-text-fill-color: transparent;
201
+ background-clip: text;
202
+ background-size: 200% 200%;
203
+ animation: shimmer 4s ease-in-out infinite;
204
+ }
205
+
206
+ .hero-tag {
207
+ background: rgba(27, 107, 125, 0.12);
208
+ border: 1px solid rgba(27, 107, 125, 0.2);
209
+ backdrop-filter: blur(16px);
210
+ }
211
+
212
+ .hero-stat-num {
213
+ background: linear-gradient(135deg, var(--clr-text-primary), var(--clr-mid));
214
+ -webkit-background-clip: text;
215
+ -webkit-text-fill-color: transparent;
216
+ background-clip: text;
217
+ }
218
+
219
+ /* --- Promo Banner Premium --- */
220
+ .promo-banner {
221
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-bg-secondary), rgba(27, 107, 125, 0.3));
222
+ border: 1px solid rgba(27, 107, 125, 0.2);
223
+ box-shadow: var(--shadow-lg);
224
+ }
225
+
226
+ /* --- Section Headings Premium --- */
227
+ .section-title {
228
+ position: relative;
229
+ }
230
+
231
+ .section-title::after {
232
+ content: '';
233
+ display: block;
234
+ width: 60px;
235
+ height: 3px;
236
+ background: linear-gradient(90deg, var(--clr-accent), var(--clr-mid));
237
+ border-radius: var(--radius-full);
238
+ margin: var(--space-md) auto 0;
239
+ }
240
+
241
+ /* --- Form Inputs Premium --- */
242
+ .form-input {
243
+ background: var(--clr-surface-solid, var(--clr-surface));
244
+ border: 1px solid var(--clr-border);
245
+ transition: all var(--transition-base);
246
+ }
247
+
248
+ .form-input:focus {
249
+ border-color: var(--clr-accent);
250
+ box-shadow: 0 0 0 3px rgba(27, 107, 125, 0.12), 0 0 20px rgba(27, 107, 125, 0.08);
251
+ }
252
+
253
+ /* --- Modals Premium --- */
254
+ .modal-overlay {
255
+ backdrop-filter: blur(8px);
256
+ }
257
+
258
+ .modal-overlay.active .modal {
259
+ box-shadow: var(--shadow-xl), 0 0 80px rgba(27, 107, 125, 0.08);
260
+ }
261
+
262
+ .modal {
263
+ border: 1px solid var(--clr-border);
264
+ background: var(--clr-surface-solid, var(--clr-surface));
265
+ }
266
+
267
+ /* --- Login Tabs Premium --- */
268
+ .login-tab.active {
269
+ background: var(--clr-accent);
270
+ color: #fff;
271
+ box-shadow: 0 0 15px rgba(27, 107, 125, 0.3);
272
+ }
273
+
274
+ /* --- Toast Notifications Premium --- */
275
+ .toast-container .toast {
276
+ backdrop-filter: blur(16px);
277
+ border: 1px solid var(--clr-border);
278
+ box-shadow: var(--shadow-lg);
279
+ }
280
+
281
+ /* --- Checkout Steps Premium --- */
282
+ .progress-step.active {
283
+ color: var(--clr-accent);
284
+ }
285
+
286
+ .progress-step.done {
287
+ color: var(--clr-success);
288
+ }
289
+
290
+ /* --- Cart Summary Premium --- */
291
+ .cart-summary {
292
+ background: var(--clr-surface-solid, var(--clr-surface));
293
+ border: 1px solid var(--clr-border);
294
+ border-radius: var(--radius-lg);
295
+ box-shadow: var(--shadow-md);
296
+ }
297
+
298
+ /* --- Profile Page Premium --- */
299
+ .profile-avatar {
300
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-mid));
301
+ box-shadow: 0 4px 20px rgba(27, 107, 125, 0.3);
302
+ }
303
+
304
+ .profile-nav a.active {
305
+ background: rgba(27, 107, 125, 0.12);
306
+ color: var(--clr-accent-light, var(--clr-mid));
307
+ border-left: 3px solid var(--clr-accent);
308
+ }
309
+
310
+ /* --- Order Status Premium --- */
311
+ .order-status.Kutilmoqda,
312
+ .order-status[class*="kutilmoqda"] {
313
+ background: rgba(251, 191, 36, 0.12);
314
+ color: var(--clr-warning);
315
+ border: 1px solid rgba(251, 191, 36, 0.2);
316
+ }
317
+
318
+ .order-status.olib-ketildi,
319
+ .order-status[class*="Olib"] {
320
+ background: rgba(52, 211, 153, 0.12);
321
+ color: var(--clr-success);
322
+ border: 1px solid rgba(52, 211, 153, 0.2);
323
+ }
324
+
325
+ .order-status[class*="Bekor"],
326
+ .order-status.bekor-qilindi {
327
+ background: rgba(248, 113, 113, 0.12);
328
+ color: var(--clr-error);
329
+ border: 1px solid rgba(248, 113, 113, 0.2);
330
+ }
331
+
332
+ /* --- Footer Premium --- */
333
+ footer {
334
+ background: var(--clr-bg-secondary);
335
+ border-top: 1px solid var(--clr-border);
336
+ position: relative;
337
+ }
338
+
339
+ footer::before {
340
+ content: '';
341
+ position: absolute;
342
+ top: 0;
343
+ left: 0;
344
+ right: 0;
345
+ height: 1px;
346
+ background: linear-gradient(90deg, transparent, var(--clr-accent), var(--clr-gold, var(--clr-mid)), var(--clr-accent), transparent);
347
+ opacity: 0.3;
348
+ }
349
+
350
+ /* --- Selection Color --- */
351
+ ::selection {
352
+ background: rgba(27, 107, 125, 0.3);
353
+ color: var(--clr-text-primary);
354
+ }
355
+
356
+ /* --- Scrollbar Premium --- */
357
+ ::-webkit-scrollbar-thumb {
358
+ background: linear-gradient(180deg, var(--clr-accent), var(--clr-mid));
359
+ border-radius: var(--radius-full);
360
+ }
361
+
362
+ ::-webkit-scrollbar-thumb:hover {
363
+ background: var(--clr-accent-light, var(--clr-mid));
364
+ }
365
+
366
+ /* --- Star Rating Premium --- */
367
+ .review-star {
368
+ transition: all var(--transition-fast);
369
+ cursor: pointer;
370
+ font-size: 1.4rem;
371
+ color: var(--clr-gray);
372
+ }
373
+
374
+ .review-star.active,
375
+ .review-star:hover {
376
+ color: var(--clr-warning);
377
+ filter: drop-shadow(0 0 6px rgba(251, 191, 36, 0.4));
378
+ transform: scale(1.15);
379
+ }
380
+
381
+ /* --- Loading shimmer for images --- */
382
+ .product-card-image {
383
+ position: relative;
384
+ background: linear-gradient(90deg, var(--clr-bg-secondary) 25%, rgba(27, 107, 125, 0.05) 50%, var(--clr-bg-secondary) 75%);
385
+ background-size: 200% 100%;
386
+ }
387
+
388
+ /* --- Smooth Page Transitions --- */
389
+ .page-fade-enter {
390
+ animation: pageEnter 0.4s ease-out;
391
+ }
392
+
393
+ @keyframes pageEnter {
394
+ from {
395
+ opacity: 0;
396
+ transform: translateY(10px);
397
+ }
398
+
399
+ to {
400
+ opacity: 1;
401
+ transform: translateY(0);
402
+ }
403
+ }
404
+
405
+ /* --- Back To Top Premium --- */
406
+ .back-to-top {
407
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-mid));
408
+ color: #fff;
409
+ box-shadow: var(--shadow-md), 0 0 20px rgba(27, 107, 125, 0.25);
410
+ border: 1px solid rgba(255, 255, 255, 0.1);
411
+ }
412
+
413
+ .back-to-top:hover {
414
+ transform: translateY(-3px);
415
+ box-shadow: var(--shadow-lg), 0 0 30px rgba(27, 107, 125, 0.4);
416
+ }
417
+
418
+ /* --- Light Mode Overrides --- */
419
+ [data-theme="light"] .navbar::after {
420
+ opacity: 0.25;
421
+ }
422
+
423
+ [data-theme="light"] .hero {
424
+ background: linear-gradient(160deg, #EBF2F5 0%, #D8E8EE 35%, rgba(27, 107, 125, 0.08) 70%, #F3F6F9 100%);
425
+ }
426
+
427
+ [data-theme="light"] .hero-title span {
428
+ background: linear-gradient(135deg, var(--clr-accent), var(--clr-mid));
429
+ -webkit-background-clip: text;
430
+ -webkit-text-fill-color: transparent;
431
+ background-clip: text;
432
+ }
433
+
434
+ [data-theme="light"] .product-card {
435
+ background: #fff;
436
+ }
437
+
438
+ [data-theme="light"] .feature-card,
439
+ [data-theme="light"] .category-card {
440
+ background: #fff;
441
+ }
442
+
443
+ [data-theme="light"] .badge-featured {
444
+ background: linear-gradient(135deg, #B8922F, #D4A847);
445
+ }
fix_ui.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+
3
+ let appJs = fs.readFileSync('js/app.js', 'utf8');
4
+ appJs = appJs.replace(/href=\"catalog\?/g, 'href="catalog.html?');
5
+ fs.writeFileSync('js/app.js', appJs);
6
+
7
+ let compCss = fs.readFileSync('css/components.css', 'utf8');
8
+ compCss = compCss.replace(/\.search-modal-overlay\s*\{[\s\S]*?inset:\s*0;/g, match => match + '\n height: 100vh;\n width: 100vw;');
9
+ compCss = compCss.replace(/\.modal-overlay\s*\{[\s\S]*?inset:\s*0;/g, match => match + '\n height: 100vh;\n width: 100vw;');
10
+ fs.writeFileSync('css/components.css', compCss);
11
+
12
+ console.log('Fixed URLs and Modals');
index.html ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description"
8
+ content="M-TEXTILE — O'zbekistondagi eng sifatli kiyim-kechak onlayn do'koni. Kiyimlar, formalar, shimlar, galistuklar va aksessuarlar.">
9
+ <meta name="theme-color" content="#0D1F23">
10
+ <meta property="og:title" content="M-TEXTILE — Kiyim-Kechak Do'koni">
11
+ <meta property="og:description"
12
+ content="Kiyimlar, formalar, shimlar, galistuklar — faqat siz uchun. Eng sifatli onlayn do'kon.">
13
+ <meta property="og:type" content="website">
14
+ <title>M-TEXTILE — Kiyim-Kechak Do'koni</title>
15
+ <link rel="stylesheet" href="css/global.css">
16
+ <link rel="stylesheet" href="css/components.css">
17
+ <link rel="stylesheet" href="css/pages.css">
18
+ <link rel="stylesheet" href="css/premium.css">
19
+ <link rel="manifest" href="manifest.json">
20
+
21
+ <!-- AOS CSS -->
22
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
23
+ </head>
24
+
25
+ <body>
26
+ <!-- Global Preloader -->
27
+ <div id="global-preloader" class="preloader">
28
+ <div class="preloader-spinner"></div>
29
+ </div>
30
+ <div class="page-wrapper">
31
+ <div class="page-content">
32
+
33
+ <!-- HERO SECTION -->
34
+ <section class="hero">
35
+ <div class="container">
36
+ <div class="hero-content">
37
+ <div id="heroSlider">
38
+ <!-- JS will render slides here -->
39
+ </div>
40
+ <div class="hero-stats">
41
+ <div>
42
+ <div class="hero-stat-num" data-count="40" data-suffix="+">0</div>
43
+ <div class="hero-stat-label">Mahsulotlar</div>
44
+ </div>
45
+ <div>
46
+ <div class="hero-stat-num" data-count="5">0</div>
47
+ <div class="hero-stat-label">Kategoriyalar</div>
48
+ </div>
49
+ <div>
50
+ <div class="hero-stat-num" data-count="24" data-suffix="/7">0</div>
51
+ <div class="hero-stat-label">Yordam</div>
52
+ </div>
53
+ </div>
54
+ <div class="hero-dots" id="heroDots"></div>
55
+ </div>
56
+ </div>
57
+ </section>
58
+
59
+ <!-- CATEGORIES -->
60
+ <section class="section-padding">
61
+ <div class="container">
62
+ <div class="section-header reveal">
63
+ <h2>Kategoriyalar</h2>
64
+ <p class="section-subtitle">Sizga kerakli kiyim-kechakni toping</p>
65
+ <div class="section-line"></div>
66
+ </div>
67
+ <div class="categories-grid stagger reveal" id="categoriesGrid"></div>
68
+ </div>
69
+ </section>
70
+
71
+ <!-- FEATURED PRODUCTS -->
72
+ <section class="section-padding" style="background: var(--clr-bg-secondary);">
73
+ <div class="container">
74
+ <div class="section-header reveal">
75
+ <h2>Eng yaxshilar</h2>
76
+ <p class="section-subtitle">Mijozlarimiz sevgan mahsulotlar</p>
77
+ <div class="section-line"></div>
78
+ </div>
79
+ <div class="grid grid-4 stagger reveal" id="featuredGrid"></div>
80
+ <div class="text-center mt-2xl">
81
+ <a href="catalog.html" class="btn btn-outline">Barchasini ko'rish →</a>
82
+ </div>
83
+ </div>
84
+ </section>
85
+
86
+ <!-- PROMO BANNER -->
87
+ <section class="section-padding">
88
+ <div class="container">
89
+ <div class="promo-banner reveal">
90
+ <div class="promo-content">
91
+ <div class="promo-tag">🔥 Maxsus taklif</div>
92
+ <h2 class="promo-title">30% gacha chegirma</h2>
93
+ <p class="promo-desc">Tanlangan mahsulotlarga katta chegirmalar. Imkoniyatni qo'ldan boy bermang!</p>
94
+ <a href="catalog.html?filter=discount" class="btn btn-primary">Chegirmalar →</a>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </section>
99
+
100
+ <!-- NEW ARRIVALS -->
101
+ <section class="section-padding" style="background: var(--clr-bg-secondary);">
102
+ <div class="container">
103
+ <div class="section-header reveal">
104
+ <h2>Yangi kelganlar</h2>
105
+ <p class="section-subtitle">Eng so'nggi qo'shilgan mahsulotlar</p>
106
+ <div class="section-line"></div>
107
+ </div>
108
+ <div class="grid grid-4 stagger reveal" id="newGrid"></div>
109
+ </div>
110
+ </section>
111
+
112
+ <!-- FEATURES -->
113
+ <section class="section-padding">
114
+ <div class="container">
115
+ <div class="features-grid stagger reveal">
116
+ <div class="feature-card">
117
+ <div class="feature-icon">🏬</div>
118
+ <h4 class="feature-title">Do'kondan olib ketish</h4>
119
+ <p class="feature-desc">Buyurtmani osongina do'kondan oling</p>
120
+ </div>
121
+ <div class="feature-card">
122
+ <div class="feature-icon">🛡️</div>
123
+ <h4 class="feature-title">Sifat kafolati</h4>
124
+ <p class="feature-desc">joyida kiyib ko'rib olish imkoniyati</p>
125
+ </div>
126
+ <div class="feature-card">
127
+ <div class="feature-icon">👕</div>
128
+ <h4 class="feature-title">Keng tanlov</h4>
129
+ <p class="feature-desc">Do'konimizda barcha o'lchamlar bor</p>
130
+ </div>
131
+ <div class="feature-card">
132
+ <div class="feature-icon">💬</div>
133
+ <h4 class="feature-title">24/7 qo'llab-quvvatlash</h4>
134
+ <p class="feature-desc">Har doim yordam berishga tayyormiz</p>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </section>
139
+
140
+ <!-- NEWSLETTER -->
141
+ <section class="section-padding" style="padding-bottom: 2rem;">
142
+ <div class="container">
143
+ <div class="newsletter-section reveal">
144
+ <h2 class="newsletter-title">📩 Yangiliklardan xabardor bo'ling</h2>
145
+ <p class="newsletter-desc">Yangi mahsulotlar, chegirmalar va maxsus takliflar haqida birinchilardan bo'lib
146
+ biling</p>
147
+ <div class="newsletter-form" id="newsletterForm">
148
+ <input type="email" placeholder="Email manzilingiz" id="newsletterEmail">
149
+ <button class="btn btn-primary" onclick="subscribeNewsletter()">Obuna bo'lish</button>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </section>
154
+
155
+ <!-- RECENTLY VIEWED -->
156
+ <section class="section-padding recently-viewed-section" style="padding-top: 2rem;">
157
+ <div class="container">
158
+ <div class="section-header reveal">
159
+ <h2>Yaqinda ko'rilgan</h2>
160
+ <div class="section-line"></div>
161
+ </div>
162
+ <div class="grid grid-4 stagger reveal" id="recentGrid"></div>
163
+ </div>
164
+ </section>
165
+
166
+ </div>
167
+ </div>
168
+
169
+ <script src="js/products.js"></script>
170
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
171
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
172
+ <script src="js/firebase-config.js"></script>
173
+ <script src="js/api.js"></script>
174
+ <script src="js/store.js"></script>
175
+ <script src="js/app.js"></script>
176
+ <script>
177
+ document.addEventListener('DOMContentLoaded', () => {
178
+ // Render categories
179
+ const catGrid = document.getElementById('categoriesGrid');
180
+ Object.entries(CATEGORIES).forEach(([slug, cat]) => {
181
+ const count = getProductsByCategory(slug).length;
182
+ const card = document.createElement('a');
183
+ card.href = `catalog.html?category=${slug}`;
184
+ card.className = 'category-card';
185
+ card.innerHTML = `
186
+ <span class="category-card-icon">${cat.icon}</span>
187
+ <div class="category-card-name">${cat.name}</div>
188
+ <div class="category-card-count">${count} ta mahsulot</div>
189
+ `;
190
+ catGrid.appendChild(card);
191
+ });
192
+
193
+ // Render featured
194
+ const featuredGrid = document.getElementById('featuredGrid');
195
+ getFeaturedProducts().slice(0, 8).forEach(p => {
196
+ featuredGrid.appendChild(renderProductCard(p));
197
+ });
198
+
199
+ // Render new arrivals
200
+ const newGrid = document.getElementById('newGrid');
201
+ getNewProducts().slice(0, 4).forEach(p => {
202
+ newGrid.appendChild(renderProductCard(p));
203
+ });
204
+
205
+ // Re-init animations
206
+ initRevealAnimations();
207
+
208
+ // Recently viewed
209
+ renderRecentlyViewed('recentGrid');
210
+ });
211
+
212
+ function subscribeNewsletter() {
213
+ const email = document.getElementById('newsletterEmail').value.trim();
214
+ if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
215
+ showToast('Xatolik', 'To\'g\'ri email kiriting', 'error');
216
+ return;
217
+ }
218
+ let subs = JSON.parse(localStorage.getItem('mtextile_subscribers') || '[]');
219
+ if (subs.includes(email)) {
220
+ showToast('Ma\'lumot', 'Siz allaqachon obuna bo\'lgansiz', 'info');
221
+ return;
222
+ }
223
+ subs.push(email);
224
+ localStorage.setItem('mtextile_subscribers', JSON.stringify(subs));
225
+ document.getElementById('newsletterEmail').value = '';
226
+ showToast('Muvaffaqiyat!', 'Obuna bo\'ldingiz! Rahmat 🎉', 'success');
227
+ }
228
+ </script>
229
+ <script>
230
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));
231
+ </script>
232
+
233
+ <!-- AOS JS -->
234
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
235
+ </body>
236
+
237
+ </html>
inject_pwa.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const files = ['index.html', 'catalog.html', 'product.html', 'cart.html', 'checkout.html', 'wishlist.html', 'profile.html'];
3
+ for (const file of files) {
4
+ let content = fs.readFileSync(file, 'utf8');
5
+ if (!content.includes('manifest.json')) {
6
+ content = content.replace('</head>', ' <link rel="manifest" href="manifest.json">\n</head>');
7
+ }
8
+ if (!content.includes('serviceWorker')) {
9
+ content = content.replace('</body>', ` <script>\n if('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));\n </script>\n</body>`);
10
+ }
11
+ fs.writeFileSync(file, content);
12
+ console.log('Updated ' + file);
13
+ }
inject_ui.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const dir = __dirname;
5
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.html'));
6
+
7
+ const headInject = `\n <!-- AOS CSS -->\n <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">`;
8
+ const bodyInject = `\n <!-- Global Preloader -->\n <div id="global-preloader" class="preloader">\n <div class="preloader-spinner"></div>\n </div>`;
9
+ const scriptInject = `\n <!-- AOS JS -->\n <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>\n`;
10
+
11
+ files.forEach(file => {
12
+ let content = fs.readFileSync(path.join(dir, file), 'utf8');
13
+ let changed = false;
14
+
15
+ if (!content.includes('aos.css')) {
16
+ content = content.replace('</head>', headInject + '\n</head>');
17
+ changed = true;
18
+ }
19
+ if (!content.includes('global-preloader')) {
20
+ content = content.replace(/<body[^>]*>/, match => match + bodyInject);
21
+ changed = true;
22
+ }
23
+ if (!content.includes('aos.js')) {
24
+ content = content.replace('</body>', scriptInject + '</body>');
25
+ changed = true;
26
+ }
27
+
28
+ if (changed) {
29
+ fs.writeFileSync(path.join(dir, file), content, 'utf8');
30
+ console.log(`Injected into ${file}`);
31
+ }
32
+ });
js/admin.js ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // ADMIN PANEL LOGIC
3
+ // ========================================
4
+
5
+ const ADMIN_PASSWORD = 'admin'; // Basic auth explicitly requested for simplicity
6
+
7
+ document.addEventListener('DOMContentLoaded', () => {
8
+ if (sessionStorage.getItem('adminAuth') === 'true') {
9
+ initAdmin();
10
+ }
11
+ });
12
+
13
+ function checkAdmin() {
14
+ const p = document.getElementById('adminPass').value;
15
+ if (p === ADMIN_PASSWORD) {
16
+ sessionStorage.setItem('adminAuth', 'true');
17
+ initAdmin();
18
+ } else {
19
+ alert("Noto'g'ri parol!");
20
+ }
21
+ }
22
+
23
+ function initAdmin() {
24
+ document.getElementById('adminLogin').style.display = 'none';
25
+ document.getElementById('adminDashboard').style.display = 'flex';
26
+ document.body.style.overflow = 'auto'; // Re-enable scrolling if disabled
27
+ updateStats();
28
+ renderAdminProducts();
29
+ renderAdminOrders();
30
+ loadAdminSettings();
31
+ }
32
+
33
+ function showSection(id, el) {
34
+ document.querySelectorAll('.admin-section').forEach(s => s.style.display = 'none');
35
+ document.querySelectorAll('.admin-nav a').forEach(a => a.classList.remove('active'));
36
+ document.getElementById('section-' + id).style.display = 'block';
37
+ if (el) el.classList.add('active');
38
+ }
39
+
40
+ function updateStats() {
41
+ document.getElementById('statProducts').textContent = PRODUCTS.length;
42
+
43
+ // Fetch orders if they exist
44
+ const orders = JSON.parse(localStorage.getItem('mtextile_orders') || '[]');
45
+ document.getElementById('statOrders').textContent = orders.length;
46
+
47
+ const revenue = orders.reduce((total, o) => total + (o.totals?.total || 0), 0);
48
+ document.getElementById('statRevenue').textContent = formatPrice(revenue);
49
+ }
50
+
51
+ // --- Settings Logic ---
52
+ function loadAdminSettings() {
53
+ try {
54
+ const settings = JSON.parse(localStorage.getItem('mtextile_settings')) || {
55
+ address: '',
56
+ mapIframe: ''
57
+ };
58
+ const addrInput = document.getElementById('settingAddress');
59
+ const mapInput = document.getElementById('settingMap');
60
+ const preview = document.getElementById('settingsMapPreview');
61
+
62
+ if (addrInput) addrInput.value = settings.address || '';
63
+ if (mapInput) mapInput.value = settings.mapIframe || '';
64
+
65
+ if (settings.mapIframe && preview) {
66
+ preview.innerHTML = settings.mapIframe;
67
+ preview.style.display = 'block';
68
+ // ensure iframe fills container
69
+ const iframe = preview.querySelector('iframe');
70
+ if (iframe) {
71
+ iframe.style.width = '100%';
72
+ iframe.style.height = '100%';
73
+ iframe.style.border = 'none';
74
+ }
75
+ }
76
+ } catch (e) {
77
+ console.error("Sozlamalarni yuklashda xatolik:", e);
78
+ }
79
+ }
80
+
81
+ function saveAdminSettings() {
82
+ const address = document.getElementById('settingAddress').value.trim();
83
+ const mapIframe = document.getElementById('settingMap').value.trim();
84
+
85
+ const settings = { address, mapIframe };
86
+ localStorage.setItem('mtextile_settings', JSON.stringify(settings));
87
+
88
+ alert("Sozlamalar muvaffaqiyatli saqlandi!");
89
+ loadAdminSettings(); // Refresh preview
90
+ }
91
+
92
+ function renderAdminProducts() {
93
+ const tbody = document.getElementById('adminProductsTable');
94
+ tbody.innerHTML = '';
95
+
96
+ PRODUCTS.slice().reverse().forEach(p => {
97
+ const tr = document.createElement('tr');
98
+ tr.innerHTML = `
99
+ <td><img src="${p.images[0]}" alt="${p.name}"></td>
100
+ <td><strong>${p.name}</strong></td>
101
+ <td>${p.category}</td>
102
+ <td>${formatPrice(p.price)}</td>
103
+ <td><span class="badge ${p.inStock ? 'badge-new' : ''}">${p.inStock ? 'Mavjud' : "Tugagan"}</span></td>
104
+ <td>
105
+ <button class="action-btn" onclick="editProduct(${p.id})">✏️</button>
106
+ <button class="action-btn delete" onclick="deleteProduct(${p.id})">🗑️</button>
107
+ </td>
108
+ `;
109
+ tbody.appendChild(tr);
110
+ });
111
+ }
112
+
113
+ function renderAdminOrders() {
114
+ const tbody = document.getElementById('ordersTableBody');
115
+ const orders = JSON.parse(localStorage.getItem('mtextile_orders') || '[]');
116
+ tbody.innerHTML = '';
117
+
118
+ if (orders.length === 0) {
119
+ tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;padding:2rem;">Hozircha buyurtmalar yo'q</td></tr>`;
120
+ return;
121
+ }
122
+
123
+ const sortedOrders = [...orders].reverse();
124
+ sortedOrders.forEach((o, i) => {
125
+ const customerName = o.customer?.name || o.name || '—';
126
+ const customerPhone = o.customer?.phone || o.phone || '—';
127
+ const orderDate = o.createdAt || o.date;
128
+ const orderTotal = o.totals?.total || o.total || 0;
129
+ const tr = document.createElement('tr');
130
+ tr.innerHTML = `
131
+ <td>#${o.id}</td>
132
+ <td><strong>${customerName}</strong></td>
133
+ <td>${customerPhone}</td>
134
+ <td>${formatPrice(orderTotal)}</td>
135
+ <td>${orderDate ? new Date(orderDate).toLocaleDateString() : '—'}</td>
136
+ <td>
137
+ <select onchange="updateOrderStatus('${o.id}', this.value)" style="padding:4px;border-radius:4px;border:1px solid var(--clr-border);background:var(--clr-surface);color:var(--clr-text-primary);cursor:pointer;">
138
+ <option value="Kutilmoqda" ${o.status === 'Kutilmoqda' || !o.status ? 'selected' : ''}>Kutilmoqda</option>
139
+ <option value="Olib ketildi" ${o.status === 'Olib ketildi' ? 'selected' : ''}>Olib ketildi</option>
140
+ <option value="Bekor qilindi" ${o.status === 'Bekor qilindi' ? 'selected' : ''}>Bekor qilindi</option>
141
+ </select>
142
+ </td>
143
+ `;
144
+ tbody.appendChild(tr);
145
+ });
146
+ }
147
+
148
+ function updateOrderStatus(orderId, newStatus) {
149
+ const orders = JSON.parse(localStorage.getItem('mtextile_orders') || '[]');
150
+ const index = orders.findIndex(o => o.id === orderId);
151
+ if (index > -1) {
152
+ orders[index].status = newStatus;
153
+ localStorage.setItem('mtextile_orders', JSON.stringify(orders));
154
+ }
155
+ }
156
+
157
+ function filterAdminProducts(query) {
158
+ const q = query.toLowerCase().trim();
159
+ const rows = document.querySelectorAll('#adminProductsTable tr');
160
+ rows.forEach(row => {
161
+ const nameColumn = row.children[1];
162
+ if (!nameColumn) return;
163
+ const name = nameColumn.textContent.toLowerCase();
164
+ row.style.display = name.includes(q) ? '' : 'none';
165
+ });
166
+ }
167
+
168
+ // ========================================
169
+ // CRUD OPERATIONS
170
+ // ========================================
171
+
172
+ const modal = document.getElementById('productModal');
173
+ const form = document.getElementById('productForm');
174
+
175
+ function openProductModal() {
176
+ form.reset();
177
+ document.getElementById('pId').value = '';
178
+ document.getElementById('modalTitle').textContent = "Mahsulot qo'shish";
179
+ modal.style.display = 'flex';
180
+ }
181
+
182
+ function closeProductModal() {
183
+ modal.style.display = 'none';
184
+ }
185
+
186
+ function editProduct(id) {
187
+ const p = getProductById(id);
188
+ if (!p) return;
189
+
190
+ document.getElementById('pId').value = p.id;
191
+ document.getElementById('pName').value = p.name;
192
+ document.getElementById('pCat').value = p.category;
193
+ document.getElementById('pSubCat').value = p.subcategory;
194
+ document.getElementById('pPrice').value = p.price;
195
+ document.getElementById('pOldPrice').value = p.oldPrice || '';
196
+ document.getElementById('pImg').value = p.images[0];
197
+ document.getElementById('pDesc').value = p.description;
198
+ document.getElementById('pNew').checked = p.isNew;
199
+ document.getElementById('pFeat').checked = p.isFeatured;
200
+
201
+ document.getElementById('modalTitle').textContent = "Mahsulotni tahrirlash";
202
+ modal.style.display = 'flex';
203
+ }
204
+
205
+ function deleteProduct(id) {
206
+ if (confirm('Rostdan ham bu mahsulotni o\'chirmoqchimisiz?')) {
207
+ PRODUCTS = PRODUCTS.filter(p => p.id !== id);
208
+ saveProducts();
209
+ }
210
+ }
211
+
212
+ form.addEventListener('submit', (e) => {
213
+ e.preventDefault();
214
+
215
+ const id = document.getElementById('pId').value;
216
+ const price = parseInt(document.getElementById('pPrice').value);
217
+ const oldPrice = parseInt(document.getElementById('pOldPrice').value) || null;
218
+ let discount = 0;
219
+ if (oldPrice && oldPrice > price) {
220
+ discount = Math.round(((oldPrice - price) / oldPrice) * 100);
221
+ }
222
+
223
+ const pData = {
224
+ name: document.getElementById('pName').value,
225
+ category: document.getElementById('pCat').value,
226
+ subcategory: document.getElementById('pSubCat').value,
227
+ price: price,
228
+ oldPrice: oldPrice,
229
+ discount: discount,
230
+ images: [document.getElementById('pImg').value],
231
+ description: document.getElementById('pDesc').value,
232
+ isNew: document.getElementById('pNew').checked,
233
+ isFeatured: document.getElementById('pFeat').checked,
234
+ // Default properties for new products
235
+ sizes: ["S", "M", "L", "XL"],
236
+ colors: ["qora", "oq"],
237
+ rating: 5.0,
238
+ reviewCount: 0,
239
+ inStock: true
240
+ };
241
+
242
+ if (id) {
243
+ // Edit existing
244
+ const index = PRODUCTS.findIndex(p => p.id === parseInt(id));
245
+ if (index > -1) {
246
+ PRODUCTS[index] = { ...PRODUCTS[index], ...pData };
247
+ }
248
+ } else {
249
+ // Add new
250
+ pData.id = Date.now(); // Generate unique ID
251
+ PRODUCTS.push(pData);
252
+ }
253
+
254
+ saveProducts();
255
+ closeProductModal();
256
+ });
257
+
258
+ function saveProducts() {
259
+ localStorage.setItem('mtextile_products', JSON.stringify(PRODUCTS));
260
+ updateStats();
261
+ renderAdminProducts();
262
+ }
js/api.js ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // API CLIENT — Frontend → Backend Communication
3
+ // ========================================
4
+ const API_BASE = (window.location.hostname === '127.0.0.1' || window.location.port === '5500')
5
+ ? 'http://localhost:3000/api'
6
+ : '/api';
7
+
8
+ const API = {
9
+ // === PRODUCTS ===
10
+ async getProducts() {
11
+ try {
12
+ const res = await fetch(`${API_BASE}/products`);
13
+ if (!res.ok) throw new Error('Products fetch failed');
14
+ return await res.json();
15
+ } catch (err) {
16
+ console.warn('API unavailable, using local data:', err.message);
17
+ return null; // Fallback to local DEFAULT_PRODUCTS
18
+ }
19
+ },
20
+
21
+ async getProduct(id) {
22
+ try {
23
+ const res = await fetch(`${API_BASE}/products/${id}`);
24
+ if (!res.ok) return null;
25
+ return await res.json();
26
+ } catch {
27
+ return null;
28
+ }
29
+ },
30
+
31
+ async createProduct(data) {
32
+ const res = await fetch(`${API_BASE}/products`, {
33
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify(data)
35
+ });
36
+ return await res.json();
37
+ },
38
+
39
+ async updateProduct(id, data) {
40
+ const res = await fetch(`${API_BASE}/products/${id}`, {
41
+ method: 'PUT', headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify(data)
43
+ });
44
+ return await res.json();
45
+ },
46
+
47
+ async deleteProduct(id) {
48
+ const res = await fetch(`${API_BASE}/products/${id}`, { method: 'DELETE' });
49
+ return await res.json();
50
+ },
51
+
52
+ // === AUTH ===
53
+ async register(name, phone, password, email) {
54
+ const res = await fetch(`${API_BASE}/auth/register`, {
55
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({ name, phone, password, email })
57
+ });
58
+ return await res.json();
59
+ },
60
+
61
+ async login(phone, password) {
62
+ const res = await fetch(`${API_BASE}/auth/login`, {
63
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({ phone, password })
65
+ });
66
+ return await res.json();
67
+ },
68
+
69
+ async getProfile(userId) {
70
+ const res = await fetch(`${API_BASE}/auth/profile/${userId}`);
71
+ return await res.json();
72
+ },
73
+
74
+ async updateProfile(userId, data) {
75
+ const res = await fetch(`${API_BASE}/auth/profile/${userId}`, {
76
+ method: 'PUT', headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify(data)
78
+ });
79
+ return await res.json();
80
+ },
81
+
82
+ // === ORDERS ===
83
+ async getOrders() {
84
+ const res = await fetch(`${API_BASE}/orders`);
85
+ return await res.json();
86
+ },
87
+
88
+ async getUserOrders(userId) {
89
+ const res = await fetch(`${API_BASE}/orders/user/${userId}`);
90
+ return await res.json();
91
+ },
92
+
93
+ async createOrder(data) {
94
+ const res = await fetch(`${API_BASE}/orders`, {
95
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify(data)
97
+ });
98
+ return await res.json();
99
+ },
100
+
101
+ async updateOrderStatus(orderId, status) {
102
+ const res = await fetch(`${API_BASE}/orders/${orderId}/status`, {
103
+ method: 'PUT', headers: { 'Content-Type': 'application/json' },
104
+ body: JSON.stringify({ status })
105
+ });
106
+ return await res.json();
107
+ },
108
+
109
+ // === SETTINGS ===
110
+ async getSettings() {
111
+ try {
112
+ const res = await fetch(`${API_BASE}/settings`);
113
+ return await res.json();
114
+ } catch {
115
+ return {};
116
+ }
117
+ },
118
+
119
+ async updateSettings(data) {
120
+ const res = await fetch(`${API_BASE}/settings`, {
121
+ method: 'PUT', headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify(data)
123
+ });
124
+ return await res.json();
125
+ },
126
+
127
+ async adminLogin(password) {
128
+ const res = await fetch(`${API_BASE}/settings/admin-login`, {
129
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
130
+ body: JSON.stringify({ password })
131
+ });
132
+ return await res.json();
133
+ }
134
+ };
js/app.js ADDED
@@ -0,0 +1,852 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // APP.JS — Shared Components & Logic
3
+ // ========================================
4
+
5
+ // SVG Icons
6
+ const ICONS = {
7
+ 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>',
8
+ 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>',
9
+ 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>',
10
+ 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>',
11
+ 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>',
12
+ 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>',
13
+ minus: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14"/></svg>',
14
+ 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>',
15
+ 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>',
16
+ 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>',
17
+ star: '★',
18
+ starHalf: '★',
19
+ 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>',
20
+ 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>',
21
+ 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>',
22
+ 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>',
23
+ 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>',
24
+ truck: '🚚', shield: '🛡️', clock: '⏰', refresh: '🔄',
25
+ 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>',
26
+ 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>',
27
+ 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>',
28
+ };
29
+
30
+ function getStarsHTML(rating) {
31
+ let html = '';
32
+ for (let i = 1; i <= 5; i++) {
33
+ html += i <= Math.round(rating) ? '<span style="color:var(--clr-warning)">★</span>' : '<span style="color:var(--clr-gray)">★</span>';
34
+ }
35
+ return html;
36
+ }
37
+
38
+ // Current page detection
39
+ function getCurrentPage() {
40
+ const path = window.location.pathname.split('/').pop().split('?')[0];
41
+ if (path === 'catalog.html') return 'catalog';
42
+ if (path === 'product.html') return 'product';
43
+ if (path === 'cart.html') return 'cart';
44
+ if (path === 'checkout.html') return 'checkout';
45
+ if (path === 'wishlist.html') return 'wishlist';
46
+ if (path === 'profile.html') return 'profile';
47
+ return 'home';
48
+ }
49
+
50
+ // ========================================
51
+ // NAVBAR
52
+ // ========================================
53
+ function renderNavbar() {
54
+ const page = getCurrentPage();
55
+ const nav = document.createElement('nav');
56
+ nav.className = 'navbar';
57
+ nav.id = 'navbar';
58
+ nav.innerHTML = `
59
+ <div class="navbar-inner">
60
+ <a href="index.html" class="navbar-logo">M-TEXTILE</a>
61
+ <div class="navbar-nav" id="navLinks">
62
+ <a href="index.html" class="${page === 'home' ? 'active' : ''}">Bosh sahifa</a>
63
+ <a href="catalog.html" class="${page === 'catalog' ? 'active' : ''}">Katalog</a>
64
+ <a href="catalog.html?category=kiyimlar">Kiyimlar</a>
65
+ <a href="catalog.html?category=shimlar">Shimlar</a>
66
+ <a href="catalog.html?category=galistuklar">Galistuklar</a>
67
+ </div>
68
+ <div class="navbar-actions">
69
+ <button class="btn btn-icon btn-ghost" id="navSearchBtn" title="Qidirish">
70
+ ${ICONS.search}
71
+ </button>
72
+ <button class="btn btn-icon btn-ghost" id="themeToggle" title="Rejimni o'zgartirish">
73
+ <span id="themeIcon">🌙</span>
74
+ </button>
75
+ <a href="wishlist.html" class="btn btn-icon btn-ghost" title="Sevimlilar">${ICONS.heart}</a>
76
+ <a href="cart.html" class="btn btn-icon btn-ghost cart-badge" title="Savatcha" id="navCartBtn">
77
+ ${ICONS.cart}
78
+ <span class="badge-count" id="cartBadge" style="display:none">0</span>
79
+ </a>
80
+ <a href="#" class="btn btn-icon btn-ghost" title="Profil" id="navProfileBtn">${ICONS.user}</a>
81
+ <div class="hamburger" id="hamburger">
82
+ <span></span><span></span><span></span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ `;
87
+ document.body.prepend(nav);
88
+
89
+ // Mobile nav
90
+ const mobileNav = document.createElement('div');
91
+ mobileNav.className = 'mobile-nav';
92
+ mobileNav.id = 'mobileNav';
93
+ mobileNav.innerHTML = `
94
+ <a href="index.html" class="${page === 'home' ? 'active' : ''}">🏠 Bosh sahifa</a>
95
+ <a href="catalog.html" class="${page === 'catalog' ? 'active' : ''}">📦 Katalog</a>
96
+ <a href="catalog.html?category=kiyimlar">👔 Kiyimlar</a>
97
+ <a href="catalog.html?category=formalar">🎓 Formalar</a>
98
+ <a href="catalog.html?category=shimlar">👖 Shimlar</a>
99
+ <a href="catalog.html?category=galistuklar">🎀 Galistuklar</a>
100
+ <a href="catalog.html?category=aksessuarlar">👜 Aksessuarlar</a>
101
+ <a href="wishlist.html">❤️ Sevimlilar</a>
102
+ <a href="cart.html">🛒 Savatcha</a>
103
+ <a href="profile.html">👤 Profil</a>
104
+ `;
105
+ document.body.prepend(mobileNav);
106
+
107
+ // Hamburger toggle
108
+ const hamburger = document.getElementById('hamburger');
109
+ hamburger.addEventListener('click', () => {
110
+ hamburger.classList.toggle('active');
111
+ mobileNav.classList.toggle('active');
112
+ });
113
+
114
+ // Profile button — login modal or profile page
115
+ document.getElementById('navProfileBtn')?.addEventListener('click', (e) => {
116
+ e.preventDefault();
117
+ if (isLoggedIn()) { window.location.href = 'profile.html'; }
118
+ else { openLoginModal(); }
119
+ });
120
+
121
+ // Scroll effect
122
+ window.addEventListener('scroll', () => {
123
+ nav.classList.toggle('scrolled', window.scrollY > 50);
124
+ });
125
+
126
+ document.getElementById('navSearchBtn')?.addEventListener('click', openSearchModal);
127
+
128
+ // Initialize Search Modal
129
+ renderSearchModal();
130
+
131
+ updateCartBadge();
132
+ }
133
+
134
+ function updateCartBadge() {
135
+ const badge = document.getElementById('cartBadge');
136
+ if (!badge) return;
137
+ const count = getCartCount();
138
+ badge.textContent = count;
139
+ badge.style.display = count > 0 ? 'flex' : 'none';
140
+ }
141
+
142
+ // ========================================
143
+ // LIVE SEARCH
144
+ // ========================================
145
+ function showSearchResults(query) {
146
+ const dropdown = document.getElementById('searchDropdown');
147
+ if (!dropdown) return;
148
+ const q = query.trim();
149
+ if (q.length < 2) { dropdown.classList.remove('active'); return; }
150
+
151
+ const results = searchProducts(q).slice(0, 6);
152
+ if (results.length === 0) {
153
+ dropdown.innerHTML = '<div class="search-dropdown-empty">Mahsulot topilmadi</div>';
154
+ } else {
155
+ dropdown.innerHTML = results.map(p => `
156
+ <a href="product.html?id=${p.id}" class="search-dropdown-item">
157
+ <img src="${p.images[0]}" alt="${p.name}">
158
+ <div class="search-dropdown-info">
159
+ <div class="search-dropdown-name">${p.name}</div>
160
+ <div class="search-dropdown-price">${formatPrice(p.price)}</div>
161
+ </div>
162
+ </a>
163
+ `).join('') + `<a href="catalog.html?search=${encodeURIComponent(q)}" class="search-dropdown-all">Barcha natijalar →</a>`;
164
+ }
165
+ dropdown.classList.add('active');
166
+ }
167
+
168
+ // ========================================
169
+ // ADVANCED SEARCH MODAL
170
+ // ========================================
171
+ function renderSearchModal() {
172
+ if (document.getElementById('searchModalOverlay')) return;
173
+ const modalHTML = `
174
+ <div class="search-modal-overlay" id="searchModalOverlay">
175
+ <div class="search-modal">
176
+ <div class="search-modal-header">
177
+ <div class="search-modal-input-wrapper">
178
+ ${ICONS.search}
179
+ <input type="text" placeholder="Mahsulot yoki kategoriya nomi..." id="modalSearchInput" autocomplete="off">
180
+ </div>
181
+ <button class="btn btn-icon btn-ghost" onclick="closeSearchModal()">${ICONS.x}</button>
182
+ </div>
183
+ <div class="search-modal-body">
184
+ <div id="searchModalSuggestions" class="search-suggestions">
185
+ <h4 style="margin-bottom:1rem;font-size:0.9rem;color:var(--clr-text-secondary);">Ko'p qidiriladiganlar:</h4>
186
+ <div style="display:flex;gap:0.5rem;flex-wrap:wrap;">
187
+ <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Ko\\'ylak')">Ko'ylak</span>
188
+ <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Kostyum')">Kostyum</span>
189
+ <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Kurtka')">Kurtka</span>
190
+ <span class="badge" style="cursor:pointer;" onclick="setSearchQuery('Shim')">Shim</span>
191
+ </div>
192
+ </div>
193
+ <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>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ `;
198
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
199
+
200
+ document.getElementById('searchModalOverlay').addEventListener('click', (e) => {
201
+ if (e.target.id === 'searchModalOverlay') closeSearchModal();
202
+ });
203
+
204
+ const input = document.getElementById('modalSearchInput');
205
+ let debounceTimeout;
206
+ input.addEventListener('input', () => {
207
+ clearTimeout(debounceTimeout);
208
+ debounceTimeout = setTimeout(() => handleModalSearch(input.value), 250);
209
+ });
210
+ }
211
+
212
+ function openSearchModal() {
213
+ const overlay = document.getElementById('searchModalOverlay');
214
+ if (!overlay) return;
215
+ overlay.classList.add('active');
216
+ document.body.style.overflow = 'hidden';
217
+ setTimeout(() => document.getElementById('modalSearchInput').focus(), 100);
218
+ }
219
+
220
+ function closeSearchModal() {
221
+ const overlay = document.getElementById('searchModalOverlay');
222
+ if (overlay) {
223
+ overlay.classList.remove('active');
224
+ document.body.style.overflow = '';
225
+ }
226
+ }
227
+
228
+ function setSearchQuery(q) {
229
+ const input = document.getElementById('modalSearchInput');
230
+ input.value = q;
231
+ handleModalSearch(q);
232
+ }
233
+
234
+ function highlightText(text, query) {
235
+ if (!query) return text;
236
+ const regex = new RegExp(`(${query})`, 'gi');
237
+ return text.replace(regex, '<mark style="background:var(--clr-accent);color:var(--clr-bg);padding:0 2px;border-radius:2px;">$1</mark>');
238
+ }
239
+
240
+ function handleModalSearch(query) {
241
+ const resultsContainer = document.getElementById('searchModalResults');
242
+ const suggestions = document.getElementById('searchModalSuggestions');
243
+ const q = query.trim().toLowerCase();
244
+
245
+ if (q.length < 2) {
246
+ resultsContainer.style.display = 'none';
247
+ suggestions.style.display = 'block';
248
+ return;
249
+ }
250
+
251
+ suggestions.style.display = 'none';
252
+ resultsContainer.style.display = 'grid';
253
+
254
+ const results = searchProducts(q).slice(0, 8);
255
+ if (results.length === 0) {
256
+ resultsContainer.innerHTML = '<div style="grid-column:1/-1;text-align:center;padding:2rem;color:var(--clr-text-secondary);">Hech narsa topilmadi</div>';
257
+ } else {
258
+ resultsContainer.innerHTML = results.map(p => `
259
+ <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;">
260
+ <img src="${p.images[0]}" alt="${p.name}" style="width:60px;height:60px;object-fit:cover;border-radius:var(--radius-sm);">
261
+ <div>
262
+ <div style="font-weight:600;color:var(--clr-text-primary);margin-bottom:0.25rem;">${highlightText(p.name, q)}</div>
263
+ <div style="color:var(--clr-text-secondary);font-size:0.85rem;margin-bottom:0.25rem;">${highlightText(p.category, q)}</div>
264
+ <div style="color:var(--clr-accent);font-weight:600;">${formatPrice(p.price)}</div>
265
+ </div>
266
+ </a>
267
+ `).join('');
268
+ }
269
+ }
270
+
271
+ // ========================================
272
+ // FOOTER
273
+ // ========================================
274
+ function renderFooter() {
275
+ const settings = JSON.parse(localStorage.getItem('mtextile_settings')) || {};
276
+ const globalAddress = settings.address || "Toshkent, O'zbekiston";
277
+
278
+ const footer = document.createElement('footer');
279
+ footer.className = 'footer';
280
+ footer.innerHTML = `
281
+ <div class="container">
282
+ <div class="footer-grid">
283
+ <div class="footer-brand">
284
+ <div class="footer-brand-name">M-TEXTILE</div>
285
+ <p class="footer-brand-desc">Eng sifatli kiyim-kechak mahsulotlarini uyingizdan turib oling. Barcha buyurtmalar do'kondan olib ketilishi kerak.</p>
286
+ <div class="footer-social" style="margin-top: 1.5rem;">
287
+ <a href="#" title="Instagram">${ICONS.instagram}</a>
288
+ <a href="#" title="Telegram">${ICONS.telegram}</a>
289
+ <a href="#" title="Facebook">${ICONS.facebook}</a>
290
+ </div>
291
+ </div>
292
+ <div>
293
+ <h4 class="footer-heading">Kategoriyalar</h4>
294
+ <div class="footer-links">
295
+ <a href="catalog.html?category=kiyimlar">Kiyimlar</a>
296
+ <a href="catalog.html?category=formalar">Formalar</a>
297
+ <a href="catalog.html?category=shimlar">Shimlar</a>
298
+ <a href="catalog.html?category=galistuklar">Galistuklar</a>
299
+ <a href="catalog.html?category=aksessuarlar">Aksessuarlar</a>
300
+ </div>
301
+ </div>
302
+ <div>
303
+ <h4 class="footer-heading">Yordam</h4>
304
+ <div class="footer-links">
305
+ <a href="#">Do'kondan olib ketish usuli</a>
306
+ <a href="#">Qaytarish siyosati</a>
307
+ <a href="#">To'lov usullari</a>
308
+ <a href="#">Ko'p so'raladigan savollar</a>
309
+ </div>
310
+ </div>
311
+ <div>
312
+ <h4 class="footer-heading">Aloqa</h4>
313
+ <div class="footer-links">
314
+ <a href="tel:+998996083712">+998 99 608 37 12</a>
315
+ <a href="mailto:info@m-textile.uz">info@m-textile.uz</a>
316
+ <a href="#">${globalAddress}</a>
317
+ <a href="#">Dushanba-Juma: 9:00-18:00</a>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ <div class="footer-bottom">
322
+ <p>© 2026 M-TEXTILE. Barcha huquqlar himoyalangan.</p>
323
+ <div style="display:flex; gap: 1.5rem;">
324
+ <a href="#">Maxfiylik siyosati</a>
325
+ <a href="#">Foydalanish shartlari</a>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ `;
330
+ document.body.appendChild(footer);
331
+ }
332
+
333
+ // ========================================
334
+ // TOAST NOTIFICATIONS
335
+ // ========================================
336
+ function initToastContainer() {
337
+ if (!document.getElementById('toastContainer')) {
338
+ const container = document.createElement('div');
339
+ container.className = 'toast-container';
340
+ container.id = 'toastContainer';
341
+ document.body.appendChild(container);
342
+ }
343
+ }
344
+
345
+ function showToast(title, message, type = 'success', duration = 3000) {
346
+ initToastContainer();
347
+ const container = document.getElementById('toastContainer');
348
+ const icons = { success: '✅', error: '❌', info: 'ℹ️' };
349
+ const toast = document.createElement('div');
350
+ toast.className = `toast toast-${type}`;
351
+ toast.innerHTML = `
352
+ <span class="toast-icon">${icons[type] || '📢'}</span>
353
+ <div class="toast-content">
354
+ <div class="toast-title">${title}</div>
355
+ ${message ? `<div class="toast-message">${message}</div>` : ''}
356
+ </div>
357
+ <button class="toast-close" onclick="this.closest('.toast').remove()">${ICONS.x}</button>
358
+ `;
359
+ container.appendChild(toast);
360
+ setTimeout(() => {
361
+ toast.classList.add('toast-exit');
362
+ setTimeout(() => toast.remove(), 300);
363
+ }, duration);
364
+ }
365
+
366
+ // ========================================
367
+ // PRODUCT CARD RENDERER
368
+ // ========================================
369
+ function renderProductCard(product) {
370
+ const wishlisted = isInWishlist(product.id);
371
+ const card = document.createElement('div');
372
+ card.className = 'product-card';
373
+ card.innerHTML = `
374
+ <div class="product-card-image">
375
+ <img src="${product.images[0]}" alt="${product.name}" loading="lazy">
376
+ <div class="product-card-badges">
377
+ ${product.isNew ? '<span class="badge badge-new">YANGI</span>' : ''}
378
+ ${product.discount > 0 ? `<span class="badge badge-discount">-${product.discount}%</span>` : ''}
379
+ </div>
380
+ <div class="product-card-actions">
381
+ <button class="product-card-action-btn ${wishlisted ? 'wishlisted' : ''}" onclick="event.stopPropagation();handleWishlist(${product.id},this)" title="Sevimlilar">
382
+ ${wishlisted ? ICONS.heartFill : ICONS.heart}
383
+ </button>
384
+ <button class="product-card-action-btn" onclick="event.stopPropagation();openQuickView(${product.id})" title="Tezkor ko'rish">${ICONS.eye}</button>
385
+ <button class="product-card-action-btn" onclick="event.stopPropagation();quickAddToCart(${product.id}, event)" title="Savatga qo'shish">${ICONS.cart}</button>
386
+ </div>
387
+ </div>
388
+ <div class="product-card-body">
389
+ <div class="product-card-category">${CATEGORIES[product.category]?.name || product.category}</div>
390
+ <h3 class="product-card-name">${product.name}</h3>
391
+ <div class="product-card-rating">
392
+ <span class="stars">${getStarsHTML(product.rating)}</span>
393
+ <span class="count">(${product.reviewCount})</span>
394
+ </div>
395
+ <div class="product-card-price">
396
+ <span class="price-current">${formatPrice(product.price)}</span>
397
+ ${product.oldPrice ? `<span class="price-old">${formatPrice(product.oldPrice)}</span>` : ''}
398
+ </div>
399
+ </div>
400
+ `;
401
+ card.addEventListener('click', () => {
402
+ window.location.href = `product.html?id=${product.id}`;
403
+ });
404
+ return card;
405
+ }
406
+
407
+ function handleWishlist(id, btn) {
408
+ toggleWishlist(id);
409
+ const wishlisted = isInWishlist(id);
410
+ btn.classList.toggle('wishlisted', wishlisted);
411
+ btn.innerHTML = wishlisted ? ICONS.heartFill : ICONS.heart;
412
+ showToast(wishlisted ? 'Sevimlilarga qo\'shildi' : 'Sevimlilardan olib tashlandi', '', wishlisted ? 'success' : 'info');
413
+ }
414
+
415
+ function quickAddToCart(id, event) {
416
+ id = parseInt(id);
417
+ const product = getProductById(id);
418
+ if (!product) return;
419
+ addToCart(id, product.sizes[0], product.colors[0], 1);
420
+ updateCartBadge();
421
+ showToast('Savatga qo\'shildi', product.name, 'success');
422
+ if (event) {
423
+ const btn = event.currentTarget || event.target;
424
+ let img = btn.closest('.product-card')?.querySelector('img') || btn.closest('.modal')?.querySelector('img') || document.querySelector('#mainImage');
425
+ if (img) flyToCart(img);
426
+ }
427
+ }
428
+
429
+ function flyToCart(imgElement) {
430
+ if (!imgElement) return;
431
+ const cartIcon = document.getElementById('navCartBtn');
432
+ if (!cartIcon) return;
433
+
434
+ const imgClone = imgElement.cloneNode(true);
435
+ const rect = imgElement.getBoundingClientRect();
436
+ const cartRect = cartIcon.getBoundingClientRect();
437
+
438
+ imgClone.className = 'flying-img';
439
+ imgClone.style.top = rect.top + 'px';
440
+ imgClone.style.left = rect.left + 'px';
441
+ imgClone.style.width = rect.width + 'px';
442
+ imgClone.style.height = rect.height + 'px';
443
+ document.body.appendChild(imgClone);
444
+
445
+ requestAnimationFrame(() => {
446
+ requestAnimationFrame(() => {
447
+ imgClone.style.top = cartRect.top + 'px';
448
+ imgClone.style.left = cartRect.left + 'px';
449
+ imgClone.style.width = '20px';
450
+ imgClone.style.height = '20px';
451
+ imgClone.style.opacity = '0';
452
+ imgClone.style.transform = 'scale(0.5)';
453
+ });
454
+ });
455
+
456
+ setTimeout(() => {
457
+ imgClone.remove();
458
+ cartIcon.classList.add('bump');
459
+ setTimeout(() => cartIcon.classList.remove('bump'), 300);
460
+ }, 700);
461
+ }
462
+
463
+ // ========================================
464
+ // INTERSECTION OBSERVER FOR ANIMATIONS
465
+ // ========================================
466
+ function initRevealAnimations() {
467
+ // Convert old manual reveals to AOS targets
468
+ document.querySelectorAll('.reveal').forEach((el, index) => {
469
+ if (!el.hasAttribute('data-aos')) {
470
+ el.setAttribute('data-aos', 'fade-up');
471
+ el.setAttribute('data-aos-delay', (index % 4) * 100);
472
+ el.classList.remove('reveal');
473
+ }
474
+ });
475
+
476
+ // Automatically add stagger effects for product grids
477
+ document.querySelectorAll('.stagger').forEach(grid => {
478
+ Array.from(grid.children).forEach((child, idx) => {
479
+ if (!child.hasAttribute('data-aos')) {
480
+ child.setAttribute('data-aos', 'fade-up');
481
+ child.setAttribute('data-aos-delay', (idx % 8) * 100);
482
+ }
483
+ });
484
+ });
485
+
486
+ if (typeof AOS !== 'undefined') {
487
+ AOS.refreshHard();
488
+ }
489
+ }
490
+
491
+ // ========================================
492
+ // QUICK VIEW MODAL
493
+ // ========================================
494
+ function openQuickView(id) {
495
+ const product = getProductById(id);
496
+ if (!product) return;
497
+ trackRecentlyViewed(id);
498
+
499
+ let overlay = document.getElementById('quickViewOverlay');
500
+ if (!overlay) {
501
+ overlay = document.createElement('div');
502
+ overlay.className = 'modal-overlay';
503
+ overlay.id = 'quickViewOverlay';
504
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) closeQuickView(); });
505
+ document.body.appendChild(overlay);
506
+ }
507
+
508
+ const wishlisted = isInWishlist(product.id);
509
+ overlay.innerHTML = `
510
+ <div class="modal" style="max-width:800px;">
511
+ <div class="modal-header">
512
+ <h2 class="modal-title">Tezkor ko'rish</h2>
513
+ <button class="modal-close" onclick="closeQuickView()">${ICONS.x}</button>
514
+ </div>
515
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;">
516
+ <div style="aspect-ratio:3/4;border-radius:var(--radius-lg);overflow:hidden;background:var(--clr-surface);">
517
+ <img src="${product.images[0]}" alt="${product.name}" style="width:100%;height:100%;object-fit:cover;">
518
+ </div>
519
+ <div>
520
+ <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>
521
+ <h3 style="font-size:1.35rem;margin-bottom:0.75rem;">${product.name}</h3>
522
+ <div style="margin-bottom:0.75rem;">${getStarsHTML(product.rating)} <span style="color:var(--clr-text-muted);font-size:0.85rem;">(${product.reviewCount})</span></div>
523
+ <div style="display:flex;align-items:baseline;gap:0.75rem;margin-bottom:1rem;">
524
+ <span style="font-size:1.5rem;font-weight:700;">${formatPrice(product.price)}</span>
525
+ ${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>` : ''}
526
+ </div>
527
+ <p style="color:var(--clr-text-secondary);font-size:0.9rem;line-height:1.7;margin-bottom:1.25rem;">${product.description}</p>
528
+ <div style="margin-bottom:0.75rem;font-size:0.85rem;">O'lchamlar: <strong>${product.sizes.join(', ')}</strong></div>
529
+ <div style="margin-bottom:1.25rem;font-size:0.85rem;">Ranglar: <strong>${product.colors.map(c => COLORS_MAP[c]?.name || c).join(', ')}</strong></div>
530
+ <div style="display:flex;gap:0.75rem;">
531
+ <button class="btn btn-primary" style="flex:1;" onclick="quickAddToCart(${product.id}, event);closeQuickView()">🛒 Savatga</button>
532
+ <button class="btn btn-outline" onclick="window.location.href='product.html?id=${product.id}'">${ICONS.eye} Batafsil</button>
533
+ <button class="btn btn-outline btn-icon" onclick="handleWishlistQV(${product.id},this)" style="${wishlisted ? 'color:var(--clr-error)' : ''}">${wishlisted ? ICONS.heartFill : ICONS.heart}</button>
534
+ </div>
535
+ </div>
536
+ </div>
537
+ </div>
538
+ `;
539
+ overlay.classList.add('active');
540
+ document.body.style.overflow = 'hidden';
541
+ }
542
+
543
+ function closeQuickView() {
544
+ const overlay = document.getElementById('quickViewOverlay');
545
+ if (overlay) { overlay.classList.remove('active'); document.body.style.overflow = ''; }
546
+ }
547
+
548
+ function handleWishlistQV(id, btn) {
549
+ toggleWishlist(id);
550
+ const w = isInWishlist(id);
551
+ btn.innerHTML = w ? ICONS.heartFill : ICONS.heart;
552
+ btn.style.color = w ? 'var(--clr-error)' : '';
553
+ showToast(w ? 'Sevimlilarga qo\'shildi' : 'Olib tashlandi', '', w ? 'success' : 'info');
554
+ }
555
+
556
+ // ========================================
557
+ // BACK TO TOP BUTTON
558
+ // ========================================
559
+ function initBackToTop() {
560
+ const btn = document.createElement('button');
561
+ btn.className = 'back-to-top';
562
+ btn.id = 'backToTop';
563
+ 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>';
564
+ btn.title = 'Yuqoriga';
565
+ btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
566
+ document.body.appendChild(btn);
567
+
568
+ window.addEventListener('scroll', () => {
569
+ btn.classList.toggle('visible', window.scrollY > 400);
570
+ });
571
+ }
572
+
573
+ // ========================================
574
+ // RECENTLY VIEWED PRODUCTS
575
+ // ========================================
576
+ function trackRecentlyViewed(id) {
577
+ let rv = JSON.parse(localStorage.getItem('mtextile_recent') || '[]');
578
+ rv = rv.filter(i => i !== id);
579
+ rv.unshift(id);
580
+ if (rv.length > 10) rv = rv.slice(0, 10);
581
+ localStorage.setItem('mtextile_recent', JSON.stringify(rv));
582
+ }
583
+
584
+ function getRecentlyViewed() {
585
+ return JSON.parse(localStorage.getItem('mtextile_recent') || '[]');
586
+ }
587
+
588
+ function renderRecentlyViewed(containerId) {
589
+ const container = document.getElementById(containerId);
590
+ if (!container) return;
591
+ const ids = getRecentlyViewed();
592
+ if (ids.length === 0) { container.closest('.recently-viewed-section')?.remove(); return; }
593
+ ids.slice(0, 4).forEach(id => {
594
+ const p = getProductById(id);
595
+ if (p) container.appendChild(renderProductCard(p));
596
+ });
597
+ }
598
+
599
+ // ========================================
600
+ // ANIMATED STAT COUNTERS
601
+ // ========================================
602
+ function animateCounters() {
603
+ document.querySelectorAll('.hero-stat-num[data-count]').forEach(el => {
604
+ const target = parseInt(el.dataset.count);
605
+ const suffix = el.dataset.suffix || '';
606
+ let current = 0;
607
+ const step = Math.max(1, Math.floor(target / 40));
608
+ const timer = setInterval(() => {
609
+ current += step;
610
+ if (current >= target) { current = target; clearInterval(timer); }
611
+ el.textContent = current + suffix;
612
+ }, 30);
613
+ });
614
+ }
615
+
616
+ // ========================================
617
+ // SIZE GUIDE MODAL
618
+ // ========================================
619
+ function openSizeGuide() {
620
+ let overlay = document.getElementById('sizeGuideOverlay');
621
+ if (!overlay) {
622
+ overlay = document.createElement('div');
623
+ overlay.className = 'modal-overlay';
624
+ overlay.id = 'sizeGuideOverlay';
625
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSizeGuide(); });
626
+ document.body.appendChild(overlay);
627
+ }
628
+ overlay.innerHTML = `
629
+ <div class="modal">
630
+ <div class="modal-header">
631
+ <h2 class="modal-title">📏 O'lcham yo'riqnomasi</h2>
632
+ <button class="modal-close" onclick="closeSizeGuide()">${ICONS.x}</button>
633
+ </div>
634
+ <table style="width:100%;border-collapse:collapse;font-size:0.9rem;">
635
+ <thead><tr style="border-bottom:2px solid var(--clr-border);">
636
+ <th style="padding:0.75rem;text-align:left;">O'lcham</th>
637
+ <th style="padding:0.75rem;text-align:center;">Ko'krak (sm)</th>
638
+ <th style="padding:0.75rem;text-align:center;">Bel (sm)</th>
639
+ <th style="padding:0.75rem;text-align:center;">Bo'y (sm)</th>
640
+ </tr></thead>
641
+ <tbody>
642
+ <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>
643
+ <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>
644
+ <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>
645
+ <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>
646
+ <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>
647
+ <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>
648
+ </tbody>
649
+ </table>
650
+ <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>
651
+ </div >
652
+ `;
653
+ overlay.classList.add('active');
654
+ document.body.style.overflow = 'hidden';
655
+ }
656
+
657
+ function closeSizeGuide() {
658
+ const overlay = document.getElementById('sizeGuideOverlay');
659
+ if (overlay) { overlay.classList.remove('active'); document.body.style.overflow = ''; }
660
+ }
661
+
662
+ // ========================================
663
+ // THEME TOGGLE (Dark / Light)
664
+ // ========================================
665
+ function initThemeToggle() {
666
+ document.getElementById('themeToggle')?.addEventListener('click', () => {
667
+ const cur = document.documentElement.getAttribute('data-theme') || 'dark';
668
+ const next = cur === 'dark' ? 'light' : 'dark';
669
+ applyTheme(next);
670
+ localStorage.setItem('mtextile_theme', next);
671
+ });
672
+ }
673
+ function applyTheme(theme) {
674
+ if (theme === 'light') document.documentElement.setAttribute('data-theme', 'light');
675
+ else document.documentElement.removeAttribute('data-theme');
676
+ const icon = document.getElementById('themeIcon');
677
+ if (icon) icon.textContent = theme === 'light' ? '☀️' : '🌙';
678
+ }
679
+
680
+ // ========================================
681
+ // HERO SLIDER
682
+ // ========================================
683
+ function initHeroSlider() {
684
+ const slider = document.getElementById('heroSlider');
685
+ if (!slider) return;
686
+ const slides = [
687
+ { tag: '✨ Yangi kolleksiya — 2026 Bahor', title: 'Stilingizni <span>kashf eting</span>', desc: 'Eng sifatli kiyim-kechak mahsulotlarini uyingizdan turib xarid qiling.' },
688
+ { tag: '🔥 Maxsus chegirma', title: '<span>30%</span> gacha tejang', desc: 'Tanlangan mahsulotlarga katta chegirmalar!' },
689
+ { tag: '🎓 Maktab formasi', title: 'Sifatli <span>formalar</span>', desc: 'Maktab va ish uchun eng sifatli formal kiyimlar.' }
690
+ ];
691
+ let cur = 0, iv;
692
+ function render(i) {
693
+ const s = slides[i];
694
+ 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>';
695
+ document.getElementById('heroDots')?.querySelectorAll('.hero-dot').forEach(function (d, j) { d.classList.toggle('active', j === i); });
696
+ }
697
+ var dots = document.getElementById('heroDots');
698
+ if (dots) slides.forEach(function (_, i) {
699
+ var d = document.createElement('button');
700
+ d.className = 'hero-dot' + (i === 0 ? ' active' : '');
701
+ d.addEventListener('click', function () { cur = i; render(i); clearInterval(iv); iv = setInterval(next, 5000); });
702
+ dots.appendChild(d);
703
+ });
704
+ function next() { cur = (cur + 1) % slides.length; render(cur); }
705
+ render(0);
706
+ iv = setInterval(next, 5000);
707
+ }
708
+
709
+ // ========================================
710
+ // LOGIN / REGISTER MODAL
711
+ // ========================================
712
+ function openLoginModal() {
713
+ var ov = document.getElementById('loginOverlay');
714
+ 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); }
715
+ 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>' +
716
+ '<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>' +
717
+ '<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>' +
718
+ '<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>' +
719
+ '<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>' +
720
+ '<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>';
721
+ ov.classList.add('active'); document.body.style.overflow = 'hidden';
722
+ document.getElementById('loginCloseBtn').addEventListener('click', closeLoginModal);
723
+ document.getElementById('tabLogin').addEventListener('click', function () { switchLoginTab('login'); });
724
+ document.getElementById('tabRegister').addEventListener('click', function () { switchLoginTab('register'); });
725
+ document.getElementById('loginSubmitBtn').addEventListener('click', doLogin);
726
+ document.getElementById('regSubmitBtn').addEventListener('click', doRegister);
727
+ document.getElementById('googleSignInBtn').addEventListener('click', async function () {
728
+ this.disabled = true; this.style.opacity = '0.7';
729
+ await signInWithGoogle();
730
+ this.disabled = false; this.style.opacity = '1';
731
+ if (isLoggedIn()) { closeLoginModal(); if (typeof updateAuthUI === 'function') updateAuthUI(); }
732
+ });
733
+ }
734
+ function closeLoginModal() { var ov = document.getElementById('loginOverlay'); if (ov) { ov.classList.remove('active'); document.body.style.overflow = ''; } }
735
+ 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'; }
736
+ async function doLogin() {
737
+ var id = document.getElementById('loginId').value.trim(), pass = document.getElementById('loginPass').value;
738
+ if (!id) { showToast('Xatolik', 'Telefon yoki email kiriting', 'error'); return; }
739
+ if (!pass || pass.length < 4) { showToast('Xatolik', 'Parol kamida 4 belgi', 'error'); return; }
740
+ try {
741
+ var result = await API.login(id, pass);
742
+ if (!result.success) { showToast('Xatolik', result.message, 'error'); return; }
743
+ localStorage.setItem('mtextile_session', JSON.stringify(result.user));
744
+ closeLoginModal(); showToast('Xush kelibsiz!', result.user.name + ', tizimga kirdingiz', 'success');
745
+ if (typeof updateAuthUI === 'function') updateAuthUI();
746
+ } catch (e) { showToast('Xatolik', "Serverga ulanib bo'lmadi", 'error'); }
747
+ }
748
+ async function doRegister() {
749
+ var name = document.getElementById('regName').value.trim(), phone = document.getElementById('regPhone').value.trim(), email = document.getElementById('regEmail').value.trim(), pass = document.getElementById('regPass').value;
750
+ if (!name || name.length < 2) { showToast('Xatolik', 'Ismingizni kiriting', 'error'); return; }
751
+ if (!phone || phone.length < 9) { showToast('Xatolik', 'Telefon raqam kiriting', 'error'); return; }
752
+ if (!pass || pass.length < 6) { showToast('Xatolik', 'Parol kamida 6 belgi', 'error'); return; }
753
+ try {
754
+ var result = await API.register(name, phone, pass, email);
755
+ if (!result.success) { showToast('Xatolik', result.message, 'error'); return; }
756
+ localStorage.setItem('mtextile_session', JSON.stringify(result.user));
757
+ closeLoginModal(); showToast('Muvaffaqiyat!', "Ro'yxatdan o'tdingiz!", 'success');
758
+ if (typeof updateAuthUI === 'function') updateAuthUI();
759
+ } catch (e) { showToast('Xatolik', "Serverga ulanib bo'lmadi", 'error'); }
760
+ }
761
+
762
+ // ========================================
763
+ // PRODUCT IMAGE ZOOM
764
+ // ========================================
765
+ function initImageZoom() {
766
+ var img = document.getElementById('mainImage');
767
+ if (!img) return;
768
+ var c = img.parentElement; c.style.overflow = 'hidden'; c.style.cursor = 'zoom-in';
769
+ img.addEventListener('mousemove', function (e) {
770
+ var r = c.getBoundingClientRect();
771
+ img.style.transformOrigin = ((e.clientX - r.left) / r.width * 100) + '% ' + ((e.clientY - r.top) / r.height * 100) + '%';
772
+ img.style.transform = 'scale(1.8)';
773
+ });
774
+ img.addEventListener('mouseleave', function () { img.style.transform = 'scale(1)'; img.style.transformOrigin = 'center'; });
775
+ }
776
+
777
+ // ========================================
778
+ // MICRO-INTERACTIONS
779
+ // ========================================
780
+ function initRippleEffect() {
781
+ document.addEventListener('click', function (e) {
782
+ const btn = e.target.closest('.btn');
783
+ if (!btn) return;
784
+ const circle = document.createElement('span');
785
+ const d = Math.max(btn.clientWidth, btn.clientHeight);
786
+ const rect = btn.getBoundingClientRect();
787
+ circle.style.width = circle.style.height = d + 'px';
788
+ circle.style.left = e.clientX - rect.left - d / 2 + 'px';
789
+ circle.style.top = e.clientY - rect.top - d / 2 + 'px';
790
+ circle.classList.add('ripple');
791
+ const existing = btn.querySelector('.ripple');
792
+ if (existing) existing.remove();
793
+ btn.appendChild(circle);
794
+ setTimeout(() => circle.remove(), 600);
795
+ });
796
+ }
797
+
798
+ function initTiltEffect() {
799
+ const cards = document.querySelectorAll('.product-card:not(.tilt-bound)');
800
+ cards.forEach(card => {
801
+ card.classList.add('tilt-bound');
802
+ card.addEventListener('mousemove', e => {
803
+ const rect = card.getBoundingClientRect();
804
+ const xPct = (e.clientX - rect.left) / rect.width;
805
+ const yPct = (e.clientY - rect.top) / rect.height;
806
+ card.style.transform = `perspective(1000px) rotateX(${(yPct - 0.5) * -12}deg) rotateY(${(xPct - 0.5) * 12}deg) scale3d(1.02, 1.02, 1.02)`;
807
+ });
808
+ card.addEventListener('mouseleave', () => {
809
+ card.style.transform = '';
810
+ card.style.transition = 'transform 0.4s ease';
811
+ });
812
+ card.addEventListener('mouseenter', () => {
813
+ card.style.transition = 'none';
814
+ });
815
+ });
816
+ }
817
+
818
+ // ========================================
819
+ // INIT
820
+ // ========================================
821
+ document.addEventListener('DOMContentLoaded', () => {
822
+ // Preloader
823
+ const pl = document.getElementById('global-preloader');
824
+ if (pl) {
825
+ setTimeout(() => { pl.classList.add('hidden'); setTimeout(() => pl.style.display = 'none', 600); }, 300);
826
+ }
827
+ // AOS
828
+ if (typeof AOS !== 'undefined') AOS.init({ duration: 800, once: true, offset: 50 });
829
+
830
+ document.body.classList.add('page-fade-enter');
831
+ setTimeout(() => document.body.classList.remove('page-fade-enter'), 450);
832
+ var saved = localStorage.getItem('mtextile_theme') || 'dark';
833
+ applyTheme(saved);
834
+ renderNavbar();
835
+ renderFooter();
836
+ initRevealAnimations();
837
+ initBackToTop();
838
+ initThemeToggle();
839
+ initRippleEffect();
840
+ document.addEventListener('cartUpdated', updateCartBadge);
841
+ if (getCurrentPage() === 'home') { setTimeout(animateCounters, 600); initHeroSlider(); }
842
+ if (getCurrentPage() === 'product') {
843
+ var id = new URLSearchParams(window.location.search).get('id');
844
+ if (id) trackRecentlyViewed(parseInt(id));
845
+ setTimeout(initImageZoom, 500);
846
+ }
847
+
848
+ // Try initializing tilt on load, and setup observer for dynamic changes
849
+ setTimeout(initTiltEffect, 500);
850
+ const observer = new MutationObserver(initTiltEffect);
851
+ observer.observe(document.body, { childList: true, subtree: true });
852
+ });
js/cart.js ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // CART PAGE LOGIC
3
+ // ========================================
4
+ let promoApplied = null;
5
+
6
+ document.addEventListener('DOMContentLoaded', () => {
7
+ renderCartPage();
8
+ document.getElementById('promoBtn').addEventListener('click', handlePromo);
9
+ document.getElementById('promoInput').addEventListener('keydown', e => { if (e.key === 'Enter') handlePromo(); });
10
+ document.addEventListener('cartUpdated', renderCartPage);
11
+ });
12
+
13
+ function renderCartPage() {
14
+ const cart = getCart();
15
+ const cartContent = document.getElementById('cartContent');
16
+ const emptyCart = document.getElementById('emptyCart');
17
+
18
+ if (cart.length === 0) {
19
+ cartContent.style.display = 'none';
20
+ emptyCart.style.display = 'block';
21
+ return;
22
+ }
23
+ cartContent.style.display = '';
24
+ emptyCart.style.display = 'none';
25
+
26
+ const itemsContainer = document.getElementById('cartItems');
27
+ itemsContainer.innerHTML = '';
28
+
29
+ cart.forEach(item => {
30
+ const product = getProductById(item.productId);
31
+ if (!product) return;
32
+ const colorName = COLORS_MAP[item.color]?.name || item.color;
33
+
34
+ const el = document.createElement('div');
35
+ el.className = 'cart-item';
36
+ el.innerHTML = `
37
+ <div class="cart-item-image">
38
+ <img src="${product.images[0]}" alt="${product.name}">
39
+ </div>
40
+ <div>
41
+ <div class="cart-item-name">${product.name}</div>
42
+ <div class="cart-item-meta">O'lcham: ${item.size} | Rang: ${colorName}</div>
43
+ <div class="cart-item-price">${formatPrice(product.price)}</div>
44
+ <div style="margin-top:0.75rem;display:flex;align-items:center;gap:1rem;">
45
+ <div class="qty-control">
46
+ <button class="qty-btn" data-action="minus" data-id="${item.productId}" data-size="${item.size}" data-color="${item.color}">−</button>
47
+ <span class="qty-value">${item.quantity}</span>
48
+ <button class="qty-btn" data-action="plus" data-id="${item.productId}" data-size="${item.size}" data-color="${item.color}">+</button>
49
+ </div>
50
+ <button class="cart-item-remove" data-id="${item.productId}" data-size="${item.size}" data-color="${item.color}">O'chirish</button>
51
+ </div>
52
+ </div>
53
+ <div style="text-align:right;font-weight:600;font-size:1.1rem;">
54
+ ${formatPrice(product.price * item.quantity)}
55
+ </div>
56
+ `;
57
+ itemsContainer.appendChild(el);
58
+ });
59
+
60
+ // Event delegation for qty buttons and remove
61
+ itemsContainer.querySelectorAll('.qty-btn').forEach(btn => {
62
+ btn.addEventListener('click', () => {
63
+ const { id, size, color, action } = btn.dataset;
64
+ const item = cart.find(i => i.productId == id && i.size === size && i.color === color);
65
+ if (!item) return;
66
+ const newQty = action === 'plus' ? item.quantity + 1 : item.quantity - 1;
67
+ updateCartQty(parseInt(id), size, color, newQty);
68
+ });
69
+ });
70
+
71
+ itemsContainer.querySelectorAll('.cart-item-remove').forEach(btn => {
72
+ btn.addEventListener('click', () => {
73
+ const { id, size, color } = btn.dataset;
74
+ removeFromCart(parseInt(id), size, color);
75
+ showToast('O\'chirildi', 'Mahsulot savatchadan olib tashlandi', 'info');
76
+ });
77
+ });
78
+
79
+ updateSummary();
80
+ }
81
+
82
+ function updateSummary() {
83
+ const totals = getCartTotal();
84
+ let promoDiscount = 0;
85
+
86
+ if (promoApplied && promoApplied.type === 'percent') {
87
+ promoDiscount = Math.round(totals.subtotal * promoApplied.discount / 100);
88
+ }
89
+
90
+ document.getElementById('sumSubtotal').textContent = formatPrice(totals.subtotal);
91
+ document.getElementById('sumDiscount').textContent = totals.discount + promoDiscount > 0 ? '-' + formatPrice(totals.discount + promoDiscount) : '0 so\'m';
92
+ document.getElementById('sumTotal').textContent = formatPrice(totals.total - promoDiscount);
93
+ }
94
+
95
+ function handlePromo() {
96
+ const code = document.getElementById('promoInput').value.trim();
97
+ if (!code) return;
98
+ const result = applyPromo(code);
99
+ const msg = document.getElementById('promoMsg');
100
+ if (result.valid) {
101
+ promoApplied = result;
102
+ msg.innerHTML = `<span style="color:var(--clr-success)">✓ ${result.desc} qo'llanildi</span>`;
103
+ updateSummary();
104
+ } else {
105
+ msg.innerHTML = `<span style="color:var(--clr-error)">✗ ${result.message}</span>`;
106
+ }
107
+ }
js/catalog.js ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // CATALOG.JS — Filter, Sort, Search, Paginate
3
+ // ========================================
4
+
5
+ const ITEMS_PER_PAGE = 12;
6
+ let currentFilters = { category: null, sizes: [], colors: [], minPrice: undefined, maxPrice: undefined, hasDiscount: false };
7
+ let currentSort = 'default';
8
+ let currentPage = 1;
9
+ let currentView = 'grid';
10
+
11
+ document.addEventListener('DOMContentLoaded', () => {
12
+ parseURLParams();
13
+ buildFilterUI();
14
+ applyAndRender();
15
+ setupEventListeners();
16
+ setupMobileFilter();
17
+ });
18
+
19
+ function parseURLParams() {
20
+ const params = new URLSearchParams(window.location.search);
21
+ if (params.get('category')) currentFilters.category = params.get('category');
22
+ if (params.get('search')) {
23
+ const si = document.getElementById('searchInput');
24
+ if (si) si.value = params.get('search');
25
+ }
26
+ if (params.get('filter') === 'discount') currentFilters.hasDiscount = true;
27
+ if (params.get('filter') === 'new') currentSort = 'newest';
28
+ if (params.get('sort')) currentSort = params.get('sort');
29
+
30
+ // Update page title
31
+ if (currentFilters.category && CATEGORIES[currentFilters.category]) {
32
+ document.getElementById('catalogTitle').textContent = CATEGORIES[currentFilters.category].name;
33
+ document.getElementById('catalogSubtitle').textContent = `${CATEGORIES[currentFilters.category].icon} ${CATEGORIES[currentFilters.category].name} kategoriyasidagi mahsulotlar`;
34
+ }
35
+ }
36
+
37
+ function buildFilterUI() {
38
+ // Categories
39
+ const catContainer = document.getElementById('filterCategory');
40
+ catContainer.innerHTML = `<div class="filter-option ${!currentFilters.category ? 'active' : ''}" data-cat="">
41
+ <div class="filter-checkbox"></div><span>Barchasi</span>
42
+ </div>`;
43
+ Object.entries(CATEGORIES).forEach(([slug, cat]) => {
44
+ const count = getProductsByCategory(slug).length;
45
+ catContainer.innerHTML += `<div class="filter-option ${currentFilters.category === slug ? 'active' : ''}" data-cat="${slug}">
46
+ <div class="filter-checkbox"></div><span>${cat.icon} ${cat.name} (${count})</span>
47
+ </div>`;
48
+ });
49
+ catContainer.querySelectorAll('.filter-option').forEach(el => {
50
+ el.addEventListener('click', () => {
51
+ catContainer.querySelectorAll('.filter-option').forEach(e => e.classList.remove('active'));
52
+ el.classList.add('active');
53
+ currentFilters.category = el.dataset.cat || null;
54
+ currentPage = 1;
55
+ applyAndRender();
56
+ });
57
+ });
58
+
59
+ // Sizes
60
+ const sizeContainer = document.getElementById('filterSizes');
61
+ SIZES.forEach(size => {
62
+ const btn = document.createElement('button');
63
+ btn.className = 'size-btn';
64
+ btn.textContent = size;
65
+ btn.addEventListener('click', () => {
66
+ btn.classList.toggle('active');
67
+ if (btn.classList.contains('active')) currentFilters.sizes.push(size);
68
+ else currentFilters.sizes = currentFilters.sizes.filter(s => s !== size);
69
+ currentPage = 1;
70
+ applyAndRender();
71
+ });
72
+ sizeContainer.appendChild(btn);
73
+ });
74
+
75
+ // Colors
76
+ const colorContainer = document.getElementById('filterColors');
77
+ Object.entries(COLORS_MAP).forEach(([key, val]) => {
78
+ const btn = document.createElement('button');
79
+ btn.className = 'color-btn';
80
+ btn.style.background = val.hex;
81
+ btn.title = val.name;
82
+ btn.addEventListener('click', () => {
83
+ btn.classList.toggle('active');
84
+ if (btn.classList.contains('active')) currentFilters.colors.push(key);
85
+ else currentFilters.colors = currentFilters.colors.filter(c => c !== key);
86
+ currentPage = 1;
87
+ applyAndRender();
88
+ });
89
+ colorContainer.appendChild(btn);
90
+ });
91
+
92
+ // Discount toggle
93
+ const discEl = document.getElementById('filterDiscount');
94
+ if (currentFilters.hasDiscount) discEl.classList.add('active');
95
+ discEl.addEventListener('click', () => {
96
+ discEl.classList.toggle('active');
97
+ currentFilters.hasDiscount = discEl.classList.contains('active');
98
+ currentPage = 1;
99
+ applyAndRender();
100
+ });
101
+ }
102
+
103
+ function setupEventListeners() {
104
+ // Sort
105
+ document.getElementById('sortSelect').addEventListener('change', (e) => {
106
+ currentSort = e.target.value;
107
+ applyAndRender();
108
+ });
109
+
110
+ // Price filter
111
+ document.getElementById('applyPrice').addEventListener('click', () => {
112
+ const min = document.getElementById('priceMin').value;
113
+ const max = document.getElementById('priceMax').value;
114
+ currentFilters.minPrice = min ? parseInt(min) : undefined;
115
+ currentFilters.maxPrice = max ? parseInt(max) : undefined;
116
+ currentPage = 1;
117
+ applyAndRender();
118
+ });
119
+
120
+ // Clear filters
121
+ document.getElementById('clearFilters').addEventListener('click', clearAllFilters);
122
+
123
+ // View toggle
124
+ document.getElementById('gridViewBtn')?.addEventListener('click', () => { setView('grid'); });
125
+ document.getElementById('listViewBtn')?.addEventListener('click', () => { setView('list'); });
126
+
127
+ // Navbar search
128
+ const si = document.getElementById('searchInput');
129
+ if (si) {
130
+ si.addEventListener('input', debounce(() => { currentPage = 1; applyAndRender(); }, 300));
131
+ }
132
+ }
133
+
134
+ function setupMobileFilter() {
135
+ const mobileBtn = document.getElementById('mobileFilterBtn');
136
+ const sidebar = document.getElementById('filterSidebar');
137
+ const overlay = document.getElementById('filterOverlay');
138
+
139
+ if (window.innerWidth <= 768) mobileBtn.style.display = 'inline-flex';
140
+
141
+ window.addEventListener('resize', () => {
142
+ mobileBtn.style.display = window.innerWidth <= 768 ? 'inline-flex' : 'none';
143
+ });
144
+
145
+ mobileBtn?.addEventListener('click', () => {
146
+ sidebar.classList.add('active');
147
+ overlay.classList.add('active');
148
+ });
149
+
150
+ overlay?.addEventListener('click', () => {
151
+ sidebar.classList.remove('active');
152
+ overlay.classList.remove('active');
153
+ });
154
+ }
155
+
156
+ function setView(view) {
157
+ currentView = view;
158
+ const grid = document.getElementById('productsGrid');
159
+ grid.classList.toggle('list-view', view === 'list');
160
+ document.getElementById('gridViewBtn')?.classList.toggle('active', view === 'grid');
161
+ document.getElementById('listViewBtn')?.classList.toggle('active', view === 'list');
162
+ }
163
+
164
+ function clearAllFilters() {
165
+ currentFilters = { category: null, sizes: [], colors: [], minPrice: undefined, maxPrice: undefined, hasDiscount: false };
166
+ currentSort = 'default';
167
+ currentPage = 1;
168
+ document.getElementById('priceMin').value = '';
169
+ document.getElementById('priceMax').value = '';
170
+ document.getElementById('sortSelect').value = 'default';
171
+ document.querySelectorAll('.filter-option.active, .size-btn.active, .color-btn.active').forEach(el => el.classList.remove('active'));
172
+ document.querySelector('[data-cat=""]')?.classList.add('active');
173
+ const si = document.getElementById('searchInput');
174
+ if (si) si.value = '';
175
+ applyAndRender();
176
+ }
177
+
178
+ function applyAndRender() {
179
+ let products = [...PRODUCTS];
180
+
181
+ // Search
182
+ const query = document.getElementById('searchInput')?.value?.trim();
183
+ if (query) products = searchProducts(query);
184
+
185
+ // Filter
186
+ products = filterProducts(products, currentFilters);
187
+
188
+ // Sort
189
+ products = sortProducts(products, currentSort);
190
+
191
+ // Update count
192
+ document.getElementById('productCount').textContent = `${products.length} ta mahsulot`;
193
+
194
+ // Pagination
195
+ const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
196
+ const start = (currentPage - 1) * ITEMS_PER_PAGE;
197
+ const pageProducts = products.slice(start, start + ITEMS_PER_PAGE);
198
+
199
+ // Render products
200
+ const grid = document.getElementById('productsGrid');
201
+ const noResults = document.getElementById('noResults');
202
+ grid.innerHTML = '';
203
+
204
+ if (pageProducts.length === 0) {
205
+ grid.style.display = 'none';
206
+ noResults.style.display = 'block';
207
+ } else {
208
+ grid.style.display = '';
209
+ noResults.style.display = 'none';
210
+ pageProducts.forEach(p => grid.appendChild(renderProductCard(p)));
211
+ }
212
+
213
+ renderPagination(totalPages);
214
+ }
215
+
216
+ function renderPagination(totalPages) {
217
+ const container = document.getElementById('pagination');
218
+ container.innerHTML = '';
219
+ if (totalPages <= 1) return;
220
+
221
+ for (let i = 1; i <= totalPages; i++) {
222
+ const btn = document.createElement('button');
223
+ btn.className = `btn btn-sm ${i === currentPage ? 'btn-primary' : 'btn-outline'}`;
224
+ btn.textContent = i;
225
+ btn.addEventListener('click', () => {
226
+ currentPage = i;
227
+ applyAndRender();
228
+ window.scrollTo({ top: 0, behavior: 'smooth' });
229
+ });
230
+ container.appendChild(btn);
231
+ }
232
+ }
233
+
234
+ function debounce(fn, ms) {
235
+ let timer;
236
+ return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), ms); };
237
+ }
js/checkout.js ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // CHECKOUT PAGE LOGIC
3
+ // ========================================
4
+ let currentStepNum = 1;
5
+ let paymentMethod = 'cash';
6
+
7
+ document.addEventListener('DOMContentLoaded', () => {
8
+ if (getCart().length === 0) { window.location.href = 'cart.html'; return; }
9
+
10
+ // Auth Check
11
+ if (!isLoggedIn()) {
12
+ showToast("Majburiy", "To'lovni amalga oshirish yoki buyurtma berish uchun iltimos tizimga kiring", "info");
13
+ setTimeout(() => {
14
+ window.location.href = 'index.html'; // In a real app we might open modal directly, but index is safer to initialize auth state
15
+ }, 2000);
16
+ return;
17
+ }
18
+
19
+ // Pre-fill user data
20
+ const user = getUser();
21
+ if (user.name) document.getElementById('userName').value = user.name;
22
+ if (user.phone) document.getElementById('userPhone').value = user.phone;
23
+ if (user.email) document.getElementById('userEmail').value = user.email;
24
+
25
+ // Payment method selection
26
+ document.querySelectorAll('[data-payment]').forEach(el => {
27
+ el.addEventListener('click', () => {
28
+ document.querySelectorAll('[data-payment]').forEach(e => e.classList.remove('active'));
29
+ el.classList.add('active');
30
+ paymentMethod = el.dataset.payment;
31
+ });
32
+ });
33
+
34
+ // Load store location info for step 2
35
+ loadStoreLocation();
36
+ });
37
+
38
+ function loadStoreLocation() {
39
+ const settings = JSON.parse(localStorage.getItem('mtextile_settings')) || {};
40
+ const addressBox = document.getElementById('storeAddressText');
41
+ const mapBox = document.getElementById('storeMapContainer');
42
+
43
+ if (settings.address && addressBox) {
44
+ addressBox.textContent = settings.address;
45
+ }
46
+ if (settings.mapIframe && mapBox) {
47
+ mapBox.innerHTML = settings.mapIframe;
48
+ mapBox.style.display = 'block';
49
+ const iframe = mapBox.querySelector('iframe');
50
+ if (iframe) {
51
+ iframe.style.width = '100%';
52
+ iframe.style.height = '100%';
53
+ iframe.style.border = 'none';
54
+ }
55
+ }
56
+ }
57
+
58
+ function goToStep(step) {
59
+ document.querySelectorAll('.checkout-step').forEach(el => el.classList.remove('active'));
60
+ document.getElementById('step' + step).classList.add('active');
61
+ document.querySelectorAll('.progress-step').forEach(el => {
62
+ const s = parseInt(el.dataset.step);
63
+ el.classList.remove('active', 'done');
64
+ if (s < step) el.classList.add('done');
65
+ if (s === step) el.classList.add('active');
66
+ });
67
+ currentStepNum = step;
68
+ window.scrollTo({ top: 0, behavior: 'smooth' });
69
+ }
70
+
71
+ function nextStep(from) {
72
+ if (from === 1 && !validateStep1()) return;
73
+ if (from === 2 && !validateStep2()) return;
74
+ if (from === 3) buildSummary();
75
+ goToStep(from + 1);
76
+ }
77
+
78
+ function prevStep(from) { goToStep(from - 1); }
79
+
80
+ function validateStep1() {
81
+ let valid = true;
82
+ const name = document.getElementById('userName').value.trim();
83
+ const phone = document.getElementById('userPhone').value.trim();
84
+ const email = document.getElementById('userEmail').value.trim();
85
+
86
+ clearErrors();
87
+
88
+ if (!name || name.length < 2) {
89
+ showFieldError('userName', 'nameError', 'Ismingizni kiriting (kamida 2 harf)');
90
+ valid = false;
91
+ }
92
+ // Strict UZ phone validation
93
+ const phoneRegex = /^(?:\+?998)[ \-]?\d{2}[ \-]?\d{3}[ \-]?\d{2}[ \-]?\d{2}$/;
94
+ if (!phone || !phoneRegex.test(phone.replace(/\s/g, ''))) {
95
+ showFieldError('userPhone', 'phoneError', 'Faqat O\'zbekiston raqami (+998...) kiritilishi shart');
96
+ valid = false;
97
+ }
98
+ if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
99
+ showFieldError('userEmail', 'emailError', 'To\'g\'ri email kiriting');
100
+ valid = false;
101
+ }
102
+
103
+ if (valid) saveUser({ name, phone, email });
104
+ return valid;
105
+ }
106
+
107
+ function validateStep2() {
108
+ // Store pickup requires no address input from the user.
109
+ return true;
110
+ }
111
+
112
+ function showFieldError(inputId, errorId, msg) {
113
+ document.getElementById(inputId).classList.add('error');
114
+ document.getElementById(errorId).textContent = msg;
115
+ }
116
+
117
+ function clearErrors() {
118
+ document.querySelectorAll('.form-input.error').forEach(el => el.classList.remove('error'));
119
+ document.querySelectorAll('.form-error').forEach(el => el.textContent = '');
120
+ }
121
+
122
+ function buildSummary() {
123
+ const user = getUser();
124
+ const cart = getCart();
125
+ const totals = getCartTotal();
126
+ const payNames = { cash: '💵 Naqd pul', card: '💳 Plastik karta', click: '📱 Click/Payme' };
127
+
128
+ let itemsHTML = '';
129
+ cart.forEach(item => {
130
+ const p = getProductById(item.productId);
131
+ if (!p) return;
132
+ itemsHTML += `<div style="display:flex;align-items:center;gap:1rem;padding:0.75rem 0;border-bottom:1px solid var(--clr-border);">
133
+ <img src="${p.images[0]}" style="width:50px;height:60px;object-fit:cover;border-radius:6px;">
134
+ <div style="flex:1">
135
+ <div style="font-weight:600;font-size:0.9rem;">${p.name}</div>
136
+ <div style="font-size:0.75rem;color:var(--clr-text-muted)">${item.size} | ${COLORS_MAP[item.color]?.name || item.color} | x${item.quantity}</div>
137
+ </div>
138
+ <div style="font-weight:600">${formatPrice(p.price * item.quantity)}</div>
139
+ </div>`;
140
+ });
141
+
142
+ document.getElementById('orderSummary').innerHTML = `
143
+ <h3 style="margin-bottom:1rem;font-size:1rem;">📋 Buyurtma tafsilotlari</h3>
144
+ <div style="margin-bottom:1rem;">
145
+ <div style="font-size:0.85rem;color:var(--clr-text-muted)">Buyurtmachi</div>
146
+ <div>${user.name} | ${user.phone}</div>
147
+ </div>
148
+ <div style="margin-bottom:1rem;">
149
+ <div style="font-size:0.85rem;color:var(--clr-text-muted)">Olib ketish manzili</div>
150
+ <div>Do'kon: ${document.getElementById('storeAddressText').textContent}</div>
151
+ </div>
152
+ <div style="margin-bottom:1rem;">
153
+ <div style="font-size:0.85rem;color:var(--clr-text-muted)">To'lov usuli</div>
154
+ <div>${payNames[paymentMethod]}</div>
155
+ </div>
156
+ <div style="margin-bottom:0.5rem;font-weight:600;">Mahsulotlar:</div>
157
+ ${itemsHTML}
158
+ <div style="display:flex;justify-content:space-between;padding-top:1rem;font-size:1.15rem;font-weight:700;">
159
+ <span>Jami summa:</span>
160
+ <span>${formatPrice(totals.total)}</span>
161
+ </div>
162
+ `;
163
+ }
164
+
165
+ function confirmOrder() {
166
+ const cartItems = getCart(); // Copy before cart is cleared
167
+
168
+ const order = addOrder({
169
+ customer: getUser(),
170
+ pickupLocation: document.getElementById('storeAddressText').textContent,
171
+ payment: paymentMethod,
172
+ });
173
+
174
+ // Deduct stock for each cart item
175
+ cartItems.forEach(item => {
176
+ deductProductStock(item.productId, item.quantity);
177
+ });
178
+
179
+ // Show success
180
+ document.querySelectorAll('.checkout-step').forEach(el => { el.classList.remove('active'); el.style.display = 'none'; });
181
+ document.querySelector('.progress-steps').style.display = 'none';
182
+ const success = document.getElementById('stepSuccess');
183
+ success.style.display = 'block';
184
+ success.classList.add('active');
185
+ document.getElementById('orderId').textContent = `Buyurtma raqami: ${order.id}`;
186
+ updateCartBadge();
187
+ }
js/firebase-config.js ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // FIREBASE CONFIGURATION
3
+ // ========================================
4
+ const firebaseConfig = {
5
+ apiKey: "AIzaSyBIQ-4MVpOHCaCI9qoAjvJPfSDdGaK-hEg",
6
+ authDomain: "m-txtile-lasted-web.firebaseapp.com",
7
+ projectId: "m-txtile-lasted-web",
8
+ storageBucket: "m-txtile-lasted-web.firebasestorage.app",
9
+ messagingSenderId: "164555967777",
10
+ appId: "1:164555967777:web:65ef2c62e3b332f1698260",
11
+ measurementId: "G-9EWB1LJ3CZ"
12
+ };
13
+
14
+ // Initialize Firebase
15
+ firebase.initializeApp(firebaseConfig);
16
+ const auth = firebase.auth();
17
+ const googleProvider = new firebase.auth.GoogleAuthProvider();
18
+
19
+ // Google Sign In
20
+ async function signInWithGoogle() {
21
+ try {
22
+ const result = await auth.signInWithPopup(googleProvider);
23
+ const user = result.user;
24
+
25
+ // Save to MongoDB via API
26
+ const authApiUrl = (window.location.hostname === '127.0.0.1' || window.location.port === '5500')
27
+ ? 'http://localhost:3000/api/auth/google-login'
28
+ : '/api/auth/google-login';
29
+
30
+ const res = await fetch(authApiUrl, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({
34
+ uid: user.uid,
35
+ name: user.displayName || 'Foydalanuvchi',
36
+ email: user.email,
37
+ phone: user.phoneNumber || '',
38
+ photoURL: user.photoURL || ''
39
+ })
40
+ });
41
+ const data = await res.json();
42
+
43
+ if (data.success) {
44
+ // Save session locally
45
+ localStorage.setItem('mtextile_session', JSON.stringify(data.user));
46
+ document.dispatchEvent(new CustomEvent('authChanged', { detail: { user: data.user } }));
47
+ showToast("Muvaffaqiyat", `Xush kelibsiz, ${data.user.name}!`, "success");
48
+
49
+ // Close login modal
50
+ const overlay = document.getElementById('loginOverlay');
51
+ if (overlay) overlay.classList.remove('active');
52
+
53
+ // Update UI
54
+ if (typeof updateAuthUI === 'function') updateAuthUI();
55
+ return data.user;
56
+ } else {
57
+ showToast("Xatolik", data.message || "Kirishda xatolik", "error");
58
+ return null;
59
+ }
60
+ } catch (error) {
61
+ if (error.code === 'auth/popup-closed-by-user') {
62
+ return null; // User closed popup, no error needed
63
+ }
64
+ console.error('Google sign-in error:', error);
65
+ showToast("Xatolik", error.message || "Google orqali kirishda xatolik yuz berdi", "error");
66
+ return null;
67
+ }
68
+ }
69
+
70
+ // Sign Out from Firebase
71
+ async function firebaseSignOut() {
72
+ try {
73
+ await auth.signOut();
74
+ } catch (e) {
75
+ console.warn('Firebase sign out error:', e);
76
+ }
77
+ }
js/product-detail.js ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // PRODUCT DETAIL PAGE LOGIC
3
+ // ========================================
4
+
5
+ let selectedSize = null, selectedColor = null, quantity = 1, currentProduct = null;
6
+
7
+ document.addEventListener('DOMContentLoaded', () => {
8
+ const id = new URLSearchParams(window.location.search).get('id');
9
+ currentProduct = getProductById(id);
10
+ if (!currentProduct) { window.location.href = 'catalog.html'; return; }
11
+
12
+ document.title = `${currentProduct.name} — M-TEXTILE`;
13
+ renderProduct();
14
+ setupActions();
15
+ renderSimilar();
16
+ setupTabs();
17
+ renderReviews();
18
+ });
19
+
20
+ function renderProduct() {
21
+ const p = currentProduct;
22
+ document.getElementById('mainImage').src = p.images[0];
23
+ document.getElementById('mainImage').alt = p.name;
24
+ document.getElementById('breadcrumbCategory').textContent = p.name;
25
+ document.getElementById('pdCategory').textContent = (CATEGORIES[p.category]?.name || p.category) + ' / ' + p.subcategory;
26
+ document.getElementById('pdName').textContent = p.name;
27
+ document.getElementById('pdStars').innerHTML = getStarsHTML(p.rating);
28
+ document.getElementById('pdRating').textContent = `${p.rating} (${p.reviewCount} ta sharh)`;
29
+
30
+ // Price
31
+ let priceHTML = `<span class="price-current">${formatPrice(p.price)}</span>`;
32
+ if (p.oldPrice) priceHTML += `<span class="price-old">${formatPrice(p.oldPrice)}</span><span class="discount-badge">-${p.discount}%</span>`;
33
+ document.getElementById('pdPrice').innerHTML = priceHTML;
34
+
35
+ document.getElementById('pdDesc').textContent = p.description;
36
+
37
+ // Sizes
38
+ const sizesContainer = document.getElementById('pdSizes');
39
+ p.sizes.forEach((s, i) => {
40
+ const btn = document.createElement('button');
41
+ btn.className = 'size-btn' + (i === 0 ? ' active' : '');
42
+ btn.textContent = s;
43
+ if (i === 0) { selectedSize = s; document.getElementById('selectedSize').textContent = s; }
44
+ btn.addEventListener('click', () => {
45
+ sizesContainer.querySelectorAll('.size-btn').forEach(b => b.classList.remove('active'));
46
+ btn.classList.add('active');
47
+ selectedSize = s;
48
+ document.getElementById('selectedSize').textContent = s;
49
+ });
50
+ sizesContainer.appendChild(btn);
51
+ });
52
+
53
+ // Colors
54
+ const colorsContainer = document.getElementById('pdColors');
55
+ p.colors.forEach((c, i) => {
56
+ const color = COLORS_MAP[c];
57
+ if (!color) return;
58
+ const btn = document.createElement('button');
59
+ btn.className = 'color-btn' + (i === 0 ? ' active' : '');
60
+ btn.style.background = color.hex;
61
+ btn.title = color.name;
62
+ if (i === 0) { selectedColor = c; document.getElementById('selectedColor').textContent = color.name; }
63
+ btn.addEventListener('click', () => {
64
+ colorsContainer.querySelectorAll('.color-btn').forEach(b => b.classList.remove('active'));
65
+ btn.classList.add('active');
66
+ selectedColor = c;
67
+ document.getElementById('selectedColor').textContent = color.name;
68
+ });
69
+ colorsContainer.appendChild(btn);
70
+ });
71
+
72
+ // Tab description
73
+ document.getElementById('tab-desc').innerHTML = `<p style="line-height:1.8;color:var(--clr-text-secondary)">${p.description}</p>`;
74
+
75
+ // Specs
76
+ document.getElementById('specCat').textContent = `${CATEGORIES[p.category]?.name} / ${p.subcategory}`;
77
+ document.getElementById('specSizes').textContent = p.sizes.join(', ');
78
+ document.getElementById('specColors').textContent = p.colors.map(c => COLORS_MAP[c]?.name || c).join(', ');
79
+ document.getElementById('specStock').innerHTML = p.inStock ? '<span style="color:var(--clr-success)">✓ Mavjud</span>' : '<span style="color:var(--clr-error)">✗ Tugagan</span>';
80
+
81
+ // Wishlist button
82
+ updateWishlistBtn();
83
+ }
84
+
85
+ function setupActions() {
86
+ // Quantity
87
+ document.getElementById('qtyMinus').addEventListener('click', () => {
88
+ if (quantity > 1) { quantity--; document.getElementById('qtyValue').textContent = quantity; }
89
+ });
90
+ document.getElementById('qtyPlus').addEventListener('click', () => {
91
+ if (quantity < 10) { quantity++; document.getElementById('qtyValue').textContent = quantity; }
92
+ });
93
+
94
+ // Add to cart
95
+ document.getElementById('addToCartBtn').addEventListener('click', () => {
96
+ if (!selectedSize || !selectedColor) {
97
+ showToast('Xatolik', 'O\'lcham va rangni tanlang', 'error');
98
+ return;
99
+ }
100
+ addToCart(currentProduct.id, selectedSize, selectedColor, quantity);
101
+ updateCartBadge();
102
+ showToast('Savatga qo\'shildi!', `${currentProduct.name} (${selectedSize}, ${COLORS_MAP[selectedColor]?.name})`, 'success');
103
+ });
104
+
105
+ // Wishlist
106
+ document.getElementById('wishlistBtn').addEventListener('click', () => {
107
+ toggleWishlist(currentProduct.id);
108
+ updateWishlistBtn();
109
+ const wishlisted = isInWishlist(currentProduct.id);
110
+ showToast(wishlisted ? 'Sevimlilarga qo\'shildi' : 'Sevimlilardan olib tashlandi', '', wishlisted ? 'success' : 'info');
111
+ });
112
+ }
113
+
114
+ function updateWishlistBtn() {
115
+ const btn = document.getElementById('wishlistBtn');
116
+ const wishlisted = isInWishlist(currentProduct.id);
117
+ btn.innerHTML = wishlisted ? ICONS.heartFill : ICONS.heart;
118
+ btn.classList.toggle('wishlisted', wishlisted);
119
+ if (wishlisted) btn.style.color = 'var(--clr-error)';
120
+ else btn.style.color = '';
121
+ }
122
+
123
+ function setupTabs() {
124
+ document.querySelectorAll('.tab-btn').forEach(btn => {
125
+ btn.addEventListener('click', () => {
126
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
127
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
128
+ btn.classList.add('active');
129
+ document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
130
+ });
131
+ });
132
+ }
133
+
134
+ function renderSimilar() {
135
+ const grid = document.getElementById('similarGrid');
136
+ const similar = PRODUCTS.filter(p => p.category === currentProduct.category && p.id !== currentProduct.id).slice(0, 4);
137
+ similar.forEach(p => grid.appendChild(renderProductCard(p)));
138
+ }
139
+
140
+ // ========================================
141
+ // REVIEW SYSTEM
142
+ // ========================================
143
+ function getReviews(productId) {
144
+ return JSON.parse(localStorage.getItem(`mtextile_reviews_${productId}`) || '[]');
145
+ }
146
+
147
+ function saveReview(productId, review) {
148
+ const reviews = getReviews(productId);
149
+ reviews.unshift(review);
150
+ localStorage.setItem(`mtextile_reviews_${productId}`, JSON.stringify(reviews));
151
+ }
152
+
153
+ function renderReviews() {
154
+ const container = document.getElementById('tab-reviews');
155
+ const reviews = getReviews(currentProduct.id);
156
+
157
+ let html = `
158
+ <div style="margin-bottom:2rem;padding:1.5rem;background:var(--clr-surface);border:1px solid var(--clr-border);border-radius:var(--radius-lg);">
159
+ <h4 style="margin-bottom:1rem;">Sharh qoldirish</h4>
160
+ <div style="margin-bottom:0.75rem;">
161
+ <div class="review-stars" id="reviewStars">
162
+ <span class="review-star" data-star="1">★</span>
163
+ <span class="review-star" data-star="2">★</span>
164
+ <span class="review-star" data-star="3">★</span>
165
+ <span class="review-star" data-star="4">★</span>
166
+ <span class="review-star" data-star="5">★</span>
167
+ </div>
168
+ </div>
169
+ <div style="margin-bottom:0.75rem;">
170
+ <input type="text" class="form-input" id="reviewName" placeholder="Ismingiz" style="margin-bottom:0.5rem;">
171
+ <textarea class="form-input" id="reviewText" placeholder="Sharhingizni yozing..." rows="3" style="resize:vertical;"></textarea>
172
+ </div>
173
+ <button class="btn btn-primary btn-sm" onclick="submitReview()">Yuborish</button>
174
+ </div>`;
175
+
176
+ if (reviews.length > 0) {
177
+ html += `<div style="margin-bottom:1rem;color:var(--clr-text-muted);font-size:0.9rem;">${reviews.length} ta sharh</div>`;
178
+ reviews.forEach(r => {
179
+ const date = new Date(r.date).toLocaleDateString('uz-UZ');
180
+ html += `
181
+ <div style="padding:1rem 0;border-bottom:1px solid var(--clr-border);">
182
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem;">
183
+ <div>
184
+ <strong>${r.name || 'Anonim'}</strong>
185
+ <span style="color:var(--clr-text-muted);font-size:0.8rem;margin-left:0.5rem;">${date}</span>
186
+ </div>
187
+ <div style="color:var(--clr-warning);">${'<span style="color:var(--clr-warning)">★</span>'.repeat(r.rating)}${'<span style="color:var(--clr-gray)">★</span>'.repeat(5 - r.rating)}</div>
188
+ </div>
189
+ <p style="color:var(--clr-text-secondary);line-height:1.6;">${r.text}</p>
190
+ </div>`;
191
+ });
192
+ } else {
193
+ html += '<p style="color:var(--clr-text-muted);margin-top:1rem;">Hozircha sharhlar yo\'q.</p>';
194
+ }
195
+
196
+ container.innerHTML = html;
197
+
198
+ // Star selection
199
+ let selectedRating = 0;
200
+ container.querySelectorAll('.review-star').forEach(star => {
201
+ star.addEventListener('mouseenter', () => {
202
+ const val = parseInt(star.dataset.star);
203
+ container.querySelectorAll('.review-star').forEach((s, i) => {
204
+ s.classList.toggle('active', i < val);
205
+ });
206
+ });
207
+ star.addEventListener('click', () => {
208
+ selectedRating = parseInt(star.dataset.star);
209
+ container.querySelectorAll('.review-star').forEach((s, i) => {
210
+ s.classList.toggle('active', i < selectedRating);
211
+ });
212
+ });
213
+ });
214
+ container.querySelector('.review-stars')?.addEventListener('mouseleave', () => {
215
+ container.querySelectorAll('.review-star').forEach((s, i) => {
216
+ s.classList.toggle('active', i < selectedRating);
217
+ });
218
+ });
219
+
220
+ window._reviewRating = () => selectedRating;
221
+ }
222
+
223
+ function submitReview() {
224
+ const rating = window._reviewRating ? window._reviewRating() : 0;
225
+ const name = document.getElementById('reviewName')?.value.trim();
226
+ const text = document.getElementById('reviewText')?.value.trim();
227
+
228
+ if (rating === 0) { showToast('Xatolik', 'Iltimos, reyting tanlang', 'error'); return; }
229
+ if (!text) { showToast('Xatolik', 'Sharh matnini kiriting', 'error'); return; }
230
+
231
+ saveReview(currentProduct.id, {
232
+ name: name || 'Anonim',
233
+ rating, text,
234
+ date: new Date().toISOString()
235
+ });
236
+
237
+ showToast('Rahmat!', 'Sharhingiz qo\'shildi', 'success');
238
+ renderReviews();
239
+ }
js/products.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // PRODUCTS DATABASE — Mock Data
3
+ // ========================================
4
+ const CATEGORIES = {
5
+ kiyimlar: { name: "Kiyimlar", icon: "👔", slug: "kiyimlar" },
6
+ formalar: { name: "Formalar", icon: "🎓", slug: "formalar" },
7
+ shimlar: { name: "Shimlar", icon: "👖", slug: "shimlar" },
8
+ galistuklar: { name: "Galistuklar", icon: "🎀", slug: "galistuklar" },
9
+ aksessuarlar: { name: "Aksessuarlar", icon: "👜", slug: "aksessuarlar" },
10
+ };
11
+ const SIZES = ["XS", "S", "M", "L", "XL", "XXL"];
12
+ const COLORS_MAP = {
13
+ qora: { name: "Qora", hex: "#1a1a2e" }, oq: { name: "Oq", hex: "#f0f0f0" },
14
+ ko_k: { name: "Ko'k", hex: "#2563eb" }, qizil: { name: "Qizil", hex: "#dc2626" },
15
+ yashil: { name: "Yashil", hex: "#16a34a" }, kulrang: { name: "Kulrang", hex: "#6b7280" },
16
+ jigarrang: { name: "Jigarrang", hex: "#92400e" }, pushti: { name: "Pushti", hex: "#ec4899" },
17
+ sariq: { name: "Sariq", hex: "#eab308" }, havorang: { name: "Havorang", hex: "#0891b2" },
18
+ };
19
+
20
+ function _p(id, name, cat, sub, price, oldPrice, disc, sizes, colors, img, rat, rev, desc, stock, isNew, feat) {
21
+ return {
22
+ id, name, category: cat, subcategory: sub, price, oldPrice, discount: disc, sizes, colors,
23
+ images: [img], rating: rat, reviewCount: rev, description: desc, inStock: stock, isNew, isFeatured: feat
24
+ };
25
+ }
26
+ const S = ["S", "M", "L", "XL"], SM = ["XS", "S", "M", "L", "XL"], ML = ["M", "L", "XL", "XXL"], SX = ["S", "M", "L", "XL", "XXL"], OS = ["One Size"];
27
+
28
+ const DEFAULT_PRODUCTS = [
29
+ // KIYIMLAR
30
+ _p(1, "Klassik Biznes Ko'ylak", "kiyimlar", "Ko'ylaklar", 289000, 350000, 17, S, ["oq", "ko_k", "kulrang"], "https://images.unsplash.com/photo-1602810318383-e386cc2a3ccf?w=600&h=800&fit=crop", 4.8, 124, "Yuqori sifatli paxta matosidan tayyorlangan klassik biznes ko'ylak.", true, false, true),
31
+ _p(2, "Premium Slim Fit Ko'ylak", "kiyimlar", "Ko'ylaklar", 345000, null, 0, SX, ["qora", "ko_k", "oq"], "https://images.unsplash.com/photo-1596755094514-f87e34085b2c?w=600&h=800&fit=crop", 4.6, 89, "Zamonaviy slim fit ko'ylak. Tanaga yopishib turadigan elegant fason.", true, true, true),
32
+ _p(3, "Qishki Issiq Kurtka", "kiyimlar", "Kurtkalar", 890000, 1100000, 19, ML, ["qora", "kulrang", "havorang"], "https://images.unsplash.com/photo-1544923246-77307dd270b5?w=600&h=800&fit=crop", 4.9, 256, "Sovuqdan himoya qiluvchi premium kurtka. Suv o'tkazmaydigan mato.", true, false, true),
33
+ _p(4, "Sport Futbolka Pro", "kiyimlar", "Futbolkalar", 149000, 189000, 21, SM, ["qora", "oq", "ko_k", "qizil"], "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=600&h=800&fit=crop", 4.5, 312, "Yuqori sifatli sport futbolka. Nafas oladigan mato.", true, true, false),
34
+ _p(5, "Elegant Blazer", "kiyimlar", "Blazerlar", 650000, null, 0, S, ["qora", "kulrang", "jigarrang"], "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&h=800&fit=crop", 4.7, 78, "Premium materialdan tayyorlangan elegant blazer.", true, false, true),
35
+ _p(6, "Casual Polo Ko'ylak", "kiyimlar", "Ko'ylaklar", 195000, 240000, 19, S, ["oq", "ko_k", "yashil", "qizil"], "https://images.unsplash.com/photo-1625910513413-5fc68e7990a7?w=600&h=800&fit=crop", 4.4, 167, "Kundalik kiyish uchun qulay polo ko'ylak.", true, false, false),
36
+ _p(7, "Denim Kurtka Classic", "kiyimlar", "Kurtkalar", 420000, null, 0, S, ["ko_k", "qora"], "https://images.unsplash.com/photo-1576995853123-5a10305d93c0?w=600&h=800&fit=crop", 4.6, 93, "Klassik denim kurtka. Barcha fasllar uchun mos.", true, true, false),
37
+ _p(8, "Premium Hoodie", "kiyimlar", "Futbolkalar", 275000, 320000, 14, SX, ["qora", "kulrang", "havorang"], "https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=600&h=800&fit=crop", 4.7, 201, "Yumshoq fleece ichlikli premium hoodie.", true, false, true),
38
+ // FORMALAR
39
+ _p(9, "Maktab Formalari To'plami", "formalar", "Maktab", 450000, 550000, 18, SM, ["qora", "ko_k"], "https://images.unsplash.com/photo-1604671801908-6f0c6a092c05?w=600&h=800&fit=crop", 4.5, 345, "To'liq maktab formalari to'plami: ko'ylak, shim va jilet.", true, false, true),
40
+ _p(10, "Ofis Biznes Kostyum", "formalar", "Ish formalari", 1250000, 1500000, 17, S, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1594938298603-c8148c4dae35?w=600&h=800&fit=crop", 4.9, 156, "Premium biznes kostyum. Italiya matosidan tayyorlangan.", true, true, true),
41
+ _p(11, "Tibbiyot Formasi", "formalar", "Ish formalari", 320000, null, 0, SX, ["oq", "ko_k", "yashil"], "https://images.unsplash.com/photo-1559839734-2b71ea197ec2?w=600&h=800&fit=crop", 4.3, 89, "Professional tibbiyot formasi.", true, false, false),
42
+ _p(12, "Oshpaz Formasi Premium", "formalar", "Ish formalari", 380000, 450000, 16, S, ["oq", "qora"], "https://images.unsplash.com/photo-1577219491135-ce391730fb2c?w=600&h=800&fit=crop", 4.6, 67, "Professional oshpaz formasi. Issiqqa chidamli.", true, false, false),
43
+ _p(13, "Sport Forma - Futbol", "formalar", "Sport", 270000, null, 0, SM, ["ko_k", "qizil", "yashil", "oq"], "https://images.unsplash.com/photo-1574629810360-7efbbe195018?w=600&h=800&fit=crop", 4.4, 234, "Professional futbol formasi. Nafas oladigan mato.", true, true, false),
44
+ _p(14, "Harbiy Uniforma", "formalar", "Ish formalari", 580000, null, 0, SX, ["yashil", "kulrang"], "https://images.unsplash.com/photo-1579912861630-f7c8ad0c7804?w=600&h=800&fit=crop", 4.7, 45, "Yuqori sifatli uniforma. Chidamli mato.", true, false, false),
45
+ _p(15, "Qizlar Maktab Formalari", "formalar", "Maktab", 420000, 490000, 14, ["XS", "S", "M", "L"], ["qora", "ko_k"], "https://images.unsplash.com/photo-1594938298603-c8148c4dae35?w=600&h=800&fit=crop", 4.5, 278, "Qizlar uchun zamonaviy maktab formalari.", true, false, true),
46
+ _p(16, "Mexanik Ish Kiyimi", "formalar", "Ish formalari", 340000, null, 0, ML, ["ko_k", "kulrang"], "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=600&h=800&fit=crop", 4.2, 56, "Mexaniklar uchun maxsus ish kiyimi.", true, false, false),
47
+ // SHIMLAR
48
+ _p(17, "Klassik Biznes Shim", "shimlar", "Klassik", 320000, 380000, 16, S, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1624378439575-d8705ad7ae80?w=600&h=800&fit=crop", 4.6, 189, "Premium klassik shim. Ofis uchun ideal.", true, false, true),
49
+ _p(18, "Slim Jinsi Shim", "shimlar", "Jinsi", 275000, null, 0, S, ["ko_k", "qora", "kulrang"], "https://images.unsplash.com/photo-1542272604-787c3835535d?w=600&h=800&fit=crop", 4.7, 412, "Zamonaviy slim fit jinsi shim.", true, true, true),
50
+ _p(19, "Sport Shim Jogger", "shimlar", "Sport", 185000, 220000, 16, SX, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?w=600&h=800&fit=crop", 4.5, 298, "Premium sport jogger shim. Fleece ichlik.", true, false, false),
51
+ _p(20, "Chino Shim Classic", "shimlar", "Klassik", 245000, null, 0, S, ["jigarrang", "kulrang", "yashil", "ko_k"], "https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=600&h=800&fit=crop", 4.4, 145, "Chino uslubidagi klassik shim.", true, false, false),
52
+ _p(21, "Straight Fit Jinsi", "shimlar", "Jinsi", 295000, 350000, 16, ML, ["ko_k", "qora"], "https://images.unsplash.com/photo-1604176354204-9268737828e4?w=600&h=800&fit=crop", 4.5, 187, "Klassik straight fit jinsi.", true, false, false),
53
+ _p(22, "Cargo Shim Tactical", "shimlar", "Sport", 310000, null, 0, S, ["yashil", "qora", "kulrang"], "https://images.unsplash.com/photo-1517438476312-10d79c077509?w=600&h=800&fit=crop", 4.3, 98, "Tactical cargo shim. Ko'p cho'ntakli.", true, true, false),
54
+ _p(23, "Linen Yoz Shim", "shimlar", "Klassik", 230000, 280000, 18, S, ["oq", "kulrang", "havorang"], "https://images.unsplash.com/photo-1506629082955-511b1aa562c8?w=600&h=800&fit=crop", 4.6, 76, "Yozgi linen shim. Nafas oladigan tabiiy mato.", true, false, true),
55
+ _p(24, "Qisqa Sport Shorts", "shimlar", "Sport", 125000, null, 0, S, ["qora", "ko_k", "kulrang"], "https://images.unsplash.com/photo-1591195853828-11db59a44f6b?w=600&h=800&fit=crop", 4.3, 234, "Qisqa sport shorts. Yengil, tez quriydi.", true, false, false),
56
+ // GALISTUKLAR
57
+ _p(25, "Ipak Klassik Galistuk", "galistuklar", "Klassik", 185000, 220000, 16, OS, ["qora", "ko_k", "qizil"], "https://images.unsplash.com/photo-1589756823695-278bc923a423?w=600&h=800&fit=crop", 4.8, 167, "100% tabiiy ipakdan tayyorlangan klassik galistuk.", true, false, true),
58
+ _p(26, "Slim Zamonaviy Galistuk", "galistuklar", "Slim", 145000, null, 0, OS, ["qora", "kulrang", "ko_k", "qizil"], "https://images.unsplash.com/photo-1598879400638-bb93c904ef41?w=600&h=800&fit=crop", 4.5, 234, "Zamonaviy slim galistuk. Yosh va trendy ko'rinish uchun.", true, true, true),
59
+ _p(27, "Bowtie Premium", "galistuklar", "Bowtie", 125000, 160000, 22, OS, ["qora", "qizil", "ko_k", "yashil"], "https://images.unsplash.com/photo-1580657018950-c7f7d6a6d990?w=600&h=800&fit=crop", 4.7, 89, "Premium bowtie. To'y va maxsus tadbirlar uchun.", true, false, false),
60
+ _p(28, "Naqshli Galistuk", "galistuklar", "Klassik", 165000, null, 0, OS, ["ko_k", "qizil"], "https://images.unsplash.com/photo-1590548784585-643d2b9f2925?w=600&h=800&fit=crop", 4.4, 112, "Chiroyli naqshli galistuk.", true, false, false),
61
+ _p(29, "Kashemir Galistuk", "galistuklar", "Klassik", 250000, 300000, 17, OS, ["jigarrang", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1598879400638-bb93c904ef41?w=600&h=800&fit=crop", 4.9, 56, "Premium kashemir galistuk. Yumshoq, tabiiy.", true, true, true),
62
+ _p(30, "Mikrofiber Galistuk", "galistuklar", "Slim", 95000, null, 0, OS, ["qora", "ko_k", "kulrang"], "https://images.unsplash.com/photo-1589756823695-278bc923a423?w=600&h=800&fit=crop", 4.2, 78, "Byudjetga mos mikrofiber galistuk.", true, false, false),
63
+ _p(31, "To'y Galistuk To'plami", "galistuklar", "Klassik", 320000, 400000, 20, OS, ["oq", "qizil", "ko_k"], "https://images.unsplash.com/photo-1580657018950-c7f7d6a6d990?w=600&h=800&fit=crop", 4.8, 134, "To'y uchun galistuk to'plami.", true, false, true),
64
+ _p(32, "Kids Bowtie", "galistuklar", "Bowtie", 65000, 85000, 24, OS, ["ko_k", "qizil", "qora"], "https://images.unsplash.com/photo-1580657018950-c7f7d6a6d990?w=600&h=800&fit=crop", 4.6, 45, "Bolalar uchun bowtie.", true, false, false),
65
+ // AKSESSUARLAR
66
+ _p(33, "Charm Kamar Premium", "aksessuarlar", "Kamarlar", 195000, 240000, 19, S, ["qora", "jigarrang"], "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&h=800&fit=crop", 4.7, 201, "Tabiiy charm kamar. Italiya ishlov berish texnologiyasi.", true, false, true),
67
+ _p(34, "Biznes Portfel", "aksessuarlar", "Sumkalar", 520000, null, 0, OS, ["qora", "jigarrang"], "https://images.unsplash.com/photo-1548036328-c9fa89d128fa?w=600&h=800&fit=crop", 4.8, 134, "Premium biznes portfel. Noutbuk bo'limi.", true, true, true),
68
+ _p(35, "Klassik Shlyapa", "aksessuarlar", "Shlyapalar", 145000, 180000, 19, ["S", "M", "L"], ["qora", "kulrang", "jigarrang"], "https://images.unsplash.com/photo-1514327605112-b887c0e61c0a?w=600&h=800&fit=crop", 4.4, 67, "Klassik fedora shlyapa.", true, false, false),
69
+ _p(36, "Ipak Cho'ntak Ro'mol", "aksessuarlar", "Aksessuarlar", 85000, null, 0, OS, ["oq", "ko_k", "qizil", "pushti"], "https://images.unsplash.com/photo-1598879400638-bb93c904ef41?w=600&h=800&fit=crop", 4.5, 89, "100% ipak cho'ntak ro'mol.", true, false, false),
70
+ _p(37, "Manjet Tugmalari Gold", "aksessuarlar", "Aksessuarlar", 175000, 210000, 17, OS, ["sariq"], "https://images.unsplash.com/photo-1590548784585-643d2b9f2925?w=600&h=800&fit=crop", 4.6, 56, "Oltin rangli manjet tugmalari.", true, false, false),
71
+ _p(38, "Qo'lqop Charm", "aksessuarlar", "Aksessuarlar", 230000, null, 0, ["S", "M", "L"], ["qora", "jigarrang"], "https://images.unsplash.com/photo-1531163051823-59adbb4b5e8e?w=600&h=800&fit=crop", 4.8, 45, "Premium charm qo'lqop. Sensorli ekran bilan ishlaydi.", true, true, false),
72
+ _p(39, "Pul Qisqich Carbon", "aksessuarlar", "Aksessuarlar", 155000, 190000, 18, OS, ["qora"], "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&h=800&fit=crop", 4.5, 123, "Carbon fiber pul qisqich.", true, false, true),
73
+ _p(40, "Sport Sumka Duffle", "aksessuarlar", "Sumkalar", 345000, null, 0, OS, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&h=800&fit=crop", 4.4, 167, "Keng sport duffle sumka. Suv o'tkazmaydigan.", true, false, false),
74
+ ];
75
+
76
+ let PRODUCTS = JSON.parse(localStorage.getItem('mtextile_products'));
77
+ if (!PRODUCTS) {
78
+ PRODUCTS = DEFAULT_PRODUCTS;
79
+ localStorage.setItem('mtextile_products', JSON.stringify(PRODUCTS));
80
+ }
81
+
82
+ // UTILITY FUNCTIONS
83
+ function formatPrice(p) { return new Intl.NumberFormat("uz-UZ").format(p) + " so'm" }
84
+ function getProductById(id) { return PRODUCTS.find(p => p.id === parseInt(id)) }
85
+
86
+ function deductProductStock(id, qty = 1) {
87
+ const p = getProductById(id);
88
+ if (p) {
89
+ if (typeof p.inStock === 'number') {
90
+ p.inStock = Math.max(0, p.inStock - qty);
91
+ }
92
+ // Boolean inStock stays true — no real count tracking
93
+ localStorage.setItem('mtextile_products', JSON.stringify(PRODUCTS));
94
+ }
95
+ }
96
+
97
+ function getProductsByCategory(c) { return PRODUCTS.filter(p => p.category === c) }
98
+ function getFeaturedProducts() { return PRODUCTS.filter(p => p.isFeatured) }
99
+ function getNewProducts() { return PRODUCTS.filter(p => p.isNew) }
100
+ function getDiscountedProducts() { return PRODUCTS.filter(p => p.discount > 0) }
101
+ function searchProducts(q) { q = q.toLowerCase().trim(); if (!q) return PRODUCTS; return PRODUCTS.filter(p => p.name.toLowerCase().includes(q) || p.description.toLowerCase().includes(q) || p.category.toLowerCase().includes(q) || p.subcategory.toLowerCase().includes(q)) }
102
+ function filterProducts(products, f) { let r = [...products]; if (f.category) r = r.filter(p => p.category === f.category); if (f.subcategory) r = r.filter(p => p.subcategory === f.subcategory); if (f.minPrice !== undefined) r = r.filter(p => p.price >= f.minPrice); if (f.maxPrice !== undefined) r = r.filter(p => p.price <= f.maxPrice); if (f.sizes && f.sizes.length) r = r.filter(p => p.sizes.some(s => f.sizes.includes(s))); if (f.colors && f.colors.length) r = r.filter(p => p.colors.some(c => f.colors.includes(c))); if (f.hasDiscount) r = r.filter(p => p.discount > 0); if (f.inStock) r = r.filter(p => p.inStock); return r }
103
+ function sortProducts(products, s) { const r = [...products]; switch (s) { case "price-asc": return r.sort((a, b) => a.price - b.price); case "price-desc": return r.sort((a, b) => b.price - a.price); case "rating": return r.sort((a, b) => b.rating - a.rating); case "newest": return r.sort((a, b) => (b.isNew ? 1 : 0) - (a.isNew ? 1 : 0)); case "name-asc": return r.sort((a, b) => a.name.localeCompare(b.name)); case "discount": return r.sort((a, b) => b.discount - a.discount); default: return r } }
js/profile.js ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // PROFILE PAGE LOGIC
3
+ // ========================================
4
+
5
+ document.addEventListener('DOMContentLoaded', () => {
6
+ // Auth Check
7
+ if (!isLoggedIn()) {
8
+ window.location.href = 'index.html';
9
+ return;
10
+ }
11
+
12
+ loadProfile();
13
+ loadOrders();
14
+ setupNavigation();
15
+ setupSaveProfile();
16
+ setupAddresses();
17
+ setupLogout();
18
+ });
19
+
20
+ function loadProfile() {
21
+ const user = getUser();
22
+ document.getElementById('pName').value = user.name || '';
23
+ document.getElementById('pPhone').value = user.phone || '';
24
+ document.getElementById('pEmail').value = user.email || '';
25
+ document.getElementById('profileName').textContent = user.name || 'Foydalanuvchi';
26
+ document.getElementById('profileEmail').textContent = user.email || user.phone || '—';
27
+ document.getElementById('profileAvatar').textContent = (user.name || 'U')[0].toUpperCase();
28
+ }
29
+
30
+ function setupSaveProfile() {
31
+ document.getElementById('saveProfileBtn').addEventListener('click', () => {
32
+ const name = document.getElementById('pName').value.trim();
33
+ const phone = document.getElementById('pPhone').value.trim();
34
+ const email = document.getElementById('pEmail').value.trim();
35
+ saveUser({ name, phone, email });
36
+ loadProfile();
37
+ showToast('Saqlandi', 'Ma\'lumotlaringiz yangilandi', 'success');
38
+ });
39
+ }
40
+
41
+ function setupNavigation() {
42
+ document.querySelectorAll('.profile-nav a').forEach(link => {
43
+ link.addEventListener('click', (e) => {
44
+ if (link.id === 'logoutBtn') return; // Handled separately
45
+ e.preventDefault();
46
+ document.querySelectorAll('.profile-nav a').forEach(l => l.classList.remove('active'));
47
+ link.classList.add('active');
48
+ const section = link.dataset.section;
49
+ document.querySelectorAll('[id^="section-"]').forEach(s => s.style.display = 'none');
50
+ document.getElementById('section-' + section).style.display = 'block';
51
+ });
52
+ });
53
+ }
54
+
55
+ function setupLogout() {
56
+ const btn = document.getElementById('logoutBtn');
57
+ if (btn) {
58
+ btn.addEventListener('click', (e) => {
59
+ e.preventDefault();
60
+ if (confirm('Tizimdan chiqmoqchimisiz?')) {
61
+ logoutUser();
62
+ window.location.href = 'index.html';
63
+ }
64
+ });
65
+ }
66
+ }
67
+
68
+ function loadOrders() {
69
+ const orders = getUserOrders();
70
+ const container = document.getElementById('ordersList');
71
+ const empty = document.getElementById('emptyOrders');
72
+
73
+ if (orders.length === 0) { empty.style.display = 'block'; return; }
74
+ empty.style.display = 'none';
75
+
76
+ const statusLabels = { 'Kutilmoqda': 'Kutilmoqda', 'Olib ketildi': 'Olib ketildi', 'Bekor qilindi': 'Bekor qilindi' };
77
+
78
+ orders.forEach(order => {
79
+ const date = new Date(order.createdAt).toLocaleDateString('uz-UZ');
80
+ const card = document.createElement('div');
81
+ card.className = 'order-card';
82
+ card.innerHTML = `
83
+ <div class="order-header">
84
+ <div>
85
+ <div class="order-id">${order.id}</div>
86
+ <div class="order-date">${date}</div>
87
+ </div>
88
+ <span class="order-status ${order.status}">${statusLabels[order.status] || order.status}</span>
89
+ </div>
90
+ <div class="order-items">
91
+ ${(order.items || []).map(item => `
92
+ <div class="order-item-thumb">
93
+ <img src="${item.image || ''}" alt="${item.name || ''}">
94
+ </div>
95
+ `).join('')}
96
+ </div>
97
+ <div class="order-total">Jami: ${formatPrice(order.totals?.total || 0)}</div>
98
+ `;
99
+ container.appendChild(card);
100
+ });
101
+ }
102
+
103
+ function setupAddresses() {
104
+ const user = getUser();
105
+ const addresses = user.addresses || [];
106
+ renderAddresses(addresses);
107
+
108
+ document.getElementById('addAddressBtn').addEventListener('click', () => {
109
+ const address = prompt('Yangi manzilni kiriting:');
110
+ if (address && address.trim()) {
111
+ const u = getUser();
112
+ u.addresses = u.addresses || [];
113
+ u.addresses.push(address.trim());
114
+ saveUser(u);
115
+ renderAddresses(u.addresses);
116
+ showToast('Qo\'shildi', 'Yangi manzil saqlandi', 'success');
117
+ }
118
+ });
119
+ }
120
+
121
+ function renderAddresses(addresses) {
122
+ const container = document.getElementById('addressList');
123
+ container.innerHTML = '';
124
+ if (addresses.length === 0) {
125
+ container.innerHTML = '<p style="color:var(--clr-text-muted)">Saqlangan manzillar yo\'q</p>';
126
+ return;
127
+ }
128
+ addresses.forEach((addr, i) => {
129
+ const el = document.createElement('div');
130
+ el.style.cssText = 'padding:1rem;border:1px solid var(--clr-border);border-radius:var(--radius-md);margin-bottom:0.75rem;display:flex;justify-content:space-between;align-items:center;';
131
+ el.innerHTML = `
132
+ <div>
133
+ <div style="font-size:0.85rem;color:var(--clr-text-muted)">Manzil ${i + 1}</div>
134
+ <div>${addr}</div>
135
+ </div>
136
+ <button class="btn btn-ghost btn-sm" style="color:var(--clr-error)" data-idx="${i}">O'chirish</button>
137
+ `;
138
+ el.querySelector('button').addEventListener('click', () => {
139
+ const u = getUser();
140
+ u.addresses.splice(i, 1);
141
+ saveUser(u);
142
+ renderAddresses(u.addresses);
143
+ showToast('O\'chirildi', 'Manzil o\'chirildi', 'info');
144
+ });
145
+ container.appendChild(el);
146
+ });
147
+ }
js/store.js ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // STATE MANAGEMENT — LocalStorage Store
3
+ // ========================================
4
+ const STORE_KEYS = { CART: "mtextile_cart", WISHLIST: "mtextile_wishlist", USER: "mtextile_user", ORDERS: "mtextile_orders" };
5
+
6
+ function _get(key) { try { return JSON.parse(localStorage.getItem(key)) || null; } catch { return null; } }
7
+ function _set(key, val) { localStorage.setItem(key, JSON.stringify(val)); }
8
+
9
+ // === CART ===
10
+ function getCart() { return _get(STORE_KEYS.CART) || []; }
11
+ function saveCart(cart) { _set(STORE_KEYS.CART, cart); document.dispatchEvent(new CustomEvent("cartUpdated", { detail: { cart } })); }
12
+
13
+ function addToCart(productId, size, color, qty = 1) {
14
+ const cart = getCart();
15
+ const existing = cart.find(i => i.productId === productId && i.size === size && i.color === color);
16
+ if (existing) { existing.quantity += qty; }
17
+ else { cart.push({ productId, size, color, quantity: qty, addedAt: Date.now() }); }
18
+ saveCart(cart);
19
+ return cart;
20
+ }
21
+
22
+ function removeFromCart(productId, size, color) {
23
+ let cart = getCart();
24
+ cart = cart.filter(i => !(i.productId === productId && i.size === size && i.color === color));
25
+ saveCart(cart);
26
+ return cart;
27
+ }
28
+
29
+ function updateCartQty(productId, size, color, qty) {
30
+ const cart = getCart();
31
+ const item = cart.find(i => i.productId === productId && i.size === size && i.color === color);
32
+ if (item) {
33
+ if (qty <= 0) return removeFromCart(productId, size, color);
34
+ item.quantity = qty;
35
+ saveCart(cart);
36
+ }
37
+ return cart;
38
+ }
39
+
40
+ function clearCart() { saveCart([]); }
41
+
42
+ function getCartCount() { return getCart().reduce((sum, i) => sum + i.quantity, 0); }
43
+
44
+ function getCartTotal() {
45
+ const cart = getCart();
46
+ let subtotal = 0, discount = 0;
47
+ cart.forEach(item => {
48
+ const product = getProductById(item.productId);
49
+ if (product) {
50
+ subtotal += product.price * item.quantity;
51
+ if (product.oldPrice) discount += (product.oldPrice - product.price) * item.quantity;
52
+ }
53
+ });
54
+ return { subtotal, discount, shipping: 0, total: subtotal };
55
+ }
56
+
57
+ // === WISHLIST ===
58
+ function getWishlist() { return _get(STORE_KEYS.WISHLIST) || []; }
59
+ function saveWishlist(wl) { _set(STORE_KEYS.WISHLIST, wl); document.dispatchEvent(new CustomEvent("wishlistUpdated", { detail: { wishlist: wl } })); }
60
+
61
+ function toggleWishlist(productId) {
62
+ let wl = getWishlist();
63
+ const idx = wl.indexOf(productId);
64
+ if (idx > -1) { wl.splice(idx, 1); } else { wl.push(productId); }
65
+ saveWishlist(wl);
66
+ return wl;
67
+ }
68
+
69
+ function isInWishlist(productId) { return getWishlist().includes(productId); }
70
+
71
+ // === USER AUTHENTICATION ===
72
+ const USERS_KEY = "mtextile_users";
73
+ const SESSION_KEY = "mtextile_session";
74
+
75
+ function getUsers() { return _get(USERS_KEY) || []; }
76
+ function setUsers(users) { _set(USERS_KEY, users); }
77
+
78
+ function getCurrentUser() {
79
+ return _get(SESSION_KEY);
80
+ }
81
+
82
+ function isLoggedIn() {
83
+ return getCurrentUser() !== null;
84
+ }
85
+
86
+ function registerUser(name, phone, password) {
87
+ const users = getUsers();
88
+ if (users.find(u => u.phone === phone)) {
89
+ return { success: false, message: "Bu telefon raqam allaqachon ro'yxatdan o'tgan." };
90
+ }
91
+ const newUser = { id: Date.now(), name, phone, password, email: "", addresses: [] };
92
+ users.push(newUser);
93
+ setUsers(users);
94
+ _set(SESSION_KEY, newUser); // Auto login
95
+ return { success: true };
96
+ }
97
+
98
+ function loginUser(phone, password) {
99
+ const users = getUsers();
100
+ const user = users.find(u => (u.phone === phone || u.email === phone) && u.password === password);
101
+ if (!user) {
102
+ return { success: false, message: "Telefon raqam yoki parol noto'g'ri." };
103
+ }
104
+ _set(SESSION_KEY, user);
105
+ return { success: true };
106
+ }
107
+
108
+ function logoutUser() {
109
+ localStorage.removeItem(SESSION_KEY);
110
+ localStorage.removeItem('mtextile_auth');
111
+ }
112
+
113
+ function getUser() {
114
+ // For backwards compatibility and profile edits
115
+ return getCurrentUser() || { name: "", phone: "", email: "", addresses: [] };
116
+ }
117
+
118
+ function saveUser(data) {
119
+ const currentUser = getCurrentUser();
120
+ if (!currentUser) return; // Must be logged in to save profile
121
+
122
+ // Update session
123
+ const updated = { ...currentUser, ...data };
124
+ _set(SESSION_KEY, updated);
125
+
126
+ // Update DB
127
+ const users = getUsers();
128
+ const index = users.findIndex(u => u.id === currentUser.id);
129
+ if (index > -1) {
130
+ users[index] = updated;
131
+ setUsers(users);
132
+ }
133
+ }
134
+
135
+ // === ORDERS ===
136
+ function getOrders() { return _get(STORE_KEYS.ORDERS) || []; }
137
+
138
+ function getUserOrders() {
139
+ const allOrders = getOrders();
140
+ const currentUser = getCurrentUser();
141
+ if (!currentUser) return [];
142
+ return allOrders.filter(o => o.userId === currentUser.id);
143
+ }
144
+
145
+ function addOrder(orderData) {
146
+ const orders = getOrders();
147
+ const currentUser = getCurrentUser();
148
+
149
+ const order = {
150
+ id: "ORD-" + Date.now().toString(36).toUpperCase(),
151
+ userId: currentUser ? currentUser.id : 'guest',
152
+ ...orderData,
153
+ status: "Kutilmoqda",
154
+ createdAt: new Date().toISOString(),
155
+ items: getCart().map(item => {
156
+ const p = getProductById(item.productId);
157
+ return { ...item, name: p?.name, price: p?.price, image: p?.images?.[0] };
158
+ }),
159
+ totals: getCartTotal()
160
+ };
161
+ orders.unshift(order);
162
+ _set(STORE_KEYS.ORDERS, orders);
163
+ clearCart();
164
+ return order;
165
+ }
166
+
167
+ // === PROMO CODES ===
168
+ const PROMO_CODES = {
169
+ "YANGI10": { discount: 10, type: "percent", desc: "10% chegirma" },
170
+ "STYLE20": { discount: 20, type: "percent", desc: "20% chegirma" },
171
+ };
172
+
173
+ function applyPromo(code) {
174
+ const promo = PROMO_CODES[code.toUpperCase()];
175
+ if (!promo) return { valid: false, message: "Noto'g'ri promo-kod" };
176
+ return { valid: true, ...promo };
177
+ }
manifest.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "M-TEXTILE",
3
+ "short_name": "Style",
4
+ "description": "Eng sifatli kiyim-kechak onlayn do'koni",
5
+ "start_url": "/index.html",
6
+ "display": "standalone",
7
+ "background_color": "#0a192f",
8
+ "theme_color": "#0D1F23",
9
+ "icons": [
10
+ {
11
+ "src": "/icon.png",
12
+ "sizes": "192x192",
13
+ "type": "image/png"
14
+ },
15
+ {
16
+ "src": "/icon.png",
17
+ "sizes": "512x512",
18
+ "type": "image/png"
19
+ }
20
+ ]
21
+ }
package-lock.json ADDED
@@ -0,0 +1,1175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "m-textile",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "m-textile",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@huggingface/hub": "^2.10.4",
13
+ "bcryptjs": "^3.0.3",
14
+ "cors": "^2.8.6",
15
+ "dotenv": "^17.3.1",
16
+ "express": "^5.2.1",
17
+ "mongoose": "^9.2.3"
18
+ }
19
+ },
20
+ "node_modules/@huggingface/hub": {
21
+ "version": "2.10.4",
22
+ "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.10.4.tgz",
23
+ "integrity": "sha512-1/DYWmbnpXKpCgzJzz/WwbzPKMbbmhGsbFxaqRwE3v9KoBL+FYjG8nDDN1/JhDPetRae+smr52b/RQ/FWsFzMg==",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@huggingface/tasks": "^0.19.86"
27
+ },
28
+ "bin": {
29
+ "hfjs": "dist/cli.js"
30
+ },
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "optionalDependencies": {
35
+ "cli-progress": "^3.12.0"
36
+ }
37
+ },
38
+ "node_modules/@huggingface/tasks": {
39
+ "version": "0.19.88",
40
+ "resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.88.tgz",
41
+ "integrity": "sha512-9dMxcn9Bc7xAYtG3ZHxTrD1J9KskxR7oI3vQq+8PIm9z1NizoIIM5oOTbpxp845Lk8IXccQW2FuXyAKwgKPa7A==",
42
+ "license": "MIT"
43
+ },
44
+ "node_modules/@mongodb-js/saslprep": {
45
+ "version": "1.4.6",
46
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz",
47
+ "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==",
48
+ "license": "MIT",
49
+ "dependencies": {
50
+ "sparse-bitfield": "^3.0.3"
51
+ }
52
+ },
53
+ "node_modules/@types/webidl-conversions": {
54
+ "version": "7.0.3",
55
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
56
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
57
+ "license": "MIT"
58
+ },
59
+ "node_modules/@types/whatwg-url": {
60
+ "version": "13.0.0",
61
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
62
+ "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
63
+ "license": "MIT",
64
+ "dependencies": {
65
+ "@types/webidl-conversions": "*"
66
+ }
67
+ },
68
+ "node_modules/accepts": {
69
+ "version": "2.0.0",
70
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
71
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
72
+ "license": "MIT",
73
+ "dependencies": {
74
+ "mime-types": "^3.0.0",
75
+ "negotiator": "^1.0.0"
76
+ },
77
+ "engines": {
78
+ "node": ">= 0.6"
79
+ }
80
+ },
81
+ "node_modules/ansi-regex": {
82
+ "version": "5.0.1",
83
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
84
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
85
+ "license": "MIT",
86
+ "optional": true,
87
+ "engines": {
88
+ "node": ">=8"
89
+ }
90
+ },
91
+ "node_modules/bcryptjs": {
92
+ "version": "3.0.3",
93
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
94
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
95
+ "license": "BSD-3-Clause",
96
+ "bin": {
97
+ "bcrypt": "bin/bcrypt"
98
+ }
99
+ },
100
+ "node_modules/body-parser": {
101
+ "version": "2.2.2",
102
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
103
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
104
+ "license": "MIT",
105
+ "dependencies": {
106
+ "bytes": "^3.1.2",
107
+ "content-type": "^1.0.5",
108
+ "debug": "^4.4.3",
109
+ "http-errors": "^2.0.0",
110
+ "iconv-lite": "^0.7.0",
111
+ "on-finished": "^2.4.1",
112
+ "qs": "^6.14.1",
113
+ "raw-body": "^3.0.1",
114
+ "type-is": "^2.0.1"
115
+ },
116
+ "engines": {
117
+ "node": ">=18"
118
+ },
119
+ "funding": {
120
+ "type": "opencollective",
121
+ "url": "https://opencollective.com/express"
122
+ }
123
+ },
124
+ "node_modules/bson": {
125
+ "version": "7.2.0",
126
+ "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz",
127
+ "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==",
128
+ "license": "Apache-2.0",
129
+ "engines": {
130
+ "node": ">=20.19.0"
131
+ }
132
+ },
133
+ "node_modules/bytes": {
134
+ "version": "3.1.2",
135
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
136
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
137
+ "license": "MIT",
138
+ "engines": {
139
+ "node": ">= 0.8"
140
+ }
141
+ },
142
+ "node_modules/call-bind-apply-helpers": {
143
+ "version": "1.0.2",
144
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
145
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
146
+ "license": "MIT",
147
+ "dependencies": {
148
+ "es-errors": "^1.3.0",
149
+ "function-bind": "^1.1.2"
150
+ },
151
+ "engines": {
152
+ "node": ">= 0.4"
153
+ }
154
+ },
155
+ "node_modules/call-bound": {
156
+ "version": "1.0.4",
157
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
158
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
159
+ "license": "MIT",
160
+ "dependencies": {
161
+ "call-bind-apply-helpers": "^1.0.2",
162
+ "get-intrinsic": "^1.3.0"
163
+ },
164
+ "engines": {
165
+ "node": ">= 0.4"
166
+ },
167
+ "funding": {
168
+ "url": "https://github.com/sponsors/ljharb"
169
+ }
170
+ },
171
+ "node_modules/cli-progress": {
172
+ "version": "3.12.0",
173
+ "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
174
+ "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
175
+ "license": "MIT",
176
+ "optional": true,
177
+ "dependencies": {
178
+ "string-width": "^4.2.3"
179
+ },
180
+ "engines": {
181
+ "node": ">=4"
182
+ }
183
+ },
184
+ "node_modules/content-disposition": {
185
+ "version": "1.0.1",
186
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
187
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
188
+ "license": "MIT",
189
+ "engines": {
190
+ "node": ">=18"
191
+ },
192
+ "funding": {
193
+ "type": "opencollective",
194
+ "url": "https://opencollective.com/express"
195
+ }
196
+ },
197
+ "node_modules/content-type": {
198
+ "version": "1.0.5",
199
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
200
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
201
+ "license": "MIT",
202
+ "engines": {
203
+ "node": ">= 0.6"
204
+ }
205
+ },
206
+ "node_modules/cookie": {
207
+ "version": "0.7.2",
208
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
209
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
210
+ "license": "MIT",
211
+ "engines": {
212
+ "node": ">= 0.6"
213
+ }
214
+ },
215
+ "node_modules/cookie-signature": {
216
+ "version": "1.2.2",
217
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
218
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
219
+ "license": "MIT",
220
+ "engines": {
221
+ "node": ">=6.6.0"
222
+ }
223
+ },
224
+ "node_modules/cors": {
225
+ "version": "2.8.6",
226
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
227
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
228
+ "license": "MIT",
229
+ "dependencies": {
230
+ "object-assign": "^4",
231
+ "vary": "^1"
232
+ },
233
+ "engines": {
234
+ "node": ">= 0.10"
235
+ },
236
+ "funding": {
237
+ "type": "opencollective",
238
+ "url": "https://opencollective.com/express"
239
+ }
240
+ },
241
+ "node_modules/debug": {
242
+ "version": "4.4.3",
243
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
244
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
245
+ "license": "MIT",
246
+ "dependencies": {
247
+ "ms": "^2.1.3"
248
+ },
249
+ "engines": {
250
+ "node": ">=6.0"
251
+ },
252
+ "peerDependenciesMeta": {
253
+ "supports-color": {
254
+ "optional": true
255
+ }
256
+ }
257
+ },
258
+ "node_modules/depd": {
259
+ "version": "2.0.0",
260
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
261
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
262
+ "license": "MIT",
263
+ "engines": {
264
+ "node": ">= 0.8"
265
+ }
266
+ },
267
+ "node_modules/dotenv": {
268
+ "version": "17.3.1",
269
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
270
+ "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
271
+ "license": "BSD-2-Clause",
272
+ "engines": {
273
+ "node": ">=12"
274
+ },
275
+ "funding": {
276
+ "url": "https://dotenvx.com"
277
+ }
278
+ },
279
+ "node_modules/dunder-proto": {
280
+ "version": "1.0.1",
281
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
282
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
283
+ "license": "MIT",
284
+ "dependencies": {
285
+ "call-bind-apply-helpers": "^1.0.1",
286
+ "es-errors": "^1.3.0",
287
+ "gopd": "^1.2.0"
288
+ },
289
+ "engines": {
290
+ "node": ">= 0.4"
291
+ }
292
+ },
293
+ "node_modules/ee-first": {
294
+ "version": "1.1.1",
295
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
296
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
297
+ "license": "MIT"
298
+ },
299
+ "node_modules/emoji-regex": {
300
+ "version": "8.0.0",
301
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
302
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
303
+ "license": "MIT",
304
+ "optional": true
305
+ },
306
+ "node_modules/encodeurl": {
307
+ "version": "2.0.0",
308
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
309
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
310
+ "license": "MIT",
311
+ "engines": {
312
+ "node": ">= 0.8"
313
+ }
314
+ },
315
+ "node_modules/es-define-property": {
316
+ "version": "1.0.1",
317
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
318
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
319
+ "license": "MIT",
320
+ "engines": {
321
+ "node": ">= 0.4"
322
+ }
323
+ },
324
+ "node_modules/es-errors": {
325
+ "version": "1.3.0",
326
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
327
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
328
+ "license": "MIT",
329
+ "engines": {
330
+ "node": ">= 0.4"
331
+ }
332
+ },
333
+ "node_modules/es-object-atoms": {
334
+ "version": "1.1.1",
335
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
336
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
337
+ "license": "MIT",
338
+ "dependencies": {
339
+ "es-errors": "^1.3.0"
340
+ },
341
+ "engines": {
342
+ "node": ">= 0.4"
343
+ }
344
+ },
345
+ "node_modules/escape-html": {
346
+ "version": "1.0.3",
347
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
348
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
349
+ "license": "MIT"
350
+ },
351
+ "node_modules/etag": {
352
+ "version": "1.8.1",
353
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
354
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
355
+ "license": "MIT",
356
+ "engines": {
357
+ "node": ">= 0.6"
358
+ }
359
+ },
360
+ "node_modules/express": {
361
+ "version": "5.2.1",
362
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
363
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
364
+ "license": "MIT",
365
+ "dependencies": {
366
+ "accepts": "^2.0.0",
367
+ "body-parser": "^2.2.1",
368
+ "content-disposition": "^1.0.0",
369
+ "content-type": "^1.0.5",
370
+ "cookie": "^0.7.1",
371
+ "cookie-signature": "^1.2.1",
372
+ "debug": "^4.4.0",
373
+ "depd": "^2.0.0",
374
+ "encodeurl": "^2.0.0",
375
+ "escape-html": "^1.0.3",
376
+ "etag": "^1.8.1",
377
+ "finalhandler": "^2.1.0",
378
+ "fresh": "^2.0.0",
379
+ "http-errors": "^2.0.0",
380
+ "merge-descriptors": "^2.0.0",
381
+ "mime-types": "^3.0.0",
382
+ "on-finished": "^2.4.1",
383
+ "once": "^1.4.0",
384
+ "parseurl": "^1.3.3",
385
+ "proxy-addr": "^2.0.7",
386
+ "qs": "^6.14.0",
387
+ "range-parser": "^1.2.1",
388
+ "router": "^2.2.0",
389
+ "send": "^1.1.0",
390
+ "serve-static": "^2.2.0",
391
+ "statuses": "^2.0.1",
392
+ "type-is": "^2.0.1",
393
+ "vary": "^1.1.2"
394
+ },
395
+ "engines": {
396
+ "node": ">= 18"
397
+ },
398
+ "funding": {
399
+ "type": "opencollective",
400
+ "url": "https://opencollective.com/express"
401
+ }
402
+ },
403
+ "node_modules/finalhandler": {
404
+ "version": "2.1.1",
405
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
406
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
407
+ "license": "MIT",
408
+ "dependencies": {
409
+ "debug": "^4.4.0",
410
+ "encodeurl": "^2.0.0",
411
+ "escape-html": "^1.0.3",
412
+ "on-finished": "^2.4.1",
413
+ "parseurl": "^1.3.3",
414
+ "statuses": "^2.0.1"
415
+ },
416
+ "engines": {
417
+ "node": ">= 18.0.0"
418
+ },
419
+ "funding": {
420
+ "type": "opencollective",
421
+ "url": "https://opencollective.com/express"
422
+ }
423
+ },
424
+ "node_modules/forwarded": {
425
+ "version": "0.2.0",
426
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
427
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
428
+ "license": "MIT",
429
+ "engines": {
430
+ "node": ">= 0.6"
431
+ }
432
+ },
433
+ "node_modules/fresh": {
434
+ "version": "2.0.0",
435
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
436
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
437
+ "license": "MIT",
438
+ "engines": {
439
+ "node": ">= 0.8"
440
+ }
441
+ },
442
+ "node_modules/function-bind": {
443
+ "version": "1.1.2",
444
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
445
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
446
+ "license": "MIT",
447
+ "funding": {
448
+ "url": "https://github.com/sponsors/ljharb"
449
+ }
450
+ },
451
+ "node_modules/get-intrinsic": {
452
+ "version": "1.3.0",
453
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
454
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
455
+ "license": "MIT",
456
+ "dependencies": {
457
+ "call-bind-apply-helpers": "^1.0.2",
458
+ "es-define-property": "^1.0.1",
459
+ "es-errors": "^1.3.0",
460
+ "es-object-atoms": "^1.1.1",
461
+ "function-bind": "^1.1.2",
462
+ "get-proto": "^1.0.1",
463
+ "gopd": "^1.2.0",
464
+ "has-symbols": "^1.1.0",
465
+ "hasown": "^2.0.2",
466
+ "math-intrinsics": "^1.1.0"
467
+ },
468
+ "engines": {
469
+ "node": ">= 0.4"
470
+ },
471
+ "funding": {
472
+ "url": "https://github.com/sponsors/ljharb"
473
+ }
474
+ },
475
+ "node_modules/get-proto": {
476
+ "version": "1.0.1",
477
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
478
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
479
+ "license": "MIT",
480
+ "dependencies": {
481
+ "dunder-proto": "^1.0.1",
482
+ "es-object-atoms": "^1.0.0"
483
+ },
484
+ "engines": {
485
+ "node": ">= 0.4"
486
+ }
487
+ },
488
+ "node_modules/gopd": {
489
+ "version": "1.2.0",
490
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
491
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
492
+ "license": "MIT",
493
+ "engines": {
494
+ "node": ">= 0.4"
495
+ },
496
+ "funding": {
497
+ "url": "https://github.com/sponsors/ljharb"
498
+ }
499
+ },
500
+ "node_modules/has-symbols": {
501
+ "version": "1.1.0",
502
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
503
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
504
+ "license": "MIT",
505
+ "engines": {
506
+ "node": ">= 0.4"
507
+ },
508
+ "funding": {
509
+ "url": "https://github.com/sponsors/ljharb"
510
+ }
511
+ },
512
+ "node_modules/hasown": {
513
+ "version": "2.0.2",
514
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
515
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
516
+ "license": "MIT",
517
+ "dependencies": {
518
+ "function-bind": "^1.1.2"
519
+ },
520
+ "engines": {
521
+ "node": ">= 0.4"
522
+ }
523
+ },
524
+ "node_modules/http-errors": {
525
+ "version": "2.0.1",
526
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
527
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
528
+ "license": "MIT",
529
+ "dependencies": {
530
+ "depd": "~2.0.0",
531
+ "inherits": "~2.0.4",
532
+ "setprototypeof": "~1.2.0",
533
+ "statuses": "~2.0.2",
534
+ "toidentifier": "~1.0.1"
535
+ },
536
+ "engines": {
537
+ "node": ">= 0.8"
538
+ },
539
+ "funding": {
540
+ "type": "opencollective",
541
+ "url": "https://opencollective.com/express"
542
+ }
543
+ },
544
+ "node_modules/iconv-lite": {
545
+ "version": "0.7.2",
546
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
547
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
548
+ "license": "MIT",
549
+ "dependencies": {
550
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
551
+ },
552
+ "engines": {
553
+ "node": ">=0.10.0"
554
+ },
555
+ "funding": {
556
+ "type": "opencollective",
557
+ "url": "https://opencollective.com/express"
558
+ }
559
+ },
560
+ "node_modules/inherits": {
561
+ "version": "2.0.4",
562
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
563
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
564
+ "license": "ISC"
565
+ },
566
+ "node_modules/ipaddr.js": {
567
+ "version": "1.9.1",
568
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
569
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
570
+ "license": "MIT",
571
+ "engines": {
572
+ "node": ">= 0.10"
573
+ }
574
+ },
575
+ "node_modules/is-fullwidth-code-point": {
576
+ "version": "3.0.0",
577
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
578
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
579
+ "license": "MIT",
580
+ "optional": true,
581
+ "engines": {
582
+ "node": ">=8"
583
+ }
584
+ },
585
+ "node_modules/is-promise": {
586
+ "version": "4.0.0",
587
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
588
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
589
+ "license": "MIT"
590
+ },
591
+ "node_modules/kareem": {
592
+ "version": "3.2.0",
593
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz",
594
+ "integrity": "sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==",
595
+ "license": "Apache-2.0",
596
+ "engines": {
597
+ "node": ">=18.0.0"
598
+ }
599
+ },
600
+ "node_modules/math-intrinsics": {
601
+ "version": "1.1.0",
602
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
603
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
604
+ "license": "MIT",
605
+ "engines": {
606
+ "node": ">= 0.4"
607
+ }
608
+ },
609
+ "node_modules/media-typer": {
610
+ "version": "1.1.0",
611
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
612
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
613
+ "license": "MIT",
614
+ "engines": {
615
+ "node": ">= 0.8"
616
+ }
617
+ },
618
+ "node_modules/memory-pager": {
619
+ "version": "1.5.0",
620
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
621
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
622
+ "license": "MIT"
623
+ },
624
+ "node_modules/merge-descriptors": {
625
+ "version": "2.0.0",
626
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
627
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
628
+ "license": "MIT",
629
+ "engines": {
630
+ "node": ">=18"
631
+ },
632
+ "funding": {
633
+ "url": "https://github.com/sponsors/sindresorhus"
634
+ }
635
+ },
636
+ "node_modules/mime-db": {
637
+ "version": "1.54.0",
638
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
639
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
640
+ "license": "MIT",
641
+ "engines": {
642
+ "node": ">= 0.6"
643
+ }
644
+ },
645
+ "node_modules/mime-types": {
646
+ "version": "3.0.2",
647
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
648
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
649
+ "license": "MIT",
650
+ "dependencies": {
651
+ "mime-db": "^1.54.0"
652
+ },
653
+ "engines": {
654
+ "node": ">=18"
655
+ },
656
+ "funding": {
657
+ "type": "opencollective",
658
+ "url": "https://opencollective.com/express"
659
+ }
660
+ },
661
+ "node_modules/mongodb": {
662
+ "version": "7.0.0",
663
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz",
664
+ "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==",
665
+ "license": "Apache-2.0",
666
+ "dependencies": {
667
+ "@mongodb-js/saslprep": "^1.3.0",
668
+ "bson": "^7.0.0",
669
+ "mongodb-connection-string-url": "^7.0.0"
670
+ },
671
+ "engines": {
672
+ "node": ">=20.19.0"
673
+ },
674
+ "peerDependencies": {
675
+ "@aws-sdk/credential-providers": "^3.806.0",
676
+ "@mongodb-js/zstd": "^7.0.0",
677
+ "gcp-metadata": "^7.0.1",
678
+ "kerberos": "^7.0.0",
679
+ "mongodb-client-encryption": ">=7.0.0 <7.1.0",
680
+ "snappy": "^7.3.2",
681
+ "socks": "^2.8.6"
682
+ },
683
+ "peerDependenciesMeta": {
684
+ "@aws-sdk/credential-providers": {
685
+ "optional": true
686
+ },
687
+ "@mongodb-js/zstd": {
688
+ "optional": true
689
+ },
690
+ "gcp-metadata": {
691
+ "optional": true
692
+ },
693
+ "kerberos": {
694
+ "optional": true
695
+ },
696
+ "mongodb-client-encryption": {
697
+ "optional": true
698
+ },
699
+ "snappy": {
700
+ "optional": true
701
+ },
702
+ "socks": {
703
+ "optional": true
704
+ }
705
+ }
706
+ },
707
+ "node_modules/mongodb-connection-string-url": {
708
+ "version": "7.0.1",
709
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz",
710
+ "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==",
711
+ "license": "Apache-2.0",
712
+ "dependencies": {
713
+ "@types/whatwg-url": "^13.0.0",
714
+ "whatwg-url": "^14.1.0"
715
+ },
716
+ "engines": {
717
+ "node": ">=20.19.0"
718
+ }
719
+ },
720
+ "node_modules/mongoose": {
721
+ "version": "9.2.3",
722
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.2.3.tgz",
723
+ "integrity": "sha512-4XFKKkXUOsdY+p07eJyio4mk0rzZOT4n5r5tLqZNeRZ/IsS68vS8Szw8uShX4p7S687XGGc+MFAp+6K1OIN0aw==",
724
+ "license": "MIT",
725
+ "dependencies": {
726
+ "kareem": "3.2.0",
727
+ "mongodb": "~7.0",
728
+ "mpath": "0.9.0",
729
+ "mquery": "6.0.0",
730
+ "ms": "2.1.3",
731
+ "sift": "17.1.3"
732
+ },
733
+ "engines": {
734
+ "node": ">=20.19.0"
735
+ },
736
+ "funding": {
737
+ "type": "opencollective",
738
+ "url": "https://opencollective.com/mongoose"
739
+ }
740
+ },
741
+ "node_modules/mpath": {
742
+ "version": "0.9.0",
743
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
744
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
745
+ "license": "MIT",
746
+ "engines": {
747
+ "node": ">=4.0.0"
748
+ }
749
+ },
750
+ "node_modules/mquery": {
751
+ "version": "6.0.0",
752
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz",
753
+ "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==",
754
+ "license": "MIT",
755
+ "engines": {
756
+ "node": ">=20.19.0"
757
+ }
758
+ },
759
+ "node_modules/ms": {
760
+ "version": "2.1.3",
761
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
762
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
763
+ "license": "MIT"
764
+ },
765
+ "node_modules/negotiator": {
766
+ "version": "1.0.0",
767
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
768
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
769
+ "license": "MIT",
770
+ "engines": {
771
+ "node": ">= 0.6"
772
+ }
773
+ },
774
+ "node_modules/object-assign": {
775
+ "version": "4.1.1",
776
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
777
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
778
+ "license": "MIT",
779
+ "engines": {
780
+ "node": ">=0.10.0"
781
+ }
782
+ },
783
+ "node_modules/object-inspect": {
784
+ "version": "1.13.4",
785
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
786
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
787
+ "license": "MIT",
788
+ "engines": {
789
+ "node": ">= 0.4"
790
+ },
791
+ "funding": {
792
+ "url": "https://github.com/sponsors/ljharb"
793
+ }
794
+ },
795
+ "node_modules/on-finished": {
796
+ "version": "2.4.1",
797
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
798
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
799
+ "license": "MIT",
800
+ "dependencies": {
801
+ "ee-first": "1.1.1"
802
+ },
803
+ "engines": {
804
+ "node": ">= 0.8"
805
+ }
806
+ },
807
+ "node_modules/once": {
808
+ "version": "1.4.0",
809
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
810
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
811
+ "license": "ISC",
812
+ "dependencies": {
813
+ "wrappy": "1"
814
+ }
815
+ },
816
+ "node_modules/parseurl": {
817
+ "version": "1.3.3",
818
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
819
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
820
+ "license": "MIT",
821
+ "engines": {
822
+ "node": ">= 0.8"
823
+ }
824
+ },
825
+ "node_modules/path-to-regexp": {
826
+ "version": "8.3.0",
827
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
828
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
829
+ "license": "MIT",
830
+ "funding": {
831
+ "type": "opencollective",
832
+ "url": "https://opencollective.com/express"
833
+ }
834
+ },
835
+ "node_modules/proxy-addr": {
836
+ "version": "2.0.7",
837
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
838
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
839
+ "license": "MIT",
840
+ "dependencies": {
841
+ "forwarded": "0.2.0",
842
+ "ipaddr.js": "1.9.1"
843
+ },
844
+ "engines": {
845
+ "node": ">= 0.10"
846
+ }
847
+ },
848
+ "node_modules/punycode": {
849
+ "version": "2.3.1",
850
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
851
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
852
+ "license": "MIT",
853
+ "engines": {
854
+ "node": ">=6"
855
+ }
856
+ },
857
+ "node_modules/qs": {
858
+ "version": "6.15.0",
859
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
860
+ "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
861
+ "license": "BSD-3-Clause",
862
+ "dependencies": {
863
+ "side-channel": "^1.1.0"
864
+ },
865
+ "engines": {
866
+ "node": ">=0.6"
867
+ },
868
+ "funding": {
869
+ "url": "https://github.com/sponsors/ljharb"
870
+ }
871
+ },
872
+ "node_modules/range-parser": {
873
+ "version": "1.2.1",
874
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
875
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
876
+ "license": "MIT",
877
+ "engines": {
878
+ "node": ">= 0.6"
879
+ }
880
+ },
881
+ "node_modules/raw-body": {
882
+ "version": "3.0.2",
883
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
884
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
885
+ "license": "MIT",
886
+ "dependencies": {
887
+ "bytes": "~3.1.2",
888
+ "http-errors": "~2.0.1",
889
+ "iconv-lite": "~0.7.0",
890
+ "unpipe": "~1.0.0"
891
+ },
892
+ "engines": {
893
+ "node": ">= 0.10"
894
+ }
895
+ },
896
+ "node_modules/router": {
897
+ "version": "2.2.0",
898
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
899
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
900
+ "license": "MIT",
901
+ "dependencies": {
902
+ "debug": "^4.4.0",
903
+ "depd": "^2.0.0",
904
+ "is-promise": "^4.0.0",
905
+ "parseurl": "^1.3.3",
906
+ "path-to-regexp": "^8.0.0"
907
+ },
908
+ "engines": {
909
+ "node": ">= 18"
910
+ }
911
+ },
912
+ "node_modules/safer-buffer": {
913
+ "version": "2.1.2",
914
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
915
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
916
+ "license": "MIT"
917
+ },
918
+ "node_modules/send": {
919
+ "version": "1.2.1",
920
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
921
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
922
+ "license": "MIT",
923
+ "dependencies": {
924
+ "debug": "^4.4.3",
925
+ "encodeurl": "^2.0.0",
926
+ "escape-html": "^1.0.3",
927
+ "etag": "^1.8.1",
928
+ "fresh": "^2.0.0",
929
+ "http-errors": "^2.0.1",
930
+ "mime-types": "^3.0.2",
931
+ "ms": "^2.1.3",
932
+ "on-finished": "^2.4.1",
933
+ "range-parser": "^1.2.1",
934
+ "statuses": "^2.0.2"
935
+ },
936
+ "engines": {
937
+ "node": ">= 18"
938
+ },
939
+ "funding": {
940
+ "type": "opencollective",
941
+ "url": "https://opencollective.com/express"
942
+ }
943
+ },
944
+ "node_modules/serve-static": {
945
+ "version": "2.2.1",
946
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
947
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
948
+ "license": "MIT",
949
+ "dependencies": {
950
+ "encodeurl": "^2.0.0",
951
+ "escape-html": "^1.0.3",
952
+ "parseurl": "^1.3.3",
953
+ "send": "^1.2.0"
954
+ },
955
+ "engines": {
956
+ "node": ">= 18"
957
+ },
958
+ "funding": {
959
+ "type": "opencollective",
960
+ "url": "https://opencollective.com/express"
961
+ }
962
+ },
963
+ "node_modules/setprototypeof": {
964
+ "version": "1.2.0",
965
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
966
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
967
+ "license": "ISC"
968
+ },
969
+ "node_modules/side-channel": {
970
+ "version": "1.1.0",
971
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
972
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
973
+ "license": "MIT",
974
+ "dependencies": {
975
+ "es-errors": "^1.3.0",
976
+ "object-inspect": "^1.13.3",
977
+ "side-channel-list": "^1.0.0",
978
+ "side-channel-map": "^1.0.1",
979
+ "side-channel-weakmap": "^1.0.2"
980
+ },
981
+ "engines": {
982
+ "node": ">= 0.4"
983
+ },
984
+ "funding": {
985
+ "url": "https://github.com/sponsors/ljharb"
986
+ }
987
+ },
988
+ "node_modules/side-channel-list": {
989
+ "version": "1.0.0",
990
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
991
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
992
+ "license": "MIT",
993
+ "dependencies": {
994
+ "es-errors": "^1.3.0",
995
+ "object-inspect": "^1.13.3"
996
+ },
997
+ "engines": {
998
+ "node": ">= 0.4"
999
+ },
1000
+ "funding": {
1001
+ "url": "https://github.com/sponsors/ljharb"
1002
+ }
1003
+ },
1004
+ "node_modules/side-channel-map": {
1005
+ "version": "1.0.1",
1006
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1007
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1008
+ "license": "MIT",
1009
+ "dependencies": {
1010
+ "call-bound": "^1.0.2",
1011
+ "es-errors": "^1.3.0",
1012
+ "get-intrinsic": "^1.2.5",
1013
+ "object-inspect": "^1.13.3"
1014
+ },
1015
+ "engines": {
1016
+ "node": ">= 0.4"
1017
+ },
1018
+ "funding": {
1019
+ "url": "https://github.com/sponsors/ljharb"
1020
+ }
1021
+ },
1022
+ "node_modules/side-channel-weakmap": {
1023
+ "version": "1.0.2",
1024
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1025
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1026
+ "license": "MIT",
1027
+ "dependencies": {
1028
+ "call-bound": "^1.0.2",
1029
+ "es-errors": "^1.3.0",
1030
+ "get-intrinsic": "^1.2.5",
1031
+ "object-inspect": "^1.13.3",
1032
+ "side-channel-map": "^1.0.1"
1033
+ },
1034
+ "engines": {
1035
+ "node": ">= 0.4"
1036
+ },
1037
+ "funding": {
1038
+ "url": "https://github.com/sponsors/ljharb"
1039
+ }
1040
+ },
1041
+ "node_modules/sift": {
1042
+ "version": "17.1.3",
1043
+ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
1044
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
1045
+ "license": "MIT"
1046
+ },
1047
+ "node_modules/sparse-bitfield": {
1048
+ "version": "3.0.3",
1049
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1050
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1051
+ "license": "MIT",
1052
+ "dependencies": {
1053
+ "memory-pager": "^1.0.2"
1054
+ }
1055
+ },
1056
+ "node_modules/statuses": {
1057
+ "version": "2.0.2",
1058
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1059
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1060
+ "license": "MIT",
1061
+ "engines": {
1062
+ "node": ">= 0.8"
1063
+ }
1064
+ },
1065
+ "node_modules/string-width": {
1066
+ "version": "4.2.3",
1067
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1068
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1069
+ "license": "MIT",
1070
+ "optional": true,
1071
+ "dependencies": {
1072
+ "emoji-regex": "^8.0.0",
1073
+ "is-fullwidth-code-point": "^3.0.0",
1074
+ "strip-ansi": "^6.0.1"
1075
+ },
1076
+ "engines": {
1077
+ "node": ">=8"
1078
+ }
1079
+ },
1080
+ "node_modules/strip-ansi": {
1081
+ "version": "6.0.1",
1082
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1083
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1084
+ "license": "MIT",
1085
+ "optional": true,
1086
+ "dependencies": {
1087
+ "ansi-regex": "^5.0.1"
1088
+ },
1089
+ "engines": {
1090
+ "node": ">=8"
1091
+ }
1092
+ },
1093
+ "node_modules/toidentifier": {
1094
+ "version": "1.0.1",
1095
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1096
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1097
+ "license": "MIT",
1098
+ "engines": {
1099
+ "node": ">=0.6"
1100
+ }
1101
+ },
1102
+ "node_modules/tr46": {
1103
+ "version": "5.1.1",
1104
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
1105
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
1106
+ "license": "MIT",
1107
+ "dependencies": {
1108
+ "punycode": "^2.3.1"
1109
+ },
1110
+ "engines": {
1111
+ "node": ">=18"
1112
+ }
1113
+ },
1114
+ "node_modules/type-is": {
1115
+ "version": "2.0.1",
1116
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
1117
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
1118
+ "license": "MIT",
1119
+ "dependencies": {
1120
+ "content-type": "^1.0.5",
1121
+ "media-typer": "^1.1.0",
1122
+ "mime-types": "^3.0.0"
1123
+ },
1124
+ "engines": {
1125
+ "node": ">= 0.6"
1126
+ }
1127
+ },
1128
+ "node_modules/unpipe": {
1129
+ "version": "1.0.0",
1130
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1131
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1132
+ "license": "MIT",
1133
+ "engines": {
1134
+ "node": ">= 0.8"
1135
+ }
1136
+ },
1137
+ "node_modules/vary": {
1138
+ "version": "1.1.2",
1139
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1140
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1141
+ "license": "MIT",
1142
+ "engines": {
1143
+ "node": ">= 0.8"
1144
+ }
1145
+ },
1146
+ "node_modules/webidl-conversions": {
1147
+ "version": "7.0.0",
1148
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1149
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1150
+ "license": "BSD-2-Clause",
1151
+ "engines": {
1152
+ "node": ">=12"
1153
+ }
1154
+ },
1155
+ "node_modules/whatwg-url": {
1156
+ "version": "14.2.0",
1157
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
1158
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
1159
+ "license": "MIT",
1160
+ "dependencies": {
1161
+ "tr46": "^5.1.0",
1162
+ "webidl-conversions": "^7.0.0"
1163
+ },
1164
+ "engines": {
1165
+ "node": ">=18"
1166
+ }
1167
+ },
1168
+ "node_modules/wrappy": {
1169
+ "version": "1.0.2",
1170
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1171
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1172
+ "license": "ISC"
1173
+ }
1174
+ }
1175
+ }
package.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "m-textile",
3
+ "version": "1.0.0",
4
+ "description": "M-TEXTILE — Kiyim-kechak do'koni",
5
+ "main": "server/server.js",
6
+ "scripts": {
7
+ "start": "node server/server.js",
8
+ "seed": "node server/seed.js",
9
+ "dev": "node server/server.js"
10
+ },
11
+ "keywords": [],
12
+ "author": "",
13
+ "license": "ISC",
14
+ "type": "commonjs",
15
+ "dependencies": {
16
+ "@huggingface/hub": "^2.10.4",
17
+ "bcryptjs": "^3.0.3",
18
+ "cors": "^2.8.6",
19
+ "dotenv": "^17.3.1",
20
+ "express": "^5.2.1",
21
+ "mongoose": "^9.2.3"
22
+ }
23
+ }
product.html ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="theme-color" content="#0D1F23">
8
+ <meta name="description" content="Mahsulot tafsilotlari — M-TEXTILE">
9
+ <title>Mahsulot — M-TEXTILE</title>
10
+ <link rel="stylesheet" href="css/global.css">
11
+ <link rel="stylesheet" href="css/components.css">
12
+ <link rel="stylesheet" href="css/pages.css">
13
+ <link rel="stylesheet" href="css/premium.css">
14
+ <link rel="manifest" href="manifest.json">
15
+
16
+ <!-- AOS CSS -->
17
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
18
+ </head>
19
+
20
+ <body>
21
+ <!-- Global Preloader -->
22
+ <div id="global-preloader" class="preloader">
23
+ <div class="preloader-spinner"></div>
24
+ </div>
25
+ <div class="page-wrapper">
26
+ <div class="page-content">
27
+ <section class="section-padding" style="padding-top: calc(var(--navbar-height) + 2rem);">
28
+ <div class="container">
29
+ <!-- Breadcrumb -->
30
+ <div class="flex items-center gap-sm mb-xl" style="font-size:0.85rem;color:var(--clr-text-muted);">
31
+ <a href="index.html" style="color:var(--clr-text-muted)">Bosh sahifa</a>
32
+ <span>/</span>
33
+ <a href="catalog.html" style="color:var(--clr-text-muted)">Katalog</a>
34
+ <span>/</span>
35
+ <span id="breadcrumbCategory">—</span>
36
+ </div>
37
+
38
+ <div class="product-detail" id="productDetail">
39
+ <!-- Gallery -->
40
+ <div class="product-gallery">
41
+ <div class="product-main-image">
42
+ <img id="mainImage" src="" alt="">
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Info -->
47
+ <div>
48
+ <div class="product-info-header">
49
+ <div class="product-info-category" id="pdCategory"></div>
50
+ <h1 class="product-info-title" id="pdName"></h1>
51
+ <div class="product-info-rating">
52
+ <span class="stars" id="pdStars"></span>
53
+ <span class="rating-text" id="pdRating"></span>
54
+ </div>
55
+ <div class="product-info-price" id="pdPrice"></div>
56
+ </div>
57
+
58
+ <p style="color:var(--clr-text-secondary);line-height:1.7;margin-bottom:1.5rem;"
59
+ id="pdDesc"></p>
60
+
61
+ <div class="product-option-group">
62
+ <div class="product-option-label"
63
+ style="display:flex;justify-content:space-between;align-items:center;">
64
+ <span>O'lcham: <span id="selectedSize">—</span></span>
65
+ <button class="btn btn-ghost btn-sm" onclick="openSizeGuide()"
66
+ style="font-size:0.75rem;color:var(--clr-mid);">📏 O'lcham yo'riqnomasi</button>
67
+ </div>
68
+ <div class="size-selector" id="pdSizes"></div>
69
+ </div>
70
+
71
+ <!-- Color selector -->
72
+ <div class="product-option-group">
73
+ <div class="product-option-label">Rang: <span id="selectedColor">—</span></div>
74
+ <div class="color-selector" id="pdColors"></div>
75
+ </div>
76
+
77
+ <!-- Quantity -->
78
+ <div class="product-option-group">
79
+ <div class="product-option-label">Miqdor:</div>
80
+ <div class="qty-control">
81
+ <button class="qty-btn" id="qtyMinus">−</button>
82
+ <span class="qty-value" id="qtyValue">1</span>
83
+ <button class="qty-btn" id="qtyPlus">+</button>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- Actions -->
88
+ <div class="product-actions">
89
+ <button class="btn btn-primary btn-lg" id="addToCartBtn">🛒 Savatga qo'shish</button>
90
+ <button class="btn btn-outline btn-icon" id="wishlistBtn" title="Sevimlilar"
91
+ style="padding:0.75rem 1rem;"></button>
92
+ </div>
93
+
94
+ <!-- Features -->
95
+ <div class="product-features">
96
+ <div class="product-feature-item">🏬 Do'kondan olib ketish</div>
97
+ <div class="product-feature-item">🛡️ Kafolat</div>
98
+ <div class="product-feature-item">🔄 30 kun qaytarish</div>
99
+ </div>
100
+
101
+ <!-- Tabs -->
102
+ <div style="margin-top:2rem;">
103
+ <div class="tabs">
104
+ <button class="tab-btn active" data-tab="desc">Tavsif</button>
105
+ <button class="tab-btn" data-tab="specs">Xususiyatlari</button>
106
+ <button class="tab-btn" data-tab="reviews">Sharhlar</button>
107
+ </div>
108
+ <div class="tab-content active" id="tab-desc"></div>
109
+ <div class="tab-content" id="tab-specs">
110
+ <table style="width:100%;font-size:0.9rem;">
111
+ <tr style="border-bottom:1px solid var(--clr-border)">
112
+ <td style="padding:0.75rem 0;color:var(--clr-text-muted)">Kategoriya</td>
113
+ <td id="specCat"></td>
114
+ </tr>
115
+ <tr style="border-bottom:1px solid var(--clr-border)">
116
+ <td style="padding:0.75rem 0;color:var(--clr-text-muted)">Mavjud o'lchamlar
117
+ </td>
118
+ <td id="specSizes"></td>
119
+ </tr>
120
+ <tr style="border-bottom:1px solid var(--clr-border)">
121
+ <td style="padding:0.75rem 0;color:var(--clr-text-muted)">Mavjud ranglar
122
+ </td>
123
+ <td id="specColors"></td>
124
+ </tr>
125
+ <tr>
126
+ <td style="padding:0.75rem 0;color:var(--clr-text-muted)">Holati</td>
127
+ <td id="specStock"></td>
128
+ </tr>
129
+ </table>
130
+ </div>
131
+ <div class="tab-content" id="tab-reviews">
132
+ <p style="color:var(--clr-text-muted);">Hozircha sharhlar yo'q. Birinchi sharh
133
+ qoldiradigan siz bo'ling!</p>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <!-- Similar Products -->
140
+ <div style="margin-top:4rem;">
141
+ <div class="section-header">
142
+ <h2>O'xshash mahsulotlar</h2>
143
+ <div class="section-line"></div>
144
+ </div>
145
+ <div class="grid grid-4 stagger" id="similarGrid"></div>
146
+ </div>
147
+ </div>
148
+ </section>
149
+ </div>
150
+ </div>
151
+
152
+ <script src="js/products.js"></script>
153
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
154
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
155
+ <script src="js/firebase-config.js"></script>
156
+ <script src="js/api.js"></script>
157
+ <script src="js/store.js"></script>
158
+ <script src="js/app.js"></script>
159
+ <script src="js/product-detail.js"></script>
160
+ <script>
161
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));
162
+ </script>
163
+
164
+ <!-- AOS JS -->
165
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
166
+ </body>
167
+
168
+ </html>
profile.html ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="theme-color" content="#0D1F23">
8
+ <title>Profil — M-TEXTILE</title>
9
+ <link rel="stylesheet" href="css/global.css">
10
+ <link rel="stylesheet" href="css/components.css">
11
+ <link rel="stylesheet" href="css/pages.css">
12
+ <link rel="stylesheet" href="css/premium.css">
13
+ <link rel="manifest" href="manifest.json">
14
+
15
+ <!-- AOS CSS -->
16
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
17
+ </head>
18
+
19
+ <body>
20
+ <!-- Global Preloader -->
21
+ <div id="global-preloader" class="preloader">
22
+ <div class="preloader-spinner"></div>
23
+ </div>
24
+ <div class="page-wrapper">
25
+ <div class="page-content">
26
+ <section class="section-padding" style="padding-top: calc(var(--navbar-height) + 2rem);">
27
+ <div class="container">
28
+ <div class="profile-layout">
29
+ <!-- Sidebar -->
30
+ <div class="profile-sidebar">
31
+ <div class="profile-avatar" id="profileAvatar">U</div>
32
+ <div class="profile-name" id="profileName">Foydalanuvchi</div>
33
+ <div class="profile-email" id="profileEmail">—</div>
34
+ <nav class="profile-nav">
35
+ <a href="#" class="active" data-section="info">👤 Ma'lumotlarim</a>
36
+ <a href="#" data-section="orders">📦 Buyurtmalarim</a>
37
+ <a href="#" data-section="addresses">📍 Manzillarim</a>
38
+ <a href="admin.html" style="margin-top:2rem;color:var(--clr-primary);">⚙️ Admin
39
+ Panel</a>
40
+ </nav>
41
+ </div>
42
+ <!-- Content -->
43
+ <div class="profile-content">
44
+ <!-- Info Section -->
45
+ <div id="section-info">
46
+ <h2 style="margin-bottom:1.5rem;">Shaxsiy ma'lumotlar</h2>
47
+ <div class="form-group">
48
+ <label class="form-label">Ism</label>
49
+ <input type="text" class="form-input" id="pName" placeholder="Ismingiz">
50
+ </div>
51
+ <div class="form-group">
52
+ <label class="form-label">Telefon</label>
53
+ <input type="tel" class="form-input" id="pPhone" placeholder="+998 90 123 45 67">
54
+ </div>
55
+ <div class="form-group">
56
+ <label class="form-label">Email</label>
57
+ <input type="email" class="form-input" id="pEmail" placeholder="email@example.com">
58
+ </div>
59
+ <button class="btn btn-primary" id="saveProfileBtn">Saqlash</button>
60
+ </div>
61
+
62
+ <!-- Orders Section -->
63
+ <div id="section-orders" style="display:none;">
64
+ <h2 style="margin-bottom:1.5rem;">Buyurtmalarim</h2>
65
+ <div id="ordersList"></div>
66
+ <div id="emptyOrders" style="display:none;">
67
+ <div class="empty-state">
68
+ <div class="empty-state-icon">📦</div>
69
+ <h3 class="empty-state-title">Buyurtmalar yo'q</h3>
70
+ <p class="empty-state-text">Siz hali buyurtma bermadingiz</p>
71
+ <a href="catalog.html" class="btn btn-primary">Xarid qilish</a>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <!-- Addresses Section -->
77
+ <div id="section-addresses" style="display:none;">
78
+ <h2 style="margin-bottom:1.5rem;">Saqlangan manzillar</h2>
79
+ <div id="addressList"></div>
80
+ <button class="btn btn-outline mt-lg" id="addAddressBtn">+ Yangi manzil
81
+ qo'shish</button>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </section>
87
+ </div>
88
+ </div>
89
+ <script src="js/products.js"></script>
90
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
91
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
92
+ <script src="js/firebase-config.js"></script>
93
+ <script src="js/api.js"></script>
94
+ <script src="js/store.js"></script>
95
+ <script src="js/app.js"></script>
96
+ <script src="js/profile.js"></script>
97
+ <script>
98
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));
99
+ </script>
100
+
101
+ <!-- AOS JS -->
102
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
103
+ </body>
104
+
105
+ </html>
server/models/Order.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const mongoose = require('mongoose');
2
+
3
+ const orderSchema = new mongoose.Schema({
4
+ orderId: { type: String, required: true, unique: true },
5
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', default: null },
6
+ customer: {
7
+ name: { type: String, required: true },
8
+ phone: { type: String, required: true },
9
+ email: { type: String, default: '' }
10
+ },
11
+ items: [{
12
+ productId: Number,
13
+ name: String,
14
+ price: Number,
15
+ size: String,
16
+ color: String,
17
+ quantity: Number,
18
+ image: String
19
+ }],
20
+ totals: {
21
+ subtotal: { type: Number, default: 0 },
22
+ discount: { type: Number, default: 0 },
23
+ total: { type: Number, default: 0 }
24
+ },
25
+ paymentMethod: { type: String, default: 'cash' },
26
+ status: { type: String, default: 'Kutilmoqda', enum: ['Kutilmoqda', 'Olib ketildi', 'Bekor qilindi'] }
27
+ }, { timestamps: true });
28
+
29
+ module.exports = mongoose.model('Order', orderSchema);
server/models/Product.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const mongoose = require('mongoose');
2
+
3
+ const productSchema = new mongoose.Schema({
4
+ id: { type: Number, required: true, unique: true },
5
+ name: { type: String, required: true },
6
+ category: { type: String, required: true },
7
+ price: { type: Number, required: true },
8
+ oldPrice: { type: Number, default: null },
9
+ image: { type: String },
10
+ images: [String],
11
+ description: { type: String },
12
+ sizes: [String],
13
+ colors: [String],
14
+ rating: { type: Number, default: 4.5 },
15
+ reviews: { type: Number, default: 0 },
16
+ inStock: { type: mongoose.Schema.Types.Mixed, default: true },
17
+ isNew: { type: Boolean, default: false },
18
+ isFeatured: { type: Boolean, default: false }
19
+ }, { timestamps: true });
20
+
21
+ module.exports = mongoose.model('Product', productSchema);
server/models/Settings.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ const mongoose = require('mongoose');
2
+
3
+ const settingsSchema = new mongoose.Schema({
4
+ key: { type: String, default: 'global', unique: true },
5
+ storeAddress: { type: String, default: '' },
6
+ mapEmbedUrl: { type: String, default: '' },
7
+ adminPassword: { type: String, default: 'admin' }
8
+ }, { timestamps: true });
9
+
10
+ module.exports = mongoose.model('Settings', settingsSchema);
server/models/User.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const mongoose = require('mongoose');
2
+ const bcrypt = require('bcryptjs');
3
+
4
+ const userSchema = new mongoose.Schema({
5
+ name: { type: String, required: true },
6
+ phone: { type: String, default: '' },
7
+ email: { type: String, default: '' },
8
+ password: { type: String, required: true },
9
+ firebaseUid: { type: String, default: '' },
10
+ photoURL: { type: String, default: '' },
11
+ addresses: [{ type: String }]
12
+ }, { timestamps: true });
13
+
14
+ // Only hash if password is modified and not a Google placeholder
15
+ userSchema.pre('save', async function (next) {
16
+ if (!this.isModified('password')) return next();
17
+ if (this.password.startsWith('google_')) return next();
18
+ this.password = await bcrypt.hash(this.password, 10);
19
+ next();
20
+ });
21
+
22
+ userSchema.methods.comparePassword = async function (candidatePassword) {
23
+ return bcrypt.compare(candidatePassword, this.password);
24
+ };
25
+
26
+ module.exports = mongoose.model('User', userSchema);
server/routes/auth.js ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const User = require('../models/User');
4
+
5
+ // POST register
6
+ router.post('/register', async (req, res) => {
7
+ try {
8
+ const { name, phone, password, email } = req.body;
9
+ const existing = await User.findOne({ phone });
10
+ if (existing) {
11
+ return res.status(400).json({ success: false, message: "Bu telefon raqam allaqachon ro'yxatdan o'tgan." });
12
+ }
13
+ const user = new User({ name, phone, password, email: email || '' });
14
+ await user.save();
15
+ res.json({
16
+ success: true,
17
+ user: { id: user._id, name: user.name, phone: user.phone, email: user.email }
18
+ });
19
+ } catch (err) {
20
+ res.status(500).json({ success: false, message: err.message });
21
+ }
22
+ });
23
+
24
+ // POST login
25
+ router.post('/login', async (req, res) => {
26
+ try {
27
+ const { phone, password } = req.body;
28
+ // Support login by phone or email
29
+ const user = await User.findOne({ $or: [{ phone }, { email: phone }] });
30
+ if (!user) {
31
+ return res.status(401).json({ success: false, message: "Telefon raqam yoki parol noto'g'ri." });
32
+ }
33
+ const isMatch = await user.comparePassword(password);
34
+ if (!isMatch) {
35
+ return res.status(401).json({ success: false, message: "Telefon raqam yoki parol noto'g'ri." });
36
+ }
37
+ res.json({
38
+ success: true,
39
+ user: { id: user._id, name: user.name, phone: user.phone, email: user.email }
40
+ });
41
+ } catch (err) {
42
+ res.status(500).json({ success: false, message: err.message });
43
+ }
44
+ });
45
+
46
+ // GET current user profile
47
+ router.get('/profile/:id', async (req, res) => {
48
+ try {
49
+ const user = await User.findById(req.params.id).select('-password');
50
+ if (!user) return res.status(404).json({ error: 'Foydalanuvchi topilmadi' });
51
+ res.json(user);
52
+ } catch (err) {
53
+ res.status(500).json({ error: err.message });
54
+ }
55
+ });
56
+
57
+ // PUT update profile
58
+ router.put('/profile/:id', async (req, res) => {
59
+ try {
60
+ const updates = { ...req.body };
61
+ delete updates.password; // Don't allow password change via this endpoint
62
+ const user = await User.findByIdAndUpdate(req.params.id, updates, { new: true }).select('-password');
63
+ if (!user) return res.status(404).json({ error: 'Foydalanuvchi topilmadi' });
64
+ res.json(user);
65
+ } catch (err) {
66
+ res.status(400).json({ error: err.message });
67
+ }
68
+ });
69
+
70
+ // POST Google login (Firebase Auth)
71
+ router.post('/google-login', async (req, res) => {
72
+ try {
73
+ const { uid, name, email, phone, photoURL } = req.body;
74
+
75
+ // Find existing user by Firebase UID or email
76
+ let user = await User.findOne({ $or: [{ firebaseUid: uid }, { email: email }] });
77
+
78
+ if (user) {
79
+ // Update existing user
80
+ user.firebaseUid = uid;
81
+ user.name = name || user.name;
82
+ if (photoURL) user.photoURL = photoURL;
83
+ await user.save();
84
+ } else {
85
+ // Create new user
86
+ user = new User({
87
+ name,
88
+ email: email || '',
89
+ phone: phone || '',
90
+ password: 'google_' + uid, // placeholder password
91
+ firebaseUid: uid,
92
+ photoURL: photoURL || ''
93
+ });
94
+ await user.save();
95
+ }
96
+
97
+ res.json({
98
+ success: true,
99
+ user: { id: user._id, name: user.name, phone: user.phone, email: user.email, photoURL: user.photoURL }
100
+ });
101
+ } catch (err) {
102
+ res.status(500).json({ success: false, message: err.message });
103
+ }
104
+ });
105
+
106
+ module.exports = router;
server/routes/orders.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const Order = require('../models/Order');
4
+
5
+ // GET all orders
6
+ router.get('/', async (req, res) => {
7
+ try {
8
+ const orders = await Order.find().sort({ createdAt: -1 });
9
+ res.json(orders);
10
+ } catch (err) {
11
+ res.status(500).json({ error: err.message });
12
+ }
13
+ });
14
+
15
+ // GET orders by user
16
+ router.get('/user/:userId', async (req, res) => {
17
+ try {
18
+ const orders = await Order.find({ userId: req.params.userId }).sort({ createdAt: -1 });
19
+ res.json(orders);
20
+ } catch (err) {
21
+ res.status(500).json({ error: err.message });
22
+ }
23
+ });
24
+
25
+ // POST create order
26
+ router.post('/', async (req, res) => {
27
+ try {
28
+ const orderId = 'ORD-' + Date.now().toString(36).toUpperCase();
29
+ const order = new Order({ orderId, ...req.body });
30
+ await order.save();
31
+ res.status(201).json(order);
32
+ } catch (err) {
33
+ res.status(400).json({ error: err.message });
34
+ }
35
+ });
36
+
37
+ // PUT update order status (admin)
38
+ router.put('/:orderId/status', async (req, res) => {
39
+ try {
40
+ const order = await Order.findOneAndUpdate(
41
+ { orderId: req.params.orderId },
42
+ { status: req.body.status },
43
+ { new: true }
44
+ );
45
+ if (!order) return res.status(404).json({ error: 'Buyurtma topilmadi' });
46
+ res.json(order);
47
+ } catch (err) {
48
+ res.status(400).json({ error: err.message });
49
+ }
50
+ });
51
+
52
+ module.exports = router;
server/routes/products.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const Product = require('../models/Product');
4
+
5
+ // GET all products
6
+ router.get('/', async (req, res) => {
7
+ try {
8
+ const products = await Product.find().sort({ id: 1 });
9
+ res.json(products);
10
+ } catch (err) {
11
+ res.status(500).json({ error: err.message });
12
+ }
13
+ });
14
+
15
+ // GET single product
16
+ router.get('/:id', async (req, res) => {
17
+ try {
18
+ const product = await Product.findOne({ id: parseInt(req.params.id) });
19
+ if (!product) return res.status(404).json({ error: 'Mahsulot topilmadi' });
20
+ res.json(product);
21
+ } catch (err) {
22
+ res.status(500).json({ error: err.message });
23
+ }
24
+ });
25
+
26
+ // POST create product (admin)
27
+ router.post('/', async (req, res) => {
28
+ try {
29
+ const maxProduct = await Product.findOne().sort({ id: -1 });
30
+ const newId = maxProduct ? maxProduct.id + 1 : 1;
31
+ const product = new Product({ ...req.body, id: newId });
32
+ await product.save();
33
+ res.status(201).json(product);
34
+ } catch (err) {
35
+ res.status(400).json({ error: err.message });
36
+ }
37
+ });
38
+
39
+ // PUT update product (admin)
40
+ router.put('/:id', async (req, res) => {
41
+ try {
42
+ const product = await Product.findOneAndUpdate(
43
+ { id: parseInt(req.params.id) },
44
+ req.body,
45
+ { new: true }
46
+ );
47
+ if (!product) return res.status(404).json({ error: 'Mahsulot topilmadi' });
48
+ res.json(product);
49
+ } catch (err) {
50
+ res.status(400).json({ error: err.message });
51
+ }
52
+ });
53
+
54
+ // DELETE product (admin)
55
+ router.delete('/:id', async (req, res) => {
56
+ try {
57
+ const product = await Product.findOneAndDelete({ id: parseInt(req.params.id) });
58
+ if (!product) return res.status(404).json({ error: 'Mahsulot topilmadi' });
59
+ res.json({ success: true });
60
+ } catch (err) {
61
+ res.status(500).json({ error: err.message });
62
+ }
63
+ });
64
+
65
+ module.exports = router;
server/routes/settings.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const Settings = require('../models/Settings');
4
+
5
+ // GET settings
6
+ router.get('/', async (req, res) => {
7
+ try {
8
+ let settings = await Settings.findOne({ key: 'global' });
9
+ if (!settings) {
10
+ settings = await Settings.create({ key: 'global' });
11
+ }
12
+ res.json(settings);
13
+ } catch (err) {
14
+ res.status(500).json({ error: err.message });
15
+ }
16
+ });
17
+
18
+ // PUT update settings (admin)
19
+ router.put('/', async (req, res) => {
20
+ try {
21
+ const settings = await Settings.findOneAndUpdate(
22
+ { key: 'global' },
23
+ req.body,
24
+ { new: true, upsert: true }
25
+ );
26
+ res.json(settings);
27
+ } catch (err) {
28
+ res.status(400).json({ error: err.message });
29
+ }
30
+ });
31
+
32
+ // POST admin login
33
+ router.post('/admin-login', async (req, res) => {
34
+ try {
35
+ const settings = await Settings.findOne({ key: 'global' });
36
+ const adminPass = settings ? settings.adminPassword : 'admin';
37
+ if (req.body.password === adminPass) {
38
+ res.json({ success: true });
39
+ } else {
40
+ res.status(401).json({ success: false, message: 'Parol noto\'g\'ri' });
41
+ }
42
+ } catch (err) {
43
+ res.status(500).json({ error: err.message });
44
+ }
45
+ });
46
+
47
+ module.exports = router;
server/seed.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
2
+ const mongoose = require('mongoose');
3
+ const Product = require('./models/Product');
4
+
5
+ function _p(id, name, cat, sub, price, oldPrice, disc, sizes, colors, img, rat, rev, desc, stock, isNew, feat) {
6
+ return {
7
+ id, name, category: cat, subcategory: sub, price, oldPrice, discount: disc, sizes, colors,
8
+ images: [img], image: img, rating: rat, reviews: rev, description: desc, inStock: stock, isNew, isFeatured: feat
9
+ };
10
+ }
11
+ const S = ["S", "M", "L", "XL"], SM = ["XS", "S", "M", "L", "XL"], ML = ["M", "L", "XL", "XXL"], SX = ["S", "M", "L", "XL", "XXL"], OS = ["One Size"];
12
+
13
+ const PRODUCTS = [
14
+ _p(1, "Klassik Biznes Ko'ylak", "kiyimlar", "Ko'ylaklar", 289000, 350000, 17, S, ["oq", "ko_k", "kulrang"], "https://images.unsplash.com/photo-1602810318383-e386cc2a3ccf?w=600&h=800&fit=crop", 4.8, 124, "Yuqori sifatli paxta matosidan tayyorlangan klassik biznes ko'ylak.", true, false, true),
15
+ _p(2, "Premium Slim Fit Ko'ylak", "kiyimlar", "Ko'ylaklar", 345000, null, 0, SX, ["qora", "ko_k", "oq"], "https://images.unsplash.com/photo-1596755094514-f87e34085b2c?w=600&h=800&fit=crop", 4.6, 89, "Zamonaviy slim fit ko'ylak.", true, true, true),
16
+ _p(3, "Qishki Issiq Kurtka", "kiyimlar", "Kurtkalar", 890000, 1100000, 19, ML, ["qora", "kulrang", "havorang"], "https://images.unsplash.com/photo-1544923246-77307dd270b5?w=600&h=800&fit=crop", 4.9, 256, "Sovuqdan himoya qiluvchi premium kurtka.", true, false, true),
17
+ _p(4, "Sport Futbolka Pro", "kiyimlar", "Futbolkalar", 149000, 189000, 21, SM, ["qora", "oq", "ko_k", "qizil"], "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=600&h=800&fit=crop", 4.5, 312, "Yuqori sifatli sport futbolka.", true, true, false),
18
+ _p(5, "Elegant Blazer", "kiyimlar", "Blazerlar", 650000, null, 0, S, ["qora", "kulrang", "jigarrang"], "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&h=800&fit=crop", 4.7, 78, "Premium materialdan tayyorlangan elegant blazer.", true, false, true),
19
+ _p(6, "Casual Polo Ko'ylak", "kiyimlar", "Ko'ylaklar", 195000, 240000, 19, S, ["oq", "ko_k", "yashil", "qizil"], "https://images.unsplash.com/photo-1625910513413-5fc68e7990a7?w=600&h=800&fit=crop", 4.4, 167, "Polo ko'ylak.", true, false, false),
20
+ _p(7, "Denim Kurtka Classic", "kiyimlar", "Kurtkalar", 420000, null, 0, S, ["ko_k", "qora"], "https://images.unsplash.com/photo-1576995853123-5a10305d93c0?w=600&h=800&fit=crop", 4.6, 93, "Klassik denim kurtka.", true, true, false),
21
+ _p(8, "Premium Hoodie", "kiyimlar", "Futbolkalar", 275000, 320000, 14, SX, ["qora", "kulrang", "havorang"], "https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=600&h=800&fit=crop", 4.7, 201, "Yumshoq fleece ichlikli premium hoodie.", true, false, true),
22
+ _p(9, "Maktab Formalari To'plami", "formalar", "Maktab", 450000, 550000, 18, SM, ["qora", "ko_k"], "https://images.unsplash.com/photo-1604671801908-6f0c6a092c05?w=600&h=800&fit=crop", 4.5, 345, "To'liq maktab formalari to'plami.", true, false, true),
23
+ _p(10, "Ofis Biznes Kostyum", "formalar", "Ish formalari", 1250000, 1500000, 17, S, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1594938298603-c8148c4dae35?w=600&h=800&fit=crop", 4.9, 156, "Premium biznes kostyum.", true, true, true),
24
+ _p(11, "Tibbiyot Formasi", "formalar", "Ish formalari", 320000, null, 0, SX, ["oq", "ko_k", "yashil"], "https://images.unsplash.com/photo-1559839734-2b71ea197ec2?w=600&h=800&fit=crop", 4.3, 89, "Professional tibbiyot formasi.", true, false, false),
25
+ _p(12, "Oshpaz Formasi Premium", "formalar", "Ish formalari", 380000, 450000, 16, S, ["oq", "qora"], "https://images.unsplash.com/photo-1577219491135-ce391730fb2c?w=600&h=800&fit=crop", 4.6, 67, "Professional oshpaz formasi.", true, false, false),
26
+ _p(13, "Sport Forma - Futbol", "formalar", "Sport", 270000, null, 0, SM, ["ko_k", "qizil", "yashil", "oq"], "https://images.unsplash.com/photo-1574629810360-7efbbe195018?w=600&h=800&fit=crop", 4.4, 234, "Professional futbol formasi.", true, true, false),
27
+ _p(14, "Harbiy Uniforma", "formalar", "Ish formalari", 580000, null, 0, SX, ["yashil", "kulrang"], "https://images.unsplash.com/photo-1579912861630-f7c8ad0c7804?w=600&h=800&fit=crop", 4.7, 45, "Yuqori sifatli uniforma.", true, false, false),
28
+ _p(15, "Qizlar Maktab Formalari", "formalar", "Maktab", 420000, 490000, 14, ["XS", "S", "M", "L"], ["qora", "ko_k"], "https://images.unsplash.com/photo-1594938298603-c8148c4dae35?w=600&h=800&fit=crop", 4.5, 278, "Qizlar maktab formalari.", true, false, true),
29
+ _p(16, "Mexanik Ish Kiyimi", "formalar", "Ish formalari", 340000, null, 0, ML, ["ko_k", "kulrang"], "https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=600&h=800&fit=crop", 4.2, 56, "Mexaniklar uchun maxsus ish kiyimi.", true, false, false),
30
+ _p(17, "Klassik Biznes Shim", "shimlar", "Klassik", 320000, 380000, 16, S, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1624378439575-d8705ad7ae80?w=600&h=800&fit=crop", 4.6, 189, "Premium klassik shim.", true, false, true),
31
+ _p(18, "Slim Jinsi Shim", "shimlar", "Jinsi", 275000, null, 0, S, ["ko_k", "qora", "kulrang"], "https://images.unsplash.com/photo-1542272604-787c3835535d?w=600&h=800&fit=crop", 4.7, 412, "Zamonaviy slim fit jinsi shim.", true, true, true),
32
+ _p(19, "Sport Shim Jogger", "shimlar", "Sport", 185000, 220000, 16, SX, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?w=600&h=800&fit=crop", 4.5, 298, "Sport jogger shim.", true, false, false),
33
+ _p(20, "Chino Shim Classic", "shimlar", "Klassik", 245000, null, 0, S, ["jigarrang", "kulrang", "yashil", "ko_k"], "https://images.unsplash.com/photo-1473966968600-fa801b869a1a?w=600&h=800&fit=crop", 4.4, 145, "Chino shim.", true, false, false),
34
+ _p(21, "Straight Fit Jinsi", "shimlar", "Jinsi", 295000, 350000, 16, ML, ["ko_k", "qora"], "https://images.unsplash.com/photo-1604176354204-9268737828e4?w=600&h=800&fit=crop", 4.5, 187, "Klassik straight fit jinsi.", true, false, false),
35
+ _p(22, "Cargo Shim Tactical", "shimlar", "Sport", 310000, null, 0, S, ["yashil", "qora", "kulrang"], "https://images.unsplash.com/photo-1517438476312-10d79c077509?w=600&h=800&fit=crop", 4.3, 98, "Tactical cargo shim.", true, true, false),
36
+ _p(23, "Linen Yoz Shim", "shimlar", "Klassik", 230000, 280000, 18, S, ["oq", "kulrang", "havorang"], "https://images.unsplash.com/photo-1506629082955-511b1aa562c8?w=600&h=800&fit=crop", 4.6, 76, "Yozgi linen shim.", true, false, true),
37
+ _p(24, "Qisqa Sport Shorts", "shimlar", "Sport", 125000, null, 0, S, ["qora", "ko_k", "kulrang"], "https://images.unsplash.com/photo-1591195853828-11db59a44f6b?w=600&h=800&fit=crop", 4.3, 234, "Qisqa sport shorts.", true, false, false),
38
+ _p(25, "Ipak Klassik Galistuk", "galistuklar", "Klassik", 185000, 220000, 16, OS, ["qora", "ko_k", "qizil"], "https://images.unsplash.com/photo-1589756823695-278bc923a423?w=600&h=800&fit=crop", 4.8, 167, "100% ipak klassik galistuk.", true, false, true),
39
+ _p(26, "Slim Zamonaviy Galistuk", "galistuklar", "Slim", 145000, null, 0, OS, ["qora", "kulrang", "ko_k", "qizil"], "https://images.unsplash.com/photo-1598879400638-bb93c904ef41?w=600&h=800&fit=crop", 4.5, 234, "Zamonaviy slim galistuk.", true, true, true),
40
+ _p(27, "Bowtie Premium", "galistuklar", "Bowtie", 125000, 160000, 22, OS, ["qora", "qizil", "ko_k", "yashil"], "https://images.unsplash.com/photo-1580657018950-c7f7d6a6d990?w=600&h=800&fit=crop", 4.7, 89, "Premium bowtie.", true, false, false),
41
+ _p(28, "Naqshli Galistuk", "galistuklar", "Klassik", 165000, null, 0, OS, ["ko_k", "qizil"], "https://images.unsplash.com/photo-1590548784585-643d2b9f2925?w=600&h=800&fit=crop", 4.4, 112, "Naqshli galistuk.", true, false, false),
42
+ _p(29, "Kashemir Galistuk", "galistuklar", "Klassik", 250000, 300000, 17, OS, ["jigarrang", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1598879400638-bb93c904ef41?w=600&h=800&fit=crop", 4.9, 56, "Premium kashemir galistuk.", true, true, true),
43
+ _p(30, "Mikrofiber Galistuk", "galistuklar", "Slim", 95000, null, 0, OS, ["qora", "ko_k", "kulrang"], "https://images.unsplash.com/photo-1589756823695-278bc923a423?w=600&h=800&fit=crop", 4.2, 78, "Mikrofiber galistuk.", true, false, false),
44
+ _p(31, "To'y Galistuk To'plami", "galistuklar", "Klassik", 320000, 400000, 20, OS, ["oq", "qizil", "ko_k"], "https://images.unsplash.com/photo-1580657018950-c7f7d6a6d990?w=600&h=800&fit=crop", 4.8, 134, "To'y uchun galistuk to'plami.", true, false, true),
45
+ _p(32, "Kids Bowtie", "galistuklar", "Bowtie", 65000, 85000, 24, OS, ["ko_k", "qizil", "qora"], "https://images.unsplash.com/photo-1580657018950-c7f7d6a6d990?w=600&h=800&fit=crop", 4.6, 45, "Bolalar uchun bowtie.", true, false, false),
46
+ _p(33, "Charm Kamar Premium", "aksessuarlar", "Kamarlar", 195000, 240000, 19, S, ["qora", "jigarrang"], "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&h=800&fit=crop", 4.7, 201, "Tabiiy charm kamar.", true, false, true),
47
+ _p(34, "Biznes Portfel", "aksessuarlar", "Sumkalar", 520000, null, 0, OS, ["qora", "jigarrang"], "https://images.unsplash.com/photo-1548036328-c9fa89d128fa?w=600&h=800&fit=crop", 4.8, 134, "Premium biznes portfel.", true, true, true),
48
+ _p(35, "Klassik Shlyapa", "aksessuarlar", "Shlyapalar", 145000, 180000, 19, ["S", "M", "L"], ["qora", "kulrang", "jigarrang"], "https://images.unsplash.com/photo-1514327605112-b887c0e61c0a?w=600&h=800&fit=crop", 4.4, 67, "Klassik fedora shlyapa.", true, false, false),
49
+ _p(36, "Ipak Cho'ntak Ro'mol", "aksessuarlar", "Aksessuarlar", 85000, null, 0, OS, ["oq", "ko_k", "qizil", "pushti"], "https://images.unsplash.com/photo-1598879400638-bb93c904ef41?w=600&h=800&fit=crop", 4.5, 89, "100% ipak cho'ntak ro'mol.", true, false, false),
50
+ _p(37, "Manjet Tugmalari Gold", "aksessuarlar", "Aksessuarlar", 175000, 210000, 17, OS, ["sariq"], "https://images.unsplash.com/photo-1590548784585-643d2b9f2925?w=600&h=800&fit=crop", 4.6, 56, "Oltin rangli manjet tugmalari.", true, false, false),
51
+ _p(38, "Qo'lqop Charm", "aksessuarlar", "Aksessuarlar", 230000, null, 0, ["S", "M", "L"], ["qora", "jigarrang"], "https://images.unsplash.com/photo-1531163051823-59adbb4b5e8e?w=600&h=800&fit=crop", 4.8, 45, "Premium charm qo'lqop.", true, true, false),
52
+ _p(39, "Pul Qisqich Carbon", "aksessuarlar", "Aksessuarlar", 155000, 190000, 18, OS, ["qora"], "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&h=800&fit=crop", 4.5, 123, "Carbon fiber pul qisqich.", true, false, true),
53
+ _p(40, "Sport Sumka Duffle", "aksessuarlar", "Sumkalar", 345000, null, 0, OS, ["qora", "kulrang", "ko_k"], "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&h=800&fit=crop", 4.4, 167, "Keng sport duffle sumka.", true, false, false),
54
+ ];
55
+
56
+ async function seed() {
57
+ try {
58
+ await mongoose.connect(process.env.MONGODB_URI);
59
+ console.log('MongoDB ga ulandi...');
60
+
61
+ await Product.deleteMany({});
62
+ console.log('Eski mahsulotlar o\'chirildi.');
63
+
64
+ await Product.insertMany(PRODUCTS);
65
+ console.log(`✅ ${PRODUCTS.length} ta mahsulot qo'shildi!`);
66
+
67
+ await mongoose.disconnect();
68
+ console.log('Tayyor!');
69
+ } catch (err) {
70
+ console.error('❌ Xatolik:', err.message);
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ seed();
server/server.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config();
2
+ const express = require('express');
3
+ const mongoose = require('mongoose');
4
+ const cors = require('cors');
5
+ const path = require('path');
6
+
7
+ const app = express();
8
+ const PORT = process.env.PORT || 3000;
9
+
10
+ // Middleware
11
+ app.use(cors());
12
+ app.use(express.json());
13
+
14
+ // Static files (frontend)
15
+ app.use(express.static(path.join(__dirname, '..')));
16
+
17
+ // API Routes
18
+ app.use('/api/products', require('./routes/products'));
19
+ app.use('/api/auth', require('./routes/auth'));
20
+ app.use('/api/orders', require('./routes/orders'));
21
+ app.use('/api/settings', require('./routes/settings'));
22
+
23
+ // Connect to MongoDB and start server
24
+ mongoose.connect(process.env.MONGODB_URI)
25
+ .then(() => {
26
+ console.log('✅ MongoDB Atlas ga ulandi!');
27
+ app.listen(PORT, '0.0.0.0', () => {
28
+ console.log(`🚀 Server ishga tushdi: http://localhost:${PORT}`);
29
+ });
30
+ })
31
+ .catch(err => {
32
+ console.error('❌ MongoDB ulanish xatosi:', err.message);
33
+ process.exit(1);
34
+ });
sw.js ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const CACHE_NAME = 'mtextile-v3';
2
+ const ASSETS = [
3
+ '/',
4
+ '/index.html',
5
+ '/catalog.html',
6
+ '/product.html',
7
+ '/cart.html',
8
+ '/checkout.html',
9
+ '/wishlist.html',
10
+ '/profile.html',
11
+ '/css/global.css',
12
+ '/css/components.css',
13
+ '/css/pages.css',
14
+ '/js/store.js',
15
+ '/js/products.js',
16
+ '/js/app.js'
17
+ ];
18
+
19
+ self.addEventListener('install', (event) => {
20
+ event.waitUntil(
21
+ caches.open(CACHE_NAME)
22
+ .then((cache) => cache.addAll(ASSETS))
23
+ );
24
+ self.skipWaiting();
25
+ });
26
+
27
+ self.addEventListener('activate', (event) => {
28
+ event.waitUntil(
29
+ caches.keys().then(keys =>
30
+ Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
31
+ )
32
+ );
33
+ self.clients.claim();
34
+ });
35
+
36
+ self.addEventListener('fetch', (event) => {
37
+ event.respondWith(
38
+ fetch(event.request)
39
+ .then((response) => {
40
+ const clone = response.clone();
41
+ caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
42
+ return response;
43
+ })
44
+ .catch(() => caches.match(event.request))
45
+ );
46
+ });
wishlist.html ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uz">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="theme-color" content="#0D1F23">
8
+ <title>Sevimlilar — M-TEXTILE</title>
9
+ <link rel="stylesheet" href="css/global.css">
10
+ <link rel="stylesheet" href="css/components.css">
11
+ <link rel="stylesheet" href="css/pages.css">
12
+ <link rel="stylesheet" href="css/premium.css">
13
+ <link rel="manifest" href="manifest.json">
14
+
15
+ <!-- AOS CSS -->
16
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
17
+ </head>
18
+
19
+ <body>
20
+ <!-- Global Preloader -->
21
+ <div id="global-preloader" class="preloader">
22
+ <div class="preloader-spinner"></div>
23
+ </div>
24
+ <div class="page-wrapper">
25
+ <div class="page-content">
26
+ <section class="section-padding" style="padding-top: calc(var(--navbar-height) + 2rem);">
27
+ <div class="container">
28
+ <h1 style="margin-bottom: 2rem;">❤️ Sevimlilar</h1>
29
+ <div class="wishlist-grid stagger" id="wishlistGrid"></div>
30
+ <div id="emptyWishlist" style="display:none;">
31
+ <div class="empty-state">
32
+ <div class="empty-state-icon">❤️</div>
33
+ <h3 class="empty-state-title">Sevimlilar ro'yxati bo'sh</h3>
34
+ <p class="empty-state-text">Mahsulotlarni ❤️ tugmasi orqali saqlang</p>
35
+ <a href="catalog.html" class="btn btn-primary">Mahsulotlarni ko'rish</a>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </section>
40
+ </div>
41
+ </div>
42
+ <script src="js/products.js"></script>
43
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
44
+ <script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
45
+ <script src="js/firebase-config.js"></script>
46
+ <script src="js/api.js"></script>
47
+ <script src="js/store.js"></script>
48
+ <script src="js/app.js"></script>
49
+ <script>
50
+ document.addEventListener('DOMContentLoaded', () => { renderWishlist(); document.addEventListener('wishlistUpdated', renderWishlist); });
51
+ function renderWishlist() {
52
+ const wl = getWishlist();
53
+ const grid = document.getElementById('wishlistGrid');
54
+ const empty = document.getElementById('emptyWishlist');
55
+ grid.innerHTML = '';
56
+ if (wl.length === 0) { grid.style.display = 'none'; empty.style.display = 'block'; return; }
57
+ grid.style.display = ''; empty.style.display = 'none';
58
+ wl.forEach(id => {
59
+ const p = getProductById(id);
60
+ if (p) grid.appendChild(renderProductCard(p));
61
+ });
62
+ }
63
+ </script>
64
+ <script>
65
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').then(() => console.log('SW Registered')).catch(e => console.error('SW Error', e));
66
+ </script>
67
+
68
+ <!-- AOS JS -->
69
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
70
+ </body>
71
+
72
+ </html>