Add image carousel functionality with dynamic slideshow and navigation controls
This commit is contained in:
1 parent
cbd65c272f
commit
8c6e2994d7
2 files changed
+185
-2
No files matched your search
@@ -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
|
||||
======================================== */
|
||||
|
||||
@@ -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 = '‹';
|
||||
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 = '›';
|
||||
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 () {
|
||||
|
||||
Reference in new issue
Block a user