/** * 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 } } }); }); } // Initialize sliders when DOM is ready document.addEventListener('DOMContentLoaded', () => { splideMenu(); splideMaCarousel(); splideAfterContent(); splideMoreStories(); });