Initial commit

This commit is contained in:
Azmat Ali
2026-03-02 13:22:47 +05:30
commit e4c373f050
36 changed files with 7304 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
const Cart = (function () {
const STORAGE_KEY = "cart";
const TAX_RATE = 0.05;
let couponDiscount = 0;
// ─────────────────────────────
// Internal
// ─────────────────────────────
function getCart() {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
}
function saveCart(cart) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(cart));
window.dispatchEvent(new Event("cartUpdated"));
}
function findItem(cart, id) {
return cart.find((item) => item.id === id);
}
// ─────────────────────────────
// Public API
// ─────────────────────────────
function addItem(product, quantity = 1) {
const cart = getCart();
const existing = findItem(cart, product.id);
if (existing) {
existing.quantity += quantity;
} else {
cart.push({
id: product.id,
name: product.basic.name,
price: product.pricing.price,
image: product.media.mainImage,
quantity,
});
}
saveCart(cart);
}
function removeItem(id) {
const cart = getCart().filter((item) => item.id !== id);
saveCart(cart);
}
function updateQuantity(id, quantity) {
const cart = getCart();
const item = findItem(cart, id);
if (!item) return;
item.quantity = Math.max(1, quantity);
saveCart(cart);
}
function clear() {
localStorage.removeItem(STORAGE_KEY);
window.dispatchEvent(new Event("cartUpdated"));
}
function applyCoupon(code) {
if (code === "MEDI10") {
couponDiscount = 0.1;
window.dispatchEvent(new Event("cartUpdated"));
return true;
}
couponDiscount = 0;
window.dispatchEvent(new Event("cartUpdated"));
return false;
}
function getTotals() {
const cart = getCart();
const subtotal = cart.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const tax = subtotal * TAX_RATE;
const discount = subtotal * couponDiscount;
const total = subtotal - discount;
return {
subtotal,
tax,
discount,
total,
};
}
function getTotalCount() {
return getCart().reduce((sum, item) => sum + item.quantity, 0);
}
return {
getCart,
addItem,
removeItem,
updateQuantity,
clear,
applyCoupon,
getTotals,
getTotalCount,
};
})();
+43
View File
@@ -0,0 +1,43 @@
document.addEventListener("DOMContentLoaded", () => {
const cartItemsContainer = document.getElementById("checkout-items");
const subtotalEl = document.getElementById("checkout-subtotal");
const totalEl = document.getElementById("checkout-total");
const qrWrapper = document.getElementById("qr-wrapper");
const form = document.getElementById("checkout-form");
const cart = Cart.getCart();
if (cart.length === 0) {
cartItemsContainer.innerHTML = "<p>Your cart is empty.</p>";
return;
}
let subtotal = 0;
cart.forEach((item) => {
subtotal += item.price * item.quantity;
const row = document.createElement("div");
row.className = "summary-row";
row.innerHTML = `
<span>${item.name} × ${item.quantity}</span>
<span>₹${(item.price * item.quantity).toFixed(2)}</span>
`;
cartItemsContainer.appendChild(row);
});
const total = subtotal;
subtotalEl.textContent = `₹${subtotal.toFixed(2)}`;
totalEl.textContent = `₹${total.toFixed(2)}`;
// Unlock QR on valid form submit
form.addEventListener("submit", (e) => {
e.preventDefault();
if (!form.checkValidity()) return;
qrWrapper.classList.remove("blurred");
});
});
+49
View File
@@ -0,0 +1,49 @@
async function loadComponent(id, file) {
const el = document.getElementById(id);
if (!el) return;
const res = await fetch(file);
const html = await res.text();
el.innerHTML = html;
}
async function initComponents() {
console.log("init");
await Promise.all([
loadComponent("topbar-placeholder", "components/topbar.html"),
loadComponent("navbar-placeholder", "components/navbar.html"),
loadComponent("footer-placeholder", "components/footer.html"),
loadComponent("toast-placeholder", "components/toast.html"),
]);
// ✅ DOM is fully ready now — safe to run these
setActiveNavLink();
updateCartCount();
// ✅ Tell cart.js it can now render
document.dispatchEvent(new Event("components:ready"));
const menuToggle = document.getElementById("menu-toggle");
const mobileMenu = document.getElementById("mobile-menu");
if (!menuToggle || !mobileMenu) return;
menuToggle.addEventListener("click", () => {
mobileMenu.classList.toggle("active");
});
}
function setActiveNavLink() {
const page = window.location.pathname.split("/").pop() || "index.html";
document.querySelectorAll(".nav-links a").forEach((a) => {
a.parentElement.classList.toggle("active", a.getAttribute("href") === page);
});
}
function updateCartCount() {
const cart = JSON.parse(localStorage.getItem("cart")) || [];
const total = cart.reduce((s, i) => s + i.quantity, 0);
const el = document.getElementById("nav-cart-count");
if (el) el.textContent = total;
}
initComponents();
+300
View File
@@ -0,0 +1,300 @@
function renderRelatedProducts(product) {
const container = document.querySelector(".related-grid");
if (!container || !product.relatedProductIds) return;
const relatedProducts = product.relatedProductIds
.map((id) => PRODUCTS.find((p) => p.id === id))
.filter(Boolean);
container.innerHTML = relatedProducts
.map(
(p) => `
<div class="product-card">
<div class="product-img">
${
p.pricing.discountPercent
? `<div class="product-disc">-${p.pricing.discountPercent}%</div>`
: ""
}
<img
src="${p.media.mainImage}"
alt="${p.basic.name}"
width="200"
height="200"
loading="lazy"
decoding="async"
draggable="false" />
</div>
<div class="product-body">
<div class="product-name">${p.basic.name}</div>
<div class="product-rating">★★★★★</div>
<div class="product-price">
<span class="rp-now">₹${p.pricing.price}</span>
${
p.pricing.originalPrice
? `<span class="rp-old">₹${p.pricing.originalPrice}</span>`
: ""
}
</div>
<a href="product.html?id=${
p.id
}" target="_blank" class="product-add-btn">
View Product
</a>
</div>
</div>
`
)
.join("");
}
function renderProduct(productId) {
const product = PRODUCTS.find((p) => p.id === productId);
if (!product) {
console.error("Product not found:", productId);
return;
}
// =========================
// BASIC INFO
// =========================
const { basic, pricing, media, rating } = product;
// Title
document.title = product.seo?.title || basic.name;
document.querySelector(".product-title").textContent = basic.name;
// Breadcrumb
const breadcrumbCurrent = document.querySelector(".breadcrumb-bar .current");
if (breadcrumbCurrent)
breadcrumbCurrent.textContent = basic.shortName || basic.name;
// Image
const mainImg = document.querySelector(".gallery-main img");
if (mainImg && media?.mainImage) {
mainImg.src = media.mainImage;
mainImg.alt = basic.name;
}
// =========================
// PRICING
// =========================
document.querySelector(".price-current").textContent = `₹${pricing.price}`;
document.querySelector(".price-original").textContent = pricing.originalPrice
? `₹${pricing.originalPrice}`
: "";
document.querySelector(".price-badge").textContent = pricing.discountPercent
? `-${pricing.discountPercent}%`
: "";
// Stock
const stockStatus = document.querySelector(".stock-status");
if (stockStatus) {
stockStatus.innerHTML = `
<span class="stock-dot"></span>
${pricing.stockStatus} — Ships within ${pricing.shipsWithin}
`;
}
// =========================
// RATING
// =========================
if (rating) {
document.querySelector(".rating-count").textContent =
rating.average.toFixed(1);
document.querySelector(
".review-link"
).textContent = `${rating.totalReviews} Reviews`;
const soldText = document.querySelector(".rating-row span[style]");
if (soldText) soldText.textContent = `${rating.sold} sold`;
// Reviews tab count
const reviewTabBtn = document.querySelectorAll(".tab-nav-btn")[2];
if (reviewTabBtn)
reviewTabBtn.textContent = `Reviews (${rating.totalReviews})`;
// Big rating
const scoreEl = document.querySelector(".rating-big .score");
if (scoreEl) scoreEl.textContent = rating.average.toFixed(1);
const totalEl = document.querySelector(".rating-big .total");
if (totalEl)
totalEl.textContent = `Based on ${rating.totalReviews} reviews`;
// Rating bars
const bars = document.querySelectorAll(".rating-bar-row");
bars.forEach((bar) => {
const label = bar.querySelector(".bar-label").textContent.trim()[0];
const count = rating.breakdown[label] || 0;
const percent = rating.totalReviews
? (count / rating.totalReviews) * 100
: 0;
bar.querySelector(".bar-fill").style.width = `${percent}%`;
bar.querySelector(".bar-count").textContent = count;
});
}
// =========================
// SUMMARY
// =========================
document.querySelector(".product-short-desc").textContent =
product.summary.shortDescription;
// Benefit pills
const pillsContainer = document.querySelector(".benefit-pills");
if (pillsContainer) {
pillsContainer.innerHTML = product.summary.benefitPills
.map((p) => `<span class="benefit-pill">${p.icon} ${p.text}</span>`)
.join("");
}
// Meta
document.querySelector(".meta-row span:last-child").textContent = basic.sku;
document
.querySelectorAll(".meta-row")[1]
.querySelector("span:last-child").textContent = basic.weight;
document
.querySelectorAll(".meta-row")[2]
.querySelector("span:last-child").textContent = `₹${pricing.price}`;
// Categories
const categoryContainer = document
.querySelectorAll(".meta-row")[3]
.querySelector("div");
categoryContainer.innerHTML = basic.category
.map((c) => `<span class="meta-tag">${c}</span>`)
.join("");
// Tags
const tagContainer = document
.querySelectorAll(".meta-row")[4]
.querySelector("div");
tagContainer.innerHTML = basic.tags
.map((t) => `<span class="meta-tag">${t}</span>`)
.join("");
// Ideal for
document
.querySelectorAll(".meta-row")[5]
.querySelector("span:last-child").textContent = basic.idealFor;
// =========================
// DESCRIPTION TAB
// =========================
const descTab = product.descriptionTab;
document.querySelector(".desc-lead").textContent = descTab.lead;
const descBody = document.querySelector(".desc-body");
descBody.innerHTML = descTab.paragraphs.map((p) => `<p>${p}</p>`).join("");
// Features
const featuresContainer = document.querySelector(".desc-features");
featuresContainer.innerHTML = descTab.features
.map(
(f) => `
<div class="desc-feature">
<div class="feat-icon">${f.icon}</div>
<div class="feat-text">
<strong>${f.title}</strong>
<span>${f.text}</span>
</div>
</div>
`
)
.join("");
// How To Use
const howSteps = document.querySelector(".how-steps");
howSteps.innerHTML = descTab.howToUse
.map(
(step, i) => `
<div class="how-step">
<div class="step-num">${i + 1}</div>
${step}
</div>
`
)
.join("");
// =========================
// INGREDIENTS TAB
// =========================
const ingTab = product.ingredientsTab;
document
.querySelectorAll(".tab-pane")[1]
.querySelector(".desc-lead").textContent = ingTab.lead;
const ingGrid = document.querySelector(".ingredients-grid");
ingGrid.innerHTML = ingTab.ingredients
.map(
(i) => `
<div class="ingredient-card">
<div class="ing-icon">${i.icon}</div>
<div class="ing-name">${i.name}</div>
<div class="ing-role">${i.role}</div>
</div>
`
)
.join("");
document.querySelector(
".full-ingredients"
).innerHTML = `<strong>Full Ingredient List:</strong> ${ingTab.fullIngredients}`;
// =========================
// REVIEWS TAB
// =========================
const reviewList = document.querySelector(".review-list");
reviewList.innerHTML = product.reviews
.map(
(r) => `
<div class="review-card">
<div class="review-header">
<div class="reviewer-info">
<div class="reviewer-avatar">${r.avatarLetter}</div>
<div>
<div class="reviewer-name">${r.name}</div>
<div class="reviewer-date">${r.date}</div>
</div>
</div>
<div class="review-stars">${"★".repeat(r.rating)}${"☆".repeat(
5 - r.rating
)}</div>
</div>
<div class="review-title">${r.title}</div>
<div class="review-text">${r.text}</div>
${
r.verified
? `<div class="verified-badge">✓ Verified Purchase</div>`
: ""
}
</div>
`
)
.join("");
// =========================
// SHIPPING TAB
// =========================
const shippingTab = product.shipping;
const shippingLead = document
.querySelectorAll(".tab-pane")[3]
.querySelector(".desc-lead");
shippingLead.textContent = shippingTab.lead;
const shippingBody = document
.querySelectorAll(".tab-pane")[3]
.querySelector(".desc-body");
shippingBody.innerHTML = shippingTab.policies
.map((p) => `<p><strong>${p.title}:</strong> ${p.text}</p>`)
.join("");
renderRelatedProducts(product);
}
File diff suppressed because it is too large Load Diff