Fix: Restore website functionality - all pages and APIs working

This commit is contained in:
Local Server
2026-01-14 07:16:04 -06:00
parent dc58a8ae5f
commit 9f659a2c59
41 changed files with 10890 additions and 3029 deletions

View File

@@ -188,6 +188,53 @@
color: #1a1a1a;
}
/* Button Styles for Cart/Wishlist Dropdowns */
.action-dropdown .btn-outline,
.action-dropdown .btn-text,
.action-dropdown .btn-primary-full {
display: inline-block;
padding: 10px 16px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
text-align: center;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
border: none;
}
.action-dropdown .btn-outline {
background: transparent;
color: #6b46c1;
border: 1px solid #6b46c1;
}
.action-dropdown .btn-outline:hover {
background: #f3f0ff;
}
.action-dropdown .btn-text {
background: transparent;
color: #6b7280;
padding: 8px;
}
.action-dropdown .btn-text:hover {
color: #FCB1D8;
}
.action-dropdown .btn-primary-full {
background: #6b46c1;
color: white;
width: 100%;
margin-top: 8px;
}
.action-dropdown .btn-primary-full:hover {
background: #5a38a3;
}
/* Scrollbar for dropdown body */
.dropdown-body::-webkit-scrollbar {
width: 6px;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,280 @@
/**
* Mobile Navbar Fixes
* Ensures hamburger menu, cart, and wishlist are visible on mobile devices
*/
/* Mobile hamburger menu - always visible on small screens */
@media (max-width: 768px) {
.mobile-toggle {
display: flex !important;
flex-direction: column;
gap: 4px;
background: none;
border: none;
cursor: pointer;
padding: 8px;
z-index: 10;
}
.toggle-line {
width: 24px;
height: 3px;
background-color: #202023;
border-radius: 2px;
transition: all 0.3s ease;
}
/* Hide desktop menu on mobile */
.navbar-menu {
display: none !important;
}
/* Ensure cart and wishlist icons are visible */
.navbar-actions {
display: flex !important;
gap: 12px;
align-items: center;
}
.action-item {
display: block !important;
}
.action-btn {
display: flex !important;
align-items: center;
justify-content: center;
position: relative;
background: none;
border: none;
cursor: pointer;
padding: 8px;
color: #202023;
font-size: 20px;
}
.action-btn i {
display: block !important;
}
.action-badge {
display: flex !important;
position: absolute;
top: 2px;
right: 2px;
background: #fcb1d8;
color: #202023;
font-size: 11px;
font-weight: 600;
min-width: 18px;
height: 18px;
border-radius: 50%;
align-items: center;
justify-content: center;
padding: 0 4px;
}
.action-badge.show {
opacity: 1 !important;
visibility: visible !important;
}
/* Mobile menu overlay */
.mobile-menu-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 999;
}
.mobile-menu-overlay.active {
opacity: 1;
visibility: visible;
}
/* Mobile menu sidebar */
.mobile-menu {
position: fixed;
top: 0;
right: -100%;
width: 80%;
max-width: 300px;
height: 100vh;
background: white;
box-shadow: -4px 0 12px rgba(0, 0, 0, 0.1);
transition: right 0.3s ease;
z-index: 1000;
overflow-y: auto;
padding: 20px;
}
.mobile-menu.active {
right: 0;
}
.mobile-menu-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.mobile-brand {
font-family: "Roboto", sans-serif;
font-size: 18px;
font-weight: 600;
color: #202023;
}
.mobile-close {
background: none;
border: none;
font-size: 24px;
color: #202023;
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.mobile-menu-list {
list-style: none;
padding: 0;
margin: 0;
}
.mobile-menu-list li {
margin-bottom: 4px;
}
.mobile-link {
display: block;
padding: 12px 16px;
color: #202023;
text-decoration: none;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
transition: all 0.2s ease;
}
.mobile-link:hover,
.mobile-link:focus {
background: #ffebeb;
color: #fcb1d8;
}
/* Dropdown menus on mobile */
.action-dropdown {
position: fixed !important;
right: 0;
left: 0;
top: auto;
bottom: 0;
width: 100% !important;
max-width: 100% !important;
max-height: 70vh;
border-radius: 16px 16px 0 0 !important;
transform: translateY(100%) !important;
}
.action-dropdown.show {
transform: translateY(0) !important;
}
/* Compact dropdown on mobile */
.dropdown-head {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
}
.dropdown-head h3 {
font-size: 18px;
}
.dropdown-body {
max-height: calc(70vh - 140px);
overflow-y: auto;
padding: 12px;
}
.dropdown-foot {
padding: 12px 16px;
border-top: 1px solid #f0f0f0;
}
/* Prevent body scroll when menu is open */
body.menu-open {
overflow: hidden;
}
}
/* Tablet adjustments */
@media (min-width: 769px) and (max-width: 1024px) {
.navbar-actions {
gap: 16px;
}
.action-btn {
font-size: 22px;
}
.action-dropdown {
max-width: 360px !important;
}
}
/* Desktop - hide mobile elements */
@media (min-width: 769px) {
.mobile-toggle {
display: none !important;
}
.mobile-menu,
.mobile-menu-overlay {
display: none !important;
}
}
/* Accessibility improvements */
.action-btn:focus,
.mobile-toggle:focus,
.mobile-close:focus {
outline: 2px solid #fcb1d8;
outline-offset: 2px;
}
.mobile-link:focus {
outline: 2px solid #fcb1d8;
outline-offset: -2px;
}
/* Smooth transitions */
* {
-webkit-tap-highlight-color: transparent;
}
button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
/* Fix for iOS Safari button styling */
button:focus {
outline: none;
}
button:focus-visible {
outline: 2px solid #fcb1d8;
outline-offset: 2px;
}

View File

@@ -0,0 +1,139 @@
/* Page-specific overrides for home, portfolio, blog, etc. */
/* Sticky Banner Wrapper */
.sticky-banner-wrapper {
position: sticky;
top: 0;
z-index: 1000;
background: #ffd0d0;
}
.sticky-banner-wrapper .modern-navbar {
position: relative;
box-shadow: none;
}
/* CRITICAL FIX: Force dropdowns below navbar */
.modern-navbar {
position: relative !important;
overflow: visible !important;
transform: none !important;
}
.modern-navbar .navbar-wrapper {
overflow: visible !important;
}
.modern-navbar .navbar-actions {
overflow: visible !important;
display: flex !important;
gap: 8px !important;
}
.modern-navbar .action-btn,
.modern-navbar #wishlistToggle,
.modern-navbar #cartToggle {
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 44px !important;
height: 44px !important;
}
.modern-navbar .action-item,
.modern-navbar .wishlist-dropdown-wrapper,
.modern-navbar .cart-dropdown-wrapper {
position: relative !important;
overflow: visible !important;
}
.modern-navbar .action-dropdown,
.modern-navbar #cartPanel,
.modern-navbar #wishlistPanel {
position: absolute !important;
right: 0 !important;
left: auto !important;
z-index: 999999 !important;
background: white !important;
width: 400px !important;
max-height: 500px !important;
}
@media (max-width: 639px) {
.modern-navbar .action-dropdown,
.modern-navbar #cartPanel,
.modern-navbar #wishlistPanel {
position: fixed !important;
top: 60px !important;
right: 8px !important;
left: 8px !important;
width: auto !important;
max-width: 400px !important;
}
}
/* Product Title Link */
.product-title-link {
text-decoration: none !important;
color: #202023 !important;
display: block !important;
cursor: pointer !important;
transition: color 0.3s ease;
position: relative;
z-index: 10;
}
.product-title-link:hover {
color: #fcb1d8 !important;
}
.product-title-link h3 {
color: inherit;
transition: color 0.3s ease;
margin: 0;
pointer-events: none;
}
.product-title-link:hover h3 {
color: #fcb1d8 !important;
}
/* Contact Page Mobile */
@media (max-width: 768px) {
#contactForm > div[style*="grid-template-columns"] {
grid-template-columns: 1fr !important;
}
.contact-form-wrapper {
padding: 24px !important;
border-radius: 12px !important;
}
.contact-section {
padding: 40px 0 !important;
}
section[style*="padding: 100px"] {
padding: 60px 0 !important;
}
h1[style*="font-size: 2.5rem"] {
font-size: 1.8rem !important;
}
h2[style*="font-size: 2rem"] {
font-size: 1.5rem !important;
}
#contactInfoSection div[style*="grid-template-columns"] {
grid-template-columns: 1fr !important;
gap: 16px !important;
}
input, textarea, button {
font-size: 16px !important;
}
}
@media (max-width: 480px) {
.contact-form-wrapper {
padding: 20px !important;
}
.container {
padding-left: 16px !important;
padding-right: 16px !important;
}
}

View File

@@ -232,18 +232,18 @@
.mobile-menu {
position: fixed;
top: 0;
left: -100%;
right: -100%;
width: 280px;
height: 100vh;
background: white;
z-index: 9999;
transition: left 0.3s ease;
transition: right 0.3s ease;
overflow-y: auto;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
box-shadow: -2px 0 10px rgba(0,0,0,0.1);
}
.mobile-menu.active {
left: 0;
right: 0;
}
.mobile-menu-overlay {

View File

@@ -0,0 +1,630 @@
/**
* Responsive Layout Utilities
* Mobile-first responsive design system
*/
/* ========================================
RESPONSIVE UTILITIES
======================================== */
/* Loading States */
.loading {
position: relative;
pointer-events: none;
opacity: 0.6;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 40px;
height: 40px;
margin: -20px 0 0 -20px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border-width: 0;
}
.focus-visible:focus {
outline: 2px solid #667eea;
outline-offset: 2px;
}
/* Responsive Images */
img {
max-width: 100%;
height: auto;
display: block;
}
/* Container Queries */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
@media (min-width: 768px) {
.container {
padding: 0 40px;
}
}
@media (min-width: 1024px) {
.container {
padding: 0 60px;
}
}
/* Grid System */
.grid {
display: grid;
gap: 20px;
grid-template-columns: 1fr;
}
@media (min-width: 640px) {
.grid-2 {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 768px) {
.grid-3 {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1024px) {
.grid-4 {
grid-template-columns: repeat(4, 1fr);
}
}
/* Flex Utilities */
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.flex-wrap {
flex-wrap: wrap;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; }
.gap-6 { gap: 1.5rem; }
/* Spacing */
.m-0 { margin: 0; }
.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.p-0 { padding: 0; }
.p-2 { padding: 0.5rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
/* Text Utilities */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.text-sm { font-size: 0.875rem; }
.text-base { font-size: 1rem; }
.text-lg { font-size: 1.125rem; }
.text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; }
.text-3xl { font-size: 1.875rem; }
.font-normal { font-weight: 400; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
/* Display Utilities */
.hidden { display: none !important; }
.block { display: block; }
.inline-block { display: inline-block; }
@media (max-width: 639px) {
.sm\\:hidden { display: none !important; }
}
@media (min-width: 640px) {
.sm\\:block { display: block; }
}
@media (max-width: 767px) {
.md\\:hidden { display: none !important; }
}
@media (min-width: 768px) {
.md\\:block { display: block; }
.md\\:flex { display: flex; }
}
@media (max-width: 1023px) {
.lg\\:hidden { display: none !important; }
}
@media (min-width: 1024px) {
.lg\\:block { display: block; }
.lg\\:flex { display: flex; }
}
/* ========================================
RESPONSIVE PRODUCT CARDS
======================================== */
.products-grid {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
padding: 20px 0;
}
@media (min-width: 640px) {
.products-grid {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
}
@media (min-width: 768px) {
.products-grid {
grid-template-columns: repeat(3, 1fr);
gap: 30px;
}
}
@media (min-width: 1024px) {
.products-grid {
grid-template-columns: repeat(4, 1fr);
}
}
.product-card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0,0,0,0.15);
}
.product-image-wrapper {
position: relative;
padding-top: 100%;
overflow: hidden;
background: #f5f5f5;
}
/* .product-image styles moved to main.css - do not override */
/* Commented out to prevent conflict with main.css product-image styles */
/*
.product-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
*/
.product-info {
padding: 16px;
}
.product-title {
font-size: 1rem;
font-weight: 600;
margin: 0 0 8px 0;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-price {
font-size: 1.25rem;
font-weight: 700;
color: #667eea;
margin: 0 0 12px 0;
}
.product-actions {
display: flex;
gap: 8px;
flex-direction: column;
}
@media (min-width: 768px) {
.product-actions {
flex-direction: row;
}
}
.wishlist-btn {
position: absolute;
top: 12px;
right: 12px;
width: 40px;
height: 40px;
border-radius: 50%;
background: white;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #666;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 10;
}
.wishlist-btn:hover {
background: #667eea;
color: white;
transform: scale(1.1);
}
.wishlist-btn.active {
background: #dc3545;
color: white;
}
/* ========================================
RESPONSIVE CART/WISHLIST DROPDOWNS
======================================== */
.action-dropdown {
position: absolute;
top: 100%;
right: 0;
width: 100vw;
max-width: 400px;
background: white;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-radius: 12px;
margin-top: 8px;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.3s ease;
z-index: 1000;
max-height: 80vh;
overflow-y: auto;
}
@media (max-width: 639px) {
.action-dropdown {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
max-width: 100%;
border-radius: 12px 12px 0 0;
transform: translateY(100%);
}
}
.action-dropdown.active {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #eee;
}
.dropdown-head h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
}
.dropdown-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #666;
padding: 4px;
line-height: 1;
}
.dropdown-body {
padding: 16px;
max-height: 400px;
overflow-y: auto;
}
.dropdown-foot {
padding: 16px 20px;
border-top: 1px solid #eee;
}
.cart-item,
.wishlist-item {
display: flex;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.cart-item:last-child,
.wishlist-item:last-child {
border-bottom: none;
}
.cart-item-image,
.wishlist-item-image {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 8px;
flex-shrink: 0;
}
.cart-item-details,
.wishlist-item-details {
flex: 1;
}
.cart-item-title,
.wishlist-item-title {
font-size: 0.875rem;
font-weight: 600;
margin: 0 0 4px 0;
}
.cart-item-price,
.wishlist-item-price {
font-size: 0.875rem;
color: #667eea;
font-weight: 600;
margin: 0;
}
.cart-item-remove,
.wishlist-item-remove {
background: none;
border: none;
color: #999;
cursor: pointer;
padding: 4px;
font-size: 16px;
}
.cart-item-remove:hover,
.wishlist-item-remove:hover {
color: #dc3545;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
/* ========================================
RESPONSIVE BUTTONS
======================================== */
button,
.btn,
.btn-primary,
.btn-secondary,
.btn-outline {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 24px;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
border: none;
white-space: nowrap;
}
.btn-primary,
.btn-primary-full {
background: #667eea;
color: white;
width: 100%;
}
.btn-primary:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102,126,234,0.3);
}
.btn-secondary {
background: #28a745;
color: white;
}
.btn-secondary:hover {
background: #218838;
}
.btn-outline {
background: white;
color: #667eea;
border: 2px solid #667eea;
width: 100%;
}
.btn-outline:hover {
background: #667eea;
color: white;
}
.btn-text {
background: none;
color: #667eea;
text-decoration: underline;
padding: 8px;
}
@media (max-width: 639px) {
button,
.btn {
font-size: 0.875rem;
padding: 10px 20px;
}
}
/* ========================================
RESPONSIVE NAVIGATION
======================================== */
/* Navbar styles removed - see navbar.css for all navbar styling */
.action-btn {
position: relative;
background: none;
border: none;
font-size: 24px;
color: #333;
cursor: pointer;
padding: 8px;
}
.action-badge {
position: absolute;
top: 0;
right: 0;
background: #dc3545;
color: white;
font-size: 10px;
font-weight: 700;
min-width: 18px;
height: 18px;
border-radius: 9px;
display: none;
align-items: center;
justify-content: center;
padding: 0 4px;
}
/* Mobile Menu */
@media (max-width: 767px) {
.mobile-menu-toggle {
display: block;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
padding: 8px;
}
.mobile-menu {
position: fixed;
top: 0;
right: -100%;
width: 80%;
max-width: 300px;
height: 100vh;
background: white;
box-shadow: -4px 0 12px rgba(0,0,0,0.1);
transition: right 0.3s ease;
z-index: 1001;
overflow-y: auto;
}
.mobile-menu.active {
right: 0;
}
.mobile-menu-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: rgba(0,0,0,0.5);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 1000;
}
.mobile-menu-overlay.active {
opacity: 1;
visibility: visible;
}
}
/* Print Styles */
@media print {
.modern-navbar,
.navbar-actions,
.mobile-menu,
.action-dropdown,
button {
display: none !important;
}
}

View File

@@ -0,0 +1,299 @@
/* Cart and Wishlist Item Styles */
/* Cart Items */
.cart-item {
display: flex;
gap: 12px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
margin-bottom: 12px;
transition: all 0.2s;
}
.cart-item:hover {
background: #f5f5f5;
}
.cart-item-image {
flex-shrink: 0;
width: 80px;
height: 80px;
border-radius: 6px;
overflow: hidden;
background: white;
}
.cart-item-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cart-item-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.cart-item-name {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.4;
}
.cart-item-price {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #6b46c1;
}
.cart-item-quantity {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.qty-btn {
width: 28px;
height: 28px;
border: 1px solid #d1d5db;
background: white;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
color: #6b7280;
}
.qty-btn:hover {
border-color: #6b46c1;
color: #6b46c1;
background: #f3f0ff;
}
.qty-value {
min-width: 32px;
text-align: center;
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
}
.cart-item-actions {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
}
.cart-item-remove {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: #9ca3af;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.cart-item-remove:hover {
background: #fee2e2;
color: #dc2626;
}
.cart-item-total {
margin: 0;
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
}
/* Wishlist Items */
.wishlist-item {
display: flex;
gap: 12px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
margin-bottom: 12px;
position: relative;
transition: all 0.2s;
}
.wishlist-item:hover {
background: #f5f5f5;
}
.wishlist-item-image {
flex-shrink: 0;
width: 80px;
height: 80px;
border-radius: 6px;
overflow: hidden;
background: white;
}
.wishlist-item-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.wishlist-item-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.wishlist-item-name {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.4;
padding-right: 24px;
}
.wishlist-item-price {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #6b46c1;
}
.btn-move-to-cart {
align-self: flex-start;
padding: 6px 14px;
border: 1px solid #6b46c1;
background: transparent;
color: #6b46c1;
font-size: 13px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.btn-move-to-cart:hover {
background: #6b46c1;
color: white;
}
.wishlist-item-remove {
position: absolute;
top: 12px;
right: 12px;
width: 28px;
height: 28px;
border: none;
background: transparent;
color: #9ca3af;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.wishlist-item-remove:hover {
background: #fee2e2;
color: #dc2626;
}
/* Notifications */
.notification {
position: fixed;
bottom: 24px;
right: 24px;
min-width: 280px;
padding: 16px 20px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
gap: 12px;
z-index: 10000;
transform: translateY(100px);
opacity: 0;
transition: all 0.3s ease;
font-family: 'Roboto', sans-serif;
}
.notification.show {
transform: translateY(0);
opacity: 1;
}
.notification i {
font-size: 20px;
flex-shrink: 0;
}
.notification-success {
border-left: 4px solid #10b981;
}
.notification-success i {
color: #10b981;
}
.notification-info {
border-left: 4px solid #3b82f6;
}
.notification-info i {
color: #3b82f6;
}
.notification span {
flex: 1;
font-size: 14px;
font-weight: 500;
color: #1a1a1a;
}
/* Mobile Responsive */
@media (max-width: 640px) {
.cart-item,
.wishlist-item {
padding: 12px;
}
.cart-item-image,
.wishlist-item-image {
width: 64px;
height: 64px;
}
.cart-item-name,
.wishlist-item-name {
font-size: 13px;
}
.notification {
right: 16px;
left: 16px;
min-width: auto;
}
}

View File

@@ -0,0 +1,287 @@
/**
* Accessibility Enhancements
* WCAG 2.1 AA Compliant
*/
(function () {
"use strict";
const A11y = {
init() {
this.addSkipLink();
this.enhanceFocusManagement();
this.addARIALabels();
this.improveKeyboardNav();
this.addLiveRegions();
this.enhanceFormAccessibility();
console.log("[A11y] Accessibility enhancements loaded");
},
// Add skip to main content link
addSkipLink() {
if (document.querySelector(".skip-link")) return;
const skipLink = document.createElement("a");
skipLink.href = "#main-content";
skipLink.className = "skip-link";
skipLink.textContent = "Skip to main content";
skipLink.addEventListener("click", (e) => {
e.preventDefault();
const main = document.querySelector("#main-content, main");
if (main) {
main.setAttribute("tabindex", "-1");
main.focus();
}
});
document.body.insertBefore(skipLink, document.body.firstChild);
},
// Enhance focus management
enhanceFocusManagement() {
// Trap focus in modals
document.addEventListener("keydown", (e) => {
if (e.key !== "Tab") return;
const modal = document.querySelector(
'.modal.active, .dropdown[style*="display: flex"]'
);
if (!modal) return;
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusable.length === 0) return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
// Focus visible styles
const style = document.createElement("style");
style.textContent = `
*:focus-visible {
outline: 3px solid #667eea !important;
outline-offset: 2px !important;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 3px solid #667eea !important;
outline-offset: 2px !important;
}
`;
document.head.appendChild(style);
},
// Add ARIA labels to interactive elements
addARIALabels() {
// Cart button
const cartBtn = document.querySelector("#cart-btn");
if (cartBtn && !cartBtn.hasAttribute("aria-label")) {
cartBtn.setAttribute("aria-label", "Shopping cart");
cartBtn.setAttribute("aria-haspopup", "true");
}
// Wishlist button
const wishlistBtn = document.querySelector("#wishlist-btn");
if (wishlistBtn && !wishlistBtn.hasAttribute("aria-label")) {
wishlistBtn.setAttribute("aria-label", "Wishlist");
wishlistBtn.setAttribute("aria-haspopup", "true");
}
// Mobile menu toggle
const menuToggle = document.querySelector(".mobile-menu-toggle");
if (menuToggle && !menuToggle.hasAttribute("aria-label")) {
menuToggle.setAttribute("aria-label", "Open navigation menu");
menuToggle.setAttribute("aria-expanded", "false");
}
// Add ARIA labels to product cards
document.querySelectorAll(".product-card").forEach((card, index) => {
if (!card.hasAttribute("role")) {
card.setAttribute("role", "article");
}
const title = card.querySelector("h3, .product-title");
if (title && !title.id) {
title.id = `product-title-${index}`;
card.setAttribute("aria-labelledby", title.id);
}
});
// Add labels to icon-only buttons
document.querySelectorAll("button:not([aria-label])").forEach((btn) => {
const icon = btn.querySelector('i[class*="bi-"]');
if (icon && !btn.textContent.trim()) {
const iconClass = icon.className;
let label = "Button";
if (iconClass.includes("cart")) label = "Add to cart";
else if (iconClass.includes("heart")) label = "Add to wishlist";
else if (iconClass.includes("trash")) label = "Remove";
else if (iconClass.includes("plus")) label = "Increase";
else if (iconClass.includes("minus") || iconClass.includes("dash"))
label = "Decrease";
else if (iconClass.includes("close") || iconClass.includes("x"))
label = "Close";
btn.setAttribute("aria-label", label);
}
});
},
// Improve keyboard navigation
improveKeyboardNav() {
// Dropdown keyboard support
document.querySelectorAll("[data-dropdown-toggle]").forEach((toggle) => {
toggle.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle.click();
}
});
});
// Product card keyboard navigation
document.querySelectorAll(".product-card").forEach((card) => {
const link = card.querySelector("a");
if (link) {
card.addEventListener("keydown", (e) => {
if (e.key === "Enter" && e.target === card) {
link.click();
}
});
}
});
// Quantity input keyboard support
document.querySelectorAll(".quantity-input").forEach((input) => {
input.addEventListener("keydown", (e) => {
if (e.key === "ArrowUp") {
e.preventDefault();
const newValue = parseInt(input.value || 1) + 1;
if (newValue <= 99) {
input.value = newValue;
input.dispatchEvent(new Event("change"));
}
} else if (e.key === "ArrowDown") {
e.preventDefault();
const newValue = parseInt(input.value || 1) - 1;
if (newValue >= 1) {
input.value = newValue;
input.dispatchEvent(new Event("change"));
}
}
});
});
},
// Add live regions for dynamic content
addLiveRegions() {
// Create announcement region
if (!document.querySelector("#a11y-announcements")) {
const announcer = document.createElement("div");
announcer.id = "a11y-announcements";
announcer.setAttribute("role", "status");
announcer.setAttribute("aria-live", "polite");
announcer.setAttribute("aria-atomic", "true");
announcer.className = "sr-only";
document.body.appendChild(announcer);
}
// Announce cart/wishlist updates
window.addEventListener("cart-updated", (e) => {
this.announce(`Cart updated. ${e.detail.length} items in cart.`);
});
window.addEventListener("wishlist-updated", (e) => {
this.announce(
`Wishlist updated. ${e.detail.length} items in wishlist.`
);
});
},
announce(message) {
const announcer = document.querySelector("#a11y-announcements");
if (announcer) {
announcer.textContent = "";
setTimeout(() => {
announcer.textContent = message;
}, 100);
}
},
// Enhance form accessibility
enhanceFormAccessibility() {
// Add required indicators
document
.querySelectorAll(
"input[required], select[required], textarea[required]"
)
.forEach((field) => {
const label = document.querySelector(`label[for="${field.id}"]`);
if (label && !label.querySelector(".required-indicator")) {
const indicator = document.createElement("span");
indicator.className = "required-indicator";
indicator.textContent = " *";
indicator.setAttribute("aria-label", "required");
label.appendChild(indicator);
}
});
// Add error message associations
document.querySelectorAll(".error-message").forEach((error, index) => {
if (!error.id) {
error.id = `error-${index}`;
}
const field = error.previousElementSibling;
if (
field &&
(field.tagName === "INPUT" ||
field.tagName === "SELECT" ||
field.tagName === "TEXTAREA")
) {
field.setAttribute("aria-describedby", error.id);
field.setAttribute("aria-invalid", "true");
}
});
// Add autocomplete attributes
document.querySelectorAll('input[type="email"]').forEach((field) => {
if (!field.hasAttribute("autocomplete")) {
field.setAttribute("autocomplete", "email");
}
});
document.querySelectorAll('input[type="tel"]').forEach((field) => {
if (!field.hasAttribute("autocomplete")) {
field.setAttribute("autocomplete", "tel");
}
});
},
};
// Initialize on DOM ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => A11y.init());
} else {
A11y.init();
}
// Export for external use
window.A11y = A11y;
})();

View File

@@ -84,7 +84,8 @@
closeId: "cartClose",
wrapperClass: ".cart-dropdown-wrapper",
eventName: "cart-updated",
emptyMessage: '<p class="empty-state"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>'
emptyMessage:
'<p class="empty-state"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>',
});
}
@@ -97,9 +98,10 @@
}
const cart = window.AppState.cart;
if (!Array.isArray(cart)) {
this.content.innerHTML = '<p class="empty-state">Error loading cart</p>';
this.content.innerHTML =
'<p class="empty-state">Error loading cart</p>';
return;
}
@@ -115,19 +117,24 @@
this.updateFooter(null);
return;
}
this.content.innerHTML = validItems.map(item => this.renderCartItem(item)).join("");
this.content.innerHTML = validItems
.map((item) => this.renderCartItem(item))
.join("");
this.setupCartItemListeners();
const total = this._calculateTotal(validItems);
this.updateFooter(total);
} catch (error) {
this.content.innerHTML = '<p class="empty-state">Error loading cart</p>';
this.content.innerHTML =
'<p class="empty-state">Error loading cart</p>';
}
}
_filterValidItems(items) {
return items.filter(item => item && item.id && typeof item.price !== 'undefined');
return items.filter(
(item) => item && item.id && typeof item.price !== "undefined"
);
}
_calculateTotal(items) {
@@ -137,7 +144,7 @@
return items.reduce((sum, item) => {
const price = parseFloat(item.price) || 0;
const quantity = parseInt(item.quantity) || 0;
return sum + (price * quantity);
return sum + price * quantity;
}, 0);
}
@@ -145,13 +152,13 @@
try {
// Validate item and Utils availability
if (!item || !item.id) {
return '';
return "";
}
if (!window.Utils) {
return '<p class="error-message">Error loading item</p>';
}
// Sanitize and validate item data with defensive checks
const imageUrl =
item.imageurl ||
@@ -164,7 +171,7 @@
const price = parseFloat(item.price) || 0;
const quantity = Math.max(1, parseInt(item.quantity) || 1);
const subtotal = price * quantity;
const priceFormatted = window.Utils.formatCurrency(price);
const subtotalFormatted = window.Utils.formatCurrency(subtotal);
@@ -191,7 +198,7 @@
</div>
`;
} catch (error) {
return '';
return "";
}
}
@@ -231,19 +238,20 @@
this._handleAction(e, () => {
const id = e.currentTarget.dataset.id;
if (!window.AppState?.cart) return;
const item = window.AppState.cart.find(
(item) => String(item.id) === String(id)
);
if (!item || !window.AppState.updateCartQuantity) return;
const newQuantity = delta > 0
? Math.min(item.quantity + delta, 999)
: Math.max(item.quantity + delta, 1);
const newQuantity =
delta > 0
? Math.min(item.quantity + delta, 999)
: Math.max(item.quantity + delta, 1);
if (delta < 0 && item.quantity <= 1) return;
window.AppState.updateCartQuantity(id, newQuantity);
this.render();
});
@@ -291,28 +299,10 @@
closeId: "wishlistClose",
wrapperClass: ".wishlist-dropdown-wrapper",
eventName: "wishlist-updated",
emptyMessage: '<p class="empty-state"><i class="bi bi-heart"></i><br>Your wishlist is empty</p>'
emptyMessage:
'<p class="empty-state"><i class="bi bi-heart"></i><br>Your wishlist is empty</p>',
});
}
this.isOpen ? this.close() : this.open();
}
open() {
if (this.wishlistPanel) {
this.wishlistPanel.classList.add("active");
this.wishlistPanel.setAttribute("aria-hidden", "false");
this.isOpen = true;
this.render();
}
}
close() {
if (this.wishlistPanel) {
this.wishlistPanel.classList.remove("active");
this.wishlistPanel.setAttribute("aria-hidden", "true");
this.isOpen = false;
}
}
render() {
if (!this.content) return;

View File

@@ -0,0 +1,818 @@
/**
* Enhanced Main Application JavaScript
* Production-Ready with No Console Errors
* Proper State Management & API Integration
*/
(function () {
"use strict";
// Production mode check
const isDevelopment =
window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1";
// Safe console wrapper
const logger = {
log: (...args) => isDevelopment && console.log(...args),
error: (...args) => console.error(...args),
warn: (...args) => isDevelopment && console.warn(...args),
info: (...args) => isDevelopment && console.info(...args),
};
// ========================================
// GLOBAL STATE MANAGEMENT
// ========================================
window.AppState = {
cart: [],
wishlist: [],
products: [],
settings: null,
user: null,
_saveCartTimeout: null,
_saveWishlistTimeout: null,
_initialized: false,
// Initialize state
init() {
if (this._initialized) {
logger.warn("[AppState] Already initialized");
return;
}
logger.info("[AppState] Initializing...");
this.loadCart();
this.loadWishlist();
this.updateUI();
this._initialized = true;
logger.info(
"[AppState] Initialized - Cart:",
this.cart.length,
"items, Wishlist:",
this.wishlist.length,
"items"
);
// Dispatch ready event
window.dispatchEvent(new CustomEvent("appstate-ready"));
},
// ========================================
// CART MANAGEMENT
// ========================================
loadCart() {
try {
const saved = localStorage.getItem("cart");
this.cart = saved ? JSON.parse(saved) : [];
// Validate cart items
this.cart = this.cart.filter((item) => item && item.id && item.price);
} catch (error) {
logger.error("Error loading cart:", error);
this.cart = [];
}
},
saveCart() {
if (this._saveCartTimeout) {
clearTimeout(this._saveCartTimeout);
}
this._saveCartTimeout = setTimeout(() => {
try {
localStorage.setItem("cart", JSON.stringify(this.cart));
this.updateUI();
window.dispatchEvent(
new CustomEvent("cart-updated", { detail: this.cart })
);
} catch (error) {
logger.error("Error saving cart:", error);
this.showNotification("Error saving cart", "error");
}
}, 100);
},
addToCart(product, quantity = 1) {
if (!product || !product.id) {
logger.error("[AppState] Invalid product:", product);
this.showNotification("Invalid product", "error");
return false;
}
try {
const existing = this.cart.find((item) => item.id === product.id);
if (existing) {
existing.quantity = (existing.quantity || 1) + quantity;
logger.info("[AppState] Updated cart quantity:", existing);
} else {
this.cart.push({
...product,
quantity,
addedAt: new Date().toISOString(),
});
logger.info("[AppState] Added to cart:", product.name);
}
this.saveCart();
this.showNotification(
`${product.name || "Item"} added to cart`,
"success"
);
return true;
} catch (error) {
logger.error("[AppState] Error adding to cart:", error);
this.showNotification("Error adding to cart", "error");
return false;
}
},
removeFromCart(productId) {
if (!productId) {
logger.error("[AppState] Invalid productId");
return false;
}
const initialLength = this.cart.length;
this.cart = this.cart.filter((item) => item.id !== productId);
if (this.cart.length < initialLength) {
this.saveCart();
this.showNotification("Removed from cart", "info");
return true;
}
return false;
},
updateCartQuantity(productId, quantity) {
const item = this.cart.find((item) => item.id === productId);
if (item) {
item.quantity = Math.max(1, parseInt(quantity) || 1);
this.saveCart();
return true;
}
return false;
},
clearCart() {
this.cart = [];
this.saveCart();
this.showNotification("Cart cleared", "info");
},
getCartTotal() {
return this.cart.reduce((sum, item) => {
const price = parseFloat(item.price) || 0;
const quantity = parseInt(item.quantity) || 1;
return sum + price * quantity;
}, 0);
},
getCartCount() {
return this.cart.reduce(
(sum, item) => sum + (parseInt(item.quantity) || 1),
0
);
},
// ========================================
// WISHLIST MANAGEMENT
// ========================================
loadWishlist() {
try {
const saved = localStorage.getItem("wishlist");
this.wishlist = saved ? JSON.parse(saved) : [];
// Validate wishlist items
this.wishlist = this.wishlist.filter((item) => item && item.id);
} catch (error) {
logger.error("Error loading wishlist:", error);
this.wishlist = [];
}
},
saveWishlist() {
if (this._saveWishlistTimeout) {
clearTimeout(this._saveWishlistTimeout);
}
this._saveWishlistTimeout = setTimeout(() => {
try {
localStorage.setItem("wishlist", JSON.stringify(this.wishlist));
this.updateUI();
window.dispatchEvent(
new CustomEvent("wishlist-updated", { detail: this.wishlist })
);
} catch (error) {
logger.error("Error saving wishlist:", error);
}
}, 100);
},
addToWishlist(product) {
if (!product || !product.id) {
logger.error("[AppState] Invalid product:", product);
this.showNotification("Invalid product", "error");
return false;
}
try {
const exists = this.wishlist.some((item) => item.id === product.id);
if (exists) {
this.showNotification("Already in wishlist", "info");
return false;
}
this.wishlist.push({
...product,
addedAt: new Date().toISOString(),
});
this.saveWishlist();
this.showNotification(
`${product.name || "Item"} added to wishlist`,
"success"
);
return true;
} catch (error) {
logger.error("[AppState] Error adding to wishlist:", error);
this.showNotification("Error adding to wishlist", "error");
return false;
}
},
removeFromWishlist(productId) {
if (!productId) return false;
const initialLength = this.wishlist.length;
this.wishlist = this.wishlist.filter((item) => item.id !== productId);
if (this.wishlist.length < initialLength) {
this.saveWishlist();
this.showNotification("Removed from wishlist", "info");
return true;
}
return false;
},
isInWishlist(productId) {
return this.wishlist.some((item) => item.id === productId);
},
getWishlistCount() {
return this.wishlist.length;
},
// ========================================
// UI UPDATES
// ========================================
updateUI() {
this.updateCartBadge();
this.updateWishlistBadge();
this.updateCartDropdown();
this.updateWishlistDropdown();
},
updateCartBadge() {
const badges = document.querySelectorAll(
".cart-count, .cart-badge, #cartCount"
);
const count = this.getCartCount();
badges.forEach((badge) => {
badge.textContent = count;
if (count > 0) {
badge.classList.add("show");
} else {
badge.classList.remove("show");
}
});
},
updateWishlistBadge() {
const badges = document.querySelectorAll(
".wishlist-count, .wishlist-badge, #wishlistCount"
);
const count = this.getWishlistCount();
badges.forEach((badge) => {
badge.textContent = count;
if (count > 0) {
badge.classList.add("show");
} else {
badge.classList.remove("show");
}
});
},
updateCartDropdown() {
const container = document.querySelector("#cart-items");
if (!container) return;
if (this.cart.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="bi bi-cart-x"></i>
<p>Your cart is empty</p>
</div>
`;
const totalEl = document.querySelector(".cart-total-value");
if (totalEl) totalEl.textContent = "$0.00";
return;
}
container.innerHTML = this.cart
.map((item) => this.renderCartItem(item))
.join("");
const totalEl = document.querySelector(".cart-total-value");
if (totalEl) {
totalEl.textContent = `$${this.getCartTotal().toFixed(2)}`;
}
this.attachCartEventListeners();
},
updateWishlistDropdown() {
const container = document.querySelector("#wishlist-items");
if (!container) return;
if (this.wishlist.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="bi bi-heart"></i>
<p>Your wishlist is empty</p>
</div>
`;
return;
}
container.innerHTML = this.wishlist
.map((item) => this.renderWishlistItem(item))
.join("");
this.attachWishlistEventListeners();
},
renderCartItem(item) {
const price = parseFloat(item.price) || 0;
const quantity = parseInt(item.quantity) || 1;
const imageUrl = this.getProductImage(item);
const name = this.sanitizeHTML(item.name || "Product");
return `
<div class="cart-item" data-product-id="${item.id}">
<div class="cart-item-image">
<img src="${imageUrl}" alt="${name}" loading="lazy" onerror="this.src='/assets/img/placeholder.jpg'">
</div>
<div class="cart-item-info">
<div class="cart-item-title">${name}</div>
<div class="cart-item-price">$${price.toFixed(2)}</div>
<div class="cart-item-controls">
<button class="btn-quantity" data-action="decrease" aria-label="Decrease quantity">
<i class="bi bi-dash"></i>
</button>
<input type="number" class="quantity-input" value="${quantity}" min="1" max="99" aria-label="Quantity">
<button class="btn-quantity" data-action="increase" aria-label="Increase quantity">
<i class="bi bi-plus"></i>
</button>
</div>
</div>
<button class="btn-remove" data-action="remove" aria-label="Remove from cart">
<i class="bi bi-x-lg"></i>
</button>
</div>
`;
},
renderWishlistItem(item) {
const price = parseFloat(item.price) || 0;
const imageUrl = this.getProductImage(item);
const name = this.sanitizeHTML(item.name || "Product");
return `
<div class="wishlist-item" data-product-id="${item.id}">
<div class="wishlist-item-image">
<img src="${imageUrl}" alt="${name}" loading="lazy" onerror="this.src='/assets/img/placeholder.jpg'">
</div>
<div class="wishlist-item-info">
<div class="wishlist-item-title">${name}</div>
<div class="wishlist-item-price">$${price.toFixed(2)}</div>
<button class="btn-add-to-cart" data-product-id="${item.id}">
<i class="bi bi-cart-plus"></i> Add to Cart
</button>
</div>
<button class="btn-remove" data-action="remove" aria-label="Remove from wishlist">
<i class="bi bi-x-lg"></i>
</button>
</div>
`;
},
attachCartEventListeners() {
document.querySelectorAll(".cart-item").forEach((item) => {
const productId = item.dataset.productId;
// Quantity controls
const decreaseBtn = item.querySelector('[data-action="decrease"]');
const increaseBtn = item.querySelector('[data-action="increase"]');
const quantityInput = item.querySelector(".quantity-input");
if (decreaseBtn) {
decreaseBtn.addEventListener("click", () => {
const currentQty = parseInt(quantityInput.value) || 1;
if (currentQty > 1) {
quantityInput.value = currentQty - 1;
this.updateCartQuantity(productId, currentQty - 1);
}
});
}
if (increaseBtn) {
increaseBtn.addEventListener("click", () => {
const currentQty = parseInt(quantityInput.value) || 1;
if (currentQty < 99) {
quantityInput.value = currentQty + 1;
this.updateCartQuantity(productId, currentQty + 1);
}
});
}
if (quantityInput) {
quantityInput.addEventListener("change", (e) => {
const newQty = parseInt(e.target.value) || 1;
this.updateCartQuantity(productId, newQty);
});
}
// Remove button
const removeBtn = item.querySelector('[data-action="remove"]');
if (removeBtn) {
removeBtn.addEventListener("click", () => {
this.removeFromCart(productId);
});
}
});
},
attachWishlistEventListeners() {
document.querySelectorAll(".wishlist-item").forEach((item) => {
const productId = item.dataset.productId;
// Add to cart button
const addBtn = item.querySelector(".btn-add-to-cart");
if (addBtn) {
addBtn.addEventListener("click", () => {
const product = this.wishlist.find((p) => p.id === productId);
if (product) {
this.addToCart(product);
}
});
}
// Remove button
const removeBtn = item.querySelector('[data-action="remove"]');
if (removeBtn) {
removeBtn.addEventListener("click", () => {
this.removeFromWishlist(productId);
});
}
});
},
// ========================================
// NOTIFICATIONS
// ========================================
showNotification(message, type = "info") {
if (!message) return;
let container = document.querySelector(".notification-container");
if (!container) {
container = document.createElement("div");
container.className = "notification-container";
document.body.appendChild(container);
}
const notification = document.createElement("div");
notification.className = `notification ${type}`;
const icon =
type === "success"
? "check-circle-fill"
: type === "error"
? "exclamation-circle-fill"
: "info-circle-fill";
notification.innerHTML = `
<i class="bi bi-${icon}"></i>
<span class="notification-message">${this.sanitizeHTML(message)}</span>
`;
container.appendChild(notification);
setTimeout(() => {
notification.style.animation = "slideOut 0.3s ease forwards";
setTimeout(() => notification.remove(), 300);
}, 3000);
},
// ========================================
// API INTEGRATION
// ========================================
async fetchProducts() {
try {
const response = await fetch("/api/products");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.success && Array.isArray(data.products)) {
this.products = data.products;
window.dispatchEvent(
new CustomEvent("products-loaded", { detail: this.products })
);
return this.products;
}
throw new Error("Invalid API response");
} catch (error) {
logger.error("Error fetching products:", error);
this.showNotification("Error loading products", "error");
return [];
}
},
async fetchSettings() {
try {
const response = await fetch("/api/settings");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.success && data.settings) {
this.settings = data.settings;
window.dispatchEvent(
new CustomEvent("settings-loaded", { detail: this.settings })
);
return this.settings;
}
throw new Error("Invalid API response");
} catch (error) {
logger.error("Error fetching settings:", error);
return null;
}
},
// ========================================
// UTILITY METHODS
// ========================================
getProductImage(product) {
if (!product) return "/assets/img/placeholder.jpg";
// Check various image properties
if (product.image_url) return product.image_url;
if (product.imageUrl) return product.imageUrl;
if (
product.images &&
Array.isArray(product.images) &&
product.images.length > 0
) {
return (
product.images[0].image_url ||
product.images[0].url ||
"/assets/img/placeholder.jpg"
);
}
if (product.thumbnail) return product.thumbnail;
return "/assets/img/placeholder.jpg";
},
sanitizeHTML(str) {
if (!str) return "";
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
},
formatPrice(price) {
const num = parseFloat(price) || 0;
return `$${num.toFixed(2)}`;
},
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
};
// ========================================
// DROPDOWN MANAGEMENT
// ========================================
window.DropdownManager = {
activeDropdown: null,
init() {
this.attachEventListeners();
logger.info("[DropdownManager] Initialized");
},
attachEventListeners() {
// Cart toggle
const cartBtn = document.querySelector("#cart-btn");
if (cartBtn) {
cartBtn.addEventListener("click", (e) => {
e.stopPropagation();
this.toggle("cart");
});
}
// Wishlist toggle
const wishlistBtn = document.querySelector("#wishlist-btn");
if (wishlistBtn) {
wishlistBtn.addEventListener("click", (e) => {
e.stopPropagation();
this.toggle("wishlist");
});
}
// Close on outside click
document.addEventListener("click", (e) => {
if (!e.target.closest(".dropdown") && !e.target.closest(".icon-btn")) {
this.closeAll();
}
});
// Close on escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
this.closeAll();
}
});
},
toggle(type) {
const dropdown = document.querySelector(`#${type}-dropdown`);
if (!dropdown) return;
if (this.activeDropdown === dropdown) {
this.close(dropdown);
} else {
this.closeAll();
this.open(dropdown, type);
}
},
open(dropdown, type) {
dropdown.style.display = "flex";
this.activeDropdown = dropdown;
// Update content
if (type === "cart") {
window.AppState.updateCartDropdown();
} else if (type === "wishlist") {
window.AppState.updateWishlistDropdown();
}
},
close(dropdown) {
if (dropdown) {
dropdown.style.display = "none";
}
this.activeDropdown = null;
},
closeAll() {
document.querySelectorAll(".dropdown").forEach((dropdown) => {
dropdown.style.display = "none";
});
this.activeDropdown = null;
},
};
// ========================================
// MOBILE MENU
// ========================================
window.MobileMenu = {
menu: null,
overlay: null,
isOpen: false,
init() {
this.menu = document.querySelector(".mobile-menu");
this.overlay = document.querySelector(".mobile-menu-overlay");
if (!this.menu || !this.overlay) {
logger.warn("[MobileMenu] Elements not found");
return;
}
this.attachEventListeners();
logger.info("[MobileMenu] Initialized");
},
attachEventListeners() {
// Toggle button
const toggleBtn = document.querySelector(".mobile-menu-toggle");
if (toggleBtn) {
toggleBtn.addEventListener("click", () => this.toggle());
}
// Close button
const closeBtn = document.querySelector(".mobile-menu-close");
if (closeBtn) {
closeBtn.addEventListener("click", () => this.close());
}
// Overlay click
if (this.overlay) {
this.overlay.addEventListener("click", () => this.close());
}
// Menu links
this.menu.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", () => {
setTimeout(() => this.close(), 100);
});
});
// Escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && this.isOpen) {
this.close();
}
});
},
toggle() {
this.isOpen ? this.close() : this.open();
},
open() {
this.menu.classList.add("active");
this.overlay.classList.add("active");
this.isOpen = true;
document.body.style.overflow = "hidden";
},
close() {
this.menu.classList.remove("active");
this.overlay.classList.remove("active");
this.isOpen = false;
document.body.style.overflow = "";
},
};
// ========================================
// INITIALIZATION
// ========================================
function initialize() {
logger.info("[App] Initializing...");
// Initialize state
if (window.AppState) {
window.AppState.init();
}
// Initialize dropdown manager
if (window.DropdownManager) {
window.DropdownManager.init();
}
// Initialize mobile menu
if (window.MobileMenu) {
window.MobileMenu.init();
}
// Fetch initial data
if (window.AppState.fetchSettings) {
window.AppState.fetchSettings();
}
logger.info("[App] Initialization complete");
}
// Run on DOM ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initialize);
} else {
initialize();
}
// Export for debugging in development
if (isDevelopment) {
window.DEBUG = {
AppState: window.AppState,
DropdownManager: window.DropdownManager,
MobileMenu: window.MobileMenu,
logger,
};
}
})();

View File

@@ -6,7 +6,7 @@
(function () {
"use strict";
console.log('[main.js] Loading...');
console.log("[main.js] Loading...");
// Global state management
window.AppState = {
@@ -20,12 +20,18 @@
// Initialize state from localStorage
init() {
console.log('[AppState] Initializing...');
console.log('[AppState] window.AppState exists:', !!window.AppState);
console.log("[AppState] Initializing...");
console.log("[AppState] window.AppState exists:", !!window.AppState);
this.loadCart();
this.loadWishlist();
this.updateUI();
console.log('[AppState] Initialized - Cart:', this.cart.length, 'items, Wishlist:', this.wishlist.length, 'items');
console.log(
"[AppState] Initialized - Cart:",
this.cart.length,
"items, Wishlist:",
this.wishlist.length,
"items"
);
},
// Cart management
@@ -55,18 +61,26 @@
},
addToCart(product, quantity = 1) {
console.log('[AppState] addToCart called:', product, 'quantity:', quantity);
console.log(
"[AppState] addToCart called:",
product,
"quantity:",
quantity
);
const existing = this.cart.find((item) => item.id === product.id);
if (existing) {
console.log('[AppState] Product exists in cart, updating quantity');
console.log("[AppState] Product exists in cart, updating quantity");
existing.quantity += quantity;
} else {
console.log('[AppState] Adding new product to cart');
console.log("[AppState] Adding new product to cart");
this.cart.push({ ...product, quantity });
}
console.log('[AppState] Cart after add:', this.cart);
console.log("[AppState] Cart after add:", this.cart);
this.saveCart();
this.showNotification(`${product.name || product.title || 'Item'} added to cart`, "success");
this.showNotification(
`${product.name || product.title || "Item"} added to cart`,
"success"
);
},
removeFromCart(productId) {
@@ -77,9 +91,11 @@
updateCartQuantity(productId, quantity) {
const item = this.cart.find((item) => item.id === productId);
// Dispatch custom event for cart dropdown
window.dispatchEvent(new CustomEvent('cart-updated', { detail: this.cart }));
// Dispatch custom event for cart dropdown
window.dispatchEvent(
new CustomEvent("cart-updated", { detail: this.cart })
);
if (item) {
item.quantity = Math.max(1, quantity);
this.saveCart();
@@ -118,18 +134,20 @@
},
addToWishlist(product) {
if (!this.wishlist.find(`${product.name || product.title || 'Item'} added to wishlist`, "success");
// Dispatch custom event for wishlist dropdown
window.dispatchEvent(new CustomEvent('wishlist-updated', { detail: this.wishlist }));
} else {
this.showNotification("Already in wishlist", "info.id)) {
if (!this.wishlist.find((item) => item.id === product.id)) {
this.wishlist.push(product);
this.saveWishlist();
// Dispatch custom event for wishlist dropdown
window.dispatchEvent(new CustomEvent('wishlist-updated', { detail: this.wishlist }));
this.showNotification("Added to wishlist", "success");
this.showNotification(
`${product.name || product.title || "Item"} added to wishlist`,
"success"
);
// Dispatch custom event for wishlist dropdown
window.dispatchEvent(
new CustomEvent("wishlist-updated", { detail: this.wishlist })
);
} else {
this.showNotification("Already in wishlist", "info");
}
},
@@ -151,32 +169,44 @@
updateCartUI() {
const count = this.getCartCount();
console.log('[AppState] Updating cart UI, count:', count);
console.log("[AppState] Updating cart UI, count:", count);
const badge = document.getElementById("cartCount");
if (badge) {
badge.textContent = count;
badge.style.display = count > 0 ? "flex" : "none";
console.log('[AppState] Cart badge updated');
if (count > 0) {
badge.classList.add("show");
} else {
badge.classList.remove("show");
}
console.log("[AppState] Cart badge updated");
} else {
console.warn('[AppState] Cart badge element not found');
console.warn("[AppState] Cart badge element not found");
}
// Also trigger cart dropdown update
window.dispatchEvent(new CustomEvent('cart-updated', { detail: this.cart }));
window.dispatchEvent(
new CustomEvent("cart-updated", { detail: this.cart })
);
},
updateWishlistUI() {
const count = this.wishlist.length;
console.log('[AppState] Updating wishlist UI, count:', count);
console.log("[AppState] Updating wishlist UI, count:", count);
const badge = document.getElementById("wishlistCount");
if (badge) {
badge.textContent = count;
badge.style.display = count > 0 ? "flex" : "none";
console.log('[AppState] Wishlist badge updated');
if (count > 0) {
badge.classList.add("show");
} else {
badge.classList.remove("show");
}
console.log("[AppState] Wishlist badge updated");
} else {
console.warn('[AppState] Wishlist badge element not found');
console.warn("[AppState] Wishlist badge element not found");
}
// Also trigger wishlist dropdown update
window.dispatchEvent(new CustomEvent('wishlist-updated', { detail: this.wishlist }));
window.dispatchEvent(
new CustomEvent("wishlist-updated", { detail: this.wishlist })
);
},
// Notifications
@@ -341,14 +371,17 @@
};
// Initialize on DOM ready
console.log('[main.js] Script loaded, document.readyState:', document.readyState);
console.log(
"[main.js] Script loaded, document.readyState:",
document.readyState
);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
console.log('[main.js] DOMContentLoaded fired');
console.log("[main.js] DOMContentLoaded fired");
window.AppState.init();
});
} else {
console.log('[main.js] DOM already loaded, initializing immediately');
console.log("[main.js] DOM already loaded, initializing immediately");
window.AppState.init();
}

View File

@@ -359,7 +359,11 @@
if (cartBadge) {
const count = this.getCartCount();
cartBadge.textContent = count;
cartBadge.style.display = count > 0 ? "flex" : "none";
if (count > 0) {
cartBadge.classList.add("show");
} else {
cartBadge.classList.remove("show");
}
}
// Update wishlist badge
@@ -367,7 +371,11 @@
if (wishlistBadge) {
const count = this.wishlist.length;
wishlistBadge.textContent = count;
wishlistBadge.style.display = count > 0 ? "flex" : "none";
if (count > 0) {
wishlistBadge.classList.add("show");
} else {
wishlistBadge.classList.remove("show");
}
}
}
@@ -463,14 +471,14 @@
const footer = document.querySelector("#cartPanel .dropdown-foot");
if (!footer) return;
if (total === 0) {
if (total === 0 || total === null) {
footer.innerHTML =
'<a href="/shop" class="btn-outline">Continue Shopping</a>';
} else {
footer.innerHTML = `
<div class="cart-total">
<span>Total:</span>
<strong>$${total.toFixed(2)}</strong>
<strong>${window.Utils.formatCurrency(total)}</strong>
</div>
<a href="/shop" class="btn-text">Continue Shopping</a>
<button class="btn-primary-full" onclick="alert('Checkout coming soon!')">

View File

@@ -226,21 +226,33 @@
// Update badges on state changes
window.StateManager.on("cartUpdated", () => {
const badge = document.querySelector(".cart-badge");
if (badge) {
const count = window.StateManager.getCartCount();
badge.textContent = count;
badge.style.display = count > 0 ? "flex" : "none";
}
const badges = document.querySelectorAll(".cart-badge, #cartCount");
const count = window.StateManager.getCartCount();
badges.forEach((badge) => {
if (badge) {
badge.textContent = count;
if (count > 0) {
badge.classList.add("show");
} else {
badge.classList.remove("show");
}
}
});
});
window.StateManager.on("wishlistUpdated", () => {
const badge = document.querySelector(".wishlist-badge");
if (badge) {
const count = window.StateManager.getWishlist().length;
badge.textContent = count;
badge.style.display = count > 0 ? "flex" : "none";
}
const badges = document.querySelectorAll(".wishlist-badge, #wishlistCount");
const count = window.StateManager.getWishlist().length;
badges.forEach((badge) => {
if (badge) {
badge.textContent = count;
if (count > 0) {
badge.classList.add("show");
} else {
badge.classList.remove("show");
}
}
});
});
// Initialize badges