Fix admin route access and backend configuration
- Added /admin redirect to login page in nginx config - Fixed backend server.js route ordering for proper admin handling - Updated authentication middleware and routes - Added user management routes - Configured PostgreSQL integration - Updated environment configuration
This commit is contained in:
81
Sky_Art_shop/Views/Shop/Index.cshtml
Normal file
81
Sky_Art_shop/Views/Shop/Index.cshtml
Normal file
@@ -0,0 +1,81 @@
|
||||
@model List<SkyArtShop.Models.Product>
|
||||
@{
|
||||
ViewData["Title"] = "Shop";
|
||||
var categories = ViewBag.Categories as List<string> ?? new();
|
||||
var selected = ViewBag.SelectedCategory as string;
|
||||
}
|
||||
|
||||
<section class="shop-hero">
|
||||
<div class="container">
|
||||
<h1>Shop All Products</h1>
|
||||
<p class="hero-subtitle">Find everything you need for your creative projects</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shop-filters">
|
||||
<div class="container">
|
||||
<div class="filter-bar">
|
||||
<div class="filter-group">
|
||||
<label for="category-filter">Category:</label>
|
||||
<select id="category-filter" onchange="window.location.href='/shop?category='+this.value;">
|
||||
<option value="">All Products</option>
|
||||
@foreach (var cat in categories)
|
||||
{
|
||||
<option value="@cat" selected="@(selected == cat ? "selected" : null)">@cat</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shop-products">
|
||||
<div class="container">
|
||||
<div class="products-grid">
|
||||
@foreach (var product in Model)
|
||||
{
|
||||
<div class="product-card">
|
||||
<a href="/shop/product/@product.Id" class="product-link">
|
||||
<div class="product-image">
|
||||
@{
|
||||
var displayImage = !string.IsNullOrEmpty(product.ImageUrl)
|
||||
? product.ImageUrl
|
||||
: (product.Images != null && product.Images.Count > 0
|
||||
? product.Images[0]
|
||||
: "/assets/images/placeholder.jpg");
|
||||
}
|
||||
<img src="@displayImage" alt="@product.Name" loading="lazy" />
|
||||
</div>
|
||||
<h3>@product.Name</h3>
|
||||
@if (!string.IsNullOrEmpty(product.Color))
|
||||
{
|
||||
<span class="product-color-badge">@product.Color</span>
|
||||
}
|
||||
<div class="product-description">@Html.Raw(product.ShortDescription ?? product.Description)</div>
|
||||
<p class="price">$@product.Price.ToString("F2")</p>
|
||||
</a>
|
||||
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem;">
|
||||
<button class="btn btn-small btn-icon"
|
||||
onclick="addToWishlist('@product.Id', '@product.Name', @product.Price, '@(product.Images != null && product.Images.Count > 0 ? product.Images[0] : product.ImageUrl ?? "/assets/images/placeholder.jpg")')"
|
||||
aria-label="Add to wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
</button>
|
||||
<button class="btn btn-small btn-icon"
|
||||
onclick="addToCart('@product.Id', '@product.Name', @product.Price, '@(product.Images != null && product.Images.Count > 0 ? product.Images[0] : product.ImageUrl ?? "/assets/images/placeholder.jpg")')" aria-label="Add to cart">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M7 4h-2l-1 2h-2v2h2l3.6 7.59-1.35 2.44c-.16.28-.25.61-.25.97 0 1.1.9 2 2 2h12v-2h-11.1c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.42c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1h-14.31l-.94-2zm3 17c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm8 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
// Cart functionality now loaded from cart.js
|
||||
</script>
|
||||
}
|
||||
463
Sky_Art_shop/Views/Shop/Product.cshtml
Normal file
463
Sky_Art_shop/Views/Shop/Product.cshtml
Normal file
@@ -0,0 +1,463 @@
|
||||
@model SkyArtShop.Models.Product
|
||||
@{
|
||||
ViewData["Title"] = Model.Name;
|
||||
}
|
||||
|
||||
<section class="product-detail-modern">
|
||||
<div class="container">
|
||||
<div class="product-split">
|
||||
<!-- LEFT: Gallery -->
|
||||
<div class="image-pane">
|
||||
<div class="gallery">
|
||||
<div class="gallery-sidebar">
|
||||
<div class="gallery-thumbs">
|
||||
@if (Model.Images != null && Model.Images.Count > 0)
|
||||
{
|
||||
@for (int i = 0; i < Model.Images.Count; i++)
|
||||
{
|
||||
var image = Model.Images[i];
|
||||
var isFirst = i == 0;
|
||||
<div class="thumb @(isFirst ? "active" : "")" data-src="@image" onclick="setImage(this)">
|
||||
<img src="@image" alt="@Model.Name">
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(Model.ImageUrl))
|
||||
{
|
||||
<div class="thumb active" data-src="@Model.ImageUrl" onclick="setImage(this)">
|
||||
<img src="@Model.ImageUrl" alt="@Model.Name">
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="thumb active" data-src="/assets/images/placeholder.jpg" onclick="setImage(this)">
|
||||
<img src="/assets/images/placeholder.jpg" alt="@Model.Name">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="zoom-hint"><i class="bi bi-zoom-in"></i> Click to view full size</div>
|
||||
</div>
|
||||
<div class="gallery-main" onclick="openLightbox()">
|
||||
<button class="nav prev" type="button" onclick="event.stopPropagation(); slideImage(-1)"><i class="bi bi-chevron-left"></i></button>
|
||||
@{
|
||||
var mainImageSrc = Model.Images != null && Model.Images.Count > 0
|
||||
? Model.Images[0]
|
||||
: (!string.IsNullOrEmpty(Model.ImageUrl) ? Model.ImageUrl : "/assets/images/placeholder.jpg");
|
||||
}
|
||||
<img id="galleryImage" src="@mainImageSrc" alt="@Model.Name">
|
||||
<button class="nav next" type="button" onclick="event.stopPropagation(); slideImage(1)"><i class="bi bi-chevron-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: Details -->
|
||||
<div class="info-pane">
|
||||
<div class="details">
|
||||
<h1 class="title">@Model.Name</h1>
|
||||
<div class="meta">
|
||||
<div class="meta-left">
|
||||
@if (!string.IsNullOrEmpty(Model.SKU))
|
||||
{
|
||||
<span class="sku">SKU: @Model.SKU</span>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(Model.Category))
|
||||
{
|
||||
<span class="sku">SKU: @Model.Category.ToUpper().Replace(" ","")@Model.Id?.Substring(Model.Id.Length - 4)</span>
|
||||
}
|
||||
@{
|
||||
var rating = Model.AverageRating > 0 ? Model.AverageRating : 5.0;
|
||||
var fullStars = (int)Math.Floor(rating);
|
||||
var hasHalfStar = (rating - fullStars) >= 0.5;
|
||||
var emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
|
||||
}
|
||||
<div class="stars">
|
||||
@for (int i = 0; i < fullStars; i++)
|
||||
{
|
||||
<i class="bi bi-star-fill"></i>
|
||||
}
|
||||
@if (hasHalfStar)
|
||||
{
|
||||
<i class="bi bi-star-half"></i>
|
||||
}
|
||||
@for (int i = 0; i < emptyStars; i++)
|
||||
{
|
||||
<i class="bi bi-star"></i>
|
||||
}
|
||||
<span class="rating-text">(@Model.TotalReviews review@(Model.TotalReviews != 1 ? "s" : ""))</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.UnitsSold > 0)
|
||||
{
|
||||
<span class="units-sold">@Model.UnitsSold sold</span>
|
||||
}
|
||||
</div>
|
||||
<!-- Price first -->
|
||||
<div class="price-row">
|
||||
<span class="label">Price:</span>
|
||||
<span class="price">$@Model.Price.ToString("F2")</span>
|
||||
</div>
|
||||
<!-- Stock info under price -->
|
||||
<div class="stock-row">
|
||||
@if (Model.StockQuantity > 0)
|
||||
{
|
||||
<div class="stock ok"><i class="bi bi-check-circle-fill"></i> In stock (@Model.StockQuantity+
|
||||
units), ready to be shipped</div>
|
||||
<div class="stock-bar green"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<!-- Actions below quantity and color -->
|
||||
<div class="actions">
|
||||
@if (Model.StockQuantity > 0)
|
||||
{
|
||||
<button class="cta" onclick="addToCartFromDetail()"><i class="bi bi-cart-plus"></i> Add to Cart</button>
|
||||
<button class="cta alt" onclick="addToWishlistFromDetail()"><i class="bi bi-heart"></i> Add to Wishlist</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="cta" disabled>Out of Stock</button>
|
||||
}
|
||||
</div>
|
||||
<div class="stock bad"><i class="bi bi-x-circle-fill"></i> Out of stock</div>
|
||||
<div class="stock-bar red"></div>
|
||||
}
|
||||
</div>
|
||||
<!-- Quantity next -->
|
||||
<div class="qty-row">
|
||||
<div class="qty-header">
|
||||
<span class="label">Quantity:</span>
|
||||
@if (Model.StockQuantity > 0)
|
||||
{
|
||||
<span class="stock-count">(@Model.StockQuantity available)</span>
|
||||
}
|
||||
</div>
|
||||
<div class="qty">
|
||||
<button type="button" class="qty-btn" onclick="decreaseQuantity()" @(Model.StockQuantity == 0 ?
|
||||
"disabled" : "")><i class="bi bi-dash"></i></button>
|
||||
<input id="quantity" type="number" value="1" min="1" max="@Model.StockQuantity" readonly>
|
||||
<button type="button" class="qty-btn" onclick="increaseQuantity()" @(Model.StockQuantity == 0 ?
|
||||
"disabled" : "")><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Actions below quantity -->
|
||||
<div class="actions">
|
||||
@if (Model.StockQuantity > 0)
|
||||
{
|
||||
<button class="cta" onclick="addToCartFromDetail()"><i class="bi bi-cart-plus"></i> Add to Cart</button>
|
||||
<button class="cta alt" onclick="addToWishlistFromDetail()"><i class="bi bi-heart"></i> Add to Wishlist</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="cta" disabled>Out of Stock</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Color picker after actions (still in info pane) -->
|
||||
@{
|
||||
var hasColors = (Model.Colors != null && Model.Colors.Any()) || !string.IsNullOrEmpty(Model.Color);
|
||||
List<string> selectedColors = new List<string>();
|
||||
Dictionary<string, string> colorHexMap = new Dictionary<string, string>();
|
||||
|
||||
if (hasColors)
|
||||
{
|
||||
selectedColors = Model.Colors != null && Model.Colors.Any()
|
||||
? Model.Colors
|
||||
: new List<string> { Model.Color ?? "" };
|
||||
|
||||
colorHexMap = new Dictionary<string, string> {
|
||||
{"Red", "#FF0000"}, {"Blue", "#0000FF"}, {"Green", "#00FF00"}, {"Yellow", "#FFFF00"},
|
||||
{"Orange", "#FFA500"}, {"Purple", "#800080"}, {"Pink", "#FFC0CB"}, {"Black", "#000000"},
|
||||
{"White", "#FFFFFF"}, {"Gray", "#808080"}, {"Brown", "#A52A2A"}, {"Gold", "#FFD700"},
|
||||
{"Silver", "#C0C0C0"}, {"Multicolor", "linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet)"},
|
||||
{"Burgundy", "#800020"}, {"Rust Orange", "#B7410E"}, {"Teal", "#008080"},
|
||||
{"Lime Green", "#32CD32"}, {"Navy Blue", "#000080"}, {"Royal Blue", "#4169E1"},
|
||||
{"Dark Green", "#006400"}, {"Hunter Green", "#355E3B"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@if (hasColors)
|
||||
{
|
||||
<div class="color-section">
|
||||
<div class="color-row" id="colorTrigger">
|
||||
<span class="label">Available Colors:</span>
|
||||
<span class="value">@string.Join(", ", selectedColors)</span>
|
||||
<i class="bi bi-chevron-down color-arrow"></i>
|
||||
</div>
|
||||
<div class="swatches" id="colorSwatches">
|
||||
@foreach (var colorName in selectedColors)
|
||||
{
|
||||
var hexColor = colorHexMap.ContainsKey(colorName) ? colorHexMap[colorName] : "#808080";
|
||||
var isGradient = colorName == "Multicolor";
|
||||
var bgStyle = isGradient ? $"background: {hexColor};" : $"background-color: {hexColor};";
|
||||
|
||||
<div class="swatch active">
|
||||
<span class="dot" style="@bgStyle"></span>
|
||||
<span class="name">@colorName</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.ShortDescription))
|
||||
{
|
||||
<div class="short">
|
||||
@Model.ShortDescription
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Description))
|
||||
{
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="desc-block">
|
||||
<h3>Description</h3>
|
||||
<div class="content">
|
||||
@Html.Raw(Model.Description)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Product Description Tabs -->
|
||||
|
||||
|
||||
<!-- Related Products Section -->
|
||||
@if (ViewBag.RelatedProducts != null && ViewBag.RelatedProducts.Count > 0)
|
||||
{
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
<h3 class="section-title mb-3">You May Also Like</h3>
|
||||
<p class="text-muted mb-4">Based on what customers are viewing</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="products-grid mb-4">
|
||||
@foreach (var relatedProduct in ViewBag.RelatedProducts)
|
||||
{
|
||||
<div class="product-card">
|
||||
<a href="/shop/product/@relatedProduct.Id" class="product-link">
|
||||
<div class="product-image">
|
||||
<img src="@(string.IsNullOrEmpty(relatedProduct.ImageUrl) ? "/assets/images/placeholder.jpg" : relatedProduct.ImageUrl)"
|
||||
alt="@relatedProduct.Name" loading="lazy">
|
||||
</div>
|
||||
<h3>@relatedProduct.Name</h3>
|
||||
@if (!string.IsNullOrEmpty(relatedProduct.Color))
|
||||
{
|
||||
<span class="product-color-badge">@relatedProduct.Color</span>
|
||||
}
|
||||
<div class="product-description">@Html.Raw(relatedProduct.ShortDescription ?? relatedProduct.Description)</div>
|
||||
<p class="price">$@relatedProduct.Price.ToString("F2")</p>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<a href="/shop?category=@Model.Category" class="btn btn-outline-primary">
|
||||
Browse More @Model.Category Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mt-5">
|
||||
<div class="col-12 text-center">
|
||||
<h3 class="section-title mb-3">Explore Our Collection</h3>
|
||||
<a href="/shop?category=@Model.Category" class="btn btn-outline-primary">
|
||||
Browse @Model.Category
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
// Simple slider/gallery with fade transition + hover zoom
|
||||
const images = [
|
||||
@if (Model.Images != null && Model.Images.Count > 0)
|
||||
{
|
||||
@for (int i = 0; i < Model.Images.Count; i++)
|
||||
{
|
||||
@: '@Model.Images[i]'@(i < Model.Images.Count - 1 ? "," : "")
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(Model.ImageUrl))
|
||||
{
|
||||
@: '@Model.ImageUrl'
|
||||
}
|
||||
else
|
||||
{
|
||||
@: '/assets/images/placeholder.jpg'
|
||||
}
|
||||
];
|
||||
let currentIndex = 0;
|
||||
let animating = false;
|
||||
|
||||
function changeImage(nextSrc, direction = 0) {
|
||||
const img = document.getElementById('galleryImage');
|
||||
if (animating) return;
|
||||
animating = true;
|
||||
// small directional nudge for slide feel
|
||||
const shift = direction === 0 ? 0 : (direction > 0 ? 12 : -12);
|
||||
img.style.transform = `translateX(${shift}px) scale(1)`;
|
||||
// start fade-out
|
||||
img.classList.add('fade-out');
|
||||
const onTransitionEnd = () => {
|
||||
img.removeEventListener('transitionend', onTransitionEnd);
|
||||
img.onload = () => {
|
||||
// fade back in once new image is loaded
|
||||
requestAnimationFrame(() => {
|
||||
img.classList.remove('fade-out');
|
||||
img.style.transform = 'scale(1)';
|
||||
animating = false;
|
||||
});
|
||||
};
|
||||
img.src = nextSrc;
|
||||
};
|
||||
// If the browser doesn't fire transitionend (short durations), fallback
|
||||
img.addEventListener('transitionend', onTransitionEnd);
|
||||
// Fallback timeout (safety)
|
||||
setTimeout(() => {
|
||||
if (img.classList.contains('fade-out')) {
|
||||
onTransitionEnd();
|
||||
}
|
||||
}, 220);
|
||||
}
|
||||
|
||||
function setImage(el) {
|
||||
const src = el.getAttribute('data-src');
|
||||
changeImage(src, 0);
|
||||
document.querySelectorAll('.gallery-thumbs .thumb').forEach(t => t.classList.remove('active'));
|
||||
el.classList.add('active');
|
||||
currentIndex = images.indexOf(src);
|
||||
}
|
||||
|
||||
function slideImage(direction) {
|
||||
currentIndex = (currentIndex + direction + images.length) % images.length;
|
||||
const nextSrc = images[currentIndex];
|
||||
changeImage(nextSrc, direction);
|
||||
// update active thumb
|
||||
document.querySelectorAll('.gallery-thumbs .thumb').forEach(t => {
|
||||
if (t.getAttribute('data-src') === nextSrc) t.classList.add('active'); else t.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
function increaseQuantity() {
|
||||
const input = document.getElementById('quantity');
|
||||
const max = parseInt(input.max);
|
||||
const current = parseInt(input.value);
|
||||
if (current < max) {
|
||||
input.value = current + 1;
|
||||
}
|
||||
}
|
||||
|
||||
function decreaseQuantity() {
|
||||
const input = document.getElementById('quantity');
|
||||
const current = parseInt(input.value);
|
||||
if (current > 1) {
|
||||
input.value = current - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function addToCartFromDetail() {
|
||||
const quantity = parseInt(document.getElementById('quantity').value);
|
||||
const productId = '@Model.Id';
|
||||
const productName = '@Model.Name';
|
||||
const productPrice = @Model.Price;
|
||||
const imageUrl = '@(Model.Images != null && Model.Images.Count > 0 ? Model.Images[0] : "/assets/images/placeholder.jpg")';
|
||||
|
||||
// Call the cart function multiple times for quantity
|
||||
for (let i = 0; i < quantity; i++) {
|
||||
addToCart(productId, productName, productPrice, imageUrl);
|
||||
}
|
||||
|
||||
// Show success message
|
||||
alert(`Added ${quantity} x ${productName} to cart!`);
|
||||
}
|
||||
|
||||
function addToWishlistFromDetail() {
|
||||
const productId = '@Model.Id';
|
||||
const productName = '@Model.Name';
|
||||
const productPrice = @Model.Price;
|
||||
const imageUrl = '@(Model.Images != null && Model.Images.Count > 0 ? Model.Images[0] : "/assets/images/placeholder.jpg")';
|
||||
|
||||
addToWishlist(productId, productName, productPrice, imageUrl);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Lightbox Viewer
|
||||
function ensureLightbox() {
|
||||
let lb = document.getElementById('lightbox');
|
||||
if (lb) return lb;
|
||||
lb = document.createElement('div');
|
||||
lb.id = 'lightbox';
|
||||
lb.className = 'lightbox';
|
||||
lb.innerHTML = `
|
||||
<div class="lightbox-content">
|
||||
<button class="lb-nav lb-prev" type="button" aria-label="Previous" onclick="lbPrev(event)"><i class="bi bi-chevron-left"></i></button>
|
||||
<img id="lbImage" alt="@Model.Name" />
|
||||
<button class="lb-nav lb-next" type="button" aria-label="Next" onclick="lbNext(event)"><i class="bi bi-chevron-right"></i></button>
|
||||
<button class="lb-close" type="button" aria-label="Close" onclick="closeLightbox(event)"><i class="bi bi-x-lg"></i></button>
|
||||
</div>`;
|
||||
document.body.appendChild(lb);
|
||||
lb.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'lightbox') closeLightbox(e);
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!lb.classList.contains('open')) return;
|
||||
if (e.key === 'Escape') closeLightbox(e);
|
||||
if (e.key === 'ArrowLeft') lbPrev(e);
|
||||
if (e.key === 'ArrowRight') lbNext(e);
|
||||
});
|
||||
return lb;
|
||||
}
|
||||
|
||||
function openLightbox() {
|
||||
const lb = ensureLightbox();
|
||||
const img = document.getElementById('lbImage');
|
||||
img.src = images[currentIndex] || document.getElementById('galleryImage').src;
|
||||
lb.classList.add('open');
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeLightbox(e) {
|
||||
if (e) e.stopPropagation();
|
||||
const lb = document.getElementById('lightbox');
|
||||
if (!lb) return;
|
||||
lb.classList.remove('open');
|
||||
document.documentElement.style.overflow = '';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function lbSet(index) {
|
||||
currentIndex = (index + images.length) % images.length;
|
||||
const img = document.getElementById('lbImage');
|
||||
if (img) img.src = images[currentIndex];
|
||||
}
|
||||
function lbPrev(e) { if (e) e.stopPropagation(); lbSet(currentIndex - 1); }
|
||||
function lbNext(e) { if (e) e.stopPropagation(); lbSet(currentIndex + 1); }
|
||||
|
||||
// Color section toggle
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const colorSection = document.querySelector('.color-section');
|
||||
const colorTrigger = document.getElementById('colorTrigger');
|
||||
|
||||
if (colorTrigger && colorSection) {
|
||||
colorTrigger.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
colorSection.classList.toggle('show');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
Reference in New Issue
Block a user