diff --git a/assets/js/theme/blocks/draggableBlocks.js b/assets/js/theme/blocks/draggableBlocks.js
index b653531f74784e774c107bf25032805d665a821a..4bd17eadb11ef20d213eb5a6bfc68900deb6d199 100644
--- a/assets/js/theme/blocks/draggableBlocks.js
+++ b/assets/js/theme/blocks/draggableBlocks.js
@@ -1,170 +1,170 @@
-const draggableBlocks = document.querySelectorAll('.block-timeline--horizontal, .block-posts--carousel');
-
-class DraggableBlock {
-    constructor (block) {
-        this.block = block;
-        this.content = this.block.querySelector('.draggable-container');
-        this.list = this.block.querySelector('ol, ul');
-        this.items = this.list.querySelectorAll('.draggable-item');
-        this.previous = this.block.querySelector('.previous');
-        this.next = this.block.querySelector('.next');
-        this.isPointerDown = false;
-
-        this.index = 0;
-
-        this.listen();
-        this.resize();
-        this.goTo(0);
-    }
-
-    listen () {
-        window.addEventListener('resize', this.resize.bind(this));
-
-        this.items.forEach((item, i) => {
-            item.addEventListener('click', this.onClickItem.bind(this, i));
-        });
-
-        if (this.previous && this.next) {
-            this.handleArrows();
-        }
-
-        this.handlePointers();
-        this.handleScroll();
-    }
-
-    resize () {
-        let maxTitleHeight = 0;
-
-        this.block.style = '';
-
-        this.itemWidth = this.items[0].offsetWidth;
-
-        this.items.forEach((item) => {
-            maxTitleHeight = Math.max(item.querySelector('.title, [itemprop="headline"]').offsetHeight, maxTitleHeight);
-        });
-
-        this.block.style.setProperty('--min-title-height', maxTitleHeight + 'px');
-        this.update();
-    }
-
-    onClickItem (i) {
-        if (!this.isManipulated) {
-            this.goTo(i);
-        }
-    }
-
-    handleArrows () {
-        this.previous.addEventListener('click', () => {
-            this.goTo(this.index-1);
-        });
-
-        this.next.addEventListener('click', () => {
-            this.goTo(this.index+1);
-        });
-    }
-
-    handlePointers () {
-        let startX,
-            endX,
-            threshold = 30;
-            // j'ai initialisé isPointerDown au début du code : this.isPointerDown
-            // j'ai enlevé endEvents = ['pointerup'] parce qu'il était seul ?
-        this.block.style.touchAction = 'pan-y';
-
-        // on passe de this.content à this.block sur chaque événement pour grab sur tout le bloc
-        this.block.addEventListener('pointerdown', (event) => {
-            // On vérifie que l'on ne soit pas en train de cliquer sur les boutons (car on cible tout le bloc pour le grab)
-            if (event.target !== this.next && event.target !== this.previous) {
-                // on utilise partout this.isPointerDown car la navigation avec les arrow buguait
-                // parfois ça naviguait de 2 items
-                this.isPointerDown = true;
-                this.content.classList.add('is-grabbing');
-                startX = event.clientX;
-            }
-        });
-
-        this.block.addEventListener('pointermove', (event) => {
-            endX = event.clientX;
-            // On vérifie que l'événement pointerdown a été activé
-            if (this.isPointerDown) {
-                event.preventDefault();
-                this.items.forEach((item) => {
-                    // on enlève le pointerevents pour que les liens ne soient pas cliquable au drag
-                    item.style.pointerEvents = 'none';
-                });
-            }
-        });
-
-        // anciennement géré avec endEvents = ['pointerup'] (j'enlève le forEach)
-        this.block.addEventListener('pointerup', (event) => {
-            endX = event.clientX;
-            // on vérifie encore isPointerDown pour éviter le pb des arrows
-            if (this.isPointerDown) {
-                this.isPointerDown = false;
-                this.onManipulationEnd(startX, endX, threshold);
-            }
-        });
-    }
-
-    handleScroll () {
-        // On écoute le scroll sur le contenu du bloc
-        this.content.addEventListener('wheel', (event) => {
-            const deltaX = event.deltaX,
-                deltaY = event.deltaY;
-            // navigation entre les items (comme onManipulationEnd)
-            if (Math.abs(deltaX) > Math.abs(deltaY)) {
-                if (deltaX !== 0) {
-                    if (deltaX > 0) {
-                        this.goTo(this.index + 1);
-                    } else {
-                        this.goTo(this.index - 1);
-                    }
-                }
-            }
-        });
-    }
-
-    onManipulationEnd (start, end, threshold) {
-        if (start > end + threshold) {
-            this.goTo(this.index+1);
-        } else if (start < end - threshold) {
-            this.goTo(this.index-1);
-        }
-
-        this.content.classList.remove('is-grabbing');
-        this.items.forEach((item) => {
-            // On rend le pointervents pour pouvoir cliquer sur le lien si on drag pas
-            item.style.pointerEvents = 'all';
-        });
-
-        setTimeout(() => {
-            this.isManipulated = false;
-        }, 100);
-    }
-
-    goTo (_index) {
-        this.index = Math.min(Math.max(_index, 0), this.items.length-1);
-        this.update();
-    }
-
-    update () {
-        this.list.style.marginLeft = `${-this.index * this.itemWidth}px`;
-
-        this.items.forEach((item, index) => {
-            if (index < this.index) {
-                item.classList.add('is-passed');
-            } else {
-                item.classList.remove('is-passed');
-            }
-        });
-
-        if (this.previous && this.next) {
-            this.previous.disabled = this.index === 0;
-            this.next.disabled = this.index === this.items.length - 1;
-        }
-    }
-}
-
-draggableBlocks.forEach((block) => {
-    new DraggableBlock(block);
-});
+// const draggableBlocks = document.querySelectorAll('.block-posts--carousel');
+
+// class DraggableBlock {
+//     constructor (block) {
+//         this.block = block;
+//         this.content = this.block.querySelector('.draggable-container');
+//         this.list = this.block.querySelector('ol, ul');
+//         this.items = this.list.querySelectorAll('.draggable-item');
+//         this.previous = this.block.querySelector('.previous');
+//         this.next = this.block.querySelector('.next');
+//         this.isPointerDown = false;
+
+//         this.index = 0;
+
+//         this.listen();
+//         this.resize();
+//         this.goTo(0);
+//     }
+
+//     listen () {
+//         window.addEventListener('resize', this.resize.bind(this));
+
+//         this.items.forEach((item, i) => {
+//             item.addEventListener('click', this.onClickItem.bind(this, i));
+//         });
+
+//         if (this.previous && this.next) {
+//             this.handleArrows();
+//         }
+
+//         this.handlePointers();
+//         this.handleScroll();
+//     }
+
+//     resize () {
+//         let maxTitleHeight = 0;
+
+//         this.block.style = '';
+
+//         this.itemWidth = this.items[0].offsetWidth;
+
+//         this.items.forEach((item) => {
+//             maxTitleHeight = Math.max(item.querySelector('.title, [itemprop="headline"]').offsetHeight, maxTitleHeight);
+//         });
+
+//         this.block.style.setProperty('--min-title-height', maxTitleHeight + 'px');
+//         this.update();
+//     }
+
+//     onClickItem (i) {
+//         if (!this.isManipulated) {
+//             this.goTo(i);
+//         }
+//     }
+
+//     handleArrows () {
+//         this.previous.addEventListener('click', () => {
+//             this.goTo(this.index-1);
+//         });
+
+//         this.next.addEventListener('click', () => {
+//             this.goTo(this.index+1);
+//         });
+//     }
+
+//     handlePointers () {
+//         let startX,
+//             endX,
+//             threshold = 30;
+//             // j'ai initialisé isPointerDown au début du code : this.isPointerDown
+//             // j'ai enlevé endEvents = ['pointerup'] parce qu'il était seul ?
+//         this.block.style.touchAction = 'pan-y';
+
+//         // on passe de this.content à this.block sur chaque événement pour grab sur tout le bloc
+//         this.block.addEventListener('pointerdown', (event) => {
+//             // On vérifie que l'on ne soit pas en train de cliquer sur les boutons (car on cible tout le bloc pour le grab)
+//             if (event.target !== this.next && event.target !== this.previous) {
+//                 // on utilise partout this.isPointerDown car la navigation avec les arrow buguait
+//                 // parfois ça naviguait de 2 items
+//                 this.isPointerDown = true;
+//                 this.content.classList.add('is-grabbing');
+//                 startX = event.clientX;
+//             }
+//         });
+
+//         this.block.addEventListener('pointermove', (event) => {
+//             endX = event.clientX;
+//             // On vérifie que l'événement pointerdown a été activé
+//             if (this.isPointerDown) {
+//                 event.preventDefault();
+//                 this.items.forEach((item) => {
+//                     // on enlève le pointerevents pour que les liens ne soient pas cliquable au drag
+//                     item.style.pointerEvents = 'none';
+//                 });
+//             }
+//         });
+
+//         // anciennement géré avec endEvents = ['pointerup'] (j'enlève le forEach)
+//         this.block.addEventListener('pointerup', (event) => {
+//             endX = event.clientX;
+//             // on vérifie encore isPointerDown pour éviter le pb des arrows
+//             if (this.isPointerDown) {
+//                 this.isPointerDown = false;
+//                 this.onManipulationEnd(startX, endX, threshold);
+//             }
+//         });
+//     }
+
+//     handleScroll () {
+//         // On écoute le scroll sur le contenu du bloc
+//         this.content.addEventListener('wheel', (event) => {
+//             const deltaX = event.deltaX,
+//                 deltaY = event.deltaY;
+//             // navigation entre les items (comme onManipulationEnd)
+//             if (Math.abs(deltaX) > Math.abs(deltaY)) {
+//                 if (deltaX !== 0) {
+//                     if (deltaX > 0) {
+//                         this.goTo(this.index + 1);
+//                     } else {
+//                         this.goTo(this.index - 1);
+//                     }
+//                 }
+//             }
+//         });
+//     }
+
+//     onManipulationEnd (start, end, threshold) {
+//         if (start > end + threshold) {
+//             this.goTo(this.index+1);
+//         } else if (start < end - threshold) {
+//             this.goTo(this.index-1);
+//         }
+
+//         this.content.classList.remove('is-grabbing');
+//         this.items.forEach((item) => {
+//             // On rend le pointervents pour pouvoir cliquer sur le lien si on drag pas
+//             item.style.pointerEvents = 'all';
+//         });
+
+//         setTimeout(() => {
+//             this.isManipulated = false;
+//         }, 100);
+//     }
+
+//     goTo (_index) {
+//         this.index = Math.min(Math.max(_index, 0), this.items.length-1);
+//         this.update();
+//     }
+
+//     update () {
+//         this.list.style.marginLeft = `${-this.index * this.itemWidth}px`;
+
+//         this.items.forEach((item, index) => {
+//             if (index < this.index) {
+//                 item.classList.add('is-passed');
+//             } else {
+//                 item.classList.remove('is-passed');
+//             }
+//         });
+
+//         if (this.previous && this.next) {
+//             this.previous.disabled = this.index === 0;
+//             this.next.disabled = this.index === this.items.length - 1;
+//         }
+//     }
+// }
+
+// draggableBlocks.forEach((block) => {
+//     new DraggableBlock(block);
+// });
diff --git a/assets/js/theme/blocks/timeline.js b/assets/js/theme/blocks/timeline.js
new file mode 100644
index 0000000000000000000000000000000000000000..53a9585184678d2632372f152914630200b19432
--- /dev/null
+++ b/assets/js/theme/blocks/timeline.js
@@ -0,0 +1,55 @@
+function Timeline(timeline) {
+    this.timeline = timeline;
+    this.eventClass = 'timeline-event'
+    this.init();
+}
+
+Timeline.prototype.init = function() {
+    this.updateTitleHeight();
+
+    // Resize
+    window.addEventListener('resize', this.handleResize.bind(this));
+};
+
+Timeline.prototype.updateTitleHeight = function () {
+    var maxTitleHeight = this.getMaxTitleHeight();
+
+    // On met à jour la variable css qui ajoute une min-height au .title
+    // L'objectif est d'aligner les lignes de la timeline entre elles
+    this.updateCssVariable('--min-title-height', maxTitleHeight + 'px');
+};
+
+Timeline.prototype.getMaxTitleHeight = function () {
+    var maxTitleHeight = 0;
+    var events = this.timeline.getElementsByClassName(this.eventClass);
+
+    // On vient regarder dans tous les .timeline-event pour vérifier quel est le titre le plus long
+    Array.prototype.forEach.call(events, function(event) {
+        var titleHeight = this.getTitleHeight(event);
+        if (titleHeight > maxTitleHeight) {
+            maxTitleHeight = titleHeight;
+        }
+    }, this);
+
+    return maxTitleHeight;
+}
+
+Timeline.prototype.getTitleHeight = function (event) {
+    var eventTitle = event.querySelector('.title');
+    return eventTitle ? eventTitle.offsetHeight : 0;
+};
+
+Timeline.prototype.updateCssVariable = function (variable, value) {
+    this.timeline.style.setProperty(variable, value);
+};
+
+Timeline.prototype.handleResize = function() {
+    this.updateTitleHeight();
+};
+
+document.addEventListener('DOMContentLoaded', function() {
+    var timelines = document.getElementsByClassName('block-timeline--horizontal');
+    for (var i = 0; i < timelines.length; i++) {
+        new Timeline(timelines[i]);
+    }
+});
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel.js b/assets/js/theme/components/carousel.js
new file mode 100644
index 0000000000000000000000000000000000000000..974eb2c9e53927aa905306ec0dea62a221a75918
--- /dev/null
+++ b/assets/js/theme/components/carousel.js
@@ -0,0 +1,15 @@
+// https://developers.osuny.org/docs/theme/components/carousel/
+// First
+import './carousel/utils';
+// Then alphabetical
+import './carousel/arrows';
+import './carousel/autoplayer';
+import './carousel/carousel';
+import './carousel/classes';
+import './carousel/config';
+import './carousel/events';
+import './carousel/manager';
+import './carousel/paginationButton';
+import './carousel/pagination';
+import './carousel/slide';
+import './carousel/slider';
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/arrows.js b/assets/js/theme/components/carousel/arrows.js
new file mode 100644
index 0000000000000000000000000000000000000000..9c5b32c9fc6b668953a1ba8636b91640d39edd8b
--- /dev/null
+++ b/assets/js/theme/components/carousel/arrows.js
@@ -0,0 +1,35 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.Arrows = function (element) {
+    this.element = element;
+    if (!this.element) { return };
+    this._findElement = window.osuny.carousel.utils.findElement.bind(this);
+    this._dispatchEvent = window.osuny.carousel.utils.dispatchEvent.bind(this);
+    this.counter = this._findElement('arrowsCounter');
+    this.next = this._findElement('arrowsNext');
+    this.previous = this._findElement('arrowsPrevious');
+    this.next.addEventListener(
+        "click",
+        this._onNext.bind(this)
+    );
+    this.previous.addEventListener(
+        "click",
+        this._onPrevious.bind(this)
+    );
+}
+window.osuny.carousel.Arrows.prototype = {
+    update: function (index, total) {
+        if (this.element) {
+            this.counter.innerHTML = (index + 1) + '/' + total;
+            this.next.disabled = index + 1 == total;
+            this.previous.disabled = index == 0;
+        }
+    },
+    _onNext: function () {
+        this._dispatchEvent('arrowsNext');
+    },
+    _onPrevious: function () {
+        this._dispatchEvent('arrowsPrevious');
+    }
+}
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/autoplayer.js b/assets/js/theme/components/carousel/autoplayer.js
new file mode 100644
index 0000000000000000000000000000000000000000..a1302cfac291af5f472222ce0bf3760fe7acf8ea
--- /dev/null
+++ b/assets/js/theme/components/carousel/autoplayer.js
@@ -0,0 +1,123 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.Autoplayer = function (element) {
+    this.element = element;
+    if (!this.element) { return };
+    this.icons = {
+        play: this.element.getElementsByClassName(window.osuny.carousel.classes.autoplayerToggleIconPlay).item(0),
+        pause: this.element.getElementsByClassName(window.osuny.carousel.classes.autoplayerToggleIconPause).item(0)
+    }
+    this.ariaLiveElement = null;
+    // Etat de l'autoplay
+    this.enabled = false;
+    // Etat de pause (quand on rollover par exemple)
+    this.paused = false;
+    // Pause au rollover
+    this.softPaused = false;
+    // Intervalle en millisecondes entre 2 déclenhements
+    this.interval = 3000;
+    this._resetLoopValues();
+    // Bouton toggle
+    this.classList = this.element.classList;
+    this.classList.add(window.osuny.carousel.classes.autoplayerPaused);
+    this.element.addEventListener(
+        "click",
+        this._onClick.bind(this)
+    );
+    this._dispatchEvent = window.osuny.carousel.utils.dispatchEvent.bind(this);
+}
+window.osuny.carousel.Autoplayer.prototype = {
+    setInterval: function (interval) {
+        this.interval = interval;
+    },
+    // enable() et disable() activent la boucle 
+    enable: function () {
+        this.enabled = true;
+        this._loop();
+        this._updateToggle();
+    },
+    disable: function () {
+        this.enabled = false;
+        this.paused = true;
+        this._updateToggle();
+    },
+    // pause() et unpause() interrompent temporairement la boucle, en gardant la position
+    pause: function () {
+        this.paused = true;
+        this._updateToggle();
+        this._updateAriaLiveElement();
+    },
+    unpause: function () {
+        this.paused = false;
+        this.softPaused = false;
+        this._updateToggle();
+        this._updateAriaLiveElement();
+    },
+    // Les méthodes soft se produisent quand on passe sur le carousel avec la souris.
+    // L'idée est de permettre aux personnes de lire une citation.
+    // Elles ne changent pas réellement l'état de l'autoplayer, 
+    // mais elles l'arrêtent temporairement.
+    softPause: function () {
+        this.softPaused = true;
+    },
+    softUnpause: function () {
+        this.softPaused = false;
+    },
+    _loop: function () {
+        if (!this.enabled) { return }
+        var now = Date.now();
+        if (!this.paused && !this.softPaused) {
+            this.elapsedSinceLastTrigger += now - this.lastLoopAt;
+            this.progression = this.elapsedSinceLastTrigger / this.interval;
+            this._dispatchProgression();
+        }
+        if (this.elapsedSinceLastTrigger > this.interval) {
+            this._dispatchTrigger();
+            this._resetLoopValues();
+            this._dispatchProgression();
+        }
+        // Mémoire du last loop pour la sortie de pause
+        this.lastLoopAt = now;
+        window.requestAnimationFrame(this._loop.bind(this));
+    },
+    _resetLoopValues: function () {
+        // Progression de 0 à 1, vers le prochain déclenchement
+        this.progression = 0;
+        // Temps écoulé depuis le dernier déclenchement
+        this.elapsedSinceLastTrigger = 0;
+        // Date de la dernière boucle
+        this.lastLoopAt = Date.now();
+    },
+    _dispatchTrigger: function () {
+        this._dispatchEvent("autoplayerTrigger");
+    },
+    _dispatchProgression(){
+        this._dispatchEvent("autoplayerProgression", this.progression);
+    },
+    _updateToggle: function () {
+        if (!this.element) { return }
+        this.classList.remove(window.osuny.carousel.classes.autoplayerPlaying);
+        this.classList.remove(window.osuny.carousel.classes.autoplayerPaused);
+        if (this.paused) {
+            this.classList.add(window.osuny.carousel.classes.autoplayerPaused);
+        } else {
+            this.classList.add(window.osuny.carousel.classes.autoplayerPlaying);
+        }
+        this.icons.play.setAttribute('aria-hidden', !this.paused);
+        this.icons.pause.setAttribute('aria-hidden', this.paused);
+    },
+    _updateAriaLiveElement: function() {
+        if (!this.ariaLiveElement) { return }
+        var state = this.paused ? "off" : "polite";
+        this.ariaLiveElement.setAttribute("aria-live", state);
+    },
+    _onClick: function () {
+        if (this.paused) {
+            this.unpause();
+            this.enable();
+        } else {
+            this.pause();
+        }
+    }
+}
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/carousel.js b/assets/js/theme/components/carousel/carousel.js
new file mode 100644
index 0000000000000000000000000000000000000000..9b931f74e86a7149a5b97624508647d1ac83004f
--- /dev/null
+++ b/assets/js/theme/components/carousel/carousel.js
@@ -0,0 +1,178 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.Carousel = function (element) {
+    this.element = element;
+    this.slides = {
+        current: 0,
+        total: 0
+    }
+    this.state = {
+        initialized: false,
+        visible: false,
+        hasMouseOver: false
+    };
+    this.windowResizeTimeout;
+    this.lastScrollXTimeout;
+    this._findElement = window.osuny.carousel.utils.findElement.bind(this);
+    this._initializeConfig();
+    this._initializeSlider();
+    this._initializePagination();
+    this._initializeArrows();
+    this._initializeAutoplayer();
+    this._initializeMouseEvents();
+    this.showSlide(0);
+    this.state.initialized = true;
+}
+window.osuny.carousel.Carousel.prototype = {
+    next: function () {
+        var index = this.slides.current + 1;
+        if (index >= this.slides.total) {
+            index = 0;
+        }
+        this.showSlide(index);
+        this.arrows.next.focus();
+    },
+    previous: function () {
+        var index = this.slides.current - 1;
+        if (index < 0) {
+            // -1 parce que 0-indexed 
+            index = this.slides.total - 1;
+        }
+        this.showSlide(index);
+        this.arrows.previous.focus();
+    },
+    showSlide: function (index) {
+        this.slides.current = index;
+        this.pagination.unselectAllButtons();
+        this.pagination.selectButton(index);
+        this.slider.showSlide(index);
+        this.arrows.update(this.slides.current, this.slides.total);
+    },
+    pause: function () {
+        this.autoplayer.pause();
+    },
+    unpause: function () {
+        this.autoplayer.unpause();
+    },
+    resize: function () {
+        clearTimeout(this.windowResizeTimeout);
+        this.windowResizeTimeout = setTimeout(function () {
+            this.slider.recompute();
+        }.bind(this), 200);
+    },
+    isInViewPort: function(){
+        var boundingRect = this.element.getBoundingClientRect(),
+            screenHeight = window.innerHeight || document.documentElement.clientHeight,
+            elementBottomInViewport = boundingRect.bottom >= 0,
+            elementTopInViewport = boundingRect.top <= screenHeight;
+        return elementBottomInViewport || elementTopInViewport;
+    },
+    getCenterPositionY: function () {
+        var boundingRect = this.element.getBoundingClientRect();
+        return boundingRect.top + boundingRect.height / 2;
+    },
+    _initializeConfig: function () {
+        this.config = new window.osuny.carousel.Config(this);
+        // Les options sont chargées depuis le data-attribute "data-carousel"
+        this.config.loadOptions(this.element.dataset.carousel);
+    },
+    _initializePagination: function () {
+        var paginationElement = this._findElement("pagination");
+        this.pagination = new window.osuny.carousel.Pagination(paginationElement);
+        if (paginationElement) {
+            paginationElement.addEventListener(
+                window.osuny.carousel.events.paginationButtonClicked,
+                this._onPaginationButtonClicked.bind(this)
+            );
+        }
+    },
+    _initializeArrows: function  () {
+        var arrowsElement = this._findElement("arrows");
+        this.arrows = new window.osuny.carousel.Arrows(arrowsElement);
+        if (arrowsElement) {
+            arrowsElement.addEventListener(
+                window.osuny.carousel.events.arrowsNext,
+                this.next.bind(this)
+            );
+            arrowsElement.addEventListener(
+                window.osuny.carousel.events.arrowsPrevious,
+                this.previous.bind(this)
+            );
+        }
+    },
+    _initializeSlider: function () {
+        var sliderElement = this._findElement("slider");
+        this.slider = new window.osuny.carousel.Slider(sliderElement);
+        this.slides.total = this.slider.length();
+        sliderElement.addEventListener(
+            "scroll",
+            this._onSliderScroll.bind(this)
+        );
+    },
+    _initializeAutoplayer(){
+        this.autoplayerElement = this._findElement("autoplayerToggle");
+        this.autoplayer = new window.osuny.carousel.Autoplayer(this.autoplayerElement);
+        this.autoplayer.setInterval(this.config.autoplayinterval);
+        this.autoplayer.ariaLiveElement = this._findElement("container");
+        if (this.autoplayerElement) {
+            this.autoplayerElement.addEventListener(
+                window.osuny.carousel.events.autoplayerTrigger,
+                this.next.bind(this)
+            );
+            this.autoplayerElement.addEventListener(
+                window.osuny.carousel.events.autoplayerProgression,
+                this._onAutoplayerProgression.bind(this)
+            );
+            if (this.config.autoplay) {
+                this.autoplayer.enable();
+            }
+        }
+    },
+    _pointerStart: function() {
+        this.autoplayer.softPause();
+        this.state.hasMouseOver = true;
+    },
+    _pointerEnd: function() {
+        this.autoplayer.softUnpause();
+        this.state.hasMouseOver = false;
+    },
+    _initializeMouseEvents: function(){
+        this.element.addEventListener(
+            "mouseenter", this._pointerStart.bind(this)
+        );
+        this.element.addEventListener(
+            "touchstart", this._pointerStart.bind(this)
+        );
+        this.element.addEventListener(
+            "mouseleave", this._pointerEnd.bind(this)
+        );
+        this.element.addEventListener(
+            "touchend", this._pointerEnd.bind(this)
+        );
+    },
+    
+    _onAutoplayerProgression: function (event) {
+        this.pagination.setProgression(event.value);
+    },
+    _onPaginationButtonClicked: function (event) {
+        this.autoplayer.disable();
+        this.showSlide(event.index);
+    },
+    _onSliderScroll: function () {
+        this.autoplayer.softPause();
+        clearTimeout(this.lastScrollXTimeout);
+        this.lastScrollXTimeout = setTimeout(function () {
+            if(!this.state.hasMouseOver){
+                this.autoplayer.softUnpause();
+            }
+            this._onSliderScrollend();
+        }.bind(this), 100);
+    },
+    _onSliderScrollend: function () {
+        var index = this.slider.currentSlideIndex();
+        if (this.slides.current != index) {
+            this.showSlide(index);
+        }
+    }
+}
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/classes.js b/assets/js/theme/components/carousel/classes.js
new file mode 100644
index 0000000000000000000000000000000000000000..351e7aad6007ac26a215d8171066ce8439686ed8
--- /dev/null
+++ b/assets/js/theme/components/carousel/classes.js
@@ -0,0 +1,24 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.classes = {
+    arrows: "carousel__arrows",
+    arrowsCounter: "counter",
+    arrowsNext: "arrow-next",
+    arrowsPrevious: "arrow-prev",
+    autoplayerPaused: "toggle__paused",
+    autoplayerPlaying: "toggle__playing",
+    autoplayerToggle: "toggle",
+    autoplayerToggleIconPause: "pause",
+    autoplayerToggleIconPlay: "play",
+    carousel: "js-carousel",
+    container: "carousel__container",
+    pagination: "carousel__pagination__tabcontainer",
+    paginationPage: "carousel__pagination__page",
+    slider: "carousel__slider",
+    slideIsBefore: "is-before",
+    slideIsPrevious: "is-previous",
+    slideIsCurrent: "is-current",
+    slideIsNext: "is-next",
+    slideIsAfter: "is-after",
+}
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/config.js b/assets/js/theme/components/carousel/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..c411931a0dcc69274b0feec338e794eba426a4f5
--- /dev/null
+++ b/assets/js/theme/components/carousel/config.js
@@ -0,0 +1,37 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.Config = function (instance) {
+    this.instance = instance;
+
+    // Valeurs par défaut et documentation des propriétés
+    // L'autoplay est-il activé ou pas ?
+    this.autoplay = false;
+
+    // Pagination sous forme de tabulation 
+    this.pagination = true;
+
+     // Controle du defilement avec les fleches
+    this.arrows = false;
+
+    // Durée d'affichage d'un slide en cas d'autoplay
+    this.autoplayinterval = 3000;
+}
+
+window.osuny.carousel.Config.prototype = {
+    valuesInOptions: [
+        "autoplay",
+        "arrows",
+        "pagination",
+        "autoplayinterval"
+    ],
+    loadOptions: function (data) {
+        var options = JSON.parse(data);
+        for (var i = 0; i <= this.valuesInOptions.length; i += 1) {
+            var value = this.valuesInOptions[i];
+            if (options[value] !== undefined) {
+                this[value] = options[value];
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/events.js b/assets/js/theme/components/carousel/events.js
new file mode 100644
index 0000000000000000000000000000000000000000..c73b1c3e656f0ea82a4e5f3096f3425d4f8e9ceb
--- /dev/null
+++ b/assets/js/theme/components/carousel/events.js
@@ -0,0 +1,10 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.events = {
+    autoplayerProgression: "osuny.carousel.autoplayer.progression",
+    autoplayerTrigger: "osuny.carousel.autoplayer.trigger",
+    arrowsNext: "osuny.carousel.arrows.next",
+    arrowsPrevious: "osuny.carousel.arrows.previous",
+    paginationButtonClicked: "osuny.carousel.pagination.buttonClicked"
+} 
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/manager.js b/assets/js/theme/components/carousel/manager.js
new file mode 100644
index 0000000000000000000000000000000000000000..9390834ab1e28573979d9894d44a81ae925f6bc3
--- /dev/null
+++ b/assets/js/theme/components/carousel/manager.js
@@ -0,0 +1,95 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.manager = {
+    initialized: false,
+    elements: [],
+    carousels: [],
+    focusedCarousel: null,
+    carouselsInViewport: [],
+    windowCenterY: null,
+    initialize: function () {
+        if (!this.initialized) {
+            this._createCarousels();
+            this._computeWindowCenterY();
+            this._initializeListeners();
+            this._findCarouselsInViewport();
+            this.initialized = true;
+        }
+    },
+    _createCarousels: function () {
+        this.elements = document.getElementsByClassName(window.osuny.carousel.classes.carousel);
+        for (var i = 0; i < this.elements.length; i += 1) {
+            var element = this.elements[i],
+                carousel = new window.osuny.carousel.Carousel(element);
+            this.carousels.push(carousel);
+        }
+    },
+    _initializeListeners: function () {
+        window.addEventListener(
+            "resize",
+            this._resize.bind(this)
+        );
+        window.addEventListener(
+            "scroll",
+            this._findCarouselsInViewport.bind(this)
+        );
+        window.addEventListener(
+            "keydown",
+            this._onKeyPress.bind(this)
+        );
+    },
+    _resize: function () {
+        this._computeWindowCenterY();
+        this.carousels.forEach(function (carousel) {
+            carousel.resize();
+        });
+    },
+    _computeWindowCenterY: function(){
+        this.windowCenterY = (window.innerHeight || document.documentElement.clientHeight) / 2;
+    },
+    _findCarouselsInViewport: function () {
+        this.carouselsInViewport = [];
+        for (var i = 0; i < this.carousels.length; i += 1) {
+            var carousel = this.carousels[i];
+            if (carousel.isInViewPort()) {
+                carousel.unpause();
+                this.carouselsInViewport.push(carousel);
+            } else {
+                carousel.pause();
+            }
+        };
+        this.focusedCarousel = this._findBestCarouselFocusCandidate();
+    },
+    _findBestCarouselFocusCandidate: function () {
+        // On démarre avec la plus grande distance possible
+        var distance = window.innerHeight,
+            bestCandidate = null;
+        for (var i = 0; i < this.carousels.length; i += 1) {
+            var carousel = this.carousels[i];
+            var currentDistanceToCenter = Math.abs(carousel.getCenterPositionY() - this.windowCenterY);
+            if (currentDistanceToCenter < distance) {
+                distance = currentDistanceToCenter;
+                bestCandidate = carousel;
+            }
+        };
+        return bestCandidate;
+    },
+    _onKeyPress: function (e) {
+        if (this.focusedCarousel) {
+            if (e.key == 'ArrowLeft') { this.focusedCarousel.previous() }
+            else if (e.key == 'ArrowRight') { this.focusedCarousel.next() }
+        }
+    },
+    invoke: function () {
+        "use strict";
+        return {
+            initialize: this.initialize.bind(this),
+            carousels: this.carousels,
+        };
+    }
+}.invoke();
+
+window.addEventListener("load", function () {
+    window.osuny.carousel.manager.initialize();
+});
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/pagination.js b/assets/js/theme/components/carousel/pagination.js
new file mode 100644
index 0000000000000000000000000000000000000000..99386d8ad4934be904d84de9c3fd09fdc7cba567
--- /dev/null
+++ b/assets/js/theme/components/carousel/pagination.js
@@ -0,0 +1,33 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.Pagination = function (element) {
+    this.element = element;
+    this.buttons = [];
+    if (!this.element) { return };
+    this.buttonElements = this.element.getElementsByClassName(window.osuny.carousel.classes.paginationPage);
+    for (var index = 0; index < this.buttonElements.length; index += 1) {
+        var buttonElement = this.buttonElements[index],
+            button = new window.osuny.carousel.PaginationButton(buttonElement, index, this.element);
+        this.buttons.push(button);
+    }
+    this.currentButton = this.buttons[0];
+}
+window.osuny.carousel.Pagination.prototype = {
+    selectButton: function (index) {
+        if (this.element) {
+            this.currentButton = this.buttons[index];
+            this.currentButton.select();
+        }
+    },
+    setProgression: function (progression) {
+        if (this.currentButton) {
+            this.currentButton.setProgression(progression);
+        }
+    },
+    unselectAllButtons: function () {
+        this.buttons.forEach(function (button) {
+            button.unselect();
+        });
+    }
+}
diff --git a/assets/js/theme/components/carousel/paginationButton.js b/assets/js/theme/components/carousel/paginationButton.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0b60b3d08b56c1e3e44c80b5299e3c97ad62d7d
--- /dev/null
+++ b/assets/js/theme/components/carousel/paginationButton.js
@@ -0,0 +1,41 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.PaginationButton = function PaginationButton(element, index, pagination) {
+    this.element = element;
+    this.index = index;
+    this.pagination = pagination;
+    this.progressBar = this.element.querySelector("i");
+    this._setAria();
+    this.setProgression(0);
+    this.element.addEventListener(
+        "click",
+        this._onClick.bind(this)
+    );
+}
+
+window.osuny.carousel.PaginationButton.prototype = {
+    setProgression: function (progression) {
+        this.progression = progression;
+        var percent = String(this.progression * 100) + "%";
+        this.progressBar.style.setProperty("width", percent);
+    },
+    select: function () {
+        this.setProgression(1);
+        this.element.setAttribute("aria-selected", "true");
+    },
+    unselect: function () {
+        this.setProgression(0);
+        this.element.setAttribute("aria-selected", "false");
+    },
+    _setAria: function () {
+        var ariaLabel = this.element.getAttribute("aria-label"),
+            ariaNewLabel = ariaLabel.replace("%s", this.index);
+        this.element.setAttribute("aria-label", ariaNewLabel);
+    },
+    _onClick: function () {
+        var event = new Event(window.osuny.carousel.events.paginationButtonClicked);
+        event.index = this.index;
+        this.pagination.dispatchEvent(event);
+    }
+}
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/slide.js b/assets/js/theme/components/carousel/slide.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce6a29406bd33b940541c741905ca6307ac4b26a
--- /dev/null
+++ b/assets/js/theme/components/carousel/slide.js
@@ -0,0 +1,50 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.Slide = function (slider, container, index) {
+    this.slider = slider;
+    this.container = container;
+    this.index = index;
+    this.classList = this.container.classList;
+    this.computedStyle = null;
+    this.width = 0;
+    this.computeWidth();
+}
+
+window.osuny.carousel.Slide.prototype = {
+    computeWidth: function () {
+        this.computedStyle = getComputedStyle(this.container);
+        this.width =  this.container.offsetWidth + 
+                        parseFloat(this.computedStyle.marginLeft) + 
+                        parseFloat(this.computedStyle.marginRight);
+    },
+    setClasses() {
+        this._setState(this._isBefore(), window.osuny.carousel.classes.slideIsBefore);
+        this._setState(this._isPrevious(), window.osuny.carousel.classes.slideIsPrevious);
+        this._setState(this._isCurrent(), window.osuny.carousel.classes.slideIsCurrent);
+        this._setState(this._isNext(), window.osuny.carousel.classes.slideIsNext);
+        this._setState(this._isAfter(), window.osuny.carousel.classes.slideIsAfter);
+    },
+    _isBefore: function () {
+        return this.index < this.slider.index;
+    },
+    _isPrevious: function () {
+        return this.index == this.slider.index - 1;
+    },
+    _isCurrent: function () {
+        return this.index == this.slider.index;
+    },
+    _isNext: function () {
+        return this.index == this.slider.index + 1;
+    },
+    _isAfter: function  () {
+        return this.index > this.slider.index;
+    },
+    _setState: function (active, className) {
+        if (active) {
+            this.classList.add(className);
+        } else {
+            this.classList.remove(className);
+        }
+    }
+}
diff --git a/assets/js/theme/components/carousel/slider.js b/assets/js/theme/components/carousel/slider.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ecca91227d484ac8b6e7e880b4de57f6445348c
--- /dev/null
+++ b/assets/js/theme/components/carousel/slider.js
@@ -0,0 +1,64 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.Slider = function Slider(element) {
+    this.element = element;
+    this._findElement = window.osuny.carousel.utils.findElement.bind(this),
+    this.container = this._findElement("container");
+    this.index = 0;
+    this.slides = [];
+    this.deltaPosition = 0;
+    this.drag = null;
+    var slidesContainers = this.container.children;
+    for (var i = 0; i < slidesContainers.length; i += 1) {
+        var slideContainer = slidesContainers.item(i);
+        this.slides.push(new window.osuny.carousel.Slide(this, slideContainer, i));
+    }
+    this.showSlide(this.index);
+}
+window.osuny.carousel.Slider.prototype = {
+    showSlide: function (index) {
+        this.index = index;
+        var behavior =  "smooth";
+        this.element.scrollTo({
+            top: 0,
+            left: this._slidePosition(index),
+            behavior: behavior
+        });
+        this._updateSlidesClasses();
+    },
+    recompute: function(){
+        this.slides.forEach(function(slide) {
+            slide.computeWidth();
+        });
+        this.showSlide(this.index);
+    },
+    length: function () {
+        return this.slides.length;
+    },
+    currentSlideIndex: function () {
+        var currentWidth = 0;
+        // Le seuil permet d'éviter des erreurs d'arrondis qui causent un retour en slide 1, par étapes
+        var threshold = 20;
+        for (var index = 0; index < this.slides.length; index += 1) {
+            var slide = this.slides[index];
+            currentWidth += slide.width;
+            if (currentWidth > this.element.scrollLeft + threshold) {
+                return index;
+            }
+        }
+        return 0;
+    },
+    _slidePosition: function (index) {
+        var position = 0;
+        for (var i = 0; i < index; i += 1) {
+            position += this.slides[i].width;
+        }
+        return position;
+    },
+    _updateSlidesClasses: function () {
+        this.slides.forEach(function(slide) {
+            slide.setClasses();
+        });
+    }
+}
\ No newline at end of file
diff --git a/assets/js/theme/components/carousel/utils.js b/assets/js/theme/components/carousel/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..73ea31ed66b9e757a50e73ddcf0db519956c03dd
--- /dev/null
+++ b/assets/js/theme/components/carousel/utils.js
@@ -0,0 +1,16 @@
+window.osuny = window.osuny || {};
+window.osuny.carousel = window.osuny.carousel || {};
+
+window.osuny.carousel.utils = {
+    // Méhodes ajoutées comme des traits (décorateur) aux objets qui en ont besoin
+    findElement: function(classKey) {
+        var className = window.osuny.carousel.classes[classKey];
+        return this.element.getElementsByClassName(className).item(0);
+    },
+    dispatchEvent: function (eventKey, value = null) {
+        var eventName = window.osuny.carousel.events[eventKey];
+        var event = new Event(eventName);
+        event.value = value;
+        this.element.dispatchEvent(event);
+    }
+}
\ No newline at end of file
diff --git a/assets/js/theme/index.js b/assets/js/theme/index.js
index 949ab10918fcb92eec7c9d7c3d1b3d7e4886355a..3cc191d2542d24b310f90336957dca8cfdd9e3b7 100644
--- a/assets/js/theme/index.js
+++ b/assets/js/theme/index.js
@@ -10,5 +10,8 @@ import './design-system/toc';
 import './blocks/keyFigures';
 import './blocks/organizations';
 import './blocks/draggableBlocks.js';
+import './blocks/timeline.js';
 import './blocks/videos.js';
-import './blocks/campus.js';
\ No newline at end of file
+import './blocks/campus.js';
+import './utils/utils.js';
+import './components/carousel.js';
\ No newline at end of file
diff --git a/assets/js/theme/utils/utils.js b/assets/js/theme/utils/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..a87aee1e7d4ecd4b4406c4fe6ed49f58e264b7cd
--- /dev/null
+++ b/assets/js/theme/utils/utils.js
@@ -0,0 +1,9 @@
+window.osuny = window.osuny || {};
+window.osuny.utils = window.osuny.utils || {};
+window.osuny.utils.instanciateIf = function (scope, model, condition) {
+    if (condition) {
+        return new model(scope);
+    } else {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/assets/sass/_theme/blocks/gallery.sass b/assets/sass/_theme/blocks/gallery.sass
index d85b11d19535fd1bc70fc58a6fb609c6f4b834e3..c66c4b95c0354b1f9df87ad846451284399cca3c 100644
--- a/assets/sass/_theme/blocks/gallery.sass
+++ b/assets/sass/_theme/blocks/gallery.sass
@@ -81,37 +81,14 @@
             background: $block-gallery-carousel-background
             padding-bottom: var(--grid-gutter)
             padding-top: var(--grid-gutter)
+        .container
+            padding-right: var(--grid-gutter-negative) !important
         .block-gallery + &,
         .block-pages--cards + &
             margin-top: 0
-        .splide
-            display: flex
-            flex-direction: column
-            @include in-page-with-sidebar
-                &.is-moving
-                    .splide__slide.is-active
-                        opacity: 0.1
-            &__track
-                overflow: visible
-                margin-right: var(--grid-gutter-negative)
-                @include in-page-with-sidebar
-                    .splide__slide
-                        transition: opacity  .3s ease
-                        opacity: 0.1
-                        &.is-next
-                            opacity: 0.6
-                        &.is-active
-                            opacity: 1
+        .carousel
             &__slide
-                flex-shrink: initial
-                &:last-child
-                    padding-right: 20%
-            figure
-                margin-right: $spacing-3
-                @include media-breakpoint-down(desktop)
-                    display: flex
-                    flex-direction: column
-                    justify-content: end
+                margin-right: calc(var(--grid-gutter) / 2)
                 picture
                     img
                         // FIXME Arnaud: I would like images at constant height, can't manage to get it right.
@@ -123,36 +100,5 @@
                             height: $block-gallery-carousel-max-height
                             width: auto
                             max-width: none
-
-            &__arrows
-                margin-left: pxToRem(-18)
-                order: 2
-                @media (min-height: 800px)
-                    padding-top: space(10)
-            &__arrow
-                &:disabled
-                    cursor: default
-                    opacity: 0.3
-                &--prev,
-                &--next
-                    @include button-reset
-                    @include icon(arrow-left-line, before)
-                    height: $spacing-4
-                    padding: 0
-                    position: static
-                    width: $spacing-4
-                    svg
-                        display: none
-                &--next
-                    @include icon(arrow-right-line, before)
-
-        @include in-page-without-sidebar
-            .splide
-                figure
-                    margin-left: var(--grid-gutter)
-                    margin-right: -$spacing-3
-                &__slide
-                    &:first-child
-                        margin-left: var(--grid-gutter)
-                &__track
-                    margin-left: var(--grid-gutter-negative)
\ No newline at end of file
+                @include in-page-without-sidebar
+                    margin-right: var(--grid-gutter)
diff --git a/assets/sass/_theme/blocks/posts.sass b/assets/sass/_theme/blocks/posts.sass
index 69cbcc2563f6a5b076250d655f4c19c02a767424..693446361b8af8b16dcbe9fd1625317277672c54 100644
--- a/assets/sass/_theme/blocks/posts.sass
+++ b/assets/sass/_theme/blocks/posts.sass
@@ -278,44 +278,35 @@
                     margin-top: $spacing-5
 
     &--carousel
-        @include draggable-block
-        .container
-            padding-right: 0
         .carousel
             padding-bottom: $spacing-3
-            &:hover
-                cursor: grab
-            &.is-grabbing
-                cursor: grabbing
-            li
+            &__slider
+                margin-left: calc(var(--grid-gutter-negative) + calc(var(--grid-gutter) / 2))
+            &__slide
                 list-style: none
-            .posts
-                display: flex
-                gap: unset
+                word-break: break-word
                 .post
                     margin: 0 calc(var(--grid-gutter) / 2)
-            .actions-arrows
+                &.is-before
+                    opacity: 0.3
+                @include in-page-with-sidebar
+                    .post
+                        width: columns(3)
+                        .post-title
+                            @include h4
+            @include media-breakpoint-down(desktop)
+                &__slider
+                    margin-left: var(--grid-gutter-negative)
+                    width: var(--grid-width)
+                &__slide
+                    .post
+                        margin-left: var(--grid-gutter)
+                        margin-right: 0
+                        width: columns(10)
+            &__arrows
                 justify-content: space-between
-        @include media-breakpoint-down(desktop)
-            .carousel
-                gap: half(var(--grid-gutter))
-                .post
-                    width: columns(10)
-                .grab-item:last-of-type
-                    margin-right: half(var(--grid-gutter))
-                .actions-arrows
-                    margin-right: var(--grid-gutter)
-        @include media-breakpoint-up(desktop)
-            .next
-                margin-right: pxToRem(-27) // Marge négative pour aligner correctement le picto à la colonne
-        @include in-page-with-sidebar
-            .post
-                width: columns(3)
-                .post-title
-                    @include h4
-            .carousel
-                .actions-arrows
-                    width: offset(6)
+                .counter
+                    display: none
         @include in-page-without-sidebar
             .block-content
                 display: flex
@@ -326,9 +317,6 @@
                     width: columns(9)
                 .post
                     width: columns(4)
-            .carousel
-                .actions-arrows
-                    width: offset(8)
 
 // Move this part to blocks/categories when categories block is ready
 .block-posts
diff --git a/assets/sass/_theme/blocks/testimonials.sass b/assets/sass/_theme/blocks/testimonials.sass
index 9b884e5a1de25a5a5348c99639955dcf6df3a8e0..f3edc02ff921ce3b7fb59f3c19881232b229f354 100644
--- a/assets/sass/_theme/blocks/testimonials.sass
+++ b/assets/sass/_theme/blocks/testimonials.sass
@@ -28,88 +28,42 @@
             display: block
     .avatar
         flex-shrink: 0
-        width: columns(1)
-        min-width: pxToRem(80)
-        margin-right: $spacing-2
         margin-bottom: 0
-
-    .splide
-        .splide__slider
-            display: flex
-            flex-direction: column-reverse
-        &__toggle
-            border: 1px solid $block-testimonials-pagination-background
-            border-radius: 50%
-            cursor: pointer
-            font-size: 0
-            height: pxToRem(42)
-            padding: 0
-            position: absolute
-            right: 0
-            top: 0
-            width: pxToRem(42)
-            font-size: 0
-            line-height: 1
-            span
-                color: var(--color-accent)
-                &::before
-                    font-size: pxToRem(22)
-                    height: pxToRem(42)
-                    width: pxToRem(42)
-                    margin-left: -0.5px
-                    margin-top: -1.5px
-        .splide__pagination
-            .is-active i
-                width: 100%
-
-            .splide__play
-                &::before
-                    margin-left: 2px
-        &__pagination
-            justify-content: unset
-            margin-right: 55px
-            li
-                flex: 1
-                margin-right: 10px
-            button
-                @include button-reset
-                position: relative
-                width: 100%
-                &::before,
-                i
-                    height: 1px
-                    left: 0
-                    top: 50%
-                    position: absolute
-                &::before
-                    background-color: $block-testimonials-pagination-background
-                    border-radius: 0
-                    width: 100%
-                i
-                    background-color: $block-testimonials-pagination-progress-background
-                    width: 0
+        margin-right: $spacing-2
+        min-width: pxToRem(80)
+        width: columns(1)
 
     @include in-page-without-sidebar
-        .top
-            padding-left: offset(3)
-        .splide__pagination
-            padding-left: offset(3)
-            padding-right: offset(1)
-        .splide__autoplay
-            margin-right: offset(1)
         figure
-            padding-right: offset(3)
             min-height: columns(2)
+            padding-right: offset(3)
             &.with-picture
-                padding-right: offset(1)
                 padding-left: offset(3)
+                padding-right: offset(1)
                 position: relative
                 figcaption
                     display: block
                     margin-top: $spacing-2
                     .avatar
-                        position: absolute
                         left: columns(1)
-                        top: 0
                         margin-left: var(--grid-gutter)
+                        position: absolute
+                        top: 0
                         width: columns(2)
+    .carousel
+        flex-direction: column-reverse
+        .toggle
+            border: 1px solid $block-testimonials-pagination-background
+        &__pagination
+            order: 1
+            button
+                &::before
+                    background-color: $block-testimonials-pagination-background
+                i
+                    background-color: $block-testimonials-pagination-progress-background
+        &__slide
+            width: columns(12)
+            opacity: 1
+            padding: 0 $spacing-1
+            @include in-page-with-sidebar
+                width: columns(8)
diff --git a/assets/sass/_theme/blocks/timeline.sass b/assets/sass/_theme/blocks/timeline.sass
index cb1be7fde5adc3a16af27eeae3d81ab2de5a9804..07b2aab888349c3da26b44a41016762b50add489 100644
--- a/assets/sass/_theme/blocks/timeline.sass
+++ b/assets/sass/_theme/blocks/timeline.sass
@@ -69,6 +69,8 @@
         padding-top: var(--block-space-y)
         &::before
             display: none
+        .carousel__slider
+            margin-left: calc(var(--grid-gutter-negative) + calc(var(--grid-gutter) / 2))
         .timeline-event
             padding: 0 calc(var(--grid-gutter) / 2)
             width: columns(4)
@@ -106,6 +108,9 @@
                     width: calc(100% + var(--grid-gutter))
 
         @include media-breakpoint-down(desktop)
+            .carousel__slider
+                margin-left: calc(var(--grid-gutter-negative))
+                width: calc(var(--grid-width) + var(--grid-gutter) * 2)
             .timeline-events
                 position: relative
             .actions-arrows
@@ -115,9 +120,14 @@
             .timeline-event
                 margin-right: 0
                 padding-right: 0
-                width: 75%
+                padding-left: var(--grid-gutter)
+                width: columns(12)
                 .line
                     margin-bottom: $spacing-5
+                    width: calc(100% + var(--grid-gutter))
+                    &::before
+                        width: 8px
+                        height: 8px
 
         @include in-page-without-sidebar
             @include media-breakpoint-up(xxl)
@@ -126,4 +136,4 @@
 
         @include in-page-with-or-without-sidebar
             .block-title
-                @include h5
+                @include h5
\ No newline at end of file
diff --git a/assets/sass/_theme/components/carousel.sass b/assets/sass/_theme/components/carousel.sass
new file mode 100644
index 0000000000000000000000000000000000000000..361f1e267d4491d752f967dfa4ec98f8a8e804da
--- /dev/null
+++ b/assets/sass/_theme/components/carousel.sass
@@ -0,0 +1,163 @@
+.carousel
+    display: flex
+    flex-direction: column
+    position: relative
+    &__slider
+        overflow: scroll
+        scroll-snap-type: x mandatory
+        scrollbar-width: none
+        &::-webkit-scrollbar
+            display: none
+    &__container
+        // Si l'image est verticale, ça fonctionne tant qu'elle fait plus de 20vw
+        padding-right: 80vw
+        display: flex
+        justify-content: flex-start
+        flex-direction: row
+        left: 0px
+        position: relative
+        top: 0
+        width: fit-content
+        justify-content: flex-start
+
+    &__slide
+        scroll-snap-align: start
+        .block-timeline &.is-before
+            opacity: 0.3
+
+    button
+        appearance: none
+        background: transparent
+        border: 0
+
+    &__arrows
+        display: flex
+        flex-direction: row
+        align-items: center
+        margin-left: - $spacing-3 
+        margin-right: - $spacing-3 
+        @include media-breakpoint-up(desktop)
+            padding-top: $spacing-3
+        .counter
+            min-width: $spacing-3
+            text-align: center
+            width: fit-content
+            @include meta
+            .block-timeline &
+                display: none
+            &:empty
+                display: block
+        button
+            color: var(--color-text)
+            svg
+                display: none
+            &--prev
+                @include icon(arrow-left-line)
+                right: 100%
+            &--next
+                @include icon(arrow-right-line)
+                left: 100%
+
+            &:disabled
+                cursor: default
+                opacity: 0.3
+            &.arrow-prev,
+            &.arrow-next
+                @include button-reset
+                @include icon(arrow-left-line, before)
+                padding: $spacing-3
+                position: static
+                svg
+                    display: none
+            &.arrow-next
+                @include icon(arrow-right-line, before)
+
+    &__pagination
+        &__tabcontainer
+            display: flex
+            list-style: none
+            margin: 0
+            padding: 0
+            &.has_toggle
+                margin: 0 50px 0 0
+            li
+                display: block
+                flex: 1
+                list-style: none
+                margin: 0
+                margin-right: 10px
+                padding: 0
+            button
+                display: block
+                height: 48px
+                padding: 0
+                position: relative
+                width: 100%
+                cursor: pointer
+                &::before
+                    -moz-transition: background .3s ease
+                    -o-transition: background .3s ease
+                    background-color: alphaColor($link-color, 0.3)
+                    border-radius: 0
+                    content: ""
+                    display: block
+                    height: 1px
+                    left: 0
+                    margin: auto
+                    position: absolute
+                    top: 50%
+                    transition: background 0.3s ease
+                    width: 100%
+                &::before,
+                i
+                    height: 1px
+                    left: 0
+                    position: absolute
+                    top: 50%
+                i
+                    background-color: alphaColor($link-color, 0.3)
+                    width: 100%
+
+                &:hover
+                    &::before
+                        background-color: alphaColor($link-color, 0.6)
+                &.is-active
+                    &::before
+                        background-color: $link-color
+        .toggle
+            border-radius: 50%
+            border: 0
+            cursor: pointer
+            font-size: 0
+            height: pxToRem(42)
+            line-height: 1
+            padding: 0
+            position: absolute
+            right: 0
+            top: 0
+            width: pxToRem(42)
+            span
+                color: var(--color-accent)
+                &::before
+                    font-size: pxToRem(22)
+                    height: pxToRem(42)
+                    width: pxToRem(42)
+                    margin-top: -1.5px
+                &.play
+                    @include icon-block(play-fill)
+                    margin-left: -0.5px
+                &.pause
+                    @include icon-block(pause-line)
+                    margin-left: -1.5px
+            &__paused
+                span
+                    &.play
+                        display: block
+                    &.pause
+                        display: none
+            &__playing
+                span
+                    &.play
+                        display: none
+                    &.pause
+                        display: block
diff --git a/assets/sass/_theme/dependencies/splide.sass b/assets/sass/_theme/dependencies/splide.sass
deleted file mode 100644
index 1a779b37e309bcfa4d01cfea2475ca01a8e567db..0000000000000000000000000000000000000000
--- a/assets/sass/_theme/dependencies/splide.sass
+++ /dev/null
@@ -1,52 +0,0 @@
-.splide
-    button
-        appearance: none
-        background: transparent
-        border: 0
-    &__arrow
-        color: var(--color-text)
-        position: absolute
-        top: 50%
-        svg
-            display: none
-        &--prev
-            @include icon(arrow-left-line)
-            right: 100%
-        &--next
-            @include icon(arrow-right-line)
-            left: 100%
-    &__pagination
-        display: flex
-        list-style: none
-        margin: 0
-        padding: 0
-        button
-            display: block
-            height: 48px
-            padding: 0
-            width: 48px
-            &::before
-                background-color: alphaColor($link-color, 0.3)
-                border-radius: 50%
-                content: ""
-                display: block
-                height: 10px
-                margin: auto
-                transition: background 0.3s ease
-                width: 10px
-            &:hover
-                &::before
-                    background-color: alphaColor($link-color, 0.6)
-            &.is-active
-                &::before
-                    background-color: $link-color
-    &__toggle
-        &__pause
-            @include icon-block(pause-line)
-        &__play
-            @include icon-block(play-fill)
-                padding-left: pxToRem(2)
-    &[data-slides-length="1"]
-        .splide
-            &__autoplay, &__pagination, &__arrow
-                display: none
diff --git a/assets/sass/_theme/hugo-osuny.sass b/assets/sass/_theme/hugo-osuny.sass
index 85c9fd8072e0087859a180d19969026288bc6c03..707b4fe42723d6deb42c8b743b5d4afe3b4f086e 100644
--- a/assets/sass/_theme/hugo-osuny.sass
+++ b/assets/sass/_theme/hugo-osuny.sass
@@ -13,9 +13,7 @@
 
 // Vendors
 @import glightbox/dist/css/glightbox
-@import @splidejs/splide/dist/css/splide-core.min
 @import dependencies/glightbox
-@import dependencies/splide
 
 // Design System
 @import design-system/layout
@@ -39,6 +37,9 @@
 @import design-system/table
 @import design-system/table_of_contents
 
+// Components
+@import components/carousel
+
 // Blocks
 @import blocks/base
 @import blocks/agenda
diff --git a/config.yaml b/config.yaml
index fdb8c248b20d59d4cc64529aa2e51cd98173a0a0..e041e5350ca70099eb2d9beeb7fc37130252642a 100644
--- a/config.yaml
+++ b/config.yaml
@@ -170,25 +170,30 @@ params:
   # BLOCKS
   blocks:
     gallery:
-      splide:
+      carousel:
         arrows: true
         pagination: false
-        autoWidth: true
         autoplay: false
     key_figures:
       animated: true
     pages:
       alternate:
         more: true
+    posts:
+      carousel:
+        arrows: true
+        pagination: false
+        autoplay: false
     testimonials:
-      splide:
-        arrows: false
+      carousel:
+        pagination: true
         autoplay: true
-        pauseOnHover: false
-        pauseOnFocus: true
-        type: loop
-        autoHeight: true
-        interval: 8000
+        autoplayinterval: 3000
+    timeline:
+      carousel:
+        arrows: true
+        pagination: false
+        autoplay: false
   image_sizes:
     design_system:
       lightbox:
diff --git a/i18n/en.yml b/i18n/en.yml
index bcc93e01d669e23bbe77b10edad3a56b1409132d..1ae8fbe7568d0e338c60e0f53ba6284b02aa1455 100644
--- a/i18n/en.yml
+++ b/i18n/en.yml
@@ -78,8 +78,8 @@ commons:
     last: Go to first slide
     next: Slide suivant
     pageX: Go to page %s
-    pause: Pause
-    play: Play
+    pause: Carousel playing. Pause the carousel
+    play: Carousel currently paused. Start the carousel
     prev: Slide précedent
     slideX: Go to slide %s
   click_to_copy:
diff --git a/i18n/fr.yml b/i18n/fr.yml
index d13d9b790fdc62f5ff34f385193e1d93ef408dc5..b3a750761ed94054fc445e415e1e6ab832ddca09 100644
--- a/i18n/fr.yml
+++ b/i18n/fr.yml
@@ -78,8 +78,8 @@ commons:
     last: Aller au dernier slide
     next: Aller à l'élément suivant
     pageX: Aller à la page %s
-    pause: Mettre en pause le carousel
-    play: Démarrer le carousel
+    pause: Carousel en cours de lecture. Mettre en pause le carousel
+    play: Carousel actuellement en pause. Démarrer le carousel
     prev: Aller à l'élément précédent
     slideX: Aller au slide %s
   click_to_copy:
diff --git a/layouts/partials/blocks/templates/carousel.html b/layouts/partials/blocks/templates/carousel.html
new file mode 100644
index 0000000000000000000000000000000000000000..c050056061d450840e59b416e8f21d567823c4de
--- /dev/null
+++ b/layouts/partials/blocks/templates/carousel.html
@@ -0,0 +1,56 @@
+{{ $partial := .partial }}
+{{ $heading_tag := .heading_tag | default 2 }}
+{{ $block_options := .block_options | default "" }}
+
+<div class="carousel js-carousel" aria-roledescription="carousel" data-carousel="{{ .options | encoding.Jsonify }}">
+  <div class="carousel__slider">
+    <div id="carousel-items" class="carousel__container" aria-live="off" aria-atomic="false">
+      {{ $slideRole := "group" }}
+      {{ if .options.pagination }}
+        {{ $slideRole = "tabpanel" }}
+      {{ end }}
+      {{ $totalSlides := len .content }}
+      {{ range $slideindex, $content := .content }}
+      {{ with $content }}
+        {{ partial $partial (dict
+          "is_carousel" true
+          "index" (add $slideindex 1)
+          "params" .
+          "role" $slideRole
+          "heading_tag" $heading_tag
+          "options" $block_options
+          "total" $totalSlides
+        ) }}
+        {{ end }}
+      {{ end }}
+    </div>
+  </div>
+  <div class="carousel__pagination">
+      {{ if .options.pagination }}
+      <ul class="carousel__pagination__tabcontainer {{ if .options.autoplay }} has_toggle {{ end }}" role="tablist">
+        {{ range $slideindex, $content := .content }}
+          <li>
+            <button id="carousel-tab-{{$slideindex}}" role="tab" aria-controls="carousel-item-{{$slideindex}}" aria-selected="false" class="carousel__pagination__page" type="button" aria-label='{{ safeHTML (i18n "commons.carousel.slideX") }}'>
+              <i></i>
+            </button>
+          </li>
+        {{ end }}
+      </ul>
+      {{ end }}
+      
+      {{ if .options.autoplay }}
+      <button class="toggle">
+        <span class="play" aria-label='{{ safeHTML (i18n "commons.carousel.play") }}'></span>
+        <span class="pause" aria-label='{{ safeHTML (i18n "commons.carousel.pause") }}'></span>
+      </button>
+      {{ end }}
+    </div>
+
+  {{ if .options.arrows }}
+    <div class="carousel__arrows">
+      <button class="arrow-prev" aria-controls="carousel-items" type="button" aria-label='{{ safeHTML (i18n "commons.carousel.prev") }}'> </button>
+      <p class="counter"></p>
+      <button class="arrow-next" aria-controls="carousel-items" type="button" aria-label='{{  safeHTML (i18n "commons.carousel.next") }}'></button>
+    </div>
+  {{ end }}
+</div>
\ No newline at end of file
diff --git a/layouts/partials/blocks/templates/gallery.html b/layouts/partials/blocks/templates/gallery.html
index c740d848bb53ceaf2d24840787b4850df71f3d4d..157b810555ede57f562c6d0d2ef26a87a0b82d7b 100644
--- a/layouts/partials/blocks/templates/gallery.html
+++ b/layouts/partials/blocks/templates/gallery.html
@@ -1,6 +1,6 @@
 {{- $block := .block -}}
 {{- $block_class := partial "GetBlockClass" .block -}}
-
+{{- $is_carousel := false -}}
 {{- with .block.data -}}
   {{- $layout := .layout | default "grid" }}
   <div class="{{ $block_class }}">
@@ -13,7 +13,24 @@
         )}}
 
         {{- if eq $layout "carousel" -}}
-          {{ partial "blocks/templates/gallery/carousel" . }}
+          {{ if gt (len .images) 1 }}
+            {{- $is_carousel = true -}}
+          {{ end }}
+        
+          {{- if $is_carousel }}
+            {{ partial "blocks/templates/carousel.html" (dict 
+            "content" .images
+            "options" site.Params.blocks.gallery.carousel
+            "partial" "blocks/templates/gallery/carousel-image.html"
+            )}}
+          {{ else }}
+            {{ range .images }}
+              {{ partial "blocks/templates/gallery/carousel-image.html" (dict 
+                "is_carousel" false
+                "params" .
+              )}}    
+            {{ end }}
+          {{ end -}}
         {{- else if eq $layout "large" -}}
           {{ partial "blocks/templates/gallery/large" . }}
         {{- else -}}
@@ -22,4 +39,4 @@
       </div>
     </div>
   </div>
-{{- end -}}
+{{- end -}}
\ No newline at end of file
diff --git a/layouts/partials/blocks/templates/gallery/carousel-image.html b/layouts/partials/blocks/templates/gallery/carousel-image.html
new file mode 100644
index 0000000000000000000000000000000000000000..bdc9ad857ae625b1f39283a010e5ebee2e1464e8
--- /dev/null
+++ b/layouts/partials/blocks/templates/gallery/carousel-image.html
@@ -0,0 +1,43 @@
+{{ $is_carousel := .is_carousel }}
+{{ $index := .index }}
+{{ $role := .role }}
+{{ with .params }}
+  {{ if .file }}
+    {{- $image := partial "GetMedia" .file -}}
+    {{- if $image -}}
+      <figure {{ if $is_carousel }} id="carousel-item-{{$index}}" role="{{$role}}" aria-roledescription="slide" class="carousel__slide" aria-label="item-{{$index}}" {{ end }}>
+        {{ partial "commons/image.html"
+          (dict
+            "image"    .id
+            "alt"      .alt
+            "sizes"    site.Params.image_sizes.blocks.gallery.carousel
+          )}}
+        {{ if not site.Params.image_sizes.design_system.lightbox.disabled }}
+          {{ $lightbox_text := false }}
+          {{ if and .text .credit }}
+            {{ $lightbox_text = delimit (slice .text .credit) " / " }}
+          {{ else }}
+            {{ $lightbox_text = or .text .credit }}
+          {{ end }}
+          <a  class="glightbox"
+              role="button"
+              data-glightbox="type: image;{{ with $lightbox_text }}description: {{ partial "PrepareHTML" . }}{{ end }}"
+              href="{{ partial "GetLightboxUrl" (dict "id" .id) }}"
+              title="{{- i18n "commons.lightbox.link.title" -}}"
+              aria-label="{{- i18n "commons.lightbox.link.title" -}}">
+          </a>
+        {{ end }}
+        {{ if or .text .credit }}
+          <figcaption>
+            {{ with .text }}
+              <p>{{ . | safeHTML }}</p>
+            {{ end }}
+            {{ with .credit }}
+              <div class="credit">{{ . | safeHTML }}</div>
+            {{ end }}
+          </figcaption>
+        {{ end }}
+      </figure>
+    {{- end -}}
+  {{ end }}
+{{ end }}
\ No newline at end of file
diff --git a/layouts/partials/blocks/templates/gallery/carousel.html b/layouts/partials/blocks/templates/gallery/carousel.html
deleted file mode 100644
index 0c472729dce6f6e60cab08147a476887d3266fba..0000000000000000000000000000000000000000
--- a/layouts/partials/blocks/templates/gallery/carousel.html
+++ /dev/null
@@ -1,57 +0,0 @@
-{{- $is_carousel := false -}}
-{{ if gt (len .images) 1 }}
-  {{- $is_carousel = true -}}
-{{ end }}
-
-{{- if $is_carousel }}
-<div class="splide" data-splide="{{ site.Params.blocks.gallery.splide | encoding.Jsonify }}">
-  <div class="splide__track">
-    <div class="splide__list">
-{{ end -}}
-
-  {{ range .images }}
-    {{ if .file }}
-      {{- $image := partial "GetMedia" .file -}}
-      {{- if $image -}}
-        <figure {{ if $is_carousel }} class="splide__slide"{{ end }}>
-          {{ partial "commons/image.html"
-            (dict
-              "image"    .id
-              "alt"      .alt
-              "sizes"    site.Params.image_sizes.blocks.gallery.carousel
-            )}}
-          {{ if not site.Params.image_sizes.design_system.lightbox.disabled }}
-            {{ $lightbox_text := false }}
-            {{ if and .text .credit }}
-              {{ $lightbox_text = delimit (slice .text .credit) " / " }}
-            {{ else }}
-              {{ $lightbox_text = or .text .credit }}
-            {{ end }}
-            <a  class="glightbox"
-                role="button"
-                data-glightbox="type: image;{{ with $lightbox_text }}description: {{ partial "PrepareHTML" . }}{{ end }}"
-                href="{{ partial "GetLightboxUrl" (dict "id" .id) }}"
-                title="{{- i18n "commons.lightbox.link.title" -}}"
-                aria-label="{{- i18n "commons.lightbox.link.title" -}}">
-            </a>
-          {{ end }}
-          {{ if or .text .credit }}
-            <figcaption>
-              {{ with .text }}
-                <p>{{ . | safeHTML }}</p>
-              {{ end }}
-              {{ with .credit }}
-                <div class="credit">{{ . | safeHTML }}</div>
-              {{ end }}
-            </figcaption>
-          {{ end }}
-        </figure>
-      {{- end -}}
-    {{ end }}
-  {{ end }}
-
-{{- if $is_carousel }}
-    </div>
-  </div>
-</div>
-{{ end -}}
diff --git a/layouts/partials/blocks/templates/posts/carousel.html b/layouts/partials/blocks/templates/posts/carousel.html
index aa5376305a7403db3e5102d273bcc85f84d4e39a..9fca8bd25abcfcc557c4b42a307ec9ce8079ea7e 100644
--- a/layouts/partials/blocks/templates/posts/carousel.html
+++ b/layouts/partials/blocks/templates/posts/carousel.html
@@ -1,26 +1,16 @@
 {{ $heading_level := .heading_level | default 3 }}
 {{ $heading := printf "h%d" $heading_level }}
+{{ $heading_tag := partial "GetHeadingTag" (dict 
+  "level" .block.ranks.children
+  "attributes" "class='title'"
+)}}
 {{ $options := .options }}
+{{ $template := printf "posts/post.html" }}
 
-<div class="carousel draggable-container">
-  <div class="carousel-posts draggable-content">
-    <ul class="posts">
-      {{ range $post := .posts -}}
-        {{ with site.GetPage (printf "/posts/%s" $post) }}
-        <li class="draggable-item">
-          {{ partial "posts/post.html" (dict 
-            "post" . 
-            "options" $options
-            "heading" $heading) }}
-        </li>
-        {{ end }}
-      {{ end }}
-    </ul>
-    {{ if  (gt (len .posts) 0) }}
-      <div class="actions-arrows">
-        <button class="previous" disabled title="{{ i18n "blocks.timeline.previous"}}"></button>
-        <button class="next" title="{{ i18n "blocks.timeline.next"}}"></button>
-      </div>
-    {{ end }}
-  </div>
-</div>
+{{ partial "blocks/templates/carousel.html" (dict 
+  "content" .posts
+  "options" site.Params.blocks.posts.carousel
+  "partial" $template
+  "heading_tag" $heading_tag
+  "block_options" $options
+)}}
diff --git a/layouts/partials/blocks/templates/testimonials.html b/layouts/partials/blocks/templates/testimonials.html
index 37d373454aadc7612944318543abbff430906f94..e12eada0562193d8e4e1f6cab4e7c4a25db29d74 100644
--- a/layouts/partials/blocks/templates/testimonials.html
+++ b/layouts/partials/blocks/templates/testimonials.html
@@ -6,74 +6,35 @@
   {{ if .testimonials }}
     {{ if gt (len .testimonials) 1 }}
       {{- $is_carousel = true -}}
-    {{ end }}
+    {{ end -}}
   {{ end }}
-  <div class="{{ $block_class }}{{ if $is_carousel }} with-carousel{{ end }}">
-    <div class="container">
-      <div class="block-content">
-        {{ partial "blocks/top.html" (dict
-          "title" $block.title
-          "heading_level" $block.ranks.self
-          "hidden" true
-        )}}
+  
+<div class="{{ $block_class }}{{ if $is_carousel }} with-carousel{{ end }}">
+  <div class="container">
+    <div class="block-content">
+      {{ partial "blocks/top.html" (dict
+      "title" $block.title
+      "heading_level" $block.ranks.self
+      "hidden" true
+      )}}
 
-        <div class="testimonials">
-          {{- if $is_carousel }}
-          <div class="splide" 
-              data-splide="{{ site.Params.blocks.testimonials.splide | encoding.Jsonify }}">
-            <div class="splide__slider">
-              <div class="splide__track">
-                <div class="splide__list">
-          {{ end -}}
-
-          {{ range .testimonials }}
-            {{ $is_long := gt (len .text) 150 }}
-            <figure  class="{{ if $is_carousel }}splide__slide{{ end }} {{ if .photo }}with-picture{{ end }}">
-              <blockquote {{- if $is_long }} class="is-long" {{- end }}>
-                <p>{{- partial "PrepareHTML" .text -}}</p>
-              </blockquote>
-              {{ if or .photo .author .job -}}
-              <figcaption>
-                {{ if .photo -}}
-                  <div class="avatar">
-                    {{- partial "commons/image.html"
-                          (dict
-                            "image"    .photo
-                            "alt"      .author
-                            "sizes"    site.Params.image_sizes.blocks.testimonials
-                          ) -}}
-                  </div>
-                {{- end }}
-                {{ if or .author .job -}}
-                  <p>
-                    {{- if .author -}}
-                      <span class="signature">{{ partial "PrepareHTML" .author }}</span>
-                    {{- end }}
-                    {{- if .job -}}
-                      <span class="meta">{{- partial "PrepareHTML" .job -}}</span>
-                    {{- end }}
-                  </p>
-                {{- end }}
-              </figcaption>
-              {{ end }}
-            </figure>
+      <div class="testimonials">
+        {{- if $is_carousel }}
+          {{ partial "blocks/templates/carousel.html" (dict 
+            "content" .testimonials
+            "options" site.Params.blocks.testimonials.carousel
+            "partial" "blocks/templates/testimonials/single.html"
+          )}}
+        {{ else }}
+          {{ range .testimonials}}
+            {{ partial "blocks/templates/testimonials/single.html" (dict
+            "is_carousel" false
+            "params" .
+            )}}
           {{ end }}
-
-          {{- if $is_carousel }}
-                </div>
-              </div>
-            </div>
-
-            <button class="splide__toggle">
-              <span class="splide__toggle__play"></span>
-              <span class="splide__toggle__pause"></span>
-            </button>
-          </div>
-          {{ end -}}
-
-        </div>
-
+        {{ end }}
       </div>
     </div>
   </div>
-{{- end -}}
+</div>
+{{- end -}}
\ No newline at end of file
diff --git a/layouts/partials/blocks/templates/testimonials/single.html b/layouts/partials/blocks/templates/testimonials/single.html
new file mode 100644
index 0000000000000000000000000000000000000000..78413b78b25ba70fec953fcaa7a5f4409a422e73
--- /dev/null
+++ b/layouts/partials/blocks/templates/testimonials/single.html
@@ -0,0 +1,31 @@
+<figure {{- if .is_carousel }} id="carousel-item-{{.index}}" role="{{.role}}" aria-roledescription="slide" class="carousel__slide" aria-label="{{.index}} / {{ .total }}" {{ end }} class="{{ if .is_carousel }}carousel__slide{{ end }} {{ if .params.photo }}with-picture{{ end }}">
+  {{ with .params }}
+    {{ $is_long := gt (len .text) 150 }}
+    <blockquote {{- if $is_long }} class="is-long" {{- end }}>
+      <p>{{- partial "PrepareHTML" .text -}}</p>
+    </blockquote>
+    {{ if or .photo .author .job -}}
+      <figcaption>
+        {{ if .photo -}}
+          <div class="avatar">
+            {{- partial "commons/image.html"
+            (dict
+            "image" .photo
+            "sizes" site.Params.image_sizes.blocks.testimonials
+            ) -}}
+          </div>
+        {{- end }}
+        {{ if or .author .job -}}
+          <p>
+            {{- if .author -}}
+              <span class="signature">{{ partial "PrepareHTML" .author }}</span>
+            {{- end }}
+            {{- if .job -}}
+              <span class="meta">{{- partial "PrepareHTML" .job -}}</span>
+            {{- end }}
+          </p>
+        {{- end }}
+      </figcaption>
+    {{ end }}
+  {{ end }}
+</figure>
\ No newline at end of file
diff --git a/layouts/partials/blocks/templates/timeline.html b/layouts/partials/blocks/templates/timeline.html
index a26afb1f4950e7d83a2d6099467db11884780001..86774dec3bff9b87ca75960f73bef6cc56604781 100644
--- a/layouts/partials/blocks/templates/timeline.html
+++ b/layouts/partials/blocks/templates/timeline.html
@@ -10,9 +10,24 @@
         "heading_level" $block.ranks.self
       )}}
       {{ $template := printf "blocks/templates/timeline/%s.html" $layout }}
-      {{ partial $template (dict 
-          "block" $block
-        ) }}
+      {{ if eq $layout "horizontal" }}
+        {{- $heading_tag := partial "GetHeadingTag" (dict 
+          "level" .block.ranks.children
+          "attributes" "class='title' itemprop='name'"
+          ) -}}
+        {{ with .block.data }}
+          {{ partial "blocks/templates/carousel.html" (dict 
+            "content" .events
+            "options" site.Params.blocks.timeline.carousel
+            "partial" $template
+            "heading_tag" $heading_tag
+          )}}
+        {{ end }}
+      {{ else }}
+        {{ partial $template (dict 
+            "block" $block
+          ) }}
+      {{ end }}
     </div>
   </div>
 </div>
diff --git a/layouts/partials/blocks/templates/timeline/horizontal.html b/layouts/partials/blocks/templates/timeline/horizontal.html
index f01f6a60a07b1f03aa4858cce20e5d0dd9165299..6dd09cb6dc88f81bc3235bb5fa86d824416aee8d 100644
--- a/layouts/partials/blocks/templates/timeline/horizontal.html
+++ b/layouts/partials/blocks/templates/timeline/horizontal.html
@@ -1,29 +1,13 @@
+{{ $heading_tag := .heading_tag }}
+{{ $index := .index }}
+{{ $role := .role }}
 
-{{- $heading_tag := partial "GetHeadingTag" (dict 
-  "level" .block.ranks.children
-  "attributes" "class='title'"
-  ) -}}
-
-<div class="timeline draggable-container">
-  {{ with .block.data }}
-    <div class="timeline-events draggable-content">
-      <ol>
-        {{ range .events }}
-          <li class="timeline-event draggable-item">
-            {{ $heading_tag.open -}}
-              {{ .title | safeHTML }}
-            {{ $heading_tag.close -}}
-            <div class="line"></div>
-            <div class="description text">{{- partial "PrepareHTML" .text | markdownify -}}</div>
-          </li>
-        {{ end }}
-      </ol>
-      {{ if  (gt (len .events) 0) }}
-        <div class="actions-arrows">
-          <button class="previous" disabled title="{{ i18n "blocks.timeline.previous"}}"></button>
-          <button class="next" title="{{ i18n "blocks.timeline.next"}}"></button>
-        </div>
-      {{ end }}
-    </div>
-  {{ end }}
-</div>
+{{ with .params }}
+  <article class="timeline-event carousel__slide" id="carousel-item-{{$index}}" role="{{$role}}" aria-roledescription="slide" class="carousel__slide" aria-label="item-{{$index}}" itemscope itemtype="https://schema.org/Article">
+    {{ $heading_tag.open -}}
+      {{ .title | safeHTML }}
+    {{ $heading_tag.close -}}
+    <div class="line"></div>
+    <div class="description text" itemprop="text">{{- partial "PrepareHTML" .text | markdownify -}}</div>
+  </article>
+{{ end }}
\ No newline at end of file
diff --git a/layouts/partials/blocks/templates/timeline/vertical.html b/layouts/partials/blocks/templates/timeline/vertical.html
index 1abf2727b8f18bf93d268dc7f7c57a21db7d3709..fc0c05cbe05cde51fd4c589f39171a0f3a4bb0ec 100644
--- a/layouts/partials/blocks/templates/timeline/vertical.html
+++ b/layouts/partials/blocks/templates/timeline/vertical.html
@@ -1,20 +1,20 @@
 
 {{- $heading_tag := partial "GetHeadingTag" (dict 
   "level" .block.ranks.children
-  "attributes" "class='title'"
+  "attributes" "class='title' itemprop='name'"
   ) -}}
 
 {{ with .block.data -}}
-  <div class="timeline-events">
+  <div class="timeline-events" >
     {{ range .events -}}
-      <article class="timeline-event">
+      <article class="timeline-event" itemscope itemtype="https://schema.org/Article">
         {{ with .title }}
           {{ $heading_tag.open -}}
             {{ . | safeHTML }}
           {{ $heading_tag.close -}}
         {{ end }}
         {{ with .text }}
-          <p>{{ . | safeHTML }}</p>
+          <p itemprop="text">{{ . | safeHTML }}</p>
         {{ end }}
       </article>
     {{ end -}}
diff --git a/layouts/partials/footer/js.html b/layouts/partials/footer/js.html
index 5c5ee8e6eb2522c24f9f45542e452105182ee3ae..31c83e3aea0d81ff8bc57cbaff57005f4ec02d81 100644
--- a/layouts/partials/footer/js.html
+++ b/layouts/partials/footer/js.html
@@ -1,5 +1,5 @@
 {{ $isLeafletNeeded := false }}
-{{ $isSplideNeed := false }}
+{{ $isCarouselNeeded := false }}
 
 {{ if .Params.contents }}
   {{ range .Params.contents }}
@@ -14,12 +14,12 @@
     {{ if eq .template "gallery"}}
       {{ with .data }}
         {{ if eq .layout "carousel" }}
-          {{ $isSplideNeed = true }}
+          {{ $isCarouselNeeded = true }}
         {{ end }}
       {{ end }}
     {{ end }}
     {{ if eq .template "testimonials"}}
-      {{ $isSplideNeed = true }}
+      {{ $isCarouselNeeded = true }}
     {{ end }}
   {{ end }}
 {{ end }}
@@ -29,11 +29,6 @@
   {{ $isLeafletNeeded = true }}
 {{ end }}
 
-{{ if $isSplideNeed }}
-  {{ $splide_js := resources.Get "js/vendors/carousel.js" | js.Build (dict "minify" hugo.IsProduction) | fingerprint }}
-  <script src="{{ $splide_js.RelPermalink }}" integrity="{{ $splide_js.Data.Integrity }}"></script>
-{{ end }}
-
 {{ if $isLeafletNeeded }}
   <!-- CSS -->
   {{- $cssOpts := (dict
diff --git a/layouts/partials/posts/post.html b/layouts/partials/posts/post.html
index 96d2a36dee77718e60de5ef74152822ca8d566e8..014afbe268341136786d3a24a7f5bb97d83b2dfc 100644
--- a/layouts/partials/posts/post.html
+++ b/layouts/partials/posts/post.html
@@ -1,4 +1,8 @@
 {{ $post := .post }}
+{{ if .params }}
+  {{ $post = site.GetPage (printf "/posts/%s" .params )}}
+{{ end }}
+
 {{ $direction := "" }}
 {{ $heading := .heading | default "h2" }}
 {{ $heading_tag := (dict 
@@ -8,6 +12,9 @@
 {{ $index := .index }}
 {{ $options := .options }}
 {{ $alternate := .alternate }}
+{{ $is_carousel := .is_carousel }}
+{{ $index := .index }}
+{{ $role := .role }}
 
 {{ with $post }}
   {{ $post_class := "post" }}
@@ -19,6 +26,9 @@
     {{ $post_class = printf "%s %s" $post_class "without-image" }}
   {{ end }}
 
+  {{ if $is_carousel }}
+    <li class="carousel__slide" id="carousel-item-{{$index}}" role="{{$role}}" aria-roledescription="slide" aria-label="item-{{$index}}">
+  {{ end }}
   <article class="{{ $post_class }}" itemprop="blogPosts" itemscope itemtype="http://schema.org/BlogPosting">
     <div class="post-content">
       {{- $title := partial "PrepareHTML" .Title -}}
@@ -73,4 +83,7 @@
       </div>
     {{- end -}}
   </article>
+  {{ if $is_carousel }}
+    </li>
+  {{ end }}
 {{ end }}
\ No newline at end of file