fix: render before/after gallery images from API with tab filtering
- Gallery section now fetches /api/gallery and renders real items instead of hardcoded placeholders - Before+after pairs render as side-by-side split with قبل/بعد labels - Single imageUrl items render as a standard gallery card - Tab buttons now filter items by category via data-cat attribute - CSS added for .before-after, .ba-half, .ba-divider, .ba-label, .gallery-caption - Fixes applied to correct file (Index.cshtml Razor page, not root index.html) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,14 @@
|
|||||||
.gallery-placeholder p { font-size:0.75rem; }
|
.gallery-placeholder p { font-size:0.75rem; }
|
||||||
.gallery-item-overlay { position:absolute; inset:0; background:rgba(184,149,90,0); display:flex; align-items:center; justify-content:center; transition:background 0.3s; }
|
.gallery-item-overlay { position:absolute; inset:0; background:rgba(184,149,90,0); display:flex; align-items:center; justify-content:center; transition:background 0.3s; }
|
||||||
.gallery-item:hover .gallery-item-overlay { background:rgba(184,149,90,0.15); }
|
.gallery-item:hover .gallery-item-overlay { background:rgba(184,149,90,0.15); }
|
||||||
|
.gallery-item.before-after { display:flex; flex-direction:row; }
|
||||||
|
.gallery-item.before-after .ba-half { flex:1; position:relative; overflow:hidden; }
|
||||||
|
.gallery-item.before-after .ba-half img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s; }
|
||||||
|
.gallery-item.before-after:hover .ba-half img { transform:scale(1.05); }
|
||||||
|
.gallery-item.before-after .ba-label { position:absolute; bottom:6px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.55); color:#fff; font-size:0.65rem; padding:2px 8px; border-radius:20px; white-space:nowrap; pointer-events:none; }
|
||||||
|
.gallery-item.before-after .ba-divider { width:2px; background:rgba(255,255,255,0.7); flex-shrink:0; }
|
||||||
|
.gallery-caption { position:absolute; bottom:0; left:0; right:0; background:linear-gradient(transparent,rgba(0,0,0,0.5)); color:#fff; font-size:0.75rem; padding:1.2rem 0.8rem 0.5rem; text-align:center; pointer-events:none; }
|
||||||
|
.gallery-item[style*="display:none"] { display:none !important; }
|
||||||
/* ─── Testimonials ─────────────────────────────────────────── */
|
/* ─── Testimonials ─────────────────────────────────────────── */
|
||||||
#testimonials { background:var(--section-bg); }
|
#testimonials { background:var(--section-bg); }
|
||||||
.testimonials-header { text-align:center; margin-bottom:3rem; }
|
.testimonials-header { text-align:center; margin-bottom:3rem; }
|
||||||
@@ -412,28 +420,37 @@
|
|||||||
<button class="tab-btn">مزوتراپی</button>
|
<button class="tab-btn">مزوتراپی</button>
|
||||||
<button class="tab-btn">پاکسازی</button>
|
<button class="tab-btn">پاکسازی</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="gallery-grid">
|
<div class="gallery-grid" id="galleryGrid">
|
||||||
@if (Model.Gallery.Any())
|
@if (Model.Gallery.Any())
|
||||||
{
|
{
|
||||||
@foreach (var item in Model.Gallery)
|
@foreach (var item in Model.Gallery)
|
||||||
{
|
{
|
||||||
<div class="gallery-item fade-in">
|
var hasBoth = !string.IsNullOrEmpty(item.BeforeImageUrl) && !string.IsNullOrEmpty(item.AfterImageUrl);
|
||||||
@if (!string.IsNullOrEmpty(item.ImageUrl))
|
var hasImg = !string.IsNullOrEmpty(item.ImageUrl);
|
||||||
{
|
if (hasBoth)
|
||||||
<img src="@item.ImageUrl" alt="@item.Caption" loading="lazy" />
|
{
|
||||||
}
|
<div class="gallery-item before-after fade-in" data-cat="@item.Category">
|
||||||
else
|
<div class="ba-half">
|
||||||
{
|
<img src="@item.BeforeImageUrl" alt="قبل از درمان" loading="lazy"/>
|
||||||
<div class="gallery-placeholder">
|
<span class="ba-label">قبل</span>
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</svg>
|
|
||||||
<p>تصویر قبل و بعد</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="ba-divider"></div>
|
||||||
<div class="gallery-item-overlay"></div>
|
<div class="ba-half">
|
||||||
</div>
|
<img src="@item.AfterImageUrl" alt="بعد از درمان" loading="lazy"/>
|
||||||
|
<span class="ba-label">بعد</span>
|
||||||
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(item.Caption)) { <div class="gallery-caption">@item.Caption</div> }
|
||||||
|
<div class="gallery-item-overlay"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (hasImg)
|
||||||
|
{
|
||||||
|
<div class="gallery-item fade-in" data-cat="@item.Category">
|
||||||
|
<img src="@item.ImageUrl" alt="@item.Caption" loading="lazy"/>
|
||||||
|
@if (!string.IsNullOrEmpty(item.Caption)) { <div class="gallery-caption">@item.Caption</div> }
|
||||||
|
<div class="gallery-item-overlay"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -703,11 +720,16 @@
|
|||||||
btns[1].classList.toggle('active');
|
btns[1].classList.toggle('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab buttons
|
// Tab buttons — filter gallery by category
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||||
btn.classList.add('active');
|
btn.classList.add('active');
|
||||||
|
const cat = btn.textContent.trim();
|
||||||
|
document.querySelectorAll('#galleryGrid .gallery-item').forEach(item => {
|
||||||
|
const match = cat === 'همه' || (item.dataset.cat || '') === cat;
|
||||||
|
item.style.display = match ? '' : 'none';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+105
-62
@@ -609,6 +609,56 @@
|
|||||||
|
|
||||||
.gallery-item:hover .gallery-item-overlay { background: rgba(184,149,90,0.15); }
|
.gallery-item:hover .gallery-item-overlay { background: rgba(184,149,90,0.15); }
|
||||||
|
|
||||||
|
/* Before/After split layout */
|
||||||
|
.gallery-item.before-after {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.gallery-item.before-after .ba-half {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.gallery-item.before-after .ba-half img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
transition: transform 0.4s;
|
||||||
|
}
|
||||||
|
.gallery-item.before-after:hover .ba-half img { transform: scale(1.05); }
|
||||||
|
.gallery-item.before-after .ba-label {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 6px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(0,0,0,0.55);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.gallery-item.before-after .ba-divider {
|
||||||
|
width: 2px;
|
||||||
|
background: rgba(255,255,255,0.7);
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.gallery-caption {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: linear-gradient(transparent, rgba(0,0,0,0.5));
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 1.2rem 0.8rem 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Testimonials ──────────────────────────────────────────── */
|
/* ─── Testimonials ──────────────────────────────────────────── */
|
||||||
#testimonials { background: var(--section-bg); }
|
#testimonials { background: var(--section-bg); }
|
||||||
|
|
||||||
@@ -1145,68 +1195,8 @@
|
|||||||
<button class="tab-btn">پاکسازی</button>
|
<button class="tab-btn">پاکسازی</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gallery-grid">
|
<div class="gallery-grid" id="galleryGrid">
|
||||||
<!-- Replace placeholders with actual before/after images -->
|
<!-- Loaded dynamically from API -->
|
||||||
<div class="gallery-item fade-in">
|
|
||||||
<div class="gallery-placeholder">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</svg>
|
|
||||||
<p>تصویر قبل و بعد</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item-overlay"></div>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item fade-in">
|
|
||||||
<div class="gallery-placeholder">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</svg>
|
|
||||||
<p>تصویر قبل و بعد</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item-overlay"></div>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item fade-in">
|
|
||||||
<div class="gallery-placeholder">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</svg>
|
|
||||||
<p>تصویر قبل و بعد</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item-overlay"></div>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item fade-in">
|
|
||||||
<div class="gallery-placeholder">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</svg>
|
|
||||||
<p>تصویر قبل و بعد</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item-overlay"></div>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item fade-in">
|
|
||||||
<div class="gallery-placeholder">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</svg>
|
|
||||||
<p>تصویر قبل و بعد</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item-overlay"></div>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item fade-in">
|
|
||||||
<div class="gallery-placeholder">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
||||||
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/>
|
|
||||||
<polyline points="21 15 16 10 5 21"/>
|
|
||||||
</svg>
|
|
||||||
<p>تصویر قبل و بعد</p>
|
|
||||||
</div>
|
|
||||||
<div class="gallery-item-overlay"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -1426,11 +1416,64 @@
|
|||||||
|
|
||||||
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
|
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
|
||||||
|
|
||||||
|
// Gallery — load from API
|
||||||
|
let allGalleryItems = [];
|
||||||
|
|
||||||
|
function renderGallery(items) {
|
||||||
|
const grid = document.getElementById('galleryGrid');
|
||||||
|
if (!items.length) {
|
||||||
|
grid.innerHTML = '<p style="color:var(--mid);text-align:center;grid-column:1/-1;padding:2rem">تصویری یافت نشد.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grid.innerHTML = items.map(g => {
|
||||||
|
const hasBoth = g.beforeImageUrl && g.afterImageUrl;
|
||||||
|
const hasImg = g.imageUrl;
|
||||||
|
if (hasBoth) {
|
||||||
|
return `<div class="gallery-item before-after fade-in" data-cat="${g.category||''}">
|
||||||
|
<div class="ba-half">
|
||||||
|
<img src="${g.beforeImageUrl}" alt="قبل" loading="lazy"/>
|
||||||
|
<span class="ba-label">قبل</span>
|
||||||
|
</div>
|
||||||
|
<div class="ba-divider"></div>
|
||||||
|
<div class="ba-half">
|
||||||
|
<img src="${g.afterImageUrl}" alt="بعد" loading="lazy"/>
|
||||||
|
<span class="ba-label">بعد</span>
|
||||||
|
</div>
|
||||||
|
${g.caption ? `<div class="gallery-caption">${g.caption}</div>` : ''}
|
||||||
|
<div class="gallery-item-overlay"></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
if (hasImg) {
|
||||||
|
return `<div class="gallery-item fade-in" data-cat="${g.category||''}">
|
||||||
|
<img src="${g.imageUrl}" alt="${g.caption||'گالری'}" loading="lazy"/>
|
||||||
|
${g.caption ? `<div class="gallery-caption">${g.caption}</div>` : ''}
|
||||||
|
<div class="gallery-item-overlay"></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}).join('');
|
||||||
|
grid.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadGallery(category) {
|
||||||
|
try {
|
||||||
|
const url = category && category !== 'همه' ? `/api/gallery?category=${encodeURIComponent(category)}` : '/api/gallery';
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) return;
|
||||||
|
const data = await res.json();
|
||||||
|
allGalleryItems = data;
|
||||||
|
renderGallery(data);
|
||||||
|
} catch(e) { /* silent — placeholders stay hidden */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
loadGallery();
|
||||||
|
|
||||||
// Tab buttons
|
// Tab buttons
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||||
btn.classList.add('active');
|
btn.classList.add('active');
|
||||||
|
loadGallery(btn.textContent.trim());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user