00a138fe46
Blog list (/blog): add robots, full Open Graph + Twitter, Blog + BreadcrumbList JSON-LD, per-page self-canonical, and rel=prev/next for paginated pages. Blog post: add robots, og:site_name, article:published_time / modified_time / author / section, twitter:image, og:image:alt, and a BreadcrumbList JSON-LD (Home → Blog → Category → Post). Gallery (/gallery): add robots, full OG + Twitter (with first image as og:image), ImageGallery + BreadcrumbList JSON-LD. Encoding: register HtmlEncoder.Create(UnicodeRanges.All) so Persian text in meta tags and JSON-LD renders literally instead of &#xXXXX; entities (smaller, cleaner output; friendlier to SEO validators). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
158 lines
7.4 KiB
Plaintext
158 lines
7.4 KiB
Plaintext
@page "/gallery"
|
|
@model DrSousan.Api.Pages.GalleryModel
|
|
@{
|
|
var galBase = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/') ?? (Request.Scheme + "://" + Request.Host);
|
|
var galUrl = galBase + "/gallery";
|
|
var galDesc = "گالری نتایج واقعی قبل و بعد درمانهای زیبایی پوست دکتر سوسن آلطه — بوتاکس، فیلر، لیزر، مزوتراپی و پاکسازی پوست.";
|
|
var firstImg = Model.Items
|
|
.Select(i => !string.IsNullOrEmpty(i.BeforeImageUrl) ? i.BeforeImageUrl : i.ImageUrl)
|
|
.FirstOrDefault(u => !string.IsNullOrEmpty(u)) ?? "";
|
|
var absFirstImg = string.IsNullOrEmpty(firstImg) ? "" : (firstImg.StartsWith("http") ? firstImg : galBase + firstImg);
|
|
}
|
|
|
|
@section Head {
|
|
<title>@ViewData["Title"]</title>
|
|
<meta name="description" content="@galDesc" />
|
|
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1" />
|
|
<meta name="author" content="@ViewData["SiteName"]" />
|
|
<meta name="theme-color" content="#B8955A" />
|
|
<link rel="canonical" href="@galUrl" />
|
|
|
|
<!-- Open Graph -->
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:site_name" content="@ViewData["SiteName"]" />
|
|
<meta property="og:title" content="@ViewData["Title"]" />
|
|
<meta property="og:description" content="@galDesc" />
|
|
<meta property="og:url" content="@galUrl" />
|
|
<meta property="og:locale" content="fa_IR" />
|
|
@if (!string.IsNullOrEmpty(absFirstImg)) {
|
|
<meta property="og:image" content="@absFirstImg" />
|
|
}
|
|
<!-- Twitter -->
|
|
<meta name="twitter:card" content="@(string.IsNullOrEmpty(absFirstImg) ? "summary" : "summary_large_image")" />
|
|
<meta name="twitter:title" content="@ViewData["Title"]" />
|
|
<meta name="twitter:description" content="@galDesc" />
|
|
@if (!string.IsNullOrEmpty(absFirstImg)) {
|
|
<meta name="twitter:image" content="@absFirstImg" />
|
|
}
|
|
|
|
<!-- Structured data: ImageGallery + breadcrumb -->
|
|
<script type="application/ld+json">
|
|
{
|
|
"@@context": "https://schema.org",
|
|
"@@type": "ImageGallery",
|
|
"name": "@ViewData["Title"]",
|
|
"description": "@galDesc",
|
|
"url": "@galUrl",
|
|
"inLanguage": "fa-IR"
|
|
}
|
|
</script>
|
|
<script type="application/ld+json">
|
|
{
|
|
"@@context": "https://schema.org",
|
|
"@@type": "BreadcrumbList",
|
|
"itemListElement": [
|
|
{ "@@type": "ListItem", "position": 1, "name": "خانه", "item": "@galBase/" },
|
|
{ "@@type": "ListItem", "position": 2, "name": "گالری", "item": "@galUrl" }
|
|
]
|
|
}
|
|
</script>
|
|
<style>
|
|
.gal-hero{background:linear-gradient(135deg,var(--gold-pale) 0%,#EDE0CA 100%);padding:6rem 2rem 3rem;text-align:center}
|
|
.gal-hero h1{font-size:clamp(1.8rem,4vw,2.5rem);font-weight:700;color:var(--dark);margin-bottom:.6rem}
|
|
.gal-hero p{font-size:1rem;color:var(--mid);max-width:560px;margin:0 auto}
|
|
.gal-tabs{display:flex;gap:.6rem;justify-content:center;flex-wrap:wrap;max-width:1100px;margin:2rem auto 0;padding:0 2rem}
|
|
.gal-tab{background:transparent;border:1.5px solid var(--border);color:var(--mid);padding:.45rem 1.2rem;border-radius:50px;font-family:'Vazirmatn',sans-serif;font-size:.85rem;cursor:pointer;transition:all .2s}
|
|
.gal-tab.active,.gal-tab:hover{background:var(--gold);border-color:var(--gold);color:#fff}
|
|
.gal-grid{max-width:1100px;margin:2.5rem auto;padding:0 2rem;display:grid;grid-template-columns:repeat(3,1fr);gap:1.2rem}
|
|
@@media(max-width:900px){.gal-grid{grid-template-columns:repeat(2,1fr)}}
|
|
@@media(max-width:600px){.gal-grid{grid-template-columns:repeat(2,1fr);gap:.8rem}}
|
|
@@media(max-width:380px){.gal-grid{grid-template-columns:1fr}}
|
|
.gal-item{border-radius:16px;overflow:hidden;background:var(--white);border:1px solid var(--border);display:flex;flex-direction:column}
|
|
.gal-imgwrap{aspect-ratio:4/3;position:relative;overflow:hidden;background:linear-gradient(135deg,var(--gold-pale),#EDE0CA)}
|
|
.gal-imgwrap img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .4s}
|
|
.gal-item:hover .gal-imgwrap img{transform:scale(1.05)}
|
|
.gal-ba{display:flex;flex-direction:row;height:100%}
|
|
.gal-ba .half{flex:1;position:relative;overflow:hidden}
|
|
.gal-ba .divider{width:2px;background:var(--border);flex-shrink:0}
|
|
.gal-labels{display:flex}
|
|
.gal-labels span{flex:1;text-align:center;padding:6px 8px;font-size:.78rem;font-weight:600;color:var(--mid);border-top:1px solid var(--border)}
|
|
.gal-labels span:first-child{border-left:1px solid var(--border)}
|
|
.gal-caption{padding:.55rem .8rem;font-size:.8rem;color:var(--mid);text-align:center;border-top:1px solid var(--border);line-height:1.5}
|
|
.gal-empty{text-align:center;padding:4rem 2rem;color:var(--light)}
|
|
.gal-back{text-align:center;padding:1rem 2rem 4rem}
|
|
</style>
|
|
}
|
|
|
|
<div class="gal-hero">
|
|
<h1>گالری نتایج قبل و بعد</h1>
|
|
<p>نمونهای از نتایج واقعی درمانهای انجامشده توسط دکتر سوسن آلطه. روی هر تصویر، قبل و بعد درمان قابل مشاهده است.</p>
|
|
</div>
|
|
|
|
@if (Model.Categories.Any())
|
|
{
|
|
<div class="gal-tabs">
|
|
<button class="gal-tab active" data-filter="">همه</button>
|
|
@foreach (var cat in Model.Categories)
|
|
{
|
|
<button class="gal-tab" data-filter="@cat">@cat</button>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<div class="gal-grid" id="galGrid">
|
|
@if (!Model.Items.Any())
|
|
{
|
|
<div class="gal-empty" style="grid-column:1/-1"><p>هنوز نمونهای ثبت نشده است.</p></div>
|
|
}
|
|
else
|
|
{
|
|
@foreach (var item in Model.Items)
|
|
{
|
|
var hasBoth = !string.IsNullOrEmpty(item.BeforeImageUrl) && !string.IsNullOrEmpty(item.AfterImageUrl);
|
|
var hasImg = !string.IsNullOrEmpty(item.ImageUrl);
|
|
<div class="gal-item" data-cat="@item.Category">
|
|
<div class="gal-imgwrap">
|
|
@if (hasBoth)
|
|
{
|
|
<div class="gal-ba">
|
|
<div class="half"><img src="@item.BeforeImageUrl" alt="قبل از درمان @item.Caption" loading="lazy"/></div>
|
|
<div class="divider"></div>
|
|
<div class="half"><img src="@item.AfterImageUrl" alt="بعد از درمان @item.Caption" loading="lazy"/></div>
|
|
</div>
|
|
}
|
|
else if (hasImg)
|
|
{
|
|
<img src="@item.ImageUrl" alt="@item.Caption" loading="lazy"/>
|
|
}
|
|
</div>
|
|
@if (hasBoth)
|
|
{
|
|
<div class="gal-labels"><span>قبل از درمان</span><span>بعد از درمان</span></div>
|
|
}
|
|
@if (!string.IsNullOrEmpty(item.Caption)) { <div class="gal-caption">@item.Caption</div> }
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<div class="gal-back">
|
|
<a href="/#contact" class="btn-primary">رزرو نوبت و مشاوره</a>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
document.querySelectorAll('.gal-tab').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.gal-tab').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
const f = btn.dataset.filter || '';
|
|
document.querySelectorAll('#galGrid .gal-item').forEach(item => {
|
|
const match = f === '' || (item.dataset.cat || '') === f;
|
|
item.style.display = match ? '' : 'none';
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
}
|