feat: add product catalog and product detail pages
This commit is contained in:
parent
9a3a106fca
commit
0d5fd84762
13 changed files with 4256 additions and 555 deletions
BIN
assets/images/asgaard_sofa.png
Normal file
BIN
assets/images/asgaard_sofa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 500 KiB |
BIN
assets/images/outdoor_sofa_set.png
Normal file
BIN
assets/images/outdoor_sofa_set.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -28,7 +28,9 @@
|
|||
</head>
|
||||
<body class="bg-white font-sans text-gray-800">
|
||||
<!-- Header -->
|
||||
<header class="absolute w-full h-28 top-0 left-0 bg-white shadow-lg z-50">
|
||||
<header
|
||||
class="absolute w-full h-28 top-0 left-0 bg-white shadow-[0_8px_24px_rgba(0,0,0,0.06)] border-b border-black/10 z-50"
|
||||
>
|
||||
<nav class="h-full">
|
||||
<div
|
||||
class="max-w-7xl mx-auto h-full flex items-center justify-between px-5"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@
|
|||
</head>
|
||||
<body class="bg-white font-sans text-gray-800">
|
||||
<!-- Header -->
|
||||
<header class="absolute w-full h-28 top-0 left-0 bg-white shadow-lg z-50">
|
||||
<header
|
||||
class="absolute w-full h-28 top-0 left-0 bg-white shadow-[0_8px_24px_rgba(0,0,0,0.06)] border-b border-black/10 z-50"
|
||||
>
|
||||
<nav class="h-full">
|
||||
<div
|
||||
class="max-w-7xl mx-auto h-full flex items-center justify-between px-5"
|
||||
|
|
|
|||
1678
data/products.json
Normal file
1678
data/products.json
Normal file
File diff suppressed because it is too large
Load diff
384
index.html
384
index.html
|
|
@ -109,7 +109,9 @@
|
|||
</head>
|
||||
<body class="bg-gray-50 font-sans text-gray-800">
|
||||
<!-- Header -->
|
||||
<header class="absolute w-full h-28 top-0 left-0 bg-white shadow-lg z-50">
|
||||
<header
|
||||
class="absolute w-full h-28 top-0 left-0 bg-white shadow-[0_8px_24px_rgba(0,0,0,0.06)] border-b border-black/10 z-50"
|
||||
>
|
||||
<nav class="h-full">
|
||||
<div
|
||||
class="max-w-7xl mx-auto h-full flex items-center justify-between px-5"
|
||||
|
|
@ -431,213 +433,231 @@
|
|||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12"
|
||||
>
|
||||
<!-- Product Card 1: Lounge Chairs -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/lounge_chair.jpg"
|
||||
alt="Lounge Chairs"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=seating" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/lounge_chair.jpg"
|
||||
alt="Lounge Chairs"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Lounge Chairs
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Stylish cafe chair
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Lounge Chairs
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Stylish cafe chair
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Product Card 2: Office Chairs -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/office_chair.jpg"
|
||||
alt="Office Chairs"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=seating" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/office_chair.jpg"
|
||||
alt="Office Chairs"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Office Chairs
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Luxury big sofa
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Office Chairs
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Luxury big sofa
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Product Card 3: Conference Room -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/conference_rooms.jpg"
|
||||
alt="Conference Room"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=tables" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/conference_rooms.jpg"
|
||||
alt="Conference Room"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Conference Room
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Tables
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Conference Room
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Tables
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Product Card 4: Storage -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/storage.jpg"
|
||||
alt="Storage"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=storage" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/storage.jpg"
|
||||
alt="Storage"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Storage
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Outdoor bar table and stool
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Storage
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Outdoor bar table and stool
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Product Card 5: Kitchen -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/kitchen.jpg"
|
||||
alt="Kitchen"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=workspace" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/kitchen.jpg"
|
||||
alt="Kitchen"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Kitchen
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Night lamp
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Kitchen
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Night lamp
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Product Card 6: Chairs -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/chairs.jpg"
|
||||
alt="Chairs"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=seating" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/chairs.jpg"
|
||||
alt="Chairs"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Chairs
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Small mug
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Chairs
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Small mug
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Product Card 7: Pods -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/pods.jpg"
|
||||
alt="Pods"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=seating" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/pods.jpg"
|
||||
alt="Pods"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Pods
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Cute bed set
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Pods
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Cute bed set
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Product Card 8: Potty -->
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/potty.jpg"
|
||||
alt="Potty"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<a href="product-catalog.html?category=workspace" class="block">
|
||||
<div
|
||||
class="product-card bg-light-bg rounded-lg overflow-hidden h-[446px]"
|
||||
>
|
||||
<div class="product-card-image h-[301px] bg-gray-200">
|
||||
<img
|
||||
src="assets/images/potty.jpg"
|
||||
alt="Potty"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Potty
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Minimalist flower pot
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 h-[145px] flex flex-col justify-center">
|
||||
<h3
|
||||
class="product-card-title font-poppins font-semibold text-xl text-gray-800 mb-2"
|
||||
>
|
||||
Potty
|
||||
</h3>
|
||||
<p
|
||||
class="product-card-description font-poppins font-medium text-sm text-gray-500"
|
||||
>
|
||||
Minimalist flower pot
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Call to Action Button -->
|
||||
<div class="text-center">
|
||||
<button
|
||||
class="bg-white border border-uc-gold text-uc-gold font-playfair font-semibold text-base px-8 py-3 rounded-lg hover:bg-uc-gold hover:text-white transition-all"
|
||||
>
|
||||
Browse product catalog
|
||||
</button>
|
||||
<a href="product-catalog.html">
|
||||
<button
|
||||
class="bg-white border border-uc-gold text-uc-gold font-playfair font-semibold text-base px-8 py-3 rounded-lg hover:bg-uc-gold hover:text-white transition-all"
|
||||
>
|
||||
Browse product catalog
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
425
product-catalog.html
Normal file
425
product-catalog.html
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Product Catalog - KHY</title>
|
||||
<link rel="stylesheet" href="styles/main.css" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Playfair+Display:wght@100;200;300;400;500;600;700&family=Poppins:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body class="bg-white font-sans text-gray-800">
|
||||
<!-- Header -->
|
||||
<header class="absolute w-full h-28 top-0 left-0 bg-white shadow-[0_8px_24px_rgba(0,0,0,0.06)] border-b border-black/10 z-50">
|
||||
<nav class="h-full">
|
||||
<div
|
||||
class="max-w-7xl mx-auto h-full flex items-center justify-between px-5"
|
||||
>
|
||||
<!-- Logo Section -->
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
src="assets/images/khy_logo.png"
|
||||
alt="KHY Logo"
|
||||
class="h-16 w-auto drop-shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<ul class="flex space-x-10">
|
||||
<li>
|
||||
<a
|
||||
href="index.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Home</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#products"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Products</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#projects-button"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Projects</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#clients"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Clients</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#about"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>About</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="contact.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Contact</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="blog.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Blog</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="quote.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Quote</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Hero Section -->
|
||||
<section class="relative h-80 mt-28">
|
||||
<!-- Background Image -->
|
||||
<div class="absolute inset-0 w-full h-full">
|
||||
<img
|
||||
src="assets/images/potty.jpg"
|
||||
alt="Product catalog background"
|
||||
class="w-full h-full object-cover object-center"
|
||||
style="filter: blur(3px)"
|
||||
/>
|
||||
|
||||
<!-- White Overlay -->
|
||||
<div class="absolute inset-0 bg-white bg-opacity-60"></div>
|
||||
|
||||
<!-- Overlay Content -->
|
||||
<div
|
||||
class="absolute z-10 inset-0 flex items-center justify-center text-center text-black"
|
||||
>
|
||||
<h1
|
||||
class="font-playfair font-medium text-3xl md:text-4xl lg:text-5xl leading-tight"
|
||||
>
|
||||
Product catalog
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<!-- Product Controls Section -->
|
||||
<section class="relative w-full h-25 bg-floral-white py-6">
|
||||
<div class="max-w-7xl mx-auto px-5">
|
||||
<div class="flex justify-between items-center">
|
||||
<!-- Left side: Filter and View Controls -->
|
||||
<div class="flex items-center gap-4 relative">
|
||||
<!-- Filter Button -->
|
||||
<button
|
||||
id="filter-toggle"
|
||||
class="flex items-center gap-2 px-4 py-2 border border-light-silver rounded-md bg-white hover:bg-light-bg transition-colors"
|
||||
>
|
||||
<span class="text-lg">☰</span>
|
||||
<span class="font-poppins text-lg text-black">Filter</span>
|
||||
</button>
|
||||
|
||||
<!-- Categories Dropdown -->
|
||||
<div
|
||||
id="filter-dropdown"
|
||||
class="hidden absolute top-12 left-0 w-56 bg-white border border-light-silver rounded-md shadow-lg p-3 space-y-2 z-20"
|
||||
>
|
||||
<!-- Categories will be injected dynamically -->
|
||||
<div id="filter-categories" class="space-y-2"></div>
|
||||
<div class="pt-2 flex justify-end gap-2">
|
||||
<button id="filter-clear" class="text-quick-silver text-sm hover:text-black">Clear</button>
|
||||
<button id="filter-apply" class="text-uc-gold text-sm hover:underline">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View Toggle Buttons (removed) -->
|
||||
|
||||
<!-- Results Count -->
|
||||
<span class="font-poppins text-base text-quick-silver"
|
||||
>Showing 1–16 of 32 results</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Right side: Sort Dropdown -->
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Sort Dropdown -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-poppins text-lg text-black">Sort by:</span>
|
||||
<select
|
||||
class="border border-light-silver rounded px-2 py-1 bg-white"
|
||||
>
|
||||
<option value="default">Default</option>
|
||||
<option value="name-asc">Name: A to Z</option>
|
||||
<option value="name-desc">Name: Z to A</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Product Grid Section -->
|
||||
<section class="relative w-full bg-white py-12">
|
||||
<div class="max-w-7xl mx-auto px-5">
|
||||
<!-- Product Grid -->
|
||||
<div
|
||||
id="product-grid"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12"
|
||||
>
|
||||
<!-- Products will be loaded dynamically -->
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div id="pagination" class="flex justify-center items-center space-x-10">
|
||||
<!-- Pagination will be loaded dynamically -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section class="relative bg-linen py-16">
|
||||
<div class="max-w-7xl mx-auto px-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
<!-- High Quality -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="w-12 h-12">
|
||||
<img
|
||||
src="assets/icons/trophy.png"
|
||||
alt="Trophy"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="font-playfair font-semibold text-2xl text-black mb-1"
|
||||
>
|
||||
High Quality
|
||||
</h3>
|
||||
<p class="font-playfair font-normal text-lg text-gray-500">
|
||||
crafted from top materials
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warranty Protection -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="w-12 h-12">
|
||||
<img
|
||||
src="assets/icons/warranty.png"
|
||||
alt="Guarantee"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="font-playfair font-semibold text-2xl text-black mb-1"
|
||||
>
|
||||
Warranty Protection
|
||||
</h3>
|
||||
<p class="font-playfair font-normal text-lg text-gray-500">
|
||||
Over 2 years
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Free Shipping -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="w-12 h-12">
|
||||
<img
|
||||
src="assets/icons/shipping.png"
|
||||
alt="Shipping"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="font-playfair font-semibold text-2xl text-black mb-1"
|
||||
>
|
||||
Free Shipping
|
||||
</h3>
|
||||
<p class="font-playfair font-normal text-lg text-gray-500">
|
||||
Order over 150 $
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 24/7 Support -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="w-12 h-12">
|
||||
<img
|
||||
src="assets/icons/support.png"
|
||||
alt="Support"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="font-playfair font-semibold text-2xl text-black mb-1"
|
||||
>
|
||||
24 / 7 Support
|
||||
</h3>
|
||||
<p class="font-playfair font-normal text-lg text-gray-500">
|
||||
Dedicated support
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white">
|
||||
<!-- Main Footer Content -->
|
||||
<div class="max-w-7xl mx-auto px-5 py-16">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
<!-- Company Information -->
|
||||
<div class="space-y-2">
|
||||
<!-- Logo -->
|
||||
<div class="w-16 h-20">
|
||||
<img
|
||||
src="assets/images/khy_logo.png"
|
||||
alt="khy"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<p
|
||||
class="font-playfair font-normal text-base leading-relaxed text-gray-600"
|
||||
>
|
||||
5 Labone Crescent, Greater Accra, Ghana
|
||||
</p>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div class="space-y-1">
|
||||
<!-- Phone -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<img
|
||||
src="assets/images/phone.png"
|
||||
alt="Phone"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
<span class="font-playfair font-normal text-base text-gray-800">
|
||||
+233 (555) 76677
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<img src="assets/images/mail.png" alt="Email" class="w-4 h-4" />
|
||||
<span class="font-playfair font-normal text-base text-gray-800">
|
||||
design@khyltd.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="space-y-6">
|
||||
<h3
|
||||
class="font-playfair font-normal text-md leading-relaxed tracking-wider text-eerie-black uppercase"
|
||||
>
|
||||
Quick Links
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Products
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help -->
|
||||
<div class="space-y-6">
|
||||
<h3
|
||||
class="font-playfair font-normal text-md leading-relaxed tracking-wider text-eerie-black uppercase"
|
||||
>
|
||||
Help
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Contact Us
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Privacy Policies
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Terms and Conditions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Newsletter -->
|
||||
<div class="space-y-6">
|
||||
<h3
|
||||
class="font-playfair font-normal text-md leading-relaxed tracking-wider text-eerie-black uppercase"
|
||||
>
|
||||
Newsletter
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter Your Email Address"
|
||||
class="flex-1 font-playfair font-normal text-sm leading-relaxed tracking-wider text-taupe-gray bg-transparent border-b border-black focus:outline-none focus:border-black"
|
||||
/>
|
||||
<button
|
||||
class="font-playfair font-normal text-sm leading-relaxed tracking-wider text-gray-800 border-b border-black hover:text-black transition-colors"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copyright Section -->
|
||||
<div class="border-t border-light-silver">
|
||||
<div class="max-w-7xl mx-auto px-5 py-4">
|
||||
<p
|
||||
class="font-playfair font-normal text-xs leading-relaxed text-davys-grey"
|
||||
>
|
||||
© 2025 khy. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="scripts/main.js"></script>
|
||||
<script src="scripts/products.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
592
product-detail.html
Normal file
592
product-detail.html
Normal file
|
|
@ -0,0 +1,592 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Product Detail - KHY</title>
|
||||
<link rel="stylesheet" href="styles/main.css" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Playfair+Display:wght@100;200;300;400;500;600;700&family=Poppins:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body class="bg-white font-sans text-gray-800">
|
||||
<!-- Header -->
|
||||
<header
|
||||
class="absolute w-full h-28 top-0 left-0 bg-white shadow-[0_8px_24px_rgba(0,0,0,0.06)] border-b border-black/10 z-50"
|
||||
>
|
||||
<nav class="h-full">
|
||||
<div
|
||||
class="max-w-7xl mx-auto h-full flex items-center justify-between px-5"
|
||||
>
|
||||
<!-- Logo Section -->
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
src="assets/images/khy_logo.png"
|
||||
alt="KHY Logo"
|
||||
class="h-16 w-auto drop-shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<ul class="flex space-x-10">
|
||||
<li>
|
||||
<a
|
||||
href="index.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Home</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#products"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Products</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#projects-button"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Projects</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#clients"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Clients</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="index.html#about"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>About</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="contact.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Contact</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="blog.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Blog</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="quote.html"
|
||||
class="nav-link text-black hover:text-gray-600 font-playfair text-md font-extralight tracking-wider transition-colors text-shadow-default"
|
||||
>Quote</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="pt-28">
|
||||
<!-- Product Details Section -->
|
||||
<section class="relative w-full bg-white py-6">
|
||||
<div class="max-w-7xl mx-auto px-5">
|
||||
<div class="flex items-center">
|
||||
<h1 class="font-playfair font-normal text-base text-quick-silver">
|
||||
Product Details
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Main Product Section -->
|
||||
<section class="relative w-full bg-white py-8">
|
||||
<div class="max-w-7xl mx-auto px-5">
|
||||
<div class="flex gap-8">
|
||||
<!-- Left Section - Product Images -->
|
||||
<div class="flex gap-4">
|
||||
<!-- Thumbnails -->
|
||||
<div class="flex flex-col gap-4 w-32">
|
||||
<div
|
||||
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="assets/images/outdoor_sofa_set.png"
|
||||
alt="Outdoor sofa set"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="assets/images/outdoor_sofa_set.png"
|
||||
alt="Outdoor sofa set 2"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="assets/images/outdoor_sofa_set.png"
|
||||
alt="Stuart sofa"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="w-32 h-32 bg-floral-white rounded-lg overflow-hidden cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src="assets/images/outdoor_sofa_set.png"
|
||||
alt="Maya sofa three seater"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Product Image -->
|
||||
<div
|
||||
class="w-[500px] h-[500px] bg-floral-white rounded-lg overflow-hidden"
|
||||
>
|
||||
<img
|
||||
src="assets/images/asgaard_sofa.png"
|
||||
alt="Asgaard sofa"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Section - Product Details -->
|
||||
<div class="w-[500px]">
|
||||
<!-- Product Title -->
|
||||
<h1 class="font-playfair font-normal text-4xl text-black mb-8">
|
||||
Asgaard sofa
|
||||
</h1>
|
||||
|
||||
<!-- Product Description -->
|
||||
<p
|
||||
class="font-playfair font-normal text-sm text-black mb-12 max-w-md"
|
||||
>
|
||||
Setting the bar as one of the loudest speakers in its class, the
|
||||
Kilburn is a compact, stout-hearted hero with a well-balanced
|
||||
audio which boasts a clear midrange and extended highs for a
|
||||
sound.
|
||||
</p>
|
||||
|
||||
<!-- Size Options -->
|
||||
<div class="mb-8">
|
||||
<h3
|
||||
class="font-playfair font-normal text-sm text-quick-silver mb-4"
|
||||
>
|
||||
Size
|
||||
</h3>
|
||||
<div class="grid grid-cols-6 gap-2" id="size-buttons-container">
|
||||
<button
|
||||
class="w-8 h-8 bg-uc-gold text-white font-poppins font-normal text-sm rounded flex items-center justify-center size-button"
|
||||
>
|
||||
L
|
||||
</button>
|
||||
<button
|
||||
class="w-8 h-8 bg-floral-white text-black font-poppins font-normal text-sm rounded flex items-center justify-center size-button"
|
||||
>
|
||||
XL
|
||||
</button>
|
||||
<button
|
||||
class="w-8 h-8 bg-floral-white text-black font-poppins font-normal text-sm rounded flex items-center justify-center size-button"
|
||||
>
|
||||
XS
|
||||
</button>
|
||||
<button
|
||||
class="w-8 h-8 bg-floral-white text-black font-poppins font-normal text-sm rounded flex items-center justify-center size-button"
|
||||
>
|
||||
S
|
||||
</button>
|
||||
<button
|
||||
class="w-8 h-8 bg-floral-white text-black font-poppins font-normal text-sm rounded flex items-center justify-center size-button"
|
||||
>
|
||||
M
|
||||
</button>
|
||||
<button
|
||||
class="w-8 h-8 bg-floral-white text-black font-poppins font-normal text-sm rounded flex items-center justify-center size-button"
|
||||
>
|
||||
2XL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color Options -->
|
||||
<div class="mb-8">
|
||||
<h3
|
||||
class="font-playfair font-normal text-sm text-quick-silver mb-4"
|
||||
>
|
||||
Color
|
||||
</h3>
|
||||
<div class="flex gap-4">
|
||||
<button
|
||||
class="w-8 h-8 bg-purple-500 rounded-full border-2 border-black"
|
||||
></button>
|
||||
<button class="w-8 h-8 bg-black rounded-full"></button>
|
||||
<button class="w-8 h-8 bg-uc-gold rounded-full"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quantity and Action Buttons -->
|
||||
<div class="flex gap-6 mb-8">
|
||||
<!-- Quantity Selector -->
|
||||
<div
|
||||
class="inline-flex items-center justify-between w-[180px] h-[64px] min-h-[64px] bg-white border border-quick-silver rounded-[15px] px-4 box-border shadow-sm hover:shadow-md transition-all duration-200"
|
||||
>
|
||||
<button
|
||||
id="qty-decr"
|
||||
aria-label="Decrease quantity"
|
||||
class="font-playfair font-light text-[20px] leading-none text-black w-8 h-8 flex items-center justify-center rounded-lg hover:bg-light-bg transition-colors cursor-pointer"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span
|
||||
id="qty-value"
|
||||
class="font-playfair font-light text-[20px] leading-none text-black"
|
||||
>1</span
|
||||
>
|
||||
<button
|
||||
id="qty-incr"
|
||||
aria-label="Increase quantity"
|
||||
class="font-playfair font-light text-[20px] leading-none text-black w-8 h-8 flex items-center justify-center rounded-lg hover:bg-light-bg transition-colors cursor-pointer"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<button
|
||||
class="inline-flex items-center justify-center w-[380px] h-[64px] min-h-[64px] bg-white text-black font-playfair font-light text-[20px] leading-none rounded-[15px] border border-quick-silver hover:bg-uc-gold hover:text-white hover:border-uc-gold hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200 box-border whitespace-nowrap"
|
||||
>
|
||||
Add To Quote
|
||||
</button>
|
||||
<button
|
||||
class="inline-flex items-center justify-center w-[440px] h-[64px] min-h-[64px] bg-white text-black font-playfair font-light text-[20px] leading-none rounded-[15px] border border-quick-silver hover:bg-black hover:text-white hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-200 box-border whitespace-nowrap"
|
||||
>
|
||||
Compare Products
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="w-full h-px bg-light-silver mb-8"></div>
|
||||
|
||||
<!-- Product Metadata -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver w-20"
|
||||
>Model No.</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-medium text-base text-quick-silver mx-2"
|
||||
>:</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver"
|
||||
>SS001</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver w-20"
|
||||
>Category</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-medium text-base text-quick-silver mx-2"
|
||||
>:</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver"
|
||||
>Sofas</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver w-20"
|
||||
>Tags</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-medium text-base text-quick-silver mx-2"
|
||||
>:</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver"
|
||||
>Sofa, Chair, Home, Shop</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver w-20"
|
||||
>Dimension</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-medium text-base text-quick-silver mx-2"
|
||||
>:</span
|
||||
>
|
||||
<span
|
||||
class="font-poppins font-normal text-base text-quick-silver"
|
||||
>180cm x 85cm x 75cm</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Product Detail Tabs Section -->
|
||||
<section id="product-tabs" class="w-full bg-white">
|
||||
<div class="border-t border-light-silver"></div>
|
||||
<div class="max-w-7xl mx-auto px-5 py-10">
|
||||
<!-- Tabs -->
|
||||
<div class="flex justify-center gap-12 mb-8">
|
||||
<button class="font-playfair text-2xl text-black">
|
||||
Description
|
||||
</button>
|
||||
<button class="font-playfair text-2xl text-quick-silver">
|
||||
Additional Information
|
||||
</button>
|
||||
<button class="font-playfair text-2xl text-quick-silver">
|
||||
Reviews [5]
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Copy -->
|
||||
<div class="max-w-5xl mx-auto space-y-6">
|
||||
<p
|
||||
class="font-playfair text-base leading-6 text-[#333333] text-justify"
|
||||
>
|
||||
Embodying the raw, wayward spirit of rock ‘n’ roll, the Kilburn
|
||||
portable active stereo speaker takes the unmistakable look and
|
||||
sound of Marshall, unplugs the chords, and takes the show on the
|
||||
road.
|
||||
</p>
|
||||
<p
|
||||
class="font-playfair text-base leading-6 text-[#333333] text-justify"
|
||||
>
|
||||
Weighing in under 7 pounds, the Kilburn is a lightweight piece of
|
||||
vintage styled engineering. Setting the bar as one of the loudest
|
||||
speakers in its class, the Kilburn is a compact, stout-hearted
|
||||
hero with a well-balanced audio which boasts a clear midrange and
|
||||
extended highs for a sound that is both articulate and pronounced.
|
||||
The analogue knobs allow you to fine tune the controls to your
|
||||
personal preferences while the guitar-influenced leather strap
|
||||
enables easy and stylish travel.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Images -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-10">
|
||||
<div class="rounded-[10px] bg-floral-white p-6">
|
||||
<img
|
||||
src="assets/images/asgaard_sofa.png"
|
||||
alt="Sofa variant left"
|
||||
class="w-full h-80 object-contain mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div class="rounded-[10px] bg-floral-white p-6">
|
||||
<img
|
||||
src="assets/images/asgaard_sofa.png"
|
||||
alt="Sofa variant right"
|
||||
class="w-full h-80 object-contain mx-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- full-width divider before related products -->
|
||||
<div class="w-full border-t border-light-silver"></div>
|
||||
|
||||
<!-- Related Products Section -->
|
||||
<section id="related-products" class="w-full bg-white py-14">
|
||||
<div class="max-w-7xl mx-auto px-5">
|
||||
<h2
|
||||
class="font-playfair text-4xl font-medium text-center text-black mb-10"
|
||||
>
|
||||
Related Products
|
||||
</h2>
|
||||
|
||||
<div
|
||||
id="related-grid"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8"
|
||||
>
|
||||
<!-- Related items will be injected here -->
|
||||
</div>
|
||||
|
||||
<div class="mt-10 flex justify-center">
|
||||
<button
|
||||
id="related-show-more"
|
||||
class="w-[245px] h-12 border border-uc-gold rounded-md bg-white font-playfair font-semibold text-base text-uc-gold hover:bg-uc-gold hover:text-white transition-colors"
|
||||
>
|
||||
Show More
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- full-width divider after related products -->
|
||||
<div class="w-full border-t border-light-silver"></div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-black border-opacity-20">
|
||||
<!-- Main Footer Content -->
|
||||
<div class="max-w-7xl mx-auto px-5 py-16">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
<!-- Company Information -->
|
||||
<div class="space-y-1">
|
||||
<!-- Logo -->
|
||||
<div class="w-16 h-20 -mt-6">
|
||||
<a
|
||||
href="index.html"
|
||||
aria-label="Go to KHY home"
|
||||
title="KHY Home"
|
||||
class="inline-block w-full h-full group focus:outline-none focus-visible:ring-2 focus-visible:ring-uc-gold rounded-md transition"
|
||||
>
|
||||
<img
|
||||
src="assets/images/khy_logo.png"
|
||||
alt="KHY logo"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-contain transition-transform duration-300 group-hover:scale-105"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<p
|
||||
class="font-playfair font-normal text-base leading-relaxed text-gray-600 -mt-4"
|
||||
>
|
||||
5 Labone Crescent, Greater Accra, Ghana
|
||||
</p>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div class="space-y-1 -mt-2">
|
||||
<!-- Phone -->
|
||||
<div class="flex items-center space-x-3 -mt-1">
|
||||
<img
|
||||
src="assets/images/phone.png"
|
||||
alt="Phone"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
<span class="font-playfair font-normal text-base text-gray-800">
|
||||
+233 (555) 76677
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="flex items-center space-x-3 -mt-1">
|
||||
<img src="assets/images/mail.png" alt="Email" class="w-4 h-4" />
|
||||
<span class="font-playfair font-normal text-base text-gray-800">
|
||||
design@khyltd.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="space-y-6">
|
||||
<h3
|
||||
class="font-playfair font-normal text-md leading-relaxed tracking-wider text-eerie-black uppercase"
|
||||
>
|
||||
Quick Links
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Products
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help -->
|
||||
<div class="space-y-6">
|
||||
<h3
|
||||
class="font-playfair font-normal text-md leading-relaxed tracking-wider text-eerie-black uppercase"
|
||||
>
|
||||
Help
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Contact Us
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Privacy Policies
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="block font-playfair font-normal text-base leading-relaxed tracking-wider text-gray-800 hover:text-black transition-colors"
|
||||
>
|
||||
Terms and Conditions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Newsletter -->
|
||||
<div class="space-y-6">
|
||||
<h3
|
||||
class="font-playfair font-normal text-md leading-relaxed tracking-wider text-eerie-black uppercase"
|
||||
>
|
||||
Newsletter
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter Your Email Address"
|
||||
class="flex-1 font-playfair font-normal text-sm leading-relaxed tracking-wider text-taupe-gray bg-transparent border-b border-black focus:outline-none focus:border-black"
|
||||
/>
|
||||
<button
|
||||
class="font-playfair font-normal text-sm leading-relaxed tracking-wider text-gray-800 border-b border-black hover:text-black transition-colors"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copyright Section -->
|
||||
<div class="border-t border-light-silver">
|
||||
<div class="max-w-7xl mx-auto px-5 py-4">
|
||||
<p
|
||||
class="font-playfair font-normal text-xs leading-relaxed text-davys-grey"
|
||||
>
|
||||
© 2025 khy. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="scripts/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
513
scripts/main.js
513
scripts/main.js
|
|
@ -1,5 +1,5 @@
|
|||
// Update year in footer and handle smooth scrolling
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSite() {
|
||||
// Update footer year
|
||||
const footer = document.querySelector("footer p");
|
||||
if (footer) {
|
||||
|
|
@ -9,81 +9,148 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
|
||||
// Smooth scrolling for navigation links
|
||||
const navLinks = document.querySelectorAll('.nav-link[href^="#"]');
|
||||
|
||||
navLinks.forEach((link) => {
|
||||
link.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const targetId = this.getAttribute("href").substring(1);
|
||||
const targetElement = document.getElementById(targetId);
|
||||
|
||||
if (targetElement) {
|
||||
// Calculate offset for header height (112px = 28 * 4)
|
||||
const headerHeight = 112;
|
||||
const targetPosition = targetElement.offsetTop - headerHeight;
|
||||
|
||||
window.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: "smooth",
|
||||
});
|
||||
window.scrollTo({ top: targetPosition, behavior: "smooth" });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle cross-page navigation links (links that point to other pages with anchors)
|
||||
const crossPageLinks = document.querySelectorAll('.nav-link[href*=".html#"]');
|
||||
|
||||
crossPageLinks.forEach((link) => {
|
||||
link.addEventListener("click", function (e) {
|
||||
// Don't prevent default - let the browser handle the navigation
|
||||
// The smooth scrolling will work on the target page
|
||||
link.addEventListener("click", function () {
|
||||
// allow default navigation
|
||||
});
|
||||
});
|
||||
|
||||
// Highlight current page link
|
||||
const currentPage = window.location.pathname.split("/").pop() || "index.html";
|
||||
const currentPageLink = document.querySelector(
|
||||
`.nav-link[href="${currentPage}"]`
|
||||
);
|
||||
if (currentPageLink) {
|
||||
currentPageLink.classList.add("active");
|
||||
}
|
||||
// Remove any lingering .active classes and avoid adding new ones
|
||||
const allNavLinks = document.querySelectorAll(".nav-link");
|
||||
allNavLinks.forEach((a) => a.classList.remove("active"));
|
||||
|
||||
// Active link highlighting on scroll
|
||||
const sections = document.querySelectorAll("section[id]");
|
||||
const navItems = document.querySelectorAll('.nav-link[href^="#"]');
|
||||
// Do not add page-specific active state anymore
|
||||
// const currentPage = window.location.pathname.split("/").pop() || "index.html";
|
||||
// const currentPageLink = document.querySelector(`.nav-link[href="${currentPage}"]`);
|
||||
// if (currentPageLink) currentPageLink.classList.add("active");
|
||||
|
||||
// Disable scroll-based active highlighting
|
||||
// (Keeping function for potential future use, but it does nothing now.)
|
||||
function updateActiveLink() {
|
||||
const scrollPosition = window.scrollY + 150; // Offset for better detection
|
||||
// no-op: active highlighting disabled globally
|
||||
}
|
||||
window.addEventListener("scroll", updateActiveLink);
|
||||
updateActiveLink();
|
||||
|
||||
sections.forEach((section) => {
|
||||
const sectionTop = section.offsetTop;
|
||||
const sectionHeight = section.offsetHeight;
|
||||
const sectionId = section.getAttribute("id");
|
||||
// Quantity controls on product detail page
|
||||
(function initQuantityControls() {
|
||||
const decr = document.getElementById("qty-decr");
|
||||
const incr = document.getElementById("qty-incr");
|
||||
const valueEl = document.getElementById("qty-value");
|
||||
if (!decr || !incr || !valueEl) return; // Not on product detail page
|
||||
|
||||
if (
|
||||
scrollPosition >= sectionTop &&
|
||||
scrollPosition < sectionTop + sectionHeight
|
||||
) {
|
||||
// Remove active class from all nav links
|
||||
navItems.forEach((item) => item.classList.remove("active"));
|
||||
function parseQty() {
|
||||
const n = parseInt(valueEl.textContent || "1", 10);
|
||||
return Number.isFinite(n) && n > 0 ? n : 1;
|
||||
}
|
||||
decr.addEventListener("click", () => {
|
||||
const next = Math.max(1, parseQty() - 1);
|
||||
valueEl.textContent = String(next);
|
||||
});
|
||||
incr.addEventListener("click", () => {
|
||||
const next = parseQty() + 1;
|
||||
valueEl.textContent = String(next);
|
||||
});
|
||||
})();
|
||||
|
||||
// Add active class to corresponding nav link
|
||||
const activeLink = document.querySelector(
|
||||
`.nav-link[href="#${sectionId}"]`
|
||||
);
|
||||
if (activeLink) {
|
||||
activeLink.classList.add("active");
|
||||
// Related products (dynamic)
|
||||
(async function initRelated() {
|
||||
const grid = document.getElementById("related-grid");
|
||||
if (!grid) return;
|
||||
|
||||
// Don't run this function on product detail pages - let the product detail function handle it
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const productId = urlParams.get("id");
|
||||
if (productId) return;
|
||||
|
||||
try {
|
||||
const res = await fetch("data/products.json", { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
const products = Array.isArray(data.products) ? data.products : [];
|
||||
|
||||
// Determine current product's category from the title text (fallback seating)
|
||||
const title = document.querySelector("h1");
|
||||
const currentName = (title?.textContent || "").trim();
|
||||
const current = products.find(
|
||||
(p) => (p.name || "").trim() === currentName
|
||||
);
|
||||
const category = current?.category || "seating";
|
||||
|
||||
// Filter related: same category but not the current product
|
||||
const related = products.filter(
|
||||
(p) => p.category === category && p.name !== currentName
|
||||
);
|
||||
|
||||
let page = 1;
|
||||
const pageSize = 4;
|
||||
|
||||
function render() {
|
||||
grid.innerHTML = "";
|
||||
const start = 0;
|
||||
const end = page * pageSize; // cumulative show-more
|
||||
related.slice(start, end).forEach((p) => {
|
||||
const card = document.createElement("article");
|
||||
card.className = "rounded-lg overflow-hidden bg-white shadow-sm";
|
||||
|
||||
// Distinct image background for Asgaard sofa to match others
|
||||
const imageBgClass =
|
||||
(p.name || "").toLowerCase() === "asgaard sofa"
|
||||
? "bg-linen"
|
||||
: "bg-white";
|
||||
const panelBgClass = "bg-light-bg";
|
||||
|
||||
card.innerHTML = `
|
||||
<div class=\"h-72 ${imageBgClass} flex items-center justify-center\">
|
||||
<img src=\"${p.image}\" alt=\"${
|
||||
p.alt || p.name
|
||||
}\" class=\"w-full h-full object-cover\" />
|
||||
</div>
|
||||
<div class=\"${panelBgClass} p-6\">
|
||||
<h3 class=\"font-poppins font-semibold text-2xl text-[#3A3A3A] mb-0\">${
|
||||
p.name
|
||||
}</h3>
|
||||
</div>`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
|
||||
// Toggle show-more visibility
|
||||
const btn = document.getElementById("related-show-more");
|
||||
if (btn) {
|
||||
const hasMore = related.length > page * pageSize;
|
||||
btn.style.display = hasMore ? "inline-flex" : "none";
|
||||
// Ensure centered label
|
||||
btn.classList.add("inline-flex", "items-center", "justify-center");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for scroll events
|
||||
window.addEventListener("scroll", updateActiveLink);
|
||||
const btn = document.getElementById("related-show-more");
|
||||
if (btn) {
|
||||
btn.addEventListener("click", () => {
|
||||
page += 1;
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
// Initial call to set active link on page load
|
||||
updateActiveLink();
|
||||
render();
|
||||
} catch (e) {
|
||||
console.error("Failed to load related products:", e);
|
||||
}
|
||||
})();
|
||||
|
||||
// Blog search functionality (only runs on blog page)
|
||||
const blogSearchInput = document.getElementById("blog-search-input");
|
||||
|
|
@ -93,10 +160,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
"#blog-pagination .blog-page-btn"
|
||||
);
|
||||
const nextButton = document.getElementById("blog-next-btn");
|
||||
const tagButtons = document.querySelectorAll(".tag-filter");
|
||||
let activeTag = ""; // empty = no tag filter
|
||||
const blogTagButtons = document.querySelectorAll(".tag-filter");
|
||||
let activeTag = "";
|
||||
let currentPage = 1;
|
||||
const pageSize = 4; // number of posts per page
|
||||
const pageSize = 4;
|
||||
|
||||
function normalize(text) {
|
||||
return (text || "")
|
||||
|
|
@ -104,16 +171,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
.replace(/[^a-z0-9]+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function getFilteredArticles() {
|
||||
const articles = Array.from(
|
||||
document.querySelectorAll("#main-blog-content article")
|
||||
);
|
||||
// Sort by date descending if data-date is present (YYYY-MM-DD)
|
||||
articles.sort((a, b) => {
|
||||
const ad = a.getAttribute("data-date") || "";
|
||||
const bd = b.getAttribute("data-date") || "";
|
||||
// Fallback: keep original order if dates missing
|
||||
if (!ad && !bd) return 0;
|
||||
return bd.localeCompare(ad);
|
||||
});
|
||||
|
|
@ -130,26 +194,20 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
return matchesQuery && matchesTag;
|
||||
});
|
||||
}
|
||||
|
||||
function renderPage() {
|
||||
const filtered = getFilteredArticles();
|
||||
const total = filtered.length;
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||
currentPage = Math.min(Math.max(1, currentPage), totalPages);
|
||||
|
||||
// Show only items for current page
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
const toShow = new Set(filtered.slice(start, end));
|
||||
document.querySelectorAll("#main-blog-content article").forEach((a) => {
|
||||
a.style.display = toShow.has(a) ? "" : "none";
|
||||
});
|
||||
|
||||
// Update pagination visibility and active state
|
||||
if (pagination) {
|
||||
const hasQuery = blogSearchInput.value.trim().length > 0;
|
||||
pagination.style.display = hasQuery ? "none" : "flex";
|
||||
|
||||
pageButtons.forEach((btn) => {
|
||||
const p = Number(btn.getAttribute("data-page"));
|
||||
btn.classList.toggle("bg-uc-gold", p === currentPage);
|
||||
|
|
@ -158,31 +216,22 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
btn.classList.toggle("text-black", p !== currentPage);
|
||||
btn.style.display = p <= totalPages ? "inline-flex" : "none";
|
||||
});
|
||||
|
||||
if (nextButton) {
|
||||
nextButton.style.display = totalPages > 1 ? "inline-flex" : "none";
|
||||
nextButton.disabled = currentPage >= totalPages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function filterPosts() {
|
||||
// Reset to first page when filters change
|
||||
currentPage = 1;
|
||||
renderPage();
|
||||
}
|
||||
|
||||
blogSearchInput.addEventListener("input", filterPosts);
|
||||
|
||||
// Tag filtering
|
||||
tagButtons.forEach((btn) => {
|
||||
blogTagButtons.forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const clickedTag = normalize(btn.getAttribute("data-tag"));
|
||||
// Toggle logic: clicking active tag clears it
|
||||
activeTag = activeTag === clickedTag ? "" : clickedTag;
|
||||
|
||||
// Visual active state
|
||||
tagButtons.forEach((b) => {
|
||||
blogTagButtons.forEach((b) => {
|
||||
b.classList.remove("border-uc-gold", "bg-gray-50", "text-uc-gold");
|
||||
b.setAttribute("aria-pressed", "false");
|
||||
});
|
||||
|
|
@ -190,12 +239,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
btn.classList.add("border-uc-gold", "bg-gray-50", "text-uc-gold");
|
||||
btn.setAttribute("aria-pressed", "true");
|
||||
}
|
||||
|
||||
filterPosts();
|
||||
});
|
||||
});
|
||||
|
||||
// Pagination handlers
|
||||
pageButtons.forEach((btn) =>
|
||||
btn.addEventListener("click", () => {
|
||||
currentPage = Number(btn.getAttribute("data-page")) || 1;
|
||||
|
|
@ -208,7 +254,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
renderPage();
|
||||
});
|
||||
}
|
||||
// Ensure correct initial state on load
|
||||
renderPage();
|
||||
}
|
||||
|
||||
|
|
@ -285,11 +330,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
subscribeButton = emailInput.parentElement?.querySelector("button");
|
||||
}
|
||||
if (!subscribeButton) return;
|
||||
|
||||
// Ensure non-submitting behavior
|
||||
subscribeButton.type = "button";
|
||||
|
||||
// Create or reuse feedback element right after the row
|
||||
let feedback = inputRow ? inputRow.nextElementSibling : null;
|
||||
if (!(feedback instanceof HTMLElement) || !feedback.dataset.feedback) {
|
||||
feedback = document.createElement("div");
|
||||
|
|
@ -300,7 +341,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
}
|
||||
feedback.className =
|
||||
"hidden mt-3 rounded-lg px-4 py-3 font-playfair text-base";
|
||||
|
||||
function showMessage(text, type) {
|
||||
feedback.textContent = text;
|
||||
feedback.className =
|
||||
|
|
@ -323,22 +363,17 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
feedback.classList.remove("hidden");
|
||||
setTimeout(() => feedback.classList.add("hidden"), 3000);
|
||||
}
|
||||
|
||||
subscribeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const value = (emailInput.value || "").trim();
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
// Reset error styles
|
||||
emailInput.classList.remove("border-red-500");
|
||||
|
||||
if (!emailRegex.test(value)) {
|
||||
emailInput.classList.add("border-red-500");
|
||||
showMessage("Please enter a valid email address.", "error");
|
||||
emailInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage("Thank you for subscribing!", "success");
|
||||
emailInput.value = "";
|
||||
subscribeButton.disabled = true;
|
||||
|
|
@ -363,4 +398,310 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Product detail page functionality
|
||||
(async function initProductDetail() {
|
||||
console.log("Product detail script running...");
|
||||
|
||||
// Check if we're on the product detail page
|
||||
const productTitle = document.querySelector("h1");
|
||||
if (!productTitle) {
|
||||
console.log("No h1 found, not on product detail page");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get product ID from URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const productId = parseInt(urlParams.get("id"));
|
||||
console.log("Product ID from URL:", productId);
|
||||
|
||||
if (!productId) {
|
||||
console.log("No product ID found in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load product data
|
||||
const response = await fetch("data/products.json");
|
||||
const data = await response.json();
|
||||
const product = data.products.find((p) => p.id === productId);
|
||||
|
||||
if (!product) {
|
||||
console.error("Product not found:", productId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Loading product:", product);
|
||||
|
||||
// Update page title
|
||||
document.title = `${product.name} - KHY`;
|
||||
|
||||
// Update product title (the main product title, not the breadcrumb)
|
||||
const allH1s = document.querySelectorAll("h1");
|
||||
console.log("All H1 elements:", allH1s);
|
||||
if (allH1s.length > 1) {
|
||||
const mainProductTitle = allH1s[1]; // The second h1 should be the product title
|
||||
console.log("Main product title element:", mainProductTitle);
|
||||
if (mainProductTitle) {
|
||||
mainProductTitle.textContent = product.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Update product description - find by text content
|
||||
const allParagraphs = document.querySelectorAll("p");
|
||||
console.log("All paragraphs:", allParagraphs);
|
||||
const descriptionEl = Array.from(allParagraphs).find(
|
||||
(p) =>
|
||||
p.textContent &&
|
||||
p.textContent.includes(
|
||||
"Setting the bar as one of the loudest speakers"
|
||||
)
|
||||
);
|
||||
console.log("Description element:", descriptionEl);
|
||||
if (descriptionEl) {
|
||||
descriptionEl.textContent = product.description;
|
||||
}
|
||||
|
||||
// Update main product image - find by alt text
|
||||
const allImages = document.querySelectorAll("img");
|
||||
console.log("All images:", allImages);
|
||||
const mainImage = Array.from(allImages).find(
|
||||
(img) => img.alt && img.alt.includes("Asgaard sofa")
|
||||
);
|
||||
console.log("Main image element:", mainImage);
|
||||
if (mainImage) {
|
||||
mainImage.src = product.image;
|
||||
mainImage.alt = product.alt || product.name;
|
||||
}
|
||||
|
||||
// Update thumbnail images - find by alt text
|
||||
if (product.images && product.images.length > 0) {
|
||||
const allImages = document.querySelectorAll("img");
|
||||
const thumbnails = Array.from(allImages).filter(
|
||||
(img) =>
|
||||
img.alt &&
|
||||
(img.alt.includes("Outdoor sofa set") ||
|
||||
img.alt.includes("Stuart sofa") ||
|
||||
img.alt.includes("Maya sofa"))
|
||||
);
|
||||
console.log("Thumbnail images found:", thumbnails);
|
||||
product.images.forEach((imageSrc, index) => {
|
||||
if (thumbnails[index]) {
|
||||
thumbnails[index].src = imageSrc;
|
||||
thumbnails[index].alt = product.alt || product.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update size options - use the specific size-button class
|
||||
if (product.sizes) {
|
||||
const sizeButtons = document.querySelectorAll(".size-button");
|
||||
console.log("Size buttons found:", sizeButtons);
|
||||
|
||||
// Hide all size buttons first
|
||||
sizeButtons.forEach((button) => {
|
||||
button.style.display = "none";
|
||||
});
|
||||
|
||||
// Show and update only the buttons we need
|
||||
product.sizes.forEach((size, index) => {
|
||||
if (sizeButtons[index]) {
|
||||
sizeButtons[index].style.display = "flex";
|
||||
sizeButtons[index].textContent = size;
|
||||
// Set selected size
|
||||
if (size === product.selectedSize) {
|
||||
sizeButtons[index].classList.remove(
|
||||
"bg-floral-white",
|
||||
"text-black"
|
||||
);
|
||||
sizeButtons[index].classList.add("bg-uc-gold", "text-white");
|
||||
} else {
|
||||
sizeButtons[index].classList.remove("bg-uc-gold", "text-white");
|
||||
sizeButtons[index].classList.add("bg-floral-white", "text-black");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update color options - find only the rounded color buttons, not size buttons
|
||||
if (product.colors) {
|
||||
const allButtons = document.querySelectorAll("button");
|
||||
const colorButtons = Array.from(allButtons).filter(
|
||||
(button) =>
|
||||
button.classList.contains("w-8") &&
|
||||
button.classList.contains("h-8") &&
|
||||
button.classList.contains("rounded-full") &&
|
||||
!button.textContent // Color buttons should not have text content
|
||||
);
|
||||
console.log("Color buttons found:", colorButtons);
|
||||
|
||||
product.colors.forEach((color, index) => {
|
||||
if (colorButtons[index]) {
|
||||
colorButtons[index].style.backgroundColor = color.value;
|
||||
// Set selected color
|
||||
if (color.selected) {
|
||||
colorButtons[index].classList.add("border-2", "border-black");
|
||||
} else {
|
||||
colorButtons[index].classList.remove("border-2", "border-black");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update product metadata - find by text content
|
||||
const allSpans = document.querySelectorAll("span");
|
||||
|
||||
// Find and update Model No.
|
||||
const modelNoLabel = Array.from(allSpans).find(
|
||||
(span) => span.textContent === "Model No."
|
||||
);
|
||||
if (modelNoLabel && product.modelNo) {
|
||||
const modelNoValue =
|
||||
modelNoLabel.parentElement.querySelector("span:last-child");
|
||||
if (modelNoValue) {
|
||||
modelNoValue.textContent = product.modelNo;
|
||||
}
|
||||
}
|
||||
|
||||
// Find and update Category
|
||||
const categoryLabel = Array.from(allSpans).find(
|
||||
(span) => span.textContent === "Category"
|
||||
);
|
||||
if (categoryLabel && product.category) {
|
||||
const categoryValue =
|
||||
categoryLabel.parentElement.querySelector("span:last-child");
|
||||
if (categoryValue) {
|
||||
categoryValue.textContent =
|
||||
product.category.charAt(0).toUpperCase() +
|
||||
product.category.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Find and update Tags
|
||||
const tagsLabel = Array.from(allSpans).find(
|
||||
(span) => span.textContent === "Tags"
|
||||
);
|
||||
if (tagsLabel && product.tags) {
|
||||
const tagsValue =
|
||||
tagsLabel.parentElement.querySelector("span:last-child");
|
||||
if (tagsValue) {
|
||||
tagsValue.textContent = product.tags.join(", ");
|
||||
}
|
||||
}
|
||||
|
||||
// Find and update Dimensions
|
||||
const dimensionsLabel = Array.from(allSpans).find(
|
||||
(span) => span.textContent === "Dimension"
|
||||
);
|
||||
console.log("Dimensions label found:", dimensionsLabel);
|
||||
console.log("Product dimensions:", product.dimensions);
|
||||
|
||||
if (dimensionsLabel && product.dimensions) {
|
||||
const dimensionsValue =
|
||||
dimensionsLabel.parentElement.querySelector("span:last-child");
|
||||
console.log("Dimensions value element:", dimensionsValue);
|
||||
if (dimensionsValue) {
|
||||
dimensionsValue.textContent = product.dimensions;
|
||||
console.log("Updated dimensions to:", product.dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
// Update description content
|
||||
if (product.descriptionLong) {
|
||||
const descriptionContainer = document.querySelector(
|
||||
".max-w-5xl.mx-auto.space-y-6"
|
||||
);
|
||||
if (descriptionContainer) {
|
||||
const descriptionParagraphs =
|
||||
descriptionContainer.querySelectorAll("p");
|
||||
console.log("Description paragraphs found:", descriptionParagraphs);
|
||||
|
||||
// Update each paragraph with the product's description content
|
||||
product.descriptionLong.forEach((paragraph, index) => {
|
||||
if (descriptionParagraphs[index]) {
|
||||
descriptionParagraphs[index].textContent = paragraph;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update gallery images
|
||||
if (product.galleryPairs) {
|
||||
// Find gallery images by alt text
|
||||
const allImages = document.querySelectorAll("img");
|
||||
const galleryImages = Array.from(allImages).filter(
|
||||
(img) =>
|
||||
img.alt &&
|
||||
(img.alt.includes("Sofa variant left") ||
|
||||
img.alt.includes("Sofa variant right"))
|
||||
);
|
||||
console.log("Gallery images found:", galleryImages);
|
||||
|
||||
product.galleryPairs.forEach((pair, index) => {
|
||||
if (galleryImages[index * 2]) {
|
||||
galleryImages[index * 2].src = pair.left;
|
||||
galleryImages[index * 2].alt = product.alt || product.name;
|
||||
}
|
||||
if (galleryImages[index * 2 + 1]) {
|
||||
galleryImages[index * 2 + 1].src = pair.right;
|
||||
galleryImages[index * 2 + 1].alt = product.alt || product.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update related products section
|
||||
const relatedGrid = document.getElementById("related-grid");
|
||||
if (relatedGrid && product.category) {
|
||||
console.log("Current product category:", product.category);
|
||||
console.log(
|
||||
"All products:",
|
||||
data.products.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
category: p.category,
|
||||
}))
|
||||
);
|
||||
|
||||
// Filter related products by category (excluding current product)
|
||||
const relatedProducts = data.products
|
||||
.filter((p) => p.category === product.category && p.id !== product.id)
|
||||
.slice(0, 4);
|
||||
|
||||
console.log("Related products found:", relatedProducts);
|
||||
|
||||
relatedGrid.innerHTML = relatedProducts
|
||||
.map(
|
||||
(p) => `
|
||||
<a href="product-detail.html?id=${p.id}" class="block">
|
||||
<div class="rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-shadow">
|
||||
<div class="h-72 ${
|
||||
p.name.toLowerCase().includes("asgaard")
|
||||
? "bg-linen"
|
||||
: "bg-white"
|
||||
} flex items-center justify-center">
|
||||
<img src="${p.image}" alt="${
|
||||
p.alt || p.name
|
||||
}" class="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div class="bg-light-bg p-6">
|
||||
<h3 class="font-poppins font-semibold text-2xl text-[#3A3A3A] mb-0">${
|
||||
p.name
|
||||
}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading product data:", error);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initSite);
|
||||
} else {
|
||||
initSite();
|
||||
}
|
||||
|
|
|
|||
381
scripts/products.js
Normal file
381
scripts/products.js
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
// Product Management System
|
||||
class ProductManager {
|
||||
constructor() {
|
||||
this.products = [];
|
||||
this.categories = [];
|
||||
this.pagination = {};
|
||||
this.currentPage = 1;
|
||||
this.itemsPerPage = 16;
|
||||
this.filteredProducts = [];
|
||||
this.selectedCategories = new Set();
|
||||
}
|
||||
|
||||
// Load products from JSON file
|
||||
async loadProducts() {
|
||||
try {
|
||||
const response = await fetch("/data/products.json");
|
||||
const data = await response.json();
|
||||
|
||||
this.products = data.products;
|
||||
this.categories = data.categories;
|
||||
this.pagination = data.pagination;
|
||||
this.filteredProducts = [...this.products];
|
||||
|
||||
this.renderProducts();
|
||||
this.updatePagination();
|
||||
this.updateResultsCount();
|
||||
this.setupEventListeners();
|
||||
this.renderCategoryFilters();
|
||||
|
||||
// Check for URL parameters and pre-select category
|
||||
this.handleUrlParameters();
|
||||
} catch (error) {
|
||||
console.error("Error loading products:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle URL parameters for pre-selecting category filters
|
||||
handleUrlParameters() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const category = urlParams.get("category");
|
||||
|
||||
if (category) {
|
||||
// Pre-select the category in the filter
|
||||
this.selectedCategories = new Set([category]);
|
||||
this.applyFilters();
|
||||
this.currentPage = 1;
|
||||
this.renderProducts();
|
||||
this.updatePagination();
|
||||
this.updateResultsCount();
|
||||
|
||||
// Update the UI to show the filter is active
|
||||
this.updateFilterUI(category);
|
||||
}
|
||||
}
|
||||
|
||||
// Update filter UI to show selected category
|
||||
updateFilterUI(category) {
|
||||
// Update filter button text to show active filter
|
||||
const filterToggle = document.getElementById("filter-toggle");
|
||||
if (filterToggle) {
|
||||
const filterText = filterToggle.querySelector("span:last-child");
|
||||
if (filterText) {
|
||||
filterText.textContent = `Filter: ${category}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the corresponding checkbox in the dropdown
|
||||
setTimeout(() => {
|
||||
const checkboxes = document.querySelectorAll(".category-checkbox");
|
||||
checkboxes.forEach((checkbox) => {
|
||||
if (checkbox.value === category) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Render products in the grid
|
||||
renderProducts() {
|
||||
const productGrid = document.getElementById("product-grid");
|
||||
if (!productGrid) return;
|
||||
|
||||
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
|
||||
const endIndex = startIndex + this.itemsPerPage;
|
||||
const productsToShow = this.filteredProducts.slice(startIndex, endIndex);
|
||||
|
||||
productGrid.innerHTML = productsToShow
|
||||
.map((product) => this.createProductCard(product))
|
||||
.join("");
|
||||
this.updateResultsCount();
|
||||
this.updatePagination();
|
||||
}
|
||||
|
||||
// Create individual product card HTML
|
||||
createProductCard(product) {
|
||||
return `
|
||||
<div class="group relative bg-light-bg rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
|
||||
<div class="relative h-80 overflow-hidden">
|
||||
<img
|
||||
src="${product.image}"
|
||||
alt="${product.alt}"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<!-- Hover Overlay -->
|
||||
<div class="absolute inset-0 bg-dark-charcoal bg-opacity-70 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<button
|
||||
class="bg-white text-uc-gold font-poppins font-semibold px-8 py-3 rounded-md hover:bg-uc-gold hover:text-white transition-colors"
|
||||
onclick="productManager.viewProduct(${product.id})"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<h3 class="font-poppins font-semibold text-2xl text-dark-charcoal mb-2">
|
||||
${product.name}
|
||||
</h3>
|
||||
<p class="font-poppins font-medium text-base text-quick-silver">
|
||||
${product.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Filter products by category
|
||||
filterByCategory(category) {
|
||||
// Single category helper (not used directly by UI)
|
||||
this.selectedCategories = new Set([category]);
|
||||
this.applyFilters();
|
||||
this.currentPage = 1;
|
||||
this.renderProducts();
|
||||
this.updatePagination();
|
||||
}
|
||||
|
||||
// Search products
|
||||
searchProducts(query) {
|
||||
if (!query.trim()) {
|
||||
this.filteredProducts = [...this.products];
|
||||
} else {
|
||||
this.filteredProducts = this.products.filter(
|
||||
(product) =>
|
||||
product.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||
product.description.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
}
|
||||
this.currentPage = 1;
|
||||
this.renderProducts();
|
||||
this.updatePagination();
|
||||
}
|
||||
|
||||
// Apply selected category filters
|
||||
applyFilters() {
|
||||
if (
|
||||
this.selectedCategories.size === 0 ||
|
||||
this.selectedCategories.has("all")
|
||||
) {
|
||||
this.filteredProducts = [...this.products];
|
||||
return;
|
||||
}
|
||||
this.filteredProducts = this.products.filter((product) =>
|
||||
this.selectedCategories.has(product.category)
|
||||
);
|
||||
}
|
||||
|
||||
// Sort products
|
||||
sortProducts(sortBy) {
|
||||
switch (sortBy) {
|
||||
case "name-asc":
|
||||
this.filteredProducts.sort((a, b) => a.name.localeCompare(b.name));
|
||||
break;
|
||||
case "name-desc":
|
||||
this.filteredProducts.sort((a, b) => b.name.localeCompare(a.name));
|
||||
break;
|
||||
default:
|
||||
// Default sorting by ID
|
||||
this.filteredProducts.sort((a, b) => a.id - b.id);
|
||||
}
|
||||
this.renderProducts();
|
||||
}
|
||||
|
||||
// Change page
|
||||
changePage(page) {
|
||||
this.currentPage = page;
|
||||
this.renderProducts();
|
||||
this.updatePagination();
|
||||
}
|
||||
|
||||
// Update pagination controls
|
||||
updatePagination() {
|
||||
const totalPages = Math.ceil(
|
||||
this.filteredProducts.length / this.itemsPerPage
|
||||
);
|
||||
const paginationContainer = document.getElementById("pagination");
|
||||
|
||||
if (!paginationContainer) return;
|
||||
|
||||
let paginationHTML = "";
|
||||
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
const isActive = i === this.currentPage;
|
||||
paginationHTML += `
|
||||
<button
|
||||
class="w-20 h-15 ${
|
||||
isActive
|
||||
? "bg-uc-gold text-white"
|
||||
: "bg-floral-white text-black hover:bg-uc-gold hover:text-white"
|
||||
} font-poppins font-normal text-xl rounded-lg flex items-center justify-center transition-colors"
|
||||
onclick="productManager.changePage(${i})"
|
||||
>
|
||||
${i}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
if (totalPages > 1 && this.currentPage < totalPages) {
|
||||
paginationHTML += `
|
||||
<button
|
||||
class="w-28 h-15 bg-floral-white text-black font-poppins font-light text-xl rounded-lg flex items-center justify-center hover:bg-uc-gold hover:text-white transition-colors"
|
||||
onclick="productManager.changePage(${this.currentPage + 1})"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
paginationContainer.innerHTML = paginationHTML;
|
||||
}
|
||||
|
||||
// Build category multi-select dropdown
|
||||
renderCategoryFilters() {
|
||||
const container = document.getElementById("filter-categories");
|
||||
if (!container) return;
|
||||
|
||||
const categoryOptions = this.categories
|
||||
.map(
|
||||
(c) => `
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" value="${c.id}" class="category-checkbox category-specific">
|
||||
<span class="font-poppins text-sm text-black">${c.name}</span>
|
||||
</label>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
const allOption = `
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" value="all" class="category-checkbox category-all">
|
||||
<span class="font-poppins text-sm text-black">All</span>
|
||||
</label>
|
||||
`;
|
||||
|
||||
container.innerHTML = allOption + categoryOptions;
|
||||
|
||||
// Add event listeners for "All" checkbox behavior
|
||||
const allCheckbox = container.querySelector(".category-all");
|
||||
const specificCheckboxes = container.querySelectorAll(".category-specific");
|
||||
|
||||
if (allCheckbox) {
|
||||
allCheckbox.addEventListener("change", (e) => {
|
||||
const isChecked = e.target.checked;
|
||||
specificCheckboxes.forEach((checkbox) => {
|
||||
checkbox.checked = isChecked;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update "All" checkbox when specific categories change
|
||||
specificCheckboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener("change", () => {
|
||||
const allChecked = Array.from(specificCheckboxes).every(
|
||||
(c) => c.checked
|
||||
);
|
||||
const anyChecked = Array.from(specificCheckboxes).some(
|
||||
(c) => c.checked
|
||||
);
|
||||
|
||||
if (allChecked) {
|
||||
allCheckbox.checked = true;
|
||||
} else if (!anyChecked) {
|
||||
allCheckbox.checked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// View product details
|
||||
viewProduct(productId) {
|
||||
const product = this.products.find((p) => p.id === productId);
|
||||
if (product) {
|
||||
// Navigate to product detail page with product ID
|
||||
window.location.href = `product-detail.html?id=${productId}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
setupEventListeners() {
|
||||
// Filter dropdown toggle and outside click
|
||||
const filterToggle = document.getElementById("filter-toggle");
|
||||
const filterDropdown = document.getElementById("filter-dropdown");
|
||||
if (filterToggle && filterDropdown) {
|
||||
filterToggle.addEventListener("click", () => {
|
||||
filterDropdown.classList.toggle("hidden");
|
||||
});
|
||||
document.addEventListener("click", (e) => {
|
||||
if (
|
||||
!filterDropdown.contains(e.target) &&
|
||||
!filterToggle.contains(e.target)
|
||||
) {
|
||||
filterDropdown.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sort dropdown
|
||||
const sortSelect = document.querySelector("select");
|
||||
if (sortSelect) {
|
||||
sortSelect.addEventListener("change", (e) => {
|
||||
this.sortProducts(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// Apply/clear category filters
|
||||
const applyBtn = document.getElementById("filter-apply");
|
||||
const clearBtn = document.getElementById("filter-clear");
|
||||
if (applyBtn) {
|
||||
applyBtn.addEventListener("click", () => {
|
||||
const checks = Array.from(
|
||||
document.querySelectorAll(".category-checkbox")
|
||||
);
|
||||
this.selectedCategories = new Set(
|
||||
checks.filter((c) => c.checked).map((c) => c.value)
|
||||
);
|
||||
this.currentPage = 1;
|
||||
this.applyFilters();
|
||||
this.renderProducts();
|
||||
this.updatePagination();
|
||||
this.updateResultsCount();
|
||||
const dropdown = document.getElementById("filter-dropdown");
|
||||
if (dropdown) dropdown.classList.add("hidden");
|
||||
});
|
||||
}
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener("click", () => {
|
||||
const checks = Array.from(
|
||||
document.querySelectorAll(".category-checkbox")
|
||||
);
|
||||
checks.forEach((c) => (c.checked = false));
|
||||
this.selectedCategories.clear();
|
||||
this.currentPage = 1;
|
||||
this.applyFilters();
|
||||
this.renderProducts();
|
||||
this.updatePagination();
|
||||
this.updateResultsCount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update results count
|
||||
updateResultsCount() {
|
||||
const resultsElement = document.querySelector(".text-quick-silver");
|
||||
if (resultsElement) {
|
||||
const startIndex = (this.currentPage - 1) * this.itemsPerPage + 1;
|
||||
const endIndex = Math.min(
|
||||
startIndex + this.itemsPerPage - 1,
|
||||
this.filteredProducts.length
|
||||
);
|
||||
resultsElement.textContent = `Showing ${startIndex}–${endIndex} of ${this.filteredProducts.length} results`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize product manager
|
||||
const productManager = new ProductManager();
|
||||
|
||||
// Load products when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
productManager.loadProducts();
|
||||
});
|
||||
91
scripts/update_products_tabs.js
Normal file
91
scripts/update_products_tabs.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const productsPath = path.join(__dirname, "../data/products.json");
|
||||
const db = JSON.parse(fs.readFileSync(productsPath, "utf8"));
|
||||
|
||||
// Default content for tabs (copy matches current UI text style)
|
||||
const defaultLong = [
|
||||
"Embodying the raw, wayward spirit of rock ‘n’ roll, the Kilburn portable active stereo speaker takes the unmistakable look and sound of Marshall, unplugs the chords, and takes the show on the road.",
|
||||
"Weighing in under 7 pounds, the Kilburn is a lightweight piece of vintage styled engineering. Setting the bar as one of the loudest speakers in its class, the Kilburn is a compact, stout-hearted hero with a well-balanced audio which boasts a clear midrange and extended highs for a sound that is both articulate and pronounced. The analogue knobs allow you to fine tune the controls to your personal preferences while the guitar-influenced leather strap enables easy and stylish travel.",
|
||||
];
|
||||
|
||||
const defaultsByCategory = {
|
||||
seating: {
|
||||
additionalInformation: {
|
||||
Material: "Fabric, engineered wood base",
|
||||
Upholstery: "Performance fabric",
|
||||
Dimensions: "See size options",
|
||||
Warranty: "2 years",
|
||||
},
|
||||
},
|
||||
tables: {
|
||||
additionalInformation: {
|
||||
Material: "Engineered wood, steel frame",
|
||||
Finish: "Matte laminate",
|
||||
Dimensions: "Small/Medium/Large",
|
||||
Warranty: "2 years",
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
additionalInformation: {
|
||||
Material: "Powder-coated steel",
|
||||
Capacity: "Modular shelves",
|
||||
Dimensions: "Standard/Large/XL",
|
||||
Warranty: "2 years",
|
||||
},
|
||||
},
|
||||
workspace: {
|
||||
additionalInformation: {
|
||||
Material: "Acoustic panels, aluminum frame",
|
||||
Power: "Integrated power module",
|
||||
Dimensions: "Standard/Large",
|
||||
Warranty: "2 years",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function ensureFields(p) {
|
||||
const cat =
|
||||
p.category && defaultsByCategory[p.category] ? p.category : "seating";
|
||||
const d = defaultsByCategory[cat];
|
||||
|
||||
if (!Array.isArray(p.descriptionLong) || p.descriptionLong.length === 0) {
|
||||
p.descriptionLong = defaultLong;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof p.additionalInformation !== "object" ||
|
||||
p.additionalInformation === null
|
||||
) {
|
||||
p.additionalInformation = { ...d.additionalInformation };
|
||||
} else {
|
||||
// Fill any missing keys with defaults without overwriting existing
|
||||
for (const [k, v] of Object.entries(d.additionalInformation)) {
|
||||
if (
|
||||
p.additionalInformation[k] == null ||
|
||||
p.additionalInformation[k] === ""
|
||||
) {
|
||||
p.additionalInformation[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure reviewsCount mirrors numeric reviews
|
||||
const reviewsNum = Number(p.reviews || 0);
|
||||
p.reviews = Number.isFinite(reviewsNum) ? reviewsNum : 0;
|
||||
if (p.reviewsCount == null) p.reviewsCount = p.reviews;
|
||||
|
||||
// Ensure galleryPairs (two wide images for tabs). Use product image as fallback.
|
||||
if (!Array.isArray(p.galleryPairs) || p.galleryPairs.length === 0) {
|
||||
const img = p.image || "assets/images/asgaard_sofa.png";
|
||||
p.galleryPairs = [{ left: img, right: img }];
|
||||
}
|
||||
}
|
||||
|
||||
(db.products || []).forEach(ensureFields);
|
||||
|
||||
fs.writeFileSync(productsPath, JSON.stringify(db, null, 2));
|
||||
console.log(
|
||||
"Ensured tabs data for all products: descriptionLong, additionalInformation, reviewsCount, galleryPairs"
|
||||
);
|
||||
736
styles/main.css
736
styles/main.css
File diff suppressed because it is too large
Load diff
|
|
@ -35,6 +35,9 @@ module.exports = {
|
|||
150: "150px",
|
||||
100: "100px",
|
||||
120: "120px",
|
||||
60: "240px",
|
||||
180: "720px",
|
||||
360: "1440px",
|
||||
},
|
||||
textShadow: {
|
||||
default: "0 2px 4px rgba(0,0,0,0.1)",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue