Add image carousel functionality with dynamic slideshow and navigation controls

This commit is contained in:
interlope committed 2026-05-30 04:12:40 +02:00
1 parent cbd65c272f
commit 8c6e2994d7
2 files changed
+185 -2

No files matched your search

+90
View File
@@ -1244,6 +1244,96 @@ footer.footer-modern a:hover {
border-left: 3px solid var(--accent);
}
/* ========================================
IMAGE CAROUSEL (```carousel fences)
======================================== */
.carousel {
position: relative;
margin: var(--space-md) 0;
border-radius: var(--radius-md);
overflow: hidden;
background: var(--bg-subtle);
border: 1px solid var(--border-color);
}
.carousel-track {
position: relative;
}
.carousel-slide {
display: none;
}
.carousel-slide.is-active {
display: block;
}
.carousel-slide img {
width: 100%;
height: auto;
display: block;
cursor: zoom-in;
}
.carousel-caption {
margin: 0;
padding: 0.5rem 1rem;
font-size: var(--font-size-sm);
color: var(--text-secondary);
text-align: center;
background: var(--bg-subtle);
}
.carousel-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
background: rgba(0, 0, 0, 0.45);
color: #ffffff;
border: none;
width: 38px;
height: 38px;
border-radius: 50%;
font-size: 1.6rem;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.15s ease;
}
.carousel-arrow:hover {
background: rgba(0, 0, 0, 0.72);
}
.carousel-prev { left: 10px; }
.carousel-next { right: 10px; }
.carousel-dots {
display: flex;
justify-content: center;
gap: 6px;
padding: 8px;
}
.carousel-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--border-color);
border: none;
padding: 0;
cursor: pointer;
transition: background 0.15s ease, transform 0.15s ease;
}
.carousel-dot.is-active {
background: var(--accent);
transform: scale(1.3);
}
/* ========================================
LIGHTBOX
======================================== */
+95 -2
View File
@@ -348,11 +348,11 @@
var pre = code.parentNode;
var raw = code.textContent;
// Split on [Tab Name] header lines
// Split on "=== Tab Name" header lines (avoids Parsedown link-ref conflicts)
var tabs = [];
var current = null;
raw.split('\n').forEach(function (line) {
var m = line.match(/^\[(.+)\]\s*$/);
var m = line.match(/^===\s+(.+?)\s*$/);
if (m) {
current = { name: m[1], lines: [] };
tabs.push(current);
@@ -414,6 +414,99 @@
})();
</script>
<!-- Image carousel: transforms ```carousel fences into a slideshow -->
<script>
(function () {
var blocks = document.querySelectorAll('pre > code.language-carousel');
Array.prototype.forEach.call(blocks, function (code) {
var pre = code.parentNode;
// Each non-empty line: "url" or "url | caption"
var slides = [];
code.textContent.split('\n').forEach(function (line) {
line = line.trim();
if (!line) return;
var parts = line.split('|');
slides.push({ url: parts[0].trim(), caption: (parts[1] || '').trim() });
});
if (!slides.length) return;
var car = document.createElement('div');
car.className = 'carousel';
var track = document.createElement('div');
track.className = 'carousel-track';
car.appendChild(track);
var slideEls = [];
var dotEls = [];
var current = 0;
slides.forEach(function (s, i) {
var slide = document.createElement('div');
slide.className = 'carousel-slide' + (i === 0 ? ' is-active' : '');
var img = document.createElement('img');
img.src = s.url;
img.alt = s.caption;
img.loading = 'lazy';
slide.appendChild(img);
if (s.caption) {
var cap = document.createElement('p');
cap.className = 'carousel-caption';
cap.textContent = s.caption;
slide.appendChild(cap);
}
track.appendChild(slide);
slideEls.push(slide);
});
function goTo(idx) {
slideEls[current].classList.remove('is-active');
if (dotEls[current]) dotEls[current].classList.remove('is-active');
current = (idx + slides.length) % slides.length;
slideEls[current].classList.add('is-active');
if (dotEls[current]) dotEls[current].classList.add('is-active');
}
if (slides.length > 1) {
var prev = document.createElement('button');
prev.type = 'button'; prev.className = 'carousel-arrow carousel-prev';
prev.innerHTML = '&#8249;';
prev.addEventListener('click', function () { goTo(current - 1); });
car.appendChild(prev);
var next = document.createElement('button');
next.type = 'button'; next.className = 'carousel-arrow carousel-next';
next.innerHTML = '&#8250;';
next.addEventListener('click', function () { goTo(current + 1); });
car.appendChild(next);
var dots = document.createElement('div');
dots.className = 'carousel-dots';
slides.forEach(function (_, i) {
var dot = document.createElement('button');
dot.type = 'button';
dot.className = 'carousel-dot' + (i === 0 ? ' is-active' : '');
dot.addEventListener('click', function () { goTo(i); });
dots.appendChild(dot);
dotEls.push(dot);
});
car.appendChild(dots);
// Swipe support
var startX = 0;
track.addEventListener('touchstart', function (e) { startX = e.touches[0].clientX; }, { passive: true });
track.addEventListener('touchend', function (e) {
var dx = e.changedTouches[0].clientX - startX;
if (Math.abs(dx) > 40) goTo(dx < 0 ? current + 1 : current - 1);
}, { passive: true });
}
pre.parentNode.replaceChild(car, pre);
});
})();
</script>
<!-- Lightbox: click any article image to zoom -->
<script>
(function () {