initCarouselSwiper()
Wraps Swiper. Every carousel on the page is found and initialised from its own
markup — there is no per-page JavaScript to write, and each carousel is
configured with data-cel-* attributes. A page can carry several
carousels with different behaviour.
HTML Markup
<div class="cel-carousel-slider" data-cel-carousel="portfolio">
<div class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img src="assets/images/demo/portfolio/portfolio-1-1024w.webp" alt="">
</div>
</div>
</div>
<!-- Optional. Present = enabled, unless an attribute says otherwise. -->
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
The outer element carries the settings. .cel-carousel-slider and
.cel-carousel-slider-no-gutter are both recognised. The inner
.swiper / .swiper-wrapper / .swiper-slide
structure is Swiper's own and is required.
Initialization
initCarouselSwiper();
Called for you on DOMContentLoaded, at the bottom of
main.js. The function is internal to that file, not a global, so
there is nothing to call from your own scripts. It skips any .swiper
that already has an instance.
Parameters
All optional, all read from the outer element. A missing attribute falls back to the preset, then to the default.
| Attribute | Values | Default | Description |
|---|---|---|---|
data-cel-carousel |
preset name | — | Selects a named preset. portfolio is the one that ships. |
data-cel-loop |
false |
true |
Wraps from the last slide back to the first. |
data-cel-speed |
ms | 800 |
Transition duration. The portfolio preset raises it to 1200. |
data-cel-slides-per-view |
number or auto |
auto |
A number fixes the columns at every width, replacing the preset's. auto lets each slide keep its own width. |
data-cel-centered |
false |
true |
Centres the active slide. The preset turns this off. |
data-cel-gutter |
px | — | One fixed gap at every width, replacing the responsive ladder. |
data-cel-pagination |
true / false |
presence | Overrides whether the dots are used. |
data-cel-navigation |
true / false |
presence | Overrides whether the arrows are used. |
data-cel-autoplay |
delay in ms | off | Presence enables autoplay; the value is the delay. |
data-cel-overlay-sync |
overlay id | — | See initOverlayCarouselSync(). |
“Presence” means the markup decides. If you put a
.swiper-pagination element in, you get dots; leave it out and you
do not. The attribute is only there for the case where the element exists but
you want it off — then it is hidden with a class, not with inline styles.
Behavior
- Gaps are a ladder, not a number. By default the space between slides grows with the viewport: 20px from 320, 24 from 768, 32 from 1024, 36 from 1280, 42 from 1536.
data-cel-gutterreplaces the whole ladder with one value. - Keyboard control is on, but only while the carousel is in the viewport — arrow keys do not hijack the page from elsewhere.
- A carousel whose slides already fit disables its own arrows and looping (
watchOverflow). - Fewer slides than columns is fine. The column count never exceeds the number of slides, so three slides on a four-column desktop widen to fill the row instead of leaving a gap. Looping needs more slides than columns, so it is decided per width: off where every slide is already on screen, on wherever the carousel scrolls.
- Reduced motion sets the transition to zero and refuses autoplay. Autoplay at zero speed would teleport a slide every few seconds, which is worse than the animation it replaces.
Presets
A preset is a named bundle of settings for a carousel whose responsive behaviour is
fundamentally different, rather than one value away. portfolio is a
fixed-count grid: 1 column from 320px, 2 from 576, 3 from 992, 4 from 1200, 5 from
2400 and 6 from 3200, with gaps of 0–8px, no centring, and a slower 1200ms
transition.
Presets live in CAROUSEL_SWIPER.presets at the top of
main.js. To add one, copy the portfolio block under a new
name and select it with data-cel-carousel="<name>".
Scalar attributes still apply on top, so a preset is a starting point rather than a
lock: data-cel-carousel="portfolio" data-cel-loop="false".
initInfoOverHover()
The panel that rises over a slide on hover, with the image scaling behind it and the text staggering in. GSAP-driven, and bound only where hovering is a real input.
HTML Markup
<div class="swiper-slide">
<div class="cel-info-over-wrapper">
<div class="cel-info-over io-h-center io-v-bottom cel-align-center">
<div class="cel-info-over-inner io-inner-bg">
<h5>The Last Great Canvas Was Twelve Inches</h5>
<p>Art, alphabetised by surname.</p>
</div>
</div>
<img src="assets/images/demo/portfolio/portfolio-1-1024w.webp" alt="">
</div>
</div>
Three elements are required inside the wrapper: .cel-info-over (the box
that moves), .cel-info-over-inner (whose children stagger), and an
img (which scales). A wrapper missing any of them is skipped rather
than half-animated.
Placement classes on .cel-info-over — io-h-center,
io-v-bottom — position the panel; io-inner-bg gives
the inner box its own fill.
Initialization
initInfoOverHover();
Called for you on DOMContentLoaded, at the bottom of
main.js.
Behavior
- Desktop only. Bound from 992px up, and torn down below it. A hover effect on a touch screen fires on tap and then sticks, so the panel would stay open with no way to dismiss it.
- The teardown is real. Crossing the breakpoint removes both listeners, kills the running timeline and clears every property GSAP set. Resizing back and forth does not accumulate handlers.
- Leaving is faster than entering — the exit runs at 0.7× the duration. An exit that takes as long as the entrance feels like the interface is reluctant to let go.
- Reduced motion collapses every duration to zero; the end states are identical, they are simply arrived at instantly.
Custom Configuration
Unlike the carousel, this one is tuned from JavaScript rather than from markup —
it is a motion curve, and motion curves are not per-instance decisions. Edit the
INFO_OVER_HOVER defaults at the top of main.js, or pass an
object to the call at the bottom of it:
initInfoOverHover({
imgScale: 1.08, // how far the image scales behind the panel
imgDuration: 0.6,
overlayDuration: 1,
overlayScale: true, // scale the panel as well as sliding it
overlayScaleFrom: 0.8,
overlayY: 40, // px the panel travels up from
contentY: 100, // px each line of content travels up from
contentDuration: 0.55,
contentStagger: 0.08, // delay between lines
leaveSpeed: 0.7, // multiplier on the exit; lower is faster
});
initOverlayCarouselSync()
Keeps the carousel behind an open overlay on the same item as the overlay itself, so closing the panel does not leave the strip somewhere else.
HTML Markup
<div class="cel-carousel-slider" data-cel-carousel="portfolio"
data-cel-overlay-sync="portfolio">
The value is the data-cel-overlay-id of the overlay to follow. One
attribute; there is nothing else to wire.
Behavior
- Only while the overlay is open. Opening an item from the strip moves nothing — the strip is already showing it. Without this, the click that ends a drag on the carousel would jump the strip to the item you grabbed.
- Listens for the
cel:overlay-navevent that Overlay Navigation publishes, and moves the matching carousel to the same index. - At zero speed. The strip is behind a panel while this runs, so an animation would only ever be seen as a jump at the moment the panel disappears.
- Uses
slideToLoop, notslideTo. With looping on, the real slides sit among duplicates, and a raw index lands on a clone.
This lives here rather than in overlay-nav.js on purpose. Swiper is
a dependency of main.js; the navigation module has no business
knowing which carousel library the page happens to use. It publishes an event
and stops there.