Open demo

CSS code

HTML
<div><span class="lbl">Featured slide with an episode list</span>
<section class="nf" role="region" aria-roledescription="carousel" aria-label="Nightfall, season 2" data-carousel>
  <div class="nf-main">
    <div class="nf-track" data-track>
      <div class="nf-slide" role="group" aria-roledescription="slide" aria-label="1 of 4">
        <img src="https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" alt="The Milky Way and a shooting star over snowy mountains">
        <div class="nf-cap"><p class="nf-tag">Episode 1 · 52 min</p><h2>Dark Sky Country</h2><p class="nf-desc">Astronomers in Chile's Atacama count the satellites crossing a sky that was empty twenty years ago.</p>
          <div class="nf-actions"><a class="nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
      <div class="nf-slide" role="group" aria-roledescription="slide" aria-label="2 of 4">
        <img src="https://images.unsplash.com/photo-1483347756197-71ef80e95f73?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" loading="lazy" alt="Green northern lights above a silhouetted pine forest">
        <div class="nf-cap"><p class="nf-tag">Episode 2 · 47 min</p><h2>Green Fire</h2><p class="nf-desc">In Finnish Lapland a reindeer herder and a physicist wait out the strongest aurora season in eleven years.</p>
          <div class="nf-actions"><a class="nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
      <div class="nf-slide" role="group" aria-roledescription="slide" aria-label="3 of 4">
        <img src="https://images.unsplash.com/photo-1509773896068-7fd415d91e2e?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" loading="lazy" alt="The Milky Way reflected in a still lake between dark forests">
        <div class="nf-cap"><p class="nf-tag">Episode 3 · 50 min</p><h2>Still Water</h2><p class="nf-desc">A lake in Ontario so calm that the night sky shows twice, and the volunteers who keep its shores dark.</p>
          <div class="nf-actions"><a class="nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
      <div class="nf-slide" role="group" aria-roledescription="slide" aria-label="4 of 4">
        <img src="https://images.unsplash.com/photo-1532767153582-b1a0e5145009?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" loading="lazy" alt="A red full moon above glowing orange clouds">
        <div class="nf-cap"><p class="nf-tag">Episode 4 · 44 min</p><h2>Blood Moon</h2><p class="nf-desc">Eclipse chasers cross three countries in four days for 82 minutes of red moon.</p>
          <div class="nf-actions"><a class="nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
    </div>
  </div>
  <div class="nf-side" data-controls hidden>
    <h3>Nightfall · Season 2</h3>
    <ol class="nf-list">
      <li><button type="button" data-dot><span class="nf-num">01</span><span class="nf-title">Dark Sky Country</span><span class="nf-mins">52 min</span></button></li>
      <li><button type="button" data-dot><span class="nf-num">02</span><span class="nf-title">Green Fire</span><span class="nf-mins">47 min</span></button></li>
      <li><button type="button" data-dot><span class="nf-num">03</span><span class="nf-title">Still Water</span><span class="nf-mins">50 min</span></button></li>
      <li><button type="button" data-dot><span class="nf-num">04</span><span class="nf-title">Blood Moon</span><span class="nf-mins">44 min</span></button></li>
    </ol>
  </div>
</section></div>

<div><span class="lbl">Centre mode with dimmed neighbours</span>
<section class="spot" role="region" aria-roledescription="carousel" aria-label="Night Sky Photo Awards 2026" data-carousel>
  <div class="spot-track" data-track tabindex="0">
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="1 of 4">
      <img src="https://images.unsplash.com/photo-1444703686981-a3abbc4d4fe3?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" alt="A person standing under a bright arc of the Milky Way">
      <div class="spot-cap"><p class="spot-tag">Winner · Night landscape</p><h3>Standing Room</h3><p>Lena Vogt · Namib Desert</p></div>
    </div>
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="2 of 4">
      <img src="https://images.unsplash.com/photo-1531366936337-7c912a4589a7?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" alt="Purple and green aurora above a snowy valley">
      <div class="spot-cap"><p class="spot-tag">Winner · Aurora</p><h3>First Light</h3><p>Arvid Holm · Tromsø</p></div>
    </div>
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="3 of 4">
      <img src="https://images.unsplash.com/photo-1419242902214-272b3f66ee7a?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" loading="lazy" alt="A meteor streaking across a purple starry sky above mountains">
      <div class="spot-cap"><p class="spot-tag">Runner-up · Night landscape</p><h3>Perseid, 2:14 am</h3><p>Ana Ruiz · La Palma</p></div>
    </div>
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="4 of 4">
      <img src="https://images.unsplash.com/photo-1502134249126-9f3755a50d78?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" loading="lazy" alt="The bright core of the Milky Way with dust lanes and stars">
      <div class="spot-cap"><p class="spot-tag">Runner-up · Deep sky</p><h3>Core</h3><p>Priya Nair · Atacama</p></div>
    </div>
  </div>
  <div class="spot-nav" data-controls hidden>
    <button class="spot-arrow" type="button" data-prev aria-label="Previous photo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg></button>
    <div class="spot-dots">
      <button type="button" data-dot aria-label="Show photo 1"></button>
      <button type="button" data-dot aria-label="Show photo 2"></button>
      <button type="button" data-dot aria-label="Show photo 3"></button>
      <button type="button" data-dot aria-label="Show photo 4"></button>
    </div>
    <button class="spot-arrow" type="button" data-next aria-label="Next photo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></button>
  </div>
</section></div>
CSS
/* Dark carousels on CSS scroll-snap tracks. The script reveals the episode list, arrows and dots
   and marks the current slide; without it everything still scrolls and swipes. */
[data-controls][hidden]{display:none}
.nf{--nf-bg:#151922;--nf-ink:#e8eaf0;--nf-muted:#9aa3b5;--nf-accent:#a78bfa;
  display:grid;grid-template-columns:minmax(0,1fr) 300px;border-radius:20px;overflow:hidden;background:var(--nf-bg);border:1px solid #232937}
.nf-track{display:flex;height:100%;overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain}
.nf.is-ready .nf-track{scrollbar-width:none}
.nf-slide{position:relative;flex:0 0 100%;min-height:460px;scroll-snap-align:start;scroll-snap-stop:always}
.nf-slide img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.nf-slide::after{content:"";position:absolute;inset:0;background:linear-gradient(to top,rgba(11,13,18,.95),rgba(11,13,18,.6) 45%,rgba(11,13,18,0) 80%)}
.nf-cap{position:absolute;z-index:1;left:32px;right:32px;bottom:30px;max-width:520px}
.nf-tag{font-size:.78rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--nf-accent)}
.nf-cap h2{margin:.4rem 0 .5rem;font-size:clamp(1.7rem,3.4vw,2.5rem);letter-spacing:-.025em;color:#fff}
.nf-desc{line-height:1.55;color:#d3d7e2}
.nf-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:1.1rem}
.nf-btn,.nf-ghost{display:inline-flex;align-items:center;gap:8px;padding:.7rem 1.15rem;border-radius:999px;font-size:.9rem;font-weight:700;text-decoration:none;transition:background .2s}
.nf-btn{background:var(--nf-accent);color:#0b0d12}.nf-btn:hover{background:#c4b5fd}
.nf-ghost{background:rgba(255,255,255,.12);color:#fff;box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.55)}.nf-ghost:hover{background:rgba(255,255,255,.22)}
.nf-btn svg,.nf-ghost svg{width:16px;height:16px}
.nf-side{padding:22px 16px;border-left:1px solid #232937}
.nf-side h3{padding:0 12px 12px;font-size:.78rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--nf-muted)}
.nf-list{list-style:none;display:grid;gap:4px}
.nf-list button{width:100%;display:grid;grid-template-columns:auto 1fr;gap:2px 14px;padding:13px 12px;border:0;border-radius:12px;background:none;color:var(--nf-ink);
  font:inherit;text-align:left;cursor:pointer;transition:background .2s,box-shadow .2s}
.nf-list button:hover{background:#1b202b}
.nf-list button[aria-current=true]{background:#1f2430;box-shadow:inset 3px 0 0 var(--nf-accent)}
.nf-num{grid-row:span 2;font-size:.85rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--nf-muted);padding-top:2px}
.nf-list [aria-current=true] .nf-num{color:var(--nf-accent)}
.nf-title{font-weight:600}
.nf-mins{font-size:.82rem;color:var(--nf-muted)}
.nf-btn:focus-visible,.nf-ghost:focus-visible,.nf-list button:focus-visible{outline:3px solid var(--nf-accent);outline-offset:2px}
@media (max-width:820px){.nf{grid-template-columns:1fr}.nf-side{border-left:0;border-top:1px solid #232937}.nf-cap{left:22px;right:22px;bottom:24px}}

/* Centre mode: the current photo in the middle, its neighbours dimmed at the edges */
.spot{--spot-accent:#a78bfa;--spot-side:18%}
.spot-track{display:flex;gap:16px;padding:6px var(--spot-side);overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain}
.spot.is-ready .spot-track{scrollbar-width:none}
.spot-slide{position:relative;flex:0 0 100%;aspect-ratio:16/10;border-radius:18px;overflow:hidden;scroll-snap-align:center;scroll-snap-stop:always;
  transition:filter .35s,transform .35s}
.spot.is-ready .spot-slide:not([data-current]){filter:brightness(.4) saturate(.6);transform:scale(.93)}
.spot-slide img{width:100%;height:100%;object-fit:cover;display:block}
.spot-slide::after{content:"";position:absolute;inset:0;background:linear-gradient(to top,rgba(11,13,18,.9),rgba(11,13,18,0) 55%)}
.spot-cap{position:absolute;z-index:1;left:24px;right:24px;bottom:20px;color:#fff}
.spot-tag{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(167,139,250,.24);color:#ddd6fe;font-size:.75rem;font-weight:700}
.spot-cap h3{margin-top:8px;font-size:1.35rem;letter-spacing:-.015em}
.spot-cap p:last-child{margin-top:2px;font-size:.9rem;color:#d3d7e2}
.spot-nav{display:flex;align-items:center;justify-content:center;gap:14px;margin-top:18px}
.spot-arrow{width:44px;height:44px;display:grid;place-items:center;border:1.5px solid #4b5264;border-radius:50%;background:none;color:#e8eaf0;cursor:pointer;transition:background .2s,border-color .2s}
.spot-arrow:hover{background:#1f2430;border-color:#e8eaf0}
.spot-arrow svg{width:18px;height:18px}
.spot-dots{display:flex;gap:2px}
.spot-dots button{width:24px;height:24px;display:grid;place-items:center;padding:0;border:0;background:none;cursor:pointer}
.spot-dots button::before{content:"";width:8px;height:8px;border-radius:50%;background:#5b6275;transition:background .2s,transform .2s}
.spot-dots button[aria-current=true]::before{background:var(--spot-accent);transform:scale(1.3)}
.spot-arrow:focus-visible,.spot-dots button:focus-visible{outline:3px solid var(--spot-accent);outline-offset:2px}
.spot-track:focus-visible{outline:3px solid var(--spot-accent);outline-offset:-3px;border-radius:20px}
@media (max-width:640px){.spot{--spot-side:7%}.spot-track{gap:10px}.spot-cap{left:16px;right:16px;bottom:14px}.spot-cap h3{font-size:1.1rem}}
@media (prefers-reduced-motion:reduce){.nf-btn,.nf-ghost,.nf-list button,.spot-slide,.spot-arrow,.spot-dots button::before{transition:none}
  .spot.is-ready .spot-slide:not([data-current]){transform:none}}
JavaScript
// Scroll-snap carousel. The track scrolls and swipes without this script; it only reveals
// the buttons and dots, wires them up and keeps their state in sync with the scroll position.
document.querySelectorAll('[data-carousel]').forEach((root) => {
  const track = root.querySelector('[data-track]');
  const slides = [...track.children];
  const dots = [...root.querySelectorAll('[data-dot]')];
  const prev = root.querySelector('[data-prev]');
  const next = root.querySelector('[data-next]');
  const loop = root.hasAttribute('data-loop');
  const reduce = matchMedia('(prefers-reduced-motion: reduce)');
  const step = () => (slides[1] ? slides[1].offsetLeft - slides[0].offsetLeft : track.clientWidth);
  const current = () => Math.round(track.scrollLeft / step());
  const atEnd = () => track.scrollLeft >= track.scrollWidth - track.clientWidth - 2;
  const go = (i) => track.scrollTo({ left: i * step(), behavior: reduce.matches ? 'auto' : 'smooth' });
  const move = (dir) => {
    if (loop && dir > 0 && atEnd()) return go(0);
    if (loop && dir < 0 && track.scrollLeft < 2) return go(slides.length - 1);
    const perView = Math.max(1, Math.floor((track.clientWidth + step() - slides[0].offsetWidth + 2) / step()));
    go(current() + dir * perView);
  };
  const sync = () => {
    const i = current();
    dots.forEach((dot, n) => dot.setAttribute('aria-current', n === i));
    slides.forEach((slide, n) => slide.toggleAttribute('data-current', n === i));
    if (!loop && prev) prev.setAttribute('aria-disabled', track.scrollLeft < 2);
    if (!loop && next) next.setAttribute('aria-disabled', atEnd());
  };
  prev?.addEventListener('click', () => move(-1));
  next?.addEventListener('click', () => move(1));
  dots.forEach((dot, n) => dot.addEventListener('click', () => go(n)));
  track.addEventListener('scroll', () => requestAnimationFrame(sync), { passive: true });
  root.querySelectorAll('[data-controls]').forEach((el) => { el.hidden = false; });
  root.classList.add('is-ready');
  sync();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<div><span class="lbl">Featured slide with an episode list</span>
<div id="nightfall" class="carousel slide carousel-fade nf" role="region" aria-roledescription="carousel" aria-label="Nightfall, season 2">
  <div class="row g-0">
    <div class="col-lg">
      <div class="carousel-inner" aria-live="polite">
        <div class="carousel-item active" role="group" aria-roledescription="slide" aria-label="1 of 4">
          <img src="https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" alt="The Milky Way and a shooting star over snowy mountains">
          <div class="carousel-caption"><p class="nf-tag">Episode 1 · 52 min</p><h2>Dark Sky Country</h2><p class="nf-desc">Astronomers in Chile's Atacama count the satellites crossing a sky that was empty twenty years ago.</p>
            <div class="d-flex flex-wrap gap-2 nf-actions"><a class="btn rounded-pill nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="btn rounded-pill nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
        </div>
        <div class="carousel-item" role="group" aria-roledescription="slide" aria-label="2 of 4">
          <img src="https://images.unsplash.com/photo-1483347756197-71ef80e95f73?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" alt="Green northern lights above a silhouetted pine forest">
          <div class="carousel-caption"><p class="nf-tag">Episode 2 · 47 min</p><h2>Green Fire</h2><p class="nf-desc">In Finnish Lapland a reindeer herder and a physicist wait out the strongest aurora season in eleven years.</p>
            <div class="d-flex flex-wrap gap-2 nf-actions"><a class="btn rounded-pill nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="btn rounded-pill nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
        </div>
        <div class="carousel-item" role="group" aria-roledescription="slide" aria-label="3 of 4">
          <img src="https://images.unsplash.com/photo-1509773896068-7fd415d91e2e?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" alt="The Milky Way reflected in a still lake between dark forests">
          <div class="carousel-caption"><p class="nf-tag">Episode 3 · 50 min</p><h2>Still Water</h2><p class="nf-desc">A lake in Ontario so calm that the night sky shows twice, and the volunteers who keep its shores dark.</p>
            <div class="d-flex flex-wrap gap-2 nf-actions"><a class="btn rounded-pill nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="btn rounded-pill nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
        </div>
        <div class="carousel-item" role="group" aria-roledescription="slide" aria-label="4 of 4">
          <img src="https://images.unsplash.com/photo-1532767153582-b1a0e5145009?auto=format&fit=crop&w=1400&h=875&q=70" width="1400" height="875" alt="A red full moon above glowing orange clouds">
          <div class="carousel-caption"><p class="nf-tag">Episode 4 · 44 min</p><h2>Blood Moon</h2><p class="nf-desc">Eclipse chasers cross three countries in four days for 82 minutes of red moon.</p>
            <div class="d-flex flex-wrap gap-2 nf-actions"><a class="btn rounded-pill nf-btn" href="#"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="btn rounded-pill nf-ghost" href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
        </div>
      </div>
    </div>
    <div class="col-lg-auto nf-side">
      <h3>Nightfall · Season 2</h3>
      <div class="carousel-indicators">
        <button type="button" data-bs-target="#nightfall" data-bs-slide-to="0" class="active" aria-current="true"><span class="nf-num">01</span><span class="nf-title">Dark Sky Country</span><span class="nf-mins">52 min</span></button>
        <button type="button" data-bs-target="#nightfall" data-bs-slide-to="1"><span class="nf-num">02</span><span class="nf-title">Green Fire</span><span class="nf-mins">47 min</span></button>
        <button type="button" data-bs-target="#nightfall" data-bs-slide-to="2"><span class="nf-num">03</span><span class="nf-title">Still Water</span><span class="nf-mins">50 min</span></button>
        <button type="button" data-bs-target="#nightfall" data-bs-slide-to="3"><span class="nf-num">04</span><span class="nf-title">Blood Moon</span><span class="nf-mins">44 min</span></button>
      </div>
    </div>
  </div>
</div></div>

<div><span class="lbl">Centre mode with dimmed neighbours</span>
<section class="spot" role="region" aria-roledescription="carousel" aria-label="Night Sky Photo Awards 2026" data-carousel>
  <div class="spot-track" data-track tabindex="0">
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="1 of 4">
      <img src="https://images.unsplash.com/photo-1444703686981-a3abbc4d4fe3?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" alt="A person standing under a bright arc of the Milky Way">
      <div class="spot-cap"><span class="badge rounded-pill spot-tag">Winner · Night landscape</span><h3>Standing Room</h3><p>Lena Vogt · Namib Desert</p></div>
    </div>
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="2 of 4">
      <img src="https://images.unsplash.com/photo-1531366936337-7c912a4589a7?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" alt="Purple and green aurora above a snowy valley">
      <div class="spot-cap"><span class="badge rounded-pill spot-tag">Winner · Aurora</span><h3>First Light</h3><p>Arvid Holm · Tromsø</p></div>
    </div>
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="3 of 4">
      <img src="https://images.unsplash.com/photo-1419242902214-272b3f66ee7a?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" loading="lazy" alt="A meteor streaking across a purple starry sky above mountains">
      <div class="spot-cap"><span class="badge rounded-pill spot-tag">Runner-up · Night landscape</span><h3>Perseid, 2:14 am</h3><p>Ana Ruiz · La Palma</p></div>
    </div>
    <div class="spot-slide" role="group" aria-roledescription="slide" aria-label="4 of 4">
      <img src="https://images.unsplash.com/photo-1502134249126-9f3755a50d78?auto=format&fit=crop&w=1200&h=750&q=70" width="1200" height="750" loading="lazy" alt="The bright core of the Milky Way with dust lanes and stars">
      <div class="spot-cap"><span class="badge rounded-pill spot-tag">Runner-up · Deep sky</span><h3>Core</h3><p>Priya Nair · Atacama</p></div>
    </div>
  </div>
  <div class="spot-nav d-flex align-items-center justify-content-center" data-controls hidden>
    <button class="btn rounded-circle spot-arrow" type="button" data-prev aria-label="Previous photo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg></button>
    <div class="spot-dots d-flex gap-1">
      <button type="button" data-dot aria-label="Show photo 1"></button>
      <button type="button" data-dot aria-label="Show photo 2"></button>
      <button type="button" data-dot aria-label="Show photo 3"></button>
      <button type="button" data-dot aria-label="Show photo 4"></button>
    </div>
    <button class="btn rounded-circle spot-arrow" type="button" data-next aria-label="Next photo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></button>
  </div>
</section></div>
CSS
/* Dark carousels with Bootstrap's colour mode (data-bs-theme="dark"). The first is a .carousel-fade whose
   indicators are an episode list; the second is a scroll-snap row, because .carousel shows one slide at a time. */
.nf{--nf-bg:#151922;--nf-ink:#e8eaf0;--nf-muted:#9aa3b5;--nf-accent:#a78bfa;border-radius:20px;overflow:hidden;background:var(--nf-bg);border:1px solid #232937}
.nf .carousel-inner,.nf .carousel-item{height:100%}
.nf .carousel-item img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.nf .carousel-item{min-height:460px}
.nf .carousel-item::after{content:"";position:absolute;inset:0;background:linear-gradient(to top,rgba(11,13,18,.95),rgba(11,13,18,.6) 45%,rgba(11,13,18,0) 80%)}
.nf .carousel-caption{z-index:1;left:32px;right:32px;bottom:30px;max-width:520px;padding:0;text-align:left}
.nf-tag{margin:0;font-size:.78rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--nf-accent)}
.nf .carousel-caption h2{margin:.4rem 0 .5rem;font-size:clamp(1.7rem,3.4vw,2.5rem);font-weight:700;letter-spacing:-.025em;color:#fff}
.nf-desc{margin:0;line-height:1.55;color:#d3d7e2}
.nf-actions{margin-top:1.1rem}
.nf .btn{--bs-btn-padding-x:1.15rem;--bs-btn-padding-y:.7rem;--bs-btn-font-size:.9rem;--bs-btn-font-weight:700;--bs-btn-line-height:normal;--bs-btn-border-width:0;
  display:inline-flex;align-items:center;gap:8px}
.nf-btn{--bs-btn-bg:var(--nf-accent);--bs-btn-color:#0b0d12;--bs-btn-hover-bg:#c4b5fd;--bs-btn-hover-color:#0b0d12;--bs-btn-active-bg:#c4b5fd;--bs-btn-active-color:#0b0d12}
.nf-ghost{--bs-btn-bg:rgba(255,255,255,.12);--bs-btn-color:#fff;--bs-btn-hover-bg:rgba(255,255,255,.22);--bs-btn-hover-color:#fff;--bs-btn-active-bg:rgba(255,255,255,.22);box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.55)}
.nf .btn svg{width:16px;height:16px}
.nf-side{padding:22px 16px;border-left:1px solid #232937}
@media (min-width:992px){.nf .nf-side{flex:0 0 300px}}
.nf-side h3{margin:0;padding:0 12px 12px;font-size:.78rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--nf-muted)}
.nf .carousel-indicators{position:static;display:grid;grid-template-columns:1fr;gap:4px;margin:0}
.nf .carousel-indicators [data-bs-target]{box-sizing:border-box;width:100%;height:auto;display:grid;grid-template-columns:auto 1fr;gap:2px 14px;margin:0;padding:13px 12px;border:0;border-radius:12px;
  background:none;color:var(--nf-ink);opacity:1;text-indent:0;text-align:left;transition:background .2s,box-shadow .2s}
.nf .carousel-indicators [data-bs-target]:hover{background:#1b202b}
.nf .carousel-indicators .active{background:#1f2430;box-shadow:inset 3px 0 0 var(--nf-accent)}
.nf-num{grid-row:span 2;font-size:.85rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--nf-muted);padding-top:2px}
.nf .carousel-indicators .active .nf-num{color:var(--nf-accent)}
.nf-title{font-weight:600}
.nf-mins{font-size:.82rem;color:var(--nf-muted)}
.nf .btn:focus-visible,.nf .carousel-indicators [data-bs-target]:focus-visible{outline:3px solid var(--nf-accent);outline-offset:2px;box-shadow:none}
@media (max-width:991px){.nf-side{border-left:0;border-top:1px solid #232937}.nf .carousel-caption{left:22px;right:22px;bottom:24px}}

/* Centre mode: the current photo in the middle, its neighbours dimmed at the edges */
[data-controls][hidden]{display:none!important}
.spot{--spot-accent:#a78bfa;--spot-side:18%}
.spot-track{display:flex;gap:16px;padding:6px var(--spot-side);overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain}
.spot.is-ready .spot-track{scrollbar-width:none}
.spot-slide{position:relative;flex:0 0 100%;aspect-ratio:16/10;border-radius:18px;overflow:hidden;scroll-snap-align:center;scroll-snap-stop:always;transition:filter .35s,transform .35s}
.spot.is-ready .spot-slide:not([data-current]){filter:brightness(.4) saturate(.6);transform:scale(.93)}
.spot-slide img{width:100%;height:100%;object-fit:cover;display:block}
.spot-slide::after{content:"";position:absolute;inset:0;background:linear-gradient(to top,rgba(11,13,18,.9),rgba(11,13,18,0) 55%)}
.spot-cap{position:absolute;z-index:1;left:24px;right:24px;bottom:20px;color:#fff}
.spot-tag{--bs-badge-padding-x:10px;--bs-badge-padding-y:4px;--bs-badge-font-size:.75rem;--bs-badge-color:#ddd6fe;background:rgba(167,139,250,.24)}
.spot-cap h3{margin:8px 0 0;font-size:1.35rem;font-weight:700;letter-spacing:-.015em}
.spot-cap p{margin:2px 0 0;font-size:.9rem;color:#d3d7e2}
.spot-nav{gap:14px;margin-top:18px}
.spot-arrow{--bs-btn-color:#e8eaf0;--bs-btn-border-color:#4b5264;--bs-btn-border-width:1.5px;--bs-btn-hover-bg:#1f2430;--bs-btn-hover-border-color:#e8eaf0;--bs-btn-hover-color:#fff;
  --bs-btn-active-bg:#1f2430;--bs-btn-active-color:#fff;width:44px;height:44px;padding:0;display:grid;place-items:center}
.spot-arrow svg{width:18px;height:18px}
.spot-dots button{width:24px;height:24px;display:grid;place-items:center;padding:0;border:0;background:none}
.spot-dots button::before{content:"";width:8px;height:8px;border-radius:50%;background:#5b6275;transition:background .2s,transform .2s}
.spot-dots button[aria-current=true]::before{background:var(--spot-accent);transform:scale(1.3)}
.spot-arrow:focus-visible,.spot-dots button:focus-visible{outline:3px solid var(--spot-accent);outline-offset:2px;box-shadow:none}
.spot-track:focus-visible{outline:3px solid var(--spot-accent);outline-offset:-3px;border-radius:20px}
@media (max-width:640px){.spot{--spot-side:7%}.spot-track{gap:10px}.spot-cap{left:16px;right:16px;bottom:14px}.spot-cap h3{font-size:1.1rem}}
@media (prefers-reduced-motion:reduce){.nf .carousel-indicators [data-bs-target],.spot-slide,.spot-dots button::before{transition:none}
  .spot.is-ready .spot-slide:not([data-current]){transform:none}}
JavaScript
// The episode carousel is Bootstrap's own; create it up front so the arrow keys work before the first click.
document.querySelectorAll('.carousel').forEach((el) => bootstrap.Carousel.getOrCreateInstance(el));

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-nf-muted uppercase">Featured slide with an episode list</span>
<section class="grid overflow-hidden rounded-[20px] border border-[#232937] bg-nf-bg min-[821px]:grid-cols-[minmax(0,1fr)_300px]" role="region" aria-roledescription="carousel" aria-label="Nightfall, season 2" data-carousel>
  <div>
    <div class="flex h-full snap-x snap-mandatory overflow-x-auto overscroll-x-contain in-[.is-ready]:[scrollbar-width:none]" data-track>
      <div class="relative min-h-[460px] shrink-0 basis-full snap-start snap-always after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.95),rgba(11,13,18,.6)_45%,rgba(11,13,18,0)_80%)]" role="group" aria-roledescription="slide" aria-label="1 of 4">
        <img src="https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1400&h=875&q=70" class="absolute inset-0 size-full object-cover" width="1400" height="875" alt="The Milky Way and a shooting star over snowy mountains">
        <div class="absolute right-[22px] bottom-6 left-[22px] z-[1] max-w-[520px] min-[821px]:right-8 min-[821px]:bottom-[30px] min-[821px]:left-8"><p class="text-[.78rem] font-bold tracking-[.12em] text-nf-accent uppercase">Episode 1 · 52 min</p><h2 class="mt-[.4rem] mb-2 text-[clamp(1.7rem,3.4vw,2.5rem)] font-bold tracking-[-.025em] text-white">Dark Sky Country</h2><p class="leading-[1.55] text-[#d3d7e2]">Astronomers in Chile's Atacama count the satellites crossing a sky that was empty twenty years ago.</p>
          <div class="mt-[1.1rem] flex flex-wrap gap-2.5"><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-nf-accent text-[#0b0d12] hover:bg-[#c4b5fd]" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-white/12 text-white shadow-[inset_0_0_0_1.5px_rgba(255,255,255,.55)] hover:bg-white/22" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
      <div class="relative min-h-[460px] shrink-0 basis-full snap-start snap-always after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.95),rgba(11,13,18,.6)_45%,rgba(11,13,18,0)_80%)]" role="group" aria-roledescription="slide" aria-label="2 of 4">
        <img src="https://images.unsplash.com/photo-1483347756197-71ef80e95f73?auto=format&fit=crop&w=1400&h=875&q=70" class="absolute inset-0 size-full object-cover" width="1400" height="875" loading="lazy" alt="Green northern lights above a silhouetted pine forest">
        <div class="absolute right-[22px] bottom-6 left-[22px] z-[1] max-w-[520px] min-[821px]:right-8 min-[821px]:bottom-[30px] min-[821px]:left-8"><p class="text-[.78rem] font-bold tracking-[.12em] text-nf-accent uppercase">Episode 2 · 47 min</p><h2 class="mt-[.4rem] mb-2 text-[clamp(1.7rem,3.4vw,2.5rem)] font-bold tracking-[-.025em] text-white">Green Fire</h2><p class="leading-[1.55] text-[#d3d7e2]">In Finnish Lapland a reindeer herder and a physicist wait out the strongest aurora season in eleven years.</p>
          <div class="mt-[1.1rem] flex flex-wrap gap-2.5"><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-nf-accent text-[#0b0d12] hover:bg-[#c4b5fd]" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-white/12 text-white shadow-[inset_0_0_0_1.5px_rgba(255,255,255,.55)] hover:bg-white/22" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
      <div class="relative min-h-[460px] shrink-0 basis-full snap-start snap-always after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.95),rgba(11,13,18,.6)_45%,rgba(11,13,18,0)_80%)]" role="group" aria-roledescription="slide" aria-label="3 of 4">
        <img src="https://images.unsplash.com/photo-1509773896068-7fd415d91e2e?auto=format&fit=crop&w=1400&h=875&q=70" class="absolute inset-0 size-full object-cover" width="1400" height="875" loading="lazy" alt="The Milky Way reflected in a still lake between dark forests">
        <div class="absolute right-[22px] bottom-6 left-[22px] z-[1] max-w-[520px] min-[821px]:right-8 min-[821px]:bottom-[30px] min-[821px]:left-8"><p class="text-[.78rem] font-bold tracking-[.12em] text-nf-accent uppercase">Episode 3 · 50 min</p><h2 class="mt-[.4rem] mb-2 text-[clamp(1.7rem,3.4vw,2.5rem)] font-bold tracking-[-.025em] text-white">Still Water</h2><p class="leading-[1.55] text-[#d3d7e2]">A lake in Ontario so calm that the night sky shows twice, and the volunteers who keep its shores dark.</p>
          <div class="mt-[1.1rem] flex flex-wrap gap-2.5"><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-nf-accent text-[#0b0d12] hover:bg-[#c4b5fd]" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-white/12 text-white shadow-[inset_0_0_0_1.5px_rgba(255,255,255,.55)] hover:bg-white/22" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
      <div class="relative min-h-[460px] shrink-0 basis-full snap-start snap-always after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.95),rgba(11,13,18,.6)_45%,rgba(11,13,18,0)_80%)]" role="group" aria-roledescription="slide" aria-label="4 of 4">
        <img src="https://images.unsplash.com/photo-1532767153582-b1a0e5145009?auto=format&fit=crop&w=1400&h=875&q=70" class="absolute inset-0 size-full object-cover" width="1400" height="875" loading="lazy" alt="A red full moon above glowing orange clouds">
        <div class="absolute right-[22px] bottom-6 left-[22px] z-[1] max-w-[520px] min-[821px]:right-8 min-[821px]:bottom-[30px] min-[821px]:left-8"><p class="text-[.78rem] font-bold tracking-[.12em] text-nf-accent uppercase">Episode 4 · 44 min</p><h2 class="mt-[.4rem] mb-2 text-[clamp(1.7rem,3.4vw,2.5rem)] font-bold tracking-[-.025em] text-white">Blood Moon</h2><p class="leading-[1.55] text-[#d3d7e2]">Eclipse chasers cross three countries in four days for 82 minutes of red moon.</p>
          <div class="mt-[1.1rem] flex flex-wrap gap-2.5"><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-nf-accent text-[#0b0d12] hover:bg-[#c4b5fd]" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5.5v13a1 1 0 0 0 1.5.9l10.4-6.5a1 1 0 0 0 0-1.8L9.5 4.6A1 1 0 0 0 8 5.5z"/></svg>Play episode</a><a class="inline-flex items-center gap-2 rounded-full px-[1.15rem] py-[.7rem] text-[.9rem] font-bold transition-[background-color] duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none bg-white/12 text-white shadow-[inset_0_0_0_1.5px_rgba(255,255,255,.55)] hover:bg-white/22" href="#"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>My list</a></div></div>
      </div>
    </div>
  </div>
  <div class="border-t border-[#232937] px-4 py-[22px] min-[821px]:border-t-0 min-[821px]:border-l" data-controls hidden>
    <h3 class="px-3 pb-3 text-[.78rem] font-bold tracking-[.12em] text-nf-muted uppercase">Nightfall · Season 2</h3>
    <ol class="grid gap-1">
      <li><button class="group/ep grid w-full cursor-pointer grid-cols-[auto_1fr] gap-x-3.5 gap-y-0.5 rounded-xl px-3 py-[13px] text-left text-nf-ink transition-[background-color,box-shadow] duration-200 hover:bg-[#1b202b] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:bg-[#1f2430] aria-[current=true]:shadow-[inset_3px_0_0_var(--color-nf-accent)] motion-reduce:transition-none" type="button" data-dot><span class="row-span-2 pt-0.5 text-[.85rem] font-bold text-nf-muted tabular-nums group-aria-[current=true]/ep:text-nf-accent">01</span><span class="font-semibold">Dark Sky Country</span><span class="text-[.82rem] text-nf-muted">52 min</span></button></li>
      <li><button class="group/ep grid w-full cursor-pointer grid-cols-[auto_1fr] gap-x-3.5 gap-y-0.5 rounded-xl px-3 py-[13px] text-left text-nf-ink transition-[background-color,box-shadow] duration-200 hover:bg-[#1b202b] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:bg-[#1f2430] aria-[current=true]:shadow-[inset_3px_0_0_var(--color-nf-accent)] motion-reduce:transition-none" type="button" data-dot><span class="row-span-2 pt-0.5 text-[.85rem] font-bold text-nf-muted tabular-nums group-aria-[current=true]/ep:text-nf-accent">02</span><span class="font-semibold">Green Fire</span><span class="text-[.82rem] text-nf-muted">47 min</span></button></li>
      <li><button class="group/ep grid w-full cursor-pointer grid-cols-[auto_1fr] gap-x-3.5 gap-y-0.5 rounded-xl px-3 py-[13px] text-left text-nf-ink transition-[background-color,box-shadow] duration-200 hover:bg-[#1b202b] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:bg-[#1f2430] aria-[current=true]:shadow-[inset_3px_0_0_var(--color-nf-accent)] motion-reduce:transition-none" type="button" data-dot><span class="row-span-2 pt-0.5 text-[.85rem] font-bold text-nf-muted tabular-nums group-aria-[current=true]/ep:text-nf-accent">03</span><span class="font-semibold">Still Water</span><span class="text-[.82rem] text-nf-muted">50 min</span></button></li>
      <li><button class="group/ep grid w-full cursor-pointer grid-cols-[auto_1fr] gap-x-3.5 gap-y-0.5 rounded-xl px-3 py-[13px] text-left text-nf-ink transition-[background-color,box-shadow] duration-200 hover:bg-[#1b202b] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:bg-[#1f2430] aria-[current=true]:shadow-[inset_3px_0_0_var(--color-nf-accent)] motion-reduce:transition-none" type="button" data-dot><span class="row-span-2 pt-0.5 text-[.85rem] font-bold text-nf-muted tabular-nums group-aria-[current=true]/ep:text-nf-accent">04</span><span class="font-semibold">Blood Moon</span><span class="text-[.82rem] text-nf-muted">44 min</span></button></li>
    </ol>
  </div>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-nf-muted uppercase">Centre mode with dimmed neighbours</span>
<section class="[--side:7%] sm:[--side:18%]" role="region" aria-roledescription="carousel" aria-label="Night Sky Photo Awards 2026" data-carousel>
  <div class="flex snap-x snap-mandatory gap-2.5 overflow-x-auto overscroll-x-contain px-(--side) py-1.5 in-[.is-ready]:[scrollbar-width:none] focus-visible:rounded-[20px] focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-nf-accent sm:gap-4" data-track tabindex="0">
    <div class="relative aspect-[16/10] shrink-0 basis-full snap-center snap-always overflow-hidden rounded-[18px] transition-[filter,transform] duration-350 after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.9),rgba(11,13,18,0)_55%)] in-[.is-ready]:not-data-current:scale-93 in-[.is-ready]:not-data-current:brightness-40 in-[.is-ready]:not-data-current:saturate-60 motion-reduce:transition-none motion-reduce:in-[.is-ready]:not-data-current:scale-100" role="group" aria-roledescription="slide" aria-label="1 of 4">
      <img src="https://images.unsplash.com/photo-1444703686981-a3abbc4d4fe3?auto=format&fit=crop&w=1200&h=750&q=70" class="block size-full object-cover" width="1200" height="750" alt="A person standing under a bright arc of the Milky Way">
      <div class="absolute right-4 bottom-3.5 left-4 z-[1] text-white sm:right-6 sm:bottom-5 sm:left-6"><p class="inline-block rounded-full bg-[rgba(167,139,250,.24)] px-2.5 py-1 text-xs font-bold text-[#ddd6fe]">Winner · Night landscape</p><h3 class="mt-2 text-[1.1rem] font-bold tracking-[-.015em] sm:text-[1.35rem]">Standing Room</h3><p class="mt-0.5 text-[.9rem] text-[#d3d7e2]">Lena Vogt · Namib Desert</p></div>
    </div>
    <div class="relative aspect-[16/10] shrink-0 basis-full snap-center snap-always overflow-hidden rounded-[18px] transition-[filter,transform] duration-350 after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.9),rgba(11,13,18,0)_55%)] in-[.is-ready]:not-data-current:scale-93 in-[.is-ready]:not-data-current:brightness-40 in-[.is-ready]:not-data-current:saturate-60 motion-reduce:transition-none motion-reduce:in-[.is-ready]:not-data-current:scale-100" role="group" aria-roledescription="slide" aria-label="2 of 4">
      <img src="https://images.unsplash.com/photo-1531366936337-7c912a4589a7?auto=format&fit=crop&w=1200&h=750&q=70" class="block size-full object-cover" width="1200" height="750" alt="Purple and green aurora above a snowy valley">
      <div class="absolute right-4 bottom-3.5 left-4 z-[1] text-white sm:right-6 sm:bottom-5 sm:left-6"><p class="inline-block rounded-full bg-[rgba(167,139,250,.24)] px-2.5 py-1 text-xs font-bold text-[#ddd6fe]">Winner · Aurora</p><h3 class="mt-2 text-[1.1rem] font-bold tracking-[-.015em] sm:text-[1.35rem]">First Light</h3><p class="mt-0.5 text-[.9rem] text-[#d3d7e2]">Arvid Holm · Tromsø</p></div>
    </div>
    <div class="relative aspect-[16/10] shrink-0 basis-full snap-center snap-always overflow-hidden rounded-[18px] transition-[filter,transform] duration-350 after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.9),rgba(11,13,18,0)_55%)] in-[.is-ready]:not-data-current:scale-93 in-[.is-ready]:not-data-current:brightness-40 in-[.is-ready]:not-data-current:saturate-60 motion-reduce:transition-none motion-reduce:in-[.is-ready]:not-data-current:scale-100" role="group" aria-roledescription="slide" aria-label="3 of 4">
      <img src="https://images.unsplash.com/photo-1419242902214-272b3f66ee7a?auto=format&fit=crop&w=1200&h=750&q=70" class="block size-full object-cover" width="1200" height="750" loading="lazy" alt="A meteor streaking across a purple starry sky above mountains">
      <div class="absolute right-4 bottom-3.5 left-4 z-[1] text-white sm:right-6 sm:bottom-5 sm:left-6"><p class="inline-block rounded-full bg-[rgba(167,139,250,.24)] px-2.5 py-1 text-xs font-bold text-[#ddd6fe]">Runner-up · Night landscape</p><h3 class="mt-2 text-[1.1rem] font-bold tracking-[-.015em] sm:text-[1.35rem]">Perseid, 2:14 am</h3><p class="mt-0.5 text-[.9rem] text-[#d3d7e2]">Ana Ruiz · La Palma</p></div>
    </div>
    <div class="relative aspect-[16/10] shrink-0 basis-full snap-center snap-always overflow-hidden rounded-[18px] transition-[filter,transform] duration-350 after:absolute after:inset-0 after:bg-[linear-gradient(to_top,rgba(11,13,18,.9),rgba(11,13,18,0)_55%)] in-[.is-ready]:not-data-current:scale-93 in-[.is-ready]:not-data-current:brightness-40 in-[.is-ready]:not-data-current:saturate-60 motion-reduce:transition-none motion-reduce:in-[.is-ready]:not-data-current:scale-100" role="group" aria-roledescription="slide" aria-label="4 of 4">
      <img src="https://images.unsplash.com/photo-1502134249126-9f3755a50d78?auto=format&fit=crop&w=1200&h=750&q=70" class="block size-full object-cover" width="1200" height="750" loading="lazy" alt="The bright core of the Milky Way with dust lanes and stars">
      <div class="absolute right-4 bottom-3.5 left-4 z-[1] text-white sm:right-6 sm:bottom-5 sm:left-6"><p class="inline-block rounded-full bg-[rgba(167,139,250,.24)] px-2.5 py-1 text-xs font-bold text-[#ddd6fe]">Runner-up · Deep sky</p><h3 class="mt-2 text-[1.1rem] font-bold tracking-[-.015em] sm:text-[1.35rem]">Core</h3><p class="mt-0.5 text-[.9rem] text-[#d3d7e2]">Priya Nair · Atacama</p></div>
    </div>
  </div>
  <div class="mt-[18px] flex items-center justify-center gap-3.5" data-controls hidden>
    <button class="grid size-11 cursor-pointer place-items-center rounded-full border-[1.5px] border-[#4b5264] text-nf-ink transition-[background-color,border-color] duration-200 hover:border-nf-ink hover:bg-[#1f2430] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none" type="button" data-prev aria-label="Previous photo"><svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg></button>
    <div class="flex gap-0.5">
      <button class="grid size-6 cursor-pointer place-items-center p-0 before:size-2 before:rounded-full before:bg-[#5b6275] before:transition-[background-color,transform] before:duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:before:scale-130 aria-[current=true]:before:bg-nf-accent motion-reduce:before:transition-none" type="button" data-dot aria-label="Show photo 1"></button>
      <button class="grid size-6 cursor-pointer place-items-center p-0 before:size-2 before:rounded-full before:bg-[#5b6275] before:transition-[background-color,transform] before:duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:before:scale-130 aria-[current=true]:before:bg-nf-accent motion-reduce:before:transition-none" type="button" data-dot aria-label="Show photo 2"></button>
      <button class="grid size-6 cursor-pointer place-items-center p-0 before:size-2 before:rounded-full before:bg-[#5b6275] before:transition-[background-color,transform] before:duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:before:scale-130 aria-[current=true]:before:bg-nf-accent motion-reduce:before:transition-none" type="button" data-dot aria-label="Show photo 3"></button>
      <button class="grid size-6 cursor-pointer place-items-center p-0 before:size-2 before:rounded-full before:bg-[#5b6275] before:transition-[background-color,transform] before:duration-200 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent aria-[current=true]:before:scale-130 aria-[current=true]:before:bg-nf-accent motion-reduce:before:transition-none" type="button" data-dot aria-label="Show photo 4"></button>
    </div>
    <button class="grid size-11 cursor-pointer place-items-center rounded-full border-[1.5px] border-[#4b5264] text-nf-ink transition-[background-color,border-color] duration-200 hover:border-nf-ink hover:bg-[#1f2430] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-nf-accent motion-reduce:transition-none" type="button" data-next aria-label="Next photo"><svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></button>
  </div>
</section></div>
CSS
@theme {
  --color-nf-bg: #151922;
  --color-nf-ink: #e8eaf0;
  --color-nf-muted: #9aa3b5;
  --color-nf-accent: #a78bfa;
}
JavaScript
// Scroll-snap carousel. The track scrolls and swipes without this script; it only reveals
// the buttons and dots, wires them up and keeps their state in sync with the scroll position.
document.querySelectorAll('[data-carousel]').forEach((root) => {
  const track = root.querySelector('[data-track]');
  const slides = [...track.children];
  const dots = [...root.querySelectorAll('[data-dot]')];
  const prev = root.querySelector('[data-prev]');
  const next = root.querySelector('[data-next]');
  const loop = root.hasAttribute('data-loop');
  const reduce = matchMedia('(prefers-reduced-motion: reduce)');
  const step = () => (slides[1] ? slides[1].offsetLeft - slides[0].offsetLeft : track.clientWidth);
  const current = () => Math.round(track.scrollLeft / step());
  const atEnd = () => track.scrollLeft >= track.scrollWidth - track.clientWidth - 2;
  const go = (i) => track.scrollTo({ left: i * step(), behavior: reduce.matches ? 'auto' : 'smooth' });
  const move = (dir) => {
    if (loop && dir > 0 && atEnd()) return go(0);
    if (loop && dir < 0 && track.scrollLeft < 2) return go(slides.length - 1);
    const perView = Math.max(1, Math.floor((track.clientWidth + step() - slides[0].offsetWidth + 2) / step()));
    go(current() + dir * perView);
  };
  const sync = () => {
    const i = current();
    dots.forEach((dot, n) => dot.setAttribute('aria-current', n === i));
    slides.forEach((slide, n) => slide.toggleAttribute('data-current', n === i));
    if (!loop && prev) prev.setAttribute('aria-disabled', track.scrollLeft < 2);
    if (!loop && next) next.setAttribute('aria-disabled', atEnd());
  };
  prev?.addEventListener('click', () => move(-1));
  next?.addEventListener('click', () => move(1));
  dots.forEach((dot, n) => dot.addEventListener('click', () => go(n)));
  track.addEventListener('scroll', () => requestAnimationFrame(sync), { passive: true });
  root.querySelectorAll('[data-controls]').forEach((el) => { el.hidden = false; });
  root.classList.add('is-ready');
  sync();
});

About this set

Carousels for dark interfaces such as streaming, music and photography sites. Featured slide with an episode list shows a documentary series: a large night-sky photo with the episode title, a short description and Play and My list buttons, and beside it a numbered list of episodes with their running times that works as the navigation, with a violet bar on the current one. On narrow screens the list moves under the photo. Centre mode with dimmed neighbours puts the current photo from a night photography award in the middle, with the previous and next ones peeking in at the edges, darkened and slightly smaller, and arrows and dots below. The palette keeps muted text at about 7:1 and the dots above 3:1 against the near-black page, so nothing turns to mud. The plain CSS and Tailwind versions use scroll-snap tracks and a short script that marks the current slide. The Bootstrap version runs in Bootstrap’s dark colour mode, builds the first as a .carousel-fade whose indicators are the episode list, and the centre mode as a scroll-snap row, because .carousel shows one slide at a time.

What’s included

  • Episode list that doubles as the carousel's navigation
  • Centre mode with the neighbouring slides dimmed and scaled down
  • Muted text and dots checked for contrast on a near-black background
  • Bootstrap version uses data-bs-theme="dark" and .carousel-fade
  • Slides swipe without JavaScript in the CSS and Tailwind versions

Accessibility

Both carousels are labelled regions with slides announced as "2 of 4". The episode buttons are named by their visible text, number, title and running time, and the current one has aria-current; the centre-mode arrows and dots are named buttons. Violet 3px outlines mark keyboard focus, and under prefers-reduced-motion the dimming stays but the scaling and transitions are removed.

Customise it

Change --nf-bg, --nf-ink, --nf-muted and --nf-accent on .nf, and --spot-accent and --spot-side on .spot, where --spot-side sets how much of each neighbour shows. In Tailwind these are the --color-nf-* theme values and the --side arbitrary property.