UNPKG

cfc-ds

Version:

Design System do Conselho Federal de Contabilidade baseado no govbr-ds

742 lines (732 loc) 821 kB
import * as i0 from '@angular/core'; import { Component, Input, EventEmitter, Output, TemplateRef, ViewChild, ContentChild, Injectable, HostListener, HostBinding, forwardRef, ChangeDetectionStrategy, Optional, Inject, ContentChildren, Directive, NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import * as i2$1 from '@angular/forms'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS, ReactiveFormsModule, FormsModule } from '@angular/forms'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i1$1 from '@angular/router'; import { NavigationEnd, RouterModule } from '@angular/router'; import { filter, interval, fromEvent, BehaviorSubject } from 'rxjs'; import * as i3 from '@angular/flex-layout/flex'; import * as i2 from '@angular/flex-layout/extended'; import { tap, map, takeWhile } from 'rxjs/operators'; import { FlexLayoutModule } from '@angular/flex-layout'; import { provideHttpClient } from '@angular/common/http'; import dayjs from 'dayjs'; import 'dayjs/locale/pt-br'; import localeData from 'dayjs/plugin/localeData'; var AvatarDensity; (function (AvatarDensity) { AvatarDensity["large"] = "large"; AvatarDensity["medium"] = "medium"; AvatarDensity["small"] = "small"; })(AvatarDensity || (AvatarDensity = {})); var AvatarType; (function (AvatarType) { AvatarType["letter"] = "letter"; AvatarType["icon"] = "icon"; AvatarType["image"] = "image"; AvatarType["dropdown"] = "dropdown"; })(AvatarType || (AvatarType = {})); class AvatarComponent { type = AvatarType.icon; name; density = AvatarDensity.medium; imageUrl = ''; listItems = []; avatarTypes = AvatarType; avatarDensities = AvatarDensity; openDropdown = false; toggleDropdown() { this.openDropdown = !this.openDropdown; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: AvatarComponent, selector: "cfc-avatar", inputs: { type: "type", name: "name", density: "density", imageUrl: "imageUrl", listItems: "listItems" }, ngImport: i0, template: "<span\r\n class=\"br-avatar mr-3\"\r\n [title]=\"name\"\r\n [class.medium]=\"density === avatarDensities.medium\"\r\n [class.large]=\"density === avatarDensities.large\"\r\n>\r\n <span *ngIf=\"type === avatarTypes.icon\" class=\"content\">\r\n <i class=\"fas fa-user bg-blue-warn-20\" aria-hidden=\"true\"></i>\r\n </span>\r\n <span\r\n *ngIf=\"type === avatarTypes.letter\"\r\n class=\"content bg-violet-50 text-pure-0\"\r\n >\r\n {{ name[0] | uppercase }}\r\n </span>\r\n <span *ngIf=\"type === avatarTypes.image\" class=\"content\">\r\n <img [src]=\"imageUrl\" alt=\"Avatar\" />\r\n </span>\r\n <span *ngIf=\"type === avatarTypes.dropdown\" class=\"dropdown\">\r\n <button class=\"br-sign-in\" type=\"button\" (click)=\"toggleDropdown()\">\r\n <span class=\"br-avatar\" title=\"Fulano da Silva\"\r\n ><span class=\"content bg-orange-vivid-30 text-pure-0\">\r\n {{ name[0] | uppercase }}</span\r\n ></span\r\n ><span class=\"ml-2 text-gray-80 text-weight-regular\">{{ name }}</span\r\n ><i class=\"fas fa-caret-down\" aria-hidden=\"true\"></i>\r\n </button>\r\n <div *ngIf=\"openDropdown\" class=\"br-list\">\r\n <div *ngIf=\"listItems?.length !== 0\">\r\n <div *ngFor=\"let items of listItems\" class=\"br-item\"><p>{{items.name}}</p></div>\r\n </div>\r\n </div>\r\n </span>\r\n</span>\r\n", styles: [".dropdown{position:relative}.br-list{position:absolute;left:0}.dropdown .br-item:not(:last-child){border-bottom:1px solid #ccc}.br-item{color:#333;height:50px;cursor:pointer}.br-item p{font-size:14px}.br-item:hover{background-color:#c6c6c6}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AvatarComponent, decorators: [{ type: Component, args: [{ selector: 'cfc-avatar', template: "<span\r\n class=\"br-avatar mr-3\"\r\n [title]=\"name\"\r\n [class.medium]=\"density === avatarDensities.medium\"\r\n [class.large]=\"density === avatarDensities.large\"\r\n>\r\n <span *ngIf=\"type === avatarTypes.icon\" class=\"content\">\r\n <i class=\"fas fa-user bg-blue-warn-20\" aria-hidden=\"true\"></i>\r\n </span>\r\n <span\r\n *ngIf=\"type === avatarTypes.letter\"\r\n class=\"content bg-violet-50 text-pure-0\"\r\n >\r\n {{ name[0] | uppercase }}\r\n </span>\r\n <span *ngIf=\"type === avatarTypes.image\" class=\"content\">\r\n <img [src]=\"imageUrl\" alt=\"Avatar\" />\r\n </span>\r\n <span *ngIf=\"type === avatarTypes.dropdown\" class=\"dropdown\">\r\n <button class=\"br-sign-in\" type=\"button\" (click)=\"toggleDropdown()\">\r\n <span class=\"br-avatar\" title=\"Fulano da Silva\"\r\n ><span class=\"content bg-orange-vivid-30 text-pure-0\">\r\n {{ name[0] | uppercase }}</span\r\n ></span\r\n ><span class=\"ml-2 text-gray-80 text-weight-regular\">{{ name }}</span\r\n ><i class=\"fas fa-caret-down\" aria-hidden=\"true\"></i>\r\n </button>\r\n <div *ngIf=\"openDropdown\" class=\"br-list\">\r\n <div *ngIf=\"listItems?.length !== 0\">\r\n <div *ngFor=\"let items of listItems\" class=\"br-item\"><p>{{items.name}}</p></div>\r\n </div>\r\n </div>\r\n </span>\r\n</span>\r\n", styles: [".dropdown{position:relative}.br-list{position:absolute;left:0}.dropdown .br-item:not(:last-child){border-bottom:1px solid #ccc}.br-item{color:#333;height:50px;cursor:pointer}.br-item p{font-size:14px}.br-item:hover{background-color:#c6c6c6}\n"] }] }], propDecorators: { type: [{ type: Input }], name: [{ type: Input }], density: [{ type: Input }], imageUrl: [{ type: Input }], listItems: [{ type: Input }] } }); class BreadcrumbComponent { router; activatedRoute; /** Lista de breadcrumbs gerados */ links = []; /** Limite de caracteres para truncamento */ maxLength = 25; /** Inscrição para monitorar mudanças na rota */ routeSubscription; constructor(router, activatedRoute) { this.router = router; this.activatedRoute = activatedRoute; } /** Inicializa o componente e observa mudanças de rota */ ngOnInit() { this.updateBreadcrumbs(); this.routeSubscription = this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => { this.updateBreadcrumbs(); }); } /** Atualiza os breadcrumbs com base na rota ativa */ updateBreadcrumbs() { const breadcrumbs = this.buildBreadcrumbs(this.router.routerState.root); this.removeLastBreadcrumbUrl(breadcrumbs); this.links = breadcrumbs; } /** * Constrói os breadcrumbs recursivamente com base nas rotas ativadas. * @param route Rota ativa. * @param breadcrumbs Lista de breadcrumbs acumulada. * @returns Lista de BreadcrumbLink atualizada. */ buildBreadcrumbs(route, breadcrumbs = []) { route.children.forEach((child) => { const routeSegment = this.getRouteURL(child); const label = this.getBreadcrumbLabel(child.snapshot.data); const fullPath = this.buildFullPath(routeSegment, breadcrumbs); if (label && !this.isDuplicateBreadcrumb(breadcrumbs, label)) { const truncated = this.shouldTruncate(label) ? this.truncateText(label) : label; const breadcrumb = { label: truncated, url: fullPath, target: '_self', }; // Adicionar tooltip apenas se o texto foi truncado if (truncated !== label) { breadcrumb.tooltipText = label; breadcrumb.tooltipPlace = 'top'; breadcrumb.tooltipType = 'info'; } breadcrumbs.push(breadcrumb); } this.buildBreadcrumbs(child, breadcrumbs); }); return breadcrumbs; } /** * Verifica se o texto deve ser truncado * @param text Texto para verificar * @returns true se o texto deve ser truncado */ shouldTruncate(text) { return text.length > this.maxLength; } /** * Trunca o texto para o tamanho máximo definido * @param text Texto para truncar * @returns Texto truncado */ truncateText(text) { return text.substring(0, this.maxLength - 3) + '...'; } /** * Obtém o texto original para exibição no tooltip * @param link Item do breadcrumb * @returns Texto original ou undefined se não houver tooltip */ getOriginalText(link) { return link.tooltipText; } /** * Obtém o segmento da URL da rota atual. * @param route Rota atual. * @returns Segmento da URL ou string vazia. */ getRouteURL(route) { return route.snapshot.url.length > 0 ? route.snapshot.url.map(segment => segment.path).join('/') : ''; } /** * Constrói o caminho completo concatenando segmentos anteriores. * @param routeSegment Segmento atual da rota. * @param breadcrumbs Lista de breadcrumbs acumulada. * @returns Caminho completo da URL. */ buildFullPath(routeSegment, breadcrumbs) { if (!routeSegment) return ''; const previousPath = breadcrumbs.length > 0 ? breadcrumbs[breadcrumbs.length - 1].url : ''; return previousPath ? `${previousPath}/${routeSegment}` : `/${routeSegment}`; } /** * Obtém o rótulo do breadcrumb a partir dos dados da rota. * @param data Dados da rota. * @returns Rótulo do breadcrumb ou null. */ getBreadcrumbLabel(data) { const label = data['breadcrumb']; return label && typeof label === 'string' && label.trim() ? label.trim() : null; } /** * Remove a URL do último breadcrumb para evitar que seja clicável. * @param breadcrumbs Lista de breadcrumbs. */ removeLastBreadcrumbUrl(breadcrumbs) { if (breadcrumbs.length > 0) { breadcrumbs[breadcrumbs.length - 1].url = undefined; breadcrumbs[breadcrumbs.length - 1].active = true; } } /** * Verifica se um breadcrumb já existe na lista para evitar duplicatas. * @param breadcrumbs Lista de breadcrumbs acumulada. * @param label Nome do breadcrumb a verificar. * @returns Verdadeiro se o breadcrumb já existir, falso caso contrário. */ isDuplicateBreadcrumb(breadcrumbs, label) { return breadcrumbs.some(bc => bc.tooltipText === label || bc.label === label); } /** Cancela a inscrição ao destruir o componente */ ngOnDestroy() { this.routeSubscription?.unsubscribe(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BreadcrumbComponent, deps: [{ token: i1$1.Router }, { token: i1$1.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: BreadcrumbComponent, selector: "cfc-breadcrumb", ngImport: i0, template: "<nav\r\n class=\"br-breadcrumb\"\r\n aria-label=\"Breadcrumbs\">\r\n <ul\r\n class=\"crumb-list\"\r\n style=\"padding-left: 0;\"\r\n role=\"list\">\r\n <li\r\n class=\"crumb\">\r\n <a\r\n class=\"br-button circle\"\r\n href=\"/\"\r\n target=\"_self\">\r\n <i class=\"fas fa-home\"></i>\r\n </a>\r\n </li>\r\n\r\n <li\r\n class=\"crumb\"\r\n *ngFor=\"let link of links; let last = last\">\r\n <i\r\n class=\"icon fas fa-chevron-right\">\r\n </i>\r\n\r\n <a\r\n *ngIf=\"!last\"\r\n [href]=\"link.url\"\r\n [target]=\"link.target\"\r\n [title]=\"link.tooltipText || ''\"\r\n [attr.data-tooltip]=\"link.tooltipText || null\"\r\n [attr.data-tooltip-place]=\"link.tooltipPlace || null\"\r\n [attr.data-tooltip-type]=\"link.tooltipType || null\"\r\n [attr.data-tooltip-timer]=\"link.tooltipTimer || null\">\r\n <span>\r\n {{ link.label | titlecase }}\r\n </span>\r\n </a>\r\n\r\n <span\r\n *ngIf=\"last\"\r\n tabindex=\"0\"\r\n aria-current=\"page\"\r\n [title]=\"link.tooltipText || ''\"\r\n [attr.data-tooltip]=\"link.tooltipText || null\"\r\n [attr.data-tooltip-place]=\"link.tooltipPlace || null\"\r\n [attr.data-tooltip-type]=\"link.tooltipType || null\"\r\n [attr.data-tooltip-timer]=\"link.tooltipTimer || null\">\r\n {{ link.label | titlecase }}\r\n </span>\r\n\r\n </li>\r\n </ul>\r\n</nav>", styles: [""], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BreadcrumbComponent, decorators: [{ type: Component, args: [{ selector: 'cfc-breadcrumb', template: "<nav\r\n class=\"br-breadcrumb\"\r\n aria-label=\"Breadcrumbs\">\r\n <ul\r\n class=\"crumb-list\"\r\n style=\"padding-left: 0;\"\r\n role=\"list\">\r\n <li\r\n class=\"crumb\">\r\n <a\r\n class=\"br-button circle\"\r\n href=\"/\"\r\n target=\"_self\">\r\n <i class=\"fas fa-home\"></i>\r\n </a>\r\n </li>\r\n\r\n <li\r\n class=\"crumb\"\r\n *ngFor=\"let link of links; let last = last\">\r\n <i\r\n class=\"icon fas fa-chevron-right\">\r\n </i>\r\n\r\n <a\r\n *ngIf=\"!last\"\r\n [href]=\"link.url\"\r\n [target]=\"link.target\"\r\n [title]=\"link.tooltipText || ''\"\r\n [attr.data-tooltip]=\"link.tooltipText || null\"\r\n [attr.data-tooltip-place]=\"link.tooltipPlace || null\"\r\n [attr.data-tooltip-type]=\"link.tooltipType || null\"\r\n [attr.data-tooltip-timer]=\"link.tooltipTimer || null\">\r\n <span>\r\n {{ link.label | titlecase }}\r\n </span>\r\n </a>\r\n\r\n <span\r\n *ngIf=\"last\"\r\n tabindex=\"0\"\r\n aria-current=\"page\"\r\n [title]=\"link.tooltipText || ''\"\r\n [attr.data-tooltip]=\"link.tooltipText || null\"\r\n [attr.data-tooltip-place]=\"link.tooltipPlace || null\"\r\n [attr.data-tooltip-type]=\"link.tooltipType || null\"\r\n [attr.data-tooltip-timer]=\"link.tooltipTimer || null\">\r\n {{ link.label | titlecase }}\r\n </span>\r\n\r\n </li>\r\n </ul>\r\n</nav>" }] }], ctorParameters: () => [{ type: i1$1.Router }, { type: i1$1.ActivatedRoute }] }); var ButtonType; (function (ButtonType) { ButtonType["primary"] = "primary"; ButtonType["secondary"] = "secondary"; ButtonType["tertiary"] = "tertiary"; ButtonType["danger"] = "danger"; })(ButtonType || (ButtonType = {})); var ButtonDensity; (function (ButtonDensity) { ButtonDensity["large"] = "large"; ButtonDensity["middle"] = "middle"; ButtonDensity["small"] = "small"; })(ButtonDensity || (ButtonDensity = {})); class ButtonComponent { cdr; label = 'button'; type = ButtonType.primary; submit = false; circle = false; density = ButtonDensity.middle; disabled = false; block = false; icon = ''; active = false; inverted = false; loading = false; onClick = new EventEmitter(); buttonTypes = ButtonType; buttonDensity = ButtonDensity; constructor(cdr) { this.cdr = cdr; } ngOnChanges(changes) { if (changes['type']) { this.cdr.detectChanges(); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ButtonComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: ButtonComponent, selector: "cfc-button", inputs: { label: "label", type: "type", submit: "submit", circle: "circle", density: "density", disabled: "disabled", block: "block", icon: "icon", active: "active", inverted: "inverted", loading: "loading" }, outputs: { onClick: "onClick" }, usesOnChanges: true, ngImport: i0, template: "<button\r\n class=\"br-button\"\r\n [ngClass]=\"{\r\n 'primary': type === buttonTypes.primary,\r\n 'secondary': type === buttonTypes.secondary,\r\n 'tertiary': type === buttonTypes.tertiary,\r\n 'danger': type === buttonTypes.danger,\r\n 'circle': circle,\r\n 'block': block,\r\n 'loading': loading,\r\n 'active': active,\r\n 'dark-mode': inverted,\r\n 'small': density === buttonDensity.small,\r\n 'lager': density === buttonDensity.large,\r\n 'middle': density === buttonDensity.middle,\r\n }\"\r\n [disabled]=\"disabled\"\r\n [attr.aria-label]=\"icon ? label : null\"\r\n [type]=\"submit ? 'submit' : 'button'\"\r\n fxLayoutGap=\"0.3rem\"\r\n (click)=\"onClick.emit()\"\r\n>\r\n <i *ngIf=\"icon\" [class]=\"'fas fa-' + icon\" aria-hidden=\"true\"></i>\r\n <span *ngIf=\"!circle\">\r\n {{ label }}\r\n </span>\r\n</button>\r\n", styles: [""], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.DefaultLayoutGapDirective, selector: " [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]", inputs: ["fxLayoutGap", "fxLayoutGap.xs", "fxLayoutGap.sm", "fxLayoutGap.md", "fxLayoutGap.lg", "fxLayoutGap.xl", "fxLayoutGap.lt-sm", "fxLayoutGap.lt-md", "fxLayoutGap.lt-lg", "fxLayoutGap.lt-xl", "fxLayoutGap.gt-xs", "fxLayoutGap.gt-sm", "fxLayoutGap.gt-md", "fxLayoutGap.gt-lg"] }, { kind: "directive", type: i2.DefaultClassDirective, selector: " [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]", inputs: ["ngClass", "ngClass.xs", "ngClass.sm", "ngClass.md", "ngClass.lg", "ngClass.xl", "ngClass.lt-sm", "ngClass.lt-md", "ngClass.lt-lg", "ngClass.lt-xl", "ngClass.gt-xs", "ngClass.gt-sm", "ngClass.gt-md", "ngClass.gt-lg"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ButtonComponent, decorators: [{ type: Component, args: [{ selector: 'cfc-button', template: "<button\r\n class=\"br-button\"\r\n [ngClass]=\"{\r\n 'primary': type === buttonTypes.primary,\r\n 'secondary': type === buttonTypes.secondary,\r\n 'tertiary': type === buttonTypes.tertiary,\r\n 'danger': type === buttonTypes.danger,\r\n 'circle': circle,\r\n 'block': block,\r\n 'loading': loading,\r\n 'active': active,\r\n 'dark-mode': inverted,\r\n 'small': density === buttonDensity.small,\r\n 'lager': density === buttonDensity.large,\r\n 'middle': density === buttonDensity.middle,\r\n }\"\r\n [disabled]=\"disabled\"\r\n [attr.aria-label]=\"icon ? label : null\"\r\n [type]=\"submit ? 'submit' : 'button'\"\r\n fxLayoutGap=\"0.3rem\"\r\n (click)=\"onClick.emit()\"\r\n>\r\n <i *ngIf=\"icon\" [class]=\"'fas fa-' + icon\" aria-hidden=\"true\"></i>\r\n <span *ngIf=\"!circle\">\r\n {{ label }}\r\n </span>\r\n</button>\r\n" }] }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { label: [{ type: Input }], type: [{ type: Input }], submit: [{ type: Input }], circle: [{ type: Input }], density: [{ type: Input }], disabled: [{ type: Input }], block: [{ type: Input }], icon: [{ type: Input }], active: [{ type: Input }], inverted: [{ type: Input }], loading: [{ type: Input }], onClick: [{ type: Output }] } }); class CardContentComponent { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CardContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: CardContentComponent, selector: "cfc-card-content", ngImport: i0, template: "<div class=\"card-content\">\r\n <ng-content></ng-content>\r\n</div>\r\n", styles: [""] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CardContentComponent, decorators: [{ type: Component, args: [{ selector: 'cfc-card-content', template: "<div class=\"card-content\">\r\n <ng-content></ng-content>\r\n</div>\r\n" }] }] }); class CardComponent { hover = false; disabled = false; hFixed = false; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: CardComponent, selector: "cfc-card", inputs: { hover: "hover", disabled: "disabled", hFixed: "hFixed" }, ngImport: i0, template: " <div class=\"br-card\"\r\n [class.hover]=\"hover\"\r\n [class.disabled]=\"disabled\"\r\n [class.h-fixed]=\"hFixed\">\r\n <ng-content select=\"[cfc-card-header]\"></ng-content>\r\n <cfc-card-content>\r\n <ng-content select=\"[cfc-card-content]\">\r\n\r\n </ng-content>\r\n </cfc-card-content>\r\n <ng-content select=\"[cfc-card-footer]\"></ng-content>\r\n </div>\r\n", styles: [""], dependencies: [{ kind: "component", type: CardContentComponent, selector: "cfc-card-content" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CardComponent, decorators: [{ type: Component, args: [{ selector: 'cfc-card', template: " <div class=\"br-card\"\r\n [class.hover]=\"hover\"\r\n [class.disabled]=\"disabled\"\r\n [class.h-fixed]=\"hFixed\">\r\n <ng-content select=\"[cfc-card-header]\"></ng-content>\r\n <cfc-card-content>\r\n <ng-content select=\"[cfc-card-content]\">\r\n\r\n </ng-content>\r\n </cfc-card-content>\r\n <ng-content select=\"[cfc-card-footer]\"></ng-content>\r\n </div>\r\n" }] }], propDecorators: { hover: [{ type: Input }], disabled: [{ type: Input }], hFixed: [{ type: Input }] } }); class CarouselComponent { items = []; config = { loop: false, autoPlay: false, autoPlayInterval: 5000, showNavigationButtons: true, showPlayButtons: false, showIndicator: true, navigationButtonsPosition: 'outside', // 'inside' ou 'outside' indicatorPosition: 'inside', // 'inside' ou 'outside' indicatorType: 'simple', itemsPerPage: 1, navigationType: 'page', transitionDuration: 300, transitionType: 'slide', enableTouch: true }; pageChange = new EventEmitter(); itemClick = new EventEmitter(); carouselStage; carouselContent; contentTemplate; currentPageIndex = 0; totalPages = 0; isPlaying = false; isPaused = false; isHovering = false; isDragging = false; dragStartX = 0; dragCurrentX = 0; initialTransform = 0; currentTransform = 0; loadedImages = new Set(); adjacentImagesLoaded = false; autoPlaySubscription; touchStartX = 0; touchEndX = 0; touchStartY = 0; touchStartTime = 0; swipeSubscriptions = []; mousedownSubscription; mousemoveSubscription; mouseupSubscription; mouseleaveSubscription; get currentPage() { return this.currentPageIndex + 1; } get pages() { return Array.from({ length: this.totalPages }, (_, i) => i); } get blocks() { const blocksCount = Math.ceil(this.items.length / (this.config.itemsPerPage ?? 1)); return Array.from({ length: blocksCount }, (_, i) => i); } ngOnInit() { // Verificar e ajustar config para indicador textual if (this.config.indicatorType === 'text') { this.config.indicatorPosition = 'outside'; } this.calculateTotalPages(); this.initializeCarousel(); // Pré-carregar a primeira imagem e as adjacentes if (this.items.length > 0) { this.preloadAdjacentImages(0); } if (this.config.autoPlay) { this.play(); } } ngAfterViewInit() { if (this.config.enableTouch) { this.setupTouchEvents(); this.setupMouseEvents(); } } ngOnDestroy() { this.stopAutoPlay(); this.cleanupTouchEvents(); this.cleanupMouseEvents(); } onMouseEnter() { this.isHovering = true; if (this.config.autoPlay && this.isPlaying) { this.pauseAutoPlay(); } } onMouseLeave() { this.isHovering = false; if (this.config.autoPlay && this.isPaused) { this.resumeAutoPlay(); } } calculateTotalPages() { if (this.config.navigationType === 'block') { this.totalPages = Math.ceil(this.items.length / (this.config.itemsPerPage ?? 1)); } else { this.totalPages = this.items.length; } } initializeCarousel() { if (this.items.length > 0) { this.items.forEach((item, index) => { item.active = index === 0; }); } } goToPage(pageIndex) { if (pageIndex < 0 || pageIndex >= this.totalPages) { if (this.config.loop) { pageIndex = pageIndex < 0 ? this.totalPages - 1 : 0; } else { return; } } // Pré-carregar imagens adjacentes antes da transição this.preloadAdjacentImages(pageIndex); this.currentPageIndex = pageIndex; // Atualizar estado de ativo para os itens if (this.config.navigationType === 'block') { const startIndex = this.currentPageIndex * (this.config.itemsPerPage ?? 1); this.items.forEach((item, index) => { item.active = index >= startIndex && index < startIndex + (this.config.itemsPerPage ?? 1); }); } else { this.items.forEach((item, index) => { item.active = index === this.currentPageIndex; }); } this.updateContentTransform(this.currentPageIndex); this.pageChange.emit(this.currentPageIndex); } // Método para pré-carregar imagens adjacentes preloadAdjacentImages(currentIndex) { // Determinar quais imagens devem ser pré-carregadas (atual, anterior e próxima) const indicesToPreload = [ currentIndex, Math.max(0, currentIndex - 1), Math.min(this.totalPages - 1, currentIndex + 1) ]; // Filtrar índices únicos const uniqueIndices = [...new Set(indicesToPreload)]; // Pré-carregar cada imagem uniqueIndices.forEach(index => { if (index >= 0 && index < this.items.length) { const item = this.items[index]; if (item.imageUrl && !this.loadedImages.has(item.imageUrl)) { this.preloadImage(item.imageUrl); } } }); } // Método para pré-carregar uma única imagem preloadImage(imageUrl) { if (!imageUrl || this.loadedImages.has(imageUrl)) return; const img = new Image(); img.onload = () => { this.loadedImages.add(imageUrl); }; img.src = imageUrl; } // Método para aplicar a transformação ao conteúdo updateContentTransform(pageIndex, animationOverride = '') { if (!this.carouselContent || !this.carouselContent.nativeElement) return; const element = this.carouselContent.nativeElement; const translateX = -pageIndex * 100; // Aplicar a transição antes de mudar a transformação element.style.transition = animationOverride || `transform ${this.config.transitionDuration}ms ease-in-out`; // Pequeno delay antes de aplicar a transformação para garantir que a transição seja registrada setTimeout(() => { element.style.transform = `translateX(${translateX}%)`; this.currentTransform = translateX; }, 10); } next() { this.goToPage(this.currentPageIndex + 1); } prev() { this.goToPage(this.currentPageIndex - 1); } play() { if (this.isPlaying) { return; } this.isPlaying = true; this.isPaused = false; this.autoPlaySubscription = interval(this.config.autoPlayInterval) .pipe(tap(() => { if (!this.isHovering && !this.isDragging) { if (this.currentPageIndex === this.totalPages - 1 && !this.config.loop) { this.stopAutoPlay(); } else { this.next(); } } })) .subscribe(); } pauseAutoPlay() { this.isPaused = true; this.stopAutoPlay(); } resumeAutoPlay() { if (this.isPaused) { this.play(); } } pause() { this.pauseAutoPlay(); } togglePlay() { if (this.isPlaying) { this.pause(); } else { this.play(); } } stopAutoPlay() { if (this.autoPlaySubscription) { this.autoPlaySubscription.unsubscribe(); this.autoPlaySubscription = undefined; this.isPlaying = false; } } setupTouchEvents() { if (!this.carouselStage || !this.carouselStage.nativeElement) { return; } const element = this.carouselStage.nativeElement; // Usando eventos nativos para melhor desempenho element.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true }); element.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false }); element.addEventListener('touchend', this.handleTouchEnd.bind(this), { passive: true }); } handleTouchStart(event) { if (this.isPlaying) { this.pauseAutoPlay(); } this.touchStartX = event.touches[0].clientX; this.touchStartY = event.touches[0].clientY; this.touchStartTime = Date.now(); this.isDragging = true; if (this.carouselContent && this.carouselContent.nativeElement) { const style = window.getComputedStyle(this.carouselContent.nativeElement); const matrix = new WebKitCSSMatrix(style.transform); this.initialTransform = matrix.m41 / this.carouselContent.nativeElement.offsetWidth * 100; this.currentTransform = this.initialTransform; // Remover transição para movimento suave durante o arrasto this.carouselContent.nativeElement.style.transition = 'none'; } } handleTouchMove(event) { if (!this.isDragging) return; const touch = event.touches[0]; const deltaX = touch.clientX - this.touchStartX; const deltaY = touch.clientY - this.touchStartY; // Verificar se o deslizamento é horizontal antes de prevenir comportamento padrão if (Math.abs(deltaX) > Math.abs(deltaY)) { event.preventDefault(); } if (this.carouselContent && this.carouselContent.nativeElement) { const containerWidth = this.carouselContent.nativeElement.offsetWidth; const movePercentage = (deltaX / containerWidth) * 100; // Adicionar resistência nas bordas quando não estiver em loop let newTransform = this.initialTransform + movePercentage; if (!this.config.loop) { if (this.currentPageIndex === 0 && movePercentage > 0) { newTransform = this.initialTransform + (movePercentage / 3); // Resistência no início } else if (this.currentPageIndex === this.totalPages - 1 && movePercentage < 0) { newTransform = this.initialTransform + (movePercentage / 3); // Resistência no final } } this.carouselContent.nativeElement.style.transform = `translateX(${newTransform}%)`; this.currentTransform = newTransform; } } handleTouchEnd(event) { if (!this.isDragging) return; const touchEndTime = Date.now(); const touchDuration = touchEndTime - this.touchStartTime; this.touchEndX = event.changedTouches[0].clientX; const deltaX = this.touchEndX - this.touchStartX; // Calcular velocidade do movimento para determinar swipe const velocity = Math.abs(deltaX) / touchDuration; // Determinar o comportamento com base no movimento e velocidade let newPage = this.currentPageIndex; if (Math.abs(deltaX) > 100 || velocity > 0.5) { // Movimento significativo ou swipe rápido newPage = deltaX > 0 ? this.currentPageIndex - 1 : this.currentPageIndex + 1; } else { // Baseado na porcentagem de movimento const movedPercentage = Math.abs(deltaX) / (this.carouselStage?.nativeElement.offsetWidth || 1); if (movedPercentage > 0.2) { newPage = deltaX > 0 ? this.currentPageIndex - 1 : this.currentPageIndex + 1; } } this.isDragging = false; // Voltar para o slide atual ou ir para o próximo/anterior if (this.carouselContent && this.carouselContent.nativeElement) { this.carouselContent.nativeElement.style.transition = `transform ${this.config.transitionDuration}ms var(--animation-ease-in-out)`; this.goToPage(newPage); } // Restaurar autoplay se necessário if (this.isPaused) { this.resumeAutoPlay(); } } // Adicionar suporte a eventos de mouse para desktop setupMouseEvents() { if (!this.carouselStage || !this.carouselStage.nativeElement) return; const element = this.carouselStage.nativeElement; this.mousedownSubscription = fromEvent(element, 'mousedown') .subscribe(this.handleMouseDown.bind(this)); this.mousemoveSubscription = fromEvent(document, 'mousemove') .subscribe(this.handleMouseMove.bind(this)); this.mouseupSubscription = fromEvent(document, 'mouseup') .subscribe(this.handleMouseUp.bind(this)); this.mouseleaveSubscription = fromEvent(document, 'mouseleave') .subscribe(this.handleMouseUp.bind(this)); } handleMouseDown(event) { // Só capturar cliques com botão esquerdo if (event.button !== 0) return; // Prevenir comportamento padrão (seleção de texto) event.preventDefault(); if (this.isPlaying) { this.pauseAutoPlay(); } this.dragStartX = event.clientX; this.isDragging = true; if (this.carouselContent && this.carouselContent.nativeElement) { const style = window.getComputedStyle(this.carouselContent.nativeElement); const matrix = new WebKitCSSMatrix(style.transform); this.initialTransform = matrix.m41 / this.carouselContent.nativeElement.offsetWidth * 100; this.currentTransform = this.initialTransform; this.carouselContent.nativeElement.style.transition = 'none'; // Cursor de arrasto document.body.style.cursor = 'grabbing'; } } handleMouseMove(event) { if (!this.isDragging) return; this.dragCurrentX = event.clientX; const deltaX = this.dragCurrentX - this.dragStartX; if (this.carouselContent && this.carouselContent.nativeElement) { const containerWidth = this.carouselContent.nativeElement.offsetWidth; const movePercentage = (deltaX / containerWidth) * 100; // Adicionar resistência nas bordas quando não estiver em loop let newTransform = this.initialTransform + movePercentage; if (!this.config.loop) { if (this.currentPageIndex === 0 && movePercentage > 0) { newTransform = this.initialTransform + (movePercentage / 3); } else if (this.currentPageIndex === this.totalPages - 1 && movePercentage < 0) { newTransform = this.initialTransform + (movePercentage / 3); } } this.carouselContent.nativeElement.style.transform = `translateX(${newTransform}%)`; this.currentTransform = newTransform; } } handleMouseUp(event) { if (!this.isDragging) return; document.body.style.cursor = ''; this.isDragging = false; if (this.dragStartX !== 0 && this.dragCurrentX !== 0) { const deltaX = this.dragCurrentX - this.dragStartX; let newPage = this.currentPageIndex; // Determinar se deve mudar de slide com base na distância do movimento if (Math.abs(deltaX) > 100) { newPage = deltaX > 0 ? this.currentPageIndex - 1 : this.currentPageIndex + 1; } else { const movedPercentage = Math.abs(deltaX) / (this.carouselStage?.nativeElement.offsetWidth || 1); if (movedPercentage > 0.2) { newPage = deltaX > 0 ? this.currentPageIndex - 1 : this.currentPageIndex + 1; } } if (this.carouselContent && this.carouselContent.nativeElement) { this.carouselContent.nativeElement.style.transition = `transform ${this.config.transitionDuration}ms var(--animation-ease-in-out)`; this.goToPage(newPage); } } this.dragStartX = 0; this.dragCurrentX = 0; // Restaurar autoplay se necessário if (this.isPaused) { this.resumeAutoPlay(); } } cleanupTouchEvents() { if (this.carouselStage && this.carouselStage.nativeElement) { const element = this.carouselStage.nativeElement; element.removeEventListener('touchstart', this.handleTouchStart.bind(this)); element.removeEventListener('touchmove', this.handleTouchMove.bind(this)); element.removeEventListener('touchend', this.handleTouchEnd.bind(this)); } this.swipeSubscriptions.forEach(sub => sub.unsubscribe()); this.swipeSubscriptions = []; } cleanupMouseEvents() { this.mousedownSubscription?.unsubscribe(); this.mousemoveSubscription?.unsubscribe(); this.mouseupSubscription?.unsubscribe(); this.mouseleaveSubscription?.unsubscribe(); } isFirstPage() { return this.currentPageIndex === 0; } isLastPage() { return this.currentPageIndex === this.totalPages - 1; } shouldDisableNavButton(direction) { if (this.config.loop) { return false; } return direction === 'prev' ? this.isFirstPage() : this.isLastPage(); } trackByFn(index, item) { return item.id || index; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: CarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: CarouselComponent, selector: "cfc-carousel", inputs: { items: "items", config: "config" }, outputs: { pageChange: "pageChange", itemClick: "itemClick" }, queries: [{ propertyName: "contentTemplate", first: true, predicate: TemplateRef, descendants: true }], viewQueries: [{ propertyName: "carouselStage", first: true, predicate: ["carouselStage"], descendants: true }, { propertyName: "carouselContent", first: true, predicate: ["carouselContent"], descendants: true }], ngImport: i0, template: "<div class=\"carousel-wrapper\">\r\n <!-- Bot\u00E3o de navega\u00E7\u00E3o (anterior) outside -->\r\n <button *ngIf=\"config.showNavigationButtons && config.navigationButtonsPosition === 'outside'\"\r\n class=\"carousel-nav-button carousel-prev-button outside\"\r\n [disabled]=\"shouldDisableNavButton('prev')\"\r\n (click)=\"prev()\">\r\n <i class=\"fas fa-angle-left\"></i>\r\n </button>\r\n <div class=\"carousel-container\"\r\n (mouseenter)=\"onMouseEnter()\"\r\n (mouseleave)=\"onMouseLeave()\">\r\n <!-- Bot\u00E3o de navega\u00E7\u00E3o (anterior) inside -->\r\n <button *ngIf=\"config.showNavigationButtons && config.navigationButtonsPosition === 'inside'\"\r\n class=\"carousel-nav-button carousel-prev-button inside\"\r\n [disabled]=\"shouldDisableNavButton('prev')\"\r\n (click)=\"prev()\">\r\n <i class=\"fas fa-angle-left\"></i>\r\n </button>\r\n <!-- Palco (\u00E1rea de conte\u00FAdo) -->\r\n <div class=\"carousel-stage\" #carouselStage\r\n [class.is-dragging]=\"isDragging\">\r\n <!-- Modifica\u00E7\u00E3o no carousel-content -->\r\n <div class=\"carousel-content\" #carouselContent>\r\n <ng-container *ngFor=\"let item of items; let i = index; trackBy: trackByFn\">\r\n <div class=\"carousel-item\"\r\n [class.active]=\"item.active\"\r\n [class.dragging]=\"isDragging\"\r\n (click)=\"!isDragging && itemClick.emit(item)\">\r\n <!-- Renderiza\u00E7\u00E3o de imagem com tratamento para pr\u00E9-carregamento -->\r\n <div *ngIf=\"item.imageUrl\" class=\"carousel-image-container\">\r\n <!-- Modificado para carregar imagem diretamente, mas usar opacity para o efeito de fade -->\r\n <img \r\n [src]=\"item.imageUrl\" \r\n [alt]=\"item.imageAlt || item.title || ''\" \r\n class=\"carousel-image\"\r\n [ngClass]=\"{'lazyloaded': loadedImages.has(item.imageUrl)}\"\r\n (load)=\"loadedImages.add(item.imageUrl)\">\r\n \r\n <!-- Imagem de placeholder enquanto carrega -->\r\n <div *ngIf=\"!loadedImages.has(item.imageUrl)\" class=\"carousel-image-placeholder\"></div>\r\n </div>\r\n\r\n <!-- Resto do conte\u00FAdo permanece igual -->\r\n <ng-container *ngIf=\"!item.imageUrl || contentTemplate\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: {$implicit: item}\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n \r\n <!-- Bot\u00E3o de navega\u00E7\u00E3o (pr\u00F3ximo) inside -->\r\n <button *ngIf=\"config.showNavigationButtons && config.navigationButtonsPosition === 'inside'\"\r\n class=\"carousel-nav-button carousel-next-button inside\"\r\n [disabled]=\"shouldDisableNavButton('next')\"\r\n (click)=\"next()\">\r\n <i class=\"fas fa-angle-right\"></i>\r\n </button>\r\n\r\n <!-- Bot\u00E3o de reprodu\u00E7\u00E3o (interno ao palco) -->\r\n <div *ngIf=\"config.showPlayButtons && config.navigationButtonsPosition === 'inside'\"\r\n class=\"carousel-play-button-container\">\r\n <button class=\"carousel-play-button\" (click)=\"togglePlay()\">\r\n <i [class]=\"isPlaying ? 'fas fa-pause' : 'fas fa-play'\"></i>\r\n </button>\r\n </div>\r\n\r\n <!-- Indicador de p\u00E1ginas (interno ao palco) -->\r\n <div *ngIf=\"config.showIndicator && config.indicatorPosition === 'inside'\"\r\n class=\"carousel-indicator-container inside\">\r\n <div *ngIf=\"config.indicatorType === 'simple'\" class=\"carousel-indicator\">\r\n <span *ngFor=\"let page of pages\"\r\n class=\"indicator-dot\"\r\n [class.active]=\"page === currentPageIndex\"\r\n (click)=\"goToPage(page)\"></span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- Bot\u00E3o de navega\u00E7\u00E3o (pr\u00F3ximo) outside -->\r\n <button *ngIf=\"config.showNavigationButtons && config.navigationButtonsPosition === 'outside'\"\r\n class=\"carousel-nav-button carousel-next-button outside\"\r\n [disabled]=\"shouldDisableNavButton('next')\"\r\n (click)=\"next()\">\r\n <i class=\"fas fa-angle-right\"></i>\r\n </button>\r\n</div>\r\n\r\n<!-- Bot\u00E3o de reprodu\u00E7\u00E3o (externo ao palco) -->\r\n<div *ngIf=\"config.showPlayButtons && config.navigationButtonsPosition === 'outside'\"\r\n class=\"carousel-play-button-container outside\">\r\n <button class=\"carousel-play-button\" (click)=\"togglePlay()\">\r\n <i [class]=\"isPlaying ? 'fas fa-pause' : 'fas fa-play'\"></i>\r\n </button>\r\n</div>\r\n\r\n<!-- Indicador de p\u00E1ginas (externo ao palco) -->\r\n<div *ngIf=\"config.showIndicator && config.indicatorPosition === 'outside'\"\r\n class=\"carousel-indicator-container outside\">\r\n <div *ngIf=\"config.indicatorType === 'simple'\" class=\"carousel-indicator\">\r\n <span *ngFor=\"let page of pages\"\r\n class=\"indicator-dot\"\r\n [class.active]=\"page === currentPageIndex\"\r\n (click)=\"goToPage(page)\"></span>\r\n </div>\r\n <div *ngIf=\"config.indicatorType === 'text'\" class=\"carousel-indicator-text\">\r\n {{ currentPage }}/{{ totalPages }}\r\n </div>\r\n</div>\r\n\r\n<!-- Template para o conte\u00FAdo -->\r\n<ng-template #itemTemplate let-item>\r\n <ng-content *ngIf=\"!contentTemplate\"></ng-content>\r\n <ng-container *ngIf=\"contentTemplate\">\r\n <ng-container *ngTemplateOutlet=\"contentTemplate; context: {$implicit: item}\"></ng-container>\r\n </ng-container>\r\n</ng-template>\r\n<ng-content select=\"[carouselContent]\"></ng-content>", styles: [":host{display:block}.carousel-wrapper{display:flex;align-items:center;position:relative;width:100%}.carousel-container{position:relative;width:100%;overflow:hidden}.carousel-container:not(:hover) .carousel-play-button-container:not(.outside){opacity:0;transition:opacity .3s ease}.carousel-container:hover .carousel-play-button-container:not(.outside){opacity:1;transition:opacity .3s ease}.carousel-stage{position:relative;overflow:hidden;width:100%;touch-action:pan-y;-webkit-user-select:none;user-select:none;cursor:grab}.carousel-stage.is-dragging{cursor:grabbing}.carousel-content{display:flex;position:relative;height:100%;width:100%;will-change:transform;touch-action:none;transition:transform .3s ease-in-out}.carousel-block{display:flex;width:100%;position:relative;transition:transform var(--transition-duration, .3s) var(--animation-ease-in-out)}.carousel-block .carousel-item{flex-shrink:0;width:100%;height:100%;position:relative;flex:0 0 100%}.carousel-block .carousel-block .carousel-item{width:calc((100% - (var(--items-per-page, 3) - 1) * var(--item-spacing, 20px)) / var(--items-per-page, 3))}.carousel-block .carousel-item.dragging{pointer-events:none}.carousel-item{flex-shrink:0;width:100%;height:100%;position:relative}.carousel-block .carousel-item{width:calc((100% - (var(--items-per-page, 3) - 1) * var(--item-spacing, 20px)) / var(--items-per-page, 3))}.carousel-item.dragging{pointer-events:none}.carousel-image-container{position:relative;width:100%;height:100%;overflow:hidden}.carousel-image{width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .3s ease-in-out}.carousel-image.lazyloaded{opacity:1}.carousel-overlay{position:absolute;bottom:0;left:0;right:0;background:#000000b3;color:#fff;padding:15px}.carousel-overlay h3{margin:0 0 8px;font-size:1.2em}.carousel-overlay p{margin:0;font-size:.9em}.carousel-nav-button{background:#ffffff80;border:none;border-radius:50%;width:40px;height:40px;display:flex;justify-content:center;align-items:center;cursor:pointer;z-index:2;transition:background-color .3s}.carousel-nav-button:hover{background:#fffc}.carousel-nav-button:disabled{opacity:.3;cursor:not-allowed}.carousel-nav-button.inside{position:absolute;top:50%;transform:translateY(-50%)}.carousel-nav-button.outside{margin:0 10px}.carousel-nav-button.carousel-prev-but