Spaces:
Sleeping
Sleeping
Initial deploy via assistant API
Browse files- .dockerignore +5 -0
- Dockerfile +17 -0
- admin.html +209 -0
- cart.html +75 -0
- catalog.html +145 -0
- checkout.html +191 -0
- css/admin.css +212 -0
- css/components.css +1563 -0
- css/global.css +791 -0
- css/pages.css +1144 -0
- css/premium.css +445 -0
- fix_ui.js +12 -0
- index.html +237 -0
- inject_pwa.js +13 -0
- inject_ui.js +32 -0
- js/admin.js +262 -0
- js/api.js +134 -0
- js/app.js +852 -0
- js/cart.js +107 -0
- js/catalog.js +237 -0
- js/checkout.js +187 -0
- js/firebase-config.js +77 -0
- js/product-detail.js +239 -0
- js/products.js +103 -0
- js/profile.js +147 -0
- js/store.js +177 -0
- manifest.json +21 -0
- package-lock.json +1175 -0
- package.json +23 -0
- product.html +168 -0
- profile.html +105 -0
- server/models/Order.js +29 -0
- server/models/Product.js +21 -0
- server/models/Settings.js +10 -0
- server/models/User.js +26 -0
- server/routes/auth.js +106 -0
- server/routes/orders.js +52 -0
- server/routes/products.js +65 -0
- server/routes/settings.js +47 -0
- server/seed.js +75 -0
- server/server.js +34 -0
- sw.js +46 -0
- wishlist.html +72 -0
.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>
|