CAROUSEL: Scroll-snap photo carousel for school/provider galleries
VARIANTS: Track (arrows + dots) | Grid + Lightbox | Single image (controls hidden)
CSS scroll-snap drives the motion — carousel_controller.js only computes
arrow/dot scrollTo targets and syncs active-slide state via an
IntersectionObserver (no scroll-event polling, no third-party carousel library).
Arrows disable at the ends rather than wrapping around (see the component doc's
"Ends behavior" section). Images below are inline SVG placeholders (no external
network calls) — swap for real `<img src="...">` in production use.
Arrows + dots: click an arrow, click a dot, or focus the track and
use Left/Right. The active dot gets aria-current="true"; arrows disable
(native disabled) at the first/last photo. An sr-only status region
announces "Photo N of M: caption" on every change.
<div data-controller="carousel"><div class="carousel"><div class="carousel-track" data-carousel-target="track" tabindex="0" data-action="keydown->carousel#keydown"><div class="carousel-slide" data-carousel-target="slide"><img src="..." alt="..."><div class="carousel-caption">Main Building</div></div>...</div><button class="carousel-arrow-prev" data-carousel-target="prev" data-action="click->carousel#prev">...</button><button class="carousel-arrow-next" data-carousel-target="next" data-action="click->carousel#next">...</button></div><div class="carousel-dots" data-carousel-target="dots"><button class="carousel-dot" data-carousel-target="dot" data-action="click->carousel#goToDot"></button>...</div></div>
Grid variant: a plain thumbnail grid — no scroll-snap track, no
carousel_controller.js. Each thumbnail is its own independent
data-controller="popup" instance; clicking it opens a
.carousel-lightbox (a dark, minimal-chrome .popup-panel
modifier) with the enlarged photo. Escape and backdrop-click close it — the popup
contract handles both, unchanged.
<div data-controller="popup"><button class="carousel-grid-item" data-action="click->popup#show"><img src="..." alt="..."></button><div class="popup-overlay" data-popup-target="overlay" data-action="click->popup#handleOverlayClick"></div><div class="popup-panel popup-lg carousel-lightbox" data-popup-target="panel" role="dialog" aria-modal="true">...<div class="popup-body"><img src="..." alt="..."></div></div></div>
One slide, nothing to navigate to: with a single
.carousel-slide, carousel_controller.js hides the arrows
and the dots row on connect (native hidden attribute) instead of
showing disabled-forever controls.
<!-- one .carousel-slide inside .carousel-track — arrows/dots hidden on connect() -->