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);
|
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
|
LIGHTBOX
|
||||||
======================================== */
|
======================================== */
|
||||||
|
|||||||
@@ -348,11 +348,11 @@
|
|||||||
var pre = code.parentNode;
|
var pre = code.parentNode;
|
||||||
var raw = code.textContent;
|
var raw = code.textContent;
|
||||||
|
|
||||||
// Split on [Tab Name] header lines
|
// Split on "=== Tab Name" header lines (avoids Parsedown link-ref conflicts)
|
||||||
var tabs = [];
|
var tabs = [];
|
||||||
var current = null;
|
var current = null;
|
||||||
raw.split('\n').forEach(function (line) {
|
raw.split('\n').forEach(function (line) {
|
||||||
var m = line.match(/^\[(.+)\]\s*$/);
|
var m = line.match(/^===\s+(.+?)\s*$/);
|
||||||
if (m) {
|
if (m) {
|
||||||
current = { name: m[1], lines: [] };
|
current = { name: m[1], lines: [] };
|
||||||
tabs.push(current);
|
tabs.push(current);
|
||||||
@@ -414,6 +414,99 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</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 -->
|
<!-- Lightbox: click any article image to zoom -->
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
|
|||||||
Reference in new issue
Block a user