UNPKG

ng-hero-carousel

Version:
407 lines (403 loc) 54.6 kB
import * as i0 from '@angular/core'; import { inject, TemplateRef, input, Directive, Input, ElementRef, Renderer2, PLATFORM_ID, output, signal, effect, HostListener, HostBinding, ViewChild, ContentChild, ContentChildren, ViewChildren, ChangeDetectionStrategy, Component } from '@angular/core'; import { isPlatformBrowser, NgTemplateOutlet, CommonModule } from '@angular/common'; class SlideForDirective { template = inject((TemplateRef)); slideFor = input(0, ...(ngDevMode ? [{ debugName: "slideFor" }] : [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: SlideForDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.4", type: SlideForDirective, isStandalone: true, selector: "ng-template[slideFor]", inputs: { slideFor: { classPropertyName: "slideFor", publicName: "slideFor", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: SlideForDirective, decorators: [{ type: Directive, args: [{ selector: 'ng-template[slideFor]' }] }] }); const HERO_CAROUSEL_LANG = { en: { autoplayPauseLabel: 'Pause carousel autoplay', autoplayPlayLabel: 'Resume carousel autoplay', hostAriaLabel: 'Main carousel', prevBtnAriaLabel: 'Go to previous slide', nextBtnAriaLabel: 'Go to next slide', slidesRegionAriaLabel: 'Wide carousel', slidesRegionRoleDescription: 'Carousel', slideAriaLabel: (currentSlide, total) => `Slide ${currentSlide} of ${total}`, slideRoleDescription: 'slide', }, es: { autoplayPauseLabel: 'Pausar la reproducción automática del carrusel', autoplayPlayLabel: 'Reanudar la reproducción automática del carrusel', hostAriaLabel: 'Carrusel principal', prevBtnAriaLabel: 'Ir a la diapositiva anterior', nextBtnAriaLabel: 'Ir a la diapositiva siguiente', slidesRegionAriaLabel: 'Carrusel amplio', slidesRegionRoleDescription: 'carrusel', slideAriaLabel: (currentSlide, total) => `Diapositiva ${currentSlide} de ${total}`, slideRoleDescription: 'diapositiva', }, fr: { autoplayPauseLabel: 'Mettre en pause la lecture automatique du carrousel', autoplayPlayLabel: 'Reprendre la lecture automatique du carrousel', hostAriaLabel: 'Carrousel principal', prevBtnAriaLabel: 'Aller à la diapositive précédente', nextBtnAriaLabel: 'Aller à la diapositive suivante', slidesRegionAriaLabel: 'Carrousel large', slidesRegionRoleDescription: 'carrousel', slideAriaLabel: (currentSlide, total) => `Diapositive ${currentSlide} sur ${total}`, slideRoleDescription: 'diapositive', }, de: { autoplayPauseLabel: 'Automatische Wiedergabe des Karussells pausieren', autoplayPlayLabel: 'Automatische Wiedergabe des Karussells fortsetzen', hostAriaLabel: 'Hauptkarussell', prevBtnAriaLabel: 'Zur vorherigen Folie gehen', nextBtnAriaLabel: 'Zur nächsten Folie gehen', slidesRegionAriaLabel: 'Breites Karussell', slidesRegionRoleDescription: 'Karussell', slideAriaLabel: (currentSlide, total) => `Folie ${currentSlide} von ${total}`, slideRoleDescription: 'Folie', }, it: { autoplayPauseLabel: 'Metti in pausa la riproduzione automatica del carosello', autoplayPlayLabel: 'Riprendi la riproduzione automatica del carosello', hostAriaLabel: 'Carosello principale', prevBtnAriaLabel: 'Vai alla diapositiva precedente', nextBtnAriaLabel: 'Vai alla diapositiva successiva', slidesRegionAriaLabel: 'Carosello ampio', slidesRegionRoleDescription: 'carosello', slideAriaLabel: (currentSlide, total) => `Diapositiva ${currentSlide} di ${total}`, slideRoleDescription: 'diapositiva', }, }; class SlideBgDirective { el; renderer; image; color; constructor(el, renderer) { this.el = el; this.renderer = renderer; } ngOnInit() { // Ejemplo de aplicación del fondo combinando ambos valores const background = this.image ? `url(${this.image}), ${this.color || 'transparent'}` : this.color || 'transparent'; this.renderer.setStyle(this.el.nativeElement, 'background', background); this.renderer.setStyle(this.el.nativeElement, 'background-size', 'cover'); this.renderer.setStyle(this.el.nativeElement, 'background-position', 'center'); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: SlideBgDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.4", type: SlideBgDirective, isStandalone: true, selector: "[slide-bg]", inputs: { image: "image", color: "color" }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: SlideBgDirective, decorators: [{ type: Directive, args: [{ selector: '[slide-bg]' // 👈 este será el selector que usarás en la plantilla }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { image: [{ type: Input }], color: [{ type: Input }] } }); class NgHeroCarousel { /** ---------- INJECTIONS ---------- */ el = inject(ElementRef); renderer = inject(Renderer2); isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); /** ---------- INPUTS ---------- */ slides = input([], ...(ngDevMode ? [{ debugName: "slides" }] : [])); /** STYLE INPUTS */ hasOverlay = input(true, ...(ngDevMode ? [{ debugName: "hasOverlay" }] : [])); transitionTime = input(800, ...(ngDevMode ? [{ debugName: "transitionTime" }] : [])); arrowsPlacement = input('auto', ...(ngDevMode ? [{ debugName: "arrowsPlacement" }] : [])); indicators = input('bars', ...(ngDevMode ? [{ debugName: "indicators" }] : [])); hasCounter = input(false, ...(ngDevMode ? [{ debugName: "hasCounter" }] : [])); /** AUTOPLAY INPUTS */ hasAutoplay = input(true, ...(ngDevMode ? [{ debugName: "hasAutoplay" }] : [])); autoplayTime = input(7000, ...(ngDevMode ? [{ debugName: "autoplayTime" }] : [])); autoplayResumeTime = input(15000, ...(ngDevMode ? [{ debugName: "autoplayResumeTime" }] : [])); /** ACCESIBILITY INPUTS */ lang = input('en', ...(ngDevMode ? [{ debugName: "lang" }] : [])); accessibilityOptions = input(null, ...(ngDevMode ? [{ debugName: "accessibilityOptions" }] : [])); /** ---------- OUTPUTS ---------- */ selected = output(); /** ---------- SIGNALS ---------- */ currentSlide = signal(0, ...(ngDevMode ? [{ debugName: "currentSlide" }] : [])); carouselId = signal(null, ...(ngDevMode ? [{ debugName: "carouselId" }] : [])); prev = signal(this.slides().length - 1, ...(ngDevMode ? [{ debugName: "prev" }] : [])); isChangingSlide = signal(false, ...(ngDevMode ? [{ debugName: "isChangingSlide" }] : [])); isPlaying = signal(true, ...(ngDevMode ? [{ debugName: "isPlaying" }] : [])); isStopped = signal(false, ...(ngDevMode ? [{ debugName: "isStopped" }] : [])); slideWidth = signal(0, ...(ngDevMode ? [{ debugName: "slideWidth" }] : [])); autoplayTimer = signal(0, ...(ngDevMode ? [{ debugName: "autoplayTimer" }] : [])); resumeTimer = signal(0, ...(ngDevMode ? [{ debugName: "resumeTimer" }] : [])); acc = signal(HERO_CAROUSEL_LANG['en'], ...(ngDevMode ? [{ debugName: "acc" }] : [])); /** ---------- VIEW CHILDREN ---------- */ indicatorBtn; indicatorsContainer; slidesElements; slideCustomContent; slideTemplates; outerContent; prevBtn; nextBtn; /** ---------- HOST BINDING / HOST LISTENERS ---------- */ userIsTabbing = false; onResize() { this.calculateSlideWidth(); } handleGlobalKeydown(event) { if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { event.preventDefault(); if (event.key === 'ArrowLeft') { this.prevSlide(); this.prevBtn.nativeElement.focus(); } ; if (event.key === 'ArrowRight') { this.nextSlide(); this.nextBtn.nativeElement.focus(); } ; } if (event.key === 'Tab') { this.userIsTabbing = true; } } onTouchStart() { this.userIsTabbing = false; } /** ---------- LIFE CYCLE---------- */ constructor() { // Effects for Angular 19, 20 ( NO ", { allowSignalWrites: true }") effect(() => { const selectedSlide = this.currentSlide(); this.selected.emit(selectedSlide); }); effect(() => { this.setAccOptions(); this.setGlobalAriaLabel(); }); // **IMPORTANT** For angular 18 add ", { allowSignalWrites: true }" to each effect } ; ngOnInit() { if (this.hasAutoplay()) this.autoplay(); this.carouselId.set(`carousel-${Math.random().toString(36).slice(2, 9)}`); this.setAccOptions(); } ; ngAfterViewInit() { if (typeof window === 'undefined' || typeof document === 'undefined') return; this.setTransitionVariable(); this.setGlobalAriaLabel(); this.calculateSlideWidth(); this.setSelectorInScroll(); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { this.setSelectorInScroll(); } }); } ; /** ---------- METHODS --------- */ // Gets custom content for every slide set in ng-template getChildren(index) { return this.slideTemplates.find(t => t.slideFor() === index)?.template ?? null; } /** AUTOPLAY METHODS */ toggleAutoplay() { if (this.isPlaying()) { this.stopAutoplay(); } else { this.isStopped.set(false); this.autoplay(); } } stopAutoplay() { clearTimeout(this.autoplayTimer()); clearTimeout(this.resumeTimer()); this.isPlaying.set(false); this.isStopped.set(true); } resetAutoplay() { if (this.hasAutoplay() && !this.isStopped()) { clearTimeout(this.autoplayTimer()); clearTimeout(this.resumeTimer()); this.isPlaying.set(false); this.resumeTimer.set(setTimeout(() => this.autoplay(), this.autoplayResumeTime())); } } ; autoplay() { if (this.isStopped()) return; clearTimeout(this.autoplayTimer()); this.autoplayTimer.set(setTimeout(() => { this.goToSlide((this.currentSlide() + 1) % this.slides().length); this.setSelectorInScroll(); this.autoplay(); }, this.autoplayTime())); this.isPlaying.set(true); } ; /** NAVIGATION METHODS */ liveRegionText = signal('', ...(ngDevMode ? [{ debugName: "liveRegionText" }] : [])); goToSlide(index) { if (this.isChangingSlide()) return; const slides = this.slides(); if (!slides || slides.length === 0) return; // Limit the index to the valid range const clamped = Math.max(0, Math.min(index, slides.length - 1)); // If it doesn't change, we don't do anything. if (clamped === this.currentSlide()) return; this.isChangingSlide.set(true); this.resetAutoplay(); this.prev.set(this.currentSlide()); this.currentSlide.set(clamped); this.setSelectorInScroll(); const slideData = slides[clamped]; const slideNumber = clamped + 1; const totalSlides = slides.length; this.liveRegionText.set(`Slide ${slideNumber} of ${totalSlides}. ${slideData.title || ''} ${slideData.subtitle || ''}`.trim()); const newSlideEl = this.slidesElements.toArray()[clamped]?.nativeElement; if (this.transitionTime() === 0) { // No transition → immediate unlock this.isChangingSlide.set(false); } else if (newSlideEl) { const onEnd = () => { newSlideEl.removeEventListener('transitionend', onEnd); this.isChangingSlide.set(false); }; newSlideEl.addEventListener('transitionend', onEnd); } else { // security fallback setTimeout(() => this.isChangingSlide.set(false), this.transitionTime()); } } ; nextSlide() { this.goToSlide((this.currentSlide() + 1) % this.slides().length); } prevSlide() { if (this.currentSlide() === 0) { this.goToSlide(this.slides().length - 1); } else { this.goToSlide(this.currentSlide() - 1); } } setSelectorInScroll() { if (!this.isBrowser) return; setTimeout(() => { const clamped = this.currentSlide(); const activeButton = this.indicatorBtn.toArray()[clamped]; if (activeButton) { // reference to the parent div of the buttons const container = this.indicatorsContainer.nativeElement; const active = activeButton.nativeElement; const offsetLeft = active.offsetLeft; const offsetWidth = active.offsetWidth; // We calculate position to center the button in the container const scrollPos = offsetLeft - (this.slideWidth() / 2) + (offsetWidth / 2); container.scrollTo({ left: scrollPos, behavior: 'smooth' }); } }, this.transitionTime()); } touchStartX = 0; startedOnIndicators = false; onUserDragStart(event) { // If we are already in transition, we ignore if (this.isChangingSlide()) return; // Detect if touch started on selectors const target = event.target; this.startedOnIndicators = !!target.closest('.carousel__indicators'); // Pause autoplay to prevent skipping while crawling clearTimeout(this.autoplayTimer()); this.touchStartX = event.touches[0].clientX; // Do not propagate to avoid conflicts with other handlers event.stopPropagation(); } onUserDragEnd(event) { // If we were in transition, we ignore and reset flag if (this.isChangingSlide()) { this.startedOnIndicators = false; return; } // If the touch started on the selectors, we do not consider it a swipe of the carousel if (this.startedOnIndicators) { this.startedOnIndicators = false; // reset return; } const touchEndX = event.changedTouches[0].clientX; const deltaX = touchEndX - this.touchStartX; // small threshold to consider swipe const THRESHOLD = 50; event.stopPropagation(); if (Math.abs(deltaX) > THRESHOLD) { // Prevent touch from generating a phantom click in some browsers event.preventDefault(); if (deltaX > 0) { // swipe right -> previous this.prevSlide(); } else { // swipe left -> next this.nextSlide(); } } // resume or reschedule autoplay this.resetAutoplay(); } /** ACCESSIBILITY METHODS */ setGlobalAriaLabel() { this.renderer.setAttribute(this.el.nativeElement, 'aria-label', this.acc().hostAriaLabel); } setAccOptions() { const currentLang = this.lang() ?? 'en'; const langDefaults = HERO_CAROUSEL_LANG[currentLang]; const userOptions = this.accessibilityOptions() ?? {}; this.acc.set({ ...langDefaults, ...userOptions, slideAriaLabel: userOptions.slideAriaLabel ?? langDefaults.slideAriaLabel, }); } /** OTHER METHODS */ get transitionCssValue() { return `${this.transitionTime()}ms`; } ; calculateSlideWidth() { const firstSlide = this.slidesElements.first; if (!firstSlide) return; const rect = firstSlide.nativeElement.getBoundingClientRect(); const width = Number(rect.width.toFixed(2)); this.slideWidth.set(width); } ; setTransitionVariable() { this.el.nativeElement.style.setProperty('--carousel-transition-time', this.transitionCssValue); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: NgHeroCarousel, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: NgHeroCarousel, isStandalone: true, selector: "ng-hero-carousel", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: false, transformFunction: null }, hasOverlay: { classPropertyName: "hasOverlay", publicName: "hasOverlay", isSignal: true, isRequired: false, transformFunction: null }, transitionTime: { classPropertyName: "transitionTime", publicName: "transitionTime", isSignal: true, isRequired: false, transformFunction: null }, arrowsPlacement: { classPropertyName: "arrowsPlacement", publicName: "arrowsPlacement", isSignal: true, isRequired: false, transformFunction: null }, indicators: { classPropertyName: "indicators", publicName: "indicators", isSignal: true, isRequired: false, transformFunction: null }, hasCounter: { classPropertyName: "hasCounter", publicName: "hasCounter", isSignal: true, isRequired: false, transformFunction: null }, hasAutoplay: { classPropertyName: "hasAutoplay", publicName: "hasAutoplay", isSignal: true, isRequired: false, transformFunction: null }, autoplayTime: { classPropertyName: "autoplayTime", publicName: "autoplayTime", isSignal: true, isRequired: false, transformFunction: null }, autoplayResumeTime: { classPropertyName: "autoplayResumeTime", publicName: "autoplayResumeTime", isSignal: true, isRequired: false, transformFunction: null }, lang: { classPropertyName: "lang", publicName: "lang", isSignal: true, isRequired: false, transformFunction: null }, accessibilityOptions: { classPropertyName: "accessibilityOptions", publicName: "accessibilityOptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, host: { listeners: { "window:resize": "onResize()", "keydown": "handleGlobalKeydown($event)", "window:touchstart": "onTouchStart()" }, properties: { "class.user-is-tabbing": "this.userIsTabbing" } }, queries: [{ propertyName: "outerContent", first: true, predicate: ["outerContent"], descendants: true }, { propertyName: "slideTemplates", predicate: SlideForDirective }], viewQueries: [{ propertyName: "indicatorsContainer", first: true, predicate: ["indicatorsContainer"], descendants: true }, { propertyName: "prevBtn", first: true, predicate: ["prevBtn"], descendants: true, static: true }, { propertyName: "nextBtn", first: true, predicate: ["nextBtn"], descendants: true, static: true }, { propertyName: "indicatorBtn", predicate: ["indicatorBtn"], descendants: true }, { propertyName: "slidesElements", predicate: ["slide"], descendants: true }, { propertyName: "slideCustomContent", predicate: ["slideCustomContent"], descendants: true }], ngImport: i0, template: "<!-- STOP AUTOPLAY -->\n<button [class]=\"!hasAutoplay() ? 'carousel__autoplayBtn--sr-only' : 'carousel__autoplayBtn'\"\n (click)=\"toggleAutoplay()\"\n [attr.aria-label]=\"hasAutoplay() ? (isPlaying() ? acc().autoplayPauseLabel : acc().autoplayPlayLabel) : null\"\n [attr.title]=\"hasAutoplay() ? (isPlaying() ? acc().autoplayPauseLabel : acc().autoplayPlayLabel) : null\"\n [attr.aria-pressed]=\"hasAutoplay() ? (isPlaying() ? 'true' : 'false') : null\"\n [attr.aria-hidden]=\"!hasAutoplay() ? 'true' : null\"\n [attr.aria-controls]=\"carouselId()\"\n>\n @if (hasAutoplay()) {\n @if(isPlaying()){\n <svg aria-hidden=\"true\" focusable=\"false\" width=\"100%\" height=\"100%\"\n viewBox=\"0 0 1920 1920\"\n xmlns=\"http://www.w3.org/2000/svg\">\n <g stroke-width=\"0\"></g>\n <g stroke-linecap=\"round\" stroke-linejoin=\"round\"></g>\n <g>\n <path d=\"M754.571 0v1920H206V0h548.571Zm960 0v1920H1166V0h548.571Z\" fill-rule=\"evenodd\"></path>\n </g>\n </svg>\n }@else{\n <svg\n aria-hidden=\"true\" focusable=\"false\" width=\"100%\" height=\"100%\"\n viewBox=\"0 0 1920 1920\"\n xmlns=\"http://www.w3.org/2000/svg\" stroke=\"#000000\">\n <g stroke-width=\"0\"></g>\n <g stroke-linecap=\"round\" stroke-linejoin=\"round\"></g>\n <g>\n <path d=\"M175 .024V1920l1570.845-959.927z\" fill-rule=\"evenodd\"></path>\n </g>\n </svg>\n }\n }\n\n</button>\n\n<!-- OUTER CONTENT -->\n@if(outerContent){\n <div class=\"carousel__outerContent\">\n <ng-container [ngTemplateOutlet]=\"outerContent\"></ng-container>\n </div>\n}\n\n<!-- READABLE CONTENT FOR EACH SLIDE -->\n<div class=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\" role=\"status\">\n {{liveRegionText()}}\n</div>\n\n\n<!-- PREV ARROW (LEFT) -->\n<div\n class=\"carousel__prevBtnContainer\"\n [class.fiveOrLess]=\"slides().length <= 5\"\n [class.auto]=\"arrowsPlacement() === 'auto'\"\n [class.up]=\"arrowsPlacement() === 'up'\"\n>\n <button\n #prevBtn\n class=\"carousel__prevBtn\"\n (click)=\"prevSlide()\"\n [attr.aria-label]=\"acc().prevBtnAriaLabel\"\n [attr.aria-controls]=\"carouselId()\"\n type=\"button\"\n >\n <svg aria-hidden=\"true\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\"\n width=\"1rem\" height=\"1rem\"\n viewBox=\"320 -720 296 480\"\n >\n <path d=\"M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z\"/>\n </svg>\n </button>\n</div>\n\n<!-- SLIDES CONTENT -->\n@if(slides()){\n <section\n #slidesContainer\n [id]=\"carouselId()\"\n class=\"carousel__slides\"\n (touchstart)=\"onUserDragStart($event)\"\n (touchend)=\"onUserDragEnd($event)\"\n (click)=\"resetAutoplay()\"\n\n role=\"region\"\n [attr.aria-roledescription]=\"acc().slidesRegionRoleDescription\"\n [attr.aria-label]=\"acc().slidesRegionAriaLabel\"\n >\n @for (slide of slides(); track $index) {\n\n <article\n #slide\n class=\"carousel__slide\"\n slide-bg [image]=\"slide.image_url\" [color]=\"slide.backgroundColor\"\n\n [class.fromRight]=\"$index > currentSlide()\"\n [class.fromLeft]=\"$index < currentSlide()\"\n [class.firstFromLeft]=\"$index === slides().length -1 && currentSlide() === 0\"\n\n [class.active]=\"$index === currentSlide()\"\n [class.toLeft]=\"prev() === $index && currentSlide() > prev()\"\n [class.toRight]=\"prev() === $index && currentSlide() < prev()\"\n\n [class.firstToRight]=\"$index === 0 && currentSlide() === slides().length - 1\"\n\n [class.overlay]=\"hasOverlay()\"\n\n role=\"group\"\n [attr.aria-roledescription]=\"acc().slideRoleDescription\"\n [attr.aria-label]=\"acc().slideAriaLabel?.($index + 1, slides().length) ?? null\"\n [attr.inert]=\"currentSlide() !== $index ? '' : null\"\n >\n <div\n #slidesContent class=\"carousel__slideContent\"\n [class.active]=\"currentSlide() === $index\"\n [attr.aria-live]=\"currentSlide() === $index ? 'polite' : null\"\n aria-atomic=\"true\"\n\n >\n\n @if(slide.title || slide.subtitle){\n <hgroup class=\"carousel__slideHeaders\">\n @if (slide.title) {\n <h2 class=\"carousel__slideTitle\">{{slide.title}}</h2>\n }\n\n @if (slide.subtitle) {\n <h3 class=\"carousel__slideSubtitle\">{{slide.subtitle}}</h3>\n }\n </hgroup>\n }\n\n <!-- \"isBrowser only needed in angular 18 and 19\" -->\n @if(getChildren($index)){\n <div class=\"slideCustomContent\" slideCustomContent [inert]=\"currentSlide() !== $index\">\n <ng-container [ngTemplateOutlet]=\"getChildren($index)\"></ng-container>\n </div>\n }\n\n </div>\n\n </article>\n }\n </section>\n\n}\n\n<!-- COUNTER -->\n@if (hasCounter()){\n <span class=\"carousel__counter\" aria-hidden=\"true\">\n {{(this.currentSlide() + 1) + '/' + this.slides().length}}\n </span>\n}\n\n\n<!-- NEXT ARROW (LEFT) -->\n<div\n class=\"carousel__nextBtnContainer\"\n [class.fiveOrLess]=\"slides().length <= 5\"\n [class.auto]=\"arrowsPlacement() === 'auto'\"\n [class.up]=\"arrowsPlacement() === 'up'\"\n>\n <button\n #nextBtn\n class=\"carousel__nextBtn\"\n (click)=\"nextSlide()\"\n [attr.aria-label]=\"acc().nextBtnAriaLabel\"\n [attr.aria-controls]=\"carouselId()\"\n type=\"button\"\n >\n <svg aria-hidden=\"true\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\"\n width=\"1rem\" height=\"1rem\"\n viewBox=\"320 -720 296 480\"\n\n >\n <path d=\"M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z\"/>\n </svg>\n </button>\n</div>\n\n\n<!-- INDICATORS -->\n@if(indicators() !== 'none'){\n <section class=\"carousel__indicatorsContainer\">\n <article\n #indicatorsContainer\n class=\"carousel__indicators\"\n (touchstart)=\"onUserDragStart($event)\"\n (touchend)=\"onUserDragEnd($event)\"\n tabindex=\"-1\"\n >\n @for (indicator of slides(); track $index) {\n <button\n #indicatorBtn\n class=\"carousel__indicatorBtn\"\n [class.active]=\"currentSlide() === $index\"\n (click)=\"goToSlide($index)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n >\n <span\n class=\"carousel__indicatorIcon\"\n [class.circles]=\"indicators() === 'circles'\"\n >\n </span>\n </button>\n }\n </article>\n </section>\n}\n", styles: [":host{container-type:inline-size;--carousel-transition-time: 1s;--carousel-transition-translate-y: -2rem;--carousel-bg: inherit;--carousel-width: 100%;--carousel-height: 100dvh;--carousel-overlay-color: black;--carousel-overlay-opacity: .5;--carousel-title-size: 1.8rem;--carousel-subtitle-size: 1.2rem;--carousel-text-color: inherit;--carousel-accent-color: currentColor;--carousel-autoplay-color: var(--carousel-accent-color);--carousel-arrow-color: var(--carousel-accent-color);--carousel-indicator-color: var(--carousel-accent-color);--carousel-autoplay-top: 0rem;--carousel-autoplay-left: 0rem;--carousel-counter-top: 0rem;--carousel-counter-right: 0rem;display:block;width:var(--carousel-width);max-width:100dvw;max-height:100dvh;height:var(--carousel-height);background:var(--carousel-bg);color:var(--carousel-text-color);overflow:hidden;scroll-snap-type:x mandatory;scroll-behavior:smooth;position:relative}:host.user-is-tabbing :focus-visble{outline:2px solid var(--carousel-accent-color);outline-offset:2px}:host::-webkit-scrollbar{display:none}*{box-sizing:border-box;margin:0;padding:0;filter:none;color:var(--carousel-text-color)}.carousel__slides,.carousel__slide,.carousel__slideContent,.carousel__slide.overlay:after,.carousel__slideTitle,.slideCustomContent,:host{transform:translateZ(0);backface-visibility:hidden;will-change:transform,opacity;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}.sr-only,.carousel__autoplayBtn--sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.carousel__outerContent{color:var(--carousel-text-color);position:inherit;z-index:30}.slideCustomContent{width:100%}.carousel__autoplayBtn{position:absolute;top:var(--carousel-autoplay-top);left:var(--carousel-autoplay-left);width:auto;height:auto;max-height:50px;border:0;font-size:1.1rem;padding:1rem;background-color:transparent;z-index:30;cursor:pointer}.carousel__autoplayBtn svg{width:100%;height:auto;max-width:18px;fill:var(--carousel-autoplay-color)}.carousel__autoplayBtn:focus-visible{outline:2px solid var(--carousel-autoplay-color);outline-offset:-.5rem}.carousel__slides{height:100%;overflow:hidden;position:relative}.carousel__slide{width:100%;height:100%;min-height:100%;background-position:center;background-repeat:no-repeat;background-size:cover;position:absolute;right:0;left:100%}.carousel__slide.bgImage{background-position:center;background-repeat:no-repeat;background-size:cover}.carousel__slide.fromLeft{left:-100%}.carousel__slide.fromRright{left:100%}.carousel__slide.firstFromLeft{left:-100%!important}.carousel__slide.firsFromRright{left:100%!important}.carousel__slide.active{transition:left var(--carousel-transition-time) ease-in-out;left:0%}.carousel__slide.toLeft{transition:left var(--carousel-transition-time) ease-in-out;left:-100%;z-index:0}.carousel__slide.toRight{transition:left var(--carousel-transition-time) ease-in-out;left:100%;z-index:0}.carousel__slide.firstToRight{transition:left var(--carousel-transition-time) ease-in-out!important;left:100%!important;z-index:0!important}@keyframes resturnPosition{0%{left:-100%}to{left:100%}}.carousel__slide.overlay:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;opacity:var(--carousel-overlay-opacity);background:var(--carousel-overlay-color);z-index:0}.carousel__slideContent{height:calc(100% - 60px);width:100%;padding:50px 7% 0;margin-bottom:60px;display:flex;flex-direction:column;align-items:center;justify-content:center;position:inherit;z-index:20;opacity:0;transform:translateY(var(--carousel-transition-translate-y));transform-origin:center;transition:opacity .3s ease-out,transform .3s ease-out}.carousel__slideContent.active{transition:opacity .5s .5s ease-in,transform .5s .5s ease-out;opacity:1;transform:translateY(0)}.carousel__slideHeaders{display:flex;align-items:center;justify-content:center;flex-direction:column}.carousel__slideTitle,.carousel__slideSubtitle{text-align:center;color:var(--carousel-text-color)}.carousel__slideTitle{text-wrap:balance;margin-bottom:1rem;font-size:var(--carousel-title-size)}.carousel__slideSubtitle{text-wrap:pretty;font-size:var(--carousel-subtitle-size);font-weight:400}.carousel__prevBtnContainer,.carousel__nextBtnContainer{display:flex;align-items:center;position:absolute;z-index:20;height:auto;max-height:60px;bottom:0}.carousel__nextBtnContainer{right:2%}.carousel__prevBtnContainer{left:2%}.carousel__nextBtnContainer.up,.carousel__prevBtnContainer.up{width:6%;height:100%;max-height:none}.carousel__nextBtnContainer.up{right:0}.carousel__prevBtnContainer.up{left:0}.carousel__prevBtnContainer.up .carousel__prevBtn,.carousel__nextBtnContainer.up .carousel__nextBtn{padding-left:0;padding-right:0;width:100%;min-width:auto;max-height:none;height:30%}.carousel__prevBtn,.carousel__nextBtn{background-color:transparent;border:none;width:100%;min-width:2.6rem;height:auto;max-height:60px;padding:1.3rem .2rem;display:flex;justify-content:center;align-items:center;cursor:pointer}.carousel__prevBtn:focus-visible,.carousel__nextBtn:focus-visible{outline:none}.carousel__prevBtn:focus-visible svg,.carousel__nextBtn:focus-visible svg{outline:2px solid var(--carousel-arrow-color);outline-offset:10px}.carousel__prevBtn svg,.carousel__nextBtn svg{fill:var(--carousel-arrow-color);width:1rem;height:1rem}.carousel__indicatorsContainer{height:60px;position:absolute;bottom:0;left:0;width:100%;margin:0 auto;display:flex;flex-direction:column;justify-content:end}.carousel__indicatorsContainer::-webkit-scrollbar{display:none}.carousel__indicators{touch-action:none;-ms-touch-action:none;width:fit-content;max-width:calc(240px + 1.6rem);height:100%;margin:0 auto;overflow-x:auto;scrollbar-width:none;display:flex;align-items:center;column-gap:.4rem}.carousel__indicatorBtn{aspect-ratio:1/1;width:100%;max-width:3rem;height:100%;background-color:transparent;border:none;display:flex;align-items:center;justify-content:center;padding:0 2px;cursor:pointer}.carousel__indicatorIcon{height:100%;max-height:.2rem;width:100%;max-width:2.5rem;border-radius:.2rem;background:var(--carousel-indicator-color);position:relative;opacity:.5;transition:opacity .3s ease-in-out}.carousel__indicatorIcon.circles{aspect-ratio:1/1;max-width:.8rem;max-height:.8rem;border-radius:50%}.carousel__indicatorBtn.active .carousel__indicatorIcon.circles{outline:2px solid var(--carousel-indicator-color);outline-offset:2px}.carousel__indicatorBtn.active .carousel__indicatorIcon{opacity:1}.carousel__counter{height:50px;position:absolute;right:var(--carousel-counter-right);top:var(--carousel-counter-top);margin:0 1rem;display:flex;justify-content:end;align-items:center;font-weight:400;font-size:1.1rem;z-index:25}@container (max-width: 350px){.carousel__indicators{max-width:calc(192px + 1.2rem)}}@container (min-width: 500px){.carousel__indicators{max-width:calc(336px + 2rem)}.carousel__nextBtnContainer.fiveOrLess{right:calc(50% - (240px + 1.2rem) + 48px)}.carousel__prevBtnContainer.fiveOrLess{left:calc(50% - (240px + 1.2rem) + 48px)}}@media screen and (min-width: 620px){:host{--carousel-title-size: 2.1rem;--carousel-subtitle-size: 1.4rem}}@container (min-width: 620px){.carousel__indicators{max-width:calc(432px + 2.8rem)}.carousel__autoplayBtn svg{max-width:20px}}@container (min-width: 820px){.carousel__indicators{max-width:calc(528px + 3.6rem)}.carousel__prevBtnContainer.auto,.carousel__nextBtnContainer.auto,.carousel__prevBtnContainer.up,.carousel__nextBtnContainer.up{height:100%;max-height:none;width:5%}.carousel__nextBtnContainer.auto{right:0}.carousel__prevBtnContainer.auto{left:0}.carousel__prevBtnContainer.auto svg,.carousel__nextBtnContainer.auto svg,.carousel__prevBtnContainer.up svg,.carousel__nextBtnContainer.up svg{width:1.5rem;height:1.5rem}}@container (min-width: 1024px){.carousel__indicators{max-width:calc(720px + 5.2rem)}.carousel__prevBtn svg,.carousel__nextBtn svg{transition:transform .2s ease-in-out}.carousel__prevBtn:hover svg,.carousel__nextBtn:hover svg{transform-origin:center;transform:scale(1.3)}.carousel__indicatorBtn:hover .carousel__indicatorIcon{opacity:1}}@media screen and (min-width: 1200px){:host{--carousel-title-size: 2.5rem;--carousel-subtitle-size: 1.5rem}}@container (min-width: 1200px){.carousel__indicators:not(.fiveOrLess){max-width:calc(816px + 6rem)}.carousel__counter{font-weight:400;font-size:1.2rem}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: SlideBgDirective, selector: "[slide-bg]", inputs: ["image", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: NgHeroCarousel, decorators: [{ type: Component, args: [{ standalone: true, selector: 'ng-hero-carousel', imports: [NgTemplateOutlet, CommonModule, SlideBgDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- STOP AUTOPLAY -->\n<button [class]=\"!hasAutoplay() ? 'carousel__autoplayBtn--sr-only' : 'carousel__autoplayBtn'\"\n (click)=\"toggleAutoplay()\"\n [attr.aria-label]=\"hasAutoplay() ? (isPlaying() ? acc().autoplayPauseLabel : acc().autoplayPlayLabel) : null\"\n [attr.title]=\"hasAutoplay() ? (isPlaying() ? acc().autoplayPauseLabel : acc().autoplayPlayLabel) : null\"\n [attr.aria-pressed]=\"hasAutoplay() ? (isPlaying() ? 'true' : 'false') : null\"\n [attr.aria-hidden]=\"!hasAutoplay() ? 'true' : null\"\n [attr.aria-controls]=\"carouselId()\"\n>\n @if (hasAutoplay()) {\n @if(isPlaying()){\n <svg aria-hidden=\"true\" focusable=\"false\" width=\"100%\" height=\"100%\"\n viewBox=\"0 0 1920 1920\"\n xmlns=\"http://www.w3.org/2000/svg\">\n <g stroke-width=\"0\"></g>\n <g stroke-linecap=\"round\" stroke-linejoin=\"round\"></g>\n <g>\n <path d=\"M754.571 0v1920H206V0h548.571Zm960 0v1920H1166V0h548.571Z\" fill-rule=\"evenodd\"></path>\n </g>\n </svg>\n }@else{\n <svg\n aria-hidden=\"true\" focusable=\"false\" width=\"100%\" height=\"100%\"\n viewBox=\"0 0 1920 1920\"\n xmlns=\"http://www.w3.org/2000/svg\" stroke=\"#000000\">\n <g stroke-width=\"0\"></g>\n <g stroke-linecap=\"round\" stroke-linejoin=\"round\"></g>\n <g>\n <path d=\"M175 .024V1920l1570.845-959.927z\" fill-rule=\"evenodd\"></path>\n </g>\n </svg>\n }\n }\n\n</button>\n\n<!-- OUTER CONTENT -->\n@if(outerContent){\n <div class=\"carousel__outerContent\">\n <ng-container [ngTemplateOutlet]=\"outerContent\"></ng-container>\n </div>\n}\n\n<!-- READABLE CONTENT FOR EACH SLIDE -->\n<div class=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\" role=\"status\">\n {{liveRegionText()}}\n</div>\n\n\n<!-- PREV ARROW (LEFT) -->\n<div\n class=\"carousel__prevBtnContainer\"\n [class.fiveOrLess]=\"slides().length <= 5\"\n [class.auto]=\"arrowsPlacement() === 'auto'\"\n [class.up]=\"arrowsPlacement() === 'up'\"\n>\n <button\n #prevBtn\n class=\"carousel__prevBtn\"\n (click)=\"prevSlide()\"\n [attr.aria-label]=\"acc().prevBtnAriaLabel\"\n [attr.aria-controls]=\"carouselId()\"\n type=\"button\"\n >\n <svg aria-hidden=\"true\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\"\n width=\"1rem\" height=\"1rem\"\n viewBox=\"320 -720 296 480\"\n >\n <path d=\"M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z\"/>\n </svg>\n </button>\n</div>\n\n<!-- SLIDES CONTENT -->\n@if(slides()){\n <section\n #slidesContainer\n [id]=\"carouselId()\"\n class=\"carousel__slides\"\n (touchstart)=\"onUserDragStart($event)\"\n (touchend)=\"onUserDragEnd($event)\"\n (click)=\"resetAutoplay()\"\n\n role=\"region\"\n [attr.aria-roledescription]=\"acc().slidesRegionRoleDescription\"\n [attr.aria-label]=\"acc().slidesRegionAriaLabel\"\n >\n @for (slide of slides(); track $index) {\n\n <article\n #slide\n class=\"carousel__slide\"\n slide-bg [image]=\"slide.image_url\" [color]=\"slide.backgroundColor\"\n\n [class.fromRight]=\"$index > currentSlide()\"\n [class.fromLeft]=\"$index < currentSlide()\"\n [class.firstFromLeft]=\"$index === slides().length -1 && currentSlide() === 0\"\n\n [class.active]=\"$index === currentSlide()\"\n [class.toLeft]=\"prev() === $index && currentSlide() > prev()\"\n [class.toRight]=\"prev() === $index && currentSlide() < prev()\"\n\n [class.firstToRight]=\"$index === 0 && currentSlide() === slides().length - 1\"\n\n [class.overlay]=\"hasOverlay()\"\n\n role=\"group\"\n [attr.aria-roledescription]=\"acc().slideRoleDescription\"\n [attr.aria-label]=\"acc().slideAriaLabel?.($index + 1, slides().length) ?? null\"\n [attr.inert]=\"currentSlide() !== $index ? '' : null\"\n >\n <div\n #slidesContent class=\"carousel__slideContent\"\n [class.active]=\"currentSlide() === $index\"\n [attr.aria-live]=\"currentSlide() === $index ? 'polite' : null\"\n aria-atomic=\"true\"\n\n >\n\n @if(slide.title || slide.subtitle){\n <hgroup class=\"carousel__slideHeaders\">\n @if (slide.title) {\n <h2 class=\"carousel__slideTitle\">{{slide.title}}</h2>\n }\n\n @if (slide.subtitle) {\n <h3 class=\"carousel__slideSubtitle\">{{slide.subtitle}}</h3>\n }\n </hgroup>\n }\n\n <!-- \"isBrowser only needed in angular 18 and 19\" -->\n @if(getChildren($index)){\n <div class=\"slideCustomContent\" slideCustomContent [inert]=\"currentSlide() !== $index\">\n <ng-container [ngTemplateOutlet]=\"getChildren($index)\"></ng-container>\n </div>\n }\n\n </div>\n\n </article>\n }\n </section>\n\n}\n\n<!-- COUNTER -->\n@if (hasCounter()){\n <span class=\"carousel__counter\" aria-hidden=\"true\">\n {{(this.currentSlide() + 1) + '/' + this.slides().length}}\n </span>\n}\n\n\n<!-- NEXT ARROW (LEFT) -->\n<div\n class=\"carousel__nextBtnContainer\"\n [class.fiveOrLess]=\"slides().length <= 5\"\n [class.auto]=\"arrowsPlacement() === 'auto'\"\n [class.up]=\"arrowsPlacement() === 'up'\"\n>\n <button\n #nextBtn\n class=\"carousel__nextBtn\"\n (click)=\"nextSlide()\"\n [attr.aria-label]=\"acc().nextBtnAriaLabel\"\n [attr.aria-controls]=\"carouselId()\"\n type=\"button\"\n >\n <svg aria-hidden=\"true\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\"\n width=\"1rem\" height=\"1rem\"\n viewBox=\"320 -720 296 480\"\n\n >\n <path d=\"M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z\"/>\n </svg>\n </button>\n</div>\n\n\n<!-- INDICATORS -->\n@if(indicators() !== 'none'){\n <section class=\"carousel__indicatorsContainer\">\n <article\n #indicatorsContainer\n class=\"carousel__indicators\"\n (touchstart)=\"onUserDragStart($event)\"\n (touchend)=\"onUserDragEnd($event)\"\n tabindex=\"-1\"\n >\n @for (indicator of slides(); track $index) {\n <button\n #indicatorBtn\n class=\"carousel__indicatorBtn\"\n [class.active]=\"currentSlide() === $index\"\n (click)=\"goToSlide($index)\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n >\n <span\n class=\"carousel__indicatorIcon\"\n [class.circles]=\"indicators() === 'circles'\"\n >\n </span>\n </button>\n }\n </article>\n </section>\n}\n", styles: [":host{container-type:inline-size;--carousel-transition-time: 1s;--carousel-transition-translate-y: -2rem;--carousel-bg: inherit;--carousel-width: 100%;--carousel-height: 100dvh;--carousel-overlay-color: black;--carousel-overlay-opacity: .5;--carousel-title-size: 1.8rem;--carousel-subtitle-size: 1.2rem;--carousel-text-color: inherit;--carousel-accent-color: currentColor;--carousel-autoplay-color: var(--carousel-accent-color);--carousel-arrow-color: var(--carousel-accent-color);--carousel-indicator-color: var(--carousel-accent-color);--carousel-autoplay-top: 0rem;--carousel-autoplay-left: 0rem;--carousel-counter-top: 0rem;--carousel-counter-right: 0rem;display:block;width:var(--carousel-width);max-width:100dvw;max-height:100dvh;height:var(--carousel-height);background:var(--carousel-bg);color:var(--carousel-text-color);overflow:hidden;scroll-snap-type:x mandatory;scroll-behavior:smooth;position:relative}:host.user-is-tabbing :focus-visble{outline:2px solid var(--carousel-accent-color);outline-offset:2px}:host::-webkit-scrollbar{display:none}*{box-sizing:border-box;margin:0;padding:0;filter:none;color:var(--carousel-text-color)}.carousel__slides,.carousel__slide,.carousel__slideContent,.carousel__slide.overlay:after,.carousel__slideTitle,.slideCustomContent,:host{transform:translateZ(0);backface-visibility:hidden;will-change:transform,opacity;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}.sr-only,.carousel__autoplayBtn--sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.carousel__outerContent{color:var(--carousel-text-color);position:inherit;z-index:30}.slideCustomContent{width:100%}.carousel__autoplayBtn{position:absolute;top:var(--carousel-autoplay-top);left:var(--carousel-autoplay-left);width:auto;height:auto;max-height:50px;border:0;font-size:1.1rem;padding:1rem;background-color:transparent;z-index:30;cursor:pointer}.carousel__autoplayBtn svg{width:100%;height:auto;max-width:18px;fill:var(--carousel-autoplay-color)}.carousel__autoplayBtn:focus-visible{outline:2px solid var(--carousel-autoplay-color);outline-offset:-.5rem}.carousel__slides{height:100%;overflow:hidden;position:relative}.carousel__slide{width:100%;height:100%;min-height:100%;background-position:center;background-repeat:no-repeat;background-size:cover;position:absolute;right:0;left:100%}.carousel__slide.bgImage{background-position:center;background-repeat:no-repeat;background-size:cover}.carousel__slide.fromLeft{left:-100%}.carousel__slide.fromRright{left:100%}.carousel__slide.firstFromLeft{left:-100%!important}.carousel__slide.firsFromRright{left:100%!important}.carousel__slide.active{transition:left var(--carousel-transition-time) ease-in-out;left:0%}.carousel__slide.toLeft{transition:left var(--carousel-transition-time) ease-in-out;left:-100%;z-index:0}.carousel__slide.toRight{transition:left var(--carousel-transition-time) ease-in-out;left:100%;z-index:0}.carousel__slide.firstToRight{transition:left var(--carousel-transition-time) ease-in-out!important;left:100%!important;z-index:0!important}@keyframes resturnPosition{0%{left:-100%}to{left:100%}}.carousel__slide.overlay:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;opacity:var(--carousel-overlay-opacity);background:var(--carousel-overlay-color);z-index:0}.carousel__slideContent{height:calc(100% - 60px);width:100%;padding:50px 7% 0;margin-bottom:60px;display:flex;flex-direction:column;align-items:center;justify-content:center;position:inherit;z-index:20;opacity:0;transform:translateY(var(--carousel-transition-translate-y));transform-origin:center;transition:opacity .3s ease-out,transform .3s ease-out}.carousel__slideContent.active{transition:opacity .5s .5s ease-in,transform .5s .5s ease-out;opacity:1;transform:translateY(0)}.carousel__slideHeaders{display:flex;align-items:center;justify-content:center;flex-direction:column}.carousel__slideTitle,.carousel__slideSubtitle{text-align:center;color:var(--carousel-text-color)}.carousel__slideTitle{text-wrap:balance;margin-bottom:1rem;font-size:var(--carousel-title-size)}.carousel__slideSubtitle{text-wrap:pretty;font-size:var(--carousel-subtitle-size);font-weight:400}.carousel__prevBtnContainer,.carousel__nextBtnContainer{display:flex;align-items:center;position:absolute;z-index:20;height:auto;max-height:60px;bottom:0}.carousel__nextBtnContainer{right:2%}.carousel__prevBtnContainer{left:2%}.carousel__nextBtnContainer.up,.carousel__prevBtnContainer.up{width:6%;height:100%;max-height:none}.carousel__nextBtnContainer.up{right:0}.carousel__prevBtnContainer.up{left:0}.carousel__prevBtnContainer.up .carousel__prevBtn,.carousel__nextBtnContainer.up .carousel__nextBtn{padding-left:0;padding-right:0;width:100%;min-width:auto;max-height:none;height:30%}.carousel__prevBtn,.carousel__nextBtn{background-color:transparent;border:none;width:100%;min-width:2.6rem;height:auto;max-height:60px;padding:1.3rem .2rem;display:flex;justify-content:center;align-items:center;cursor:pointer}.carousel__prevBtn:focus-visible,.carousel__nextBtn:focus-visible{outline:none}.carousel__prevBtn:focus-visible svg,.carousel__nextBtn:focus-visible svg{outline:2px solid var(--carousel-arrow-color);outline-offset:10px}.carousel__prevBtn svg,.carousel__nextBtn svg{fill:var(--carousel-arrow-color);width:1rem;height:1rem}.carousel__indicatorsContainer{height:60px;position:absolute;bottom:0;left:0;width:100%;margin:0 auto;display:flex;flex-direction:column;justify-c