Files
SkyArtShop/website/public/portfolio.html
Local Server 2db9f83d2d Performance: Optimize database and frontend-backend communication
Major optimizations implemented:

DATABASE:
- Added 6 new composite indexes for products queries
- Added slug index for blogposts and products
- Added composite index for portfolio active + display order
- ANALYZE all tables to update query planner statistics
- VACUUM database for optimal performance

FRONTEND API CACHING:
- Created api-cache.js with intelligent caching system
- Request deduplication for simultaneous calls
- Custom TTL per endpoint (5-30 minutes)
- Automatic cache cleanup every minute
- Cache hit/miss logging for monitoring

FRONTEND INTEGRATION:
- Updated portfolio.html to use apiCache
- Updated blog.html to use apiCache
- Updated shop.html to use apiCache
- Updated home.html to use apiCache
- Updated product.html to use apiCache (2 endpoints)

PERFORMANCE RESULTS:
- API response times: 7-12ms (excellent)
- Backend cache hit rates showing 0-41% improvement
- All endpoints returning HTTP 200
- All pages loading in under 10ms

TESTING:
- Added test-api-performance.sh for continuous monitoring
- Verified all 6 API endpoints functional
- Verified all frontend pages loading correctly
- Database indexes verified (30+ indexes active)

No functionality changes - pure performance optimization.
2026-01-14 08:19:20 -06:00

479 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About - Sky Art Shop</title>
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
/>
<link rel="stylesheet" href="/assets/css/theme-colors.css" />
<link rel="stylesheet" href="/assets/css/main.css?v=1735692100" />
<link rel="stylesheet" href="/assets/css/navbar.css?v=1767233028" />
<link rel="stylesheet" href="/assets/css/page-overrides.css?v=1736790001" />
<link rel="stylesheet" href="/assets/css/cart-wishlist.css" />
<link rel="stylesheet" href="/assets/css/shopping.css" />
<link rel="stylesheet" href="/assets/css/responsive.css" />
<link rel="stylesheet" href="/assets/css/navbar-mobile-fix.css?v=1736790000" />
</head>
<body>
<script>window.__bodyReady=true</script>
<div class="sticky-banner-wrapper">
<!-- Modern Navigation -->
<nav class="modern-navbar">
<div class="navbar-wrapper">
<div class="navbar-brand">
<a href="/home" class="brand-link">
<img
src="/uploads/cat-png-1767324141436-368259437.png"
alt="Sky Art Shop Logo"
class="brand-logo"
/>
<span class="brand-name">Sky' Art Shop</span>
</a>
</div>
<div class="navbar-menu">
<ul class="nav-menu-list">
<li class="nav-item">
<a href="/home" class="nav-link">Home</a>
</li>
<li class="nav-item">
<a href="/shop" class="nav-link">Shop</a>
</li>
<li class="nav-item">
<a href="/portfolio" class="nav-link active">Portfolio</a>
</li>
<li class="nav-item">
<a href="/about" class="nav-link">About</a>
</li>
<li class="nav-item">
<a href="/blog" class="nav-link">Blog</a>
</li>
<li class="nav-item">
<a href="/contact" class="nav-link">Contact</a>
</li>
</ul>
</div>
<div class="navbar-actions">
<div class="action-item wishlist-dropdown-wrapper">
<button
class="action-btn"
id="wishlistToggle"
aria-label="Wishlist"
>
<i class="bi bi-heart"></i>
<span class="action-badge" id="wishlistCount">0</span>
</button>
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
<div class="dropdown-head">
<h3>My Wishlist</h3>
<button class="dropdown-close" id="wishlistClose">
<i class="bi bi-x-lg"></i>
</button>
</div>
<div class="dropdown-body" id="wishlistContent">
<p class="empty-state">Your wishlist is empty</p>
</div>
<div class="dropdown-foot">
<a href="/shop" class="btn-outline">Continue Shopping</a>
</div>
</div>
</div>
<div class="action-item cart-dropdown-wrapper">
<button
class="action-btn"
id="cartToggle"
aria-label="Shopping Cart"
>
<i class="bi bi-cart3"></i>
<span class="action-badge" id="cartCount">0</span>
</button>
<div class="action-dropdown cart-dropdown" id="cartPanel">
<div class="dropdown-head">
<h3>Shopping Cart</h3>
<button class="dropdown-close" id="cartClose">
<i class="bi bi-x-lg"></i>
</button>
</div>
<div class="dropdown-body" id="cartContent">
<p class="empty-state"><i class="bi bi-cart-x"></i><br>Your cart is empty</p>
</div>
<div class="dropdown-foot">
<a href="/shop" class="btn-outline">Continue Shopping</a>
</div>
</div>
</div>
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
<span class="toggle-line"></span>
<span class="toggle-line"></span>
<span class="toggle-line"></span>
</button>
</div>
</div>
<div class="mobile-menu" id="mobileMenu">
<div class="mobile-menu-header">
<span class="mobile-brand">Sky' Art Shop</span>
<button class="mobile-close" id="mobileMenuClose">
<i class="bi bi-x-lg"></i>
</button>
</div>
<ul class="mobile-menu-list">
<li><a href="/home" class="mobile-link">Home</a></li>
<li><a href="/shop" class="mobile-link">Shop</a></li>
<li><a href="/portfolio" class="mobile-link">Portfolio</a></li>
<li><a href="/about" class="mobile-link">About</a></li>
<li><a href="/blog" class="mobile-link">Blog</a></li>
<li><a href="/contact" class="mobile-link">Contact</a></li>
</ul>
</div>
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
</nav>
<script>
// Mobile Menu Toggle
(function() {
const mobileToggle = document.getElementById('mobileMenuToggle');
const mobileMenu = document.getElementById('mobileMenu');
const mobileClose = document.getElementById('mobileMenuClose');
const overlay = document.getElementById('mobileMenuOverlay');
function openMenu() {
mobileMenu.classList.add('active');
overlay.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeMenu() {
mobileMenu.classList.remove('active');
overlay.classList.remove('active');
document.body.style.overflow = '';
}
if (mobileToggle) mobileToggle.addEventListener('click', openMenu);
if (mobileClose) mobileClose.addEventListener('click', closeMenu);
if (overlay) overlay.addEventListener('click', closeMenu);
// Close on ESC key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && mobileMenu.classList.contains('active')) {
closeMenu();
}
});
})();
</script>
<section class="about-hero">
<div class="container">
<h1>Portfolio</h1>
<p class="hero-subtitle">Explore our creative projects and artwork</p>
</section>
<section
class="portfolio-section"
style="padding: 60px 0; background: #ffebeb"
>
<div class="container">
<div id="loadingMessage" style="text-align: center; padding: 40px">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p style="margin-top: 15px; color: #666">
Loading portfolio projects...
</p>
</div>
<div
id="portfolioGrid"
class="products-grid"
style="
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 30px;
"
></div>
<div
id="noProjects"
style="display: none; text-align: center; padding: 40px; color: #666"
>
<i
class="bi bi-images"
style="font-size: 48px; color: #ccc; margin-bottom: 15px"
></i>
<p>No portfolio projects available at the moment.</p>
</div>
</div>
</section>
<footer class="footer">
<div class="container">
<div class="footer-grid">
<div class="footer-col">
<h3 class="footer-title">Sky Art Shop</h3>
<p class="footer-text">
Your destination for unique art pieces and creative supplies.
</p>
<div class="social-links">
<a href="#" class="social-link"><i class="bi bi-facebook"></i></a>
<a href="#" class="social-link"><i class="bi bi-instagram"></i></a>
<a href="#" class="social-link"><i class="bi bi-twitter"></i></a>
<a href="#" class="social-link"><i class="bi bi-pinterest"></i></a>
</div>
</div>
<div class="footer-col">
<h4 class="footer-heading">Shop</h4>
<ul class="footer-links">
<li><a href="/shop">All Products</a></li>
<li><a href="/shop?category=paintings">Paintings</a></li>
<li><a href="/shop?category=prints">Prints</a></li>
<li><a href="/shop?category=supplies">Art Supplies</a></li>
</ul>
</div>
<div class="footer-col">
<h4 class="footer-heading">About</h4>
<ul class="footer-links">
<li><a href="/about">Our Story</a></li>
<li><a href="/portfolio">Portfolio</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<div class="footer-col">
<h4 class="footer-heading">Customer Service</h4>
<ul class="footer-links">
<li><a href="/shipping-info">Shipping Info</a></li>
<li><a href="/returns">Returns</a></li>
<li><a href="/faq">FAQ</a></li>
<li><a href="/privacy">Privacy Policy</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 Sky Art Shop. All rights reserved.</p>
</div>
</div>
</footer>
<!-- Project Modal -->
<div
id="projectModal"
style="
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
z-index: 9999;
overflow-y: auto;
overflow-x: hidden;
padding: 40px 0;
"
onclick="if(event.target.id === 'projectModal') closeProjectModal();"
>
<div
style="
position: relative;
margin: auto;
width: 90%;
max-width: 900px;
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
"
onclick="event.stopPropagation();"
>
<button
onclick="closeProjectModal()"
style="
position: absolute;
top: 20px;
right: 20px;
background: white;
border: none;
width: 44px;
height: 44px;
border-radius: 50%;
cursor: pointer;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: all 0.2s;
"
onmouseover="this.style.transform='scale(1.1)'; this.style.background='#f8f9fa';"
onmouseout="this.style.transform='scale(1)'; this.style.background='white';"
>
<i class="bi bi-x-lg"></i>
</button>
<div
id="modalContent"
></div>
</div>
</div>
<script src="/assets/js/page-transitions.js?v=1766709739"></script>
<script src="/assets/js/back-button-control.js?v=1766723554"></script>
<script src="/assets/js/main.js"></script>
<script src="/assets/js/navigation.js"></script>
<script src="/assets/js/shop-system.js"></script>
<script src="/assets/js/shopping.js"></script>
<script src="/assets/js/api-cache.js"></script>
<script>
let portfolioProjects = [];
// Open project modal
function openProjectModal(projectId) {
const project = portfolioProjects.find((p) => p.id === projectId);
if (!project) {
console.error('[Portfolio] Project not found:', projectId);
return;
}
if (!project.title) {
console.error('[Portfolio] Invalid project data - missing title:', project);
return;
}
const modal = document.getElementById("projectModal");
const modalContent = document.getElementById("modalContent");
if (!modal || !modalContent) {
console.error('[Portfolio] Modal elements not found');
return;
}
// Build category badge HTML
const categoryBadge = project.category
? `<span style="display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 8px 18px; border-radius: 24px; font-size: 13px; font-weight: 600; margin-bottom: 24px; letter-spacing: 0.5px; text-transform: uppercase;">${project.category}</span>`
: '';
// Build modal content
modalContent.innerHTML = `
<div class="project-image" style="width: 100%; height: 450px; overflow: hidden; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); flex-shrink: 0;">
<img src="${project.featuredimage || project.imageurl || '/assets/images/placeholder.svg'}"
alt="${project.title}"
onerror="this.src='/assets/images/placeholder.svg'"
style="width: 100%; height: 100%; object-fit: cover;" />
</div>
<div style="padding: 40px; background: white;">
${categoryBadge}
<h2 style="font-size: 36px; font-weight: 700; margin: 0 0 24px 0; color: #1a1a1a; line-height: 1.2;">${project.title}</h2>
<div style="color: #555; font-size: 17px; line-height: 1.9; margin-bottom: 32px; font-weight: 400;">
${project.description || 'No description available.'}
</div>
</div>
`;
modal.style.display = "block";
document.body.style.overflow = "hidden";
document.documentElement.style.overflow = "hidden";
}
// Close project modal
function closeProjectModal() {
document.getElementById("projectModal").style.display = "none";
document.body.style.overflow = "auto";
document.documentElement.style.overflow = "auto";
}
// Close modal on outside click
document.addEventListener("click", (e) => {
const modal = document.getElementById("projectModal");
if (e.target === modal) {
closeProjectModal();
}
});
// Close modal on Escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
closeProjectModal();
}
});
// Load portfolio projects from API
async function loadPortfolio() {
try {
const response = await window.apiCache.fetch("/api/portfolio/projects");
if (response.ok) {
const data = await response.json();
portfolioProjects = data.projects || [];
document.getElementById("loadingMessage").style.display = "none";
if (portfolioProjects.length === 0) {
document.getElementById("noProjects").style.display = "block";
return;
}
// Validate and filter projects
const validProjects = portfolioProjects.filter(project => {
if (!project || !project.id || !project.title) {
console.warn('[Portfolio] Skipping invalid project:', project);
return false;
}
return true;
});
if (validProjects.length === 0) {
document.getElementById("noProjects").style.display = "block";
return;
}
const grid = document.getElementById("portfolioGrid");
grid.innerHTML = validProjects
.map(
(project) => `
<div class="product-card" onclick="openProjectModal('${
project.id
}')" style="background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s; cursor: pointer;">
<div class="product-image" style="position: relative; padding-top: 100%; overflow: hidden; background: #f5f5f5;">
<img src="${
project.featuredimage || project.imageurl || "/assets/images/placeholder.svg"
}"
alt="${project.title}"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s;"
loading="lazy"
onerror="this.src='/assets/images/placeholder.svg'"
onmouseover="this.style.transform='scale(1.05)'"
onmouseout="this.style.transform='scale(1)'" />
${
project.category
? `<span style="position: absolute; top: 10px; right: 10px; background: rgba(102, 126, 234, 0.9); color: white; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 500;">${project.category}</span>`
: ""
}
</div>
<div style="padding: 20px; text-align: center;">
<h3 style="font-size: 18px; font-weight: 600; margin: 0; color: #333;">${
project.title
}</h3>
${
project.description && project.description.replace(/<[^>]*>/g, '').trim()
? `<p style="font-size: 14px; color: #666; margin: 8px 0 0 0; line-height: 1.5;">${project.description.replace(/<[^>]*>/g, '').substring(0, 80)}${project.description.length > 80 ? '...' : ''}</p>`
: ""
}
</div>
</div>
`
)
.join("");
} else {
document.getElementById("noProjects").style.display = "block";
}
} catch (error) {
console.error("Error loading portfolio:", error);
document.getElementById("loadingMessage").innerHTML =
'<p style="color: #dc3545;">Error loading portfolio projects. Please try again later.</p>';
}
}
// Initialize
loadPortfolio();
</script>
</body>
</html>