/** * Slider functionality using Splide * Handles various responsive carousel components */ (() => { // Configuration const SLIDERCONFIG = { SLIDER: { SPEED: 400, REWIND_SPEED: 500, DRAG_ANGLE_THRESHOLD: 60, GAP: "var(--space--24)", GAP_MOBILE: "var(--space--16)", BREAKPOINTS: { TABLET: 991, MOBILE_LANDSCAPE: 767, MOBILE_PORTRAIT: 479, }, }, SELECTORS: { ARROW_PREV: ".component-library--splide__arrow--prev", ARROW_NEXT: ".component-library--splide__arrow--next", PAGINATION: ".component-library--splide__pagination.component-library--cc-slideshow", }, CLASSES: { SPLIDE: "splide", SPLIDE_TRACK: "splide__track", SPLIDE_LIST: "splide__list", SPLIDE_SLIDE: "splide__slide", }, }; /** * Maps required Splide classes to slider elements * @param {string} sliderName - The name of the slider (e.g., 'menu') */ function mapSplideClasses(sliderName) { const components = $(`[r-${sliderName}-slider="component"]`); components.each(function () { const component = $(this); const wrap = component.find(`[r-${sliderName}-slider="wrap"]`); const list = component.find(`[r-${sliderName}-slider="list"]`); const slides = component.find(`[r-${sliderName}-slider="item"]`); component.addClass(SLIDERCONFIG.CLASSES.SPLIDE); list.addClass(SLIDERCONFIG.CLASSES.SPLIDE_LIST); slides.addClass(SLIDERCONFIG.CLASSES.SPLIDE_SLIDE); // If wrap element exists, use it as the track if (wrap.length) { wrap.addClass(SLIDERCONFIG.CLASSES.SPLIDE_TRACK); } else { // No wrap element - create track wrapper around the list list.wrap(`
`); } }); } /** * Creates base Splide configuration object * @param {Object} overrides - Configuration overrides * @returns {Object} Splide configuration */ function createSplideConfig(overrides = {}) { return { perPage: 1, perMove: 1, focus: "center", type: "slide", gap: SLIDERCONFIG.SLIDER.GAP, arrows: false, pagination: true, speed: SLIDERCONFIG.SLIDER.SPEED, dragAngleThreshold: SLIDERCONFIG.SLIDER.DRAG_ANGLE_THRESHOLD, autoWidth: false, rewind: true, rewindSpeed: SLIDERCONFIG.SLIDER.REWIND_SPEED, waitForTransition: false, updateOnMove: true, trimSpace: true, ...overrides, }; } /** * Connects custom navigation arrows to Splide instance * @param {jQuery} component - The component element * @param {Object} splide - Splide instance */ function connectSliderArrows(component, splide) { const prevArrow = component.find(SLIDERCONFIG.SELECTORS.ARROW_PREV); const nextArrow = component.find(SLIDERCONFIG.SELECTORS.ARROW_NEXT); prevArrow.on("click", function (e) { e.preventDefault(); splide.go("<"); }); nextArrow.on("click", function (e) { e.preventDefault(); splide.go(">"); }); } /** * Generic slider initialization function * @param {string} sliderName - The name of the slider (e.g., 'menu', 'ma-carousel') * @param {Object} config - Splide configuration overrides */ function initSlider(sliderName, config) { mapSplideClasses(sliderName); const components = $(`[r-${sliderName}-slider="component"]`); if (!components.length) return; const sliderConfig = createSplideConfig(config); components.each(function () { const component = $(this); const paginationElement = component.find(SLIDERCONFIG.SELECTORS.PAGINATION); const finalConfig = { ...sliderConfig, pagination: paginationElement.length ? true : false, }; const splide = new Splide(component[0], finalConfig); // If custom pagination exists, move Splide's pagination content into it if (paginationElement.length) { splide.on("pagination:mounted", () => { const splidePagination = component.find(".splide__pagination"); if (splidePagination.length) { // Clear existing pagination content to prevent duplicates paginationElement.empty(); // Move the pagination content (buttons) into your custom container paginationElement.append(splidePagination.children()); // Remove the default Splide pagination container splidePagination.remove(); } }); } splide.mount(); connectSliderArrows(component, splide); }); } /** * Initializes Menu slider components */ function splideMenu() { initSlider("menu", { perPage: 2.5, breakpoints: { [SLIDERCONFIG.SLIDER.BREAKPOINTS.MOBILE_LANDSCAPE]: { perPage: 1.05, gap: SLIDERCONFIG.SLIDER.GAP_MOBILE, }, }, }); } /** * Initializes MA Carousel slider components */ function splideMaCarousel() { initSlider("ma-carousel", { perPage: 3, breakpoints: { [SLIDERCONFIG.SLIDER.BREAKPOINTS.TABLET]: { perPage: 1.5, }, [SLIDERCONFIG.SLIDER.BREAKPOINTS.MOBILE_LANDSCAPE]: { perPage: 1.05, gap: SLIDERCONFIG.SLIDER.GAP_MOBILE, }, }, }); } /** * Initializes After-Content slider components */ function splideAfterContent() { initSlider("after-content", { // perPage: 1, type: "fade", }); } /** * Initializes More Stories slider components (tablet and mobile only) */ function splideMoreStories() { // Only initialize on tablet and smaller screens gsap .matchMedia() .add(`(max-width: ${SLIDERCONFIG.SLIDER.BREAKPOINTS.TABLET}px)`, () => { initSlider("more-stories", { perPage: 1.5, breakpoints: { [SLIDERCONFIG.SLIDER.BREAKPOINTS.TABLET]: { perPage: 1.5, }, [SLIDERCONFIG.SLIDER.BREAKPOINTS.MOBILE_LANDSCAPE]: { perPage: 1.05, gap: SLIDERCONFIG.SLIDER.GAP_MOBILE, }, }, }); }); } function initSliders() { splideMenu(); splideMaCarousel(); splideAfterContent(); splideMoreStories(); } // Initialize when DOM is ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initSliders); } else { initSliders(); } })();