feat(blog): in-content image carousel/slider
CI/CD / CI · dotnet build (push) Successful in 6m28s
CI/CD / Deploy · drsousan (push) Successful in 29s

Editor: new 🎠 اسلایدر toolbar button — pick multiple images (min 2),
uploads them all, inserts a <div class="post-carousel" data-carousel>
block at the cursor. Editor preview shows a tidy filmstrip with the
non-functional arrows/dots hidden.

Public post page: carousel CSS (scroll-snap track) + JS that wires up
prev/next arrows, clickable dots, and native touch swipe. Single-image
blocks auto-collapse their controls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-11 11:55:33 +03:30
parent 9c93b4e51a
commit 99a54be3ac
2 changed files with 79 additions and 0 deletions
+40
View File
@@ -72,6 +72,18 @@
.article-content a{color:var(--gold);border-bottom:1px solid var(--gold-pale)}
.article-content blockquote{border-right:4px solid var(--gold);padding:.8rem 1.2rem;background:var(--gold-pale);border-radius:0 8px 8px 0;margin:1.2rem 0;font-style:italic;color:var(--mid)}
.article-content img{max-width:100%;height:auto;border-radius:12px;margin:1.2rem 0;display:block}
/* ─── In-content image carousel ───────────────────────────────── */
.post-carousel{position:relative;margin:1.5rem 0;border-radius:14px;overflow:hidden;background:#111}
.post-carousel .pc-track{display:flex;direction:ltr;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scrollbar-width:none}
.post-carousel .pc-track::-webkit-scrollbar{display:none}
.post-carousel .pc-track img{flex:0 0 100%;width:100%;max-height:480px;object-fit:cover;scroll-snap-align:center;display:block;margin:0 !important;border-radius:0 !important}
.post-carousel .pc-prev,.post-carousel .pc-next{position:absolute;top:50%;transform:translateY(-50%);background:rgba(0,0,0,.45);color:#fff;border:none;width:40px;height:40px;border-radius:50%;font-size:1.5rem;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:2;transition:background .2s}
.post-carousel .pc-prev:hover,.post-carousel .pc-next:hover{background:rgba(0,0,0,.72)}
.post-carousel .pc-prev{left:10px}
.post-carousel .pc-next{right:10px}
.post-carousel .pc-dots{position:absolute;bottom:10px;left:0;right:0;display:flex;gap:6px;justify-content:center;z-index:2}
.post-carousel .pc-dots span{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,.5);cursor:pointer;transition:background .2s}
.post-carousel .pc-dots span.active{background:#fff;width:20px;border-radius:4px}
/* ─── Tags ─────────────────────────────────────────────────────── */
.article-tags{margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--border);display:flex;gap:.5rem;flex-wrap:wrap}
.tag{background:var(--bg);border:1px solid var(--border);padding:.25rem .75rem;border-radius:50px;font-size:.78rem;color:var(--mid)}
@@ -300,3 +312,31 @@
</div>
</aside>
</div>
@section Scripts {
<script>
// Initialise any in-content image carousels (swipe + arrows + dots)
document.querySelectorAll('[data-carousel]').forEach(c => {
const track = c.querySelector('.pc-track');
if (!track) return;
const imgs = [...track.querySelectorAll('img')];
const dots = c.querySelector('.pc-dots');
if (imgs.length <= 1) {
c.querySelector('.pc-prev')?.remove();
c.querySelector('.pc-next')?.remove();
dots?.remove();
return;
}
if (dots) dots.innerHTML = imgs.map((_, i) => `<span data-i="${i}"></span>`).join('');
const dotEls = dots ? [...dots.querySelectorAll('span')] : [];
const current = () => Math.round(track.scrollLeft / track.clientWidth);
const update = () => { const i = current(); dotEls.forEach((d, di) => d.classList.toggle('active', di === i)); };
const go = (i) => { i = Math.max(0, Math.min(imgs.length - 1, i)); track.scrollTo({ left: i * track.clientWidth, behavior: 'smooth' }); };
c.querySelector('.pc-prev')?.addEventListener('click', () => go(current() - 1));
c.querySelector('.pc-next')?.addEventListener('click', () => go(current() + 1));
dotEls.forEach((d, di) => d.addEventListener('click', () => go(di)));
track.addEventListener('scroll', () => { clearTimeout(track._t); track._t = setTimeout(update, 60); });
update();
});
</script>
}