valtech-components 2.0.500 → 2.0.501

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/esm2022/lib/components/atoms/button/button.component.mjs +87 -48
  2. package/esm2022/lib/components/molecules/action-header/action-header.component.mjs +1 -1
  3. package/esm2022/lib/components/molecules/ad-slot/ad-slot.component.mjs +249 -0
  4. package/esm2022/lib/components/molecules/button-group/button-group.component.mjs +1 -1
  5. package/esm2022/lib/components/molecules/card/card.component.mjs +2 -2
  6. package/esm2022/lib/components/molecules/file-input/file-input.component.mjs +1 -1
  7. package/esm2022/lib/components/molecules/raffle-status-card/raffle-status-card.component.mjs +2 -2
  8. package/esm2022/lib/components/organisms/article/article.component.mjs +2 -2
  9. package/esm2022/lib/components/organisms/menu/menu.component.mjs +1 -1
  10. package/esm2022/lib/components/templates/page-template/page-template.component.mjs +1 -1
  11. package/esm2022/lib/services/ads/ads-consent.service.mjs +152 -0
  12. package/esm2022/lib/services/ads/ads-loader.service.mjs +160 -0
  13. package/esm2022/lib/services/ads/ads.service.mjs +449 -0
  14. package/esm2022/lib/services/ads/config.mjs +118 -0
  15. package/esm2022/lib/services/ads/index.mjs +14 -0
  16. package/esm2022/lib/services/ads/types.mjs +23 -0
  17. package/esm2022/public-api.mjs +6 -1
  18. package/fesm2022/valtech-components.mjs +1330 -154
  19. package/fesm2022/valtech-components.mjs.map +1 -1
  20. package/lib/components/atoms/button/button.component.d.ts +30 -6
  21. package/lib/components/molecules/ad-slot/ad-slot.component.d.ts +78 -0
  22. package/lib/components/organisms/article/article.component.d.ts +3 -3
  23. package/lib/services/ads/ads-consent.service.d.ts +59 -0
  24. package/lib/services/ads/ads-loader.service.d.ts +46 -0
  25. package/lib/services/ads/ads.service.d.ts +123 -0
  26. package/lib/services/ads/config.d.ts +69 -0
  27. package/lib/services/ads/index.d.ts +10 -0
  28. package/lib/services/ads/types.d.ts +163 -0
  29. package/package.json +1 -1
  30. package/public-api.d.ts +2 -0
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { EventEmitter, Component, Input, Output, Injectable, HostListener, inject, Pipe, ChangeDetectionStrategy, ViewChild, signal, computed, makeEnvironmentProviders, APP_INITIALIZER, ChangeDetectorRef, ElementRef, PLATFORM_ID, Inject, ErrorHandler, DestroyRef, InjectionToken, Optional, runInInjectionContext, effect } from '@angular/core';
2
+ import { EventEmitter, Component, Input, Output, Injectable, signal, makeEnvironmentProviders, APP_INITIALIZER, inject, HostListener, Pipe, ChangeDetectionStrategy, ViewChild, computed, ChangeDetectorRef, ElementRef, PLATFORM_ID, Inject, ErrorHandler, DestroyRef, InjectionToken, Optional, runInInjectionContext, effect, Injector } from '@angular/core';
3
3
  import * as i2$1 from '@ionic/angular/standalone';
4
4
  import { IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal, IonHeader, IonToolbar, IonContent, IonButtons, IonTitle, IonProgressBar, IonSkeletonText, IonFab, IonFabButton, IonFabList, IonLabel, IonCardContent, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCheckbox, IonTextarea, IonDatetime, IonDatetimeButton, IonInput, IonSelect, IonSelectOption, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonAccordion, IonAccordionGroup, IonItem, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonPopover, IonList, IonNote, ToastController as ToastController$1, IonCol, IonRow, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, AlertController } from '@ionic/angular/standalone';
5
5
  import * as i1 from '@angular/common';
@@ -245,6 +245,154 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
245
245
  type: Output
246
246
  }] } });
247
247
 
248
+ /**
249
+ * Servicio para gestionar presets de componentes.
250
+ *
251
+ * Los presets permiten definir configuraciones reutilizables
252
+ * de componentes (tamaño, color, variante, etc.) que se pueden
253
+ * aplicar con un nombre semántico.
254
+ *
255
+ * @example
256
+ * // En un componente
257
+ * presets = inject(PresetService);
258
+ *
259
+ * // Obtener preset
260
+ * const buttonProps = this.presets.get<ButtonMetadata>('button', 'primary-action');
261
+ * // { size: 'large', color: 'primary', fill: 'solid' }
262
+ */
263
+ class PresetService {
264
+ constructor() {
265
+ this._presets = signal({});
266
+ }
267
+ /**
268
+ * Obtiene un preset específico para un componente
269
+ *
270
+ * @param component Tipo de componente (ej: 'button', 'card', 'input')
271
+ * @param presetName Nombre del preset (ej: 'primary-action', 'compact')
272
+ * @returns Propiedades del preset o objeto vacío si no existe
273
+ *
274
+ * @example
275
+ * // Obtener preset de botón
276
+ * const props = presets.get<ButtonMetadata>('button', 'primary-action');
277
+ *
278
+ * // Usar en componente
279
+ * <val-button [props]="props"></val-button>
280
+ */
281
+ get(component, presetName) {
282
+ const componentPresets = this._presets()[component];
283
+ if (!componentPresets) {
284
+ console.warn(`[presets] No presets registered for component: ${component}`);
285
+ return {};
286
+ }
287
+ const preset = componentPresets[presetName];
288
+ if (!preset) {
289
+ console.warn(`[presets] Preset '${presetName}' not found for component: ${component}`);
290
+ return {};
291
+ }
292
+ return preset;
293
+ }
294
+ /**
295
+ * Verifica si existe un preset
296
+ */
297
+ has(component, presetName) {
298
+ return !!this._presets()[component]?.[presetName];
299
+ }
300
+ /**
301
+ * Registra presets de la aplicación
302
+ *
303
+ * @param presets Configuración de presets
304
+ *
305
+ * @example
306
+ * presets.registerPresets({
307
+ * button: {
308
+ * 'primary-action': { size: 'large', color: 'primary' },
309
+ * },
310
+ * card: {
311
+ * 'feature': { variant: 'elevated' },
312
+ * }
313
+ * });
314
+ */
315
+ registerPresets(presets) {
316
+ this._presets.set(presets);
317
+ }
318
+ /**
319
+ * Agrega presets para un componente específico (merge con existentes)
320
+ */
321
+ registerComponentPresets(component, presets) {
322
+ this._presets.update((current) => ({
323
+ ...current,
324
+ [component]: {
325
+ ...current[component],
326
+ ...presets,
327
+ },
328
+ }));
329
+ }
330
+ /**
331
+ * Obtiene todos los nombres de presets para un componente
332
+ */
333
+ getPresetNames(component) {
334
+ return Object.keys(this._presets()[component] || {});
335
+ }
336
+ /**
337
+ * Obtiene todos los componentes con presets registrados
338
+ */
339
+ getRegisteredComponents() {
340
+ return Object.keys(this._presets());
341
+ }
342
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
343
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, providedIn: 'root' }); }
344
+ }
345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, decorators: [{
346
+ type: Injectable,
347
+ args: [{ providedIn: 'root' }]
348
+ }] });
349
+
350
+ /**
351
+ * Configura el sistema de presets de Valtech Components.
352
+ *
353
+ * @param presets Configuración de presets por componente
354
+ * @returns Providers para agregar en app.config.ts
355
+ *
356
+ * @example
357
+ * // app.config.ts
358
+ * import { provideValtechPresets } from 'valtech-components';
359
+ *
360
+ * export const appConfig: ApplicationConfig = {
361
+ * providers: [
362
+ * provideValtechPresets({
363
+ * button: {
364
+ * 'primary-action': { size: 'large', color: 'primary', fill: 'solid' },
365
+ * 'secondary': { size: 'medium', color: 'secondary', fill: 'outline' },
366
+ * 'danger': { size: 'medium', color: 'danger', fill: 'solid' },
367
+ * },
368
+ * card: {
369
+ * 'feature': { variant: 'elevated', padding: 'large' },
370
+ * 'compact': { variant: 'flat', padding: 'small' },
371
+ * },
372
+ * input: {
373
+ * 'form-field': { size: 'medium', fill: 'outline', labelPosition: 'floating' },
374
+ * }
375
+ * }),
376
+ * ]
377
+ * };
378
+ */
379
+ function provideValtechPresets(presets) {
380
+ return makeEnvironmentProviders([
381
+ {
382
+ provide: APP_INITIALIZER,
383
+ useFactory: (presetService) => {
384
+ return () => {
385
+ presetService.registerPresets(presets);
386
+ };
387
+ },
388
+ deps: [PresetService],
389
+ multi: true,
390
+ },
391
+ ]);
392
+ }
393
+
394
+ // Service
395
+
248
396
  const ENABLED = 'ENABLED';
249
397
  const DISABLED = 'DISABLED';
250
398
  const WORKING = 'WORKING';
@@ -479,7 +627,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
479
627
  /**
480
628
  * val-button
481
629
  *
482
- * A customizable button supporting icons, loading state, and navigation.
630
+ * A customizable button supporting icons, loading state, navigation, and presets.
631
+ *
632
+ * @example With preset (recommended with i18n):
633
+ * <val-button preset="primary-action" [props]="{ text: 'Submit' | t }"></val-button>
483
634
  *
484
635
  * @example Static text:
485
636
  * <val-button [props]="{
@@ -490,41 +641,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
490
641
  * icon: { name: 'save', slot: 'start' }
491
642
  * }" (onClick)="handler()"></val-button>
492
643
  *
493
- * @input props: ButtonMetadata - Configuration for the button (text, color, icon, state, etc.)
644
+ * @input preset: string - Name of preset to apply (e.g., 'primary-action', 'danger')
645
+ * @input props: ButtonMetadata - Configuration for the button (overrides preset values)
494
646
  * @output onClick - Emits when the button is clicked
495
647
  */
496
648
  class ButtonComponent {
497
- constructor(download, icon, navigation) {
649
+ constructor(download, _icon, navigation) {
498
650
  this.download = download;
499
651
  this.navigation = navigation;
500
652
  this.states = ComponentStates;
653
+ this.presets = inject(PresetService);
501
654
  /**
502
655
  * The text to display on the button.
503
656
  */
504
657
  this.displayText = '';
658
+ /**
659
+ * Button configuration. Values here override preset values.
660
+ * When using presets, only partial props are needed (preset provides defaults).
661
+ */
662
+ this.props = {};
663
+ /**
664
+ * Resolved props after merging preset + explicit props.
665
+ * Preset values are overridden by explicit props.
666
+ */
667
+ this.resolvedProps = {};
505
668
  /**
506
669
  * Event emitted when the button is clicked.
507
670
  */
508
671
  this.onClick = new EventEmitter();
509
672
  }
510
673
  ngOnInit() {
674
+ this.resolveProps();
511
675
  this.setupDisplayText();
512
676
  }
677
+ ngOnChanges(changes) {
678
+ if (changes['preset'] || changes['props']) {
679
+ this.resolveProps();
680
+ this.setupDisplayText();
681
+ }
682
+ }
683
+ /**
684
+ * Merge preset configuration with explicit props.
685
+ * Explicit props take precedence over preset values.
686
+ */
687
+ resolveProps() {
688
+ const presetProps = this.preset
689
+ ? this.presets.get('button', this.preset)
690
+ : {};
691
+ // Merge: preset defaults < explicit props
692
+ this.resolvedProps = {
693
+ ...presetProps,
694
+ ...this.props,
695
+ };
696
+ }
513
697
  /**
514
- * Set up the text content based on the props configuration.
698
+ * Set up the text content based on the resolved props configuration.
515
699
  */
516
700
  setupDisplayText() {
517
- if (this.props.text) {
518
- if (this.props.contentInterpolation) {
519
- this.displayText = this.interpolateContent(this.props.text, this.props.contentInterpolation);
701
+ if (this.resolvedProps.text) {
702
+ if (this.resolvedProps.contentInterpolation) {
703
+ this.displayText = this.interpolateContent(this.resolvedProps.text, this.resolvedProps.contentInterpolation);
520
704
  }
521
705
  else {
522
- this.displayText = this.props.text;
706
+ this.displayText = this.resolvedProps.text;
523
707
  }
524
708
  }
525
- else if (this.props.contentFallback) {
709
+ else if (this.resolvedProps.contentFallback) {
526
710
  // Backwards compatibility: use fallback if text is not provided
527
- this.displayText = this.props.contentFallback;
711
+ this.displayText = this.resolvedProps.contentFallback;
528
712
  }
529
713
  else {
530
714
  this.displayText = '';
@@ -540,38 +724,38 @@ class ButtonComponent {
540
724
  });
541
725
  }
542
726
  clickHandler() {
543
- if (this.props.state === this.states.DISABLED) {
727
+ if (this.resolvedProps.state === this.states.DISABLED) {
544
728
  return;
545
729
  }
546
- if (this.props.actionType === ActionType.APP_NAVIGATION) {
547
- this.navigation.navigateByUrl(this.props.link);
730
+ if (this.resolvedProps.actionType === ActionType.APP_NAVIGATION) {
731
+ this.navigation.navigateByUrl(this.resolvedProps.link);
548
732
  }
549
- if (this.props.download) {
550
- this.download.downloadLinkFromBrowser(this.props.download);
733
+ if (this.resolvedProps.download) {
734
+ this.download.downloadLinkFromBrowser(this.resolvedProps.download);
551
735
  }
552
- if (this.props.handler) {
553
- this.props.handler(this.props.ref);
736
+ if (this.resolvedProps.handler) {
737
+ this.resolvedProps.handler(this.resolvedProps.ref);
554
738
  }
555
- this.onClick.emit(this.props.token);
739
+ this.onClick.emit(this.resolvedProps.token);
556
740
  }
557
741
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, deps: [{ token: DownloadService }, { token: IconService }, { token: NavigationService }], target: i0.ɵɵFactoryTarget.Component }); }
558
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "val-button", inputs: { props: "props" }, outputs: { onClick: "onClick" }, ngImport: i0, template: `
742
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "val-button", inputs: { preset: "preset", props: "props" }, outputs: { onClick: "onClick" }, usesOnChanges: true, ngImport: i0, template: `
559
743
  <ion-button
560
- [type]="props.type"
561
- [color]="props.color"
562
- [expand]="props.expand"
563
- [fill]="props.fill"
564
- [size]="props.size"
565
- [href]="props.href"
566
- [target]="props.target"
567
- [shape]="props.shape"
744
+ [type]="resolvedProps.type"
745
+ [color]="resolvedProps.color"
746
+ [expand]="resolvedProps.expand"
747
+ [fill]="resolvedProps.fill"
748
+ [size]="resolvedProps.size"
749
+ [href]="resolvedProps.href"
750
+ [target]="resolvedProps.target"
751
+ [shape]="resolvedProps.shape"
568
752
  (click)="clickHandler()"
569
- [disabled]="props.state === states.DISABLED"
570
- [ngClass]="props.size ? [props.size] : []"
753
+ [disabled]="resolvedProps.state === states.DISABLED"
754
+ [ngClass]="resolvedProps.size ? [resolvedProps.size] : []"
571
755
  >
572
- <ion-icon *ngIf="props.icon" [slot]="props.icon.slot" [name]="props.icon.name"></ion-icon>
573
- <ion-spinner *ngIf="props.state === states.WORKING" name="circular"></ion-spinner>
574
- <ion-text *ngIf="props.state !== states.WORKING">{{ displayText }}</ion-text>
756
+ <ion-icon *ngIf="resolvedProps.icon" [slot]="resolvedProps.icon.slot" [name]="resolvedProps.icon.name"></ion-icon>
757
+ <ion-spinner *ngIf="resolvedProps.state === states.WORKING" name="circular"></ion-spinner>
758
+ <ion-text *ngIf="resolvedProps.state !== states.WORKING">{{ displayText }}</ion-text>
575
759
  </ion-button>
576
760
  `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }] }); }
577
761
  }
@@ -579,24 +763,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
579
763
  type: Component,
580
764
  args: [{ selector: 'val-button', standalone: true, imports: [CommonModule, IonButton, IonIcon, IonSpinner, IonText], template: `
581
765
  <ion-button
582
- [type]="props.type"
583
- [color]="props.color"
584
- [expand]="props.expand"
585
- [fill]="props.fill"
586
- [size]="props.size"
587
- [href]="props.href"
588
- [target]="props.target"
589
- [shape]="props.shape"
766
+ [type]="resolvedProps.type"
767
+ [color]="resolvedProps.color"
768
+ [expand]="resolvedProps.expand"
769
+ [fill]="resolvedProps.fill"
770
+ [size]="resolvedProps.size"
771
+ [href]="resolvedProps.href"
772
+ [target]="resolvedProps.target"
773
+ [shape]="resolvedProps.shape"
590
774
  (click)="clickHandler()"
591
- [disabled]="props.state === states.DISABLED"
592
- [ngClass]="props.size ? [props.size] : []"
775
+ [disabled]="resolvedProps.state === states.DISABLED"
776
+ [ngClass]="resolvedProps.size ? [resolvedProps.size] : []"
593
777
  >
594
- <ion-icon *ngIf="props.icon" [slot]="props.icon.slot" [name]="props.icon.name"></ion-icon>
595
- <ion-spinner *ngIf="props.state === states.WORKING" name="circular"></ion-spinner>
596
- <ion-text *ngIf="props.state !== states.WORKING">{{ displayText }}</ion-text>
778
+ <ion-icon *ngIf="resolvedProps.icon" [slot]="resolvedProps.icon.slot" [name]="resolvedProps.icon.name"></ion-icon>
779
+ <ion-spinner *ngIf="resolvedProps.state === states.WORKING" name="circular"></ion-spinner>
780
+ <ion-text *ngIf="resolvedProps.state !== states.WORKING">{{ displayText }}</ion-text>
597
781
  </ion-button>
598
782
  `, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}\n"] }]
599
- }], ctorParameters: () => [{ type: DownloadService }, { type: IconService }, { type: NavigationService }], propDecorators: { props: [{
783
+ }], ctorParameters: () => [{ type: DownloadService }, { type: IconService }, { type: NavigationService }], propDecorators: { preset: [{
784
+ type: Input
785
+ }], props: [{
600
786
  type: Input
601
787
  }], onClick: [{
602
788
  type: Output
@@ -4191,7 +4377,7 @@ class ButtonGroupComponent {
4191
4377
  [ngStyle]="{ width: props.buttons.length === 1 ? '100%' : 'auto' }"
4192
4378
  ></val-button>
4193
4379
  </div>
4194
- `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}val-button{display:inline-block;margin-top:.25rem;margin-right:.25rem}.group-container{width:100%}.group-container.left{text-align:left}.group-container.center{text-align:center}.group-container.right{text-align:right}.group-container.column{display:flex;flex-direction:column-reverse}.group-container.spaced{display:flex;justify-content:space-between}.group-container.leftocenter{text-align:left}@media (min-width: 768px){.group-container.leftocenter{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
4380
+ `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}val-button{display:inline-block;margin-top:.25rem;margin-right:.25rem}.group-container{width:100%}.group-container.left{text-align:left}.group-container.center{text-align:center}.group-container.right{text-align:right}.group-container.column{display:flex;flex-direction:column-reverse}.group-container.spaced{display:flex;justify-content:space-between}.group-container.leftocenter{text-align:left}@media (min-width: 768px){.group-container.leftocenter{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
4195
4381
  }
4196
4382
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonGroupComponent, decorators: [{
4197
4383
  type: Component,
@@ -4411,7 +4597,7 @@ class CardComponent {
4411
4597
  </ng-container>
4412
4598
  </ion-buttons>
4413
4599
  </ion-card>
4414
- `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-card.tapable{transition:transform .3s ease,box-shadow .3s ease}ion-card.tapable:hover{transform:scale(1.01);box-shadow:.1875rem .625rem .5rem #1219541a}.tapable{cursor:pointer}.checker{display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-header{padding:10px;display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-content{padding-left:10px;padding-top:4px;padding-bottom:10px}.complex{border-radius:16px}img{border-radius:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
4600
+ `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-card.tapable{transition:transform .3s ease,box-shadow .3s ease}ion-card.tapable:hover{transform:scale(1.01);box-shadow:.1875rem .625rem .5rem #1219541a}.tapable{cursor:pointer}.checker{display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-header{padding:10px;display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-content{padding-left:10px;padding-top:4px;padding-bottom:10px}.complex{border-radius:16px}img{border-radius:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
4415
4601
  }
4416
4602
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, decorators: [{
4417
4603
  type: Component,
@@ -4979,7 +5165,7 @@ class FileInputComponent {
4979
5165
  </div>
4980
5166
  <val-button [props]="contrastButton" (onClick)="fileInput.click()"></val-button>
4981
5167
  </div>
4982
- `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.file-container{margin-top:.25rem}.name-container{display:flex;flex-direction:row;align-items:center}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
5168
+ `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.file-container{margin-top:.25rem}.name-container{display:flex;flex-direction:row;align-items:center}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
4983
5169
  }
4984
5170
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileInputComponent, decorators: [{
4985
5171
  type: Component,
@@ -7835,7 +8021,7 @@ class ActionHeaderComponent {
7835
8021
  <val-display [props]="props.title" />
7836
8022
  <val-button [props]="props.action" />
7837
8023
  </section>
7838
- `, isInline: true, styles: [".header-content-container{width:100%;display:flex;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
8024
+ `, isInline: true, styles: [".header-content-container{width:100%;display:flex;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
7839
8025
  }
7840
8026
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ActionHeaderComponent, decorators: [{
7841
8027
  type: Component,
@@ -13628,7 +13814,7 @@ class RaffleStatusCardComponent {
13628
13814
  }
13629
13815
  </div>
13630
13816
  </article>
13631
- `, isInline: true, styles: [":host{display:block}.raffle-card{display:flex;flex-direction:column;background:var(--ion-color-light);border-radius:12px;overflow:hidden;box-shadow:0 2px 8px #00000014;transition:transform .2s,box-shadow .2s}.raffle-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.card-image{position:relative;aspect-ratio:16/9;overflow:hidden}.card-image val-image{width:100%;height:100%}.card-image val-image ::ng-deep img{width:100%;height:100%;object-fit:cover}.status-badge{position:absolute;top:12px;left:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px}.status-badge.inline{position:static;margin-bottom:8px}.countdown-overlay{position:absolute;bottom:0;left:0;right:0;padding:8px 12px;background:linear-gradient(transparent,#000000b3)}.card-content{display:flex;flex-direction:column;gap:12px;padding:16px}.card-header{display:flex;flex-direction:column;gap:4px}.card-title{font-size:18px;font-weight:600;color:var(--ion-color-dark);margin:0;line-height:1.3}.card-description{font-size:14px;color:var(--ion-color-medium-shade);margin:0;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.beneficiary-info{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ion-color-medium-shade)}.beneficiary-info ion-icon{font-size:16px;color:var(--ion-color-danger)}.beneficiary-info .beneficiary-image{width:24px;height:24px;border-radius:50%;overflow:hidden}.prize-preview{padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.prize-header{display:flex;align-items:center;gap:4px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium);margin-bottom:4px}.prize-header ion-icon{font-size:14px;color:var(--ion-color-warning)}.prize-name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.prize-value{font-size:18px;font-weight:700;color:var(--ion-color-success);margin-top:4px}.progress-section{display:flex;flex-direction:column;gap:6px}.progress-header{display:flex;justify-content:space-between;align-items:center}.progress-label{font-size:12px;color:var(--ion-color-medium-shade)}.progress-value{font-size:14px;font-weight:600;color:var(--ion-color-dark)}ion-progress-bar{height:8px;border-radius:4px;--background: var(--ion-color-light-shade)}.ticket-stats{display:flex;align-items:center;justify-content:center;gap:16px;padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.stat{display:flex;align-items:center;gap:6px;font-size:13px}.stat ion-icon{font-size:18px;color:var(--ion-color-primary)}.stat-value{font-weight:700;color:var(--ion-color-dark);font-size:16px}.stat-label{color:var(--ion-color-medium-shade)}.stat-divider{width:1px;height:24px;background:var(--ion-color-medium-tint)}.price-section{display:flex;justify-content:center;padding:8px 0}.dates-section{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--ion-color-medium-shade)}.date-item{display:flex;align-items:center;gap:6px}.date-item ion-icon{font-size:16px;color:var(--ion-color-primary)}.card-actions{display:flex;gap:8px;margin-top:4px}.card-actions val-button{flex:1}.variant-featured{border:2px solid var(--ion-color-warning)}.variant-featured .card-image{aspect-ratio:2/1}.variant-featured .card-title{font-size:22px}.variant-compact .card-content{padding:12px;gap:8px}.variant-compact .card-title{font-size:15px}.variant-compact .card-description{font-size:13px;-webkit-line-clamp:1}.variant-compact .ticket-stats,.variant-compact .dates-section{display:none}.variant-horizontal{flex-direction:row}.variant-horizontal .card-image{width:40%;min-width:150px;aspect-ratio:1}.variant-horizontal .card-content{flex:1;justify-content:center}.size-small .card-content{padding:12px;gap:8px}.size-small .card-title{font-size:15px}.size-small .card-description{font-size:12px}.size-small .ticket-stats{padding:8px}.size-small .stat-value{font-size:14px}.size-large .card-content{padding:24px;gap:16px}.size-large .card-title{font-size:24px}.size-large .card-description{font-size:16px}.size-large .prize-name{font-size:18px}.size-large .prize-value{font-size:22px}.status-upcoming{opacity:.9}.status-sold_out .card-image:after{content:\"\";position:absolute;inset:0;background:#0000004d}.status-completed .card-actions{opacity:.5;pointer-events:none}.status-cancelled{opacity:.6}.status-cancelled .card-image:after{content:\"\";position:absolute;inset:0;background:#0006}@media (max-width: 480px){.variant-horizontal{flex-direction:column}.variant-horizontal .card-image{width:100%;aspect-ratio:16/9}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: CountdownComponent, selector: "val-countdown", inputs: ["props"], outputs: ["complete", "tick"] }, { kind: "component", type: PriceTagComponent, selector: "val-price-tag", inputs: ["props"] }] }); }
13817
+ `, isInline: true, styles: [":host{display:block}.raffle-card{display:flex;flex-direction:column;background:var(--ion-color-light);border-radius:12px;overflow:hidden;box-shadow:0 2px 8px #00000014;transition:transform .2s,box-shadow .2s}.raffle-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.card-image{position:relative;aspect-ratio:16/9;overflow:hidden}.card-image val-image{width:100%;height:100%}.card-image val-image ::ng-deep img{width:100%;height:100%;object-fit:cover}.status-badge{position:absolute;top:12px;left:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px}.status-badge.inline{position:static;margin-bottom:8px}.countdown-overlay{position:absolute;bottom:0;left:0;right:0;padding:8px 12px;background:linear-gradient(transparent,#000000b3)}.card-content{display:flex;flex-direction:column;gap:12px;padding:16px}.card-header{display:flex;flex-direction:column;gap:4px}.card-title{font-size:18px;font-weight:600;color:var(--ion-color-dark);margin:0;line-height:1.3}.card-description{font-size:14px;color:var(--ion-color-medium-shade);margin:0;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.beneficiary-info{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ion-color-medium-shade)}.beneficiary-info ion-icon{font-size:16px;color:var(--ion-color-danger)}.beneficiary-info .beneficiary-image{width:24px;height:24px;border-radius:50%;overflow:hidden}.prize-preview{padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.prize-header{display:flex;align-items:center;gap:4px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium);margin-bottom:4px}.prize-header ion-icon{font-size:14px;color:var(--ion-color-warning)}.prize-name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.prize-value{font-size:18px;font-weight:700;color:var(--ion-color-success);margin-top:4px}.progress-section{display:flex;flex-direction:column;gap:6px}.progress-header{display:flex;justify-content:space-between;align-items:center}.progress-label{font-size:12px;color:var(--ion-color-medium-shade)}.progress-value{font-size:14px;font-weight:600;color:var(--ion-color-dark)}ion-progress-bar{height:8px;border-radius:4px;--background: var(--ion-color-light-shade)}.ticket-stats{display:flex;align-items:center;justify-content:center;gap:16px;padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.stat{display:flex;align-items:center;gap:6px;font-size:13px}.stat ion-icon{font-size:18px;color:var(--ion-color-primary)}.stat-value{font-weight:700;color:var(--ion-color-dark);font-size:16px}.stat-label{color:var(--ion-color-medium-shade)}.stat-divider{width:1px;height:24px;background:var(--ion-color-medium-tint)}.price-section{display:flex;justify-content:center;padding:8px 0}.dates-section{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--ion-color-medium-shade)}.date-item{display:flex;align-items:center;gap:6px}.date-item ion-icon{font-size:16px;color:var(--ion-color-primary)}.card-actions{display:flex;gap:8px;margin-top:4px}.card-actions val-button{flex:1}.variant-featured{border:2px solid var(--ion-color-warning)}.variant-featured .card-image{aspect-ratio:2/1}.variant-featured .card-title{font-size:22px}.variant-compact .card-content{padding:12px;gap:8px}.variant-compact .card-title{font-size:15px}.variant-compact .card-description{font-size:13px;-webkit-line-clamp:1}.variant-compact .ticket-stats,.variant-compact .dates-section{display:none}.variant-horizontal{flex-direction:row}.variant-horizontal .card-image{width:40%;min-width:150px;aspect-ratio:1}.variant-horizontal .card-content{flex:1;justify-content:center}.size-small .card-content{padding:12px;gap:8px}.size-small .card-title{font-size:15px}.size-small .card-description{font-size:12px}.size-small .ticket-stats{padding:8px}.size-small .stat-value{font-size:14px}.size-large .card-content{padding:24px;gap:16px}.size-large .card-title{font-size:24px}.size-large .card-description{font-size:16px}.size-large .prize-name{font-size:18px}.size-large .prize-value{font-size:22px}.status-upcoming{opacity:.9}.status-sold_out .card-image:after{content:\"\";position:absolute;inset:0;background:#0000004d}.status-completed .card-actions{opacity:.5;pointer-events:none}.status-cancelled{opacity:.6}.status-cancelled .card-image:after{content:\"\";position:absolute;inset:0;background:#0006}@media (max-width: 480px){.variant-horizontal{flex-direction:column}.variant-horizontal .card-image{width:100%;aspect-ratio:16/9}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: CountdownComponent, selector: "val-countdown", inputs: ["props"], outputs: ["complete", "tick"] }, { kind: "component", type: PriceTagComponent, selector: "val-price-tag", inputs: ["props"] }] }); }
13632
13818
  }
13633
13819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RaffleStatusCardComponent, decorators: [{
13634
13820
  type: Component,
@@ -15978,7 +16164,7 @@ class ArticleComponent {
15978
16164
  </ng-container>
15979
16165
  </div>
15980
16166
  </article>
15981
- `, isInline: true, styles: [".val-article{width:100%;margin:0 auto;line-height:1.6;color:var(--ion-color-dark, #1f2937);display:block;position:static}.val-article--centered{margin:0 auto}.val-article--light{background-color:var(--ion-color-light, #ffffff);color:var(--ion-color-dark, #1f2937)}.val-article--dark{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article__element{position:relative}.val-article__element:last-child{margin-bottom:0!important}.val-article__spacing-top--none{margin-top:0!important}.val-article__spacing-top--small{margin-top:.5rem!important}.val-article__spacing-top--medium{margin-top:1rem!important}.val-article__spacing-top--large{margin-top:1.5rem!important}.val-article__spacing-top--xlarge{margin-top:2.5rem!important}.val-article__spacing-bottom--none{margin-bottom:0!important}.val-article__spacing-bottom--small{margin-bottom:.5rem!important}.val-article__spacing-bottom--medium{margin-bottom:1rem!important}.val-article__spacing-bottom--large{margin-bottom:1.5rem!important}.val-article__spacing-bottom--xlarge{margin-bottom:2.5rem!important}.val-article__spacing-horizontal--none{margin-left:0!important;margin-right:0!important}.val-article__spacing-horizontal--small{margin-left:.5rem!important;margin-right:.5rem!important}.val-article__spacing-horizontal--medium{margin-left:1rem!important;margin-right:1rem!important}.val-article__spacing-horizontal--large{margin-left:1.5rem!important;margin-right:1.5rem!important}.val-article__spacing-horizontal--xlarge{margin-left:2.5rem!important;margin-right:2.5rem!important}.val-article__quote{border-left:4px solid #0969da;padding:1rem;background-color:var(--ion-color-light-shade, #f8f9fa);border-radius:0 8px 8px 0;font-style:italic}.val-article__quote-content{margin-bottom:.5rem;font-size:1.1em}.val-article__quote-author{font-size:.9em;color:var(--ion-color-medium, #6c757d);font-style:normal;font-weight:500;text-align:right}.val-article__quote-source{font-weight:400;opacity:.8}.val-article__highlight{padding:1rem;background-color:var(--ion-color-warning-tint, #fff3cd);border:1px solid var(--ion-color-warning, #ffc107);border-radius:4px}.val-article__highlight--rounded{border-radius:12px}.val-article__code{border-radius:8px;overflow:hidden;border:1px solid #e1e5e9}.val-article__code-language{background-color:var(--ion-color-medium-tint, #f8f9fa);padding:.5rem 1rem;font-size:.875em;font-weight:500;color:var(--ion-color-dark, #495057);border-bottom:1px solid #e1e5e9;text-transform:uppercase;letter-spacing:.5px}.val-article__code-content{background-color:#f6f8fa;color:#24292e;padding:1rem;margin:0;overflow-x:auto;font-family:SFMono-Regular,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;line-height:1.5}.val-article__code-content code{background:none;padding:0;font-size:inherit;color:inherit}.val-article__code-content--dark{background-color:#161b22;color:#f0f6fc}.val-article__list{padding-left:1.5rem}.val-article__list--ordered{list-style-type:decimal}.val-article__list-item{margin-bottom:.5rem;line-height:1.6;position:relative}.val-article__list-item:last-child{margin-bottom:0}.val-article__list-check{color:var(--ion-color-success, #28a745);font-weight:700;margin-right:.5rem;position:absolute;left:-1.5rem}.val-article .val-article__list:has(.val-article__list-check){list-style:none;padding-left:1rem}.val-article__button-container--left{text-align:left}.val-article__button-container--center{text-align:center}.val-article__button-container--right{text-align:right}.val-article__separator{text-align:center}.val-article__separator-line{border:none;height:1px;background-color:#d0d7de;margin:0}.val-article__separator-line--thin{height:1px}.val-article__separator-line--thick{height:3px}.val-article__separator-dots{color:#d0d7de;font-size:1.5em;letter-spacing:.5em;-webkit-user-select:none;user-select:none}.val-article__separator-space{height:0}.val-article__image{text-align:center}.val-article__image--left{text-align:left}.val-article__image--center{text-align:center}.val-article__image--right{text-align:right}.val-article__image-content{max-width:100%;height:auto;border-radius:4px;box-shadow:0 2px 8px #0000001a;transition:transform .2s ease}.val-article__image-content:hover{transform:scale(1.02)}.val-article__image-content--rounded{border-radius:12px}.val-article__image-caption{margin-top:.5rem;font-size:.875em;color:var(--ion-color-medium, #6c757d);font-style:italic;text-align:center}.val-article__video{text-align:center}.val-article__video-content{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px #00000026}.val-article__video-title{margin-top:.5rem;font-size:.9em;color:var(--ion-color-medium, #6c757d);font-weight:500}.val-article__custom *{max-width:100%}@media (max-width: 768px){.val-article__code-content{font-size:.8em;padding:.5rem}.val-article__image-content:hover{transform:none}.val-article__quote,.val-article__highlight{padding:.5rem}}@media (prefers-color-scheme: dark){.val-article--auto{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article--auto .val-article__quote{background-color:#ffffff0d}.val-article--auto .val-article__highlight{background-color:#ffc1071a;border-color:var(--ion-color-warning-shade, #e0a800)}.val-article--auto .val-article__code-content{background-color:#161b22;color:#f0f6fc}}.val-article__element{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@media (prefers-contrast: high){.val-article__separator-line{background-color:currentColor;opacity:.5}.val-article__quote{border-left-width:6px}}@media (prefers-reduced-motion: reduce){.val-article .val-article__element{animation:none}.val-article__image-content{transition:none}.val-article__image-content:hover{transform:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: CodeDisplayComponent, selector: "val-code-display", inputs: ["props"] }, { kind: "component", type: NotesBoxComponent, selector: "val-notes-box", inputs: ["props"] }, { kind: "component", type: PlainCodeBoxComponent, selector: "val-plain-code-box", inputs: ["props"] }] }); }
16167
+ `, isInline: true, styles: [".val-article{width:100%;margin:0 auto;line-height:1.6;color:var(--ion-color-dark, #1f2937);display:block;position:static}.val-article--centered{margin:0 auto}.val-article--light{background-color:var(--ion-color-light, #ffffff);color:var(--ion-color-dark, #1f2937)}.val-article--dark{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article__element{position:relative}.val-article__element:last-child{margin-bottom:0!important}.val-article__spacing-top--none{margin-top:0!important}.val-article__spacing-top--small{margin-top:.5rem!important}.val-article__spacing-top--medium{margin-top:1rem!important}.val-article__spacing-top--large{margin-top:1.5rem!important}.val-article__spacing-top--xlarge{margin-top:2.5rem!important}.val-article__spacing-bottom--none{margin-bottom:0!important}.val-article__spacing-bottom--small{margin-bottom:.5rem!important}.val-article__spacing-bottom--medium{margin-bottom:1rem!important}.val-article__spacing-bottom--large{margin-bottom:1.5rem!important}.val-article__spacing-bottom--xlarge{margin-bottom:2.5rem!important}.val-article__spacing-horizontal--none{margin-left:0!important;margin-right:0!important}.val-article__spacing-horizontal--small{margin-left:.5rem!important;margin-right:.5rem!important}.val-article__spacing-horizontal--medium{margin-left:1rem!important;margin-right:1rem!important}.val-article__spacing-horizontal--large{margin-left:1.5rem!important;margin-right:1.5rem!important}.val-article__spacing-horizontal--xlarge{margin-left:2.5rem!important;margin-right:2.5rem!important}.val-article__quote{border-left:4px solid #0969da;padding:1rem;background-color:var(--ion-color-light-shade, #f8f9fa);border-radius:0 8px 8px 0;font-style:italic}.val-article__quote-content{margin-bottom:.5rem;font-size:1.1em}.val-article__quote-author{font-size:.9em;color:var(--ion-color-medium, #6c757d);font-style:normal;font-weight:500;text-align:right}.val-article__quote-source{font-weight:400;opacity:.8}.val-article__highlight{padding:1rem;background-color:var(--ion-color-warning-tint, #fff3cd);border:1px solid var(--ion-color-warning, #ffc107);border-radius:4px}.val-article__highlight--rounded{border-radius:12px}.val-article__code{border-radius:8px;overflow:hidden;border:1px solid #e1e5e9}.val-article__code-language{background-color:var(--ion-color-medium-tint, #f8f9fa);padding:.5rem 1rem;font-size:.875em;font-weight:500;color:var(--ion-color-dark, #495057);border-bottom:1px solid #e1e5e9;text-transform:uppercase;letter-spacing:.5px}.val-article__code-content{background-color:#f6f8fa;color:#24292e;padding:1rem;margin:0;overflow-x:auto;font-family:SFMono-Regular,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;line-height:1.5}.val-article__code-content code{background:none;padding:0;font-size:inherit;color:inherit}.val-article__code-content--dark{background-color:#161b22;color:#f0f6fc}.val-article__list{padding-left:1.5rem}.val-article__list--ordered{list-style-type:decimal}.val-article__list-item{margin-bottom:.5rem;line-height:1.6;position:relative}.val-article__list-item:last-child{margin-bottom:0}.val-article__list-check{color:var(--ion-color-success, #28a745);font-weight:700;margin-right:.5rem;position:absolute;left:-1.5rem}.val-article .val-article__list:has(.val-article__list-check){list-style:none;padding-left:1rem}.val-article__button-container--left{text-align:left}.val-article__button-container--center{text-align:center}.val-article__button-container--right{text-align:right}.val-article__separator{text-align:center}.val-article__separator-line{border:none;height:1px;background-color:#d0d7de;margin:0}.val-article__separator-line--thin{height:1px}.val-article__separator-line--thick{height:3px}.val-article__separator-dots{color:#d0d7de;font-size:1.5em;letter-spacing:.5em;-webkit-user-select:none;user-select:none}.val-article__separator-space{height:0}.val-article__image{text-align:center}.val-article__image--left{text-align:left}.val-article__image--center{text-align:center}.val-article__image--right{text-align:right}.val-article__image-content{max-width:100%;height:auto;border-radius:4px;box-shadow:0 2px 8px #0000001a;transition:transform .2s ease}.val-article__image-content:hover{transform:scale(1.02)}.val-article__image-content--rounded{border-radius:12px}.val-article__image-caption{margin-top:.5rem;font-size:.875em;color:var(--ion-color-medium, #6c757d);font-style:italic;text-align:center}.val-article__video{text-align:center}.val-article__video-content{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px #00000026}.val-article__video-title{margin-top:.5rem;font-size:.9em;color:var(--ion-color-medium, #6c757d);font-weight:500}.val-article__custom *{max-width:100%}@media (max-width: 768px){.val-article__code-content{font-size:.8em;padding:.5rem}.val-article__image-content:hover{transform:none}.val-article__quote,.val-article__highlight{padding:.5rem}}@media (prefers-color-scheme: dark){.val-article--auto{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article--auto .val-article__quote{background-color:#ffffff0d}.val-article--auto .val-article__highlight{background-color:#ffc1071a;border-color:var(--ion-color-warning-shade, #e0a800)}.val-article--auto .val-article__code-content{background-color:#161b22;color:#f0f6fc}}.val-article__element{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@media (prefers-contrast: high){.val-article__separator-line{background-color:currentColor;opacity:.5}.val-article__quote{border-left-width:6px}}@media (prefers-reduced-motion: reduce){.val-article .val-article__element{animation:none}.val-article__image-content{transition:none}.val-article__image-content:hover{transform:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: CodeDisplayComponent, selector: "val-code-display", inputs: ["props"] }, { kind: "component", type: NotesBoxComponent, selector: "val-notes-box", inputs: ["props"] }, { kind: "component", type: PlainCodeBoxComponent, selector: "val-plain-code-box", inputs: ["props"] }] }); }
15982
16168
  }
15983
16169
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ArticleComponent, decorators: [{
15984
16170
  type: Component,
@@ -19914,7 +20100,7 @@ class MenuComponent {
19914
20100
  />
19915
20101
  }
19916
20102
  </ion-menu>
19917
- `, isInline: true, styles: ["ion-menu{--min-width: 75%}@media (min-width: 768px){ion-menu{--min-width: 40%}}ion-menu ion-header{background:var(--ion-color-light)}ion-menu ion-content{--background: var(--ion-color-light)}.menu-close-button{display:block;margin:1rem .5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
20103
+ `, isInline: true, styles: ["ion-menu{--min-width: 75%}@media (min-width: 768px){ion-menu{--min-width: 40%}}ion-menu ion-header{background:var(--ion-color-light)}ion-menu ion-content{--background: var(--ion-color-light)}.menu-close-button{display:block;margin:1rem .5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
19918
20104
  }
19919
20105
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MenuComponent, decorators: [{
19920
20106
  type: Component,
@@ -20395,7 +20581,7 @@ class PageTemplateComponent {
20395
20581
  </ion-row>
20396
20582
  }
20397
20583
  </ion-grid>
20398
- `, isInline: true, styles: [".page-title{padding:0;font-size:1.6rem;font-weight:800}@media (min-width: 768px){.page-title{font-size:2rem}}.description-row{margin-bottom:16px}.description-container{margin-top:1rem}.back-row{margin-bottom:16px}.back-button{display:block;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: ExpandableTextComponent, selector: "val-expandable-text", inputs: ["props"] }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
20584
+ `, isInline: true, styles: [".page-title{padding:0;font-size:1.6rem;font-weight:800}@media (min-width: 768px){.page-title{font-size:2rem}}.description-row{margin-bottom:16px}.description-container{margin-top:1rem}.back-row{margin-bottom:16px}.back-button{display:block;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: ExpandableTextComponent, selector: "val-expandable-text", inputs: ["props"] }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
20399
20585
  }
20400
20586
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageTemplateComponent, decorators: [{
20401
20587
  type: Component,
@@ -22075,6 +22261,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
22075
22261
  args: [PLATFORM_ID]
22076
22262
  }] }] });
22077
22263
 
22264
+ var analytics_service = /*#__PURE__*/Object.freeze({
22265
+ __proto__: null,
22266
+ AnalyticsService: AnalyticsService
22267
+ });
22268
+
22078
22269
  /**
22079
22270
  * Analytics Error Handler
22080
22271
  *
@@ -28122,152 +28313,1137 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
28122
28313
  // Tipos
28123
28314
 
28124
28315
  /**
28125
- * Servicio para gestionar presets de componentes.
28316
+ * Ads Types
28126
28317
  *
28127
- * Los presets permiten definir configuraciones reutilizables
28128
- * de componentes (tamaño, color, variante, etc.) que se pueden
28129
- * aplicar con un nombre semántico.
28318
+ * Tipos e interfaces para el servicio de Google Ad Manager.
28319
+ * Usa Google Publisher Tags (GPT) para renderizar ads.
28320
+ */
28321
+ /**
28322
+ * Mapeo de tamanos predefinidos a dimensiones.
28323
+ */
28324
+ const AD_SIZE_MAP = {
28325
+ leaderboard: [728, 90],
28326
+ billboard: [970, 250],
28327
+ 'medium-rectangle': [300, 250],
28328
+ 'large-rectangle': [336, 280],
28329
+ 'half-page': [300, 600],
28330
+ skyscraper: [160, 600],
28331
+ 'mobile-banner': [320, 50],
28332
+ 'mobile-leaderboard': [320, 100],
28333
+ fluid: 'fluid',
28334
+ native: 'fluid',
28335
+ custom: 'fluid',
28336
+ };
28337
+
28338
+ /**
28339
+ * Ads Consent Service
28130
28340
  *
28131
- * @example
28132
- * // En un componente
28133
- * presets = inject(PresetService);
28341
+ * Integra el servicio de Ads con el Consent Mode v2 existente en AnalyticsService.
28342
+ * Proporciona estado de consent especifico para ads (advertising).
28343
+ */
28344
+ /**
28345
+ * Servicio que maneja el consent para ads.
28134
28346
  *
28135
- * // Obtener preset
28136
- * const buttonProps = this.presets.get<ButtonMetadata>('button', 'primary-action');
28137
- * // { size: 'large', color: 'primary', fill: 'solid' }
28347
+ * Se integra con AnalyticsService para reutilizar el estado de consent GDPR.
28348
+ * Si AnalyticsService no esta disponible, usa valores por defecto conservadores.
28138
28349
  */
28139
- class PresetService {
28350
+ class AdsConsentService {
28140
28351
  constructor() {
28141
- this._presets = signal({});
28352
+ this.platformId = inject(PLATFORM_ID);
28353
+ this.injector = inject(Injector);
28354
+ // Cache del estado de consent
28355
+ this._consentState = signal({
28356
+ adStorage: false,
28357
+ adPersonalization: false,
28358
+ adUserData: false,
28359
+ });
28360
+ /** Estado de consent para ads (readonly) */
28361
+ this.adsConsentState = this._consentState.asReadonly();
28362
+ /** Indica si se pueden mostrar ads (al menos consent basico) */
28363
+ this.canShowAds = computed(() => {
28364
+ // Siempre permitimos mostrar ads (pueden ser no personalizados)
28365
+ // El control real esta en canPersonalize
28366
+ return true;
28367
+ });
28368
+ /** Indica si se pueden mostrar ads personalizados */
28369
+ this.canPersonalize = computed(() => {
28370
+ const state = this._consentState();
28371
+ return state.adStorage && state.adPersonalization && state.adUserData;
28372
+ });
28373
+ this.analyticsService = null;
28374
+ this.consentSyncInterval = null;
28375
+ if (isPlatformBrowser(this.platformId)) {
28376
+ this.initializeConsentSync();
28377
+ }
28142
28378
  }
28143
28379
  /**
28144
- * Obtiene un preset específico para un componente
28145
- *
28146
- * @param component Tipo de componente (ej: 'button', 'card', 'input')
28147
- * @param presetName Nombre del preset (ej: 'primary-action', 'compact')
28148
- * @returns Propiedades del preset o objeto vacío si no existe
28149
- *
28150
- * @example
28151
- * // Obtener preset de botón
28152
- * const props = presets.get<ButtonMetadata>('button', 'primary-action');
28153
- *
28154
- * // Usar en componente
28155
- * <val-button [props]="props"></val-button>
28380
+ * Inicializa la sincronizacion con AnalyticsService.
28156
28381
  */
28157
- get(component, presetName) {
28158
- const componentPresets = this._presets()[component];
28159
- if (!componentPresets) {
28160
- console.warn(`[presets] No presets registered for component: ${component}`);
28161
- return {};
28382
+ initializeConsentSync() {
28383
+ // Intentar obtener AnalyticsService de forma lazy
28384
+ // Usamos setTimeout para dar tiempo a que se inicialice
28385
+ setTimeout(() => {
28386
+ this.syncWithAnalyticsConsent();
28387
+ }, 100);
28388
+ // Re-sincronizar periodicamente por si el usuario cambia consent
28389
+ this.consentSyncInterval = setInterval(() => {
28390
+ this.syncWithAnalyticsConsent();
28391
+ }, 5000);
28392
+ }
28393
+ /**
28394
+ * Sincroniza el estado de consent desde AnalyticsService.
28395
+ */
28396
+ async syncWithAnalyticsConsent() {
28397
+ try {
28398
+ // Importar AnalyticsService dinamicamente
28399
+ const { AnalyticsService } = await Promise.resolve().then(function () { return analytics_service; });
28400
+ if (!this.analyticsService) {
28401
+ this.analyticsService = this.injector.get(AnalyticsService, null);
28402
+ }
28403
+ if (this.analyticsService) {
28404
+ // AnalyticsService expone consentState como signal
28405
+ const consentState = this.analyticsService.consentState();
28406
+ this.updateFromAnalyticsConsent(consentState.settings);
28407
+ }
28162
28408
  }
28163
- const preset = componentPresets[presetName];
28164
- if (!preset) {
28165
- console.warn(`[presets] Preset '${presetName}' not found for component: ${component}`);
28166
- return {};
28409
+ catch {
28410
+ // AnalyticsService no disponible - usar defaults
28411
+ this.setDefaultConsent();
28167
28412
  }
28168
- return preset;
28169
28413
  }
28170
28414
  /**
28171
- * Verifica si existe un preset
28415
+ * Actualiza el estado de consent desde AnalyticsService.
28172
28416
  */
28173
- has(component, presetName) {
28174
- return !!this._presets()[component]?.[presetName];
28417
+ updateFromAnalyticsConsent(settings) {
28418
+ const hasAdvertisingConsent = settings['advertising'] === true;
28419
+ this._consentState.set({
28420
+ adStorage: hasAdvertisingConsent,
28421
+ adPersonalization: hasAdvertisingConsent,
28422
+ adUserData: hasAdvertisingConsent,
28423
+ });
28175
28424
  }
28176
28425
  /**
28177
- * Registra presets de la aplicación
28178
- *
28179
- * @param presets Configuración de presets
28180
- *
28181
- * @example
28182
- * presets.registerPresets({
28183
- * button: {
28184
- * 'primary-action': { size: 'large', color: 'primary' },
28185
- * },
28186
- * card: {
28187
- * 'feature': { variant: 'elevated' },
28188
- * }
28189
- * });
28426
+ * Establece consent por defecto (sin datos personales).
28190
28427
  */
28191
- registerPresets(presets) {
28192
- this._presets.set(presets);
28428
+ setDefaultConsent() {
28429
+ this._consentState.set({
28430
+ adStorage: false,
28431
+ adPersonalization: false,
28432
+ adUserData: false,
28433
+ });
28193
28434
  }
28194
28435
  /**
28195
- * Agrega presets para un componente específico (merge con existentes)
28436
+ * Actualiza manualmente el consent para ads.
28437
+ * Usar cuando el usuario actualiza sus preferencias directamente.
28438
+ *
28439
+ * @param consent - Nuevo estado de consent parcial
28196
28440
  */
28197
- registerComponentPresets(component, presets) {
28198
- this._presets.update((current) => ({
28441
+ updateConsent(consent) {
28442
+ this._consentState.update((current) => ({
28199
28443
  ...current,
28200
- [component]: {
28201
- ...current[component],
28202
- ...presets,
28203
- },
28444
+ ...consent,
28204
28445
  }));
28446
+ // Notificar a GPT si ya esta cargado
28447
+ this.applyConsentToGPT();
28205
28448
  }
28206
28449
  /**
28207
- * Obtiene todos los nombres de presets para un componente
28450
+ * Aplica el estado de consent actual a GPT.
28208
28451
  */
28209
- getPresetNames(component) {
28210
- return Object.keys(this._presets()[component] || {});
28452
+ applyConsentToGPT() {
28453
+ if (!isPlatformBrowser(this.platformId))
28454
+ return;
28455
+ const googletag = window.googletag;
28456
+ if (!googletag)
28457
+ return;
28458
+ googletag.cmd.push(() => {
28459
+ const canPersonalize = this.canPersonalize();
28460
+ // 0 = personalized, 1 = non-personalized
28461
+ googletag.pubads().setRequestNonPersonalizedAds(canPersonalize ? 0 : 1);
28462
+ });
28211
28463
  }
28212
28464
  /**
28213
- * Obtiene todos los componentes con presets registrados
28465
+ * Obtiene el estado actual de consent.
28214
28466
  */
28215
- getRegisteredComponents() {
28216
- return Object.keys(this._presets());
28467
+ getConsentState() {
28468
+ return this._consentState();
28217
28469
  }
28218
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
28219
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, providedIn: 'root' }); }
28470
+ /**
28471
+ * Destruye el servicio y limpia recursos.
28472
+ */
28473
+ destroy() {
28474
+ if (this.consentSyncInterval) {
28475
+ clearInterval(this.consentSyncInterval);
28476
+ this.consentSyncInterval = null;
28477
+ }
28478
+ }
28479
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
28480
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, providedIn: 'root' }); }
28220
28481
  }
28221
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, decorators: [{
28482
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, decorators: [{
28222
28483
  type: Injectable,
28223
28484
  args: [{ providedIn: 'root' }]
28224
- }] });
28485
+ }], ctorParameters: () => [] });
28225
28486
 
28226
28487
  /**
28227
- * Configura el sistema de presets de Valtech Components.
28488
+ * Ads Loader Service
28228
28489
  *
28229
- * @param presets Configuración de presets por componente
28230
- * @returns Providers para agregar en app.config.ts
28490
+ * Maneja la carga lazy del script GPT (Google Publisher Tag).
28491
+ * Solo carga el script cuando se necesita el primer ad.
28492
+ */
28493
+ /** URL del script GPT */
28494
+ const GPT_SCRIPT_URL = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js';
28495
+ /**
28496
+ * Servicio para cargar el script de Google Publisher Tags.
28497
+ *
28498
+ * Implementa lazy loading: el script solo se carga cuando
28499
+ * se solicita renderizar el primer ad slot.
28500
+ */
28501
+ class AdsLoaderService {
28502
+ constructor(config, platformId, consentService) {
28503
+ this.config = config;
28504
+ this.platformId = platformId;
28505
+ this.consentService = consentService;
28506
+ this._isLoading = signal(false);
28507
+ this._isLoaded = signal(false);
28508
+ this._error = signal(null);
28509
+ /** Indica si el script esta cargando */
28510
+ this.isLoading = this._isLoading.asReadonly();
28511
+ /** Indica si el script esta cargado */
28512
+ this.isLoaded = this._isLoaded.asReadonly();
28513
+ /** Error de carga (si existe) */
28514
+ this.error = this._error.asReadonly();
28515
+ this.loadPromise = null;
28516
+ }
28517
+ /**
28518
+ * Carga el script GPT de forma lazy.
28519
+ * Retorna la instancia de googletag o null si falla.
28520
+ *
28521
+ * @returns Promise con googletag o null
28522
+ */
28523
+ loadGPT() {
28524
+ // SSR check
28525
+ if (!isPlatformBrowser(this.platformId)) {
28526
+ return Promise.resolve(null);
28527
+ }
28528
+ // Ya cargado
28529
+ if (this._isLoaded() && window.googletag) {
28530
+ return Promise.resolve(window.googletag);
28531
+ }
28532
+ // Ya hay una carga en progreso
28533
+ if (this.loadPromise) {
28534
+ return this.loadPromise;
28535
+ }
28536
+ this._isLoading.set(true);
28537
+ this._error.set(null);
28538
+ this.loadPromise = new Promise((resolve) => {
28539
+ // Inicializar cmd queue
28540
+ window.googletag = window.googletag || { cmd: [] };
28541
+ const googletag = window.googletag;
28542
+ // Crear script
28543
+ const script = document.createElement('script');
28544
+ script.async = true;
28545
+ script.src = GPT_SCRIPT_URL;
28546
+ script.onload = () => {
28547
+ googletag.cmd.push(() => {
28548
+ this.configureGPT(googletag);
28549
+ this._isLoaded.set(true);
28550
+ this._isLoading.set(false);
28551
+ if (this.config.debugMode) {
28552
+ console.log('[ValtechAds] Script GPT cargado y configurado');
28553
+ }
28554
+ resolve(googletag);
28555
+ });
28556
+ };
28557
+ script.onerror = (error) => {
28558
+ console.error('[ValtechAds] Error cargando GPT:', error);
28559
+ this._error.set(new Error('Error cargando Google Publisher Tag'));
28560
+ this._isLoading.set(false);
28561
+ this.loadPromise = null;
28562
+ resolve(null);
28563
+ };
28564
+ // Insertar script
28565
+ const firstScript = document.getElementsByTagName('script')[0];
28566
+ firstScript.parentNode?.insertBefore(script, firstScript);
28567
+ });
28568
+ return this.loadPromise;
28569
+ }
28570
+ /**
28571
+ * Configura GPT con las opciones de la aplicacion.
28572
+ */
28573
+ configureGPT(googletag) {
28574
+ const pubads = googletag.pubads();
28575
+ // Single Request Architecture para mejor performance
28576
+ pubads.enableSingleRequest();
28577
+ // Colapsar divs vacios antes de fetch
28578
+ pubads.collapseEmptyDivs(true);
28579
+ // Lazy loading nativo de GPT
28580
+ if (this.config.lazyLoad && this.config.lazyLoadConfig) {
28581
+ pubads.enableLazyLoad({
28582
+ fetchMarginPercent: this.config.lazyLoadConfig.fetchMarginPercent,
28583
+ renderMarginPercent: this.config.lazyLoadConfig.renderMarginPercent,
28584
+ mobileScaling: 2.0,
28585
+ });
28586
+ }
28587
+ // Targeting global
28588
+ if (this.config.globalTargeting) {
28589
+ for (const [key, value] of Object.entries(this.config.globalTargeting)) {
28590
+ pubads.setTargeting(key, value);
28591
+ }
28592
+ }
28593
+ // Non-personalized ads segun consent
28594
+ if (!this.consentService.canPersonalize()) {
28595
+ pubads.setRequestNonPersonalizedAds(1);
28596
+ }
28597
+ // Event listeners
28598
+ pubads.addEventListener('slotRenderEnded', (event) => {
28599
+ const slotId = event.slot.getSlotElementId();
28600
+ if (this.config.debugMode) {
28601
+ console.log(`[ValtechAds] Slot ${slotId} rendered, empty: ${event.isEmpty}`);
28602
+ }
28603
+ });
28604
+ // Debug mode
28605
+ if (this.config.debugMode) {
28606
+ console.log('[ValtechAds] GPT configurado:', {
28607
+ lazyLoad: this.config.lazyLoad,
28608
+ personalized: this.consentService.canPersonalize(),
28609
+ targeting: this.config.globalTargeting,
28610
+ });
28611
+ }
28612
+ }
28613
+ /**
28614
+ * Verifica si el script GPT esta disponible.
28615
+ */
28616
+ isGPTAvailable() {
28617
+ return isPlatformBrowser(this.platformId) && !!window.googletag && this._isLoaded();
28618
+ }
28619
+ /**
28620
+ * Obtiene la instancia de googletag si esta cargada.
28621
+ */
28622
+ getGPT() {
28623
+ if (this.isGPTAvailable()) {
28624
+ return window.googletag;
28625
+ }
28626
+ return null;
28627
+ }
28628
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, deps: [{ token: VALTECH_ADS_CONFIG }, { token: PLATFORM_ID }, { token: AdsConsentService }], target: i0.ɵɵFactoryTarget.Injectable }); }
28629
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, providedIn: 'root' }); }
28630
+ }
28631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, decorators: [{
28632
+ type: Injectable,
28633
+ args: [{ providedIn: 'root' }]
28634
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
28635
+ type: Inject,
28636
+ args: [VALTECH_ADS_CONFIG]
28637
+ }] }, { type: Object, decorators: [{
28638
+ type: Inject,
28639
+ args: [PLATFORM_ID]
28640
+ }] }, { type: AdsConsentService }] });
28641
+
28642
+ /**
28643
+ * Ads Service
28644
+ *
28645
+ * Servicio principal para Google Ad Manager (GPT).
28646
+ * Integra con el sistema de consent existente y respeta usuarios premium.
28647
+ */
28648
+ /**
28649
+ * Servicio principal de Google Ad Manager.
28650
+ *
28651
+ * Maneja la creacion, destruccion y refresh de ad slots.
28652
+ * Integra automaticamente con consent mode y detecta usuarios premium.
28231
28653
  *
28232
28654
  * @example
28233
- * // app.config.ts
28234
- * import { provideValtechPresets } from 'valtech-components';
28655
+ * ```typescript
28656
+ * @Component({...})
28657
+ * export class MyComponent {
28658
+ * private ads = inject(AdsService);
28235
28659
  *
28236
- * export const appConfig: ApplicationConfig = {
28660
+ * // Verificar si mostrar ads
28661
+ * showAds = this.ads.isEnabled;
28662
+ *
28663
+ * // Verificar si usuario es premium
28664
+ * isPremium = this.ads.isPremiumUser;
28665
+ * }
28666
+ * ```
28667
+ */
28668
+ class AdsService {
28669
+ constructor(injector, config, platformId, router, loaderService, consentService) {
28670
+ this.injector = injector;
28671
+ this.config = config;
28672
+ this.platformId = platformId;
28673
+ this.router = router;
28674
+ this.loaderService = loaderService;
28675
+ this.consentService = consentService;
28676
+ // ===========================================================================
28677
+ // ESTADO (Signals)
28678
+ // ===========================================================================
28679
+ this._isInitialized = signal(false);
28680
+ this._isEnabled = signal(false);
28681
+ this._isPremiumUser = signal(false);
28682
+ this._isDebugMode = signal(false);
28683
+ this._slots = signal(new Map());
28684
+ this._slotStates = signal(new Map());
28685
+ this._events = signal([]);
28686
+ /** Indica si el servicio esta inicializado */
28687
+ this.isInitialized = this._isInitialized.asReadonly();
28688
+ /** Indica si los ads estan habilitados (consent + no premium) */
28689
+ this.isEnabled = computed(() => {
28690
+ return (this._isInitialized() &&
28691
+ this._isEnabled() &&
28692
+ !this._isPremiumUser() &&
28693
+ this.consentService.canShowAds());
28694
+ });
28695
+ /** Indica si el usuario es premium (no ve ads) */
28696
+ this.isPremiumUser = this._isPremiumUser.asReadonly();
28697
+ /** Indica si esta en modo debug */
28698
+ this.isDebugMode = this._isDebugMode.asReadonly();
28699
+ /** Estado de consent para ads */
28700
+ this.consentState = this.consentService.adsConsentState;
28701
+ /** Eventos de ads (historial) */
28702
+ this.events = this._events.asReadonly();
28703
+ // ===========================================================================
28704
+ // INTERNOS
28705
+ // ===========================================================================
28706
+ this.activeSlots = new Map();
28707
+ this.routerSubscription = null;
28708
+ this.refreshTimerId = null;
28709
+ this._isDebugMode.set(config.debugMode ?? false);
28710
+ }
28711
+ // ===========================================================================
28712
+ // INICIALIZACION
28713
+ // ===========================================================================
28714
+ /**
28715
+ * Inicializa el servicio de ads.
28716
+ * Llamado automaticamente por APP_INITIALIZER.
28717
+ * NO carga el script GPT hasta que se necesite el primer ad.
28718
+ */
28719
+ async initialize() {
28720
+ if (!isPlatformBrowser(this.platformId)) {
28721
+ return;
28722
+ }
28723
+ // Verificar si usuario es premium
28724
+ if (this.config.isPremiumUser) {
28725
+ try {
28726
+ this._isPremiumUser.set(this.config.isPremiumUser());
28727
+ }
28728
+ catch {
28729
+ this._isPremiumUser.set(false);
28730
+ }
28731
+ }
28732
+ // Si es premium, no inicializar ads
28733
+ if (this._isPremiumUser()) {
28734
+ if (this._isDebugMode()) {
28735
+ console.log('[ValtechAds] Usuario premium detectado - ads deshabilitados');
28736
+ }
28737
+ this._isInitialized.set(true);
28738
+ return;
28739
+ }
28740
+ // Verificar consent basico
28741
+ if (!this.consentService.canShowAds() && !this.config.showNonPersonalizedAds) {
28742
+ if (this._isDebugMode()) {
28743
+ console.log('[ValtechAds] Sin consent para ads');
28744
+ }
28745
+ this._isInitialized.set(true);
28746
+ return;
28747
+ }
28748
+ // Suscribirse a cambios de ruta para verificar exclusiones
28749
+ this.setupRouteListener();
28750
+ // Configurar auto-refresh si esta habilitado
28751
+ if (this.config.autoRefreshInterval && this.config.autoRefreshInterval > 0) {
28752
+ this.setupAutoRefresh();
28753
+ }
28754
+ this._isEnabled.set(true);
28755
+ this._isInitialized.set(true);
28756
+ if (this._isDebugMode()) {
28757
+ console.log('[ValtechAds] Inicializado en modo debug', {
28758
+ networkId: this.config.networkId,
28759
+ lazyLoad: this.config.lazyLoad,
28760
+ excludeRoutes: this.config.excludeRoutes,
28761
+ });
28762
+ }
28763
+ }
28764
+ /**
28765
+ * Carga el script GPT de forma lazy.
28766
+ * Llamado automaticamente cuando se renderiza el primer ad slot.
28767
+ */
28768
+ async ensureGPTLoaded() {
28769
+ if (!this.isEnabled()) {
28770
+ return null;
28771
+ }
28772
+ return this.loaderService.loadGPT();
28773
+ }
28774
+ // ===========================================================================
28775
+ // GESTION DE SLOTS
28776
+ // ===========================================================================
28777
+ /**
28778
+ * Define y muestra un ad slot.
28779
+ * Llamado internamente por el componente AdSlot.
28780
+ *
28781
+ * @param config - Configuracion del slot
28782
+ * @returns ID del slot o null si no se puede mostrar
28783
+ */
28784
+ async defineSlot(config) {
28785
+ if (!this.isEnabled()) {
28786
+ this.updateSlotState(config.slotId, 'hidden');
28787
+ return null;
28788
+ }
28789
+ // Verificar si la ruta actual esta excluida
28790
+ if (this.isRouteExcluded()) {
28791
+ this.updateSlotState(config.slotId, 'hidden');
28792
+ return null;
28793
+ }
28794
+ this.updateSlotState(config.slotId, 'loading');
28795
+ const googletag = await this.ensureGPTLoaded();
28796
+ if (!googletag) {
28797
+ this.updateSlotState(config.slotId, 'error');
28798
+ return null;
28799
+ }
28800
+ return new Promise((resolve) => {
28801
+ googletag.cmd.push(() => {
28802
+ try {
28803
+ const adUnitPath = `${this.config.networkId}${config.adUnitPath}`;
28804
+ const sizes = this.resolveSizes(config.size);
28805
+ const slot = googletag.defineSlot(adUnitPath, sizes, config.slotId);
28806
+ if (!slot) {
28807
+ this.updateSlotState(config.slotId, 'error');
28808
+ this.emitEvent({ type: 'error', slotId: config.slotId, error: new Error('No se pudo crear el slot') });
28809
+ resolve(null);
28810
+ return;
28811
+ }
28812
+ // Agregar al servicio pubads
28813
+ slot.addService(googletag.pubads());
28814
+ // Configurar size mapping responsivo
28815
+ if (config.sizeMapping && config.sizeMapping.length > 0) {
28816
+ const mapping = this.buildSizeMapping(googletag, config.sizeMapping);
28817
+ slot.defineSizeMapping(mapping);
28818
+ }
28819
+ // Configurar targeting
28820
+ this.applyTargeting(slot, config.targeting);
28821
+ // Colapsar si esta vacio
28822
+ if (config.collapseEmpty ?? this.config.defaultSlotConfig?.collapseEmpty) {
28823
+ slot.setCollapseEmptyDiv(true, true);
28824
+ }
28825
+ // Guardar referencia
28826
+ this.activeSlots.set(config.slotId, slot);
28827
+ this._slots.update((slots) => new Map(slots).set(config.slotId, slot));
28828
+ // Mostrar el ad
28829
+ googletag.enableServices();
28830
+ googletag.display(config.slotId);
28831
+ this.updateSlotState(config.slotId, 'rendered');
28832
+ this.emitEvent({ type: 'loaded', slotId: config.slotId, isEmpty: false });
28833
+ if (this._isDebugMode()) {
28834
+ console.log(`[ValtechAds] Slot definido: ${config.slotId}`, {
28835
+ adUnitPath,
28836
+ sizes: config.size,
28837
+ });
28838
+ }
28839
+ resolve(config.slotId);
28840
+ }
28841
+ catch (error) {
28842
+ console.error('[ValtechAds] Error definiendo slot:', error);
28843
+ this.updateSlotState(config.slotId, 'error');
28844
+ this.emitEvent({ type: 'error', slotId: config.slotId, error: error });
28845
+ resolve(null);
28846
+ }
28847
+ });
28848
+ });
28849
+ }
28850
+ /**
28851
+ * Destruye un slot y libera recursos.
28852
+ *
28853
+ * @param slotId - ID del slot a destruir
28854
+ */
28855
+ destroySlot(slotId) {
28856
+ const slot = this.activeSlots.get(slotId);
28857
+ if (!slot)
28858
+ return;
28859
+ const googletag = window.googletag;
28860
+ if (googletag) {
28861
+ googletag.cmd.push(() => {
28862
+ googletag.destroySlots([slot]);
28863
+ });
28864
+ }
28865
+ this.activeSlots.delete(slotId);
28866
+ this._slots.update((slots) => {
28867
+ const newSlots = new Map(slots);
28868
+ newSlots.delete(slotId);
28869
+ return newSlots;
28870
+ });
28871
+ this._slotStates.update((states) => {
28872
+ const newStates = new Map(states);
28873
+ newStates.delete(slotId);
28874
+ return newStates;
28875
+ });
28876
+ if (this._isDebugMode()) {
28877
+ console.log(`[ValtechAds] Slot destruido: ${slotId}`);
28878
+ }
28879
+ }
28880
+ /**
28881
+ * Refresca un slot especifico o todos los slots activos.
28882
+ *
28883
+ * @param slotIds - IDs de slots a refrescar (undefined = todos)
28884
+ */
28885
+ refreshSlots(slotIds) {
28886
+ if (!this.isEnabled())
28887
+ return;
28888
+ const googletag = window.googletag;
28889
+ if (!googletag)
28890
+ return;
28891
+ googletag.cmd.push(() => {
28892
+ if (slotIds && slotIds.length > 0) {
28893
+ const slots = slotIds.map((id) => this.activeSlots.get(id)).filter((s) => !!s);
28894
+ if (slots.length > 0) {
28895
+ googletag.pubads().refresh(slots);
28896
+ if (this._isDebugMode()) {
28897
+ console.log(`[ValtechAds] Slots refrescados: ${slotIds.join(', ')}`);
28898
+ }
28899
+ }
28900
+ }
28901
+ else {
28902
+ googletag.pubads().refresh();
28903
+ if (this._isDebugMode()) {
28904
+ console.log('[ValtechAds] Todos los slots refrescados');
28905
+ }
28906
+ }
28907
+ });
28908
+ }
28909
+ /**
28910
+ * Obtiene el estado de un slot.
28911
+ *
28912
+ * @param slotId - ID del slot
28913
+ * @returns Estado actual del slot
28914
+ */
28915
+ getSlotState(slotId) {
28916
+ return this._slotStates().get(slotId) ?? 'idle';
28917
+ }
28918
+ // ===========================================================================
28919
+ // PREMIUM USER
28920
+ // ===========================================================================
28921
+ /**
28922
+ * Actualiza el estado premium del usuario.
28923
+ * Llamar cuando cambie el estado de suscripcion.
28924
+ *
28925
+ * @param isPremium - Nuevo estado premium
28926
+ */
28927
+ updatePremiumStatus(isPremium) {
28928
+ const wasEnabled = this.isEnabled();
28929
+ this._isPremiumUser.set(isPremium);
28930
+ if (isPremium && wasEnabled) {
28931
+ // Destruir todos los slots activos
28932
+ this.destroyAllSlots();
28933
+ if (this._isDebugMode()) {
28934
+ console.log('[ValtechAds] Usuario ahora es premium - ads deshabilitados');
28935
+ }
28936
+ }
28937
+ else if (!isPremium && !wasEnabled && this._isInitialized()) {
28938
+ if (this._isDebugMode()) {
28939
+ console.log('[ValtechAds] Usuario ya no es premium - ads habilitados');
28940
+ }
28941
+ }
28942
+ }
28943
+ /**
28944
+ * Destruye todos los slots activos.
28945
+ */
28946
+ destroyAllSlots() {
28947
+ const googletag = window.googletag;
28948
+ if (googletag) {
28949
+ googletag.cmd.push(() => {
28950
+ googletag.destroySlots();
28951
+ });
28952
+ }
28953
+ this.activeSlots.clear();
28954
+ this._slots.set(new Map());
28955
+ this._slotStates.set(new Map());
28956
+ if (this._isDebugMode()) {
28957
+ console.log('[ValtechAds] Todos los slots destruidos');
28958
+ }
28959
+ }
28960
+ // ===========================================================================
28961
+ // PRIVATE METHODS
28962
+ // ===========================================================================
28963
+ resolveSizes(size) {
28964
+ if (typeof size === 'string') {
28965
+ // Single preset size
28966
+ const mapped = AD_SIZE_MAP[size];
28967
+ return mapped === 'fluid' ? 'fluid' : [mapped];
28968
+ }
28969
+ if (Array.isArray(size)) {
28970
+ if (typeof size[0] === 'string') {
28971
+ // Array of preset sizes
28972
+ return size.map((s) => {
28973
+ const mapped = AD_SIZE_MAP[s];
28974
+ return mapped === 'fluid' ? 'fluid' : mapped;
28975
+ });
28976
+ }
28977
+ // Direct size array
28978
+ return size;
28979
+ }
28980
+ return 'fluid';
28981
+ }
28982
+ buildSizeMapping(googletag, mapping) {
28983
+ const builder = googletag.sizeMapping();
28984
+ // Ordenar por viewport descendente
28985
+ const sorted = [...mapping].sort((a, b) => b.viewportWidth - a.viewportWidth);
28986
+ for (const m of sorted) {
28987
+ builder.addSize([m.viewportWidth, 0], m.sizes);
28988
+ }
28989
+ return builder.build();
28990
+ }
28991
+ applyTargeting(slot, targeting) {
28992
+ // Aplicar targeting global
28993
+ if (this.config.globalTargeting) {
28994
+ for (const [key, value] of Object.entries(this.config.globalTargeting)) {
28995
+ slot.setTargeting(key, value);
28996
+ }
28997
+ }
28998
+ // Aplicar targeting del slot
28999
+ if (targeting) {
29000
+ for (const [key, value] of Object.entries(targeting)) {
29001
+ slot.setTargeting(key, value);
29002
+ }
29003
+ }
29004
+ // Agregar targeting de debug si esta habilitado
29005
+ if (this._isDebugMode()) {
29006
+ slot.setTargeting('test', 'true');
29007
+ }
29008
+ }
29009
+ updateSlotState(slotId, state) {
29010
+ this._slotStates.update((states) => new Map(states).set(slotId, state));
29011
+ }
29012
+ emitEvent(event) {
29013
+ const fullEvent = { ...event, timestamp: new Date() };
29014
+ this._events.update((events) => [...events.slice(-99), fullEvent]);
29015
+ // Callbacks de configuracion
29016
+ if (event.type === 'loaded' || event.type === 'empty') {
29017
+ this.config.onAdLoaded?.(event.slotId, event.isEmpty ?? false);
29018
+ }
29019
+ else if (event.type === 'error' && event.error) {
29020
+ this.config.onAdError?.(event.slotId, event.error);
29021
+ }
29022
+ }
29023
+ setupRouteListener() {
29024
+ if (!this.config.excludeRoutes || this.config.excludeRoutes.length === 0) {
29025
+ return;
29026
+ }
29027
+ this.routerSubscription = this.router.events
29028
+ .pipe(filter$1((event) => event instanceof NavigationEnd))
29029
+ .subscribe((event) => {
29030
+ const shouldHide = this.isRouteExcluded(event.urlAfterRedirects);
29031
+ if (shouldHide) {
29032
+ this.destroyAllSlots();
29033
+ }
29034
+ });
29035
+ }
29036
+ setupAutoRefresh() {
29037
+ const intervalMs = (this.config.autoRefreshInterval ?? 0) * 1000;
29038
+ if (intervalMs <= 0)
29039
+ return;
29040
+ this.refreshTimerId = setInterval(() => {
29041
+ if (this.isEnabled() && this.activeSlots.size > 0) {
29042
+ this.refreshSlots();
29043
+ }
29044
+ }, intervalMs);
29045
+ }
29046
+ isRouteExcluded(url) {
29047
+ if (!this.config.excludeRoutes || this.config.excludeRoutes.length === 0) {
29048
+ return false;
29049
+ }
29050
+ const currentUrl = url ?? this.router.url;
29051
+ return this.config.excludeRoutes.some((pattern) => {
29052
+ const regex = new RegExp(pattern);
29053
+ return regex.test(currentUrl);
29054
+ });
29055
+ }
29056
+ // ===========================================================================
29057
+ // LIFECYCLE
29058
+ // ===========================================================================
29059
+ ngOnDestroy() {
29060
+ this.routerSubscription?.unsubscribe();
29061
+ if (this.refreshTimerId) {
29062
+ clearInterval(this.refreshTimerId);
29063
+ }
29064
+ this.destroyAllSlots();
29065
+ this.consentService.destroy();
29066
+ }
29067
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, deps: [{ token: i0.Injector }, { token: VALTECH_ADS_CONFIG }, { token: PLATFORM_ID }, { token: i1$1.Router }, { token: AdsLoaderService }, { token: AdsConsentService }], target: i0.ɵɵFactoryTarget.Injectable }); }
29068
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, providedIn: 'root' }); }
29069
+ }
29070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, decorators: [{
29071
+ type: Injectable,
29072
+ args: [{ providedIn: 'root' }]
29073
+ }], ctorParameters: () => [{ type: i0.Injector }, { type: undefined, decorators: [{
29074
+ type: Inject,
29075
+ args: [VALTECH_ADS_CONFIG]
29076
+ }] }, { type: Object, decorators: [{
29077
+ type: Inject,
29078
+ args: [PLATFORM_ID]
29079
+ }] }, { type: i1$1.Router }, { type: AdsLoaderService }, { type: AdsConsentService }] });
29080
+
29081
+ /**
29082
+ * Ads Configuration
29083
+ *
29084
+ * Configuracion e inicializacion de Google Ad Manager para Angular.
29085
+ * Usa provideValtechAds() en el bootstrap de tu aplicacion.
29086
+ */
29087
+ /**
29088
+ * Token de inyeccion para la configuracion de Ads.
29089
+ * Usado internamente por los servicios de ads.
29090
+ */
29091
+ const VALTECH_ADS_CONFIG = new InjectionToken('ValtechAdsConfig');
29092
+ /**
29093
+ * Configuracion por defecto de lazy loading.
29094
+ */
29095
+ const DEFAULT_LAZY_LOAD_CONFIG = {
29096
+ rootMargin: '200px',
29097
+ threshold: 0,
29098
+ fetchMarginPercent: 200,
29099
+ renderMarginPercent: 100,
29100
+ };
29101
+ /**
29102
+ * Configuracion por defecto del servicio de ads.
29103
+ */
29104
+ const DEFAULT_ADS_CONFIG = {
29105
+ debugMode: false,
29106
+ lazyLoad: true,
29107
+ lazyLoadConfig: DEFAULT_LAZY_LOAD_CONFIG,
29108
+ enablePersonalization: true,
29109
+ showNonPersonalizedAds: true,
29110
+ autoRefreshInterval: 0, // Deshabilitado por defecto
29111
+ defaultSlotConfig: {
29112
+ collapseEmpty: true,
29113
+ showSkeleton: true,
29114
+ minHeight: '90px',
29115
+ },
29116
+ };
29117
+ /**
29118
+ * Factory para inicializar AdsService.
29119
+ * Se ejecuta durante el bootstrap de la aplicacion.
29120
+ */
29121
+ function initializeAds(adsService) {
29122
+ return () => adsService.initialize();
29123
+ }
29124
+ /**
29125
+ * Provee el servicio de Ads a la aplicacion Angular.
29126
+ *
29127
+ * @param config - Configuracion de Google Ad Manager
29128
+ * @returns EnvironmentProviders para usar en bootstrapApplication
29129
+ *
29130
+ * @example
29131
+ * ```typescript
29132
+ * // main.ts
29133
+ * import { bootstrapApplication } from '@angular/platform-browser';
29134
+ * import { provideValtechAds, provideValtechFirebase } from 'valtech-components';
29135
+ * import { environment } from './environments/environment';
29136
+ *
29137
+ * bootstrapApplication(AppComponent, {
28237
29138
  * providers: [
28238
- * provideValtechPresets({
28239
- * button: {
28240
- * 'primary-action': { size: 'large', color: 'primary', fill: 'solid' },
28241
- * 'secondary': { size: 'medium', color: 'secondary', fill: 'outline' },
28242
- * 'danger': { size: 'medium', color: 'danger', fill: 'solid' },
28243
- * },
28244
- * card: {
28245
- * 'feature': { variant: 'elevated', padding: 'large' },
28246
- * 'compact': { variant: 'flat', padding: 'small' },
28247
- * },
28248
- * input: {
28249
- * 'form-field': { size: 'medium', fill: 'outline', labelPosition: 'floating' },
28250
- * }
29139
+ * // Firebase primero (para consent mode)
29140
+ * provideValtechFirebase({
29141
+ * firebase: environment.firebase,
29142
+ * enableAnalytics: true,
28251
29143
  * }),
28252
- * ]
28253
- * };
29144
+ *
29145
+ * // Luego Ads
29146
+ * provideValtechAds({
29147
+ * networkId: '/12345678',
29148
+ * debugMode: !environment.production,
29149
+ * isPremiumUser: () => inject(AuthService).hasRole('premium'),
29150
+ * }),
29151
+ * ],
29152
+ * });
29153
+ * ```
29154
+ *
29155
+ * @example Con targeting global y rutas excluidas
29156
+ * ```typescript
29157
+ * provideValtechAds({
29158
+ * networkId: '/12345678',
29159
+ * globalTargeting: {
29160
+ * site: 'myvaltech',
29161
+ * section: 'app',
29162
+ * },
29163
+ * excludeRoutes: [
29164
+ * '^/checkout',
29165
+ * '^/payment',
29166
+ * '^/premium',
29167
+ * ],
29168
+ * })
29169
+ * ```
28254
29170
  */
28255
- function provideValtechPresets(presets) {
29171
+ function provideValtechAds(config) {
29172
+ // Merge config con defaults
29173
+ const mergedConfig = {
29174
+ ...DEFAULT_ADS_CONFIG,
29175
+ ...config,
29176
+ lazyLoadConfig: {
29177
+ ...DEFAULT_LAZY_LOAD_CONFIG,
29178
+ ...config.lazyLoadConfig,
29179
+ },
29180
+ defaultSlotConfig: {
29181
+ ...DEFAULT_ADS_CONFIG.defaultSlotConfig,
29182
+ ...config.defaultSlotConfig,
29183
+ },
29184
+ };
28256
29185
  return makeEnvironmentProviders([
29186
+ { provide: VALTECH_ADS_CONFIG, useValue: mergedConfig },
29187
+ // Inicializar AdsService al arrancar (pero NO cargar script GPT aun)
28257
29188
  {
28258
29189
  provide: APP_INITIALIZER,
28259
- useFactory: (presetService) => {
28260
- return () => {
28261
- presetService.registerPresets(presets);
28262
- };
28263
- },
28264
- deps: [PresetService],
29190
+ useFactory: initializeAds,
29191
+ deps: [AdsService],
28265
29192
  multi: true,
28266
29193
  },
28267
29194
  ]);
28268
29195
  }
28269
29196
 
28270
- // Service
29197
+ /**
29198
+ * Ads Module
29199
+ *
29200
+ * Exports publicos para el servicio de Google Ad Manager.
29201
+ */
29202
+ // Configuration
29203
+
29204
+ /**
29205
+ * Ad Slot Component
29206
+ *
29207
+ * Componente standalone para mostrar ads de Google Ad Manager.
29208
+ * Se integra automaticamente con el servicio de Ads y respeta consent + premium.
29209
+ *
29210
+ * @example
29211
+ * ```html
29212
+ * <!-- Banner basico -->
29213
+ * <val-ad-slot
29214
+ * slotId="homepage-top"
29215
+ * adUnitPath="/homepage/top"
29216
+ * size="leaderboard"
29217
+ * />
29218
+ *
29219
+ * <!-- Con responsivo -->
29220
+ * <val-ad-slot
29221
+ * slotId="sidebar-ad"
29222
+ * adUnitPath="/sidebar"
29223
+ * [size]="['medium-rectangle', 'half-page']"
29224
+ * [sizeMapping]="[
29225
+ * { viewportWidth: 1024, sizes: [[300, 600]] },
29226
+ * { viewportWidth: 768, sizes: [[300, 250]] },
29227
+ * { viewportWidth: 0, sizes: [[320, 50]] }
29228
+ * ]"
29229
+ * />
29230
+ *
29231
+ * <!-- Native ad -->
29232
+ * <val-ad-slot
29233
+ * slotId="article-native"
29234
+ * adUnitPath="/article/native"
29235
+ * size="native"
29236
+ * [isNative]="true"
29237
+ * />
29238
+ * ```
29239
+ */
29240
+ class AdSlotComponent {
29241
+ constructor() {
29242
+ this.adsService = inject(AdsService);
29243
+ this.platformId = inject(PLATFORM_ID);
29244
+ /** Tamano del ad */
29245
+ this.size = 'medium-rectangle';
29246
+ /** Colapsar si vacio */
29247
+ this.collapseEmpty = true;
29248
+ /** Es native ad */
29249
+ this.isNative = false;
29250
+ /** CSS class adicional */
29251
+ this.cssClass = '';
29252
+ /** Altura minima */
29253
+ this.minHeight = '90px';
29254
+ /** Mostrar skeleton */
29255
+ this.showSkeleton = true;
29256
+ // ===========================================================================
29257
+ // ESTADO
29258
+ // ===========================================================================
29259
+ this._state = signal('idle');
29260
+ this.state = this._state.asReadonly();
29261
+ /** Indica si el componente debe renderizarse */
29262
+ this.shouldRender = computed(() => {
29263
+ // No renderizar en SSR
29264
+ if (!isPlatformBrowser(this.platformId)) {
29265
+ return false;
29266
+ }
29267
+ // No renderizar si usuario es premium
29268
+ if (this.adsService.isPremiumUser()) {
29269
+ return false;
29270
+ }
29271
+ // No renderizar si ads estan deshabilitados y aun no inicializado
29272
+ if (!this.adsService.isInitialized()) {
29273
+ return false;
29274
+ }
29275
+ return true;
29276
+ });
29277
+ /** Ancho del skeleton */
29278
+ this.skeletonWidth = computed(() => {
29279
+ const firstSize = this.getFirstSize();
29280
+ return firstSize ? `${firstSize[0]}px` : '100%';
29281
+ });
29282
+ /** Altura del skeleton */
29283
+ this.skeletonHeight = computed(() => {
29284
+ const firstSize = this.getFirstSize();
29285
+ return firstSize ? `${firstSize[1]}px` : this.minHeight;
29286
+ });
29287
+ }
29288
+ // ===========================================================================
29289
+ // LIFECYCLE
29290
+ // ===========================================================================
29291
+ async ngOnInit() {
29292
+ if (!this.shouldRender()) {
29293
+ return;
29294
+ }
29295
+ await this.initializeSlot();
29296
+ }
29297
+ ngOnDestroy() {
29298
+ if (isPlatformBrowser(this.platformId)) {
29299
+ this.adsService.destroySlot(this.slotId);
29300
+ }
29301
+ }
29302
+ // ===========================================================================
29303
+ // PRIVATE
29304
+ // ===========================================================================
29305
+ async initializeSlot() {
29306
+ this._state.set('loading');
29307
+ const config = {
29308
+ slotId: this.slotId,
29309
+ adUnitPath: this.adUnitPath,
29310
+ size: this.size,
29311
+ sizeMapping: this.sizeMapping,
29312
+ targeting: this.targeting,
29313
+ collapseEmpty: this.collapseEmpty,
29314
+ isNative: this.isNative,
29315
+ cssClass: this.cssClass,
29316
+ minHeight: this.minHeight,
29317
+ showSkeleton: this.showSkeleton,
29318
+ };
29319
+ const result = await this.adsService.defineSlot(config);
29320
+ if (result) {
29321
+ this._state.set('rendered');
29322
+ }
29323
+ else {
29324
+ this._state.set(this.adsService.getSlotState(this.slotId));
29325
+ }
29326
+ }
29327
+ getFirstSize() {
29328
+ if (typeof this.size === 'string') {
29329
+ const mapped = AD_SIZE_MAP[this.size];
29330
+ return mapped === 'fluid' ? null : mapped;
29331
+ }
29332
+ if (Array.isArray(this.size)) {
29333
+ if (typeof this.size[0] === 'number') {
29334
+ return this.size;
29335
+ }
29336
+ if (typeof this.size[0] === 'string') {
29337
+ const first = AD_SIZE_MAP[this.size[0]];
29338
+ return first === 'fluid' ? null : first;
29339
+ }
29340
+ if (Array.isArray(this.size[0])) {
29341
+ return this.size[0];
29342
+ }
29343
+ }
29344
+ return null;
29345
+ }
29346
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29347
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AdSlotComponent, isStandalone: true, selector: "val-ad-slot", inputs: { slotId: "slotId", adUnitPath: "adUnitPath", size: "size", sizeMapping: "sizeMapping", targeting: "targeting", collapseEmpty: "collapseEmpty", isNative: "isNative", cssClass: "cssClass", minHeight: "minHeight", showSkeleton: "showSkeleton" }, ngImport: i0, template: `
29348
+ @if (shouldRender()) {
29349
+ <div
29350
+ class="val-ad-slot"
29351
+ [class.val-ad-slot--loading]="state() === 'loading'"
29352
+ [class.val-ad-slot--rendered]="state() === 'rendered'"
29353
+ [class.val-ad-slot--empty]="state() === 'empty'"
29354
+ [class.val-ad-slot--hidden]="state() === 'hidden'"
29355
+ [class.val-ad-slot--native]="isNative"
29356
+ [class]="cssClass"
29357
+ [style.min-height]="minHeight"
29358
+ >
29359
+ <!-- Skeleton mientras carga -->
29360
+ @if (showSkeleton && state() === 'loading') {
29361
+ <div
29362
+ class="val-ad-slot__skeleton"
29363
+ [style.width]="skeletonWidth()"
29364
+ [style.height]="skeletonHeight()"
29365
+ ></div>
29366
+ }
29367
+
29368
+ <!-- Container del ad -->
29369
+ <div
29370
+ [id]="slotId"
29371
+ class="val-ad-slot__container"
29372
+ [class.val-ad-slot__container--hidden]="state() === 'loading' && showSkeleton"
29373
+ ></div>
29374
+
29375
+ <!-- Debug info -->
29376
+ @if (adsService.isDebugMode()) {
29377
+ <div class="val-ad-slot__debug">
29378
+ <small>{{ slotId }} | {{ adUnitPath }} | {{ state() }}</small>
29379
+ </div>
29380
+ }
29381
+ </div>
29382
+ }
29383
+ `, isInline: true, styles: [".val-ad-slot{position:relative;display:flex;justify-content:center;align-items:center;overflow:hidden}.val-ad-slot--loading{background-color:var(--ion-color-light, #f4f4f4)}.val-ad-slot--empty,.val-ad-slot--hidden{display:none!important}.val-ad-slot--native{width:100%}.val-ad-slot__skeleton{background:linear-gradient(90deg,var(--ion-color-light, #f4f4f4) 25%,var(--ion-color-light-shade, #e0e0e0) 50%,var(--ion-color-light, #f4f4f4) 75%);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px}.val-ad-slot__container--hidden{visibility:hidden;position:absolute}.val-ad-slot__debug{position:absolute;bottom:0;left:0;background:#000000b3;color:#fff;padding:2px 6px;font-size:10px;z-index:1000;font-family:monospace}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29384
+ }
29385
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdSlotComponent, decorators: [{
29386
+ type: Component,
29387
+ args: [{ selector: 'val-ad-slot', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
29388
+ @if (shouldRender()) {
29389
+ <div
29390
+ class="val-ad-slot"
29391
+ [class.val-ad-slot--loading]="state() === 'loading'"
29392
+ [class.val-ad-slot--rendered]="state() === 'rendered'"
29393
+ [class.val-ad-slot--empty]="state() === 'empty'"
29394
+ [class.val-ad-slot--hidden]="state() === 'hidden'"
29395
+ [class.val-ad-slot--native]="isNative"
29396
+ [class]="cssClass"
29397
+ [style.min-height]="minHeight"
29398
+ >
29399
+ <!-- Skeleton mientras carga -->
29400
+ @if (showSkeleton && state() === 'loading') {
29401
+ <div
29402
+ class="val-ad-slot__skeleton"
29403
+ [style.width]="skeletonWidth()"
29404
+ [style.height]="skeletonHeight()"
29405
+ ></div>
29406
+ }
29407
+
29408
+ <!-- Container del ad -->
29409
+ <div
29410
+ [id]="slotId"
29411
+ class="val-ad-slot__container"
29412
+ [class.val-ad-slot__container--hidden]="state() === 'loading' && showSkeleton"
29413
+ ></div>
29414
+
29415
+ <!-- Debug info -->
29416
+ @if (adsService.isDebugMode()) {
29417
+ <div class="val-ad-slot__debug">
29418
+ <small>{{ slotId }} | {{ adUnitPath }} | {{ state() }}</small>
29419
+ </div>
29420
+ }
29421
+ </div>
29422
+ }
29423
+ `, styles: [".val-ad-slot{position:relative;display:flex;justify-content:center;align-items:center;overflow:hidden}.val-ad-slot--loading{background-color:var(--ion-color-light, #f4f4f4)}.val-ad-slot--empty,.val-ad-slot--hidden{display:none!important}.val-ad-slot--native{width:100%}.val-ad-slot__skeleton{background:linear-gradient(90deg,var(--ion-color-light, #f4f4f4) 25%,var(--ion-color-light-shade, #e0e0e0) 50%,var(--ion-color-light, #f4f4f4) 75%);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px}.val-ad-slot__container--hidden{visibility:hidden;position:absolute}.val-ad-slot__debug{position:absolute;bottom:0;left:0;background:#000000b3;color:#fff;padding:2px 6px;font-size:10px;z-index:1000;font-family:monospace}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
29424
+ }], propDecorators: { slotId: [{
29425
+ type: Input,
29426
+ args: [{ required: true }]
29427
+ }], adUnitPath: [{
29428
+ type: Input,
29429
+ args: [{ required: true }]
29430
+ }], size: [{
29431
+ type: Input
29432
+ }], sizeMapping: [{
29433
+ type: Input
29434
+ }], targeting: [{
29435
+ type: Input
29436
+ }], collapseEmpty: [{
29437
+ type: Input
29438
+ }], isNative: [{
29439
+ type: Input
29440
+ }], cssClass: [{
29441
+ type: Input
29442
+ }], minHeight: [{
29443
+ type: Input
29444
+ }], showSkeleton: [{
29445
+ type: Input
29446
+ }] } });
28271
29447
 
28272
29448
  /*
28273
29449
  * Public API Surface of valtech-components
@@ -28277,5 +29453,5 @@ function provideValtechPresets(presets) {
28277
29453
  * Generated bundle index. Do not edit.
28278
29454
  */
28279
29455
 
28280
- export { ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, ArticleBuilder, ArticleComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_AUTH_CONFIG, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DeviceService, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAuthCallbackComponent, OAuthService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RightsFooterComponent, SKELETON_PRESETS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, VALTECH_AUTH_CONFIG, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildPath, collections, createFirebaseConfig, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFirebase, provideValtechI18n, provideValtechPresets, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
29456
+ export { AD_SIZE_MAP, ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsConsentService, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, ArticleBuilder, ArticleComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_LAZY_LOAD_CONFIG, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DeviceService, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAuthCallbackComponent, OAuthService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RightsFooterComponent, SKELETON_PRESETS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, VALTECH_ADS_CONFIG, VALTECH_AUTH_CONFIG, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildPath, collections, createFirebaseConfig, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAds, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFirebase, provideValtechI18n, provideValtechPresets, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
28281
29457
  //# sourceMappingURL=valtech-components.mjs.map